Phase 1: Backend plugin kommunikation (13 files, 10 tables, REST API, WebSocket, RBAC, DMS Bridge, Participant Registry, Mini-App Registry, Search Provider) Phase 2: AI plugins as participants (ai_assistant + ai_proactive dock as participants, heartbeat job) Phase 3: system_notif plugin (system events → chat messages, pinned System room) Phase 4: Frontend MessageSidebar (replaces AISidebar, same design, comm API client, WebSocket hook, commStore) Phase 5: Rich Content Block Renderer (11 components: Markdown, HTML, Image, Audio, Video, File, ActionCard, ContactCard, MiniApp, BlockRenderer) BasePlugin: added services property + _container in on_activate
25 KiB
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:
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, MessageReactionschemas.py— Pydantic-Schemas für APIroutes.py— REST-API + WebSocketservices.py— Business Logic (Nachrichten senden, Konversationen verwalten)participant_registry.py— Registrierungs-Interface für andere Pluginscontent_types.py— Rich Content Type-Definitionenwebsocket_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.
# 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:
# 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))
# 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
ParticipantHandlerund registriert sich beikommunikation - Neu: Lauscht auf
message.receivedEvents → wenn@KIerwähnt wird oder Konversation AI als Teilnehmer hat → generiert Response - Alt: Eigene
AIChatSession/AIChatMessageTabellen bleiben für Abwärtskompatibilität, werden langfristig migriert - Neu: Schreibt Nachrichten in
kommunikation.messagesstatt nur in eigene Tabellen
4. Plugin system_notif (Neu)
Verantwortung: System-Events in Nachrichten umwandeln.
- Registriert sich als
systemTeilnehmer - Hört auf EventBus-Events (
lead.created,contact.created,task.overdue, ...) - Erzeugt Nachrichten in der System-Konversation des Users
- Notifications haben
metadata.action_urlundmetadata.severity - Bestehende Notification-Tabelle wird migriert
5. Messenger-Gateway Plugins (Später)
Jeder Messenger ist ein eigenes Plugin:
whatsapp_gateway— registriert sich alswhatsappTeilnehmertelegram_gateway— registriert sich alstelegramTeilnehmeremail_gateway— registriert sich alsemailTeilnehmer
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": "<div>...</div>"} |
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.
# 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
{
"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
- Neue Tabellen:
comm_conversations,comm_participants,comm_messages,comm_message_attachments,comm_message_blocks,comm_message_reactions,comm_message_reads - Participant Registry Interface
- REST-API + WebSocket
- Rich Content Block System
- Mini-App Registry Interface
Phase 2: Backend — Plugin ai_assistant anpassen
ParticipantHandlerimplementieren- Bei
kommunikationregistrieren - Auf
message.receivedhören → AI-Response generieren - Streaming-Responses über WebSocket pushen
- Alte
AIChatSession/AIChatMessagebehalten für Abwärtskompatibilität
Phase 3: Backend — Plugin system_notif (neu)
ParticipantHandlerimplementieren- System-Events → Nachrichten in System-Konversation
- Bestehende Notifications migrieren
- Action-URLs als
action_cardBlocks
Phase 4: Frontend — MessageSidebar
- AISidebar → MessageSidebar umbauen
- Konversations-Liste mit Pinning
- Unified Feed mit Rich Content Rendering
- Eingabefeld mit Datei-Upload + @Mention
- WebSocket-Verbindung
- Mini-App Rendering Framework
Phase 5: Frontend — Rich Content Renderer
- Block-Renderer: Markdown, HTML, Image, Audio, Video, File
- Action-Card Renderer mit Button-Handler
- Mini-App Renderer (Plugin-basiert)
- Contact-Card, Lead-Card, etc.
Phase 6: Messenger-Gateway Plugins (später)
whatsapp_gatewayPlugintelegram_gatewayPluginemail_gatewayPlugin- 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
-
Soll
kommunikationdas bestehende AI Copilot System (AIConversation/AIMessage) ersetzen oder parallel laufen?- Vorschlag: Parallel, langfristig migrieren
-
Datei-Speicherung: DMS-Plugin nutzen oder eigener Speicher für Attachments?
- Vorschlag: DMS-Integration,
file_pathverweist auf DMS-Dokument
- Vorschlag: DMS-Integration,
-
Berechtigungen: Wer darf Konversationen erstellen? Wer darf Teilnehmer hinzufügen?
- Vorschlag:
comm:writefür erstellen,comm:managefür Teilnehmer verwalten
- Vorschlag:
-
Gruppen-Chat-Limit: Maximale Anzahl Teilnehmer?
- Vorschlag: Kein Limit, Performance-Test später
-
Nachrichten-Historie: Endlos oder Paginierung mit Lazy-Loading?
- Vorschlag: Paginierung (50 pro Seite), Lazy-Load beim Scrollen
-
Suche: Über alle Konversationen? Global mit unified_search Plugin?
- Vorschlag: Ja,
unified_searchProvider fürkommunikation
- Vorschlag: Ja,
-
Push-Notifications: Browser-Notifications bei neuen Nachrichten?
- Vorschlag: Ja, über Notification API + Service Worker
-
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