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
c6a9df67
authored
Jun 16, 2025
by
creamlike1024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: auto分组
parent
c4e65246
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
437 additions
and
146 deletions
+437
-146
controller/group.go
+8
-1
controller/misc.go
+2
-1
controller/model.go
+16
-2
controller/playground.go
+4
-3
controller/relay.go
+2
-2
controller/user.go
+3
-0
middleware/distributor.go
+9
-2
model/cache.go
+37
-1
model/option.go
+6
-0
relay/helper/price.go
+11
-1
service/quota.go
+27
-3
setting/auto_group.go
+31
-0
setting/user_usable_group.go
+7
-0
web/src/components/settings/OperationSetting.js
+5
-1
web/src/pages/Setting/Operation/GroupRatioSettings.js
+36
-0
web/src/pages/Token/EditToken.js
+233
-129
No files found.
controller/group.go
View file @
c6a9df67
package
controller
package
controller
import
(
import
(
"github.com/gin-gonic/gin"
"net/http"
"net/http"
"one-api/model"
"one-api/model"
"one-api/setting"
"one-api/setting"
"github.com/gin-gonic/gin"
)
)
func
GetGroups
(
c
*
gin
.
Context
)
{
func
GetGroups
(
c
*
gin
.
Context
)
{
...
@@ -34,6 +35,12 @@ func GetUserGroups(c *gin.Context) {
...
@@ -34,6 +35,12 @@ func GetUserGroups(c *gin.Context) {
}
}
}
}
}
}
if
setting
.
GroupInUserUsableGroups
(
"auto"
)
{
usableGroups
[
"auto"
]
=
map
[
string
]
interface
{}{
"ratio"
:
"自动"
,
"desc"
:
setting
.
GetUsableGroupDescription
(
"auto"
),
}
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"success"
:
true
,
"message"
:
""
,
"message"
:
""
,
...
...
controller/misc.go
View file @
c6a9df67
...
@@ -9,9 +9,9 @@ import (
...
@@ -9,9 +9,9 @@ import (
"one-api/middleware"
"one-api/middleware"
"one-api/model"
"one-api/model"
"one-api/setting"
"one-api/setting"
"one-api/setting/console_setting"
"one-api/setting/operation_setting"
"one-api/setting/operation_setting"
"one-api/setting/system_setting"
"one-api/setting/system_setting"
"one-api/setting/console_setting"
"strings"
"strings"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin"
...
@@ -75,6 +75,7 @@ func GetStatus(c *gin.Context) {
...
@@ -75,6 +75,7 @@ func GetStatus(c *gin.Context) {
"chats"
:
setting
.
Chats
,
"chats"
:
setting
.
Chats
,
"demo_site_enabled"
:
operation_setting
.
DemoSiteEnabled
,
"demo_site_enabled"
:
operation_setting
.
DemoSiteEnabled
,
"self_use_mode_enabled"
:
operation_setting
.
SelfUseModeEnabled
,
"self_use_mode_enabled"
:
operation_setting
.
SelfUseModeEnabled
,
"default_use_auto_group"
:
setting
.
DefaultUseAutoGroup
,
// 面板启用开关
// 面板启用开关
"api_info_enabled"
:
cs
.
ApiInfoEnabled
,
"api_info_enabled"
:
cs
.
ApiInfoEnabled
,
...
...
controller/model.go
View file @
c6a9df67
...
@@ -2,7 +2,6 @@ package controller
...
@@ -2,7 +2,6 @@ package controller
import
(
import
(
"fmt"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"net/http"
"one-api/common"
"one-api/common"
"one-api/constant"
"one-api/constant"
...
@@ -15,6 +14,9 @@ import (
...
@@ -15,6 +14,9 @@ import (
"one-api/relay/channel/moonshot"
"one-api/relay/channel/moonshot"
relaycommon
"one-api/relay/common"
relaycommon
"one-api/relay/common"
relayconstant
"one-api/relay/constant"
relayconstant
"one-api/relay/constant"
"one-api/setting"
"github.com/gin-gonic/gin"
)
)
// https://platform.openai.com/docs/api-reference/models/list
// https://platform.openai.com/docs/api-reference/models/list
...
@@ -179,7 +181,19 @@ func ListModels(c *gin.Context) {
...
@@ -179,7 +181,19 @@ func ListModels(c *gin.Context) {
if
tokenGroup
!=
""
{
if
tokenGroup
!=
""
{
group
=
tokenGroup
group
=
tokenGroup
}
}
models
:=
model
.
GetGroupModels
(
group
)
var
models
[]
string
if
tokenGroup
==
"auto"
{
for
_
,
autoGroup
:=
range
setting
.
AutoGroups
{
groupModels
:=
model
.
GetGroupModels
(
autoGroup
)
for
_
,
g
:=
range
groupModels
{
if
!
common
.
StringsContains
(
models
,
g
)
{
models
=
append
(
models
,
g
)
}
}
}
}
else
{
models
=
model
.
GetGroupModels
(
group
)
}
for
_
,
s
:=
range
models
{
for
_
,
s
:=
range
models
{
if
_
,
ok
:=
openAIModelsMap
[
s
];
ok
{
if
_
,
ok
:=
openAIModelsMap
[
s
];
ok
{
userOpenAiModels
=
append
(
userOpenAiModels
,
openAIModelsMap
[
s
])
userOpenAiModels
=
append
(
userOpenAiModels
,
openAIModelsMap
[
s
])
...
...
controller/playground.go
View file @
c6a9df67
...
@@ -3,7 +3,6 @@ package controller
...
@@ -3,7 +3,6 @@ package controller
import
(
import
(
"errors"
"errors"
"fmt"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"net/http"
"one-api/common"
"one-api/common"
"one-api/constant"
"one-api/constant"
...
@@ -13,6 +12,8 @@ import (
...
@@ -13,6 +12,8 @@ import (
"one-api/service"
"one-api/service"
"one-api/setting"
"one-api/setting"
"time"
"time"
"github.com/gin-gonic/gin"
)
)
func
Playground
(
c
*
gin
.
Context
)
{
func
Playground
(
c
*
gin
.
Context
)
{
...
@@ -57,9 +58,9 @@ func Playground(c *gin.Context) {
...
@@ -57,9 +58,9 @@ func Playground(c *gin.Context) {
c
.
Set
(
"group"
,
group
)
c
.
Set
(
"group"
,
group
)
}
}
c
.
Set
(
"token_name"
,
"playground-"
+
group
)
c
.
Set
(
"token_name"
,
"playground-"
+
group
)
channel
,
err
:=
model
.
CacheGetRandomSatisfiedChannel
(
group
,
playgroundRequest
.
Model
,
0
)
channel
,
finalGroup
,
err
:=
model
.
CacheGetRandomSatisfiedChannel
(
c
,
group
,
playgroundRequest
.
Model
,
0
)
if
err
!=
nil
{
if
err
!=
nil
{
message
:=
fmt
.
Sprintf
(
"当前分组 %s 下对于模型 %s 无可用渠道"
,
g
roup
,
playgroundRequest
.
Model
)
message
:=
fmt
.
Sprintf
(
"当前分组 %s 下对于模型 %s 无可用渠道"
,
finalG
roup
,
playgroundRequest
.
Model
)
openaiErr
=
service
.
OpenAIErrorWrapperLocal
(
errors
.
New
(
message
),
"get_playground_channel_failed"
,
http
.
StatusInternalServerError
)
openaiErr
=
service
.
OpenAIErrorWrapperLocal
(
errors
.
New
(
message
),
"get_playground_channel_failed"
,
http
.
StatusInternalServerError
)
return
return
}
}
...
...
controller/relay.go
View file @
c6a9df67
...
@@ -259,7 +259,7 @@ func getChannel(c *gin.Context, group, originalModel string, retryCount int) (*m
...
@@ -259,7 +259,7 @@ func getChannel(c *gin.Context, group, originalModel string, retryCount int) (*m
AutoBan
:
&
autoBanInt
,
AutoBan
:
&
autoBanInt
,
},
nil
},
nil
}
}
channel
,
err
:=
model
.
CacheGetRandomSatisfiedChannel
(
group
,
originalModel
,
retryCount
)
channel
,
_
,
err
:=
model
.
CacheGetRandomSatisfiedChannel
(
c
,
group
,
originalModel
,
retryCount
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
errors
.
New
(
fmt
.
Sprintf
(
"获取重试渠道失败: %s"
,
err
.
Error
()))
return
nil
,
errors
.
New
(
fmt
.
Sprintf
(
"获取重试渠道失败: %s"
,
err
.
Error
()))
}
}
...
@@ -388,7 +388,7 @@ func RelayTask(c *gin.Context) {
...
@@ -388,7 +388,7 @@ func RelayTask(c *gin.Context) {
retryTimes
=
0
retryTimes
=
0
}
}
for
i
:=
0
;
shouldRetryTaskRelay
(
c
,
channelId
,
taskErr
,
retryTimes
)
&&
i
<
retryTimes
;
i
++
{
for
i
:=
0
;
shouldRetryTaskRelay
(
c
,
channelId
,
taskErr
,
retryTimes
)
&&
i
<
retryTimes
;
i
++
{
channel
,
err
:=
model
.
CacheGetRandomSatisfiedChannel
(
group
,
originalModel
,
i
)
channel
,
_
,
err
:=
model
.
CacheGetRandomSatisfiedChannel
(
c
,
group
,
originalModel
,
i
)
if
err
!=
nil
{
if
err
!=
nil
{
common
.
LogError
(
c
,
fmt
.
Sprintf
(
"CacheGetRandomSatisfiedChannel failed: %s"
,
err
.
Error
()))
common
.
LogError
(
c
,
fmt
.
Sprintf
(
"CacheGetRandomSatisfiedChannel failed: %s"
,
err
.
Error
()))
break
break
...
...
controller/user.go
View file @
c6a9df67
...
@@ -226,6 +226,9 @@ func Register(c *gin.Context) {
...
@@ -226,6 +226,9 @@ func Register(c *gin.Context) {
UnlimitedQuota
:
true
,
UnlimitedQuota
:
true
,
ModelLimitsEnabled
:
false
,
ModelLimitsEnabled
:
false
,
}
}
if
setting
.
DefaultUseAutoGroup
{
token
.
Group
=
"auto"
}
if
err
:=
token
.
Insert
();
err
!=
nil
{
if
err
:=
token
.
Insert
();
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"success"
:
false
,
...
...
middleware/distributor.go
View file @
c6a9df67
...
@@ -49,9 +49,11 @@ func Distribute() func(c *gin.Context) {
...
@@ -49,9 +49,11 @@ func Distribute() func(c *gin.Context) {
}
}
// check group in common.GroupRatio
// check group in common.GroupRatio
if
!
setting
.
ContainsGroupRatio
(
tokenGroup
)
{
if
!
setting
.
ContainsGroupRatio
(
tokenGroup
)
{
if
tokenGroup
!=
"auto"
{
abortWithOpenAiMessage
(
c
,
http
.
StatusForbidden
,
fmt
.
Sprintf
(
"分组 %s 已被弃用"
,
tokenGroup
))
abortWithOpenAiMessage
(
c
,
http
.
StatusForbidden
,
fmt
.
Sprintf
(
"分组 %s 已被弃用"
,
tokenGroup
))
return
return
}
}
}
userGroup
=
tokenGroup
userGroup
=
tokenGroup
}
}
c
.
Set
(
"group"
,
userGroup
)
c
.
Set
(
"group"
,
userGroup
)
...
@@ -95,9 +97,14 @@ func Distribute() func(c *gin.Context) {
...
@@ -95,9 +97,14 @@ func Distribute() func(c *gin.Context) {
}
}
if
shouldSelectChannel
{
if
shouldSelectChannel
{
channel
,
err
=
model
.
CacheGetRandomSatisfiedChannel
(
userGroup
,
modelRequest
.
Model
,
0
)
var
selectGroup
string
channel
,
selectGroup
,
err
=
model
.
CacheGetRandomSatisfiedChannel
(
c
,
userGroup
,
modelRequest
.
Model
,
0
)
if
err
!=
nil
{
if
err
!=
nil
{
message
:=
fmt
.
Sprintf
(
"当前分组 %s 下对于模型 %s 无可用渠道"
,
userGroup
,
modelRequest
.
Model
)
showGroup
:=
userGroup
if
userGroup
==
"auto"
{
showGroup
=
fmt
.
Sprintf
(
"auto(%s)"
,
selectGroup
)
}
message
:=
fmt
.
Sprintf
(
"当前分组 %s 下对于模型 %s 无可用渠道"
,
showGroup
,
modelRequest
.
Model
)
// 如果错误,但是渠道不为空,说明是数据库一致性问题
// 如果错误,但是渠道不为空,说明是数据库一致性问题
if
channel
!=
nil
{
if
channel
!=
nil
{
common
.
SysError
(
fmt
.
Sprintf
(
"渠道不存在:%d"
,
channel
.
Id
))
common
.
SysError
(
fmt
.
Sprintf
(
"渠道不存在:%d"
,
channel
.
Id
))
...
...
model/cache.go
View file @
c6a9df67
...
@@ -3,12 +3,16 @@ package model
...
@@ -3,12 +3,16 @@ package model
import
(
import
(
"errors"
"errors"
"fmt"
"fmt"
"log"
"math/rand"
"math/rand"
"one-api/common"
"one-api/common"
"one-api/setting"
"sort"
"sort"
"strings"
"strings"
"sync"
"sync"
"time"
"time"
"github.com/gin-gonic/gin"
)
)
var
group2model2channels
map
[
string
]
map
[
string
][]
*
Channel
var
group2model2channels
map
[
string
]
map
[
string
][]
*
Channel
...
@@ -75,7 +79,39 @@ func SyncChannelCache(frequency int) {
...
@@ -75,7 +79,39 @@ func SyncChannelCache(frequency int) {
}
}
}
}
func
CacheGetRandomSatisfiedChannel
(
group
string
,
model
string
,
retry
int
)
(
*
Channel
,
error
)
{
func
CacheGetRandomSatisfiedChannel
(
c
*
gin
.
Context
,
group
string
,
model
string
,
retry
int
)
(
*
Channel
,
string
,
error
)
{
var
channel
*
Channel
var
err
error
selectGroup
:=
group
if
group
==
"auto"
{
if
len
(
setting
.
AutoGroups
)
==
0
{
return
nil
,
selectGroup
,
errors
.
New
(
"auto groups is not enabled"
)
}
for
_
,
autoGroup
:=
range
setting
.
AutoGroups
{
log
.
Printf
(
"autoGroup: %s"
,
autoGroup
)
channel
,
_
=
getRandomSatisfiedChannel
(
autoGroup
,
model
,
retry
)
if
channel
==
nil
{
continue
}
else
{
c
.
Set
(
"auto_group"
,
autoGroup
)
selectGroup
=
autoGroup
log
.
Printf
(
"selectGroup: %s"
,
selectGroup
)
break
}
}
}
else
{
channel
,
err
=
getRandomSatisfiedChannel
(
group
,
model
,
retry
)
if
err
!=
nil
{
return
nil
,
group
,
err
}
}
if
channel
==
nil
{
return
nil
,
group
,
errors
.
New
(
"channel not found"
)
}
return
channel
,
selectGroup
,
nil
}
func
getRandomSatisfiedChannel
(
group
string
,
model
string
,
retry
int
)
(
*
Channel
,
error
)
{
if
strings
.
HasPrefix
(
model
,
"gpt-4-gizmo"
)
{
if
strings
.
HasPrefix
(
model
,
"gpt-4-gizmo"
)
{
model
=
"gpt-4-gizmo-*"
model
=
"gpt-4-gizmo-*"
}
}
...
...
model/option.go
View file @
c6a9df67
...
@@ -76,6 +76,8 @@ func InitOptionMap() {
...
@@ -76,6 +76,8 @@ func InitOptionMap() {
common
.
OptionMap
[
"MinTopUp"
]
=
strconv
.
Itoa
(
setting
.
MinTopUp
)
common
.
OptionMap
[
"MinTopUp"
]
=
strconv
.
Itoa
(
setting
.
MinTopUp
)
common
.
OptionMap
[
"TopupGroupRatio"
]
=
common
.
TopupGroupRatio2JSONString
()
common
.
OptionMap
[
"TopupGroupRatio"
]
=
common
.
TopupGroupRatio2JSONString
()
common
.
OptionMap
[
"Chats"
]
=
setting
.
Chats2JsonString
()
common
.
OptionMap
[
"Chats"
]
=
setting
.
Chats2JsonString
()
common
.
OptionMap
[
"AutoGroups"
]
=
setting
.
AutoGroups2JsonString
()
common
.
OptionMap
[
"DefaultUseAutoGroup"
]
=
strconv
.
FormatBool
(
setting
.
DefaultUseAutoGroup
)
common
.
OptionMap
[
"GitHubClientId"
]
=
""
common
.
OptionMap
[
"GitHubClientId"
]
=
""
common
.
OptionMap
[
"GitHubClientSecret"
]
=
""
common
.
OptionMap
[
"GitHubClientSecret"
]
=
""
common
.
OptionMap
[
"TelegramBotToken"
]
=
""
common
.
OptionMap
[
"TelegramBotToken"
]
=
""
...
@@ -287,6 +289,10 @@ func updateOptionMap(key string, value string) (err error) {
...
@@ -287,6 +289,10 @@ func updateOptionMap(key string, value string) (err error) {
setting
.
PayAddress
=
value
setting
.
PayAddress
=
value
case
"Chats"
:
case
"Chats"
:
err
=
setting
.
UpdateChatsByJsonString
(
value
)
err
=
setting
.
UpdateChatsByJsonString
(
value
)
case
"AutoGroups"
:
err
=
setting
.
UpdateAutoGroupsByJsonString
(
value
)
case
"DefaultUseAutoGroup"
:
setting
.
DefaultUseAutoGroup
=
value
==
"true"
case
"CustomCallbackAddress"
:
case
"CustomCallbackAddress"
:
setting
.
CustomCallbackAddress
=
value
setting
.
CustomCallbackAddress
=
value
case
"EpayId"
:
case
"EpayId"
:
...
...
relay/helper/price.go
View file @
c6a9df67
...
@@ -2,6 +2,7 @@ package helper
...
@@ -2,6 +2,7 @@ package helper
import
(
import
(
"fmt"
"fmt"
"log"
"one-api/common"
"one-api/common"
constant2
"one-api/constant"
constant2
"one-api/constant"
relaycommon
"one-api/relay/common"
relaycommon
"one-api/relay/common"
...
@@ -31,10 +32,19 @@ func (p PriceData) ToSetting() string {
...
@@ -31,10 +32,19 @@ func (p PriceData) ToSetting() string {
func
ModelPriceHelper
(
c
*
gin
.
Context
,
info
*
relaycommon
.
RelayInfo
,
promptTokens
int
,
maxTokens
int
)
(
PriceData
,
error
)
{
func
ModelPriceHelper
(
c
*
gin
.
Context
,
info
*
relaycommon
.
RelayInfo
,
promptTokens
int
,
maxTokens
int
)
(
PriceData
,
error
)
{
modelPrice
,
usePrice
:=
operation_setting
.
GetModelPrice
(
info
.
OriginModelName
,
false
)
modelPrice
,
usePrice
:=
operation_setting
.
GetModelPrice
(
info
.
OriginModelName
,
false
)
groupRatio
:=
setting
.
GetGroupRatio
(
info
.
Group
)
groupRatio
:=
setting
.
GetGroupRatio
(
info
.
Group
)
var
userGroupRatio
float64
autoGroup
,
exists
:=
c
.
Get
(
"auto_group"
)
if
exists
{
groupRatio
=
setting
.
GetGroupRatio
(
autoGroup
.
(
string
))
log
.
Printf
(
"final group ratio: %f"
,
groupRatio
)
info
.
Group
=
autoGroup
.
(
string
)
}
actualGroupRatio
:=
groupRatio
userGroupRatio
,
ok
:=
setting
.
GetGroupGroupRatio
(
info
.
UserGroup
,
info
.
Group
)
userGroupRatio
,
ok
:=
setting
.
GetGroupGroupRatio
(
info
.
UserGroup
,
info
.
Group
)
if
ok
{
if
ok
{
g
roupRatio
=
userGroupRatio
actualG
roupRatio
=
userGroupRatio
}
}
groupRatio
=
actualGroupRatio
var
preConsumedQuota
int
var
preConsumedQuota
int
var
modelRatio
float64
var
modelRatio
float64
var
completionRatio
float64
var
completionRatio
float64
...
...
service/quota.go
View file @
c6a9df67
...
@@ -3,6 +3,7 @@ package service
...
@@ -3,6 +3,7 @@ package service
import
(
import
(
"errors"
"errors"
"fmt"
"fmt"
"log"
"one-api/common"
"one-api/common"
constant2
"one-api/constant"
constant2
"one-api/constant"
"one-api/dto"
"one-api/dto"
...
@@ -94,11 +95,20 @@ func PreWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usag
...
@@ -94,11 +95,20 @@ func PreWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usag
audioInputTokens
:=
usage
.
InputTokenDetails
.
AudioTokens
audioInputTokens
:=
usage
.
InputTokenDetails
.
AudioTokens
audioOutTokens
:=
usage
.
OutputTokenDetails
.
AudioTokens
audioOutTokens
:=
usage
.
OutputTokenDetails
.
AudioTokens
groupRatio
:=
setting
.
GetGroupRatio
(
relayInfo
.
Group
)
groupRatio
:=
setting
.
GetGroupRatio
(
relayInfo
.
Group
)
modelRatio
,
_
:=
operation_setting
.
GetModelRatio
(
modelName
)
autoGroup
,
exists
:=
ctx
.
Get
(
"auto_group"
)
if
exists
{
groupRatio
=
setting
.
GetGroupRatio
(
autoGroup
.
(
string
))
log
.
Printf
(
"final group ratio: %f"
,
groupRatio
)
relayInfo
.
Group
=
autoGroup
.
(
string
)
}
actualGroupRatio
:=
groupRatio
userGroupRatio
,
ok
:=
setting
.
GetGroupGroupRatio
(
relayInfo
.
UserGroup
,
relayInfo
.
Group
)
userGroupRatio
,
ok
:=
setting
.
GetGroupGroupRatio
(
relayInfo
.
UserGroup
,
relayInfo
.
Group
)
if
ok
{
if
ok
{
g
roupRatio
=
userGroupRatio
actualG
roupRatio
=
userGroupRatio
}
}
modelRatio
,
_
:=
operation_setting
.
GetModelRatio
(
modelName
)
quotaInfo
:=
QuotaInfo
{
quotaInfo
:=
QuotaInfo
{
InputDetails
:
TokenDetails
{
InputDetails
:
TokenDetails
{
...
@@ -112,7 +122,7 @@ func PreWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usag
...
@@ -112,7 +122,7 @@ func PreWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usag
ModelName
:
modelName
,
ModelName
:
modelName
,
UsePrice
:
relayInfo
.
UsePrice
,
UsePrice
:
relayInfo
.
UsePrice
,
ModelRatio
:
modelRatio
,
ModelRatio
:
modelRatio
,
GroupRatio
:
g
roupRatio
,
GroupRatio
:
actualG
roupRatio
,
}
}
quota
:=
calculateAudioQuota
(
quotaInfo
)
quota
:=
calculateAudioQuota
(
quotaInfo
)
...
@@ -149,6 +159,13 @@ func PostWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, mod
...
@@ -149,6 +159,13 @@ func PostWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, mod
audioRatio
:=
decimal
.
NewFromFloat
(
operation_setting
.
GetAudioRatio
(
relayInfo
.
OriginModelName
))
audioRatio
:=
decimal
.
NewFromFloat
(
operation_setting
.
GetAudioRatio
(
relayInfo
.
OriginModelName
))
audioCompletionRatio
:=
decimal
.
NewFromFloat
(
operation_setting
.
GetAudioCompletionRatio
(
modelName
))
audioCompletionRatio
:=
decimal
.
NewFromFloat
(
operation_setting
.
GetAudioCompletionRatio
(
modelName
))
autoGroup
,
exists
:=
ctx
.
Get
(
"auto_group"
)
if
exists
{
groupRatio
=
setting
.
GetGroupRatio
(
autoGroup
.
(
string
))
log
.
Printf
(
"final group ratio: %f"
,
groupRatio
)
relayInfo
.
Group
=
autoGroup
.
(
string
)
}
actualGroupRatio
:=
groupRatio
actualGroupRatio
:=
groupRatio
userGroupRatio
,
ok
:=
setting
.
GetGroupGroupRatio
(
relayInfo
.
UserGroup
,
relayInfo
.
Group
)
userGroupRatio
,
ok
:=
setting
.
GetGroupGroupRatio
(
relayInfo
.
UserGroup
,
relayInfo
.
Group
)
if
ok
{
if
ok
{
...
@@ -290,6 +307,13 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
...
@@ -290,6 +307,13 @@ func PostAudioConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo,
modelPrice
:=
priceData
.
ModelPrice
modelPrice
:=
priceData
.
ModelPrice
usePrice
:=
priceData
.
UsePrice
usePrice
:=
priceData
.
UsePrice
autoGroup
,
exists
:=
ctx
.
Get
(
"auto_group"
)
if
exists
{
groupRatio
=
setting
.
GetGroupRatio
(
autoGroup
.
(
string
))
log
.
Printf
(
"final group ratio: %f"
,
groupRatio
)
relayInfo
.
Group
=
autoGroup
.
(
string
)
}
actualGroupRatio
:=
groupRatio
actualGroupRatio
:=
groupRatio
userGroupRatio
,
ok
:=
setting
.
GetGroupGroupRatio
(
relayInfo
.
UserGroup
,
relayInfo
.
Group
)
userGroupRatio
,
ok
:=
setting
.
GetGroupGroupRatio
(
relayInfo
.
UserGroup
,
relayInfo
.
Group
)
if
ok
{
if
ok
{
...
...
setting/auto_group.go
0 → 100644
View file @
c6a9df67
package
setting
import
"encoding/json"
var
AutoGroups
=
[]
string
{
"default"
,
}
var
DefaultUseAutoGroup
=
false
func
ContainsAutoGroup
(
group
string
)
bool
{
for
_
,
autoGroup
:=
range
AutoGroups
{
if
autoGroup
==
group
{
return
true
}
}
return
false
}
func
UpdateAutoGroupsByJsonString
(
jsonString
string
)
error
{
AutoGroups
=
make
([]
string
,
0
)
return
json
.
Unmarshal
([]
byte
(
jsonString
),
&
AutoGroups
)
}
func
AutoGroups2JsonString
()
string
{
jsonBytes
,
err
:=
json
.
Marshal
(
AutoGroups
)
if
err
!=
nil
{
return
"[]"
}
return
string
(
jsonBytes
)
}
setting/user_usable_group.go
View file @
c6a9df67
...
@@ -50,3 +50,10 @@ func GroupInUserUsableGroups(groupName string) bool {
...
@@ -50,3 +50,10 @@ func GroupInUserUsableGroups(groupName string) bool {
_
,
ok
:=
userUsableGroups
[
groupName
]
_
,
ok
:=
userUsableGroups
[
groupName
]
return
ok
return
ok
}
}
func
GetUsableGroupDescription
(
groupName
string
)
string
{
if
desc
,
ok
:=
userUsableGroups
[
groupName
];
ok
{
return
desc
}
return
groupName
}
web/src/components/settings/OperationSetting.js
View file @
c6a9df67
...
@@ -31,6 +31,8 @@ const OperationSetting = () => {
...
@@ -31,6 +31,8 @@ const OperationSetting = () => {
ModelPrice
:
''
,
ModelPrice
:
''
,
GroupRatio
:
''
,
GroupRatio
:
''
,
GroupGroupRatio
:
''
,
GroupGroupRatio
:
''
,
AutoGroups
:
''
,
DefaultUseAutoGroup
:
false
,
UserUsableGroups
:
''
,
UserUsableGroups
:
''
,
TopUpLink
:
''
,
TopUpLink
:
''
,
'general_setting.docs_link'
:
''
,
'general_setting.docs_link'
:
''
,
...
@@ -76,6 +78,7 @@ const OperationSetting = () => {
...
@@ -76,6 +78,7 @@ const OperationSetting = () => {
item
.
key
===
'ModelRatio'
||
item
.
key
===
'ModelRatio'
||
item
.
key
===
'GroupRatio'
||
item
.
key
===
'GroupRatio'
||
item
.
key
===
'GroupGroupRatio'
||
item
.
key
===
'GroupGroupRatio'
||
item
.
key
===
'AutoGroups'
||
item
.
key
===
'UserUsableGroups'
||
item
.
key
===
'UserUsableGroups'
||
item
.
key
===
'CompletionRatio'
||
item
.
key
===
'CompletionRatio'
||
item
.
key
===
'ModelPrice'
||
item
.
key
===
'ModelPrice'
||
...
@@ -85,7 +88,8 @@ const OperationSetting = () => {
...
@@ -85,7 +88,8 @@ const OperationSetting = () => {
}
}
if
(
if
(
item
.
key
.
endsWith
(
'Enabled'
)
||
item
.
key
.
endsWith
(
'Enabled'
)
||
[
'DefaultCollapseSidebar'
].
includes
(
item
.
key
)
[
'DefaultCollapseSidebar'
].
includes
(
item
.
key
)
||
[
'DefaultUseAutoGroup'
].
includes
(
item
.
key
)
)
{
)
{
newInputs
[
item
.
key
]
=
item
.
value
===
'true'
?
true
:
false
;
newInputs
[
item
.
key
]
=
item
.
value
===
'true'
?
true
:
false
;
}
else
{
}
else
{
...
...
web/src/pages/Setting/Operation/GroupRatioSettings.js
View file @
c6a9df67
...
@@ -17,6 +17,8 @@ export default function GroupRatioSettings(props) {
...
@@ -17,6 +17,8 @@ export default function GroupRatioSettings(props) {
GroupRatio
:
''
,
GroupRatio
:
''
,
UserUsableGroups
:
''
,
UserUsableGroups
:
''
,
GroupGroupRatio
:
''
,
GroupGroupRatio
:
''
,
AutoGroups
:
''
,
DefaultUseAutoGroup
:
false
,
});
});
const
refForm
=
useRef
();
const
refForm
=
useRef
();
const
[
inputsRow
,
setInputsRow
]
=
useState
(
inputs
);
const
[
inputsRow
,
setInputsRow
]
=
useState
(
inputs
);
...
@@ -167,6 +169,40 @@ export default function GroupRatioSettings(props) {
...
@@ -167,6 +169,40 @@ export default function GroupRatioSettings(props) {
/
>
/
>
<
/Col
>
<
/Col
>
<
/Row
>
<
/Row
>
<
Row
gutter
=
{
16
}
>
<
Col
xs
=
{
24
}
sm
=
{
16
}
>
<
Form
.
TextArea
label
=
{
t
(
'自动分组auto,从第一个开始选择'
)}
placeholder
=
{
t
(
'为一个 JSON 文本'
)}
field
=
{
'AutoGroups'
}
autosize
=
{{
minRows
:
6
,
maxRows
:
12
}}
trigger
=
'blur'
stopValidateWithError
rules
=
{[
{
validator
:
(
rule
,
value
)
=>
verifyJSON
(
value
),
message
:
t
(
'不是合法的 JSON 字符串'
),
},
]}
onChange
=
{(
value
)
=>
setInputs
({
...
inputs
,
AutoGroups
:
value
})
}
/
>
<
/Col
>
<
/Row
>
<
Row
gutter
=
{
16
}
>
<
Col
span
=
{
16
}
>
<
Form
.
Switch
label
=
{
t
(
'创建令牌默认选择auto分组,初始令牌也将设为auto(否则留空,为用户默认分组)'
,
)}
field
=
{
'DefaultUseAutoGroup'
}
onChange
=
{(
value
)
=>
setInputs
({
...
inputs
,
DefaultUseAutoGroup
:
value
})
}
/
>
<
/Col
>
<
/Row
>
<
/Form.Section
>
<
/Form.Section
>
<
/Form
>
<
/Form
>
<
Button
onClick
=
{
onSubmit
}
>
{
t
(
'保存分组倍率设置'
)}
<
/Button
>
<
Button
onClick
=
{
onSubmit
}
>
{
t
(
'保存分组倍率设置'
)}
<
/Button
>
...
...
web/src/pages/Token/EditToken.js
View file @
c6a9df67
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
React
,
{
useEffect
,
useState
,
useContext
}
from
'react'
;
import
{
useNavigate
}
from
'react-router-dom'
;
import
{
useNavigate
}
from
'react-router-dom'
;
import
{
import
{
API
,
API
,
...
@@ -7,7 +7,7 @@ import {
...
@@ -7,7 +7,7 @@ import {
showSuccess
,
showSuccess
,
timestamp2string
,
timestamp2string
,
renderGroupOption
,
renderGroupOption
,
renderQuotaWithPrompt
renderQuotaWithPrompt
,
}
from
'../../helpers'
;
}
from
'../../helpers'
;
import
{
import
{
AutoComplete
,
AutoComplete
,
...
@@ -37,11 +37,13 @@ import {
...
@@ -37,11 +37,13 @@ import {
IconPlusCircle
,
IconPlusCircle
,
}
from
'@douyinfe/semi-icons'
;
}
from
'@douyinfe/semi-icons'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
StatusContext
}
from
'../../context/Status'
;
const
{
Text
,
Title
}
=
Typography
;
const
{
Text
,
Title
}
=
Typography
;
const
EditToken
=
(
props
)
=>
{
const
EditToken
=
(
props
)
=>
{
const
{
t
}
=
useTranslation
();
const
{
t
}
=
useTranslation
();
const
[
statusState
,
statusDispatch
]
=
useContext
(
StatusContext
);
const
[
isEdit
,
setIsEdit
]
=
useState
(
false
);
const
[
isEdit
,
setIsEdit
]
=
useState
(
false
);
const
[
loading
,
setLoading
]
=
useState
(
isEdit
);
const
[
loading
,
setLoading
]
=
useState
(
isEdit
);
const
originInputs
=
{
const
originInputs
=
{
...
@@ -119,7 +121,19 @@ const EditToken = (props) => {
...
@@ -119,7 +121,19 @@ const EditToken = (props) => {
value
:
group
,
value
:
group
,
ratio
:
info
.
ratio
,
ratio
:
info
.
ratio
,
}));
}));
if
(
statusState
?.
status
?.
default_use_auto_group
)
{
// if contain auto, add it to the first position
if
(
localGroupOptions
.
some
((
group
)
=>
group
.
value
===
'auto'
))
{
// 排序
localGroupOptions
.
sort
((
a
,
b
)
=>
(
a
.
value
===
'auto'
?
-
1
:
1
));
}
else
{
localGroupOptions
.
unshift
({
label
:
t
(
'自动选择'
),
value
:
'auto'
});
}
}
setGroups
(
localGroupOptions
);
setGroups
(
localGroupOptions
);
if
(
statusState
?.
status
?.
default_use_auto_group
)
{
setInputs
({
...
inputs
,
group
:
'auto'
});
}
}
else
{
}
else
{
showError
(
t
(
message
));
showError
(
t
(
message
));
}
}
...
@@ -268,32 +282,37 @@ const EditToken = (props) => {
...
@@ -268,32 +282,37 @@ const EditToken = (props) => {
placement
=
{
isEdit
?
'right'
:
'left'
}
placement
=
{
isEdit
?
'right'
:
'left'
}
title
=
{
title
=
{
<
Space
>
<
Space
>
{
isEdit
?
{
isEdit
?
(
<
Tag
color
=
"blue"
shape
=
"circle"
>
{
t
(
'更新'
)}
<
/Tag>
:
<
Tag
color
=
'blue'
shape
=
'circle'
>
<
Tag
color
=
"green"
shape
=
"circle"
>
{
t
(
'新建'
)}
<
/Tag
>
{
t
(
'更新'
)}
}
<
/Tag
>
<
Title
heading
=
{
4
}
className
=
"m-0"
>
)
:
(
<
Tag
color
=
'green'
shape
=
'circle'
>
{
t
(
'新建'
)}
<
/Tag
>
)}
<
Title
heading
=
{
4
}
className
=
'm-0'
>
{
isEdit
?
t
(
'更新令牌信息'
)
:
t
(
'创建新的令牌'
)}
{
isEdit
?
t
(
'更新令牌信息'
)
:
t
(
'创建新的令牌'
)}
<
/Title
>
<
/Title
>
<
/Space
>
<
/Space
>
}
}
headerStyle
=
{{
headerStyle
=
{{
borderBottom
:
'1px solid var(--semi-color-border)'
,
borderBottom
:
'1px solid var(--semi-color-border)'
,
padding
:
'24px'
padding
:
'24px'
,
}}
}}
bodyStyle
=
{{
bodyStyle
=
{{
backgroundColor
:
'var(--semi-color-bg-0)'
,
backgroundColor
:
'var(--semi-color-bg-0)'
,
padding
:
'0'
padding
:
'0'
,
}}
}}
visible
=
{
props
.
visiable
}
visible
=
{
props
.
visiable
}
width
=
{
isMobile
()
?
'100%'
:
600
}
width
=
{
isMobile
()
?
'100%'
:
600
}
footer
=
{
footer
=
{
<
div
className
=
"flex justify-end bg-white"
>
<
div
className
=
'flex justify-end bg-white'
>
<
Space
>
<
Space
>
<
Button
<
Button
theme
=
"solid"
theme
=
'solid'
size
=
"large"
size
=
'large'
className
=
"!rounded-full"
className
=
'!rounded-full'
onClick
=
{
submit
}
onClick
=
{
submit
}
icon
=
{
<
IconSave
/>
}
icon
=
{
<
IconSave
/>
}
loading
=
{
loading
}
loading
=
{
loading
}
...
@@ -301,10 +320,10 @@ const EditToken = (props) => {
...
@@ -301,10 +320,10 @@ const EditToken = (props) => {
{
t
(
'提交'
)}
{
t
(
'提交'
)}
<
/Button
>
<
/Button
>
<
Button
<
Button
theme
=
"light"
theme
=
'light'
size
=
"large"
size
=
'large'
className
=
"!rounded-full"
className
=
'!rounded-full'
type
=
"primary"
type
=
'primary'
onClick
=
{
handleCancel
}
onClick
=
{
handleCancel
}
icon
=
{
<
IconClose
/>
}
icon
=
{
<
IconClose
/>
}
>
>
...
@@ -317,87 +336,107 @@ const EditToken = (props) => {
...
@@ -317,87 +336,107 @@ const EditToken = (props) => {
onCancel
=
{()
=>
handleCancel
()}
onCancel
=
{()
=>
handleCancel
()}
>
>
<
Spin
spinning
=
{
loading
}
>
<
Spin
spinning
=
{
loading
}
>
<
div
className
=
"p-6"
>
<
div
className
=
'p-6'
>
<
Card
className
=
"!rounded-2xl shadow-sm border-0 mb-6"
>
<
Card
className
=
'!rounded-2xl shadow-sm border-0 mb-6'
>
<
div
className
=
"flex items-center mb-4 p-6 rounded-xl"
style
=
{{
<
div
background
:
'linear-gradient(135deg, #1e3a8a 0%, #2563eb 50%, #3b82f6 100%)'
,
className
=
'flex items-center mb-4 p-6 rounded-xl'
position
:
'relative'
style
=
{{
}}
>
background
:
<
div
className
=
"absolute inset-0 overflow-hidden"
>
'linear-gradient(135deg, #1e3a8a 0%, #2563eb 50%, #3b82f6 100%)'
,
<
div
className
=
"absolute -top-10 -right-10 w-40 h-40 bg-white opacity-5 rounded-full"
><
/div
>
position
:
'relative'
,
<
div
className
=
"absolute -bottom-8 -left-8 w-24 h-24 bg-white opacity-10 rounded-full"
><
/div
>
}}
>
<
div
className
=
'absolute inset-0 overflow-hidden'
>
<
div
className
=
'absolute -top-10 -right-10 w-40 h-40 bg-white opacity-5 rounded-full'
><
/div
>
<
div
className
=
'absolute -bottom-8 -left-8 w-24 h-24 bg-white opacity-10 rounded-full'
><
/div
>
<
/div
>
<
div
className
=
'w-10 h-10 rounded-full bg-white/20 flex items-center justify-center mr-4 relative'
>
<
IconPlusCircle
size
=
'large'
style
=
{{
color
:
'#ffffff'
}}
/
>
<
/div
>
<
/div
>
<
div
className
=
"w-10 h-10 rounded-full bg-white/20 flex items-center justify-center mr-4 relative"
>
<
div
className
=
'relative'
>
<
IconPlusCircle
size
=
"large"
style
=
{{
color
:
'#ffffff'
}}
/
>
<
Text
style
=
{{
color
:
'#ffffff'
}}
className
=
'text-lg font-medium'
>
{
t
(
'基本信息'
)}
<
/Text
>
<
div
style
=
{{
color
:
'#ffffff'
}}
className
=
'text-sm opacity-80'
>
{
t
(
'设置令牌的基本信息'
)}
<
/div
>
<
/div
>
<
div
className
=
"relative"
>
<
Text
style
=
{{
color
:
'#ffffff'
}}
className
=
"text-lg font-medium"
>
{
t
(
'基本信息'
)}
<
/Text
>
<
div
style
=
{{
color
:
'#ffffff'
}}
className
=
"text-sm opacity-80"
>
{
t
(
'设置令牌的基本信息'
)}
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
div
className
=
"space-y-4"
>
<
div
className
=
'space-y-4'
>
<
div
>
<
div
>
<
Text
strong
className
=
"block mb-2"
>
{
t
(
'名称'
)}
<
/Text
>
<
Text
strong
className
=
'block mb-2'
>
{
t
(
'名称'
)}
<
/Text
>
<
Input
<
Input
placeholder
=
{
t
(
'请输入名称'
)}
placeholder
=
{
t
(
'请输入名称'
)}
onChange
=
{(
value
)
=>
handleInputChange
(
'name'
,
value
)}
onChange
=
{(
value
)
=>
handleInputChange
(
'name'
,
value
)}
value
=
{
name
}
value
=
{
name
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
size
=
"large"
size
=
'large'
className
=
"!rounded-lg"
className
=
'!rounded-lg'
showClear
showClear
required
required
/>
/>
<
/div
>
<
/div
>
<
div
>
<
div
>
<
Text
strong
className
=
"block mb-2"
>
{
t
(
'过期时间'
)}
<
/Text
>
<
Text
strong
className
=
'block mb-2'
>
<
div
className
=
"mb-2"
>
{
t
(
'过期时间'
)}
<
/Text
>
<
div
className
=
'mb-2'
>
<
DatePicker
<
DatePicker
placeholder
=
{
t
(
'请选择过期时间'
)}
placeholder
=
{
t
(
'请选择过期时间'
)}
onChange
=
{(
value
)
=>
handleInputChange
(
'expired_time'
,
value
)}
onChange
=
{(
value
)
=>
handleInputChange
(
'expired_time'
,
value
)
}
value
=
{
expired_time
}
value
=
{
expired_time
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
type
=
"dateTime"
type
=
'dateTime'
className
=
"w-full !rounded-lg"
className
=
'w-full !rounded-lg'
size
=
"large"
size
=
'large'
prefix
=
{
<
IconCalendar
/>
}
prefix
=
{
<
IconCalendar
/>
}
/
>
/
>
<
/div
>
<
/div
>
<
div
className
=
"flex flex-wrap gap-2"
>
<
div
className
=
'flex flex-wrap gap-2'
>
<
Button
<
Button
theme
=
"light"
theme
=
'light'
type
=
"primary"
type
=
'primary'
onClick
=
{()
=>
setExpiredTime
(
0
,
0
,
0
,
0
)}
onClick
=
{()
=>
setExpiredTime
(
0
,
0
,
0
,
0
)}
className
=
"!rounded-full"
className
=
'!rounded-full'
>
>
{
t
(
'永不过期'
)}
{
t
(
'永不过期'
)}
<
/Button
>
<
/Button
>
<
Button
<
Button
theme
=
"light"
theme
=
'light'
type
=
"tertiary"
type
=
'tertiary'
onClick
=
{()
=>
setExpiredTime
(
0
,
0
,
1
,
0
)}
onClick
=
{()
=>
setExpiredTime
(
0
,
0
,
1
,
0
)}
className
=
"!rounded-full"
className
=
'!rounded-full'
icon
=
{
<
IconClock
/>
}
icon
=
{
<
IconClock
/>
}
>
>
{
t
(
'一小时'
)}
{
t
(
'一小时'
)}
<
/Button
>
<
/Button
>
<
Button
<
Button
theme
=
"light"
theme
=
'light'
type
=
"tertiary"
type
=
'tertiary'
onClick
=
{()
=>
setExpiredTime
(
0
,
1
,
0
,
0
)}
onClick
=
{()
=>
setExpiredTime
(
0
,
1
,
0
,
0
)}
className
=
"!rounded-full"
className
=
'!rounded-full'
icon
=
{
<
IconCalendar
/>
}
icon
=
{
<
IconCalendar
/>
}
>
>
{
t
(
'一天'
)}
{
t
(
'一天'
)}
<
/Button
>
<
/Button
>
<
Button
<
Button
theme
=
"light"
theme
=
'light'
type
=
"tertiary"
type
=
'tertiary'
onClick
=
{()
=>
setExpiredTime
(
1
,
0
,
0
,
0
)}
onClick
=
{()
=>
setExpiredTime
(
1
,
0
,
0
,
0
)}
className
=
"!rounded-full"
className
=
'!rounded-full'
icon
=
{
<
IconCalendar
/>
}
icon
=
{
<
IconCalendar
/>
}
>
>
{
t
(
'一个月'
)}
{
t
(
'一个月'
)}
...
@@ -407,44 +446,62 @@ const EditToken = (props) => {
...
@@ -407,44 +446,62 @@ const EditToken = (props) => {
<
/div
>
<
/div
>
<
/Card
>
<
/Card
>
<
Card
className
=
"!rounded-2xl shadow-sm border-0 mb-6"
>
<
Card
className
=
'!rounded-2xl shadow-sm border-0 mb-6'
>
<
div
className
=
"flex items-center mb-4 p-6 rounded-xl"
style
=
{{
<
div
background
:
'linear-gradient(135deg, #065f46 0%, #059669 50%, #10b981 100%)'
,
className
=
'flex items-center mb-4 p-6 rounded-xl'
position
:
'relative'
style
=
{{
}}
>
background
:
<
div
className
=
"absolute inset-0 overflow-hidden"
>
'linear-gradient(135deg, #065f46 0%, #059669 50%, #10b981 100%)'
,
<
div
className
=
"absolute -top-10 -right-10 w-40 h-40 bg-white opacity-5 rounded-full"
><
/div
>
position
:
'relative'
,
<
div
className
=
"absolute -bottom-8 -left-8 w-24 h-24 bg-white opacity-10 rounded-full"
><
/div
>
}}
>
<
div
className
=
'absolute inset-0 overflow-hidden'
>
<
div
className
=
'absolute -top-10 -right-10 w-40 h-40 bg-white opacity-5 rounded-full'
><
/div
>
<
div
className
=
'absolute -bottom-8 -left-8 w-24 h-24 bg-white opacity-10 rounded-full'
><
/div
>
<
/div
>
<
/div
>
<
div
className
=
"w-10 h-10 rounded-full bg-white/20 flex items-center justify-center mr-4 relative"
>
<
div
className
=
'w-10 h-10 rounded-full bg-white/20 flex items-center justify-center mr-4 relative'
>
<
IconCreditCard
size
=
"large"
style
=
{{
color
:
'#ffffff'
}}
/
>
<
IconCreditCard
size
=
'large'
style
=
{{
color
:
'#ffffff'
}}
/
>
<
/div
>
<
div
className
=
'relative'
>
<
Text
style
=
{{
color
:
'#ffffff'
}}
className
=
'text-lg font-medium'
>
{
t
(
'额度设置'
)}
<
/Text
>
<
div
style
=
{{
color
:
'#ffffff'
}}
className
=
'text-sm opacity-80'
>
{
t
(
'设置令牌可用额度和数量'
)}
<
/div
>
<
/div
>
<
div
className
=
"relative"
>
<
Text
style
=
{{
color
:
'#ffffff'
}}
className
=
"text-lg font-medium"
>
{
t
(
'额度设置'
)}
<
/Text
>
<
div
style
=
{{
color
:
'#ffffff'
}}
className
=
"text-sm opacity-80"
>
{
t
(
'设置令牌可用额度和数量'
)}
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
Banner
<
Banner
type
=
"warning"
type
=
'warning'
description
=
{
t
(
'注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。'
)}
description
=
{
t
(
className
=
"mb-4 !rounded-lg"
'注意,令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制。'
,
)}
className
=
'mb-4 !rounded-lg'
/>
/>
<
div
className
=
"space-y-4"
>
<
div
className
=
'space-y-4'
>
<
div
>
<
div
>
<
div
className
=
"flex justify-between mb-2"
>
<
div
className
=
'flex justify-between mb-2'
>
<
Text
strong
>
{
t
(
'额度'
)}
<
/Text
>
<
Text
strong
>
{
t
(
'额度'
)}
<
/Text
>
<
Text
type
=
"tertiary"
>
{
renderQuotaWithPrompt
(
remain_quota
)}
<
/Text
>
<
Text
type
=
'tertiary'
>
{
renderQuotaWithPrompt
(
remain_quota
)}
<
/Text
>
<
/div
>
<
/div
>
<
AutoComplete
<
AutoComplete
placeholder
=
{
t
(
'请输入额度'
)}
placeholder
=
{
t
(
'请输入额度'
)}
onChange
=
{(
value
)
=>
handleInputChange
(
'remain_quota'
,
value
)}
onChange
=
{(
value
)
=>
handleInputChange
(
'remain_quota'
,
value
)}
value
=
{
remain_quota
}
value
=
{
remain_quota
}
autoComplete
=
"new-password"
autoComplete
=
'new-password'
type
=
"number"
type
=
'number'
size
=
"large"
size
=
'large'
className
=
"w-full !rounded-lg"
className
=
'w-full !rounded-lg'
prefix
=
{
<
IconCreditCard
/>
}
prefix
=
{
<
IconCreditCard
/>
}
data
=
{[
data
=
{[
{
value
:
500000
,
label
:
'1$'
},
{
value
:
500000
,
label
:
'1$'
},
...
@@ -460,16 +517,18 @@ const EditToken = (props) => {
...
@@ -460,16 +517,18 @@ const EditToken = (props) => {
{
!
isEdit
&&
(
{
!
isEdit
&&
(
<
div
>
<
div
>
<
Text
strong
className
=
"block mb-2"
>
{
t
(
'新建数量'
)}
<
/Text
>
<
Text
strong
className
=
'block mb-2'
>
{
t
(
'新建数量'
)}
<
/Text
>
<
AutoComplete
<
AutoComplete
placeholder
=
{
t
(
'请选择或输入创建令牌的数量'
)}
placeholder
=
{
t
(
'请选择或输入创建令牌的数量'
)}
onChange
=
{(
value
)
=>
handleTokenCountChange
(
value
)}
onChange
=
{(
value
)
=>
handleTokenCountChange
(
value
)}
onSelect
=
{(
value
)
=>
handleTokenCountChange
(
value
)}
onSelect
=
{(
value
)
=>
handleTokenCountChange
(
value
)}
value
=
{
tokenCount
.
toString
()}
value
=
{
tokenCount
.
toString
()}
autoComplete
=
"off"
autoComplete
=
'off'
type
=
"number"
type
=
'number'
className
=
"w-full !rounded-lg"
className
=
'w-full !rounded-lg'
size
=
"large"
size
=
'large'
prefix
=
{
<
IconPlusCircle
/>
}
prefix
=
{
<
IconPlusCircle
/>
}
data
=
{[
data
=
{[
{
value
:
10
,
label
:
t
(
'10个'
)
},
{
value
:
10
,
label
:
t
(
'10个'
)
},
...
@@ -482,12 +541,12 @@ const EditToken = (props) => {
...
@@ -482,12 +541,12 @@ const EditToken = (props) => {
<
/div
>
<
/div
>
)}
)}
<
div
className
=
"flex justify-end"
>
<
div
className
=
'flex justify-end'
>
<
Button
<
Button
theme
=
"light"
theme
=
'light'
type
=
{
unlimited_quota
?
"danger"
:
"warning"
}
type
=
{
unlimited_quota
?
'danger'
:
'warning'
}
onClick
=
{
setUnlimitedQuota
}
onClick
=
{
setUnlimitedQuota
}
className
=
"!rounded-full"
className
=
'!rounded-full'
>
>
{
unlimited_quota
?
t
(
'取消无限额度'
)
:
t
(
'设为无限额度'
)}
{
unlimited_quota
?
t
(
'取消无限额度'
)
:
t
(
'设为无限额度'
)}
<
/Button
>
<
/Button
>
...
@@ -495,92 +554,137 @@ const EditToken = (props) => {
...
@@ -495,92 +554,137 @@ const EditToken = (props) => {
<
/div
>
<
/div
>
<
/Card
>
<
/Card
>
<
Card
className
=
"!rounded-2xl shadow-sm border-0 mb-6"
>
<
Card
className
=
'!rounded-2xl shadow-sm border-0 mb-6'
>
<
div
className
=
"flex items-center mb-4 p-6 rounded-xl"
style
=
{{
<
div
background
:
'linear-gradient(135deg, #4c1d95 0%, #6d28d9 50%, #7c3aed 100%)'
,
className
=
'flex items-center mb-4 p-6 rounded-xl'
position
:
'relative'
style
=
{{
}}
>
background
:
<
div
className
=
"absolute inset-0 overflow-hidden"
>
'linear-gradient(135deg, #4c1d95 0%, #6d28d9 50%, #7c3aed 100%)'
,
<
div
className
=
"absolute -top-10 -right-10 w-40 h-40 bg-white opacity-5 rounded-full"
><
/div
>
position
:
'relative'
,
<
div
className
=
"absolute -bottom-8 -left-8 w-24 h-24 bg-white opacity-10 rounded-full"
><
/div
>
}}
>
<
div
className
=
'absolute inset-0 overflow-hidden'
>
<
div
className
=
'absolute -top-10 -right-10 w-40 h-40 bg-white opacity-5 rounded-full'
><
/div
>
<
div
className
=
'absolute -bottom-8 -left-8 w-24 h-24 bg-white opacity-10 rounded-full'
><
/div
>
<
/div
>
<
div
className
=
'w-10 h-10 rounded-full bg-white/20 flex items-center justify-center mr-4 relative'
>
<
IconLink
size
=
'large'
style
=
{{
color
:
'#ffffff'
}}
/
>
<
/div
>
<
/div
>
<
div
className
=
"w-10 h-10 rounded-full bg-white/20 flex items-center justify-center mr-4 relative"
>
<
div
className
=
'relative'
>
<
IconLink
size
=
"large"
style
=
{{
color
:
'#ffffff'
}}
/
>
<
Text
style
=
{{
color
:
'#ffffff'
}}
className
=
'text-lg font-medium'
>
{
t
(
'访问限制'
)}
<
/Text
>
<
div
style
=
{{
color
:
'#ffffff'
}}
className
=
'text-sm opacity-80'
>
{
t
(
'设置令牌的访问限制'
)}
<
/div
>
<
/div
>
<
div
className
=
"relative"
>
<
Text
style
=
{{
color
:
'#ffffff'
}}
className
=
"text-lg font-medium"
>
{
t
(
'访问限制'
)}
<
/Text
>
<
div
style
=
{{
color
:
'#ffffff'
}}
className
=
"text-sm opacity-80"
>
{
t
(
'设置令牌的访问限制'
)}
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
div
className
=
"space-y-4"
>
<
div
className
=
'space-y-4'
>
<
div
>
<
div
>
<
Text
strong
className
=
"block mb-2"
>
{
t
(
'IP白名单'
)}
<
/Text
>
<
Text
strong
className
=
'block mb-2'
>
{
t
(
'IP白名单'
)}
<
/Text
>
<
TextArea
<
TextArea
placeholder
=
{
t
(
'允许的IP,一行一个,不填写则不限制'
)}
placeholder
=
{
t
(
'允许的IP,一行一个,不填写则不限制'
)}
onChange
=
{(
value
)
=>
handleInputChange
(
'allow_ips'
,
value
)}
onChange
=
{(
value
)
=>
handleInputChange
(
'allow_ips'
,
value
)}
value
=
{
inputs
.
allow_ips
}
value
=
{
inputs
.
allow_ips
}
style
=
{{
fontFamily
:
'JetBrains Mono, Consolas'
}}
style
=
{{
fontFamily
:
'JetBrains Mono, Consolas'
}}
className
=
"!rounded-lg"
className
=
'!rounded-lg'
rows
=
{
4
}
rows
=
{
4
}
/
>
/
>
<
Text
type
=
"tertiary"
className
=
"mt-1 block text-xs"
>
{
t
(
'请勿过度信任此功能,IP可能被伪造'
)}
<
/Text
>
<
Text
type
=
'tertiary'
className
=
'mt-1 block text-xs'
>
{
t
(
'请勿过度信任此功能,IP可能被伪造'
)}
<
/Text
>
<
/div
>
<
/div
>
<
div
>
<
div
>
<
div
className
=
"flex items-center mb-2"
>
<
div
className
=
'flex items-center mb-2'
>
<
Checkbox
<
Checkbox
checked
=
{
model_limits_enabled
}
checked
=
{
model_limits_enabled
}
onChange
=
{(
e
)
=>
handleInputChange
(
'model_limits_enabled'
,
e
.
target
.
checked
)}
onChange
=
{(
e
)
=>
handleInputChange
(
'model_limits_enabled'
,
e
.
target
.
checked
,
)
}
>
>
<
Text
strong
>
{
t
(
'模型限制'
)}
<
/Text
>
<
Text
strong
>
{
t
(
'模型限制'
)}
<
/Text
>
<
/Checkbox
>
<
/Checkbox
>
<
/div
>
<
/div
>
<
Select
<
Select
placeholder
=
{
model_limits_enabled
?
t
(
'请选择该渠道所支持的模型'
)
:
t
(
'勾选启用模型限制后可选择'
)}
placeholder
=
{
model_limits_enabled
?
t
(
'请选择该渠道所支持的模型'
)
:
t
(
'勾选启用模型限制后可选择'
)
}
onChange
=
{(
value
)
=>
handleInputChange
(
'model_limits'
,
value
)}
onChange
=
{(
value
)
=>
handleInputChange
(
'model_limits'
,
value
)}
value
=
{
inputs
.
model_limits
}
value
=
{
inputs
.
model_limits
}
multiple
multiple
size
=
"large"
size
=
'large'
className
=
"w-full !rounded-lg"
className
=
'w-full !rounded-lg'
prefix
=
{
<
IconServer
/>
}
prefix
=
{
<
IconServer
/>
}
optionList
=
{
models
}
optionList
=
{
models
}
disabled
=
{
!
model_limits_enabled
}
disabled
=
{
!
model_limits_enabled
}
maxTagCount
=
{
3
}
maxTagCount
=
{
3
}
/
>
/
>
<
Text
type
=
"tertiary"
className
=
"mt-1 block text-xs"
>
{
t
(
'非必要,不建议启用模型限制'
)}
<
/Text
>
<
Text
type
=
'tertiary'
className
=
'mt-1 block text-xs'
>
{
t
(
'非必要,不建议启用模型限制'
)}
<
/Text
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/Card
>
<
/Card
>
<
Card
className
=
"!rounded-2xl shadow-sm border-0"
>
<
Card
className
=
'!rounded-2xl shadow-sm border-0'
>
<
div
className
=
"flex items-center mb-4 p-6 rounded-xl"
style
=
{{
<
div
background
:
'linear-gradient(135deg, #92400e 0%, #d97706 50%, #f59e0b 100%)'
,
className
=
'flex items-center mb-4 p-6 rounded-xl'
position
:
'relative'
style
=
{{
}}
>
background
:
<
div
className
=
"absolute inset-0 overflow-hidden"
>
'linear-gradient(135deg, #92400e 0%, #d97706 50%, #f59e0b 100%)'
,
<
div
className
=
"absolute -top-10 -right-10 w-40 h-40 bg-white opacity-5 rounded-full"
><
/div
>
position
:
'relative'
,
<
div
className
=
"absolute -bottom-8 -left-8 w-24 h-24 bg-white opacity-10 rounded-full"
><
/div
>
}}
>
<
div
className
=
'absolute inset-0 overflow-hidden'
>
<
div
className
=
'absolute -top-10 -right-10 w-40 h-40 bg-white opacity-5 rounded-full'
><
/div
>
<
div
className
=
'absolute -bottom-8 -left-8 w-24 h-24 bg-white opacity-10 rounded-full'
><
/div
>
<
/div
>
<
/div
>
<
div
className
=
"w-10 h-10 rounded-full bg-white/20 flex items-center justify-center mr-4 relative"
>
<
div
className
=
'w-10 h-10 rounded-full bg-white/20 flex items-center justify-center mr-4 relative'
>
<
IconUserGroup
size
=
"large"
style
=
{{
color
:
'#ffffff'
}}
/
>
<
IconUserGroup
size
=
'large'
style
=
{{
color
:
'#ffffff'
}}
/
>
<
/div
>
<
div
className
=
'relative'
>
<
Text
style
=
{{
color
:
'#ffffff'
}}
className
=
'text-lg font-medium'
>
{
t
(
'分组信息'
)}
<
/Text
>
<
div
style
=
{{
color
:
'#ffffff'
}}
className
=
'text-sm opacity-80'
>
{
t
(
'设置令牌的分组'
)}
<
/div
>
<
/div
>
<
div
className
=
"relative"
>
<
Text
style
=
{{
color
:
'#ffffff'
}}
className
=
"text-lg font-medium"
>
{
t
(
'分组信息'
)}
<
/Text
>
<
div
style
=
{{
color
:
'#ffffff'
}}
className
=
"text-sm opacity-80"
>
{
t
(
'设置令牌的分组'
)}
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
div
>
<
div
>
<
Text
strong
className
=
"block mb-2"
>
{
t
(
'令牌分组'
)}
<
/Text
>
<
Text
strong
className
=
'block mb-2'
>
{
t
(
'令牌分组'
)}
<
/Text
>
{
groups
.
length
>
0
?
(
{
groups
.
length
>
0
?
(
<
Select
<
Select
placeholder
=
{
t
(
'令牌分组,默认为用户的分组'
)}
placeholder
=
{
t
(
'令牌分组,默认为用户的分组'
)}
onChange
=
{(
value
)
=>
handleInputChange
(
'group'
,
value
)}
onChange
=
{(
value
)
=>
handleInputChange
(
'group'
,
value
)}
renderOptionItem
=
{
renderGroupOption
}
renderOptionItem
=
{
renderGroupOption
}
value
=
{
inputs
.
group
}
value
=
{
inputs
.
group
}
size
=
"large"
size
=
'large'
className
=
"w-full !rounded-lg"
className
=
'w-full !rounded-lg'
prefix
=
{
<
IconUserGroup
/>
}
prefix
=
{
<
IconUserGroup
/>
}
optionList
=
{
groups
}
optionList
=
{
groups
}
/
>
/
>
...
@@ -588,8 +692,8 @@ const EditToken = (props) => {
...
@@ -588,8 +692,8 @@ const EditToken = (props) => {
<
Select
<
Select
placeholder
=
{
t
(
'管理员未设置用户可选分组'
)}
placeholder
=
{
t
(
'管理员未设置用户可选分组'
)}
disabled
=
{
true
}
disabled
=
{
true
}
size
=
"large"
size
=
'large'
className
=
"w-full !rounded-lg"
className
=
'w-full !rounded-lg'
prefix
=
{
<
IconUserGroup
/>
}
prefix
=
{
<
IconUserGroup
/>
}
/
>
/
>
)}
)}
...
...
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