基于Next.js构建本地AI编程助手聊天聚合器:隐私优先的数据整合方案
1. 项目概述:一个本地优先的AI编程助手聊天记录聚合器
如果你和我一样,日常重度依赖像 Claude Code 和 Cursor 这样的 AI 编程助手,那你肯定也遇到过这个痛点:想找上周和 Claude 讨论的那个数据库优化方案,或者昨天在 Cursor 里调试的某个 React 组件逻辑,结果发现聊天记录散落在两个不同的应用里,翻找起来极其麻烦。更别提想全局搜索某个关键词,看看自己之前是怎么解决类似问题的了。这就是我动手开发 Chat Recall 的初衷——一个完全运行在你本地机器上的工具,帮你把 Claude Code 和 Cursor 的聊天历史聚合到一个统一的界面里,支持搜索、按项目分组,甚至还能生成每周的工作总结。
这个项目的核心设计哲学是 “离线优先” 和 “隐私至上” 。你的所有聊天数据都只存在于你的电脑上,Chat Recall 只是作为一个本地的“阅读器”,直接读取这些应用存储在磁盘上的历史文件。这意味着没有网络请求,没有数据上传到任何云端服务器,也没有任何形式的遥测或分析。对于处理代码、设计思路甚至可能包含敏感信息的对话来说,这种本地化处理带来的安全感是无价的。整个技术栈基于 Next.js 16 的 App Router 和 React Server Components,配合 TypeScript 确保类型安全,UI 则选用了 Material-UI 来快速搭建一个清晰可用的界面。
2. 核心需求解析与设计思路
2.1 为什么需要这样一个工具?
在深入代码之前,我们先聊聊需求。现代 AI 编程助手已经成为开发者工作流中不可或缺的一部分,但它们各自为政的数据存储方式带来了几个显著问题:
信息孤岛与检索困难 :Claude Code 和 Cursor 将聊天记录分别存储在自己的私有目录下,格式也互不兼容。当你试图回忆一个模糊的解决方案时,不得不在两个应用间来回切换、手动翻找,效率极低。
缺乏上下文关联与知识沉淀 :我们与 AI 的对话往往围绕特定项目展开。但这些对话被淹没在按时间排序的线性列表中,无法与项目文件、代码仓库形成有效关联。宝贵的讨论过程和决策依据难以沉淀为可复用的团队知识。
隐私与数据主权的考量 :作为开发者,我们与 AI 讨论的代码、业务逻辑、甚至未公开的产品设计,都可能包含敏感信息。将这些对话历史无条件信任给第三方云服务或未经审视的本地工具,存在潜在的数据泄露风险。
Chat Recall 的目标,就是成为你本地工作环境中的一个“中枢神经系统”,在不牺牲隐私和安全的前提下,解决上述痛点。它不生产数据,只是数据的搬运工和整理者。
2.2 架构选型背后的“为什么”
面对这个需求,技术选型上我做了几个关键决策,每一个都经过了仔细的权衡。
为什么选择 Next.js 16 和 App Router? 首先,这是一个典型的富交互前端应用,需要良好的路由、状态管理和构建体验。Next.js 提供了开箱即用的解决方案。更重要的是,我选择了 App Router 而非旧的 Pages Router,并大量使用了 React Server Components 。这是本项目架构的核心。因为所有数据读取(访问本地 SQLite 数据库、解析 JSON 文件)都是 I/O 密集型的同步操作,且完全在本地进行,没有网络延迟。将这些逻辑放在 Server Components 中执行,可以:
- 避免将敏感的本地文件路径或原始数据发送到客户端,提升了安全性。
- 直接生成静态的 HTML,减少了客户端 JavaScript 的包体积,提升了初始加载速度。
- 简化了数据获取逻辑,在组件内直接使用
fs、better-sqlite3等 Node.js API,代码更直观。
为什么前端用 MUI (Material-UI)? 对于一个工具类应用,UI 的清晰、一致和快速搭建比独特的视觉设计更重要。MUI 提供了丰富、成熟且可访问性良好的组件库,让我能快速构建出包含列表、搜索框、折叠面板、日期选择器等复杂交互的界面,而无需从零开始编写样式和交互逻辑。这能将开发精力集中在核心的数据处理和业务逻辑上。
为什么用 better-sqlite3 读取 Cursor 数据? 经过逆向工程(后面会详细讲),我发现 Cursor 将聊天记录存储在一个本地的 SQLite 数据库中。在 Node.js 环境中操作 SQLite, better-sqlite3 是性能最高、最稳定的选择之一。它提供了同步 API,这对于在 Server Component 中直接读取数据非常合适,避免了不必要的异步复杂度。
为什么构建工具是 pnpm? 纯粹是个人偏好和项目一致性。pnpm 的磁盘空间效率和速度优势在 monorepo 或依赖较多的项目中很明显。本项目依赖不算特别多,但用 pnpm 能确保依赖树清晰,安装速度快。
3. 数据源解析与本地文件读取实战
这是整个项目最基础也是最关键的一环:如何找到并正确解析 Claude Code 和 Cursor 存储在本地磁盘上的聊天数据。如果这一步出错,后面所有功能都是空中楼阁。
3.1 定位数据存储路径
不同的操作系统和应用程序有不同的数据存储规范。Chat Recall 需要兼容 macOS、Windows 和 Linux。
Claude Code 的数据路径 Claude Code(基于 VS Code)遵循常规的配置存储模式。在类 Unix 系统(macOS, Linux)上,数据通常位于用户主目录下的隐藏文件夹中。
# macOS / Linux
~/.claude/projects/
在 Windows 系统上,路径则位于 %APPDATA% 目录下,对应为:
# Windows
C:\Users\<YourUsername>\AppData\Roaming\claude\projects\
注意 :路径中的
~代表当前用户的主目录。在代码中,我们可以使用 Node.js 的os.homedir()方法来动态获取这个路径,从而实现跨平台兼容。
在这个 projects 目录下,每个你打开过的项目(或文件夹)都会有一个以项目路径哈希值命名的子文件夹。里面就存放着该项目的聊天记录 JSON 文件。
Cursor 的数据路径 Cursor 的数据存储位置更依赖于操作系统的“应用程序支持”目录。
- macOS :
~/Library/Application Support/Cursor/User/globalStorage/state.vscdb - Windows :
%APPDATA%\Cursor\User\globalStorage\state.vscdb - Linux :
~/.config/Cursor/User/globalStorage/state.vscdb
关键点在于,Cursor 将大量状态信息(包括聊天记录)存储在一个名为 state.vscdb 的 SQLite 数据库文件 中,而不是明文的 JSON 文件。这增加了读取的复杂度。
3.2 解析数据格式与结构
找到文件只是第一步,理解其内部结构才能提取出有用的对话信息。
解析 Claude Code 的 JSON 文件 Claude Code 的聊天记录以相对友好的 JSON 格式存储。每个项目文件夹下可能有多个以时间戳命名的 .json 文件。我们需要遍历这些文件,解析其结构。一个典型的文件内容骨架如下:
{
"chatSessions": [
{
"id": "session_123",
"createdAt": "2024-01-01T10:00:00Z",
"title": "讨论用户登录逻辑",
"messages": [
{
"role": "user",
"content": "如何实现一个安全的 JWT 认证流程?",
"timestamp": "..."
},
{
"role": "assistant",
"content": "一个典型的 JWT 流程包括...",
"timestamp": "..."
}
],
"projectPath": "/Users/me/code/my-app"
}
]
}
我们的解析逻辑需要:
- 递归遍历
~/.claude/projects/下的所有子目录。 - 找到所有
.json文件。 - 读取并解析 JSON 内容。
- 从
chatSessions中提取出每一条对话的元数据(ID、时间、标题)和消息内容。 - 将项目路径从哈希值映射回可读的路径(如果可能)。
逆向工程 Cursor 的 SQLite 数据库 这是更有挑战性的一部分。 state.vscdb 是一个二进制数据库文件,我们需要用 better-sqlite3 打开它并查询正确的表。
首先,通过 SQLite 命令行工具或 DB Browser for SQLite 这样的 GUI 工具打开数据库文件,探索其结构。经过分析,我发现聊天记录主要存储在 ItemTable 这个表中,其中 key 字段标识数据类型, value 字段存储了经过压缩或编码的实际内容。
一个关键的 key 值可能是 chat.sessions 。其对应的 value 是一个经过 JSON 编码的字符串,里面包含了所有聊天会话的列表。解析流程如下:
- 使用
better-sqlite3建立数据库连接。 - 执行 SQL 查询:
SELECT value FROM ItemTable WHERE key = 'chat.sessions'。 - 将查询结果的
value字段(一个 Buffer)转换为字符串。 - 解析这个 JSON 字符串,其结构可能是一个数组,包含了每个会话的 ID、时间戳、消息列表等信息。
- 消息内容本身可能被进一步编码或压缩,需要额外的解码步骤。
实操心得 :Cursor 的数据格式可能随着版本更新而变化。在代码中,必须对 JSON 解析、字段访问做好异常处理(try-catch),并考虑在无法识别新格式时提供友好的错误提示,而不是让整个应用崩溃。可以增加一个“数据版本检测”的逻辑,在启动时检查并告知用户当前是否支持其 Cursor 版本。
3.3 实现统一的数据模型与读取服务
为了给前端提供一致的数据,我们需要将来自两个不同源的数据,转换成一个统一的内部模型。我定义了一个 ChatSession 接口:
interface ChatSession {
id: string; // 全局唯一ID,可用源ID+会话ID组合生成
source: 'claude' | 'cursor'; // 数据来源
title: string; // 会话标题,可从前几条消息自动生成
preview: string; // 内容预览(如第一条消息)
projectName: string; // 项目名称,从路径提取
projectPath: string; // 项目完整路径
timestamp: Date; // 会话最后活动时间
messageCount: number; // 消息总数
// 如果需要完整渲染,可以包含完整的消息数组
// messages: ChatMessage[];
}
然后,创建两个数据服务类: ClaudeDataService 和 CursorDataService 。它们分别实现一个共同的接口 IChatDataService ,提供 getAllSessions(): Promise<ChatSession[]> 等方法。
在 Next.js 的 Server Component 中,我们可以同时调用这两个服务,将结果合并、排序(例如按时间倒序),然后传递给 UI 组件进行渲染。由于这是服务端逻辑,所有文件读取和数据库操作都安全地运行在服务器环境(即你的本地机器)上,不会暴露给浏览器。
4. 核心功能实现与界面构建
有了统一的数据模型,我们就可以在此基础上构建用户界面和功能了。前端采用 Next.js App Router,页面结构清晰。
4.1 构建统一会话列表视图
主页面 ( app/page.tsx ) 是一个 Server Component,它负责在服务端获取所有聊天会话数据。
// app/page.tsx
import { getCombinedChatSessions } from '@/services/data-aggregator';
export default async function HomePage() {
// 在服务端获取数据
const sessions = await getCombinedChatSessions();
// 将数据传递给客户端组件进行渲染
return <SessionListView initialSessions={sessions} />;
}
SessionListView 是一个客户端组件,它接收会话数组,并使用 MUI 的 List 、 ListItem 等组件来渲染一个可交互的列表。每个列表项显示会话标题、项目名称、时间戳和内容预览。
关键实现细节:虚拟滚动 如果用户积累了大量的聊天记录(比如上千条),一次性渲染所有列表项会导致页面卡顿。为了解决这个问题,我引入了 虚拟滚动 。我选择了 react-virtuoso 这个库,它只渲染当前视窗内可见的列表项,无论数据量多大,都能保持流畅滚动。
import { Virtuoso } from 'react-virtuoso';
// ... 在组件内
<Virtuoso
data={sessions}
itemContent={(index, session) => <SessionListItem session={session} />}
/>
4.2 实现全局全文搜索功能
搜索是提升信息检索效率的核心。我们需要支持跨所有会话、所有消息内容进行关键词搜索。
前端搜索交互 在页面顶部放置一个搜索输入框(MUI 的 TextField )。使用 React 的 useState 来管理搜索关键词,并用 useMemo 来执行实际的过滤逻辑,避免在每次渲染时都进行昂贵的计算。
const [searchTerm, setSearchTerm] = useState('');
const filteredSessions = useMemo(() => {
if (!searchTerm.trim()) return sessions;
const term = searchTerm.toLowerCase();
return sessions.filter(session =>
session.title.toLowerCase().includes(term) ||
session.preview.toLowerCase().includes(term) ||
// 如果存储了完整消息,这里需要遍历 messages.content
session.messages?.some(msg => msg.content.toLowerCase().includes(term))
);
}, [sessions, searchTerm]);
后端增强搜索与性能考量 上述前端搜索简单快捷,但有两个局限:1) 如果只存储了预览,则无法搜索历史消息的完整内容;2) 数据量极大时,前端过滤仍有压力。
更强大的方案是实现 服务端搜索 。我们可以:
- 在 Server Component 或 API Route 中,读取原始数据时,不仅构建
ChatSession模型,同时为每条消息的内容建立 倒排索引 (一个简单的版本就是关键词 -> [会话ID, 消息ID]的映射)。 - 当用户输入搜索词时,前端向一个 API 端点 (
/api/search) 发送请求。 - 后端在这个索引中进行查找,快速返回匹配的会话列表。
- 为了提升体验,可以引入防抖(debounce),避免用户每输入一个字符就发起请求。
注意事项 :建立全文索引会占用更多内存。对于纯本地工具,我们需要权衡索引的详细程度和内存消耗。一个折中方案是只索引消息的前 N 个字符(例如500字),或者采用分段加载索引的策略。
4.3 按项目分组与筛选
聊天会话天然属于某个项目。按项目分组能极大提升浏览效率。
项目信息提取 从 ChatSession.projectPath 中,我们可以提取出项目名称。通常,项目名就是路径的最后一部分(例如 /Users/me/code/my-awesome-app 的项目名是 my-awesome-app )。我们可以提供一个侧边栏或下拉筛选器,列出所有唯一的项目名。
实现项目筛选器 使用 MUI 的 Chip 、 Autocomplete 或 FormGroup 组件来创建一组项目筛选标签。状态管理上,可以使用一个数组 selectedProjects 来记录被选中的项目。
const [selectedProjects, setSelectedProjects] = useState<string[]>([]);
const filteredSessions = useMemo(() => {
let result = sessions;
if (selectedProjects.length > 0) {
result = result.filter(session => selectedProjects.includes(session.projectName));
}
// ... 再叠加搜索过滤
return result;
}, [sessions, selectedProjects, searchTerm]);
4.4 开发“每周总结”视图
“每周总结”是我个人非常喜欢的一个功能。它能以周为单位,将你的聊天活动可视化,帮你回顾每周在哪些项目上投入了最多时间与 AI 进行讨论。
数据聚合逻辑
- 按周分组 :遍历所有会话,根据
session.timestamp计算出它所属的年份和周数(ISO 周数)。可以使用date-fns库的getISOWeek和getISOWeekYear函数。 - 按项目聚合 :在每一周内,再按
session.projectName进行分组。 - 计算指标 :对于每一周下的每一个项目,可以计算:
- 会话总数
- 消息总数
- 总对话时长(如果数据中有时间信息)
- 一个代表性的会话标题或预览
UI 呈现 可以设计一个专门的 /weekly-summary 页面。使用 MUI 的 Grid 或 Card 组件来布局。每一周是一个大卡片,里面包含多个代表不同项目的小卡片或列表。用 Typography 和 Chip 组件来展示数据和标签。
交互设计 :点击某一周下的某个项目,可以跳转回主列表视图,并自动应用该项目和该周的时间范围筛选,直接看到对应的所有会话。这提供了从宏观总结到微观细节的无缝导航。
5. 开发、构建与部署实操指南
5.1 本地开发环境搭建
按照项目 README 的步骤操作基本无误,但有几个细节需要注意:
-
克隆与安装 :
git clone https://github.com/icyJoseph/chat-recall.git cd chat-recall pnpm install # 确保使用 pnpm,如果未安装请先 `npm install -g pnpm`常见问题 :如果
pnpm install失败,尤其是better-sqlite3编译失败,很可能是因为你的系统缺少编译原生模块所需的工具链。- macOS : 安装 Xcode Command Line Tools:
xcode-select --install - Windows : 需要安装 Visual Studio Build Tools 或 Windows Build Tools。
- Linux : 安装
build-essential等基础编译工具和python3。
- macOS : 安装 Xcode Command Line Tools:
-
环境变量与配置 :本项目理论上无需环境变量,因为它直接读取固定路径。但如果你的 Claude Code 或 Cursor 安装在非标准位置,可以考虑创建一个
.env.local文件来覆盖默认路径,例如:# .env.local CLAUDE_DATA_PATH=/custom/path/to/.claude/projects CURSOR_DB_PATH=/custom/path/to/state.vscdb然后在代码中通过
process.env.CLAUDE_DATA_PATH读取。 -
启动开发服务器 :
pnpm dev访问
http://localhost:3000。Next.js 的热重载功能会让你在修改代码后几乎立刻看到变化。
5.2 生产构建与优化
开发完成后,需要构建一个用于生产环境(即你自己长期使用)的版本。
pnpm build
这个命令会启动 Next.js 的构建过程:
- 检查 TypeScript 类型错误。
- 打包并优化所有 JavaScript 和 CSS 资源。
- 对页面进行静态生成或服务端渲染优化。
构建成功后,你可以使用以下命令启动生产服务器:
pnpm start
生产服务器更高效、稳定,适合长期在后台运行。
关于静态导出 :由于本项目严重依赖服务端读取本地文件系统的能力, 无法 使用 next export 进行完全静态导出。因为静态导出后的网站是纯前端的,无法运行 Node.js 代码。所以必须运行 Node.js 服务器 ( pnpm start )。
5.3 打包为桌面应用(可选进阶)
虽然通过浏览器访问 localhost:3000 已经可用,但作为一个本地工具,打包成独立的桌面应用体验会更佳。这里推荐使用 Tauri 。
Tauri 相比 Electron 的优势是打包体积极小(仅几MB),因为它使用系统自带的 WebView,而不是捆绑一个完整的 Chromium。
简要步骤 :
- 在项目根目录初始化 Tauri:
pnpm add -D @tauri-apps/cli,然后运行pnpm tauri init。 - 按照向导回答配置问题。前端构建命令填
pnpm build,输出目录填.next(Next.js 默认输出目录)。 - 修改 Tauri 配置 (
src-tauri/tauri.conf.json),允许应用访问本地文件系统(需要配置fs作用域)。 - 因为 Next.js 生产服务器在
start时运行,而 Tauri 需要的是构建好的静态文件,这里需要调整。一个可行的方法是配置 Next.js 以 静态站点生成 模式输出,但如前所述,这要求剥离服务端数据读取逻辑,将其移至 Tauri 的 Rust 后端,通过前端与后端通信来获取数据。这涉及较大的架构改动,是另一个项目的起点。
对于第一版,建议先以本地服务器形式使用,体验核心功能。桌面化可以作为未来的优化方向。
6. 隐私安全设计与常见问题排查
6.1 隐私架构深度解析
“隐私第一”不是一句空话,它贯穿于 Chat Recall 的整个架构设计。
- 无网络请求 :应用启动后,不会向
claude.ai、cursor.sh或任何第三方服务器发送任何 HTTP 请求。所有数据流动都发生在你的计算机内存和磁盘之间。 - 数据不离境 :聊天数据的读取、解析、索引、展示全流程均在本地完成。即便是搜索功能,也是在本地内存中进行的字符串匹配,没有查询词上传。
- 无遥测与日志 :应用不包含任何 analytics(如 Google Analytics, Mixpanel)、错误收集(如 Sentry)或使用情况统计代码。控制台日志仅在开发模式下输出,用于调试。
- Server Components 的屏障 :使用 Next.js Server Components 是关键。敏感的文件路径和原始聊天数据仅在 Node.js 服务端环境中被处理。传递给客户端组件的只是经过清洗和格式化后的安全数据(如标题、预览文本、脱敏后的消息内容)。客户端永远无法接触到
~/.claude/projects/这样的原始路径字符串或数据库文件句柄。
6.2 常见问题与解决方案实录
在实际使用和开发中,你可能会遇到以下问题:
问题一:启动应用后看不到任何聊天记录。
-
可能原因 1:路径不正确 。
- 排查 :检查控制台(浏览器开发者工具或终端)是否有错误日志。应用在启动时会尝试读取默认路径。可以在代码中添加调试日志,打印出它正在尝试读取的完整路径。
- 解决 :确认 Claude Code 和 Cursor 已在你当前使用的电脑上生成过聊天记录。如果它们安装在自定义位置,需要通过环境变量配置正确路径。
-
可能原因 2:数据格式不兼容 。
- 排查 :Claude Code 或 Cursor 可能更新了数据存储格式。查看项目
issues页面是否有类似报告。 - 解决 :尝试用文本编辑器打开 Claude 的 JSON 文件,或用 SQLite 工具打开 Cursor 的数据库,检查其结构是否与代码中的解析逻辑匹配。可能需要更新解析器。
- 排查 :Claude Code 或 Cursor 可能更新了数据存储格式。查看项目
问题二:搜索功能慢或卡顿。
- 可能原因 :会话数量过多(例如超过 5000 条),且前端在进行全文搜索时未做优化。
- 解决 :
- 实施虚拟滚动 :确保列表使用了
react-virtuoso,避免渲染所有 DOM 节点。 - 实现服务端搜索 :如前所述,在后端建立索引。
- 添加搜索防抖 :在前端搜索输入框上添加防抖函数,避免频繁触发过滤计算。
import { useDebounce } from 'use-debounce'; const [searchTerm, setSearchTerm] = useState(''); const [debouncedSearchTerm] = useDebounce(searchTerm, 300); // 延迟300毫秒 // 使用 debouncedSearchTerm 进行过滤计算 - 实施虚拟滚动 :确保列表使用了
问题三:在特定操作系统上构建失败(特别是 better-sqlite3 )。
- 可能原因 :缺少编译原生 Node.js 模块所需的系统依赖。
- 解决 :
- macOS : 确保已安装 Xcode Command Line Tools。
- Windows : 安装
windows-build-tools(可能需要以管理员身份运行 PowerShell:npm install --global windows-build-tools) 或 Visual Studio 2019/2022 Build Tools。 - Linux : 安装
build-essential,python3,pkg-config等包。例如在 Ubuntu/Debian 上:sudo apt-get install build-essential python3 pkg-config。
问题四:生产构建后,页面样式错乱或功能异常。
- 可能原因 :Next.js 构建过程中的静态优化与客户端动态行为不匹配,或环境变量在构建时和运行时不同。
- 解决 :
- 运行
pnpm build后,仔细阅读构建输出日志,看是否有警告或错误。 - 检查是否错误地将一个使用了浏览器 API(如
window,document)的组件标记为了 Server Component。这会导致构建错误或运行时错误。 - 确保所有环境变量在
.env.local中正确设置,并且运行pnpm start时能读取到。
- 运行
问题五:如何备份我的 Chat Recall 配置或数据?
- 解答 :Chat Recall 本身不存储你的原始聊天数据,它只是实时读取。因此,你无需备份 Chat Recall 的数据。你需要备份的是源数据:
- Claude Code 数据 :备份
~/.claude/projects/目录(或 Windows 对应路径)。 - Cursor 数据 :备份
state.vscdb文件所在目录。 整个 Chat Recall 应用就是一个可执行的程序(或源代码),重新安装即可。你的所有聊天记录仍然安全地存放在原应用中。
- Claude Code 数据 :备份
更多推荐
所有评论(0)