### What problem does this PR solve? #627 fix: filter knowledge list by keywords fix: clear the selected file list after the file is uploaded successfully feat: add ellipsis pattern to chunk list ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)tags/v0.5.0
| visible, | visible, | ||||
| documentExtension, | documentExtension, | ||||
| parserConfig, | parserConfig, | ||||
| loading, | |||||
| }) => { | }) => { | ||||
| const { parserList, handleChange, selectedTag } = useFetchParserListOnMount( | const { parserList, handleChange, selectedTag } = useFetchParserListOnMount( | ||||
| documentId, | documentId, | ||||
| onOk={handleOk} | onOk={handleOk} | ||||
| onCancel={hideModal} | onCancel={hideModal} | ||||
| afterClose={afterClose} | afterClose={afterClose} | ||||
| confirmLoading={loading} | |||||
| > | > | ||||
| <Space size={[0, 8]} wrap> | <Space size={[0, 8]} wrap> | ||||
| <Form.Item label={t('chunkMethod')} className={styles.chunkMethod}> | <Form.Item label={t('chunkMethod')} className={styles.chunkMethod}> |
| const [fileList, setFileList] = useState<UploadFile[]>([]); | const [fileList, setFileList] = useState<UploadFile[]>([]); | ||||
| const [directoryFileList, setDirectoryFileList] = useState<UploadFile[]>([]); | const [directoryFileList, setDirectoryFileList] = useState<UploadFile[]>([]); | ||||
| const clearFileList = () => { | |||||
| setFileList([]); | |||||
| setDirectoryFileList([]); | |||||
| }; | |||||
| const onOk = async () => { | const onOk = async () => { | ||||
| const ret = await onFileUploadOk?.([...fileList, ...directoryFileList]); | const ret = await onFileUploadOk?.([...fileList, ...directoryFileList]); | ||||
| if (ret !== undefined && ret === 0) { | |||||
| setFileList([]); | |||||
| setDirectoryFileList([]); | |||||
| } | |||||
| return ret; | return ret; | ||||
| }; | }; | ||||
| const afterClose = () => { | |||||
| clearFileList(); | |||||
| }; | |||||
| const items: TabsProps['items'] = [ | const items: TabsProps['items'] = [ | ||||
| { | { | ||||
| key: '1', | key: '1', | ||||
| onOk={onOk} | onOk={onOk} | ||||
| onCancel={hideModal} | onCancel={hideModal} | ||||
| confirmLoading={loading} | confirmLoading={loading} | ||||
| afterClose={afterClose} | |||||
| > | > | ||||
| <Flex gap={'large'} vertical> | <Flex gap={'large'} vertical> | ||||
| <Segmented | <Segmented |
| }, [fetchKnowledgeBaseConfiguration]); | }, [fetchKnowledgeBaseConfiguration]); | ||||
| }; | }; | ||||
| export const useSelectKnowledgeList = () => { | |||||
| const knowledgeModel = useSelector((state) => state.knowledgeModel); | |||||
| const { data = [] } = knowledgeModel; | |||||
| return data; | |||||
| }; | |||||
| export const useFetchKnowledgeList = ( | export const useFetchKnowledgeList = ( | ||||
| shouldFilterListWithoutDocument: boolean = false, | shouldFilterListWithoutDocument: boolean = false, | ||||
| ) => { | ) => { | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const loading = useOneNamespaceEffectsLoading('knowledgeModel', ['getList']); | const loading = useOneNamespaceEffectsLoading('knowledgeModel', ['getList']); | ||||
| const knowledgeModel = useSelector((state: any) => state.knowledgeModel); | |||||
| const knowledgeModel = useSelector((state) => state.knowledgeModel); | |||||
| const { data = [] } = knowledgeModel; | const { data = [] } = knowledgeModel; | ||||
| const list: IKnowledge[] = useMemo(() => { | const list: IKnowledge[] = useMemo(() => { | ||||
| return shouldFilterListWithoutDocument | return shouldFilterListWithoutDocument |
| } | } | ||||
| } | } | ||||
| } | } | ||||
| .textEllipsis() { | |||||
| overflow: hidden; | |||||
| text-overflow: ellipsis; | |||||
| white-space: nowrap; | |||||
| } | |||||
| .multipleLineEllipsis(@line) { | |||||
| display: -webkit-box; | |||||
| -webkit-box-orient: vertical; | |||||
| -webkit-line-clamp: @line; | |||||
| overflow: hidden; | |||||
| text-overflow: ellipsis; | |||||
| } |
| name: 'Name', | name: 'Name', | ||||
| namePlaceholder: 'Please input name!', | namePlaceholder: 'Please input name!', | ||||
| doc: 'Docs', | doc: 'Docs', | ||||
| searchKnowledgePlaceholder: 'Search', | |||||
| }, | }, | ||||
| knowledgeDetails: { | knowledgeDetails: { | ||||
| dataset: 'Dataset', | dataset: 'Dataset', | ||||
| keyword: 'Keyword', | keyword: 'Keyword', | ||||
| function: 'Function', | function: 'Function', | ||||
| chunkMessage: 'Please input value!', | chunkMessage: 'Please input value!', | ||||
| full: 'Full text', | |||||
| ellipse: 'Ellipse', | |||||
| }, | }, | ||||
| chat: { | chat: { | ||||
| createAssistant: 'Create an Assistant', | createAssistant: 'Create an Assistant', |
| name: '名稱', | name: '名稱', | ||||
| namePlaceholder: '請輸入名稱', | namePlaceholder: '請輸入名稱', | ||||
| doc: '文件', | doc: '文件', | ||||
| searchKnowledgePlaceholder: '搜索', | |||||
| }, | }, | ||||
| knowledgeDetails: { | knowledgeDetails: { | ||||
| dataset: '數據集', | dataset: '數據集', | ||||
| keyword: '關鍵詞', | keyword: '關鍵詞', | ||||
| function: '函數', | function: '函數', | ||||
| chunkMessage: '請輸入值!', | chunkMessage: '請輸入值!', | ||||
| full: '全文', | |||||
| ellipse: '省略', | |||||
| }, | }, | ||||
| chat: { | chat: { | ||||
| createAssistant: '新建助理', | createAssistant: '新建助理', |
| name: '名称', | name: '名称', | ||||
| namePlaceholder: '请输入名称', | namePlaceholder: '请输入名称', | ||||
| doc: '文档', | doc: '文档', | ||||
| searchKnowledgePlaceholder: '搜索', | |||||
| }, | }, | ||||
| knowledgeDetails: { | knowledgeDetails: { | ||||
| dataset: '数据集', | dataset: '数据集', | ||||
| keyword: '关键词', | keyword: '关键词', | ||||
| function: '函数', | function: '函数', | ||||
| chunkMessage: '请输入值!', | chunkMessage: '请输入值!', | ||||
| full: '全文', | |||||
| ellipse: '省略', | |||||
| }, | }, | ||||
| chat: { | chat: { | ||||
| createAssistant: '新建助理', | createAssistant: '新建助理', |
| .chunkText; | .chunkText; | ||||
| } | } | ||||
| .contentEllipsis { | |||||
| .multipleLineEllipsis(3); | |||||
| } | |||||
| .chunkCard { | .chunkCard { | ||||
| width: 100%; | width: 100%; | ||||
| } | } |
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||
| import { useState } from 'react'; | import { useState } from 'react'; | ||||
| import { ChunkTextMode } from '../../constant'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| interface IProps { | interface IProps { | ||||
| handleCheckboxClick: (chunkId: string, checked: boolean) => void; | handleCheckboxClick: (chunkId: string, checked: boolean) => void; | ||||
| selected: boolean; | selected: boolean; | ||||
| clickChunkCard: (chunkId: string) => void; | clickChunkCard: (chunkId: string) => void; | ||||
| textMode: ChunkTextMode; | |||||
| } | } | ||||
| const ChunkCard = ({ | const ChunkCard = ({ | ||||
| switchChunk, | switchChunk, | ||||
| selected, | selected, | ||||
| clickChunkCard, | clickChunkCard, | ||||
| textMode, | |||||
| }: IProps) => { | }: IProps) => { | ||||
| const available = Number(item.available_int); | const available = Number(item.available_int); | ||||
| const [enabled, setEnabled] = useState(available === 1); | const [enabled, setEnabled] = useState(available === 1); | ||||
| onDoubleClick={handleContentDoubleClick} | onDoubleClick={handleContentDoubleClick} | ||||
| onClick={handleContentClick} | onClick={handleContentClick} | ||||
| className={styles.content} | className={styles.content} | ||||
| dangerouslySetInnerHTML={{ __html: item.content_with_weight }} | |||||
| ></section> | |||||
| > | |||||
| <div | |||||
| dangerouslySetInnerHTML={{ __html: item.content_with_weight }} | |||||
| className={classNames({ | |||||
| [styles.contentEllipsis]: textMode === ChunkTextMode.Ellipse, | |||||
| })} | |||||
| ></div> | |||||
| </section> | |||||
| <div> | <div> | ||||
| <Switch checked={enabled} onChange={onChange} /> | <Switch checked={enabled} onChange={onChange} /> | ||||
| </div> | </div> |
| Popover, | Popover, | ||||
| Radio, | Radio, | ||||
| RadioChangeEvent, | RadioChangeEvent, | ||||
| Segmented, | |||||
| SegmentedProps, | |||||
| Space, | Space, | ||||
| Typography, | |||||
| } from 'antd'; | } from 'antd'; | ||||
| import { ChangeEventHandler, useCallback, useMemo, useState } from 'react'; | import { ChangeEventHandler, useCallback, useMemo, useState } from 'react'; | ||||
| import { Link, useDispatch, useSelector } from 'umi'; | import { Link, useDispatch, useSelector } from 'umi'; | ||||
| import { ChunkTextMode } from '../../constant'; | |||||
| import { ChunkModelState } from '../../model'; | import { ChunkModelState } from '../../model'; | ||||
| const { Text } = Typography; | |||||
| interface IProps { | interface IProps { | ||||
| checked: boolean; | checked: boolean; | ||||
| getChunkList: () => void; | getChunkList: () => void; | ||||
| createChunk: () => void; | createChunk: () => void; | ||||
| removeChunk: () => void; | removeChunk: () => void; | ||||
| switchChunk: (available: number) => void; | switchChunk: (available: number) => void; | ||||
| changeChunkTextMode(mode: ChunkTextMode): void; | |||||
| } | } | ||||
| const ChunkToolBar = ({ | const ChunkToolBar = ({ | ||||
| createChunk, | createChunk, | ||||
| removeChunk, | removeChunk, | ||||
| switchChunk, | switchChunk, | ||||
| changeChunkTextMode, | |||||
| }: IProps) => { | }: IProps) => { | ||||
| const { documentInfo, available, searchString }: ChunkModelState = | const { documentInfo, available, searchString }: ChunkModelState = | ||||
| useSelector((state: any) => state.chunkModel); | useSelector((state: any) => state.chunkModel); | ||||
| <ArrowLeftOutlined /> | <ArrowLeftOutlined /> | ||||
| </Link> | </Link> | ||||
| <FilePdfOutlined /> | <FilePdfOutlined /> | ||||
| {documentInfo.name} | |||||
| <Text ellipsis={{ tooltip: documentInfo.name }} style={{ width: 150 }}> | |||||
| {documentInfo.name} | |||||
| </Text> | |||||
| </Space> | </Space> | ||||
| <Space> | <Space> | ||||
| <Segmented | |||||
| options={[ | |||||
| { label: t(ChunkTextMode.Full), value: ChunkTextMode.Full }, | |||||
| { label: t(ChunkTextMode.Ellipse), value: ChunkTextMode.Ellipse }, | |||||
| ]} | |||||
| onChange={changeChunkTextMode as SegmentedProps['onChange']} | |||||
| /> | |||||
| <Popover content={content} placement="bottom" arrow={false}> | <Popover content={content} placement="bottom" arrow={false}> | ||||
| <Button> | <Button> | ||||
| {t('bulk')} | {t('bulk')} |
| export enum ChunkTextMode { | |||||
| Full = 'full', | |||||
| Ellipse = 'ellipse', | |||||
| } |
| import { useCallback, useMemo, useState } from 'react'; | import { useCallback, useMemo, useState } from 'react'; | ||||
| import { IHighlight } from 'react-pdf-highlighter'; | import { IHighlight } from 'react-pdf-highlighter'; | ||||
| import { useSelector } from 'umi'; | import { useSelector } from 'umi'; | ||||
| import { ChunkTextMode } from './constant'; | |||||
| export const useSelectDocumentInfo = () => { | export const useSelectDocumentInfo = () => { | ||||
| const documentInfo: IKnowledgeFile = useSelector( | const documentInfo: IKnowledgeFile = useSelector( | ||||
| 'switch_chunk', | 'switch_chunk', | ||||
| ]); | ]); | ||||
| }; | }; | ||||
| // Switch chunk text to be fully displayed or ellipse | |||||
| export const useChangeChunkTextMode = () => { | |||||
| const [textMode, setTextMode] = useState<ChunkTextMode>(ChunkTextMode.Full); | |||||
| const changeChunkTextMode = useCallback((mode: ChunkTextMode) => { | |||||
| setTextMode(mode); | |||||
| }, []); | |||||
| return { textMode, changeChunkTextMode }; | |||||
| }; |
| import ChunkToolBar from './components/chunk-toolbar'; | import ChunkToolBar from './components/chunk-toolbar'; | ||||
| import DocumentPreview from './components/document-preview/preview'; | import DocumentPreview from './components/document-preview/preview'; | ||||
| import { | import { | ||||
| useChangeChunkTextMode, | |||||
| useHandleChunkCardClick, | useHandleChunkCardClick, | ||||
| useSelectChunkListLoading, | useSelectChunkListLoading, | ||||
| useSelectDocumentInfo, | useSelectDocumentInfo, | ||||
| const { handleChunkCardClick, selectedChunkId } = useHandleChunkCardClick(); | const { handleChunkCardClick, selectedChunkId } = useHandleChunkCardClick(); | ||||
| const isPdf = documentInfo.type === 'pdf'; | const isPdf = documentInfo.type === 'pdf'; | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const { changeChunkTextMode, textMode } = useChangeChunkTextMode(); | |||||
| const getChunkList = useFetchChunkList(); | const getChunkList = useFetchChunkList(); | ||||
| }, | }, | ||||
| [], | [], | ||||
| ); | ); | ||||
| const showSelectedChunkWarning = () => { | |||||
| const showSelectedChunkWarning = useCallback(() => { | |||||
| message.warning(t('message.pleaseSelectChunk')); | message.warning(t('message.pleaseSelectChunk')); | ||||
| }; | |||||
| }, [t]); | |||||
| const handleRemoveChunk = useCallback(async () => { | const handleRemoveChunk = useCallback(async () => { | ||||
| if (selectedChunkIds.length > 0) { | if (selectedChunkIds.length > 0) { | ||||
| } else { | } else { | ||||
| showSelectedChunkWarning(); | showSelectedChunkWarning(); | ||||
| } | } | ||||
| }, [selectedChunkIds, documentId, removeChunk]); | |||||
| }, [selectedChunkIds, documentId, removeChunk, showSelectedChunkWarning]); | |||||
| const switchChunk = useCallback( | const switchChunk = useCallback( | ||||
| async (available?: number, chunkIds?: string[]) => { | async (available?: number, chunkIds?: string[]) => { | ||||
| getChunkList(); | getChunkList(); | ||||
| } | } | ||||
| }, | }, | ||||
| [dispatch, documentId, getChunkList, selectedChunkIds], | |||||
| [ | |||||
| dispatch, | |||||
| documentId, | |||||
| getChunkList, | |||||
| selectedChunkIds, | |||||
| showSelectedChunkWarning, | |||||
| ], | |||||
| ); | ); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| removeChunk={handleRemoveChunk} | removeChunk={handleRemoveChunk} | ||||
| checked={selectedChunkIds.length === data.length} | checked={selectedChunkIds.length === data.length} | ||||
| switchChunk={switchChunk} | switchChunk={switchChunk} | ||||
| changeChunkTextMode={changeChunkTextMode} | |||||
| ></ChunkToolBar> | ></ChunkToolBar> | ||||
| <Divider></Divider> | <Divider></Divider> | ||||
| <Flex flex={1} gap={'middle'}> | <Flex flex={1} gap={'middle'}> | ||||
| switchChunk={switchChunk} | switchChunk={switchChunk} | ||||
| clickChunkCard={handleChunkCardClick} | clickChunkCard={handleChunkCardClick} | ||||
| selected={item.chunk_id === selectedChunkId} | selected={item.chunk_id === selectedChunkId} | ||||
| textMode={textMode} | |||||
| ></ChunkCard> | ></ChunkCard> | ||||
| ))} | ))} | ||||
| </Space> | </Space> |
| useFetchDocumentListOnMount, | useFetchDocumentListOnMount, | ||||
| useGetPagination, | useGetPagination, | ||||
| useHandleSearchChange, | useHandleSearchChange, | ||||
| useNavigateToOtherPage, | |||||
| } from './hooks'; | } from './hooks'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const { handleInputChange } = useHandleSearchChange(setPagination); | const { handleInputChange } = useHandleSearchChange(setPagination); | ||||
| const removeDocument = useRemoveDocument(); | const removeDocument = useRemoveDocument(); | ||||
| const showDeleteConfirm = useShowDeleteConfirm(); | const showDeleteConfirm = useShowDeleteConfirm(); | ||||
| const { linkToUploadPage } = useNavigateToOtherPage(); | |||||
| const runDocumentByIds = useRunDocument(); | const runDocumentByIds = useRunDocument(); | ||||
| const { knowledgeId } = useGetKnowledgeSearchParams(); | const { knowledgeId } = useGetKnowledgeSearchParams(); | ||||
| const changeStatus = useSetDocumentStatus(); | const changeStatus = useSetDocumentStatus(); | ||||
| </Button> | </Button> | ||||
| </div> | </div> | ||||
| ), | ), | ||||
| // disabled: true, | |||||
| }, | }, | ||||
| ]; | ]; | ||||
| }, [showDocumentUploadModal, showCreateModal, t]); | }, [showDocumentUploadModal, showCreateModal, t]); |
| .datasetWrapper { | .datasetWrapper { | ||||
| padding: 30px; | |||||
| flex: 1; | |||||
| padding: 30px 30px 0; | |||||
| height: 100%; | |||||
| } | |||||
| .documentTable { | |||||
| tbody { | |||||
| // height: calc(100vh - 508px); | |||||
| } | |||||
| } | } | ||||
| .filter { | .filter { |
| // loading={loading} | // loading={loading} | ||||
| pagination={pagination} | pagination={pagination} | ||||
| rowSelection={rowSelection} | rowSelection={rowSelection} | ||||
| scroll={{ scrollToFirstRowOnChange: true, x: 1300, y: 'fill' }} | |||||
| className={styles.documentTable} | |||||
| scroll={{ scrollToFirstRowOnChange: true, x: 1300 }} | |||||
| /> | /> | ||||
| <CreateFileModal | <CreateFileModal | ||||
| visible={createVisible} | visible={createVisible} |
| .uploader { | |||||
| :global { | |||||
| .ant-upload-list { | |||||
| max-height: 40vh; | |||||
| overflow-y: auto; | |||||
| } | |||||
| } | |||||
| } |
| import { useTranslate } from '@/hooks/commonHooks'; | |||||
| import { IModalProps } from '@/interfaces/common'; | |||||
| import { InboxOutlined } from '@ant-design/icons'; | |||||
| import { | |||||
| Flex, | |||||
| Modal, | |||||
| Segmented, | |||||
| Tabs, | |||||
| TabsProps, | |||||
| Upload, | |||||
| UploadFile, | |||||
| UploadProps, | |||||
| } from 'antd'; | |||||
| import { Dispatch, SetStateAction, useState } from 'react'; | |||||
| import styles from './index.less'; | |||||
| const { Dragger } = Upload; | |||||
| const FileUpload = ({ | |||||
| directory, | |||||
| fileList, | |||||
| setFileList, | |||||
| }: { | |||||
| directory: boolean; | |||||
| fileList: UploadFile[]; | |||||
| setFileList: Dispatch<SetStateAction<UploadFile[]>>; | |||||
| }) => { | |||||
| const { t } = useTranslate('fileManager'); | |||||
| const props: UploadProps = { | |||||
| multiple: true, | |||||
| onRemove: (file) => { | |||||
| const index = fileList.indexOf(file); | |||||
| const newFileList = fileList.slice(); | |||||
| newFileList.splice(index, 1); | |||||
| setFileList(newFileList); | |||||
| }, | |||||
| beforeUpload: (file) => { | |||||
| setFileList((pre) => { | |||||
| return [...pre, file]; | |||||
| }); | |||||
| return false; | |||||
| }, | |||||
| directory, | |||||
| fileList, | |||||
| }; | |||||
| return ( | |||||
| <Dragger {...props} className={styles.uploader}> | |||||
| <p className="ant-upload-drag-icon"> | |||||
| <InboxOutlined /> | |||||
| </p> | |||||
| <p className="ant-upload-text">{t('uploadTitle')}</p> | |||||
| <p className="ant-upload-hint">{t('uploadDescription')}</p> | |||||
| </Dragger> | |||||
| ); | |||||
| }; | |||||
| const FileUploadModal = ({ | |||||
| visible, | |||||
| hideModal, | |||||
| loading, | |||||
| onOk: onFileUploadOk, | |||||
| }: IModalProps<UploadFile[]>) => { | |||||
| const { t } = useTranslate('fileManager'); | |||||
| const [value, setValue] = useState<string | number>('local'); | |||||
| const [fileList, setFileList] = useState<UploadFile[]>([]); | |||||
| const [directoryFileList, setDirectoryFileList] = useState<UploadFile[]>([]); | |||||
| const onOk = async () => { | |||||
| const ret = await onFileUploadOk?.([...fileList, ...directoryFileList]); | |||||
| console.info(ret); | |||||
| if (ret !== undefined && ret === 0) { | |||||
| setFileList([]); | |||||
| setDirectoryFileList([]); | |||||
| } | |||||
| return ret; | |||||
| }; | |||||
| const items: TabsProps['items'] = [ | |||||
| { | |||||
| key: '1', | |||||
| label: t('file'), | |||||
| children: ( | |||||
| <FileUpload | |||||
| directory={false} | |||||
| fileList={fileList} | |||||
| setFileList={setFileList} | |||||
| ></FileUpload> | |||||
| ), | |||||
| }, | |||||
| { | |||||
| key: '2', | |||||
| label: t('directory'), | |||||
| children: ( | |||||
| <FileUpload | |||||
| directory | |||||
| fileList={directoryFileList} | |||||
| setFileList={setDirectoryFileList} | |||||
| ></FileUpload> | |||||
| ), | |||||
| }, | |||||
| ]; | |||||
| return ( | |||||
| <> | |||||
| <Modal | |||||
| title={t('uploadFile')} | |||||
| open={visible} | |||||
| onOk={onOk} | |||||
| onCancel={hideModal} | |||||
| confirmLoading={loading} | |||||
| > | |||||
| <Flex gap={'large'} vertical> | |||||
| <Segmented | |||||
| options={[ | |||||
| { label: t('local'), value: 'local' }, | |||||
| { label: t('s3'), value: 's3' }, | |||||
| ]} | |||||
| block | |||||
| value={value} | |||||
| onChange={setValue} | |||||
| /> | |||||
| {value === 'local' ? ( | |||||
| <Tabs defaultActiveKey="1" items={items} /> | |||||
| ) : ( | |||||
| t('comingSoon', { keyPrefix: 'common' }) | |||||
| )} | |||||
| </Flex> | |||||
| </Modal> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default FileUploadModal; |
| useSelectFileListLoading, | useSelectFileListLoading, | ||||
| } from './hooks'; | } from './hooks'; | ||||
| import FileUploadModal from '@/components/file-upload-modal'; | |||||
| import RenameModal from '@/components/rename-modal'; | import RenameModal from '@/components/rename-modal'; | ||||
| import SvgIcon from '@/components/svg-icon'; | import SvgIcon from '@/components/svg-icon'; | ||||
| import { useTranslate } from '@/hooks/commonHooks'; | import { useTranslate } from '@/hooks/commonHooks'; | ||||
| import { formatNumberWithThousandsSeparator } from '@/utils/commonUtil'; | import { formatNumberWithThousandsSeparator } from '@/utils/commonUtil'; | ||||
| import { getExtension } from '@/utils/documentUtils'; | import { getExtension } from '@/utils/documentUtils'; | ||||
| import ConnectToKnowledgeModal from './connect-to-knowledge-modal'; | import ConnectToKnowledgeModal from './connect-to-knowledge-modal'; | ||||
| import FileUploadModal from './file-upload-modal'; | |||||
| import FolderCreateModal from './folder-create-modal'; | import FolderCreateModal from './folder-create-modal'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| import { useSelectKnowledgeList } from '@/hooks/knowledgeHook'; | |||||
| import { useState } from 'react'; | |||||
| export const useSearchKnowledge = () => { | |||||
| const [searchString, setSearchString] = useState<string>(''); | |||||
| const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { | |||||
| setSearchString(e.target.value); | |||||
| }; | |||||
| return { | |||||
| searchString, | |||||
| handleInputChange, | |||||
| }; | |||||
| }; | |||||
| export const useSelectKnowledgeListByKeywords = (keywords: string) => { | |||||
| const list = useSelectKnowledgeList(); | |||||
| return list.filter((x) => x.name.includes(keywords)); | |||||
| }; |
| import ModalManager from '@/components/modal-manager'; | import ModalManager from '@/components/modal-manager'; | ||||
| import { useFetchKnowledgeList } from '@/hooks/knowledgeHook'; | import { useFetchKnowledgeList } from '@/hooks/knowledgeHook'; | ||||
| import { useSelectUserInfo } from '@/hooks/userSettingHook'; | import { useSelectUserInfo } from '@/hooks/userSettingHook'; | ||||
| import { PlusOutlined } from '@ant-design/icons'; | |||||
| import { Button, Empty, Flex, Space, Spin } from 'antd'; | |||||
| import { PlusOutlined, SearchOutlined } from '@ant-design/icons'; | |||||
| import { Button, Empty, Flex, Input, Space, Spin } from 'antd'; | |||||
| import KnowledgeCard from './knowledge-card'; | import KnowledgeCard from './knowledge-card'; | ||||
| import KnowledgeCreatingModal from './knowledge-creating-modal'; | import KnowledgeCreatingModal from './knowledge-creating-modal'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { useSearchKnowledge, useSelectKnowledgeListByKeywords } from './hooks'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const Knowledge = () => { | |||||
| const { list, loading } = useFetchKnowledgeList(); | |||||
| const KnowledgeList = () => { | |||||
| const { searchString, handleInputChange } = useSearchKnowledge(); | |||||
| const { loading } = useFetchKnowledgeList(); | |||||
| const list = useSelectKnowledgeListByKeywords(searchString); | |||||
| const userInfo = useSelectUserInfo(); | const userInfo = useSelectUserInfo(); | ||||
| const { t } = useTranslation('translation', { keyPrefix: 'knowledgeList' }); | const { t } = useTranslation('translation', { keyPrefix: 'knowledgeList' }); | ||||
| <p className={styles.description}>{t('description')}</p> | <p className={styles.description}>{t('description')}</p> | ||||
| </div> | </div> | ||||
| <Space size={'large'}> | <Space size={'large'}> | ||||
| {/* <Button icon={<FilterIcon />} className={styles.filterButton}> | |||||
| Filters | |||||
| </Button> */} | |||||
| <Input | |||||
| placeholder={t('searchKnowledgePlaceholder')} | |||||
| value={searchString} | |||||
| style={{ width: 220 }} | |||||
| allowClear | |||||
| onChange={handleInputChange} | |||||
| prefix={<SearchOutlined />} | |||||
| /> | |||||
| <ModalManager> | <ModalManager> | ||||
| {({ visible, hideModal, showModal }) => ( | {({ visible, hideModal, showModal }) => ( | ||||
| <> | <> | ||||
| ); | ); | ||||
| }; | }; | ||||
| export default Knowledge; | |||||
| export default KnowledgeList; |