diff --git a/frontend/src/app/api/knowledge/delVectorDocs/route.ts b/frontend/src/app/api/knowledge/delVectorDocs/route.ts new file mode 100644 index 00000000..2dad6f62 --- /dev/null +++ b/frontend/src/app/api/knowledge/delVectorDocs/route.ts @@ -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 +}; diff --git a/frontend/src/app/api/knowledge/deleteDocs/route.ts b/frontend/src/app/api/knowledge/deleteDocs/route.ts new file mode 100644 index 00000000..2dad6f62 --- /dev/null +++ b/frontend/src/app/api/knowledge/deleteDocs/route.ts @@ -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 +}; diff --git a/frontend/src/app/api/knowledge/downloadDocs/route.ts b/frontend/src/app/api/knowledge/downloadDocs/route.ts new file mode 100644 index 00000000..0877da14 --- /dev/null +++ b/frontend/src/app/api/knowledge/downloadDocs/route.ts @@ -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; +}; diff --git a/frontend/src/app/api/knowledge/listFiles/route.ts b/frontend/src/app/api/knowledge/listFiles/route.ts new file mode 100644 index 00000000..b625da85 --- /dev/null +++ b/frontend/src/app/api/knowledge/listFiles/route.ts @@ -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; +}; diff --git a/frontend/src/app/api/knowledge/reAddVectorDB/route.ts b/frontend/src/app/api/knowledge/reAddVectorDB/route.ts new file mode 100644 index 00000000..da805580 --- /dev/null +++ b/frontend/src/app/api/knowledge/reAddVectorDB/route.ts @@ -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}/update_docs`, { + body: JSON.stringify(params), + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + }); + return fetchRes +}; diff --git a/frontend/src/app/api/knowledge/rebuildVectorDB/route.ts b/frontend/src/app/api/knowledge/rebuildVectorDB/route.ts new file mode 100644 index 00000000..cc59e1d5 --- /dev/null +++ b/frontend/src/app/api/knowledge/rebuildVectorDB/route.ts @@ -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 +}; diff --git a/frontend/src/app/api/knowledge/update/route.ts b/frontend/src/app/api/knowledge/update/route.ts new file mode 100644 index 00000000..b9c04cf7 --- /dev/null +++ b/frontend/src/app/api/knowledge/update/route.ts @@ -0,0 +1,15 @@ + +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}/update_info`, { + body: JSON.stringify(params), + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + }); + return fetchRes +}; diff --git a/frontend/src/app/api/knowledge/uploadDocs/route.ts b/frontend/src/app/api/knowledge/uploadDocs/route.ts new file mode 100644 index 00000000..a5902c72 --- /dev/null +++ b/frontend/src/app/api/knowledge/uploadDocs/route.ts @@ -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 +}; diff --git a/frontend/src/app/api/knowledge/接口存在问题.txt b/frontend/src/app/api/knowledge/接口存在问题.txt deleted file mode 100644 index 4547f0d7..00000000 --- a/frontend/src/app/api/knowledge/接口存在问题.txt +++ /dev/null @@ -1,3 +0,0 @@ - - -1. 创建接口没有简介字段 diff --git a/frontend/src/app/knowledge/(desktop)/features/KnowledgeCard.tsx b/frontend/src/app/knowledge/(desktop)/features/KnowledgeCard.tsx index 81d9bf03..4a7bba15 100644 --- a/frontend/src/app/knowledge/(desktop)/features/KnowledgeCard.tsx +++ b/frontend/src/app/knowledge/(desktop)/features/KnowledgeCard.tsx @@ -9,18 +9,28 @@ const { Meta } = Card; interface KnowLedgeCardProps { intro: string; name: string; + vector_store_type?: string; + embed_model?: string; } const KnowledgeCard: React.FC = (props: KnowLedgeCardProps) => { - const [useFetchKnowledgeDel] = useKnowledgeStore((s) => [ - s.useFetchKnowledgeDel + + const [useFetchKnowledgeDel, useFetchKnowledgeList, setEditKnowledge] = useKnowledgeStore((s) => [ + s.useFetchKnowledgeDel, s.useFetchKnowledgeList, s.setEditKnowledge ]); + const { mutate } = useFetchKnowledgeList() const [loading, setLoading] = useState(false); const { name, intro } = props; const router = useRouter(); const handleCardEditClick = () => { - router.push('/knowledge/1/base'); + setEditKnowledge({ + knowledge_base_name: props.name, + kb_info: props.intro, + vector_store_type: props.vector_store_type, + embed_model:props.embed_model, + }); + router.push(`/knowledge/${encodeURIComponent(name)}/base`); }; const delClick = async () => { Modal.confirm({ @@ -32,6 +42,7 @@ const KnowledgeCard: React.FC = (props: KnowLedgeCardProps) message.error(resMsg) } else { message.success(resMsg) + mutate() } return Promise.resolve(); }, diff --git a/frontend/src/app/knowledge/(desktop)/features/KnowledgeList.tsx b/frontend/src/app/knowledge/(desktop)/features/KnowledgeList.tsx index 3ecb7737..d65d1161 100644 --- a/frontend/src/app/knowledge/(desktop)/features/KnowledgeList.tsx +++ b/frontend/src/app/knowledge/(desktop)/features/KnowledgeList.tsx @@ -20,11 +20,6 @@ const useStyles = createStyles(({ css, token, stylish }) => ({ `, })); -// const list = [ -// { intro: '知识库简介', name: '知识库名称' }, -// { intro: '知识库简介', name: '知识库名称' }, -// ]; - const RenderList = memo(() => { const { styles } = useStyles(); const [listData, useFetchKnowledgeList] = useKnowledgeStore((s) => [ @@ -32,11 +27,15 @@ const RenderList = memo(() => { ]); const { isLoading } = useFetchKnowledgeList(); - const list = listData.map((item) => ({ - intro: '知识库简介', - // 等接口更改... - name: item as unknown as string + const list = listData.map(({ kb_info, kb_name }) => ({ + intro: kb_info, + name: kb_name })) + // const list = [ + // { intro: '知识库简介', name: '知识库名称' }, + // { intro: '知识库简介', name: '知识库名称' }, + // ]; + if (!isLoading && !listData.length) { return
diff --git a/frontend/src/app/knowledge/(desktop)/features/ModalCreateKnowledge.tsx b/frontend/src/app/knowledge/(desktop)/features/ModalCreateKnowledge.tsx index 68442543..fd2f3cfc 100644 --- a/frontend/src/app/knowledge/(desktop)/features/ModalCreateKnowledge.tsx +++ b/frontend/src/app/knowledge/(desktop)/features/ModalCreateKnowledge.tsx @@ -1,37 +1,43 @@ import { Modal, type ModalProps } from '@lobehub/ui'; 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 { Flexbox } from 'react-layout-kit'; -import { useKnowledgeStore } from '@/store/knowledge'; +import { useKnowledgeStore } from '@/store/knowledge'; -const DEFAULT_FIELD_VALUE = { - vector_store_type: "faiss", - embed_model:"bge-large-zh-v1.5" +const DEFAULT_FIELD_VALUE = { + vector_store_type: "faiss", + embed_model: "bge-large-zh-v1.5" }; interface ModalCreateKnowledgeProps extends ModalProps { toggleModal: (open: boolean) => void; } const CreateKnowledgeBase = memo(({ toggleModal, open }) => { + const [confirmLoading, setConfirmLoading] = useState(false); const { t } = useTranslation('chat'); const antdFormInstance = useRef(); - const [useFetchKnowledgeAdd] = useKnowledgeStore((s) => [ - s.useFetchKnowledgeAdd + const [useFetchKnowledgeAdd, useFetchKnowledgeList] = useKnowledgeStore((s) => [ + s.useFetchKnowledgeAdd, s.useFetchKnowledgeList ]); + const { mutate } = useFetchKnowledgeList() const onSubmit = async () => { - if(!antdFormInstance.current) return; + if (!antdFormInstance.current) return; const fieldsError = await antdFormInstance.current.validateFields(); - if(fieldsError.length) return; - const values = antdFormInstance.current.getFieldsValue(true); + if (fieldsError.length) return; + const values = antdFormInstance.current.getFieldsValue(true); - const { code:resCode, data: resData, msg: resMsg } = await useFetchKnowledgeAdd({ ...values }) - if(resCode !== 200){ + setConfirmLoading(true); + const { code: resCode, data: resData, msg: resMsg } = await useFetchKnowledgeAdd({ ...values }) + setConfirmLoading(true); + + if (resCode !== 200) { message.error(resMsg) return; } - toggleModal(false); + mutate(); + toggleModal(false); } return ( @@ -43,10 +49,11 @@ const CreateKnowledgeBase = memo(({ toggleModal, open onOk={onSubmit} open={open} title="创建知识库" + confirmLoading={confirmLoading} >
- + @@ -54,7 +61,7 @@ const CreateKnowledgeBase = memo(({ toggleModal, open
- faiss milvus zilliz @@ -66,9 +73,9 @@ const CreateKnowledgeBase = memo(({ toggleModal, open
- bge-large-zh-v1.5 - text-embedding-v1 + text-embedding-v1 Bert Word2Vec FastText diff --git a/frontend/src/app/knowledge/(desktop)/index.tsx b/frontend/src/app/knowledge/(desktop)/index.tsx index bc9357a9..3699fe6e 100644 --- a/frontend/src/app/knowledge/(desktop)/index.tsx +++ b/frontend/src/app/knowledge/(desktop)/index.tsx @@ -17,7 +17,8 @@ const DesktopPage = memo(() => { <> - } onClick={() => setShowModal(true)}> + } onClick={() => setShowModal(true)}> 新建知识库 diff --git a/frontend/src/app/knowledge/[id]/base/features/ModalAddFile.tsx b/frontend/src/app/knowledge/[id]/base/features/ModalAddFile.tsx index 734159bc..e1f04ad6 100644 --- a/frontend/src/app/knowledge/[id]/base/features/ModalAddFile.tsx +++ b/frontend/src/app/knowledge/[id]/base/features/ModalAddFile.tsx @@ -1,48 +1,172 @@ import { InboxOutlined } from '@ant-design/icons'; -import type { UploadProps } from 'antd'; -import { Modal, Upload, message } from 'antd'; -import React, { memo } from 'react'; - -const { Dragger } = Upload; +import { Form, Modal, Upload, InputNumber, Radio, message, Input } from 'antd'; +import React, { memo, useState } from 'react'; +import { useKnowledgeStore } from '@/store/knowledge'; +import type { GetProp, UploadFile, UploadProps } from 'antd'; +import type { KnowledgeUplodDocsParams } from '@/types/knowledge'; type ModalAddFileProps = { + kbName: string; open: boolean; setModalOpen: (open: boolean) => void; + setSelectedRowKeys: React.Dispatch>; + + selectedRowKeys: string[]; + isRebuildVectorDB?: boolean; + initialValue?: KnowledgeUplodDocsParams; }; -const props: UploadProps = { - action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload', - multiple: true, - name: 'file', - onChange(info) { - const { status } = info.file; - if (status !== 'uploading') { - console.log(info.file, info.fileList); - } - if (status === 'done') { - message.success(`${info.file.name} file uploaded successfully.`); - } else if (status === 'error') { - message.error(`${info.file.name} file upload failed.`); - } - }, - onDrop(e) { - console.log('Dropped files', e.dataTransfer.files); - }, -}; +type FileType = Parameters>[0]; + +const ModalAddFile = memo(({ open, setModalOpen, setSelectedRowKeys, selectedRowKeys, kbName, initialValue, isRebuildVectorDB }) => { + const [confirmLoading, setConfirmLoading] = useState(false); + const [antdFormInstance] = Form.useForm(); + const [useFetchKnowledgeUploadDocs, useFetchKnowledgeFilesList, useFetcReAddVectorDB] = useKnowledgeStore((s) => [ + s.useFetchKnowledgeUploadDocs, s.useFetchKnowledgeFilesList, + s.useFetcReAddVectorDB + ]); + const { mutate } = useFetchKnowledgeFilesList(kbName) + const [fileList, setFileList] = useState([]); + + const antdUploadProps: UploadProps = { + name: "files", + onRemove: (file) => { + const index = fileList.indexOf(file); + const newFileList = fileList.slice(); + newFileList.splice(index, 1); + setFileList(newFileList); + }, + 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 (isRebuildVectorDB) { + // Re-add to vector library + setConfirmLoading(true); + await useFetcReAddVectorDB({ + ...values, + "knowledge_base_name": kbName, + "file_names": selectedRowKeys, + }).catch(() => { + message.error(`更新知识库失败`); + }) + setConfirmLoading(false); + setSelectedRowKeys([]); + return; + } + + 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, 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(({ open, setModalOpen }) => { return ( - setModalOpen(false)} open={open} title="添加文件"> - -

- -

-

Click or drag file to this area to upload

-

- Support for a single or bulk upload. Strictly prohibited from uploading company data or - other banned files. -

-
+ setModalOpen(false)} + open={open} + title={isRebuildVectorDB ? "重新添加至向量库" : "添加文件"} + onOk={onSubmit} + confirmLoading={confirmLoading} + width={600} + > + + + + {!isRebuildVectorDB && <> +
+ +

+ +

+

单击或拖动文件到此区域进行上传

+

支持单个或批量上传。

+
+
+ + + + + + + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
); }); diff --git a/frontend/src/app/knowledge/[id]/base/page.tsx b/frontend/src/app/knowledge/[id]/base/page.tsx index 94e1d9ea..18be1d98 100644 --- a/frontend/src/app/knowledge/[id]/base/page.tsx +++ b/frontend/src/app/knowledge/[id]/base/page.tsx @@ -1,38 +1,72 @@ 'use client'; -import { Button, Table } from 'antd'; +import { Button, Table, message, Spin, Modal } from 'antd'; import type { TableColumnsType } from 'antd'; import dynamic from 'next/dynamic'; import Link from 'next/link'; import React, { useState } from 'react'; 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')); interface DataType { - address: string; - age: number; - key: React.Key; + id: React.Key; name: string; + loader: string; + splitter: string; + source: string; + vector: string; } -const data: DataType[] = []; -for (let i = 0; i < 46; i++) { - data.push({ - address: `London, Park Lane no. ${i}`, - age: 32, - index: i, - key: i, - name: `Edward King ${i}`, - }); -} +const App: React.FC<{ params: { id: string } }> = ({ params }) => { + const router = useRouter(); + const [ + filesData, + useFetchKnowledgeFilesList, + useFetchKnowledgeDownloadDocs, + useFetcDelInknowledgeDB, + useFetcDelInVectorDB, + useFetcRebuildVectorDB, + useFetchKnowledgeDel + ] = useKnowledgeStore((s) => [ + s.filesData, + s.useFetchKnowledgeFilesList, + s.useFetchKnowledgeDownloadDocs, + s.useFetcDelInknowledgeDB, + s.useFetcDelInVectorDB, + s.useFetcRebuildVectorDB, + s.useFetchKnowledgeDel + ]); + const { isLoading } = useFetchKnowledgeFilesList(params.id); -const App: React.FC<{ params }> = ({ params }) => { - const [selectedRowKeys, setSelectedRowKeys] = useState([]); + const [downloadLoading, setDownloadLoading] = useState(false); + const [delDocsLoading, setDelDocsLoading] = useState(false); + const [delVSLoading, setDelVSLoading] = 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([]); const [loading, setLoading] = useState(false); const [isShowModal, setModal] = useState(false); - const onSelectChange = (newSelectedRowKeys: React.Key[]) => { - console.log('selectedRowKeys changed:', newSelectedRowKeys); + const [isRebuildVectorDB, setIsRebuildVectorDB] = useState(false); + const onSelectChange = (newSelectedRowKeys: string[]) => { setSelectedRowKeys(newSelectedRowKeys); }; const columns: TableColumnsType = [ @@ -42,17 +76,13 @@ const App: React.FC<{ params }> = ({ params }) => { }, { dataIndex: 'name', - render: (text) => {text}, + render: (text, rowData) => {text}, title: '文档名称', }, { dataIndex: 'loader', title: '文档加载器', }, - { - dataIndex: 'loader', - title: '文档加载器', - }, { dataIndex: 'splitter', title: '分词器', @@ -71,40 +101,134 @@ const App: React.FC<{ params }> = ({ params }) => { selectedRowKeys, }; 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 () => { + setIsRebuildVectorDB(true); + setModal(true) + } + const rebuildVectorDB = async () => { + 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 () => { + setDelVSLoading(true); + await useFetcDelInVectorDB({ + "knowledge_base_name": params.id, + "file_names": [...selectedRowKeys], + "delete_content": false, // 不删除文件 + "not_refresh_vs_cache": false + }).catch(() => { + message.error(`删除失败`); + }) + setDelVSLoading(false); + setSelectedRowKeys([]); + } + const delInknowledgeDB = async () => { + setDelDocsLoading(true); + await useFetcDelInknowledgeDB({ + "knowledge_base_name": params.id, + "file_names": [...selectedRowKeys], + "delete_content": true, + "not_refresh_vs_cache": false + }).catch(() => { + message.error(`删除失败`); + }) + setDelDocsLoading(false); + setSelectedRowKeys([]); + } + const delKnowledge = async () => { + Modal.confirm({ + title: `确认 ${params.id} 删除吗?`, + icon: , + 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 ( <> - - - - -
- + -
+
- - - + +
+ +
} + /> + + + ); }; diff --git a/frontend/src/app/knowledge/[id]/config/page.tsx b/frontend/src/app/knowledge/[id]/config/page.tsx index ac67ac9e..4e21b7b9 100644 --- a/frontend/src/app/knowledge/[id]/config/page.tsx +++ b/frontend/src/app/knowledge/[id]/config/page.tsx @@ -1,61 +1,79 @@ 'use client'; import { Form, type ItemGroup } from '@lobehub/ui'; -import { Form as AntForm, Button, Input, InputNumber, Switch } from 'antd'; +import { Form as AntForm, Button, Input, InputNumber, Switch, message } from 'antd'; import { Settings } from 'lucide-react'; -import { memo, useCallback } from 'react'; +import { memo, useCallback, useState } from 'react'; import { Flexbox } from 'react-layout-kit'; - +import { useKnowledgeStore } from '@/store/knowledge'; import { FORM_STYLE } from '@/const/layoutTokens'; -const KnowledgeBaseConfig = memo(() => { - const [form] = AntForm.useForm(); +const KnowledgeBaseConfig = memo(({ params }: { params: { id: string } }) => { + const [form] = AntForm.useForm(); + const [submitLoading, setSubmitLoading] = useState(false); + const [ + editKnowledgeInfo, + useFetchKnowledgeUpdate + ] = useKnowledgeStore((s) => [ + s.editKnowledgeInfo, + s.useFetchKnowledgeUpdate + ]); + // console.log("editKnowledgeInfo===", editKnowledgeInfo); const handleConfigChange = useCallback(async () => { try { const values = await form.validateFields(); - console.log('Success:', values); + // console.log('Success:', values); + setSubmitLoading(true); + const { msg, code } = await useFetchKnowledgeUpdate(values); + if (code === 200) { + message.success(msg); + } else { + message.error(msg); + } + setSubmitLoading(false); } catch (errorInfo) { console.log('Failed:', errorInfo); } }, [form]); + const system: ItemGroup = { children: [ { - children: , + children: , label: '知识库名称', - name: 'name', - rules: [{ message: '请输入知识库名称', required: true }], + name: 'knowledge_base_name', + rules: [{ message: '请输入知识库名称', required: true }], }, { children: , label: '知识库简介', - name: 'intro', + name: 'kb_info', rules: [{ message: '请输入知识库简介', required: true }], }, - { - children: , - label: '单段文本最大长度', - name: 'paragraphMaxLength', - rules: [{ message: '请输入知识库名称', required: true }], - }, - { - children: , - label: '相邻文本重合长度', - name: 'paragraphOverlapLength', - rules: [{ message: '请输入知识库名称', required: true }], - }, - { - children: , - label: '文本匹配条数', - name: 'paragraphMatchCount', - rules: [{ message: '请输入知识库名称', required: true }], - }, - { - children: , - label: '开启中文标题加强', - name: 'chineseTitleEnhance', - }, + // { + // children: , + // label: '单段文本最大长度', + // name: 'paragraphMaxLength', + // rules: [{ message: '请输入知识库名称', required: true }], + // }, + // { + // children: , + // label: '相邻文本重合长度', + // name: 'paragraphOverlapLength', + // rules: [{ message: '请输入知识库名称', required: true }], + // }, + // { + // children: , + // label: '文本匹配条数', + // name: 'paragraphMatchCount', + // rules: [{ message: '请输入知识库名称', required: true }], + // }, + // { + // children: , + // label: '开启中文标题加强', + // name: 'chineseTitleEnhance', + // }, ], icon: Settings, title: '知识库设置', @@ -63,9 +81,9 @@ const KnowledgeBaseConfig = memo(() => { return ( <> -
+ - diff --git a/frontend/src/app/knowledge/[id]/layout.tsx b/frontend/src/app/knowledge/[id]/layout.tsx index a2806266..9154eb53 100644 --- a/frontend/src/app/knowledge/[id]/layout.tsx +++ b/frontend/src/app/knowledge/[id]/layout.tsx @@ -2,18 +2,23 @@ import { PropsWithChildren, memo } from 'react'; import { Center, Flexbox } from 'react-layout-kit'; - import AppLayoutDesktop from '@/layout/AppLayout.desktop'; import { SidebarTabKey } from '@/store/global/initialState'; - +import { LeftOutlined } from "@ant-design/icons" +import { Button } from "antd" import KnowledgeTabs from './tabs'; +import { useRouter } from 'next/navigation'; interface LayoutProps extends PropsWithChildren { params: Record; } export default memo(({ children, params }) => { - console.log(params); - + // console.log(params); + const router = useRouter(); + + function goBack(){ + router.push('/knowledge') + } return ( @@ -24,8 +29,10 @@ export default memo(({ children, params }) => { style={{ borderInlineEnd: '1px solid #333333' }} > -
图标占位
-
知识库名称
+
+ +
+ {/*
{params.id}
*/}
diff --git a/frontend/src/services/_url.ts b/frontend/src/services/_url.ts index fe826e5e..9750104e 100644 --- a/frontend/src/services/_url.ts +++ b/frontend/src/services/_url.ts @@ -48,5 +48,15 @@ export const API_ENDPOINTS = mapWithBasePath({ // knowledge knowledgeList: '/api/knowledge/list', knowledgeAdd: '/api/knowledge/add', - knowledgeDel: '/api/knowledge/del' + knowledgeUpdate: '/api/knowledge/update', + knowledgeDel: '/api/knowledge/del', + // knowledge files + knowledgeFilesList: '/api/knowledge/listFiles', + knowledgeUploadDocs: '/api/knowledge/uploadDocs', + knowledgeDownloadDocs: '/api/knowledge/downloadDocs', + knowledgeDelInknowledgeDB: '/api/knowledge/deleteDocs', + knowledgeDelVectorDB:'/api/knowledge/delVectorDocs', + knowledgeRebuildVectorDB: '/api/knowledge/rebuildVectorDB', + knowledgeReAddVectorDB: '/api/knowledge/reAddVectorDB', + }); diff --git a/frontend/src/services/knowledge.ts b/frontend/src/services/knowledge.ts index 23815d88..030c9b4e 100644 --- a/frontend/src/services/knowledge.ts +++ b/frontend/src/services/knowledge.ts @@ -1,16 +1,23 @@ -import { KnowledgeList, KnowledgeFormFields, Reseponse } from '@/types/knowledge'; +import type { + KnowledgeList, KnowledgeFormFields, Reseponse, + KnowledgeFilesList, KnowledgeDelDocsParams, KnowledgeDelDocsRes, + KnowledgeRebuildVectorParams, + ReAddVectorDBParams, ReAddVectorDBRes, + KnowledgeUplodDocsParams, KnowledgeUplodDocsRes +} from '@/types/knowledge'; +import { fetchSSE, FetchSSEOptions } from '@/utils/fetch'; import { API_ENDPOINTS } from './_url'; class KnowledgeService { - getList = async (): Promise> => { + getList = async (): Promise> => { const res = await fetch(`${API_ENDPOINTS.knowledgeList}`); const data = await res.json(); return data; }; - add = async (formValues: KnowledgeFormFields) => { + add = async (formValues: KnowledgeFormFields) => { const res = await fetch(`${API_ENDPOINTS.knowledgeAdd}`, { body: JSON.stringify(formValues), headers: { @@ -20,8 +27,18 @@ class KnowledgeService { }); return res.json(); }; + update = async (formValues: Partial) => { + const res = await fetch(`${API_ENDPOINTS.knowledgeUpdate}`, { + body: JSON.stringify(formValues), + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + }); + return res.json(); + }; - del = async (name: string) => { + del = async (name: string) => { const res = await fetch(`${API_ENDPOINTS.knowledgeDel}`, { body: JSON.stringify(name), headers: { @@ -31,6 +48,99 @@ class KnowledgeService { }); return res.json(); }; + + getFilesList = (name: string): () => Promise> => { + 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> => { + const res = await fetch(`${API_ENDPOINTS.knowledgeUploadDocs}`, { + body: formData, + method: 'POST', + }); + return res.json(); + }; + + delInknowledgeDB = async (params: KnowledgeDelDocsParams): Promise> => { + 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 (params: KnowledgeDelDocsParams): Promise> => { + const res = await fetch(`${API_ENDPOINTS.knowledgeDelVectorDB}`, { + body: JSON.stringify({ + ...params, + }), + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + }); + return res.json(); + }; + + downloadDocs = async (kbName: string, docName: string): Promise> => { + 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 (params: ReAddVectorDBParams): Promise> => { + const res = await fetch(`${API_ENDPOINTS.knowledgeReAddVectorDB}`, { + body: JSON.stringify({ + ...params, + }), + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + }); + return res.json(); + }; + + + } export const knowledgeService = new KnowledgeService(); diff --git a/frontend/src/store/knowledge/action.ts b/frontend/src/store/knowledge/action.ts index ca3db06a..e7e7de8b 100644 --- a/frontend/src/store/knowledge/action.ts +++ b/frontend/src/store/knowledge/action.ts @@ -1,19 +1,44 @@ import useSWR, { SWRResponse } from 'swr'; import type { StateCreator } from 'zustand/vanilla'; - import { knowledgeService } from '@/services/knowledge'; - import { globalHelpers } from '@/store/global/helpers'; -import { KnowledgeFormFields, KnowledgeList, Reseponse } from '@/types/knowledge'; + +import type { + KnowledgeFormFields, KnowledgeList, Reseponse, KnowledgeFilesList, + KnowledgeDelDocsParams, KnowledgeDelDocsRes, + KnowledgeRebuildVectorParams, KnowledgeUplodDocsParams, KnowledgeUplodDocsRes, + ReAddVectorDBParams, ReAddVectorDBRes, +} from '@/types/knowledge'; +import type { FetchSSEOptions } from '@/utils/fetch'; import type { Store } from './store'; export interface StoreAction { + // 知识库数据列表 listData: KnowledgeList; + + // 当前编辑的知识库 + editKnowledgeInfo: null | KnowledgeFormFields; + setEditKnowledge: (data: KnowledgeFormFields) => void; + useFetchKnowledgeList: () => SWRResponse>; useFetchKnowledgeAdd: (arg: KnowledgeFormFields) => Promise>; - useFetchKnowledgeDel: (name: string)=> Promise>; + useFetchKnowledgeUpdate: (arg: Partial) => Promise>; + useFetchKnowledgeDel: (name: string) => Promise>; + + // files + filesData: KnowledgeFilesList; + useFetchKnowledgeFilesList: (name: string) => SWRResponse>; + useFetchKnowledgeUploadDocs: (arg: FormData) => Promise>; + useFetchKnowledgeDownloadDocs: (kbName: string, docName: string) => Promise>; + useFetcDelInknowledgeDB: (arg: KnowledgeDelDocsParams) => Promise>; + useFetcDelInVectorDB: (arg: KnowledgeDelDocsParams) => Promise>; + useFetcRebuildVectorDB: (arg: KnowledgeRebuildVectorParams, options: { + onFinish: FetchSSEOptions["onFinish"]; + onMessageHandle: FetchSSEOptions["onMessageHandle"] + }) => void; + useFetcReAddVectorDB: (arg: ReAddVectorDBParams) => Promise>; } export const createKnowledgeAction: StateCreator< @@ -28,7 +53,7 @@ export const createKnowledgeAction: StateCreator< globalHelpers.getCurrentLanguage(), knowledgeService.getList, { - onSuccess: (res) => { + onSuccess: (res) => { set({ listData: res.data }) }, }, @@ -37,7 +62,47 @@ export const createKnowledgeAction: StateCreator< useFetchKnowledgeAdd: async (formValues) => { return await knowledgeService.add(formValues) }, + useFetchKnowledgeUpdate: async (formValues) => { + return await knowledgeService.update(formValues) + }, useFetchKnowledgeDel: async (name) => { return await knowledgeService.del(name) }, + filesData: [], + useFetchKnowledgeFilesList: (knowledge_base_name) => { + return useSWR>( + 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); + }, + useFetcDelInVectorDB: async (name) => { + return await knowledgeService.delVectorDocs(name) + }, + useFetcRebuildVectorDB: (params, options) => { + return knowledgeService.rebuildVectorDB(params, options); + }, + useFetcReAddVectorDB: (params) => { + return knowledgeService.reAddVectorDB(params); + }, + + + editKnowledgeInfo: null, + setEditKnowledge: (data) => { + set({ editKnowledgeInfo: data }) + } + }); diff --git a/frontend/src/types/knowledge.ts b/frontend/src/types/knowledge.ts index 567c71be..c0a6980b 100644 --- a/frontend/src/types/knowledge.ts +++ b/frontend/src/types/knowledge.ts @@ -1,12 +1,80 @@ -export interface Reseponse { code: number; msg: string; data: T } +export interface Reseponse { code: number; msg: string; data: T } + +// create Knowledge fields export interface KnowledgeFormFields { knowledge_base_name: string; - vector_store_type: string; + vector_store_type?: string; kb_info?: string; - 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 {} + + +// upload docs +export interface KnowledgeUplodDocsParams { + knowledge_base_name: string; + files: File[]; + override?: boolean; + to_vector_store?: string; + chunk_size?: string; + chunk_overlap?: string; + zh_title_enhance?: string; + docs?: string; + docsnot_refresh_vs_cache?: string; +} +export interface KnowledgeUplodDocsRes { } + + +// re add docs +export interface ReAddVectorDBParams { + "knowledge_base_name": string, + "file_names":string[]; + "chunk_size": number; + "chunk_overlap": number; + "zh_title_enhance": boolean; + "override_custom_docs": boolean; + "docs": string; + "not_refresh_vs_cache": boolean +} +export interface ReAddVectorDBRes { } + + +// 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[];