feat(user-models): fall back to category icon when no brand logo matches

This commit is contained in:
jialin
2026-07-16 18:00:27 +08:00
committed by jialin
parent e30dd10fe4
commit d8cb5261b9
8 changed files with 172 additions and 6 deletions
+17 -2
View File
@@ -20,7 +20,11 @@ import {
MyModelsStatusValueMap
} from '../config';
import { categoryToPathMap } from '../config/button-actions';
import { getModelLogo } from '../utils/model-logo';
import {
defaultModelLogo,
getCategoryLogo,
getModelLogo
} from '../utils/model-logo';
const CardWrapper = styled.div`
&:hover {
@@ -165,6 +169,11 @@ const ModelItem: React.FC<{
navigate(`/playground/chat?model=${modelName}`);
};
// Logo priority: brand logo matched from the name → tinted category
// icon (from model_icons) → generic default image.
const brandLogo = getModelLogo(model.name);
const categoryLogo = brandLogo ? null : getCategoryLogo(model.categories);
// context length
const maxToken = useMemo(() => {
const meta = model.meta || {};
@@ -194,7 +203,13 @@ const ModelItem: React.FC<{
header={
<Header>
<span className="text gap-16">
<ModelLogo src={getModelLogo(model.name)} alt="" />
{brandLogo ? (
<ModelLogo src={brandLogo} alt="" />
) : categoryLogo ? (
<ModelLogo src={categoryLogo} alt="" />
) : (
<ModelLogo src={defaultModelLogo} alt="" />
)}
<span>{model.name}</span>
</span>
<StatusTag
+32 -4
View File
@@ -44,6 +44,7 @@ const MODEL_ICON_RULES: [string, string][] = [
// 其余厂商/系列 → 对应 logo(按子串命中)
['gemini', 'google'],
['gpt', 'openai'],
['hy', 'hunyuan'],
['openai', 'openai'],
['internvl', 'opengvlab'],
['minicpm', 'openbmb'],
@@ -71,13 +72,40 @@ const MODEL_ICON_RULES: [string, string][] = [
['alibaba', 'alibaba']
];
// Default llm logo, used as the ultimate fallback when neither a brand
// logo nor a model-category icon can be resolved.
export const defaultModelLogo = iconMap[DEFAULT_ICON];
// 模型类别 → model_icons 下的类别图标文件名。
const CATEGORY_ICON_MAP: Record<string, string> = {
llm: 'llm',
embedding: 'embedding',
reranker: 'reranker',
image: 'image',
text_to_speech: 'tts',
speech_to_text: 'stt'
};
/**
* Resolve a model logo from its name.
* Resolve a category icon (from model_icons) for a model's categories.
* Returns the first matching category icon url, or null when none match.
*/
export const getCategoryLogo = (categories?: string[]): string | null => {
const iconName = categories
?.map((category) => CATEGORY_ICON_MAP[category])
.find((name) => name && iconMap[name]);
return iconName ? iconMap[iconName] : null;
};
/**
* Resolve a brand logo from a model 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.
* 3. Return null when nothing matches — the caller then falls back to a
* category-based icon (see `categoryConfig`) rather than a default image.
*/
export const getModelLogo = (modelName?: string): string => {
export const getModelLogo = (modelName?: string): string | null => {
const name = (modelName || '').toLowerCase();
const rule = MODEL_ICON_RULES.find(([keyword]) => name.includes(keyword));
@@ -90,5 +118,5 @@ export const getModelLogo = (modelName?: string): string => {
.sort((a, b) => b.length - a.length)
.find((iconName) => name.includes(iconName));
return iconMap[directHit || DEFAULT_ICON] || iconMap[DEFAULT_ICON];
return directHit ? iconMap[directHit] : null;
};