### What problem does this PR solve? #345 feat: translate FileManager feat: batch delete files from the file table in the knowledge base ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.4.0
| @@ -27,7 +27,7 @@ export default defineConfig({ | |||
| devtool: 'source-map', | |||
| proxy: { | |||
| '/v1': { | |||
| target: 'http://192.168.200.233:9380/', | |||
| target: 'http://123.60.95.134:9380/', | |||
| changeOrigin: true, | |||
| // pathRewrite: { '^/v1': '/v1' }, | |||
| }, | |||
| @@ -160,12 +160,12 @@ export const useRemoveDocument = () => { | |||
| const { knowledgeId } = useGetKnowledgeSearchParams(); | |||
| const removeDocument = useCallback( | |||
| (documentId: string) => { | |||
| (documentIds: string[]) => { | |||
| try { | |||
| return dispatch<any>({ | |||
| type: 'kFModel/document_rm', | |||
| payload: { | |||
| doc_id: documentId, | |||
| doc_id: documentIds, | |||
| kb_id: knowledgeId, | |||
| }, | |||
| }); | |||
| @@ -14,5 +14,5 @@ export interface IModalProps<T> { | |||
| hideModal(): void; | |||
| visible: boolean; | |||
| loading?: boolean; | |||
| onOk?(payload?: T): Promise<void> | void; | |||
| onOk?(payload?: T): Promise<any> | void; | |||
| } | |||
| @@ -1,5 +1,5 @@ | |||
| import { ReactComponent as StarIon } from '@/assets/svg/chat-star.svg'; | |||
| // import { ReactComponent as FileIcon } from '@/assets/svg/file-management.svg'; | |||
| import { ReactComponent as FileIcon } from '@/assets/svg/file-management.svg'; | |||
| import { ReactComponent as KnowledgeBaseIcon } from '@/assets/svg/knowledge-base.svg'; | |||
| import { ReactComponent as Logo } from '@/assets/svg/logo.svg'; | |||
| import { useTranslate } from '@/hooks/commonHooks'; | |||
| @@ -25,7 +25,7 @@ const RagHeader = () => { | |||
| () => [ | |||
| { path: '/knowledge', name: t('knowledgeBase'), icon: KnowledgeBaseIcon }, | |||
| { path: '/chat', name: t('chat'), icon: StarIon }, | |||
| // { path: '/file', name: 'File Management', icon: FileIcon }, | |||
| { path: '/file', name: t('fileManager'), icon: FileIcon }, | |||
| ], | |||
| [t], | |||
| ); | |||
| @@ -22,6 +22,7 @@ export default { | |||
| languagePlaceholder: 'select your language', | |||
| copy: 'Copy', | |||
| copied: 'Copied', | |||
| comingSoon: 'Coming Soon', | |||
| }, | |||
| login: { | |||
| login: 'Sign in', | |||
| @@ -52,6 +53,7 @@ export default { | |||
| home: 'Home', | |||
| setting: '用户设置', | |||
| logout: '登出', | |||
| fileManager: 'File Management', | |||
| }, | |||
| knowledgeList: { | |||
| welcome: 'Welcome back', | |||
| @@ -459,6 +461,7 @@ export default { | |||
| renamed: 'Renamed', | |||
| operated: 'Operated', | |||
| updated: 'Updated', | |||
| uploaded: 'Uploaded', | |||
| 200: 'The server successfully returns the requested data.', | |||
| 201: 'Create or modify data successfully.', | |||
| 202: 'A request has been queued in the background (asynchronous task).', | |||
| @@ -480,6 +483,24 @@ export default { | |||
| networkAnomaly: 'network anomaly', | |||
| hint: 'hint', | |||
| }, | |||
| fileManager: { | |||
| name: 'Name', | |||
| uploadDate: 'Upload Date', | |||
| knowledgeBase: 'Knowledge Base', | |||
| size: 'Size', | |||
| action: 'Action', | |||
| addToKnowledge: 'Add to Knowledge Base', | |||
| pleaseSelect: 'Please select', | |||
| newFolder: 'New Folder', | |||
| file: 'File', | |||
| uploadFile: 'Upload File', | |||
| directory: 'Directory', | |||
| uploadTitle: 'Click or drag file to this area to upload', | |||
| uploadDescription: | |||
| 'Support for a single or bulk upload. Strictly prohibited from uploading company data or other banned files.', | |||
| local: 'Local uploads', | |||
| s3: 'S3 uploads', | |||
| }, | |||
| footer: { | |||
| profile: 'All rights reserved @ React', | |||
| }, | |||
| @@ -22,6 +22,7 @@ export default { | |||
| languagePlaceholder: '請選擇語言', | |||
| copy: '複製', | |||
| copied: '複製成功', | |||
| comingSoon: '即將推出', | |||
| }, | |||
| login: { | |||
| login: '登入', | |||
| @@ -52,6 +53,7 @@ export default { | |||
| home: '首頁', | |||
| setting: '用戶設置', | |||
| logout: '登出', | |||
| fileManager: '文件管理', | |||
| }, | |||
| knowledgeList: { | |||
| welcome: '歡迎回來', | |||
| @@ -218,7 +220,7 @@ export default { | |||
| 您只需與<i>'ragflow'</i>交談即可列出所有符合資格的候選人。 | |||
| </p> | |||
| `, | |||
| table: `支持<p><b>excel</b>和<b>csv/txt</b>格式文件。</p><p>以下是一些提示: <ul> <li>对于Csv或Txt文件,列之间的分隔符为 <em><b>tab</b></em>。</li> <li>第一行必须是列标题。</li> <li>列标题必须是有意义的术语,以便我们的法学硕士能够理解。列举一些同义词时最好使用斜杠<i>'/'</i>来分隔,甚至更好使用方括号枚举值,例如 <i>“性別/性別(男性,女性)”</i>.<p>以下是标题的一些示例:<ol> <li>供应商/供货商<b>'tab'</b>顏色(黃色、紅色、棕色)<b>'tab'</b>性別(男、女)<b>'tab'</B>尺码(m、l、xl、xxl)</li> <li>姓名/名字<b>'tab'</b>電話/手機/微信<b>'tab'</b>最高学历(高中,职高,硕士,本科,博士,初中,中技,中专,专科,专升本,mpa,mba,emba)</li> </ol> </p> </li> <li>表中的每一行都将被视为一个块。</li> </ul>`, | |||
| table: `支持<p><b>excel</b>和<b>csv/txt</b>格式文件。</p><p>以下是一些提示: <ul> <li>对于Csv或Txt文件,列之间的分隔符为 <em><b>tab</b></em>。</li> <li>第一行必须是列标题。</li> <li>列标题必须是有意义的术语,以便我们的大語言模型能够理解。列举一些同义词时最好使用斜杠<i>'/'</i>来分隔,甚至更好使用方括号枚举值,例如 <i>“性別/性別(男性,女性)”</i>.<p>以下是标题的一些示例:<ol> <li>供应商/供货商<b>'tab'</b>顏色(黃色、紅色、棕色)<b>'tab'</b>性別(男、女)<b>'tab'</B>尺码(m、l、xl、xxl)</li> <li>姓名/名字<b>'tab'</b>電話/手機/微信<b>'tab'</b>最高学历(高中,职高,硕士,本科,博士,初中,中技,中专,专科,专升本,mpa,mba,emba)</li> </ol> </p> </li> <li>表中的每一行都将被视为一个块。</li> </ul>`, | |||
| picture: ` | |||
| <p>支持圖像文件。視頻即將推出。</p><p> | |||
| 如果圖片中有文字,則應用 OCR 提取文字作為其文字描述。 | |||
| @@ -424,6 +426,7 @@ export default { | |||
| renamed: '重命名成功', | |||
| operated: '操作成功', | |||
| updated: '更新成功', | |||
| uploaded: '上傳成功', | |||
| 200: '服務器成功返回請求的數據。', | |||
| 201: '新建或修改數據成功。', | |||
| 202: '一個請求已經進入後台排隊(異步任務)。', | |||
| @@ -444,6 +447,23 @@ export default { | |||
| networkAnomaly: '網絡異常', | |||
| hint: '提示', | |||
| }, | |||
| fileManager: { | |||
| name: '名稱', | |||
| uploadDate: '上傳日期', | |||
| knowledgeBase: '知識庫', | |||
| size: '大小', | |||
| action: '操作', | |||
| addToKnowledge: '添加到知識庫', | |||
| pleaseSelect: '請選擇', | |||
| newFolder: '新建文件夾', | |||
| uploadFile: '上傳文件', | |||
| uploadTitle: '點擊或拖拽文件至此區域即可上傳', | |||
| uploadDescription: '支持單次或批量上傳。嚴禁上傳公司數據或其他違禁文件。', | |||
| file: '文件', | |||
| directory: '文件夾', | |||
| local: '本地上傳', | |||
| s3: 'S3 上傳', | |||
| }, | |||
| footer: { | |||
| profile: '“保留所有權利 @ react”', | |||
| }, | |||
| @@ -22,6 +22,7 @@ export default { | |||
| languagePlaceholder: '请选择语言', | |||
| copy: '复制', | |||
| copied: '复制成功', | |||
| comingSoon: '即将推出', | |||
| }, | |||
| login: { | |||
| login: '登录', | |||
| @@ -52,6 +53,7 @@ export default { | |||
| home: '首页', | |||
| setting: '用户设置', | |||
| logout: '登出', | |||
| fileManager: '文件管理', | |||
| }, | |||
| knowledgeList: { | |||
| welcome: '欢迎回来', | |||
| @@ -225,7 +227,7 @@ export default { | |||
| <ul> | |||
| <li>对于 csv 或 txt 文件,列之间的分隔符为 <em><b>TAB</b></em>。</li> | |||
| <li>第一行必须是列标题。</li> | |||
| <li>列标题必须是有意义的术语,以便我们的法学硕士能够理解。 | |||
| <li>列标题必须是有意义的术语,以便我们的大语言模型能够理解。 | |||
| 列举一些同义词时最好使用斜杠<i>'/'</i>来分隔,甚至更好 | |||
| 使用方括号枚举值,例如 <i>'gender/sex(male,female)'</i>.<p> | |||
| 以下是标题的一些示例:<ol> | |||
| @@ -298,7 +300,7 @@ export default { | |||
| systemTip: | |||
| '当LLM回答问题时,你需要LLM遵循的说明,比如角色设计、答案长度和答案语言等。', | |||
| topN: 'Top N', | |||
| topNTip: `并非所有相似度得分高于“相似度阈值”的块都会被提供给法学硕士。 LLM 只能看到这些“Top N”块。`, | |||
| topNTip: `并非所有相似度得分高于“相似度阈值”的块都会被提供给大语言模型。 LLM 只能看到这些“Top N”块。`, | |||
| variable: '变量', | |||
| variableTip: `如果您使用对话 API,变量可能会帮助您使用不同的策略与客户聊天。 | |||
| 这些变量用于填写提示中的“系统”部分,以便给LLM一个提示。 | |||
| @@ -315,7 +317,7 @@ export default { | |||
| improvise: '即兴创作', | |||
| precise: '精确', | |||
| balance: '平衡', | |||
| freedomTip: `“精确”意味着法学硕士会保守并谨慎地回答你的问题。 “即兴发挥”意味着你希望法学硕士能够自由地畅所欲言。 “平衡”是谨慎与自由之间的平衡。`, | |||
| freedomTip: `“精确”意味着大语言模型会保守并谨慎地回答你的问题。 “即兴发挥”意味着你希望大语言模型能够自由地畅所欲言。 “平衡”是谨慎与自由之间的平衡。`, | |||
| temperature: '温度', | |||
| temperatureMessage: '温度是必填项', | |||
| temperatureTip: | |||
| @@ -441,6 +443,7 @@ export default { | |||
| renamed: '重命名成功', | |||
| operated: '操作成功', | |||
| updated: '更新成功', | |||
| uploaded: '上传成功', | |||
| 200: '服务器成功返回请求的数据。', | |||
| 201: '新建或修改数据成功。', | |||
| 202: '一个请求已经进入后台排队(异步任务)。', | |||
| @@ -461,6 +464,24 @@ export default { | |||
| networkAnomaly: '网络异常', | |||
| hint: '提示', | |||
| }, | |||
| fileManager: { | |||
| name: '名称', | |||
| uploadDate: '上传日期', | |||
| knowledgeBase: '知识库', | |||
| size: '大小', | |||
| action: '操作', | |||
| addToKnowledge: '添加到知识库', | |||
| pleaseSelect: '请选择', | |||
| newFolder: '新建文件夹', | |||
| uploadFile: '上传文件', | |||
| uploadTitle: '点击或拖拽文件至此区域即可上传', | |||
| uploadDescription: | |||
| '支持单次或批量上传。 严禁上传公司数据或其他违禁文件。', | |||
| file: '文件', | |||
| directory: '文件夹', | |||
| local: '本地上传', | |||
| s3: 'S3 上传', | |||
| }, | |||
| footer: { | |||
| profile: 'All rights reserved @ React', | |||
| }, | |||
| @@ -80,9 +80,7 @@ const DocumentToolbar = ({ selectedRowKeys, showCreateModal }: IProps) => { | |||
| const handleDelete = useCallback(() => { | |||
| showDeleteConfirm({ | |||
| onOk: () => { | |||
| selectedRowKeys.forEach((id) => { | |||
| removeDocument(id); | |||
| }); | |||
| removeDocument(selectedRowKeys); | |||
| }, | |||
| }); | |||
| }, [removeDocument, showDeleteConfirm, selectedRowKeys]); | |||
| @@ -35,7 +35,7 @@ const ParsingActionCell = ({ | |||
| const onRmDocument = () => { | |||
| if (!isRunning) { | |||
| showDeleteConfirm({ onOk: () => removeDocument(documentId) }); | |||
| showDeleteConfirm({ onOk: () => removeDocument([documentId]) }); | |||
| } | |||
| }; | |||
| @@ -38,7 +38,7 @@ const ActionCell = ({ | |||
| const onDownloadDocument = () => { | |||
| downloadFile({ | |||
| url: `${api_host}/document/get/${documentId}`, | |||
| url: `${api_host}/file/get/${documentId}`, | |||
| filename: record.name, | |||
| }); | |||
| }; | |||
| @@ -1,3 +1,4 @@ | |||
| import { useTranslate } from '@/hooks/commonHooks'; | |||
| import { useFetchKnowledgeList } from '@/hooks/knowledgeHook'; | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { Form, Modal, Select, SelectProps } from 'antd'; | |||
| @@ -8,9 +9,11 @@ const ConnectToKnowledgeModal = ({ | |||
| hideModal, | |||
| onOk, | |||
| initialValue, | |||
| loading, | |||
| }: IModalProps<string[]> & { initialValue: string[] }) => { | |||
| const [form] = Form.useForm(); | |||
| const { list, fetchList } = useFetchKnowledgeList(); | |||
| const { t } = useTranslate('fileManager'); | |||
| const options: SelectProps['options'] = list?.map((item) => ({ | |||
| label: item.name, | |||
| @@ -32,10 +35,11 @@ const ConnectToKnowledgeModal = ({ | |||
| return ( | |||
| <Modal | |||
| title="Add to Knowledge Base" | |||
| title={t('addToKnowledge')} | |||
| open={visible} | |||
| onOk={handleOk} | |||
| onCancel={hideModal} | |||
| confirmLoading={loading} | |||
| > | |||
| <Form form={form}> | |||
| <Form.Item name="knowledgeIds" noStyle> | |||
| @@ -43,7 +47,7 @@ const ConnectToKnowledgeModal = ({ | |||
| mode="multiple" | |||
| allowClear | |||
| style={{ width: '100%' }} | |||
| placeholder="Please select" | |||
| placeholder={t('pleaseSelect')} | |||
| options={options} | |||
| /> | |||
| </Form.Item> | |||
| @@ -20,12 +20,12 @@ import { | |||
| import { useMemo } from 'react'; | |||
| import { | |||
| useFetchDocumentListOnMount, | |||
| useHandleBreadcrumbClick, | |||
| useHandleDeleteFile, | |||
| useHandleSearchChange, | |||
| useSelectBreadcrumbItems, | |||
| } from './hooks'; | |||
| import { Link } from 'umi'; | |||
| import styles from './index.less'; | |||
| interface IProps { | |||
| @@ -35,20 +35,6 @@ interface IProps { | |||
| setSelectedRowKeys: (keys: string[]) => void; | |||
| } | |||
| const itemRender: BreadcrumbProps['itemRender'] = ( | |||
| currentRoute, | |||
| params, | |||
| items, | |||
| ) => { | |||
| const isLast = currentRoute?.path === items[items.length - 1]?.path; | |||
| return isLast ? ( | |||
| <span>{currentRoute.title}</span> | |||
| ) : ( | |||
| <Link to={`${currentRoute.path}`}>{currentRoute.title}</Link> | |||
| ); | |||
| }; | |||
| const FileToolbar = ({ | |||
| selectedRowKeys, | |||
| showFolderCreateModal, | |||
| @@ -59,6 +45,26 @@ const FileToolbar = ({ | |||
| useFetchDocumentListOnMount(); | |||
| const { handleInputChange, searchString } = useHandleSearchChange(); | |||
| const breadcrumbItems = useSelectBreadcrumbItems(); | |||
| const { handleBreadcrumbClick } = useHandleBreadcrumbClick(); | |||
| const itemRender: BreadcrumbProps['itemRender'] = ( | |||
| currentRoute, | |||
| params, | |||
| items, | |||
| ) => { | |||
| const isLast = currentRoute?.path === items[items.length - 1]?.path; | |||
| return isLast ? ( | |||
| <span>{currentRoute.title}</span> | |||
| ) : ( | |||
| <span | |||
| className={styles.breadcrumbItemButton} | |||
| onClick={() => handleBreadcrumbClick(currentRoute.path)} | |||
| > | |||
| {currentRoute.title} | |||
| </span> | |||
| ); | |||
| }; | |||
| const actionItems: MenuProps['items'] = useMemo(() => { | |||
| return [ | |||
| @@ -70,7 +76,7 @@ const FileToolbar = ({ | |||
| <Button type="link"> | |||
| <Space> | |||
| <FileTextOutlined /> | |||
| {t('localFiles')} | |||
| {t('uploadFile', { keyPrefix: 'fileManager' })} | |||
| </Space> | |||
| </Button> | |||
| </div> | |||
| @@ -83,12 +89,13 @@ const FileToolbar = ({ | |||
| label: ( | |||
| <div> | |||
| <Button type="link"> | |||
| <FolderOpenOutlined /> | |||
| New Folder | |||
| <Space> | |||
| <FolderOpenOutlined /> | |||
| {t('newFolder', { keyPrefix: 'fileManager' })} | |||
| </Space> | |||
| </Button> | |||
| </div> | |||
| ), | |||
| // disabled: true, | |||
| }, | |||
| ]; | |||
| }, [t, showFolderCreateModal, showFileUploadModal]); | |||
| @@ -0,0 +1,8 @@ | |||
| .uploader { | |||
| :global { | |||
| .ant-upload-list { | |||
| max-height: 40vh; | |||
| overflow-y: auto; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,3 +1,4 @@ | |||
| import { useTranslate } from '@/hooks/commonHooks'; | |||
| import { IModalProps } from '@/interfaces/common'; | |||
| import { InboxOutlined } from '@ant-design/icons'; | |||
| import { | |||
| @@ -12,6 +13,8 @@ import { | |||
| } from 'antd'; | |||
| import { Dispatch, SetStateAction, useState } from 'react'; | |||
| import styles from './index.less'; | |||
| const { Dragger } = Upload; | |||
| const FileUpload = ({ | |||
| @@ -23,6 +26,7 @@ const FileUpload = ({ | |||
| fileList: UploadFile[]; | |||
| setFileList: Dispatch<SetStateAction<UploadFile[]>>; | |||
| }) => { | |||
| const { t } = useTranslate('fileManager'); | |||
| const props: UploadProps = { | |||
| multiple: true, | |||
| onRemove: (file) => { | |||
| @@ -43,17 +47,12 @@ const FileUpload = ({ | |||
| }; | |||
| return ( | |||
| <Dragger {...props}> | |||
| <Dragger {...props} className={styles.uploader}> | |||
| <p className="ant-upload-drag-icon"> | |||
| <InboxOutlined /> | |||
| </p> | |||
| <p className="ant-upload-text"> | |||
| Click or drag file to this area to upload | |||
| </p> | |||
| <p className="ant-upload-hint"> | |||
| Support for a single or bulk upload. Strictly prohibited from uploading | |||
| company data or other banned files. | |||
| </p> | |||
| <p className="ant-upload-text">{t('uploadTitle')}</p> | |||
| <p className="ant-upload-hint">{t('uploadDescription')}</p> | |||
| </Dragger> | |||
| ); | |||
| }; | |||
| @@ -64,18 +63,25 @@ const FileUploadModal = ({ | |||
| 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 = () => { | |||
| return onFileUploadOk?.([...fileList, ...directoryFileList]); | |||
| 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: 'File', | |||
| label: t('file'), | |||
| children: ( | |||
| <FileUpload | |||
| directory={false} | |||
| @@ -86,7 +92,7 @@ const FileUploadModal = ({ | |||
| }, | |||
| { | |||
| key: '2', | |||
| label: 'Directory', | |||
| label: t('directory'), | |||
| children: ( | |||
| <FileUpload | |||
| directory | |||
| @@ -100,7 +106,7 @@ const FileUploadModal = ({ | |||
| return ( | |||
| <> | |||
| <Modal | |||
| title="File upload" | |||
| title={t('uploadFile')} | |||
| open={visible} | |||
| onOk={onOk} | |||
| onCancel={hideModal} | |||
| @@ -109,8 +115,8 @@ const FileUploadModal = ({ | |||
| <Flex gap={'large'} vertical> | |||
| <Segmented | |||
| options={[ | |||
| { label: 'Local uploads', value: 'local' }, | |||
| { label: 'S3 uploads', value: 's3' }, | |||
| { label: t('local'), value: 'local' }, | |||
| { label: t('s3'), value: 's3' }, | |||
| ]} | |||
| block | |||
| value={value} | |||
| @@ -119,7 +125,7 @@ const FileUploadModal = ({ | |||
| {value === 'local' ? ( | |||
| <Tabs defaultActiveKey="1" items={items} /> | |||
| ) : ( | |||
| 'coming soon' | |||
| t('comingSoon', { keyPrefix: 'common' }) | |||
| )} | |||
| </Flex> | |||
| </Modal> | |||
| @@ -35,7 +35,7 @@ const FolderCreateModal = ({ visible, hideModal, loading, onOk }: IProps) => { | |||
| return ( | |||
| <Modal | |||
| title={'New Folder'} | |||
| title={t('newFolder', { keyPrefix: 'fileManager' })} | |||
| open={visible} | |||
| onOk={handleOk} | |||
| onCancel={handleCancel} | |||
| @@ -244,14 +244,14 @@ export const useHandleUploadFile = () => { | |||
| const id = useGetFolderId(); | |||
| const onFileUploadOk = useCallback( | |||
| async (fileList: UploadFile[]) => { | |||
| console.info('fileList', fileList); | |||
| async (fileList: UploadFile[]): Promise<number | undefined> => { | |||
| if (fileList.length > 0) { | |||
| const ret = await uploadFile(fileList, id); | |||
| const ret: number = await uploadFile(fileList, id); | |||
| console.info(ret); | |||
| if (ret === 0) { | |||
| hideFileUploadModal(); | |||
| } | |||
| return ret; | |||
| } | |||
| }, | |||
| [uploadFile, hideFileUploadModal, id], | |||
| @@ -295,6 +295,7 @@ export const useHandleConnectToKnowledge = () => { | |||
| if (ret === 0) { | |||
| hideConnectToKnowledgeModal(); | |||
| } | |||
| return ret; | |||
| }, | |||
| [connectToKnowledge, hideConnectToKnowledgeModal, id, record.id], | |||
| ); | |||
| @@ -320,3 +321,20 @@ export const useHandleConnectToKnowledge = () => { | |||
| showConnectToKnowledgeModal: handleShowConnectToKnowledgeModal, | |||
| }; | |||
| }; | |||
| export const useHandleBreadcrumbClick = () => { | |||
| const navigate = useNavigate(); | |||
| const setPagination = useSetPagination('fileManager'); | |||
| const handleBreadcrumbClick = useCallback( | |||
| (path?: string) => { | |||
| if (path) { | |||
| setPagination(); | |||
| navigate(path); | |||
| } | |||
| }, | |||
| [setPagination, navigate], | |||
| ); | |||
| return { handleBreadcrumbClick }; | |||
| }; | |||
| @@ -20,3 +20,10 @@ | |||
| .linkButton { | |||
| padding: 0; | |||
| } | |||
| .breadcrumbItemButton { | |||
| cursor: pointer; | |||
| color: #1677ff; | |||
| padding: 0; | |||
| height: auto; | |||
| } | |||
| @@ -1,7 +1,7 @@ | |||
| import { useSelectFileList } from '@/hooks/fileManagerHooks'; | |||
| import { IFile } from '@/interfaces/database/file-manager'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { Button, Flex, Table } from 'antd'; | |||
| import { Button, Flex, Space, Table, Tag } from 'antd'; | |||
| import { ColumnsType } from 'antd/es/table'; | |||
| import ActionCell from './action-cell'; | |||
| import FileToolbar from './file-toolbar'; | |||
| @@ -18,6 +18,8 @@ import { | |||
| import RenameModal from '@/components/rename-modal'; | |||
| import SvgIcon from '@/components/svg-icon'; | |||
| import { useTranslate } from '@/hooks/commonHooks'; | |||
| import { formatNumberWithThousandsSeparator } from '@/utils/commonUtil'; | |||
| import { getExtension } from '@/utils/documentUtils'; | |||
| import ConnectToKnowledgeModal from './connect-to-knowledge-modal'; | |||
| import FileUploadModal from './file-upload-modal'; | |||
| @@ -25,6 +27,7 @@ import FolderCreateModal from './folder-create-modal'; | |||
| import styles from './index.less'; | |||
| const FileManager = () => { | |||
| const { t } = useTranslate('fileManager'); | |||
| const fileList = useSelectFileList(); | |||
| const { rowSelection, setSelectedRowKeys } = useGetRowSelection(); | |||
| const loading = useSelectFileListLoading(); | |||
| @@ -57,12 +60,13 @@ const FileManager = () => { | |||
| showConnectToKnowledgeModal, | |||
| onConnectToKnowledgeOk, | |||
| initialValue, | |||
| connectToKnowledgeLoading, | |||
| } = useHandleConnectToKnowledge(); | |||
| const { pagination } = useGetFilesPagination(); | |||
| const columns: ColumnsType<IFile> = [ | |||
| { | |||
| title: 'Name', | |||
| title: t('name'), | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| render(value, record) { | |||
| @@ -88,7 +92,7 @@ const FileManager = () => { | |||
| }, | |||
| }, | |||
| { | |||
| title: 'Upload Date', | |||
| title: t('uploadDate'), | |||
| dataIndex: 'create_date', | |||
| key: 'create_date', | |||
| render(text) { | |||
| @@ -96,22 +100,35 @@ const FileManager = () => { | |||
| }, | |||
| }, | |||
| { | |||
| title: 'Knowledge Base', | |||
| dataIndex: 'kbs_info', | |||
| key: 'kbs_info', | |||
| title: t('size'), | |||
| dataIndex: 'size', | |||
| key: 'size', | |||
| render(value) { | |||
| return Array.isArray(value) | |||
| ? value?.map((x) => x.kb_name).join(',') | |||
| : ''; | |||
| return ( | |||
| formatNumberWithThousandsSeparator((value / 1024).toFixed(2)) + ' KB' | |||
| ); | |||
| }, | |||
| }, | |||
| { | |||
| title: 'Location', | |||
| dataIndex: 'location', | |||
| key: 'location', | |||
| title: t('knowledgeBase'), | |||
| dataIndex: 'kbs_info', | |||
| key: 'kbs_info', | |||
| render(value) { | |||
| return Array.isArray(value) ? ( | |||
| <Space wrap> | |||
| {value?.map((x) => ( | |||
| <Tag color="blue" key={x.kb_id}> | |||
| {x.kb_name} | |||
| </Tag> | |||
| ))} | |||
| </Space> | |||
| ) : ( | |||
| '' | |||
| ); | |||
| }, | |||
| }, | |||
| { | |||
| title: 'Action', | |||
| title: t('action'), | |||
| dataIndex: 'action', | |||
| key: 'action', | |||
| render: (text, record) => ( | |||
| @@ -168,6 +185,7 @@ const FileManager = () => { | |||
| visible={connectToKnowledgeVisible} | |||
| hideModal={hideConnectToKnowledgeModal} | |||
| onOk={onConnectToKnowledgeOk} | |||
| loading={connectToKnowledgeLoading} | |||
| ></ConnectToKnowledgeModal> | |||
| </section> | |||
| ); | |||
| @@ -1,7 +1,9 @@ | |||
| import { paginationModel } from '@/base'; | |||
| import { BaseState } from '@/interfaces/common'; | |||
| import { IFile, IFolder } from '@/interfaces/database/file-manager'; | |||
| import i18n from '@/locales/config'; | |||
| import fileManagerService from '@/services/fileManagerService'; | |||
| import { message } from 'antd'; | |||
| import omit from 'lodash/omit'; | |||
| import { DvaModel } from 'umi'; | |||
| @@ -33,6 +35,7 @@ const model: DvaModel<FileManagerModelState> = { | |||
| }); | |||
| const { retcode } = data; | |||
| if (retcode === 0) { | |||
| message.success(i18n.t('message.deleted')); | |||
| yield put({ | |||
| type: 'listFile', | |||
| payload: { parentId: payload.parentId }, | |||
| @@ -69,6 +72,7 @@ const model: DvaModel<FileManagerModelState> = { | |||
| omit(payload, ['parentId']), | |||
| ); | |||
| if (data.retcode === 0) { | |||
| message.success(i18n.t('message.renamed')); | |||
| yield put({ | |||
| type: 'listFile', | |||
| payload: { parentId: payload.parentId }, | |||
| @@ -89,6 +93,8 @@ const model: DvaModel<FileManagerModelState> = { | |||
| }); | |||
| const { data } = yield call(fileManagerService.uploadFile, formData); | |||
| if (data.retcode === 0) { | |||
| message.success(i18n.t('message.uploaded')); | |||
| yield put({ | |||
| type: 'listFile', | |||
| payload: { parentId: payload.parentId }, | |||
| @@ -99,6 +105,8 @@ const model: DvaModel<FileManagerModelState> = { | |||
| *createFolder({ payload = {} }, { call, put }) { | |||
| const { data } = yield call(fileManagerService.createFolder, payload); | |||
| if (data.retcode === 0) { | |||
| message.success(i18n.t('message.created')); | |||
| yield put({ | |||
| type: 'listFile', | |||
| payload: { parentId: payload.parentId }, | |||
| @@ -125,6 +133,7 @@ const model: DvaModel<FileManagerModelState> = { | |||
| omit(payload, 'parentId'), | |||
| ); | |||
| if (data.retcode === 0) { | |||
| message.success(i18n.t('message.operated')); | |||
| yield put({ | |||
| type: 'listFile', | |||
| payload: { parentId: payload.parentId }, | |||
| @@ -27,3 +27,9 @@ export const getSearchValue = (key: string) => { | |||
| const params = new URL(document.location as any).searchParams; | |||
| return params.get(key); | |||
| }; | |||
| // Formatize numbers, add thousands of separators | |||
| export const formatNumberWithThousandsSeparator = (numberStr: string) => { | |||
| const formattedNumber = numberStr.replace(/\B(?=(\d{3})+(?!\d))/g, ','); | |||
| return formattedNumber; | |||
| }; | |||