🚀 refactor: refine pricing refresh logic & hide disabled models

Summary
-------
1. Pricing generation
   • `model/pricing.go`: skip any model whose `status != 1` when building
     `pricingMap`, ensuring disabled models are never returned to the
     front-end.

2. Cache refresh placement
   • `controller/model_meta.go`
     – Removed `model.RefreshPricing()` from pure read handlers
       (`GetAllModelsMeta`, `SearchModelsMeta`).
     – Kept refresh only in mutating handlers
       (`Create`, `Update`, `Delete`), guaranteeing data is updated
       immediately after an admin change while avoiding redundant work
       on every read.

Result
------
Front-end no longer receives information about disabled models, and
pricing cache refreshes occur exactly when model data is modified,
improving efficiency and consistency.
This commit is contained in:
t0ng7u
2025-08-05 23:18:12 +08:00
parent d951485431
commit 327a0ca323
3 changed files with 20 additions and 16 deletions

View File

@@ -13,8 +13,6 @@ import (
// GetAllModelsMeta 获取模型列表(分页) // GetAllModelsMeta 获取模型列表(分页)
func GetAllModelsMeta(c *gin.Context) { func GetAllModelsMeta(c *gin.Context) {
model.RefreshPricing()
pageInfo := common.GetPageQuery(c) pageInfo := common.GetPageQuery(c)
modelsMeta, err := model.GetAllModels(pageInfo.GetStartIdx(), pageInfo.GetPageSize()) modelsMeta, err := model.GetAllModels(pageInfo.GetStartIdx(), pageInfo.GetPageSize())
if err != nil { if err != nil {
@@ -35,8 +33,6 @@ func GetAllModelsMeta(c *gin.Context) {
// SearchModelsMeta 搜索模型列表 // SearchModelsMeta 搜索模型列表
func SearchModelsMeta(c *gin.Context) { func SearchModelsMeta(c *gin.Context) {
model.RefreshPricing()
keyword := c.Query("keyword") keyword := c.Query("keyword")
vendor := c.Query("vendor") vendor := c.Query("vendor")
pageInfo := common.GetPageQuery(c) pageInfo := common.GetPageQuery(c)
@@ -87,6 +83,7 @@ func CreateModelMeta(c *gin.Context) {
common.ApiError(c, err) common.ApiError(c, err)
return return
} }
model.RefreshPricing()
common.ApiSuccess(c, &m) common.ApiSuccess(c, &m)
} }
@@ -116,6 +113,7 @@ func UpdateModelMeta(c *gin.Context) {
return return
} }
} }
model.RefreshPricing()
common.ApiSuccess(c, &m) common.ApiSuccess(c, &m)
} }
@@ -131,6 +129,7 @@ func DeleteModelMeta(c *gin.Context) {
common.ApiError(c, err) common.ApiError(c, err)
return return
} }
model.RefreshPricing()
common.ApiSuccess(c, nil) common.ApiSuccess(c, nil)
} }
@@ -149,5 +148,4 @@ func fillModelExtra(m *model.Model) {
m.EnableGroups = model.GetModelEnableGroups(m.ModelName) m.EnableGroups = model.GetModelEnableGroups(m.ModelName)
// 填充计费类型 // 填充计费类型
m.QuotaType = model.GetModelQuotaType(m.ModelName) m.QuotaType = model.GetModelQuotaType(m.ModelName)
} }

View File

@@ -118,17 +118,21 @@ func updatePricing() {
for _, m := range prefixList { for _, m := range prefixList {
for _, pricingModel := range enableAbilities { for _, pricingModel := range enableAbilities {
if strings.HasPrefix(pricingModel.Model, m.ModelName) { if strings.HasPrefix(pricingModel.Model, m.ModelName) {
if _, exists := metaMap[pricingModel.Model]; !exists {
metaMap[pricingModel.Model] = m metaMap[pricingModel.Model] = m
} }
} }
} }
}
for _, m := range suffixList { for _, m := range suffixList {
for _, pricingModel := range enableAbilities { for _, pricingModel := range enableAbilities {
if strings.HasSuffix(pricingModel.Model, m.ModelName) { if strings.HasSuffix(pricingModel.Model, m.ModelName) {
if _, exists := metaMap[pricingModel.Model]; !exists {
metaMap[pricingModel.Model] = m metaMap[pricingModel.Model] = m
} }
} }
} }
}
for _, m := range containsList { for _, m := range containsList {
for _, pricingModel := range enableAbilities { for _, pricingModel := range enableAbilities {
if strings.Contains(pricingModel.Model, m.ModelName) { if strings.Contains(pricingModel.Model, m.ModelName) {
@@ -205,8 +209,12 @@ func updatePricing() {
SupportedEndpointTypes: modelSupportEndpointTypes[model], SupportedEndpointTypes: modelSupportEndpointTypes[model],
} }
// 补充模型元数据(描述、标签、供应商 // 补充模型元数据(描述、标签、供应商、状态
if meta, ok := metaMap[model]; ok { if meta, ok := metaMap[model]; ok {
// 若模型被禁用(status!=1),则直接跳过,不返回给前端
if meta.Status != 1 {
continue
}
pricing.Description = meta.Description pricing.Description = meta.Description
pricing.Tags = meta.Tags pricing.Tags = meta.Tags
pricing.VendorID = meta.VendorID pricing.VendorID = meta.VendorID

View File

@@ -305,7 +305,6 @@ const EditModelModal = (props) => {
label={t('模型名称')} label={t('模型名称')}
placeholder={t('请输入模型名称gpt-4')} placeholder={t('请输入模型名称gpt-4')}
rules={[{ required: true, message: t('请输入模型名称') }]} rules={[{ required: true, message: t('请输入模型名称') }]}
disabled={isEdit || !!props.editingModel?.model_name}
showClear showClear
/> />
</Col> </Col>
@@ -317,9 +316,8 @@ const EditModelModal = (props) => {
placeholder={t('请选择名称匹配类型')} placeholder={t('请选择名称匹配类型')}
optionList={nameRuleOptions.map(o => ({ label: t(o.label), value: o.value }))} optionList={nameRuleOptions.map(o => ({ label: t(o.label), value: o.value }))}
rules={[{ required: true, message: t('请选择名称匹配类型') }]} rules={[{ required: true, message: t('请选择名称匹配类型') }]}
disabled={!!props.editingModel?.model_name} // 通过未配置模型过来的禁用选择
style={{ width: '100%' }}
extraText={t('根据模型名称和匹配规则查找模型元数据,优先级:精确 > 前缀 > 后缀 > 包含')} extraText={t('根据模型名称和匹配规则查找模型元数据,优先级:精确 > 前缀 > 后缀 > 包含')}
style={{ width: '100%' }}
/> />
</Col> </Col>
@@ -339,13 +337,13 @@ const EditModelModal = (props) => {
placeholder={t('选择标签组后将自动填充标签')} placeholder={t('选择标签组后将自动填充标签')}
optionList={tagGroups.map(g => ({ label: g.name, value: g.id }))} optionList={tagGroups.map(g => ({ label: g.name, value: g.id }))}
showClear showClear
style={{ width: '100%' }}
onChange={(value) => { onChange={(value) => {
const g = tagGroups.find(item => item.id === value); const g = tagGroups.find(item => item.id === value);
if (g && formApiRef.current) { if (g && formApiRef.current) {
formApiRef.current.setValue('tags', g.items || []); formApiRef.current.setValue('tags', g.items || []);
} }
}} }}
style={{ width: '100%' }}
/> />
</Col> </Col>
@@ -356,7 +354,6 @@ const EditModelModal = (props) => {
placeholder={t('输入标签或使用","分隔多个标签')} placeholder={t('输入标签或使用","分隔多个标签')}
addOnBlur addOnBlur
showClear showClear
style={{ width: '100%' }}
onChange={(newTags) => { onChange={(newTags) => {
if (!formApiRef.current) return; if (!formApiRef.current) return;
const normalize = (tags) => { const normalize = (tags) => {
@@ -366,6 +363,7 @@ const EditModelModal = (props) => {
const normalized = normalize(newTags); const normalized = normalize(newTags);
formApiRef.current.setValue('tags', normalized); formApiRef.current.setValue('tags', normalized);
}} }}
style={{ width: '100%' }}
/> />
</Col> </Col>
</Row> </Row>
@@ -391,13 +389,13 @@ const EditModelModal = (props) => {
optionList={vendors.map(v => ({ label: v.name, value: v.id }))} optionList={vendors.map(v => ({ label: v.name, value: v.id }))}
filter filter
showClear showClear
style={{ width: '100%' }}
onChange={(value) => { onChange={(value) => {
const vendorInfo = vendors.find(v => v.id === value); const vendorInfo = vendors.find(v => v.id === value);
if (vendorInfo && formApiRef.current) { if (vendorInfo && formApiRef.current) {
formApiRef.current.setValue('vendor', vendorInfo.name); formApiRef.current.setValue('vendor', vendorInfo.name);
} }
}} }}
style={{ width: '100%' }}
/> />
</Col> </Col>
</Row> </Row>