mirror of
https://github.com/RYDE-WORK/Langchain-Chatchat.git
synced 2026-01-19 13:23:16 +08:00
Merge pull request #4117 from wangzongming/dev-frontend
知识库:列表、新增、删除接口对接
This commit is contained in:
commit
c2bb287263
@ -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
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
|
||||
15
frontend/src/app/api/knowledge/add/route.ts
Normal file
15
frontend/src/app/api/knowledge/add/route.ts
Normal 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
|
||||
};
|
||||
15
frontend/src/app/api/knowledge/del/route.ts
Normal file
15
frontend/src/app/api/knowledge/del/route.ts
Normal 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
|
||||
};
|
||||
7
frontend/src/app/api/knowledge/list/route.ts
Normal file
7
frontend/src/app/api/knowledge/list/route.ts
Normal 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;
|
||||
};
|
||||
3
frontend/src/app/api/knowledge/接口存在问题.txt
Normal file
3
frontend/src/app/api/knowledge/接口存在问题.txt
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
|
||||
1. 创建接口没有简介字段
|
||||
@ -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 }}
|
||||
|
||||
@ -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 />;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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',
|
||||
};
|
||||
};
|
||||
|
||||
@ -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'
|
||||
});
|
||||
|
||||
36
frontend/src/services/knowledge.ts
Normal file
36
frontend/src/services/knowledge.ts
Normal 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();
|
||||
43
frontend/src/store/knowledge/action.ts
Normal file
43
frontend/src/store/knowledge/action.ts
Normal 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)
|
||||
},
|
||||
});
|
||||
3
frontend/src/store/knowledge/index.ts
Normal file
3
frontend/src/store/knowledge/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './store';
|
||||
export { useKnowledgeStore } from './store';
|
||||
export { type Store } from './store';
|
||||
22
frontend/src/store/knowledge/store.ts
Normal file
22
frontend/src/store/knowledge/store.ts
Normal 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,
|
||||
);
|
||||
12
frontend/src/types/knowledge.ts
Normal file
12
frontend/src/types/knowledge.ts
Normal 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[];
|
||||
Loading…
x
Reference in New Issue
Block a user