Files
swyxweb/AGENTS.md
T

21 KiB
Raw Blame History

SwyxWeb – Leitfaden für Coding-Agents

Projektüberblick

SwyxWeb ist eine deutschsprachige Telefonie- und Kundenoberfläche für SwyxTray/SwyxIt!. Das System besteht aus drei zusammenarbeitenden Teilen:

  • Frontend: React 19, TypeScript und Vite; stellt Dashboard, Entwickleransicht, Anruf-Popups und Aufnahmeverwaltung bereit.
  • Backend: Spring Boot 4.1 mit Java 21 und Maven Wrapper; stellt REST-API und Server-Sent Events bereit und archiviert Daten in MongoDB.
  • Transkriptionsworker: FastAPI mit faster-whisper; verarbeitet WAV-Dateien lokal, speichert Aufträge in SQLite und erstellt optional Zusammenfassungen über eine OpenAI-kompatible LLM-API.

Die Standardansicht ist ein Dashboard der gespeicherten eingehenden Anrufe. Der Schalter Dev blendet die weiteren Bereiche für Telefonie, Adressdaten, Webhooks, Firefox-Tabs und Verbindungsdiagnose ein. Eingehende Anrufe können Kunden- und Kurierdaten sowie zugehörige Jobs anzeigen. Gesprächsaufnahmen werden transkribiert und optional zusammengefasst; eine Sprecherzuordnung ist nicht implementiert.

Die ausführliche fachliche und protokollbezogene Dokumentation steht in README.md. Bei Änderungen an Verhalten, Protokoll, Konfiguration oder Deployment müssen README und gegebenenfalls diese Datei mitaktualisiert werden.

Wichtige Laufzeitgrenzen

Die Verbindungen dürfen nicht verwechselt werden:

  1. Browser ↔ SwyxTray: Das Frontend öffnet selbst einen WebSocket zur SwyxTray-App, standardmäßig ws://127.0.0.1:17654/ws. Das Java-Backend hält keine Verbindung zur echten Telefonanlage.
  2. Browser ↔ Spring Boot: REST-Aufrufe und der Webhook-Ereignisstrom laufen über /api/**, in der Entwicklung zunächst über den Vite-Proxy. Webhook-Ereignisse verwenden SSE unter /api/webhook/events, nicht den SwyxTray-WebSocket.
  3. Spring Boot ↔ Whisper-Worker: Das Backend reicht WAV-Uploads an den FastAPI-Worker weiter. Im Container ist das ausschließlich http://127.0.0.1:8765; der Worker-Port wird nicht nach außen veröffentlicht.
  4. Backend ↔ MongoDB: MongoDB ist ein externer Dienst. compose.yaml enthält keinen MongoDB-Container.

Standardports:

Port Zweck
5173 Vite-Entwicklungsserver
8080 Spring Boot, REST-API und im Produktiv-Image das statische Frontend
8765 FastAPI-Whisper-Worker, nur containerintern auf Loopback
17654 SwyxTray-App auf dem Arbeitsplatz, vom Browser aus erreicht

Zentrale Datenflüsse

  • SwyxTray sendet vollständige Snapshots und bei neueren Protokollen zusätzlich Anrufereignisse. frontend/src/swyx/protocol.ts und useSwyxTray.ts gleichen Zustände ab, verhindern doppelte Ende-Ereignisse und melden Anrufdaten an POST /api/calls.
  • Leitungen sind 1-basiert. Bei Protokoll 10 und neuer identifiziert callId zusammen mit line einen Anruf. Aufnahme- und Auflegebefehle müssen diese Identität beachten, damit verspätete Befehle keinen Folgeanruf auf derselben Leitung treffen.
  • Der Adress-Cache der Tray-App wird vom Browser an POST /api/addresses gemeldet. Dieser Import aktualisiert nur Name und Beschreibung und erhält Rollen, Kundenkennungen, Job-Zuordnungen und URLs aus vollständigen Webhook-Daten.
  • Fremdsysteme liefern Adressen und Jobs über die Webhook-Endpunkte. Das Backend verteilt angenommene Nutzlasten sofort per SSE und schreibt MongoDB-Daten asynchron.
  • Bei Protokoll 11 überträgt SwyxTray eine fertige WAV nach bestätigtem Gesprächsende in Blöcken über den bestehenden WebSocket. Der Browser lädt sie an /api/transcriptions hoch und bestätigt SwyxTray erst nach Erhalt der dauerhaften Worker-Auftragskennung.
  • Der Worker verarbeitet jeweils einen Auftrag, speichert das Transkript vor einem optionalen LLM-Aufruf und setzt unterbrochene Aufträge nach einem Neustart fort. Ein Retry nach einem Zusammenfassungsfehler wiederholt nicht die Whisper-Transkription.

Repository-Struktur und wichtige Dateien

SwyxWeb/
├── backend/
│   ├── pom.xml                         Spring Boot 4.1, Java 21, Abhängigkeiten
│   ├── mvnw                            Maven Wrapper verwenden
│   └── src/
│       ├── main/java/de/appcreation/swyxweb/
│       │   ├── config/                Properties, CORS und Profil-geschützter Mock
│       │   ├── web/                   REST-Controller für Config, Calls, Adressen, Jobs, Webhooks
│       │   ├── webhook/               SSE-Verlauf und Verteilung
│       │   ├── storage/               MongoDB-Dokumente, Archiv und Job-Bereinigung
│       │   ├── transcription/         Validierung und Weiterleitung an FastAPI
│       │   └── websocket/             SwyxTray-Mock und dessen Leitungsmodell
│       ├── main/resources/application.properties
│       └── test/java/                 JUnit-/Spring-/Mockito-Tests
├── frontend/
│   ├── package.json                   npm-Skripte und Abhängigkeiten
│   ├── package-lock.json              Für reproduzierbare Installation mit npm ci
│   ├── vite.config.ts                 Dev-Proxy für /api
│   ├── tsconfig*.json                 Strikter TypeScript-Build
│   └── src/
│       ├── pages/HomePage.tsx         Dashboard/Dev-Umschaltung und Gesamtseite
│       ├── components/                React-Panels, Dashboard, Popup, Aufnahmeanzeige
│       ├── hooks/                     Tray-, Webhook-, Caller-Lookup- und Upload-Zustand
│       ├── swyx/                      WebSocket-Client, Protokoll und Adressabgleich
│       ├── calls.ts                   Anruf-API und Suche
│       ├── addresses.ts               Adress-API und Rufnummernabgleich
│       ├── jobs.ts                    Typisierte Job-Nutzlasten
│       ├── recordings.ts              WAV-Validierung und Transkriptions-API
│       └── *.test.ts(x)               Vitest-Tests neben dem getesteten Code
├── transcription/
│   ├── service.py                     FastAPI, SQLite-Aufträge und Whisper-Verarbeitung
│   ├── summary.py                     OpenAI-kompatible Zusammenfassungen
│   ├── requirements.txt               Fixierte Laufzeitabhängigkeiten
│   ├── requirements-test.txt          Laufzeit- plus Testabhängigkeiten
│   └── tests/                         pytest-Tests
├── Dockerfile                         Vierstufiger Gesamt-Build
├── compose.yaml                       Ein Container plus persistentem Aufnahme-Volume
├── docker/entrypoint.sh               Start und gemeinsamer Abbruch von Java und Python
├── docker_push.sh                     Buildx-Build/Push für linux/amd64
├── .env.example                       Öffentliche Compose-Konfigurationsvorlage
└── .vscode/launch.json                Compound-Starts für Backend und Frontend

Entwicklung und lokale Starts

Voraussetzungen sind Java 21, Node.js/npm und für den lokalen Worker eine Python-Umgebung mit den Projektabhängigkeiten. Der Docker-Build verwendet Node 22 und Python 3.12. Für produktive Datenbankfunktionen muss die konfigurierte MongoDB erreichbar sein.

Backend

cd backend
./mvnw spring-boot:run

Das Backend lauscht auf 0.0.0.0:8080. Alternativer Port:

./mvnw spring-boot:run -Dspring-boot.run.arguments="--server.port=8081"

application.properties verweist standardmäßig auf eine MongoDB im internen Netz. Schreibvorgänge des Archivs sind asynchron und best-effort; eine nicht erreichbare Datenbank verhindert daher nicht jede Anzeige, wohl aber Datenbankabfragen und dauerhafte Speicherung.

Frontend

cd frontend
npm ci
npm run dev

Vite läuft auf http://localhost:5173 und leitet /api standardmäßig an http://localhost:8080 weiter. VITE_BACKEND_URL ändert das Proxy-Ziel. Der SwyxTray-WebSocket läuft nicht über diesen Proxy.

In VS Code startet die Compound-Konfiguration „SwyxWeb starten (Backend + Frontend)" beide Seiten mit Debugging. Die zweite Compound-Konfiguration verwendet den Mock. Diese Startkonfigurationen lesen die projektweite .env; ein reiner Terminalstart aus backend tut das nicht automatisch.

SwyxTray-Mock

Der Mock existiert nur mit Spring-Profil mock:

cd backend
./mvnw spring-boot:run -Dspring-boot.run.profiles=mock

Dann stehen /ws und /api/mock/** zur Verfügung. Für die vorbereitete VS-Code-Konfiguration zeigt /api/config automatisch auf ws://localhost:8080/ws. Einen Testanruf auslösen:

curl -X POST "http://localhost:8080/api/mock/incoming-call?number=%2B493012345&name=Muster%20GmbH"

Der Mock bildet Telefonie-, Adress-, Tab- und Aufnahmeprotokolle ab, erzeugt aber keine Audiodateien. Ohne das Profil sollen /ws und /api/mock/** mit 404 antworten; dieses Opt-in nicht durch allgemeine Konfigurationsänderungen aushebeln.

Lokaler Transkriptionsworker

Ein vollständiges CTranslate2-Whisper-Modell muss bereits lokal vorliegen; der Worker lädt es nicht selbst aus dem Internet:

python3 -m venv transcription/.venv
transcription/.venv/bin/pip install -r transcription/requirements.txt
WHISPER_MODEL_PATH=/pfad/zum/whisper-modell WHISPER_DATA_DIR=/pfad/zu/auftraegen \
  transcription/.venv/bin/python -m uvicorn service:app --app-dir transcription \
  --host 127.0.0.1 --port 8765

Für den Terminalstart des Backends zusätzlich:

cd backend
APP_TRANSCRIPTION_BASE_URL=http://127.0.0.1:8765 ./mvnw spring-boot:run

Ohne APP_TRANSCRIPTION_BASE_URL bleibt die Transkriptionsübergabe außerhalb von Docker deaktiviert.

Build-Befehle

Es gibt kein übergreifendes Root-Buildskript; die Teilprojekte werden einzeln oder durch das Dockerfile gebaut.

# Backend: Tests ausführen und target/backend-0.0.1-SNAPSHOT.jar erzeugen
cd backend
./mvnw clean package

# Frontend: Typprüfung und Produktionsbundle in frontend/dist
cd frontend
npm ci
npm run typecheck
npm run build

# Produktionsbundle lokal über Vite ansehen
npm run preview

npm run build führt tsc -b && vite build aus. Um ein lokal gebautes Frontend in das Spring-Jar aufzunehmen, muss frontend/dist/* vor dem Maven-Packen nach backend/src/main/resources/static/ kopiert werden. Der Docker-Build übernimmt diesen Schritt selbst.

Tests und Prüfstrategie

Backend

cd backend
./mvnw test
# Einzelne Testklasse:
./mvnw -Dtest=WebhookControllerTests test
  • JUnit 5, AssertJ, Mockito, Spring Boot Test und MockMvc.
  • Controller- und Profil-Tests ersetzen die Archivschicht mit @MockitoBean; MongoDB ist für die Tests nicht erforderlich.
  • ArchiveService wird mit einem gemockten MongoTemplate geprüft.
  • Der Transkriptionsclient wird gegen einen lokalen JDK-HttpServer getestet, nicht gegen einen echten Whisper-Dienst.
  • Der Mock wird einschließlich Profilaktivierung, SSE-Verhalten und Aufnahmeprotokoll getestet.

Frontend

cd frontend
npm test
# Einzelne Datei:
npm test -- src/swyx/protocol.test.ts
  • Vitest-Tests liegen als *.test.ts beziehungsweise *.test.tsx neben dem Code.
  • Die meisten Tests laufen in der Standardumgebung mit gefakten WebSockets, Timern und HTTP-Funktionen. Hook-Tests, die DOM benötigen, kennzeichnen // @vitest-environment jsdom direkt in der Datei.
  • Schwerpunkte sind Protokoll-Parsing, Snapshot-/Ende-Reihenfolgen, Leitungswiederverwendung, Aufnahme-Timeouts, WAV-Blöcke, Adressabgleich, Job-Parsing und Webhook-Zusammenführung.
  • React-Hooks werden mit react-dom/client und act getestet; es ist keine separate React-Testing-Library konfiguriert.

Transkriptionsworker

Vom Projektroot:

python3 -m venv transcription/.venv
transcription/.venv/bin/pip install -r transcription/requirements-test.txt
transcription/.venv/bin/pytest -q transcription/tests
  • Transkription und Zusammenfassung werden überwiegend durch Funktionen ersetzt; kein Modell-Download und kein externes LLM sind für die Tests nötig.
  • Ein Test prüft zusätzlich die echte WAV-Dekodierung mit den installierten faster-whisper/PyAV-Abhängigkeiten.
  • LLM-Tests verwenden einen lokalen HTTP-Testserver und prüfen auch Timeouts, Redirect-Verbot, Antwortgrenzen und das Vermeiden von Geheimnissen in Fehlermeldungen.

Es ist keine separate Browser-E2E- oder CI-Pipeline im Repository konfiguriert. Reale Aufnahme- und Telefonieänderungen müssen zusätzlich auf einem Windows-Arbeitsplatz mit SwyxTray/SwyxIt! geprüft werden; der Mock und synthetische WAVs ersetzen diesen Praxistest nicht.

Code- und Projektkonventionen

  • Dokumentation, Kommentare, UI-Texte und fachliche Fehlermeldungen sind überwiegend Deutsch. Bezeichner und API-Strukturen sind überwiegend Englisch. Diese Sprachtrennung beibehalten.
  • TypeScript verwendet zwei Leerzeichen, einfache Anführungszeichen und normalerweise keine Semikolons. strict, noUnusedLocals, noUnusedParameters und noFallthroughCasesInSwitch sind aktiv.
  • Java-Produktivcode verwendet überwiegend vier Leerzeichen, Records für Datenmodelle und Constructor Injection. Bei Testdateien und älteren Abschnitten die vorhandene Einrückung der jeweiligen Datei beibehalten.
  • Python verwendet vier Leerzeichen und für HTTP zum LLM bewusst die Standardbibliothek. Keine neue Bibliothek allein aufgrund ihrer Üblichkeit einführen; vorhandene Manifeste und Idiome sind maßgeblich.
  • Es sind keine eigenen Lint-, Formatierungs-, Checkstyle- oder Python-Formatter-Befehle konfiguriert. Typecheck, Compiler und Tests sind die vorhandenen automatischen Prüfungen.
  • Spring Boot 4 verwendet im Code tools.jackson.databind für JSON-Verarbeitung; einzelne Mapping-Annotationen stammen weiterhin aus com.fasterxml.jackson.annotation. Diese Imports nicht vorschnell vereinheitlichen.
  • JSON-Feldnamen des Fremdsystems wie csc_id, job_ids und map_url müssen an den API-/MongoDB-Grenzen korrekt auf Java-/TypeScript-Felder abgebildet werden. Unbekannte Jobfelder werden ignoriert; Job-IDs müssen positive Long-Werte sein.
  • HTTP 202 bei Archiv- und Upload-Endpunkten bedeutet angenommen, nicht bereits vollständig verarbeitet. Die Asynchronität und die best-effort-Semantik des MongoDB-Archivs nicht unbeabsichtigt in synchrone Erfolgsgarantien umwandeln.
  • Änderungen am SwyxTray-Protokoll gehören in Parser, Client, Mock und Tests gemeinsam. Ältere Protokollversionen bleiben relevant; insbesondere Snapshot-basierte Abläufe und Deduplizierung nicht entfernen.
  • Abhängigkeiten sind in Maven, package-lock.json und den Python-Requirements fixiert beziehungsweise versioniert. Die PyAV-Version ist wegen Kompatibilität mit faster-whisper ausdrücklich angepinnt.

Konfiguration und Persistenz

Zentrale Konfigurationsdateien:

  • backend/src/main/resources/application.properties: Server, MongoDB, WebSocket-Ziel, Webhook und Transkription.
  • frontend/.env.example: VITE_WS_URL und VITE_BACKEND_URL für Frontend-/Dev-Builds.
  • .env.example: Vorlage für Compose und VS-Code-Starts; nach .env kopieren und lokale Werte eintragen.
  • compose.yaml: Übergibt Laufzeitwerte an den Container und legt das Transkriptionsvolume fest.

Die SwyxTray-Adresse ergibt sich aus zuletzt erfolgreich verwendeter Browser-Eingabe beziehungsweise localStorage, danach aus GET /api/config; ohne Backend-Antwort dient VITE_WS_URL und schließlich der Code-Default als Fallback. Laufzeitwerte für den Container sind APP_WEBSOCKET_HOST, APP_WEBSOCKET_PORT, APP_WEBSOCKET_PATH und APP_WEBSOCKET_SECURE. Ein festes VITE_WS_URL ist normalerweise nicht erforderlich.

Weitere wichtige Werte:

  • SPRING_MONGODB_URI: externe MongoDB.
  • APP_WEBHOOK_TOKEN, app.webhook.history und app.webhook.max-size: Webhook-Schutz und Grenzen.
  • APP_TRANSCRIPTION_BASE_URL, WHISPER_MODEL_PATH, WHISPER_DATA_DIR, WHISPER_THREADS: Worker-Integration und lokale Verarbeitung.
  • LLM_BASE_URL, LLM_MODEL, LLM_API_KEY, LLM_TIMEOUT_SECONDS, LLM_CHUNK_CHARS: optionale Zusammenfassung. URL und Modell müssen gemeinsam konfiguriert sein; ohne beide bleibt die Zusammenfassung deaktiviert.

Persistenzregeln:

  • MongoDB-Sammlungen: addresses, jobs und calls.
  • Adressen werden per Rufnummer upserted. Vollständige Webhook-Adressen ersetzen auch Zuordnungen; der Telefonbuchimport erhält sie.
  • Jobs werden anhand ihrer externen id ersetzt. JobCleanupService löscht jede Nacht um 0 Uhr deutscher Zeit Jobs, deren ordertime mehr als vier Wochen zurückliegt; Jobs ohne ordertime bleiben erhalten.
  • Der Webhook-Verlauf umfasst standardmäßig die letzten 50 Nachrichten nur im Java-Speicher und ist nach einem Neustart leer. SSE nutzt Last-Event-ID zur Nachlieferung und sendet alle 25 Sekunden einen Heartbeat.
  • WAVs, jobs.sqlite3, Transkripte und Zusammenfassungen liegen im Volume /data/transcriptions. Dafür gibt es keine automatische Löschfrist. docker compose down erhält das Volume; docker compose down -v löscht es.

Docker und Deployment

# Frischen Checkout einmalig vorbereiten; vorhandene .env nicht überschreiben
test -f .env || cp .env.example .env

docker compose up -d --build

Der mehrstufige Dockerfile:

  1. baut das Frontend mit Node 22,
  2. kopiert dist in die statischen Spring-Ressourcen und baut das Java-21-Jar,
  3. lädt ein fixiertes faster-whisper-large-v3-turbo-Modell in eine eigene Build-Stufe,
  4. erzeugt ein Laufzeit-Image mit Java, Python, Whisper und Modell.

Der Container läuft als Benutzer swyx mit UID/GID 10001 und veröffentlicht Port 8080. Das Modell ist im Image enthalten; Hugging-Face-Offline-Modus und local_files_only=True verhindern Modell-Downloads zur Laufzeit. WHISPER_MODEL_REPO und WHISPER_MODEL_REVISION sind Build-Argumente. Für den Container sind zunächst mindestens 4 GB RAM einzuplanen; der Java-Heap ist standardmäßig auf 25 % des verfügbaren Speichers begrenzt und über JAVA_OPTS änderbar.

docker/entrypoint.sh startet Java und Python gemeinsam. Endet einer der beiden Prozesse, wird auch der andere beendet, damit Compose den Container neu starten kann. Der Healthcheck prüft sowohl /api/config als auch /health des Workers. Der Maven-Schritt im Dockerfile verwendet -DskipTests; Tests deshalb vor dem Image-Build separat ausführen.

Registry-Veröffentlichung:

docker login gitea.appcreation.de
./docker_push.sh 0.9.13
./docker_push.sh 0.9.13 --dry-run

Das Skript baut für linux/amd64 und pusht nach gitea.appcreation.de/sven/swyxweb:<x.y.z>. Die Semver-artige Versionsangabe ist Pflicht und wird nicht aus pom.xml abgeleitet. --dry-run baut ohne Push. Lokale Maven-/npm-Builds sind vorher nicht nötig.

Der Produktiv-Container setzt das Profil mock nicht. HTTPS-Seiten benötigen für nicht-lokale SwyxTray-Ziele wss://; der WebSocket wird weiterhin vom Arbeitsplatzbrowser aus aufgebaut, nicht vom Docker-Host.

Sicherheits- und Datenschutzregeln

  • .env, lokale Frontend-Umgebungen, Build-Ergebnisse und virtuelle Umgebungen sind von Git und Docker-Build-Kontext ausgeschlossen. Keine echten Tokens, API-Schlüssel oder Zugangsdaten in versionierte Dateien oder Beispiele übernehmen.
  • Für öffentlich erreichbare Instanzen muss APP_WEBHOOK_TOKEN gesetzt sein. Ein leerer Wert lässt alle Webhook-POSTs ohne Token zu und erzeugt eine Start-Warnung. Das Token schützt nur die Webhook-POST-Endpunkte; Verlauf, SSE und die übrigen APIs besitzen keine allgemeine Benutzerauthentifizierung.
  • CORS für /api/** und Origins des Mocks sind derzeit mit * geöffnet. Das ist keine Zugriffskontrolle und muss für eine produktive Exposition gegebenenfalls eingeschränkt werden. Den Mock nicht unbeabsichtigt produktiv aktivieren.
  • Die Standard-MongoDB-Konfiguration enthält keine Zugangsdaten und zeigt auf ein internes Netz. Netzwerkzugriff und Datenbankabsicherung müssen zur tatsächlichen Umgebung passen.
  • WAV-Uploads werden im Browser, Java-Backend und Worker auf WAV-Dateikopf und höchstens 500 MB geprüft. Diese Grenzen und die Validierung von UUIDs beziehungsweise 32-stelligen Hex-Job-IDs beibehalten. Externe Karten-/Download-URLs dürfen nur vorgesehene HTTP(S)-Formen verwenden.
  • Der FastAPI-Worker deaktiviert Dokumentationsendpunkte, beschränkt den Host-Header auf Loopback-Namen und soll nicht öffentlich exponiert werden.
  • Whisper verarbeitet Audio lokal. An das optionale LLM wird ausschließlich Transkripttext gesendet, nicht die WAV. API-Schlüssel gehören nur in die Worker-Umgebung, nicht ins Frontend, Image oder in Auftragsdaten. LLM-Redirects werden nicht verfolgt; Provider-Antworten und Zugangsdaten dürfen nicht in Logs oder Fehlermeldungen durchsickern.
  • Aufnahmen, Rufnummern, Kunden-/Jobdaten, Transkripte und Zusammenfassungen sind sensible Daten. Das persistente Transkriptionsvolume hat keine automatische Aufbewahrungsgrenze; Löschung und Zugriffsschutz müssen betrieblich berücksichtigt werden.