### What problem does this PR solve? feat: delete the added model #503 feat: display an error message when the requested file fails to parse #684 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.6.0
| devtool: 'source-map', | devtool: 'source-map', | ||||
| proxy: { | proxy: { | ||||
| '/v1': { | '/v1': { | ||||
| target: 'http://123.60.95.134:9380/', | |||||
| target: '', | |||||
| changeOrigin: true, | changeOrigin: true, | ||||
| // pathRewrite: { '^/v1': '/v1' }, | // pathRewrite: { '^/v1': '/v1' }, | ||||
| }, | }, |
| "js-base64": "^3.7.5", | "js-base64": "^3.7.5", | ||||
| "jsencrypt": "^3.3.2", | "jsencrypt": "^3.3.2", | ||||
| "lodash": "^4.17.21", | "lodash": "^4.17.21", | ||||
| "mammoth": "^1.7.2", | |||||
| "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", |
| ) : null; | ) : null; | ||||
| const DocumentPreviewer = ({ chunk, documentId, visible }: IProps) => { | const DocumentPreviewer = ({ chunk, documentId, visible }: IProps) => { | ||||
| const url = useGetDocumentUrl(documentId); | |||||
| const getDocumentUrl = useGetDocumentUrl(documentId); | |||||
| const { highlights: state, setWidthAndHeight } = useGetChunkHighlights(chunk); | const { highlights: state, setWidthAndHeight } = useGetChunkHighlights(chunk); | ||||
| const ref = useRef<(highlight: IHighlight) => void>(() => {}); | const ref = useRef<(highlight: IHighlight) => void>(() => {}); | ||||
| const [loaded, setLoaded] = useState(false); | const [loaded, setLoaded] = useState(false); | ||||
| return ( | return ( | ||||
| <div className={styles.documentContainer}> | <div className={styles.documentContainer}> | ||||
| <PdfLoader | <PdfLoader | ||||
| url={url} | |||||
| url={getDocumentUrl()} | |||||
| beforeLoad={<Skeleton active />} | beforeLoad={<Skeleton active />} | ||||
| workerSrc="/pdfjs-dist/pdf.worker.min.js" | workerSrc="/pdfjs-dist/pdf.worker.min.js" | ||||
| > | > |
| mp4: 'video/mp4', | mp4: 'video/mp4', | ||||
| }; | }; | ||||
| export const Domain = 'demo.ragflow.io'; | |||||
| //#region file preview | //#region file preview | ||||
| export const Images = [ | export const Images = [ | ||||
| 'jpg', | 'jpg', | ||||
| ]; | ]; | ||||
| // Without FileViewer | // Without FileViewer | ||||
| export const ExceptiveType = ['xlsx', 'xls', 'pdf', ...Images]; | |||||
| export const ExceptiveType = ['xlsx', 'xls', 'pdf', 'docx', ...Images]; | |||||
| export const SupportedPreviewDocumentTypes = ['docx', 'csv', ...ExceptiveType]; | |||||
| export const SupportedPreviewDocumentTypes = [...ExceptiveType]; | |||||
| //#endregion | //#endregion |
| IMyLlmValue, | IMyLlmValue, | ||||
| IThirdOAIModelCollection, | IThirdOAIModelCollection, | ||||
| } from '@/interfaces/database/llm'; | } from '@/interfaces/database/llm'; | ||||
| import { IAddLlmRequestBody } from '@/interfaces/request/llm'; | |||||
| import { | |||||
| IAddLlmRequestBody, | |||||
| IDeleteLlmRequestBody, | |||||
| } from '@/interfaces/request/llm'; | |||||
| import { sortLLmFactoryListBySpecifiedOrder } from '@/utils/commonUtil'; | import { sortLLmFactoryListBySpecifiedOrder } from '@/utils/commonUtil'; | ||||
| import { useCallback, useEffect, useMemo } from 'react'; | import { useCallback, useEffect, useMemo } from 'react'; | ||||
| import { useDispatch, useSelector } from 'umi'; | import { useDispatch, useSelector } from 'umi'; | ||||
| export const useAddLlm = () => { | export const useAddLlm = () => { | ||||
| const dispatch = useDispatch(); | const dispatch = useDispatch(); | ||||
| const saveTenantInfo = useCallback( | |||||
| const addLlm = useCallback( | |||||
| (requestBody: IAddLlmRequestBody) => { | (requestBody: IAddLlmRequestBody) => { | ||||
| return dispatch<any>({ | return dispatch<any>({ | ||||
| type: 'settingModel/add_llm', | type: 'settingModel/add_llm', | ||||
| [dispatch], | [dispatch], | ||||
| ); | ); | ||||
| return saveTenantInfo; | |||||
| return addLlm; | |||||
| }; | |||||
| export const useDeleteLlm = () => { | |||||
| const dispatch = useDispatch(); | |||||
| const deleteLlm = useCallback( | |||||
| (requestBody: IDeleteLlmRequestBody) => { | |||||
| return dispatch<any>({ | |||||
| type: 'settingModel/delete_llm', | |||||
| payload: requestBody, | |||||
| }); | |||||
| }, | |||||
| [dispatch], | |||||
| ); | |||||
| return deleteLlm; | |||||
| }; | }; |
| model_type: string; | model_type: string; | ||||
| api_base?: string; // chat|embedding|speech2text|image2text | api_base?: string; // chat|embedding|speech2text|image2text | ||||
| } | } | ||||
| export interface IDeleteLlmRequestBody { | |||||
| llm_factory: string; // Ollama | |||||
| llm_name: string; | |||||
| } |
| local: 'Local uploads', | local: 'Local uploads', | ||||
| s3: 'S3 uploads', | s3: 'S3 uploads', | ||||
| preview: 'Preview', | preview: 'Preview', | ||||
| fileError: 'File error', | |||||
| }, | }, | ||||
| footer: { | footer: { | ||||
| profile: 'All rights reserved @ React', | profile: 'All rights reserved @ React', |
| local: '本地上傳', | local: '本地上傳', | ||||
| s3: 'S3 上傳', | s3: 'S3 上傳', | ||||
| preview: '預覽', | preview: '預覽', | ||||
| fileError: '文件錯誤', | |||||
| }, | }, | ||||
| footer: { | footer: { | ||||
| profile: '“保留所有權利 @ react”', | profile: '“保留所有權利 @ react”', |
| local: '本地上传', | local: '本地上传', | ||||
| s3: 'S3 上传', | s3: 'S3 上传', | ||||
| preview: '预览', | preview: '预览', | ||||
| fileError: '文件错误', | |||||
| }, | }, | ||||
| footer: { | footer: { | ||||
| profile: 'All rights reserved @ React', | profile: 'All rights reserved @ React', |
| { | { | ||||
| key: 'process_duation', | key: 'process_duation', | ||||
| label: t('processDuration'), | label: t('processDuration'), | ||||
| children: record.process_duation, | |||||
| children: `${record.process_duation} s`, | |||||
| }, | }, | ||||
| { | { | ||||
| key: 'progress_msg', | key: 'progress_msg', |
| import LineChart from '@/components/line-chart'; | import LineChart from '@/components/line-chart'; | ||||
| import { Domain } from '@/constants/common'; | |||||
| import { useSetModalState, useTranslate } from '@/hooks/commonHooks'; | import { useSetModalState, useTranslate } from '@/hooks/commonHooks'; | ||||
| import { IModalProps } from '@/interfaces/common'; | import { IModalProps } from '@/interfaces/common'; | ||||
| import { IDialog, IStats } from '@/interfaces/database/chat'; | import { IDialog, IStats } from '@/interfaces/database/chat'; | ||||
| <Flex gap={8} vertical> | <Flex gap={8} vertical> | ||||
| {t('serviceApiEndpoint')} | {t('serviceApiEndpoint')} | ||||
| <Paragraph copyable className={styles.linkText}> | <Paragraph copyable className={styles.linkText}> | ||||
| https://demo.ragflow.io/v1/api/ | |||||
| https:// | |||||
| {location.hostname === Domain ? Domain : '<YOUR_MACHINE_IP>'} | |||||
| /v1/api/ | |||||
| </Paragraph> | </Paragraph> | ||||
| </Flex> | </Flex> | ||||
| <Space size={'middle'}> | <Space size={'middle'}> |
| import CopyToClipboard from '@/components/copy-to-clipboard'; | import CopyToClipboard from '@/components/copy-to-clipboard'; | ||||
| import HightLightMarkdown from '@/components/highlight-markdown'; | import HightLightMarkdown from '@/components/highlight-markdown'; | ||||
| import { Domain } from '@/constants/common'; | |||||
| import { useTranslate } from '@/hooks/commonHooks'; | import { useTranslate } from '@/hooks/commonHooks'; | ||||
| import { IModalProps } from '@/interfaces/common'; | import { IModalProps } from '@/interfaces/common'; | ||||
| import { Card, Modal, Tabs, TabsProps } from 'antd'; | import { Card, Modal, Tabs, TabsProps } from 'antd'; | ||||
| const text = ` | const text = ` | ||||
| ~~~ html | ~~~ html | ||||
| <iframe | <iframe | ||||
| src="https://demo.ragflow.io/chat/share?shared_id=${token}" | |||||
| src="https://${Domain}/chat/share?shared_id=${token}" | |||||
| style="width: 100%; height: 100%; min-height: 600px" | style="width: 100%; height: 100%; min-height: 600px" | ||||
| frameborder="0" | frameborder="0" | ||||
| > | > |
| // Copyright (c) 2017 PlanGrid, Inc. | |||||
| .docxViewerWrapper { | |||||
| overflow-y: scroll; | |||||
| height: 100%; | |||||
| width: 100%; | |||||
| .box { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| } | |||||
| :global(.document-container) { | |||||
| padding: 30px; | |||||
| width: 700px; | |||||
| background: white; | |||||
| margin: auto; | |||||
| } | |||||
| html, | |||||
| bodyaddress, | |||||
| blockquote, | |||||
| body, | |||||
| dd, | |||||
| div, | |||||
| dl, | |||||
| dt, | |||||
| fieldset, | |||||
| form, | |||||
| frame, | |||||
| frameset, | |||||
| h1, | |||||
| h2, | |||||
| h3, | |||||
| h4, | |||||
| h5, | |||||
| h6, | |||||
| noframes, | |||||
| ol, | |||||
| p, | |||||
| ul, | |||||
| center, | |||||
| dir, | |||||
| hr, | |||||
| menu, | |||||
| pre { | |||||
| display: block; | |||||
| unicode-bidi: embed; | |||||
| } | |||||
| li { | |||||
| display: list-item; | |||||
| list-style-type: disc; | |||||
| } | |||||
| head { | |||||
| display: none; | |||||
| } | |||||
| table { | |||||
| display: table; | |||||
| } | |||||
| img { | |||||
| width: 100%; | |||||
| } | |||||
| tr { | |||||
| display: table-row; | |||||
| } | |||||
| thead { | |||||
| display: table-header-group; | |||||
| } | |||||
| tbody { | |||||
| display: table-row-group; | |||||
| } | |||||
| tfoot { | |||||
| display: table-footer-group; | |||||
| } | |||||
| col { | |||||
| display: table-column; | |||||
| } | |||||
| colgroup { | |||||
| display: table-column-group; | |||||
| } | |||||
| th { | |||||
| display: table-cell; | |||||
| } | |||||
| td { | |||||
| display: table-cell; | |||||
| border-bottom: 1px solid #ccc; | |||||
| border-right: 1px solid #ccc; | |||||
| padding: 0.2em 0.5em; | |||||
| } | |||||
| caption { | |||||
| display: table-caption; | |||||
| } | |||||
| th { | |||||
| font-weight: bolder; | |||||
| text-align: center; | |||||
| } | |||||
| caption { | |||||
| text-align: center; | |||||
| } | |||||
| body { | |||||
| margin: 8px; | |||||
| } | |||||
| h1 { | |||||
| font-size: 2em; | |||||
| margin: 0.67em 0; | |||||
| } | |||||
| h2 { | |||||
| font-size: 1.5em; | |||||
| margin: 0.75em 0; | |||||
| } | |||||
| h3 { | |||||
| font-size: 1.17em; | |||||
| margin: 0.83em 0; | |||||
| } | |||||
| h4, | |||||
| p, | |||||
| blockquote, | |||||
| ul, | |||||
| fieldset, | |||||
| form, | |||||
| ol, | |||||
| dl, | |||||
| dir, | |||||
| menu { | |||||
| margin: 1.12em 0; | |||||
| } | |||||
| h5 { | |||||
| font-size: 0.83em; | |||||
| margin: 1.5em 0; | |||||
| } | |||||
| h6 { | |||||
| font-size: 0.75em; | |||||
| margin: 1.67em 0; | |||||
| } | |||||
| h1, | |||||
| h2, | |||||
| h3, | |||||
| h4, | |||||
| h5, | |||||
| h6, | |||||
| b, | |||||
| strong { | |||||
| font-weight: bolder; | |||||
| } | |||||
| blockquote { | |||||
| margin-left: 40px; | |||||
| margin-right: 40px; | |||||
| } | |||||
| i, | |||||
| cite, | |||||
| em, | |||||
| var, | |||||
| address { | |||||
| font-style: italic; | |||||
| } | |||||
| pre, | |||||
| tt, | |||||
| code, | |||||
| kbd, | |||||
| samp { | |||||
| font-family: monospace; | |||||
| } | |||||
| pre { | |||||
| white-space: pre; | |||||
| } | |||||
| button, | |||||
| textarea, | |||||
| input, | |||||
| select { | |||||
| display: inline-block; | |||||
| } | |||||
| big { | |||||
| font-size: 1.17em; | |||||
| } | |||||
| small, | |||||
| sub, | |||||
| sup { | |||||
| font-size: 0.83em; | |||||
| } | |||||
| sub { | |||||
| vertical-align: sub; | |||||
| } | |||||
| sup { | |||||
| vertical-align: super; | |||||
| } | |||||
| table { | |||||
| border-spacing: 2px; | |||||
| } | |||||
| thead, | |||||
| tbody, | |||||
| tfoot { | |||||
| vertical-align: middle; | |||||
| } | |||||
| td, | |||||
| th, | |||||
| tr { | |||||
| vertical-align: inherit; | |||||
| } | |||||
| s, | |||||
| strike, | |||||
| del { | |||||
| text-decoration: line-through; | |||||
| } | |||||
| hr { | |||||
| border: 1px inset; | |||||
| } | |||||
| ol, | |||||
| ul, | |||||
| dir, | |||||
| menu, | |||||
| dd { | |||||
| margin-left: 40px; | |||||
| } | |||||
| ol { | |||||
| list-style-type: decimal; | |||||
| } | |||||
| ol ul, | |||||
| ol ul, | |||||
| ul ol, | |||||
| ul ol, | |||||
| ul ul, | |||||
| ul ul, | |||||
| ol ol, | |||||
| ol ol { | |||||
| margin-top: 0; | |||||
| margin-bottom: 0; | |||||
| } | |||||
| u, | |||||
| ins { | |||||
| text-decoration: underline; | |||||
| } | |||||
| br:before { | |||||
| content: '\A'; | |||||
| white-space: pre-line; | |||||
| } | |||||
| center { | |||||
| text-align: center; | |||||
| } | |||||
| :link, | |||||
| :visited { | |||||
| text-decoration: underline; | |||||
| } | |||||
| :focus { | |||||
| outline: thin dotted invert; | |||||
| } | |||||
| /* Begin bidirectionality settings (do not change) */ | |||||
| BDO[DIR='ltr'] { | |||||
| direction: ltr; | |||||
| unicode-bidi: bidi-override; | |||||
| } | |||||
| BDO[DIR='rtl'] { | |||||
| direction: rtl; | |||||
| unicode-bidi: bidi-override; | |||||
| } | |||||
| *[DIR='ltr'] { | |||||
| direction: ltr; | |||||
| unicode-bidi: embed; | |||||
| } | |||||
| *[DIR='rtl'] { | |||||
| direction: rtl; | |||||
| unicode-bidi: embed; | |||||
| } | |||||
| @media print { | |||||
| h1 { | |||||
| page-break-before: always; | |||||
| } | |||||
| h1, | |||||
| h2, | |||||
| h3, | |||||
| h4, | |||||
| h5, | |||||
| h6 { | |||||
| page-break-after: avoid; | |||||
| } | |||||
| ul, | |||||
| ol, | |||||
| dl { | |||||
| page-break-before: avoid; | |||||
| } | |||||
| } | |||||
| } |
| import { Spin } from 'antd'; | |||||
| import FileError from '../file-error'; | |||||
| import { useFetchDocx } from '../hooks'; | |||||
| import styles from './index.less'; | |||||
| const Docx = ({ filePath }: { filePath: string }) => { | |||||
| const { succeed, containerRef } = useFetchDocx(filePath); | |||||
| return ( | |||||
| <> | |||||
| {succeed ? ( | |||||
| <section className={styles.docxViewerWrapper}> | |||||
| <div id="docx" ref={containerRef} className={styles.box}> | |||||
| <Spin /> | |||||
| </div> | |||||
| </section> | |||||
| ) : ( | |||||
| <FileError></FileError> | |||||
| )} | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default Docx; |
| import jsPreviewExcel from '@js-preview/excel'; | |||||
| import '@js-preview/excel/lib/index.css'; | import '@js-preview/excel/lib/index.css'; | ||||
| import { useEffect } from 'react'; | |||||
| import FileError from '../file-error'; | |||||
| import { useFetchExcel } from '../hooks'; | |||||
| const Excel = ({ filePath }: { filePath: string }) => { | 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); | |||||
| }); | |||||
| } | |||||
| }; | |||||
| }; | |||||
| const { status, containerRef } = useFetchExcel(filePath); | |||||
| useEffect(() => { | |||||
| fetchDocument(); | |||||
| }, []); | |||||
| return <div id="excel" style={{ height: '100%' }}></div>; | |||||
| return ( | |||||
| <div | |||||
| id="excel" | |||||
| ref={containerRef} | |||||
| style={{ height: '100%', width: '100%' }} | |||||
| > | |||||
| {status || <FileError></FileError>} | |||||
| </div> | |||||
| ); | |||||
| }; | }; | ||||
| export default Excel; | export default Excel; |
| .errorWrapper { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| } |
| import { Alert, Flex } from 'antd'; | |||||
| import { useTranslate } from '@/hooks/commonHooks'; | |||||
| import styles from './index.less'; | |||||
| const FileError = () => { | |||||
| const { t } = useTranslate('fileManager'); | |||||
| return ( | |||||
| <Flex align="center" justify="center" className={styles.errorWrapper}> | |||||
| <Alert type="error" message={<h1>{t('fileError')}</h1>}></Alert> | |||||
| </Flex> | |||||
| ); | |||||
| }; | |||||
| export default FileError; |
| import jsPreviewExcel from '@js-preview/excel'; | |||||
| import axios from 'axios'; | |||||
| import mammoth from 'mammoth'; | |||||
| import { useCallback, useEffect, useRef, useState } from 'react'; | |||||
| const useFetchDocument = () => { | |||||
| const fetchDocument = useCallback((api: string) => { | |||||
| return axios.get(api, { responseType: 'arraybuffer' }); | |||||
| }, []); | |||||
| return fetchDocument; | |||||
| }; | |||||
| export const useFetchExcel = (filePath: string) => { | |||||
| const [status, setStatus] = useState(true); | |||||
| const fetchDocument = useFetchDocument(); | |||||
| const containerRef = useRef<HTMLDivElement>(null); | |||||
| const fetchDocumentAsync = useCallback(async () => { | |||||
| let myExcelPreviewer; | |||||
| if (containerRef.current) { | |||||
| myExcelPreviewer = jsPreviewExcel.init(containerRef.current); | |||||
| } | |||||
| const jsonFile = await fetchDocument(filePath); | |||||
| myExcelPreviewer | |||||
| ?.preview(jsonFile.data) | |||||
| .then(() => { | |||||
| console.log('succeed'); | |||||
| setStatus(true); | |||||
| }) | |||||
| .catch((e) => { | |||||
| console.warn('failed', e); | |||||
| myExcelPreviewer.destroy(); | |||||
| setStatus(false); | |||||
| }); | |||||
| }, [filePath, fetchDocument]); | |||||
| useEffect(() => { | |||||
| fetchDocumentAsync(); | |||||
| }, [fetchDocumentAsync]); | |||||
| return { status, containerRef }; | |||||
| }; | |||||
| export const useFetchDocx = (filePath: string) => { | |||||
| const [succeed, setSucceed] = useState(true); | |||||
| const fetchDocument = useFetchDocument(); | |||||
| const containerRef = useRef<HTMLDivElement>(null); | |||||
| const fetchDocumentAsync = useCallback(async () => { | |||||
| const jsonFile = await fetchDocument(filePath); | |||||
| mammoth | |||||
| .convertToHtml( | |||||
| { arrayBuffer: jsonFile.data }, | |||||
| { includeDefaultStyleMap: true }, | |||||
| ) | |||||
| .then((result) => { | |||||
| setSucceed(true); | |||||
| const docEl = document.createElement('div'); | |||||
| docEl.className = 'document-container'; | |||||
| docEl.innerHTML = result.value; | |||||
| const container = containerRef.current; | |||||
| if (container) { | |||||
| container.innerHTML = docEl.outerHTML; | |||||
| } | |||||
| }) | |||||
| .catch((a) => { | |||||
| setSucceed(false); | |||||
| console.warn('alexei: something went wrong', a); | |||||
| }); | |||||
| }, [filePath, fetchDocument]); | |||||
| useEffect(() => { | |||||
| fetchDocumentAsync(); | |||||
| }, [fetchDocumentAsync]); | |||||
| return { succeed, containerRef }; | |||||
| }; |
| import { ExceptiveType, Images } from '@/constants/common'; | |||||
| import { Images } from '@/constants/common'; | |||||
| import { api_host } from '@/utils/api'; | import { api_host } from '@/utils/api'; | ||||
| import { Flex, Image } from 'antd'; | import { Flex, Image } from 'antd'; | ||||
| import FileViewer from 'react-file-viewer'; | |||||
| import { useParams, useSearchParams } from 'umi'; | import { useParams, useSearchParams } from 'umi'; | ||||
| import Docx from './docx'; | |||||
| import Excel from './excel'; | import Excel from './excel'; | ||||
| import Pdf from './pdf'; | import Pdf from './pdf'; | ||||
| // TODO: The interface returns an incorrect content-type for the SVG. | // TODO: The interface returns an incorrect content-type for the SVG. | ||||
| const isNotExceptiveType = (ext: string) => ExceptiveType.indexOf(ext) === -1; | |||||
| const DocumentViewer = () => { | const DocumentViewer = () => { | ||||
| const { id: documentId } = useParams(); | const { id: documentId } = useParams(); | ||||
| const api = `${api_host}/file/get/${documentId}`; | const api = `${api_host}/file/get/${documentId}`; | ||||
| const [currentQueryParameters] = useSearchParams(); | const [currentQueryParameters] = useSearchParams(); | ||||
| const ext = currentQueryParameters.get('ext'); | const ext = currentQueryParameters.get('ext'); | ||||
| const onError = (e: any) => { | |||||
| console.error(e, 'error in file-viewer'); | |||||
| }; | |||||
| return ( | return ( | ||||
| <section className={styles.viewerWrapper}> | <section className={styles.viewerWrapper}> | ||||
| {Images.includes(ext!) && ( | {Images.includes(ext!) && ( | ||||
| )} | )} | ||||
| {ext === 'pdf' && <Pdf url={api}></Pdf>} | {ext === 'pdf' && <Pdf url={api}></Pdf>} | ||||
| {(ext === 'xlsx' || ext === 'xls') && <Excel filePath={api}></Excel>} | {(ext === 'xlsx' || ext === 'xls') && <Excel filePath={api}></Excel>} | ||||
| {isNotExceptiveType(ext!) && ( | |||||
| <FileViewer fileType={ext} filePath={api} onError={onError} /> | |||||
| )} | |||||
| {ext === 'docx' && <Docx filePath={api}></Docx>} | |||||
| </section> | </section> | ||||
| ); | ); | ||||
| }; | }; |
| import { Skeleton } from 'antd'; | import { Skeleton } from 'antd'; | ||||
| import { PdfHighlighter, PdfLoader } from 'react-pdf-highlighter'; | import { PdfHighlighter, PdfLoader } from 'react-pdf-highlighter'; | ||||
| import FileError from '../file-error'; | |||||
| interface IProps { | interface IProps { | ||||
| url: string; | url: string; | ||||
| const resetHash = () => {}; | const resetHash = () => {}; | ||||
| return ( | return ( | ||||
| <div style={{ width: '100%' }}> | |||||
| <div style={{ width: '100%', height: '100%' }}> | |||||
| <PdfLoader | <PdfLoader | ||||
| url={url} | url={url} | ||||
| beforeLoad={<Skeleton active />} | beforeLoad={<Skeleton active />} | ||||
| workerSrc="/pdfjs-dist/pdf.worker.min.js" | workerSrc="/pdfjs-dist/pdf.worker.min.js" | ||||
| errorMessage={<FileError></FileError>} | |||||
| onError={(e) => { | |||||
| console.warn(e); | |||||
| }} | |||||
| > | > | ||||
| {(pdfDocument) => { | {(pdfDocument) => { | ||||
| return ( | return ( |
| import { Icon, useNavigate } from 'umi'; | import { Icon, useNavigate } from 'umi'; | ||||
| import RightPanel from './right-panel'; | import RightPanel from './right-panel'; | ||||
| import { Domain } from '@/constants/common'; | |||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| const Login = () => { | const Login = () => { | ||||
| Sign in with Google | Sign in with Google | ||||
| </div> | </div> | ||||
| </Button> */} | </Button> */} | ||||
| {location.host === 'demo.ragflow.io' && ( | |||||
| {location.host === Domain && ( | |||||
| <Button | <Button | ||||
| block | block | ||||
| size="large" | size="large" |
| } | } | ||||
| return retcode; | return retcode; | ||||
| }, | }, | ||||
| *delete_llm({ payload = {} }, { call, put }) { | |||||
| const { data } = yield call(userService.delete_llm, payload); | |||||
| const { retcode } = data; | |||||
| if (retcode === 0) { | |||||
| message.success(i18n.t('message.deleted')); | |||||
| yield put({ type: 'my_llm' }); | |||||
| yield put({ type: 'factories_list' }); | |||||
| } | |||||
| return retcode; | |||||
| }, | |||||
| }, | }, | ||||
| }; | }; | ||||
| export default model; | export default model; |
| import { useSetModalState } from '@/hooks/commonHooks'; | |||||
| import { useSetModalState, useShowDeleteConfirm } from '@/hooks/commonHooks'; | |||||
| import { | import { | ||||
| IApiKeySavingParams, | IApiKeySavingParams, | ||||
| ISystemModelSettingSavingParams, | ISystemModelSettingSavingParams, | ||||
| useAddLlm, | useAddLlm, | ||||
| useDeleteLlm, | |||||
| useFetchLlmList, | useFetchLlmList, | ||||
| useSaveApiKey, | useSaveApiKey, | ||||
| useSaveTenantInfo, | useSaveTenantInfo, | ||||
| selectedLlmFactory, | selectedLlmFactory, | ||||
| }; | }; | ||||
| }; | }; | ||||
| export const useHandleDeleteLlm = (llmFactory: string) => { | |||||
| const deleteLlm = useDeleteLlm(); | |||||
| const showDeleteConfirm = useShowDeleteConfirm(); | |||||
| const handleDeleteLlm = (name: string) => () => { | |||||
| showDeleteConfirm({ | |||||
| onOk: async () => { | |||||
| deleteLlm({ llm_factory: llmFactory, llm_name: name }); | |||||
| }, | |||||
| }); | |||||
| }; | |||||
| return { handleDeleteLlm }; | |||||
| }; |
| useFetchLlmFactoryListOnMount, | useFetchLlmFactoryListOnMount, | ||||
| useFetchMyLlmListOnMount, | useFetchMyLlmListOnMount, | ||||
| } from '@/hooks/llmHooks'; | } from '@/hooks/llmHooks'; | ||||
| import { SettingOutlined, UserOutlined } from '@ant-design/icons'; | |||||
| import { | |||||
| CloseCircleOutlined, | |||||
| SettingOutlined, | |||||
| UserOutlined, | |||||
| } from '@ant-design/icons'; | |||||
| import { | import { | ||||
| Avatar, | Avatar, | ||||
| Button, | Button, | ||||
| Space, | Space, | ||||
| Spin, | Spin, | ||||
| Tag, | Tag, | ||||
| Tooltip, | |||||
| Typography, | Typography, | ||||
| } from 'antd'; | } from 'antd'; | ||||
| import { useCallback } from 'react'; | import { useCallback } from 'react'; | ||||
| import { isLocalLlmFactory } from '../utils'; | import { isLocalLlmFactory } from '../utils'; | ||||
| import ApiKeyModal from './api-key-modal'; | import ApiKeyModal from './api-key-modal'; | ||||
| import { | import { | ||||
| useHandleDeleteLlm, | |||||
| useSelectModelProvidersLoading, | useSelectModelProvidersLoading, | ||||
| useSubmitApiKey, | useSubmitApiKey, | ||||
| useSubmitOllama, | useSubmitOllama, | ||||
| const ModelCard = ({ item, clickApiKey }: IModelCardProps) => { | const ModelCard = ({ item, clickApiKey }: IModelCardProps) => { | ||||
| const { visible, switchVisible } = useSetModalState(); | const { visible, switchVisible } = useSetModalState(); | ||||
| const { t } = useTranslate('setting'); | const { t } = useTranslate('setting'); | ||||
| const { handleDeleteLlm } = useHandleDeleteLlm(item.name); | |||||
| const handleApiKeyClick = () => { | const handleApiKeyClick = () => { | ||||
| clickApiKey(item.name); | clickApiKey(item.name); | ||||
| <List.Item> | <List.Item> | ||||
| <Space> | <Space> | ||||
| {item.name} <Tag color="#b8b8b8">{item.type}</Tag> | {item.name} <Tag color="#b8b8b8">{item.type}</Tag> | ||||
| <Tooltip title={t('delete', { keyPrefix: 'common' })}> | |||||
| <Button type={'text'} onClick={handleDeleteLlm(item.name)}> | |||||
| <CloseCircleOutlined style={{ color: '#D92D20' }} /> | |||||
| </Button> | |||||
| </Tooltip> | |||||
| </Space> | </Space> | ||||
| </List.Item> | </List.Item> | ||||
| )} | )} |
| set_api_key, | set_api_key, | ||||
| set_tenant_info, | set_tenant_info, | ||||
| add_llm, | add_llm, | ||||
| delete_llm, | |||||
| } = api; | } = api; | ||||
| const methods = { | const methods = { | ||||
| url: add_llm, | url: add_llm, | ||||
| method: 'post', | method: 'post', | ||||
| }, | }, | ||||
| delete_llm: { | |||||
| url: delete_llm, | |||||
| method: 'post', | |||||
| }, | |||||
| } as const; | } as const; | ||||
| const userService = registerServer<keyof typeof methods>(methods, request); | const userService = registerServer<keyof typeof methods>(methods, request); |
| my_llm: `${api_host}/llm/my_llms`, | my_llm: `${api_host}/llm/my_llms`, | ||||
| set_api_key: `${api_host}/llm/set_api_key`, | set_api_key: `${api_host}/llm/set_api_key`, | ||||
| add_llm: `${api_host}/llm/add_llm`, | add_llm: `${api_host}/llm/add_llm`, | ||||
| delete_llm: `${api_host}/llm/delete_llm`, | |||||
| // knowledge base | // knowledge base | ||||
| kb_list: `${api_host}/kb/list`, | kb_list: `${api_host}/kb/list`, |
| 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, |