Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
c9b276c6
authored
Aug 11, 2025
by
CaIon
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/alpha' into alpha
# Conflicts: # relay/channel/openai/adaptor.go
parents
be77f3d7
067f3ce9
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
335 additions
and
63 deletions
+335
-63
controller/model.go
+49
-7
controller/model_meta.go
+109
-8
dto/pricing.go
+24
-0
middleware/auth.go
+9
-7
model/main.go
+23
-15
model/model_meta.go
+18
-0
router/relay-router.go
+38
-4
web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx
+11
-6
web/src/components/table/model-pricing/view/card/PricingCardView.jsx
+16
-5
web/src/components/table/models/ModelsColumnDefs.js
+24
-7
web/src/helpers/utils.js
+14
-4
No files found.
controller/model.go
View file @
c9b276c6
...
@@ -16,6 +16,7 @@ import (
...
@@ -16,6 +16,7 @@ import (
"one-api/relay/channel/moonshot"
"one-api/relay/channel/moonshot"
relaycommon
"one-api/relay/common"
relaycommon
"one-api/relay/common"
"one-api/setting"
"one-api/setting"
"time"
)
)
// https://platform.openai.com/docs/api-reference/models/list
// https://platform.openai.com/docs/api-reference/models/list
...
@@ -102,7 +103,7 @@ func init() {
...
@@ -102,7 +103,7 @@ func init() {
})
})
}
}
func
ListModels
(
c
*
gin
.
Context
)
{
func
ListModels
(
c
*
gin
.
Context
,
modelType
int
)
{
userOpenAiModels
:=
make
([]
dto
.
OpenAIModels
,
0
)
userOpenAiModels
:=
make
([]
dto
.
OpenAIModels
,
0
)
modelLimitEnable
:=
common
.
GetContextKeyBool
(
c
,
constant
.
ContextKeyTokenModelLimitEnabled
)
modelLimitEnable
:=
common
.
GetContextKeyBool
(
c
,
constant
.
ContextKeyTokenModelLimitEnabled
)
...
@@ -171,10 +172,41 @@ func ListModels(c *gin.Context) {
...
@@ -171,10 +172,41 @@ func ListModels(c *gin.Context) {
}
}
}
}
}
}
c
.
JSON
(
200
,
gin
.
H
{
switch
modelType
{
"success"
:
true
,
case
constant
.
ChannelTypeAnthropic
:
"data"
:
userOpenAiModels
,
useranthropicModels
:=
make
([]
dto
.
AnthropicModel
,
len
(
userOpenAiModels
))
})
for
i
,
model
:=
range
userOpenAiModels
{
useranthropicModels
[
i
]
=
dto
.
AnthropicModel
{
ID
:
model
.
Id
,
CreatedAt
:
time
.
Unix
(
int64
(
model
.
Created
),
0
)
.
UTC
()
.
Format
(
time
.
RFC3339
),
DisplayName
:
model
.
Id
,
Type
:
"model"
,
}
}
c
.
JSON
(
200
,
gin
.
H
{
"data"
:
useranthropicModels
,
"first_id"
:
useranthropicModels
[
0
]
.
ID
,
"has_more"
:
false
,
"last_id"
:
useranthropicModels
[
len
(
useranthropicModels
)
-
1
]
.
ID
,
})
case
constant
.
ChannelTypeGemini
:
userGeminiModels
:=
make
([]
dto
.
GeminiModel
,
len
(
userOpenAiModels
))
for
i
,
model
:=
range
userOpenAiModels
{
userGeminiModels
[
i
]
=
dto
.
GeminiModel
{
Name
:
model
.
Id
,
DisplayName
:
model
.
Id
,
}
}
c
.
JSON
(
200
,
gin
.
H
{
"models"
:
userGeminiModels
,
"nextPageToken"
:
nil
,
})
default
:
c
.
JSON
(
200
,
gin
.
H
{
"success"
:
true
,
"data"
:
userOpenAiModels
,
})
}
}
}
func
ChannelListModels
(
c
*
gin
.
Context
)
{
func
ChannelListModels
(
c
*
gin
.
Context
)
{
...
@@ -198,10 +230,20 @@ func EnabledListModels(c *gin.Context) {
...
@@ -198,10 +230,20 @@ func EnabledListModels(c *gin.Context) {
})
})
}
}
func
RetrieveModel
(
c
*
gin
.
Context
)
{
func
RetrieveModel
(
c
*
gin
.
Context
,
modelType
int
)
{
modelId
:=
c
.
Param
(
"model"
)
modelId
:=
c
.
Param
(
"model"
)
if
aiModel
,
ok
:=
openAIModelsMap
[
modelId
];
ok
{
if
aiModel
,
ok
:=
openAIModelsMap
[
modelId
];
ok
{
c
.
JSON
(
200
,
aiModel
)
switch
modelType
{
case
constant
.
ChannelTypeAnthropic
:
c
.
JSON
(
200
,
dto
.
AnthropicModel
{
ID
:
aiModel
.
Id
,
CreatedAt
:
time
.
Unix
(
int64
(
aiModel
.
Created
),
0
)
.
UTC
()
.
Format
(
time
.
RFC3339
),
DisplayName
:
aiModel
.
Id
,
Type
:
"model"
,
})
default
:
c
.
JSON
(
200
,
aiModel
)
}
}
else
{
}
else
{
openAIError
:=
dto
.
OpenAIError
{
openAIError
:=
dto
.
OpenAIError
{
Message
:
fmt
.
Sprintf
(
"The model '%s' does not exist"
,
modelId
),
Message
:
fmt
.
Sprintf
(
"The model '%s' does not exist"
,
modelId
),
...
...
controller/model_meta.go
View file @
c9b276c6
...
@@ -3,8 +3,10 @@ package controller
...
@@ -3,8 +3,10 @@ package controller
import
(
import
(
"encoding/json"
"encoding/json"
"strconv"
"strconv"
"strings"
"one-api/common"
"one-api/common"
"one-api/constant"
"one-api/model"
"one-api/model"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin"
...
@@ -162,17 +164,116 @@ func DeleteModelMeta(c *gin.Context) {
...
@@ -162,17 +164,116 @@ func DeleteModelMeta(c *gin.Context) {
// 辅助函数:填充 Endpoints 和 BoundChannels 和 EnableGroups
// 辅助函数:填充 Endpoints 和 BoundChannels 和 EnableGroups
func
fillModelExtra
(
m
*
model
.
Model
)
{
func
fillModelExtra
(
m
*
model
.
Model
)
{
if
m
.
Endpoints
==
""
{
// 若为精确匹配,保持原有逻辑
eps
:=
model
.
GetModelSupportEndpointTypes
(
m
.
ModelName
)
if
m
.
NameRule
==
model
.
NameRuleExact
{
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
)
return
}
// 非精确匹配:计算并集
pricings
:=
model
.
GetPricing
()
// 匹配到的模型名称集合
matchedNames
:=
make
([]
string
,
0
)
// 端点去重集合
endpointSet
:=
make
(
map
[
constant
.
EndpointType
]
struct
{})
// 已绑定渠道去重集合
channelSet
:=
make
(
map
[
string
]
model
.
BoundChannel
)
// 分组去重集合
groupSet
:=
make
(
map
[
string
]
struct
{})
// 计费类型(若有任意模型为 1,则返回 1)
quotaTypeSet
:=
make
(
map
[
int
]
struct
{})
for
_
,
p
:=
range
pricings
{
var
matched
bool
switch
m
.
NameRule
{
case
model
.
NameRulePrefix
:
matched
=
strings
.
HasPrefix
(
p
.
ModelName
,
m
.
ModelName
)
case
model
.
NameRuleSuffix
:
matched
=
strings
.
HasSuffix
(
p
.
ModelName
,
m
.
ModelName
)
case
model
.
NameRuleContains
:
matched
=
strings
.
Contains
(
p
.
ModelName
,
m
.
ModelName
)
}
if
!
matched
{
continue
}
// 记录匹配到的模型名称
matchedNames
=
append
(
matchedNames
,
p
.
ModelName
)
// 收集端点
for
_
,
et
:=
range
p
.
SupportedEndpointTypes
{
endpointSet
[
et
]
=
struct
{}{}
}
// 收集分组
for
_
,
g
:=
range
p
.
EnableGroup
{
groupSet
[
g
]
=
struct
{}{}
}
// 收集计费类型
quotaTypeSet
[
p
.
QuotaType
]
=
struct
{}{}
}
// 序列化端点
if
len
(
endpointSet
)
>
0
&&
m
.
Endpoints
==
""
{
eps
:=
make
([]
constant
.
EndpointType
,
0
,
len
(
endpointSet
))
for
et
:=
range
endpointSet
{
eps
=
append
(
eps
,
et
)
}
if
b
,
err
:=
json
.
Marshal
(
eps
);
err
==
nil
{
if
b
,
err
:=
json
.
Marshal
(
eps
);
err
==
nil
{
m
.
Endpoints
=
string
(
b
)
m
.
Endpoints
=
string
(
b
)
}
}
}
}
if
channels
,
err
:=
model
.
GetBoundChannels
(
m
.
ModelName
);
err
==
nil
{
m
.
BoundChannels
=
channels
// 序列化分组
if
len
(
groupSet
)
>
0
{
groups
:=
make
([]
string
,
0
,
len
(
groupSet
))
for
g
:=
range
groupSet
{
groups
=
append
(
groups
,
g
)
}
m
.
EnableGroups
=
groups
}
}
// 填充启用分组
m
.
EnableGroups
=
model
.
GetModelEnableGroups
(
m
.
ModelName
)
// 确定计费类型:仅当所有匹配模型计费类型一致时才返回该类型,否则返回 -1 表示未知/不确定
// 填充计费类型
if
len
(
quotaTypeSet
)
==
1
{
m
.
QuotaType
=
model
.
GetModelQuotaType
(
m
.
ModelName
)
for
k
:=
range
quotaTypeSet
{
m
.
QuotaType
=
k
}
}
else
{
m
.
QuotaType
=
-
1
}
// 批量查询并序列化渠道
if
len
(
matchedNames
)
>
0
{
if
channels
,
err
:=
model
.
GetBoundChannelsForModels
(
matchedNames
);
err
==
nil
{
for
_
,
ch
:=
range
channels
{
key
:=
ch
.
Name
+
"_"
+
strconv
.
Itoa
(
ch
.
Type
)
channelSet
[
key
]
=
ch
}
}
if
len
(
channelSet
)
>
0
{
chs
:=
make
([]
model
.
BoundChannel
,
0
,
len
(
channelSet
))
for
_
,
ch
:=
range
channelSet
{
chs
=
append
(
chs
,
ch
)
}
m
.
BoundChannels
=
chs
}
}
// 设置匹配信息
m
.
MatchedModels
=
matchedNames
m
.
MatchedCount
=
len
(
matchedNames
)
}
}
dto/pricing.go
View file @
c9b276c6
...
@@ -2,6 +2,7 @@ package dto
...
@@ -2,6 +2,7 @@ package dto
import
"one-api/constant"
import
"one-api/constant"
// 这里不好动就不动了,本来想独立出来的(
type
OpenAIModels
struct
{
type
OpenAIModels
struct
{
Id
string
`json:"id"`
Id
string
`json:"id"`
Object
string
`json:"object"`
Object
string
`json:"object"`
...
@@ -9,3 +10,26 @@ type OpenAIModels struct {
...
@@ -9,3 +10,26 @@ type OpenAIModels struct {
OwnedBy
string
`json:"owned_by"`
OwnedBy
string
`json:"owned_by"`
SupportedEndpointTypes
[]
constant
.
EndpointType
`json:"supported_endpoint_types"`
SupportedEndpointTypes
[]
constant
.
EndpointType
`json:"supported_endpoint_types"`
}
}
type
AnthropicModel
struct
{
ID
string
`json:"id"`
CreatedAt
string
`json:"created_at"`
DisplayName
string
`json:"display_name"`
Type
string
`json:"type"`
}
type
GeminiModel
struct
{
Name
interface
{}
`json:"name"`
BaseModelId
interface
{}
`json:"baseModelId"`
Version
interface
{}
`json:"version"`
DisplayName
interface
{}
`json:"displayName"`
Description
interface
{}
`json:"description"`
InputTokenLimit
interface
{}
`json:"inputTokenLimit"`
OutputTokenLimit
interface
{}
`json:"outputTokenLimit"`
SupportedGenerationMethods
[]
interface
{}
`json:"supportedGenerationMethods"`
Thinking
interface
{}
`json:"thinking"`
Temperature
interface
{}
`json:"temperature"`
MaxTemperature
interface
{}
`json:"maxTemperature"`
TopP
interface
{}
`json:"topP"`
TopK
interface
{}
`json:"topK"`
}
middleware/auth.go
View file @
c9b276c6
...
@@ -192,16 +192,18 @@ func TokenAuth() func(c *gin.Context) {
...
@@ -192,16 +192,18 @@ func TokenAuth() func(c *gin.Context) {
}
}
c
.
Request
.
Header
.
Set
(
"Authorization"
,
"Bearer "
+
key
)
c
.
Request
.
Header
.
Set
(
"Authorization"
,
"Bearer "
+
key
)
}
}
anthropicKey
:=
c
.
Request
.
Header
.
Get
(
"x-api-key"
)
// 检查path包含/v1/messages
// 检查path包含/v1/messages
if
strings
.
Contains
(
c
.
Request
.
URL
.
Path
,
"/v1/messages"
)
{
// 或者是否 x-api-key 不为空且存在anthropic-version
// 从x-api-key中获取key
// 谁知道有多少不符合规范没写anthropic-version的
key
:=
c
.
Request
.
Header
.
Get
(
"x-api-key"
)
// 所以就这样随它去吧(
if
key
!=
""
{
if
strings
.
Contains
(
c
.
Request
.
URL
.
Path
,
"/v1/messages"
)
||
(
anthropicKey
!=
""
&&
c
.
Request
.
Header
.
Get
(
"anthropic-version"
)
!=
""
)
{
c
.
Request
.
Header
.
Set
(
"Authorization"
,
"Bearer "
+
key
)
c
.
Request
.
Header
.
Set
(
"Authorization"
,
"Bearer "
+
anthropicKey
)
}
}
}
// gemini api 从query中获取key
// gemini api 从query中获取key
if
strings
.
HasPrefix
(
c
.
Request
.
URL
.
Path
,
"/v1beta/models/"
)
||
strings
.
HasPrefix
(
c
.
Request
.
URL
.
Path
,
"/v1/models/"
)
{
if
strings
.
HasPrefix
(
c
.
Request
.
URL
.
Path
,
"/v1beta/models"
)
||
strings
.
HasPrefix
(
c
.
Request
.
URL
.
Path
,
"/v1beta/openai/models"
)
||
strings
.
HasPrefix
(
c
.
Request
.
URL
.
Path
,
"/v1/models/"
)
{
skKey
:=
c
.
Query
(
"key"
)
skKey
:=
c
.
Query
(
"key"
)
if
skKey
!=
""
{
if
skKey
!=
""
{
c
.
Request
.
Header
.
Set
(
"Authorization"
,
"Bearer "
+
skKey
)
c
.
Request
.
Header
.
Set
(
"Authorization"
,
"Bearer "
+
skKey
)
...
...
model/main.go
View file @
c9b276c6
...
@@ -66,18 +66,18 @@ var LOG_DB *gorm.DB
...
@@ -66,18 +66,18 @@ var LOG_DB *gorm.DB
// dropIndexIfExists drops a MySQL index only if it exists to avoid noisy 1091 errors
// dropIndexIfExists drops a MySQL index only if it exists to avoid noisy 1091 errors
func
dropIndexIfExists
(
tableName
string
,
indexName
string
)
{
func
dropIndexIfExists
(
tableName
string
,
indexName
string
)
{
if
!
common
.
UsingMySQL
{
if
!
common
.
UsingMySQL
{
return
return
}
}
var
count
int64
var
count
int64
// Check index existence via information_schema
// Check index existence via information_schema
err
:=
DB
.
Raw
(
err
:=
DB
.
Raw
(
"SELECT COUNT(1) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?"
,
"SELECT COUNT(1) FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = ? AND index_name = ?"
,
tableName
,
indexName
,
tableName
,
indexName
,
)
.
Scan
(
&
count
)
.
Error
)
.
Scan
(
&
count
)
.
Error
if
err
==
nil
&&
count
>
0
{
if
err
==
nil
&&
count
>
0
{
_
=
DB
.
Exec
(
"ALTER TABLE "
+
tableName
+
" DROP INDEX "
+
indexName
+
";"
)
.
Error
_
=
DB
.
Exec
(
"ALTER TABLE "
+
tableName
+
" DROP INDEX "
+
indexName
+
";"
)
.
Error
}
}
}
}
func
createRootAccountIfNeed
()
error
{
func
createRootAccountIfNeed
()
error
{
...
@@ -252,8 +252,12 @@ func InitLogDB() (err error) {
...
@@ -252,8 +252,12 @@ func InitLogDB() (err error) {
func
migrateDB
()
error
{
func
migrateDB
()
error
{
// 修复旧版本留下的唯一索引,允许软删除后重新插入同名记录
// 修复旧版本留下的唯一索引,允许软删除后重新插入同名记录
dropIndexIfExists
(
"models"
,
"uk_model_name"
)
// 删除单列唯一索引(列级 UNIQUE)及早期命名方式,防止与新复合唯一索引 (model_name, deleted_at) 冲突
dropIndexIfExists
(
"vendors"
,
"uk_vendor_name"
)
dropIndexIfExists
(
"models"
,
"uk_model_name"
)
// 新版复合索引名称(若已存在)
dropIndexIfExists
(
"models"
,
"model_name"
)
// 旧版列级唯一索引名称
dropIndexIfExists
(
"vendors"
,
"uk_vendor_name"
)
// 新版复合索引名称(若已存在)
dropIndexIfExists
(
"vendors"
,
"name"
)
// 旧版列级唯一索引名称
if
!
common
.
UsingPostgreSQL
{
if
!
common
.
UsingPostgreSQL
{
return
migrateDBFast
()
return
migrateDBFast
()
}
}
...
@@ -284,8 +288,12 @@ func migrateDB() error {
...
@@ -284,8 +288,12 @@ func migrateDB() error {
func
migrateDBFast
()
error
{
func
migrateDBFast
()
error
{
// 修复旧版本留下的唯一索引,允许软删除后重新插入同名记录
// 修复旧版本留下的唯一索引,允许软删除后重新插入同名记录
// 删除单列唯一索引(列级 UNIQUE)及早期命名方式,防止与新复合唯一索引冲突
dropIndexIfExists
(
"models"
,
"uk_model_name"
)
dropIndexIfExists
(
"models"
,
"uk_model_name"
)
dropIndexIfExists
(
"models"
,
"model_name"
)
dropIndexIfExists
(
"vendors"
,
"uk_vendor_name"
)
dropIndexIfExists
(
"vendors"
,
"uk_vendor_name"
)
dropIndexIfExists
(
"vendors"
,
"name"
)
var
wg
sync
.
WaitGroup
var
wg
sync
.
WaitGroup
...
@@ -305,7 +313,7 @@ func migrateDBFast() error {
...
@@ -305,7 +313,7 @@ func migrateDBFast() error {
{
&
QuotaData
{},
"QuotaData"
},
{
&
QuotaData
{},
"QuotaData"
},
{
&
Task
{},
"Task"
},
{
&
Task
{},
"Task"
},
{
&
Model
{},
"Model"
},
{
&
Model
{},
"Model"
},
{
&
Vendor
{},
"Vendor"
},
{
&
Vendor
{},
"Vendor"
},
{
&
PrefillGroup
{},
"PrefillGroup"
},
{
&
PrefillGroup
{},
"PrefillGroup"
},
{
&
Setup
{},
"Setup"
},
{
&
Setup
{},
"Setup"
},
{
&
TwoFA
{},
"TwoFA"
},
{
&
TwoFA
{},
"TwoFA"
},
...
...
model/model_meta.go
View file @
c9b276c6
...
@@ -51,6 +51,9 @@ type Model struct {
...
@@ -51,6 +51,9 @@ type Model struct {
EnableGroups
[]
string
`json:"enable_groups,omitempty" gorm:"-"`
EnableGroups
[]
string
`json:"enable_groups,omitempty" gorm:"-"`
QuotaType
int
`json:"quota_type" gorm:"-"`
QuotaType
int
`json:"quota_type" gorm:"-"`
NameRule
int
`json:"name_rule" gorm:"default:0"`
NameRule
int
`json:"name_rule" gorm:"default:0"`
MatchedModels
[]
string
`json:"matched_models,omitempty" gorm:"-"`
MatchedCount
int
`json:"matched_count,omitempty" gorm:"-"`
}
}
// Insert 创建新的模型元数据记录
// Insert 创建新的模型元数据记录
...
@@ -136,6 +139,21 @@ func GetBoundChannels(modelName string) ([]BoundChannel, error) {
...
@@ -136,6 +139,21 @@ func GetBoundChannels(modelName string) ([]BoundChannel, error) {
return
channels
,
err
return
channels
,
err
}
}
// GetBoundChannelsForModels 批量查询多模型的绑定渠道并去重返回
func
GetBoundChannelsForModels
(
modelNames
[]
string
)
([]
BoundChannel
,
error
)
{
if
len
(
modelNames
)
==
0
{
return
make
([]
BoundChannel
,
0
),
nil
}
var
channels
[]
BoundChannel
err
:=
DB
.
Table
(
"channels"
)
.
Select
(
"channels.name, channels.type"
)
.
Joins
(
"join abilities on abilities.channel_id = channels.id"
)
.
Where
(
"abilities.model IN ? AND abilities.enabled = ?"
,
modelNames
,
true
)
.
Group
(
"channels.id"
)
.
Scan
(
&
channels
)
.
Error
return
channels
,
err
}
// FindModelByNameWithRule 根据模型名称和匹配规则查找模型元数据,优先级:精确 > 前缀 > 后缀 > 包含
// FindModelByNameWithRule 根据模型名称和匹配规则查找模型元数据,优先级:精确 > 前缀 > 后缀 > 包含
func
FindModelByNameWithRule
(
name
string
)
(
*
Model
,
error
)
{
func
FindModelByNameWithRule
(
name
string
)
(
*
Model
,
error
)
{
// 1. 精确匹配
// 1. 精确匹配
...
...
router/relay-router.go
View file @
c9b276c6
package
router
package
router
import
(
import
(
"github.com/gin-gonic/gin"
"one-api/constant"
"one-api/controller"
"one-api/controller"
"one-api/middleware"
"one-api/middleware"
"one-api/relay"
"one-api/relay"
"github.com/gin-gonic/gin"
)
)
func
SetRelayRouter
(
router
*
gin
.
Engine
)
{
func
SetRelayRouter
(
router
*
gin
.
Engine
)
{
...
@@ -16,9 +16,43 @@ func SetRelayRouter(router *gin.Engine) {
...
@@ -16,9 +16,43 @@ func SetRelayRouter(router *gin.Engine) {
modelsRouter
:=
router
.
Group
(
"/v1/models"
)
modelsRouter
:=
router
.
Group
(
"/v1/models"
)
modelsRouter
.
Use
(
middleware
.
TokenAuth
())
modelsRouter
.
Use
(
middleware
.
TokenAuth
())
{
{
modelsRouter
.
GET
(
""
,
controller
.
ListModels
)
modelsRouter
.
GET
(
""
,
func
(
c
*
gin
.
Context
)
{
modelsRouter
.
GET
(
"/:model"
,
controller
.
RetrieveModel
)
switch
{
case
c
.
GetHeader
(
"x-api-key"
)
!=
""
&&
c
.
GetHeader
(
"anthropic-version"
)
!=
""
:
controller
.
ListModels
(
c
,
constant
.
ChannelTypeAnthropic
)
case
c
.
GetHeader
(
"x-goog-api-key"
)
!=
""
||
c
.
Query
(
"key"
)
!=
""
:
// 单独的适配
controller
.
RetrieveModel
(
c
,
constant
.
ChannelTypeGemini
)
default
:
controller
.
ListModels
(
c
,
constant
.
ChannelTypeOpenAI
)
}
})
modelsRouter
.
GET
(
"/:model"
,
func
(
c
*
gin
.
Context
)
{
switch
{
case
c
.
GetHeader
(
"x-api-key"
)
!=
""
&&
c
.
GetHeader
(
"anthropic-version"
)
!=
""
:
controller
.
RetrieveModel
(
c
,
constant
.
ChannelTypeAnthropic
)
default
:
controller
.
RetrieveModel
(
c
,
constant
.
ChannelTypeOpenAI
)
}
})
}
}
geminiRouter
:=
router
.
Group
(
"/v1beta/models"
)
geminiRouter
.
Use
(
middleware
.
TokenAuth
())
{
geminiRouter
.
GET
(
""
,
func
(
c
*
gin
.
Context
)
{
controller
.
ListModels
(
c
,
constant
.
ChannelTypeGemini
)
})
}
geminiCompatibleRouter
:=
router
.
Group
(
"/v1beta/openai/models"
)
geminiCompatibleRouter
.
Use
(
middleware
.
TokenAuth
())
{
geminiCompatibleRouter
.
GET
(
""
,
func
(
c
*
gin
.
Context
)
{
controller
.
ListModels
(
c
,
constant
.
ChannelTypeOpenAI
)
})
}
playgroundRouter
:=
router
.
Group
(
"/pg"
)
playgroundRouter
:=
router
.
Group
(
"/pg"
)
playgroundRouter
.
Use
(
middleware
.
UserAuth
(),
middleware
.
Distribute
())
playgroundRouter
.
Use
(
middleware
.
UserAuth
(),
middleware
.
Distribute
())
{
{
...
...
web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx
View file @
c9b276c6
...
@@ -63,7 +63,7 @@ const ModelPricingTable = ({
...
@@ -63,7 +63,7 @@ const ModelPricingTable = ({
key
:
group
,
key
:
group
,
group
:
group
,
group
:
group
,
ratio
:
groupRatioValue
,
ratio
:
groupRatioValue
,
billingType
:
modelData
?.
quota_type
===
0
?
t
(
'按量计费'
)
:
t
(
'按次计费
'
),
billingType
:
modelData
?.
quota_type
===
0
?
t
(
'按量计费'
)
:
(
modelData
?.
quota_type
===
1
?
t
(
'按次计费'
)
:
'-
'
),
inputPrice
:
modelData
?.
quota_type
===
0
?
priceData
.
inputPrice
:
'-'
,
inputPrice
:
modelData
?.
quota_type
===
0
?
priceData
.
inputPrice
:
'-'
,
outputPrice
:
modelData
?.
quota_type
===
0
?
(
priceData
.
completionPrice
||
priceData
.
outputPrice
)
:
'-'
,
outputPrice
:
modelData
?.
quota_type
===
0
?
(
priceData
.
completionPrice
||
priceData
.
outputPrice
)
:
'-'
,
fixedPrice
:
modelData
?.
quota_type
===
1
?
priceData
.
price
:
'-'
,
fixedPrice
:
modelData
?.
quota_type
===
1
?
priceData
.
price
:
'-'
,
...
@@ -100,11 +100,16 @@ const ModelPricingTable = ({
...
@@ -100,11 +100,16 @@ const ModelPricingTable = ({
columns
.
push
({
columns
.
push
({
title
:
t
(
'计费类型'
),
title
:
t
(
'计费类型'
),
dataIndex
:
'billingType'
,
dataIndex
:
'billingType'
,
render
:
(
text
)
=>
(
render
:
(
text
)
=>
{
<
Tag
color=
{
text
===
t
(
'按量计费'
)
?
'violet'
:
'teal'
}
size=
"small"
shape=
"circle"
>
let
color
=
'white'
;
{
text
}
if
(
text
===
t
(
'按量计费'
))
color
=
'violet'
;
</
Tag
>
else
if
(
text
===
t
(
'按次计费'
))
color
=
'teal'
;
),
return
(
<
Tag
color=
{
color
}
size=
"small"
shape=
"circle"
>
{
text
||
'-'
}
</
Tag
>
);
},
});
});
// 根据计费类型添加价格列
// 根据计费类型添加价格列
...
...
web/src/components/table/model-pricing/view/card/PricingCardView.jsx
View file @
c9b276c6
...
@@ -144,13 +144,24 @@ const PricingCardView = ({
...
@@ -144,13 +144,24 @@ const PricingCardView = ({
// 渲染标签
// 渲染标签
const
renderTags
=
(
record
)
=>
{
const
renderTags
=
(
record
)
=>
{
// 计费类型标签(左边)
// 计费类型标签(左边)
const
billingType
=
record
.
quota_type
===
1
?
'teal'
:
'violet'
;
let
billingTag
=
(
const
billingText
=
record
.
quota_type
===
1
?
t
(
'按次计费'
)
:
t
(
'按量计费'
);
<
Tag
key=
"billing"
shape=
'circle'
color=
'white'
size=
'small'
>
const
billingTag
=
(
-
<
Tag
key=
"billing"
shape=
'circle'
color=
{
billingType
}
size=
'small'
>
{
billingText
}
</
Tag
>
</
Tag
>
);
);
if
(
record
.
quota_type
===
1
)
{
billingTag
=
(
<
Tag
key=
"billing"
shape=
'circle'
color=
'teal'
size=
'small'
>
{
t
(
'按次计费'
)
}
</
Tag
>
);
}
else
if
(
record
.
quota_type
===
0
)
{
billingTag
=
(
<
Tag
key=
"billing"
shape=
'circle'
color=
'violet'
size=
'small'
>
{
t
(
'按量计费'
)
}
</
Tag
>
);
}
// 自定义标签(右边)
// 自定义标签(右边)
const
customTags
=
[];
const
customTags
=
[];
...
...
web/src/components/table/models/ModelsColumnDefs.js
View file @
c9b276c6
...
@@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
...
@@ -18,7 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
*/
*/
import
React
from
'react'
;
import
React
from
'react'
;
import
{
Button
,
Space
,
Tag
,
Typography
,
Modal
}
from
'@douyinfe/semi-ui'
;
import
{
Button
,
Space
,
Tag
,
Typography
,
Modal
,
Tooltip
}
from
'@douyinfe/semi-ui'
;
import
{
import
{
timestamp2string
,
timestamp2string
,
getLobeHubIcon
,
getLobeHubIcon
,
...
@@ -137,7 +137,8 @@ const renderQuotaType = (qt, t) => {
...
@@ -137,7 +137,8 @@ const renderQuotaType = (qt, t) => {
<
/Tag
>
<
/Tag
>
);
);
}
}
return
qt
??
'-'
;
// 未知
return
'-'
;
};
};
// Render bound channels
// Render bound channels
...
@@ -207,8 +208,8 @@ const renderOperations = (text, record, setEditingModel, setShowEdit, manageMode
...
@@ -207,8 +208,8 @@ const renderOperations = (text, record, setEditingModel, setShowEdit, manageMode
);
);
};
};
// 名称匹配类型渲染
// 名称匹配类型渲染
(带匹配数量 Tooltip)
const
renderNameRule
=
(
rule
,
t
)
=>
{
const
renderNameRule
=
(
rule
,
record
,
t
)
=>
{
const
map
=
{
const
map
=
{
0
:
{
color
:
'green'
,
label
:
t
(
'精确'
)
},
0
:
{
color
:
'green'
,
label
:
t
(
'精确'
)
},
1
:
{
color
:
'blue'
,
label
:
t
(
'前缀'
)
},
1
:
{
color
:
'blue'
,
label
:
t
(
'前缀'
)
},
...
@@ -217,11 +218,27 @@ const renderNameRule = (rule, t) => {
...
@@ -217,11 +218,27 @@ const renderNameRule = (rule, t) => {
};
};
const
cfg
=
map
[
rule
];
const
cfg
=
map
[
rule
];
if
(
!
cfg
)
return
'-'
;
if
(
!
cfg
)
return
'-'
;
return
(
let
label
=
cfg
.
label
;
if
(
rule
!==
0
&&
record
.
matched_count
)
{
label
=
`
${
cfg
.
label
}
${
record
.
matched_count
}${
t
(
'个模型'
)}
`
;
}
const
tagElement
=
(
<
Tag
color
=
{
cfg
.
color
}
size
=
"small"
shape
=
'circle'
>
<
Tag
color
=
{
cfg
.
color
}
size
=
"small"
shape
=
'circle'
>
{
cfg
.
label
}
{
label
}
<
/Tag
>
<
/Tag
>
);
);
if
(
rule
===
0
||
!
record
.
matched_models
||
record
.
matched_models
.
length
===
0
)
{
return
tagElement
;
}
return
(
<
Tooltip
content
=
{
record
.
matched_models
.
join
(
', '
)}
showArrow
>
{
tagElement
}
<
/Tooltip
>
);
};
};
export
const
getModelsColumns
=
({
export
const
getModelsColumns
=
({
...
@@ -252,7 +269,7 @@ export const getModelsColumns = ({
...
@@ -252,7 +269,7 @@ export const getModelsColumns = ({
{
{
title
:
t
(
'匹配类型'
),
title
:
t
(
'匹配类型'
),
dataIndex
:
'name_rule'
,
dataIndex
:
'name_rule'
,
render
:
(
val
)
=>
renderNameRule
(
val
,
t
),
render
:
(
val
,
record
)
=>
renderNameRule
(
val
,
record
,
t
),
},
},
{
{
title
:
t
(
'描述'
),
title
:
t
(
'描述'
),
...
...
web/src/helpers/utils.js
View file @
c9b276c6
...
@@ -632,12 +632,22 @@ export const calculateModelPrice = ({
...
@@ -632,12 +632,22 @@ export const calculateModelPrice = ({
};
};
}
}
// 按次计费
if
(
record
.
quota_type
===
1
)
{
const
priceUSD
=
parseFloat
(
record
.
model_price
)
*
usedGroupRatio
;
// 按次计费
const
displayVal
=
displayPrice
(
priceUSD
);
const
priceUSD
=
parseFloat
(
record
.
model_price
)
*
usedGroupRatio
;
const
displayVal
=
displayPrice
(
priceUSD
);
return
{
price
:
displayVal
,
isPerToken
:
false
,
usedGroup
,
usedGroupRatio
,
};
}
// 未知计费类型,返回占位信息
return
{
return
{
price
:
displayVal
,
price
:
'-'
,
isPerToken
:
false
,
isPerToken
:
false
,
usedGroup
,
usedGroup
,
usedGroupRatio
,
usedGroupRatio
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment