From 2db89bc33a27796e706a8f2914cca33359d4c941 Mon Sep 17 00:00:00 2001 From: wb Date: Mon, 25 May 2026 12:02:47 +0200 Subject: [PATCH] docs: add project documentation (architecture, API, interfaces, config, deployment) Co-Authored-By: Claude Sonnet 4.6 --- doc/README.md | 11 ++++ doc/api.md | 100 ++++++++++++++++++++++++++++++++++ doc/architecture.md | 124 +++++++++++++++++++++++++++++++++++++++++++ doc/configuration.md | 42 +++++++++++++++ doc/deployment.md | 94 ++++++++++++++++++++++++++++++++ doc/interfaces.md | 104 ++++++++++++++++++++++++++++++++++++ 6 files changed, 475 insertions(+) create mode 100644 doc/README.md create mode 100644 doc/api.md create mode 100644 doc/architecture.md create mode 100644 doc/configuration.md create mode 100644 doc/deployment.md create mode 100644 doc/interfaces.md diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..12a1c3c --- /dev/null +++ b/doc/README.md @@ -0,0 +1,11 @@ +# VoiceLog – Projektdokumentation + +> Zentrales Verzeichnis für Architektur, Schnittstellen und Betrieb. + +| Dokument | Inhalt | +|---|---| +| [architecture.md](architecture.md) | Gesamtarchitektur, Datenmodell, Hintergrundverarbeitung | +| [api.md](api.md) | Alle HTTP-Endpunkte der Anwendung | +| [interfaces.md](interfaces.md) | Externe Schnittstellen (Transcription-API, Ollama) | +| [configuration.md](configuration.md) | Umgebungsvariablen und Laufzeiteinstellungen | +| [deployment.md](deployment.md) | Build, Deploy, Betrieb mit Podman | diff --git a/doc/api.md b/doc/api.md new file mode 100644 index 0000000..1050258 --- /dev/null +++ b/doc/api.md @@ -0,0 +1,100 @@ +# HTTP-API + +Alle Endpunkte werden von `app.py` bereitgestellt. HTML-Seiten geben `text/html` zurück; Daten-Endpunkte geben JSON zurück. + +## PWA / System + +| Methode | Pfad | Beschreibung | +|---|---|---| +| GET | `/` | Upload-Seite | +| GET | `/manifest.webmanifest` | PWA-Manifest | +| GET | `/icon.svg` | App-Icon | +| GET | `/sw.js` | Service Worker | +| GET | `/healthz` | Statuscheck (JSON): API_BASE, OLLAMA_BASE_URL, Modell, DB-Pfad | + +## Einstellungen + +| Methode | Pfad | Beschreibung | +|---|---|---| +| GET | `/settings` | Einstellungsseite | +| POST | `/settings` | Einstellungen speichern | +| GET | `/settings/models` | Verfügbare Ollama-Modelle laden (JSON) | + +### `GET /settings/models` Response +```json +{ "ok": true, "models": ["llama3.2:3b", "qwen3.5:9b", ...] } +{ "ok": false, "error": "...", "models": [] } +``` + +## Projekte + +| Methode | Pfad | Beschreibung | +|---|---|---| +| POST | `/projects` | Projekt anlegen (Form: `name`) | +| POST | `/projects/update` | Projekt umbenennen (Form: `id`, `name`) | +| POST | `/projects/{id}/delete` | Projekt löschen (Dokumente → Default) | +| POST | `/projects/create-api` | Projekt anlegen, gibt JSON zurück (für JS-Upload-Flow) | + +## Upload + +| Methode | Pfad | Beschreibung | +|---|---|---| +| POST | `/upload` | Audio-/Videodatei hochladen (Form: `project_id`, `file`); legt Upload-Job an | + +## Library / Dokumente + +| Methode | Pfad | Beschreibung | +|---|---|---| +| GET | `/library` | Dokumentenliste (Query: `project_id`, `q_title`, `q_content`) | +| GET | `/document/{id}` | Dokument anzeigen | +| GET | `/document/{id}/download.md` | Dokument als Markdown herunterladen | +| POST | `/document/{id}/edit` | Inhalt bearbeiten (Form: `content_md`) | +| POST | `/document/{id}/rename` | Umbenennen (Form: `title`) | +| POST | `/document/{id}/move` | Projekt wechseln (Form: `project_id`) | +| POST | `/document/{id}/delete` | Dokument löschen | +| POST | `/documents/bulk-move` | Mehrere Dokumente verschieben (Form: `ids[]`, `project_id`) | +| POST | `/documents/bulk-delete` | Mehrere Dokumente löschen (Form: `ids[]`) | + +## Prompts & Projekte + +| Methode | Pfad | Beschreibung | +|---|---|---| +| GET | `/prompts` | Prompts- und Projektverwaltung | +| GET | `/prompts/{id}/preview` | Prompt-Vorschau (JSON: `{html}`) | +| POST | `/prompts/add` | Prompt anlegen (Form: `name`, `prompt`) | +| POST | `/prompts/update` | Prompt speichern inkl. KI-Einstellungen (siehe unten) | +| POST | `/prompts/{id}/delete` | Prompt löschen | + +### `POST /prompts/update` Felder +| Feld | Typ | Beschreibung | +|---|---|---| +| `id` | int | Prompt-ID | +| `name` | str | Name | +| `prompt` | str | Prompttext | +| `llm_use_default` | int | 1 = globale KI-Einstellungen, 0 = promptspezifisch | +| `llm_model` | str? | Modellname | +| `llm_think` | str? | `"true"` / `"false"` | +| `llm_num_ctx` | str? | Kontextgröße oder `"auto"` | +| `llm_num_predict` | str? | Max. Tokens | +| `llm_repeat_penalty` | str? | | +| `llm_repeat_last_n` | str? | | + +## Hintergrundjobs + +| Methode | Pfad | Beschreibung | +|---|---|---| +| GET | `/jobs` | Job-Übersicht (HTML) | +| GET | `/jobs/data` | Job-Liste als JSON (Query: `limit=200`) | +| GET | `/jobs/{id}/debug-data` | Detail-Daten eines Jobs (JSON) | +| POST | `/jobs/{id}/cancel` | Job abbrechen (JSON-Response) | +| POST | `/jobs/{id}/cancel-form` | Job abbrechen (Redirect) | +| POST | `/jobs/{id}/delete` | Job löschen (JSON-Response) | +| POST | `/jobs/{id}/delete-form` | Job löschen (Redirect) | + +## Prompt ausführen + +| Methode | Pfad | Beschreibung | +|---|---|---| +| GET | `/run` | Formular: Prompt auf Dokument(e) anwenden | +| POST | `/run` | Analyse-Job(s) starten | +| POST | `/run/project` | Analyse-Job für alle Transkripte eines Projekts starten | diff --git a/doc/architecture.md b/doc/architecture.md new file mode 100644 index 0000000..7dbf990 --- /dev/null +++ b/doc/architecture.md @@ -0,0 +1,124 @@ +# Architektur + +## Überblick + +VoiceLog ist eine Single-File-FastAPI-Anwendung (`app.py`, ~2000 Zeilen). Es gibt keine Templates, kein separates Frontend-Build-System und keine weiteren Module. HTML wird serverseitig als Python-f-Strings erzeugt; Bootstrap 5.3 (CDN) übernimmt das Layout. + +``` +Browser + │ + ▼ +FastAPI (app.py, Port 8094) + ├── SQLite (DB_PATH, Standard: /data/ui.db) + ├── ThreadPoolExecutor (2 Workers) + │ ├── Upload-Job → Transcription-API (API_BASE) + │ └── Analyse-Job → Ollama (OLLAMA_BASE_URL) + └── Dateisystem (JOB_DIR, Standard: /data/jobs/) +``` + +Traefik sitzt als Reverse-Proxy davor und terminiert TLS (`https://voicelog.aquantico.lan`). + +--- + +## Datenmodell (SQLite) + +### `projects` +| Spalte | Typ | Beschreibung | +|---|---|---| +| `id` | INTEGER PK | | +| `name` | TEXT UNIQUE | Projektname | +| `created_at` | TEXT | ISO-8601-UTC | + +### `prompts` +| Spalte | Typ | Beschreibung | +|---|---|---| +| `id` | INTEGER PK | | +| `name` | TEXT UNIQUE | Anzeigename | +| `prompt` | TEXT | Prompttext für das LLM | +| `created_at` | TEXT | | +| `updated_at` | TEXT | | +| `llm_use_default` | INTEGER | 1 = globale KI-Einstellungen nutzen (Standard) | +| `llm_model` | TEXT | Überschreibt globales Modell wenn `llm_use_default=0` | +| `llm_think` | TEXT | `"true"` / `"false"` | +| `llm_num_ctx` | TEXT | Kontextgröße oder `"auto"` | +| `llm_num_predict` | INTEGER | Max. Ausgabe-Tokens | +| `llm_repeat_penalty` | REAL | | +| `llm_repeat_last_n` | INTEGER | | + +### `documents` +| Spalte | Typ | Beschreibung | +|---|---|---| +| `id` | INTEGER PK | | +| `project_id` | INTEGER FK | → projects | +| `kind` | TEXT | `transcript` oder `analysis` | +| `title` | TEXT | | +| `content_md` | TEXT | Inhalt (Markdown) | +| `source_document_id` | INTEGER FK | Bei `analysis`: Quell-Transkript | +| `prompt_id` | INTEGER FK | Bei `analysis`: verwendeter Prompt | +| `raw_json` | TEXT | Rohantwort der externen API als JSON | +| `created_at` | TEXT | | + +### `jobs` +| Spalte | Typ | Beschreibung | +|---|---|---| +| `id` | INTEGER PK | | +| `kind` | TEXT | `upload` oder `analysis` | +| `status` | TEXT | `queued` → `running` → `done` / `error` / `cancelled` | +| `project_id` | INTEGER | | +| `document_id` | INTEGER | Quell-Dokument (bei `analysis`) | +| `prompt_id` | INTEGER | | +| `title` | TEXT | Anzeigename | +| `file_path` | TEXT | Temporärer Pfad der Audiodatei (bei `upload`) | +| `error` | TEXT | Fehlermeldung bei Status `error` | +| `result_document_id` | INTEGER | Erzeugtes Dokument nach `done` | +| `user_prompt` | TEXT | Zusatzinformation vom Nutzer | +| `llm_prompt` | TEXT | Vollständiger LLM-Prompt inkl. `[num_ctx=…]`-Header | +| `llm_response` | TEXT | Rohausgabe des LLM | +| `llm_thinking` | TEXT | Thinking-Chain-of-Thought-Ausgabe | +| `created_at` | TEXT | | +| `started_at` | TEXT | | +| `finished_at` | TEXT | | + +### `settings` +Einfache Key-Value-Tabelle für persistente Laufzeiteinstellungen. + +| Spalte | Typ | +|---|---| +| `key` | TEXT PK | +| `value` | TEXT | + +Bekannte Keys: `ollama_model`, `ollama_think`, `ollama_num_ctx`, `ollama_num_predict`, `ollama_repeat_penalty`, `ollama_repeat_last_n`. + +--- + +## Hintergrundverarbeitung + +Jobs werden über `enqueue_job()` in die DB geschrieben und sofort an einen `ThreadPoolExecutor(max_workers=2)` übergeben. + +### Upload-Job (`_process_upload_job`) +1. Liest temporäre Audiodatei aus `JOB_DIR` +2. Sendet sie als `multipart/form-data` an `{API_BASE}/transcribe-diarize` +3. Speichert `formatted_text` aus der Antwort als `transcript`-Dokument +4. Löscht die temporäre Datei + +### Analyse-Job (`_process_analysis_job`) +1. Lädt Dokument und Prompt aus der DB +2. Prüft `llm_use_default` am Prompt: globale oder promptspezifische KI-Einstellungen +3. Berechnet `num_ctx` dynamisch über `_estimate_num_ctx()` (außer bei manuellem Override) +4. Streamt Anfrage an `{OLLAMA_BASE_URL}/api/generate` +5. Puffert `thinking`- und `response`-Chunks in `_JOB_STREAMS` (für Live-Anzeige) +6. Speichert Ergebnis als `analysis`-Dokument + +Beide Worker prüfen nach jedem externen Call, ob der Job zwischenzeitlich auf `cancelled` gesetzt wurde. + +--- + +## Datenbankmigrationen + +Werden inline in `init_db()` mit `ALTER TABLE … ADD COLUMN` in `try/except`-Blöcken ausgeführt. Die DB wird beim Start automatisch angelegt und migriert. + +--- + +## PWA + +Die App liefert `/manifest.webmanifest`, `/icon.svg` und `/sw.js` direkt aus Route-Handlern aus und ist damit als Progressive Web App installierbar. diff --git a/doc/configuration.md b/doc/configuration.md new file mode 100644 index 0000000..fb54a10 --- /dev/null +++ b/doc/configuration.md @@ -0,0 +1,42 @@ +# Konfiguration + +## Umgebungsvariablen + +Werden beim Start ausgewertet und dienen als Fallback-Werte, wenn keine DB-Einstellung vorhanden ist. + +| Variable | Standard | Beschreibung | +|---|---|---| +| `API_BASE` | `http://gx10.aquantico.lan:8093` | URL der Transcription-API | +| `OLLAMA_BASE_URL` | `http://gx10.aquantico.lan:11434` | URL der Ollama-Instanz | +| `OLLAMA_MODEL` | `qwen3.5:9b` | Standard-LLM-Modell (Fallback) | +| `OLLAMA_NUM_PREDICT` | `16384` | Max. Ausgabe-Tokens (Fallback) | +| `OLLAMA_THINK` | `true` | Extended Thinking aktivieren (Fallback) | +| `DB_PATH` | `/data/ui.db` | Pfad zur SQLite-Datenbank | +| `JOB_DIR` | `/data/jobs` | Verzeichnis für temporäre Upload-Dateien | + +## Laufzeiteinstellungen (DB) + +Überschreiben die Umgebungsvariablen. Werden über die Einstellungsseite (`/settings`) gesetzt und in der `settings`-Tabelle gespeichert. + +| Key | Beschreibung | Mögliche Werte | +|---|---|---| +| `ollama_model` | Aktives LLM-Modell | z.B. `qwen3.5:9b`, `llama3.2:3b` | +| `ollama_think` | Extended Thinking | `true` / `false` | +| `ollama_num_ctx` | Kontextfenstergröße | `auto`, `4096`, `8192`, `16384`, `32768`, `65536`, `131072` | +| `ollama_num_predict` | Max. Ausgabe-Tokens | Integer, z.B. `16384` | +| `ollama_repeat_penalty` | Wiederholungsstrafe | Float, z.B. `1.15` | +| `ollama_repeat_last_n` | Repeat-Fenster (Tokens) | Integer, z.B. `128` | + +## Promptspezifische KI-Einstellungen + +Jeder Prompt kann eigene LLM-Parameter haben (Spalten `llm_*` in der `prompts`-Tabelle). Solange `llm_use_default=1` (Standard), werden die globalen DB-Einstellungen verwendet. + +## `num_ctx`-Automatik + +Bei `ollama_num_ctx = "auto"` (Standard) wird die Kontextgröße dynamisch berechnet: + +```python +needed = len(prompt) // 3 + 2048 # grobe Token-Schätzung + Antwortpuffer +# wählt kleinstes passendes aus: 4096, 8192, 16384, 32768, 65536 +# Fallback: 65536 +``` diff --git a/doc/deployment.md b/doc/deployment.md new file mode 100644 index 0000000..a6ff991 --- /dev/null +++ b/doc/deployment.md @@ -0,0 +1,94 @@ +# Deployment + +## Voraussetzungen + +- Podman + podman-compose +- Externes Podman-Netzwerk `traefik` (Traefik-Instanz läuft im selben Netzwerk) +- Traefik konfiguriert für Entrypoint `websecure` (HTTPS) + +## Erstkonfiguration + +```bash +cp .env.example .env # Umgebungsvariablen anpassen +``` + +## Build & Start + +```bash +sudo podman compose up -d --build +``` + +Die App ist danach erreichbar unter: +- `https://voicelog.aquantico.lan` (via Traefik) +- `http://127.0.0.1:8094` (direkt) + +## Neustart nach Code-Änderungen + +```bash +sudo podman rm -f diarization-ui +sudo podman compose up -d --build +``` + +## Image in Registry publizieren + +```bash +podman build -t registry.aquantico.lan/claw/diarization-ui:latest . +podman push registry.aquantico.lan/claw/diarization-ui:latest +``` + +## Datenpersistenz + +Alle persistenten Daten liegen im Podman-Volume `diarization-ui_diarization_ui_data`, gemountet unter `/data` im Container: + +``` +/data/ui.db — SQLite-Datenbank +/data/jobs/ — Temporäre Upload-Dateien (werden nach Verarbeitung gelöscht) +``` + +> **Achtung:** Beim Neuerstellen des Containers kein `podman volume rm` ausführen — sonst gehen alle Daten verloren. Nur `podman rm -f diarization-ui` reicht. + +## Daten-Backup + +```bash +sudo podman cp diarization-ui:/data/ui.db ./ui.db.backup +``` + +## Volumes prüfen + +```bash +sudo podman volume ls | grep diarization +``` + +Das korrekte Volume heißt `diarization-ui_diarization_ui_data` (nicht `voicelog_diarization_ui_data`, das ist ein veraltetes Volume aus einer früheren Benennung). + +## Logs + +```bash +sudo podman logs -f diarization-ui +``` + +## docker-compose.yml Struktur + +```yaml +services: + diarization-ui: + networks: [traefik] + labels: + - traefik.enable=true + - traefik.http.routers.openwebui.rule=Host(`voicelog.aquantico.lan`) + - traefik.http.routers.openwebui.entrypoints=websecure + - traefik.http.routers.openwebui.tls=true + - traefik.http.services.openwebui.loadbalancer.server.port=8094 + +networks: + traefik: + external: true +``` + +## Lokale Entwicklung (ohne Docker/Podman) + +```bash +pip install -r requirements.txt +API_BASE=http://... OLLAMA_BASE_URL=http://... DB_PATH=./ui.db \ + uvicorn app:app --host 0.0.0.0 --port 8094 --reload +``` diff --git a/doc/interfaces.md b/doc/interfaces.md new file mode 100644 index 0000000..e45f7f8 --- /dev/null +++ b/doc/interfaces.md @@ -0,0 +1,104 @@ +# Externe Schnittstellen + +## Transcription-API (`API_BASE`) + +Wird für Audio-/Videotranskription mit Diarisierung verwendet. + +**Standard-URL:** `http://gx10.aquantico.lan:8093` + +### `POST /transcribe-diarize` + +Upload der Mediendatei als `multipart/form-data`. + +**Request:** +``` +Content-Type: multipart/form-data +field: file (binary, application/octet-stream) +``` + +Unterstützte Formate: alle gängigen Audio- und Videoformate (MP3, WAV, M4A, OGG, MP4, MKV, MOV, …). Die API extrahiert den Audiotrack selbst. + +**Response (JSON):** +```json +{ + "formatted_text": "Speaker 1: Hallo...\nSpeaker 2: ...", + ... +} +``` + +Das Feld `formatted_text` wird als Markdown-Inhalt des `transcript`-Dokuments gespeichert. Die vollständige JSON-Antwort landet in `documents.raw_json`. + +**Timeout:** 1800 s (30 Minuten, für lange Aufnahmen) + +--- + +## Ollama (`OLLAMA_BASE_URL`) + +Wird für die LLM-Analyse von Transkripten verwendet. + +**Standard-URL:** `http://gx10.aquantico.lan:11434` + +### `POST /api/generate` + +**Request (JSON):** +```json +{ + "model": "qwen3.5:9b", + "prompt": "...", + "stream": true, + "think": true, + "options": { + "num_ctx": 16384, + "num_predict": 16384, + "repeat_penalty": 1.15, + "repeat_last_n": 128 + } +} +``` + +- `stream: true` — Antwort wird zeilenweise als JSON-Objekte gestreamt +- `think` — aktiviert Extended Thinking / Chain-of-Thought (modellabhängig) +- `num_ctx` — wird dynamisch berechnet (`_estimate_num_ctx`) oder aus den Einstellungen gelesen +- Alle `options`-Parameter sind über die Einstellungsseite oder pro Prompt konfigurierbar + +**Response (NDJSON-Stream):** +```json +{"response": "Teil der Antwort", "thinking": "...", "done": false} +... +{"response": "", "done": true, "total_duration": ...} +``` + +Chunks werden in `_JOB_STREAMS[job_id]` gepuffert für die Live-Anzeige auf der Jobs-Seite. + +**Timeout:** 1200 s + +### `GET /api/tags` + +Wird von `GET /settings/models` aufgerufen, um verfügbare Modelle zu listen. + +**Response (JSON):** +```json +{ + "models": [ + { "name": "qwen3.5:9b", ... }, + ... + ] +} +``` + +**Timeout:** 5 s + +--- + +## KI-Einstellungen (Prioritäten) + +Beim Analyse-Job gelten folgende Prioritäten für die LLM-Parameter: + +``` +Prompt-spezifische Einstellungen (llm_use_default=0) + → Werte aus prompts.llm_* Spalten +Globale Einstellungen (llm_use_default=1, Standard) + → Werte aus der settings-Tabelle +Fallback + → Umgebungsvariablen (OLLAMA_MODEL, OLLAMA_THINK, …) +```