返回文档
指南 02

架构

Simmetric Chat 是一个模块化的 TypeScript monorepo,包含 5 个 package 以及一个 Tauri 桌面应用。指导原则是边界分离:每个 package 都有各自独立的职责,并且只通过定义良好的接口(HTTP 或 shared package)与其他 package 通信。

4 分钟阅读

Simmetric Chat 是一个模块化的 TypeScript monorepo,包含 5 个 package 以及一个 Tauri 桌面应用。指导原则是边界分离:每个 package 都有各自独立的职责,并且只通过定义良好的接口(HTTP 或 shared package)与其他 package 通信。

总览

text
┌──────────────┐   ┌──────────────┐   ┌──────────────┐
│   frontend   │   │    widget    │   │  src-tauri   │
│  React 19    │   │  Preact IIFE │   │  Desktop app │
│  Vite :5173  │   │  Express:3211│   │              │
└──────┬───────┘   └──────┬───────┘   └──────┬───────┘
    │  /api (JWT)     │  /api/internal/widget (X-Api-Key) │
    │                 │                                  │
    ▼                 ▼                                  │
┌────────────────────────────────────┐                      │
│              server                │◄──── Tauri usa il ────┘
│  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)       │
└──────────────────────────┘

5 个 package

shared - 叶子节点

类型、Zod schema 和常量。零业务逻辑,唯一的运行时依赖是 zod。它被 server、collector、frontend 导入。绝不从其他 package 导入:方向始终是 * → shared。这保证了 Turborepo 的构建顺序并防止出现循环。

  • src/types/ - 实体接口(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)。每个领域一个 schema,使用 z.infer 进行类型推断。
  • src/constants/ - permissions.ts(31 个 RBAC 权限)、license.ts(11 个 enterprise feature flag;8 个 commodity flag 在 Community 中始终启用)。

server - 核心(Express :3000)

REST API + RBAC + agent 编排器。参见 packages/server/CLAUDE.md。

  • src/routes/ - 每个领域一个文件(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(权限 + 防 IDOR)、rateLimit.ts(200/min 通用、10/min auth、10/min probe - 没有专门的聊天限流器:agent 在内部施加并发/预算/超时限制)、license.ts(feature flag gating)、widgetCors.ts(按 origin 的动态 CORS)。
  • src/services/ - authService、providerService(模型解析 + capability)、hybridSearchService(RRF)、ftsService(tsvector)、encryptionService(AES-256-GCM)、backupSchedulerService(Bree)、eventLogService(+ webhook 分发)、dlpFilter 等。
  • src/agent/ - orchestrator.ts(ReAct 循环)、builtinSkills.ts(rag_search、workspace_memory、document_temp_process)、llmStreaming.ts(Ollama/OpenAI/Anthropic)、mcpClient.ts(出站 MCP 连接)、mcpServer.ts(将 RAG 作为 MCP tool 提供给 IDE)。
  • src/config/env.ts - 所有 env var 的 Zod schema;无效时 process.exit(1)。

collector - 摄取微服务(Express :3210)

仅通过 HTTP 与服务器通信(密钥 COLLECTOR_SECRET)。不导入 Prisma,也不导入服务器服务:这是 air-gap 部署到独立节点的关键边界。

  • src/routes/ingest.ts - 上传(multer)、query、youtube、delete。
  • src/services/ - parser(PDF/DOCX/PPTX/XLSX/TXT/CSV/YouTube)、chunker(RecursiveCharacterTextSplitter)、embeddings(本地 Xenova / OpenAI)、vectorStore(LanceDB / Qdrant)。
  • 流程:server → collector POST /api/ingest/upload → parse → chunk → embed → store → collector 回调 PUT /api/documents/:id/status → 服务器更新 DB。

widget - 可嵌入聊天(Express :3211)

Express CommonJS + Preact IIFE bundle。通过 X-Api-Key 向服务器进行身份验证。

  • src/index.ts - createApp() 工厂;static /widget/app.js + loader routes + API。
  • src/routes/ - chat(SSE 代理)、session、config、loader、lead。
  • src/widget/ - sandboxed iframe 中的 Preact 组件(12 个组件)。
  • 透明 SSE 代理:server→iframe 的原始字节中继,零解析。

frontend - SPA(Vite + React 19 :5173)

没有 Next.js。代理 /api → server。

  • 三层 state 管理:TanStack Query(REST/CRUD)、React Context(UI 状态)、fetchEventSource + useState/useRef(SSE,不是 TanStack Query)。
  • src/queries/(22 个 hook)、src/contexts/、src/hooks/useChat.ts(流式)。
  • i18n:8 种语言(en、it、ru、de、fr、es、zh、pt)。

src-tauri - 桌面应用

Tauri wrapper,将前端打包为桌面应用;通过 HTTP 与服务器通信(同一套 API)。专为没有浏览器的机器上的 air-gap 部署而设计。

主要 Data Flow

聊天流式(内部用户)

text
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) | error

模型解析:per-chat override → workspace default → global default → ENV。如果模型不可用,则进行 3 级优雅回退。

文档摄取

text
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 completed

Widget 聊天(匿名访客)

text
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/error

边界与架构规则

  1. `shared` 是叶子 - 不向其他 package 导入,连 import type 也不行。
  2. Server ↔ collector = 仅 HTTP - 没有跨 package 的运行时导入;collector 不能直接访问 DB。允许将 collector 扩展到独立节点。
  3. Widget = 独立服务 - 不与 React 前端共享代码;使用 Preact 以在 sandboxed iframe 中保持小体积 bundle。
  4. Prisma singleton - 始终从 src/utils/prisma 导入,绝不 new PrismaClient()。
  5. 通用软删除 - 可删除实体使用 deletedAt;所有查询过滤 where: { deletedAt: null }。
  6. Config resolution - ALWAYS_READONLY(JWT_SECRET、DATABASE_URL、端口、URL):ENV > Default。所有其他键:DB > ENV > Default(可从 UI 修改)。

扩展架构

  • 新实体/endpoint → 在 shared 中添加 Zod schema、Prisma 模型、server/src/routes/ 中的路由、frontend/src/queries/ 中的 TanStack hook。
  • 新 agent skill → 在 server/src/agent/builtinSkills.ts 中注册(sandboxed,不执行任意代码)。
  • 新语言 → 添加 frontend/src/i18n/<lang>/translation.json + 更新 i18n-check.cjs(见 10 - Development)。
  • 新 LLM provider → 扩展 shared 中的 providerTypeSchema + server/src/agent/llmStreaming.ts 中的 handler + providerService。

交叉链接