• 请不要在回答技术问题时复制粘贴 AI 生成的内容
lerongwan1
V2EX  ›  程序员

LLM 知识库,如何统一本地与云端修改

  •  
  •   lerongwan1 · 9h 14m ago · 504 views

    LLM 知识库,如何统一本地与云端修改

    立即体验 NoteDeep

    做 NoteDeep Desktop 时,我希望本地知识库不只是云端数据的缓存:用户可以离线编辑 Markdown ,Codex 、Claude Code 或 Cursor 可以直接改文件,云端仍然保留富文本编辑、历史版本与协作能力。

    真正困难的不是上传文件,而是三类修改可能同时发生:它们从哪里进入、以谁为准、断网后如何恢复、云端领先时又如何合并?最后,我把它们收敛到同一套本地状态和 JSON0 同步链路中。

    背景与目标

    这个方案先确定了两个产品边界:本地目录必须在没有网络、甚至没有登录时正常工作; Markdown 必须是人和 AI 工具都能直接读写的开放文件,而不是某种只能由 NoteDeep 解析的缓存格式。

    因此,本地与云端分别拥有自己的事实源:

    • 页面正文的本地事实源是 Markdown 文件。
    • 页面树的本地事实源是 .notedeep/manifest.json 中的 manifest.pages
    • 云端事实源是 ShareDB 的 workspaces/{workspaceUuid}pages/{pageUuid}
    • .notedeep/state/ 保存同步基线和待提交 ops ,不冒充业务事实源。
    • sync-index.json 只用于跳过未变化文件,丢失后可以重建。

    这层区分很重要。如果把 snapshot 、文件索引和业务数据混在一起,同步异常后很难判断应该相信谁。

    三类修改如何汇流

    产品视角下有三类输入,但当前实现没有为它们各写一套同步器。

    mermaid

    本地编辑器产生的是 JsonML ,但保存前先转换为 Markdown ; Codex 等外部工具直接修改 Markdown 。两者到了同步层都表现为文件变化,并以 source: filesystem 写入 pending ops 。

    这是一个刻意的收敛:无论内容来自富文本编辑器还是外部工具,磁盘上的 Markdown 都是本地正文事实。同步引擎不需要猜测修改来自哪个 UI ,也不会形成两条互相竞争的本地写入链路。

    云端编辑器则直接向 ShareDB 提交 JSON0 ops 。本地同步时发现云端版本领先,拉取远端 ops ,更新本地 snapshot ,再把 JsonML 转回 Markdown 。这样三类修改最终都经过同一个 LocalDocState 对齐。

    一轮同步做了什么

    统一入口是 flushLocalWorkspaceSync。它会合并同一 workspace 的并发同步请求,避免保存、watcher 和定时任务同时跑多轮同步。

    一轮同步的主链路是:

    1. 用 workspace tree hash 判断页面树是否变化。
    2. 用文件的 path 、size 、mtime 和 content hash 筛出真正变化的 Markdown 。
    3. 将 Markdown 转成 JsonML ,与旧 snapshot 做 diff 。
    4. stageSnapshot 先把 JSON0 ops 持久化到 pendingOps
    5. 登录且账号匹配时,批量查询 ShareDB 当前版本。
    6. 云端领先时先拉取远端 ops ,并 transform 本地 pending ops 。
    7. 提交本地 ops ;远端页面变化再写回 Markdown 。

    mermaid

    这里最重要的顺序是“先 stage ,再联网”。未登录、离线或请求失败,只会让同步暂停,不会阻塞 Markdown 保存。下次打开、保存、文件变化、手动同步或 60 秒定时任务触发时,继续消费本地 pending ops 。

    页面树为什么单独同步

    正文变化和页面树变化不是一回事。正文进入 pages/{pageUuid},页面路径、标题、父子关系、排序和删除状态进入 workspaces/{workspaceUuid}

    本地页面树由 manifest.pages 构造为 WorkspaceDoc ,再通过字段级 JSON0 ops 上行。远端新增页面下行时,先把 WorkspaceDoc 投影回 manifest ,为新 pageUuid 生成本地路径,再拉取对应 page doc 并写成 Markdown 。

    这让“云端新建一页”不只是多出一份远端正文,而是能够完整落到本地目录结构中。

    这套方案的取舍

    当前正文 diff 是 block 粒度的 JsonML 替换,不做字符级 Markdown diff 。好处是复用编辑器现有的 JsonML 与 JSON0 协议,也能统一处理自定义 block ;代价是同一 block 内的并发修改不够细。

    目前并发冲突依赖 JSON0 transform ,没有人工冲突选择界面。Markdown 与 JsonML 的往返也会执行格式规范化,因此它不是保留每个空格和排版细节的文本同步工具。

    此外,appliedOps 目前会持续增长,压缩策略仍待确定。这些都是当前实现边界,而不是已经解决的问题。

    结果与后续

    这套设计最终把三类修改收敛成了一个简单原则:本地先可靠地写 Markdown ,再由持久化状态把结构化变化同步到 ShareDB ;云端变化沿相反方向写回本地。

    它没有消灭同步系统的复杂度,但把复杂度集中在 LocalDocStateflushDocState,没有扩散到编辑器、文件 watcher 和每一种外部工具中。对一个需要同时服务人、AI coding tools 和云端协作的知识库来说,这个边界比“实时上传文件”更重要。

    如果你也在设计离线优先或本地优先应用,希望这套取舍能提供一些参考。

    使用 NoteDeep 开始整理你的本地知识库

    #架构设计 #离线优先 #本地优先 #协同编辑 #NoteDeep

    3 replies    2026-07-12 22:10:44 +08:00
    1874w
        1
    1874w  
       6h 58m ago
    emmm ,我直接 Obsidian + LiveSync 插件,服务器部署个 couchdb 作为 LiveSync 的后端,每个需要同步的设备都装 LiveSync 来同步

    有现成的为啥不用
    1874w
        2
    1874w  
       6h 56m ago
    @1874w LiveSync 同步 + Git 备份 + NAS 备份 + NAS 文件版本管理。都是本地优先,目前非常稳定。
    1874w
        3
    1874w  
       6h 55m ago
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   838 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 21:05 · PVG 05:05 · LAX 14:05 · JFK 17:05
    ♥ Do have faith in what you're doing.