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 961d0e5108..5ef3df27e2 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 9e05c32b6a..fa01f15fb1 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 34e41061ea..601e687410 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 116c8c3d31..b84be06a88 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 dad85c0814..d84621c52b 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 173b756bc6..569338ae80 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 ed71e357ea..fada40afe1 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 931c02743b..be113bd088 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 20288b2818..983cccd05b 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 332947438c..8347469837 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[] }