Spaces:
Running
Running
| <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
| <title>Romuald WADAGNI — Assistant de communication</title> | |
| <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='16' fill='%23008751'/%3E%3Ctext x='16' y='22' text-anchor='middle' font-size='18' font-family='serif' fill='white'%3ERW%3C/text%3E%3C/svg%3E" /> | |
| <style> | |
| *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; | |
| background: #f4f6f8; | |
| height: 100dvh; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| padding: 12px 24px; | |
| } | |
| /* ── Header ── */ | |
| .header { | |
| display: flex; | |
| height: 72px; | |
| width: 100%; | |
| max-width: 1400px; | |
| border-radius: 12px; | |
| overflow: hidden; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.15); | |
| flex-shrink: 0; | |
| margin-bottom: 12px; | |
| } | |
| .header-left { | |
| background: #008751; | |
| flex: 2; | |
| display: flex; | |
| align-items: center; | |
| padding: 0 18px; | |
| gap: 14px; | |
| } | |
| .header-left img { | |
| width: 54px; | |
| height: 54px; | |
| border-radius: 50%; | |
| object-fit: cover; | |
| border: 2px solid white; | |
| flex-shrink: 0; | |
| } | |
| .header-name { color: white; font-weight: 700; font-size: 15px; letter-spacing: 0.3px; } | |
| .header-badge { | |
| background: white; | |
| color: #008751; | |
| font-weight: 600; | |
| font-size: 11px; | |
| padding: 2px 10px; | |
| border-radius: 999px; | |
| margin-top: 3px; | |
| display: inline-block; | |
| } | |
| .header-right { | |
| flex: 1; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| .header-right .yellow { background: #FCD116; flex: 1; } | |
| .header-right .red { background: #E8112D; flex: 1; } | |
| /* ── Chat window ── */ | |
| .chat-window { | |
| width: 100%; | |
| max-width: 1400px; | |
| flex: 1; | |
| background: white; | |
| border-radius: 12px; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.1); | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| } | |
| .messages { | |
| flex: 1; | |
| overflow-y: auto; | |
| padding: 20px 16px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 12px; | |
| scroll-behavior: smooth; | |
| } | |
| /* ── Bubbles ── */ | |
| .bubble-wrap { | |
| display: flex; | |
| align-items: flex-end; | |
| gap: 8px; | |
| } | |
| .bubble-wrap.user { flex-direction: row-reverse; } | |
| .bubble-wrap.bot { flex-direction: row; } | |
| .avatar { | |
| width: 32px; | |
| height: 32px; | |
| border-radius: 50%; | |
| flex-shrink: 0; | |
| object-fit: cover; | |
| } | |
| .avatar-placeholder { | |
| width: 32px; | |
| height: 32px; | |
| border-radius: 50%; | |
| background: #008751; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 16px; | |
| flex-shrink: 0; | |
| } | |
| .bubble { | |
| max-width: 55%; | |
| padding: 10px 14px; | |
| border-radius: 18px; | |
| font-size: 14px; | |
| line-height: 1.5; | |
| white-space: pre-wrap; | |
| word-break: break-word; | |
| } | |
| .bubble.user { | |
| background: #008751; | |
| color: white; | |
| border-bottom-right-radius: 4px; | |
| } | |
| .bubble.bot { | |
| background: #f0f2f5; | |
| color: #1a1a1a; | |
| border-bottom-left-radius: 4px; | |
| } | |
| /* ── Typing indicator ── */ | |
| .typing { | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| padding: 10px 14px; | |
| background: #f0f2f5; | |
| border-radius: 18px; | |
| border-bottom-left-radius: 4px; | |
| width: fit-content; | |
| } | |
| .typing span { | |
| width: 7px; height: 7px; | |
| background: #888; | |
| border-radius: 50%; | |
| animation: bounce 1.2s infinite; | |
| } | |
| .typing span:nth-child(2) { animation-delay: 0.2s; } | |
| .typing span:nth-child(3) { animation-delay: 0.4s; } | |
| @keyframes bounce { | |
| 0%, 60%, 100% { transform: translateY(0); } | |
| 30% { transform: translateY(-6px); } | |
| } | |
| /* ── Input bar ── */ | |
| .input-bar { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 12px 16px; | |
| border-top: 1px solid #e8e8e8; | |
| background: white; | |
| } | |
| .input-bar textarea { | |
| flex: 1; | |
| resize: none; | |
| border: 1px solid #ddd; | |
| border-radius: 22px; | |
| padding: 9px 16px; | |
| font-size: 14px; | |
| font-family: inherit; | |
| outline: none; | |
| max-height: 120px; | |
| line-height: 1.4; | |
| transition: border-color 0.2s; | |
| } | |
| .input-bar textarea:focus { border-color: #008751; } | |
| .input-bar button { | |
| width: 40px; height: 40px; | |
| border: none; | |
| background: #008751; | |
| color: white; | |
| border-radius: 50%; | |
| cursor: pointer; | |
| font-size: 18px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| flex-shrink: 0; | |
| transition: background 0.2s; | |
| } | |
| .input-bar button:hover:not(:disabled) { background: #006640; } | |
| .input-bar button:disabled { background: #b0c4b8; cursor: not-allowed; } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Header drapeau béninois --> | |
| <div class="header"> | |
| <div class="header-left"> | |
| <img id="photoImg" src="/photo" alt="Romuald WADAGNI" onerror="this.style.display='none'" /> | |
| <div> | |
| <div class="header-name">Romuald WADAGNI</div> | |
| <span class="header-badge">Assistant de communication</span> | |
| </div> | |
| </div> | |
| <div class="header-right"> | |
| <div class="yellow"></div> | |
| <div class="red"></div> | |
| </div> | |
| </div> | |
| <!-- Fenêtre de chat --> | |
| <div class="chat-window"> | |
| <div class="messages" id="messages"> | |
| <!-- Message de bienvenue --> | |
| <div class="bubble-wrap bot"> | |
| <img class="avatar" src="/photo" alt="bot" onerror="this.outerHTML='<div class=\'avatar-placeholder\'>🇧🇯</div>'" /> | |
| <div class="bubble bot">Bonjour ! Je suis l'assistant IA de communication de Son Excellence le Président Romuald WADAGNI. Comment puis-je vous aider ?</div> | |
| </div> | |
| <!-- Bloc consentement --> | |
| <div id="consent-block" style=" | |
| display: flex; | |
| align-items: flex-start; | |
| gap: 8px; | |
| padding: 10px 14px; | |
| margin: 4px 16px 0 48px; | |
| background: rgba(0, 135, 81, 0.06); | |
| border: 1px solid rgba(0, 135, 81, 0.2); | |
| border-radius: 10px; | |
| font-size: 0.82rem; | |
| color: #555; | |
| line-height: 1.5; | |
| "> | |
| <input type="checkbox" id="consentCheckbox" style=" | |
| width: 18px; | |
| height: 18px; | |
| accent-color: #008751; | |
| cursor: pointer; | |
| flex-shrink: 0; | |
| margin-top: 2px; | |
| " /> | |
| <label for="consentCheckbox" style="cursor: pointer;"> | |
| J'ai lu et j'accepte la | |
| <a href="/privacy.html" target="_blank" style="color: #008751; text-decoration: underline; font-weight: 600;">politique de confidentialité</a> | |
| et le traitement de mes données. | |
| </label> | |
| </div> | |
| </div> | |
| <!-- Barre de saisie --> | |
| <div class="input-bar"> | |
| <textarea | |
| id="userInput" | |
| placeholder="Posez votre question…" | |
| rows="1" | |
| ></textarea> | |
| <button id="sendBtn" title="Envoyer">▶</button> | |
| </div> | |
| </div> | |
| <script> | |
| const messagesEl = document.getElementById("messages"); | |
| const inputEl = document.getElementById("userInput"); | |
| const sendBtn = document.getElementById("sendBtn"); | |
| // Historique au format [{role, content}] | |
| const history = []; | |
| function scrollBottom() { | |
| messagesEl.scrollTop = messagesEl.scrollHeight; | |
| } | |
| function addBubble(role, text) { | |
| const wrap = document.createElement("div"); | |
| wrap.className = `bubble-wrap ${role === "user" ? "user" : "bot"}`; | |
| if (role !== "user") { | |
| const av = document.createElement("img"); | |
| av.className = "avatar"; | |
| av.src = "/photo"; | |
| av.alt = "bot"; | |
| av.onerror = () => { av.outerHTML = '<div class="avatar-placeholder">🇧🇯</div>'; }; | |
| wrap.appendChild(av); | |
| } | |
| const bubble = document.createElement("div"); | |
| bubble.className = `bubble ${role === "user" ? "user" : "bot"}`; | |
| bubble.textContent = text; | |
| wrap.appendChild(bubble); | |
| messagesEl.appendChild(wrap); | |
| scrollBottom(); | |
| return bubble; | |
| } | |
| function addTyping() { | |
| const wrap = document.createElement("div"); | |
| wrap.className = "bubble-wrap bot"; | |
| wrap.id = "typing-wrap"; | |
| const av = document.createElement("img"); | |
| av.className = "avatar"; | |
| av.src = "/photo"; | |
| av.alt = "bot"; | |
| av.onerror = () => { av.outerHTML = '<div class="avatar-placeholder">🇧🇯</div>'; }; | |
| wrap.appendChild(av); | |
| const t = document.createElement("div"); | |
| t.className = "typing"; | |
| t.innerHTML = "<span></span><span></span><span></span>"; | |
| wrap.appendChild(t); | |
| messagesEl.appendChild(wrap); | |
| scrollBottom(); | |
| } | |
| function removeTyping() { | |
| const el = document.getElementById("typing-wrap"); | |
| if (el) el.remove(); | |
| } | |
| async function sendMessage() { | |
| const text = inputEl.value.trim(); | |
| if (!text) return; | |
| // ── Vérification consentement côté client ── | |
| const consentCheckbox = document.getElementById("consentCheckbox"); | |
| if (!consentCheckbox.checked) { | |
| const block = document.getElementById("consent-block"); | |
| block.style.border = "1px solid #E8112D"; | |
| block.style.background = "rgba(232, 17, 45, 0.06)"; | |
| setTimeout(() => { | |
| block.style.border = "1px solid rgba(0, 135, 81, 0.2)"; | |
| block.style.background = "rgba(0, 135, 81, 0.06)"; | |
| }, 3000); | |
| addBubble("bot", "⚠️ Veuillez accepter la politique de confidentialité avant d'envoyer votre message."); | |
| return; | |
| } | |
| inputEl.value = ""; | |
| inputEl.style.height = "auto"; | |
| sendBtn.disabled = true; | |
| addBubble("user", text); | |
| addTyping(); | |
| try { | |
| const res = await fetch("/chat", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ message: text, history: [...history], consent: consentCheckbox.checked }), | |
| }); | |
| if (!res.ok) throw new Error(`Erreur serveur : ${res.status}`); | |
| const data = await res.json(); | |
| removeTyping(); | |
| if (data.consent_required) { | |
| addBubble("bot", data.response); | |
| return; | |
| } | |
| addBubble("bot", data.response); | |
| // Mettre à jour l'historique | |
| history.push({ role: "user", content: text }); | |
| history.push({ role: "assistant", content: data.response }); | |
| } catch (err) { | |
| removeTyping(); | |
| addBubble("bot", "Désolé, une erreur s'est produite. Veuillez réessayer."); | |
| console.error(err); | |
| } finally { | |
| sendBtn.disabled = false; | |
| inputEl.focus(); | |
| } | |
| } | |
| sendBtn.addEventListener("click", sendMessage); | |
| inputEl.addEventListener("keydown", (e) => { | |
| if (e.key === "Enter" && !e.shiftKey) { | |
| e.preventDefault(); | |
| sendMessage(); | |
| } | |
| }); | |
| // Auto-resize du textarea | |
| inputEl.addEventListener("input", () => { | |
| inputEl.style.height = "auto"; | |
| inputEl.style.height = Math.min(inputEl.scrollHeight, 120) + "px"; | |
| }); | |
| </script> | |
| </body> | |
| </html> | |