Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
a044070
🎨 feat(model-pricing): refactor layout and component structure (#1365)
t0ng7u Jul 22, 2025
b964f75
✨ feat(ui): enhance pricing table & filters with responsive button-gr…
t0ng7u Jul 22, 2025
902aee4
📌 fix(pricing-search): make search bar sticky within PricingContent (…
t0ng7u Jul 22, 2025
c15e753
🔧 refactor(pricing-filters): extract display settings & improve mobil…
t0ng7u Jul 22, 2025
bf491d6
♻️ refactor(model-pricing): extract `resetPricingFilters` utility and…
t0ng7u Jul 22, 2025
4247883
💄 feat(ui): replace availability indicators with icons in PricingTabl…
t0ng7u Jul 22, 2025
6d06cb8
✨ feat: enhance SelectableButtonGroup with checkbox support and refac…
t0ng7u Jul 22, 2025
3f96bd9
✨ feat: Add skeleton loading animation to SelectableButtonGroup compo…
t0ng7u Jul 22, 2025
8a54512
🔧 fix: filter out empty string group from pricing groups selector (#1…
t0ng7u Jul 23, 2025
a99dbc7
♻️ refactor(model-pricing): improve table UI and optimize code struct…
t0ng7u Jul 23, 2025
c4b69b3
Merge branch 'alpha' into refactor/model-pricing
t0ng7u Jul 23, 2025
53be79a
💄 style(pricing): enhance card view UI and skeleton loading experienc…
t0ng7u Jul 23, 2025
59a76b3
✨ feat: Add endpoint type filter to model pricing system
t0ng7u Jul 23, 2025
e417c26
🖼️ style(ui): change skeleton button size to 16*16
t0ng7u Jul 23, 2025
1880164
♻️ Refactor: Move token unit toggle from table header to filter settings
t0ng7u Jul 24, 2025
2fe3706
♻️ refactor(utils): optimize resetPricingFilters function for better …
t0ng7u Jul 24, 2025
5ceb898
♻️ refactor(utils): optimize resetPricingFilters function for better …
t0ng7u Jul 24, 2025
1c25e29
📱 fix(ui): adjust responsive breakpoints for pricing card grid layout
t0ng7u Jul 24, 2025
aecdbfa
Merge remote-tracking branch 'origin/alpha' into refactor/model-pricing
t0ng7u Jul 24, 2025
34d45bb
🍭 style(ui): Optimize style layout and improve responsive layout
t0ng7u Jul 24, 2025
52a9cee
Merge branch 'alpha' into refactor/model-pricing
t0ng7u Jul 25, 2025
0b1a1ca
✨ refactor: Restructure model pricing components and improve UX consi…
t0ng7u Jul 25, 2025
9110611
Merge branch 'alpha' into refactor/model-pricing
t0ng7u Jul 26, 2025
75548c4
✨ refactor: pricing filters for dynamic counting & cleaner logic
t0ng7u Jul 26, 2025
b41c24d
Merge branch 'alpha' into refactor/model-pricing
t0ng7u Jul 26, 2025
4cc76f2
Merge branch 'alpha' into refactor/model-pricing
t0ng7u Jul 27, 2025
82bf149
Merge branch 'alpha' into refactor/model-pricing
t0ng7u Jul 30, 2025
af59b61
🚀 feat: Introduce full Model & Vendor Management suite (backend + fro…
t0ng7u Jul 31, 2025
6a37efb
Merge branch 'alpha' into refactor/model-pricing
t0ng7u Jul 31, 2025
2326128
🔄 fix: improve vendor-tab filtering & counts, resolve SQL ambiguity, …
t0ng7u Jul 31, 2025
eb42eb6
🐛 fix(model): preserve created_time on Model update and streamline fi…
t0ng7u Jul 31, 2025
5e81ef4
♻️ refactor(hooks/models): deduplicate `useModelsData` and optimize v…
t0ng7u Jul 31, 2025
508799c
🎨 style(sidebar): unify highlight color & assign unique icon for Models
t0ng7u Jul 31, 2025
9730b9b
✨ feat(ui): enhance tag input
t0ng7u Jul 31, 2025
6a62654
Merge branch 'alpha' into refactor/model-pricing
t0ng7u Aug 2, 2025
984c8ee
♻️ refactor(models-table): extract reusable `renderLimitedItems` for …
t0ng7u Aug 3, 2025
8a2aebf
✨ feat(edit-vendor-modal): add icon-library reference link & tidy sta…
t0ng7u Aug 3, 2025
e74d3f4
✨ feat: polish “Missing Models” UX & mobile actions layout
t0ng7u Aug 3, 2025
b64c8ea
🚀 feat: expose “Enabled Groups” for models with real-time refresh
t0ng7u Aug 3, 2025
9f60273
✨ feat: Add prefill group management system for models
t0ng7u Aug 3, 2025
57b194c
Merge remote-tracking branch 'origin/alpha' into refactor/model-pricing
t0ng7u Aug 4, 2025
5e70274
💰 feat: Add model billing type (`quota_type`) support across backend …
t0ng7u Aug 4, 2025
fc69f4f
✨ feat: add model name matching rules with priority-based lookup
t0ng7u Aug 4, 2025
0e9c3cd
🏗️ refactor: Replace model categories with vendor-based filtering and…
t0ng7u Aug 4, 2025
512850e
Merge remote-tracking branch 'origin/alpha' into refactor/model-pricing
t0ng7u Aug 4, 2025
11ee80d
🍎 chore: modify the `JSONEditor` component import path
t0ng7u Aug 4, 2025
1ccc728
💄 fix(pricing-card): align skeleton responsive grid with actual card …
t0ng7u Aug 4, 2025
f3a1f98
🐛 fix(models): eliminate vendor column flicker by loading vendors bef…
t0ng7u Aug 4, 2025
d951485
✨ feat: enhance soft-delete handling & boost pricing cache performance
t0ng7u Aug 5, 2025
327a0ca
🚀 refactor: refine pricing refresh logic & hide disabled models
t0ng7u Aug 5, 2025
d61a862
Merge remote-tracking branch 'origin/alpha' into refactor/model-pricing
t0ng7u Aug 5, 2025
7c814a5
🚀 refactor: migrate vendor-count aggregation to model layer & align f…
t0ng7u Aug 5, 2025
94506be
✨ feat(models): Revamp EditModelModal UI and UX
t0ng7u Aug 5, 2025
0a231a8
🎨 feat(models): add row styling for disabled models in ModelsTable
t0ng7u Aug 7, 2025
7cfeb6e
Merge remote-tracking branch 'origin/alpha' into refactor/model-pricing
t0ng7u Aug 7, 2025
8fba001
✨ feat(pricing+endpoints+ui): wire custom endpoint mapping end‑to‑end…
t0ng7u Aug 7, 2025
26f44b8
Merge remote-tracking branch 'origin/alpha' into refactor/model-pricing
t0ng7u Aug 7, 2025
4e75a9b
✨ feat: Improve models UX and robustness: add JSONEditor extraFooter,…
t0ng7u Aug 7, 2025
6341847
🎨 refactor(ui): merge “Content Configuration” into “Basic Information…
t0ng7u Aug 7, 2025
4b37029
🤔 revert: revert `go.sum` & `go.mod` version
t0ng7u Aug 7, 2025
bbac342
🤔 revert: revert `go.sum` & `go.mod` version
t0ng7u Aug 7, 2025
7f1a471
♻️ refactor(model): replace gorm.io/datatypes with json.RawMessage fo…
t0ng7u Aug 7, 2025
473f3b6
♻️ refactor(model): replace gorm.io/datatypes with JSONValue for Pref…
t0ng7u Aug 7, 2025
d96f846
🐛 fix(model, web): robust JSON handling; remove datatypes dep; stabil…
t0ng7u Aug 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions common/endpoint_defaults.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package common

import "one-api/constant"

// EndpointInfo 描述单个端点的默认请求信息
// path: 上游路径
// method: HTTP 请求方式,例如 POST/GET
// 目前均为 POST,后续可扩展
//
// json 标签用于直接序列化到 API 输出
// 例如:{"path":"/v1/chat/completions","method":"POST"}

type EndpointInfo struct {
Path string `json:"path"`
Method string `json:"method"`
}

// defaultEndpointInfoMap 保存内置端点的默认 Path 与 Method
var defaultEndpointInfoMap = map[constant.EndpointType]EndpointInfo{
constant.EndpointTypeOpenAI: {Path: "/v1/chat/completions", Method: "POST"},
constant.EndpointTypeOpenAIResponse: {Path: "/v1/responses", Method: "POST"},
constant.EndpointTypeAnthropic: {Path: "/v1/messages", Method: "POST"},
constant.EndpointTypeGemini: {Path: "/v1beta/models/{model}:generateContent", Method: "POST"},
constant.EndpointTypeJinaRerank: {Path: "/rerank", Method: "POST"},
constant.EndpointTypeImageGeneration: {Path: "/v1/images/generations", Method: "POST"},
}

// GetDefaultEndpointInfo 返回指定端点类型的默认信息以及是否存在
func GetDefaultEndpointInfo(et constant.EndpointType) (EndpointInfo, bool) {
info, ok := defaultEndpointInfoMap[et]
return info, ok
}
27 changes: 27 additions & 0 deletions controller/missing_models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package controller

import (
"net/http"
"one-api/model"

"github.com/gin-gonic/gin"
)

// GetMissingModels returns the list of model names that are referenced by channels
// but do not have corresponding records in the models meta table.
// This helps administrators quickly discover models that need configuration.
func GetMissingModels(c *gin.Context) {
missing, err := model.GetMissingModels()
if err != nil {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": err.Error(),
})
return
}

c.JSON(http.StatusOK, gin.H{
"success": true,
"data": missing,
})
}
178 changes: 178 additions & 0 deletions controller/model_meta.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package controller

import (
"encoding/json"
"strconv"

"one-api/common"
"one-api/model"

"github.com/gin-gonic/gin"
)

// GetAllModelsMeta 获取模型列表(分页)
func GetAllModelsMeta(c *gin.Context) {

pageInfo := common.GetPageQuery(c)
modelsMeta, err := model.GetAllModels(pageInfo.GetStartIdx(), pageInfo.GetPageSize())
if err != nil {
common.ApiError(c, err)
return
}
// 填充附加字段
for _, m := range modelsMeta {
fillModelExtra(m)
}
var total int64
model.DB.Model(&model.Model{}).Count(&total)

// 统计供应商计数(全部数据,不受分页影响)
vendorCounts, _ := model.GetVendorModelCounts()

pageInfo.SetTotal(int(total))
pageInfo.SetItems(modelsMeta)
common.ApiSuccess(c, gin.H{
"items": modelsMeta,
"total": total,
"page": pageInfo.GetPage(),
"page_size": pageInfo.GetPageSize(),
"vendor_counts": vendorCounts,
})
}

// SearchModelsMeta 搜索模型列表
func SearchModelsMeta(c *gin.Context) {

keyword := c.Query("keyword")
vendor := c.Query("vendor")
pageInfo := common.GetPageQuery(c)

modelsMeta, total, err := model.SearchModels(keyword, vendor, pageInfo.GetStartIdx(), pageInfo.GetPageSize())
if err != nil {
common.ApiError(c, err)
return
}
for _, m := range modelsMeta {
fillModelExtra(m)
}
pageInfo.SetTotal(int(total))
pageInfo.SetItems(modelsMeta)
common.ApiSuccess(c, pageInfo)
}

// GetModelMeta 根据 ID 获取单条模型信息
func GetModelMeta(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.Atoi(idStr)
if err != nil {
common.ApiError(c, err)
return
}
var m model.Model
if err := model.DB.First(&m, id).Error; err != nil {
common.ApiError(c, err)
return
}
fillModelExtra(&m)
common.ApiSuccess(c, &m)
}

// CreateModelMeta 新建模型
func CreateModelMeta(c *gin.Context) {
var m model.Model
if err := c.ShouldBindJSON(&m); err != nil {
common.ApiError(c, err)
return
}
if m.ModelName == "" {
common.ApiErrorMsg(c, "模型名称不能为空")
return
}
// 名称冲突检查
if dup, err := model.IsModelNameDuplicated(0, m.ModelName); err != nil {
common.ApiError(c, err)
return
} else if dup {
common.ApiErrorMsg(c, "模型名称已存在")
return
}

if err := m.Insert(); err != nil {
common.ApiError(c, err)
return
}
model.RefreshPricing()
common.ApiSuccess(c, &m)
}

// UpdateModelMeta 更新模型
func UpdateModelMeta(c *gin.Context) {
statusOnly := c.Query("status_only") == "true"

var m model.Model
if err := c.ShouldBindJSON(&m); err != nil {
common.ApiError(c, err)
return
}
if m.Id == 0 {
common.ApiErrorMsg(c, "缺少模型 ID")
return
}

if statusOnly {
// 只更新状态,防止误清空其他字段
if err := model.DB.Model(&model.Model{}).Where("id = ?", m.Id).Update("status", m.Status).Error; err != nil {
common.ApiError(c, err)
return
}
} else {
// 名称冲突检查
if dup, err := model.IsModelNameDuplicated(m.Id, m.ModelName); err != nil {
common.ApiError(c, err)
return
} else if dup {
common.ApiErrorMsg(c, "模型名称已存在")
return
}

if err := m.Update(); err != nil {
common.ApiError(c, err)
return
}
}
model.RefreshPricing()
common.ApiSuccess(c, &m)
}

// DeleteModelMeta 删除模型
func DeleteModelMeta(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.Atoi(idStr)
if err != nil {
common.ApiError(c, err)
return
}
if err := model.DB.Delete(&model.Model{}, id).Error; err != nil {
common.ApiError(c, err)
return
}
model.RefreshPricing()
common.ApiSuccess(c, nil)
}

// 辅助函数:填充 Endpoints 和 BoundChannels 和 EnableGroups
func fillModelExtra(m *model.Model) {
if m.Endpoints == "" {
eps := model.GetModelSupportEndpointTypes(m.ModelName)
if b, err := json.Marshal(eps); err == nil {
m.Endpoints = string(b)
}
}
if channels, err := model.GetBoundChannels(m.ModelName); err == nil {
m.BoundChannels = channels
}
// 填充启用分组
m.EnableGroups = model.GetModelEnableGroups(m.ModelName)
// 填充计费类型
m.QuotaType = model.GetModelQuotaType(m.ModelName)
}
90 changes: 90 additions & 0 deletions controller/prefill_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package controller

import (
"strconv"

"one-api/common"
"one-api/model"

"github.com/gin-gonic/gin"
)

// GetPrefillGroups 获取预填组列表,可通过 ?type=xxx 过滤
func GetPrefillGroups(c *gin.Context) {
groupType := c.Query("type")
groups, err := model.GetAllPrefillGroups(groupType)
if err != nil {
common.ApiError(c, err)
return
}
common.ApiSuccess(c, groups)
}

// CreatePrefillGroup 创建新的预填组
func CreatePrefillGroup(c *gin.Context) {
var g model.PrefillGroup
if err := c.ShouldBindJSON(&g); err != nil {
common.ApiError(c, err)
return
}
if g.Name == "" || g.Type == "" {
common.ApiErrorMsg(c, "组名称和类型不能为空")
return
}
// 创建前检查名称
if dup, err := model.IsPrefillGroupNameDuplicated(0, g.Name); err != nil {
common.ApiError(c, err)
return
} else if dup {
common.ApiErrorMsg(c, "组名称已存在")
return
}

if err := g.Insert(); err != nil {
common.ApiError(c, err)
return
}
common.ApiSuccess(c, &g)
}

// UpdatePrefillGroup 更新预填组
func UpdatePrefillGroup(c *gin.Context) {
var g model.PrefillGroup
if err := c.ShouldBindJSON(&g); err != nil {
common.ApiError(c, err)
return
}
if g.Id == 0 {
common.ApiErrorMsg(c, "缺少组 ID")
return
}
// 名称冲突检查
if dup, err := model.IsPrefillGroupNameDuplicated(g.Id, g.Name); err != nil {
common.ApiError(c, err)
return
} else if dup {
common.ApiErrorMsg(c, "组名称已存在")
return
}

if err := g.Update(); err != nil {
common.ApiError(c, err)
return
}
common.ApiSuccess(c, &g)
}

// DeletePrefillGroup 删除预填组
func DeletePrefillGroup(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.Atoi(idStr)
if err != nil {
common.ApiError(c, err)
return
}
if err := model.DeletePrefillGroupByID(id); err != nil {
common.ApiError(c, err)
return
}
common.ApiSuccess(c, nil)
}
8 changes: 5 additions & 3 deletions controller/pricing.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ func GetPricing(c *gin.Context) {
c.JSON(200, gin.H{
"success": true,
"data": pricing,
"group_ratio": groupRatio,
"usable_group": usableGroup,
})
"vendors": model.GetVendors(),
"group_ratio": groupRatio,
"usable_group": usableGroup,
"supported_endpoint": model.GetSupportedEndpointMap(),
})
}

func ResetModelRatio(c *gin.Context) {
Expand Down
Loading