「阅读30篇文档找答案,这是2025年该有的体验吗?」
一位Trilium用户上周被这个问题困住了。他的内部文档站有30篇笔记,结构清晰、搜索可用,但非技术同事被拉进技术对话时,仍需逐篇翻阅才能抓住要点。
他想要的是那种"问文档任何事"的聊天气泡——现代文档站的标配。但不想换平台、不想搭服务、不想学新工具。
于是他写了一个740行的JS文件。零依赖、零构建步骤,通过Trilium的一个relation(关系)直接嵌入。5分钟部署完成。
这篇是他完整的技术路径复盘。
硬门槛:TriliumNext ≥ 0.91
这个方案依赖两个新特性:~shareJs和~shareCss关系。0.91版本才引入。
部署流程极简:
第一步,右键你的共享根笔记,复制note ID。或者直接从分享页URL截取——https://your-trilium.com/share/里的那段字符串。
第二步,新建Code类型笔记,MIME选application/javascript,把widget.js完整粘贴进去。
第三步,修改文件顶部的常量:
const ROOT_NOTE_ID = "REPLACE_ME_WITH_YOUR_ROOT_NOTE_ID";
换成你的实际ID。
第四步,给JS笔记加标签:#shareHiddenFromTree(隐藏于树形结构)。
第五步,在共享根笔记上添加relation:~shareJs@your-js-note-id。
硬刷新分享页。右下角出现气泡。首次点击展开面板,首条消息提示输入API密钥。
全程没有npm install,没有webpack,没有Docker。
核心设计:让模型自己导航,而非预加载全部
常见的"文档AI"有两种粗暴实现:一是把全文塞进prompt,二是跑一套向量嵌入(embedding)管道做语义检索。
作者两者都没选。理由是Trilium提供了干净的树形结构——这是可以被模型主动探索的。
widget只向大语言模型暴露两个工具函数:
第一个是search_notes:按查询词搜索笔记,返回最多8条最佳匹配。参数只有一个字符串类型的query。
第二个是read_note:通过noteId读取特定笔记的完整文本。参数是字符串类型的noteId。
聊天循环的逻辑很直接:持续调用LLM,直到它不再请求工具。
while (true) { const choice = await callLLM(history, base, key, model, systemPrompt, toc); history.push(choice.message); if (choice.finish_reason !== "tool_calls") break; // 处理工具调用... }
模型自己决定搜什么、读哪篇。不需要预计算向量,不需要维护检索索引,文档更新即时生效。
这是用交互设计替代基础设施的典型案例。
三个技术支点:工具循环、目录注入、影子DOM
工具循环(tool-use loop)是上面描述的机制。它把检索决策权交给模型,而非固定算法。
目录注入(TOC injection)解决的是上下文窗口限制。Trilium的笔记树结构被序列化为目录,随每次请求提交。模型据此理解文档层级,规划搜索路径。
影子DOM(Shadow DOM)隔离样式。widget的CSS不会污染宿主页面,Trilium的原生样式也不会干扰聊天界面。740行代码里,UI层完全自包含。
这三个选择共同指向同一个约束:单文件、零依赖、即插即用。
为什么是Trilium?笔记工具的结构化红利
这个方案换到Notion或Obsidian上很难复制。不是因为API开放度,而是因为Trilium的底层数据模型——树形笔记网络,带有明确的父子关系和内部链接。
作者点出了关键洞察:大多数RAG(检索增强生成)系统需要额外构建文档结构,而Trilium的结构是原生自带的。
这意味着工具函数可以直接映射到用户既有的组织方式。不需要重新标注、分段、向量化。文档怎么写,模型就怎么读。
对于已经深度使用Trilium的团队,这是沉没成本的意外变现。
产品逻辑:用交互层弥补信息架构的断层
30篇结构化文档"完整但难用"——这是典型的信息架构与用户场景错位。
非技术同事被临时拉进技术对话,需要的不是系统学习,而是快速定位、提取要点、组织回应。搜索框要求用户知道关键词,目录树要求用户理解分类体系,都不如自然语言提问直接。
聊天气泡的价值不在于AI有多聪明,而在于它把"导航成本"从用户侧转移到了系统侧。
作者的选择很克制:没有重新设计文档站,没有迁移内容,没有引入新平台。只是一个JS文件,嫁接在现有工作流上。
这是"渐进式增强"(progressive enhancement)的当代版本。
边界与权衡:什么没做,为什么
方案明确排除了向量嵌入管道。作者认为,对于30篇笔记的规模,模型自主导航足够高效,额外的基础设施是过度设计。
也没有做对话历史持久化。刷新页面,上下文丢失。这是刻意为之——保持无状态,避免服务端依赖。
API密钥由用户首次使用时输入,存储在浏览器本地。没有后端代理,没有密钥管理服务。这意味着每个使用者需要自己的大模型API额度。
这些取舍定义了方案的适用范围:内部小团队、低频次查询、对延迟不敏感的场景。超出这个边界,需要更重的架构。
行业参照:文档AI的两种哲学
这个单文件方案代表了"轻量嫁接"路径。与之对照的是Vercel的AI SDK、LangChain的文档聊天模板——全功能、可扩展、但依赖现代前端工具链。
两者的差异不是技术优劣,而是假设前提不同。后者假设你会新建一个Next.js项目,前者假设你想在现有系统上打补丁。
Trilium用户的典型画像——自托管、隐私敏感、厌恶订阅制——恰好与"轻量嫁接"的哲学契合。
这也解释了为什么方案选择纯ES2020+:不需要转译,浏览器直接执行。Trilium的分享页本身就是静态HTML,不需要构建步骤。
可复用的模式:工具即接口
widget.js的核心设计——向模型暴露结构化工具,而非预处理数据——正在成为一种范式。
OpenAI的Function Calling、Claude的Tool Use、Gemini的Function Calling,都在推动同一方向:让模型与外部系统通过契约交互,而非通过上下文猜测。
这个方案的特别之处在于,它把"外部系统"压缩到了最小:两个函数,操作一个树形笔记API。没有数据库,没有向量存储,没有编排框架。
对于原型验证和小规模部署,这种极简主义有显著优势。调试时你只需要看网络面板里的两个API调用。
部署后的真实体验
根据作者描述,实际运行流程是:用户点击气泡→输入问题→模型决定搜索关键词→获取笔记列表→选择阅读→综合回答。
首次对话需要粘贴API密钥,之后同会话内持续有效。没有用户认证层,依赖Trilium分享页本身的访问控制。
响应延迟取决于两个因素:模型推理时间(通常是主要部分)和笔记检索时间(Trilium本地API,毫秒级)。
对于"被拉进对话前快速补课"这个场景,体验足够流畅。
为什么这件事值得关注
它示范了一种被低估的AI集成策略:不改造数据层,只增加交互层。
企业文档系统的常见困境是,内容分散在Confluence、Notion、GitHub Wiki、邮件线程里。统一检索需要ETL管道、向量数据库、同步机制——项目启动即重型。
这个方案的反思路是:如果文档已经在某个有结构的地方,能否让AI学会在那个地方查找,而非把所有东西搬到新地方?
Trilium的树形结构恰好提供了这种可能性。模型通过工具调用"浏览"笔记,就像人类用户点击目录和链接。
这不是通用解法。但对于已经深度使用特定工具的团队,它提示了一种低成本实验路径:先验证交互价值,再决定是否投入基础设施。
延伸思考
作者没有讨论但自然浮现的问题:如果笔记数量从30增长到300,这个架构还成立吗?工具调用的轮次增加会不会导致延迟爆炸?是否需要引入缓存或预检索?
另一个维度是权限。Trilium的分享页可以设置密码,但widget本身没有细粒度访问控制。如果不同用户应该看到不同的笔记子集,当前方案如何扩展?
还有模型选择。代码里预留了base URL和model参数,意味着可以用OpenAI、Anthropic或任何兼容端点。但不同模型的工具调用能力差异显著,这会如何影响实际效果?
这些问题没有标准答案,但定义了方案的可能演进方向。
当AI能力变成单文件可插拔的组件,我们是否会看到更多"传统软件+AI层"的轻量组合?这种组合的天花板在哪里——是笔记数量、工具复杂度,还是模型本身的规划能力?
热门跟贴