# Konzept: Unified Messaging System für LeoCRM ## Vision Alle Kommunikation in LeoCRM — KI-Chat, Mitarbeiter-Chat, System-Benachrichtigungen, externe Messenger — läuft über **ein einheitliches Messaging-System**, das als Plugin-Architektur realisiert wird. **Kernprinzip:** Alles ist ein Teilnehmer. Die KI ist ein Teilnehmer. Das System ist ein Teilnehmer. Ein WhatsApp-Gateway ist ein Teilnehmer. Es gibt keine Sonderbehandlung. --- ## Plugin-Architektur ### Übersicht: Drei Plugin-Ebenen ``` ┌─────────────────────────────────────────────────────────────┐ │ Frontend (MessageSidebar) │ │ Ein Feed, eine Konversations-Liste, ein Eingabefeld │ └──────────────────────────┬──────────────────────────────────┘ │ │ WebSocket │ ┌──────────────────────────┴──────────────────────────────────┐ │ Plugin: kommunikation (Core) │ │ ┌─────────────┐ ┌──────────────┐ ┌────────────────────┐ │ │ │ conversations│ │ messages │ │ participants │ │ │ │ + Räume │ │ + Rich Cont.│ │ + Registrierung │ │ │ └─────────────┘ └──────────────┘ └────────────────────┘ │ │ ┌──────────────────────────────────────────────────────┐ │ │ │ Participant Registry (Hook) │ │ │ │ Andockpunkt für: ai_assistant, system_notif, │ │ │ │ whatsapp_gateway, telegram_gateway, ... │ │ │ └──────────────────────────────────────────────────────┘ │ └──────────────────────────┬──────────────────────────────────┘ │ │ EventBus │ ┌──────────────────────────┴──────────────────────────────────┐ │ Plugin: ai_assistant │ Plugin: system_notif │ Plugin: │ │ dockt als Teilnehmer an │ dockt als Teilnehmer │ whatsapp │ │ @KI → AI-Response │ Events → Messages │ Gateway │ └─────────────────────────────────────────────────────────────┘ ``` ### 1. Plugin `kommunikation` (Core-Plugin) **Verantwortung:** Chat-Infrastruktur — Konversationen, Nachrichten, Teilnehmer-Verwaltung, WebSocket, Rich Content Transport. **Basiert auf:** AI Assistant Plugin (Sessions/Messages/Streaming) als Grundlage, erweitert um Multi-Teilnehmer und Rich Content. **Manifest:** ```python PluginManifest( name="kommunikation", version="1.0.0", display_name="Kommunikation", description="Unified Messaging: Chat, KI, System, Messenger", dependencies=[], routes=[ PluginRouteDef(path="/api/v1/comm", module="...routes", router_attr="router"), ], events=["message.received", "conversation.created", "participant.joined"], migrations=["0001_initial.sql"], permissions=["comm:read", "comm:write", "comm:manage"], is_core=True, ) ``` **Komponenten:** - `models.py` — Conversation, Message, Participant, MessageAttachment, MessageReaction - `schemas.py` — Pydantic-Schemas für API - `routes.py` — REST-API + WebSocket - `services.py` — Business Logic (Nachrichten senden, Konversationen verwalten) - `participant_registry.py` — Registrierungs-Interface für andere Plugins - `content_types.py` — Rich Content Type-Definitionen - `websocket_manager.py` — WebSocket-Verbindungs-Manager ### 2. Participant Registry (Andockpunkt) Das `kommunikation` Plugin stellt eine **Participant Registry** bereit — ein Interface, über das sich andere Plugins als Teilnehmer registrieren. ```python # In kommunikation/participant_registry.py class ParticipantType(Enum): USER = "user" AI = "ai" SYSTEM = "system" WHATSAPP = "whatsapp" TELEGRAM = "telegram" EMAIL = "email" SLACK = "slack" # Erweiterbar... class ParticipantHandler(ABC): """Interface das Plugins implementieren um als Teilnehmer zu fungieren.""" @abstractmethod async def on_message_received(self, conversation_id, message, context) -> Message | None: """Wird aufgerufen wenn eine neue Nachricht in einer Konversation ankommt, an der dieser Teilnehmer beteiligt ist. Rückgabe: Optional eine neue Message (z.B. AI-Response). Für reine Leser (system) → return None. Für reaktive Teilnehmer (ai) → return Message(...). """ pass @abstractmethod def get_participant_info(self) -> dict: """Metadaten: name, avatar_url, display_name, capabilities.""" pass class ParticipantRegistry: """Global registry für Plugin-Teilnehmer.""" def register(self, participant_type: str, handler: ParticipantHandler) -> None: """Plugin registriert sich als Teilnehmer-Typ.""" def get_handler(self, participant_type: str) -> ParticipantHandler | None: """Handler für einen Teilnehmer-Typ abrufen.""" ``` **Wie Plugins andocken:** ```python # In ai_assistant/plugin.py on_activate(): from app.plugins.builtins.kommunikation.participant_registry import get_registry class AIAssistantPlugin(BasePlugin): async def on_activate(self, db, container, event_bus): await super().on_activate(db, container, event_bus) # Als AI-Teilnehmer registrieren registry = get_registry() registry.register("ai", AIParticipantHandler(self.services)) ``` ```python # In system_notif/plugin.py on_activate(): class SystemNotificationPlugin(BasePlugin): async def on_activate(self, db, container, event_bus): await super().on_activate(db, container, event_bus) # Als System-Teilnehmer registrieren registry = get_registry() registry.register("system", SystemParticipantHandler(...)) # Auf Events hören und Nachrichten erzeugen event_bus.subscribe("lead.created", self.on_lead_created) ``` ### 3. Plugin `ai_assistant` (Anpassung) Das bestehende AI Assistant Plugin wird angepasst: - Behält: Provider-Verwaltung, Modelle, Agents, Tools, Streaming - Neu: Implementiert `ParticipantHandler` und registriert sich bei `kommunikation` - Neu: Lauscht auf `message.received` Events → wenn `@KI` erwähnt wird oder Konversation AI als Teilnehmer hat → generiert Response - Alt: Eigene `AIChatSession` / `AIChatMessage` Tabellen bleiben für Abwärtskompatibilität, werden langfristig migriert - Neu: Schreibt Nachrichten in `kommunikation.messages` statt nur in eigene Tabellen ### 4. Plugin `system_notif` (Neu) **Verantwortung:** System-Events in Nachrichten umwandeln. - Registriert sich als `system` Teilnehmer - Hört auf EventBus-Events (`lead.created`, `contact.created`, `task.overdue`, ...) - Erzeugt Nachrichten in der System-Konversation des Users - Notifications haben `metadata.action_url` und `metadata.severity` - Bestehende Notification-Tabelle wird migriert ### 5. Messenger-Gateway Plugins (Später) Jeder Messenger ist ein eigenes Plugin: - `whatsapp_gateway` — registriert sich als `whatsapp` Teilnehmer - `telegram_gateway` — registriert sich als `telegram` Teilnehmer - `email_gateway` — registriert sich als `email` Teilnehmer Jedes implementiert `ParticipantHandler` und ggf. Webhook-Routes. --- ## Datenmodell ### Tabelle `comm_conversations` ``` id UUID PK tenant_id UUID NOT NULL title TEXT NULL -- benannte Räume title_set_by UUID NULL -- user_id der den Titel gesetzt hat is_pinned BOOLEAN DEFAULT FALSE is_direct BOOLEAN DEFAULT FALSE -- 1:1 vs Gruppe created_by UUID NULL last_msg_at TIMESTAMP last_msg_preview TEXT NULL -- für Konversations-Liste last_msg_sender_type TEXT NULL -- für Icon in Liste metadata JSONB DEFAULT '{}' -- z.B. {"pinned_by": "user_id"} created_at TIMESTAMP DEFAULT NOW() updated_at TIMESTAMP DEFAULT NOW() ``` ### Tabelle `comm_participants` ``` id UUID PK conversation_id UUID FK → comm_conversations participant_id UUID NULL -- user_id (NULL für ai/system/gateways) participant_type TEXT NOT NULL -- 'user', 'ai', 'system', 'whatsapp', ... display_name TEXT NULL -- override (z.B. WhatsApp-Kontakt-Name) joined_at TIMESTAMP DEFAULT NOW() left_at TIMESTAMP NULL ``` ### Tabelle `comm_messages` ``` id UUID PK tenant_id UUID NOT NULL conversation_id UUID FK → comm_conversations sender_id UUID NULL -- user_id (NULL für ai/system/gateways) sender_type TEXT NOT NULL -- 'user', 'ai', 'system', 'whatsapp', ... content TEXT NOT NULL -- Text-Inhalt (Markdown) content_format TEXT DEFAULT 'text' -- 'text', 'markdown', 'html' metadata JSONB DEFAULT '{}' -- typ-spezifische Daten reply_to_id UUID NULL FK → comm_messages -- Thread-Antwort created_at TIMESTAMP DEFAULT NOW() read_at TIMESTAMP NULL edited_at TIMESTAMP NULL deleted_at TIMESTAMP NULL ``` ### Tabelle `comm_message_attachments` ``` id UUID PK message_id UUID FK → comm_messages file_name TEXT NOT NULL file_path TEXT NOT NULL -- Pfad im DMS oder S3 file_type TEXT NOT NULL -- MIME type file_size BIGINT thumbnail_path TEXT NULL -- für Bilder/Videos metadata JSONB DEFAULT '{}' -- z.B. {"width": 1920, "height": 1080} created_at TIMESTAMP DEFAULT NOW() ``` ### Tabelle `comm_message_reactions` ``` id UUID PK message_id UUID FK → comm_messages user_id UUID NOT NULL emoji TEXT NOT NULL created_at TIMESTAMP DEFAULT NOW() UNIQUE(message_id, user_id, emoji) ``` ### Tabelle `comm_message_reads` ``` id UUID PK conversation_id UUID FK user_id UUID NOT NULL last_read_msg_id UUID FK → comm_messages last_read_at TIMESTAMP DEFAULT NOW() ``` ### Tabelle `comm_message_blocks` (Rich Content / Mini-Apps) ``` id UUID PK message_id UUID FK → comm_messages block_type TEXT NOT NULL -- 'file', 'image', 'audio', 'video', -- 'markdown', 'html', 'miniapp', -- 'action_card', 'contact_card', ... block_data JSONB NOT NULL -- typ-spezifische strukturierte Daten sort_order INT DEFAULT 0 ``` **Das ist der Schlüssel für Rich Content:** Eine Nachricht hat einen `content` (Text) plus beliebig viele `blocks` (strukturierte Elemente). --- ## Rich Content Transport ### Block-Typen (erweiterbar durch Plugins) | block_type | Beschreibung | block_data Beispiel | |---|---|---| | `text` | Reiner Text (Fallback) | `{"text": "..."}` | | `markdown` | Markdown-Content | `{"markdown": "# Titel\n..."}` | | `html` | HTML-Content (sanitized) | `{"html": "
...
"}` | | `image` | Bild | `{"url": "...", "alt": "...", "width": 800}` | | `audio` | Audio-Datei | `{"url": "...", "duration": 120, "waveform": [...]}` | | `video` | Video-Datei | `{"url": "...", "duration": 60, "thumbnail": "..."}` | | `file` | Allgemeine Datei | `{"url": "...", "name": "...", "size": 1024}` | | `action_card` | Interaktive Karte mit Buttons | `{"title": "...", "body": "...", "actions": [{"label": "Öffnen", "url": "..."}]}` | | `contact_card` | Kontakt-Referenz | `{"contact_id": "...", "name": "..."}` | | `miniapp` | Eingebettete Mini-App | `{"app_id": "...", "config": {...}}` | ### Mini-App System Mini-Apps sind kleine interaktive Komponenten, die **von Plugins registriert** und im Chat gerendert werden. ```python # Plugin registriert eine Mini-App: class MiniAppRegistry: def register(self, app_id: str, component: dict) -> None: """Registriert eine Mini-App. component = { 'name': 'Lead Qualifier', 'icon': 'clipboard', 'render_schema': {...}, # JSON-Schema für Frontend 'handler': async function # Backend-Handler } """ ``` **Beispiel:** Ein Plugin `lead_qualifier` registriert eine Mini-App. Ein User schickt `/miniapp lead_qualifier` im Chat → eine Mini-App-Block wird erzeugt → Frontend rendert das interaktive Formular → Ergebnis wird als Nachricht zurückgeschrieben. ### Nachricht mit Rich Content — Beispiel ```json { "id": "...", "conversation_id": "...", "sender_type": "ai", "content": "Hier ist die Zusammenfassung der neuen Leads:", "blocks": [ { "block_type": "markdown", "block_data": { "markdown": "## 3 neue Leads\n- **Acme Corp** — €50k potential\n- **Globex** — €20k potential\n- **Initech** — €10k potential" } }, { "block_type": "action_card", "block_data": { "title": "Nächste Schritte", "body": "3 Leads warten auf Qualifizierung.", "actions": [ {"label": "Alle öffnen", "action": "open_leads", "type": "primary"}, {"label": "Ignorieren", "action": "dismiss", "type": "secondary"} ] } } ] } ``` --- ## API ### REST Endpoints ``` # Konversationen GET /api/v1/comm/conversations -- Liste (für aktuellen User) POST /api/v1/comm/conversations -- Neue Konversation GET /api/v1/comm/conversations/{id} -- Details + Teilnehmer PATCH /api/v1/comm/conversations/{id} -- Titel ändern, pinnen DELETE /api/v1/comm/conversations/{id} -- Löschen/Verlassen # Teilnehmer POST /api/v1/comm/conversations/{id}/participants -- Teilnehmer hinzufügen DELETE /api/v1/comm/conversations/{id}/participants/{pid} -- Entfernen # Nachrichten GET /api/v1/comm/conversations/{id}/messages -- Nachrichten (paginiert) POST /api/v1/comm/conversations/{id}/messages -- Nachricht senden PATCH /api/v1/comm/messages/{id} -- Bearbeiten/Lesen DELETE /api/v1/comm/messages/{id} -- Löschen # Attachments POST /api/v1/comm/messages/{id}/attachments -- Datei hochladen GET /api/v1/comm/attachments/{id} -- Datei herunterladen # Reaktionen POST /api/v1/comm/messages/{id}/reactions -- Reaktion hinzufügen DELETE /api/v1/comm/messages/{id}/reactions/{emoji} -- Reaktion entfernen # Read State POST /api/v1/comm/conversations/{id}/read -- Als gelesen markieren # Mini-Apps GET /api/v1/comm/miniapps -- Verfügbare Mini-Apps POST /api/v1/comm/conversations/{id}/miniapps -- Mini-App starten ``` ### WebSocket ``` WS /api/v1/comm/ws # Client → Server {"type": "subscribe", "conversation_id": "..."} {"type": "typing", "conversation_id": "...", "is_typing": true} {"type": "ping"} # Server → Client {"type": "message.new", "conversation_id": "...", "message": {...}} {"type": "message.updated", "message": {...}} {"type": "message.deleted", "id": "..."} {"type": "participant.joined", "conversation_id": "...", "participant": {...}} {"type": "participant.left", "conversation_id": "...", "participant_id": "..."} {"type": "typing", "conversation_id": "...", "user_id": "...", "is_typing": true} {"type": "reaction.added", "message_id": "...", "emoji": "👍", "user_id": "..."} {"type": "conversation.updated", "conversation": {...}} {"type": "pong"} ``` --- ## UI-Konzept ### MessageSidebar (ersetzt AISidebar) ``` ┌──────────────────────────────────────────┐ │ Kommunikation [×] │ ├──────────────────────────────────────────┤ │ 🔍 Suche... │ ├──────────────────────────────────────────┤ │ 📌 Projekt Alpha │ ←angepinnt │ 🤖 KI: 3 Leads zusammengefasst... │ │ ┌────────────────────────────────────┐ │ │ │ Max: @KI fasse die Leads zusammen │ │ │ │ 🤖 KI: 3 neue Leads, 2 aus... │ │ │ │ Lisa: Super, danke! │ │ │ │ ┌──────────────────────────────┐ │ │ │ │ │ 📎 lead_report.pdf │ │ │ │ │ │ ──────────────────────────── │ │ │ │ │ │ ## 3 neue Leads │ │ │ │ │ │ - **Acme Corp** — €50k │ │ │ │ │ │ ──────────────────────────── │ │ │ │ │ │ [Öffnen] [Archivieren] │ │ │ │ │ └──────────────────────────────┘ │ │ │ └────────────────────────────────────┘ │ │ │ │ 📌 Assistent (1:1 mit KI) │ │ 🤖 47 Kontakte ohne Email... │ │ │ │ 👥 Sales Team │ │ Max: Hat jemand die Q3-Zahlen? │ │ │ │ 🔔 System │ │ 3 neue Leads importiert │ │ │ ├──────────────────────────────────────────┤ │ [📎] [Eingabefeld...] [Senden] │ └──────────────────────────────────────────┘ ``` ### Konversations-Liste (links im Panel) - Angespinnte Konversationen oben (📌) - Ungelesene-Badge pro Konversation - Letzte Nachricht mit Sender-Icon (🤖/👤/🔔) - Klick → öffnet Konversation im Feed ### Feed (Mitte) - Chronologische Nachrichten - Sender-Icon + Name pro Nachricht - Rich Content Blocks inline gerendert - Action-Cards mit Buttons - Datei-Anhänge mit Vorschau - Reaktionen (Emoji-Bar beim Hover) - Lesebestätigung (gelesen-Häkchen) ### Eingabefeld (unten) - Text-Eingabe mit Markdown-Support - Datei-Anhang Button (📎) - Mini-App Picker (/command) - @Mention Support (@KI, @Max) - Senden-Button - Kontextsensitiv: in System-Konversation → kein Eingabefeld ### Teilnehmer-Info - In Konversations-Header: Avatare aller Teilnehmer - Klick auf Avatar → Info-Popover - KI-Teilnehmer: zeigt Modell/Agent - System-Teilnehmer: zeigt Quelle ### Räume - Konversationen können benannt werden (Titel editierbar) - Anpinnen möglich (📌) - Gruppierung durch Titel, nicht durch spezielle Raum-Logik - Ein "Raum" ist einfach eine benannte Konversation --- ## EventBus Integration ### Events vom kommunikation Plugin ``` message.received → {conversation_id, message, sender_type} message.sent → {conversation_id, message} conversation.created → {conversation_id, participants, created_by} participant.joined → {conversation_id, participant_type, participant_id} participant.left → {conversation_id, participant_id} ``` ### Events die andere Plugins hören ``` # ai_assistant hört auf: message.received → prüft ob @KI erwähnt oder AI Teilnehmer → generiert Response # system_notif hört auf (vom Core-System): lead.created → erzeugt System-Nachricht contact.created → erzeugt System-Nachricht task.overdue → erzeugt System-Nachricht # whatsapp_gateway hört auf: message.received → wenn Konversation WhatsApp-Teilnehmer hat → sende extern ``` --- ## Migration ### Phase 1: Backend — Plugin `kommunikation` 1. Neue Tabellen: `comm_conversations`, `comm_participants`, `comm_messages`, `comm_message_attachments`, `comm_message_blocks`, `comm_message_reactions`, `comm_message_reads` 2. Participant Registry Interface 3. REST-API + WebSocket 4. Rich Content Block System 5. Mini-App Registry Interface ### Phase 2: Backend — Plugin `ai_assistant` anpassen 1. `ParticipantHandler` implementieren 2. Bei `kommunikation` registrieren 3. Auf `message.received` hören → AI-Response generieren 4. Streaming-Responses über WebSocket pushen 5. Alte `AIChatSession`/`AIChatMessage` behalten für Abwärtskompatibilität ### Phase 3: Backend — Plugin `system_notif` (neu) 1. `ParticipantHandler` implementieren 2. System-Events → Nachrichten in System-Konversation 3. Bestehende Notifications migrieren 4. Action-URLs als `action_card` Blocks ### Phase 4: Frontend — MessageSidebar 1. AISidebar → MessageSidebar umbauen 2. Konversations-Liste mit Pinning 3. Unified Feed mit Rich Content Rendering 4. Eingabefeld mit Datei-Upload + @Mention 5. WebSocket-Verbindung 6. Mini-App Rendering Framework ### Phase 5: Frontend — Rich Content Renderer 1. Block-Renderer: Markdown, HTML, Image, Audio, Video, File 2. Action-Card Renderer mit Button-Handler 3. Mini-App Renderer (Plugin-basiert) 4. Contact-Card, Lead-Card, etc. ### Phase 6: Messenger-Gateway Plugins (später) 1. `whatsapp_gateway` Plugin 2. `telegram_gateway` Plugin 3. `email_gateway` Plugin 4. Jeweils: ParticipantHandler + Webhook-Routes + Gateway-Adapter --- ## Technische Entscheidungen ### WebSocket vs Polling **WebSocket** — eine Verbindung pro User, pusht alle Konversationen. Grund: Real-time ist essenziell für Chat, und eine Verbindung für alles ist effizienter als Multiple Polling. ### Rich Content: Blocks vs Inline **Blocks** — separate Tabelle `comm_message_blocks` mit `block_type` + `block_data`. Grund: Erweiterbar durch Plugins, strukturiert, frontend kann unbekannte Typen graceful ignorieren. ### Mini-Apps: Plugin-basiert **Registry Pattern** — Plugins registrieren Mini-Apps bei `kommunikation`. Grund: Plugins können eigene Mini-Apps mitbringen, Frontend rendert sie dynamisch. ### Räume: Keine separate Tabelle **Titel + Pinning** — eine Konversation mit Titel ist ein Raum. Grund: Minimalistisch, keine zusätzliche Komplexität, flexibel. ### @Mention Detection **Im Backend** — `message.received` Event enthält geparste mentions. Grund: Zentrale Logik, alle Teilnehmer-Plugins bekommen saubere Daten. ### Abwärtskompatibilität **Alte Tabellen behalten** — `ai_chat_sessions`, `ai_chat_messages`, `ai_conversations`, `ai_messages` bleiben erhalten. Grund: Bestehende Daten gehen nicht verloren, Migration schrittweise. --- ## Offene Fragen 1. **Soll `kommunikation` das bestehende AI Copilot System (AIConversation/AIMessage) ersetzen oder parallel laufen?** - Vorschlag: Parallel, langfristig migrieren 2. **Datei-Speicherung:** DMS-Plugin nutzen oder eigener Speicher für Attachments? - Vorschlag: DMS-Integration, `file_path` verweist auf DMS-Dokument 3. **Berechtigungen:** Wer darf Konversationen erstellen? Wer darf Teilnehmer hinzufügen? - Vorschlag: `comm:write` für erstellen, `comm:manage` für Teilnehmer verwalten 4. **Gruppen-Chat-Limit:** Maximale Anzahl Teilnehmer? - Vorschlag: Kein Limit, Performance-Test später 5. **Nachrichten-Historie:** Endlos oder Paginierung mit Lazy-Loading? - Vorschlag: Paginierung (50 pro Seite), Lazy-Load beim Scrollen 6. **Suche:** Über alle Konversationen? Global mit unified_search Plugin? - Vorschlag: Ja, `unified_search` Provider für `kommunikation` 7. **Push-Notifications:** Browser-Notifications bei neuen Nachrichten? - Vorschlag: Ja, über Notification API + Service Worker 8. **Verschlüsselung:** E2E für bestimmte Konversationen? - Vorschlag: Nein in Phase 1, später evaluieren --- ## Zusammenfassung ``` Ein Plugin (kommunikation) → Chat-Infrastruktur + Rich Content + WebSocket Ein Interface (ParticipantHandler) → Plugins docken als Teilnehmer an Ein Datenmodell (3+Tabellen) → Konversationen, Teilnehmer, Nachrichten + Blocks Eine UI (MessageSidebar) → Ein Feed, eine Liste, ein Eingabefeld Eine WebSocket → Real-time für alles Ein EventBus → Plugins reagieren auf Nachrichten KI = Teilnehmer → @KI in jedem Chat System = Teilnehmer → Notifications als Nachrichten WhatsApp = Teilnehmer → Externe Messenger andocken Mini-Apps = Plugin-Blocks → Erweiterbar im Chat Räume = Benannte Chats → Titel + Pinning ```