137 lines
4.6 KiB
TypeScript
137 lines
4.6 KiB
TypeScript
import { apiFetch, apiFetchForm } from "@/lib/api";
|
|
|
|
/**
|
|
* 知识库(/api/knowledge/*)适配层。
|
|
* 权限:后端 require_knowledge_operator —— 仅管理员或运营角色,前端不做二次判断。
|
|
*/
|
|
|
|
/**
|
|
* 知识库集合白名单,必须与后端 `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;
|
|
}
|
|
|
|
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;
|
|
}
|