VoiceVault / static /index.html
NinjainPJs's picture
Initial release: VoiceVault v1.0.0 β€” Voice-First RAG Knowledge Agent
85f900d
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VoiceVault β€” AI Knowledge Agent</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=Inter:wght@300;400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<!-- Animated background orbs -->
<div class="bg-orbs" aria-hidden="true">
<div class="orb orb-1"></div>
<div class="orb orb-2"></div>
<div class="orb orb-3"></div>
</div>
<!-- App shell -->
<div class="app">
<!-- ─── Sidebar ───────────────────────────────────────── -->
<aside class="sidebar">
<!-- Logo -->
<div class="sidebar-logo">
<div class="logo-icon">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 18.75a6 6 0 0 0 6-6v-1.5m-6 7.5a6 6 0 0 1-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 0 1-3-3V4.5a3 3 0 1 1 6 0v8.25a3 3 0 0 1-3 3Z"/>
</svg>
</div>
<div class="logo-text">
<span class="logo-name">VoiceVault</span>
<span class="logo-tagline">AI Knowledge Agent</span>
</div>
</div>
<!-- Navigation -->
<nav class="sidebar-nav">
<button class="nav-item active" data-view="ask" onclick="switchView('ask')">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z"/>
</svg>
Ask
</button>
<button class="nav-item" data-view="kbs" onclick="switchView('kbs')">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25"/>
</svg>
Knowledge Bases
</button>
<button class="nav-item" data-view="analytics" onclick="switchView('analytics')">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M3 13.125C3 12.504 3.504 12 4.125 12h2.25c.621 0 1.125.504 1.125 1.125v6.75C7.5 20.496 6.996 21 6.375 21h-2.25A1.125 1.125 0 0 1 3 19.875v-6.75ZM9.75 8.625c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125v11.25c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V8.625ZM16.5 4.125c0-.621.504-1.125 1.125-1.125h2.25C20.496 3 21 3.504 21 4.125v15.75c0 .621-.504 1.125-1.125 1.125h-2.25a1.125 1.125 0 0 1-1.125-1.125V4.125Z"/>
</svg>
Analytics
</button>
</nav>
<!-- KB quick-list -->
<div class="sidebar-section-title">Knowledge Bases</div>
<div id="sidebar-kb-list">
<div class="sidebar-kb-empty">No KBs yet</div>
</div>
<div style="flex:1"></div>
<!-- Footer -->
<div class="sidebar-footer">
<span class="version-badge">v0.1.0</span>
<div class="status-dot" title="Server online"></div>
</div>
</aside>
<!-- ─── Main Content ──────────────────────────────────── -->
<main class="main">
<!-- ═══ Ask View ═══════════════════════════════════════ -->
<div class="view" id="view-ask">
<div class="view-header">
<div>
<div class="view-title">Ask VoiceVault</div>
<div class="view-subtitle">Speak or type β€” get cited answers from your documents</div>
</div>
<button class="btn btn-ghost" onclick="clearChat()" title="Clear conversation">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"/>
</svg>
Clear
</button>
</div>
<!-- Chat messages -->
<div class="chat-area" id="chat-area">
<div class="chat-empty" id="chat-empty">
<div class="chat-empty-icon">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 18.75a6 6 0 0 0 6-6v-1.5m-6 7.5a6 6 0 0 1-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 0 1-3-3V4.5a3 3 0 1 1 6 0v8.25a3 3 0 0 1-3 3Z"/>
</svg>
</div>
<h3>Ready to answer</h3>
<p>Record your voice or type a question below, then select a Knowledge Base.</p>
</div>
</div>
<!-- Input area -->
<div class="input-area">
<!-- KB chip selector -->
<div class="kb-selector-wrap">
<span class="kb-selector-label">KBs:</span>
<div class="kb-chips" id="kb-chips">
<span class="kb-chips-empty">No knowledge bases β€” create one first</span>
</div>
</div>
<!-- Text + controls -->
<div class="input-row">
<button class="mic-btn" id="mic-btn" onclick="toggleRecording()" title="Hold to record">
<!-- Mic icon (default) -->
<svg id="mic-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 18.75a6 6 0 0 0 6-6v-1.5m-6 7.5a6 6 0 0 1-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 0 1-3-3V4.5a3 3 0 1 1 6 0v8.25a3 3 0 0 1-3 3Z"/>
</svg>
<!-- Stop icon (while recording) -->
<svg id="stop-icon" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" style="display:none">
<rect x="6" y="6" width="12" height="12" rx="2"/>
</svg>
</button>
<div class="input-field-wrap">
<textarea
id="query-input"
placeholder="Speak using the mic, or type your question here…"
rows="1"
onkeydown="handleInputKey(event)"
oninput="autoResize(this)"
></textarea>
</div>
<div class="input-actions">
<button class="tts-btn" id="tts-btn" onclick="speakLastAnswer()" title="Read answer aloud">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M19.114 5.636a9 9 0 0 1 0 12.728M16.463 8.288a5.25 5.25 0 0 1 0 7.424M6.75 8.25l4.72-4.72a.75.75 0 0 1 1.28.53v15.88a.75.75 0 0 1-1.28.53l-4.72-4.72H4.51c-.88 0-1.704-.507-1.938-1.354A9.009 9.009 0 0 1 2.25 12c0-.83.112-1.633.322-2.396C2.806 8.756 3.63 8.25 4.51 8.25H6.75Z"/>
</svg>
Speak
</button>
<button class="send-btn" id="send-btn" onclick="sendQuery()" title="Send question">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5"/>
</svg>
</button>
</div>
</div>
<!-- Status indicators -->
<div class="recording-status" id="recording-status">
<div class="rec-dot"></div>
<div class="waveform show">
<div class="wave-bar"></div><div class="wave-bar"></div>
<div class="wave-bar"></div><div class="wave-bar"></div>
<div class="wave-bar"></div>
</div>
<span>Recording… click Stop when done</span>
</div>
<div class="transcribing-status" id="transcribing-status">
<div class="transcribing-spinner"></div>
<span>Transcribing with Whisper…</span>
</div>
</div>
</div>
<!-- ═══ Knowledge Bases View ════════════════════════════ -->
<div class="view hidden" id="view-kbs">
<div class="view-header">
<div>
<div class="view-title">Knowledge Bases</div>
<div class="view-subtitle">Create and manage document collections</div>
</div>
<button class="btn btn-primary" onclick="openCreateKBModal()">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>
</svg>
New Knowledge Base
</button>
</div>
<div class="kbs-content">
<div class="kb-grid" id="kb-grid">
<div class="kb-empty-state" id="kb-empty">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25"/>
</svg>
<h3>No knowledge bases yet</h3>
<p>Create your first KB and upload documents to get started.</p>
<button class="btn btn-primary" style="margin-top:8px" onclick="openCreateKBModal()">
Create Knowledge Base
</button>
</div>
</div>
</div>
</div>
<!-- ═══ Analytics View ══════════════════════════════════ -->
<div class="view hidden" id="view-analytics">
<div class="view-header">
<div>
<div class="view-title">Analytics</div>
<div class="view-subtitle">Query statistics β€” last 7 days</div>
</div>
<button class="btn btn-secondary" onclick="loadAnalytics()">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"/>
</svg>
Refresh
</button>
</div>
<div class="analytics-content">
<div class="stats-grid" id="stats-grid">
<div class="stat-card">
<div class="stat-card-icon">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z"/>
</svg>
</div>
<div class="stat-card-value" id="stat-total-queries">β€”</div>
<div class="stat-card-label">Total Queries (7d)</div>
</div>
<div class="stat-card">
<div class="stat-card-icon">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"/>
</svg>
</div>
<div class="stat-card-value" id="stat-avg-latency">β€”</div>
<div class="stat-card-label">Avg Latency (ms)</div>
</div>
<div class="stat-card">
<div class="stat-card-icon">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/>
</svg>
</div>
<div class="stat-card-value" id="stat-avg-citations">β€”</div>
<div class="stat-card-label">Avg Citations / Answer</div>
</div>
<div class="stat-card">
<div class="stat-card-icon">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25"/>
</svg>
</div>
<div class="stat-card-value" id="stat-kb-count">β€”</div>
<div class="stat-card-label">Knowledge Bases</div>
</div>
</div>
<!-- Daily bar chart -->
<div class="section-title">Queries by Day</div>
<div class="day-chart" id="day-chart">
<div style="color:var(--text-3);font-size:12px;width:100%;text-align:center">Click Refresh to load data</div>
</div>
<!-- KB inventory -->
<div class="section-title">Knowledge Base Inventory</div>
<table class="analytics-table" id="kb-inventory-table">
<thead>
<tr>
<th>Name</th>
<th>Slug</th>
<th>Documents</th>
<th>Chunks</th>
</tr>
</thead>
<tbody id="kb-inventory-body">
<tr><td colspan="4" style="color:var(--text-3);text-align:center">Click Refresh to load data</td></tr>
</tbody>
</table>
</div>
</div>
</main>
</div>
<!-- ─── Modals ─────────────────────────────────────────── -->
<div id="modal-overlay" class="modal-overlay hidden" onclick="handleOverlayClick(event)">
<!-- Create KB Modal -->
<div id="modal-create-kb" class="modal hidden">
<div class="modal-header">
<div class="modal-title">New Knowledge Base</div>
<button class="modal-close" onclick="closeModal()">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div class="form-group">
<label class="form-label" for="new-kb-slug">Slug (ID) <span class="optional">required</span></label>
<input class="form-input" id="new-kb-slug" type="text" placeholder="e.g. research-papers"
oninput="slugifyInput(this)">
<div class="form-hint">Lowercase letters, digits, hyphens only. 1–64 chars. Cannot start/end with hyphen.</div>
</div>
<div class="form-group">
<label class="form-label" for="new-kb-name">Display Name <span class="optional">required</span></label>
<input class="form-input" id="new-kb-name" type="text" placeholder="e.g. Research Papers">
</div>
<div class="form-group">
<label class="form-label" for="new-kb-pass">Password <span class="optional">optional</span></label>
<input class="form-input" id="new-kb-pass" type="password" placeholder="Leave blank for public KB">
<div class="form-hint">If set, a password will be required to upload documents.</div>
</div>
<div class="modal-actions">
<button class="btn btn-ghost" onclick="closeModal()">Cancel</button>
<button class="btn btn-primary" id="create-kb-btn" onclick="createKB()">
Create Knowledge Base
</button>
</div>
</div>
<!-- Upload Documents Modal -->
<div id="modal-upload" class="modal hidden">
<div class="modal-header">
<div class="modal-title">Upload Documents</div>
<button class="modal-close" onclick="closeModal()">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div class="form-group">
<label class="form-label">Knowledge Base</label>
<input class="form-input" id="upload-kb-name" type="text" readonly style="opacity:0.6">
</div>
<div class="form-group">
<label class="form-label" for="upload-pass">Password <span class="optional">if protected</span></label>
<input class="form-input" id="upload-pass" type="password" placeholder="Leave blank for public KBs">
</div>
<div class="form-group">
<div class="file-drop-zone" id="file-drop-zone"
ondrop="handleDrop(event)" ondragover="handleDragOver(event)" ondragleave="handleDragLeave(event)">
<input type="file" id="file-input" multiple
accept=".pdf,.docx,.doc,.txt,.md,.html"
onchange="handleFileSelect(event)">
<div class="file-drop-icon">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 16.5V9.75m0 0 3 3m-3-3-3 3M6.75 19.5a4.5 4.5 0 0 1-1.41-8.775 5.25 5.25 0 0 1 10.338-2.32 3.75 3.75 0 0 1 3.832 3.849 4.5 4.5 0 0 1-1.41 8.775H6.75Z"/>
</svg>
</div>
<div class="file-drop-text">Drop files here or click to browse</div>
<div class="file-drop-hint">PDF, DOCX, TXT, MD, HTML</div>
<div class="file-list" id="file-list"></div>
</div>
</div>
<div class="upload-progress" id="upload-progress"></div>
<div class="modal-actions">
<button class="btn btn-ghost" onclick="closeModal()">Cancel</button>
<button class="btn btn-primary" id="upload-btn" onclick="uploadDocuments()">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5"/>
</svg>
Upload & Index
</button>
</div>
</div>
</div>
<!-- Toast container -->
<div id="toasts"></div>
<script src="/static/app.js"></script>
</body>
</html>