# 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 ```text 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 ```bash cd backend ./mvnw spring-boot:run ``` Das Backend lauscht auf `0.0.0.0:8080`. Alternativer Port: ```bash ./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 ```bash 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`: ```bash 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: ```bash 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: ```bash 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: ```bash 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. ```bash # 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 ```bash 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 ```bash 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: ```bash 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 ```bash # 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: ```bash 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:`. 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.