Skip to content

Commit 8653a3f

Browse files
authored
feat(ModelPrice):增加模型详细信息模块&&一个漂亮的可用模型页面 (#860)
* feat(model): 添加模型信息管理功能 - 新增EditModal组件用于创建和编辑模型信息 - 新增ModelInfo页面组件用于展示和管理模型列表 - 支持模型标识、名称、描述、上下文长度、最大Token、输入/输出模态和标签的配置 - 集成模型列表获取、搜索、分页和删除功能 * feat(model): 添加批量导入模型信息功能 * 新增ImportModal组件用于批量导入模型信息 * 支持从JSON URL获取模型数据并提供预览功能 * 实现冲突处理策略(跳过或覆盖已存在的模型) * 添加导入进度显示和结果统计 * 优化EditModal组件中key prop的传递方式 * feat(ui): 添加模型价格卡片和详情弹窗组件 * 新增ModelCard组件用于展示模型信息卡片 * 新增ModelDetailModal组件用于展示模型详细信息弹窗 * 支持显示模型描述、模态、标签和价格信息 * 添加用户组和渠道价格对比功能 * 优化卡片悬停效果和交互体验 * feat(ui): 完善模型价格页面国际化支持 - 替换硬编码文本为多语言翻译函数调用 - 新增模型定价、模态类型、标签等核心功能的翻译键 - 支持繁体中文、英文、简体中文和日语的完整翻译 - 优化价格详情表格结构,移除其他价格列显示 - 统一组件内的国际化调用规范 * feat(ui): 优化模型价格显示与标签样式 * 支持按计费方式(token/次数)动态显示价格格式 * 改进标签显示,Hot标签增加火焰图标,使用Label组件替代Chip * 优化价格详情弹窗布局,调整关闭按钮位置 * 重构formatPrice函数,统一价格格式化逻辑 * 修复当值为0时隐藏相关信息的问题 * refactor(pricing): 优化价格计算中的数值处理逻辑 * 统一价格计算函数中的数值转换,确保类型安全 * 将extra_ratios的数值转换逻辑移至提交处理阶段 * 优化价格显示逻辑,统一使用Number()进行类型转换 * 简化表单处理中的数值转换逻辑,减少重复转换 * 调整ExtraRatiosSelector组件中的数值处理方式
1 parent 1de7ac3 commit 8653a3f

File tree

22 files changed

+2911
-282
lines changed

22 files changed

+2911
-282
lines changed

controller/model_info.go

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package controller
2+
3+
import (
4+
"net/http"
5+
"one-api/model"
6+
"strconv"
7+
8+
"github.com/gin-gonic/gin"
9+
)
10+
11+
func GetAllModelInfo(c *gin.Context) {
12+
modelInfos, err := model.GetAllModelInfo()
13+
if err != nil {
14+
c.JSON(http.StatusOK, gin.H{
15+
"success": false,
16+
"message": err.Error(),
17+
})
18+
return
19+
}
20+
c.JSON(http.StatusOK, gin.H{
21+
"success": true,
22+
"message": "",
23+
"data": modelInfos,
24+
})
25+
}
26+
27+
func GetModelInfo(c *gin.Context) {
28+
id, err := strconv.Atoi(c.Param("id"))
29+
if err != nil {
30+
c.JSON(http.StatusOK, gin.H{
31+
"success": false,
32+
"message": err.Error(),
33+
})
34+
return
35+
}
36+
modelInfo, err := model.GetModelInfo(id)
37+
if err != nil {
38+
c.JSON(http.StatusOK, gin.H{
39+
"success": false,
40+
"message": err.Error(),
41+
})
42+
return
43+
}
44+
c.JSON(http.StatusOK, gin.H{
45+
"success": true,
46+
"message": "",
47+
"data": modelInfo,
48+
})
49+
}
50+
51+
func CreateModelInfo(c *gin.Context) {
52+
modelInfo := model.ModelInfo{}
53+
err := c.ShouldBindJSON(&modelInfo)
54+
if err != nil {
55+
c.JSON(http.StatusOK, gin.H{
56+
"success": false,
57+
"message": err.Error(),
58+
})
59+
return
60+
}
61+
existingModel, _ := model.GetModelInfoByModel(modelInfo.Model)
62+
if existingModel != nil {
63+
c.JSON(http.StatusOK, gin.H{
64+
"success": false,
65+
"message": "model identifier already exists",
66+
})
67+
return
68+
}
69+
err = model.CreateModelInfo(&modelInfo)
70+
if err != nil {
71+
c.JSON(http.StatusOK, gin.H{
72+
"success": false,
73+
"message": err.Error(),
74+
})
75+
return
76+
}
77+
c.JSON(http.StatusOK, gin.H{
78+
"success": true,
79+
"message": "",
80+
})
81+
}
82+
83+
func UpdateModelInfo(c *gin.Context) {
84+
modelInfo := model.ModelInfo{}
85+
err := c.ShouldBindJSON(&modelInfo)
86+
if err != nil {
87+
c.JSON(http.StatusOK, gin.H{
88+
"success": false,
89+
"message": err.Error(),
90+
})
91+
return
92+
}
93+
existingModel, _ := model.GetModelInfoByModel(modelInfo.Model)
94+
if existingModel != nil && existingModel.Id != modelInfo.Id {
95+
c.JSON(http.StatusOK, gin.H{
96+
"success": false,
97+
"message": "model identifier already exists",
98+
})
99+
return
100+
}
101+
err = model.UpdateModelInfo(&modelInfo)
102+
if err != nil {
103+
c.JSON(http.StatusOK, gin.H{
104+
"success": false,
105+
"message": err.Error(),
106+
})
107+
return
108+
}
109+
c.JSON(http.StatusOK, gin.H{
110+
"success": true,
111+
"message": "",
112+
})
113+
}
114+
115+
func DeleteModelInfo(c *gin.Context) {
116+
id, err := strconv.Atoi(c.Param("id"))
117+
if err != nil {
118+
c.JSON(http.StatusOK, gin.H{
119+
"success": false,
120+
"message": err.Error(),
121+
})
122+
return
123+
}
124+
err = model.DeleteModelInfo(id)
125+
if err != nil {
126+
c.JSON(http.StatusOK, gin.H{
127+
"success": false,
128+
"message": err.Error(),
129+
})
130+
return
131+
}
132+
c.JSON(http.StatusOK, gin.H{
133+
"success": true,
134+
"message": "",
135+
})
136+
}

model/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ func InitDB() (err error) {
184184
return err
185185
}
186186

187+
err = db.AutoMigrate(&ModelInfo{})
188+
if err != nil {
189+
return err
190+
}
191+
187192
err = DB.AutoMigrate(&WebAuthnCredential{})
188193
if err != nil {
189194
return err

model/model_info.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package model
2+
3+
import (
4+
"one-api/common/logger"
5+
)
6+
7+
type ModelInfo struct {
8+
Id int `json:"id" gorm:"index"`
9+
Model string `json:"model" gorm:"type:varchar(100);index"`
10+
Name string `json:"name" gorm:"type:varchar(100)"`
11+
Description string `json:"description" gorm:"type:text"`
12+
ContextLength int `json:"context_length"`
13+
MaxTokens int `json:"max_tokens"`
14+
InputModalities string `json:"input_modalities" gorm:"type:text"`
15+
OutputModalities string `json:"output_modalities" gorm:"type:text"`
16+
Tags string `json:"tags" gorm:"type:text"`
17+
SupportUrl string `json:"support_url" gorm:"type:text"`
18+
CreatedAt int64 `json:"created_at" gorm:"autoCreateTime"`
19+
UpdatedAt int64 `json:"updated_at" gorm:"autoUpdateTime"`
20+
}
21+
22+
func (m *ModelInfo) TableName() string {
23+
return "model_info"
24+
}
25+
26+
func CreateModelInfo(modelInfo *ModelInfo) error {
27+
err := DB.Create(modelInfo).Error
28+
if err != nil {
29+
return err
30+
}
31+
return nil
32+
}
33+
34+
func UpdateModelInfo(modelInfo *ModelInfo) error {
35+
err := DB.Omit("id", "created_at").Save(modelInfo).Error
36+
if err != nil {
37+
return err
38+
}
39+
return nil
40+
}
41+
42+
func GetModelInfo(id int) (*ModelInfo, error) {
43+
modelInfo := &ModelInfo{}
44+
err := DB.Where("id = ?", id).First(modelInfo).Error
45+
if err != nil {
46+
return nil, err
47+
}
48+
return modelInfo, nil
49+
}
50+
51+
func GetModelInfoByModel(model string) (*ModelInfo, error) {
52+
modelInfo := &ModelInfo{}
53+
err := DB.Where("model = ?", model).First(modelInfo).Error
54+
if err != nil {
55+
return nil, err
56+
}
57+
return modelInfo, nil
58+
}
59+
60+
func GetAllModelInfo() ([]*ModelInfo, error) {
61+
var modelInfos []*ModelInfo
62+
err := DB.Order("id desc").Find(&modelInfos).Error
63+
if err != nil {
64+
return nil, err
65+
}
66+
return modelInfos, nil
67+
}
68+
69+
func DeleteModelInfo(id int) error {
70+
err := DB.Delete(&ModelInfo{}, id).Error
71+
if err != nil {
72+
return err
73+
}
74+
return nil
75+
}
76+
77+
func InitModelInfo() {
78+
// Auto migrate logic is handled centrally usually, but if needed here:
79+
err := DB.AutoMigrate(&ModelInfo{})
80+
if err != nil {
81+
logger.SysError("Failed to auto migrate ModelInfo: " + err.Error())
82+
}
83+
}

router/api-router.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,16 @@ func SetApiRouter(router *gin.Engine) {
128128
modelOwnedByRoute.DELETE("/:id", controller.DeleteModelOwnedBy)
129129
}
130130

131+
modelInfoRoute := apiRouter.Group("/model_info")
132+
modelInfoRoute.GET("/", controller.GetAllModelInfo)
133+
modelInfoRoute.Use(middleware.AdminAuth())
134+
{
135+
modelInfoRoute.GET("/:id", controller.GetModelInfo)
136+
modelInfoRoute.POST("/", controller.CreateModelInfo)
137+
modelInfoRoute.PUT("/", controller.UpdateModelInfo)
138+
modelInfoRoute.DELETE("/:id", controller.DeleteModelInfo)
139+
}
140+
131141
userGroup := apiRouter.Group("/user_group")
132142
userGroup.Use(middleware.AdminAuth())
133143
{

web/src/constants/Modality.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export const MODALITY_OPTIONS = {
2+
audio: {
3+
value: 'audio',
4+
text: 'Audio',
5+
color: 'warning'
6+
},
7+
image: {
8+
value: 'image',
9+
text: 'Image',
10+
color: 'info'
11+
},
12+
music: {
13+
value: 'music',
14+
text: 'Music',
15+
color: 'error'
16+
},
17+
text: {
18+
value: 'text',
19+
text: 'Text',
20+
color: 'success'
21+
},
22+
video: {
23+
value: 'video',
24+
text: 'Video',
25+
color: 'secondary'
26+
}
27+
};

web/src/i18n/locales/en_US.json

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -540,37 +540,51 @@
540540
"modelpricePage": {
541541
"availableModels": "Available Models",
542542
"channelType": "Supplier",
543-
"group": "grouping",
544-
"inputMultiplier": "Input price",
543+
"group": "Group",
544+
"inputMultiplier": "Input Price",
545545
"model": "Model Name",
546-
"noneGroup": "The current group is not available.",
547-
"outputMultiplier": "Output price",
548-
"search": "Search",
549-
"times": "times",
550-
"tokens": "tokens",
546+
"noneGroup": "Current group unavailable",
547+
"outputMultiplier": "Output Price",
548+
"search": "Search model name or description",
549+
"times": "Pay per call",
550+
"tokens": "Pay per token",
551551
"type": "Type",
552-
"input_audio_tokens": "Audio input ratio",
552+
"input_audio_tokens": "Audio Input Ratio",
553553
"other": "Other",
554-
"output_audio_tokens": "Audio output ratio",
554+
"output_audio_tokens": "Audio Output Ratio",
555555
"rate": "Rate",
556-
"RPM": "API rate",
556+
"RPM": "API Rate",
557557
"free": "Free",
558558
"cached_tokens": "Cache Ratio",
559-
"cached_write_tokens": "Cache write ratio",
559+
"cached_write_tokens": "Cache Write Ratio",
560560
"cached_read_tokens": "Cache Read Ratio",
561561
"reasoning_tokens": "Reasoning Ratio",
562-
"input_text_tokens": "Input text Ratio",
563-
"output_text_tokens": "Output text Ratio",
562+
"input_text_tokens": "Input Text Ratio",
563+
"output_text_tokens": "Output Text Ratio",
564564
"all": "All",
565-
"extraRatios": "Expand price",
565+
"extraRatios": "Extended Pricing",
566566
"input": "Input",
567-
"input_image_tokens": "Enter image magnification",
568-
"noExtraRatios": "No additional charge",
569-
"output": "output",
570-
"output_image_tokens": "Output image magnification",
567+
"input_image_tokens": "Input Image Ratio",
568+
"noExtraRatios": "No extended pricing",
569+
"output": "Output",
570+
"output_image_tokens": "Output Image Ratio",
571571
"price": "Price",
572-
"showAll": "Show all",
573-
"onlyAvailable": "Only show available"
572+
"showAll": "Show All",
573+
"onlyAvailable": "Only Available",
574+
"modelPricing": "Model Pricing",
575+
"unit": "Unit",
576+
"modalityType": "Modality Type",
577+
"allModality": "All",
578+
"tags": "Tags",
579+
"allTags": "All",
580+
"modelInfo": "Model Information",
581+
"contextLength": "Context Length",
582+
"maxTokens": "Max Tokens",
583+
"inputModality": "Input Modality",
584+
"outputModality": "Output Modality",
585+
"priceDetails": "Price Details",
586+
"priceNote": "Different groups have different prices. Switch groups in token management.",
587+
"otherInfo": "Other Information"
574588
},
575589
"nova 映射": "nova mapping",
576590
"onyx 映射": "onyx mapping",
@@ -1508,4 +1522,4 @@
15081522
}
15091523
},
15101524
"这里填写禁用流式的模型,注意:如果填写了禁用流式的模型,那么这些模型在流式请求时会跳过该渠道": "Fill in here to disable the streaming model. Note: If you fill in to disable the streaming model, these models will be skipped for streaming requests on that channel."
1511-
}
1525+
}

web/src/i18n/locales/ja_JP.json

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,21 @@
558558
"output_image_tokens": "画像倍率",
559559
"price": "価格",
560560
"showAll": "すべて表示",
561-
"onlyAvailable": "利用可能なだけ表示"
561+
"onlyAvailable": "利用可能なだけ表示",
562+
"modelPricing": "モデル価格設定",
563+
"unit": "単位",
564+
"modalityType": "モダリティタイプ",
565+
"allModality": "すべて",
566+
"tags": "タグ",
567+
"allTags": "すべて",
568+
"modelInfo": "モデル情報",
569+
"contextLength": "コンテキスト長",
570+
"maxTokens": "最大トークン数",
571+
"inputModality": "入力モダリティ",
572+
"outputModality": "出力モダリティ",
573+
"priceDetails": "価格詳細",
574+
"priceNote": "グループによって価格が異なります。トークン管理でグループを切り替えてください。",
575+
"otherInfo": "その他の情報"
562576
},
563577
"nova 映射": "新星マッピング",
564578
"onyx 映射": "オニキスマッピング",
@@ -1476,4 +1490,4 @@
14761490
}
14771491
},
14781492
"这里填写禁用流式的模型,注意:如果填写了禁用流式的模型,那么这些模型在流式请求时会跳过该渠道": "ここには、ストリーミングを無効にするモデルを記入してください。注意:ストリーミングを無効にするモデルを記入した場合、これらのモデルはストリームリクエスト時にそのチャンネルをスキップします。"
1479-
}
1493+
}

0 commit comments

Comments
 (0)