* feat: confirm before deleting knowledge base * feat: add ChunkToolBartags/v0.1.0
| @@ -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) => { | |||
| @@ -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 | |||
| @@ -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; | |||
| @@ -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 | |||
| @@ -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); | |||
| @@ -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> | |||
| ), | |||
| @@ -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, | |||
| @@ -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 | |||
| @@ -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); | |||
| @@ -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); | |||
| @@ -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>; | |||
| @@ -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> | |||
| @@ -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; | |||
| @@ -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', | |||
| @@ -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`, | |||
| }; | |||