Files
hope-voice-api/web/index.html
T

216 lines
20 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="Conversations"><div class="sidebar-brand"><span class="hope-mark" aria-hidden="true">✳</span><strong>Hope</strong><button id="drawerClose" class="btn btn-ghost" aria-label="Close navigation">×</button></div><button id="newConversation" class="btn new-chat">+ New chat</button><label class="sr-only" for="conversationSearch">Search conversations</label><input id="conversationSearch" type="search" placeholder="Search conversations"><nav aria-label="Chat history"><h2>Pinned</h2><div id="pinnedConversations"></div><div class="nav-heading"><h2>Workspaces</h2><button id="newWorkspace" class="btn btn-ghost" aria-label="Create workspace">+</button></div><div id="workspaceList"></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"><button id="openVoice" class="btn btn-ghost">Voice &amp; account</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-ghost" aria-label="Open navigation" aria-expanded="false" aria-controls="chatSidebar">☰</button><h1 id="chatTitle">New chat</h1><button id="talkToHope" class="btn talk-trigger" type="button" aria-controls="legacyVoice">Talk to Hope</button><div id="featureActions" class="feature-actions"></div><button id="conversationMenu" class="btn btn-ghost" popovertarget="conversationActions" aria-label="Conversation actions" hidden>•••</button><div id="conversationActions" class="conversation-actions"><button id="pinConversation" class="btn btn-ghost" disabled>Pin</button><button id="renameConversation" class="btn btn-ghost" disabled>Rename</button><button id="exportMarkdown" class="btn btn-ghost">Download Markdown</button><button id="exportJSON" class="btn btn-ghost">Download JSON</button><button id="moveConversation" class="btn btn-ghost" disabled>Move</button><button id="archiveConversation" class="btn btn-ghost" disabled>Archive</button><button id="deleteConversation" class="btn btn-ghost" disabled>Delete</button></div></header><div id="chatNotice" class="chat-notice" role="status" aria-live="polite"></div><div id="chatTimeline" class="chat-timeline" tabindex="-1" aria-label="Messages"><div class="chat-welcome"><span class="hope-mark" aria-hidden="true">✳</span><h2>A little space to think.</h2><p>Ask a question, work through an idea, or start something new.</p></div></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"><label id="researchModeLabel" for="researchMode" hidden>Mode</label><select id="researchMode" hidden><option value="normal">Chat</option><option value="research">Research</option></select><span>Enter to send · Shift + Enter for a new line</span><button id="cancelRun" class="btn" type="button" hidden>Stop response</button><button id="primarySend" class="btn btn-primary" disabled>Send ↑</button></div></form><p class="chat-footnote">Your chats are private to your account. Hope can make mistakes.</p></section><section id="legacyVoice" class="legacy-voice" hidden aria-label="Voice and account settings"><button id="backToChat" class="btn btn-secondary">← Chat</button><p>Live voice controls and account settings.</p> <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" id="sessionActions" hidden>
<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" id="identityChip" aria-label="Current account">
<span class="presence-dot" aria-hidden="true"></span>
<span>Private to <strong id="identityName"></strong></span>
</div>
<button id="tasksBtn" class="btn btn-secondary tasks-trigger" aria-label="Assistant activity" title="Assistant activity">
<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>Activity</span>
<span class="tasks-badge" id="tasksBadge" hidden>0</span>
</button>
</div>
</header>
<div class="account-toolbar">
<p id="accountStatus" role="status"></p>
<button id="adminBtn" class="btn btn-secondary" hidden aria-expanded="false" aria-controls="adminSection">Manage accounts</button>
<button id="logoutBtn" class="btn btn-secondary">Sign out</button>
</div>
<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>
<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="led-wall" id="ledWall" data-state="idle" aria-hidden="true">
</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="Hope settings" title="Hope 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" role="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>
<span id="privacyIdentity"></span>
</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>
</section> </main>
<div class="sheet-backdrop" id="sheetBackdrop" hidden></div>
<div class="settings-sheet" id="settingsSheet" hidden inert role="dialog" aria-modal="true" aria-label="Hope settings">
<div class="sheet-handle" aria-hidden="true"></div>
<div class="sheet-header">
<div>
<h2>Hope’s settings</h2>
<p>Your voice preference applies to this conversation.</p>
</div>
<button id="closeSheetBtn" class="btn btn-icon" aria-label="Close 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="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>
<div class="tasks-panel" id="tasksPanel" hidden inert aria-label="Assistant activity">
<div class="tasks-header">
<div>
<h2>Assistant activity</h2>
<p>Progress from your private assistant.</p>
</div>
<button id="closeTasksBtn" class="btn btn-icon" aria-label="Close activity">
<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>No activity yet</strong>
<span>Progress appears here while Hope works. Replies stay in your conversation.</span>
</div>
</div>
</div>
<script src="livekit-client.umd.js"></script>
<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="auth.js"></script>
<script src="app.js"></script>
<script src="chat.js"></script>
<script src="features.js"></script>
</body>
</html>