feat(diarization-ui): redesign modern app-like UI and add PWA manifest/service worker/icon

This commit is contained in:
2026-03-21 14:30:15 +01:00
parent be416b7766
commit 3215dde312

89
app.py
View File

@@ -6,7 +6,7 @@ from typing import Optional
import requests import requests
from fastapi import FastAPI, File, Form, HTTPException, UploadFile from fastapi import FastAPI, File, Form, HTTPException, UploadFile
from fastapi.responses import HTMLResponse, PlainTextResponse from fastapi.responses import HTMLResponse, PlainTextResponse, Response
API_BASE = os.getenv("API_BASE", "http://gx10.aquantico.lan:8093").rstrip("/") API_BASE = os.getenv("API_BASE", "http://gx10.aquantico.lan:8093").rstrip("/")
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://gx10.aquantico.lan:11434").rstrip("/") OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://gx10.aquantico.lan:11434").rstrip("/")
@@ -93,29 +93,46 @@ def init_db():
def layout(title: str, body: str) -> str: def layout(title: str, body: str) -> str:
return f""" return f"""
<!doctype html> <!doctype html>
<html><head><meta charset='utf-8'><meta name='viewport' content='width=device-width, initial-scale=1'> <html>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1, viewport-fit=cover'>
<meta name='theme-color' content='#0f172a'>
<link rel='manifest' href='/manifest.webmanifest'>
<link rel='icon' href='/icon.svg' type='image/svg+xml'>
<title>{title}</title> <title>{title}</title>
<style> <style>
body{{font-family:Arial;margin:0;display:flex;min-height:100vh}} :root{{--bg:#0b1020;--bg2:#111827;--card:#0f172a;--txt:#e5e7eb;--muted:#94a3b8;--acc:#22d3ee;--acc2:#38bdf8;--ok:#34d399;--border:#1f2937}}
nav{{width:240px;background:#111;color:#fff;padding:16px}} *{{box-sizing:border-box}}
nav a{{display:block;color:#fff;text-decoration:none;padding:8px 10px;border-radius:6px;margin:4px 0}} body{{font-family:Inter,system-ui,Arial;margin:0;background:linear-gradient(180deg,var(--bg),#020617);color:var(--txt);display:flex;min-height:100vh}}
nav a:hover{{background:#2a2a2a}} nav{{width:250px;background:rgba(15,23,42,.92);backdrop-filter:blur(8px);border-right:1px solid var(--border);padding:16px;position:sticky;top:0;height:100vh}}
main{{flex:1;padding:20px;max-width:1200px}} .brand{{font-weight:700;letter-spacing:.3px;margin:0 0 12px 0}}
.card{{border:1px solid #ddd;border-radius:8px;padding:12px;margin:10px 0}} nav a{{display:flex;gap:10px;align-items:center;color:var(--txt);text-decoration:none;padding:10px 12px;border-radius:12px;margin:6px 0;border:1px solid transparent}}
input,select,textarea,button{{padding:8px;font-size:14px}} nav a:hover{{background:#0b1222;border-color:#243244}}
textarea{{width:100%;min-height:140px}} main{{flex:1;padding:18px;max-width:1200px}}
pre{{white-space:pre-wrap;background:#111;color:#0f0;padding:10px;border-radius:8px}} .card{{background:rgba(15,23,42,.88);border:1px solid var(--border);border-radius:16px;padding:14px;margin:10px 0;box-shadow:0 8px 30px rgba(0,0,0,.25)}}
input,select,textarea,button{{padding:10px 12px;font-size:14px;border-radius:12px;border:1px solid #334155;background:#0b1222;color:var(--txt)}}
button{{background:linear-gradient(90deg,var(--acc),var(--acc2));color:#001018;border:none;font-weight:700}}
button:hover{{filter:brightness(1.05)}}
textarea{{width:100%;min-height:150px}}
pre{{white-space:pre-wrap;background:#020617;color:#86efac;padding:12px;border-radius:12px;border:1px solid #1e293b}}
.row{{display:flex;gap:8px;flex-wrap:wrap;align-items:center}} .row{{display:flex;gap:8px;flex-wrap:wrap;align-items:center}}
small{{color:#666}} small{{color:var(--muted)}}
</style></head> .hint{{color:var(--muted);font-size:13px}}
@media (max-width:900px){{nav{{width:86px;padding:10px}} nav a span{{display:none}} .brand{{font-size:12px}} main{{padding:12px}}}}
</style>
<script>
if ('serviceWorker' in navigator) {{ window.addEventListener('load', () => navigator.serviceWorker.register('/sw.js').catch(()=>{{}})); }}
</script>
</head>
<body> <body>
<nav> <nav>
<h3>Menü</h3> <div class='brand'>🎙️ Audio Copilot</div>
<a href='/'>Upload</a> <a href='/'><span>⤴️</span><span>Upload</span></a>
<a href='/library'>Datenbank</a> <a href='/library'><span>🗂️</span><span>Datenbank</span></a>
<a href='/prompts'>Prompt-Konfig</a> <a href='/prompts'><span>🧩</span><span>Prompts</span></a>
<a href='/run'>Prompt ausführen</a> <a href='/run'><span>🤖</span><span>Prompt ausführen</span></a>
<a href='/healthz'>Health</a> <a href='/healthz'><span>💚</span><span>Health</span></a>
</nav> </nav>
<main>{body}</main> <main>{body}</main>
</body></html> </body></html>
@@ -143,6 +160,40 @@ def startup():
init_db() init_db()
@app.get("/manifest.webmanifest")
def manifest():
return {
"name": "Audio Copilot",
"short_name": "Copilot",
"start_url": "/",
"display": "standalone",
"background_color": "#020617",
"theme_color": "#0f172a",
"icons": [
{"src": "/icon.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "any maskable"}
],
}
@app.get("/icon.svg")
def icon_svg():
svg = """<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 128 128'><rect rx='24' width='128' height='128' fill='#0f172a'/><text x='64' y='82' font-size='72' text-anchor='middle'>🎙️</text></svg>"""
return Response(content=svg, media_type="image/svg+xml")
@app.get("/sw.js")
def sw_js():
js = """
self.addEventListener('install', (event) => { self.skipWaiting(); });
self.addEventListener('activate', (event) => { event.waitUntil(clients.claim()); });
self.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET') return;
event.respondWith(fetch(event.request).catch(() => new Response('offline', {status: 503})));
});
"""
return Response(content=js, media_type="application/javascript")
@app.get("/healthz") @app.get("/healthz")
def healthz(): def healthz():
return {"ok": True, "api_base": API_BASE, "ollama_base_url": OLLAMA_BASE_URL, "ollama_model": OLLAMA_MODEL, "db_path": DB_PATH} return {"ok": True, "api_base": API_BASE, "ollama_base_url": OLLAMA_BASE_URL, "ollama_model": OLLAMA_MODEL, "db_path": DB_PATH}