Commit c9915a6b by DigHuang Committed by GitHub

fix(sandbox): xlsx/avi 等不可预览文件走兜底 (#6754)

parent b35288fe
...@@ -81,19 +81,29 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => { ...@@ -81,19 +81,29 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => {
// 初始加载根目录的 loading 状态 // 初始加载根目录的 loading 状态
const [loadingRoot, setLoadingRoot] = useState(false); const [loadingRoot, setLoadingRoot] = useState(false);
// 读取文件内容 - 根据 language 来决定解码策略 // 读取文件内容 - 根据 language 决定解码策略
// - 媒体(image/audio/video)→ blob URL
// - 其他 → 严格 UTF-8 解码;解不出来视为不可预览(如 xlsx/zip 等真二进制)
const { runAsync: loadFile, loading: loadingFile } = useRequest( const { runAsync: loadFile, loading: loadingFile } = useRequest(
async (filePath: string, language: string): Promise<string> => { async (
filePath: string,
language: string
): Promise<{ content: string; isUnknown: boolean }> => {
const response = await getSandboxFile({ appId, chatId, outLinkAuthData, path: filePath }); const response = await getSandboxFile({ appId, chatId, outLinkAuthData, path: filePath });
const isBinary = getIsBinaryByLanguage(language); const isBinary = getIsBinaryByLanguage(language);
if (isBinary) { if (isBinary) {
const blob = await response.blob(); const blob = await response.blob();
return URL.createObjectURL(blob); return { content: URL.createObjectURL(blob), isUnknown: false };
} else { }
const content = await response.text();
return content; 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 } { manual: true }
...@@ -106,7 +116,7 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => { ...@@ -106,7 +116,7 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => {
if (!targetPath) return; if (!targetPath) return;
const targetFile = openedFiles.find((f) => f.path === targetPath); const targetFile = openedFiles.find((f) => f.path === targetPath);
if (!targetFile || targetFile.isBinary) return; if (!targetFile || targetFile.isBinary || targetFile.isUnknown) return;
await writeSandboxFile({ await writeSandboxFile({
appId, appId,
...@@ -160,7 +170,7 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => { ...@@ -160,7 +170,7 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => {
const language = getLanguageByFileName(fileName); const language = getLanguageByFileName(fileName);
const isBinary = getIsBinaryByLanguage(language); const isBinary = getIsBinaryByLanguage(language);
const content = await loadFile(filePath, language); const { content, isUnknown } = await loadFile(filePath, language);
const newFile: OpenedFile = { const newFile: OpenedFile = {
path: filePath, path: filePath,
...@@ -168,7 +178,8 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => { ...@@ -168,7 +178,8 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => {
content, content,
language, language,
isBinary, isBinary,
isDirty: false isDirty: false,
isUnknown
}; };
setOpenedFiles((prev) => [...prev, newFile]); setOpenedFiles((prev) => [...prev, newFile]);
...@@ -214,7 +225,7 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => { ...@@ -214,7 +225,7 @@ const SandboxEditor = ({ appId, chatId, outLinkAuthData }: Props) => {
// 当切换 tab 时,更新编辑器内容 // 当切换 tab 时,更新编辑器内容
useEffect(() => { useEffect(() => {
if (!editorRef.current || !activeFilePath || !activeFile) return; if (!editorRef.current || !activeFilePath || !activeFile) return;
if (activeFile.isBinary) return; if (activeFile.isBinary || activeFile.isUnknown) return;
// 使用 ref 标记防止循环更新 // 使用 ref 标记防止循环更新
isUpdatingRef.current = true; isUpdatingRef.current = true;
......
...@@ -88,6 +88,11 @@ const EditorContent = ({ ...@@ -88,6 +88,11 @@ const EditorContent = ({
const renderFileContent = () => { const renderFileContent = () => {
if (!activeFile) return null; if (!activeFile) return null;
// 非媒体文件 UTF-8 解码失败 → 走兜底(如 xlsx/zip 等真二进制)
if (activeFile.isUnknown) {
return t('chat:sandbox_binary_file_no_preview');
}
// 二进制文件预览 (图片/音频/视频) // 二进制文件预览 (图片/音频/视频)
if (activeFile.isBinary) { if (activeFile.isBinary) {
const { language, content, name } = activeFile; const { language, content, name } = activeFile;
......
...@@ -10,6 +10,8 @@ export type OpenedFile = { ...@@ -10,6 +10,8 @@ export type OpenedFile = {
language: string; language: string;
isBinary: boolean; isBinary: boolean;
isDirty: boolean; isDirty: boolean;
// 非媒体文件 UTF-8 解码失败时为 true,前端走「无法预览」兜底
isUnknown?: boolean;
}; };
type Props = { type Props = {
......
...@@ -80,7 +80,8 @@ const extensionToLang: Record<string, string[]> = { ...@@ -80,7 +80,8 @@ const extensionToLang: Record<string, string[]> = {
svg: ['svg'], svg: ['svg'],
pdf: ['pdf'], pdf: ['pdf'],
audio: ['mp3', 'wav', 'm4a', 'flac', 'ogg'], audio: ['mp3', 'wav', 'm4a', 'flac', 'ogg'],
video: ['avi', 'mp4', 'webm', 'mov', 'm4v'] // 仅保留浏览器 <video> 原生可解码的容器;avi/mkv/wmv/flv/mov/m4v 等走兜底
video: ['mp4', 'webm']
}; };
const langMap = Object.entries(extensionToLang).reduce( const langMap = Object.entries(extensionToLang).reduce(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment