Files
hope-voice-api/web/index.html
T
snowspeeder 120ab192f1
Rebuild & Deploy / rebuild (push) Successful in 49s
feat: add seamless text and voice chat
2026-08-29 07:49:29 -04:00

152 lines
8.9 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#08111f">
<title>Hope · Voice companion</title>
<link rel="manifest" href="manifest.json">
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<link rel="stylesheet" href="style.css">
</head>
<body data-mode="voice">
<a class="skip-link" href="#conversation">Skip to conversation</a>
<main class="app-shell">
<header class="topbar">
<div class="brand">
<span class="brand-glyph" aria-hidden="true"><i></i></span>
<div>
<h1>Hope</h1>
<p>Voice and text companion</p>
</div>
</div>
<div class="topbar-actions">
<div class="mode-switch" role="group" aria-label="Conversation mode">
<button id="voiceModeBtn" class="mode-option active" type="button" aria-pressed="true">
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path></svg>
<span>Voice</span>
</button>
<button id="textModeBtn" class="mode-option" type="button" aria-pressed="false">
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M21 15a4 4 0 0 1-4 4H8l-5 3V7a4 4 0 0 1 4-4h10a4 4 0 0 1 4 4Z"></path></svg>
<span>Text</span>
</button>
</div>
<div class="identity-chip" aria-label="Current profile: Shane">
<span class="presence-dot" aria-hidden="true"></span>
<span>Private to <strong>Shane</strong></span>
</div>
<button id="tasksBtn" class="btn btn-secondary tasks-trigger" aria-label="Background tasks" title="Background tasks">
<svg viewBox="0 0 24 24" width="17" height="17" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 6h11M8 12h11M8 18h7"></path><path d="m3 6 1 1 2-2M3 12l1 1 2-2M3 18l1 1 2-2"></path></svg>
<span>Tasks</span>
<span class="tasks-badge" id="tasksBadge" hidden>0</span>
</button>
</div>
</header>
<div class="workspace">
<section class="voice-console" aria-labelledby="voiceHeading">
<div class="console-intro">
<h2 id="voiceHeading">I'm here.</h2>
<p id="modeDescription">Speak naturally. I’ll listen, remember, and answer in real time.</p>
</div>
<div class="orb-stage">
<div class="orb" id="orb" data-state="idle" aria-hidden="true">
<div class="orb-orbit orbit-one"></div>
<div class="orb-orbit orbit-two"></div>
<div class="orb-core"><span class="orb-shimmer"></span></div>
<div class="orb-ring"></div>
</div>
<div class="orb-labels" aria-live="polite">
<span class="state-label" id="stateLabel">Ready when you are</span>
<span class="thinking-chip" id="thinkingChip" hidden>Thinking&hellip;</span>
<span class="barge-hint" id="bargeHint" hidden>Speak to interrupt</span>
</div>
</div>
<div class="controls" aria-label="Conversation controls">
<button id="startBtn" class="btn btn-primary">
<span class="button-spark" aria-hidden="true"></span>
Start conversation
</button>
<button id="stopBtn" class="btn btn-danger" disabled>End conversation</button>
<button id="muteBtn" class="btn btn-icon" aria-label="Mute microphone" title="Mute microphone" disabled>
<svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 2a3 3 0 0 0-3 3v7a3 3 0 0 0 6 0V5a3 3 0 0 0-3-3Z"></path><path d="M19 10v2a7 7 0 0 1-14 0v-2"></path><line x1="8" y1="22" x2="16" y2="22"></line></svg>
</button>
<button id="settingsBtn" class="btn btn-icon" aria-label="Voice settings" title="Voice settings">
<svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h10M18 7h2M4 17h2M10 17h10"></path><circle cx="16" cy="7" r="2"></circle><circle cx="8" cy="17" r="2"></circle></svg>
</button>
</div>
<div class="connection-strip">
<div class="status" id="status">Secure connection ready</div>
<div class="mic-meter" id="micLevel" aria-hidden="true"><span class="mic-meter-fill" id="micMeterFill"></span></div>
</div>
<p class="privacy-note">
<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><rect x="5" y="10" width="14" height="10" rx="2"></rect><path d="M8 10V7a4 4 0 0 1 8 0v3"></path></svg>
Your memory stays in Shane’s private profile.
</p>
</section>
<section class="transcript" id="conversation" aria-labelledby="conversationHeading">
<div class="transcript-header">
<div>
<h2 id="conversationHeading">Conversation</h2>
<p id="transcriptModeLabel">Live voice transcript</p>
</div>
<button id="clearBtn" class="btn btn-ghost" title="Clear conversation">Clear</button>
</div>
<div id="messages" aria-live="polite"></div>
<form class="chat-composer" id="chatComposer" aria-label="Send a text message">
<label class="sr-only" for="chatInput">Message Hope</label>
<textarea id="chatInput" rows="1" maxlength="2000" placeholder="Message Hope…" autocomplete="off"></textarea>
<button id="sendChatBtn" class="send-chat" type="submit" disabled aria-label="Send message">
<svg viewBox="0 0 24 24" width="19" height="19" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m22 2-7 20-4-9-9-4Z"></path><path d="M22 2 11 13"></path></svg>
</button>
<span class="composer-hint">Enter to send · Shift+Enter for a new line</span>
</form>
</section>
</div>
</main>
<div class="sheet-backdrop" id="sheetBackdrop" hidden></div>
<div class="settings-sheet" id="settingsSheet" role="dialog" aria-modal="true" aria-label="Voice settings">
<div class="sheet-handle" aria-hidden="true"></div>
<div class="sheet-header">
<div>
<h2>Choose Hope’s voice</h2>
<p>Changes apply to this conversation.</p>
</div>
<button id="closeSheetBtn" class="btn btn-icon" aria-label="Close voice settings">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"></path></svg>
</button>
</div>
<div class="voice-list" id="voiceList"></div>
</div>
<div class="tasks-panel" id="tasksPanel" aria-label="Background tasks">
<div class="tasks-header">
<div>
<h2>Background tasks</h2>
<p>Longer work continues here.</p>
</div>
<button id="closeTasksBtn" class="btn btn-icon" aria-label="Close background tasks">
<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M18 6 6 18M6 6l12 12"></path></svg>
</button>
</div>
<div class="tasks-list" id="tasksList">
<div class="tasks-empty">
<strong>Nothing running</strong>
<span>Ask Hope to research or compare something.</span>
</div>
</div>
</div>
<script src="livekit-client.umd.js"></script>
<script src="app.js"></script>
</body>
</html>