Files
Mutual_Fund/frontend/lib/knowledge-api.ts
T

137 lines
4.6 KiB
TypeScript
Raw Normal View History

2026-09-14 20:31:06 +08:00
import { apiFetch, apiFetchForm } from "@/lib/api";
/**
* 知识库(/api/knowledge/*)适配层。
* 权限:后端 require_knowledge_operator —— 仅管理员或运营角色,前端不做二次判断。
*/
2026-09-15 16:08:44 +08:00
/**
* 知识库集合白名单,必须与后端 `rag/milvus_collections.py::KNOWLEDGE_COLLECTIONS` 完全一致。
* 后端 `KnowledgeUploadService.confirm` 会校验 `collection_name`,不在白名单内直接
* 抛 400「知识库集合不在允许范围内」——所以 confirm 必须带上这个字段,不能为空。
*/
export const KNOWLEDGE_COLLECTIONS = [
{
value: "fin_fund_doc",
label: "基金文档",
description: "基金产品说明、公司业务资料",
},
{
value: "fin_faq",
label: "常见问答",
description: "FAQ 问答对,客服与投顾高频问题",
},
{
value: "fin_policy",
label: "政策法规",
description: "监管政策、合规文件",
},
] as const;
/** 默认入库到基金文档集合(也是检索权重最高的一类业务资料)。 */
export const DEFAULT_KNOWLEDGE_COLLECTION = "fin_fund_doc";
/** 集合英文名 → 中文标签;未知集合原样返回,便于发现后端新增了集合。 */
export function collectionLabel(value?: string | null): string {
if (!value) return "";
return KNOWLEDGE_COLLECTIONS.find((item) => item.value === value)?.label ?? value;
}
/** 校验集合是否在白名单内,供前端提交前前置拦截。 */
export function isValidCollection(value?: string | null): boolean {
return !!value && KNOWLEDGE_COLLECTIONS.some((item) => item.value === value);
}
/**
* 把后端返回的原始报错翻译成可执行的下一步提示。
* 后端 `api/chat/knowledge.py` 统一把 UploadValidationError 转成 400 + 原文,
* 未捕获的异常则被全局处理器兜成 500「服务内部错误」,这里对两者都给出方向。
*/
export function knowledgeErrorHint(message: string): string {
const text = message ?? "";
if (text.includes("知识库集合不在允许范围内")) {
return "请选择入库的知识库集合后再提交";
}
if (text.includes("Unsupported document type")) {
return "文件类型不支持,请上传 PDF / Word / Markdown / TXT 等文本类文档";
}
if (text.includes("doc_id已存在")) {
return "该文档已入库,请先删除旧文档或修改 doc_id";
}
if (text.includes("Embedding维度必须为")) {
return "向量维度与集合不一致,需按当前 embedding 模型重建集合";
}
if (text.includes("文档切片为空")) {
return "文档没切出任何内容,请确认文件不是空文档或纯图片扫描件";
}
if (text.includes("服务内部错误")) {
return "后端依赖异常(通常是向量库 Milvus 未启动或 embedding 服务不可用),请检查基础设施后重试";
}
return text;
}
2026-09-14 20:31:06 +08:00
export interface KnowledgeDocument {
doc_id?: string;
id?: string;
title?: string | null;
filename?: string | null;
collection_name?: string | null;
chunk_count?: number | null;
strategy?: string | null;
create_time?: string | null;
[key: string]: unknown;
}
export interface UploadPreview {
filename?: string;
strategy?: string;
chunk_size?: number;
chunk_overlap?: number;
chunks?: unknown[];
preview?: unknown;
[key: string]: unknown;
}
export const knowledgeApi = {
list: () => apiFetch<KnowledgeDocument[]>("/knowledge/documents"),
get: (docId: string) =>
apiFetch<KnowledgeDocument>(`/knowledge/documents/${encodeURIComponent(docId)}`),
/** 第一步:上传文件拿切分预览,不落库。 */
preview: (form: FormData) =>
apiFetchForm<UploadPreview>("/knowledge/documents/preview", form),
/** 第二步:确认入库(可复用 preview 返回的 doc_id)。 */
confirm: (form: FormData) =>
apiFetchForm<KnowledgeDocument>("/knowledge/documents/confirm", form),
remove: (docId: string) =>
apiFetch<{ doc_id?: string; deleted?: boolean; [key: string]: unknown }>(
`/knowledge/documents/${encodeURIComponent(docId)}`,
{ method: "DELETE" }
),
};
/** 组装知识库上传表单,空值不提交,避免后端把空串当成有效参数。 */
export function buildKnowledgeForm(
file: File,
extra: {
title?: string;
doc_id?: string;
collection_name?: string;
strategy?: string;
chunk_size?: number | null;
chunk_overlap?: number | null;
} = {}
): FormData {
const form = new FormData();
form.append("file", file);
for (const [key, value] of Object.entries(extra)) {
if (value !== undefined && value !== null && value !== "") {
form.append(key, String(value));
}
}
return form;
}