Architektur
Simmetric Chat ist ein modulares TypeScript-Monorepo mit 5 Packages plus einer Tauri-Desktop-App. Das Leitprinzip ist die Trennung der Grenzen: Jedes Package hat eigene Zuständigkeiten und kommuniziert mit den anderen nur über wohldefinierte Schnittstell…
Simmetric Chat ist ein modulares TypeScript-Monorepo mit 5 Packages plus einer Tauri-Desktop-App. Das Leitprinzip ist die Trennung der Grenzen: Jedes Package hat eigene Zuständigkeiten und kommuniziert mit den anderen nur über wohldefinierte Schnittstellen (HTTP oder das shared-Package).
Überblick
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ frontend │ │ widget │ │ src-tauri │
│ React 19 │ │ Preact IIFE │ │ Desktop app │
│ Vite :5173 │ │ Express:3211│ │ │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ /api (JWT) │ /api/internal/widget (X-Api-Key) │
│ │ │
▼ ▼ │
┌────────────────────────────────────┐ │
│ server │◄──── Tauri nutzt den ─┘
│ Express :3000 (RBAC, agent, RAG) │ Server via HTTP
└──────┬──────────────────┬──────────┘
│ HTTP (COLLECTOR_SECRET) │ HTTP (status callback)
▼ ▼
┌──────────────┐ ┌──────────────┐
│ collector │ │ PostgreSQL │
│ Express:3210│ │ (Prisma) │
│ parse/embed │ └──────────────┘
└──────────────┘
│
▼
┌──────────────────────────┐
│ Vector DB (LanceDB/ │
│ Qdrant) + LLM (Ollama/ │
│ OpenAI/Anthropic) │
└──────────────────────────┘Die 5 Packages
shared - der Leaf-Node
Typen, Zod-Schemas und Konstanten. Null Business Logic, einzige Runtime-Abhängigkeit zod. Es wird von server, collector und frontend importiert. Nie aus anderen Packages importieren: Die Richtung ist immer * → shared. Das garantiert die Turborepo-Build-Reihenfolge und verhindert Zyklen.
src/types/- Entitäts-Interfaces (User, Workspace, Chat, Document, Provider, Widget, BackupDestination, Archive, OcrJob, SynthesisRun, ...).src/schemas/- Zod (auth, chat, document, widget, mcpConnection, license, backup, archive, wiki, ocr, synthesis, provider). Ein Schema pro Domäne, Typinferenz überz.infer.src/constants/-permissions.ts(31 RBAC-Berechtigungen),license.ts(11 Enterprise-Feature-Flags; die 8 Commodity-Flags sind in Community immer aktiv).
server - das Herz (Express :3000)
REST-API + RBAC + Agent-Orchestrator. Siehe packages/server/CLAUDE.md.
src/routes/- eine Datei pro Domäne (auth, users, roles, projects, workspaces, documents, agent, chat, providers, apiKeys, eventLogs, analytics, backups, backupJobs, backupDestinations, webhooks, push, license, sso, templates, settings, mcpConnections, marketplace, widgets, internalWidget, health, ocr, archives, wiki, synthesis).src/middleware/-auth.ts(JWT + API-Key),rbac.ts(Berechtigungen + IDOR-Prävention),rateLimit.ts(200/min allgemein, 10/min auth, 10/min probe - kein dedizierter Chat-Limiter: Der Agent erzwingt Nebenläufigkeit/Budget/Timeout intern),license.ts(Feature-Flag-Gating),widgetCors.ts(dynamisches CORS pro Origin).src/services/- authService, providerService (Modell-Auflösung + Capability), hybridSearchService (RRF), ftsService (tsvector), encryptionService (AES-256-GCM), backupSchedulerService (Bree), eventLogService (+ Webhook-Dispatch), dlpFilter usw.src/agent/-orchestrator.ts(ReAct-Loop),builtinSkills.ts(rag_search, workspace_memory, document_temp_process),llmStreaming.ts(Ollama/OpenAI/Anthropic),mcpClient.ts(ausgehende MCP-Verbindungen),mcpServer.ts(RAG als MCP-Tool für IDEs).src/config/env.ts- Zod-Schema für alle Env-Variablen;process.exit(1)bei ungültigen Werten.
collector - Ingestion-Microservice (Express :3210)
Er spricht mit dem Server nur über HTTP (Secret COLLECTOR_SECRET). Er importiert kein Prisma und keine Server-Services: kritische Grenze für das Air-Gap-Deployment auf getrennten Knoten.
src/routes/ingest.ts- Upload (multer), query, youtube, delete.src/services/- parser (PDF/DOCX/PPTX/XLSX/TXT/CSV/YouTube), chunker (RecursiveCharacterTextSplitter), embeddings (Xenova lokal / OpenAI), vectorStore (LanceDB / Qdrant).- Ablauf: server → collector
POST /api/ingest/upload→ parse → chunk → embed → store → Collector-CallbackPUT /api/documents/:id/status→ der Server aktualisiert die DB.
widget - einbettbarer Chat (Express :3211)
Express CommonJS + Preact-IIFE-Bundle. Authentifiziert sich am Server über X-Api-Key.
src/index.ts- FactorycreateApp(); statisches/widget/app.js+ Loader-Routes + API.src/routes/- chat (SSE-Proxy), session, config, loader, lead.src/widget/- Preact-Komponenten (12 Komponenten) in einem sandboxed iframe.- Transparenter SSE-Proxy: Relay roher Bytes server→iframe, null Parsing.
frontend - SPA (Vite + React 19 :5173)
Kein Next.js. Proxy /api → Server.
- 3-Tier-State-Management: TanStack Query (REST/CRUD), React Context (UI-State), fetchEventSource + useState/useRef (SSE, NICHT TanStack Query).
src/queries/(22 Hooks),src/contexts/,src/hooks/useChat.ts(Streaming).- i18n: 8 Sprachen (en, it, ru, de, fr, es, zh, pt).
src-tauri - Desktop-App
Tauri-Wrapper, der das Frontend als Desktop-App paketiert; kommuniziert per HTTP mit dem Server (dieselbe API). Gedacht für Air-Gap-Deployment auf Maschinen ohne Browser.
Zentrale Data Flows
Chat-Streaming (interner Nutzer)
Frontend useChat → POST /api/workspaces/:id/chat/stream (SSE)
→ requireWorkspaceAccess (RBAC/IDOR)
→ agent orchestrator (ReAct loop)
├─ resolveSkillsForChat (builtin + MCP pinned)
├─ rag_search (vector + tsvector → RRF merge) se serve
├─ llmStreaming (provider risolto: per-chat → workspace → global → ENV)
└─ tool execution (sandboxed)
→ SSE: token | status | citations | done(modelUsed,providerUsed,mcpSources) | errorModell-Auflösung: Per-Chat-Override → Workspace-Default → Global Default → ENV. Graceful Fallback auf 3 Ebenen, wenn das Modell nicht verfügbar ist.
Dokument-Ingestion
Frontend dropzone → POST /api/documents (server, multer)
→ server salva Document(status=pending) + forwarda file a collector
→ collector: parse → chunk → embed → store(vector) → ritorna chunks con text
→ collector callback PUT /api/documents/:id/status { status, chunkCount }
→ server scrive document_chunks + popola searchVector (tsvector) lato DB
→ frontend polla stato fino a completedWidget-Chat (anonymer Besucher)
Pagina host → <script src="/widget/:id.js"> (loader)
→ iframe sandboxed → Preact app
→ POST /api/sessions (widget) → server crea WidgetSession (256-bit token, 24h)
→ GET /api/config/:widgetId (branding, trigger, lead)
→ user messaggio → POST /api/chat/:widgetId/stream (widget, x-session-token)
→ sessionMiddleware valida → pre-search RAG (whitelist workspaces, IDOR-safe)
→ proxy a server /api/workspaces/:id/chat/stream (X-Api-Key)
→ relay SSE byte verbatim → Preact parse token/citations/done/errorGrenzen und Architekturregeln
- `shared` ist Leaf - keine Importe in andere Packages, auch kein
import type. - Server ↔ Collector = nur HTTP - kein Runtime-Import über Packages hinweg; der Collector hat keinen direkten DB-Zugriff. Das erlaubt es, den Collector auf getrennten Knoten zu skalieren.
- Widget = separater Service - teilt keinen Code mit dem React-Frontend; nutzt Preact für ein kleines Bundle im sandboxed iframe.
- Prisma-Singleton - immer aus
src/utils/prismaimportieren, nienew PrismaClient(). - Universelles Soft Delete - löschbare Entitäten verwenden
deletedAt; alle Queries filternwhere: { deletedAt: null }. - Config Resolution -
ALWAYS_READONLY(JWT_SECRET, DATABASE_URL, Ports, URLs):ENV > Default. Alle anderen Keys:DB > ENV > Default(über die UI änderbar).
Die Architektur erweitern
- Neue Entität/Endpoint → Zod-Schema in
sharedergänzen, Prisma-Modell, Route inserver/src/routes/, TanStack-Hook infrontend/src/queries/. - Neue Agent-Skill → in
server/src/agent/builtinSkills.tsregistrieren (sandboxed, keine beliebige Code-Ausführung). - Neue Sprache →
frontend/src/i18n/<lang>/translation.jsonhinzufügen +i18n-check.cjsaktualisieren (siehe 10 - Development). - Neuer LLM-Provider →
providerTypeSchemainsharederweitern + Handler inserver/src/agent/llmStreaming.ts+providerService.
Cross-Link
- Env-/Config-Details: 03 - Configuration
- Endpoints pro Domäne: 04 - API Reference
- Feature-Internals: 05 - Feature Guide