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

This commit is contained in:
DigHuang
2026-04-15 17:10:36 +08:00
committed by GitHub
parent b35288fe5b
commit c9915a6bac
4 changed files with 30 additions and 11 deletions
@@ -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<string> => {
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;
@@ -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;
@@ -10,6 +10,8 @@ export type OpenedFile = {
language: string;
isBinary: boolean;
isDirty: boolean;
// 非媒体文件 UTF-8 解码失败时为 true,前端走「无法预览」兜底
isUnknown?: boolean;
};
type Props = {
@@ -80,7 +80,8 @@ const extensionToLang: Record<string, string[]> = {
svg: ['svg'],
pdf: ['pdf'],
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(