🚀 feat: expose “Enabled Groups” for models with real-time refresh
Backend • model/model_meta.go – Added `EnableGroups []string` to Model struct – fillModelExtra now populates EnableGroups • model/model_groups.go – New helper `GetModelEnableGroups` (reuses Pricing cache) • model/pricing_refresh.go – Added `RefreshPricing()` to force immediate cache rebuild • controller/model_meta.go – `GetAllModelsMeta` & `SearchModelsMeta` call `model.RefreshPricing()` before querying, ensuring groups / endpoints are up-to-date Frontend • ModelsColumnDefs.js – Added `renderGroups` util and “可用分组” table column displaying color-coded tags Result Admins can now see which user groups can access each model, and any ability/group changes are reflected instantly without the previous 1-minute delay.
This commit is contained in:
@@ -12,6 +12,9 @@ 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 {
|
||||||
@@ -31,6 +34,9 @@ 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)
|
||||||
@@ -128,7 +134,7 @@ func DeleteModelMeta(c *gin.Context) {
|
|||||||
common.ApiSuccess(c, nil)
|
common.ApiSuccess(c, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助函数:填充 Endpoints 和 BoundChannels
|
// 辅助函数:填充 Endpoints 和 BoundChannels 和 EnableGroups
|
||||||
func fillModelExtra(m *model.Model) {
|
func fillModelExtra(m *model.Model) {
|
||||||
if m.Endpoints == "" {
|
if m.Endpoints == "" {
|
||||||
eps := model.GetModelSupportEndpointTypes(m.ModelName)
|
eps := model.GetModelSupportEndpointTypes(m.ModelName)
|
||||||
@@ -139,5 +145,7 @@ func fillModelExtra(m *model.Model) {
|
|||||||
if channels, err := model.GetBoundChannels(m.ModelName); err == nil {
|
if channels, err := model.GetBoundChannels(m.ModelName); err == nil {
|
||||||
m.BoundChannels = channels
|
m.BoundChannels = channels
|
||||||
}
|
}
|
||||||
|
// 填充启用分组
|
||||||
|
m.EnableGroups = model.GetModelEnableGroups(m.ModelName)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
12
model/model_groups.go
Normal file
12
model/model_groups.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
// GetModelEnableGroups 返回指定模型名称可用的用户分组列表。
|
||||||
|
// 复用缓存的定价映射,避免额外的数据库查询。
|
||||||
|
func GetModelEnableGroups(modelName string) []string {
|
||||||
|
for _, p := range GetPricing() {
|
||||||
|
if p.ModelName == modelName {
|
||||||
|
return p.EnableGroup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return make([]string, 0)
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ type Model struct {
|
|||||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
||||||
|
|
||||||
BoundChannels []BoundChannel `json:"bound_channels,omitempty" gorm:"-"`
|
BoundChannels []BoundChannel `json:"bound_channels,omitempty" gorm:"-"`
|
||||||
|
EnableGroups []string `json:"enable_groups,omitempty" gorm:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert 创建新的模型元数据记录
|
// Insert 创建新的模型元数据记录
|
||||||
|
|||||||
14
model/pricing_refresh.go
Normal file
14
model/pricing_refresh.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
// RefreshPricing 强制立即重新计算与定价相关的缓存。
|
||||||
|
// 该方法用于需要最新数据的内部管理 API,
|
||||||
|
// 因此会绕过默认的 1 分钟延迟刷新。
|
||||||
|
func RefreshPricing() {
|
||||||
|
updatePricingLock.Lock()
|
||||||
|
defer updatePricingLock.Unlock()
|
||||||
|
|
||||||
|
modelSupportEndpointsLock.Lock()
|
||||||
|
defer modelSupportEndpointsLock.Unlock()
|
||||||
|
|
||||||
|
updatePricing()
|
||||||
|
}
|
||||||
@@ -91,6 +91,19 @@ const renderDescription = (text) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Render groups (enable_groups)
|
||||||
|
const renderGroups = (groups) => {
|
||||||
|
if (!groups || groups.length === 0) return '-';
|
||||||
|
return renderLimitedItems({
|
||||||
|
items: groups,
|
||||||
|
renderItem: (g, idx) => (
|
||||||
|
<Tag key={idx} size="small" shape='circle' color={stringToColor(g)}>
|
||||||
|
{g}
|
||||||
|
</Tag>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Render tags
|
// Render tags
|
||||||
const renderTags = (text) => {
|
const renderTags = (text) => {
|
||||||
if (!text) return '-';
|
if (!text) return '-';
|
||||||
@@ -232,6 +245,11 @@ export const getModelsColumns = ({
|
|||||||
dataIndex: 'bound_channels',
|
dataIndex: 'bound_channels',
|
||||||
render: renderBoundChannels,
|
render: renderBoundChannels,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: t('可用分组'),
|
||||||
|
dataIndex: 'enable_groups',
|
||||||
|
render: renderGroups,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: t('创建时间'),
|
title: t('创建时间'),
|
||||||
dataIndex: 'created_time',
|
dataIndex: 'created_time',
|
||||||
|
|||||||
Reference in New Issue
Block a user