feat: confirm before deleting knowledge base and add ChunkToolBar (#56)

* feat: confirm before deleting knowledge base

* feat: add ChunkToolBar
This commit is contained in:
balibabu
2024-02-05 19:26:03 +08:00
committed by GitHub
parent 407b2523b6
commit eb381963b3
16 changed files with 311 additions and 80 deletions

View File

@ -1,11 +1,12 @@
import { useState } from 'react'; import { useState } from 'react';
export interface IModalManagerChildrenProps {
showModal(): void;
hideModal(): void;
visible: boolean;
}
interface IProps { interface IProps {
children: (props: { children: (props: IModalManagerChildrenProps) => React.ReactNode;
showModal(): void;
hideModal(): void;
visible: boolean;
}) => React.ReactNode;
} }
const ModalManager = ({ children }: IProps) => { const ModalManager = ({ children }: IProps) => {

View File

@ -1,4 +1,4 @@
import { getOneNamespaceEffectsLoading } from '@/utils/stroreUtil'; import { getOneNamespaceEffectsLoading } from '@/utils/storeUtil';
import { useSelector } from 'umi'; import { useSelector } from 'umi';
// Get the loading status of given effects under a certain namespace // Get the loading status of given effects under a certain namespace

View File

@ -0,0 +1,106 @@
import { ReactComponent as FilterIcon } from '@/assets/filter.svg';
import {
ArrowLeftOutlined,
CheckCircleOutlined,
CloseCircleOutlined,
DeleteOutlined,
DownOutlined,
FilePdfOutlined,
PlusOutlined,
SearchOutlined,
} from '@ant-design/icons';
import { Button, Checkbox, Flex, Menu, MenuProps, Popover, Space } from 'antd';
import { useMemo } from 'react';
const ChunkToolBar = () => {
const items: MenuProps['items'] = useMemo(() => {
return [
{
key: '1',
label: (
<>
<Checkbox>
<b>Select All</b>
</Checkbox>
</>
),
},
{ type: 'divider' },
{
key: '2',
label: (
<Space>
<CheckCircleOutlined />
<b>Enabled Selected</b>
</Space>
),
},
{
key: '3',
label: (
<Space>
<CloseCircleOutlined />
<b>Disabled Selected</b>
</Space>
),
},
{ type: 'divider' },
{
key: '4',
label: (
<Space>
<DeleteOutlined />
<b>Delete Selected</b>
</Space>
),
},
];
}, []);
const content = (
<Menu style={{ width: 200 }} items={items} selectable={false} />
);
return (
<Flex justify="space-between" align="center">
<Space>
<ArrowLeftOutlined />
<FilePdfOutlined />
xxx.pdf
</Space>
<Space>
{/* <Select
defaultValue="lucy"
style={{ width: 100 }}
popupMatchSelectWidth={false}
optionRender={() => null}
dropdownRender={(menu) => (
<div style={{ width: 300 }}>
{menu}
<Menu
// onClick={onClick}
style={{ width: 256 }}
// defaultSelectedKeys={['1']}
// defaultOpenKeys={['sub1']}
// mode="inline"
items={actionItems}
/>
</div>
)}
></Select> */}
<Popover content={content} placement="bottomLeft" arrow={false}>
<Button>
Bulk
<DownOutlined />
</Button>
</Popover>
<Button icon={<SearchOutlined />} />
<Button icon={<FilterIcon />} />
<Button icon={<DeleteOutlined />} />
<Button icon={<PlusOutlined />} type="primary" />
</Space>
</Flex>
);
};
export default ChunkToolBar;

View File

@ -1,5 +1,5 @@
import { api_host } from '@/utils/api'; import { api_host } from '@/utils/api';
import { getOneNamespaceEffectsLoading } from '@/utils/stroreUtil'; import { getOneNamespaceEffectsLoading } from '@/utils/storeUtil';
import { DeleteOutlined, MinusSquareOutlined } from '@ant-design/icons'; import { DeleteOutlined, MinusSquareOutlined } from '@ant-design/icons';
import type { PaginationProps } from 'antd'; import type { PaginationProps } from 'antd';
import { import {
@ -19,6 +19,7 @@ import React, { useCallback, useEffect, useState } from 'react';
import { useDispatch, useSearchParams, useSelector } from 'umi'; import { useDispatch, useSearchParams, useSelector } from 'umi';
import CreateModal from './components/createModal'; import CreateModal from './components/createModal';
import ChunkToolBar from './components/chunk-toolbar';
import styles from './index.less'; import styles from './index.less';
interface PayloadType { interface PayloadType {
@ -126,6 +127,7 @@ const Chunk = () => {
return ( return (
<> <>
<div className={styles.chunkPage}> <div className={styles.chunkPage}>
<ChunkToolBar></ChunkToolBar>
<div className={styles.filter}> <div className={styles.filter}>
<div> <div>
<Input <Input

View File

@ -28,7 +28,7 @@ import {
UploadProps, UploadProps,
} from 'antd'; } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { ReactElement, useEffect, useState } from 'react'; import { ReactElement, useEffect, useRef, useState } from 'react';
import { Nullable } from 'typings'; import { Nullable } from 'typings';
import { Link, useDispatch, useNavigate, useSelector } from 'umi'; import { Link, useDispatch, useNavigate, useSelector } from 'umi';
@ -72,11 +72,15 @@ const UploaderItem = ({
const content = ( const content = (
<Radio.Group onChange={onChange} value={value}> <Radio.Group onChange={onChange} value={value}>
<Space direction="vertical"> <Space direction="vertical">
{parserArray.map((x) => ( {parserArray.map(
<Radio value={x} key={x}> (
{x} x, // value is lowercase, key is uppercase
</Radio> ) => (
))} <Radio value={x.toLowerCase()} key={x}>
{x}
</Radio>
),
)}
</Space> </Space>
</Radio.Group> </Radio.Group>
); );
@ -147,6 +151,7 @@ const KnowledgeUploadFile = () => {
(state: any) => state.settingModel.tenantIfo, (state: any) => state.settingModel.tenantIfo,
); );
const navigate = useNavigate(); const navigate = useNavigate();
const fileListRef = useRef<UploadFile[]>([]);
const parserArray = tenantIfo?.parser_ids.split(',') ?? []; const parserArray = tenantIfo?.parser_ids.split(',') ?? [];
@ -168,6 +173,7 @@ const KnowledgeUploadFile = () => {
name: 'file', name: 'file',
multiple: true, multiple: true,
itemRender(originNode, file, fileList, actions) { itemRender(originNode, file, fileList, actions) {
fileListRef.current = fileList;
return ( return (
<UploaderItem <UploaderItem
isUpload={isUpload} isUpload={isUpload}
@ -185,8 +191,17 @@ const KnowledgeUploadFile = () => {
}, },
}; };
const runSelectedDocument = () => {
const ids = fileListRef.current.map((x) => x.response.id);
dispatch({
type: 'kFModel/document_run',
payload: { doc_ids: ids, run: 1 },
});
};
const handleNextClick = () => { const handleNextClick = () => {
if (!isUpload) { if (!isUpload) {
runSelectedDocument();
navigate(`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeBaseId}`); navigate(`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeBaseId}`);
} else { } else {
setIsUpload(false); setIsUpload(false);

View File

@ -5,8 +5,13 @@ import {
} from '@/hooks/knowledgeHook'; } from '@/hooks/knowledgeHook';
import { Pagination } from '@/interfaces/common'; import { Pagination } from '@/interfaces/common';
import { IKnowledgeFile } from '@/interfaces/database/knowledge'; import { IKnowledgeFile } from '@/interfaces/database/knowledge';
import { getOneNamespaceEffectsLoading } from '@/utils/stroreUtil'; import { getOneNamespaceEffectsLoading } from '@/utils/storeUtil';
import { PlusOutlined, SearchOutlined } from '@ant-design/icons'; import {
FileOutlined,
FileTextOutlined,
PlusOutlined,
SearchOutlined,
} from '@ant-design/icons';
import type { MenuProps } from 'antd'; import type { MenuProps } from 'antd';
import { import {
Button, Button,
@ -21,14 +26,13 @@ import {
import type { ColumnsType } from 'antd/es/table'; import type { ColumnsType } from 'antd/es/table';
import { PaginationProps } from 'antd/lib'; import { PaginationProps } from 'antd/lib';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { useDispatch, useNavigate, useSelector } from 'umi'; import { Link, 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 ParsingActionCell from './parsing-action-cell';
import ParsingStatusCell from './parsing-status-cell'; import ParsingStatusCell from './parsing-status-cell';
import RenameModal from './rename-modal'; import RenameModal from './rename-modal';
import SegmentSetModal from './segmentSetModal'; import SegmentSetModal from './segmentSetModal';
import UploadFile from './upload';
const KnowledgeFile = () => { const KnowledgeFile = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -155,24 +159,32 @@ const KnowledgeFile = () => {
key: '1', key: '1',
label: ( label: (
<div> <div>
<UploadFile kb_id={knowledgeBaseId} getKfList={getKfList} /> <Button type="link">
<Link to={`/knowledge/dataset/upload?id=${knowledgeBaseId}`}>
<Space>
<FileTextOutlined />
Local files
</Space>
</Link>
</Button>
</div> </div>
), ),
}, },
{ type: 'divider' },
{ {
key: '2', key: '2',
label: ( label: (
<div> <div>
<Button type="link" onClick={showCEFModal}> <Button type="link" onClick={showCEFModal}>
{' '} <FileOutlined />
Create empty file
</Button> </Button>
</div> </div>
), ),
// disabled: true, // disabled: true,
}, },
]; ];
}, [knowledgeBaseId]); }, []);
const chunkItems: MenuProps['items'] = [ const chunkItems: MenuProps['items'] = [
{ {
key: '1', key: '1',
@ -191,7 +203,7 @@ const KnowledgeFile = () => {
<div> <div>
<Button type="link" onClick={onRmDocument}> <Button type="link" onClick={onRmDocument}>
{' '} {' '}
Delete
</Button> </Button>
</div> </div>
), ),

View File

@ -168,8 +168,8 @@ const model: DvaModel<KFModelState> = {
return retcode; return retcode;
}, },
*document_create({ payload = {} }, { call, put }) { *document_create({ payload = {} }, { call, put }) {
const { data, response } = yield call(kbService.document_create, payload); const { data } = yield call(kbService.document_create, payload);
const { retcode, data: res, retmsg } = data; const { retcode, data: res } = data;
if (retcode === 0) { if (retcode === 0) {
put({ put({
type: 'kFModel/updateState', type: 'kFModel/updateState',
@ -181,6 +181,14 @@ const model: DvaModel<KFModelState> = {
} }
return retcode; return retcode;
}, },
*document_run({ payload = {} }, { call, put }) {
const { data } = yield call(kbService.document_run, payload);
const { retcode } = data;
if (retcode === 0) {
message.success('Run successfully ');
}
return retcode;
},
*document_change_parser({ payload = {} }, { call, put }) { *document_change_parser({ payload = {} }, { call, put }) {
const { data, response } = yield call( const { data, response } = yield call(
kbService.document_change_parser, kbService.document_change_parser,

View File

@ -55,7 +55,8 @@ export const ParsingStatusCell = ({ record }: IProps) => {
{isRunning ? ( {isRunning ? (
<Space> <Space>
<Badge color={runningStatus.color} /> <Badge color={runningStatus.color} />
`${runningStatus.label}${record.progress * 100}%` {runningStatus.label}
<span>{record.progress * 100}%</span>
</Space> </Space>
) : ( ) : (
runningStatus.label runningStatus.label

View File

@ -19,7 +19,8 @@ const KnowledgeSetting = () => {
const settingModel = useSelector((state: any) => state.settingModel); const settingModel = useSelector((state: any) => state.settingModel);
let navigate = useNavigate(); let navigate = useNavigate();
const { tenantIfo = {} } = settingModel; const { tenantIfo = {} } = settingModel;
const { parser_ids = '', embd_id = '' } = tenantIfo; const parser_ids = tenantIfo?.parser_ids ?? '';
const embd_id = tenantIfo?.embd_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);

View File

@ -35,7 +35,7 @@ const model: DvaModel<KSModelState> = {
if (retcode === 0) { if (retcode === 0) {
message.success('创建知识库成功!'); message.success('创建知识库成功!');
} }
return retcode; return data;
}, },
*updateKb({ payload = {} }, { call, put }) { *updateKb({ payload = {} }, { call, put }) {
const { data } = yield call(kbService.updateKb, payload); const { data } = yield call(kbService.updateKb, payload);

View File

@ -1,11 +1,13 @@
import { ReactComponent as FilterIcon } from '@/assets/filter.svg'; import { ReactComponent as FilterIcon } from '@/assets/filter.svg';
import { KnowledgeRouteKey } from '@/constants/knowledge'; import ModalManager from '@/components/modal-manager';
import { PlusOutlined } from '@ant-design/icons'; import { PlusOutlined } from '@ant-design/icons';
import { Button, Flex, 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 KnowledgeCard from './knowledge-card'; import KnowledgeCard from './knowledge-card';
import KnowledgeCreatingModal from './knowledge-creating-modal';
import styles from './index.less';
const Knowledge = () => { const Knowledge = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -20,9 +22,9 @@ const Knowledge = () => {
}); });
}, []); }, []);
const handleAddKnowledge = () => { // const handleAddKnowledge = () => {
navigate(`/knowledge/${KnowledgeRouteKey.Configuration}`); // navigate(`/knowledge/${KnowledgeRouteKey.Configuration}`);
}; // };
useEffect(() => { useEffect(() => {
fetchList(); fetchList();
@ -41,32 +43,28 @@ const Knowledge = () => {
<Button icon={<FilterIcon />} className={styles.filterButton}> <Button icon={<FilterIcon />} className={styles.filterButton}>
Filters Filters
</Button> </Button>
<Button <ModalManager>
type="primary" {({ visible, hideModal, showModal }) => (
icon={<PlusOutlined />} <>
onClick={handleAddKnowledge} <Button
className={styles.topButton} type="primary"
> icon={<PlusOutlined />}
Create knowledge base onClick={() => {
</Button> showModal();
}}
className={styles.topButton}
>
Create knowledge base
</Button>
<KnowledgeCreatingModal
visible={visible}
hideModal={hideModal}
></KnowledgeCreatingModal>
</>
)}
</ModalManager>
</Space> </Space>
</div> </div>
{/* <Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }}>
{data.map((item: any) => {
return (
<Col
className="gutter-row"
key={item.name}
xs={24}
sm={12}
md={10}
lg={8}
>
<KnowledgeCard item={item}></KnowledgeCard>
</Col>
);
})}
</Row> */}
<Flex gap="large" wrap="wrap"> <Flex gap="large" wrap="wrap">
{data.map((item: any) => { {data.map((item: any) => {
return <KnowledgeCard item={item} key={item.name}></KnowledgeCard>; return <KnowledgeCard item={item} key={item.name}></KnowledgeCard>;

View File

@ -1,5 +1,6 @@
import { ReactComponent as MoreIcon } from '@/assets/svg/more.svg'; import { ReactComponent as MoreIcon } from '@/assets/svg/more.svg';
import { KnowledgeRouteKey } from '@/constants/knowledge'; import { KnowledgeRouteKey } from '@/constants/knowledge';
import { IKnowledge } from '@/interfaces/database/knowledge';
import { formatDate } from '@/utils/date'; import { formatDate } from '@/utils/date';
import { import {
CalendarOutlined, CalendarOutlined,
@ -11,18 +12,19 @@ import { Avatar, Card, Dropdown, MenuProps, Space } from 'antd';
import { MouseEvent } from 'react'; import { MouseEvent } from 'react';
import { useDispatch, useNavigate } from 'umi'; import { useDispatch, useNavigate } from 'umi';
import showDeleteConfirm from '@/components/deleting-confirm';
import styles from './index.less'; import styles from './index.less';
interface IProps { interface IProps {
item: any; item: IKnowledge;
} }
const KnowledgeCard = ({ item }: IProps) => { const KnowledgeCard = ({ item }: IProps) => {
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useDispatch(); const dispatch = useDispatch();
const handleDelete = (e: MouseEvent<HTMLButtonElement>) => { const handleDelete = () => {
e.stopPropagation(); showDeleteConfirm({ onOk: removeKnowledge });
}; };
const items: MenuProps['items'] = [ const items: MenuProps['items'] = [
@ -30,32 +32,34 @@ const KnowledgeCard = ({ item }: IProps) => {
key: '1', key: '1',
label: ( label: (
<Space> <Space>
Delete
<DeleteOutlined onClick={handleDelete} /> <DeleteOutlined />
</Space> </Space>
), ),
}, },
]; ];
const confirm = (id: string) => { const handleDropdownMenuClick: MenuProps['onClick'] = ({ domEvent, key }) => {
dispatch({ domEvent.preventDefault();
domEvent.stopPropagation();
if (key === '1') {
handleDelete();
}
};
const removeKnowledge = () => {
return dispatch({
type: 'knowledgeModel/rmKb', type: 'knowledgeModel/rmKb',
payload: { payload: {
kb_id: id, kb_id: item.id,
}, },
}); });
}; };
const handleCardClick = () => { const handleCardClick = (e: MouseEvent<HTMLElement>) => {
navigate(`/knowledge/${KnowledgeRouteKey.Dataset}?id=${item.id}`); navigate(`/knowledge/${KnowledgeRouteKey.Dataset}?id=${item.id}`);
}; };
const onConfirmDelete = (e?: MouseEvent<HTMLElement>) => {
e?.stopPropagation();
e?.nativeEvent.stopImmediatePropagation();
confirm(item.id);
};
return ( return (
<Card className={styles.card} onClick={handleCardClick}> <Card className={styles.card} onClick={handleCardClick}>
<div className={styles.container}> <div className={styles.container}>
@ -63,16 +67,12 @@ const KnowledgeCard = ({ item }: IProps) => {
<Avatar size={34} icon={<UserOutlined />} /> <Avatar size={34} icon={<UserOutlined />} />
<span className={styles.delete}> <span className={styles.delete}>
{/* <Popconfirm <Dropdown
title="Delete the task" menu={{
description="Are you sure to delete this task?" items,
onConfirm={onConfirmDelete} onClick: handleDropdownMenuClick,
okText="Yes" }}
cancelText="No"
> >
<DeleteOutlined onClick={handleDelete} />
</Popconfirm> */}
<Dropdown menu={{ items }}>
<MoreIcon /> <MoreIcon />
</Dropdown> </Dropdown>
</span> </span>

View File

@ -0,0 +1,81 @@
import { IModalManagerChildrenProps } from '@/components/modal-manager';
import { KnowledgeRouteKey } from '@/constants/knowledge';
import { Form, Input, Modal } from 'antd';
import { useDispatch, useNavigate, useSelector } from 'umi';
type FieldType = {
name?: string;
};
const KnowledgeCreatingModal = ({
visible,
hideModal,
}: Omit<IModalManagerChildrenProps, 'showModal'>) => {
const [form] = Form.useForm();
const dispatch = useDispatch();
const loading = useSelector(
(state: any) => state.loading.effects['kSModel/createKb'],
);
const navigate = useNavigate();
const handleOk = async () => {
const ret = await form.validateFields();
const data = await dispatch<any>({
type: 'kSModel/createKb',
payload: {
name: ret.name,
},
});
if (data.retcode === 0) {
navigate(
`/knowledge/${KnowledgeRouteKey.Configuration}?id=${data.data.kb_id}`,
);
hideModal();
}
};
const handleCancel = () => {
hideModal();
};
const onFinish = (values: any) => {
console.log('Success:', values);
};
const onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
return (
<Modal
title="Create knowledge base"
open={visible}
onOk={handleOk}
onCancel={handleCancel}
okButtonProps={{ loading }}
>
<Form
name="Create"
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 KnowledgeCreatingModal;

View File

@ -21,6 +21,7 @@ const {
rm_chunk, rm_chunk,
retrieval_test, retrieval_test,
document_rename, document_rename,
document_run,
} = api; } = api;
const methods = { const methods = {
@ -66,6 +67,10 @@ const methods = {
url: document_create, url: document_create,
method: 'post', method: 'post',
}, },
document_run: {
url: document_run,
method: 'post',
},
document_change_parser: { document_change_parser: {
url: document_change_parser, url: document_change_parser,
method: 'post', method: 'post',

View File

@ -40,5 +40,6 @@ export default {
document_rm: `${api_host}/document/rm`, document_rm: `${api_host}/document/rm`,
document_rename: `${api_host}/document/rename`, document_rename: `${api_host}/document/rename`,
document_create: `${api_host}/document/create`, document_create: `${api_host}/document/create`,
document_run: `${api_host}/document/run`,
document_change_parser: `${api_host}/document/change_parser`, document_change_parser: `${api_host}/document/change_parser`,
}; };