知识库更改:知识库文档上传、删除、重构向量库、下载文档、按钮增加小图标

This commit is contained in:
wangzongming 2024-06-02 19:02:59 +08:00
parent 54dfef4796
commit 7e5a093641
18 changed files with 632 additions and 125 deletions

View File

@ -0,0 +1,13 @@
import { getServerConfig } from '@/config/server';
const { KNOWLEDGE_PROXY_URL } = getServerConfig();
export const POST = async (request: Request) => {
const params = await request.json();
const fetchRes = await fetch(`${KNOWLEDGE_PROXY_URL}/delete_docs`, {
body: JSON.stringify(params),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
});
return fetchRes
};

View File

@ -0,0 +1,13 @@
import { getServerConfig } from '@/config/server';
const { KNOWLEDGE_PROXY_URL } = getServerConfig();
export const POST = async (request: Request) => {
const params = await request.json();
const fetchRes = await fetch(`${KNOWLEDGE_PROXY_URL}/delete_docs`, {
body: JSON.stringify(params),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
});
return fetchRes
};

View File

@ -0,0 +1,13 @@
import { getServerConfig } from '@/config/server';
const { KNOWLEDGE_PROXY_URL } = getServerConfig();
export const GET = async (request: Request) => {
const searchParams = new URL(request.url).searchParams;
const knowledge_base_name = searchParams.get('knowledge_base_name') as string;
const file_name = searchParams.get('file_name') as string;
const preview = searchParams.get('preview') as string;
const queryString = new URLSearchParams({ knowledge_base_name, file_name, preview }).toString();
const fetchRes = await fetch(`${KNOWLEDGE_PROXY_URL}/download_doc?${queryString}`);
return fetchRes;
};

View File

@ -0,0 +1,9 @@
import { getServerConfig } from '@/config/server';
const { KNOWLEDGE_PROXY_URL } = getServerConfig();
export const GET = async (request: Request) => {
const knowledge_base_name: string = new URL(request.url).searchParams.get('knowledge_base_name') as string;
const queryString = new URLSearchParams({ knowledge_base_name }).toString();
const fetchRes = await fetch(`${KNOWLEDGE_PROXY_URL}/list_files?${queryString}`);
return fetchRes;
};

View File

@ -0,0 +1,14 @@
import { getServerConfig } from '@/config/server';
const { KNOWLEDGE_PROXY_URL } = getServerConfig();
export const POST = async (request: Request) => {
const params = await request.json();
const fetchRes = await fetch(`${KNOWLEDGE_PROXY_URL}/recreate_vector_store`, {
body: JSON.stringify(params),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
});
return fetchRes
};

View File

@ -0,0 +1,14 @@
import { getServerConfig } from '@/config/server';
const { KNOWLEDGE_PROXY_URL } = getServerConfig();
export const POST = async (request: Request) => {
const params = await request.json();
const fetchRes = await fetch(`${KNOWLEDGE_PROXY_URL}/recreate_vector_store`, {
body: JSON.stringify(params),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
});
return fetchRes
};

View File

@ -0,0 +1,12 @@
import { getServerConfig } from '@/config/server';
const { KNOWLEDGE_PROXY_URL } = getServerConfig();
export const POST = async (request: Request) => {
const formData = await request.formData();
const fetchRes = await fetch(`${KNOWLEDGE_PROXY_URL}/upload_docs`, {
body: formData,
method: 'POST',
});
return fetchRes
};

View File

@ -1,3 +0,0 @@
1. 创建接口没有简介字段

View File

@ -2,7 +2,7 @@ import { DeleteOutlined, EditOutlined, ExclamationCircleOutlined } from '@ant-de
import { Card, Skeleton, message, Modal } from 'antd'; import { Card, Skeleton, message, Modal } from 'antd';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useKnowledgeStore } from '@/store/knowledge'; import { useKnowledgeStore } from '@/store/knowledge';
const { Meta } = Card; const { Meta } = Card;
@ -11,16 +11,18 @@ interface KnowLedgeCardProps {
name: string; name: string;
} }
const KnowledgeCard: React.FC<KnowLedgeCardProps> = (props: KnowLedgeCardProps) => { const KnowledgeCard: React.FC<KnowLedgeCardProps> = (props: KnowLedgeCardProps) => {
const [useFetchKnowledgeDel] = useKnowledgeStore((s) => [ const [useFetchKnowledgeDel, useFetchKnowledgeList] = useKnowledgeStore((s) => [
s.useFetchKnowledgeDel s.useFetchKnowledgeDel, s.useFetchKnowledgeList
]); ]);
const { mutate } = useFetchKnowledgeList()
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const { name, intro } = props; const { name, intro } = props;
const router = useRouter(); const router = useRouter();
const handleCardEditClick = () => { const handleCardEditClick = () => {
router.push('/knowledge/1/base'); router.push(`/knowledge/${encodeURIComponent(name)}/base`);
}; };
const delClick = async () => { const delClick = async () => {
Modal.confirm({ Modal.confirm({
@ -31,7 +33,8 @@ const KnowledgeCard: React.FC<KnowLedgeCardProps> = (props: KnowLedgeCardProps)
if (resCode !== 200) { if (resCode !== 200) {
message.error(resMsg) message.error(resMsg)
} else { } else {
message.success(resMsg) message.success(resMsg)
mutate()
} }
return Promise.resolve(); return Promise.resolve();
}, },

View File

@ -20,11 +20,6 @@ const useStyles = createStyles(({ css, token, stylish }) => ({
`, `,
})); }));
// const list = [
// { intro: '知识库简介', name: '知识库名称' },
// { intro: '知识库简介', name: '知识库名称' },
// ];
const RenderList = memo(() => { const RenderList = memo(() => {
const { styles } = useStyles(); const { styles } = useStyles();
const [listData, useFetchKnowledgeList] = useKnowledgeStore((s) => [ const [listData, useFetchKnowledgeList] = useKnowledgeStore((s) => [
@ -32,11 +27,15 @@ const RenderList = memo(() => {
]); ]);
const { isLoading } = useFetchKnowledgeList(); const { isLoading } = useFetchKnowledgeList();
const list = listData.map((item) => ({ const list = listData.map(({ kb_info, kb_name }) => ({
intro: '知识库简介', intro: kb_info,
// 等接口更改... name: kb_name
name: item as unknown as string
})) }))
// const list = [
// { intro: '知识库简介', name: '知识库名称' },
// { intro: '知识库简介', name: '知识库名称' },
// ];
if (!isLoading && !listData.length) { if (!isLoading && !listData.length) {
return <div className={styles.null}> return <div className={styles.null}>
<Empty /> <Empty />

View File

@ -1,37 +1,43 @@
import { Modal, type ModalProps } from '@lobehub/ui'; import { Modal, type ModalProps } from '@lobehub/ui';
import { Form, Input, Select, FormInstance, message } from 'antd'; import { Form, Input, Select, FormInstance, message } from 'antd';
import { memo, useRef } from 'react'; import { memo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit'; import { Flexbox } from 'react-layout-kit';
import { useKnowledgeStore } from '@/store/knowledge'; import { useKnowledgeStore } from '@/store/knowledge';
const DEFAULT_FIELD_VALUE = { const DEFAULT_FIELD_VALUE = {
vector_store_type: "faiss", vector_store_type: "faiss",
embed_model:"bge-large-zh-v1.5" embed_model: "bge-large-zh-v1.5"
}; };
interface ModalCreateKnowledgeProps extends ModalProps { interface ModalCreateKnowledgeProps extends ModalProps {
toggleModal: (open: boolean) => void; toggleModal: (open: boolean) => void;
} }
const CreateKnowledgeBase = memo<ModalCreateKnowledgeProps>(({ toggleModal, open }) => { const CreateKnowledgeBase = memo<ModalCreateKnowledgeProps>(({ toggleModal, open }) => {
const [confirmLoading, setConfirmLoading] = useState(false);
const { t } = useTranslation('chat'); const { t } = useTranslation('chat');
const antdFormInstance = useRef<FormInstance>(); const antdFormInstance = useRef<FormInstance>();
const [useFetchKnowledgeAdd] = useKnowledgeStore((s) => [ const [useFetchKnowledgeAdd, useFetchKnowledgeList] = useKnowledgeStore((s) => [
s.useFetchKnowledgeAdd s.useFetchKnowledgeAdd, s.useFetchKnowledgeList
]); ]);
const { mutate } = useFetchKnowledgeList()
const onSubmit = async () => { const onSubmit = async () => {
if(!antdFormInstance.current) return; if (!antdFormInstance.current) return;
const fieldsError = await antdFormInstance.current.validateFields(); const fieldsError = await antdFormInstance.current.validateFields();
if(fieldsError.length) return; if (fieldsError.length) return;
const values = antdFormInstance.current.getFieldsValue(true); const values = antdFormInstance.current.getFieldsValue(true);
const { code:resCode, data: resData, msg: resMsg } = await useFetchKnowledgeAdd({ ...values }) setConfirmLoading(true);
if(resCode !== 200){ const { code: resCode, data: resData, msg: resMsg } = await useFetchKnowledgeAdd({ ...values })
setConfirmLoading(true);
if (resCode !== 200) {
message.error(resMsg) message.error(resMsg)
return; return;
} }
toggleModal(false); mutate();
toggleModal(false);
} }
return ( return (
@ -43,10 +49,11 @@ const CreateKnowledgeBase = memo<ModalCreateKnowledgeProps>(({ toggleModal, open
onOk={onSubmit} onOk={onSubmit}
open={open} open={open}
title="创建知识库" title="创建知识库"
confirmLoading={confirmLoading}
> >
<Form initialValues={DEFAULT_FIELD_VALUE} layout="vertical" ref={antdFormInstance}> <Form initialValues={DEFAULT_FIELD_VALUE} layout="vertical" ref={antdFormInstance}>
<Form.Item label="知识库名称" name="knowledge_base_name" rules={[{ required: true, message: '请输入知识库名称' }]}> <Form.Item label="知识库名称" name="knowledge_base_name" rules={[{ required: true, message: '请输入知识库名称' }]}>
<Input autoFocus/> <Input autoFocus />
</Form.Item> </Form.Item>
<Form.Item label="知识库简介" name="kb_info"> <Form.Item label="知识库简介" name="kb_info">
<Input /> <Input />
@ -54,7 +61,7 @@ const CreateKnowledgeBase = memo<ModalCreateKnowledgeProps>(({ toggleModal, open
<Flexbox direction="horizontal" gap={10} justify="space-between"> <Flexbox direction="horizontal" gap={10} justify="space-between">
<div style={{ flex: '1' }}> <div style={{ flex: '1' }}>
<Form.Item label="向量库类型" name="vector_store_type" rules={[{ required: true, message: '请选择向量库类型' }]}> <Form.Item label="向量库类型" name="vector_store_type" rules={[{ required: true, message: '请选择向量库类型' }]}>
<Select> <Select>
<Select.Option value="faiss">faiss</Select.Option> <Select.Option value="faiss">faiss</Select.Option>
<Select.Option value="milvus">milvus</Select.Option> <Select.Option value="milvus">milvus</Select.Option>
<Select.Option value="zilliz">zilliz</Select.Option> <Select.Option value="zilliz">zilliz</Select.Option>
@ -66,9 +73,9 @@ const CreateKnowledgeBase = memo<ModalCreateKnowledgeProps>(({ toggleModal, open
</div> </div>
<div style={{ flex: '1' }}> <div style={{ flex: '1' }}>
<Form.Item label="Embedding模型" name="embed_model" rules={[{ required: true, message: '请选择Embedding模型' }]}> <Form.Item label="Embedding模型" name="embed_model" rules={[{ required: true, message: '请选择Embedding模型' }]}>
<Select> <Select>
<Select.Option value="bge-large-zh-v1.5">bge-large-zh-v1.5</Select.Option> <Select.Option value="bge-large-zh-v1.5">bge-large-zh-v1.5</Select.Option>
<Select.Option value="text-embedding-v1">text-embedding-v1</Select.Option> <Select.Option value="text-embedding-v1">text-embedding-v1</Select.Option>
<Select.Option value="Bert">Bert</Select.Option> <Select.Option value="Bert">Bert</Select.Option>
<Select.Option value="Word2Vec">Word2Vec</Select.Option> <Select.Option value="Word2Vec">Word2Vec</Select.Option>
<Select.Option value="FastText">FastText</Select.Option> <Select.Option value="FastText">FastText</Select.Option>

View File

@ -1,48 +1,154 @@
import { InboxOutlined } from '@ant-design/icons'; import { InboxOutlined } from '@ant-design/icons';
import type { UploadProps } from 'antd'; import { Form, Modal, Upload, InputNumber, Radio, message } from 'antd';
import { Modal, Upload, message } from 'antd'; import React, { memo, useState } from 'react';
import React, { memo } from 'react'; import { useKnowledgeStore } from '@/store/knowledge';
import type { GetProp, UploadFile, UploadProps } from 'antd';
const { Dragger } = Upload;
type ModalAddFileProps = { type ModalAddFileProps = {
kbName: string;
open: boolean; open: boolean;
setModalOpen: (open: boolean) => void; setModalOpen: (open: boolean) => void;
}; };
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
const props: UploadProps = { const ModalAddFile = memo<ModalAddFileProps>(({ open, setModalOpen, kbName }) => {
action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload', const [confirmLoading, setConfirmLoading] = useState(false);
multiple: true, const [antdFormInstance] = Form.useForm();
name: 'file', const [useFetchKnowledgeUploadDocs, useFetchKnowledgeFilesList] = useKnowledgeStore((s) => [
onChange(info) { s.useFetchKnowledgeUploadDocs, s.useFetchKnowledgeFilesList
const { status } = info.file; ]);
if (status !== 'uploading') { const { mutate } = useFetchKnowledgeFilesList(kbName)
console.log(info.file, info.fileList); const [fileList, setFileList] = useState<UploadFile[]>([]);
}
if (status === 'done') { const antdUploadProps: UploadProps = {
message.success(`${info.file.name} file uploaded successfully.`); name: "files",
} else if (status === 'error') { onRemove: (file) => {
message.error(`${info.file.name} file upload failed.`); const index = fileList.indexOf(file);
} const newFileList = fileList.slice();
}, newFileList.splice(index, 1);
onDrop(e) { setFileList(newFileList);
console.log('Dropped files', e.dataTransfer.files); },
}, beforeUpload: (file) => {
}; setFileList([...fileList, file]);
return false;
},
fileList,
};
const onSubmit = async () => {
if (!antdFormInstance) return;
const fieldsError = await antdFormInstance.validateFields();
if (fieldsError.length) return;
const values = antdFormInstance.getFieldsValue(true);
if(!fileList.length){
message.error('请选择文件')
return;
}
const formData = new FormData();
fileList.forEach((file) => {
formData.append('files', file as FileType);
});
for (const key in values) {
formData.append(key, values[key]);
}
formData.append('knowledge_base_name', kbName);
setConfirmLoading(true);
const { code: resCode, data: resData, msg: resMsg } = await useFetchKnowledgeUploadDocs(formData)
setConfirmLoading(true);
if (resCode !== 200) {
message.error(resMsg)
return;
}
message.success(resMsg)
mutate();
setModalOpen(false);
}
const layout = {
labelCol: { span: 10 },
wrapperCol: { span: 14 },
}
const ModalAddFile = memo<ModalAddFileProps>(({ open, setModalOpen }) => {
return ( return (
<Modal onCancel={() => setModalOpen(false)} open={open} title="添加文件"> <Modal
<Dragger {...props}> onCancel={() => setModalOpen(false)}
<p className="ant-upload-drag-icon"> open={open} title="添加文件"
<InboxOutlined /> onOk={onSubmit}
</p> confirmLoading={confirmLoading}
<p className="ant-upload-text">Click or drag file to this area to upload</p> width={600}
<p className="ant-upload-hint"> >
Support for a single or bulk upload. Strictly prohibited from uploading company data or
other banned files. <Form
</p> name="validate_other"
</Dragger> initialValues={{
override: true,
chunk_size: 0,
chunk_overlap: 0,
to_vector_store: true,
}}
form={antdFormInstance}
>
<div style={{ padding: `24px 0px` }}>
<Upload.Dragger {...antdUploadProps}>
<p className="ant-upload-drag-icon">
<InboxOutlined />
</p>
<p className="ant-upload-text"></p>
<p className="ant-upload-hint"></p>
</Upload.Dragger>
</div>
<Form.Item name="override" label="覆盖已有文件" {...layout}>
<Radio.Group>
<Radio value={true}></Radio>
<Radio value={false}></Radio>
</Radio.Group>
</Form.Item>
<Form.Item name="to_vector_store" label="上传文件后是否进行向量化" {...layout}>
<Radio.Group>
<Radio value={true}></Radio>
<Radio value={false}></Radio>
</Radio.Group>
</Form.Item>
<Form.Item name="zh_title_enhance" label="是否开启中文标题加强" {...layout}>
<Radio.Group>
<Radio value={true}></Radio>
<Radio value={false}></Radio>
</Radio.Group>
</Form.Item>
<Form.Item name="not_refresh_vs_cache" label="暂不保存向量库用于FAISS" {...layout}>
<Radio.Group>
<Radio value={true}></Radio>
<Radio value={false}></Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="知识库中单段文本最大长度" {...layout} {...layout}>
<Form.Item name="chunk_size">
<InputNumber min={0} />
</Form.Item>
</Form.Item>
<Form.Item label="知识库中相邻文本重合长度" {...layout} {...layout}>
<Form.Item name="chunk_overlap">
<InputNumber min={0} />
</Form.Item>
</Form.Item>
{/* <Form.Item label="docs">
<Form.Item name="docs">
<Input />
</Form.Item>
</Form.Item> */}
</Form>
</Modal> </Modal>
); );
}); });

View File

@ -1,37 +1,69 @@
'use client'; 'use client';
import { Button, Table } from 'antd'; import { Button, Table, message, Spin, Modal } from 'antd';
import type { TableColumnsType } from 'antd'; import type { TableColumnsType } from 'antd';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
import Link from 'next/link'; import Link from 'next/link';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { Flexbox } from 'react-layout-kit'; import { Flexbox } from 'react-layout-kit';
import { useKnowledgeStore } from '@/store/knowledge';
import { useRouter } from 'next/navigation';
import { UndoOutlined, DeleteOutlined, DownloadOutlined, PlusOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
// import ModalAddFile from './features/ModalAddFile';
const ModalAddFile = dynamic(() => import('./features/ModalAddFile')); const ModalAddFile = dynamic(() => import('./features/ModalAddFile'));
interface DataType { interface DataType {
address: string; id: React.Key;
age: number;
key: React.Key;
name: string; name: string;
loader: string;
splitter: string;
source: string;
vector: string;
} }
const data: DataType[] = []; const App: React.FC<{ params: { id: string } }> = ({ params }) => {
for (let i = 0; i < 46; i++) { const router = useRouter();
data.push({ const [
address: `London, Park Lane no. ${i}`, filesData,
age: 32, useFetchKnowledgeFilesList,
index: i, useFetchKnowledgeDownloadDocs,
key: i, useFetcDelInknowledgeDB,
name: `Edward King ${i}`, useFetcRebuildVectorDB,
}); useFetchKnowledgeDel
}
const App: React.FC<{ params }> = ({ params }) => { ] = useKnowledgeStore((s) => [
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]); s.filesData,
s.useFetchKnowledgeFilesList,
s.useFetchKnowledgeDownloadDocs,
s.useFetcDelInknowledgeDB,
s.useFetcRebuildVectorDB,
s.useFetchKnowledgeDel
]);
const { isLoading } = useFetchKnowledgeFilesList(params.id);
const [downloadLoading, setDownloadLoading] = useState(false);
const [delDocsLoading, setDelDocsLoading] = useState(false);
const [rebuildVectorDBLoading, setRebuildVectorDBLoading] = useState(false);
// rebuild progress
const [rebuildProgress, setRebuildProgress] = useState("0%");
// const data: DataType[] = filesData.map((item, i) => ({
// id: i, // item 应该为对象,待提交问题...
// name: item, // item 应该为对象,待提交问题...
// loader: "",
// splitter: "",
// source: "",
// vector: ""
// }));
const data = [
{ id: '1', name: 'name1', loader: "loader", splitter: "splitter", source: "source", vector: "vector" },
{ id: '2', name: 'name2', loader: "loader", splitter: "splitter", source: "source", vector: "vector" },
];
const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [isShowModal, setModal] = useState(false); const [isShowModal, setModal] = useState(false);
const onSelectChange = (newSelectedRowKeys: React.Key[]) => { const onSelectChange = (newSelectedRowKeys: string[]) => {
console.log('selectedRowKeys changed:', newSelectedRowKeys); console.log('selectedRowKeys changed:', newSelectedRowKeys);
setSelectedRowKeys(newSelectedRowKeys); setSelectedRowKeys(newSelectedRowKeys);
}; };
@ -42,17 +74,13 @@ const App: React.FC<{ params }> = ({ params }) => {
}, },
{ {
dataIndex: 'name', dataIndex: 'name',
render: (text) => <Link href={`/knowledge/${params.id}/base/2`}>{text}</Link>, render: (text, rowData) => <Link href={`/knowledge/${params.id}/base/${rowData.id}`}>{text}</Link>,
title: '文档名称', title: '文档名称',
}, },
{ {
dataIndex: 'loader', dataIndex: 'loader',
title: '文档加载器', title: '文档加载器',
}, },
{
dataIndex: 'loader',
title: '文档加载器',
},
{ {
dataIndex: 'splitter', dataIndex: 'splitter',
title: '分词器', title: '分词器',
@ -71,40 +99,126 @@ const App: React.FC<{ params }> = ({ params }) => {
selectedRowKeys, selectedRowKeys,
}; };
const hasSelected = selectedRowKeys.length > 0; const hasSelected = selectedRowKeys.length > 0;
console.log(params);
const download = async () => {
setDownloadLoading(true);
for (const docName of selectedRowKeys) {
await useFetchKnowledgeDownloadDocs(params.id, docName).catch(() => {
message.error(`下载 ${docName} 失败`);
})
}
setDownloadLoading(false);
};
const reAddVectorDB = async () => {
console.log('reAddShapeDB: ', selectedRowKeys);
}
const rebuildVectorDB = async () => {
console.log('rebuildVectorDB ');
setRebuildVectorDBLoading(true);
try {
useFetcRebuildVectorDB({
"knowledge_base_name": params.id,
"allow_empty_kb": true,
"vs_type": "faiss",
"embed_model": "text-embedding-v1",
"chunk_size": 250,
"chunk_overlap": 50,
"zh_title_enhance": false,
"not_refresh_vs_cache": false,
}, {
onFinish: async () => {
message.success(`重建向量库成功`);
setRebuildVectorDBLoading(false);
},
onMessageHandle: (text) => {
setRebuildProgress(text)
}
})
} catch (err) {
message.error(`请求错误`);
}
setRebuildVectorDBLoading(false);
}
const delInVectorDB = async () => {
console.log('delInShapeDB: ', selectedRowKeys);
setSelectedRowKeys([]);
}
const delInknowledgeDB = async () => {
setDelDocsLoading(true);
await useFetcDelInknowledgeDB({
"knowledge_base_name": params.id,
"file_names": [...selectedRowKeys],
"delete_content": false,
"not_refresh_vs_cache": false
}).catch(() => {
message.error(`删除失败`);
})
setDelDocsLoading(false);
setSelectedRowKeys([]);
}
const delKnowledge = async () => {
Modal.confirm({
title: `确认 ${params.id} 删除吗?`,
icon: <ExclamationCircleOutlined />,
async onOk() {
const { code: resCode, msg: resMsg } = await useFetchKnowledgeDel(params.id)
if (resCode !== 200) {
message.error(resMsg)
} else {
message.success(resMsg);
router.push('/knowledge')
}
return Promise.resolve();
},
});
}
return ( return (
<> <>
<Flexbox width={'100%'}> <Flexbox width={'100%'}>
<Flexbox direction="horizontal" justify="space-between" style={{ marginBottom: 16 }}> <Flexbox direction="horizontal" justify="space-between" style={{ marginBottom: 16 }}>
<Flexbox direction="horizontal" gap={20}> <Flexbox direction="horizontal" gap={20}>
<Button disabled={!hasSelected} loading={loading} type="default"> <Button disabled={!hasSelected} loading={downloadLoading} type="default" onClick={download} icon={<DownloadOutlined />}>
</Button> </Button>
<Button disabled={!hasSelected} loading={loading} type="default"> <Button disabled={!hasSelected} loading={loading} type="default" onClick={reAddVectorDB}>
</Button> </Button>
<Button disabled={!hasSelected} loading={loading} type="default"> <Button danger disabled={!hasSelected} loading={loading} type="default" onClick={delInVectorDB} icon={<DeleteOutlined />}>
</Button> </Button>
<Button disabled={!hasSelected} loading={loading} type="default"> <Button danger disabled={!hasSelected} loading={delDocsLoading} type="default" onClick={delInknowledgeDB} icon={<DeleteOutlined />}>
</Button> </Button>
</Flexbox> </Flexbox>
<div> <Flexbox direction="horizontal" gap={20}>
<Button loading={loading} onClick={() => setModal(true)} type="primary"> <Button danger loading={loading} onClick={delKnowledge} type="primary" icon={<DeleteOutlined />}>
</Button>
<Button loading={loading} onClick={() => setModal(true)} type="primary" icon={<PlusOutlined />}>
</Button> </Button>
</div> </Flexbox>
</Flexbox> </Flexbox>
<Table <Spin spinning={rebuildVectorDBLoading} tip={rebuildProgress}>
columns={columns} <Table
dataSource={data} columns={columns}
rowSelection={rowSelection} dataSource={data}
size="middle" rowSelection={rowSelection}
style={{ width: '100%' }} size="middle"
/> style={{ width: '100%' }}
</Flexbox> rowKey={"name"}
<ModalAddFile open={isShowModal} setModalOpen={setModal} /> loading={isLoading}
footer={() => <div>
<Button danger loading={rebuildVectorDBLoading} onClick={rebuildVectorDB} icon={<UndoOutlined />}>
</Button>
</div>}
/>
</Spin>
</Flexbox >
<ModalAddFile open={isShowModal} setModalOpen={setModal} kbName={params.id} />
</> </>
); );
}; };

View File

@ -12,7 +12,7 @@ interface LayoutProps extends PropsWithChildren {
params: Record<string, string>; params: Record<string, string>;
} }
export default memo<LayoutProps>(({ children, params }) => { export default memo<LayoutProps>(({ children, params }) => {
console.log(params); // console.log(params);
return ( return (
<AppLayoutDesktop sidebarKey={SidebarTabKey.Knowledge}> <AppLayoutDesktop sidebarKey={SidebarTabKey.Knowledge}>

View File

@ -48,5 +48,13 @@ export const API_ENDPOINTS = mapWithBasePath({
// knowledge // knowledge
knowledgeList: '/api/knowledge/list', knowledgeList: '/api/knowledge/list',
knowledgeAdd: '/api/knowledge/add', knowledgeAdd: '/api/knowledge/add',
knowledgeDel: '/api/knowledge/del' knowledgeDel: '/api/knowledge/del',
// knowledge files
knowledgeFilesList: '/api/knowledge/listFiles',
knowledgeUploadDocs: '/api/knowledge/uploadDocs',
knowledgeDownloadDocs: '/api/knowledge/downloadDocs',
knowledgeDelInknowledgeDB: '/api/knowledge/deleteDocs',
knowledgeRebuildVectorDB: '/api/knowledge/rebuildVectorDB',
knowledgeReAddVectorDB: '/api/knowledge/reAddVectorDB',
}); });

View File

@ -1,16 +1,21 @@
import { KnowledgeList, KnowledgeFormFields, Reseponse } from '@/types/knowledge'; import type {
KnowledgeList, KnowledgeFormFields, Reseponse,
KnowledgeFilesList, KnowledgeDelDocsParams, KnowledgeDelDocsRes,
KnowledgeRebuildVectorParams, KnowledgeRebuildVectorRes
} from '@/types/knowledge';
import { fetchSSE, FetchSSEOptions } from '@/utils/fetch';
import { API_ENDPOINTS } from './_url'; import { API_ENDPOINTS } from './_url';
class KnowledgeService { class KnowledgeService {
getList = async (): Promise<Reseponse<KnowledgeList>> => { getList = async (): Promise<Reseponse<KnowledgeList>> => {
const res = await fetch(`${API_ENDPOINTS.knowledgeList}`); const res = await fetch(`${API_ENDPOINTS.knowledgeList}`);
const data = await res.json(); const data = await res.json();
return data; return data;
}; };
add = async (formValues: KnowledgeFormFields) => { add = async (formValues: KnowledgeFormFields) => {
const res = await fetch(`${API_ENDPOINTS.knowledgeAdd}`, { const res = await fetch(`${API_ENDPOINTS.knowledgeAdd}`, {
body: JSON.stringify(formValues), body: JSON.stringify(formValues),
headers: { headers: {
@ -21,7 +26,7 @@ class KnowledgeService {
return res.json(); return res.json();
}; };
del = async (name: string) => { del = async (name: string) => {
const res = await fetch(`${API_ENDPOINTS.knowledgeDel}`, { const res = await fetch(`${API_ENDPOINTS.knowledgeDel}`, {
body: JSON.stringify(name), body: JSON.stringify(name),
headers: { headers: {
@ -31,6 +36,96 @@ class KnowledgeService {
}); });
return res.json(); return res.json();
}; };
getFilesList = (name: string): () => Promise<Reseponse<KnowledgeFilesList>> => {
const queryString = new URLSearchParams({
knowledge_base_name: name
}).toString();
return async () => {
const res = await fetch(`${API_ENDPOINTS.knowledgeFilesList}?${queryString}`);
const data = await res.json();
return data;
}
};
uploadDocs = async (formData: FormData): Promise<Reseponse<{}>> => {
const res = await fetch(`${API_ENDPOINTS.knowledgeUploadDocs}`, {
body: formData,
method: 'POST',
});
return res.json();
};
delInknowledgeDB = async (params: KnowledgeDelDocsParams): Promise<Reseponse<KnowledgeDelDocsRes>> => {
const res = await fetch(`${API_ENDPOINTS.knowledgeDelInknowledgeDB}`, {
body: JSON.stringify({
...params,
}),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
});
return res.json();
};
rebuildVectorDB = async (params: KnowledgeRebuildVectorParams, opts:
{ onFinish: FetchSSEOptions["onFinish"]; onMessageHandle: FetchSSEOptions["onMessageHandle"] }
) => {
const { onFinish, onMessageHandle } = opts;
fetchSSE(async ()=> await fetch(`${API_ENDPOINTS.knowledgeRebuildVectorDB}`, {
body: JSON.stringify({
...params,
}),
headers: {
'Content-Type': 'application/json',
},
method: 'POST'
}), {
onErrorHandle: (error) => {
throw new Error('请求错误:' + error);
},
onFinish,
onMessageHandle
})
};
// delVectorDocs = async (formData: FormData): Promise<Reseponse<{}>> => {
// // const res = await fetch(`${API_ENDPOINTS.knowledgeUploadDocs}`, {
// // body: formData,
// // // headers: {
// // // 'Content-Type': 'application/json',
// // // },
// // method: 'POST',
// // });
// // return res.json();
// };
downloadDocs = async (kbName: string, docName: string): Promise<Reseponse<{}>> => {
const queryString = new URLSearchParams({
knowledge_base_name: kbName,
file_name: docName,
preview: 'false'
}).toString();
const res = await fetch(`${API_ENDPOINTS.knowledgeDownloadDocs}?${queryString}`);
console.log('res', res)
const data = await res.json();
return data;
};
// reAddVectorDB = async (formData: FormData): Promise<Reseponse<{}>> => {
// // const res = await fetch(`${API_ENDPOINTS.knowledgeUploadDocs}`, {
// // body: formData,
// // // headers: {
// // // 'Content-Type': 'application/json',
// // // },
// // method: 'POST',
// // });
// // return res.json();
// };
} }
export const knowledgeService = new KnowledgeService(); export const knowledgeService = new KnowledgeService();

View File

@ -5,7 +5,13 @@ import type { StateCreator } from 'zustand/vanilla';
import { knowledgeService } from '@/services/knowledge'; import { knowledgeService } from '@/services/knowledge';
import { globalHelpers } from '@/store/global/helpers'; import { globalHelpers } from '@/store/global/helpers';
import { KnowledgeFormFields, KnowledgeList, Reseponse } from '@/types/knowledge'; import {
KnowledgeFormFields, KnowledgeList, Reseponse, KnowledgeFilesList,
KnowledgeDelDocsParams, KnowledgeDelDocsRes,
KnowledgeRebuildVectorParams, KnowledgeRebuildVectorRes
} from '@/types/knowledge';
import type { FetchSSEOptions } from '@/utils/fetch';
import type { Store } from './store'; import type { Store } from './store';
@ -13,7 +19,19 @@ export interface StoreAction {
listData: KnowledgeList; listData: KnowledgeList;
useFetchKnowledgeList: () => SWRResponse<Reseponse<KnowledgeList>>; useFetchKnowledgeList: () => SWRResponse<Reseponse<KnowledgeList>>;
useFetchKnowledgeAdd: (arg: KnowledgeFormFields) => Promise<Reseponse<KnowledgeFormFields>>; useFetchKnowledgeAdd: (arg: KnowledgeFormFields) => Promise<Reseponse<KnowledgeFormFields>>;
useFetchKnowledgeDel: (name: string)=> Promise<Reseponse<{}>>; useFetchKnowledgeDel: (name: string) => Promise<Reseponse<{}>>;
// files
filesData: KnowledgeFilesList;
useFetchKnowledgeFilesList: (name: string) => SWRResponse<Reseponse<KnowledgeFilesList>>;
useFetchKnowledgeUploadDocs: (arg: FormData) => Promise<Reseponse<{}>>;
useFetchKnowledgeDownloadDocs: (kbName: string, docName: string) => Promise<Reseponse<{}>>;
useFetcDelInknowledgeDB: (arg: KnowledgeDelDocsParams) => Promise<Reseponse<KnowledgeDelDocsRes>>;
useFetcRebuildVectorDB: (arg: KnowledgeRebuildVectorParams, options: {
onFinish: FetchSSEOptions["onFinish"];
onMessageHandle: FetchSSEOptions["onMessageHandle"]
}) => void;
} }
export const createKnowledgeAction: StateCreator< export const createKnowledgeAction: StateCreator<
@ -28,7 +46,7 @@ export const createKnowledgeAction: StateCreator<
globalHelpers.getCurrentLanguage(), globalHelpers.getCurrentLanguage(),
knowledgeService.getList, knowledgeService.getList,
{ {
onSuccess: (res) => { onSuccess: (res) => {
set({ listData: res.data }) set({ listData: res.data })
}, },
}, },
@ -40,4 +58,31 @@ export const createKnowledgeAction: StateCreator<
useFetchKnowledgeDel: async (name) => { useFetchKnowledgeDel: async (name) => {
return await knowledgeService.del(name) return await knowledgeService.del(name)
}, },
filesData: [],
useFetchKnowledgeFilesList: (knowledge_base_name) => {
return useSWR<Reseponse<KnowledgeFilesList>>(
globalHelpers.getCurrentLanguage(),
knowledgeService.getFilesList(knowledge_base_name),
{
onSuccess: (res) => {
set({ filesData: res.data })
},
},
)
},
useFetchKnowledgeUploadDocs: (formData) => {
return knowledgeService.uploadDocs(formData);
},
useFetchKnowledgeDownloadDocs: (kbName: string, docName: string) => {
return knowledgeService.downloadDocs(kbName, docName);
},
useFetcDelInknowledgeDB: (params) => {
return knowledgeService.delInknowledgeDB(params);
},
useFetcRebuildVectorDB: (params, options) => {
return knowledgeService.rebuildVectorDB(params, options);
}
}); });

View File

@ -1,5 +1,7 @@
export interface Reseponse <T> { code: number; msg: string; data: T } export interface Reseponse<T> { code: number; msg: string; data: T }
// create Knowledge fields
export interface KnowledgeFormFields { export interface KnowledgeFormFields {
knowledge_base_name: string; knowledge_base_name: string;
vector_store_type: string; vector_store_type: string;
@ -7,6 +9,49 @@ export interface KnowledgeFormFields {
embed_model: string; embed_model: string;
} }
export interface KnowledgeListItemFields extends KnowledgeFormFields { } // Knowledge base list
export interface KnowledgeListFields {
"id": number;
"kb_name": string;
"kb_info": string;
"vs_type": string;
"embed_model": string;
"file_count": number;
"create_time": string;
}
export type KnowledgeList = KnowledgeListFields[];
// Knowledge base file list
export type KnowledgeFilesFields = string;
export type KnowledgeFilesList = KnowledgeFilesFields[];
// Example Delete parameters of the knowledge base file
export interface KnowledgeDelDocsParams {
knowledge_base_name: string;
file_names: string[];
delete_content: boolean;
not_refresh_vs_cache: boolean;
}
export interface KnowledgeDelDocsRes {
knowledge_base_name: string;
file_names: string[];
delete_content: boolean;
not_refresh_vs_cache: boolean;
}
// Rebuild the vector library
export interface KnowledgeRebuildVectorParams {
"knowledge_base_name": string;
"allow_empty_kb": boolean;
"vs_type": string;
"embed_model": string
"chunk_size": number;
"chunk_overlap": number;
"zh_title_enhance": boolean;
"not_refresh_vs_cache": boolean;
}
export interface KnowledgeRebuildVectorRes { }
export type KnowledgeList = KnowledgeListItemFields[];