feat: modify routing to nested mode and rename document (#52)

* feat: modify routing to nested mode

* feat: rename document
This commit is contained in:
balibabu 2024-02-02 18:49:54 +08:00 committed by GitHub
parent 503735cd1d
commit 7b71fb2db6
33 changed files with 681 additions and 175 deletions

View File

@ -0,0 +1,28 @@
import { ExclamationCircleFilled } from '@ant-design/icons';
import { Modal } from 'antd';
const { confirm } = Modal;
interface IProps {
onOk?: (...args: any[]) => any;
onCancel?: (...args: any[]) => any;
}
export const showDeleteConfirm = ({ onOk, onCancel }: IProps) => {
confirm({
title: 'Are you sure delete this item?',
icon: <ExclamationCircleFilled />,
content: 'Some descriptions',
okText: 'Yes',
okType: 'danger',
cancelText: 'No',
onOk() {
onOk?.();
},
onCancel() {
onCancel?.();
},
});
};
export default showDeleteConfirm;

View File

@ -0,0 +1,24 @@
import { useState } from 'react';
interface IProps {
children: (props: {
showModal(): void;
hideModal(): void;
visible: boolean;
}) => React.ReactNode;
}
const ModalManager = ({ children }: IProps) => {
const [visible, setVisible] = useState(false);
const showModal = () => {
setVisible(true);
};
const hideModal = () => {
setVisible(false);
};
return children({ visible, showModal, hideModal });
};
export default ModalManager;

View File

@ -1,5 +1,13 @@
export enum KnowledgeRouteKey { export enum KnowledgeRouteKey {
Dataset = 'dataset', Dataset = 'dataset',
Testing = 'testing', Testing = 'testing',
Configration = 'configration', Configuration = 'configuration',
}
export enum RunningStatus {
UNSTART = '0',
RUNNING = '1',
CANCEL = '2',
DONE = '3',
FAIL = '4',
} }

View File

@ -0,0 +1,8 @@
import { useSearchParams } from 'umi';
export const useKnowledgeBaseId = (): string => {
const [searchParams] = useSearchParams();
const knowledgeBaseId = searchParams.get('id');
return knowledgeBaseId || '';
};

View File

@ -0,0 +1,21 @@
import { useLocation } from 'umi';
export enum SegmentIndex {
Second = '2',
Third = '3',
}
export const useSegmentedPathName = (index: SegmentIndex) => {
const { pathname } = useLocation();
const pathArray = pathname.split('/');
return pathArray[index] || '';
};
export const useSecondPathName = () => {
return useSegmentedPathName(SegmentIndex.Second);
};
export const useThirdPathName = () => {
return useSegmentedPathName(SegmentIndex.Third);
};

View File

@ -0,0 +1,26 @@
import { RunningStatus } from '@/constants/knowledge';
export interface IKnowledgeFile {
chunk_num: number;
create_date: string;
create_time: number;
created_by: string;
id: string;
kb_id: string;
location: string;
name: string;
parser_id: string;
process_begin_at?: any;
process_duation: number;
progress: number; // parsing process
progress_msg: string; // parsing log
run: RunningStatus; // parsing status
size: number;
source_type: string;
status: string; // enabled
thumbnail?: any; // base64
token_num: number;
type: string;
update_date: string;
update_time: number;
}

View File

@ -14,11 +14,11 @@ import {
Spin, Spin,
Switch, Switch,
} from 'antd'; } from 'antd';
import { debounce } from 'lodash';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import { useDispatch, useNavigate, useSelector } from 'umi'; import { useDispatch, useSearchParams, useSelector } from 'umi';
import CreateModal from './components/createModal'; import CreateModal from './components/createModal';
import { debounce } from 'lodash';
import styles from './index.less'; import styles from './index.less';
interface PayloadType { interface PayloadType {
@ -27,18 +27,13 @@ interface PayloadType {
available_int?: number; available_int?: number;
} }
interface IProps { const Chunk = () => {
doc_id: string;
}
const Chunk = ({ doc_id }: IProps) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const chunkModel = useSelector((state: any) => state.chunkModel); const chunkModel = useSelector((state: any) => state.chunkModel);
const [keywords, SetKeywords] = useState(''); const [keywords, SetKeywords] = useState('');
const [available_int, setAvailableInt] = useState(-1); const [available_int, setAvailableInt] = useState(-1);
const navigate = useNavigate(); const [searchParams] = useSearchParams();
const [pagination, setPagination] = useState({ page: 1, size: 30 }); const [pagination, setPagination] = useState({ page: 1, size: 30 });
// const [datas, setDatas] = useState(data)
const { data = [], total, chunk_id, isShowCreateModal } = chunkModel; const { data = [], total, chunk_id, isShowCreateModal } = chunkModel;
const effects = useSelector((state: any) => state.loading.effects); const effects = useSelector((state: any) => state.loading.effects);
const loading = getOneNamespaceEffectsLoading('chunkModel', effects, [ const loading = getOneNamespaceEffectsLoading('chunkModel', effects, [
@ -46,10 +41,11 @@ const Chunk = ({ doc_id }: IProps) => {
'chunk_list', 'chunk_list',
'switch_chunk', 'switch_chunk',
]); ]);
const documentId: string = searchParams.get('doc_id') || '';
const getChunkList = (value?: string) => { const getChunkList = (value?: string) => {
const payload: PayloadType = { const payload: PayloadType = {
doc_id, doc_id: documentId,
keywords: value || keywords, keywords: value || keywords,
available_int, available_int,
}; };
@ -81,7 +77,7 @@ const Chunk = ({ doc_id }: IProps) => {
payload: { payload: {
isShowCreateModal: true, isShowCreateModal: true,
chunk_id, chunk_id,
doc_id, doc_id: documentId,
}, },
}); });
getChunkList(); getChunkList();
@ -100,7 +96,7 @@ const Chunk = ({ doc_id }: IProps) => {
payload: { payload: {
chunk_ids: [id], chunk_ids: [id],
available_int: Number(available_int), available_int: Number(available_int),
doc_id, doc_id: documentId,
}, },
}); });
@ -109,7 +105,7 @@ const Chunk = ({ doc_id }: IProps) => {
useEffect(() => { useEffect(() => {
getChunkList(); getChunkList();
}, [doc_id, available_int, pagination]); }, [documentId, available_int, pagination]);
const debounceChange = debounce(getChunkList, 300); const debounceChange = debounce(getChunkList, 300);
const debounceCallback = useCallback( const debounceCallback = useCallback(
@ -270,7 +266,7 @@ const Chunk = ({ doc_id }: IProps) => {
</div> </div>
</div> </div>
<CreateModal <CreateModal
doc_id={doc_id} doc_id={documentId}
isShowCreateModal={isShowCreateModal} isShowCreateModal={isShowCreateModal}
chunk_id={chunk_id} chunk_id={chunk_id}
getChunkList={getChunkList} getChunkList={getChunkList}

View File

@ -0,0 +1,7 @@
import { Outlet } from 'umi';
export const KnowledgeDataset = () => {
return <Outlet></Outlet>;
};
export default KnowledgeDataset;

View File

@ -0,0 +1,5 @@
const KnowledgeUploadFile = () => {
return <div>KnowledgeUploadFile</div>;
};
export default KnowledgeUploadFile;

View File

@ -0,0 +1,17 @@
import { RunningStatus } from '@/constants/knowledge';
export const RunningStatusMap = {
[RunningStatus.UNSTART]: {
label: 'UNSTART',
color: 'cyan',
},
[RunningStatus.RUNNING]: {
label: 'Parsing',
color: 'blue',
},
[RunningStatus.CANCEL]: { label: 'CANCEL', color: 'orange' },
[RunningStatus.DONE]: { label: 'SUCCESS', color: 'geekblue' },
[RunningStatus.FAIL]: { label: 'FAIL', color: 'red' },
};
export * from '@/constants/knowledge';

View File

@ -13,9 +13,9 @@ interface kFProps {
const FileCreatingModal: React.FC<kFProps> = ({ getKfList, kb_id }) => { const FileCreatingModal: React.FC<kFProps> = ({ getKfList, kb_id }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const [form] = Form.useForm();
const kFModel = useSelector((state: any) => state.kFModel); const kFModel = useSelector((state: any) => state.kFModel);
const { isShowCEFwModal } = kFModel; const { isShowCEFwModal } = kFModel;
const [form] = Form.useForm();
const { t } = useTranslation(); const { t } = useTranslation();
const handleCancel = () => { const handleCancel = () => {
@ -26,7 +26,8 @@ const FileCreatingModal: React.FC<kFProps> = ({ getKfList, kb_id }) => {
}, },
}); });
}; };
const handleOk = async () => {
const createDocument = async () => {
try { try {
const values = await form.validateFields(); const values = await form.validateFields();
const retcode = await dispatch<any>({ const retcode = await dispatch<any>({
@ -44,9 +45,13 @@ const FileCreatingModal: React.FC<kFProps> = ({ getKfList, kb_id }) => {
} }
}; };
const handleOk = async () => {
createDocument();
};
return ( return (
<Modal <Modal
title="Basic Modal" title="File Name"
open={isShowCEFwModal} open={isShowCEFwModal}
onOk={handleOk} onOk={handleOk}
onCancel={handleCancel} onCancel={handleCancel}
@ -54,15 +59,15 @@ const FileCreatingModal: React.FC<kFProps> = ({ getKfList, kb_id }) => {
<Form <Form
form={form} form={form}
name="validateOnly" name="validateOnly"
labelCol={{ span: 8 }} labelCol={{ span: 4 }}
wrapperCol={{ span: 16 }} wrapperCol={{ span: 20 }}
style={{ maxWidth: 600 }} style={{ maxWidth: 600 }}
autoComplete="off" autoComplete="off"
> >
<Form.Item<FieldType> <Form.Item<FieldType>
label="文件名" label="File Name"
name="name" name="name"
rules={[{ required: true, message: 'Please input value!' }]} rules={[{ required: true, message: 'Please input name!' }]}
> >
<Input /> <Input />
</Form.Item> </Form.Item>

View File

@ -1,28 +1,34 @@
.datasetWrapper {
padding: 30px;
flex: 1;
}
.filter { .filter {
height: 32px; height: 32px;
display: flex; display: flex;
margin: 10px 0; margin: 10px 0;
justify-content: space-between; justify-content: space-between;
padding: 24px 20px;
.search { // .search {
flex: 1; // flex: 1;
} // }
.operate { // .operate {
width: 200px; // width: 200px;
} // }
} }
.img { .img {
height: 16px; height: 16px;
width: 16px; width: 16px;
margin-right: 6px; margin-right: 6px;
} }
.column { .column {
min-width: 200px min-width: 200px;
} }
.tochunks { .tochunks {
cursor: pointer; cursor: pointer;
} }

View File

@ -1,37 +1,38 @@
import { KnowledgeRouteKey } from '@/constants/knowledge'; import { KnowledgeRouteKey } from '@/constants/knowledge';
import { useKnowledgeBaseId } from '@/hooks/knowledgeHook';
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
import { getOneNamespaceEffectsLoading } from '@/utils/stroreUtil'; import { getOneNamespaceEffectsLoading } from '@/utils/stroreUtil';
import { DownOutlined } from '@ant-design/icons'; import { PlusOutlined, SearchOutlined } from '@ant-design/icons';
import type { MenuProps } from 'antd'; import type { MenuProps } from 'antd';
import { Button, Dropdown, Input, Space, Switch, Table } from 'antd'; import {
Button,
Divider,
Dropdown,
Input,
Space,
Switch,
Table,
Tag,
} from 'antd';
import type { ColumnsType } from 'antd/es/table'; import type { ColumnsType } from 'antd/es/table';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useDispatch, useNavigate, useSelector } from 'umi'; import { useDispatch, useNavigate, useSelector } from 'umi';
import CreateEPModal from './createEFileModal'; import CreateEPModal from './createEFileModal';
import styles from './index.less'; import styles from './index.less';
import ParsingActionCell from './parsing-action-cell';
import ParsingStatusCell from './parsing-status-cell';
import RenameModal from './rename-modal';
import SegmentSetModal from './segmentSetModal'; import SegmentSetModal from './segmentSetModal';
import UploadFile from './upload'; import UploadFile from './upload';
interface DataType { const KnowledgeFile = () => {
name: string;
chunk_num: string;
token_num: number;
update_date: string;
size: string;
status: string;
id: string;
parser_id: string;
}
interface KFProps {
kb_id: string;
}
const KnowledgeFile: React.FC<KFProps> = ({ kb_id }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const kFModel = useSelector((state: any) => state.kFModel); const kFModel = useSelector((state: any) => state.kFModel);
const effects = useSelector((state: any) => state.loading.effects); const effects = useSelector((state: any) => state.loading.effects);
const { data } = kFModel; const { data } = kFModel;
const knowledgeBaseId = useKnowledgeBaseId();
const loading = getOneNamespaceEffectsLoading('kFModel', effects, [ const loading = getOneNamespaceEffectsLoading('kFModel', effects, [
'getKfList', 'getKfList',
'updateDocumentStatus', 'updateDocumentStatus',
@ -43,7 +44,7 @@ const KnowledgeFile: React.FC<KFProps> = ({ kb_id }) => {
const getKfList = (keywords?: string) => { const getKfList = (keywords?: string) => {
const payload = { const payload = {
kb_id, kb_id: knowledgeBaseId,
keywords, keywords,
}; };
if (!keywords) { if (!keywords) {
@ -56,10 +57,10 @@ const KnowledgeFile: React.FC<KFProps> = ({ kb_id }) => {
}; };
useEffect(() => { useEffect(() => {
if (kb_id) { if (knowledgeBaseId) {
getKfList(); getKfList();
} }
}, [kb_id]); }, [knowledgeBaseId]);
const debounceChange = debounce(getKfList, 300); const debounceChange = debounce(getKfList, 300);
const debounceCallback = useCallback( const debounceCallback = useCallback(
@ -79,7 +80,7 @@ const KnowledgeFile: React.FC<KFProps> = ({ kb_id }) => {
payload: { payload: {
doc_id, doc_id,
status: Number(e), status: Number(e),
kb_id, kb_id: knowledgeBaseId,
}, },
}); });
}; };
@ -88,7 +89,7 @@ const KnowledgeFile: React.FC<KFProps> = ({ kb_id }) => {
type: 'kFModel/document_rm', type: 'kFModel/document_rm',
payload: { payload: {
doc_id, doc_id,
kb_id, kb_id: knowledgeBaseId,
}, },
}); });
}; };
@ -109,13 +110,14 @@ const KnowledgeFile: React.FC<KFProps> = ({ kb_id }) => {
}, },
}); });
}; };
const actionItems: MenuProps['items'] = useMemo(() => { const actionItems: MenuProps['items'] = useMemo(() => {
return [ return [
{ {
key: '1', key: '1',
label: ( label: (
<div> <div>
<UploadFile kb_id={kb_id} getKfList={getKfList} /> <UploadFile kb_id={knowledgeBaseId} getKfList={getKfList} />
</div> </div>
), ),
}, },
@ -132,7 +134,7 @@ const KnowledgeFile: React.FC<KFProps> = ({ kb_id }) => {
// disabled: true, // disabled: true,
}, },
]; ];
}, [kb_id]); }, [knowledgeBaseId]);
const chunkItems: MenuProps['items'] = [ const chunkItems: MenuProps['items'] = [
{ {
key: '1', key: '1',
@ -158,14 +160,21 @@ const KnowledgeFile: React.FC<KFProps> = ({ kb_id }) => {
// disabled: true, // disabled: true,
}, },
]; ];
const toChunk = (id: string) => { const toChunk = (id: string) => {
navigate( navigate(
`/knowledge/${KnowledgeRouteKey.Dataset}?id=${kb_id}&doc_id=${id}`, `/knowledge/${KnowledgeRouteKey.Dataset}/chunk?id=${knowledgeBaseId}&doc_id=${id}`,
); );
}; };
const columns: ColumnsType<DataType> = [
const setDocumentAndParserId = (record: IKnowledgeFile) => () => {
setDocId(record.id);
setParserId(record.parser_id);
};
const columns: ColumnsType<IKnowledgeFile> = [
{ {
title: '名称', title: 'Name',
dataIndex: 'name', dataIndex: 'name',
key: 'name', key: 'name',
render: (text: any, { id }) => ( render: (text: any, { id }) => (
@ -178,32 +187,30 @@ const KnowledgeFile: React.FC<KFProps> = ({ kb_id }) => {
{text} {text}
</div> </div>
), ),
className: `${styles.column}`,
}, },
{ {
title: '数据总量', title: 'Chunk Number',
dataIndex: 'chunk_num', dataIndex: 'chunk_num',
key: 'chunk_num', key: 'chunk_num',
className: `${styles.column}`,
}, },
{ {
title: 'Tokens', title: 'Upload Date',
dataIndex: 'token_num', dataIndex: 'create_date',
key: 'token_num', key: 'create_date',
className: `${styles.column}`,
}, },
{ {
title: '文件大小', title: 'Parsing Status',
dataIndex: 'size', dataIndex: 'run',
key: 'size', key: 'run',
className: `${styles.column}`, render: (text, record) => {
return <ParsingStatusCell record={record}></ParsingStatusCell>;
},
}, },
{ {
title: '状态', title: 'Enabled',
key: 'status', key: 'status',
dataIndex: 'status', dataIndex: 'status',
className: `${styles.column}`, render: (_, { status, id }) => (
render: (_, { status: string, id }) => (
<> <>
<Switch <Switch
defaultChecked={status === '1'} defaultChecked={status === '1'}
@ -217,58 +224,65 @@ const KnowledgeFile: React.FC<KFProps> = ({ kb_id }) => {
{ {
title: 'Action', title: 'Action',
key: 'action', key: 'action',
className: `${styles.column}`,
render: (_, record) => ( render: (_, record) => (
<Space size="middle"> <ParsingActionCell
<Dropdown menu={{ items: chunkItems }} trigger={['click']}> documentId={doc_id}
<a knowledgeBaseId={knowledgeBaseId}
onClick={() => { setDocumentAndParserId={setDocumentAndParserId(record)}
setDocId(record.id); record={record}
setParserId(record.parser_id); ></ParsingActionCell>
}}
>
<DownOutlined />
</a>
</Dropdown>
</Space>
), ),
}, },
]; ];
const finalColumns = columns.map((x) => ({
...x,
className: `${styles.column}`,
}));
return ( return (
<> <div className={styles.datasetWrapper}>
<h3>Dataset</h3>
<p>Hey, don't forget to adjust the chunk after adding the dataset! 😉</p>
<Divider></Divider>
<div className={styles.filter}> <div className={styles.filter}>
<div className="search"> <Space>
<h3>Total</h3>
<Tag color="purple">100 files</Tag>
</Space>
<Space>
<Input <Input
placeholder="搜索" placeholder="Seach your files"
value={inputValue} value={inputValue}
style={{ width: 220 }} style={{ width: 220 }}
allowClear allowClear
onChange={handleInputChange} onChange={handleInputChange}
prefix={<SearchOutlined />}
/> />
</div>
<div className="operate">
<Dropdown menu={{ items: actionItems }} trigger={['click']}> <Dropdown menu={{ items: actionItems }} trigger={['click']}>
<a> <Button type="primary" icon={<PlusOutlined />}>
<DownOutlined /> Add file
</a> </Button>
</Dropdown> </Dropdown>
</div> </Space>
</div> </div>
<Table <Table
rowKey="id" rowKey="id"
columns={columns} columns={finalColumns}
dataSource={data} dataSource={data}
loading={loading} loading={loading}
pagination={false} pagination={false}
scroll={{ scrollToFirstRowOnChange: true, x: true }} scroll={{ scrollToFirstRowOnChange: true, x: true, y: 'fill' }}
/> />
<CreateEPModal getKfList={getKfList} kb_id={kb_id} /> <CreateEPModal getKfList={getKfList} kb_id={knowledgeBaseId} />
<SegmentSetModal <SegmentSetModal
getKfList={getKfList} getKfList={getKfList}
parser_id={parser_id} parser_id={parser_id}
doc_id={doc_id} doc_id={doc_id}
/> />
</> <RenameModal></RenameModal>
</div>
); );
}; };

View File

@ -1,14 +1,19 @@
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
import kbService from '@/services/kbService'; import kbService from '@/services/kbService';
import { message } from 'antd'; import { message } from 'antd';
import omit from 'lodash/omit';
import pick from 'lodash/pick'; import pick from 'lodash/pick';
import { Nullable } from 'typings';
import { DvaModel } from 'umi'; import { DvaModel } from 'umi';
export interface KFModelState { export interface KFModelState {
isShowCEFwModal: boolean; isShowCEFwModal: boolean;
isShowTntModal: boolean; isShowTntModal: boolean;
isShowSegmentSetModal: boolean; isShowSegmentSetModal: boolean;
isShowRenameModal: boolean;
tenantIfo: any; tenantIfo: any;
data: any[]; data: IKnowledgeFile[];
currentRecord: Nullable<IKnowledgeFile>;
} }
const model: DvaModel<KFModelState> = { const model: DvaModel<KFModelState> = {
@ -17,8 +22,10 @@ const model: DvaModel<KFModelState> = {
isShowCEFwModal: false, isShowCEFwModal: false,
isShowTntModal: false, isShowTntModal: false,
isShowSegmentSetModal: false, isShowSegmentSetModal: false,
isShowRenameModal: false,
tenantIfo: {}, tenantIfo: {},
data: [], data: [],
currentRecord: null,
}, },
reducers: { reducers: {
updateState(state, { payload }) { updateState(state, { payload }) {
@ -27,6 +34,12 @@ const model: DvaModel<KFModelState> = {
...payload, ...payload,
}; };
}, },
setIsShowRenameModal(state, { payload }) {
return { ...state, isShowRenameModal: payload };
},
setCurrentRecord(state, { payload }) {
return { ...state, currentRecord: payload };
},
}, },
subscriptions: { subscriptions: {
setup({ dispatch, history }) { setup({ dispatch, history }) {
@ -99,6 +112,26 @@ const model: DvaModel<KFModelState> = {
}); });
} }
}, },
*document_rename({ payload = {} }, { call, put }) {
const { data } = yield call(
kbService.document_rename,
omit(payload, ['kb_id']),
);
const { retcode, data: res, retmsg } = data;
if (retcode === 0) {
message.success('rename success');
yield put({
type: 'setIsShowRenameModal',
payload: false,
});
yield put({
type: 'getKfList',
payload: { kb_id: payload.kb_id },
});
}
return retcode;
},
*document_create({ payload = {} }, { call, put }) { *document_create({ payload = {} }, { call, put }) {
const { data, response } = yield call(kbService.document_create, payload); const { data, response } = yield call(kbService.document_create, payload);
const { retcode, data: res, retmsg } = data; const { retcode, data: res, retmsg } = data;

View File

@ -0,0 +1,88 @@
import showDeleteConfirm from '@/components/deleting-confirm';
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
import { DeleteOutlined, EditOutlined, ToolOutlined } from '@ant-design/icons';
import { Button, Dropdown, MenuProps, Space, Tooltip } from 'antd';
import { useDispatch } from 'umi';
interface IProps {
documentId: string;
knowledgeBaseId: string;
record: IKnowledgeFile;
setDocumentAndParserId: () => void;
}
const ParsingActionCell = ({
documentId,
knowledgeBaseId,
record,
setDocumentAndParserId,
}: IProps) => {
const dispatch = useDispatch();
const removeDocument = () => {
dispatch({
type: 'kFModel/document_rm',
payload: {
doc_id: documentId,
kb_id: knowledgeBaseId,
},
});
};
const onRmDocument = () => {
showDeleteConfirm({ onOk: removeDocument });
};
const setCurrentRecord = () => {
dispatch({
type: 'kFModel/setCurrentRecord',
payload: record,
});
};
const showSegmentSetModal = () => {
dispatch({
type: 'kFModel/updateState',
payload: {
isShowSegmentSetModal: true,
},
});
};
const showRenameModal = () => {
setCurrentRecord();
dispatch({
type: 'kFModel/setIsShowRenameModal',
payload: true,
});
};
const onRename = () => {};
const chunkItems: MenuProps['items'] = [
{
key: '1',
label: (
<div>
<Button type="link" onClick={showSegmentSetModal}>
</Button>
</div>
),
},
];
return (
<Space size={'middle'}>
<Dropdown menu={{ items: chunkItems }} trigger={['click']}>
<ToolOutlined size={20} onClick={setDocumentAndParserId} />
</Dropdown>
<Tooltip title="Rename">
<EditOutlined size={20} onClick={showRenameModal} />
</Tooltip>
<DeleteOutlined size={20} onClick={onRmDocument} />
</Space>
);
};
export default ParsingActionCell;

View File

@ -0,0 +1,3 @@
.popover-content {
width: 300px;
}

View File

@ -0,0 +1,68 @@
import { IKnowledgeFile } from '@/interfaces/database/knowledge';
import { Badge, DescriptionsProps, Flex, Popover, Space, Tag } from 'antd';
import { RunningStatus, RunningStatusMap } from '../constant';
import styles from './index.less';
interface IProps {
record: IKnowledgeFile;
}
const PopoverContent = ({ record }: IProps) => {
const items: DescriptionsProps['items'] = [
{
key: 'process_begin_at',
label: 'Process Begin At',
children: record.process_begin_at,
},
{
key: 'process_duation',
label: 'Process Duration',
children: record.process_duation,
},
{
key: 'progress_msg',
label: 'Progress Msg',
children: record.progress_msg,
},
];
return (
<Flex vertical className={styles['popover-content']}>
{items.map((x) => {
return (
<div>
<b>{x.label}:</b>
<p>{x.children}</p>
</div>
);
})}
</Flex>
);
};
export const ParsingStatusCell = ({ record }: IProps) => {
const text = record.run;
const runningStatus = RunningStatusMap[text];
const isRunning = text === RunningStatus.RUNNING;
return (
<Popover
content={isRunning && <PopoverContent record={record}></PopoverContent>}
>
<Tag color={runningStatus.color}>
{isRunning ? (
<Space>
<Badge color={runningStatus.color} />
`${runningStatus.label}${record.progress * 100}%`
</Space>
) : (
runningStatus.label
)}
</Tag>
</Popover>
);
};
export default ParsingStatusCell;

View File

@ -0,0 +1,88 @@
import { useKnowledgeBaseId } from '@/hooks/knowledgeHook';
import { Form, Input, Modal } from 'antd';
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'umi';
const RenameModal = () => {
const [form] = Form.useForm();
const dispatch = useDispatch();
const kFModel = useSelector((state: any) => state.kFModel);
const loading = useSelector(
(state: any) => state.loading.effects['kFModel/document_rename'],
);
const knowledgeBaseId = useKnowledgeBaseId();
const isModalOpen = kFModel.isShowRenameModal;
const initialName = kFModel.currentRecord?.name;
const documentId = kFModel.currentRecord?.id;
type FieldType = {
name?: string;
};
const closeModal = () => {
dispatch({
type: 'kFModel/setIsShowRenameModal',
payload: false,
});
};
const handleOk = async () => {
const ret = await form.validateFields();
dispatch({
type: 'kFModel/document_rename',
payload: {
doc_id: documentId,
name: ret.name,
kb_id: knowledgeBaseId,
},
});
};
const handleCancel = () => {
closeModal();
};
const onFinish = (values: any) => {
console.log('Success:', values);
};
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
useEffect(() => {
form.setFieldValue('name', initialName);
}, [initialName, documentId]);
return (
<Modal
title="Rename"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}
okButtonProps={{ loading }}
>
<Form
name="basic"
labelCol={{ span: 4 }}
wrapperCol={{ span: 20 }}
style={{ maxWidth: 600 }}
onFinish={onFinish}
onFinishFailed={onFinishFailed}
autoComplete="off"
form={form}
>
<Form.Item<FieldType>
label="Name"
name="name"
rules={[{ required: true, message: 'Please input name!' }]}
>
<Input />
</Form.Item>
</Form>
</Modal>
);
};
export default RenameModal;

View File

@ -41,7 +41,6 @@ const SegmentSetModal: React.FC<kFProps> = ({
}; };
const handleOk = async () => { const handleOk = async () => {
console.log(1111, selectedTag);
const retcode = await dispatch<any>({ const retcode = await dispatch<any>({
type: 'kFModel/document_change_parser', type: 'kFModel/document_change_parser',
payload: { payload: {

View File

@ -1,3 +1,5 @@
import { useKnowledgeBaseId } from '@/hooks/knowledgeHook';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
import { api_host } from '@/utils/api'; import { api_host } from '@/utils/api';
import { DeleteOutlined, MinusSquareOutlined } from '@ant-design/icons'; import { DeleteOutlined, MinusSquareOutlined } from '@ant-design/icons';
import type { PaginationProps } from 'antd'; import type { PaginationProps } from 'antd';
@ -12,18 +14,14 @@ import {
Spin, Spin,
Switch, Switch,
} from 'antd'; } from 'antd';
import { debounce } from 'lodash';
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
import { useDispatch, useSelector } from 'umi'; import { useDispatch, useSelector } from 'umi';
import CreateModal from '../knowledge-chunk/components/createModal'; import CreateModal from '../knowledge-chunk/components/createModal';
import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks';
import { debounce } from 'lodash';
import styles from './index.less'; import styles from './index.less';
interface chunkProps {
kb_id: string;
}
const KnowledgeSearching: React.FC<chunkProps> = ({ kb_id }) => { const KnowledgeSearching = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const kSearchModel = useSelector((state: any) => state.kSearchModel); const kSearchModel = useSelector((state: any) => state.kSearchModel);
const chunkModel = useSelector((state: any) => state.chunkModel); const chunkModel = useSelector((state: any) => state.chunkModel);
@ -31,6 +29,7 @@ const KnowledgeSearching: React.FC<chunkProps> = ({ kb_id }) => {
'chunk_list', 'chunk_list',
'switch_chunk', 'switch_chunk',
]); ]);
const knowledgeBaseId = useKnowledgeBaseId();
const { const {
data = [], data = [],
@ -46,7 +45,7 @@ const KnowledgeSearching: React.FC<chunkProps> = ({ kb_id }) => {
dispatch({ dispatch({
type: 'kSearchModel/chunk_list', type: 'kSearchModel/chunk_list',
payload: { payload: {
kb_id, kb_id: knowledgeBaseId,
}, },
}); });
}; };
@ -55,7 +54,7 @@ const KnowledgeSearching: React.FC<chunkProps> = ({ kb_id }) => {
type: 'kSearchModel/rm_chunk', type: 'kSearchModel/rm_chunk',
payload: { payload: {
chunk_ids: [id], chunk_ids: [id],
kb_id, kb_id: knowledgeBaseId,
}, },
}); });
}; };
@ -93,7 +92,7 @@ const KnowledgeSearching: React.FC<chunkProps> = ({ kb_id }) => {
dispatch({ dispatch({
type: 'kSearchModel/getKfList', type: 'kSearchModel/getKfList',
payload: { payload: {
kb_id, kb_id: knowledgeBaseId,
}, },
}); });
}, []); }, []);
@ -106,7 +105,7 @@ const KnowledgeSearching: React.FC<chunkProps> = ({ kb_id }) => {
chunk_ids: [chunk_id], chunk_ids: [chunk_id],
doc_id, doc_id,
available_int, available_int,
kb_id, kb_id: knowledgeBaseId,
}, },
}); });
}; };

View File

@ -1,6 +1,7 @@
import { KnowledgeRouteKey } from '@/constants/knowledge'; import { KnowledgeRouteKey } from '@/constants/knowledge';
import { useKnowledgeBaseId } from '@/hooks/knowledgeHook';
import { Button, Form, Input, Radio, Select, Space, Tag } from 'antd'; import { Button, Form, Input, Radio, Select, Space, Tag } from 'antd';
import React, { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useDispatch, useNavigate, useSelector } from 'umi'; import { useDispatch, useNavigate, useSelector } from 'umi';
import styles from './index.less'; import styles from './index.less';
@ -13,10 +14,7 @@ const layout = {
const { Option } = Select; const { Option } = Select;
/* eslint-disable no-template-curly-in-string */ /* eslint-disable no-template-curly-in-string */
interface kSProps { const KnowledgeSetting = () => {
kb_id: string;
}
const KnowledgeSetting: React.FC<kSProps> = ({ kb_id }) => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const settingModel = useSelector((state: any) => state.settingModel); const settingModel = useSelector((state: any) => state.settingModel);
let navigate = useNavigate(); let navigate = useNavigate();
@ -25,17 +23,18 @@ const KnowledgeSetting: React.FC<kSProps> = ({ kb_id }) => {
const [form] = Form.useForm(); const [form] = Form.useForm();
const [selectedTag, setSelectedTag] = useState(''); const [selectedTag, setSelectedTag] = useState('');
const values = Form.useWatch([], form); const values = Form.useWatch([], form);
const knowledgeBaseId = useKnowledgeBaseId();
const getTenantInfo = useCallback(async () => { const getTenantInfo = useCallback(async () => {
dispatch({ dispatch({
type: 'settingModel/getTenantInfo', type: 'settingModel/getTenantInfo',
payload: {}, payload: {},
}); });
if (kb_id) { if (knowledgeBaseId) {
const data = await dispatch<any>({ const data = await dispatch<any>({
type: 'kSModel/getKbDetail', type: 'kSModel/getKbDetail',
payload: { payload: {
kb_id, kb_id: knowledgeBaseId,
}, },
}); });
if (data.retcode === 0) { if (data.retcode === 0) {
@ -44,19 +43,19 @@ const KnowledgeSetting: React.FC<kSProps> = ({ kb_id }) => {
setSelectedTag(data.data.parser_id); setSelectedTag(data.data.parser_id);
} }
} }
}, [kb_id]); }, [knowledgeBaseId]);
const onFinish = async () => { const onFinish = async () => {
try { try {
await form.validateFields(); await form.validateFields();
if (kb_id) { if (knowledgeBaseId) {
dispatch({ dispatch({
type: 'kSModel/updateKb', type: 'kSModel/updateKb',
payload: { payload: {
...values, ...values,
parser_id: selectedTag, parser_id: selectedTag,
kb_id, kb_id: knowledgeBaseId,
embd_id: undefined, embd_id: undefined,
}, },
}); });
@ -69,7 +68,9 @@ const KnowledgeSetting: React.FC<kSProps> = ({ kb_id }) => {
}, },
}); });
retcode === 0 && retcode === 0 &&
navigate(`/knowledge/${KnowledgeRouteKey.Dataset}?id=${kb_id}`); navigate(
`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeBaseId}`,
);
} }
} catch (error) { } catch (error) {
console.warn(error); console.warn(error);

View File

@ -1,12 +1,13 @@
import { ReactComponent as ConfigrationIcon } from '@/assets/svg/knowledge-configration.svg'; import { ReactComponent as ConfigrationIcon } from '@/assets/svg/knowledge-configration.svg';
import { ReactComponent as DatasetIcon } from '@/assets/svg/knowledge-dataset.svg'; import { ReactComponent as DatasetIcon } from '@/assets/svg/knowledge-dataset.svg';
import { ReactComponent as TestingIcon } from '@/assets/svg/knowledge-testing.svg'; import { ReactComponent as TestingIcon } from '@/assets/svg/knowledge-testing.svg';
import { useSecondPathName } from '@/hooks/routeHook';
import { getWidth } from '@/utils'; import { getWidth } from '@/utils';
import { AntDesignOutlined } from '@ant-design/icons'; import { AntDesignOutlined } from '@ant-design/icons';
import { Avatar, Menu, MenuProps, Space } from 'antd'; import { Avatar, Menu, MenuProps, Space } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { useEffect, useMemo, useState } from 'react'; import { useEffect, useMemo, useState } from 'react';
import { useNavigate, useParams, useSelector } from 'umi'; import { useNavigate, useSelector } from 'umi';
import { KnowledgeRouteKey, routeMap } from '../../constant'; import { KnowledgeRouteKey, routeMap } from '../../constant';
import styles from './index.less'; import styles from './index.less';
@ -14,8 +15,7 @@ const KnowledgeSidebar = () => {
const kAModel = useSelector((state: any) => state.kAModel); const kAModel = useSelector((state: any) => state.kAModel);
const { id } = kAModel; const { id } = kAModel;
let navigate = useNavigate(); let navigate = useNavigate();
const params = useParams(); const activeKey = useSecondPathName();
const activeKey = params.module || KnowledgeRouteKey.Dataset;
const [windowWidth, setWindowWidth] = useState(getWidth()); const [windowWidth, setWindowWidth] = useState(getWidth());
const [collapsed, setCollapsed] = useState(false); const [collapsed, setCollapsed] = useState(false);
@ -56,8 +56,8 @@ const KnowledgeSidebar = () => {
<TestingIcon />, <TestingIcon />,
), ),
getItem( getItem(
routeMap[KnowledgeRouteKey.Configration], routeMap[KnowledgeRouteKey.Configuration],
KnowledgeRouteKey.Configration, KnowledgeRouteKey.Configuration,
<ConfigrationIcon />, <ConfigrationIcon />,
), ),
]; ];

View File

@ -3,7 +3,17 @@ import { KnowledgeRouteKey } from '@/constants/knowledge';
export const routeMap = { export const routeMap = {
[KnowledgeRouteKey.Dataset]: 'Dataset', [KnowledgeRouteKey.Dataset]: 'Dataset',
[KnowledgeRouteKey.Testing]: 'Retrieval testing', [KnowledgeRouteKey.Testing]: 'Retrieval testing',
[KnowledgeRouteKey.Configration]: 'Configuration', [KnowledgeRouteKey.Configuration]: 'Configuration',
};
export enum KnowledgeDatasetRouteKey {
Chunk = 'chunk',
File = 'file',
}
export const datasetRouteMap = {
[KnowledgeDatasetRouteKey.Chunk]: 'Chunk',
[KnowledgeDatasetRouteKey.File]: 'File Upload',
}; };
export * from '@/constants/knowledge'; export * from '@/constants/knowledge';

View File

@ -7,9 +7,12 @@
height: 100%; height: 100%;
background-color: rgba(247, 248, 250, 1); background-color: rgba(247, 248, 250, 1);
padding: 16px 20px 28px 40px; padding: 16px 20px 28px 40px;
display: flex;
flex-direction: column;
} }
.content { .content {
background-color: white; background-color: white;
margin-top: 16px; margin-top: 16px;
// flex: 1;
} }
} }

View File

@ -1,45 +1,60 @@
import { useKnowledgeBaseId } from '@/hooks/knowledgeHook';
import { useSecondPathName, useThirdPathName } from '@/hooks/routeHook';
import { Breadcrumb } from 'antd'; import { Breadcrumb } from 'antd';
import { ItemType } from 'antd/es/breadcrumb/Breadcrumb';
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import { import { Link, Outlet, useDispatch, useLocation, useNavigate } from 'umi';
useDispatch,
useLocation,
useNavigate,
useParams,
useSelector,
} from 'umi';
import Chunk from './components/knowledge-chunk';
import File from './components/knowledge-file';
import Search from './components/knowledge-search';
import Setting from './components/knowledge-setting';
import Siderbar from './components/knowledge-sidebar'; import Siderbar from './components/knowledge-sidebar';
import { KnowledgeRouteKey, routeMap } from './constant'; import {
KnowledgeDatasetRouteKey,
KnowledgeRouteKey,
datasetRouteMap,
routeMap,
} from './constant';
import styles from './index.less'; import styles from './index.less';
const KnowledgeAdding = () => { const KnowledgeAdding = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const kAModel = useSelector((state: any) => state.kAModel);
const navigate = useNavigate(); const navigate = useNavigate();
const { id, doc_id } = kAModel; const knowledgeBaseId = useKnowledgeBaseId();
const location = useLocation(); const location = useLocation();
const params = useParams();
const activeKey: KnowledgeRouteKey = const activeKey: KnowledgeRouteKey =
(params.module as KnowledgeRouteKey) || KnowledgeRouteKey.Dataset; (useSecondPathName() as KnowledgeRouteKey) || KnowledgeRouteKey.Dataset;
const datasetActiveKey: KnowledgeDatasetRouteKey =
useThirdPathName() as KnowledgeDatasetRouteKey;
const gotoList = () => { const gotoList = () => {
navigate('/knowledge'); navigate('/knowledge');
}; };
const breadcrumbItems = useMemo(() => { const breadcrumbItems: ItemType[] = useMemo(() => {
return [ const items: ItemType[] = [
{ {
title: <a onClick={gotoList}>Knowledge Base</a>, title: <a onClick={gotoList}>Knowledge Base</a>,
}, },
{ {
title: routeMap[activeKey], title: datasetActiveKey ? (
<Link
to={`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeBaseId}`}
>
{routeMap[activeKey]}
</Link>
) : (
routeMap[activeKey]
),
}, },
]; ];
}, [activeKey]);
if (datasetActiveKey) {
items.push({
title: datasetRouteMap[datasetActiveKey],
});
}
return items;
}, [activeKey, datasetActiveKey]);
useEffect(() => { useEffect(() => {
const search: string = location.search.slice(1); const search: string = location.search.slice(1);
@ -65,16 +80,7 @@ const KnowledgeAdding = () => {
<div className={styles.contentWrapper}> <div className={styles.contentWrapper}>
<Breadcrumb items={breadcrumbItems} /> <Breadcrumb items={breadcrumbItems} />
<div className={styles.content}> <div className={styles.content}>
{activeKey === KnowledgeRouteKey.Dataset && !doc_id && ( <Outlet></Outlet>
<File kb_id={id} />
)}
{activeKey === KnowledgeRouteKey.Configration && (
<Setting kb_id={id} />
)}
{activeKey === KnowledgeRouteKey.Testing && <Search kb_id={id} />}
{activeKey === KnowledgeRouteKey.Dataset && !!doc_id && (
<Chunk doc_id={doc_id} />
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,7 @@
import { ReactComponent as FilterIcon } from '@/assets/filter.svg'; import { ReactComponent as FilterIcon } from '@/assets/filter.svg';
import { KnowledgeRouteKey } from '@/constants/knowledge';
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined } from '@ant-design/icons';
import { Button, Col, Row, Space } from 'antd'; import { Button, Flex, Space } from 'antd';
import { useCallback, useEffect } from 'react'; import { useCallback, useEffect } from 'react';
import { useDispatch, useNavigate, useSelector } from 'umi'; import { useDispatch, useNavigate, useSelector } from 'umi';
import styles from './index.less'; import styles from './index.less';
@ -20,7 +21,7 @@ const Knowledge = () => {
}, []); }, []);
const handleAddKnowledge = () => { const handleAddKnowledge = () => {
navigate(`add/setting`); navigate(`/knowledge/${KnowledgeRouteKey.Configuration}`);
}; };
useEffect(() => { useEffect(() => {
@ -50,7 +51,7 @@ const Knowledge = () => {
</Button> </Button>
</Space> </Space>
</div> </div>
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }}> {/* <Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }}>
{data.map((item: any) => { {data.map((item: any) => {
return ( return (
<Col <Col
@ -58,14 +59,19 @@ const Knowledge = () => {
key={item.name} key={item.name}
xs={24} xs={24}
sm={12} sm={12}
md={8} md={10}
lg={6} lg={8}
> >
<KnowledgeCard item={item}></KnowledgeCard> <KnowledgeCard item={item}></KnowledgeCard>
</Col> </Col>
); );
})} })}
</Row> </Row> */}
<Flex gap="large" wrap="wrap">
{data.map((item: any) => {
return <KnowledgeCard item={item} key={item.name}></KnowledgeCard>;
})}
</Flex>
</div> </div>
); );
}; };

View File

@ -26,7 +26,7 @@
border: 1px solid rgba(234, 236, 240, 1); border: 1px solid rgba(234, 236, 240, 1);
box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05); box-shadow: 0px 1px 2px 0px rgba(16, 24, 40, 0.05);
padding: 24px; padding: 24px;
min-width: 300px; max-width: 300px;
cursor: pointer; cursor: pointer;
.titleWrapper { .titleWrapper {

View File

@ -16,8 +16,37 @@ const routes = [
component: '@/pages/knowledge', component: '@/pages/knowledge',
}, },
{ {
path: '/knowledge/:module', path: '/knowledge',
component: '@/pages/add-knowledge', component: '@/pages/add-knowledge',
routes: [
{
path: '/knowledge/dataset',
component: '@/pages/add-knowledge/components/knowledge-dataset',
routes: [
{
path: '/knowledge/dataset',
component: '@/pages/add-knowledge/components/knowledge-file',
},
{
path: '/knowledge/dataset/upload',
component:
'@/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file',
},
{
path: '/knowledge/dataset/chunk',
component: '@/pages/add-knowledge/components/knowledge-chunk',
},
],
},
{
path: '/knowledge/configuration',
component: '@/pages/add-knowledge/components/knowledge-setting',
},
{
path: '/knowledge/testing',
component: '@/pages/add-knowledge/components/knowledge-search',
},
],
}, },
{ {
path: '/chat', path: '/chat',

View File

@ -20,6 +20,7 @@ const {
switch_chunk, switch_chunk,
rm_chunk, rm_chunk,
retrieval_test, retrieval_test,
document_rename,
} = api; } = api;
const methods = { const methods = {
@ -57,6 +58,10 @@ const methods = {
url: document_rm, url: document_rm,
method: 'post', method: 'post',
}, },
document_rename: {
url: document_rename,
method: 'post',
},
document_create: { document_create: {
url: document_create, url: document_create,
method: 'post', method: 'post',

View File

@ -33,11 +33,12 @@ export default {
rm_chunk: `${api_host}/chunk/rm`, rm_chunk: `${api_host}/chunk/rm`,
retrieval_test: `${api_host}/chunk/retrieval_test`, retrieval_test: `${api_host}/chunk/retrieval_test`,
// 上传 // 文件管理
upload: `${api_host}/document/upload`, upload: `${api_host}/document/upload`,
get_document_list: `${api_host}/document/list`, get_document_list: `${api_host}/document/list`,
document_change_status: `${api_host}/document/change_status`, document_change_status: `${api_host}/document/change_status`,
document_rm: `${api_host}/document/rm`, document_rm: `${api_host}/document/rm`,
document_rename: `${api_host}/document/rename`,
document_create: `${api_host}/document/create`, document_create: `${api_host}/document/create`,
document_change_parser: `${api_host}/document/change_parser`, document_change_parser: `${api_host}/document/change_parser`,
}; };

4
web/typings.d.ts vendored
View File

@ -1,2 +1,4 @@
import 'umi/typings'; import 'umi/typings';
declare module 'lodash' declare module 'lodash';
export type Nullable<T> = T | null;