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[] }