From cb75e25a1ad09352d8e48893d86d932de2cba747 Mon Sep 17 00:00:00 2001 From: t0ng7u Date: Sun, 10 Aug 2025 01:38:59 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20Add=20model=20icon=20suppor?= =?UTF-8?q?t=20across=20backend=20and=20UI;=20prefer=20model=20icon=20over?= =?UTF-8?q?=20vendor;=20add=20icon=20column=20in=20Models=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Backend: - Model: Add `icon` field to `model.Model` (gorm: varchar(128)); auto-migrated via GORM. - Pricing API: Extend `model.Pricing` with `icon` and populate from model meta in `GetPricing()`. Frontend: - EditModelModal: Add `icon` input (with @lobehub/icons helper link); wire into init/load/submit flows. - ModelHeader / PricingCardView: Prefer rendering `model.icon`; fallback to `vendor_icon`; final fallback to initials avatar. - Models table: Add leading “Icon” column, rendering `model.icon` or `vendor` icon via `getLobeHubIcon`. Notes: - Backward-compatible. Existing data without `icon` remain unaffected. - No manual SQL needed; column is added by AutoMigrate. Affected files: - model/model_meta.go - model/pricing.go - web/src/components/table/models/modals/EditModelModal.jsx - web/src/components/table/model-pricing/modal/components/ModelHeader.jsx - web/src/components/table/model-pricing/view/card/PricingCardView.jsx - web/src/components/table/models/ModelsColumnDefs.js --- model/model_meta.go | 1 + model/pricing.go | 2 ++ .../modal/components/ModelHeader.jsx | 14 +++++++++-- .../view/card/PricingCardView.jsx | 12 +++++++++- .../table/models/ModelsColumnDefs.js | 18 +++++++++++++++ .../table/models/modals/EditModelModal.jsx | 23 +++++++++++++++++++ web/src/i18n/locales/en.json | 9 +++++++- 7 files changed, 75 insertions(+), 4 deletions(-) diff --git a/model/model_meta.go b/model/model_meta.go index 53b00f28..63b6800e 100644 --- a/model/model_meta.go +++ b/model/model_meta.go @@ -38,6 +38,7 @@ type Model struct { Id int `json:"id"` ModelName string `json:"model_name" gorm:"size:128;not null;uniqueIndex:uk_model_name,priority:1"` Description string `json:"description,omitempty" gorm:"type:text"` + Icon string `json:"icon,omitempty" gorm:"type:varchar(128)"` Tags string `json:"tags,omitempty" gorm:"type:varchar(255)"` VendorID int `json:"vendor_id,omitempty" gorm:"index"` Endpoints string `json:"endpoints,omitempty" gorm:"type:text"` diff --git a/model/pricing.go b/model/pricing.go index 2b3920ba..c5fbff36 100644 --- a/model/pricing.go +++ b/model/pricing.go @@ -16,6 +16,7 @@ import ( type Pricing struct { ModelName string `json:"model_name"` Description string `json:"description,omitempty"` + Icon string `json:"icon,omitempty"` Tags string `json:"tags,omitempty"` VendorID int `json:"vendor_id,omitempty"` QuotaType int `json:"quota_type"` @@ -272,6 +273,7 @@ func updatePricing() { continue } pricing.Description = meta.Description + pricing.Icon = meta.Icon pricing.Tags = meta.Tags pricing.VendorID = meta.VendorID } diff --git a/web/src/components/table/model-pricing/modal/components/ModelHeader.jsx b/web/src/components/table/model-pricing/modal/components/ModelHeader.jsx index 63475819..c5302ea5 100644 --- a/web/src/components/table/model-pricing/modal/components/ModelHeader.jsx +++ b/web/src/components/table/model-pricing/modal/components/ModelHeader.jsx @@ -29,9 +29,19 @@ const CARD_STYLES = { }; const ModelHeader = ({ modelData, vendorsMap = {}, t }) => { - // 获取模型图标(使用供应商图标) + // 获取模型图标(优先模型图标,其次供应商图标) const getModelIcon = () => { - // 优先使用供应商图标 + // 1) 优先使用模型自定义图标 + if (modelData?.icon) { + return ( +
+
+ {getLobeHubIcon(modelData.icon, 32)} +
+
+ ); + } + // 2) 退化为供应商图标 if (modelData?.vendor_icon) { return (
diff --git a/web/src/components/table/model-pricing/view/card/PricingCardView.jsx b/web/src/components/table/model-pricing/view/card/PricingCardView.jsx index 516d43e5..a849dbaa 100644 --- a/web/src/components/table/model-pricing/view/card/PricingCardView.jsx +++ b/web/src/components/table/model-pricing/view/card/PricingCardView.jsx @@ -81,7 +81,17 @@ const PricingCardView = ({
); } - // 优先使用供应商图标 + // 1) 优先使用模型自定义图标 + if (model.icon) { + return ( +
+
+ {getLobeHubIcon(model.icon, 32)} +
+
+ ); + } + // 2) 退化为供应商图标 if (model.vendor_icon) { return (
diff --git a/web/src/components/table/models/ModelsColumnDefs.js b/web/src/components/table/models/ModelsColumnDefs.js index ef433553..6514e752 100644 --- a/web/src/components/table/models/ModelsColumnDefs.js +++ b/web/src/components/table/models/ModelsColumnDefs.js @@ -33,6 +33,17 @@ function renderTimestamp(timestamp) { return <>{timestamp2string(timestamp)}; } +// Render model icon column: prefer model.icon, then fallback to vendor icon +const renderModelIconCol = (record, vendorMap) => { + const iconKey = record?.icon || vendorMap[record?.vendor_id]?.icon; + if (!iconKey) return '-'; + return ( +
+ {getLobeHubIcon(iconKey, 20)} +
+ ); +}; + // Render vendor column with icon const renderVendorTag = (vendorId, vendorMap, t) => { if (!vendorId || !vendorMap[vendorId]) return '-'; @@ -222,6 +233,13 @@ export const getModelsColumns = ({ vendorMap, }) => { return [ + { + title: t('图标'), + dataIndex: 'icon', + width: 70, + align: 'center', + render: (text, record) => renderModelIconCol(record, vendorMap), + }, { title: t('模型名称'), dataIndex: 'model_name', diff --git a/web/src/components/table/models/modals/EditModelModal.jsx b/web/src/components/table/models/modals/EditModelModal.jsx index f2f50018..cacb639e 100644 --- a/web/src/components/table/models/modals/EditModelModal.jsx +++ b/web/src/components/table/models/modals/EditModelModal.jsx @@ -33,6 +33,7 @@ import { Row, } from '@douyinfe/semi-ui'; import { Save, X, FileText } from 'lucide-react'; +import { IconLink } from '@douyinfe/semi-icons'; import { API, showError, showSuccess } from '../../../../helpers'; import { useTranslation } from 'react-i18next'; import { useIsMobile } from '../../../../hooks/common/useIsMobile'; @@ -112,6 +113,7 @@ const EditModelModal = (props) => { const getInitValues = () => ({ model_name: props.editingModel?.model_name || '', description: '', + icon: '', tags: [], vendor_id: undefined, vendor: '', @@ -314,6 +316,27 @@ const EditModelModal = (props) => { /> + + + {t('图标使用@lobehub/icons库,如:OpenAI、Claude.Color,支持链式参数:OpenAI.Avatar.type={\'platform\'}、OpenRouter.Avatar.shape={\'square\'},查询所有可用图标请 ')} + } + underline + > + {t('请点击我')} + + + } + showClear + /> + +