feat: remove KnowledgeSearching and add knowledge configuration page and add a run button to the document (#64)

* feat: add a run button to the document

* feat: add knowledge configuration page

* feat: remove KnowledgeSearching
This commit is contained in:
balibabu
2024-02-18 18:18:20 +08:00
committed by GitHub
parent 53be70c7a9
commit f3d0ebd293
22 changed files with 495 additions and 687 deletions

View File

@ -26,6 +26,7 @@
.chunkContainer {
height: calc(100vh - 320px);
overflow: auto;
width: 100%;
}
.pageFooter {

View File

@ -2,10 +2,11 @@ import { ReactComponent as SelectFilesEndIcon } from '@/assets/svg/select-files-
import { ReactComponent as SelectFilesStartIcon } from '@/assets/svg/select-files-start.svg';
import {
useDeleteDocumentById,
useFetchParserList,
useGetDocumentDefaultParser,
useKnowledgeBaseId,
useSelectParserList,
} from '@/hooks/knowledgeHook';
import { ITenantInfo } from '@/interfaces/database/knowledge';
import uploadService from '@/services/uploadService';
import {
ArrowLeftOutlined,
@ -28,9 +29,8 @@ import {
UploadProps,
} from 'antd';
import classNames from 'classnames';
import { ReactElement, useEffect, useMemo, useRef, useState } from 'react';
import { Nullable } from 'typings';
import { Link, useDispatch, useNavigate, useSelector } from 'umi';
import { ReactElement, useEffect, useRef, useState } from 'react';
import { Link, useDispatch, useNavigate } from 'umi';
import { KnowledgeRouteKey } from '@/constants/knowledge';
import styles from './index.less';
@ -45,13 +45,11 @@ const UploaderItem = ({
file,
actions,
isUpload,
parserArray,
}: {
isUpload: boolean;
originNode: ReactElement;
file: UploadFile;
fileList: object[];
parserArray: string[];
actions: { download: Function; preview: Function; remove: any };
}) => {
const { parserConfig, defaultParserId } = useGetDocumentDefaultParser(
@ -63,12 +61,7 @@ const UploaderItem = ({
const documentId = file?.response?.id;
const parserList = useMemo(() => {
return parserArray.map((x) => {
const arr = x.split(':');
return { value: arr[0], label: arr[1] };
});
}, [parserArray]);
const parserList = useSelectParserList();
const saveParser = (parserId: string) => {
dispatch({
@ -154,14 +147,10 @@ const KnowledgeUploadFile = () => {
const knowledgeBaseId = useKnowledgeBaseId();
const [isUpload, setIsUpload] = useState(true);
const dispatch = useDispatch();
const tenantIfo: Nullable<ITenantInfo> = useSelector(
(state: any) => state.settingModel.tenantIfo,
);
const navigate = useNavigate();
const fileListRef = useRef<UploadFile[]>([]);
const parserArray = tenantIfo?.parser_ids.split(',') ?? [];
const createRequest: (props: UploadRequestOption) => void = async function ({
file,
onSuccess,
@ -170,9 +159,13 @@ const KnowledgeUploadFile = () => {
}) {
const { data } = await uploadService.uploadFile(file, knowledgeBaseId);
if (data.retcode === 0) {
onSuccess && onSuccess(data.data);
if (onSuccess) {
onSuccess(data.data);
}
} else {
onError && onError(data.data);
if (onError) {
onError(data.data);
}
}
};
@ -188,7 +181,6 @@ const KnowledgeUploadFile = () => {
fileList={fileList}
originNode={originNode}
actions={actions}
parserArray={parserArray}
></UploaderItem>
);
},
@ -215,11 +207,7 @@ const KnowledgeUploadFile = () => {
}
};
useEffect(() => {
dispatch({
type: 'settingModel/getTenantInfo',
});
}, []);
useFetchParserList();
return (
<div className={styles.uploadWrapper}>

View File

@ -1,8 +1,5 @@
import { KnowledgeRouteKey } from '@/constants/knowledge';
import {
useDeleteDocumentById,
useKnowledgeBaseId,
} from '@/hooks/knowledgeHook';
import { useKnowledgeBaseId } from '@/hooks/knowledgeHook';
import { Pagination } from '@/interfaces/common';
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
import { getOneNamespaceEffectsLoading } from '@/utils/storeUtil';
@ -40,7 +37,6 @@ const KnowledgeFile = () => {
const effects = useSelector((state: any) => state.loading.effects);
const { data, total } = kFModel;
const knowledgeBaseId = useKnowledgeBaseId();
const { removeDocument } = useDeleteDocumentById();
const loading = getOneNamespaceEffectsLoading('kFModel', effects, [
'getKfList',
@ -132,9 +128,7 @@ const KnowledgeFile = () => {
},
});
};
const onRmDocument = () => {
removeDocument(doc_id);
};
const showCEFModal = () => {
dispatch({
type: 'kFModel/updateState',
@ -144,15 +138,6 @@ const KnowledgeFile = () => {
});
};
const showSegmentSetModal = () => {
dispatch({
type: 'kFModel/updateState',
payload: {
isShowSegmentSetModal: true,
},
});
};
const actionItems: MenuProps['items'] = useMemo(() => {
return [
{
@ -185,31 +170,6 @@ const KnowledgeFile = () => {
},
];
}, []);
const chunkItems: MenuProps['items'] = [
{
key: '1',
label: (
<div>
<Button type="link" onClick={showSegmentSetModal}>
{' '}
</Button>
</div>
),
},
{
key: '2',
label: (
<div>
<Button type="link" onClick={onRmDocument}>
{' '}
Delete
</Button>
</div>
),
// disabled: true,
},
];
const toChunk = (id: string) => {
navigate(

View File

@ -55,33 +55,23 @@ const model: DvaModel<KFModelState> = {
return { ...state, pagination: { ...state.pagination, ...payload } };
},
},
subscriptions: {
setup({ dispatch, history }) {
history.listen((location) => {});
},
},
effects: {
*createKf({ payload = {} }, { call, put }) {
const { data, response } = yield call(kbService.createKb, payload);
const { retcode, data: res, retmsg } = data;
*createKf({ payload = {} }, { call }) {
const { data } = yield call(kbService.createKb, payload);
const { retcode } = data;
if (retcode === 0) {
message.success('创建成功!');
}
},
*updateKf({ payload = {} }, { call, put }) {
const { data, response } = yield call(kbService.updateKb, payload);
const { retcode, data: res, retmsg } = data;
*updateKf({ payload = {} }, { call }) {
const { data } = yield call(kbService.updateKb, payload);
const { retcode } = data;
if (retcode === 0) {
message.success('修改成功!');
}
},
*getKfDetail({ payload = {}, callback }, { call, put }) {
const { data, response } = yield call(kbService.get_kb_detail, payload);
const { retcode, data: res, retmsg } = data;
if (retcode === 0) {
// localStorage.setItem('userInfo',res.)
callback && callback(res);
}
*getKfDetail({ payload = {} }, { call }) {
const { data } = yield call(kbService.get_kb_detail, payload);
},
*getKfList({ payload = {} }, { call, put, select }) {
const state: KFModelState = yield select((state: any) => state.kFModel);
@ -119,11 +109,11 @@ const model: DvaModel<KFModelState> = {
{ type: 'poll', delay: 5000 }, // TODO: Provide type support for this effect
],
*updateDocumentStatus({ payload = {} }, { call, put }) {
const { data, response } = yield call(
const { data } = yield call(
kbService.document_change_status,
pick(payload, ['doc_id', 'status']),
);
const { retcode, data: res, retmsg } = data;
const { retcode } = data;
if (retcode === 0) {
message.success('修改成功!');
put({
@ -133,10 +123,10 @@ const model: DvaModel<KFModelState> = {
}
},
*document_rm({ payload = {} }, { call, put }) {
const { data, response } = yield call(kbService.document_rm, {
const { data } = yield call(kbService.document_rm, {
doc_id: payload.doc_id,
});
const { retcode, data: res, retmsg } = data;
const { retcode } = data;
if (retcode === 0) {
message.success('删除成功!');
yield put({
@ -151,7 +141,7 @@ const model: DvaModel<KFModelState> = {
kbService.document_rename,
omit(payload, ['kb_id']),
);
const { retcode, data: res, retmsg } = data;
const { retcode } = data;
if (retcode === 0) {
message.success('rename success');
yield put({
@ -168,7 +158,7 @@ const model: DvaModel<KFModelState> = {
},
*document_create({ payload = {} }, { call, put }) {
const { data } = yield call(kbService.document_create, payload);
const { retcode, data: res } = data;
const { retcode } = data;
if (retcode === 0) {
put({
type: 'kFModel/updateState',
@ -181,19 +171,25 @@ const model: DvaModel<KFModelState> = {
return retcode;
},
*document_run({ payload = {} }, { call, put }) {
const { data } = yield call(kbService.document_run, payload);
const { data } = yield call(
kbService.document_run,
omit(payload, ['knowledgeBaseId']),
);
const { retcode } = data;
if (retcode === 0) {
message.success('Run successfully ');
if (payload.knowledgeBaseId) {
yield put({
type: 'getKfList',
payload: { kb_id: payload.knowledgeBaseId },
});
}
message.success('Operation successfully ');
}
return retcode;
},
*document_change_parser({ payload = {} }, { call, put }) {
const { data, response } = yield call(
kbService.document_change_parser,
payload,
);
const { retcode, data: res, retmsg } = data;
const { data } = yield call(kbService.document_change_parser, payload);
const { retcode } = data;
if (retcode === 0) {
put({
type: 'updateState',

View File

@ -1,3 +1,12 @@
.popover-content {
width: 300px;
}
.operationIcon {
text-align: center;
margin-right: 20%;
width: 20px;
&:hover {
cursor: pointer;
}
}

View File

@ -1,9 +1,21 @@
import { ReactComponent as RefreshIcon } from '@/assets/svg/refresh.svg';
import { ReactComponent as RunIcon } from '@/assets/svg/run.svg';
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
import { Badge, DescriptionsProps, Flex, Popover, Space, Tag } from 'antd';
import { RunningStatus, RunningStatusMap } from '../constant';
import { CloseCircleOutlined } from '@ant-design/icons';
import { useDispatch } from 'umi';
import styles from './index.less';
const iconMap = {
[RunningStatus.UNSTART]: RunIcon,
[RunningStatus.RUNNING]: CloseCircleOutlined,
[RunningStatus.CANCEL]: RefreshIcon,
[RunningStatus.DONE]: RefreshIcon,
[RunningStatus.FAIL]: RefreshIcon,
};
interface IProps {
record: IKnowledgeFile;
}
@ -31,7 +43,7 @@ const PopoverContent = ({ record }: IProps) => {
<Flex vertical className={styles['popover-content']}>
{items.map((x) => {
return (
<div>
<div key={x.key}>
<b>{x.label}:</b>
<p>{x.children}</p>
</div>
@ -42,27 +54,46 @@ const PopoverContent = ({ record }: IProps) => {
};
export const ParsingStatusCell = ({ record }: IProps) => {
const dispatch = useDispatch();
const text = record.run;
const runningStatus = RunningStatusMap[text];
const isRunning = text === RunningStatus.RUNNING;
const OperationIcon = iconMap[text];
const handleOperationIconClick = () => {
dispatch({
type: 'kFModel/document_run',
payload: {
doc_ids: [record.id],
run: isRunning ? 2 : 1,
knowledgeBaseId: record.kb_id,
},
});
};
return (
<Popover
content={isRunning && <PopoverContent record={record}></PopoverContent>}
>
<Tag color={runningStatus.color}>
{isRunning ? (
<Space>
<Badge color={runningStatus.color} />
{runningStatus.label}
<span>{record.progress * 100}%</span>
</Space>
) : (
runningStatus.label
)}
</Tag>
</Popover>
<Flex justify={'space-between'}>
<Popover
content={isRunning && <PopoverContent record={record}></PopoverContent>}
>
<Tag color={runningStatus.color}>
{isRunning ? (
<Space>
<Badge color={runningStatus.color} />
{runningStatus.label}
<span>{(record.progress * 100).toFixed(2)}%</span>
</Space>
) : (
runningStatus.label
)}
</Tag>
</Popover>
<div onClick={handleOperationIconClick} className={styles.operationIcon}>
<OperationIcon />
</div>
</Flex>
);
};

View File

@ -1,79 +0,0 @@
.chunkPage {
padding: 24px;
display: flex;
height: calc(100vh - 112px);
// flex-direction: column;
.filter {
margin-right: 20px;
display: flex;
height: 32px;
width: 300px;
flex-wrap: wrap;
justify-content: space-between;
}
.pageContainer {
flex: 1;
display: flex;
flex-direction: column;
.pageContent {
flex: 1;
width: 100%;
padding-right: 12px;
overflow-y: auto;
.spin {
min-height: 400px;
}
}
.pageFooter {
height: 32px;
float: right;
}
}
}
.container {
height: 100px;
display: flex;
flex-direction: column;
justify-content: space-between;
.content {
display: flex;
justify-content: space-between;
.context {
flex: 1;
// width: 207px;
height: 88px;
overflow: hidden;
}
}
.footer {
height: 20px;
.text {
margin-left: 10px;
}
}
}
.card {
:global {
.ant-card-body {
padding: 10px;
margin: 0;
}
margin-bottom: 10px;
}
cursor: pointer;
}

View File

@ -1,276 +0,0 @@
import { useKnowledgeBaseId } from '@/hooks/knowledgeHook';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
import { api_host } from '@/utils/api';
import { DeleteOutlined, MinusSquareOutlined } from '@ant-design/icons';
import type { PaginationProps } from 'antd';
import {
Card,
Col,
Input,
Pagination,
Popconfirm,
Row,
Select,
Spin,
Switch,
} from 'antd';
import { debounce } from 'lodash';
import React, { useCallback, useEffect } from 'react';
import { useDispatch, useSelector } from 'umi';
import CreateModal from '../knowledge-chunk/components/chunk-creating-modal';
import styles from './index.less';
const KnowledgeSearching = () => {
const dispatch = useDispatch();
const kSearchModel = useSelector((state: any) => state.kSearchModel);
const chunkModel = useSelector((state: any) => state.chunkModel);
const loading = useOneNamespaceEffectsLoading('kSearchModel', [
'chunk_list',
'switch_chunk',
]);
const knowledgeBaseId = useKnowledgeBaseId();
const {
data = [],
total,
d_list = [],
question,
doc_ids,
pagination,
} = kSearchModel;
const { chunk_id, doc_id, isShowCreateModal } = chunkModel;
const getChunkList = () => {
dispatch({
type: 'kSearchModel/chunk_list',
payload: {
kb_id: knowledgeBaseId,
},
});
};
const confirm = (id: string) => {
dispatch({
type: 'kSearchModel/rm_chunk',
payload: {
chunk_ids: [id],
kb_id: knowledgeBaseId,
},
});
};
const handleEditchunk = (item: any) => {
const { chunk_id, doc_id } = item;
dispatch({
type: 'chunkModel/updateState',
payload: {
isShowCreateModal: true,
chunk_id,
doc_id,
},
});
getChunkList();
};
const onShowSizeChange: PaginationProps['onShowSizeChange'] = (
page,
size,
) => {
dispatch({
type: 'kSearchModel/updateState',
payload: {
pagination: { page, size },
},
});
};
useEffect(() => {
dispatch({
type: 'kSearchModel/updateState',
payload: {
doc_ids: [],
question: '',
},
});
dispatch({
type: 'kSearchModel/getKfList',
payload: {
kb_id: knowledgeBaseId,
},
});
}, []);
const switchChunk = (item: any, available_int: boolean) => {
const { chunk_id, doc_id } = item;
dispatch({
type: 'kSearchModel/switch_chunk',
payload: {
chunk_ids: [chunk_id],
doc_id,
available_int,
kb_id: knowledgeBaseId,
},
});
};
useEffect(() => {
getChunkList();
}, [doc_ids, pagination, question]);
const debounceChange = debounce((value) => {
dispatch({
type: 'kSearchModel/updateState',
payload: {
question: value,
},
});
}, 300);
const debounceCallback = useCallback(
(value: string) => debounceChange(value),
[],
);
const handleInputChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
const value = e.target.value;
debounceCallback(value);
};
const handleSelectChange = (value: any[]) => {
dispatch({
type: 'kSearchModel/updateState',
payload: {
doc_ids: value,
},
});
};
return (
<>
<div className={styles.chunkPage}>
<div className={styles.filter}>
<Select
showSearch
placeholder="文件列表"
optionFilterProp="children"
onChange={handleSelectChange}
style={{ width: 300, marginBottom: 20 }}
options={d_list}
fieldNames={{ label: 'name', value: 'id' }}
mode="multiple"
/>
<Input.TextArea
autoSize={{ minRows: 6, maxRows: 6 }}
placeholder="搜索"
style={{ width: 300 }}
allowClear
onChange={handleInputChange}
/>
</div>
<div className={styles.pageContainer}>
<div className={styles.pageContent}>
<Spin spinning={loading} className={styles.spin} size="large">
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 24 }}>
{data.map((item: any) => {
return (
<Col
className="gutter-row"
key={item.chunk_id}
xs={24}
sm={12}
md={12}
lg={8}
>
<Card
className={styles.card}
onClick={() => {
handleEditchunk(item);
}}
>
<img
style={{ width: '50px' }}
src={`${api_host}/document/image/${item.img_id}`}
alt=""
/>
<div className={styles.container}>
<div className={styles.content}>
<span className={styles.context}>
{item.content_ltks}
</span>
<span className={styles.delete}>
<Switch
size="small"
defaultValue={item.doc_ids == '1'}
onChange={(checked: boolean, e: any) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
switchChunk(item, checked);
}}
/>
</span>
</div>
<div className={styles.footer}>
<span className={styles.text}>
<MinusSquareOutlined />
{item.doc_num}
</span>
<span className={styles.text}>
<MinusSquareOutlined />
{item.chunk_num}
</span>
<span className={styles.text}>
<MinusSquareOutlined />
{item.token_num}
</span>
<span style={{ float: 'right' }}>
<Popconfirm
title="Delete the task"
description="Are you sure to delete this task?"
onConfirm={(e: any) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
console.log(confirm);
confirm(item.chunk_id);
}}
okText="Yes"
cancelText="No"
>
<DeleteOutlined
onClick={(e) => {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
}}
/>
</Popconfirm>
</span>
</div>
</div>
</Card>
</Col>
);
})}
</Row>
</Spin>
</div>
<div className={styles.pageFooter}>
<Pagination
responsive
showLessItems
showQuickJumper
showSizeChanger
onChange={onShowSizeChange}
defaultPageSize={30}
pageSizeOptions={[30, 60, 90]}
defaultCurrent={pagination.page}
total={total}
/>
</div>
</div>
</div>
<CreateModal
isShowCreateModal={isShowCreateModal}
chunk_id={chunk_id}
doc_id={doc_id}
/>
</>
);
};
export default KnowledgeSearching;

View File

@ -1,160 +0,0 @@
import kbService from '@/services/kbService';
import omit from 'lodash/omit';
import { DvaModel } from 'umi';
export interface KSearchModelState {
loading: boolean;
data: any[];
total: number;
isShowCreateModal: boolean;
chunk_id: string;
chunkInfo: any;
d_list: any[];
question: string;
doc_ids: any[];
pagination: any;
doc_id: string;
}
const model: DvaModel<KSearchModelState> = {
namespace: 'kSearchModel',
state: {
loading: false,
data: [],
total: 0,
isShowCreateModal: false,
chunk_id: '',
chunkInfo: {},
d_list: [],
question: '',
doc_ids: [],
pagination: { page: 1, size: 30 },
doc_id: '',
},
reducers: {
updateState(state, { payload }) {
return {
...state,
...payload,
};
},
},
subscriptions: {
setup({ dispatch, history }) {
history.listen((location) => {
console.log(location);
});
},
},
effects: {
*getKfList({ payload = {} }, { call, put }) {
const { data, response } = yield call(
kbService.get_document_list,
payload,
);
const { retcode, data: res, retmsg } = data;
if (retcode === 0) {
yield put({
type: 'updateState',
payload: {
d_list: res,
},
});
}
},
*chunk_list({ payload = {} }, { call, put, select }) {
const { question, doc_ids, pagination }: KSearchModelState = yield select(
(state: any) => state.kSearchModel,
);
const { data } = yield call(kbService.retrieval_test, {
...payload,
...pagination,
question,
doc_ids,
similarity_threshold: 0.1,
});
const { retcode, data: res, retmsg } = data;
if (retcode === 0) {
yield put({
type: 'updateState',
payload: {
data: res.chunks,
total: res.total,
},
});
}
},
*switch_chunk({ payload = {} }, { call, put }) {
const { data } = yield call(
kbService.switch_chunk,
omit(payload, ['kb_id']),
);
const { retcode } = data;
if (retcode === 0) {
yield put({
type: 'chunk_list',
payload: {
kb_id: payload.kb_id,
},
});
}
},
*rm_chunk({ payload = {} }, { call, put }) {
const { data } = yield call(kbService.rm_chunk, {
chunk_ids: payload.chunk_ids,
});
const { retcode, data: res, retmsg } = data;
if (retcode === 0) {
// TODO: Can be extracted
yield put({
type: 'chunk_list',
payload: {
kb_id: payload.kb_id,
},
});
}
},
*get_chunk({ payload = {} }, { call, put }) {
const { data, response } = yield call(kbService.get_chunk, payload);
const { retcode, data: res, retmsg } = data;
if (retcode === 0) {
yield put({
type: 'updateState',
payload: {
chunkInfo: res,
},
});
}
},
*create_hunk({ payload = {} }, { call, put }) {
yield put({
type: 'updateState',
payload: {
loading: true,
},
});
let service = kbService.create_chunk;
if (payload.chunk_id) {
service = kbService.set_chunk;
}
const { data } = yield call(service, payload);
const { retcode } = data;
yield put({
type: 'updateState',
payload: {
loading: false,
},
});
if (retcode === 0) {
yield put({
type: 'updateState',
payload: {
isShowCreateModal: false,
},
});
}
},
},
};
export default model;

View File

@ -0,0 +1,217 @@
import {
useFetchKnowledgeBaseConfiguration,
useFetchParserList,
useKnowledgeBaseId,
useSelectParserList,
} from '@/hooks/knowledgeHook';
import {
Button,
Divider,
Form,
Input,
Radio,
Select,
Space,
Typography,
Upload,
UploadFile,
} from 'antd';
import pick from 'lodash/pick';
import { useCallback, useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'umi';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
import { IKnowledge } from '@/interfaces/database/knowledge';
import { IThirdOAIModelCollection } from '@/interfaces/database/llm';
import { PlusOutlined } from '@ant-design/icons';
import styles from './index.less';
const { Title } = Typography;
const { Option } = Select;
const Configuration = () => {
const [form] = Form.useForm();
const dispatch = useDispatch();
const knowledgeBaseId = useKnowledgeBaseId();
const loading = useOneNamespaceEffectsLoading('kSModel', ['updateKb']);
const llmInfo: IThirdOAIModelCollection = useSelector(
(state: any) => state.settingModel.llmInfo,
);
const knowledgeDetails: IKnowledge = useSelector(
(state: any) => state.kSModel.knowledgeDetails,
);
const normFile = (e: any) => {
if (Array.isArray(e)) {
return e;
}
return e?.fileList;
};
const parserList = useSelectParserList();
const embeddingModelOptions = useMemo(() => {
return Object.entries(llmInfo).map(([key, value]) => {
return {
label: key,
options: value.map((x) => ({
label: x.llm_name,
value: x.llm_name,
})),
};
});
}, [llmInfo]);
const onFinish = async (values: any) => {
console.info(values);
const fileList = values.avatar;
let avatar;
if (Array.isArray(fileList)) {
avatar = fileList[0].thumbUrl;
}
dispatch({
type: 'kSModel/updateKb',
payload: {
...values,
avatar,
kb_id: knowledgeBaseId,
},
});
};
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
const fetchLlmList = useCallback(() => {
dispatch({
type: 'settingModel/llm_list',
payload: { model_type: 'embedding' },
});
}, [dispatch]);
useEffect(() => {
const avatar = knowledgeDetails.avatar;
let fileList: UploadFile[] = [];
if (avatar) {
fileList = [{ uid: '1', name: 'file', thumbUrl: avatar, status: 'done' }];
}
form.setFieldsValue({
...pick(knowledgeDetails, [
'description',
'name',
'permission',
'embd_id',
'parser_id',
]),
avatar: fileList,
});
}, [form, knowledgeDetails]);
useFetchParserList();
useFetchKnowledgeBaseConfiguration();
useEffect(() => {
fetchLlmList();
}, [fetchLlmList]);
return (
<div className={styles.configurationWrapper}>
<Title level={5}>Configuration</Title>
<p>Update your knowledge base details especially parsing method here.</p>
<Divider></Divider>
<Form
form={form}
name="validateOnly"
layout="vertical"
autoComplete="off"
onFinish={onFinish}
onFinishFailed={onFinishFailed}
>
<Form.Item
name="name"
label="Knowledge base name"
rules={[{ required: true }]}
>
<Input />
</Form.Item>
<Form.Item
name="avatar"
label="Knowledge base photo"
valuePropName="fileList"
getValueFromEvent={normFile}
>
<Upload
listType="picture-card"
maxCount={1}
showUploadList={{ showPreviewIcon: false, showRemoveIcon: false }}
>
<button style={{ border: 0, background: 'none' }} type="button">
<PlusOutlined />
<div style={{ marginTop: 8 }}>Upload</div>
</button>
</Upload>
</Form.Item>
<Form.Item name="description" label="Knowledge base bio">
<Input />
</Form.Item>
<Form.Item
name="permission"
label="Permissions"
rules={[{ required: true }]}
>
<Radio.Group>
<Radio value="me">Only me</Radio>
<Radio value="team">Team</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
name="embd_id"
label="Embedding Model"
rules={[{ required: true }]}
>
<Select
placeholder="Please select a country"
options={embeddingModelOptions}
></Select>
</Form.Item>
<Form.Item
name="parser_id"
label="Knowledge base category"
rules={[{ required: true }]}
>
<Select placeholder="Please select a country">
{parserList.map((x) => (
<Option value={x.value} key={x.value}>
{x.label}
</Option>
))}
</Select>
</Form.Item>
<Form.Item>
<div className={styles.buttonWrapper}>
<Space>
<Button htmlType="reset" size={'middle'}>
Cancel
</Button>
<Button
htmlType="submit"
type="primary"
size={'middle'}
loading={loading}
>
Save
</Button>
</Space>
</div>
</Form.Item>
</Form>
</div>
);
};
export default Configuration;

View File

@ -1,24 +1,30 @@
.tags {
margin-bottom: 24px;
margin-bottom: 24px;
}
.preset {
display: flex;
height: 80px;
background-color: rgba(0, 0, 0, 0.1);
border-radius: 5px;
display: flex;
height: 80px;
background-color: rgba(0, 0, 0, 0.1);
border-radius: 5px;
padding: 5px;
margin-bottom: 24px;
.left {
flex: 1;
}
.right {
width: 100px;
border-left: 1px solid rgba(0, 0, 0, 0.4);
margin: 10px 0px;
padding: 5px;
margin-bottom: 24px;
}
}
.left {
flex: 1;
}
.right {
width: 100px;
border-left: 1px solid rgba(0, 0, 0, 0.4);
margin: 10px 0px;
padding: 5px;
}
}
.configurationWrapper {
padding: 0 52px;
.buttonWrapper {
text-align: right;
}
}

View File

@ -3,6 +3,8 @@ import { useKnowledgeBaseId } from '@/hooks/knowledgeHook';
import { Button, Form, Input, Radio, Select, Space, Tag } from 'antd';
import { useCallback, useEffect, useState } from 'react';
import { useDispatch, useNavigate, useSelector } from 'umi';
import Configuration from './configuration';
import styles from './index.less';
const { CheckableTag } = Tag;
@ -12,7 +14,6 @@ const layout = {
labelAlign: 'left' as const,
};
const { Option } = Select;
/* eslint-disable no-template-curly-in-string */
const KnowledgeSetting = () => {
const dispatch = useDispatch();
@ -44,7 +45,7 @@ const KnowledgeSetting = () => {
setSelectedTag(data.data.parser_id);
}
}
}, [knowledgeBaseId]);
}, [knowledgeBaseId, dispatch, form]);
const onFinish = async () => {
try {
@ -68,10 +69,11 @@ const KnowledgeSetting = () => {
parser_id: selectedTag,
},
});
retcode === 0 &&
if (retcode === 0) {
navigate(
`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeBaseId}`,
);
}
}
} catch (error) {
console.warn(error);
@ -158,4 +160,6 @@ const KnowledgeSetting = () => {
);
};
export default KnowledgeSetting;
// export default KnowledgeSetting;
export default Configuration;

View File

@ -1,3 +1,4 @@
import { IKnowledge } from '@/interfaces/database/knowledge';
import kbService from '@/services/kbService';
import { message } from 'antd';
import { DvaModel } from 'umi';
@ -6,6 +7,7 @@ export interface KSModelState {
isShowPSwModal: boolean;
isShowTntModal: boolean;
tenantIfo: any;
knowledgeDetails: IKnowledge;
}
const model: DvaModel<KSModelState> = {
@ -14,6 +16,7 @@ const model: DvaModel<KSModelState> = {
isShowPSwModal: false,
isShowTntModal: false,
tenantIfo: {},
knowledgeDetails: {} as any,
},
reducers: {
updateState(state, { payload }) {
@ -22,31 +25,32 @@ const model: DvaModel<KSModelState> = {
...payload,
};
},
},
subscriptions: {
setup({ dispatch, history }) {
history.listen((location) => {});
setKnowledgeDetails(state, { payload }) {
return { ...state, knowledgeDetails: payload };
},
},
effects: {
*createKb({ payload = {} }, { call, put }) {
*createKb({ payload = {} }, { call }) {
const { data } = yield call(kbService.createKb, payload);
const { retcode } = data;
if (retcode === 0) {
message.success('创建知识库成功!');
message.success('Created successfully!');
}
return data;
},
*updateKb({ payload = {} }, { call, put }) {
const { data } = yield call(kbService.updateKb, payload);
const { retcode, data: res, retmsg } = data;
const { retcode } = data;
if (retcode === 0) {
message.success('更新知识库成功!');
yield put({ type: 'getKbDetail', payload: { kb_id: payload.kb_id } });
message.success('Updated successfully!');
}
},
*getKbDetail({ payload = {} }, { call, put }) {
const { data } = yield call(kbService.get_kb_detail, payload);
if (data.retcode === 0) {
yield put({ type: 'setKnowledgeDetails', payload: data.data });
}
return data;
},
},

View File

@ -1,9 +1,10 @@
import { ReactComponent as ConfigrationIcon } from '@/assets/svg/knowledge-configration.svg';
import { ReactComponent as ConfigurationIcon } from '@/assets/svg/knowledge-configration.svg';
import { ReactComponent as DatasetIcon } from '@/assets/svg/knowledge-dataset.svg';
import { ReactComponent as TestingIcon } from '@/assets/svg/knowledge-testing.svg';
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/knowledgeHook';
import { useSecondPathName } from '@/hooks/routeHook';
import { IKnowledge } from '@/interfaces/database/knowledge';
import { getWidth } from '@/utils';
import { AntDesignOutlined } from '@ant-design/icons';
import { Avatar, Menu, MenuProps, Space } from 'antd';
import classNames from 'classnames';
import { useCallback, useEffect, useMemo, useState } from 'react';
@ -16,6 +17,9 @@ const KnowledgeSidebar = () => {
const { id } = kAModel;
let navigate = useNavigate();
const activeKey = useSecondPathName();
const knowledgeDetails: IKnowledge = useSelector(
(state: any) => state.kSModel.knowledgeDetails,
);
const [windowWidth, setWindowWidth] = useState(getWidth());
const [collapsed, setCollapsed] = useState(false);
@ -62,12 +66,7 @@ const KnowledgeSidebar = () => {
getItem(
routeMap[KnowledgeRouteKey.Configuration],
KnowledgeRouteKey.Configuration,
<ConfigrationIcon />,
),
getItem(
routeMap[KnowledgeRouteKey.TempTesting],
KnowledgeRouteKey.TempTesting,
<TestingIcon />,
<ConfigurationIcon />,
),
];
}, [getItem]);
@ -93,16 +92,17 @@ const KnowledgeSidebar = () => {
};
}, []);
useFetchKnowledgeBaseConfiguration();
return (
<div className={styles.sidebarWrapper}>
<div className={styles.sidebarTop}>
<Space size={8} direction="vertical">
<Avatar size={64} icon={<AntDesignOutlined />} />
<div className={styles.knowledgeTitle}>Cloud Computing</div>
<Avatar size={64} src={knowledgeDetails.avatar} />
<div className={styles.knowledgeTitle}>{knowledgeDetails.name}</div>
</Space>
<p className={styles.knowledgeDescription}>
A scalable, secure cloud-based database optimized for high-performance
computing and data storage.
{knowledgeDetails.description}
</p>
</div>
<div className={styles.divider}></div>

View File

@ -1,4 +1,5 @@
import { ITenantInfo } from '@/interfaces/database/knowledge';
import { IThirdOAIModelCollection as IThirdAiModelCollection } from '@/interfaces/database/llm';
import userService from '@/services/userService';
import authorizationUtil from '@/utils/authorizationUtil';
import { message } from 'antd';
@ -12,7 +13,7 @@ export interface SettingModelState {
isShowSSModal: boolean;
llm_factory: string;
tenantIfo: Nullable<ITenantInfo>;
llmInfo: any;
llmInfo: IThirdAiModelCollection;
myLlm: any[];
factoriesList: any[];
}
@ -126,8 +127,8 @@ const model: DvaModel<SettingModelState> = {
}
},
*llm_list({ payload = {} }, { call, put }) {
const { data, response } = yield call(userService.llm_list, payload);
const { retcode, data: res, retmsg } = data;
const { data } = yield call(userService.llm_list, payload);
const { retcode, data: res } = data;
if (retcode === 0) {
yield put({
type: 'updateState',