第 1 章:开篇 —— Jan 为什么值得拆解¶
Jan 的核心问题不是“怎样显示一个聊天气泡”,而是“怎样把本地模型、云端模型、工具和隐私边界装进一个跨平台产品”。
一句话定义¶
Jan 是一个本地优先、可接入云端 Provider、支持桌面与移动端的开源 AI 聊天应用。它同时承担三种身份:
- 产品:聊天、助手、模型管理、设置、项目和附件。
- 运行时:启动/停止推理后端,维护 MCP 服务,暴露本地 OpenAI 兼容 API。
- 平台:通过 Core 类型、Extension 抽象和 Tauri plugin guest API 让能力可以替换。
用户看到的能力,对应什么源码¶
| 产品能力 | 主要实现 |
|---|---|
| 聊天和流式输出 | web-app/src/routes/threads/$threadId.tsx、use-chat.ts、custom-chat-transport.ts |
| 模型选择与参数 | model-factory.ts、useModelProvider、predefinedParams.ts |
| 本地 GGUF 模型 | extensions/llamacpp-extension + src-tauri/plugins/tauri-plugin-llamacpp |
| Apple Silicon MLX | extensions/mlx-extension + macOS 条件编译的 MLX plugin |
| 云端 Provider | TypeScript Provider SDK + Rust server/proxy.rs 与 converters |
| 助手 | extensions/assistant-extension,磁盘上的 assistants/*/assistant.json |
| 历史对话 | extensions/conversational-extension → Tauri threads commands |
| MCP 工具 | src-tauri/src/core/mcp + web-app/src/lib/mcp-orchestrator |
| RAG/向量检索 | rag-extension、vector-db-extension + 两个 Tauri plugin |
| 外部客户端接入 | Rust API server,默认产品文档宣传 localhost:1337 |
一张图看懂四个层次¶
flowchart TB
subgraph Product[产品层]
UI[React + TanStack Router]
State[Zustand stores + hooks]
Chat[AI SDK Chat + CustomChatTransport]
end
subgraph SDK[共享协议层]
Core[TypeScript Core]
Types[Thread / Message / Model / Extension types]
Ext[ExtensionManager + AIEngine]
end
subgraph Host[Rust 宿主层]
Cmd[Tauri commands]
State2[AppState]
Services[threads / server / mcp / downloads]
end
subgraph Runtime[执行时层]
Local[llama.cpp / MLX]
Remote[remote providers]
Tools[MCP / RAG / Web Search]
Store[files / SQLite / keyring]
end
UI --> State --> Chat --> Core --> Cmd --> State2
Types -. shared shapes .- Core
Ext --> Local
State2 --> Services
Services --> Local
Services --> Remote
Services --> Tools
Services --> Store
最重要的架构事实¶
1. WebView 不是“薄 UI”¶
WebView 里有大量业务编排:模型工厂、参数过滤、上下文裁剪、工具 schema 修复、会话级 chat 对象、分支消息和附件处理都在 TypeScript 中。这让 UI 响应快、迭代快,也意味着 Web 层承担了相当多的 AI 运行时知识。
2. Rust 不是“后台 API”那么简单¶
Rust 负责系统边界:进程、文件、密钥、端口、MCP 子进程、跨平台条件编译、退出清理和本地服务器。AppState 是这些长生命周期资源的共享容器。
3. Extension 是运行时的“插座”¶
@janhq/core 定义 BaseExtension、AIEngine、ConversationalExtension 等协议;具体能力在 extensions/* 实现。它不是微服务,也不是完全动态的插件沙箱,而是“可注册的同进程模块 + Tauri plugin 支持的系统能力”。
这套设计的代价¶
- 同一个概念可能同时出现在 TypeScript 类型、前端 store、Tauri command 参数、Rust serde struct 和 plugin guest API 中。
- 一个请求要跨 WebView、IPC、Rust、子进程或网络,错误信息必须经过多层翻译。
- “本地优先”和“云端兼容”导致参数能力矩阵很大:
top_k对 llama.cpp 有意义,对 OpenAI 可能是非法字段。 - 为了保持产品体验,系统有大量迁移、回退、重连、清理和兼容代码;这些才是 Jan 的工程核心。