### 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
| "author": "zhaofengchao <13723060510@163.com>", | "author": "zhaofengchao <13723060510@163.com>", | ||||
| "scripts": { | "scripts": { | ||||
| "build": "umi build", | "build": "umi build", | ||||
| "dev": "cross-env PORT=9000 umi dev", | |||||
| "dev": "cross-env PORT=9200 umi dev", | |||||
| "postinstall": "umi setup", | "postinstall": "umi setup", | ||||
| "lint": "umi lint --eslint-only", | "lint": "umi lint --eslint-only", | ||||
| "setup": "umi setup", | "setup": "umi setup", | ||||
| "@ant-design/icons": "^5.2.6", | "@ant-design/icons": "^5.2.6", | ||||
| "@ant-design/pro-components": "^2.6.46", | "@ant-design/pro-components": "^2.6.46", | ||||
| "@ant-design/pro-layout": "^7.17.16", | "@ant-design/pro-layout": "^7.17.16", | ||||
| "@js-preview/excel": "^1.7.8", | |||||
| "ahooks": "^3.7.10", | "ahooks": "^3.7.10", | ||||
| "antd": "^5.12.7", | "antd": "^5.12.7", | ||||
| "axios": "^1.6.3", | "axios": "^1.6.3", | ||||
| "rc-tween-one": "^3.0.6", | "rc-tween-one": "^3.0.6", | ||||
| "react-chat-elements": "^12.0.13", | "react-chat-elements": "^12.0.13", | ||||
| "react-copy-to-clipboard": "^5.1.0", | "react-copy-to-clipboard": "^5.1.0", | ||||
| "react-file-viewer": "^1.2.1", | |||||
| "react-i18next": "^14.0.0", | "react-i18next": "^14.0.0", | ||||
| "react-infinite-scroll-component": "^6.1.0", | "react-infinite-scroll-component": "^6.1.0", | ||||
| "react-markdown": "^9.0.1", | "react-markdown": "^9.0.1", |
| <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> |
| Chinese: 'zh', | Chinese: 'zh', | ||||
| 'Traditional Chinese': 'zh-TRADITIONAL', | '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', | |||||
| }; |
| 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; |
| .viewerWrapper { | |||||
| width: 100%; | |||||
| :global { | |||||
| .pdf-canvas { | |||||
| text-align: center; | |||||
| } | |||||
| } | |||||
| } |
| 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; |
| LinkOutlined, | LinkOutlined, | ||||
| } from '@ant-design/icons'; | } from '@ant-design/icons'; | ||||
| import { Button, Space, Tooltip } from 'antd'; | import { Button, Space, Tooltip } from 'antd'; | ||||
| import { useHandleDeleteFile } from '../hooks'; | |||||
| import { useHandleDeleteFile, useNavigateToDocument } from '../hooks'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| [documentId], | [documentId], | ||||
| setSelectedRowKeys, | setSelectedRowKeys, | ||||
| ); | ); | ||||
| const navigateToDocument = useNavigateToDocument(record.id, record.name); | |||||
| const onDownloadDocument = () => { | const onDownloadDocument = () => { | ||||
| downloadFile({ | downloadFile({ | ||||
| return ( | return ( | ||||
| <Space size={0}> | <Space size={0}> | ||||
| {/* <Tooltip title={t('addToKnowledge')}> | |||||
| <Button | |||||
| type="text" | |||||
| className={styles.iconButton} | |||||
| onClick={navigateToDocument} | |||||
| > | |||||
| <EyeOutlined size={20} /> | |||||
| </Button> | |||||
| </Tooltip> */} | |||||
| <Tooltip title={t('addToKnowledge')}> | <Tooltip title={t('addToKnowledge')}> | ||||
| <Button | <Button | ||||
| type="text" | type="text" |
| import { useGetPagination, useSetPagination } from '@/hooks/logicHooks'; | import { useGetPagination, useSetPagination } from '@/hooks/logicHooks'; | ||||
| import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; | import { useOneNamespaceEffectsLoading } from '@/hooks/storeHooks'; | ||||
| import { IFile } from '@/interfaces/database/file-manager'; | import { IFile } from '@/interfaces/database/file-manager'; | ||||
| import { getExtension } from '@/utils/documentUtils'; | |||||
| import { PaginationProps } from 'antd'; | import { PaginationProps } from 'antd'; | ||||
| import { UploadFile } from 'antd/lib'; | import { UploadFile } from 'antd/lib'; | ||||
| import { useCallback, useEffect, useMemo, useState } from 'react'; | import { useCallback, useEffect, useMemo, useState } from 'react'; | ||||
| return { handleBreadcrumbClick }; | return { handleBreadcrumbClick }; | ||||
| }; | }; | ||||
| export const useNavigateToDocument = (documentId: string, name: string) => { | |||||
| const navigate = useNavigate(); | |||||
| const navigateToDocument = () => { | |||||
| navigate(`/document/${documentId}?ext=${getExtension(name)}`); | |||||
| }; | |||||
| return navigateToDocument; | |||||
| }; |
| import { useSelectFileList } from '@/hooks/fileManagerHooks'; | import { useSelectFileList } from '@/hooks/fileManagerHooks'; | ||||
| import { IFile } from '@/interfaces/database/file-manager'; | import { IFile } from '@/interfaces/database/file-manager'; | ||||
| import { formatDate } from '@/utils/date'; | 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 { ColumnsType } from 'antd/es/table'; | ||||
| import ActionCell from './action-cell'; | import ActionCell from './action-cell'; | ||||
| import FileToolbar from './file-toolbar'; | import FileToolbar from './file-toolbar'; | ||||
| import FolderCreateModal from './folder-create-modal'; | import FolderCreateModal from './folder-create-modal'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const { Text } = Typography; | |||||
| const FileManager = () => { | const FileManager = () => { | ||||
| const { t } = useTranslate('fileManager'); | const { t } = useTranslate('fileManager'); | ||||
| const fileList = useSelectFileList(); | const fileList = useSelectFileList(); | ||||
| title: t('name'), | title: t('name'), | ||||
| dataIndex: 'name', | dataIndex: 'name', | ||||
| key: 'name', | key: 'name', | ||||
| fixed: 'left', | |||||
| render(value, record) { | render(value, record) { | ||||
| return ( | return ( | ||||
| <Flex gap={10} align="center"> | <Flex gap={10} align="center"> | ||||
| className={styles.linkButton} | className={styles.linkButton} | ||||
| onClick={() => navigateToOtherFolder(record.id)} | onClick={() => navigateToOtherFolder(record.id)} | ||||
| > | > | ||||
| {value} | |||||
| <Text ellipsis={{ tooltip: value }}>{value}</Text> | |||||
| </Button> | </Button> | ||||
| ) : ( | ) : ( | ||||
| value | |||||
| <Text ellipsis={{ tooltip: value }}>{value}</Text> | |||||
| )} | )} | ||||
| </Flex> | </Flex> | ||||
| ); | ); | ||||
| rowSelection={rowSelection} | rowSelection={rowSelection} | ||||
| loading={loading} | loading={loading} | ||||
| pagination={pagination} | pagination={pagination} | ||||
| scroll={{ scrollToFirstRowOnChange: true, x: '100%' }} | |||||
| /> | /> | ||||
| <RenameModal | <RenameModal | ||||
| visible={fileRenameVisible} | visible={fileRenameVisible} |
| import { BaseState } from '@/interfaces/common'; | import { BaseState } from '@/interfaces/common'; | ||||
| import { IFile, IFolder } from '@/interfaces/database/file-manager'; | import { IFile, IFolder } from '@/interfaces/database/file-manager'; | ||||
| import i18n from '@/locales/config'; | import i18n from '@/locales/config'; | ||||
| import fileManagerService from '@/services/fileManagerService'; | |||||
| import fileManagerService, { | |||||
| getDocumentFile, | |||||
| } from '@/services/fileManagerService'; | |||||
| import { message } from 'antd'; | import { message } from 'antd'; | ||||
| import omit from 'lodash/omit'; | import omit from 'lodash/omit'; | ||||
| import { DvaModel } from 'umi'; | import { DvaModel } from 'umi'; | ||||
| } | } | ||||
| return data.retcode; | return data.retcode; | ||||
| }, | }, | ||||
| *getDocumentFile({ payload = {} }, { call }) { | |||||
| const ret = yield call(getDocumentFile, payload); | |||||
| return ret; | |||||
| }, | |||||
| }, | }, | ||||
| }; | }; | ||||
| 文心一言: 'wenxin', | 文心一言: 'wenxin', | ||||
| Ollama: 'ollama', | Ollama: 'ollama', | ||||
| Xinference: 'xinference', | Xinference: 'xinference', | ||||
| DeepSeek: 'deepseek', | |||||
| }; | }; | ||||
| const LlmIcon = ({ name }: { name: string }) => { | const LlmIcon = ({ name }: { name: string }) => { |
| path: '/flow', | path: '/flow', | ||||
| component: '@/pages/flow', | component: '@/pages/flow', | ||||
| }, | }, | ||||
| { | |||||
| path: 'document/:id', | |||||
| component: '@/pages/document-viewer', | |||||
| }, | |||||
| ], | ], | ||||
| }, | }, | ||||
| { | { |
| import api from '@/utils/api'; | import api from '@/utils/api'; | ||||
| import registerServer from '@/utils/registerServer'; | import registerServer from '@/utils/registerServer'; | ||||
| import request from '@/utils/request'; | import request from '@/utils/request'; | ||||
| import pureRequest from 'axios'; | |||||
| const { | const { | ||||
| listFile, | listFile, | ||||
| getAllParentFolder, | getAllParentFolder, | ||||
| createFolder, | createFolder, | ||||
| connectFileToKnowledge, | connectFileToKnowledge, | ||||
| get_document_file, | |||||
| getFile, | |||||
| } = api; | } = api; | ||||
| const methods = { | const methods = { | ||||
| url: connectFileToKnowledge, | url: connectFileToKnowledge, | ||||
| method: 'post', | method: 'post', | ||||
| }, | }, | ||||
| getDocumentFile: { | |||||
| url: getFile, | |||||
| method: 'get', | |||||
| responseType: 'blob', | |||||
| }, | |||||
| } as const; | } as const; | ||||
| const fileManagerService = registerServer<keyof typeof methods>( | const fileManagerService = registerServer<keyof typeof methods>( | ||||
| ); | ); | ||||
| export default fileManagerService; | 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); | |||||
| }); | |||||
| }; |
| getAllParentFolder: `${api_host}/file/all_parent_folder`, | getAllParentFolder: `${api_host}/file/all_parent_folder`, | ||||
| createFolder: `${api_host}/file/create`, | createFolder: `${api_host}/file/create`, | ||||
| connectFileToKnowledge: `${api_host}/file2document/convert`, | connectFileToKnowledge: `${api_host}/file2document/convert`, | ||||
| getFile: `${api_host}/file/get`, | |||||
| }; | }; |
| import omit from 'lodash/omit'; | |||||
| import { RequestMethod } from 'umi-request'; | import { RequestMethod } from 'umi-request'; | ||||
| type Service<T extends string> = Record<T, (params: any) => any>; | type Service<T extends string> = Record<T, (params: any) => any>; | ||||
| for (let key in opt) { | for (let key in opt) { | ||||
| server[key] = (params: any, urlAppendix?: string) => { | server[key] = (params: any, urlAppendix?: string) => { | ||||
| let url = opt[key].url; | let url = opt[key].url; | ||||
| const requestOptions = opt[key]; | |||||
| if (urlAppendix) { | if (urlAppendix) { | ||||
| url = url + '/' + urlAppendix; | url = url + '/' + urlAppendix; | ||||
| } | } | ||||
| if (opt[key].method === 'get' || opt[key].method === 'GET') { | if (opt[key].method === 'get' || opt[key].method === 'GET') { | ||||
| return request.get(url, { | return request.get(url, { | ||||
| ...omit(requestOptions, ['method', 'url']), | |||||
| params, | params, | ||||
| }); | }); | ||||
| } | } |
| import { SettingModelState } from '@/pages/user-setting/model'; | import { SettingModelState } from '@/pages/user-setting/model'; | ||||
| declare module 'lodash'; | declare module 'lodash'; | ||||
| declare module 'react-file-viewer'; | |||||
| function useSelector<TState = RootState, TSelected = unknown>( | function useSelector<TState = RootState, TSelected = unknown>( | ||||
| selector: (state: TState) => TSelected, | selector: (state: TState) => TSelected, |