From eb381963b3eb2ce81fc21c960a8ea8f0d4703d2f Mon Sep 17 00:00:00 2001 From: balibabu <cike8899@users.noreply.github.com> Date: Mon, 5 Feb 2024 19:26:03 +0800 Subject: [PATCH] feat: confirm before deleting knowledge base and add ChunkToolBar (#56) * feat: confirm before deleting knowledge base * feat: add ChunkToolBar --- web/src/components/modal-manager.tsx | 11 +- web/src/hooks/storeHooks.ts | 2 +- .../components/chunk-toolbar/index.tsx | 106 ++++++++++++++++++ .../components/knowledge-chunk/index.tsx | 4 +- .../knowledge-upload-file/index.tsx | 27 ++++- .../components/knowledge-file/index.tsx | 30 +++-- .../components/knowledge-file/model.ts | 12 +- .../parsing-status-cell/index.tsx | 3 +- .../components/knowledge-setting/index.tsx | 3 +- .../components/knowledge-setting/model.ts | 2 +- web/src/pages/knowledge/index.tsx | 56 +++++---- .../pages/knowledge/knowledge-card/index.tsx | 48 ++++---- .../knowledge-creating-modal/index.tsx | 81 +++++++++++++ web/src/services/kbService.ts | 5 + web/src/utils/api.ts | 1 + web/src/utils/{stroreUtil.ts => storeUtil.ts} | 0 16 files changed, 311 insertions(+), 80 deletions(-) create mode 100644 web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-toolbar/index.tsx create mode 100644 web/src/pages/knowledge/knowledge-creating-modal/index.tsx rename web/src/utils/{stroreUtil.ts => storeUtil.ts} (100%) diff --git a/web/src/components/modal-manager.tsx b/web/src/components/modal-manager.tsx index 14aec07..a3d16d1 100644 --- a/web/src/components/modal-manager.tsx +++ b/web/src/components/modal-manager.tsx @@ -1,11 +1,12 @@ import { useState } from 'react'; +export interface IModalManagerChildrenProps { + showModal(): void; + hideModal(): void; + visible: boolean; +} interface IProps { - children: (props: { - showModal(): void; - hideModal(): void; - visible: boolean; - }) => React.ReactNode; + children: (props: IModalManagerChildrenProps) => React.ReactNode; } const ModalManager = ({ children }: IProps) => { diff --git a/web/src/hooks/storeHooks.ts b/web/src/hooks/storeHooks.ts index 3f69ce7..6670bea 100644 --- a/web/src/hooks/storeHooks.ts +++ b/web/src/hooks/storeHooks.ts @@ -1,4 +1,4 @@ -import { getOneNamespaceEffectsLoading } from '@/utils/stroreUtil'; +import { getOneNamespaceEffectsLoading } from '@/utils/storeUtil'; import { useSelector } from 'umi'; // Get the loading status of given effects under a certain namespace diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-toolbar/index.tsx b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-toolbar/index.tsx new file mode 100644 index 0000000..d812c8e --- /dev/null +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/components/chunk-toolbar/index.tsx @@ -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; diff --git a/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx b/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx index b997ccb..d303a71 100644 --- a/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-chunk/index.tsx @@ -1,5 +1,5 @@ import { api_host } from '@/utils/api'; -import { getOneNamespaceEffectsLoading } from '@/utils/stroreUtil'; +import { getOneNamespaceEffectsLoading } from '@/utils/storeUtil'; import { DeleteOutlined, MinusSquareOutlined } from '@ant-design/icons'; import type { PaginationProps } from 'antd'; import { @@ -19,6 +19,7 @@ import React, { useCallback, useEffect, useState } from 'react'; import { useDispatch, useSearchParams, useSelector } from 'umi'; import CreateModal from './components/createModal'; +import ChunkToolBar from './components/chunk-toolbar'; import styles from './index.less'; interface PayloadType { @@ -126,6 +127,7 @@ const Chunk = () => { return ( <> <div className={styles.chunkPage}> + <ChunkToolBar></ChunkToolBar> <div className={styles.filter}> <div> <Input diff --git a/web/src/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file/index.tsx b/web/src/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file/index.tsx index acb6bb5..d6a0da6 100644 --- a/web/src/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-dataset/knowledge-upload-file/index.tsx @@ -28,7 +28,7 @@ import { UploadProps, } from 'antd'; import classNames from 'classnames'; -import { ReactElement, useEffect, useState } from 'react'; +import { ReactElement, useEffect, useRef, useState } from 'react'; import { Nullable } from 'typings'; import { Link, useDispatch, useNavigate, useSelector } from 'umi'; @@ -72,11 +72,15 @@ const UploaderItem = ({ const content = ( <Radio.Group onChange={onChange} value={value}> <Space direction="vertical"> - {parserArray.map((x) => ( - <Radio value={x} key={x}> - {x} - </Radio> - ))} + {parserArray.map( + ( + x, // value is lowercase, key is uppercase + ) => ( + <Radio value={x.toLowerCase()} key={x}> + {x} + </Radio> + ), + )} </Space> </Radio.Group> ); @@ -147,6 +151,7 @@ const KnowledgeUploadFile = () => { (state: any) => state.settingModel.tenantIfo, ); const navigate = useNavigate(); + const fileListRef = useRef<UploadFile[]>([]); const parserArray = tenantIfo?.parser_ids.split(',') ?? []; @@ -168,6 +173,7 @@ const KnowledgeUploadFile = () => { name: 'file', multiple: true, itemRender(originNode, file, fileList, actions) { + fileListRef.current = fileList; return ( <UploaderItem 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 = () => { if (!isUpload) { + runSelectedDocument(); navigate(`/knowledge/${KnowledgeRouteKey.Dataset}?id=${knowledgeBaseId}`); } else { setIsUpload(false); diff --git a/web/src/pages/add-knowledge/components/knowledge-file/index.tsx b/web/src/pages/add-knowledge/components/knowledge-file/index.tsx index 416a518..6d8c784 100644 --- a/web/src/pages/add-knowledge/components/knowledge-file/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-file/index.tsx @@ -5,8 +5,13 @@ import { } from '@/hooks/knowledgeHook'; import { Pagination } from '@/interfaces/common'; import { IKnowledgeFile } from '@/interfaces/database/knowledge'; -import { getOneNamespaceEffectsLoading } from '@/utils/stroreUtil'; -import { PlusOutlined, SearchOutlined } from '@ant-design/icons'; +import { getOneNamespaceEffectsLoading } from '@/utils/storeUtil'; +import { + FileOutlined, + FileTextOutlined, + PlusOutlined, + SearchOutlined, +} from '@ant-design/icons'; import type { MenuProps } from 'antd'; import { Button, @@ -21,14 +26,13 @@ import { import type { ColumnsType } from 'antd/es/table'; import { PaginationProps } from 'antd/lib'; 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 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 UploadFile from './upload'; const KnowledgeFile = () => { const dispatch = useDispatch(); @@ -155,24 +159,32 @@ const KnowledgeFile = () => { key: '1', label: ( <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> ), }, + { type: 'divider' }, { key: '2', label: ( <div> <Button type="link" onClick={showCEFModal}> - {' '} - 导入虚拟文件 + <FileOutlined /> + Create empty file </Button> </div> ), // disabled: true, }, ]; - }, [knowledgeBaseId]); + }, []); const chunkItems: MenuProps['items'] = [ { key: '1', @@ -191,7 +203,7 @@ const KnowledgeFile = () => { <div> <Button type="link" onClick={onRmDocument}> {' '} - ĺ 除 + Delete </Button> </div> ), diff --git a/web/src/pages/add-knowledge/components/knowledge-file/model.ts b/web/src/pages/add-knowledge/components/knowledge-file/model.ts index bc24bda..875665b 100644 --- a/web/src/pages/add-knowledge/components/knowledge-file/model.ts +++ b/web/src/pages/add-knowledge/components/knowledge-file/model.ts @@ -168,8 +168,8 @@ const model: DvaModel<KFModelState> = { return retcode; }, *document_create({ payload = {} }, { call, put }) { - const { data, response } = yield call(kbService.document_create, payload); - const { retcode, data: res, retmsg } = data; + const { data } = yield call(kbService.document_create, payload); + const { retcode, data: res } = data; if (retcode === 0) { put({ type: 'kFModel/updateState', @@ -181,6 +181,14 @@ const model: DvaModel<KFModelState> = { } 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 }) { const { data, response } = yield call( kbService.document_change_parser, diff --git a/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx b/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx index 55a450a..7c2784c 100644 --- a/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-file/parsing-status-cell/index.tsx @@ -55,7 +55,8 @@ export const ParsingStatusCell = ({ record }: IProps) => { {isRunning ? ( <Space> <Badge color={runningStatus.color} /> - `${runningStatus.label}${record.progress * 100}%` + {runningStatus.label} + <span>{record.progress * 100}%</span> </Space> ) : ( runningStatus.label diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/index.tsx b/web/src/pages/add-knowledge/components/knowledge-setting/index.tsx index 47dd4aa..1926acb 100644 --- a/web/src/pages/add-knowledge/components/knowledge-setting/index.tsx +++ b/web/src/pages/add-knowledge/components/knowledge-setting/index.tsx @@ -19,7 +19,8 @@ const KnowledgeSetting = () => { const settingModel = useSelector((state: any) => state.settingModel); let navigate = useNavigate(); 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 [selectedTag, setSelectedTag] = useState(''); const values = Form.useWatch([], form); diff --git a/web/src/pages/add-knowledge/components/knowledge-setting/model.ts b/web/src/pages/add-knowledge/components/knowledge-setting/model.ts index 1884771..0cd5488 100644 --- a/web/src/pages/add-knowledge/components/knowledge-setting/model.ts +++ b/web/src/pages/add-knowledge/components/knowledge-setting/model.ts @@ -35,7 +35,7 @@ const model: DvaModel<KSModelState> = { if (retcode === 0) { message.success('ĺ›ĺ»şçźĄčŻ†ĺş“ć功ďĽ'); } - return retcode; + return data; }, *updateKb({ payload = {} }, { call, put }) { const { data } = yield call(kbService.updateKb, payload); diff --git a/web/src/pages/knowledge/index.tsx b/web/src/pages/knowledge/index.tsx index d15cca1..97a8f42 100644 --- a/web/src/pages/knowledge/index.tsx +++ b/web/src/pages/knowledge/index.tsx @@ -1,11 +1,13 @@ 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 { Button, Flex, Space } from 'antd'; import { useCallback, useEffect } from 'react'; import { useDispatch, useNavigate, useSelector } from 'umi'; -import styles from './index.less'; import KnowledgeCard from './knowledge-card'; +import KnowledgeCreatingModal from './knowledge-creating-modal'; + +import styles from './index.less'; const Knowledge = () => { const dispatch = useDispatch(); @@ -20,9 +22,9 @@ const Knowledge = () => { }); }, []); - const handleAddKnowledge = () => { - navigate(`/knowledge/${KnowledgeRouteKey.Configuration}`); - }; + // const handleAddKnowledge = () => { + // navigate(`/knowledge/${KnowledgeRouteKey.Configuration}`); + // }; useEffect(() => { fetchList(); @@ -41,32 +43,28 @@ const Knowledge = () => { <Button icon={<FilterIcon />} className={styles.filterButton}> Filters </Button> - <Button - type="primary" - icon={<PlusOutlined />} - onClick={handleAddKnowledge} - className={styles.topButton} - > - Create knowledge base - </Button> + <ModalManager> + {({ visible, hideModal, showModal }) => ( + <> + <Button + type="primary" + icon={<PlusOutlined />} + onClick={() => { + showModal(); + }} + className={styles.topButton} + > + Create knowledge base + </Button> + <KnowledgeCreatingModal + visible={visible} + hideModal={hideModal} + ></KnowledgeCreatingModal> + </> + )} + </ModalManager> </Space> </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"> {data.map((item: any) => { return <KnowledgeCard item={item} key={item.name}></KnowledgeCard>; diff --git a/web/src/pages/knowledge/knowledge-card/index.tsx b/web/src/pages/knowledge/knowledge-card/index.tsx index 31aaa3c..07d1616 100644 --- a/web/src/pages/knowledge/knowledge-card/index.tsx +++ b/web/src/pages/knowledge/knowledge-card/index.tsx @@ -1,5 +1,6 @@ import { ReactComponent as MoreIcon } from '@/assets/svg/more.svg'; import { KnowledgeRouteKey } from '@/constants/knowledge'; +import { IKnowledge } from '@/interfaces/database/knowledge'; import { formatDate } from '@/utils/date'; import { CalendarOutlined, @@ -11,18 +12,19 @@ import { Avatar, Card, Dropdown, MenuProps, Space } from 'antd'; import { MouseEvent } from 'react'; import { useDispatch, useNavigate } from 'umi'; +import showDeleteConfirm from '@/components/deleting-confirm'; import styles from './index.less'; interface IProps { - item: any; + item: IKnowledge; } const KnowledgeCard = ({ item }: IProps) => { const navigate = useNavigate(); const dispatch = useDispatch(); - const handleDelete = (e: MouseEvent<HTMLButtonElement>) => { - e.stopPropagation(); + const handleDelete = () => { + showDeleteConfirm({ onOk: removeKnowledge }); }; const items: MenuProps['items'] = [ @@ -30,32 +32,34 @@ const KnowledgeCard = ({ item }: IProps) => { key: '1', label: ( <Space> - ĺ 除 - <DeleteOutlined onClick={handleDelete} /> + Delete + <DeleteOutlined /> </Space> ), }, ]; - const confirm = (id: string) => { - dispatch({ + const handleDropdownMenuClick: MenuProps['onClick'] = ({ domEvent, key }) => { + domEvent.preventDefault(); + domEvent.stopPropagation(); + if (key === '1') { + handleDelete(); + } + }; + + const removeKnowledge = () => { + return dispatch({ type: 'knowledgeModel/rmKb', payload: { - kb_id: id, + kb_id: item.id, }, }); }; - const handleCardClick = () => { + const handleCardClick = (e: MouseEvent<HTMLElement>) => { navigate(`/knowledge/${KnowledgeRouteKey.Dataset}?id=${item.id}`); }; - const onConfirmDelete = (e?: MouseEvent<HTMLElement>) => { - e?.stopPropagation(); - e?.nativeEvent.stopImmediatePropagation(); - confirm(item.id); - }; - return ( <Card className={styles.card} onClick={handleCardClick}> <div className={styles.container}> @@ -63,16 +67,12 @@ const KnowledgeCard = ({ item }: IProps) => { <Avatar size={34} icon={<UserOutlined />} /> <span className={styles.delete}> - {/* <Popconfirm - title="Delete the task" - description="Are you sure to delete this task?" - onConfirm={onConfirmDelete} - okText="Yes" - cancelText="No" + <Dropdown + menu={{ + items, + onClick: handleDropdownMenuClick, + }} > - <DeleteOutlined onClick={handleDelete} /> - </Popconfirm> */} - <Dropdown menu={{ items }}> <MoreIcon /> </Dropdown> </span> diff --git a/web/src/pages/knowledge/knowledge-creating-modal/index.tsx b/web/src/pages/knowledge/knowledge-creating-modal/index.tsx new file mode 100644 index 0000000..abb9c25 --- /dev/null +++ b/web/src/pages/knowledge/knowledge-creating-modal/index.tsx @@ -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; diff --git a/web/src/services/kbService.ts b/web/src/services/kbService.ts index d0d6c91..ca25bc4 100644 --- a/web/src/services/kbService.ts +++ b/web/src/services/kbService.ts @@ -21,6 +21,7 @@ const { rm_chunk, retrieval_test, document_rename, + document_run, } = api; const methods = { @@ -66,6 +67,10 @@ const methods = { url: document_create, method: 'post', }, + document_run: { + url: document_run, + method: 'post', + }, document_change_parser: { url: document_change_parser, method: 'post', diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts index fb5adcb..d7634ef 100644 --- a/web/src/utils/api.ts +++ b/web/src/utils/api.ts @@ -40,5 +40,6 @@ export default { document_rm: `${api_host}/document/rm`, document_rename: `${api_host}/document/rename`, document_create: `${api_host}/document/create`, + document_run: `${api_host}/document/run`, document_change_parser: `${api_host}/document/change_parser`, }; diff --git a/web/src/utils/stroreUtil.ts b/web/src/utils/storeUtil.ts similarity index 100% rename from web/src/utils/stroreUtil.ts rename to web/src/utils/storeUtil.ts -- GitLab