diff --git a/web/app/components/workflow/skill/hooks/use-file-type-info.ts b/web/app/components/workflow/skill/hooks/use-file-type-info.ts index b582deb6c1..35d6529cfc 100644 --- a/web/app/components/workflow/skill/hooks/use-file-type-info.ts +++ b/web/app/components/workflow/skill/hooks/use-file-type-info.ts @@ -3,6 +3,7 @@ import { getFileExtension, isImageFile, isMarkdownFile, + isPdfFile, isSQLiteFile, isTextLikeFile, isVideoFile, @@ -13,6 +14,7 @@ export type FileTypeInfo = { isCodeOrText: boolean isImage: boolean isVideo: boolean + isPdf: boolean isSQLite: boolean isEditable: boolean isMediaFile: boolean @@ -27,6 +29,7 @@ export function useFileTypeInfo(fileNode: { name: string, extension?: string | n isCodeOrText: false, isImage: false, isVideo: false, + isPdf: false, isSQLite: false, isEditable: false, isMediaFile: false, @@ -38,6 +41,7 @@ export function useFileTypeInfo(fileNode: { name: string, extension?: string | n const markdown = isMarkdownFile(ext) const image = isImageFile(ext) const video = isVideoFile(ext) + const pdf = isPdfFile(ext) const sqlite = isSQLiteFile(ext) const editable = isTextLikeFile(ext) const codeOrText = editable && !markdown @@ -47,10 +51,11 @@ export function useFileTypeInfo(fileNode: { name: string, extension?: string | n isCodeOrText: codeOrText, isImage: image, isVideo: video, + isPdf: pdf, isSQLite: sqlite, isEditable: editable, isMediaFile: image || video, - isPreviewable: editable || image || video || sqlite, + isPreviewable: editable || image || video || pdf || sqlite, } }, [fileNode?.name, fileNode?.extension]) } diff --git a/web/app/components/workflow/skill/utils/file-utils.ts b/web/app/components/workflow/skill/utils/file-utils.ts index 811e9c57ae..fec1ca025c 100644 --- a/web/app/components/workflow/skill/utils/file-utils.ts +++ b/web/app/components/workflow/skill/utils/file-utils.ts @@ -5,6 +5,7 @@ const CODE_EXTENSIONS = new Set(['json', 'yaml', 'yml', 'toml', 'js', 'jsx', 'ts const IMAGE_EXTENSIONS = new Set(['png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp', 'ico', 'tiff', 'psd', 'heic', 'heif', 'avif']) const VIDEO_EXTENSIONS = new Set(['mp4', 'mov', 'webm', 'mpeg', 'mpg', 'm4v', 'avi', 'mkv', 'flv', 'wmv', '3gp']) const SQLITE_EXTENSIONS = new Set(['db', 'sqlite', 'sqlite3']) +const PDF_EXTENSIONS_SET = new Set(['pdf']) const BINARY_EXTENSIONS = new Set([ 'mp3', @@ -46,7 +47,6 @@ const BINARY_EXTENSIONS = new Set([ 'msi', 'deb', 'rpm', - 'pdf', 'doc', 'docx', 'xls', @@ -87,7 +87,6 @@ export function getFileExtension(name?: string, extension?: string): string { } const AUDIO_EXTENSIONS = ['mp3', 'm4a', 'wav', 'amr', 'mpga', 'ogg', 'flac', 'aac', 'wma', 'aiff', 'opus'] -const PDF_EXTENSIONS = ['pdf'] const EXCEL_EXTENSIONS = ['xlsx', 'xls', 'csv'] const WORD_EXTENSIONS = ['doc', 'docx'] const PPT_EXTENSIONS = ['ppt', 'pptx'] @@ -98,7 +97,7 @@ const EXTENSION_TO_ICON_TYPE = new Map( [IMAGE_EXTENSIONS, FileAppearanceTypeEnum.image], [VIDEO_EXTENSIONS, FileAppearanceTypeEnum.video], [AUDIO_EXTENSIONS, FileAppearanceTypeEnum.audio], - [PDF_EXTENSIONS, FileAppearanceTypeEnum.pdf], + [PDF_EXTENSIONS_SET, FileAppearanceTypeEnum.pdf], [MARKDOWN_EXTENSIONS, FileAppearanceTypeEnum.markdown], [EXCEL_EXTENSIONS, FileAppearanceTypeEnum.excel], [WORD_EXTENSIONS, FileAppearanceTypeEnum.word], @@ -139,6 +138,10 @@ export function isSQLiteFile(extension: string): boolean { return SQLITE_EXTENSIONS.has(extension) } +export function isPdfFile(extension: string): boolean { + return PDF_EXTENSIONS_SET.has(extension) +} + export function getFileLanguage(name: string): string { const extension = name.split('.').pop()?.toLowerCase() ?? '' diff --git a/web/app/components/workflow/skill/viewer/pdf-file-preview.tsx b/web/app/components/workflow/skill/viewer/pdf-file-preview.tsx new file mode 100644 index 0000000000..5cb7b95874 --- /dev/null +++ b/web/app/components/workflow/skill/viewer/pdf-file-preview.tsx @@ -0,0 +1,83 @@ +'use client' + +import { RiZoomInLine, RiZoomOutLine } from '@remixicon/react' +import { noop } from 'es-toolkit/function' +import * as React from 'react' +import { useState } from 'react' +import { useHotkeys } from 'react-hotkeys-hook' +import { PdfHighlighter, PdfLoader } from 'react-pdf-highlighter' +import Loading from '@/app/components/base/loading' +import 'react-pdf-highlighter/dist/style.css' + +type PdfFilePreviewProps = { + downloadUrl: string +} + +const PdfFilePreview = ({ downloadUrl }: PdfFilePreviewProps) => { + const [scale, setScale] = useState(1) + + const zoomIn = () => { + setScale(prevScale => Math.min(prevScale * 1.2, 3)) + } + + const zoomOut = () => { + setScale(prevScale => Math.max(prevScale / 1.2, 0.5)) + } + + useHotkeys('up', zoomIn) + useHotkeys('down', zoomOut) + + return ( +
+
+ + +
+ +
+
+ + +
+ )} + > + {pdfDocument => ( + false} + scrollRef={noop} + onScrollChange={noop} + onSelectionFinished={() => null} + highlightTransform={() =>
} + highlights={[]} + /> + )} + +
+
+
+ ) +} + +export default React.memo(PdfFilePreview) diff --git a/web/app/components/workflow/skill/viewer/read-only-file-preview.tsx b/web/app/components/workflow/skill/viewer/read-only-file-preview.tsx index 8d59d002f1..2856e5ef85 100644 --- a/web/app/components/workflow/skill/viewer/read-only-file-preview.tsx +++ b/web/app/components/workflow/skill/viewer/read-only-file-preview.tsx @@ -24,6 +24,11 @@ const SQLiteFilePreview = dynamic( { ssr: false, loading: () => }, ) +const PdfFilePreview = dynamic( + () => import('./pdf-file-preview'), + { ssr: false, loading: () => }, +) + type ReadOnlyFilePreviewProps = { downloadUrl: string fileName: string @@ -41,7 +46,7 @@ const ReadOnlyFilePreview = ({ () => ({ name: fileName, extension }), [fileName, extension], ) - const { isMarkdown, isCodeOrText, isImage, isVideo, isSQLite, isPreviewable } = useFileTypeInfo(fileNode) + const { isMarkdown, isCodeOrText, isImage, isVideo, isPdf, isSQLite, isPreviewable } = useFileTypeInfo(fileNode) const isTextFile = isPreviewable && (isMarkdown || isCodeOrText) const { data: textContent, isLoading: isTextLoading } = useFetchTextContent( isTextFile ? downloadUrl : undefined, @@ -78,6 +83,9 @@ const ReadOnlyFilePreview = ({ if (isSQLite) return + if (isPdf) + return + return (