mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-13 02:19:10 +08:00
### What problem does this PR solve? fix: fix uploaded file time error #680 feat: support preview of word and excel #684 ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue)
This commit is contained in:
parent
99be226c7c
commit
793e29f23a
@ -1,22 +1,24 @@
|
|||||||
import { api_host } from '@/utils/api';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
interface IProps extends React.PropsWithChildren {
|
interface IProps extends React.PropsWithChildren {
|
||||||
documentId: string;
|
link: string;
|
||||||
preventDefault?: boolean;
|
preventDefault?: boolean;
|
||||||
|
color?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NewDocumentLink = ({
|
const NewDocumentLink = ({
|
||||||
children,
|
children,
|
||||||
documentId,
|
link,
|
||||||
preventDefault = false,
|
preventDefault = false,
|
||||||
|
color = 'rgb(15, 79, 170)',
|
||||||
}: IProps) => {
|
}: IProps) => {
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
target="_blank"
|
target="_blank"
|
||||||
onClick={!preventDefault ? undefined : (e) => e.preventDefault()}
|
onClick={!preventDefault ? undefined : (e) => e.preventDefault()}
|
||||||
href={`${api_host}/document/get/${documentId}`}
|
href={link}
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
|
style={{ color }}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</a>
|
</a>
|
||||||
|
@ -68,3 +68,23 @@ export const FileMimeTypeMap = {
|
|||||||
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||||
mp4: 'video/mp4',
|
mp4: 'video/mp4',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//#region file preview
|
||||||
|
export const Images = [
|
||||||
|
'jpg',
|
||||||
|
'jpeg',
|
||||||
|
'png',
|
||||||
|
'gif',
|
||||||
|
'bmp',
|
||||||
|
'tif',
|
||||||
|
'tiff',
|
||||||
|
'webp',
|
||||||
|
// 'svg',
|
||||||
|
'ico',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Without FileViewer
|
||||||
|
export const ExceptiveType = ['xlsx', 'xls', 'pdf', ...Images];
|
||||||
|
|
||||||
|
export const SupportedPreviewDocumentTypes = ['docx', 'csv', ...ExceptiveType];
|
||||||
|
//#endregion
|
||||||
|
@ -9,12 +9,15 @@ import { useDispatch, useSelector } from 'umi';
|
|||||||
import { useGetKnowledgeSearchParams } from './routeHook';
|
import { useGetKnowledgeSearchParams } from './routeHook';
|
||||||
import { useOneNamespaceEffectsLoading } from './storeHooks';
|
import { useOneNamespaceEffectsLoading } from './storeHooks';
|
||||||
|
|
||||||
export const useGetDocumentUrl = (documentId: string) => {
|
export const useGetDocumentUrl = (documentId?: string) => {
|
||||||
const url = useMemo(() => {
|
const getDocumentUrl = useCallback(
|
||||||
return `${api_host}/document/get/${documentId}`;
|
(id?: string) => {
|
||||||
}, [documentId]);
|
return `${api_host}/document/get/${documentId || id}`;
|
||||||
|
},
|
||||||
|
[documentId],
|
||||||
|
);
|
||||||
|
|
||||||
return url;
|
return getDocumentUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useGetChunkHighlights = (selectedChunk: IChunk) => {
|
export const useGetChunkHighlights = (selectedChunk: IChunk) => {
|
||||||
|
@ -505,6 +505,7 @@ export default {
|
|||||||
'Support for a single or bulk upload. Strictly prohibited from uploading company data or other banned files.',
|
'Support for a single or bulk upload. Strictly prohibited from uploading company data or other banned files.',
|
||||||
local: 'Local uploads',
|
local: 'Local uploads',
|
||||||
s3: 'S3 uploads',
|
s3: 'S3 uploads',
|
||||||
|
preview: 'Preview',
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
profile: 'All rights reserved @ React',
|
profile: 'All rights reserved @ React',
|
||||||
|
@ -468,6 +468,7 @@ export default {
|
|||||||
directory: '文件夾',
|
directory: '文件夾',
|
||||||
local: '本地上傳',
|
local: '本地上傳',
|
||||||
s3: 'S3 上傳',
|
s3: 'S3 上傳',
|
||||||
|
preview: '預覽',
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
profile: '“保留所有權利 @ react”',
|
profile: '“保留所有權利 @ react”',
|
||||||
|
@ -486,6 +486,7 @@ export default {
|
|||||||
directory: '文件夹',
|
directory: '文件夹',
|
||||||
local: '本地上传',
|
local: '本地上传',
|
||||||
s3: 'S3 上传',
|
s3: 'S3 上传',
|
||||||
|
preview: '预览',
|
||||||
},
|
},
|
||||||
footer: {
|
footer: {
|
||||||
profile: 'All rights reserved @ React',
|
profile: 'All rights reserved @ React',
|
||||||
|
@ -106,8 +106,8 @@ const KnowledgeFile = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('uploadDate'),
|
title: t('uploadDate'),
|
||||||
dataIndex: 'create_date',
|
dataIndex: 'create_time',
|
||||||
key: 'create_date',
|
key: 'create_time',
|
||||||
render(value) {
|
render(value) {
|
||||||
return formatDate(value);
|
return formatDate(value);
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ReactComponent as NavigationPointerIcon } from '@/assets/svg/navigation-pointer.svg';
|
import { ReactComponent as NavigationPointerIcon } from '@/assets/svg/navigation-pointer.svg';
|
||||||
import NewDocumentLink from '@/components/new-document-link';
|
import NewDocumentLink from '@/components/new-document-link';
|
||||||
|
import { useGetDocumentUrl } from '@/hooks/documentHooks';
|
||||||
import { ITestingDocument } from '@/interfaces/database/knowledge';
|
import { ITestingDocument } from '@/interfaces/database/knowledge';
|
||||||
import { isPdf } from '@/utils/documentUtils';
|
import { isPdf } from '@/utils/documentUtils';
|
||||||
import { Table, TableProps } from 'antd';
|
import { Table, TableProps } from 'antd';
|
||||||
@ -15,6 +16,7 @@ const SelectFiles = ({ handleTesting }: IProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const getDocumentUrl = useGetDocumentUrl();
|
||||||
|
|
||||||
const columns: TableProps<ITestingDocument>['columns'] = [
|
const columns: TableProps<ITestingDocument>['columns'] = [
|
||||||
{
|
{
|
||||||
@ -35,7 +37,10 @@ const SelectFiles = ({ handleTesting }: IProps) => {
|
|||||||
key: 'view',
|
key: 'view',
|
||||||
width: 50,
|
width: 50,
|
||||||
render: (_, { doc_id, doc_name }) => (
|
render: (_, { doc_id, doc_name }) => (
|
||||||
<NewDocumentLink documentId={doc_id} preventDefault={!isPdf(doc_name)}>
|
<NewDocumentLink
|
||||||
|
link={getDocumentUrl(doc_id)}
|
||||||
|
preventDefault={!isPdf(doc_name)}
|
||||||
|
>
|
||||||
<NavigationPointerIcon />
|
<NavigationPointerIcon />
|
||||||
</NewDocumentLink>
|
</NewDocumentLink>
|
||||||
),
|
),
|
||||||
|
@ -30,6 +30,7 @@ import MarkdownContent from '../markdown-content';
|
|||||||
|
|
||||||
import SvgIcon from '@/components/svg-icon';
|
import SvgIcon from '@/components/svg-icon';
|
||||||
import { useTranslate } from '@/hooks/commonHooks';
|
import { useTranslate } from '@/hooks/commonHooks';
|
||||||
|
import { useGetDocumentUrl } from '@/hooks/documentHooks';
|
||||||
import { getExtension, isPdf } from '@/utils/documentUtils';
|
import { getExtension, isPdf } from '@/utils/documentUtils';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ const MessageItem = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const userInfo = useSelectUserInfo();
|
const userInfo = useSelectUserInfo();
|
||||||
const fileThumbnails = useSelectFileThumbnails();
|
const fileThumbnails = useSelectFileThumbnails();
|
||||||
|
const getDocumentUrl = useGetDocumentUrl();
|
||||||
|
|
||||||
const isAssistant = item.role === MessageType.Assistant;
|
const isAssistant = item.role === MessageType.Assistant;
|
||||||
|
|
||||||
@ -113,7 +115,7 @@ const MessageItem = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<NewDocumentLink
|
<NewDocumentLink
|
||||||
documentId={item.doc_id}
|
link={getDocumentUrl(item.doc_id)}
|
||||||
preventDefault={!isPdf(item.doc_name)}
|
preventDefault={!isPdf(item.doc_name)}
|
||||||
>
|
>
|
||||||
{item.doc_name}
|
{item.doc_name}
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
.viewerWrapper {
|
.viewerWrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
:global {
|
:global {
|
||||||
.pdf-canvas {
|
.pdf-canvas {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
|
import { ExceptiveType, Images } from '@/constants/common';
|
||||||
import { api_host } from '@/utils/api';
|
import { api_host } from '@/utils/api';
|
||||||
|
import { Flex, Image } from 'antd';
|
||||||
import FileViewer from 'react-file-viewer';
|
import FileViewer from 'react-file-viewer';
|
||||||
import { useParams, useSearchParams } from 'umi';
|
import { useParams, useSearchParams } from 'umi';
|
||||||
import Excel from './excel';
|
import Excel from './excel';
|
||||||
|
import Pdf from './pdf';
|
||||||
|
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
|
// 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}`;
|
||||||
@ -17,8 +24,14 @@ const DocumentViewer = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={styles.viewerWrapper}>
|
<section className={styles.viewerWrapper}>
|
||||||
{ext === 'xlsx' && <Excel filePath={api}></Excel>}
|
{Images.includes(ext!) && (
|
||||||
{ext !== 'xlsx' && (
|
<Flex className={styles.image} align="center" justify="center">
|
||||||
|
<Image src={api} preview={false}></Image>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
{ext === 'pdf' && <Pdf url={api}></Pdf>}
|
||||||
|
{(ext === 'xlsx' || ext === 'xls') && <Excel filePath={api}></Excel>}
|
||||||
|
{isNotExceptiveType(ext!) && (
|
||||||
<FileViewer fileType={ext} filePath={api} onError={onError} />
|
<FileViewer fileType={ext} filePath={api} onError={onError} />
|
||||||
)}
|
)}
|
||||||
</section>
|
</section>
|
||||||
|
38
web/src/pages/document-viewer/pdf/index.tsx
Normal file
38
web/src/pages/document-viewer/pdf/index.tsx
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Skeleton } from 'antd';
|
||||||
|
import { PdfHighlighter, PdfLoader } from 'react-pdf-highlighter';
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DocumentPreviewer = ({ url }: IProps) => {
|
||||||
|
const resetHash = () => {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ width: '100%' }}>
|
||||||
|
<PdfLoader
|
||||||
|
url={url}
|
||||||
|
beforeLoad={<Skeleton active />}
|
||||||
|
workerSrc="/pdfjs-dist/pdf.worker.min.js"
|
||||||
|
>
|
||||||
|
{(pdfDocument) => {
|
||||||
|
return (
|
||||||
|
<PdfHighlighter
|
||||||
|
pdfDocument={pdfDocument}
|
||||||
|
enableAreaSelection={(event) => event.altKey}
|
||||||
|
onScrollChange={resetHash}
|
||||||
|
scrollRef={() => {}}
|
||||||
|
onSelectionFinished={() => null}
|
||||||
|
highlightTransform={() => {
|
||||||
|
return <div></div>;
|
||||||
|
}}
|
||||||
|
highlights={[]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</PdfLoader>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DocumentPreviewer;
|
@ -6,13 +6,21 @@ import {
|
|||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
DownloadOutlined,
|
DownloadOutlined,
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
|
EyeOutlined,
|
||||||
LinkOutlined,
|
LinkOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { Button, Space, Tooltip } from 'antd';
|
import { Button, Space, Tooltip } from 'antd';
|
||||||
import { useHandleDeleteFile, useNavigateToDocument } from '../hooks';
|
import { useHandleDeleteFile } from '../hooks';
|
||||||
|
|
||||||
|
import NewDocumentLink from '@/components/new-document-link';
|
||||||
|
import { SupportedPreviewDocumentTypes } from '@/constants/common';
|
||||||
|
import { getExtension } from '@/utils/documentUtils';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
|
const isSupportedPreviewDocumentType = (fileExtension: string) => {
|
||||||
|
return SupportedPreviewDocumentTypes.includes(fileExtension);
|
||||||
|
};
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
record: IFile;
|
record: IFile;
|
||||||
setCurrentRecord: (record: any) => void;
|
setCurrentRecord: (record: any) => void;
|
||||||
@ -35,7 +43,7 @@ const ActionCell = ({
|
|||||||
[documentId],
|
[documentId],
|
||||||
setSelectedRowKeys,
|
setSelectedRowKeys,
|
||||||
);
|
);
|
||||||
const navigateToDocument = useNavigateToDocument(record.id, record.name);
|
const extension = getExtension(record.name);
|
||||||
|
|
||||||
const onDownloadDocument = () => {
|
const onDownloadDocument = () => {
|
||||||
downloadFile({
|
downloadFile({
|
||||||
@ -59,15 +67,6 @@ const ActionCell = ({
|
|||||||
|
|
||||||
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"
|
||||||
@ -110,6 +109,18 @@ const ActionCell = ({
|
|||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
{isSupportedPreviewDocumentType(extension) && (
|
||||||
|
<NewDocumentLink
|
||||||
|
color="black"
|
||||||
|
link={`/document/${documentId}?ext=${extension}`}
|
||||||
|
>
|
||||||
|
<Tooltip title={t('preview')}>
|
||||||
|
<Button type="text" className={styles.iconButton}>
|
||||||
|
<EyeOutlined size={20} />
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</NewDocumentLink>
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -13,7 +13,6 @@ import {
|
|||||||
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';
|
||||||
@ -339,12 +338,3 @@ export const useHandleBreadcrumbClick = () => {
|
|||||||
|
|
||||||
return { handleBreadcrumbClick };
|
return { handleBreadcrumbClick };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useNavigateToDocument = (documentId: string, name: string) => {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const navigateToDocument = () => {
|
|
||||||
navigate(`/document/${documentId}?ext=${getExtension(name)}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
return navigateToDocument;
|
|
||||||
};
|
|
||||||
|
@ -96,8 +96,8 @@ const FileManager = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('uploadDate'),
|
title: t('uploadDate'),
|
||||||
dataIndex: 'create_date',
|
dataIndex: 'create_time',
|
||||||
key: 'create_date',
|
key: 'create_time',
|
||||||
render(text) {
|
render(text) {
|
||||||
return formatDate(text);
|
return formatDate(text);
|
||||||
},
|
},
|
||||||
|
@ -100,7 +100,7 @@ const KnowledgeCard = ({ item }: IProps) => {
|
|||||||
<div className={styles.bottomLeft}>
|
<div className={styles.bottomLeft}>
|
||||||
<CalendarOutlined className={styles.leftIcon} />
|
<CalendarOutlined className={styles.leftIcon} />
|
||||||
<span className={styles.rightText}>
|
<span className={styles.rightText}>
|
||||||
{formatDate(item.update_date)}
|
{formatDate(item.update_time)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{/* <Avatar.Group size={25}>
|
{/* <Avatar.Group size={25}>
|
||||||
|
@ -88,12 +88,13 @@ const routes = [
|
|||||||
path: '/flow',
|
path: '/flow',
|
||||||
component: '@/pages/flow',
|
component: '@/pages/flow',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'document/:id',
|
|
||||||
component: '@/pages/document-viewer',
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'document/:id',
|
||||||
|
component: '@/pages/document-viewer',
|
||||||
|
layout: false,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/*',
|
path: '/*',
|
||||||
component: '@/pages/404',
|
component: '@/pages/404',
|
||||||
|
@ -16,5 +16,5 @@ export function formatDate(date: any) {
|
|||||||
if (!date) {
|
if (!date) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
return dayjs(date).format('DD/MM/YYYY');
|
return dayjs(date).format('DD/MM/YYYY HH:mm:ss');
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user