2025-03-05 07:27:03 +00:00
<!DOCTYPE html>
2024-06-08 22:03:26 +00:00
< html lang = "en" >
{{template "views/partials/head" .}}
2025-01-07 16:18:21 +00:00
< script defer src = "static/talk.js" > < / script >
2026-03-13 20:37:15 +00:00
< body class = "bg-[var(--color-bg-primary)] text-[var(--color-text-primary)]" >
2026-02-17 23:14:39 +00:00
< div class = "app-layout" >
2025-03-05 07:27:03 +00:00
{{template "views/partials/navbar" .}}
2026-03-13 20:37:15 +00:00
2026-02-17 23:14:39 +00:00
< main class = "main-content" >
< div class = "main-content-inner" >
2024-06-08 22:03:26 +00:00
2025-03-05 07:27:03 +00:00
< div class = "container mx-auto px-4 py-8 flex-grow" >
<!-- Hero Section -->
2025-11-29 21:11:44 +00:00
< div class = "hero-section" >
< div class = "hero-content" >
< h1 class = "hero-title" >
< i class = "fas fa-comments mr-2" > < / i > Talk Interface
2025-03-05 07:27:03 +00:00
< / h1 >
2026-03-13 20:37:15 +00:00
< p class = "hero-subtitle" > Real-time voice conversation with your AI models via WebRTC< / p >
2024-06-08 22:03:26 +00:00
< / div >
< / div >
2025-03-05 07:27:03 +00:00
<!-- Talk Interface -->
< div class = "max-w-3xl mx-auto" >
2025-11-29 21:11:44 +00:00
< div class = "card overflow-hidden" >
2025-03-05 07:27:03 +00:00
< div class = "p-6" >
2026-03-13 20:37:15 +00:00
<!-- Connection Status -->
< div id = "connectionStatus" class = "rounded-lg p-4 mb-4 flex items-center space-x-3 bg-[var(--color-bg-primary)]/50 border border-[var(--color-border-subtle)]" >
< i id = "statusIcon" class = "fa-solid fa-circle text-[var(--color-text-secondary)]" > < / i >
< span id = "statusLabel" class = "font-medium text-[var(--color-text-secondary)]" > Disconnected< / span >
2025-03-05 07:27:03 +00:00
< / div >
2026-03-13 20:37:15 +00:00
2025-03-05 07:27:03 +00:00
<!-- Note -->
2026-02-17 23:14:39 +00:00
< div class = "bg-[var(--color-primary-light)] border border-[var(--color-primary)]/20 rounded-lg p-4 mb-6" >
2025-03-05 07:27:03 +00:00
< div class = "flex items-start" >
2026-02-17 23:14:39 +00:00
< i class = "fas fa-info-circle text-[var(--color-primary)] mt-1 mr-3 flex-shrink-0" > < / i >
< p class = "text-[var(--color-text-secondary)]" >
2026-03-13 20:37:15 +00:00
< strong class = "text-[var(--color-primary)]" > Note:< / strong > Select a pipeline model below and click 'Connect' to start a real-time voice conversation. The pipeline model includes VAD, transcription, LLM, and TTS components. Your microphone audio streams continuously; the server detects speech and responds automatically.
2025-03-05 07:27:03 +00:00
< / p >
< / div >
< / div >
2026-03-13 20:37:15 +00:00
<!-- Model Selector -->
< div class = "mb-4 space-y-2" >
< label for = "modelSelect" class = "flex items-center text-[var(--color-text-secondary)] font-medium" >
< i class = "fas fa-brain text-[var(--color-primary)] mr-2" > < / i > Pipeline Model
< / label >
< select id = "modelSelect"
class="w-full bg-[var(--color-bg-primary)] text-[var(--color-text-primary)] border border-[var(--color-border-subtle)] focus:border-[var(--color-primary)] focus:ring-2 focus:ring-[var(--color-primary)]/50 rounded-lg shadow-sm p-2.5 appearance-none">
< option value = "" disabled class = "text-[var(--color-text-secondary)]" > Select a pipeline model< / option >
{{ range .PipelineModels }}
< option value = "{{.Name}}"
data-vad="{{.VAD}}"
data-stt="{{.Transcription}}"
data-llm="{{.LLM}}"
data-tts="{{.TTS}}"
data-voice="{{.Voice}}"
class="bg-[var(--color-bg-primary)] text-[var(--color-text-primary)]">{{.Name}}< / option >
{{ end }}
< / select >
< / div >
<!-- Pipeline Details (shown when a model is selected) -->
< div id = "pipelineDetails" class = "mb-6 hidden" >
< div class = "grid grid-cols-2 md:grid-cols-4 gap-2 text-xs" >
< div class = "bg-[var(--color-bg-primary)]/50 rounded p-2 border border-[var(--color-border-subtle)]" >
< p class = "text-[var(--color-text-secondary)] mb-0.5" > VAD< / p >
< p id = "pipelineVAD" class = "font-mono text-[var(--color-text-primary)] truncate" > < / p >
< / div >
< div class = "bg-[var(--color-bg-primary)]/50 rounded p-2 border border-[var(--color-border-subtle)]" >
< p class = "text-[var(--color-text-secondary)] mb-0.5" > Transcription< / p >
< p id = "pipelineSTT" class = "font-mono text-[var(--color-text-primary)] truncate" > < / p >
< / div >
< div class = "bg-[var(--color-bg-primary)]/50 rounded p-2 border border-[var(--color-border-subtle)]" >
< p class = "text-[var(--color-text-secondary)] mb-0.5" > LLM< / p >
< p id = "pipelineLLM" class = "font-mono text-[var(--color-text-primary)] truncate" > < / p >
< / div >
< div class = "bg-[var(--color-bg-primary)]/50 rounded p-2 border border-[var(--color-border-subtle)]" >
< p class = "text-[var(--color-text-secondary)] mb-0.5" > TTS< / p >
< p id = "pipelineTTS" class = "font-mono text-[var(--color-text-primary)] truncate" > < / p >
< / div >
2025-03-05 07:27:03 +00:00
< / div >
2026-03-13 20:37:15 +00:00
< / div >
<!-- Session Settings (collapsible) -->
< details class = "mb-6 border border-[var(--color-border-subtle)] rounded-lg" >
< summary class = "cursor-pointer p-3 flex items-center text-[var(--color-text-secondary)] font-medium hover:bg-[var(--color-bg-primary)]/50 rounded-lg" >
< i class = "fas fa-sliders text-[var(--color-primary)] mr-2" > < / i > Session Settings
< / summary >
< div class = "p-4 pt-2 space-y-4" >
<!-- Instructions -->
< div class = "space-y-1" >
< label for = "instructionsInput" class = "text-sm text-[var(--color-text-secondary)]" > Instructions< / label >
< textarea id = "instructionsInput" rows = "3"
class="w-full bg-[var(--color-bg-primary)] text-[var(--color-text-primary)] border border-[var(--color-border-subtle)] focus:border-[var(--color-primary)] focus:ring-2 focus:ring-[var(--color-primary)]/50 rounded-lg shadow-sm p-2.5 text-sm"
placeholder="System instructions for the model (e.g. 'be extremely succinct', 'talk quickly')">You are a helpful voice assistant. Your responses will be spoken aloud using text-to-speech, so keep them concise and conversational. Do not use markdown formatting, bullet points, numbered lists, code blocks, or special characters. Speak naturally as you would in a phone conversation. Avoid parenthetical asides, URLs, and anything that cannot be clearly vocalized.< / textarea >
< / div >
<!-- Voice -->
< div class = "space-y-1" >
< label for = "voiceInput" class = "text-sm text-[var(--color-text-secondary)]" > Voice< / label >
< input id = "voiceInput" type = "text"
class="w-full bg-[var(--color-bg-primary)] text-[var(--color-text-primary)] border border-[var(--color-border-subtle)] focus:border-[var(--color-primary)] focus:ring-2 focus:ring-[var(--color-primary)]/50 rounded-lg shadow-sm p-2.5 text-sm"
placeholder="Voice name (leave blank for model default)">
< / div >
<!-- Language -->
< div class = "space-y-1" >
< label for = "languageInput" class = "text-sm text-[var(--color-text-secondary)]" > Transcription Language< / label >
< input id = "languageInput" type = "text"
class="w-full bg-[var(--color-bg-primary)] text-[var(--color-text-primary)] border border-[var(--color-border-subtle)] focus:border-[var(--color-primary)] focus:ring-2 focus:ring-[var(--color-primary)]/50 rounded-lg shadow-sm p-2.5 text-sm"
placeholder="Language code (e.g. 'en', 'es') — leave blank for auto-detect">
< / div >
2025-03-05 07:27:03 +00:00
< / div >
2026-03-13 20:37:15 +00:00
< / details >
<!-- Conversation Transcript -->
< div id = "transcript" class = "mb-6 space-y-3 max-h-96 overflow-y-auto p-3 bg-[var(--color-bg-primary)]/50 border border-[var(--color-border-subtle)] rounded-lg" style = "min-height: 6rem;" >
< p class = "text-[var(--color-text-secondary)] italic" > Conversation will appear here...< / p >
2025-03-05 07:27:03 +00:00
< / div >
2026-03-13 20:37:15 +00:00
2025-03-05 07:27:03 +00:00
<!-- Buttons -->
< div class = "flex items-center justify-between mt-8" >
2026-03-13 20:37:15 +00:00
< div class = "flex items-center space-x-3" >
< button id = "connectButton"
class="inline-flex items-center bg-[var(--color-success)] hover:bg-[var(--color-success)]/90 text-white font-semibold py-2 px-6 rounded-lg transition-colors">
< i class = "fas fa-plug mr-2" > < / i >
< span > Connect< / span >
< / button >
< button id = "testToneButton"
class="inline-flex items-center bg-[var(--color-accent)] hover:bg-[var(--color-accent)]/90 text-white font-semibold py-2 px-6 rounded-lg transition-colors"
style="display: none;">
< i class = "fas fa-wave-square mr-2" > < / i >
< span > Test Tone< / span >
< / button >
< button id = "diagnosticsButton"
class="inline-flex items-center bg-[var(--color-bg-primary)] hover:bg-[var(--color-bg-primary)]/80 text-[var(--color-text-secondary)] font-semibold py-2 px-4 rounded-lg transition-colors border border-[var(--color-border-subtle)]"
style="display: none;">
< i class = "fas fa-chart-line mr-2" > < / i >
< span > Diag< / span >
< / button >
< / div >
< button id = "disconnectButton"
class="inline-flex items-center bg-[var(--color-error)] hover:bg-[var(--color-error)]/90 text-white font-semibold py-2 px-6 rounded-lg transition-colors"
style="display: none;">
< i class = "fas fa-plug-circle-xmark mr-2" > < / i >
< span > Disconnect< / span >
2025-03-05 07:27:03 +00:00
< / button >
< / div >
2026-03-13 20:37:15 +00:00
<!-- Audio element for WebRTC playback -->
< audio id = "audioPlayback" autoplay style = "display:none;" > < / audio >
<!-- Audio Diagnostics (toggled by button) -->
< div id = "diagnosticsPanel" style = "display: none;" class = "mt-6 border border-[var(--color-border-subtle)] rounded-lg p-4" >
< h3 class = "font-semibold text-[var(--color-text-primary)] mb-3" >
< i class = "fas fa-chart-line text-[var(--color-primary)] mr-2" > < / i > Audio Diagnostics
< / h3 >
< div class = "grid grid-cols-1 md:grid-cols-2 gap-4 mb-4" >
< div >
< p class = "text-xs text-[var(--color-text-secondary)] mb-1" > Waveform (time domain)< / p >
< canvas id = "waveformCanvas" width = "400" height = "120" class = "w-full border border-[var(--color-border-subtle)] rounded bg-black" > < / canvas >
< / div >
< div >
< p class = "text-xs text-[var(--color-text-secondary)] mb-1" > Spectrum (FFT)< / p >
< canvas id = "spectrumCanvas" width = "400" height = "120" class = "w-full border border-[var(--color-border-subtle)] rounded bg-black" > < / canvas >
< / div >
< / div >
< div class = "grid grid-cols-2 md:grid-cols-4 gap-3 mb-3" >
< div class = "bg-[var(--color-bg-primary)]/50 rounded p-2" >
< p class = "text-xs text-[var(--color-text-secondary)]" > Peak Freq< / p >
< p id = "statPeakFreq" class = "font-mono text-sm text-[var(--color-text-primary)]" > --< / p >
< / div >
< div class = "bg-[var(--color-bg-primary)]/50 rounded p-2" >
< p class = "text-xs text-[var(--color-text-secondary)]" > THD< / p >
< p id = "statTHD" class = "font-mono text-sm text-[var(--color-text-primary)]" > --< / p >
< / div >
< div class = "bg-[var(--color-bg-primary)]/50 rounded p-2" >
< p class = "text-xs text-[var(--color-text-secondary)]" > RMS Level< / p >
< p id = "statRMS" class = "font-mono text-sm text-[var(--color-text-primary)]" > --< / p >
< / div >
< div class = "bg-[var(--color-bg-primary)]/50 rounded p-2" >
< p class = "text-xs text-[var(--color-text-secondary)]" > Sample Rate< / p >
< p id = "statSampleRate" class = "font-mono text-sm text-[var(--color-text-primary)]" > --< / p >
< / div >
< / div >
< div class = "grid grid-cols-2 md:grid-cols-4 gap-3 mb-3" >
< div class = "bg-[var(--color-bg-primary)]/50 rounded p-2" >
< p class = "text-xs text-[var(--color-text-secondary)]" > Packets Recv< / p >
< p id = "statPacketsRecv" class = "font-mono text-sm text-[var(--color-text-primary)]" > --< / p >
< / div >
< div class = "bg-[var(--color-bg-primary)]/50 rounded p-2" >
< p class = "text-xs text-[var(--color-text-secondary)]" > Packets Lost< / p >
< p id = "statPacketsLost" class = "font-mono text-sm text-[var(--color-text-primary)]" > --< / p >
< / div >
< div class = "bg-[var(--color-bg-primary)]/50 rounded p-2" >
< p class = "text-xs text-[var(--color-text-secondary)]" > Jitter< / p >
< p id = "statJitter" class = "font-mono text-sm text-[var(--color-text-primary)]" > --< / p >
< / div >
< div class = "bg-[var(--color-bg-primary)]/50 rounded p-2" >
< p class = "text-xs text-[var(--color-text-secondary)]" > Concealed< / p >
< p id = "statConcealed" class = "font-mono text-sm text-[var(--color-text-primary)]" > --< / p >
< / div >
< / div >
< pre id = "statsRaw" class = "text-xs text-[var(--color-text-secondary)] bg-[var(--color-bg-primary)]/50 rounded p-2 max-h-32 overflow-y-auto font-mono" style = "white-space: pre-wrap;" > < / pre >
< / div >
2025-03-05 07:27:03 +00:00
< / div >
< / div >
2024-06-08 22:03:26 +00:00
< / div >
< / div >
2026-03-13 20:37:15 +00:00
2025-03-05 07:27:03 +00:00
{{template "views/partials/footer" .}}
2026-02-17 23:14:39 +00:00
< / div >
< / main >
< / div >
2024-06-08 22:03:26 +00:00
< / body >
2026-02-17 23:14:39 +00:00
< / html >