Valdeuck-final / index.html
bsalvan's picture
Synchronisation finale margot + voix
af1d94f
Raw History Blame Contribute Delete
7.63 kB
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>La Bincherie Chat</title>
<style>
* { box-sizing: border-box; }
body, html { margin: 0; padding: 0; height: 100%; font-family: 'Segoe UI', sans-serif; background: #000; overflow: hidden; }
#chat-window { display: flex; flex-direction: column; height: 100vh; width: 100%; background: #fff; position: fixed; top: 0; left: 0; z-index: 99999; }
#header { background: #1a1a1a; color: #fff; padding: 10px 15px; display: flex; align-items: center; justify-content: space-between; border-bottom: 3px solid #f39c12; flex-shrink: 0; }
.header-brand { display: flex; align-items: center; gap: 12px; }
#header img { width: 50px; height: 50px; border-radius: 50%; border: 2px solid #f39c12; object-fit: cover; }
#messages { flex: 1; overflow-y: auto; padding: 20px; background: #fdfdfd; display: flex; flex-direction: column; gap: 15px; }
.line { display: flex; width: 100%; animation: fadeIn 0.3s ease; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
.bot-line { justify-content: flex-start; }
.user-line { justify-content: flex-end; }
.bubble { padding: 12px 16px; border-radius: 18px; max-width: 80%; font-size: 15px; line-height: 1.4; }
.bot-bubble { background: #eee; color: #222; border-bottom-left-radius: 2px; }
.user-bubble { background: #f39c12; color: #fff; border-bottom-right-radius: 2px; }
.mini-avatar { width: 35px; height: 35px; border-radius: 50%; margin-right: 8px; align-self: flex-end; border: 1px solid #f39c12; }
#input-area { padding: 15px; background: #fff; border-top: 1px solid #ddd; display: flex; gap: 10px; flex-shrink: 0; align-items: center; }
#q { flex: 1; padding: 12px 18px; border-radius: 25px; border: 1px solid #ccc; outline: none; font-size: 16px; }
.btn { border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; }
#send-btn { background: #f39c12; color: #fff; width: 45px; height: 45px; border-radius: 50%; font-size: 20px; }
#mic-btn { background: #f0f0f0; color: #333; width: 45px; height: 45px; border-radius: 50%; font-size: 20px; border: 1px solid #ccc; }
#mic-btn.active { background: #ff4757; color: #fff; animation: pulse 1s infinite; }
@keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.1); } 100% { transform: scale(1); } }
</style>
</head>
<body>
<div id="chat-window">
<div id="header">
<div class="header-brand">
<img id="main-avatar" src="maitre_deuck.webp">
<div><strong id="main-name">Maître-Deuck</strong><br><span style="font-size:10px; color:#2ecc71;">● En ligne</span></div>
</div>
<button id="v-tog" onclick="toggleV()" style="background:none; border:none; color:#f39c12; font-size:24px;">🔊</button>
</div>
<div id="messages"></div>
<div id="input-area">
<button id="mic-btn" class="btn" onclick="startRec()">🎤</button>
<input type="text" id="q" placeholder="Posez votre question..." onkeypress="if(event.key==='Enter') send()">
<button id="send-btn" class="btn" onclick="send()">🚀</button>
</div>
</div>
<script>
const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);
let chatHistory = [];
const config = {
name: isMobile ? "Margot" : "Maître-Deuck",
avatar: isMobile ? "margot.webp" : "maitre_deuck.webp",
welcome: isMobile ? "Bonjour ! Je suis Margot, l'assistante de Maître-Deuck. Comment puis-je vous aider ? 🍻" : "Bienvenue ! Je suis Maître-Deuck. Qu'est-ce qu'on vous sert aujourd'hui ? 🍻",
pitch: isMobile ? 1.0 : 0.35,
rate: isMobile ? 1.0 : 0.85
};
document.getElementById('main-avatar').src = config.avatar;
document.getElementById('main-name').textContent = config.name;
let voiceOn = true;
function toggleV() { voiceOn = !voiceOn; document.getElementById('v-tog').textContent = voiceOn ? "🔊" : "🔇"; }
// --- FONCTION SPEAK UNIQUE ET CORRIGÉE ---
function speak(text) {
if(!voiceOn) return;
window.speechSynthesis.cancel();
const u = new SpeechSynthesisUtterance(text);
u.lang = 'fr-FR';
u.pitch = config.pitch;
u.rate = config.rate;
if(!isMobile) {
const voices = window.speechSynthesis.getVoices();
const maleVoice = voices.find(v =>
v.lang.includes('fr') &&
/(thomas|nicolas|paul|daniel|google|mâle|male)/i.test(v.name.toLowerCase())
);
if(maleVoice) { u.voice = maleVoice; }
}
setTimeout(() => window.speechSynthesis.speak(u), 150);
}
const Rec = window.SpeechRecognition || window.webkitSpeechRecognition;
if(Rec) {
const r = new Rec(); r.lang = 'fr-FR';
r.onstart = () => document.getElementById('mic-btn').classList.add('active');
r.onend = () => document.getElementById('mic-btn').classList.remove('active');
r.onresult = (e) => { document.getElementById('q').value = e.results[0][0].transcript; send(); };
function startRec() { try { r.start(); } catch(e) { r.stop(); } }
}
async function send() {
const el = document.getElementById('q');
const val = el.value.trim();
if(!val) return;
addMsg('user', val);
el.value = '';
const contextHistory = chatHistory.slice(-4).join("\n");
const queryWithHistory = contextHistory ? `Historique récent :\n${contextHistory}\n\nNouvelle question : ${val}` : val;
try {
const res = await fetch('/ask', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ query: queryWithHistory })
});
const d = await res.json();
addMsg('bot', d.response);
speak(d.response);
chatHistory.push(`Utilisateur: ${val}`);
chatHistory.push(`${config.name}: ${d.response}`);
if(chatHistory.length > 6) chatHistory.shift();
} catch(e) { addMsg('bot', "Désolé, j'ai une fuite dans mon fût de données... 🍺"); }
}
function addMsg(type, text) {
const container = document.getElementById('messages');
const line = document.createElement('div');
line.className = `line ${type}-line`;
line.innerHTML = type === 'bot'
? `<img src="${config.avatar}" class="mini-avatar"><div class="bubble bot-bubble">${text}</div>`
: `<div class="bubble user-bubble">${text}</div>`;
container.appendChild(line);
container.scrollTop = container.scrollHeight;
}
window.speechSynthesis.onvoiceschanged = () => window.speechSynthesis.getVoices();
setTimeout(() => { if(document.getElementById('messages').children.length === 0) addMsg('bot', config.welcome); }, 1000);
</script>
</body>
</html>