import { GoogleGenAI, Chat, GenerateContentResponse } from "@google/genai";
declare global {
interface Window {
AMP: any;
}
}
class Chatbot {
private ai: GoogleGenAI;
private chat: Chat | null = null;
private elements: {
container: HTMLElement | null;
messagesContainer: HTMLElement | null;
input: HTMLTextAreaElement | null;
sendBtn: HTMLButtonElement | null;
typingIndicator: HTMLElement | null;
};
private systemInstruction: string = `
You are an expert virtual sales assistant named AbidjanBot, integrated into a high-traffic website.
Your primary role is to proactively engage with visitors, understand their specific needs regarding our products/services, and qualify them.
Your ultimate goal is to convert their interest into a concrete action: scheduling a phone call or requesting a quote/invoice.
Conversation Flow:
1. Greeting & Proactive Engagement: Start with a warm, professional, and slightly proactive greeting. Example: "Bonjour et bienvenue sur Abidjan Solution ! Je suis AbidjanBot, votre assistant. Cherchez-vous quelque chose de spécifique aujourd'hui ou puis-je vous présenter nos produits phares ?"
2. Needs Discovery: Use open-ended questions to understand the user's requirements. Examples: "Pourriez-vous m'en dire plus sur le type de machine que vous recherchez ?", "Quel est le secteur d'activité de votre entreprise ?", "Avez-vous un projet spécifique en tête ?"
3. Product/Service Matching: Based on their needs, recommend specific products or services from our catalog. Be precise and helpful using the categories below.
4. Qualification & Call to Action: Once interest is confirmed, pivot towards the conversion goal. Be direct but courteous.
- For a Call: "Cela semble être un projet intéressant. Un de nos experts pourrait vous appeler pour discuter des détails techniques et vous fournir un conseil personnalisé. Seriez-vous disponible pour un bref appel ?" If yes, ask for their phone number.
- For a Quote: "Je peux vous préparer un devis détaillé pour cet équipement. Pour cela, j'aurai besoin de votre nom complet, du nom de votre entreprise et de votre adresse e-mail. Souhaitez-vous que je lance la procédure ?"
5. Handling Objections/Questions: Answer any questions concisely and accurately. If you don't know the answer, state: "C'est une excellente question. Pour vous donner une information précise, il serait préférable qu'un de nos spécialistes vous contacte directement."
Our Product Categories:
Quand un utilisateur vous interroge sur nos produits, utilisez cette liste pour le guider. Soyez prêt à discuter des articles dans ces catégories.
- Fabrication Locale : Ceci inclut des machines fabriquées localement comme la 'Broyeuse de Manioc' et la 'Presse à Huile'. Elles sont destinées à la transformation alimentaire.
- Appareils Électroménagers (Neuf) : Des appareils ménagers et industriels neufs. Cela comprend des machines à coudre industrielles et des moteurs diesel.
- Matériel Lourd (Location/Vente Occasion) : Nous proposons du matériel lourd d'occasion à la location ou à la vente. Par exemple, des tracteurs agricoles (John Deere), des pelleteuses (CAT), des groupes électrogènes diesel et des camions-bennes (Renault Kerax).
- Packs Industriels : Des solutions industrielles complètes pour la mise en place d'unités de production, comme pour l'Attiéké ou l'Huile de Palme.
- Services Numériques & Virtuelles : Nous fournissons des services numériques, y compris l'échange de cryptomonnaies (Bitcoin, USDT) et la création de sites e-commerce.
Essayez toujours de faire correspondre les besoins de l'utilisateur à l'une de ces catégories et donnez des exemples précis si possible.
Key Instructions:
- Language: Always communicate in clear, professional, and friendly French (fr-FR).
- Conciseness: Keep your responses brief and to the point.
- Goal-Oriented: Never lose sight of the primary goal: getting a phone number for a call or details for a quote.
- Persona: You are helpful, expert, and efficient.
- Data Collection: Only ask for contact information (phone or email) AFTER the user has agreed to be contacted or receive a quote.
`;
constructor(apiKey: string) {
if (!apiKey) {
throw new Error("API key is missing. Please ensure the API_KEY environment variable is set.");
}
this.ai = new GoogleGenAI({ apiKey });
this.elements = {
container: null,
messagesContainer: null,
input: null,
sendBtn: null,
typingIndicator: null,
};
this.initializeChatbot();
}
private async initializeChatbot() {
try {
this.chat = this.ai.chats.create({
model: 'gemini-2.5-flash',
config: {
systemInstruction: this.systemInstruction,
},
});
this.buildUI();
this.addEventListeners();
this.addMessage("Bonjour et bienvenue ! Je suis AbidjanBot. Comment puis-je vous aider à trouver le produit ou service parfait pour votre projet aujourd'hui ?", 'bot');
} catch (error) {
console.error("Chat initialization failed:", error);
this.displayError("L'assistant IA n'a pas pu démarrer. Veuillez vérifier la configuration.");
}
}
private buildUI() {
const root = document.getElementById('chatbot-root');
if (!root) return;
root.innerHTML = `
`;
this.elements.container = root.querySelector('.chatbot-container');
this.elements.messagesContainer = root.querySelector('.chatbot-messages');
this.elements.input = root.querySelector('textarea');
this.elements.sendBtn = root.querySelector('button');
// Add typing indicator element
const typingIndicator = document.createElement('div');
typingIndicator.className = 'message bot typing-indicator';
typingIndicator.innerHTML = '';
this.elements.typingIndicator = typingIndicator;
}
private addEventListeners() {
this.elements.sendBtn?.addEventListener('click', () => this.sendMessage());
this.elements.input?.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.sendMessage();
}
});
this.elements.input?.addEventListener('input', this.autoResizeInput);
}
private autoResizeInput = () => {
if (this.elements.input) {
this.elements.input.style.height = 'auto';
this.elements.input.style.height = `${this.elements.input.scrollHeight}px`;
}
}
private addMessage(text: string, sender: 'user' | 'bot') {
const messageDiv = document.createElement('div');
messageDiv.className = `message ${sender}`;
messageDiv.textContent = text;
this.elements.messagesContainer?.appendChild(messageDiv);
this.scrollToBottom();
}
private displayError(message: string) {
const root = document.getElementById('chatbot-root');
if (root) {
root.innerHTML = `${message}
`;
}
}
private showTyping(show: boolean) {
if (show && this.elements.typingIndicator) {
this.elements.messagesContainer?.appendChild(this.elements.typingIndicator);
this.scrollToBottom();
} else {
this.elements.typingIndicator?.remove();
}
}
private scrollToBottom() {
this.elements.messagesContainer?.scrollTo({
top: this.elements.messagesContainer.scrollHeight,
behavior: 'smooth'
});
}
private async sendMessage() {
const userInput = this.elements.input?.value.trim();
if (!userInput || !this.chat) return;
this.addMessage(userInput, 'user');
this.elements.input!.value = '';
this.autoResizeInput();
this.showTyping(true);
try {
const response: GenerateContentResponse = await this.chat.sendMessage({ message: userInput });
this.showTyping(false);
this.addMessage(response.text, 'bot');
} catch (error) {
console.error("Gemini API call failed:", error);
this.showTyping(false);
this.addMessage("Désolé, une erreur s'est produite. Veuillez réessayer.", 'bot');
}
}
}
// --- Main Execution ---
// This code runs only inside the chatbot.html iframe.
if (window.location.pathname.includes('chatbot.html')) {
try {
// The API key MUST be provided as an environment variable `process.env.API_KEY`.
// This is a secure way to manage credentials and prevents exposing them in the source code.
new Chatbot(process.env.API_KEY!);
} catch (e: any) {
console.error(e.message);
const root = document.getElementById('chatbot-root');
if (root) {
root.innerHTML = `Could not initialize chatbot: ${e.message}
`;
}
}
}
// --- Payment Page Logic ---
if (window.location.pathname.includes('payment.html')) {
document.addEventListener('DOMContentLoaded', () => {
const urlParams = new URLSearchParams(window.location.search);
const productName = urlParams.get('name');
const productPrice = urlParams.get('price');
const imageUrl = urlParams.get('imageUrl');
const imageEl = document.getElementById('product-image');
const nameEl = document.getElementById('product-name');
const priceEl = document.getElementById('product-price');
const totalEl = document.getElementById('total-price');
if (nameEl) nameEl.textContent = productName || 'Produit non spécifié';
if (priceEl) priceEl.textContent = productPrice || 'Prix non disponible';
if (totalEl) totalEl.textContent = productPrice || 'N/A';
if (imageEl && imageUrl) {
const ampImg = document.createElement('amp-img');
ampImg.setAttribute('src', imageUrl);
ampImg.setAttribute('width', '80');
ampImg.setAttribute('height', '80');
ampImg.setAttribute('layout', 'responsive');
ampImg.setAttribute('alt', productName || 'Image du produit');
imageEl.appendChild(ampImg);
} else if (imageEl) {
const placeholder = document.createElement('div');
placeholder.textContent = 'Image non disponible';
placeholder.style.cssText = 'width: 80px; height: 80px; background: #eee; text-align: center; line-height: 80px; font-size: 12px; color: #888; border-radius: 5px;';
imageEl.appendChild(placeholder);
}
const form = document.getElementById('checkout-form');
const formContainer = document.getElementById('payment-form-container');
const successMessage = document.getElementById('order-success-message');
form?.addEventListener('submit', (e) => {
e.preventDefault();
// Simple validation
const nameInput = document.getElementById('fullname') as HTMLInputElement;
const phoneInput = document.getElementById('phone') as HTMLInputElement;
if (!nameInput || !phoneInput) return;
if (nameInput.value.trim() === '' || phoneInput.value.trim() === '') {
alert('Veuillez remplir votre nom complet et votre numéro de téléphone.');
return;
}
if(formContainer && successMessage) {
formContainer.style.display = 'none';
successMessage.removeAttribute('hidden');
window.scrollTo(0, 0);
}
});
});
}