feat(knowledge): 支持文件导入沉淀知识,知识图谱可视化随主题切换

- 后端新增 /api/knowledge/ingest/file:上传 md/txt/pdf/word/ppt/excel,
  转 Markdown 后走 wiki-capture LLM 蒸馏为陈述式知识笔记,失败回退逐字草稿
- service.capture_file + 文档专用蒸馏提示词,按主题拆分 1~4 页
- 知识图谱导出 HTML 支持 ?theme=light|dark,布局稳定后冻结,
  按度数缩放节点,悬停/点击聚焦邻居
- 前端知识库页新增「导入文件」对话框与 ingestKnowledgeFile API
- 智能体问答页与简介侧边栏 UI 优化;批量人物蒸馏脚本提取正文逻辑修正

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
吉鑫升andClaude Opus 4.8 committed 2026-06-11 10:25:27 +08:00
1 parent 17bb3715d1
commit 3845d5090b
14 files changed
+1040 -352

No files matched your search

+24
View File
@@ -127,6 +127,30 @@ export async function ingestThreadsBatch(payload: BatchIngestPayload = {}) {
);
}
export interface IngestFileResult {
note: KnowledgeNote;
created: boolean;
notes: KnowledgeNote[];
llm_fallback: boolean;
}
/** Import a file (md/txt/pdf/word/ppt/excel) into the KB as wiki note(s). */
export async function ingestKnowledgeFile(
file: File,
opts: { title?: string; tags?: string; status?: string; model_name?: string | null } = {},
) {
const form = new FormData();
form.append("file", file);
if (opts.title) form.append("title", opts.title);
if (opts.tags) form.append("tags", opts.tags);
if (opts.status) form.append("status", opts.status);
if (opts.model_name) form.append("model_name", opts.model_name);
// No Content-Type header — the browser sets the multipart boundary itself.
return parseResponse<IngestFileResult>(
await apiFetch(`${baseUrl()}/ingest/file`, { method: "POST", body: form }),
);
}
/** Resolve `[[wikilink]]` targets to note ids / vault pages. */
export async function resolveWikilinks(targets: string[]) {
const res = await parseResponse<{ resolved: WikilinkResolutionMap }>(
+11
View File
@@ -11,6 +11,7 @@ import {
exportKnowledgeGraph,
fetchVaultPage,
getKnowledgeNote,
ingestKnowledgeFile,
ingestThread,
ingestThreadsBatch,
listDuplicates,
@@ -106,6 +107,16 @@ export function useKnowledgeActions() {
mutationFn: ingestThreadsBatch,
onSuccess: invalidate,
}),
ingestFile: useMutation({
mutationFn: ({
file,
opts,
}: {
file: File;
opts?: Parameters<typeof ingestKnowledgeFile>[1];
}) => ingestKnowledgeFile(file, opts),
onSuccess: invalidate,
}),
exportGraph: useMutation({ mutationFn: exportKnowledgeGraph }),
merge: useMutation({
mutationFn: ({ primaryId, duplicateIds }: { primaryId: string; duplicateIds: string[] }) =>