diff --git a/concepts/Agent-Toolbox-Dateisystem-Konzept.md b/concepts/Agent-Toolbox-Dateisystem-Konzept.md new file mode 100644 index 0000000..fdbd8e6 --- /dev/null +++ b/concepts/Agent-Toolbox-Dateisystem-Konzept.md @@ -0,0 +1,594 @@ +# Konzept: Agent Toolbox & Dateisystem-Erweiterung + +> Poweron Platform -- Erweiterung der Agent-Fähigkeiten und des Dateisystems +> Status: Konzept / Version 1.2 / März 2026 + +--- + +## 1. Ausgangslage (IST-Zustand) + +### 1.1 Datenmodelle + +Die Grundstruktur für ein hierarchisches Dateisystem existiert bereits, wird aber kaum genutzt. + +**FileItem** (`gateway/modules/datamodels/datamodelFiles.py`): + +| Feld | Typ | Beschreibung | +|------|-----|-------------| +| `id` | str | Primary Key (UUID) | +| `fileName` | str | Dateiname | +| `mimeType` | str | MIME-Typ | +| `fileSize` | int | Grösse in Bytes | +| `folderId` | Optional[str] | Verweis auf übergeordneten Folder | +| `featureInstanceId` | Optional[str] | Feature-Instanz-Zuordnung | +| `tags` | Optional[List[str]] | Tags | +| `status` | Optional[str] | Verarbeitungsstatus | +| `description` | Optional[str] | Beschreibung | + +**FileFolder** (`gateway/modules/datamodels/datamodelFileFolder.py`): + +| Feld | Typ | Beschreibung | +|------|-----|-------------| +| `id` | str | Primary Key (UUID) | +| `name` | str | Ordnername | +| `parentId` | Optional[str] | Übergeordneter Folder (None = Root) | +| `mandateId` | Optional[str] | Mandant | +| `featureInstanceId` | Optional[str] | Feature-Instanz-Zuordnung | + +### 1.2 Backend-Operationen + +``` +Vorhanden Fehlend +───────────────────── ────────────────────── +Files: Files: + createFile ✓ copyFile ✗ + deleteFile ✓ + updateFile ✓ (Metadaten) + createFileData ✓ (Update-Pfad: Logik aus Codeeditor applyEdit kopieren) + getAllFiles ✓ + +Folders: Folders: + createFolder ✓ deleteFolder ✗ + listFolders ✓ renameFolder ✗ + moveFolder ✗ + getFolder ✗ +``` + +### 1.3 Agent-Tools (27 registriert) + +Dateibezogene Tools im aktuellen Agent: + +| Tool | Funktion | Status | +|------|----------|--------| +| `readFile` | Datei lesen | Vorhanden | +| `listFiles` | Dateien auflisten | Vorhanden | +| `searchFiles` | Dateien suchen | Vorhanden | +| `writeFile` | Neue Datei erstellen | Vorhanden | +| `moveFile` | Datei verschieben (via `updateFile({folderId})`) | Vorhanden | +| `tagFile` | Tags setzen | Vorhanden | +| `createFolder` | Ordner erstellen | Vorhanden | +| `listFolders` | Ordner auflisten | Vorhanden | +| `deleteFile` | Datei löschen | **Fehlt** (Backend existiert) | +| `renameFile` | Datei umbenennen | **Fehlt** (Backend existiert) | +| `editFile` | Dateiinhalt ändern | **Fehlt** (Logik aus Codeeditor `applyEdit` extrahieren) | +| `copyFile` | Datei duplizieren | **Fehlt** (Backend muss gebaut werden) | + +### 1.4 Codeeditor-Feature (wird entfernt -- Logik extrahieren) + +Das bestehende **Codeeditor-Feature** (`gateway/modules/features/codeeditor/`) wird perspektivisch entfernt. Es enthält jedoch bewährte File-Edit-Logik, die vor dem Entfernen in den Workspace-Agent **kopiert** werden muss: + +**Zu extrahierende Backend-Logik:** + +| Datei | Was kopieren | +|-------|-------------| +| `routeFeatureCodeeditor.py` | `applyEdit()`-Logik: File-Write via `createFileData(fileId, contentBytes)`, MIME-Type-Erkennung | +| `datamodelCodeeditor.py` | `FileEditProposal`, `EditStatusEnum` (PENDING → ACCEPTED / REJECTED) | +| `responseParser.py` | `file_edit`-Block-Parsing (`fileName`, `oldContent`, `newContent`) | + +**Zu extrahierende Frontend-Logik:** + +| Datei | Was kopieren | +|-------|-------------| +| `DiffPreviewPanel.tsx` | Diff-View mit Accept/Reject-Buttons → als Shared-Komponente extrahieren | +| `useCodeEditor.ts` | `acceptEdit` / `rejectEdit` Handlers → in Workspace-Hook übernehmen | + +### 1.5 Frontend + +- **FileBrowser** (`frontend_nyla/src/pages/views/workspace/FileBrowser.tsx`): Gruppiert Dateien nach `featureInstanceId`, ignoriert `folderId` und Folder komplett. Kein Folder-Tree. +- **FilesPage** (`frontend_nyla/src/pages/basedata/FilesPage.tsx`): Flache Tabelle mit FormGeneratorTable. Keine Ordner-Navigation. +- **DataSourcePanel**: Externer Datenquellen-Tree existiert als Referenzimplementierung für Lazy-Loading-Tree-UI. + +--- + +## 2. Dateisystem-Erweiterung (Backend) + +### 2.1 Übersicht + +```mermaid +graph TB + subgraph fileSystem [Hierarchisches Dateisystem] + Root["(Global) Root"] + FolderA["Folder: Projekte"] + FolderB["Folder: Analysen"] + FolderC["Folder: Q1"] + FileA["File: Report.pdf"] + FileB["File: Daten.xlsx"] + FileC["File: Summary.md"] + Root --> FolderA + Root --> FolderB + FolderA --> FolderC + FolderA --> FileA + FolderC --> FileB + FolderB --> FileC + end + + subgraph agent [Agent Tools] + CRUD_Files["Files: create, read, edit, copy, delete, rename, move"] + CRUD_Folders["Folders: create, delete, rename, move, list"] + end + + agent -->|"operiert auf"| fileSystem +``` + +### 2.2 Neue/Erweiterte DB-Methoden + +Alle Methoden in `interfaceDbManagement.py` ergänzen: + +**Übergreifende Regeln für alle Folder-Operationen:** +- **Unique-Name-Constraint:** Innerhalb eines Folders darf ein Foldername nur einmal vorkommen (unique pro `parentId`) +- **Geschützter Name:** Der Name `"(Global)"` ist reserviert für den virtuellen Root und darf nicht für echte Folder verwendet werden +- Validation bei `createFolder`, `renameFolder`, `moveFolder` + +**deleteFolder(folderId, recursive=False)** +- Wenn `recursive=True`: Alle Dateien und Unterordner kaskadierend löschen +- Wenn `recursive=False` und Folder nicht leer: Fehler werfen +- RBAC-Check auf Folder-Ebene + +**renameFolder(folderId, newName)** +- Via `recordModify(FileFolder, folderId, {"name": newName})` +- **Unique-Name-Constraint:** Prüfen, dass im gleichen `parentId` kein anderer Folder mit `newName` existiert +- **Geschützter Name:** `"(Global)"` darf nicht als Foldername verwendet werden (reserviert für virtuellen Root) + +**moveFolder(folderId, targetParentId)** +- Validierung: Zirkelverweis verhindern (Folder darf nicht in eigenen Unterbaum verschoben werden) +- **Unique-Name-Constraint:** Prüfen, dass im Ziel-Folder kein Folder mit gleichem Namen existiert +- Via `recordModify(FileFolder, folderId, {"parentId": targetParentId})` + +**copyFile(sourceFileId, targetFolderId=None, newFileName=None)** +- FileItem duplizieren mit neuer ID +- **FileData vollständig duplizieren** (eigenständige Kopie, damit die Kopie unabhängig bearbeitet werden kann) +- Optional: neuer Name, anderer Ziel-Folder + +**updateFileData(fileId, data)** -- Logik aus Codeeditor `applyEdit` kopieren +- `createFileData(fileId, contentBytes)` zum Überschreiben bestehender Dateien +- Falls `createFileData` bei existierenden Daten überspringt: vorher `deleteFileData(fileId)`, dann `createFileData(fileId, newData)` +- FileItem-Metadaten aktualisieren (fileSize, fileHash via `updateFile`) +- Hinweis: Codeeditor wird später entfernt -- relevante Logik vorher in `interfaceDbManagement` oder Agent-Tool extrahieren + +### 2.3 Root-Folder "(Global)" + +Der Root-Folder ist **virtuell** und wird nicht als DB-Eintrag gespeichert: +- Dateien mit `folderId=None` gehören zum Root +- Ordner mit `parentId=None` sind Top-Level-Ordner unter Root +- Anzeige im UI als "(Global)" Label + +### 2.4 Migration: featureInstanceId entfernen + +Die Gruppierung nach `featureInstanceId` wird durch die Ordnerstruktur ersetzt: + +1. **Spalte beibehalten** aber nicht mehr für Gruppierung nutzen +2. `featureInstanceId` als informatives Metadatum ("Erstellt in: Workspace A") in einer zusätzlichen Tabellenspalte anzeigen +3. Bestehende Dateien: `folderId` bleibt `None` (Root), keine Migration nötig +4. Frontend-Gruppierung nach `featureInstanceId` entfernen, stattdessen Folder-Baum nutzen + +--- + +## 3. Neue Agent-Tools + +### 3.1 Phase 1 -- Quick Wins (Backend existiert) + +#### deleteFile + +| Eigenschaft | Wert | +|-------------|------| +| Backend | `interfaceDbManagement.deleteFile(fileId)` | +| Parameter | `fileId` (required) | +| readOnly | false | +| Hinweis | Knowledge-Store-Einträge ebenfalls bereinigen | + +#### renameFile + +| Eigenschaft | Wert | +|-------------|------| +| Backend | `interfaceDbManagement.updateFile(fileId, {"fileName": newName})` | +| Parameter | `fileId` (required), `newName` (required) | +| readOnly | false | + +#### readUrl + +| Eigenschaft | Wert | +|-------------|------| +| Backend | `serviceWeb._performWebCrawl(urls=[url])` | +| Parameter | `url` (required) | +| readOnly | true | +| Hinweis | Ergänzt `webSearch`: Während `webSearch` nach Informationen sucht, liest `readUrl` gezielt eine bekannte URL | + +#### translateText + +| Eigenschaft | Wert | +|-------------|------| +| Backend | `interfaceVoiceObjects.translateText(text, targetLanguage)` + Google Cloud Translation | +| Parameter | `text` (required), `targetLanguage` (required), `sourceLanguage` (optional, auto-detect) | +| readOnly | true | +| Hinweis | Effizienter als AI-basierte Übersetzung für grosse Textmengen | + +### 3.2 Phase 2 -- Dateisystem-Tools + +#### deleteFolder + +| Eigenschaft | Wert | +|-------------|------| +| Backend | Neu: `interfaceDbManagement.deleteFolder()` | +| Parameter | `folderId` (required), `recursive` (optional, default false) | +| readOnly | false | +| Sicherheit | Agent muss vor kaskadierendem Löschen bestätigen (Anzahl betroffener Dateien nennen) | + +#### renameFolder + +| Eigenschaft | Wert | +|-------------|------| +| Backend | Neu: `interfaceDbManagement.renameFolder()` | +| Parameter | `folderId` (required), `newName` (required) | +| readOnly | false | + +#### moveFolder + +| Eigenschaft | Wert | +|-------------|------| +| Backend | Neu: `interfaceDbManagement.moveFolder()` | +| Parameter | `folderId` (required), `targetParentId` (required, null für Root) | +| readOnly | false | +| Validierung | Zirkelverweis-Check: Ziel darf kein Unterordner der Quelle sein | + +#### copyFile + +| Eigenschaft | Wert | +|-------------|------| +| Backend | Neu: `interfaceDbManagement.copyFile()` | +| Parameter | `fileId` (required), `targetFolderId` (optional), `newFileName` (optional) | +| readOnly | false | +| Implementierung | Vollständige Duplikation: FileItem + FileData werden als eigenständige Kopie erstellt (editierbar) | + +#### editFile + +| Eigenschaft | Wert | +|-------------|------| +| Backend | `dbManagement.createFileData(fileId, contentBytes)` (Logik aus Codeeditor kopiert, siehe 5.2) | +| Parameter | `fileId` (required), `content` (required, Text-Inhalt) | +| readOnly | false | +| Einschränkung | Nur für Text-basierte Dateien (text/\*, application/json, etc.) | +| UX-Option | Optional: Approve/Reject-Mode (Agent schlägt Edit als Diff vor, User bestätigt) | + +### 3.3 Phase 3 -- Erweiterte Fähigkeiten + +#### speechToText + +| Eigenschaft | Wert | +|-------------|------| +| Backend | `connectorVoiceGoogle.speechToText()` | +| Parameter | `fileId` (required, Audio-Datei), `language` (optional, auto-detect) | +| readOnly | true | +| Hinweis | Gegenstück zu `textToSpeech`. Akzeptiert Audio-Dateien aus dem Workspace | + +#### detectLanguage + +| Eigenschaft | Wert | +|-------------|------| +| Backend | `interfaceVoiceObjects.detectLanguage()` | +| Parameter | `text` (required) | +| readOnly | true | + +#### searchImages + +| Eigenschaft | Wert | +|-------------|------| +| Backend | Neu: Google Custom Search API (Konzept in `local/pending/doc_enhancement_web_image_actions_pending.md`) | +| Parameter | `query` (required), `maxResults` (optional), `imageType` (optional), `size` (optional) | +| readOnly | true | +| Abhängigkeit | Google Custom Search API Key + Engine ID | +| Hinweis | Bilder werden als URLs zurückgegeben, können via `downloadFromDataSource` oder einem neuen Download-Mechanismus heruntergeladen werden | + +#### neutralizeData + +| Eigenschaft | Wert | +|-------------|------| +| Backend | `serviceNeutralization.processText()` / `processFile()` | +| Parameter | `text` (optional) oder `fileId` (optional, eines von beiden required) | +| readOnly | true (gibt anonymisierten Text zurück, ändert Original nicht) | + +#### executeCode + +| Eigenschaft | Wert | +|-------------|------| +| Backend | Neu: Sandboxed Code Execution | +| Parameter | `code` (required), `language` (required: "python" oder "javascript") | +| readOnly | true | +| Sicherheitskonzept | Siehe Abschnitt 5.1 | + +--- + +## 4. UI-Umbau + +### 4.1 Dateien-Seite (Split-View) + +``` +┌──────────────────────────────────────────────────────────┐ +│ Dateien │ +├───────────────────┬──────────────────────────────────────┤ +│ │ [+ Upload / Drag&Drop] [Filter]│ +│ (Global) │ ─────────────────────────────────── │ +│ ├── Projekte │ Name Typ Grösse Quelle │ +│ │ ├── Q1 │ ─────────────────────────────────── │ +│ │ └── Q2 │ Report.pdf PDF 1.2 MB Workspace│ +│ ├── Analysen │ Daten.xlsx XLSX 450 KB Chat AI │ +│ └── Archiv │ Notes.md MD 12 KB Chat AI │ +│ │ │ +│ [+ Neuer Ordner] │ │ +│ │ │ +├───────────────────┴──────────────────────────────────────┤ +│ ◄═══════════════ Divider (verschiebbar) ═══════════════►│ +└──────────────────────────────────────────────────────────┘ + +Wichtig: Die Action-Buttons (Upload, Filter) sind OBERHALB der Tabelle fixiert, +damit sie bei wechselnder Tabellengrösse nicht springen. +``` + +### 4.2 Komponenten + +**FolderTree (neue Shared-Komponente)** +- Rekursiv verschachtelbarer Baum +- Lazy-Loading der Unterordner (Referenz: `DataSourcePanel.tsx` für Tree-Pattern) +- **Inline-Icons pro Folder-Zeile:** Delete (🗑), Add Subfolder (+), Rename (✏) -- direkt sichtbar, kein Kontextmenü nötig +- **Drag-and-Drop:** Folders und Files können per Drag auf andere Folders verschoben werden +- Visuelles Feedback: Drop-Target-Highlighting beim Hover über gültiges Ziel +- Wiederverwendbar in der Dateien-Seite UND im Workspace-Chat (gleiche Komponente) + +**FileTable (bestehend, erweitert)** +- Zeigt Dateien des aktuell markierten Folders +- Neue Spalte "Quelle" (zeigt `featureInstanceLabel` als Information, nicht als Gruppierung) +- **Drag-and-Drop:** Dateien aus der Tabelle in den Folder-Tree ziehen zum Verschieben +- Kontextmenü pro Datei: Umbenennen, Löschen, Kopieren, Verschieben, Download + +**SplitView / Divider** +- Verschiebbar per Maus-Drag +- Default-Position: 25% links / 75% rechts +- Minimum-Breite pro Seite: 200px +- Position persistent im LocalStorage + +### 4.3 Workspace-Integration + +Im Workspace-Chat (`WorkspacePage.tsx`) wird der bestehende `FileBrowser` umgebaut: +- Statt Gruppierung nach `featureInstanceId`: der gleiche `FolderTree` wie auf der Dateien-Seite +- Kompaktere Darstellung (kein Split-View nötig, Tree + Dateien untereinander) +- Gleiche Drag-and-Drop-Fähigkeiten + +--- + +## 5. Kritische Bewertung + +### 5.1 executeCode -- Sicherheitskonzept + +**Anforderung:** "Hier einfach nur für Analysen und Berechnungen zulassen, nicht für Codeelemente ausserhalb der Umgebung oder Installationen." + +**Bewertung:** Sinnvoll und notwendig für Datenanalyse. Die Einschränkung auf Analysen/Berechnungen ist korrekt. + +**Empfohlene Absicherung:** +- **Isolierte Ausführung:** RestrictedPython (Python) oder vm2/isolated-vm (JS) -- keine eigene Docker-Sandbox nötig für reine Berechnungen +- **Kein Dateisystem-Zugriff:** `open()`, `os`, `subprocess`, `sys` blockieren +- **Kein Netzwerk-Zugriff:** `socket`, `urllib`, `requests` blockieren +- **Keine Installationen:** `pip`, `import` nur für Whitelist (math, statistics, json, csv, re, datetime, collections, itertools, functools, decimal, fractions) +- **Timeout:** Max. 30 Sekunden Ausführungszeit +- **Memory-Limit:** Max. 256 MB +- **Output-Limit:** Max. 50.000 Zeichen Rückgabe + +**Risiko-Bewertung:** Mittel. RestrictedPython ist gut getestet, aber kein perfekter Sandbox. Für den Use Case (Berechnungen, Datenanalyse) ist das Risiko vertretbar, solange die Whitelist strikt eingehalten wird. + +### 5.2 editFile -- Logik aus Codeeditor übernehmen + +**Anforderung:** "Bestehende Datei-Inhalte aktualisieren." + +**Bewertung:** Das Codeeditor-Feature hat eine bewährte File-Edit-Pipeline (`applyEdit`-Route, `createFileData(fileId, contentBytes)`). Da der Codeeditor später entfernt wird, muss die benötigte Logik in den Workspace-Agent **kopiert** werden -- keine Referenzen auf Codeeditor-Module. + +**Zu übernehmende Logik (aus Codeeditor extrahieren):** +- File-Write: `createFileData(fileId, contentBytes)` zum Überschreiben bestehender Dateien +- Falls `createFileData` bei existierenden Daten überspringt: `deleteFileData` → `createFileData` als Fallback +- MIME-Type-Erkennung: `_guessMimeType(fileName)` Logik kopieren +- FileItem-Metadaten (fileSize, fileHash) via `updateFile` aktualisieren +- MIME-Type-Check: Nur Text-basierte Dateien erlauben (text/\*, application/json, etc.) +- Knowledge-Store re-indexieren nach Edit (bestehende Pipeline nutzen) + +**Zu übernehmende UX-Logik (aus Codeeditor extrahieren):** +- `FileEditProposal`-Datenmodell (`EditStatusEnum`: PENDING → ACCEPTED / REJECTED) in Agent-Datenmodelle kopieren +- `file_edit_proposal` SSE-Event-Pattern in den Workspace-Agent übernehmen +- Diff-View-Komponente (`DiffPreviewPanel.tsx`) als eigenständige Shared-Komponente extrahieren + +**Optionaler Approve/Reject-Mode:** +- Agent emittiert `file_edit_proposal` SSE-Event mit Old/New-Content +- Frontend zeigt Diff-View (extrahierte Shared-Komponente) +- User akzeptiert oder lehnt ab +- Konfigurierbar: Auto-Apply für kleine Edits, Approve für Dateien > 1 KB + +**Race-Condition-Risiko:** Gering. Benutzerbezogen, sequenzieller Agent-Zugriff. + +### 5.3 copyFile -- Vollständige Kopie + +**Anforderung:** "Datei im Workspace duplizieren." + +**Bewertung:** Korrekt. Nur File-Level-Kopie sinnvoll. FileData **muss** vollständig dupliziert werden, damit die Kopie unabhängig bearbeitet werden kann (z.B. via `editFile`). + +**Empfehlung:** +- Nur einzelne Dateien kopieren, keine ganzen Ordner +- **Vollständige Duplikation:** FileItem UND FileData werden als eigenständige Einträge mit neuer ID erstellt. Keine Shared-Referenz auf dieselben Daten, da die Kopie editierbar sein muss. +- Ordner-Kopie als eigenes Feature für später (komplexer wegen Rekursion und Namenskonflikten) + +### 5.4 createCalendarEvent -- Nicht implementierbar + +**Anforderung:** "Kalender-Eintrag erstellen." + +**Bewertung: Noch nicht umsetzbar.** Calendar-API (Microsoft Graph `/calendar/events`) ist nicht gebaut. + +**Empfehlung:** Aus dem aktuellen Scope entfernen. Als separates Feature planen: +1. Microsoft Graph Calendar-Endpoints in `connectorMsft.py` implementieren +2. Workflow-Actions für Calendar erstellen (`outlook.createEvent`, `outlook.listEvents`) +3. Dann als Agent-Tool exponieren + +**Bestandsaufnahme Mail-Operationen (für Kontext):** + +| Operation | Microsoft Outlook | Google Gmail | +|-----------|-------------------|--------------| +| Mail-Ordner auflisten | `OutlookAdapter.browse(me/mailFolders)` | `GmailAdapter.browse(labels)` | +| Mails in Ordner lesen | `OutlookAdapter.browse(me/mailFolders/{id}/messages)` | `GmailAdapter.browse(messages?labelIds=)` | +| Mail herunterladen | `OutlookAdapter.download(me/messages/{id})` | **Stub** (gibt `b""` zurück) | +| Mails suchen | `OutlookAdapter.search(me/messages?$search=)` | `GmailAdapter.search(users/me/messages?q=)` | +| Mail senden | `OutlookAdapter.sendMail(me/sendMail)` | **Nicht implementiert** | +| Draft erstellen | `composeAndDraftEmailWithContext` (Workflow) | **Nicht implementiert** | +| Draft senden | `sendDraftEmail` (Workflow) | **Nicht implementiert** | +| Agent-Tool `sendMail` | Implementiert (nur Outlook trotz Doku "Outlook, Gmail") | **Nicht unterstützt** | + +**Fazit Mail:** Microsoft Outlook ist vollständig integriert (lesen, suchen, senden, drafts). Google Gmail hat nur Lesen/Suchen (readonly Scopes). Gmail-Senden, Drafts und Download sind nicht implementiert -- bei Bedarf separates Feature. + +### 5.5 featureInstanceId-Entfernung + +**Anforderung:** "Die Strukturierung der Daten nach deren Creation Feature Instanz entfernen wir. Dazu eine zusätzliche Spalte ergänzen." + +**Bewertung:** Guter Ansatz. Die Ordnerstruktur ersetzt die Feature-Instanz-Gruppierung. + +**Empfehlung:** +- `featureInstanceId` auf `FileItem` beibehalten (nicht aus dem Modell entfernen) +- Nicht mehr für Gruppierung nutzen, nur als informatives Metadatum +- Neue Tabellenspalte "Quelle" zeigt den Feature-Instanz-Namen +- Kein Datenmigrations-Aufwand nötig -- bestehende Dateien bleiben im Root und können manuell in Ordner verschoben werden +- Die Zuordnung `featureInstanceId` bei neuen Dateien weiterhin setzen (Audit-Trail) + +### 5.6 Root "(Global)" -- Multi-Mandanten-Implikation + +**Anforderung:** "Der Root ist der Folder '(Global)'." + +**Bewertung:** Konsistent mit dem bestehenden System. Dateien sind bereits benutzerbezogen (`_createdBy` Filter). Der Root-Scope ist pro User, nicht systemweit. + +**Empfehlung:** +- Root ist virtuell (kein DB-Eintrag), repräsentiert `folderId=None` +- Dateien sind weiterhin pro User isoliert (bestehende RBAC-Filter) +- Label "(Global)" im UI zeigt dem User, dass dies sein persönlicher Root ist +- Für Multi-Mandanten: Dateien haben `mandateId`, Filter bleibt bestehen + +### 5.7 searchImages -- Abhängigkeit und Integration + +**Anforderung:** Aus `doc_enhancement_web_image_actions_pending.md` ein Tool ableiten. + +**Bewertung:** Das bestehende Konzept ist solide (Google Custom Search API, `WEB_SEARCH_MEDIA` Operation Type). Für den Agent-Tool-Kontext reicht eine vereinfachte Version. + +**Empfehlung:** +- Das Agent-Tool `searchImages` als Wrapper um die neue `WEB_SEARCH_MEDIA`-Operation implementieren +- Ergebnisse als Liste von URLs zurückgeben (der Agent kann dann `downloadFromDataSource` oder einen neuen Mechanismus nutzen, um Bilder herunterzuladen) +- Google API Key als Voraussetzung klar dokumentieren +- Fallback: Wenn kein Google API Key konfiguriert, Tool nicht registrieren + +### 5.8 Dokument-Rendering mit Bildern + +**Anforderung:** Können professionelle Berichte (DOCX, XLSX, PPTX, PDF, HTML) mit eingebetteten Bildern generiert werden? + +**Bewertung: Ja, alle 5 Renderer unterstützen Bilder bereits vollständig.** + +| Renderer | Pfad | Bild-Methode | Bibliothek | +|----------|------|-------------|------------| +| DOCX | `renderers/rendererDocx.py` | `_renderJsonImage()` → `doc.add_picture()` | python-docx | +| XLSX | `renderers/rendererXlsx.py` | `_addImageToExcel()` → `sheet.add_image()` | openpyxl | +| PPTX | `renderers/rendererPptx.py` | `_addImagesToSlide()` → `slide.shapes.add_picture()` | python-pptx | +| PDF | `renderers/rendererPdf.py` | `_renderJsonImage()` → `ReportLabImage` | reportlab | +| HTML | `renderers/rendererHtml.py` | `_renderJsonImage()` → `` | native | + +**Gemeinsames Bild-Format (alle Renderer):** +```json +{ + "type": "image", + "content": { + "base64Data": "", + "altText": "Bildbeschreibung", + "caption": "Optionale Bildunterschrift" + } +} +``` + +**Basis-Klasse** (`documentRendererBaseTemplate.py`): Bietet `_validateImageData()`, `_getImageDimensions()`, `_resizeImageIfNeeded()` als Shared-Funktionen. + +**Fazit:** Kein Handlungsbedarf für Bild-Rendering. Der Agent kann Bilder (z.B. via `generateImage` oder `searchImages` + Download) als Base64-Daten in das Dokument-Schema einbetten, und alle Renderer verarbeiten diese korrekt. + +### 5.9 neutralizeData -- Scope + +**Anforderung:** "Text/Datei anonymisieren." + +**Bewertung:** `serviceNeutralization` existiert, ist aber Feature-spezifisch (Neutralization Feature). Als Agent-Tool muss es Feature-unabhängig aufrufbar sein. + +**Empfehlung:** +- Read-Only: Gibt anonymisierten Text zurück, ändert das Original nicht +- Für Dateien: Temporär extrahieren, neutralisieren, Ergebnis als neuen Text zurückgeben +- Feature-Abhängigkeit prüfen: Wenn Neutralization-Feature nicht lizenziert, Tool nicht registrieren + +--- + +## 6. Zusammenfassung und Priorisierung + +### Tool-Übersicht nach Aufwand und Nutzen + +| Tool | Aufwand | Nutzen | Priorität | Phase | +|------|---------|--------|-----------|-------| +| `deleteFile` | Niedrig | Hoch | **Must** | 1 | +| `renameFile` | Niedrig | Hoch | **Must** | 1 | +| `readUrl` | Niedrig | Hoch | **Must** | 1 | +| `translateText` | Niedrig | Mittel | **Should** | 1 | +| `deleteFolder` | Mittel | Hoch | **Must** | 2 | +| `renameFolder` | Niedrig | Mittel | **Should** | 2 | +| `moveFolder` | Mittel | Mittel | **Should** | 2 | +| `copyFile` | Niedrig | Mittel | **Should** | 2 | +| `editFile` | Niedrig (Codeeditor-Logik vorhanden) | Hoch | **Must** | 2 | +| `speechToText` | Niedrig | Mittel | **Should** | 3 | +| `detectLanguage` | Niedrig | Niedrig | **Could** | 3 | +| `searchImages` | Hoch | Mittel | **Should** | 3 | +| `neutralizeData` | Mittel | Niedrig | **Could** | 3 | +| `executeCode` | Hoch | Hoch | **Should** | 3 | +| `createCalendarEvent` | Hoch | Mittel | **Won't** (vorerst) | - | + +### Geschätzter Gesamtaufwand + +| Bereich | Aufwand | +|---------|---------| +| Phase 1: Quick-Win Tools | 2-3 Tage | +| Phase 2: Dateisystem-Backend + Tools | 4-5 Tage | +| Phase 3: Erweiterte Tools | 5-7 Tage | +| UI: Dateien-Seite Split-View | 3-4 Tage | +| UI: Folder-Tree Komponente | 2-3 Tage | +| UI: Workspace-Integration | 1-2 Tage | +| **Gesamt** | **17-24 Tage** | + +--- + +## 7. Referenzen + +| Dokument | Pfad | +|----------|------| +| AI Agent Architecture | `wiki/concepts/AI-Agent-Architecture-Konzept.md` | +| Web Image Search Konzept | `local/pending/doc_enhancement_web_image_actions_pending.md` | +| Codeeditor (Logik extrahieren, Feature wird entfernt) | `gateway/modules/features/codeeditor/routeFeatureCodeeditor.py` | +| Codeeditor Datenmodell (extrahieren) | `gateway/modules/features/codeeditor/datamodelCodeeditor.py` | +| Codeeditor Response Parser (extrahieren) | `gateway/modules/features/codeeditor/responseParser.py` | +| Codeeditor DiffPreview (als Shared-Komponente extrahieren) | `frontend_nyla/src/pages/views/codeeditor/DiffPreviewPanel.tsx` | +| Dokument-Renderer (Bild-Support) | `gateway/modules/serviceCenter/services/serviceGeneration/renderers/` | +| FileItem Datenmodell | `gateway/modules/datamodels/datamodelFiles.py` | +| FileFolder Datenmodell | `gateway/modules/datamodels/datamodelFileFolder.py` | +| DB-Operationen | `gateway/modules/interfaces/interfaceDbManagement.py` | +| Agent Tools | `gateway/modules/serviceCenter/services/serviceAgent/mainServiceAgent.py` | +| Tool Registry | `gateway/modules/serviceCenter/services/serviceAgent/toolRegistry.py` | +| Frontend FileBrowser | `frontend_nyla/src/pages/views/workspace/FileBrowser.tsx` | +| Frontend FilesPage | `frontend_nyla/src/pages/basedata/FilesPage.tsx` | +| DataSourcePanel (Tree-Referenz) | `frontend_nyla/src/pages/views/workspace/DataSourcePanel.tsx` | diff --git a/concepts/Commcoach-Voice-Recording-Streaming-Konzept.md b/concepts/Commcoach-Voice-Recording-Streaming-Konzept.md new file mode 100644 index 0000000..df939f7 --- /dev/null +++ b/concepts/Commcoach-Voice-Recording-Streaming-Konzept.md @@ -0,0 +1,334 @@ +# CommCoach Voice Recording Streaming Konzept + +## Ausgangslage + +Die aktuelle CommCoach-Spracheingabe basiert auf Browser `SpeechRecognition` (`webkitSpeechRecognition`). +Auf Mobile-Browsern wird die Aufnahme typischerweise alle ca. 5-7 Sekunden automatisch beendet und neu gestartet. +Das ist kein sauber behebbarer App-Bug, sondern eine Browser/API-Limitation. + +## Zielbild + +Die Spracheingabe soll auf Mobile und Desktop stabil laufen, ohne erzwungene 5-Sekunden-Resets, ohne Textverlust zwischen Restart-Gaps und ohne MSFT-Abhängigkeiten. + +## Antwort auf die Kernfragen + +### 1) Ist Option 3 ein genereller Umbau oder nur Mobile? + +Beides ist möglich: + +- **Variante A (Mobile-only Umbau):** + Neue Audio-Streaming-Pipeline nur auf Mobile aktivieren, Desktop bleibt vorerst bei Browser STT. +- **Variante B (Genereller Umbau):** + Neue Pipeline für alle Clients (Mobile + Desktop), einheitliches Verhalten und weniger Wartung. + +Empfehlung: **Variante B**, da nur ein Stack gepflegt werden muss und CommCoach-Logik konsistent bleibt. + +### 2) Kann das komplett in unserer Plattform umgesetzt werden? + +Ja. Vollständig in eigener Plattform ist möglich. + +Notwendig sind: + +- Eigene UI- und Gateway-Implementierung (WebSocket Streaming) +- Eigener STT-Service (self-hosted), z. B. `faster-whisper` +- Optional eigene VAD (Voice Activity Detection), z. B. `webrtcvad` + +Nicht zwingend notwendig: + +- Externe SaaS-STT Provider +- MSFT/Azure Speech + +## Zielarchitektur (MSFT-frei) + +### UI (Frontend CommCoach) + +- Mikrofonaufnahme via `MediaStream` + `AudioWorklet` oder `MediaRecorder` +- Chunking (z. B. 100-250 ms) +- Streaming per WebSocket an Gateway +- Lokaler Live-Preview-Text kommt nicht mehr aus Browser-STT, sondern aus Server-Interims +- Bestehende State-Machine bleibt als Steuerlogik erhalten (`idle`, `listening`, `botSpeaking`, `interrupted`, `muted` orthogonal) + +### Gateway + +- Neuer Endpoint, z. B. `ws /api/feature/commcoach/{instanceId}/stt/stream` +- Sessiongebundene Stream-Verarbeitung +- Weiterleitung der Audio-Chunks an STT-Worker +- Rückkanal von Interims + Final-Text zum UI +- Serverseitige Segmentierung (Silence / VAD) statt Browser `onend` + +### STT-Service (self-hosted) + +- `faster-whisper` als Runtime (GPU empfohlen, CPU möglich) +- Sprachen konfigurierbar (z. B. `de`, `en`) +- Ausgabe: + - `interim` Events (optional, je nach Latenzbudget) + - `final` Segmente + - Zeitmarken für Debug und Nachvollziehbarkeit + +### Optional: VAD-Service + +- Entweder im Gateway oder STT-Worker +- Trigger für Segment-Ende statt fester 1s-Timer +- Stabiler als browserseitige `onspeechstart/onspeechend` + +## Ist-Analyse der bestehenden Codebase (UI + Gateway) + +### Frontend (`frontend_nyla`) + +- `src/pages/views/commcoach/useVoiceController.ts` + - Nutzt Browser `SpeechRecognition` direkt. + - Auto-Restart bei `onend` mit `REC_AUTORESTART_DELAY_MS = 300`. + - `SILENCE_TIMEOUT_MS = 1000` finalisiert User-Text. + - Mobile-Problem entsteht hier durch Browser-`onend`-Zwang (alle ~5-7s). + +- `src/pages/views/commcoach/CommcoachDossierView.tsx` + - Bindet Voice-State-Machine an UI/TTS. + - `voice.liveTranscript` wird als Live-User-Bubble angezeigt. + - Debuglog existiert bereits via `window.__dlog`. + +- `src/hooks/useCommcoach.ts` + - Text läuft über `sendMessageStreamApi` (SSE). + - Audio läuft derzeit nur als One-Shot Blob über `sendAudioStreamApi` (SSE). + - Kein echtes bidirektionales Low-Latency Audio-Streaming. + +- `src/api/commcoachApi.ts` + - Bestehende Endpunkte sind HTTP + SSE. + - Es gibt noch keinen WebSocket-Endpunkt für chunkweises Audio. + +### Gateway (`gateway`) + +- `modules/features/commcoach/routeFeatureCommcoach.py` + - `POST .../message/stream` (SSE) für Text. + - `POST .../audio/stream` (SSE) für Audio-One-Shot. + - Audio-Endpoint liest `await request.body()` komplett und startet danach STT. + +- `modules/features/commcoach/serviceCommcoach.py` + - `processAudioMessage(...)`: STT auf gesamtem Blob, danach `processMessage(...)`. + - Keine Segment-/Chunk-Logik für laufende Erkennung. + +- `modules/interfaces/interfaceVoiceObjects.py` + - Kapselt STT/TTS aktuell über Google Connector. + - API ist bereits zentralisiert und damit gut erweiterbar. + +- `modules/connectors/connectorVoiceGoogle.py` + - Verwendet `recognize` auf Einzeldateien (nicht Streaming). + - Damit strukturell ungeeignet für kontinuierliche Mobile-Spracheingabe. + +## Implementierung im bestehenden System (konkret) + +### Ziel: bestehende CommCoach-Pipeline beibehalten, nur STT-Eingang ersetzen + +Unverändert bleiben: +- Session- und Message-Processing in `serviceCommcoach.py` (`processMessage`, Events, TTS). +- UI-State-Machine-Fachlogik (`idle`, `listening`, `botSpeaking`, `interrupted`, `muted`). + +Ersetzt wird: +- Browser-STT (`SpeechRecognition`) durch Streaming-STT Provider. +- One-Shot Audio-Upload durch WebSocket-Audio-Stream. + +### A) Frontend Änderungen + +1. Neue API-Funktion in `src/api/commcoachApi.ts` + - `openSttStreamApi(instanceId, sessionId, handlers, options)` via WebSocket. + - Handler: `onStatus`, `onInterim`, `onFinal`, `onError`, `onClose`. + +2. Neues Hook `src/hooks/useAudioStreamTranscription.ts` + - Mikrofon aufnehmen (`MediaStream` + `AudioWorklet` oder `MediaRecorder`). + - Audio in 100-250ms Chunks an WS senden. + - Event-Rückkanal verarbeiten (interim/final/status/error). + - Reconnect-Mechanismus für Mobile-Netzwechsel. + +3. `src/pages/views/commcoach/useVoiceController.ts` refactor + - Provider-Interface einführen: + - `browserSpeech` (legacy) + - `streamedStt` (neu) + - `transcriptPartsRef` und `liveTranscript` aus Serverevents speisen. + - `SILENCE_TIMEOUT_MS` nur noch als optionales Guard-Rail, nicht als Kernsegmentierung. + +4. `src/pages/views/commcoach/CommcoachDossierView.tsx` + - Keine Verhaltensänderung in Tabs/State nötig. + - Nur Provider initialisieren und vorhandene Debuganzeige um STT-WS Events erweitern. + +### B) Gateway Änderungen + +1. Neue WS-Route in `modules/features/commcoach/routeFeatureCommcoach.py` + - Beispiel: `ws /api/commcoach/{instanceId}/sessions/{sessionId}/stt/stream` + - Ownership-/Session-Checks analog zu `message/stream`. + - WebSocket akzeptieren, Audio-Chunks entgegennehmen, Events zurücksenden. + +2. Neuer Service `modules/features/commcoach/serviceCommcoachSttStream.py` + - Sessiongebundene Streamverwaltung. + - Chunk-Buffer, VAD/Silence-Regeln, Segmentbildung. + - Für jedes finale Segment: `processMessage(sessionId, contextId, finalText, interface)` aufrufen. + +3. Anpassung `modules/features/commcoach/serviceCommcoach.py` + - `processAudioMessage` bleibt für Legacy/Upload kompatibel. + - Neue Streaming-Nutzung erfolgt über den neuen STT-Stream-Service. + +4. Erweiterung `modules/interfaces/interfaceVoiceObjects.py` + - Neue Methoden ergänzen: + - `startSttStream(...)` + - `pushSttAudioChunk(...)` + - `stopSttStream(...)` + - Bestehende Methoden (`speechToText`, `textToSpeech`) unverändert lassen. + +### C) STT Worker (self-hosted) + +1. Neuer Connector (z. B.) `modules/connectors/connectorVoiceWhisper.py` + - `faster-whisper` Integration. + - Modell, Sprache, VAD-Parameter konfigurierbar. + +2. Optional separater Worker-Prozess + - Entkoppelt Gateway-Latenz von STT-Rechenlast. + - Kommunikation intern über Queue/IPC oder internen WS. + +## WS Event Contract (verbindlich) + +### Client -> Server + +- `open` + - `{ "type": "open", "sessionId": "...", "language": "de-DE", "codec": "pcm16" }` +- `audio` + - `{ "type": "audio", "seq": 123, "chunk": "" }` +- `commit` + - `{ "type": "commit", "reason": "silence|manual|stateChange" }` +- `close` + - `{ "type": "close" }` + +### Server -> Client + +- `status` + - `{ "type": "status", "label": "Sprache wird erkannt..." }` +- `interim` + - `{ "type": "interim", "text": "...", "segmentId": "..." }` +- `final` + - `{ "type": "final", "text": "...", "segmentId": "...", "confidence": 0.0 }` +- `ack` + - `{ "type": "ack", "seq": 123 }` +- `error` + - `{ "type": "error", "code": "stt_failed", "message": "..." }` +- `closed` + - `{ "type": "closed", "reason": "client|server|timeout" }` + +## Migrationsstrategie (ohne doppelte Logikfalle) + +1. Feature-Flag `commcoachVoiceProvider` auf Instanzebene + - Werte: `browserSpeech` | `streamedStt`. + +2. Rolloutpfad + - Schritt 1: intern auf Mobile aktivieren. + - Schritt 2: nach Stabilisierung global aktivieren. + - Schritt 3: Browser-STT-Code entfernen. + +3. Expliziter Fehlerpfad + - Kein stiller Fallback auf Browser-STT, wenn `streamedStt` aktiv ist. + - Fehler wird sichtbar im UI (Banner + Debuglog). + +## Konkrete Taskliste pro Repository + +### Repo `frontend_nyla` + +1. `commcoachApi.ts`: `openSttStreamApi` ergänzen. +2. Neues Hook `useAudioStreamTranscription.ts` implementieren. +3. `useVoiceController.ts`: Provider-Abstraktion + streamed Provider integrieren. +4. `CommcoachDossierView.tsx`: Provider-Init + Debug-Events anzeigen. +5. Tests: + - unit: Segment-Assembler + - integration: state transitions bei interim/final/error + +### Repo `gateway` + +1. WS-Route für STT-Stream in `routeFeatureCommcoach.py`. +2. Neuer Stream-Service `serviceCommcoachSttStream.py`. +3. `interfaceVoiceObjects.py` um Streaming-Methoden erweitern. +4. Neuer Whisper-Connector `connectorVoiceWhisper.py`. +5. Telemetrie + Logs: + - stream open/close + - first interim latency + - first final latency + - reconnect count + +## Variante A vs. Variante B + +| Kriterium | Variante A: Nur Mobile | Variante B: Alle Plattformen | +|---|---|---| +| Time-to-first-fix | schneller | mittel | +| Komplexität gesamt | höher langfristig (2 Stacks) | niedriger langfristig (1 Stack) | +| UX-Konsistenz | unterschiedlich je Device | einheitlich | +| Wartung | doppelt | einfach | +| Risiko | mittelhoch (Divergenz) | mittel (einmaliger Umbau) | + +**Empfehlung:** Variante B. + +## Implementierungsplan + +### Phase 1: Infrastruktur und Prototyp + +1. STT-Worker als eigener Service im Gateway-Umfeld bereitstellen +2. Streaming-Protokoll definieren (Events: `audio`, `interim`, `final`, `status`, `error`, `close`) +3. WS-Route im Gateway für CommCoach implementieren +4. Test-Client mit Beispielaudio aufbauen (ohne UI) zur Last-/Latenzprüfung + +### Phase 2: Frontend Integration + +1. Neues Modul `useAudioStreamTranscription` einführen +2. `useVoiceController` auf Provider-Abstraktion umstellen: + - Provider `browserSpeech` (bestehend) + - Provider `streamedStt` (neu) +3. Transcript-Handling auf Serverevents umstellen: + - Interim in `liveTranscript` + - Final in `transcriptParts` +4. State-Transitions unverändert belassen, nur STT-Quelle ersetzen + +### Phase 3: Segmentierung und Qualität + +1. Serverseitige VAD aktivieren +2. Segment-Ende sauber in `onMessage` überführen +3. Doppelungen/Fragmentverluste mit Session-IDs und Segment-Counter verhindern +4. Mobile Netzwechsel/WS-Reconnect robust behandeln + +### Phase 4: Rollout + +1. Feature-Flag: zuerst intern, dann Pilotmandanten +2. Optionale Stufen: + - Stufe 1: Nur Mobile + - Stufe 2: Alle Plattformen +3. Browser-STT nach Stabilitätsphase vollständig entfernen + +## Technische Leitplanken + +- Keine stillen Fallbacks, die Fehler verdecken +- Explizite Fehlerzustände im UI (Mikrofon, Netzwerk, STT nicht verfügbar) +- Klares Telemetrie-Set: + - Time to first interim + - Time to first final + - Segment-Länge + - Abbruchgründe + - WS-Reconnect-Rate + +## Drittkomponenten + +### Minimal erforderlich + +- Python Packages (self-hosted): + - `faster-whisper` + - optional `webrtcvad` + - Audio-Decode (`ffmpeg` Laufzeit) + +### Nicht erforderlich + +- MSFT/Azure Speech +- Externe STT-SaaS + +## Risiken und Gegenmassnahmen + +- **GPU-Kapazität fehlt:** zunächst kleines Modell und Queueing, später GPU-Skalierung +- **Latenz zu hoch:** Chunk-Grösse reduzieren, VAD feinjustieren, Modellwahl anpassen +- **Mobile Netz instabil:** robustes Reconnect-Handling + Segment-ACKs +- **Drift zwischen UI und Backend:** eindeutige Stream-/Segment-IDs und Idempotenzregeln + +## Fazit + +Der Umbau ist vollständig in der eigenen Plattform machbar und löst das Mobile-5-Sekunden-Problem an der Wurzel. +Für Wartbarkeit und konsistentes Verhalten ist ein **genereller Umbau (Variante B)** sinnvoll. +Ein stufenweiser Rollout mit Feature-Flag minimiert Risiko. diff --git a/deployment/poweron_sec.kdbx b/deployment/poweron_sec.kdbx index c1d6c6a..ea65af5 100644 Binary files a/deployment/poweron_sec.kdbx and b/deployment/poweron_sec.kdbx differ