Assistant Virtuel Expert

En ligne – Prêt à vous accompagner

Dossier Transmis avec Succès !

Notre équipe a bien reçu votre demande. Voici le récapitulatif détaillé de notre proposition.

OUFRID

Villa 27, Riad Yassmin, Rte Ain Chkef, Fès, 30000, Maroc

imad@oufrid.com | + 212 661 29 76 13

DEVIS ESTIMATIF

Date : 30/04/2026

Réf : EST-C288A4

Informations Client

Client Anonyme

Contact non défini

Prestation / ProduitDescription DétailléeMontant HT
Total HT
TVA (20%)
NET À PAYER
Nous Appeler
'); doc.close(); setTimeout(() => { iframe.contentWindow.focus(); iframe.contentWindow.print(); setTimeout(() => { document.body.removeChild(iframe); }, 1500); }, 500); }; document.addEventListener('DOMContentLoaded', function() { const chatBox = document.getElementById('g-chat-box'); const chatAnchor = document.getElementById('g-chat-anchor'); const inputField = document.getElementById('g-chat-input'); const sendBtn = document.getElementById('g-send-btn'); const inputContainer = document.getElementById('g-input-container'); const optionsContainer = document.getElementById('g-options-container'); const contactForm = document.getElementById('g-contact-form'); const successDashboard = document.getElementById('g-success-dashboard'); const ajaxUrl = 'https://oufrid.com/wp-admin/admin-ajax.php'; const nonce = '244c71f432'; const sourceUrl = window.location.href; const currencySymbol = 'MAD'; let conversationHistory =[]; let leadData = { temperature: 'COLD', estimated_price: 'Non défini', contact_method: '', contact_value: '', name: 'Non défini', source_url: sourceUrl, lead_type: 'UNKNOWN' }; let currentState = 'QUALIFYING'; let thinkingDiv = null; inputField.addEventListener('input', function() { this.style.height = '52px'; this.style.height = (this.scrollHeight) + 'px'; }); function scrollToBottom() { setTimeout(() => { const anchor = chatBox.querySelector('#g-chat-anchor') || chatAnchor; if(anchor) chatBox.appendChild(anchor); chatBox.scrollTo({ top: chatBox.scrollHeight, behavior: 'smooth' }); }, 50); } function addMessage(text, sender) { const msgDiv = document.createElement('div'); msgDiv.className = `g-msg ${sender}`; msgDiv.innerHTML = text; const anchor = chatBox.querySelector('#g-chat-anchor') || chatAnchor; chatBox.insertBefore(msgDiv, anchor); scrollToBottom(); if (sender === 'user') conversationHistory.push({ role: "user", content: text }); else if (sender === 'ai' && !text.includes('g-system-msg')) conversationHistory.push({ role: "model", content: text }); if (conversationHistory.length > 20) { conversationHistory = conversationHistory.slice(conversationHistory.length - 20); } } addMessage(`Bonjour ! 👋
Je suis l\'assistant virtuel de l\'entreprise. Comment puis-je vous aider aujourd\'hui ? Que vous cherchiez un de nos produits ou un devis sur-mesure pour un service, je suis à votre disposition.`, 'ai'); function setThinkingStatus(message) { if (!thinkingDiv) { thinkingDiv = document.createElement('div'); thinkingDiv.className = `g-msg ai g-thinking-active`; const anchor = chatBox.querySelector('#g-chat-anchor') || chatAnchor; chatBox.insertBefore(thinkingDiv, anchor); } thinkingDiv.innerHTML = ` ${message}`; scrollToBottom(); } function removeThinkingStatus() { if (thinkingDiv) { thinkingDiv.remove(); thinkingDiv = null; } } function setProcessing(isProcessing) { inputField.disabled = isProcessing; sendBtn.disabled = isProcessing; } /** * MOTEUR DE RETRY ET POLLING FRONT-END INTELLIGENT */ async function fetchWithRetry(url, options, retries = 10, backoff = 2000) { try { const res = await fetch(url, options); const text = await res.text(); if (!res.ok) { if (res.status >= 500 || res.status === 429) { throw new Error(`Erreur HTTP serveur (${res.status}). Surcharge ou timeout.`); } throw new Error(`Erreur HTTP ${res.status} du serveur.`); } try { const data = JSON.parse(text); if (data.success === false && data.message && (data.message.toLowerCase().includes('technique') || data.message.toLowerCase().includes('échec'))) { throw new Error(`API Backend Error: ${data.message}`); } return data; } catch(err) { console.error("JSON Parse Error. Réponse brute du serveur :", text); throw new Error("Le serveur a retourné une réponse invalide (Non-JSON)."); } } catch (err) { if (retries > 0 && !err.message.includes('Session expirée')) { console.warn(`[Gemini Smart Form] Échec de la requête, tentative de récupération... (${retries} essais restants). Pause de ${backoff}ms. Erreur:`, err.message); await new Promise(r => setTimeout(r, backoff)); return fetchWithRetry(url, options, retries - 1, Math.min(backoff * 1.5, 30000)); } else { throw err; } } } // ÉTAPE 1 : LA CONVERSATION LÉGÈRE async function sendMessageToAI(userText = null) { if (userText) addMessage(userText, 'user'); setProcessing(true); try { let marketData = ""; if (userText &&['QUALIFYING', 'QUOTING', 'NEGOTIATING', 'PRODUCT_PITCH'].includes(currentState)) { setThinkingStatus('Je lis votre message et j\'analyse votre besoin...'); let fdIntent = new FormData(); fdIntent.append('action', 'gemini_chat_intent'); fdIntent.append('nonce', nonce); fdIntent.append('last_message', userText); const intentRes = await fetchWithRetry(ajaxUrl, { method: 'POST', body: fdIntent }, 5, 2000); if (intentRes) { if(intentRes.data?.scraped_web_data) marketData += "\nDONNÉES WEB EXTRAITES :\n" + intentRes.data.scraped_web_data; if(intentRes.data?.needs_research) { setThinkingStatus('Je recherche des informations complémentaires pour vous répondre...'); let fdGrounding = new FormData(); fdGrounding.append('action', 'gemini_chat_grounding'); fdGrounding.append('nonce', nonce); fdGrounding.append('query', intentRes.data.query); const groundRes = await fetchWithRetry(ajaxUrl, { method: 'POST', body: fdGrounding }, 5, 2000); if (groundRes && groundRes.success && groundRes.data.context) marketData += "\nCONTEXTE DYNAMIQUE :\n" + groundRes.data.context; } } } setThinkingStatus('Je prépare ma réponse...'); let fdReply = new FormData(); fdReply.append('action', 'gemini_chat_reply'); fdReply.append('nonce', nonce); fdReply.append('history', JSON.stringify(conversationHistory)); fdReply.append('market_data', marketData); fdReply.append('current_state', currentState); const replyRes = await fetchWithRetry(ajaxUrl, { method: 'POST', body: fdReply }, 10, 2000); if (replyRes && replyRes.success && replyRes.data) { const data = replyRes.data; if(data.estimated_price) leadData.estimated_price = data.estimated_price; if(data.lead_type) leadData.lead_type = data.lead_type; currentState = data.internal_state; removeThinkingStatus(); addMessage(data.reply_message, 'ai'); if (currentState === 'COLLECTING_METHOD') { inputContainer.style.display = 'none'; optionsContainer.style.display = 'flex'; scrollToBottom(); } } else { throw new Error(replyRes?.data?.message || 'Erreur lors de la préparation de la réponse.'); } } catch (error) { console.error("Détail de l'Erreur :", error); removeThinkingStatus(); let errMsg = error.message || "Une erreur technique est survenue après plusieurs tentatives de récupération."; if(errMsg.includes("Session") || errMsg.includes("Nonce")) { errMsg = "Votre session de sécurité a expiré. Veuillez cliquer ici pour rafraîchir la page."; } addMessage(`🚨 ${errMsg}`, 'ai'); if (conversationHistory.length > 0 && conversationHistory[conversationHistory.length - 1].role === 'user') { conversationHistory.pop(); } } finally { setProcessing(false); inputField.focus(); } } sendBtn.addEventListener('click', () => { const text = inputField.value.trim(); if (!text) return; inputField.value = ''; inputField.style.height = '52px'; sendMessageToAI(text); }); inputField.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendBtn.click(); } }); document.querySelectorAll('.g-option-btn').forEach(btn => { btn.addEventListener('click', function() { leadData.contact_method = this.getAttribute('data-method'); optionsContainer.style.display = 'none'; contactForm.style.display = 'flex'; let placeholder = "Votre E-mail"; if(leadData.contact_method === 'WHATSAPP') placeholder = "Numéro WhatsApp (+33...)"; if(leadData.contact_method === 'PHONE') placeholder = "Numéro de téléphone"; document.getElementById('g-final-contact').placeholder = placeholder; document.getElementById('g-final-name').focus(); scrollToBottom(); }); }); document.getElementById('g-submit-contact').addEventListener('click', () => { const name = document.getElementById('g-final-name').value.trim(); const contact = document.getElementById('g-final-contact').value.trim(); if(!name || !contact) { if(typeof Swal !== 'undefined') Swal.fire('Attention', 'Veuillez remplir les deux champs de contact.', 'warning'); else alert("Veuillez remplir les deux champs de contact."); return; } leadData.name = name; leadData.contact_value = contact; contactForm.style.display = 'none'; executeFinalHeavyTask(); }); // ÉTAPE 2 : GÉNÉRATION UNIFIÉE DU DEVIS (Tâche 2) async function executeFinalHeavyTask() { setProcessing(true); setThinkingStatus('Création de votre devis détaillé en cours... (Cela prend généralement 10 secondes)'); try { const fd = new FormData(); fd.append('action', 'gemini_finalize_lead'); fd.append('nonce', nonce); fd.append('history', JSON.stringify(conversationHistory)); fd.append('lead_data', JSON.stringify(leadData)); const res = await fetchWithRetry(ajaxUrl, { method: 'POST', body: fd }, 10, 3000); if (res && res.success && res.data && res.data.quote_details) { renderSuccessDashboard(res.data.quote_details); } else { throw new Error(res?.data?.message || 'Erreur lors de la génération du devis final.'); } } catch (e) { console.error('Finalize Error:', e); if(typeof Swal !== 'undefined') Swal.fire('Information', 'Votre demande a été enregistrée avec succès. Notre équipe va finaliser le devis détaillé et vous recontacter.', 'info'); else alert("Votre demande a été enregistrée. Notre équipe va finaliser le devis détaillé et vous recontacter."); renderSuccessDashboard([{ item: "Projet Global", description: "Estimation globale selon nos échanges", price: leadData.estimated_price }]); } finally { removeThinkingStatus(); setProcessing(false); } } function renderSuccessDashboard(quoteDetailsArray) { const itemsContainer = document.getElementById('g-quote-items'); itemsContainer.innerHTML = ''; document.getElementById('g-doc-client-name').innerText = leadData.name; document.getElementById('g-doc-client-contact').innerText = leadData.contact_method + " : " + leadData.contact_value; if(leadData.lead_type === 'PRODUCT_SALE' || leadData.lead_type === 'PRODUCT_SOLD') { document.getElementById('g-doc-type').innerText = "RÉCAPITULATIF DE COMMANDE"; document.getElementById('g-success-subtitle').innerText = "Merci pour votre achat ou votre intérêt. Voici le récapitulatif."; } let subtotal = 0; if(quoteDetailsArray && quoteDetailsArray.length > 0) { quoteDetailsArray.forEach(item => { const priceClean = parseFloat(String(item.price).replace(/[^0-9.,]/g, '').replace(',', '.')); if(!isNaN(priceClean)) subtotal += priceClean; itemsContainer.innerHTML += ` ${item.item}${item.description || ''}${item.price}`; }); } if(subtotal > 0) { let tax = subtotal * 0.20; let grandTotal = subtotal + tax; document.getElementById('g-quote-subtotal').innerText = subtotal.toFixed(2) + " " + currencySymbol; document.getElementById('g-quote-tax').innerText = tax.toFixed(2) + " " + currencySymbol; document.getElementById('g-quote-total-price').innerText = grandTotal.toFixed(2) + " " + currencySymbol; } else { document.getElementById('g-quote-subtotal').innerText = "--"; document.getElementById('g-quote-tax').innerText = "--"; document.getElementById('g-quote-total-price').innerText = leadData.estimated_price; } chatBox.style.display = 'none'; document.getElementById('g-header').style.display = 'none'; inputContainer.style.display = 'none'; successDashboard.style.display = 'flex'; } });