feat: 实时入库监控 + 向量拆分可视化(异步入库 + 进度 SSE)
入库从同步改为异步流水线 + 进度回流(复用 token 流 NATS streaming)。 UI 实时看到 解析→切块→向量化(分批)→写入 各阶段 + 拆分块预览。 - shared: contract.IngestEvent(stage/done/total/chunks/error) - mcp-go: rag.Ingest 加 onProgress + 分批向量化(10/批)逐批回报;kb_ingest 带 job_id 把进度发到 sundynix.streams.<job_id> + CompleteStream - gateway: 入库异步返回 job_id,后台 runIngest 发进度;GET /kb/ingest/:id/stream SSE - frontend: streamIngest(EventSource);KbView 实时进度面板(阶段徽标+进度条+拆分列表) - 验证: build✓+e2e PASS; 浏览器 12 行→6 阶段点亮+进度条 12/12+拆分 12 块逐条 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -47,27 +47,57 @@ export function streamTokens(
|
||||
return () => es.close();
|
||||
}
|
||||
|
||||
// ingestKb: POST /api/v1/kb/ingest,把文本入库(→ mcp-go kb_ingest:切块/embedding/Milvus)。
|
||||
// 入库进度事件(与后端 contract.IngestEvent 对应)。
|
||||
export interface IngestEvent {
|
||||
stage: string;
|
||||
msg?: string;
|
||||
done?: number;
|
||||
total?: number;
|
||||
chunks?: string[];
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// ingestKb: POST /api/v1/kb/ingest —— 文本入库(异步,返回 job_id)。
|
||||
export async function ingestKb(kb: string, text: string): Promise<string> {
|
||||
const res = await fetch(`${GATEWAY}/api/v1/kb/ingest`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ kb, text }),
|
||||
});
|
||||
const data = (await res.json()) as { message?: string; error?: string };
|
||||
if (!res.ok) throw new Error(data.error ?? `ingest failed: ${res.status}`);
|
||||
return data.message ?? "ok";
|
||||
const data = (await res.json()) as { job_id?: string; error?: string };
|
||||
if (!res.ok || !data.job_id) throw new Error(data.error ?? `ingest failed: ${res.status}`);
|
||||
return data.job_id;
|
||||
}
|
||||
|
||||
// ingestFile: POST /api/v1/kb/ingest_file(multipart)—— 上传文件入库(docx/xlsx/pdf… → mcp-py 解析)。
|
||||
// ingestFile: POST /api/v1/kb/ingest_file(multipart)—— 文件入库(异步,返回 job_id)。
|
||||
export async function ingestFile(kb: string, file: File): Promise<string> {
|
||||
const fd = new FormData();
|
||||
fd.append("kb", kb);
|
||||
fd.append("file", file);
|
||||
const res = await fetch(`${GATEWAY}/api/v1/kb/ingest_file`, { method: "POST", body: fd });
|
||||
const data = (await res.json()) as { message?: string; chars?: number; error?: string };
|
||||
if (!res.ok) throw new Error(data.error ?? `ingest file failed: ${res.status}`);
|
||||
return `${file.name}:解析 ${data.chars ?? 0} 字 → ${data.message ?? "ok"}`;
|
||||
const data = (await res.json()) as { job_id?: string; error?: string };
|
||||
if (!res.ok || !data.job_id) throw new Error(data.error ?? `ingest file failed: ${res.status}`);
|
||||
return data.job_id;
|
||||
}
|
||||
|
||||
// streamIngest: SSE 订阅入库进度(/kb/ingest/:id/stream)。返回关闭函数。
|
||||
export function streamIngest(
|
||||
jobId: string,
|
||||
onEvent: (ev: IngestEvent) => void,
|
||||
onDone: () => void,
|
||||
onError?: () => void,
|
||||
): () => void {
|
||||
const es = new EventSource(`${GATEWAY}/api/v1/kb/ingest/${jobId}/stream`);
|
||||
es.addEventListener("progress", (e) => onEvent(JSON.parse((e as MessageEvent).data) as IngestEvent));
|
||||
es.addEventListener("done", () => {
|
||||
es.close();
|
||||
onDone();
|
||||
});
|
||||
es.onerror = () => {
|
||||
es.close();
|
||||
onError?.();
|
||||
};
|
||||
return () => es.close();
|
||||
}
|
||||
|
||||
export interface KbHit {
|
||||
|
||||
Reference in New Issue
Block a user