refactor: refactored file icon retrieval logic (#757)

* refactor: refactored file icon retrieval logic

* update

* update

* update
This commit is contained in:
ayangweb
2025-07-10 18:10:39 +08:00
committed by GitHub
parent 5b034c28ac
commit dd3be3a819
10 changed files with 32 additions and 268 deletions

View File

@@ -18,7 +18,6 @@
"release-beta": "release-it --preRelease=beta --preReleaseBase=1"
},
"dependencies": {
"@ant-design/icons": "^6.0.0",
"@headlessui/react": "^2.2.2",
"@tauri-apps/api": "^2.5.0",
"@tauri-apps/plugin-autostart": "~2.2.0",

59
pnpm-lock.yaml generated
View File

@@ -8,9 +8,6 @@ importers:
.:
dependencies:
'@ant-design/icons':
specifier: ^6.0.0
version: 6.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@headlessui/react':
specifier: ^2.2.2
version: 2.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -239,23 +236,6 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
'@ant-design/colors@8.0.0':
resolution: {integrity: sha512-6YzkKCw30EI/E9kHOIXsQDHmMvTllT8STzjMb4K2qzit33RW2pqCJP0sk+hidBntXxE+Vz4n1+RvCTfBw6OErw==}
'@ant-design/fast-color@3.0.0':
resolution: {integrity: sha512-eqvpP7xEDm2S7dUzl5srEQCBTXZMmY3ekf97zI+M2DHOYyKdJGH0qua0JACHTqbkRnD/KHFQP9J1uMJ/XWVzzA==}
engines: {node: '>=8.x'}
'@ant-design/icons-svg@4.4.2':
resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==}
'@ant-design/icons@6.0.0':
resolution: {integrity: sha512-o0aCCAlHc1o4CQcapAwWzHeaW2x9F49g7P3IDtvtNXgHowtRWYb7kiubt8sQPFvfVIVU/jLw2hzeSlNt0FU+Uw==}
engines: {node: '>=8'}
peerDependencies:
react: '>=16.0.0'
react-dom: '>=16.0.0'
'@antfu/install-pkg@1.1.0':
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
@@ -1005,12 +985,6 @@ packages:
resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==}
engines: {node: '>=12'}
'@rc-component/util@1.2.1':
resolution: {integrity: sha512-AUVu6jO+lWjQnUOOECwu8iR0EdElQgWW5NBv5vP/Uf9dWbAX3udhMutRlkVXjuac2E40ghkFy+ve00mc/3Fymg==}
peerDependencies:
react: '>=18.0.0'
react-dom: '>=18.0.0'
'@react-aria/focus@3.20.2':
resolution: {integrity: sha512-Q3rouk/rzoF/3TuH6FzoAIKrl+kzZi9LHmr8S5EqLAOyP9TXIKG34x2j42dZsAhrw7TbF9gA8tBKwnCNH4ZV+Q==}
peerDependencies:
@@ -1679,9 +1653,6 @@ packages:
resolution: {integrity: sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==}
engines: {node: '>=8'}
classnames@2.5.1:
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
cli-boxes@3.0.0:
resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
engines: {node: '>=10'}
@@ -3161,9 +3132,6 @@ packages:
typescript:
optional: true
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
react-markdown@9.1.0:
resolution: {integrity: sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==}
peerDependencies:
@@ -3809,23 +3777,6 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.8
'@jridgewell/trace-mapping': 0.3.25
'@ant-design/colors@8.0.0':
dependencies:
'@ant-design/fast-color': 3.0.0
'@ant-design/fast-color@3.0.0': {}
'@ant-design/icons-svg@4.4.2': {}
'@ant-design/icons@6.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@ant-design/colors': 8.0.0
'@ant-design/icons-svg': 4.4.2
'@rc-component/util': 1.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
classnames: 2.5.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
'@antfu/install-pkg@1.1.0':
dependencies:
package-manager-detector: 1.3.0
@@ -4468,12 +4419,6 @@ snapshots:
'@pnpm/network.ca-file': 1.0.2
config-chain: 1.1.13
'@rc-component/util@1.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-is: 18.3.1
'@react-aria/focus@3.20.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@react-aria/interactions': 3.25.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -5132,8 +5077,6 @@ snapshots:
ci-info@4.2.0: {}
classnames@2.5.1: {}
cli-boxes@3.0.0: {}
cli-cursor@5.0.0:
@@ -6881,8 +6824,6 @@ snapshots:
react-dom: 18.3.1(react@18.3.1)
typescript: 5.8.3
react-is@18.3.1: {}
react-markdown@9.1.0(@types/react@18.3.21)(react@18.3.1):
dependencies:
'@types/hast': 3.0.4

View File

@@ -6,9 +6,9 @@ import { useTranslation } from "react-i18next";
import { useChatStore, UploadFile } from "@/stores/chatStore";
import { useConnectStore } from "@/stores/connectStore";
import FileIcon from "../Common/Icons/FileIcon";
import platformAdapter from "@/utils/platformAdapter";
import Tooltip2 from "../Common/Tooltip2";
import FileIcon from "../Common/Icons/FileIcon";
interface FileListProps {
sessionId: string;
@@ -87,6 +87,7 @@ const FileList = (props: FileListProps) => {
const {
id,
name,
path,
extname,
size,
uploaded,
@@ -109,7 +110,7 @@ const FileList = (props: FileListProps) => {
</div>
)}
<FileIcon extname={extname} />
<FileIcon path={path} />
<div className="flex flex-col justify-between overflow-hidden">
<div className="truncate text-[#333333] dark:text-[#D8D8D8]">

View File

@@ -6,10 +6,10 @@ import { useTranslation } from "react-i18next";
import { useConnectStore } from "@/stores/connectStore";
import Checkbox from "@/components/Common/Checkbox";
import FileIcon from "@/components/Common/Icons/FileIcon";
import { AttachmentHit } from "@/types/commands";
import { useAppStore } from "@/stores/appStore";
import platformAdapter from "@/utils/platformAdapter";
import FileIcon from "../Common/Icons/FileIcon";
interface SessionFileProps {
sessionId: string;
@@ -44,8 +44,6 @@ const SessionFile = (props: SessionFileProps) => {
sessionId,
});
console.log("get_attachment response", response);
setUploadedFiles(response?.hits?.hits ?? []);
} else {
}
@@ -138,16 +136,16 @@ const SessionFile = (props: SessionFileProps) => {
key={id}
className="flex items-center justify-between min-h-12 px-2 rounded-[4px] bg-[#ededed] dark:bg-[#202126]"
>
<div className="flex items-center gap-2">
<FileIcon extname={icon} />
<div className="flex items-center gap-1">
<FileIcon path={name} />
<div>
<div className="text-sm leading-4 text-[#333] dark:text-[#D8D8D8]">
{name}
</div>
<div className="text-xs text-[#999]">
<span>{icon}</span>
<span className="pl-2">
{icon && <span className="pr-2">{icon}</span>}
<span>
{filesize(size, { standard: "jedec", spacer: "" })}
</span>
</div>

View File

@@ -0,0 +1,24 @@
import platformAdapter from "@/utils/platformAdapter";
import { FC, useEffect, useState } from "react";
import FontIcon from "./FontIcon";
import { twMerge } from "tailwind-merge";
interface FileIconProps {
path: string;
className?: string;
}
const FileIcon: FC<FileIconProps> = (props) => {
const { path, className } = props;
const [iconName, setIconName] = useState("");
useEffect(() => {
platformAdapter
.invokeBackend<string>("get_file_icon", { path })
.then(setIconName);
});
return <FontIcon name={iconName} className={twMerge("size-8", className)} />;
};
export default FileIcon;

View File

@@ -1,21 +0,0 @@
const AudioIcon = () => {
return (
<svg
width="1em"
height="1em"
viewBox="0 0 16 16"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<title>audio</title>
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<path
d="M14.1178571,4.0125 C14.225,4.11964286 14.2857143,4.26428571 14.2857143,4.41607143 L14.2857143,15.4285714 C14.2857143,15.7446429 14.0303571,16 13.7142857,16 L2.28571429,16 C1.96964286,16 1.71428571,15.7446429 1.71428571,15.4285714 L1.71428571,0.571428571 C1.71428571,0.255357143 1.96964286,0 2.28571429,0 L9.86964286,0 C10.0214286,0 10.1678571,0.0607142857 10.275,0.167857143 L14.1178571,4.0125 Z M10.7315824,7.11216117 C10.7428131,7.15148751 10.7485063,7.19218979 10.7485063,7.23309113 L10.7485063,8.07742614 C10.7484199,8.27364959 10.6183424,8.44607275 10.4296853,8.50003683 L8.32984514,9.09986306 L8.32984514,11.7071803 C8.32986605,12.5367078 7.67249692,13.217028 6.84345686,13.2454634 L6.79068592,13.2463395 C6.12766108,13.2463395 5.53916361,12.8217001 5.33010655,12.1924966 C5.1210495,11.563293 5.33842118,10.8709227 5.86959669,10.4741173 C6.40077221,10.0773119 7.12636292,10.0652587 7.67042486,10.4442027 L7.67020842,7.74937024 L7.68449368,7.74937024 C7.72405122,7.59919041 7.83988806,7.48101083 7.98924584,7.4384546 L10.1880418,6.81004755 C10.42156,6.74340323 10.6648954,6.87865515 10.7315824,7.11216117 Z M9.60714286,1.31785714 L12.9678571,4.67857143 L9.60714286,4.67857143 L9.60714286,1.31785714 Z"
fill="currentColor"
/>
</g>
</svg>
);
};
export default AudioIcon;

View File

@@ -1,21 +0,0 @@
const VideoIcon = () => {
return (
<svg
width="1em"
height="1em"
viewBox="0 0 16 16"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<title>video</title>
<g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd">
<path
d="M14.1178571,4.0125 C14.225,4.11964286 14.2857143,4.26428571 14.2857143,4.41607143 L14.2857143,15.4285714 C14.2857143,15.7446429 14.0303571,16 13.7142857,16 L2.28571429,16 C1.96964286,16 1.71428571,15.7446429 1.71428571,15.4285714 L1.71428571,0.571428571 C1.71428571,0.255357143 1.96964286,0 2.28571429,0 L9.86964286,0 C10.0214286,0 10.1678571,0.0607142857 10.275,0.167857143 L14.1178571,4.0125 Z M12.9678571,4.67857143 L9.60714286,1.31785714 L9.60714286,4.67857143 L12.9678571,4.67857143 Z M10.5379461,10.3101106 L6.68957555,13.0059749 C6.59910784,13.0693494 6.47439406,13.0473861 6.41101953,12.9569184 C6.3874624,12.9232903 6.37482581,12.8832269 6.37482581,12.8421686 L6.37482581,7.45043999 C6.37482581,7.33998304 6.46436886,7.25043999 6.57482581,7.25043999 C6.61588409,7.25043999 6.65594753,7.26307658 6.68957555,7.28663371 L10.5379461,9.98249803 C10.6284138,10.0458726 10.6503772,10.1705863 10.5870027,10.2610541 C10.5736331,10.2801392 10.5570312,10.2967411 10.5379461,10.3101106 Z"
fill="currentColor"
/>
</g>
</svg>
);
};
export default VideoIcon;

View File

@@ -1,154 +0,0 @@
import {
FileExcelFilled,
FileImageFilled,
FileMarkdownFilled,
FilePdfFilled,
FilePptFilled,
FileTextFilled,
FileWordFilled,
FileZipFilled,
} from "@ant-design/icons";
import AudioIcon from "./AudioIcon";
import VideoIcon from "./VideoIcon";
import { FC, useMemo } from "react";
import clsx from "clsx";
interface FileIconProps {
extname: string;
className?: string;
}
const FileIcon: FC<FileIconProps> = (props) => {
const { extname, className } = props;
const presetFileIcons = [
{
icon: <FileExcelFilled />,
color: "#22b35e",
extnames: ["xlsx", "xls", "csv", "xlsm", "xltx", "xltm", "xlsb"],
},
{
icon: <FileImageFilled />,
color: "#13c2c2",
extnames: [
"png",
"jpg",
"jpeg",
"gif",
"bmp",
"webp",
"svg",
"ico",
"tiff",
"raw",
"heic",
"psd",
"ai",
],
},
{
icon: <FileMarkdownFilled />,
color: "#722ed1",
extnames: ["md", "mdx", "markdown", "mdown", "mkd", "mkdn"],
},
{
icon: <FilePdfFilled />,
color: "#ff4d4f",
extnames: ["pdf", "xps", "oxps"],
},
{
icon: <FilePptFilled />,
color: "#d04423",
extnames: [
"ppt",
"pptx",
"pps",
"ppsx",
"pot",
"potx",
"pptm",
"potm",
"ppsm",
],
},
{
icon: <FileWordFilled />,
color: "#1677ff",
extnames: ["doc", "docx", "dot", "dotx", "docm", "dotm", "rtf", "odt"],
},
{
icon: <FileZipFilled />,
color: "#fab714",
extnames: [
"zip",
"rar",
"7z",
"tar",
"gz",
"bz2",
"xz",
"tgz",
"iso",
"dmg",
],
},
{
icon: <VideoIcon />,
color: "#7b61ff",
extnames: [
"mp4",
"avi",
"mov",
"wmv",
"flv",
"mkv",
"webm",
"m4v",
"mpeg",
"mpg",
"3gp",
"rmvb",
"ts",
],
},
{
icon: <AudioIcon />,
color: "#eb2f96",
extnames: [
"mp3",
"wav",
"flac",
"ape",
"aac",
"ogg",
"wma",
"m4a",
"opus",
"ac3",
"mid",
"midi",
],
},
];
const [icon, iconColor] = useMemo(() => {
for (const item of presetFileIcons) {
const { icon, color, extnames } = item;
if (extnames.includes(extname)) {
return [icon, color];
}
}
return [<FileTextFilled key="defaultIcon" />, "#8c8c8c"];
}, [extname]);
return (
<div className={clsx("text-3xl", className)} style={{ color: iconColor }}>
{icon}
</div>
);
};
export default FileIcon;

View File

@@ -59,7 +59,6 @@ const InputUpload: FC<InputUploadProps> = (props) => {
captureWindowScreenshot,
openFileDialog,
getFileMetadata,
getFileIcon,
} = props;
const { t, i18n } = useTranslation();
const { uploadFiles, setUploadFiles } = useChatStore();
@@ -105,7 +104,6 @@ const InputUpload: FC<InputUploadProps> = (props) => {
...stat,
id: nanoid(),
path,
icon: await getFileIcon(path, 256),
});
}

View File

@@ -8,7 +8,6 @@ import { Metadata } from "tauri-plugin-fs-pro-api";
export interface UploadFile extends Metadata {
id: string;
path: string;
icon: string;
uploaded?: boolean;
attachmentId?: string;
uploadFailed?: boolean;