import classnames from 'classnames'; import React from 'react'; import type { Attachment } from '..'; import { AttachmentContext } from '../context'; import Style from '../style/fileCard'; import { useProviderContext } from '@agentscope-ai/chat'; import { SparkFalseLine } from '@agentscope-ai/icons'; import ImageCard from './ImageCard'; import type { ImageCardProps } from './ImageCard'; export interface FileListCardProps extends Pick { /** * @description 自定义CSS类名前缀,用于样式隔离和主题定制 * @descriptionEn Custom CSS class name prefix for style isolation and theme customization */ prefixCls?: string; /** * @description 文件附件数据对象,包含文件的基本信息 * @descriptionEn File attachment data object containing basic file information */ item: Attachment; /** * @description 文件移除时的回调函数,用于处理文件删除操作 * @descriptionEn Callback function when file is removed for handling file deletion operations */ onRemove?: (item: Attachment) => void; /** * @description 组件的CSS类名 * @descriptionEn CSS class name for the component */ className?: string; /** * @description 组件的内联样式对象 * @descriptionEn Inline style object for the component */ style?: React.CSSProperties; /** * @description 渲染类型,目前仅支持默认渲染模式 * @descriptionEn Render type, currently only supports default render mode */ renderType?: 'default', } const EMPTY = '\u00A0'; const DEFAULT_ICON_COLOR = '#8c8c8c'; const IMG_EXTS = ['png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'svg']; const IconImage = ({ url }) => const PRESET_FILE_ICONS: { ext: string[]; color: string; icon: React.ReactElement; }[] = [ { icon: , color: '#22b35e', ext: ['xlsx', 'xls'], }, { icon: , color: DEFAULT_ICON_COLOR, ext: IMG_EXTS, }, { icon: , color: DEFAULT_ICON_COLOR, ext: ['md', 'mdx'], }, { icon: , color: '#ff4d4f', ext: ['pdf'], }, { icon: , color: '#ff6e31', ext: ['ppt', 'pptx'], }, { icon: , color: '#1677ff', ext: ['doc', 'docx'], }, { icon: , color: '#fab714', ext: ['zip', 'rar', '7z', 'tar', 'gz'], }, { icon: , color: '#ff4d4f', ext: ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv'], }, { icon: , color: '#8c8c8c', ext: ['mp3', 'wav', 'flac', 'ape', 'aac', 'ogg'], }, ]; function matchExt(suffix: string, ext: string[]) { return ext.some((e) => suffix.toLowerCase() === `.${e}`); } function getSize(size: number) { let retSize = size; const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB']; let unitIndex = 0; while (retSize >= 1024 && unitIndex < units.length - 1) { retSize /= 1024; unitIndex++; } return `${retSize.toFixed(0)} ${units[unitIndex]}`; } function FileListCard(props: FileListCardProps, ref: React.Ref) { const { getPrefixCls } = useProviderContext(); const { item, onRemove, onReplace, className, style } = props; const context = React.useContext(AttachmentContext); const { disabled } = context || {}; const { name, size, percent, status = 'done', description } = item; const prefixCls = getPrefixCls('attachment'); const cardCls = `${prefixCls}-list-card`; const [namePrefix, nameSuffix] = React.useMemo(() => { const nameStr = name || ''; const match = nameStr.match(/^(.*)\.[^.]+$/); return match ? [match[1], nameStr.slice(match[1].length)] : [nameStr, '']; }, [name]); const isImg = React.useMemo(() => matchExt(nameSuffix, IMG_EXTS), [nameSuffix]); const renderType = props.renderType || 'default'; const isImgPreview = isImg && (item.originFileObj || item.thumbUrl || item.url) && renderType === 'default'; if (isImgPreview) { return ( ); } const desc = (() => { if (description) { return description; } if (status === 'uploading') { return `${percent || 0}%`; } if (status === 'error') { return item.response || EMPTY; } return size ? getSize(size) : EMPTY; })(); const [icon, iconColor] = (() => { for (const { ext, icon, color } of PRESET_FILE_ICONS) { if (matchExt(nameSuffix, ext)) { return [icon, color]; } } return [, DEFAULT_ICON_COLOR]; })(); return ( <>