Funk-lab/stt/stt/hud/index.html
Claude 1ff3fd9bed
fix(stt): embarquer le HUD dans le package (404 après pipx install)
Le HUD était à la racine du projet (stt/hud/) donc absent du package installé
par pipx → HTTP 404 sur /. Déplacé dans le package (stt/stt/hud/) + package-data,
HUD_DIR ajusté. Vérifié : le wheel contient bien stt/hud/index.html.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FmcxGsyXZXogiAHQLjnZT
2026-06-17 09:21:28 +00:00

82 lines
3.5 KiB
HTML

<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>STT — Funk</title>
<style>
:root { --accent:#36d1ff; --bg:#04080f; --dim:#1b2a3a; }
* { box-sizing:border-box; }
body {
margin:0; height:100vh; background:radial-gradient(circle at 50% 40%, #0a1626, var(--bg));
color:#cfe9ff; font-family:system-ui, sans-serif; overflow:hidden;
display:flex; flex-direction:column; align-items:center; justify-content:center;
}
#core {
width:240px; height:240px; border-radius:50%; position:relative;
display:flex; align-items:center; justify-content:center; transition:all .4s;
box-shadow:0 0 60px -10px var(--accent);
}
#core::before, #core::after {
content:""; position:absolute; border-radius:50%; border:2px solid var(--accent);
inset:0; opacity:.5; animation:pulse 3s ease-in-out infinite;
}
#core::after { inset:30px; opacity:.25; animation-delay:.6s; }
#state { font-size:1.1rem; letter-spacing:.25em; text-transform:uppercase; }
.idle #core { box-shadow:0 0 50px -15px var(--accent); filter:saturate(.6); }
.listening #core { box-shadow:0 0 80px 0 var(--accent); }
.thinking #core { box-shadow:0 0 70px 0 #ffd23f; }
.speaking #core { box-shadow:0 0 90px 5px #36ff9e; }
@keyframes pulse { 0%,100%{transform:scale(1);opacity:.5} 50%{transform:scale(1.08);opacity:.15} }
#log {
position:absolute; bottom:0; left:0; right:0; max-height:32vh; overflow:auto;
padding:1rem 1.5rem; font-size:.95rem; line-height:1.5; backdrop-filter:blur(4px);
}
.turn { margin:.3rem 0; }
.user { color:#9fd0ff; }
.asst { color:#aef5cf; }
.err { color:#ff8c8c; }
.tag { opacity:.5; font-size:.75rem; margin-left:.5rem; }
#conn { position:absolute; top:12px; right:16px; font-size:.75rem; opacity:.6; }
</style>
</head>
<body class="idle">
<div id="conn"></div>
<div id="core"></div>
<div id="state">veille</div>
<div id="log"></div>
<script>
const WS_PORT = 9300; // doit matcher ui.ws_port
const LABELS = { idle:"veille", listening:"écoute", thinking:"réflexion", speaking:"réponse" };
const body = document.body, stateEl = document.getElementById("state");
const logEl = document.getElementById("log"), connEl = document.getElementById("conn");
function setState(s) {
body.className = s;
stateEl.textContent = LABELS[s] || s;
}
function addTurn(cls, text, tag) {
const d = document.createElement("div");
d.className = "turn " + cls;
d.textContent = text;
if (tag) { const t = document.createElement("span"); t.className="tag"; t.textContent=tag; d.appendChild(t); }
logEl.appendChild(d);
logEl.scrollTop = logEl.scrollHeight;
}
function connect() {
const ws = new WebSocket(`ws://${location.hostname}:${WS_PORT}`);
ws.onopen = () => connEl.style.color = "#36ff9e";
ws.onclose = () => { connEl.style.color = "#ff8c8c"; setTimeout(connect, 1500); };
ws.onmessage = (e) => {
const ev = JSON.parse(e.data);
if (ev.type === "state") setState(ev.state);
else if (ev.type === "user") addTurn("user", "🗣️ " + ev.text);
else if (ev.type === "assistant") addTurn("asst", "🤖 " + ev.text, ev.mode);
else if (ev.type === "error") addTurn("err", "⚠️ " + ev.text);
};
}
connect();
</script>
</body>
</html>