### What problem does this PR solve? #666 feat: support DeepSeek feat: preview word and excel ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.5.0
| @@ -3,7 +3,7 @@ | |||
| "author": "zhaofengchao <13723060510@163.com>", | |||
| "scripts": { | |||
| "build": "umi build", | |||
| "dev": "cross-env PORT=9000 umi dev", | |||
| "dev": "cross-env PORT=9200 umi dev", | |||
| "postinstall": "umi setup", | |||
| "lint": "umi lint --eslint-only", | |||
| "setup": "umi setup", | |||
| @@ -13,6 +13,7 @@ | |||
| "@ant-design/icons": "^5.2.6", | |||
| "@ant-design/pro-components": "^2.6.46", | |||
| "@ant-design/pro-layout": "^7.17.16", | |||
| "@js-preview/excel": "^1.7.8", | |||
| "ahooks": "^3.7.10", | |||
| "antd": "^5.12.7", | |||
| "axios": "^1.6.3", | |||
| @@ -25,6 +26,7 @@ | |||
| "rc-tween-one": "^3.0.6", | |||
| "react-chat-elements": "^12.0.13", | |||
| "react-copy-to-clipboard": "^5.1.0", | |||
| "react-file-viewer": "^1.2.1", | |||
| "react-i18next": "^14.0.0", | |||
| "react-infinite-scroll-component": "^6.1.0", | |||
| "react-markdown": "^9.0.1", | |||
| @@ -0,0 +1,6 @@ | |||
| <svg t="1715133624982" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4263" | |||
| width="200" height="200"> | |||
| <path | |||
| d="M320.512 804.864C46.08 676.864 77.824 274.432 362.496 274.432c34.816 0 86.016-7.168 114.688-14.336 59.392-16.384 99.328-10.24 69.632 10.24-9.216 7.168-15.36 19.456-13.312 28.672 5.12 20.48 158.72 161.792 177.152 161.792 27.648 0 27.648-32.768 1.024-57.344-43.008-38.912-55.296-90.112-35.84-141.312l9.216-26.624 54.272 52.224c35.84 34.816 58.368 49.152 68.608 44.032 9.216-4.096 30.72-9.216 49.152-12.288 18.432-2.048 38.912-10.24 45.056-18.432 19.456-23.552 43.008-17.408 35.84 9.216-3.072 12.288-6.144 27.648-6.144 34.816 0 23.552-62.464 83.968-92.16 90.112-23.552 5.12-30.72 12.288-30.72 30.72 0 46.08-38.912 148.48-75.776 198.656l-37.888 51.2 36.864 15.36c56.32 23.552 40.96 41.984-37.888 43.008-43.008 1.024-75.776 7.168-92.16 18.432-68.608 45.056-198.656 50.176-281.6 12.288z m251.904-86.016c-24.576-27.648-66.56-79.872-93.184-117.76-69.632-98.304-158.72-150.528-256-150.528-37.888 0-38.912 1.024-38.912 34.816 0 94.208 99.328 240.64 175.104 257.024 38.912 9.216 59.392-7.168 39.936-29.696-7.168-9.216-10.24-23.552-6.144-31.744 5.12-14.336 9.216-14.336 38.912 1.024 18.432 9.216 50.176 29.696 69.632 45.056 35.84 27.648 58.368 37.888 96.256 39.936 14.336 1.024 9.216-10.24-25.6-48.128z m88.064-145.408c8.192-13.312-31.744-78.848-56.32-92.16-10.24-6.144-26.624-10.24-34.816-10.24-23.552 0-20.48 27.648 4.096 33.792 13.312 3.072 20.48 14.336 20.48 29.696 0 13.312 5.12 29.696 12.288 36.864 15.36 15.36 46.08 16.384 54.272 2.048z" | |||
| fill="#4D6BFE" p-id="4264"></path> | |||
| </svg> | |||
| @@ -46,3 +46,25 @@ export const LanguageTranslationMap = { | |||
| Chinese: 'zh', | |||
| 'Traditional Chinese': 'zh-TRADITIONAL', | |||
| }; | |||
| export const FileMimeTypeMap = { | |||
| bmp: 'image/bmp', | |||
| csv: 'text/csv', | |||
| odt: 'application/vnd.oasis.opendocument.text', | |||
| doc: 'application/msword', | |||
| docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', | |||
| gif: 'image/gif', | |||
| htm: 'text/htm', | |||
| html: 'text/html', | |||
| jpg: 'image/jpg', | |||
| jpeg: 'image/jpeg', | |||
| pdf: 'application/pdf', | |||
| png: 'image/png', | |||
| ppt: 'application/vnd.ms-powerpoint', | |||
| pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation', | |||
| tiff: 'image/tiff', | |||
| txt: 'text/plain', | |||
| xls: 'application/vnd.ms-excel', | |||
| xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', | |||
| mp4: 'video/mp4', | |||
| }; | |||
| @@ -0,0 +1,35 @@ | |||
| import jsPreviewExcel from '@js-preview/excel'; | |||
| import '@js-preview/excel/lib/index.css'; | |||
| import { useEffect } from 'react'; | |||
| const Excel = ({ filePath }: { filePath: string }) => { | |||
| const fetchDocument = async () => { | |||
| const myExcelPreviewer = jsPreviewExcel.init( | |||
| document.getElementById('excel'), | |||
| ); | |||
| const jsonFile = new XMLHttpRequest(); | |||
| jsonFile.open('GET', filePath, true); | |||
| jsonFile.send(); | |||
| jsonFile.responseType = 'arraybuffer'; | |||
| jsonFile.onreadystatechange = () => { | |||
| if (jsonFile.readyState === 4 && jsonFile.status === 200) { | |||
| myExcelPreviewer | |||
| .preview(jsonFile.response) | |||
| .then((res: any) => { | |||
| console.log('succeed'); | |||
| }) | |||
| .catch((e) => { | |||
| console.log('failed', e); | |||
| }); | |||
| } | |||
| }; | |||
| }; | |||
| useEffect(() => { | |||
| fetchDocument(); | |||
| }, []); | |||
| return <div id="excel" style={{ height: '100%' }}></div>; | |||
| }; | |||
| export default Excel; | |||
| @@ -0,0 +1,8 @@ | |||
| .viewerWrapper { | |||
| width: 100%; | |||
| :global { | |||
| .pdf-canvas { | |||
| text-align: center; | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,28 @@ | |||
| import { api_host } from '@/utils/api'; | |||
| import FileViewer from 'react-file-viewer'; | |||
| import { useParams, useSearchParams } from 'umi'; | |||
| import Excel from './excel'; | |||
| import styles from './index.less'; | |||
| const DocumentViewer = () => { | |||
| const { id: documentId } = useParams(); | |||
| const api = `${api_host}/file/get/${documentId}`; | |||
| const [currentQueryParameters] = useSearchParams(); | |||
| const ext = currentQueryParameters.get('ext'); | |||
| const onError = (e: any) => { | |||
| console.error(e, 'error in file-viewer'); | |||
| }; | |||
| return ( | |||
| <section className={styles.viewerWrapper}> | |||
| {ext === 'xlsx' && <Excel filePath={api}></Excel>} | |||
| {ext !== 'xlsx' && ( | |||
| <FileViewer fileType={ext} filePath={api} onError={onError} /> | |||
| )} | |||
| </section> | |||
| ); | |||
| }; | |||
| export default DocumentViewer; | |||
| @@ -9,7 +9,7 @@ import { | |||
| LinkOutlined, | |||
| } from '@ant-design/icons'; | |||
| import { Button, Space, Tooltip } from 'antd'; | |||
| import { useHandleDeleteFile } from '../hooks'; | |||
| import { useHandleDeleteFile, useNavigateToDocument } from '../hooks'; | |||
| import styles from './index.less'; | |||
| @@ -35,6 +35,7 @@ const ActionCell = ({ | |||
| [documentId], | |||
| setSelectedRowKeys, | |||
| ); | |||
| const navigateToDocument = useNavigateToDocument(record.id, record.name); | |||
| const onDownloadDocument = () => { | |||
| downloadFile({ | |||
| @@ -58,6 +59,15 @@ const ActionCell = ({ | |||
| return ( | |||
| <Space size={0}> | |||
| {/* <Tooltip title={t('addToKnowledge')}> | |||
| <Button | |||
| type="text" | |||
| className={styles.iconButton} | |||
| onClick={navigateToDocument} | |||
| > | |||
| <EyeOutlined size={20} /> | |||
| </Button> | |||
| </Tooltip> */} | |||
| <Tooltip title={t('addToKnowledge')}> | |||
| <Button | |||
| type="text" | |||
| @@ -13,6 +13,7 @@ import { | |||
| import { useGetPagination, useSetPagination } from '@/hooks/logicHooks'; | |||
| import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; | |||
| import { IFile } from '@/interfaces/database/file-manager'; | |||
| import { getExtension } from '@/utils/documentUtils'; | |||
| import { PaginationProps } from 'antd'; | |||
| import { UploadFile } from 'antd/lib'; | |||
| import { useCallback, useEffect, useMemo, useState } from 'react'; | |||
| @@ -338,3 +339,12 @@ export const useHandleBreadcrumbClick = () => { | |||
| return { handleBreadcrumbClick }; | |||
| }; | |||
| export const useNavigateToDocument = (documentId: string, name: string) => { | |||
| const navigate = useNavigate(); | |||
| const navigateToDocument = () => { | |||
| navigate(`/document/${documentId}?ext=${getExtension(name)}`); | |||
| }; | |||
| return navigateToDocument; | |||
| }; | |||
| @@ -1,7 +1,7 @@ | |||
| import { useSelectFileList } from '@/hooks/fileManagerHooks'; | |||
| import { IFile } from '@/interfaces/database/file-manager'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { Button, Flex, Space, Table, Tag } from 'antd'; | |||
| import { Button, Flex, Space, Table, Tag, Typography } from 'antd'; | |||
| import { ColumnsType } from 'antd/es/table'; | |||
| import ActionCell from './action-cell'; | |||
| import FileToolbar from './file-toolbar'; | |||
| @@ -26,6 +26,8 @@ import ConnectToKnowledgeModal from './connect-to-knowledge-modal'; | |||
| import FolderCreateModal from './folder-create-modal'; | |||
| import styles from './index.less'; | |||
| const { Text } = Typography; | |||
| const FileManager = () => { | |||
| const { t } = useTranslate('fileManager'); | |||
| const fileList = useSelectFileList(); | |||
| @@ -69,6 +71,7 @@ const FileManager = () => { | |||
| title: t('name'), | |||
| dataIndex: 'name', | |||
| key: 'name', | |||
| fixed: 'left', | |||
| render(value, record) { | |||
| return ( | |||
| <Flex gap={10} align="center"> | |||
| @@ -82,10 +85,10 @@ const FileManager = () => { | |||
| className={styles.linkButton} | |||
| onClick={() => navigateToOtherFolder(record.id)} | |||
| > | |||
| {value} | |||
| <Text ellipsis={{ tooltip: value }}>{value}</Text> | |||
| </Button> | |||
| ) : ( | |||
| value | |||
| <Text ellipsis={{ tooltip: value }}>{value}</Text> | |||
| )} | |||
| </Flex> | |||
| ); | |||
| @@ -160,6 +163,7 @@ const FileManager = () => { | |||
| rowSelection={rowSelection} | |||
| loading={loading} | |||
| pagination={pagination} | |||
| scroll={{ scrollToFirstRowOnChange: true, x: '100%' }} | |||
| /> | |||
| <RenameModal | |||
| visible={fileRenameVisible} | |||
| @@ -2,7 +2,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 fileManagerService, { | |||
| getDocumentFile, | |||
| } from '@/services/fileManagerService'; | |||
| import { message } from 'antd'; | |||
| import omit from 'lodash/omit'; | |||
| import { DvaModel } from 'umi'; | |||
| @@ -139,6 +141,11 @@ const model: DvaModel<FileManagerModelState> = { | |||
| } | |||
| return data.retcode; | |||
| }, | |||
| *getDocumentFile({ payload = {} }, { call }) { | |||
| const ret = yield call(getDocumentFile, payload); | |||
| return ret; | |||
| }, | |||
| }, | |||
| }; | |||
| @@ -45,6 +45,7 @@ const IconMap = { | |||
| 文心一言: 'wenxin', | |||
| Ollama: 'ollama', | |||
| Xinference: 'xinference', | |||
| DeepSeek: 'deepseek', | |||
| }; | |||
| const LlmIcon = ({ name }: { name: string }) => { | |||
| @@ -88,6 +88,10 @@ const routes = [ | |||
| path: '/flow', | |||
| component: '@/pages/flow', | |||
| }, | |||
| { | |||
| path: 'document/:id', | |||
| component: '@/pages/document-viewer', | |||
| }, | |||
| ], | |||
| }, | |||
| { | |||
| @@ -1,6 +1,7 @@ | |||
| import api from '@/utils/api'; | |||
| import registerServer from '@/utils/registerServer'; | |||
| import request from '@/utils/request'; | |||
| import pureRequest from 'axios'; | |||
| const { | |||
| listFile, | |||
| @@ -10,6 +11,8 @@ const { | |||
| getAllParentFolder, | |||
| createFolder, | |||
| connectFileToKnowledge, | |||
| get_document_file, | |||
| getFile, | |||
| } = api; | |||
| const methods = { | |||
| @@ -41,6 +44,11 @@ const methods = { | |||
| url: connectFileToKnowledge, | |||
| method: 'post', | |||
| }, | |||
| getDocumentFile: { | |||
| url: getFile, | |||
| method: 'get', | |||
| responseType: 'blob', | |||
| }, | |||
| } as const; | |||
| const fileManagerService = registerServer<keyof typeof methods>( | |||
| @@ -49,3 +57,45 @@ const fileManagerService = registerServer<keyof typeof methods>( | |||
| ); | |||
| export default fileManagerService; | |||
| export const getDocumentFile = (documentId: string) => { | |||
| return pureRequest(getFile + '/' + documentId, { | |||
| responseType: 'blob', | |||
| method: 'get', | |||
| // headers: { | |||
| // 'content-type': | |||
| // 'text/plain;charset=UTF-8, application/vnd.openxmlformats-officeddocument.spreadsheetml.sheet;charset=UTF-8', | |||
| // }, | |||
| // parseResponse: false, | |||
| // getResponse: true, | |||
| }) | |||
| .then((res) => { | |||
| const x = res?.headers?.get('content-disposition'); | |||
| const y = res?.headers?.get('Content-Type'); | |||
| console.info(res); | |||
| console.info(x); | |||
| console.info('Content-Type', y); | |||
| return res; | |||
| }) | |||
| .then((res) => { | |||
| // const objectURL = URL.createObjectURL(res); | |||
| // let btn = document.createElement('a'); | |||
| // btn.download = '文件名.pdf'; | |||
| // btn.href = objectURL; | |||
| // btn.click(); | |||
| // URL.revokeObjectURL(objectURL); | |||
| // btn = null; | |||
| return res; | |||
| }) | |||
| .catch((err) => { | |||
| console.info(err); | |||
| }); | |||
| }; | |||
| @@ -75,4 +75,5 @@ export default { | |||
| getAllParentFolder: `${api_host}/file/all_parent_folder`, | |||
| createFolder: `${api_host}/file/create`, | |||
| connectFileToKnowledge: `${api_host}/file2document/convert`, | |||
| getFile: `${api_host}/file/get`, | |||
| }; | |||
| @@ -1,3 +1,4 @@ | |||
| import omit from 'lodash/omit'; | |||
| import { RequestMethod } from 'umi-request'; | |||
| type Service<T extends string> = Record<T, (params: any) => any>; | |||
| @@ -10,6 +11,7 @@ const registerServer = <T extends string>( | |||
| for (let key in opt) { | |||
| server[key] = (params: any, urlAppendix?: string) => { | |||
| let url = opt[key].url; | |||
| const requestOptions = opt[key]; | |||
| if (urlAppendix) { | |||
| url = url + '/' + urlAppendix; | |||
| } | |||
| @@ -22,6 +24,7 @@ const registerServer = <T extends string>( | |||
| if (opt[key].method === 'get' || opt[key].method === 'GET') { | |||
| return request.get(url, { | |||
| ...omit(requestOptions, ['method', 'url']), | |||
| params, | |||
| }); | |||
| } | |||
| @@ -10,6 +10,7 @@ import { LoginModelState } from '@/pages/login/model'; | |||
| import { SettingModelState } from '@/pages/user-setting/model'; | |||
| declare module 'lodash'; | |||
| declare module 'react-file-viewer'; | |||
| function useSelector<TState = RootState, TSelected = unknown>( | |||
| selector: (state: TState) => TSelected, | |||