痛点:AI 对话是一次性的
作为一个重度 AI 编程用户,我每天和 Claude Code、Cursor、Codex CLI 的对话少则十几轮,多则上百轮。解决了一个棘手的 bug、搞清楚了一个架构方案、摸索出了一套部署流程……然后呢?
关掉终端,这些知识就消失了。
下次遇到类似问题,我发现自己在做同样的事:重新问一遍 AI、重新搜索一遍 Google。明明三天前刚解决过,但就是找不到当时的对话了。
Claude Code 的对话散落在 ~/.claude/projects/ 下几百个 JSONL 文件里;Cursor 的藏在 SQLite 数据库深处;Codex CLI 的又是另一套格式。手动翻?不现实。
我做了什么
所以我写了 ChatCrystal —— 一个把 AI 对话「结晶」成个人知识库的桌面工具。
核心思路很简单:
散落的 AI 对话 → 统一采集 → LLM 提炼 → 结构化笔记 → 语义搜索 + 知识图谱
它能做什么
1. 多数据源自动采集
支持 Claude Code、Codex CLI、Cursor 三种数据源,自动扫描并导入对话记录。基于 chokidar 实现文件监听,新对话产生后实时同步,不用手动操作。
2. LLM 智能摘要
通过 Vercel AI SDK 接入你手头的任意 LLM,将一段冗长的对话提炼为结构化笔记:
- 标题和摘要
- 关键结论(一句话总结核心收获)
- 代码片段(提取对话中的关键代码)
- 自动标签
3. 语义搜索
不再靠关键词碰运气。基于 Embedding 向量索引(vectra),按语义相关度检索笔记。「那次解决内存泄漏的方案」—— 搜「memory leak cleanup」就能找到,即使笔记里写的是中文。
4. 知识图谱
这是我觉得最有意思的功能。LLM 会自动分析笔记之间的关系——因果、依赖、引用、扩展等 8 种关系类型,用力导向图可视化呈现。
比如你会发现:「登录失败的 bug」是由「Token 过期逻辑重构」引起的,而那次重构又依赖于「Auth 中间件改造」。这些隐藏在时间线里的知识脉络,图谱帮你串起来了。
技术实现
作为技术文章,聊聊架构和实现细节。
整体架构
ChatCrystal/
├── electron/ # Electron 主进程
├── shared/types/ # 共享 TypeScript 类型
├── server/src/ # Fastify 后端
│ ├── parser/ # 插件式数据源适配器
│ ├── services/ # 核心服务(导入、摘要、Embedding、关系发现)
│ ├── routes/ # REST API
│ └── queue/ # p-queue 任务队列
├── client/src/ # React SPA
└── data/ # 运行时数据
| 层 | 技术选型 |
|---|---|
| 后端 | Node.js + Fastify v5 + TypeScript |
| 前端 | Vite v8 + React 19 + Tailwind CSS v4 + TanStack Query v5 |
| 桌面 | Electron + electron-builder |
| 数据库 | sql.js(WASM SQLite,零依赖) |
| LLM | Vercel AI SDK v6 |
| 向量搜索 | vectra |
数据源适配:插件式设计
不同 AI 工具的数据格式完全不同,我抽象了一个 SourceAdapter 接口:
interface SourceAdapter {
name: string;
displayName: string;
detect(): Promise<SourceInfo | null>;
scan(): Promise<ConversationMeta[]>;
parse(meta: ConversationMeta): Promise<ParsedConversation>;
}
目前三个内置适配器:
| 适配器 | 数据格式 | 难点 |
|---|---|---|
| Claude Code | ~/.claude/projects/**/*.jsonl | 需要过滤 <system-reminder> 等系统标签噪音 |
| Codex CLI | ~/.codex/sessions/**/*.jsonl | 事件流格式,需从 event 重建对话 |
| Cursor | workspaceStorage/state.vscdb | SQLite KV 存储,需解析 composer 元数据 |
想接入新数据源?实现这个接口就行,放到 server/src/parser/adapters/ 下注册即可。
摘要生成:截断 + 结构化提取
AI 对话动辄几万 token,直接丢给 LLM 不现实。我的做法是:
- 预处理截断:保留开头和结尾,中间按重要度采样,控制在 ~8000 token
- 结构化 Prompt:要求 LLM 输出严格 JSON 格式(标题、摘要、结论、代码、标签)
- 后处理:正则提取 JSON,容错处理 LLM 输出不规范的情况
- 自动触发 Embedding:摘要生成后自动生成向量索引,无需手动操作
语义搜索:分块 + 向量检索 + 关系扩展
Embedding 部分我用了 vectra(纯 JS 的本地向量索引),实现思路:
笔记文本 → 按段落分块(500字/块) → Embedding → vectra 索引
↓
搜索查询 → Embedding → vectra.queryItems → 去重 → 沿关系边扩展
关系扩展是亮点:搜到一条笔记后,会沿知识图谱的边找到关联笔记,用折扣系数(原始分数 × 0.7 × 置信度)加入结果。这样搜「部署失败」可能还会带出「Docker 镜像优化」的笔记——因为它们有依赖关系。
多 Provider 支持
通过 Vercel AI SDK v6 统一了 LLM 调用接口,支持运行时热切换:
- Ollama(本地推理,免费,隐私友好)
- OpenAI / Anthropic / Google AI / Azure OpenAI
- 任意 OpenAI 兼容服务(OpenRouter、Poe 等)
⚠️ 一个踩坑点:LLM 和 Embedding 必须分开配置。大语言模型(Claude、GPT-4)不能当 Embedding 模型用,它们不支持
/v1/embeddings端点。Embedding 得用专用模型,比如nomic-embed-text、text-embedding-3-small。
任务队列
批量摘要和 Embedding 生成通过 p-queue 排队执行(并发=1,限速 1 req/s),避免打爆 LLM 服务。支持实时进度追踪和取消操作,前端通过轮询 /api/queue/status 展示进度条。
快速上手
最简方案:Ollama 本地运行
# 1. 安装 Ollama 并拉取模型
ollama pull qwen2.5:7b # 用于摘要
ollama pull nomic-embed-text # 用于 Embedding
# 2. 克隆并启动
git clone https://github.com/ZengLiangYi/ChatCrystal.git
cd ChatCrystal
npm install
cp .env.example .env
npm run dev:electron # 桌面应用开发模式
用云端 API
在 .env 或设置页面配置即可:
# 用 OpenAI
LLM_PROVIDER=openai
LLM_API_KEY=sk-...
LLM_MODEL=gpt-4o
EMBEDDING_PROVIDER=openai
EMBEDDING_API_KEY=sk-...
EMBEDDING_MODEL=text-embedding-3-small
启动后的操作流程:
- 点击侧边栏「导入对话」→ 自动扫描本地 AI 对话
- 「批量生成」→ LLM 把对话提炼为笔记
- 「搜索」页 → 按语义找到你需要的知识
- 「图谱」页 → 可视化知识之间的关联
为什么是本地优先
ChatCrystal 所有数据都存在本地(SQLite + 文件系统),不上传任何对话内容到第三方服务(除了你自己配置的 LLM Provider)。
对于经常和 AI 讨论公司代码、内部架构的开发者来说,这一点很重要。配合 Ollama 使用,整个流程可以完全离线运行。
后续规划
- macOS / Linux 支持
- 更多数据源适配器(ChatGPT、Windsurf 等)
- 笔记编辑和手动创建
- 导出为 Markdown / Obsidian vault
- 对话内嵌搜索(直接在对话中查找历史知识)
总结
如果你也是 Claude Code / Cursor / Codex 的重度用户,应该能感受到这个痛点——和 AI 的对话产生了大量有价值的知识,但它们散落在各处,无法复用。
ChatCrystal 要做的就是把这些知识「结晶」下来,变成可搜索、可关联的个人知识库。
项目完全开源,欢迎 Star 和 PR:
👉 GitHub: ZengLiangYi/ChatCrystal
有问题或建议欢迎在 Issue 区交流,中英文都可以。