Voice-AI-Agent / frontend /index.html
rakib72642's picture
Refactor voice handling for barge-in support and update UI text labels
caa1385
<!DOCTYPE html>
<html lang="bn">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Hospital Assistant</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=JetBrains+Mono:wght@300;400&family=Hind+Siliguri:wght@300;400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/style.css" />
</head>
<body>
<div class="app" id="app">
<!-- ── Sidebar ── -->
<aside class="sidebar" id="sidebar">
<div class="sidebar-header">
<div class="brand">
<svg width="28" height="28" viewBox="0 0 56 56" fill="none">
<circle cx="28" cy="28" r="26" stroke="url(#gs1)" stroke-width="2"/>
<path d="M18 28 Q28 16 38 28 Q28 40 18 28Z" fill="url(#gs2)" opacity="0.9"/>
<defs>
<linearGradient id="gs1" x1="0" y1="0" x2="56" y2="56">
<stop offset="0%" stop-color="#22d3ee"/><stop offset="100%" stop-color="#818cf8"/>
</linearGradient>
<linearGradient id="gs2" x1="0" y1="0" x2="56" y2="56">
<stop offset="0%" stop-color="#22d3ee"/><stop offset="100%" stop-color="#818cf8"/>
</linearGradient>
</defs>
</svg>
<span>Hospital Assistant</span>
</div>
<button class="sidebar-toggle" id="sidebar-toggle" title="Toggle sidebar"></button>
</div>
<!-- System Status -->
<!-- <div class="status-panel">
<div class="status-row">
<span class="status-label">System</span>
<span class="status-badge badge-green" id="sys-status">Ready</span>
</div>
<div class="status-row">
<span class="status-label">STT</span>
<span class="status-badge badge-green" id="stt-status">Online</span>
</div>
<div class="status-row">
<span class="status-label">LLM</span>
<span class="status-badge badge-green" id="llm-status">Gemini 2.0</span>
</div>
<div class="status-row">
<span class="status-label">TTS</span>
<span class="status-badge badge-green" id="tts-status">Edge TTS</span>
</div>
</div> -->
<div class="sidebar-divider"></div>
<!-- Latency Dashboard -->
<div class="dash-section">
<div class="dash-title">⚡ Latency Dashboard</div>
<div class="metric-grid">
<div class="metric-card">
<div class="metric-val" id="m-stt"></div>
<div class="metric-label">STT (ms)</div>
</div>
<div class="metric-card">
<div class="metric-val" id="m-llm"></div>
<div class="metric-label">LLM (ms)</div>
</div>
<div class="metric-card">
<div class="metric-val" id="m-tts"></div>
<div class="metric-label">TTS (ms)</div>
</div>
<div class="metric-card">
<div class="metric-val" id="m-total"></div>
<div class="metric-label">Total (ms)</div>
</div>
</div>
</div>
<div class="sidebar-divider"></div>
<!-- Voice Settings -->
<div class="dash-section">
<div class="dash-title">🎛️ Voice Settings</div>
<div class="setting-row">
<label>Silence Threshold</label>
<div class="slider-wrap">
<input type="range" id="s-threshold" min="-60" max="-20" value="-32" step="1">
<span id="s-threshold-val">-32 dB</span>
</div>
</div>
<div class="setting-row">
<label>Silence Timeout</label>
<div class="slider-wrap">
<input type="range" id="s-timeout" min="300" max="2000" value="900" step="50">
<span id="s-timeout-val">900 ms</span>
</div>
</div>
<!-- <div class="setting-row">
<label>TTS Voice</label>
<select id="s-voice" class="setting-select">
<option value="bn-BD-NabanitaNeural">Nabanita (Female)</option>
<option value="bn-BD-PradeepNeural">Pradeep (Male)</option>
<option value="bn-IN-BashkarNeural">Bashkar (IN Male)</option>
<option value="bn-IN-TanishaaNeural">Tanishaa (IN Female)</option>
</select>
</div> -->
</div>
<div class="sidebar-divider"></div>
<!-- Audio Queue -->
<div class="dash-section">
<div class="dash-title">📊 Audio Stream</div>
<div class="queue-vis" id="queue-vis">
<div class="queue-bar" style="height:4px"></div>
<div class="queue-bar" style="height:4px"></div>
<div class="queue-bar" style="height:4px"></div>
<div class="queue-bar" style="height:4px"></div>
<div class="queue-bar" style="height:4px"></div>
<div class="queue-bar" style="height:4px"></div>
<div class="queue-bar" style="height:4px"></div>
<div class="queue-bar" style="height:4px"></div>
</div>
<div class="queue-label">Chunks in flight: <span id="chunks-count">0</span></div>
</div>
</aside>
<!-- ── Main area ── -->
<main class="main">
<!-- Top bar -->
<header class="topbar">
<div class="topbar-left">
<button class="mobile-menu-btn" id="mobile-menu-btn"></button>
<div class="topbar-state">
<div class="state-dot" id="state-dot"></div>
<span id="state-label">প্রস্তুত</span>
</div>
</div>
<div class="topbar-center">
<span class="topbar-title">🏥 ডাক্তার অ্যাপয়েন্টমেন্ট সহকারী</span>
</div>
<div class="topbar-right">
<button class="brain-btn" id="brain-mode-btn" title="Brain mode" aria-pressed="false" aria-label="Brain mode">
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M8.5 5.5c-1.7 0-3 1.4-3 3.1 0 .8.3 1.5.8 2.1-1.1.5-1.8 1.5-1.8 2.8 0 1.7 1.4 3.1 3.1 3.1h.3c.2 1.3 1.4 2.3 2.8 2.3.9 0 1.8-.4 2.3-1.1.5.7 1.4 1.1 2.3 1.1 1.4 0 2.6-1 2.8-2.3h.3c1.7 0 3.1-1.4 3.1-3.1 0-1.2-.7-2.3-1.8-2.8.5-.6.8-1.3.8-2.1 0-1.7-1.3-3.1-3-3.1-.6 0-1.2.2-1.7.5-.5-1.1-1.5-1.8-2.7-1.8-1.2 0-2.2.7-2.7 1.8-.5-.3-1.1-.5-1.7-.5Z"/>
<path d="M7.6 8.4 9.8 11M16.4 8.4 14.2 11M6.8 13.4 9.4 13.8M17.2 13.4 14.6 13.8M10.1 15.8 12 17.5 13.9 15.8"/>
<circle cx="10" cy="11" r="0.8"/>
<circle cx="14" cy="11" r="0.8"/>
<circle cx="12" cy="13.8" r="0.9"/>
</svg>
</button>
<button class="clear-btn" id="clear-btn" title="Clear conversation">↺ Clear</button>
</div>
</header>
<div class="voice-caption" id="voice-caption" aria-live="polite"></div>
<!-- Chat -->
<div id="chat-box"></div>
<!-- Brain mode -->
<section class="brain-stage" id="brain-stage" aria-hidden="true">
<div class="brain-shell">
<div class="brain-bubbles" aria-hidden="true">
<div class="brain-bubble brain-bubble-stt" id="brain-bubble-stt">
<div class="brain-bubble-label">You</div>
<div class="brain-bubble-text" id="brain-bubble-stt-text">Listening…</div>
</div>
<div class="brain-bubble brain-bubble-tts" id="brain-bubble-tts">
<div class="brain-bubble-label">AI</div>
<div class="brain-bubble-text" id="brain-bubble-tts-text">Waiting…</div>
</div>
</div>
<div class="brain-scan" aria-hidden="true"></div>
<div class="brain-pulse pulse-a" aria-hidden="true"></div>
<div class="brain-pulse pulse-b" aria-hidden="true"></div>
<div class="brain-pulse pulse-c" aria-hidden="true"></div>
<svg class="brain-svg" viewBox="0 0 960 620" role="presentation" aria-hidden="true">
<defs>
<linearGradient id="brainGlow" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#0ea5e9"/>
<stop offset="50%" stop-color="#8b5cf6"/>
<stop offset="100%" stop-color="#22c55e"/>
</linearGradient>
<filter id="brainBlur" x="-20%" y="-20%" width="140%" height="140%">
<feGaussianBlur stdDeviation="8"/>
</filter>
</defs>
<g class="brain-net">
<path class="brain-wire wire-a" d="M190 330C280 190 380 150 480 150s200 40 290 180"/>
<path class="brain-wire wire-b" d="M160 250C250 180 350 160 480 160s230 20 340 130"/>
<path class="brain-wire wire-c" d="M160 390C250 460 360 480 480 480s220-20 340-140"/>
<path class="brain-wire wire-d" d="M220 180C290 240 360 260 480 260s190-20 260-80"/>
<path class="brain-wire wire-e" d="M220 440C300 380 390 350 480 350s170 30 260 100"/>
<circle class="brain-node node-1" cx="190" cy="330" r="9"/>
<circle class="brain-node node-2" cx="300" cy="220" r="7"/>
<circle class="brain-node node-3" cx="410" cy="180" r="8"/>
<circle class="brain-node node-4" cx="480" cy="150" r="10"/>
<circle class="brain-node node-5" cx="560" cy="185" r="7"/>
<circle class="brain-node node-6" cx="670" cy="240" r="9"/>
<circle class="brain-node node-7" cx="760" cy="330" r="10"/>
<circle class="brain-node node-8" cx="660" cy="430" r="8"/>
<circle class="brain-node node-9" cx="520" cy="485" r="9"/>
<circle class="brain-node node-10" cx="360" cy="455" r="7"/>
<circle class="brain-node node-11" cx="260" cy="390" r="8"/>
<circle class="brain-node node-12" cx="300" cy="280" r="6"/>
</g>
<path class="brain-outline" d="M332 144c33-44 87-70 148-70 52 0 99 18 135 48 31 26 50 57 58 91 61 18 105 72 105 135 0 60-37 111-90 131-11 67-73 117-146 117-41 0-78-15-108-40-25 12-53 18-83 18-85 0-154-58-166-139-46-15-79-56-79-105 0-60 42-110 99-127 10-20 23-39 37-59 25-37 61-63 90-70z"/>
<circle class="brain-core" cx="480" cy="310" r="62" filter="url(#brainBlur)"/>
<circle class="brain-core ring" cx="480" cy="310" r="88"/>
</svg>
</div>
</section>
<!-- Voice visualizer — shown only while mic is active -->
<div class="voice-visualizer" id="voice-viz">
<div class="viz-bar"></div><div class="viz-bar"></div><div class="viz-bar"></div>
<div class="viz-bar"></div><div class="viz-bar"></div><div class="viz-bar"></div>
<div class="viz-bar"></div><div class="viz-bar"></div><div class="viz-bar"></div>
<div class="viz-bar"></div><div class="viz-bar"></div><div class="viz-bar"></div>
<div class="viz-bar"></div><div class="viz-bar"></div><div class="viz-bar"></div>
</div>
<!-- Controls -->
<footer class="controls">
<div class="text-row">
<!-- FIX-4: textarea is created by script.js to replace this input,
keeping HTML clean while gaining auto-resize + shift-enter -->
<input
type="text"
id="text-input"
placeholder="বার্তা লিখুন… (Enter পাঠান · Shift+Enter নতুন লাইন)"
autocomplete="off"
/>
<button id="send-btn" title="Send">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2">
<line x1="22" y1="2" x2="11" y2="13"/>
<polygon points="22 2 15 22 11 13 2 9 22 2"/>
</svg>
</button>
</div>
<div class="voice-row">
<button id="mic-btn" class="mic-btn mic-off">
<span class="mic-icon">🎤</span>
<span class="mic-label">Start</span>
</button>
<button id="stop-btn" class="stop-btn" title="Stop AI speech">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<rect x="4" y="4" width="16" height="16" rx="2"/>
</svg>
Stop
</button>
</div>
</footer>
</main>
</div>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="/static/script.js"></script>
</body>
</html>