refactor(kb): 文件主表 + 文档关联改用雪花ID(弃用按名关联)

把 sundynix_doc 明确为"文件主表",补齐文件基础信息字段;
文档间 [[双链]] 改为以 Doc.ID 关联,查询/渲染一律按文件 ID。

store:
- Doc 增 Ext(后缀)/MD5(内容指纹) 字段;ObjectKey 即"存放链接"
- DocLink 由 (FromName,ToName) 改为 (FromID,ToID,ToName)
  · FromID/ToID 关联 Doc.ID;ToName 保留用于悬空链接展示与回填
- SaveDoc 返回新建/更新文件的雪花 ID(供建链)
- 新增 GetDocByID(按 ID + owner 取正文,防越权)
- ReplaceDocLinks 以 fromID 重建出链,按 [[名称]] 解析目标 ID
- 新增 ResolveInboundLinks:目标入库后回填指向它的悬空链接
- ListLinks 只返回已解析(to_id 非空)的 ID→ID 边
- migrateDocLinkToID:旧按名双链表无 from_id 列则重建为按 ID 关联

gateway/handler:
- runIngest 计算 ext/md5,SaveDoc 取回 ID 后建链 + 回填悬空
- KbDoc 改为 GET ?id=(按文件 ID 取全文)
- KbVault 返回 id+ext;KbLinks 返回 from/to 为 ID

desktop:
- VaultDoc 增 id/ext;getDoc(docId) 按 ID 取正文
- VaultPanel 选中态/正文/反链/关系图改用 ID,名↔ID 双向映射
  渲染;保存笔记后按名定位回其新 ID

验证(gateway+PG+MinIO 实测):vault 带 id+ext;双链 ID→ID 且
A→B 悬空链接在 B 入库后成功回填;按 ID 取大文档(15006字)从
MinIO 完整取回;跨 owner 按 ID 取文档 404(隔离生效)。桌面端
文库 Tab 按 ID 选中/载入/反链渲染正常,无控制台报错。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-06-15 09:38:02 +08:00
parent f610d8d2da
commit 5d76652bff
5 changed files with 142 additions and 70 deletions
+7 -5
View File
@@ -122,13 +122,15 @@ export async function createKb(id: Identity, name: string, kind: string): Promis
}
export interface VaultDoc {
id: string; // 文件主表雪花 ID —— 选中/取正文/关联一律用它
name: string;
ext?: string; // 文件后缀(.md/.pdf…;笔记为空)
size?: number;
preview?: string;
}
export interface DocLink {
from: string;
to: string;
from: string; // 源文件 ID
to: string; // 目标文件 ID
}
// ---- 我的 Agent 编排(服务端保存,owner 隔离)----
@@ -182,9 +184,9 @@ export async function listVault(id: Identity, kb: string): Promise<VaultDoc[]> {
return data.docs ?? [];
}
// getDoc: GET /api/v1/kb/doc —— 取单篇文档全文(按需加载,避免列表拉全量)。
export async function getDoc(id: Identity, kb: string, name: string): Promise<string> {
const res = await fetch(`${GATEWAY}/api/v1/kb/doc?kb=${encodeURIComponent(kb)}&name=${encodeURIComponent(name)}`, { headers: idHeaders(id) });
// getDoc: GET /api/v1/kb/doc?id= —— 按文件 ID 取单篇全文(按需加载,避免列表拉全量)。
export async function getDoc(id: Identity, docId: string): Promise<string> {
const res = await fetch(`${GATEWAY}/api/v1/kb/doc?id=${encodeURIComponent(docId)}`, { headers: idHeaders(id) });
const data = (await res.json()) as { content?: string; error?: string };
if (!res.ok) throw new Error(data.error ?? `doc failed: ${res.status}`);
return data.content ?? "";