From 6dfed934a5aa70dcc46f066b58a1133e3feb7a5b Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 30 Jan 2026 17:32:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(file-preview):=20=E5=A2=9E=E5=8A=A0PDF?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E9=A2=84=E8=A7=88=E5=8A=9F=E8=83=BD=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=A2=84=E8=A7=88=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 引入统一的文件预览工具函数和类型定义 - 添加PDF文件类型的识别和预览支持 - 使用iframe实现PDF文件在线预览 - 重构文件预览逻辑,统一处理不同文件类型的预览 - 优化文本内容预览的长度截取机制 - 更新预览按钮加载状态显示 - 统一预览窗口的最大高度配置 - 修改API调用路径为专门的预览接口 --- .../components/CreateAnnotationTaskDialog.tsx | 73 ++++++++----------- .../Detail/components/Overview.tsx | 29 ++++++-- .../Detail/useFilesOperation.ts | 15 ++-- .../Detail/KnowledgeSetDetail.tsx | 16 +++- frontend/src/utils/filePreview.ts | 6 +- 5 files changed, 82 insertions(+), 57 deletions(-) diff --git a/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx b/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx index 451e1e1..daf28a1 100644 --- a/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx +++ b/frontend/src/pages/DataAnnotation/Create/components/CreateAnnotationTaskDialog.tsx @@ -6,6 +6,12 @@ import TextArea from "antd/es/input/TextArea"; import { useEffect, useMemo, useState } from "react"; import type { ReactNode } from "react"; import { Eye } from "lucide-react"; +import { + PREVIEW_TEXT_MAX_LENGTH, + resolvePreviewFileType, + truncatePreviewText, + type PreviewFileType, +} from "@/utils/filePreview"; import { createAnnotationTaskUsingPost, getAnnotationTaskByIdUsingGet, @@ -53,6 +59,7 @@ const isRecord = (value: unknown): value is Record => !!value && typeof value === "object" && !Array.isArray(value); const DEFAULT_SEGMENTATION_ENABLED = true; +const FILE_PREVIEW_MAX_HEIGHT = 500; const SEGMENTATION_OPTIONS = [ { label: "需要切片段", value: true }, { label: "不需要切片段", value: false }, @@ -116,7 +123,7 @@ export default function CreateAnnotationTask({ const [fileContent, setFileContent] = useState(""); const [fileContentLoading, setFileContentLoading] = useState(false); const [previewFileName, setPreviewFileName] = useState(""); - const [previewFileType, setPreviewFileType] = useState<"text" | "image" | "video" | "audio">("text"); + const [previewFileType, setPreviewFileType] = useState("text"); const [previewMediaUrl, setPreviewMediaUrl] = useState(""); // 任务详情加载状态(编辑模式) @@ -297,57 +304,32 @@ export default function CreateAnnotationTask({ // 预览文件内容 const handlePreviewFileContent = async (file: DatasetPreviewFile) => { - const fileName = file.fileName?.toLowerCase() || ''; - - // 文件类型扩展名映射 - const textExtensions = ['.json', '.jsonl', '.txt', '.csv', '.tsv', '.xml', '.md', '.yaml', '.yml']; - const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg']; - const videoExtensions = ['.mp4', '.webm', '.ogg', '.mov', '.avi']; - const audioExtensions = ['.mp3', '.wav', '.ogg', '.aac', '.flac', '.m4a']; - - const isTextFile = textExtensions.some(ext => fileName.endsWith(ext)); - const isImageFile = imageExtensions.some(ext => fileName.endsWith(ext)); - const isVideoFile = videoExtensions.some(ext => fileName.endsWith(ext)); - const isAudioFile = audioExtensions.some(ext => fileName.endsWith(ext)); - - if (!isTextFile && !isImageFile && !isVideoFile && !isAudioFile) { + const fileType = resolvePreviewFileType(file.fileName); + if (!fileType) { message.warning("不支持预览该文件类型"); return; } setFileContentLoading(true); setPreviewFileName(file.fileName); + setPreviewFileType(fileType); + setFileContent(""); + setPreviewMediaUrl(""); - const fileUrl = `/api/data-management/datasets/${selectedDatasetId}/files/${file.id}/download`; + const previewUrl = `/api/data-management/datasets/${selectedDatasetId}/files/${file.id}/preview`; try { - if (isTextFile) { + if (fileType === "text") { // 文本文件:获取内容 - const response = await fetch(fileUrl); + const response = await fetch(previewUrl); if (!response.ok) { throw new Error('下载失败'); } const text = await response.text(); - // 限制预览内容长度 - const maxLength = 50000; - if (text.length > maxLength) { - setFileContent(text.substring(0, maxLength) + '\n\n... (内容过长,仅显示前 50000 字符)'); - } else { - setFileContent(text); - } - setPreviewFileType("text"); - } else if (isImageFile) { - // 图片文件:直接使用 URL - setPreviewMediaUrl(fileUrl); - setPreviewFileType("image"); - } else if (isVideoFile) { - // 视频文件:使用 URL - setPreviewMediaUrl(fileUrl); - setPreviewFileType("video"); - } else if (isAudioFile) { - // 音频文件:使用 URL - setPreviewMediaUrl(fileUrl); - setPreviewFileType("audio"); + setFileContent(truncatePreviewText(text, PREVIEW_TEXT_MAX_LENGTH)); + } else { + // 媒体/PDF 文件:直接使用预览地址 + setPreviewMediaUrl(previewUrl); } setFileContentVisible(true); } catch (error) { @@ -878,7 +860,7 @@ export default function CreateAnnotationTask({ ]} > -
点击文件名可预览文件内容(支持文本、图片、音频、视频)
+
点击文件名可预览文件内容(支持文本、图片、音频、视频、PDF)
)} + {previewFileType === "pdf" && ( +