Files
hope-voice-api/web/index.html
T
snowspeeder 8905dc2c2a
Rebuild & Deploy / rebuild (push) Successful in 3m7s
fix: give realtime voice a dedicated conversation view
2026-10-04 10:35:34 -04:00

111 lines
19 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="#1C191F">
<title>Hope · Your private companion</title>
<link rel="manifest" href="manifest.json">
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<link rel="apple-touch-icon" href="brand/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="192x192" href="brand/hope-icon-192.png">
<link rel="icon" type="image/png" sizes="512x512" href="brand/hope-icon-512.png">
<link rel="stylesheet" href="hope-themes.css">
<link rel="stylesheet" href="style.css">
</head>
<body data-mode="voice">
<a class="skip-link" href="#chatTimeline">Skip to conversation</a>
<section id="loginGate" class="login-gate" aria-labelledby="loginHeading">
<h1 id="loginHeading">Welcome to Hope</h1>
<p>Sign in to your private voice and text companion.</p>
<form id="loginForm" class="account-form">
<label for="loginUsername">Username</label><input id="loginUsername" autocomplete="username" required maxlength="64">
<label for="loginPassword">Password</label><input id="loginPassword" type="password" autocomplete="current-password" required>
<button id="loginSubmit" class="btn btn-primary" disabled>Sign in</button>
</form>
<p id="loginMessage" role="status" aria-live="polite">Checking your session…</p>
<button id="retryLogout" class="btn btn-secondary" hidden>Retry sign out</button>
</section>
<main class="app-shell" id="authenticatedApp" hidden>
<aside id="chatSidebar" class="chat-sidebar" aria-label="Main navigation">
<div class="sidebar-brand"><span class="hope-mark" aria-hidden="true"></span><strong>Hope</strong><button id="drawerClose" class="btn btn-icon" aria-label="Close navigation">×</button></div>
<button id="newConversation" class="btn new-chat">+ New conversation</button>
<label class="sr-only" for="conversationSearch">Search conversations</label><input id="conversationSearch" type="search" placeholder="Search conversations">
<nav aria-label="Chat history"><div class="nav-heading"><h2>Workspaces</h2><button id="newWorkspace" class="btn btn-icon" aria-label="Create workspace">+</button></div><div id="workspaceList"></div><h2>Pinned</h2><div id="pinnedConversations"></div><div class="nav-heading"><h2 id="recentHeading">Recent</h2><button id="showArchived" class="btn btn-ghost" aria-pressed="false">Archived</button></div><div id="recentConversations"></div><button id="loadMoreConversations" class="btn btn-ghost" hidden>Load more</button></nav>
<div class="sidebar-footer" id="sessionActions" hidden><button id="openLibrary" class="btn btn-ghost">▱ Library</button><button id="tasksBtn" class="btn btn-ghost">⌁ Activity <span id="tasksBadge" class="tasks-badge" hidden>0</span></button><button id="accountMenuBtn" data-settings-open="account" class="btn account-trigger"><span class="account-avatar" aria-hidden="true">◦</span><span id="identityChip"><strong id="identityName"></strong><small>Account &amp; settings</small></span><span aria-hidden="true">⌄</span></button><button id="openPreferences" class="btn btn-ghost">Appearance</button></div>
</aside><button id="drawerBackdrop" class="drawer-backdrop" aria-label="Close navigation" hidden></button>
<section id="primaryChat" class="primary-chat" aria-labelledby="chatTitle">
<header class="chat-topbar"><button id="drawerOpen" class="btn btn-icon" aria-label="Open navigation" aria-expanded="false" aria-controls="chatSidebar">☰</button><div class="chat-heading"><h1 id="chatTitle">New conversation</h1><p>Your private conversation</p></div><span class="privacy-badge">Only you</span><button id="conversationMenu" class="btn btn-icon" popovertarget="conversationActions" aria-label="Conversation actions" hidden>•••</button></header>
<div id="conversationActions" class="conversation-actions" popover="auto"><button id="pinConversation" class="btn btn-ghost" disabled>Pin</button><button id="renameConversation" class="btn btn-ghost" disabled>Rename</button><button id="moveConversation" class="btn btn-ghost" disabled>Move</button><button id="exportMarkdown" class="btn btn-ghost">Download Markdown</button><button id="exportJSON" class="btn btn-ghost">Download JSON</button><button id="archiveConversation" class="btn btn-ghost" disabled>Archive</button><button id="deleteConversation" class="btn btn-ghost" disabled>Delete</button></div>
<p id="accountStatus" role="status" class="chat-notice"></p><div id="chatNotice" class="chat-notice" role="status" aria-live="polite"></div>
<section id="legacyVoice" class="inline-voice workspace" hidden aria-labelledby="voiceHeading">
<header class="voice-session-header"><div class="led-wall" id="ledWall" data-state="idle" aria-hidden="true"></div><div class="voice-summary"><h2 id="voiceHeading">Voice conversation</h2><p id="stateLabel" aria-live="polite">Ready when you are</p><p id="modeDescription" class="sr-only">Speak naturally. You can interrupt Hope.</p><span id="thinkingChip" hidden>Thinking…</span><span id="bargeHint" hidden>Speak to interrupt</span><div id="status" class="status" role="status">Secure connection ready</div><div class="mic-meter" id="micLevel" aria-hidden="true"><span id="micMeterFill"></span></div></div><button id="settingsBtn" class="btn btn-ghost" type="button">Settings</button></header>
<div id="conversation" class="live-transcript" tabindex="0" role="region" aria-labelledby="conversationHeading"><h3 id="conversationHeading" class="sr-only">Voice transcript</h3><span id="transcriptModeLabel" class="sr-only">Live voice transcript</span><div id="voiceHistory"></div><p id="voiceTranscriptHint" class="voice-transcript-hint">Your live transcript will appear here when you speak.</p><div id="messages" role="log" aria-label="Live voice messages" aria-live="polite" aria-relevant="additions text"></div></div>
<div class="voice-controls"><button id="startBtn" class="btn btn-primary" type="button" hidden>Start voice</button><button id="muteBtn" class="btn btn-secondary" aria-label="Mute microphone" title="Mute microphone" aria-pressed="false" disabled>Mute</button><button id="stopBtn" class="btn btn-danger" type="button" disabled>End voice</button></div>
</section>
<div id="chatTimeline" class="chat-timeline" tabindex="-1" aria-label="Messages"></div><div id="runProgress" class="run-progress" role="status" aria-live="polite"></div>
<form id="primaryComposer" class="primary-composer" aria-label="Message Hope"><label class="sr-only" for="primaryInput">Message Hope</label><textarea id="primaryInput" rows="2" placeholder="What’s on your mind?" required disabled></textarea><div id="attachmentPreviews"></div><div class="composer-footer"><div id="featureActions" class="feature-actions"></div><label id="researchModeLabel" for="researchMode" hidden>Mode</label><select id="researchMode" hidden><option value="normal">Standard</option><option value="research">Research</option><option value="deep_research">Deep research</option></select><div class="composer-right"><span>Talk</span><button id="talkToHope" class="btn lightwell-button" type="button" aria-label="Start realtime voice conversation" aria-controls="legacyVoice" disabled><span class="lightwell-symbol" aria-hidden="true"></span></button><button id="cancelRun" class="btn" type="button" hidden>Stop</button><button id="primarySend" class="btn btn-primary send-button" aria-label="Send message" disabled>↑</button></div></div></form>
<p class="chat-footnote"><span>Your conversation, private to you.</span><span class="keyhint">Enter to send · Shift + Enter for a new line</span></p>
</section></main>
<dialog id="settingsDialog" class="settings-window" aria-labelledby="settingsTitle"><div class="dialog-head"><div><h2 id="settingsTitle">Settings</h2><p>Your Hope, the way you like it.</p></div><button id="settingsClose" class="btn btn-icon" aria-label="Close settings">×</button></div><div class="settings-layout"><nav class="settings-nav" aria-label="Settings sections"><button class="btn selected" data-settings-target="account">Account</button><button class="btn" data-settings-target="appearance">Appearance</button><button class="btn" data-settings-target="preferences">Voice &amp; model</button><button class="btn" data-settings-target="privacy">Memory &amp; data</button><button class="btn" data-settings-target="security">Security</button><button id="adminBtn" class="btn admin-link" data-settings-target="admin" hidden aria-expanded="false" aria-controls="adminSection">Administration</button></nav><div class="settings-body">
<section data-settings-panel="account"><h3>Your account</h3><p>Personal settings belong here, separate from your conversations.</p><div class="account-heading"><strong id="settingsAccountName"></strong><small id="settingsAccountUsername"></small><small id="settingsAccountRole"></small></div><p id="settingsAccountMessage" role="status"></p><p>Your private assistant and saved conversations belong to this account.</p><button id="logoutBtn" class="btn btn-secondary">Sign out</button></section>
<section data-settings-panel="appearance" hidden><h3>Appearance</h3><p>One visual identity across text, voice, and your devices.</p><div id="settingsAppearanceContent"></div></section>
<section data-settings-panel="preferences" hidden><div id="settingsSheet" class="settings-sheet" hidden inert><div class="sheet-header"><h3>Voice &amp; model</h3><button id="closeSheetBtn" class="btn btn-icon" aria-label="Close voice settings">×</button></div><div class="sheet-content">
<form id="modelSettingsForm" class="account-form model-settings" aria-labelledby="modelSettingsHeading" aria-describedby="modelSettingsScope">
<h3 id="modelSettingsHeading">Model and thinking</h3>
<p id="modelSettingsScope" class="model-settings-note">Saved for your account. Applies to your next request, including in an active conversation, without interrupting it.</p>
<div>
<label for="modelSelect">Model</label>
<select id="modelSelect" name="model" required disabled aria-describedby="modelSettingsStatus"><option value="">Open settings to load models</option></select>
</div>
<div>
<label for="thinkingLevelSelect">Thinking level</label>
<select id="thinkingLevelSelect" name="thinking_level" required disabled aria-describedby="thinkingLevelNote"><option value="">Open settings to load thinking levels</option></select>
<p id="thinkingLevelNote" class="model-settings-note">Thinking requests may be limited or ignored by the selected model.</p>
</div>
<p id="modelSettingsStatus" class="model-settings-status" role="status" aria-live="polite" aria-atomic="true">Open settings to load your saved preferences.</p>
<div class="model-settings-actions">
<button id="saveModelSettings" type="submit" class="btn btn-primary" disabled>Save</button>
<button id="retryModelSettings" type="button" class="btn btn-secondary" hidden>Retry</button>
</div>
</form>
<h3 class="sheet-section-title">Voice</h3>
<label for="voiceSearch">Search English voices</label>
<input id="voiceSearch" type="search" placeholder="Name, locale, gender, or Omni" autocomplete="off" aria-controls="voiceList">
<p id="voiceCatalogStatus" class="model-settings-note" role="status" aria-live="polite">Ava Dragon HD Omni is the recommended female default.</p>
<button id="retryVoices" type="button" class="btn btn-secondary" hidden>Retry voice catalog</button>
<div class="voice-list" id="voiceList" role="radiogroup" aria-label="English voices"></div>
</div></div></section>
<section data-settings-panel="privacy" hidden><h3>Memory &amp; data</h3><p class="settings-intro">Choose what Hope keeps for future conversations. Saved memory is separate from your chat history and uploaded files.</p><p id="privacyIdentity"></p><p id="settingsMemoryStatus" role="status"></p><div id="privacyMemory"></div><section class="data-guide" aria-labelledby="dataGuideTitle"><h4 id="dataGuideTitle">Manage your other saved data</h4><dl><div><dt>Conversations</dt><dd>Use the ••• menu beside a chat to archive it. To delete a chat, open it and use Conversation actions at the top right. Archiving keeps the chat; deleting removes it.</dd></div><div><dt>Files &amp; saved drafts</dt><dd>Review downloads and delete individual items in Library. Deleting a file also makes it unavailable to old chats that reference it.</dd></div></dl><button id="settingsOpenLibrary" class="btn btn-secondary">Manage files &amp; drafts</button></section></section>
<section data-settings-panel="security" hidden><h3>Security</h3><p class="settings-intro">Your current connection and account access—not another set of switches to configure.</p><dl class="security-facts"><div><dt>Connected server</dt><dd id="securityServer"></dd></div><div><dt>Connection</dt><dd id="securityConnection"></dd></div><div><dt>Signed-in account</dt><dd id="securityIdentity"></dd></div><div><dt>Session access</dt><dd>Hope uses a server-managed cookie session. Signing out ends this browser’s current session.</dd></div></dl><section class="security-action"><h4>Leaving a shared device?</h4><p>Sign out so the next person cannot open your conversations or files.</p><button id="securitySignOut" data-settings-signout class="btn btn-secondary" type="button">Sign out of this browser</button></section><section class="security-action"><h4>Password or account concerns?</h4><p>Contact your Hope administrator for a password reset. A reset revokes existing sessions. Session lists, self-service password changes and two-factor settings are not available here.</p></section></section>
<section data-settings-panel="admin" hidden> <section id="adminSection" class="admin-section" hidden aria-labelledby="adminHeading">
<div class="admin-header"><div><h2 id="adminHeading" tabindex="-1">Accounts</h2><p>Create a private assistant for each person.</p></div><button id="refreshUsers" class="btn btn-secondary">Refresh status</button></div>
<p id="adminMessage" role="status">Loading accounts…</p>
<div class="table-scroll"><table><caption class="sr-only">Accounts and private assistant provisioning status</caption><thead><tr><th scope="col">Account</th><th scope="col">Role</th><th scope="col">Status</th><th scope="col">Container</th><th scope="col">Error</th><th scope="col">Actions</th></tr></thead><tbody id="usersBody"></tbody></table></div>
<h3>Create account</h3>
<form id="createUserForm" class="create-user-form account-form">
<div><label for="newUsername">Username</label><input id="newUsername" required autocomplete="off" maxlength="64"></div>
<div><label for="newDisplayName">Display name</label><input id="newDisplayName" required autocomplete="off" maxlength="120"></div>
<div><label for="newPassword">Initial password</label><input id="newPassword" type="password" required minlength="12" autocomplete="new-password"></div>
<div><label for="newRole">Role</label><select id="newRole"><option value="user">User</option><option value="admin">Administrator</option></select></div>
<button id="createUserSubmit" class="btn btn-primary">Create account</button>
</form><p id="createUserMessage" role="status"></p>
</section>
</section>
</div></div></dialog>
<div id="sheetBackdrop" class="sheet-backdrop" hidden></div>
<dialog id="libraryDialog" class="library-window" aria-labelledby="libraryTitle"><div class="dialog-head"><div><h2 id="libraryTitle">Library</h2><p class="settings-intro">Your reference files and saved drafts.</p></div><button id="closeLibrary" class="btn btn-icon" aria-label="Close Library">×</button></div><p class="library-guide">Upload a file, choose <strong>Use in conversation</strong>, then ask Hope about it. Files are only included when you send a message—not automatically in every chat.</p><div id="libraryContent"></div></dialog>
<div class="tasks-panel" id="tasksPanel" hidden inert aria-label="Assistant activity"><div class="tasks-header"><div><h2>Activity</h2><p>Progress from your private assistant.</p></div><button id="closeTasksBtn" class="btn btn-icon" aria-label="Close activity">×</button></div><div class="tasks-list" id="tasksList"><div class="tasks-empty"><strong>No activity yet</strong><span>Progress appears here while Hope works. Replies stay in your conversation.</span></div></div><div id="activityContent"></div></div>
<div hidden aria-hidden="true"><button id="voiceModeBtn" type="button">Voice</button><button id="textModeBtn" type="button">Text</button><button id="clearBtn" type="button">Clear</button><button id="openVoice" type="button">Voice</button><button id="backToChat" type="button">Back to chat</button><form id="chatComposer"><textarea id="chatInput"></textarea><button id="sendChatBtn" disabled>Send</button></form></div>
<dialog id="accountDialog" aria-labelledby="accountDialogTitle" aria-describedby="accountDialogDescription">
<form id="accountActionForm" class="account-form"><h2 id="accountDialogTitle"></h2><p id="accountDialogDescription"></p>
<div id="resetPasswordField"><label for="resetPassword">New password</label><input id="resetPassword" type="password" minlength="12" autocomplete="new-password"></div>
<p id="accountDialogMessage" role="status"></p><div class="dialog-actions"><button id="cancelAccountAction" type="button" class="btn btn-secondary">Cancel</button><button id="confirmAccountAction" class="btn btn-danger">Confirm</button></div>
</form>
</dialog>
<dialog id="chatActionDialog" aria-labelledby="chatActionTitle"><form id="chatActionForm" class="account-form"><h2 id="chatActionTitle"></h2><p id="chatActionDescription"></p><label id="chatActionLabel" for="chatActionValue">Name</label><textarea id="chatActionValue" rows="3" required></textarea><label id="chatWorkspaceLabel" for="chatWorkspaceSelect" hidden>Workspace</label><select id="chatWorkspaceSelect" hidden></select><p id="chatActionError" role="status"></p><div class="dialog-actions"><button id="chatActionCancel" type="button" class="btn">Cancel</button><button id="chatActionSave" class="btn btn-primary">Save</button></div></form></dialog><dialog id="preferencesDialog" aria-labelledby="preferencesTitle"><h2 id="preferencesTitle">Appearance</h2><form id="preferencesForm" class="account-form"><label for="paletteSelect">Palette</label><select id="paletteSelect"><option value="opal">Opal</option><option value="graphite">Graphite</option><option value="ocean">Ocean</option><option value="sage">Sage</option><option value="lavender">Lavender</option></select><label for="appearanceSelect">Appearance</label><select id="appearanceSelect"><option value="system">System</option><option value="light">Light</option><option value="dark">Dark</option></select><p id="preferencesMessage" role="status"></p><div class="dialog-actions"><button id="closePreferences" class="btn" type="button">Close</button><button class="btn btn-primary">Save appearance</button></div></form></dialog>
<script src="livekit-client.umd.js"></script><script src="auth.js"></script><script src="app.js"></script><script src="chat.js"></script><script src="features.js"></script><script src="settings.js"></script>
</body></html>