Merge pull request #4117 from wangzongming/dev-frontend

知识库:列表、新增、删除接口对接
This commit is contained in:
panhong 2024-06-02 16:53:04 +08:00 committed by GitHub
commit c2bb287263
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 294 additions and 47 deletions

View File

@ -101,6 +101,9 @@ OPENAI_API_KEY = sk-xxxxxxxxx
# you can use ChatChat.The local/remote ChatChat service url
CHATCHAT_PROXY_URL = 'http://localhost:7861/v1'
# Knowledge Base Service. Default as follows
KNOWLEDGE_PROXY_URL = 'http://localhost:7861/knowledge_base'
# The LobeChat plugins store index url
# PLUGINS_INDEX_URL=https://chat-plugins.lobehub.com

View File

@ -58,7 +58,7 @@ const nextConfig = {
{ source: '/docs', destination: `${docsBasePath}/docs` },
{ source: '/docs/zh', destination: `${docsBasePath}/docs/zh` },
{ source: '/docs/en', destination: `${docsBasePath}/docs/en` },
{ source: '/docs/:path*', destination: `${docsBasePath}/docs/:path*` },
{ source: '/docs/:path*', destination: `${docsBasePath}/docs/:path*` }
],
reactStrictMode: true,

View File

@ -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}/create_knowledge_base`, {
body: JSON.stringify(params),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
});
return fetchRes
};

View File

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

View File

@ -0,0 +1,7 @@
import { getServerConfig } from '@/config/server';
const { KNOWLEDGE_PROXY_URL } = getServerConfig();
export const GET = async (request: Request) => {
const fetchRes = await fetch(`${KNOWLEDGE_PROXY_URL}/list_knowledge_bases`);
return fetchRes;
};

View File

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

View File

@ -1,7 +1,8 @@
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
import { Card, Skeleton } from 'antd';
import { DeleteOutlined, EditOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
import { Card, Skeleton, message, Modal } from 'antd';
import { useRouter } from 'next/navigation';
import React, { useState } from 'react';
import { useKnowledgeStore } from '@/store/knowledge';
const { Meta } = Card;
@ -9,18 +10,39 @@ interface KnowLedgeCardProps {
intro: string;
name: string;
}
const KnowledgeCard: React.FC = (props: KnowLedgeCardProps) => {
const KnowledgeCard: React.FC<KnowLedgeCardProps> = (props: KnowLedgeCardProps) => {
const [useFetchKnowledgeDel] = useKnowledgeStore((s) => [
s.useFetchKnowledgeDel
]);
const [loading, setLoading] = useState(false);
const { name, intro } = props;
const router = useRouter();
const handleCardEditClick = () => {
router.push('/knowledge/1/base');
};
const delClick = async () => {
Modal.confirm({
title: `确认 ${name} 删除吗?`,
icon: <ExclamationCircleOutlined />,
async onOk() {
const { code: resCode, msg: resMsg } = await useFetchKnowledgeDel(name)
if (resCode !== 200) {
message.error(resMsg)
} else {
message.success(resMsg)
}
return Promise.resolve();
},
});
};
return (
<Card
actions={[
<EditOutlined key="edit" onClick={handleCardEditClick} />,
<DeleteOutlined key="ellipsis" />,
<DeleteOutlined key="ellipsis" onClick={delClick} />,
]}
bordered={false}
style={{ marginTop: 16, width: 300 }}

View File

@ -1,22 +1,59 @@
import React, { memo } from 'react';
import { Empty, Spin } from 'antd';
import { createStyles } from 'antd-style';
import KnowledgeCard from './KnowledgeCard';
import { useKnowledgeStore } from '@/store/knowledge';
import { Flexbox } from 'react-layout-kit';
const list = [
{ intro: '知识库简介', name: '知识库名称' },
{ intro: '知识库简介', name: '知识库名称' },
{ intro: '知识库简介', name: '知识库名称' },
{ intro: '知识库简介', name: '知识库名称' },
{ intro: '知识库简介', name: '知识库名称' },
{ intro: '知识库简介', name: '知识库名称' },
{ intro: '知识库简介', name: '知识库名称' },
];
const useStyles = createStyles(({ css, token, stylish }) => ({
wrap: css`
min-height: 200px;
height: 100%;
width: 100%;
`,
null: css`
display: block;
position: absolute;
top: 0px; bottom: 0px; left: 0px; right: 0px;
margin: auto;
height: 100px;
`,
}));
const RenderList = memo(() =>
list.map((item, index) => {
return <KnowledgeCard key={index} {...item} />;
}),
);
// const list = [
// { intro: '知识库简介', name: '知识库名称' },
// { intro: '知识库简介', name: '知识库名称' },
// ];
const RenderList = memo(() => {
const { styles } = useStyles();
const [listData, useFetchKnowledgeList] = useKnowledgeStore((s) => [
s.listData, s.useFetchKnowledgeList
]);
const { isLoading } = useFetchKnowledgeList();
const list = listData.map((item) => ({
intro: '知识库简介',
// 等接口更改...
name: item as unknown as string
}))
if (!isLoading && !listData.length) {
return <div className={styles.null}>
<Empty />
</div>
}
return <div className={styles.wrap}>
<Spin tip="Loading..." spinning={isLoading}>
<div className={styles.wrap}>
<Flexbox gap={20} horizontal justify="flex-start" wrap="wrap">
{list.map((item, index) => {
return <KnowledgeCard key={index} {...item} />;
})}
</Flexbox>
</div>
</Spin>
</div>
});
const KnowledgeCardList = memo(() => {
return <RenderList />;

View File

@ -1,21 +1,38 @@
import { Modal, type ModalProps } from '@lobehub/ui';
import { Form, Input, Select } from 'antd';
import { memo } from 'react';
import { Form, Input, Select, FormInstance, message } from 'antd';
import { memo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
const DEFAULT_FIELD_VALUE = {
// imageType: ImageType.JPG,
withBackground: true,
withFooter: false,
withPluginInfo: false,
withSystemRole: false,
import { useKnowledgeStore } from '@/store/knowledge';
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<ModalCreateKnowledgeProps>(({ toggleModal, open }) => {
const { t } = useTranslation('chat');
const antdFormInstance = useRef<FormInstance>();
const [useFetchKnowledgeAdd] = useKnowledgeStore((s) => [
s.useFetchKnowledgeAdd
]);
const onSubmit = async () => {
if(!antdFormInstance.current) return;
const fieldsError = await antdFormInstance.current.validateFields();
if(fieldsError.length) return;
const values = antdFormInstance.current.getFieldsValue(true);
const { code:resCode, data: resData, msg: resMsg } = await useFetchKnowledgeAdd({ ...values })
if(resCode !== 200){
message.error(resMsg)
return;
}
toggleModal(false);
}
return (
<Modal
@ -23,32 +40,38 @@ const CreateKnowledgeBase = memo<ModalCreateKnowledgeProps>(({ toggleModal, open
centered={false}
maxHeight={false}
onCancel={() => toggleModal(false)}
onOk={() => toggleModal(false)}
onOk={onSubmit}
open={open}
title="创建知识库"
>
<Form initialValues={DEFAULT_FIELD_VALUE} layout="vertical">
<Form.Item label="知识库名称" name="base_name">
<Input />
<Form initialValues={DEFAULT_FIELD_VALUE} layout="vertical" ref={antdFormInstance}>
<Form.Item label="知识库名称" name="knowledge_base_name" rules={[{ required: true, message: '请输入知识库名称' }]}>
<Input autoFocus/>
</Form.Item>
<Form.Item label="知识库简介" name="base_intro">
<Form.Item label="知识库简介" name="kb_info">
<Input />
</Form.Item>
<Flexbox direction="horizontal" gap={10} justify="space-between">
<div style={{ flex: '1' }}>
<Form.Item label="向量库类型" name="base_type">
<Select>
<Select.Option value="0"></Select.Option>
<Select.Option value="1"></Select.Option>
<Form.Item label="向量库类型" name="vector_store_type" rules={[{ required: true, message: '请选择向量库类型' }]}>
<Select>
<Select.Option value="faiss">faiss</Select.Option>
<Select.Option value="milvus">milvus</Select.Option>
<Select.Option value="zilliz">zilliz</Select.Option>
<Select.Option value="pg">pg</Select.Option>
<Select.Option value="es">es</Select.Option>
<Select.Option value="chromadb">chromadb</Select.Option>
</Select>
</Form.Item>
</div>
<div style={{ flex: '1' }}>
<Form.Item label="Embedding模型" name="embedding_model">
<Select>
<Select.Option value="0">Bert</Select.Option>
<Select.Option value="1">Word2Vec</Select.Option>
<Select.Option value="2">FastText</Select.Option>
<Form.Item label="Embedding模型" name="embed_model" rules={[{ required: true, message: '请选择Embedding模型' }]}>
<Select>
<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="Bert">Bert</Select.Option>
<Select.Option value="Word2Vec">Word2Vec</Select.Option>
<Select.Option value="FastText">FastText</Select.Option>
</Select>
</Form.Item>
</div>

View File

@ -4,7 +4,6 @@ import { FloatButton } from 'antd';
import { Plus } from 'lucide-react';
import dynamic from 'next/dynamic';
import { memo, useState } from 'react';
import { Flexbox } from 'react-layout-kit';
import KnowledgeCardList from './features/KnowledgeList';
// import CreateKnowledgeBase from './features/createKnowledgeBase';
@ -17,9 +16,7 @@ const DesktopPage = memo(() => {
return (
<>
<Layout>
<Flexbox gap={20} horizontal justify="flex-start" wrap="wrap">
<KnowledgeCardList />
</Flexbox>
<KnowledgeCardList />
<FloatButton icon={<Plus />} onClick={() => setShowModal(true)}>
</FloatButton>

View File

@ -49,6 +49,9 @@ declare global {
// ChatChat
CHATCHAT_PROXY_URL?: string;
// knowledeg
KNOWLEDGE_PROXY_URL?: string;
}
}
}
@ -76,7 +79,7 @@ export const getProviderConfig = () => {
if (process.env.OPENAI_FUNCTION_REGIONS) {
regions = process.env.OPENAI_FUNCTION_REGIONS.split(',');
}
return {
CUSTOM_MODELS: process.env.CUSTOM_MODELS,
@ -119,5 +122,6 @@ export const getProviderConfig = () => {
OLLAMA_PROXY_URL: process.env.OLLAMA_PROXY_URL || '',
CHATCHAT_PROXY_URL: process.env.CHATCHAT_PROXY_URL || '',
KNOWLEDGE_PROXY_URL: process.env.KNOWLEDGE_PROXY_URL || 'http://localhost:7861/knowledge_base',
};
};

View File

@ -44,4 +44,9 @@ export const API_ENDPOINTS = mapWithBasePath({
tts: '/api/openai/tts',
edge: '/api/tts/edge-speech',
microsoft: '/api/tts/microsoft-speech',
// knowledge
knowledgeList: '/api/knowledge/list',
knowledgeAdd: '/api/knowledge/add',
knowledgeDel: '/api/knowledge/del'
});

View File

@ -0,0 +1,36 @@
import { KnowledgeList, KnowledgeFormFields, Reseponse } from '@/types/knowledge';
import { API_ENDPOINTS } from './_url';
class KnowledgeService {
getList = async (): Promise<Reseponse<KnowledgeList>> => {
const res = await fetch(`${API_ENDPOINTS.knowledgeList}`);
const data = await res.json();
return data;
};
add = async (formValues: KnowledgeFormFields) => {
const res = await fetch(`${API_ENDPOINTS.knowledgeAdd}`, {
body: JSON.stringify(formValues),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
});
return res.json();
};
del = async (name: string) => {
const res = await fetch(`${API_ENDPOINTS.knowledgeDel}`, {
body: JSON.stringify(name),
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
});
return res.json();
};
}
export const knowledgeService = new KnowledgeService();

View File

@ -0,0 +1,43 @@
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 { Store } from './store';
export interface StoreAction {
listData: KnowledgeList;
useFetchKnowledgeList: () => SWRResponse<Reseponse<KnowledgeList>>;
useFetchKnowledgeAdd: (arg: KnowledgeFormFields) => Promise<Reseponse<KnowledgeFormFields>>;
useFetchKnowledgeDel: (name: string)=> Promise<Reseponse<{}>>;
}
export const createKnowledgeAction: StateCreator<
Store,
[['zustand/devtools', never]],
[],
StoreAction
> = (set, get) => ({
listData: [],
useFetchKnowledgeList: () => {
return useSWR<Reseponse<KnowledgeList>>(
globalHelpers.getCurrentLanguage(),
knowledgeService.getList,
{
onSuccess: (res) => {
set({ listData: res.data })
},
},
)
},
useFetchKnowledgeAdd: async (formValues) => {
return await knowledgeService.add(formValues)
},
useFetchKnowledgeDel: async (name) => {
return await knowledgeService.del(name)
},
});

View File

@ -0,0 +1,3 @@
export * from './store';
export { useKnowledgeStore } from './store';
export { type Store } from './store';

View File

@ -0,0 +1,22 @@
import { subscribeWithSelector, devtools, persist } from 'zustand/middleware';
import { shallow } from 'zustand/shallow';
import { createWithEqualityFn } from 'zustand/traditional';
import type { StateCreator } from 'zustand/vanilla';
import { isDev } from '@/utils/env';
import { type StoreAction, createKnowledgeAction } from './action';
export type Store = StoreAction;
const createStore: StateCreator<Store, [['zustand/devtools', never]]> = (...parameters) => ({
...createKnowledgeAction(...parameters),
});
export const useKnowledgeStore = createWithEqualityFn<Store>()(
subscribeWithSelector(
devtools(createStore, {
name: 'ChatChat_Chat' + (isDev ? '_DEV' : ''),
}),
),
shallow,
);

View File

@ -0,0 +1,12 @@
export interface Reseponse <T> { code: number; msg: string; data: T }
export interface KnowledgeFormFields {
knowledge_base_name: string;
vector_store_type: string;
kb_info?: string;
embed_model: string;
}
export interface KnowledgeListItemFields extends KnowledgeFormFields { }
export type KnowledgeList = KnowledgeListItemFields[];