From c9915a6bacbe49d2c0829803f7c9261396fb1f7a Mon Sep 17 00:00:00 2001 From: DigHuang <114602213+DigHuang@users.noreply.github.com> Date: Wed, 15 Apr 2026 17:10:36 +0800 Subject: [PATCH] =?UTF-8?q?fix(sandbox):=20xlsx/avi=20=E7=AD=89=E4=B8=8D?= =?UTF-8?q?=E5=8F=AF=E9=A2=84=E8=A7=88=E6=96=87=E4=BB=B6=E8=B5=B0=E5=85=9C?= =?UTF-8?q?=E5=BA=95=20(#6754)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat/SandboxEditor/Editor.tsx | 31 +++++++++++++------ .../components/EditorContent.tsx | 5 +++ .../SandboxEditor/components/FileTabs.tsx | 2 ++ .../chat/SandboxEditor/utils.tsx | 3 +- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/projects/app/src/pageComponents/chat/SandboxEditor/Editor.tsx b/projects/app/src/pageComponents/chat/SandboxEditor/Editor.tsx index 52620ca9e4..c70a098d16 100644 --- a/projects/app/src/pageComponents/chat/SandboxEditor/Editor.tsx +++ b/projects/app/src/pageComponents/chat/SandboxEditor/Editor.tsx @@ -81,19 +81,29 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => { // 初始加载根目录的 loading 状态 const [loadingRoot, setLoadingRoot] = useState(false); - // 读取文件内容 - 根据 language 来决定解码策略 + // 读取文件内容 - 根据 language 决定解码策略 + // - 媒体(image/audio/video)→ blob URL + // - 其他 → 严格 UTF-8 解码;解不出来视为不可预览(如 xlsx/zip 等真二进制) const { runAsync: loadFile, loading: loadingFile } = useRequest( - async (filePath: string, language: string): Promise => { + async ( + filePath: string, + language: string + ): Promise<{ content: string; isUnknown: boolean }> => { const response = await getSandboxFile({ appId, chatId, outLinkAuthData, path: filePath }); const isBinary = getIsBinaryByLanguage(language); if (isBinary) { const blob = await response.blob(); - return URL.createObjectURL(blob); - } else { - const content = await response.text(); - return content; + return { content: URL.createObjectURL(blob), isUnknown: false }; + } + + const buffer = await response.arrayBuffer(); + try { + const content = new TextDecoder('utf-8', { fatal: true }).decode(buffer); + return { content, isUnknown: false }; + } catch { + return { content: '', isUnknown: true }; } }, { manual: true } @@ -106,7 +116,7 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => { if (!targetPath) return; const targetFile = openedFiles.find((f) => f.path === targetPath); - if (!targetFile || targetFile.isBinary) return; + if (!targetFile || targetFile.isBinary || targetFile.isUnknown) return; await writeSandboxFile({ appId, @@ -160,7 +170,7 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => { const language = getLanguageByFileName(fileName); const isBinary = getIsBinaryByLanguage(language); - const content = await loadFile(filePath, language); + const { content, isUnknown } = await loadFile(filePath, language); const newFile: OpenedFile = { path: filePath, @@ -168,7 +178,8 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => { content, language, isBinary, - isDirty: false + isDirty: false, + isUnknown }; setOpenedFiles((prev) => [...prev, newFile]); @@ -214,7 +225,7 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => { // 当切换 tab 时,更新编辑器内容 useEffect(() => { if (!editorRef.current || !activeFilePath || !activeFile) return; - if (activeFile.isBinary) return; + if (activeFile.isBinary || activeFile.isUnknown) return; // 使用 ref 标记防止循环更新 isUpdatingRef.current = true; diff --git a/projects/app/src/pageComponents/chat/SandboxEditor/components/EditorContent.tsx b/projects/app/src/pageComponents/chat/SandboxEditor/components/EditorContent.tsx index 3864e3be93..c935762fda 100644 --- a/projects/app/src/pageComponents/chat/SandboxEditor/components/EditorContent.tsx +++ b/projects/app/src/pageComponents/chat/SandboxEditor/components/EditorContent.tsx @@ -88,6 +88,11 @@ const EditorContent = ({ const renderFileContent = () => { if (!activeFile) return null; + // 非媒体文件 UTF-8 解码失败 → 走兜底(如 xlsx/zip 等真二进制) + if (activeFile.isUnknown) { + return t('chat:sandbox_binary_file_no_preview'); + } + // 二进制文件预览 (图片/音频/视频) if (activeFile.isBinary) { const { language, content, name } = activeFile; diff --git a/projects/app/src/pageComponents/chat/SandboxEditor/components/FileTabs.tsx b/projects/app/src/pageComponents/chat/SandboxEditor/components/FileTabs.tsx index 26fbf44b27..a7631bcaa3 100644 --- a/projects/app/src/pageComponents/chat/SandboxEditor/components/FileTabs.tsx +++ b/projects/app/src/pageComponents/chat/SandboxEditor/components/FileTabs.tsx @@ -10,6 +10,8 @@ export type OpenedFile = { language: string; isBinary: boolean; isDirty: boolean; + // 非媒体文件 UTF-8 解码失败时为 true,前端走「无法预览」兜底 + isUnknown?: boolean; }; type Props = { diff --git a/projects/app/src/pageComponents/chat/SandboxEditor/utils.tsx b/projects/app/src/pageComponents/chat/SandboxEditor/utils.tsx index 94f4fe7b2b..e91725218e 100644 --- a/projects/app/src/pageComponents/chat/SandboxEditor/utils.tsx +++ b/projects/app/src/pageComponents/chat/SandboxEditor/utils.tsx @@ -80,7 +80,8 @@ const extensionToLang: Record = { svg: ['svg'], pdf: ['pdf'], audio: ['mp3', 'wav', 'm4a', 'flac', 'ogg'], - video: ['avi', 'mp4', 'webm', 'mov', 'm4v'] + // 仅保留浏览器