feat: add model icon in my models
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 2.1 MiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
|
After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 640 B |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 801 B |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
IconFont,
|
|
||||||
StatusTag,
|
StatusTag,
|
||||||
TagsWrapper,
|
TagsWrapper,
|
||||||
TemplateCard,
|
TemplateCard,
|
||||||
@@ -20,6 +19,7 @@ import {
|
|||||||
MyModelsStatusValueMap
|
MyModelsStatusValueMap
|
||||||
} from '../config';
|
} from '../config';
|
||||||
import { categoryToPathMap } from '../config/button-actions';
|
import { categoryToPathMap } from '../config/button-actions';
|
||||||
|
import { getModelLogo } from '../utils/model-logo';
|
||||||
|
|
||||||
const CardWrapper = styled.div`
|
const CardWrapper = styled.div`
|
||||||
&:hover {
|
&: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`
|
const Header = styled.div`
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -176,10 +184,11 @@ const ModelItem: React.FC<{
|
|||||||
header={
|
header={
|
||||||
<Header>
|
<Header>
|
||||||
<span className="text gap-8">
|
<span className="text gap-8">
|
||||||
<IconFont
|
{/* <IconFont
|
||||||
type={sourceIconMap[model.source]}
|
type={sourceIconMap[model.source]}
|
||||||
style={{ fontSize: 24 }}
|
style={{ fontSize: 24 }}
|
||||||
/>
|
/> */}
|
||||||
|
<ModelLogo src={getModelLogo(model.name)} alt="" />
|
||||||
<span>{model.name}</span>
|
<span>{model.name}</span>
|
||||||
</span>
|
</span>
|
||||||
<StatusTag
|
<StatusTag
|
||||||
|
|||||||
@@ -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];
|
||||||
|
};
|
||||||