用树状结构来组织所有笔记
OneBook 把一切都存成区块。笔记、清单、智能体的定义、你发出的提问、模型的回复,都是同一张 blocks 表里的记录,靠 parent_id 串起来,用 fractional position 排序。
Electron 桌面应用,数据落在本机文件里。
- agent文献综述 agent
- prompt整理我们关于 RAG 评测的记录
- completion三类基准,各自的取舍
- prompt这些在中文语料上哪些会失效?
- context出处:Lewis et al., 2020
- noteagent 归档的检索笔记
七种区块类型,一张表。
没有按类型分表。七种里只有少数会得到引擎的特殊处理。
| 类型 | 界面名称 | 行为 |
|---|---|---|
| note | 笔记 | Markdown 预览支持 GFM、公式、Mermaid 和代码高亮,编辑用 CodeMirror。 |
| prompt | 提问 | 你发出的一个问题,映射成对话里的 user。 |
| completion | 回复 | 模型的答复,就地流式写入,对应 assistant。不能手工新建。 |
| agent | 智能体 | 区块正文就是系统提示词,它的子区块是这个智能体跑过的记录。 |
| craft | 工序 | 反复使用的清单、步骤、模板,渲染方式和笔记一样。 |
| script | 脚本 | 当代码编辑和着色,能读不能跑——执行还没接上。 |
| context | 上下文 | 把另一个区块挂进来当参考资料,运行时以 XML 注入。 |
把分支折叠起来,再看某个区块到根的路径
上面那张图的内容都在这里,另外多三行它没画的。按一行,它下面的内容就收进去;选中一行,旁边的面板会顺着 parent_id 一路往上找到根。
agent文献综述 agent
prompt整理我们关于 RAG 评测的记录
completion三类基准,各自的取舍
prompt这些在中文语料上哪些会失效?
craft下一篇论文的审稿清单
对话是笔记树的一根分支。
每一次追问都把自己的提问区块挂在上一轮回复下面,所以祖先链就是消息历史。拼一次请求用的是一条递归 CTE,不是一份会话日志。
在同一个回复下面问两次,就得到两根分支,两条历史都留在树里,不会互相覆盖。
WITH RECURSIVE strict_path AS (
SELECT id, parent_id, position, 0 AS depth
FROM blocks
WHERE id = ? AND type = 'prompt'
AND is_deleted = 0
UNION ALL
SELECT b.id, b.parent_id, b.position, sp.depth + 1
FROM blocks b
JOIN strict_path sp ON b.id = sp.parent_id
WHERE b.type = 'prompt' AND b.is_deleted = 0
)
SELECT b.*, sp.depth
FROM strict_path sp
JOIN blocks b
ON (b.parent_id = sp.parent_id
OR (b.parent_id IS NULL
AND sp.parent_id IS NULL))
AND b.position <= sp.position
ORDER BY sp.depth DESC, b.position ASC提问 → 回复 → 提问 → 回复,落成嵌套的行。 · block.repository.ts
智能体在你已有的笔记里干活。
智能体本身就是一个区块,正文就是系统提示词。它有七个读写区块的工具,可以先搜一遍笔记本,再把找到的东西归到你正在看的那个区块下面。
这些工具跑在主进程里,读写的就是搜索面板用的那个数据库,所以 agent 存下来的东西你马上能翻到。如果这次调用要改某个区块的正文,OneBook 会先把旧内容写成一个快照子区块。
- getBlock读一个区块
- getChildBlocks按顺序列子区块
- getBlockAncestors向上回溯到根
- searchBlocks和搜索面板读同一份索引,可按 ref_id 收窄
- createBlock把内容归到某个父区块下
- updateBlock替换正文或元数据
- deleteBlock软删除,可恢复
工具就这些。没有一个会走网络。
数据库就是一个文件。
一个项目是一个目录。复制它就是备份,换台机器就带走笔记、智能体和聊天记录。
没有遥测,也不用登录。真要出网的请求只发给你自己配的那家服务商。想确认里面存了什么,用 sqlite3 打开数据库就行。
OneBook 往磁盘上写的全部东西。
中文搜索和英文一样。
标题和正文进 FTS5 表,按 MATCH 排序。中文在入索引之前先用 nodejieba 分词,所以不用建 trigram 表,也不用前缀匹配的折中办法;再拿停用词表把索引压小。
搜索面板和 agent 的 searchBlocks 工具读同一份索引,你搜得到的东西它也搜得到。
一份索引,两个读法。
[[页面名]] 点开就是真实区块。
remark 插件解析 [[slug]] 链接和 ((block-id)) 嵌入,把它们渲染成可以点开的区块卡片。slug 在写入和读取时走同一个规范化函数,所以你写 “My Page”,照样能找到 my-page。
保存时这些引用会被抽进 block_links,分成 block_link 或 block_embed 两种边,反链查询于是就是一个 SELECT。展示反链的面板还没做。
写入路径和读取路径共用一个规范化函数。
人们拿它装什么
树就是全部界面,所以下面这些做法本质相同:把一个区块放到某个位置,再往里放区块。
- 跨很多材料的调研
- 一份材料一个父区块,摘录作为子区块,材料之间用 [[slug]] 互链。
- 比较不同回答
- 同一个提问跑三遍,或者交给不同 agent,让回复并排留着。
- 长文和报告
- 用区块搭大纲,收起暂时不写的部分,整个章节连子区块一起拖走。
- agent 工作台
- 把 agent 写成区块,附上它该看的笔记,让它把结论发回树里。
- 会议记录
- 先平着记,再把待办重新挂到负责它的项目下面。
- 喂给 AI 的语料
- 内容本来就被切成区块大小并建好索引,检索是一条查询,不是一次入库。
模型自己带。
模型访问走 Vercel AI SDK,注册了 21 个 provider adapter。
- OpenAI
- Anthropic
- xAI
- Mistral
- Groq
- DeepSeek
- MoonshotAI
- Alibaba
- ByteDance
- Cohere
- Perplexity
- Cerebras
- Fireworks
- Together
- DeepInfra
- Baseten
- Hugging Face
- OpenRouter
- Ollama
- OpenAI-compatible
密钥存在 ~/.onebook/models.json,只在主进程里读。
还没做的。
大致按离得近的排。
- 反链面板和链接总览。边已经在 block_links 里了。
- Markdown 或 JSON 的导入导出,一个区块对应一个文件,好让已有的笔记搬进来。
- 关键词搜索旁边加向量检索。sqlite-vec 在 optionalDependencies 里,没有代码调用它。
- 在沙箱里运行 script 区块。
- 把一次运行发布成作品,以及跨设备同步——两者都建立在按项目分库的文件上。
- 自动更新、打包流水线、CI。