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.
This commit is contained in:
yyh
2026-01-27 16:21:03 +08:00
parent 85ecf1a198
commit ab52550abe
10 changed files with 41 additions and 16 deletions

View File

@ -24,7 +24,7 @@ const FilePickerTreeNode: FC<FilePickerTreeNodeProps> = ({ 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()

View File

@ -33,7 +33,7 @@ const FileReferenceBlock: FC<FileReferenceBlockProps> = ({ 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) => {

View File

@ -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<FileTabItemProps> = ({
fileId,
name,
extension,
isActive,
isDirty,
isPreview,
@ -32,7 +34,7 @@ const FileTabItem: FC<FileTabItemProps> = ({
onDoubleClick,
}) => {
const { t } = useTranslation()
const iconType = getFileIconType(name)
const iconType = getFileIconType(name, extension)
const handleClick = useCallback(() => {
onClick(fileId)

View File

@ -86,6 +86,7 @@ const FileTabs: FC = () => {
key={fileId}
fileId={fileId}
name={name}
extension={node?.extension}
isActive={isActive}
isDirty={isDirty}
isPreview={isPreview}

View File

@ -58,7 +58,7 @@ const ArtifactsTreeNode: FC<ArtifactsTreeNodeProps> = ({
onDownload(node)
}, [node, onDownload])
const fileIconType = !isFolder ? getFileIconType(node.name) : null
const fileIconType = !isFolder ? getFileIconType(node.name, node.extension) : null
return (
<div>

View File

@ -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<TreeNodeIconProps> = ({
isFolder,
isOpen,
fileName,
extension,
isDirty,
onToggle,
}) => {
@ -46,7 +48,7 @@ export const TreeNodeIcon: FC<TreeNodeIconProps> = ({
)
}
const fileIconType = getFileIconType(fileName)
const fileIconType = getFileIconType(fileName, extension)
return (
<div className="relative flex size-full items-center justify-center">

View File

@ -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}
/>

View File

@ -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<string, FileAppearanceTypeEnum>(
([
[['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 {

View File

@ -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: [],
}

View File

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