feat: add model icon in my models

This commit is contained in:
jialin
2026-07-01 11:42:30 +08:00
committed by jialin
parent 64720ccbfa
commit 185e3475e7
45 changed files with 106 additions and 3 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 801 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

+12 -3
View File
@@ -1,5 +1,4 @@
import {
IconFont,
StatusTag,
TagsWrapper,
TemplateCard,
@@ -20,6 +19,7 @@ import {
MyModelsStatusValueMap
} from '../config';
import { categoryToPathMap } from '../config/button-actions';
import { getModelLogo } from '../utils/model-logo';
const CardWrapper = styled.div`
&:hover {
@@ -84,6 +84,14 @@ const ModelItemContent = styled.div`
}
`;
const ModelLogo = styled.img`
width: 24px;
height: 24px;
border-radius: 4px;
object-fit: contain;
flex: none;
`;
const Header = styled.div`
margin-bottom: 8px;
display: flex;
@@ -176,10 +184,11 @@ const ModelItem: React.FC<{
header={
<Header>
<span className="text gap-8">
<IconFont
{/* <IconFont
type={sourceIconMap[model.source]}
style={{ fontSize: 24 }}
/>
/> */}
<ModelLogo src={getModelLogo(model.name)} alt="" />
<span>{model.name}</span>
</span>
<StatusTag
+94
View File
@@ -0,0 +1,94 @@
// Load every image under src/assets/model_icons at build time.
const iconContext = (require as any).context(
'@/assets/model_icons',
false,
/\.(png|jpe?g|svg)$/
);
const DEFAULT_ICON = 'llm-icon';
// Map of lowercased base filename (without extension) -> resolved asset url.
const iconMap: Record<string, string> = iconContext
.keys()
.reduce((acc: Record<string, string>, key: string) => {
const base = key
.replace(/^\.\//, '')
.replace(/\.(png|jpe?g|svg)$/, '')
.toLowerCase();
acc[base] = iconContext(key).default || iconContext(key);
return acc;
}, {});
// 模型名关键字 → logo 文件名(按顺序匹配第一个命中的子串)。
const MODEL_ICON_RULES: [string, string][] = [
['llama', 'meta'],
['qwen', 'qwen'],
['deepseek', 'deepseek'],
['mixtral', 'mistral'],
['mistral', 'mistral'],
['phi', 'microsoft'],
['gemma', 'google'],
['command-r', 'cohere'],
['cohere', 'cohere'],
['glm', 'thudm'],
['yi-', '01ai'],
['ernie', 'ernie'],
['hunyuan', 'hunyuan'],
['kimi', 'kimi'],
['minimax', 'minimax'],
['falcon', 'falcon3'],
['stable-diffusion', 'stability'],
['sdxl', 'stability'],
['stepfun', 'stepfun'],
['nvidia', 'nvidia'],
// 其余厂商/系列 → 对应 logo(按子串命中)
['gemini', 'google'],
['gpt', 'openai'],
['openai', 'openai'],
['internvl', 'opengvlab'],
['minicpm', 'openbmb'],
['mimo', 'xiaomimimo'],
['flux', 'blackforestlabs'],
['granite', 'ibm'],
['lfm', 'liquidai'],
['liquid', 'liquidai'],
['nomic', 'nomic'],
['jina', 'jina'],
['bge', 'bge'],
['cosyvoice', 'funaudiollm'],
['sensevoice', 'funaudiollm'],
['paddle', 'paddlepaddle'],
['baidu', 'baidu'],
['youdao', 'youdao'],
['systran', 'systran'],
['nanbeige', 'nanbeige'],
['nari', 'narilabs'],
['bagel', 'bagel'],
['zai', 'zai'],
['tencent', 'tencentarc'],
['jetbrains', 'jetbrains'],
['freepik', 'freepik'],
['alibaba', 'alibaba']
];
/**
* Resolve a model logo from its name.
* 1. Match against the keyword rules in order (first substring hit wins).
* 2. Fall back to a direct match where the name contains an icon filename.
* 3. Fall back to the default llm icon.
*/
export const getModelLogo = (modelName?: string): string => {
const name = (modelName || '').toLowerCase();
const rule = MODEL_ICON_RULES.find(([keyword]) => name.includes(keyword));
if (rule && iconMap[rule[1]]) {
return iconMap[rule[1]];
}
const directHit = Object.keys(iconMap)
.filter((iconName) => iconName !== DEFAULT_ICON)
.sort((a, b) => b.length - a.length)
.find((iconName) => name.includes(iconName));
return iconMap[directHit || DEFAULT_ICON] || iconMap[DEFAULT_ICON];
};