From ab52550abe8cdac734bc6786a3899f0c0297da75 Mon Sep 17 00:00:00 2001 From: yyh Date: Tue, 27 Jan 2026 16:21:03 +0800 Subject: [PATCH] feat(sandbox): use extension field for file icon type mapping Enhance getFileIconType to accept an extension parameter and cover all 13 FileAppearanceTypeEnum types using an O(1) Map lookup. Update all call sites to pass the API-provided extension for accurate icon display. --- .../plugins/file-picker-panel.tsx | 2 +- .../file-reference-block/component.tsx | 2 +- .../workflow/skill/file-tab-item.tsx | 4 ++- .../components/workflow/skill/file-tabs.tsx | 1 + .../skill/file-tree/artifacts-tree.tsx | 2 +- .../skill/file-tree/tree-node-icon.tsx | 4 ++- .../workflow/skill/file-tree/tree-node.tsx | 1 + .../workflow/skill/utils/file-utils.ts | 36 +++++++++++++------ web/service/use-sandbox-file.ts | 1 + web/types/sandbox-file.ts | 4 +++ 10 files changed, 41 insertions(+), 16 deletions(-) diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/file-picker-panel.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/file-picker-panel.tsx index 961d0e51086..5ef3df27e2f 100644 --- a/web/app/components/workflow/skill/editor/skill-editor/plugins/file-picker-panel.tsx +++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/file-picker-panel.tsx @@ -24,7 +24,7 @@ const FilePickerTreeNode: FC = ({ node, style, dragHand const { t } = useTranslation('workflow') const isFolder = node.data.node_type === 'folder' const isSelected = node.isSelected - const fileIconType = !isFolder ? getFileIconType(node.data.name) : null + const fileIconType = !isFolder ? getFileIconType(node.data.name, node.data.extension) : null const handleClick = useCallback((e: React.MouseEvent) => { e.stopPropagation() diff --git a/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/component.tsx b/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/component.tsx index 9e05c32b6ae..fa01f15fb17 100644 --- a/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/component.tsx +++ b/web/app/components/workflow/skill/editor/skill-editor/plugins/file-reference-block/component.tsx @@ -33,7 +33,7 @@ const FileReferenceBlock: FC = ({ nodeKey, resourceId } const currentNode = useMemo(() => nodeMap?.get(resourceId), [nodeMap, resourceId]) const isFolder = currentNode?.node_type === 'folder' const displayName = currentNode?.name ?? resourceId - const iconType = !isFolder && currentNode ? getFileIconType(currentNode.name) : null + const iconType = !isFolder && currentNode ? getFileIconType(currentNode.name, currentNode.extension) : null const title = currentNode?.path ?? displayName const handleSelect = useCallback((node: TreeNodeData) => { diff --git a/web/app/components/workflow/skill/file-tab-item.tsx b/web/app/components/workflow/skill/file-tab-item.tsx index 34e41061eaf..601e6874105 100644 --- a/web/app/components/workflow/skill/file-tab-item.tsx +++ b/web/app/components/workflow/skill/file-tab-item.tsx @@ -13,6 +13,7 @@ import { getFileIconType } from './utils/file-utils' type FileTabItemProps = { fileId: string name: string + extension?: string isActive: boolean isDirty: boolean isPreview: boolean @@ -24,6 +25,7 @@ type FileTabItemProps = { const FileTabItem: FC = ({ fileId, name, + extension, isActive, isDirty, isPreview, @@ -32,7 +34,7 @@ const FileTabItem: FC = ({ onDoubleClick, }) => { const { t } = useTranslation() - const iconType = getFileIconType(name) + const iconType = getFileIconType(name, extension) const handleClick = useCallback(() => { onClick(fileId) diff --git a/web/app/components/workflow/skill/file-tabs.tsx b/web/app/components/workflow/skill/file-tabs.tsx index 116c8c3d311..b84be06a889 100644 --- a/web/app/components/workflow/skill/file-tabs.tsx +++ b/web/app/components/workflow/skill/file-tabs.tsx @@ -86,6 +86,7 @@ const FileTabs: FC = () => { key={fileId} fileId={fileId} name={name} + extension={node?.extension} isActive={isActive} isDirty={isDirty} isPreview={isPreview} diff --git a/web/app/components/workflow/skill/file-tree/artifacts-tree.tsx b/web/app/components/workflow/skill/file-tree/artifacts-tree.tsx index dad85c08146..d84621c52bb 100644 --- a/web/app/components/workflow/skill/file-tree/artifacts-tree.tsx +++ b/web/app/components/workflow/skill/file-tree/artifacts-tree.tsx @@ -58,7 +58,7 @@ const ArtifactsTreeNode: FC = ({ onDownload(node) }, [node, onDownload]) - const fileIconType = !isFolder ? getFileIconType(node.name) : null + const fileIconType = !isFolder ? getFileIconType(node.name, node.extension) : null return (
diff --git a/web/app/components/workflow/skill/file-tree/tree-node-icon.tsx b/web/app/components/workflow/skill/file-tree/tree-node-icon.tsx index 173b756bc6e..569338ae80b 100644 --- a/web/app/components/workflow/skill/file-tree/tree-node-icon.tsx +++ b/web/app/components/workflow/skill/file-tree/tree-node-icon.tsx @@ -14,6 +14,7 @@ type TreeNodeIconProps = { isFolder: boolean isOpen: boolean fileName: string + extension?: string isDirty: boolean onToggle?: (e: React.MouseEvent) => void } @@ -22,6 +23,7 @@ export const TreeNodeIcon: FC = ({ isFolder, isOpen, fileName, + extension, isDirty, onToggle, }) => { @@ -46,7 +48,7 @@ export const TreeNodeIcon: FC = ({ ) } - const fileIconType = getFileIconType(fileName) + const fileIconType = getFileIconType(fileName, extension) return (
diff --git a/web/app/components/workflow/skill/file-tree/tree-node.tsx b/web/app/components/workflow/skill/file-tree/tree-node.tsx index ed71e357ea8..fada40afe1b 100644 --- a/web/app/components/workflow/skill/file-tree/tree-node.tsx +++ b/web/app/components/workflow/skill/file-tree/tree-node.tsx @@ -126,6 +126,7 @@ const TreeNode = ({ node, style, dragHandle, treeChildren }: TreeNodeProps) => { isFolder={isFolder} isOpen={node.isOpen} fileName={node.data.name} + extension={node.data.extension} isDirty={isDirty} onToggle={handleToggle} /> diff --git a/web/app/components/workflow/skill/utils/file-utils.ts b/web/app/components/workflow/skill/utils/file-utils.ts index 931c02743bd..be113bd0885 100644 --- a/web/app/components/workflow/skill/utils/file-utils.ts +++ b/web/app/components/workflow/skill/utils/file-utils.ts @@ -86,19 +86,33 @@ export function getFileExtension(name?: string, extension?: string): string { return name.split('.').pop()?.toLowerCase() ?? '' } -export function getFileIconType(name: string): FileAppearanceTypeEnum { - const extension = name.split('.').pop()?.toLowerCase() ?? '' +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'] - if (MARKDOWN_EXTENSIONS.includes(extension)) - return FileAppearanceTypeEnum.markdown +const EXTENSION_TO_ICON_TYPE = new Map( + ([ + [['gif'], FileAppearanceTypeEnum.gif], + [IMAGE_EXTENSIONS, FileAppearanceTypeEnum.image], + [VIDEO_EXTENSIONS, FileAppearanceTypeEnum.video], + [AUDIO_EXTENSIONS, FileAppearanceTypeEnum.audio], + [PDF_EXTENSIONS, FileAppearanceTypeEnum.pdf], + [MARKDOWN_EXTENSIONS, FileAppearanceTypeEnum.markdown], + [EXCEL_EXTENSIONS, FileAppearanceTypeEnum.excel], + [WORD_EXTENSIONS, FileAppearanceTypeEnum.word], + [PPT_EXTENSIONS, FileAppearanceTypeEnum.ppt], + [CODE_EXTENSIONS, FileAppearanceTypeEnum.code], + [SQLITE_EXTENSIONS, FileAppearanceTypeEnum.database], + ] as [string[], FileAppearanceTypeEnum][]).flatMap( + ([exts, type]) => exts.map(e => [e, type] as [string, FileAppearanceTypeEnum]), + ), +) - if (CODE_EXTENSIONS.includes(extension)) - return FileAppearanceTypeEnum.code - - if (SQLITE_EXTENSIONS.includes(extension)) - return FileAppearanceTypeEnum.database - - return FileAppearanceTypeEnum.document +export function getFileIconType(name: string, ext?: string | null): FileAppearanceTypeEnum { + const extension = ext?.toLowerCase() ?? name.split('.').pop()?.toLowerCase() ?? '' + return EXTENSION_TO_ICON_TYPE.get(extension) ?? FileAppearanceTypeEnum.document } export function isMarkdownFile(extension: string): boolean { diff --git a/web/service/use-sandbox-file.ts b/web/service/use-sandbox-file.ts index 20288b28185..983cccd05bd 100644 --- a/web/service/use-sandbox-file.ts +++ b/web/service/use-sandbox-file.ts @@ -73,6 +73,7 @@ function buildTreeFromFlatList(nodes: SandboxFileNode[]): SandboxFileTreeNode[] node_type: node.is_dir ? 'folder' : 'file', size: node.size, mtime: node.mtime, + extension: node.extension, children: [], } diff --git a/web/types/sandbox-file.ts b/web/types/sandbox-file.ts index 332947438ce..83474698374 100644 --- a/web/types/sandbox-file.ts +++ b/web/types/sandbox-file.ts @@ -18,6 +18,8 @@ export type SandboxFileNode = { size: number | null /** Modification timestamp in seconds (null for some directories) */ mtime: number | null + /** File extension (null for directories) */ + extension: string | null } /** @@ -48,6 +50,8 @@ export type SandboxFileTreeNode = { size: number | null /** Modification timestamp */ mtime: number | null + /** File extension (null for directories) */ + extension: string | null /** Child nodes (for folders) */ children: SandboxFileTreeNode[] }