Spaces:
Running
Running
je veux que quan je parle au micro , la discussion s'écris dans écrivez votre message comme sa je vois ce que je disaussi
52c54ae verified | <html lang="fr"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Rosalinda — Espace Codage</title> | |
| <link rel="stylesheet" href="style.css"> | |
| <script src="script.js"></script> | |
| </head> | |
| <body class="bg-gray-900 text-gray-100"> | |
| <div class="app-container h-screen flex"> | |
| <!-- Sidebar --> | |
| <aside class="w-80 bg-gray-800 border-r border-gray-700 flex flex-col"> | |
| <div class="p-4 border-b border-gray-700 flex items-center gap-3"> | |
| <div class="w-3 h-3 rounded-full bg-blue-500 shadow-lg shadow-blue-500/30"></div> | |
| <h1 class="font-bold">Espace Codage</h1> | |
| </div> | |
| <nav class="flex-1 overflow-y-auto p-2"> | |
| <h2 class="text-xs uppercase tracking-wider text-gray-500 px-3 py-2">Générateurs IA</h2> | |
| <a href="baleine.html" class="flex items-center gap-3 px-3 py-2 rounded-lg hover:bg-gray-750/50 border border-transparent hover:border-gray-700 text-gray-300 mb-1"> | |
| <div class="p-2 rounded-lg bg-gray-750 border border-gray-700"> | |
| <i data-feather="image" class="w-4 h-4"></i> | |
| </div> | |
| <span class="truncate">La Baleine IA</span> | |
| </a> | |
| <a href="rosalinda.html" class="flex items-center gap-3 px-3 py-2 rounded-lg bg-blue-900/30 border border-blue-800/50 text-blue-200 mb-1"> | |
| <div class="p-2 rounded-lg bg-blue-900/50 border border-blue-800/50"> | |
| <i data-feather="message-circle" class="w-4 h-4"></i> | |
| </div> | |
| <span class="truncate">Rosalinda IA</span> | |
| </a> | |
| </nav> | |
| </aside> | |
| <!-- Main Content --> | |
| <main class="flex-1 flex flex-col bg-gray-850/50"> | |
| <div class="p-3 border-b border-gray-700 flex gap-2 bg-gray-850"> | |
| <span class="text-xs px-3 py-1 rounded-full bg-gray-800 border border-gray-700 text-gray-400">Rosalinda IA</span> | |
| <span class="text-xs px-3 py-1 rounded-full bg-gray-800 border border-gray-700 text-gray-400">Chat</span> | |
| </div> | |
| <div class="flex-1 overflow-y-auto p-4 flex flex-col gap-3" id="msgs"></div> | |
| <div class="p-3 border-t border-gray-700 bg-gray-850"> | |
| <div class="flex items-center gap-2 p-2 rounded-xl bg-gray-800 border border-gray-700"> | |
| <button id="micBtn" class="p-2 rounded-lg hover:bg-gray-700 text-gray-400 hover:text-gray-200"> | |
| <i data-feather="mic" class="w-5 h-5"></i> | |
| </button> | |
| <input id="inp" placeholder="Écris à Rosalinda…" | |
| class="flex-1 bg-transparent px-3 py-2 focus:outline-none text-sm"> | |
| <button id="sendBtn" class="p-2 rounded-lg bg-blue-600 hover:bg-blue-500 text-white"> | |
| <i data-feather="send" class="w-5 h-5"></i> | |
| </button> | |
| <button id="speakBtn" title="Lire la dernière réponse" | |
| class="p-2 rounded-lg hover:bg-gray-700 text-gray-400 hover:text-gray-200"> | |
| <i data-feather="volume-2" class="w-5 h-5"></i> | |
| </button> | |
| </div> | |
| <div class="text-xs text-gray-500 mt-2 flex justify-between"> | |
| <span id="status">Micro: prêt</span> | |
| <span>Rosalinda IA v1.0</span> | |
| </div> | |
| </div> | |
| </main> | |
| <div class="small text-gray-400 text-sm mt-4 leading-relaxed"> | |
| ✅ Micro & voix = API du navigateur (Chrome/Edge).<br> | |
| ⚠️ Cette version répond avec une "IA locale simple" (règles). Étape suivante: brancher un vrai modèle IA (local ou API). | |
| </div> | |
| </div> | |
| <script> | |
| feather.replace(); | |
| const msgs = document.getElementById("msgs"); | |
| const inp = document.getElementById("inp"); | |
| const sendBtn = document.getElementById("sendBtn"); | |
| const micBtn = document.getElementById("micBtn"); | |
| const speakBtn = document.getElementById("speakBtn"); | |
| const statusEl = document.getElementById("status"); | |
| let lastAIText = ""; | |
| function addMsg(text, who){ | |
| const div = document.createElement("div"); | |
| div.className = `msg ${who === "me" ? | |
| "ml-auto bg-blue-900/10 border-blue-800/50" : | |
| "bg-gray-700/50 border-gray-700"} max-w-[80%] p-3 rounded-xl border`; | |
| div.textContent = text; | |
| msgs.appendChild(div); | |
| msgs.scrollTop = msgs.scrollHeight; | |
| } | |
| function rosalindaBrain(userText){ | |
| const t = userText.toLowerCase(); | |
| if (t.includes("bonjour") || t.includes("salut")) return "Bonjour 😄 Je suis Rosalinda. Dis-moi ce que tu veux créer : site, thème, image, vidéo, plugin…"; | |
| if (t.includes("theme") || t.includes("thème")) return "Ok ✅ Dis-moi : (1) style (moderne, luxe, minimal, flashy), (2) couleurs, (3) 3 colonnes ou non, (4) Shopify/WooCommerce/autre."; | |
| if (t.includes("image")) return "Je peux préparer une demande d'image. Dis-moi : sujet + style + format (1:1, 16:9, 9:16) + texte à afficher."; | |
| if (t.includes("vidéo") || t.includes("video")) return "Je peux préparer une demande vidéo. Dis-moi : durée, style (réaliste/3D), texte à l'écran, musique oui/non."; | |
| if (t.includes("micro")) return "Pour le micro : clique 🎤, autorise le micro dans ton navigateur, puis parle. Je transcris et je réponds."; | |
| return "Compris ✅ Donne-moi plus de détails (objectif + plateforme + style), et je te génère une réponse claire."; | |
| } | |
| function speak(text){ | |
| if (!("speechSynthesis" in window)) { | |
| alert("TTS non supporté sur ce navigateur."); | |
| return; | |
| } | |
| const u = new SpeechSynthesisUtterance(text); | |
| u.lang = "fr-FR"; | |
| window.speechSynthesis.cancel(); | |
| window.speechSynthesis.speak(u); | |
| } | |
| function handleSend(text){ | |
| const v = (text ?? inp.value).trim(); | |
| if (!v) return; | |
| addMsg(v, "me"); | |
| inp.value = ""; | |
| const reply = rosalindaBrain(v); | |
| lastAIText = reply; | |
| addMsg(reply, "ai"); | |
| speak(reply); | |
| } | |
| sendBtn.onclick = () => handleSend(); | |
| inp.addEventListener("keydown", (e) => { | |
| if (e.key === "Enter") handleSend(); | |
| }); | |
| speakBtn.onclick = () => lastAIText && speak(lastAIText); | |
| let rec = null; | |
| let listening = false; | |
| function setupSTT(){ | |
| try { | |
| const SR = window.SpeechRecognition || window.webkitSpeechRecognition; | |
| if (!SR) { | |
| statusEl.textContent = "Micro: non supporté"; | |
| return null; | |
| } | |
| const r = new SR(); | |
| r.lang = "fr-FR"; | |
| r.interimResults = true; | |
| r.continuous = false; | |
| return r; | |
| } catch (e) { | |
| statusEl.textContent = "Micro: erreur"; | |
| return null; | |
| } | |
| } | |
| rec = setupSTT(); | |
| if (!rec) { | |
| statusEl.textContent = "Micro: non supporté"; | |
| micBtn.disabled = true; | |
| micBtn.classList.add("opacity-50"); | |
| return; | |
| } | |
| // Check microphone permission status | |
| async function checkMicrophonePermission() { | |
| try { | |
| const permissionStatus = await navigator.permissions.query({ name: 'microphone' }); | |
| permissionStatus.onchange = () => { | |
| if (permissionStatus.state === 'granted') { | |
| statusEl.textContent = "Micro: prêt"; | |
| } else { | |
| statusEl.textContent = "Micro: permission requise"; | |
| } | |
| }; | |
| return permissionStatus.state; | |
| } catch (e) { | |
| console.error("Permission API not supported", e); | |
| return 'prompt'; | |
| } | |
| } | |
| // Initialize microphone status | |
| checkMicrophonePermission().then(state => { | |
| if (state === 'granted') { | |
| statusEl.textContent = "Micro: prêt"; | |
| } else { | |
| statusEl.textContent = "Micro: cliquez pour autoriser"; | |
| } | |
| }); | |
| micBtn.onclick = async () => { | |
| if (!rec) return; | |
| // Check if we need to request permission | |
| try { | |
| const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); | |
| // Immediately close the stream as we just needed permission | |
| stream.getTracks().forEach(track => track.stop()); | |
| } catch (err) { | |
| console.error("Microphone access error:", err); | |
| statusEl.textContent = "Micro: accès refusé"; | |
| return; | |
| } | |
| if (listening) { | |
| rec.stop(); | |
| return; | |
| } | |
| listening = true; | |
| statusEl.textContent = "Micro: écoute…"; | |
| micBtn.classList.add("listening"); | |
| let finalText = ""; | |
| let timeoutId; | |
| rec.onresult = (e) => { | |
| clearTimeout(timeoutId); | |
| let interimTranscript = ''; | |
| let finalTranscript = ''; | |
| for (let i = e.resultIndex; i < e.results.length; i++) { | |
| const transcript = e.results[i][0].transcript; | |
| if (e.results[i].isFinal) { | |
| finalTranscript += transcript + ' '; | |
| } else { | |
| interimTranscript += transcript; | |
| } | |
| } | |
| // Show interim results in real-time | |
| inp.value = interimTranscript || finalTranscript; | |
| if (finalTranscript) { | |
| finalText += finalTranscript; | |
| } | |
| // Reset timeout on new results | |
| timeoutId = setTimeout(() => { | |
| if (listening) rec.stop(); | |
| }, 2000); | |
| }; | |
| rec.onerror = (e) => { | |
| console.error("Speech recognition error", e); | |
| stopListening(); | |
| statusEl.textContent = "Micro: erreur - " + e.error; | |
| }; | |
| rec.onend = () => { | |
| stopListening(); | |
| if (inp.value.trim()) { | |
| handleSend(inp.value); | |
| inp.value = ""; | |
| } | |
| }; | |
| function stopListening() { | |
| listening = false; | |
| statusEl.textContent = "Micro: prêt"; | |
| micBtn.classList.remove("listening"); | |
| } | |
| try { | |
| rec.start(); | |
| // Auto-stop after 10 seconds if no speech detected | |
| setTimeout(() => { | |
| if (listening) rec.stop(); | |
| }, 10000); | |
| } catch (e) { | |
| console.error("Speech recognition error:", e); | |
| stopListening(); | |
| statusEl.textContent = "Micro: erreur de démarrage"; | |
| } | |
| }; | |
| addMsg("Bonjour 👋 Je suis Rosalinda. Clique 🎤 pour parler ou écris-moi.", "ai"); | |
| </script> | |
| </body> | |
| </html> |