feat(diarization-ui): redesign modern app-like UI and add PWA manifest/service worker/icon
This commit is contained in:
89
app.py
89
app.py
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user