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
1042408f
authored
May 13, 2024
by
QuentinHsu
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'main' into refactor-settings-operation
parents
2b03ce53
699e29e2
Hide whitespace changes
Inline
Side-by-side
Showing
28 changed files
with
390 additions
and
150 deletions
+390
-150
common/constants.go
+2
-0
common/model-ratio.go
+37
-2
common/utils.go
+9
-0
controller/channel-test.go
+1
-1
controller/misc.go
+1
-1
controller/model.go
+44
-7
model/log.go
+4
-1
model/option.go
+3
-0
model/user.go
+7
-4
relay/channel/ai360/constants.go
+2
-0
relay/channel/ollama/constants.go
+3
-1
relay/channel/openai/constant.go
+1
-1
relay/constant/api_type.go
+11
-1
relay/relay-audio.go
+4
-1
relay/relay-image.go
+4
-1
relay/relay-mj.go
+8
-2
relay/relay-text.go
+6
-1
router/api-router.go
+1
-0
service/channel.go
+4
-1
web/src/components/ChannelsTable.js
+17
-13
web/src/components/LogsTable.js
+44
-10
web/src/components/MjLogsTable.js
+34
-0
web/src/components/OperationSetting.js
+23
-0
web/src/components/utils.js
+29
-0
web/src/constants/channel.constants.js
+2
-2
web/src/helpers/render.js
+26
-0
web/src/pages/Channel/EditChannel.js
+14
-91
web/src/pages/User/EditUser.js
+49
-9
No files found.
common/constants.go
View file @
1042408f
...
...
@@ -208,6 +208,8 @@ const (
ChannelTypeLingYiWanWu
=
31
ChannelTypeAws
=
33
ChannelTypeCohere
=
34
ChannelTypeDummy
// this one is only for count, do not add any channel after this
)
var
ChannelBaseURLs
=
[]
string
{
...
...
common/model-ratio.go
View file @
1042408f
...
...
@@ -28,6 +28,7 @@ var DefaultModelRatio = map[string]float64{
"gpt-4-vision-preview"
:
5
,
// $0.01 / 1K tokens
"gpt-4-1106-vision-preview"
:
5
,
// $0.01 / 1K tokens
"gpt-4-turbo"
:
5
,
// $0.01 / 1K tokens
"gpt-4-turbo-2024-04-09"
:
5
,
// $0.01 / 1K tokens
"gpt-3.5-turbo"
:
0.25
,
// $0.0015 / 1K tokens
//"gpt-3.5-turbo-0301": 0.75, //deprecated
"gpt-3.5-turbo-0613"
:
0.75
,
...
...
@@ -111,6 +112,8 @@ var DefaultModelRatio = map[string]float64{
"command-light-nightly"
:
0.5
,
"command-r"
:
0.25
,
"command-r-plus "
:
1.5
,
"deepseek-chat"
:
0.07
,
"deepseek-coder"
:
0.07
,
}
var
DefaultModelPrice
=
map
[
string
]
float64
{
...
...
@@ -135,6 +138,12 @@ var DefaultModelPrice = map[string]float64{
var
modelPrice
map
[
string
]
float64
=
nil
var
modelRatio
map
[
string
]
float64
=
nil
var
CompletionRatio
map
[
string
]
float64
=
nil
var
DefaultCompletionRatio
=
map
[
string
]
float64
{
"gpt-4-gizmo-*"
:
2
,
"gpt-4-all"
:
2
,
}
func
ModelPrice2JSONString
()
string
{
if
modelPrice
==
nil
{
modelPrice
=
DefaultModelPrice
...
...
@@ -199,6 +208,22 @@ func GetModelRatio(name string) float64 {
return
ratio
}
func
CompletionRatio2JSONString
()
string
{
if
CompletionRatio
==
nil
{
CompletionRatio
=
DefaultCompletionRatio
}
jsonBytes
,
err
:=
json
.
Marshal
(
CompletionRatio
)
if
err
!=
nil
{
SysError
(
"error marshalling completion ratio: "
+
err
.
Error
())
}
return
string
(
jsonBytes
)
}
func
UpdateCompletionRatioByJSONString
(
jsonStr
string
)
error
{
CompletionRatio
=
make
(
map
[
string
]
float64
)
return
json
.
Unmarshal
([]
byte
(
jsonStr
),
&
CompletionRatio
)
}
func
GetCompletionRatio
(
name
string
)
float64
{
if
strings
.
HasPrefix
(
name
,
"gpt-3.5"
)
{
if
name
==
"gpt-3.5-turbo"
||
strings
.
HasSuffix
(
name
,
"0125"
)
{
...
...
@@ -211,7 +236,7 @@ func GetCompletionRatio(name string) float64 {
}
return
4.0
/
3.0
}
if
strings
.
HasPrefix
(
name
,
"gpt-4"
)
{
if
strings
.
HasPrefix
(
name
,
"gpt-4"
)
&&
name
!=
"gpt-4-all"
&&
!
strings
.
HasPrefix
(
name
,
"gpt-4-gizmo"
)
{
if
strings
.
HasPrefix
(
name
,
"gpt-4-turbo"
)
||
strings
.
HasSuffix
(
name
,
"preview"
)
{
return
3
}
...
...
@@ -240,9 +265,19 @@ func GetCompletionRatio(name string) float64 {
return
2
}
}
if
strings
.
HasPrefix
(
name
,
"deepseek"
)
{
return
2
}
switch
name
{
case
"llama2-70b-4096"
:
return
0.8
/
0.7
return
0.8
/
0.64
case
"llama3-8b-8192"
:
return
2
case
"llama3-70b-8192"
:
return
0.79
/
0.59
}
if
ratio
,
ok
:=
CompletionRatio
[
name
];
ok
{
return
ratio
}
return
1
}
common/utils.go
View file @
1042408f
package
common
import
(
"encoding/json"
"fmt"
"github.com/google/uuid"
"html/template"
...
...
@@ -241,3 +242,11 @@ func RandomSleep() {
// Sleep for 0-3000 ms
time
.
Sleep
(
time
.
Duration
(
rand
.
Intn
(
3000
))
*
time
.
Millisecond
)
}
func
MapToJsonStr
(
m
map
[
string
]
interface
{})
string
{
bytes
,
err
:=
json
.
Marshal
(
m
)
if
err
!=
nil
{
return
""
}
return
string
(
bytes
)
}
controller/channel-test.go
View file @
1042408f
...
...
@@ -208,7 +208,7 @@ func testAllChannels(notify bool) error {
if
isChannelEnabled
&&
service
.
ShouldDisableChannel
(
openaiErr
,
-
1
)
&&
ban
{
service
.
DisableChannel
(
channel
.
Id
,
channel
.
Name
,
err
.
Error
())
}
if
!
isChannelEnabled
&&
service
.
ShouldEnableChannel
(
err
,
openaiErr
)
{
if
!
isChannelEnabled
&&
service
.
ShouldEnableChannel
(
err
,
openaiErr
,
channel
.
Status
)
{
service
.
EnableChannel
(
channel
.
Id
,
channel
.
Name
)
}
channel
.
UpdateResponseTime
(
milliseconds
)
...
...
controller/misc.go
View file @
1042408f
...
...
@@ -147,7 +147,7 @@ func SendEmailVerification(c *gin.Context) {
}
}
if
common
.
EmailAliasRestrictionEnabled
{
containsSpecialSymbols
:=
strings
.
Contains
(
localPart
,
"+"
)
||
strings
.
Co
unt
(
localPart
,
"."
)
>
1
containsSpecialSymbols
:=
strings
.
Contains
(
localPart
,
"+"
)
||
strings
.
Co
ntains
(
localPart
,
"."
)
if
containsSpecialSymbols
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
...
...
controller/model.go
View file @
1042408f
...
...
@@ -4,13 +4,15 @@ import (
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"one-api/common"
"one-api/constant"
"one-api/dto"
"one-api/model"
"one-api/relay"
"one-api/relay/channel/ai360"
"one-api/relay/channel/moonshot"
"one-api/relay/channel/lingyiwanwu"
"one-api/relay/channel/moonshot"
relaycommon
"one-api/relay/common"
relayconstant
"one-api/relay/constant"
)
...
...
@@ -43,8 +45,9 @@ type OpenAIModels struct {
var
openAIModels
[]
OpenAIModels
var
openAIModelsMap
map
[
string
]
OpenAIModels
var
channelId2Models
map
[
int
][]
string
func
init
()
{
func
getPermission
()
[]
OpenAIModelPermission
{
var
permission
[]
OpenAIModelPermission
permission
=
append
(
permission
,
OpenAIModelPermission
{
Id
:
"modelperm-LwHkVFn8AcMItP432fKKDIKJ"
,
...
...
@@ -60,7 +63,12 @@ func init() {
Group
:
nil
,
IsBlocking
:
false
,
})
return
permission
}
func
init
()
{
// https://platform.openai.com/docs/models/model-endpoint-compatibility
permission
:=
getPermission
()
for
i
:=
0
;
i
<
relayconstant
.
APITypeDummy
;
i
++
{
if
i
==
relayconstant
.
APITypeAIProxyLibrary
{
continue
...
...
@@ -85,7 +93,7 @@ func init() {
Id
:
modelName
,
Object
:
"model"
,
Created
:
1626777600
,
OwnedBy
:
"360"
,
OwnedBy
:
ai360
.
ChannelName
,
Permission
:
permission
,
Root
:
modelName
,
Parent
:
nil
,
...
...
@@ -128,6 +136,17 @@ func init() {
for
_
,
model
:=
range
openAIModels
{
openAIModelsMap
[
model
.
Id
]
=
model
}
channelId2Models
=
make
(
map
[
int
][]
string
)
for
i
:=
1
;
i
<=
common
.
ChannelTypeDummy
;
i
++
{
apiType
:=
relayconstant
.
ChannelType2APIType
(
i
)
if
apiType
==
-
1
||
apiType
==
relayconstant
.
APITypeAIProxyLibrary
{
continue
}
meta
:=
&
relaycommon
.
RelayInfo
{
ChannelType
:
i
}
adaptor
:=
relay
.
GetAdaptor
(
apiType
)
adaptor
.
Init
(
meta
,
dto
.
GeneralOpenAIRequest
{})
channelId2Models
[
i
]
=
adaptor
.
GetModelList
()
}
}
func
ListModels
(
c
*
gin
.
Context
)
{
...
...
@@ -142,21 +161,39 @@ func ListModels(c *gin.Context) {
}
models
:=
model
.
GetGroupModels
(
user
.
Group
)
userOpenAiModels
:=
make
([]
OpenAIModels
,
0
)
permission
:=
getPermission
()
for
_
,
s
:=
range
models
{
if
_
,
ok
:=
openAIModelsMap
[
s
];
ok
{
userOpenAiModels
=
append
(
userOpenAiModels
,
openAIModelsMap
[
s
])
}
else
{
userOpenAiModels
=
append
(
userOpenAiModels
,
OpenAIModels
{
Id
:
s
,
Object
:
"model"
,
Created
:
1626777600
,
OwnedBy
:
"openai"
,
Permission
:
permission
,
Root
:
s
,
Parent
:
nil
,
})
}
}
c
.
JSON
(
200
,
gin
.
H
{
"
object"
:
"list"
,
"data"
:
userOpenAiModels
,
"
success"
:
true
,
"data"
:
userOpenAiModels
,
})
}
func
ChannelListModels
(
c
*
gin
.
Context
)
{
c
.
JSON
(
200
,
gin
.
H
{
"object"
:
"list"
,
"data"
:
openAIModels
,
"success"
:
true
,
"data"
:
openAIModels
,
})
}
func
DashboardListModels
(
c
*
gin
.
Context
)
{
c
.
JSON
(
200
,
gin
.
H
{
"success"
:
true
,
"data"
:
channelId2Models
,
})
}
...
...
model/log.go
View file @
1042408f
...
...
@@ -24,6 +24,7 @@ type Log struct {
IsStream
bool
`json:"is_stream" gorm:"default:false"`
ChannelId
int
`json:"channel" gorm:"index"`
TokenId
int
`json:"token_id" gorm:"default:0;index"`
Other
string
`json:"other"`
}
const
(
...
...
@@ -57,12 +58,13 @@ func RecordLog(userId int, logType int, content string) {
}
}
func
RecordConsumeLog
(
ctx
context
.
Context
,
userId
int
,
channelId
int
,
promptTokens
int
,
completionTokens
int
,
modelName
string
,
tokenName
string
,
quota
int
,
content
string
,
tokenId
int
,
userQuota
int
,
useTimeSeconds
int
,
isStream
bool
)
{
func
RecordConsumeLog
(
ctx
context
.
Context
,
userId
int
,
channelId
int
,
promptTokens
int
,
completionTokens
int
,
modelName
string
,
tokenName
string
,
quota
int
,
content
string
,
tokenId
int
,
userQuota
int
,
useTimeSeconds
int
,
isStream
bool
,
other
map
[
string
]
interface
{}
)
{
common
.
LogInfo
(
ctx
,
fmt
.
Sprintf
(
"record consume log: userId=%d, 用户调用前余额=%d, channelId=%d, promptTokens=%d, completionTokens=%d, modelName=%s, tokenName=%s, quota=%d, content=%s"
,
userId
,
userQuota
,
channelId
,
promptTokens
,
completionTokens
,
modelName
,
tokenName
,
quota
,
content
))
if
!
common
.
LogConsumeEnabled
{
return
}
username
,
_
:=
CacheGetUsername
(
userId
)
otherStr
:=
common
.
MapToJsonStr
(
other
)
log
:=
&
Log
{
UserId
:
userId
,
Username
:
username
,
...
...
@@ -78,6 +80,7 @@ func RecordConsumeLog(ctx context.Context, userId int, channelId int, promptToke
TokenId
:
tokenId
,
UseTime
:
useTimeSeconds
,
IsStream
:
isStream
,
Other
:
otherStr
,
}
err
:=
DB
.
Create
(
log
)
.
Error
if
err
!=
nil
{
...
...
model/option.go
View file @
1042408f
...
...
@@ -83,6 +83,7 @@ func InitOptionMap() {
common
.
OptionMap
[
"ModelRatio"
]
=
common
.
ModelRatio2JSONString
()
common
.
OptionMap
[
"ModelPrice"
]
=
common
.
ModelPrice2JSONString
()
common
.
OptionMap
[
"GroupRatio"
]
=
common
.
GroupRatio2JSONString
()
common
.
OptionMap
[
"CompletionRatio"
]
=
common
.
CompletionRatio2JSONString
()
common
.
OptionMap
[
"TopUpLink"
]
=
common
.
TopUpLink
common
.
OptionMap
[
"ChatLink"
]
=
common
.
ChatLink
common
.
OptionMap
[
"ChatLink2"
]
=
common
.
ChatLink2
...
...
@@ -290,6 +291,8 @@ func updateOptionMap(key string, value string) (err error) {
err
=
common
.
UpdateModelRatioByJSONString
(
value
)
case
"GroupRatio"
:
err
=
common
.
UpdateGroupRatioByJSONString
(
value
)
case
"CompletionRatio"
:
err
=
common
.
UpdateCompletionRatioByJSONString
(
value
)
case
"ModelPrice"
:
err
=
common
.
UpdateModelPriceByJSONString
(
value
)
case
"TopUpLink"
:
...
...
model/user.go
View file @
1042408f
...
...
@@ -253,14 +253,17 @@ func (user *User) Edit(updatePassword bool) error {
}
}
newUser
:=
*
user
DB
.
First
(
&
user
,
user
.
Id
)
err
=
DB
.
Model
(
user
)
.
Updates
(
map
[
string
]
interface
{}{
updates
:=
map
[
string
]
interface
{}{
"username"
:
newUser
.
Username
,
"password"
:
newUser
.
Password
,
"display_name"
:
newUser
.
DisplayName
,
"group"
:
newUser
.
Group
,
"quota"
:
newUser
.
Quota
,
})
.
Error
}
if
updatePassword
{
updates
[
"password"
]
=
newUser
.
Password
}
DB
.
First
(
&
user
,
user
.
Id
)
err
=
DB
.
Model
(
user
)
.
Updates
(
updates
)
.
Error
if
err
==
nil
{
if
common
.
RedisEnabled
{
_
=
common
.
RedisSet
(
fmt
.
Sprintf
(
"user_group:%d"
,
user
.
Id
),
user
.
Group
,
time
.
Duration
(
UserId2GroupCacheSeconds
)
*
time
.
Second
)
...
...
relay/channel/ai360/constants.go
View file @
1042408f
...
...
@@ -6,3 +6,5 @@ var ModelList = []string{
"embedding_s1_v1"
,
"semantic_similarity_s1_v1"
,
}
var
ChannelName
=
"ai360"
relay/channel/ollama/constants.go
View file @
1042408f
package
ollama
var
ModelList
[]
string
var
ModelList
=
[]
string
{
"llama3-7b"
,
}
var
ChannelName
=
"ollama"
relay/channel/openai/constant.go
View file @
1042408f
...
...
@@ -6,7 +6,7 @@ var ModelList = []string{
"gpt-3.5-turbo-instruct"
,
"gpt-4"
,
"gpt-4-0314"
,
"gpt-4-0613"
,
"gpt-4-1106-preview"
,
"gpt-4-0125-preview"
,
"gpt-4-32k"
,
"gpt-4-32k-0314"
,
"gpt-4-32k-0613"
,
"gpt-4-turbo-preview"
,
"gpt-4-turbo-preview"
,
"gpt-4-turbo"
,
"gpt-4-turbo-2024-04-09"
,
"gpt-4-vision-preview"
,
"text-embedding-ada-002"
,
"text-embedding-3-small"
,
"text-embedding-3-large"
,
"text-curie-001"
,
"text-babbage-001"
,
"text-ada-001"
,
"text-davinci-002"
,
"text-davinci-003"
,
...
...
relay/constant/api_type.go
View file @
1042408f
...
...
@@ -25,8 +25,18 @@ const (
)
func
ChannelType2APIType
(
channelType
int
)
int
{
apiType
:=
APITypeOpenAI
apiType
:=
-
1
switch
channelType
{
case
common
.
ChannelTypeOpenAI
:
apiType
=
APITypeOpenAI
case
common
.
ChannelTypeAzure
:
apiType
=
APITypeOpenAI
case
common
.
ChannelTypeMoonshot
:
apiType
=
APITypeOpenAI
case
common
.
ChannelTypeLingYiWanWu
:
apiType
=
APITypeOpenAI
case
common
.
ChannelType360
:
apiType
=
APITypeOpenAI
case
common
.
ChannelTypeAnthropic
:
apiType
=
APITypeAnthropic
case
common
.
ChannelTypeBaidu
:
...
...
relay/relay-audio.go
View file @
1042408f
...
...
@@ -196,7 +196,10 @@ func AudioHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusCode {
if
quota
!=
0
{
tokenName
:=
c
.
GetString
(
"token_name"
)
logContent
:=
fmt
.
Sprintf
(
"模型倍率 %.2f,分组倍率 %.2f"
,
modelRatio
,
groupRatio
)
model
.
RecordConsumeLog
(
ctx
,
userId
,
channelId
,
promptTokens
,
0
,
audioRequest
.
Model
,
tokenName
,
quota
,
logContent
,
tokenId
,
userQuota
,
int
(
useTimeSeconds
),
false
)
other
:=
make
(
map
[
string
]
interface
{})
other
[
"model_ratio"
]
=
modelRatio
other
[
"group_ratio"
]
=
groupRatio
model
.
RecordConsumeLog
(
ctx
,
userId
,
channelId
,
promptTokens
,
0
,
audioRequest
.
Model
,
tokenName
,
quota
,
logContent
,
tokenId
,
userQuota
,
int
(
useTimeSeconds
),
false
,
other
)
model
.
UpdateUserUsedQuotaAndRequestCount
(
userId
,
quota
)
channelId
:=
c
.
GetInt
(
"channel_id"
)
model
.
UpdateChannelUsedQuota
(
channelId
,
quota
)
...
...
relay/relay-image.go
View file @
1042408f
...
...
@@ -191,7 +191,10 @@ func RelayImageHelper(c *gin.Context, relayMode int) *dto.OpenAIErrorWithStatusC
quality
=
"hd"
}
logContent
:=
fmt
.
Sprintf
(
"模型倍率 %.2f,分组倍率 %.2f, 大小 %s, 品质 %s"
,
modelRatio
,
groupRatio
,
imageRequest
.
Size
,
quality
)
model
.
RecordConsumeLog
(
ctx
,
userId
,
channelId
,
0
,
0
,
imageRequest
.
Model
,
tokenName
,
quota
,
logContent
,
tokenId
,
userQuota
,
int
(
useTimeSeconds
),
false
)
other
:=
make
(
map
[
string
]
interface
{})
other
[
"model_ratio"
]
=
modelRatio
other
[
"group_ratio"
]
=
groupRatio
model
.
RecordConsumeLog
(
ctx
,
userId
,
channelId
,
0
,
0
,
imageRequest
.
Model
,
tokenName
,
quota
,
logContent
,
tokenId
,
userQuota
,
int
(
useTimeSeconds
),
false
,
other
)
model
.
UpdateUserUsedQuotaAndRequestCount
(
userId
,
quota
)
channelId
:=
c
.
GetInt
(
"channel_id"
)
model
.
UpdateChannelUsedQuota
(
channelId
,
quota
)
...
...
relay/relay-mj.go
View file @
1042408f
...
...
@@ -202,7 +202,10 @@ func RelaySwapFace(c *gin.Context) *dto.MidjourneyResponse {
if
quota
!=
0
{
tokenName
:=
c
.
GetString
(
"token_name"
)
logContent
:=
fmt
.
Sprintf
(
"模型固定价格 %.2f,分组倍率 %.2f,操作 %s"
,
modelPrice
,
groupRatio
,
constant
.
MjActionSwapFace
)
model
.
RecordConsumeLog
(
ctx
,
userId
,
channelId
,
0
,
0
,
modelName
,
tokenName
,
quota
,
logContent
,
tokenId
,
userQuota
,
0
,
false
)
other
:=
make
(
map
[
string
]
interface
{})
other
[
"model_price"
]
=
modelPrice
other
[
"group_ratio"
]
=
groupRatio
model
.
RecordConsumeLog
(
ctx
,
userId
,
channelId
,
0
,
0
,
modelName
,
tokenName
,
quota
,
logContent
,
tokenId
,
userQuota
,
0
,
false
,
other
)
model
.
UpdateUserUsedQuotaAndRequestCount
(
userId
,
quota
)
channelId
:=
c
.
GetInt
(
"channel_id"
)
model
.
UpdateChannelUsedQuota
(
channelId
,
quota
)
...
...
@@ -498,7 +501,10 @@ func RelayMidjourneySubmit(c *gin.Context, relayMode int) *dto.MidjourneyRespons
if
quota
!=
0
{
tokenName
:=
c
.
GetString
(
"token_name"
)
logContent
:=
fmt
.
Sprintf
(
"模型固定价格 %.2f,分组倍率 %.2f,操作 %s"
,
modelPrice
,
groupRatio
,
midjRequest
.
Action
)
model
.
RecordConsumeLog
(
ctx
,
userId
,
channelId
,
0
,
0
,
modelName
,
tokenName
,
quota
,
logContent
,
tokenId
,
userQuota
,
0
,
false
)
other
:=
make
(
map
[
string
]
interface
{})
other
[
"model_price"
]
=
modelPrice
other
[
"group_ratio"
]
=
groupRatio
model
.
RecordConsumeLog
(
ctx
,
userId
,
channelId
,
0
,
0
,
modelName
,
tokenName
,
quota
,
logContent
,
tokenId
,
userQuota
,
0
,
false
,
other
)
model
.
UpdateUserUsedQuotaAndRequestCount
(
userId
,
quota
)
channelId
:=
c
.
GetInt
(
"channel_id"
)
model
.
UpdateChannelUsedQuota
(
channelId
,
quota
)
...
...
relay/relay-text.go
View file @
1042408f
...
...
@@ -315,7 +315,12 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, textRe
logModel
=
"gpt-4-gizmo-*"
logContent
+=
fmt
.
Sprintf
(
",模型 %s"
,
textRequest
.
Model
)
}
model
.
RecordConsumeLog
(
ctx
,
relayInfo
.
UserId
,
relayInfo
.
ChannelId
,
promptTokens
,
completionTokens
,
logModel
,
tokenName
,
quota
,
logContent
,
relayInfo
.
TokenId
,
userQuota
,
int
(
useTimeSeconds
),
relayInfo
.
IsStream
)
other
:=
make
(
map
[
string
]
interface
{})
other
[
"model_ratio"
]
=
modelRatio
other
[
"group_ratio"
]
=
groupRatio
other
[
"completion_ratio"
]
=
completionRatio
other
[
"model_price"
]
=
modelPrice
model
.
RecordConsumeLog
(
ctx
,
relayInfo
.
UserId
,
relayInfo
.
ChannelId
,
promptTokens
,
completionTokens
,
logModel
,
tokenName
,
quota
,
logContent
,
relayInfo
.
TokenId
,
userQuota
,
int
(
useTimeSeconds
),
relayInfo
.
IsStream
,
other
)
//if quota != 0 {
//
...
...
router/api-router.go
View file @
1042408f
...
...
@@ -14,6 +14,7 @@ func SetApiRouter(router *gin.Engine) {
apiRouter
.
Use
(
middleware
.
GlobalAPIRateLimit
())
{
apiRouter
.
GET
(
"/status"
,
controller
.
GetStatus
)
apiRouter
.
GET
(
"/models"
,
middleware
.
UserAuth
(),
controller
.
DashboardListModels
)
apiRouter
.
GET
(
"/status/test"
,
middleware
.
AdminAuth
(),
controller
.
TestStatus
)
apiRouter
.
GET
(
"/notice"
,
controller
.
GetNotice
)
apiRouter
.
GET
(
"/about"
,
controller
.
GetAbout
)
...
...
service/channel.go
View file @
1042408f
...
...
@@ -63,7 +63,7 @@ func ShouldDisableChannel(err *relaymodel.OpenAIError, statusCode int) bool {
return
false
}
func
ShouldEnableChannel
(
err
error
,
openAIErr
*
relaymodel
.
OpenAIError
)
bool
{
func
ShouldEnableChannel
(
err
error
,
openAIErr
*
relaymodel
.
OpenAIError
,
status
int
)
bool
{
if
!
common
.
AutomaticEnableChannelEnabled
{
return
false
}
...
...
@@ -73,5 +73,8 @@ func ShouldEnableChannel(err error, openAIErr *relaymodel.OpenAIError) bool {
if
openAIErr
!=
nil
{
return
false
}
if
status
!=
common
.
ChannelStatusAutoDisabled
{
return
false
}
return
true
}
web/src/components/ChannelsTable.js
View file @
1042408f
...
...
@@ -31,6 +31,7 @@ import {
}
from
'@douyinfe/semi-ui'
;
import
EditChannel
from
'../pages/Channel/EditChannel'
;
import
{
IconTreeTriangleDown
}
from
'@douyinfe/semi-icons'
;
import
{
loadChannelModels
}
from
'./utils.js'
;
function
renderTimestamp
(
timestamp
)
{
return
<>
{
timestamp2string
(
timestamp
)}
<
/>
;
...
...
@@ -354,27 +355,29 @@ const ChannelsTable = () => {
};
const
copySelectedChannel
=
async
(
id
)
=>
{
const
channelToCopy
=
channels
.
find
(
channel
=>
String
(
channel
.
id
)
===
String
(
id
));
console
.
log
(
channelToCopy
)
const
channelToCopy
=
channels
.
find
(
(
channel
)
=>
String
(
channel
.
id
)
===
String
(
id
),
);
console
.
log
(
channelToCopy
);
channelToCopy
.
name
+=
'_复制'
;
channelToCopy
.
created_time
=
null
;
channelToCopy
.
balance
=
0
;
channelToCopy
.
used_quota
=
0
;
if
(
!
channelToCopy
)
{
showError
(
"渠道未找到,请刷新页面后重试。"
);
return
;
showError
(
'渠道未找到,请刷新页面后重试。'
);
return
;
}
try
{
const
newChannel
=
{...
channelToCopy
,
id
:
undefined
};
const
response
=
await
API
.
post
(
'/api/channel/'
,
newChannel
);
if
(
response
.
data
.
success
)
{
showSuccess
(
"渠道复制成功"
);
await
refresh
();
}
else
{
showError
(
response
.
data
.
message
);
}
const
newChannel
=
{
...
channelToCopy
,
id
:
undefined
};
const
response
=
await
API
.
post
(
'/api/channel/'
,
newChannel
);
if
(
response
.
data
.
success
)
{
showSuccess
(
'渠道复制成功'
);
await
refresh
();
}
else
{
showError
(
response
.
data
.
message
);
}
}
catch
(
error
)
{
showError
(
"渠道复制失败: "
+
error
.
message
);
showError
(
'渠道复制失败: '
+
error
.
message
);
}
};
...
...
@@ -395,6 +398,7 @@ const ChannelsTable = () => {
showError
(
reason
);
});
fetchGroups
().
then
();
loadChannelModels
().
then
();
},
[]);
const
manageChannel
=
async
(
id
,
action
,
record
,
value
)
=>
{
...
...
web/src/components/LogsTable.js
View file @
1042408f
...
...
@@ -19,9 +19,15 @@ import {
Spin
,
Table
,
Tag
,
Tooltip
,
}
from
'@douyinfe/semi-ui'
;
import
{
ITEMS_PER_PAGE
}
from
'../constants'
;
import
{
renderNumber
,
renderQuota
,
stringToColor
}
from
'../helpers/render'
;
import
{
renderModelPrice
,
renderNumber
,
renderQuota
,
stringToColor
,
}
from
'../helpers/render'
;
import
Paragraph
from
'@douyinfe/semi-ui/lib/es/typography/paragraph'
;
const
{
Header
}
=
Layout
;
...
...
@@ -292,16 +298,44 @@ const LogsTable = () => {
title
:
'详情'
,
dataIndex
:
'content'
,
render
:
(
text
,
record
,
index
)
=>
{
if
(
record
.
other
===
''
)
{
return
(
<
Paragraph
ellipsis
=
{{
rows
:
2
,
showTooltip
:
{
type
:
'popover'
,
opts
:
{
style
:
{
width
:
240
}
},
},
}}
style
=
{{
maxWidth
:
240
}}
>
{
text
}
<
/Paragraph
>
);
}
let
other
=
JSON
.
parse
(
record
.
other
);
let
content
=
renderModelPrice
(
other
.
model_ratio
,
other
.
model_price
,
other
.
completion_ratio
,
other
.
group_ratio
,
);
return
(
<
Paragraph
ellipsis
=
{{
rows
:
2
,
showTooltip
:
{
type
:
'popover'
,
opts
:
{
style
:
{
width
:
240
}
}
},
}}
style
=
{{
maxWidth
:
240
}}
>
{
text
}
<
/Paragraph
>
<
Tooltip
content
=
{
content
}
>
<
Paragraph
ellipsis
=
{{
rows
:
2
,
showTooltip
:
{
type
:
'popover'
,
opts
:
{
style
:
{
width
:
240
}
},
},
}}
style
=
{{
maxWidth
:
240
}}
>
{
text
}
<
/Paragraph
>
<
/Tooltip
>
);
},
},
...
...
web/src/components/MjLogsTable.js
View file @
1042408f
...
...
@@ -236,6 +236,31 @@ const renderTimestamp = (timestampInSeconds) => {
return
`
${
year
}
-
${
month
}
-
${
day
}
${
hours
}
:
${
minutes
}
:
${
seconds
}
`
;
// 格式化输出
};
// 修改renderDuration函数以包含颜色逻辑
function
renderDuration
(
submit_time
,
finishTime
)
{
// 确保startTime和finishTime都是有效的时间戳
if
(
!
submit_time
||
!
finishTime
)
return
'N/A'
;
// 将时间戳转换为Date对象
const
start
=
new
Date
(
submit_time
);
const
finish
=
new
Date
(
finishTime
);
// 计算时间差(毫秒)
const
durationMs
=
finish
-
start
;
// 将时间差转换为秒,并保留一位小数
const
durationSec
=
(
durationMs
/
1000
).
toFixed
(
1
);
// 设置颜色:大于60秒则为红色,小于等于60秒则为绿色
const
color
=
durationSec
>
60
?
'red'
:
'green'
;
// 返回带有样式的颜色标签
return
(
<
Tag
color
=
{
color
}
size
=
"large"
>
{
durationSec
}
秒
<
/Tag
>
);
}
const
LogsTable
=
()
=>
{
const
[
isModalOpen
,
setIsModalOpen
]
=
useState
(
false
);
...
...
@@ -249,6 +274,15 @@ const LogsTable = () => {
},
},
{
title
:
'花费时间'
,
dataIndex
:
'finish_time'
,
// 以finish_time作为dataIndex
key
:
'finish_time'
,
render
:
(
finish
,
record
)
=>
{
// 假设record.start_time是存在的,并且finish是完成时间的时间戳
return
renderDuration
(
record
.
submit_time
,
finish
);
},
},
{
title
:
'渠道'
,
dataIndex
:
'channel_id'
,
className
:
isAdmin
()
?
'tableShow'
:
'tableHiddle'
,
...
...
web/src/components/OperationSetting.js
View file @
1042408f
...
...
@@ -29,6 +29,7 @@ const OperationSetting = () => {
PreConsumedQuota
:
0
,
StreamCacheQueueLength
:
0
,
ModelRatio
:
''
,
CompletionRatio
:
''
,
ModelPrice
:
''
,
GroupRatio
:
''
,
TopUpLink
:
''
,
...
...
@@ -69,6 +70,7 @@ const OperationSetting = () => {
if
(
item
.
key
===
'ModelRatio'
||
item
.
key
===
'GroupRatio'
||
item
.
key
===
'CompletionRatio'
||
item
.
key
===
'ModelPrice'
)
{
item
.
value
=
JSON
.
stringify
(
JSON
.
parse
(
item
.
value
),
null
,
2
);
...
...
@@ -166,6 +168,13 @@ const OperationSetting = () => {
}
await
updateOption
(
'ModelRatio'
,
inputs
.
ModelRatio
);
}
if
(
originInputs
[
'CompletionRatio'
]
!==
inputs
.
CompletionRatio
)
{
if
(
!
verifyJSON
(
inputs
.
CompletionRatio
))
{
showError
(
'模型补全倍率不是合法的 JSON 字符串'
);
return
;
}
await
updateOption
(
'CompletionRatio'
,
inputs
.
CompletionRatio
);
}
if
(
originInputs
[
'GroupRatio'
]
!==
inputs
.
GroupRatio
)
{
if
(
!
verifyJSON
(
inputs
.
GroupRatio
))
{
showError
(
'分组倍率不是合法的 JSON 字符串'
);
...
...
@@ -306,6 +315,20 @@ const OperationSetting = () => {
<
/Form.Group
>
<
Form
.
Group
widths
=
'equal'
>
<
Form
.
TextArea
label
=
'模型补全倍率(仅对自定义模型有效)'
name
=
'CompletionRatio'
onChange
=
{
handleInputChange
}
style
=
{{
minHeight
:
250
,
fontFamily
:
'JetBrains Mono, Consolas'
,
}}
autoComplete
=
'new-password'
value
=
{
inputs
.
CompletionRatio
}
placeholder
=
'为一个 JSON 文本,键为分组名称,值为倍率'
/>
<
/Form.Group
>
<
Form
.
Group
widths
=
'equal'
>
<
Form
.
TextArea
label
=
'分组倍率'
name
=
'GroupRatio'
onChange
=
{
handleInputChange
}
...
...
web/src/components/utils.js
View file @
1042408f
...
...
@@ -18,3 +18,32 @@ export async function onGitHubOAuthClicked(github_client_id) {
`https://github.com/login/oauth/authorize?client_id=
${
github_client_id
}
&state=
${
state
}
&scope=user:email`
,
);
}
let
channelModels
=
undefined
;
export
async
function
loadChannelModels
()
{
const
res
=
await
API
.
get
(
'/api/models'
);
const
{
success
,
data
}
=
res
.
data
;
if
(
!
success
)
{
return
;
}
channelModels
=
data
;
localStorage
.
setItem
(
'channel_models'
,
JSON
.
stringify
(
data
));
}
export
function
getChannelModels
(
type
)
{
if
(
channelModels
!==
undefined
&&
type
in
channelModels
)
{
if
(
!
channelModels
[
type
])
{
return
[];
}
return
channelModels
[
type
];
}
let
models
=
localStorage
.
getItem
(
'channel_models'
);
if
(
!
models
)
{
return
[];
}
channelModels
=
JSON
.
parse
(
models
);
if
(
type
in
channelModels
)
{
return
channelModels
[
type
];
}
return
[];
}
web/src/constants/channel.constants.js
View file @
1042408f
...
...
@@ -86,13 +86,13 @@ export const CHANNEL_OPTIONS = [
label
:
'智谱 ChatGLM'
,
},
{
key
:
1
6
,
key
:
2
6
,
text
:
'智谱 GLM-4V'
,
value
:
26
,
color
:
'purple'
,
label
:
'智谱 GLM-4V'
,
},
{
key
:
16
,
text
:
'Moonshot'
,
value
:
25
,
color
:
'green'
,
label
:
'Moonshot'
},
{
key
:
25
,
text
:
'Moonshot'
,
value
:
25
,
color
:
'green'
,
label
:
'Moonshot'
},
{
key
:
19
,
text
:
'360 智脑'
,
value
:
19
,
color
:
'blue'
,
label
:
'360 智脑'
},
{
key
:
23
,
text
:
'腾讯混元'
,
value
:
23
,
color
:
'teal'
,
label
:
'腾讯混元'
},
{
key
:
31
,
text
:
'零一万物'
,
value
:
31
,
color
:
'green'
,
label
:
'零一万物'
},
...
...
web/src/helpers/render.js
View file @
1042408f
...
...
@@ -135,6 +135,32 @@ export function renderQuota(quota, digits = 2) {
return
renderNumber
(
quota
);
}
export
function
renderModelPrice
(
modelRatio
,
modelPrice
=
-
1
,
completionRatio
,
groupRatio
,
)
{
// 1 ratio = $0.002 / 1K tokens
if
(
modelPrice
!==
-
1
)
{
return
'模型价格:$'
+
modelPrice
*
groupRatio
;
}
else
{
if
(
completionRatio
===
undefined
)
{
completionRatio
=
0
;
}
let
inputRatioPrice
=
modelRatio
*
0.002
*
groupRatio
;
let
completionRatioPrice
=
modelRatio
*
completionRatio
*
0.002
*
groupRatio
;
return
(
'输入:$'
+
inputRatioPrice
.
toFixed
(
3
)
+
'/1K tokens,补全:$'
+
completionRatioPrice
.
toFixed
(
3
)
+
'/1K tokens'
);
}
}
export
function
renderQuotaWithPrompt
(
quota
,
digits
)
{
let
displayInCurrency
=
localStorage
.
getItem
(
'display_in_currency'
);
displayInCurrency
=
displayInCurrency
===
'true'
;
...
...
web/src/pages/Channel/EditChannel.js
View file @
1042408f
...
...
@@ -23,6 +23,7 @@ import {
Banner
,
}
from
'@douyinfe/semi-ui'
;
import
{
Divider
}
from
'semantic-ui-react'
;
import
{
getChannelModels
,
loadChannelModels
}
from
'../../components/utils.js'
;
const
MODEL_MAPPING_EXAMPLE
=
{
'gpt-3.5-turbo-0301'
:
'gpt-3.5-turbo'
,
...
...
@@ -87,97 +88,9 @@ const EditChannel = (props) => {
const
[
customModel
,
setCustomModel
]
=
useState
(
''
);
const
handleInputChange
=
(
name
,
value
)
=>
{
setInputs
((
inputs
)
=>
({
...
inputs
,
[
name
]:
value
}));
if
(
name
===
'type'
&&
inputs
.
models
.
length
===
0
)
{
if
(
name
===
'type'
)
{
let
localModels
=
[];
switch
(
value
)
{
case
33
:
case
14
:
localModels
=
[
'claude-instant-1.2'
,
'claude-2'
,
'claude-2.0'
,
'claude-2.1'
,
'claude-3-opus-20240229'
,
'claude-3-sonnet-20240229'
,
'claude-3-haiku-20240307'
,
];
break
;
case
11
:
localModels
=
[
'PaLM-2'
];
break
;
case
15
:
localModels
=
[
'ERNIE-Bot'
,
'ERNIE-Bot-turbo'
,
'ERNIE-Bot-4'
,
'Embedding-V1'
,
];
break
;
case
17
:
localModels
=
[
'qwen-turbo'
,
'qwen-plus'
,
'qwen-max'
,
'qwen-max-longcontext'
,
'text-embedding-v1'
,
];
break
;
case
16
:
localModels
=
[
'chatglm_pro'
,
'chatglm_std'
,
'chatglm_lite'
];
break
;
case
18
:
localModels
=
[
'SparkDesk'
,
'SparkDesk-v1.1'
,
'SparkDesk-v2.1'
,
'SparkDesk-v3.1'
,
'SparkDesk-v3.5'
,
];
break
;
case
19
:
localModels
=
[
'360GPT_S2_V9'
,
'embedding-bert-512-v1'
,
'embedding_s1_v1'
,
'semantic_similarity_s1_v1'
,
];
break
;
case
23
:
localModels
=
[
'hunyuan'
];
break
;
case
24
:
localModels
=
[
'gemini-1.0-pro-001'
,
'gemini-1.0-pro-vision-001'
,
'gemini-1.5-pro'
,
'gemini-1.5-pro-latest'
,
'gemini-pro'
,
'gemini-pro-vision'
,
];
break
;
case
34
:
localModels
=
[
'command-r'
,
'command-r-plus'
,
'command-light'
,
'command-light-nightly'
,
'command'
,
'command-nightly'
,
];
break
;
case
25
:
localModels
=
[
'moonshot-v1-8k'
,
'moonshot-v1-32k'
,
'moonshot-v1-128k'
,
];
break
;
case
26
:
localModels
=
[
'glm-4'
,
'glm-4v'
,
'glm-3-turbo'
];
break
;
case
31
:
localModels
=
[
'yi-34b-chat-0205'
,
'yi-34b-chat-200k'
,
'yi-vl-plus'
];
break
;
case
2
:
localModels
=
[
'mj_imagine'
,
...
...
@@ -207,8 +120,14 @@ const EditChannel = (props) => {
'mj_pan'
,
];
break
;
default
:
localModels
=
getChannelModels
(
value
);
break
;
}
setInputs
((
inputs
)
=>
({
...
inputs
,
models
:
localModels
}));
if
(
inputs
.
models
.
length
===
0
)
{
setInputs
((
inputs
)
=>
({
...
inputs
,
models
:
localModels
}));
}
setBasicModels
(
localModels
);
}
//setAutoBan
};
...
...
@@ -244,6 +163,7 @@ const EditChannel = (props) => {
}
else
{
setAutoBan
(
true
);
}
setBasicModels
(
getChannelModels
(
data
.
type
));
// console.log(data);
}
else
{
showError
(
message
);
...
...
@@ -312,6 +232,9 @@ const EditChannel = (props) => {
loadChannel
().
then
(()
=>
{});
}
else
{
setInputs
(
originInputs
);
let
localModels
=
getChannelModels
(
inputs
.
type
);
setBasicModels
(
localModels
);
setInputs
((
inputs
)
=>
({
...
inputs
,
models
:
localModels
}));
}
},
[
props
.
editingChannel
.
id
]);
...
...
@@ -596,7 +519,7 @@ const EditChannel = (props) => {
handleInputChange
(
'models'
,
basicModels
);
}}
>
填入
基础
模型
填入
相关
模型
<
/Button
>
<
Button
type
=
'secondary'
...
...
web/src/pages/User/EditUser.js
View file @
1042408f
import
React
,
{
useEffect
,
useState
}
from
'react'
;
import
{
useNavigate
}
from
'react-router-dom'
;
import
{
API
,
isMobile
,
showError
,
showSuccess
}
from
'../../helpers'
;
import
{
renderQuotaWithPrompt
}
from
'../../helpers/render'
;
import
{
renderQuota
,
renderQuota
WithPrompt
}
from
'../../helpers/render'
;
import
Title
from
'@douyinfe/semi-ui/lib/es/typography/title'
;
import
{
Button
,
Divider
,
Input
,
Modal
,
Select
,
SideSheet
,
Space
,
...
...
@@ -17,6 +18,8 @@ import {
const
EditUser
=
(
props
)
=>
{
const
userId
=
props
.
editingUser
.
id
;
const
[
loading
,
setLoading
]
=
useState
(
true
);
const
[
addQuotaModalOpen
,
setIsModalOpen
]
=
useState
(
false
);
const
[
addQuotaLocal
,
setAddQuotaLocal
]
=
useState
(
''
);
const
[
inputs
,
setInputs
]
=
useState
({
username
:
''
,
display_name
:
''
,
...
...
@@ -107,6 +110,16 @@ const EditUser = (props) => {
setLoading
(
false
);
};
const
addLocalQuota
=
()
=>
{
let
newQuota
=
parseInt
(
quota
)
+
parseInt
(
addQuotaLocal
);
setInputs
((
inputs
)
=>
({
...
inputs
,
quota
:
newQuota
}));
};
const
openAddQuotaModal
=
()
=>
{
setAddQuotaLocal
(
'0'
);
setIsModalOpen
(
true
);
};
return
(
<>
<
SideSheet
...
...
@@ -192,14 +205,17 @@ const EditUser = (props) => {
<
div
style
=
{{
marginTop
:
20
}}
>
<
Typography
.
Text
>
{
`剩余额度
${
renderQuotaWithPrompt
(
quota
)}
`
}
<
/Typography.Text
>
<
/div
>
<
Input
name
=
'quota'
placeholder
=
{
'请输入新的剩余额度'
}
onChange
=
{(
value
)
=>
handleInputChange
(
'quota'
,
value
)}
value
=
{
quota
}
type
=
{
'number'
}
autoComplete
=
'new-password'
/>
<
Space
>
<
Input
name
=
'quota'
placeholder
=
{
'请输入新的剩余额度'
}
onChange
=
{(
value
)
=>
handleInputChange
(
'quota'
,
value
)}
value
=
{
quota
}
type
=
{
'number'
}
autoComplete
=
'new-password'
/>
<
Button
onClick
=
{
openAddQuotaModal
}
>
添加额度
<
/Button
>
<
/Space
>
<
/
>
)}
<
Divider
style
=
{{
marginTop
:
20
}}
>
以下信息不可修改
<
/Divider
>
...
...
@@ -245,6 +261,30 @@ const EditUser = (props) => {
/>
<
/Spin
>
<
/SideSheet
>
<
Modal
centered
=
{
true
}
visible
=
{
addQuotaModalOpen
}
onOk
=
{()
=>
{
addLocalQuota
();
setIsModalOpen
(
false
);
}}
onCancel
=
{()
=>
setIsModalOpen
(
false
)}
closable
=
{
null
}
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Typography
.
Text
>
{
`新额度
${
renderQuota
(
quota
)}
+
${
renderQuota
(
addQuotaLocal
)}
=
${
renderQuota
(
quota
+
parseInt
(
addQuotaLocal
))}
`
}
<
/Typography.Text
>
<
/div
>
<
Input
name
=
'addQuotaLocal'
placeholder
=
{
'需要添加的额度(支持负数)'
}
onChange
=
{(
value
)
=>
{
setAddQuotaLocal
(
value
);
}}
value
=
{
addQuotaLocal
}
type
=
{
'number'
}
autoComplete
=
'new-password'
/>
<
/Modal
>
<
/
>
);
};
...
...
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