- Aussprache-Lexikon (de/en) komplett im Browser editierbar: Einträge hinzufügen/löschen in Sektionen Abkürzungen/Einheiten/Begriffe; LRU-Cache wird nach jedem Schreibvorgang automatisch geleert. - Live-Log-Tab: WebSocket auf /api/admin/log streamt journalctl des voice-assistant.service live im Terminal-Style ins Admin-Panel. - DB-Export: SQLite-Datenbank über /api/admin/db-export herunterladen. - Metriken-Tab: CSS-Balkendiagramm (Anfragen je Nutzer) ergänzt. - Dokumentation: §7.5 Admin-Web-Panel, B.5 API-Endpunkte, Sachregister. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
207 lines
13 KiB
HTML
207 lines
13 KiB
HTML
<!doctype html>
|
|
<html lang="de" class="h-full">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
<title>Voice Assistant</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script>tailwind.config = { darkMode: "class" };</script>
|
|
<script>
|
|
(function () {
|
|
try {
|
|
var t = localStorage.getItem("theme");
|
|
if (t === "dark" || (!t && matchMedia("(prefers-color-scheme: dark)").matches))
|
|
document.documentElement.classList.add("dark");
|
|
} catch (e) {}
|
|
})();
|
|
</script>
|
|
</head>
|
|
<body class="h-[100dvh] flex flex-col bg-slate-50 text-slate-800 dark:bg-slate-900 dark:text-slate-100">
|
|
|
|
<!-- ═══════════════════ ADMIN PANEL OVERLAY ═══════════════════ -->
|
|
<div id="admin-panel" class="hidden fixed inset-0 z-50 flex flex-col bg-slate-50 dark:bg-slate-900">
|
|
|
|
<!-- Admin Header -->
|
|
<header class="shrink-0 h-14 flex items-center gap-3 px-4 border-b border-slate-200 dark:border-slate-700 bg-white/90 dark:bg-slate-800/90 backdrop-blur">
|
|
<span class="text-lg">⚙️</span>
|
|
<h2 class="flex-1 font-semibold text-base">Admin-Bereich</h2>
|
|
<button id="admin-close" title="Schließen"
|
|
class="h-9 w-9 grid place-items-center rounded-lg text-slate-500 hover:bg-slate-100 dark:hover:bg-slate-700 text-xl leading-none transition-colors">✕</button>
|
|
</header>
|
|
|
|
<!-- Tab Navigation -->
|
|
<nav class="shrink-0 flex border-b border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 overflow-x-auto">
|
|
<button class="admin-tab px-4 py-3 text-sm font-medium whitespace-nowrap border-b-2 border-blue-600 text-blue-600 dark:border-blue-400 dark:text-blue-400" data-tab="users">👥 Nutzer</button>
|
|
<button class="admin-tab px-4 py-3 text-sm font-medium whitespace-nowrap border-b-2 border-transparent text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 transition-colors" data-tab="chat">💬 Gespräche</button>
|
|
<button class="admin-tab px-4 py-3 text-sm font-medium whitespace-nowrap border-b-2 border-transparent text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 transition-colors" data-tab="emergency">🚨 Notfälle</button>
|
|
<button class="admin-tab px-4 py-3 text-sm font-medium whitespace-nowrap border-b-2 border-transparent text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 transition-colors" data-tab="status">📡 Status</button>
|
|
<button class="admin-tab px-4 py-3 text-sm font-medium whitespace-nowrap border-b-2 border-transparent text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 transition-colors" data-tab="metrics">📊 Metriken</button>
|
|
<button class="admin-tab px-4 py-3 text-sm font-medium whitespace-nowrap border-b-2 border-transparent text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 transition-colors" data-tab="words">🔤 Wörterbuch</button>
|
|
<button class="admin-tab px-4 py-3 text-sm font-medium whitespace-nowrap border-b-2 border-transparent text-slate-500 hover:text-slate-700 dark:text-slate-400 dark:hover:text-slate-200 transition-colors" data-tab="log">📋 Log</button>
|
|
</nav>
|
|
|
|
<!-- Tab Content Container -->
|
|
<div class="flex-1 overflow-hidden">
|
|
|
|
<!-- ── Tab: Nutzer ── -->
|
|
<div id="admin-tab-users" class="h-full overflow-y-auto p-4">
|
|
<div class="max-w-3xl mx-auto space-y-4">
|
|
|
|
<!-- Nutzer anlegen -->
|
|
<div class="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 p-4">
|
|
<h3 class="text-sm font-medium text-slate-700 dark:text-slate-300 mb-3">Nutzer anlegen</h3>
|
|
<form id="create-user-form" class="flex gap-2">
|
|
<input id="new-user-name" type="text" placeholder="Anzeigename" required
|
|
class="flex-1 rounded-lg border border-slate-300 dark:border-slate-600 bg-transparent px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
|
<button type="submit"
|
|
class="rounded-lg bg-blue-600 hover:bg-blue-700 text-white text-sm px-4 py-2 font-medium transition-colors">Anlegen</button>
|
|
</form>
|
|
<div id="new-token-box" class="hidden mt-3 rounded-lg bg-emerald-50 dark:bg-emerald-900/30 border border-emerald-200 dark:border-emerald-800 p-3">
|
|
<p class="text-xs text-emerald-700 dark:text-emerald-400 mb-1 font-medium">Token (nur einmal sichtbar — jetzt kopieren!):</p>
|
|
<code id="new-token-value" class="text-xs font-mono break-all text-emerald-800 dark:text-emerald-200 select-all"></code>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Nutzerliste -->
|
|
<div id="admin-user-cards" class="space-y-3">
|
|
<p class="text-sm text-slate-400 text-center py-4">lade …</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Tab: Gespräche ── -->
|
|
<div id="admin-tab-chat" class="hidden h-full flex overflow-hidden">
|
|
<!-- Linke Spalte: Nutzerliste -->
|
|
<div class="w-48 md:w-60 shrink-0 border-r border-slate-200 dark:border-slate-700 flex flex-col">
|
|
<div class="px-3 py-2.5 border-b border-slate-200 dark:border-slate-700 bg-slate-100/60 dark:bg-slate-800/60">
|
|
<p class="text-xs font-semibold text-slate-500 dark:text-slate-400 uppercase tracking-wide">Nutzer</p>
|
|
</div>
|
|
<ul id="chat-user-list" class="flex-1 overflow-y-auto divide-y divide-slate-100 dark:divide-slate-800/60"></ul>
|
|
</div>
|
|
<!-- Rechte Spalte: Sessions & Transkript -->
|
|
<div id="chat-right-panel" class="flex-1 overflow-y-auto">
|
|
<p class="p-6 text-sm text-slate-400">← Nutzer auswählen</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Tab: Notfälle ── -->
|
|
<div id="admin-tab-emergency" class="hidden h-full overflow-y-auto p-4">
|
|
<div class="max-w-4xl mx-auto">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="font-semibold text-base">Notfall-Ereignisse</h3>
|
|
<button id="load-emergency"
|
|
class="rounded-lg border border-slate-300 dark:border-slate-600 text-sm px-3 py-1.5 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">Aktualisieren</button>
|
|
</div>
|
|
<div id="emergency-content">
|
|
<p class="text-sm text-slate-400">lade …</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Tab: Status ── -->
|
|
<div id="admin-tab-status" class="hidden h-full overflow-y-auto p-4">
|
|
<div class="max-w-3xl mx-auto">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="font-semibold text-base">System-Status</h3>
|
|
<button id="load-status"
|
|
class="rounded-lg border border-slate-300 dark:border-slate-600 text-sm px-3 py-1.5 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">Aktualisieren</button>
|
|
</div>
|
|
<div id="status-content" class="space-y-4">
|
|
<p class="text-sm text-slate-400">lade …</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Tab: Metriken ── -->
|
|
<div id="admin-tab-metrics" class="hidden h-full overflow-y-auto p-4">
|
|
<div class="max-w-3xl mx-auto">
|
|
<div class="flex items-center justify-between mb-4">
|
|
<h3 class="font-semibold text-base">Nutzungsstatistik</h3>
|
|
<button id="load-metrics"
|
|
class="rounded-lg border border-slate-300 dark:border-slate-600 text-sm px-3 py-1.5 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">Aktualisieren</button>
|
|
</div>
|
|
<div id="metrics-content">
|
|
<p class="text-sm text-slate-400">lade …</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Tab: Wörterbuch ── -->
|
|
<div id="admin-tab-words" class="hidden h-full overflow-y-auto p-4">
|
|
<div class="max-w-3xl mx-auto">
|
|
<div class="flex items-center justify-between mb-4 flex-wrap gap-2">
|
|
<h3 class="font-semibold text-base">Aussprache-Lexikon</h3>
|
|
<div class="flex items-center gap-2">
|
|
<select id="words-lang"
|
|
class="text-sm rounded-lg border border-slate-300 dark:border-slate-600 bg-transparent px-2 py-1.5">
|
|
<option value="de">Deutsch (de)</option>
|
|
<option value="en">Englisch (en)</option>
|
|
</select>
|
|
<button id="load-words"
|
|
class="rounded-lg border border-slate-300 dark:border-slate-600 text-sm px-3 py-1.5 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">Aktualisieren</button>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-slate-400 mb-4">Wirkt nur bei lokalem TTS (Piper, Stufe „full"). <strong>Abkürzungen</strong> ersetzen ganze Token. <strong>Einheiten</strong> wirken nur direkt nach einer Zahl. <strong>Begriffe</strong> für Eigennamen, Fachwörter, Aussprache-Korrekturen.</p>
|
|
<div id="words-content" class="space-y-6">
|
|
<p class="text-sm text-slate-400">lade …</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Tab: Log ── -->
|
|
<div id="admin-tab-log" class="hidden h-full flex flex-col">
|
|
<div class="shrink-0 flex items-center gap-3 px-4 py-2.5 border-b border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/60">
|
|
<button id="log-connect"
|
|
class="rounded-lg bg-emerald-600 hover:bg-emerald-700 text-white text-xs px-3 py-1.5 font-medium transition-colors">▶ Verbinden</button>
|
|
<button id="log-disconnect" disabled
|
|
class="rounded-lg border border-slate-300 dark:border-slate-600 text-xs px-3 py-1.5 opacity-40 transition-colors">■ Trennen</button>
|
|
<span id="log-status" class="text-xs text-slate-400">getrennt</span>
|
|
<button id="log-clear" class="ml-auto text-xs text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 transition-colors">Leeren</button>
|
|
</div>
|
|
<div id="log-output"
|
|
class="flex-1 overflow-y-auto bg-slate-950 text-emerald-300 font-mono text-xs p-4 leading-relaxed whitespace-pre-wrap">
|
|
<span class="text-slate-500">— Verbinden um Log zu starten —</span>
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- /Tab Content Container -->
|
|
</div><!-- /Admin Panel -->
|
|
|
|
|
|
<!-- ═══════════════════ CHAT INTERFACE ═══════════════════ -->
|
|
<header class="shrink-0 h-14 flex items-center gap-3 px-4 border-b border-slate-200 dark:border-slate-700 bg-white/80 dark:bg-slate-800/80 backdrop-blur">
|
|
<h1 class="flex-1 truncate font-semibold text-base">Voice Assistant</h1>
|
|
<select id="tts" title="Stimme"
|
|
class="text-sm rounded-lg border border-slate-300 dark:border-slate-600 bg-transparent px-2 py-1.5">
|
|
<option value="">Schnell</option>
|
|
<option value="chatterbox">Hohe Qualität</option>
|
|
<option value="openrouter">Cloud</option>
|
|
</select>
|
|
<button id="admin-toggle" title="Admin"
|
|
class="hidden h-9 w-9 grid place-items-center rounded-lg border border-slate-300 dark:border-slate-600 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">⚙️</button>
|
|
<button id="theme-toggle" title="Tag/Nacht"
|
|
class="h-9 w-9 grid place-items-center rounded-lg border border-slate-300 dark:border-slate-600 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors">🌙</button>
|
|
</header>
|
|
|
|
<div class="shrink-0 flex items-center justify-between px-4 py-1 text-xs text-slate-500 dark:text-slate-400 border-b border-slate-200 dark:border-slate-700">
|
|
<span id="identity">lade …</span>
|
|
<a id="logout" class="hidden hover:underline" href="#">Abmelden</a>
|
|
</div>
|
|
|
|
<main id="messages" class="flex-1 overflow-y-auto px-4 py-4 space-y-3 w-full max-w-3xl mx-auto"></main>
|
|
|
|
<footer class="shrink-0 border-t border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 px-3 pt-3 pb-[calc(env(safe-area-inset-bottom)+0.6rem)]">
|
|
<form id="prompt-form" class="flex items-center gap-2 w-full max-w-3xl mx-auto">
|
|
<button type="button" id="mic" title="Mikrofon"
|
|
class="shrink-0 h-11 w-11 grid place-items-center rounded-full bg-emerald-600 text-white text-lg">🎤</button>
|
|
<input id="prompt" type="text" autocomplete="off" placeholder="Nachricht …"
|
|
class="flex-1 rounded-xl border border-slate-300 dark:border-slate-600 bg-transparent px-4 py-2.5 focus:outline-none focus:ring-2 focus:ring-blue-500" />
|
|
<button type="submit" id="send"
|
|
class="shrink-0 rounded-xl bg-blue-600 hover:bg-blue-700 text-white px-4 py-2.5 font-medium transition-colors">Senden</button>
|
|
</form>
|
|
<div id="status" class="w-full max-w-3xl mx-auto mt-1 min-h-[1rem] text-xs text-slate-500 dark:text-slate-400"></div>
|
|
</footer>
|
|
|
|
<script src="/app.js?v=9"></script>
|
|
</body>
|
|
</html>
|