refactor(kb): 文库列表/正文分离 + [[双链]]索引表(可扛大文件)

不再一次拉回整库正文、不再前端扫全文 —— 列表只读元数据,正文按需取,链接走索引。

- store: SaveDoc 维护 size+preview(前 500 字);ListVault 仅 Select 元数据(name/size/preview,
  不含 content);GetDoc 取单篇全文;DocLink 表 + ReplaceDocLinks(入库/编辑时按 from 重建出链)
  + ListLinks。
- gateway: 入库/笔记保存时正则抽 [[链接]]→ReplaceDocLinks 维护索引;
  /kb/vault 改返元数据+预览;新增 /kb/doc(单篇全文) 与 /kb/links(全库双链)。
- 前端:listVault 返元数据,新增 getDoc/listLinks;VaultPanel 列表只展示名/字数,
  选中后 getDoc 按需载正文(带加载态),反链/笔记关系图改用服务端 links 索引(不扫全文)。

验证:curl /kb/vault 仅 name/size/preview;/kb/doc 取单篇;/kb/links 返 3 条双链。
Preview:文库点「架构总览」按需载正文(平台分五层)、反向链接(1)=Dispatcher(来自索引)。tsc+vite+gateway build 通过。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-06-13 16:53:23 +08:00
parent 4fd44380aa
commit 69967ea534
5 changed files with 174 additions and 46 deletions
+23 -2
View File
@@ -123,7 +123,12 @@ export async function createKb(id: Identity, name: string, kind: string): Promis
export interface VaultDoc {
name: string;
content: string;
size?: number;
preview?: string;
}
export interface DocLink {
from: string;
to: string;
}
// ---- 我的 Agent 编排(服务端保存,owner 隔离)----
@@ -169,7 +174,7 @@ export async function listChatModels(): Promise<string[]> {
}
}
// listVault: GET /api/v1/kb/vault —— 某知识库的原始文档(Obsidian 式文库浏览)。
// listVault: GET /api/v1/kb/vault —— 文库列表(仅元数据 + 预览,不拉全文)。
export async function listVault(id: Identity, kb: string): Promise<VaultDoc[]> {
const res = await fetch(`${GATEWAY}/api/v1/kb/vault?kb=${encodeURIComponent(kb)}`, { headers: idHeaders(id) });
const data = (await res.json()) as { docs?: VaultDoc[]; error?: string };
@@ -177,6 +182,22 @@ 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) });
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 ?? "";
}
// listLinks: GET /api/v1/kb/links —— 某库全部 [[双链]](反链/笔记关系图,数据小)。
export async function listLinks(id: Identity, kb: string): Promise<DocLink[]> {
const res = await fetch(`${GATEWAY}/api/v1/kb/links?kb=${encodeURIComponent(kb)}`, { headers: idHeaders(id) });
const data = (await res.json()) as { links?: DocLink[]; error?: string };
if (!res.ok) throw new Error(data.error ?? `links failed: ${res.status}`);
return data.links ?? [];
}
// saveNote: POST /api/v1/kb/note —— 新建/编辑笔记(落库 + 按 doc 重入库替换旧块)。
export async function saveNote(id: Identity, kb: string, name: string, content: string): Promise<void> {
const res = await fetch(`${GATEWAY}/api/v1/kb/note`, {