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
23e5132e
authored
Sep 18, 2025
by
CaIon
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'main' into ssrf
# Conflicts: # service/cf_worker.go
parents
98d5b3db
3169065b
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
221 additions
and
28 deletions
+221
-28
controller/channel-test.go
+39
-0
controller/setup.go
+10
-10
controller/topup_stripe.go
+1
-1
dto/openai_response.go
+42
-0
relay/channel/openai/relay_responses.go
+24
-11
relay/channel/task/jimeng/adaptor.go
+26
-4
relay/channel/volcengine/adaptor.go
+2
-0
relay/channel/volcengine/constants.go
+1
-0
relay/compatible_handler.go
+13
-0
setting/operation_setting/tools.go
+40
-0
web/src/helpers/render.jsx
+21
-2
web/src/hooks/usage-logs/useUsageLogsData.jsx
+2
-0
No files found.
controller/channel-test.go
View file @
23e5132e
...
@@ -90,6 +90,11 @@ func testChannel(channel *model.Channel, testModel string) testResult {
...
@@ -90,6 +90,11 @@ func testChannel(channel *model.Channel, testModel string) testResult {
requestPath
=
"/v1/embeddings"
// 修改请求路径
requestPath
=
"/v1/embeddings"
// 修改请求路径
}
}
// VolcEngine 图像生成模型
if
channel
.
Type
==
constant
.
ChannelTypeVolcEngine
&&
strings
.
Contains
(
testModel
,
"seedream"
)
{
requestPath
=
"/v1/images/generations"
}
c
.
Request
=
&
http
.
Request
{
c
.
Request
=
&
http
.
Request
{
Method
:
"POST"
,
Method
:
"POST"
,
URL
:
&
url
.
URL
{
Path
:
requestPath
},
// 使用动态路径
URL
:
&
url
.
URL
{
Path
:
requestPath
},
// 使用动态路径
...
@@ -109,6 +114,21 @@ func testChannel(channel *model.Channel, testModel string) testResult {
...
@@ -109,6 +114,21 @@ func testChannel(channel *model.Channel, testModel string) testResult {
}
}
}
}
// 重新检查模型类型并更新请求路径
if
strings
.
Contains
(
strings
.
ToLower
(
testModel
),
"embedding"
)
||
strings
.
HasPrefix
(
testModel
,
"m3e"
)
||
strings
.
Contains
(
testModel
,
"bge-"
)
||
strings
.
Contains
(
testModel
,
"embed"
)
||
channel
.
Type
==
constant
.
ChannelTypeMokaAI
{
requestPath
=
"/v1/embeddings"
c
.
Request
.
URL
.
Path
=
requestPath
}
if
channel
.
Type
==
constant
.
ChannelTypeVolcEngine
&&
strings
.
Contains
(
testModel
,
"seedream"
)
{
requestPath
=
"/v1/images/generations"
c
.
Request
.
URL
.
Path
=
requestPath
}
cache
,
err
:=
model
.
GetUserCache
(
1
)
cache
,
err
:=
model
.
GetUserCache
(
1
)
if
err
!=
nil
{
if
err
!=
nil
{
return
testResult
{
return
testResult
{
...
@@ -140,6 +160,9 @@ func testChannel(channel *model.Channel, testModel string) testResult {
...
@@ -140,6 +160,9 @@ func testChannel(channel *model.Channel, testModel string) testResult {
if
c
.
Request
.
URL
.
Path
==
"/v1/embeddings"
{
if
c
.
Request
.
URL
.
Path
==
"/v1/embeddings"
{
relayFormat
=
types
.
RelayFormatEmbedding
relayFormat
=
types
.
RelayFormatEmbedding
}
}
if
c
.
Request
.
URL
.
Path
==
"/v1/images/generations"
{
relayFormat
=
types
.
RelayFormatOpenAIImage
}
info
,
err
:=
relaycommon
.
GenRelayInfo
(
c
,
relayFormat
,
request
,
nil
)
info
,
err
:=
relaycommon
.
GenRelayInfo
(
c
,
relayFormat
,
request
,
nil
)
...
@@ -201,6 +224,22 @@ func testChannel(channel *model.Channel, testModel string) testResult {
...
@@ -201,6 +224,22 @@ func testChannel(channel *model.Channel, testModel string) testResult {
}
}
// 调用专门用于 Embedding 的转换函数
// 调用专门用于 Embedding 的转换函数
convertedRequest
,
err
=
adaptor
.
ConvertEmbeddingRequest
(
c
,
info
,
embeddingRequest
)
convertedRequest
,
err
=
adaptor
.
ConvertEmbeddingRequest
(
c
,
info
,
embeddingRequest
)
}
else
if
info
.
RelayMode
==
relayconstant
.
RelayModeImagesGenerations
{
// 创建一个 ImageRequest
prompt
:=
"cat"
if
request
.
Prompt
!=
nil
{
if
promptStr
,
ok
:=
request
.
Prompt
.
(
string
);
ok
&&
promptStr
!=
""
{
prompt
=
promptStr
}
}
imageRequest
:=
dto
.
ImageRequest
{
Prompt
:
prompt
,
Model
:
request
.
Model
,
N
:
uint
(
request
.
N
),
Size
:
request
.
Size
,
}
// 调用专门用于图像生成的转换函数
convertedRequest
,
err
=
adaptor
.
ConvertImageRequest
(
c
,
info
,
imageRequest
)
}
else
{
}
else
{
// 对其他所有请求类型(如 Chat),保持原有逻辑
// 对其他所有请求类型(如 Chat),保持原有逻辑
convertedRequest
,
err
=
adaptor
.
ConvertOpenAIRequest
(
c
,
info
,
request
)
convertedRequest
,
err
=
adaptor
.
ConvertOpenAIRequest
(
c
,
info
,
request
)
...
...
controller/setup.go
View file @
23e5132e
...
@@ -53,7 +53,7 @@ func GetSetup(c *gin.Context) {
...
@@ -53,7 +53,7 @@ func GetSetup(c *gin.Context) {
func
PostSetup
(
c
*
gin
.
Context
)
{
func
PostSetup
(
c
*
gin
.
Context
)
{
// Check if setup is already completed
// Check if setup is already completed
if
constant
.
Setup
{
if
constant
.
Setup
{
c
.
JSON
(
4
00
,
gin
.
H
{
c
.
JSON
(
2
00
,
gin
.
H
{
"success"
:
false
,
"success"
:
false
,
"message"
:
"系统已经初始化完成"
,
"message"
:
"系统已经初始化完成"
,
})
})
...
@@ -66,7 +66,7 @@ func PostSetup(c *gin.Context) {
...
@@ -66,7 +66,7 @@ func PostSetup(c *gin.Context) {
var
req
SetupRequest
var
req
SetupRequest
err
:=
c
.
ShouldBindJSON
(
&
req
)
err
:=
c
.
ShouldBindJSON
(
&
req
)
if
err
!=
nil
{
if
err
!=
nil
{
c
.
JSON
(
4
00
,
gin
.
H
{
c
.
JSON
(
2
00
,
gin
.
H
{
"success"
:
false
,
"success"
:
false
,
"message"
:
"请求参数有误"
,
"message"
:
"请求参数有误"
,
})
})
...
@@ -77,7 +77,7 @@ func PostSetup(c *gin.Context) {
...
@@ -77,7 +77,7 @@ func PostSetup(c *gin.Context) {
if
!
rootExists
{
if
!
rootExists
{
// Validate username length: max 12 characters to align with model.User validation
// Validate username length: max 12 characters to align with model.User validation
if
len
(
req
.
Username
)
>
12
{
if
len
(
req
.
Username
)
>
12
{
c
.
JSON
(
4
00
,
gin
.
H
{
c
.
JSON
(
2
00
,
gin
.
H
{
"success"
:
false
,
"success"
:
false
,
"message"
:
"用户名长度不能超过12个字符"
,
"message"
:
"用户名长度不能超过12个字符"
,
})
})
...
@@ -85,7 +85,7 @@ func PostSetup(c *gin.Context) {
...
@@ -85,7 +85,7 @@ func PostSetup(c *gin.Context) {
}
}
// Validate password
// Validate password
if
req
.
Password
!=
req
.
ConfirmPassword
{
if
req
.
Password
!=
req
.
ConfirmPassword
{
c
.
JSON
(
4
00
,
gin
.
H
{
c
.
JSON
(
2
00
,
gin
.
H
{
"success"
:
false
,
"success"
:
false
,
"message"
:
"两次输入的密码不一致"
,
"message"
:
"两次输入的密码不一致"
,
})
})
...
@@ -93,7 +93,7 @@ func PostSetup(c *gin.Context) {
...
@@ -93,7 +93,7 @@ func PostSetup(c *gin.Context) {
}
}
if
len
(
req
.
Password
)
<
8
{
if
len
(
req
.
Password
)
<
8
{
c
.
JSON
(
4
00
,
gin
.
H
{
c
.
JSON
(
2
00
,
gin
.
H
{
"success"
:
false
,
"success"
:
false
,
"message"
:
"密码长度至少为8个字符"
,
"message"
:
"密码长度至少为8个字符"
,
})
})
...
@@ -103,7 +103,7 @@ func PostSetup(c *gin.Context) {
...
@@ -103,7 +103,7 @@ func PostSetup(c *gin.Context) {
// Create root user
// Create root user
hashedPassword
,
err
:=
common
.
Password2Hash
(
req
.
Password
)
hashedPassword
,
err
:=
common
.
Password2Hash
(
req
.
Password
)
if
err
!=
nil
{
if
err
!=
nil
{
c
.
JSON
(
5
00
,
gin
.
H
{
c
.
JSON
(
2
00
,
gin
.
H
{
"success"
:
false
,
"success"
:
false
,
"message"
:
"系统错误: "
+
err
.
Error
(),
"message"
:
"系统错误: "
+
err
.
Error
(),
})
})
...
@@ -120,7 +120,7 @@ func PostSetup(c *gin.Context) {
...
@@ -120,7 +120,7 @@ func PostSetup(c *gin.Context) {
}
}
err
=
model
.
DB
.
Create
(
&
rootUser
)
.
Error
err
=
model
.
DB
.
Create
(
&
rootUser
)
.
Error
if
err
!=
nil
{
if
err
!=
nil
{
c
.
JSON
(
5
00
,
gin
.
H
{
c
.
JSON
(
2
00
,
gin
.
H
{
"success"
:
false
,
"success"
:
false
,
"message"
:
"创建管理员账号失败: "
+
err
.
Error
(),
"message"
:
"创建管理员账号失败: "
+
err
.
Error
(),
})
})
...
@@ -135,7 +135,7 @@ func PostSetup(c *gin.Context) {
...
@@ -135,7 +135,7 @@ func PostSetup(c *gin.Context) {
// Save operation modes to database for persistence
// Save operation modes to database for persistence
err
=
model
.
UpdateOption
(
"SelfUseModeEnabled"
,
boolToString
(
req
.
SelfUseModeEnabled
))
err
=
model
.
UpdateOption
(
"SelfUseModeEnabled"
,
boolToString
(
req
.
SelfUseModeEnabled
))
if
err
!=
nil
{
if
err
!=
nil
{
c
.
JSON
(
5
00
,
gin
.
H
{
c
.
JSON
(
2
00
,
gin
.
H
{
"success"
:
false
,
"success"
:
false
,
"message"
:
"保存自用模式设置失败: "
+
err
.
Error
(),
"message"
:
"保存自用模式设置失败: "
+
err
.
Error
(),
})
})
...
@@ -144,7 +144,7 @@ func PostSetup(c *gin.Context) {
...
@@ -144,7 +144,7 @@ func PostSetup(c *gin.Context) {
err
=
model
.
UpdateOption
(
"DemoSiteEnabled"
,
boolToString
(
req
.
DemoSiteEnabled
))
err
=
model
.
UpdateOption
(
"DemoSiteEnabled"
,
boolToString
(
req
.
DemoSiteEnabled
))
if
err
!=
nil
{
if
err
!=
nil
{
c
.
JSON
(
5
00
,
gin
.
H
{
c
.
JSON
(
2
00
,
gin
.
H
{
"success"
:
false
,
"success"
:
false
,
"message"
:
"保存演示站点模式设置失败: "
+
err
.
Error
(),
"message"
:
"保存演示站点模式设置失败: "
+
err
.
Error
(),
})
})
...
@@ -160,7 +160,7 @@ func PostSetup(c *gin.Context) {
...
@@ -160,7 +160,7 @@ func PostSetup(c *gin.Context) {
}
}
err
=
model
.
DB
.
Create
(
&
setup
)
.
Error
err
=
model
.
DB
.
Create
(
&
setup
)
.
Error
if
err
!=
nil
{
if
err
!=
nil
{
c
.
JSON
(
5
00
,
gin
.
H
{
c
.
JSON
(
2
00
,
gin
.
H
{
"success"
:
false
,
"success"
:
false
,
"message"
:
"系统初始化失败: "
+
err
.
Error
(),
"message"
:
"系统初始化失败: "
+
err
.
Error
(),
})
})
...
...
controller/topup_stripe.go
View file @
23e5132e
...
@@ -217,7 +217,7 @@ func genStripeLink(referenceId string, customerId string, email string, amount i
...
@@ -217,7 +217,7 @@ func genStripeLink(referenceId string, customerId string, email string, amount i
params
:=
&
stripe
.
CheckoutSessionParams
{
params
:=
&
stripe
.
CheckoutSessionParams
{
ClientReferenceID
:
stripe
.
String
(
referenceId
),
ClientReferenceID
:
stripe
.
String
(
referenceId
),
SuccessURL
:
stripe
.
String
(
system_setting
.
ServerAddress
+
"/log"
),
SuccessURL
:
stripe
.
String
(
system_setting
.
ServerAddress
+
"/
console/
log"
),
CancelURL
:
stripe
.
String
(
system_setting
.
ServerAddress
+
"/topup"
),
CancelURL
:
stripe
.
String
(
system_setting
.
ServerAddress
+
"/topup"
),
LineItems
:
[]
*
stripe
.
CheckoutSessionLineItemParams
{
LineItems
:
[]
*
stripe
.
CheckoutSessionLineItemParams
{
{
{
...
...
dto/openai_response.go
View file @
23e5132e
...
@@ -6,6 +6,10 @@ import (
...
@@ -6,6 +6,10 @@ import (
"one-api/types"
"one-api/types"
)
)
const
(
ResponsesOutputTypeImageGenerationCall
=
"image_generation_call"
)
type
SimpleResponse
struct
{
type
SimpleResponse
struct
{
Usage
`json:"usage"`
Usage
`json:"usage"`
Error
any
`json:"error"`
Error
any
`json:"error"`
...
@@ -273,6 +277,42 @@ func (o *OpenAIResponsesResponse) GetOpenAIError() *types.OpenAIError {
...
@@ -273,6 +277,42 @@ func (o *OpenAIResponsesResponse) GetOpenAIError() *types.OpenAIError {
return
GetOpenAIError
(
o
.
Error
)
return
GetOpenAIError
(
o
.
Error
)
}
}
func
(
o
*
OpenAIResponsesResponse
)
HasImageGenerationCall
()
bool
{
if
len
(
o
.
Output
)
==
0
{
return
false
}
for
_
,
output
:=
range
o
.
Output
{
if
output
.
Type
==
ResponsesOutputTypeImageGenerationCall
{
return
true
}
}
return
false
}
func
(
o
*
OpenAIResponsesResponse
)
GetQuality
()
string
{
if
len
(
o
.
Output
)
==
0
{
return
""
}
for
_
,
output
:=
range
o
.
Output
{
if
output
.
Type
==
ResponsesOutputTypeImageGenerationCall
{
return
output
.
Quality
}
}
return
""
}
func
(
o
*
OpenAIResponsesResponse
)
GetSize
()
string
{
if
len
(
o
.
Output
)
==
0
{
return
""
}
for
_
,
output
:=
range
o
.
Output
{
if
output
.
Type
==
ResponsesOutputTypeImageGenerationCall
{
return
output
.
Size
}
}
return
""
}
type
IncompleteDetails
struct
{
type
IncompleteDetails
struct
{
Reasoning
string
`json:"reasoning"`
Reasoning
string
`json:"reasoning"`
}
}
...
@@ -283,6 +323,8 @@ type ResponsesOutput struct {
...
@@ -283,6 +323,8 @@ type ResponsesOutput struct {
Status
string
`json:"status"`
Status
string
`json:"status"`
Role
string
`json:"role"`
Role
string
`json:"role"`
Content
[]
ResponsesOutputContent
`json:"content"`
Content
[]
ResponsesOutputContent
`json:"content"`
Quality
string
`json:"quality"`
Size
string
`json:"size"`
}
}
type
ResponsesOutputContent
struct
{
type
ResponsesOutputContent
struct
{
...
...
relay/channel/openai/relay_responses.go
View file @
23e5132e
...
@@ -33,6 +33,12 @@ func OaiResponsesHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http
...
@@ -33,6 +33,12 @@ func OaiResponsesHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http
return
nil
,
types
.
WithOpenAIError
(
*
oaiError
,
resp
.
StatusCode
)
return
nil
,
types
.
WithOpenAIError
(
*
oaiError
,
resp
.
StatusCode
)
}
}
if
responsesResponse
.
HasImageGenerationCall
()
{
c
.
Set
(
"image_generation_call"
,
true
)
c
.
Set
(
"image_generation_call_quality"
,
responsesResponse
.
GetQuality
())
c
.
Set
(
"image_generation_call_size"
,
responsesResponse
.
GetSize
())
}
// 写入新的 response body
// 写入新的 response body
service
.
IOCopyBytesGracefully
(
c
,
resp
,
responseBody
)
service
.
IOCopyBytesGracefully
(
c
,
resp
,
responseBody
)
...
@@ -80,18 +86,25 @@ func OaiResponsesStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp
...
@@ -80,18 +86,25 @@ func OaiResponsesStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp
sendResponsesStreamData
(
c
,
streamResponse
,
data
)
sendResponsesStreamData
(
c
,
streamResponse
,
data
)
switch
streamResponse
.
Type
{
switch
streamResponse
.
Type
{
case
"response.completed"
:
case
"response.completed"
:
if
streamResponse
.
Response
!=
nil
&&
streamResponse
.
Response
.
Usage
!=
nil
{
if
streamResponse
.
Response
!=
nil
{
if
streamResponse
.
Response
.
Usage
.
InputTokens
!=
0
{
if
streamResponse
.
Response
.
Usage
!=
nil
{
usage
.
PromptTokens
=
streamResponse
.
Response
.
Usage
.
InputTokens
if
streamResponse
.
Response
.
Usage
.
InputTokens
!=
0
{
}
usage
.
PromptTokens
=
streamResponse
.
Response
.
Usage
.
InputTokens
if
streamResponse
.
Response
.
Usage
.
OutputTokens
!=
0
{
}
usage
.
CompletionTokens
=
streamResponse
.
Response
.
Usage
.
OutputTokens
if
streamResponse
.
Response
.
Usage
.
OutputTokens
!=
0
{
}
usage
.
CompletionTokens
=
streamResponse
.
Response
.
Usage
.
OutputTokens
if
streamResponse
.
Response
.
Usage
.
TotalTokens
!=
0
{
}
usage
.
TotalTokens
=
streamResponse
.
Response
.
Usage
.
TotalTokens
if
streamResponse
.
Response
.
Usage
.
TotalTokens
!=
0
{
usage
.
TotalTokens
=
streamResponse
.
Response
.
Usage
.
TotalTokens
}
if
streamResponse
.
Response
.
Usage
.
InputTokensDetails
!=
nil
{
usage
.
PromptTokensDetails
.
CachedTokens
=
streamResponse
.
Response
.
Usage
.
InputTokensDetails
.
CachedTokens
}
}
}
if
streamResponse
.
Response
.
Usage
.
InputTokensDetails
!=
nil
{
if
streamResponse
.
Response
.
HasImageGenerationCall
()
{
usage
.
PromptTokensDetails
.
CachedTokens
=
streamResponse
.
Response
.
Usage
.
InputTokensDetails
.
CachedTokens
c
.
Set
(
"image_generation_call"
,
true
)
c
.
Set
(
"image_generation_call_quality"
,
streamResponse
.
Response
.
GetQuality
())
c
.
Set
(
"image_generation_call_size"
,
streamResponse
.
Response
.
GetSize
())
}
}
}
}
case
"response.output_text.delta"
:
case
"response.output_text.delta"
:
...
...
relay/channel/task/jimeng/adaptor.go
View file @
23e5132e
...
@@ -36,6 +36,7 @@ type requestPayload struct {
...
@@ -36,6 +36,7 @@ type requestPayload struct {
Prompt
string
`json:"prompt,omitempty"`
Prompt
string
`json:"prompt,omitempty"`
Seed
int64
`json:"seed"`
Seed
int64
`json:"seed"`
AspectRatio
string
`json:"aspect_ratio"`
AspectRatio
string
`json:"aspect_ratio"`
Frames
int
`json:"frames,omitempty"`
}
}
type
responsePayload
struct
{
type
responsePayload
struct
{
...
@@ -311,10 +312,15 @@ func hmacSHA256(key []byte, data []byte) []byte {
...
@@ -311,10 +312,15 @@ func hmacSHA256(key []byte, data []byte) []byte {
func
(
a
*
TaskAdaptor
)
convertToRequestPayload
(
req
*
relaycommon
.
TaskSubmitReq
)
(
*
requestPayload
,
error
)
{
func
(
a
*
TaskAdaptor
)
convertToRequestPayload
(
req
*
relaycommon
.
TaskSubmitReq
)
(
*
requestPayload
,
error
)
{
r
:=
requestPayload
{
r
:=
requestPayload
{
ReqKey
:
"jimeng_vgfm_i2v_l20"
,
ReqKey
:
req
.
Model
,
Prompt
:
req
.
Prompt
,
Prompt
:
req
.
Prompt
,
AspectRatio
:
"16:9"
,
// Default aspect ratio
}
Seed
:
-
1
,
// Default to random
switch
req
.
Duration
{
case
10
:
r
.
Frames
=
241
// 24*10+1 = 241
default
:
r
.
Frames
=
121
// 24*5+1 = 121
}
}
// Handle one-of image_urls or binary_data_base64
// Handle one-of image_urls or binary_data_base64
...
@@ -334,6 +340,22 @@ func (a *TaskAdaptor) convertToRequestPayload(req *relaycommon.TaskSubmitReq) (*
...
@@ -334,6 +340,22 @@ func (a *TaskAdaptor) convertToRequestPayload(req *relaycommon.TaskSubmitReq) (*
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
errors
.
Wrap
(
err
,
"unmarshal metadata failed"
)
return
nil
,
errors
.
Wrap
(
err
,
"unmarshal metadata failed"
)
}
}
// 即梦视频3.0 ReqKey转换
// https://www.volcengine.com/docs/85621/1792707
if
strings
.
Contains
(
r
.
ReqKey
,
"jimeng_v30"
)
{
if
len
(
r
.
ImageUrls
)
>
1
{
// 多张图片:首尾帧生成
r
.
ReqKey
=
strings
.
Replace
(
r
.
ReqKey
,
"jimeng_v30"
,
"jimeng_i2v_first_tail_v30"
,
1
)
}
else
if
len
(
r
.
ImageUrls
)
==
1
{
// 单张图片:图生视频
r
.
ReqKey
=
strings
.
Replace
(
r
.
ReqKey
,
"jimeng_v30"
,
"jimeng_i2v_first_v30"
,
1
)
}
else
{
// 无图片:文生视频
r
.
ReqKey
=
strings
.
Replace
(
r
.
ReqKey
,
"jimeng_v30"
,
"jimeng_t2v_v30"
,
1
)
}
}
return
&
r
,
nil
return
&
r
,
nil
}
}
...
...
relay/channel/volcengine/adaptor.go
View file @
23e5132e
...
@@ -41,6 +41,8 @@ func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInf
...
@@ -41,6 +41,8 @@ func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInf
func
(
a
*
Adaptor
)
ConvertImageRequest
(
c
*
gin
.
Context
,
info
*
relaycommon
.
RelayInfo
,
request
dto
.
ImageRequest
)
(
any
,
error
)
{
func
(
a
*
Adaptor
)
ConvertImageRequest
(
c
*
gin
.
Context
,
info
*
relaycommon
.
RelayInfo
,
request
dto
.
ImageRequest
)
(
any
,
error
)
{
switch
info
.
RelayMode
{
switch
info
.
RelayMode
{
case
constant
.
RelayModeImagesGenerations
:
return
request
,
nil
case
constant
.
RelayModeImagesEdits
:
case
constant
.
RelayModeImagesEdits
:
var
requestBody
bytes
.
Buffer
var
requestBody
bytes
.
Buffer
...
...
relay/channel/volcengine/constants.go
View file @
23e5132e
...
@@ -8,6 +8,7 @@ var ModelList = []string{
...
@@ -8,6 +8,7 @@ var ModelList = []string{
"Doubao-lite-32k"
,
"Doubao-lite-32k"
,
"Doubao-lite-4k"
,
"Doubao-lite-4k"
,
"Doubao-embedding"
,
"Doubao-embedding"
,
"doubao-seedream-4-0-250828"
,
}
}
var
ChannelName
=
"volcengine"
var
ChannelName
=
"volcengine"
relay/compatible_handler.go
View file @
23e5132e
...
@@ -276,6 +276,13 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage
...
@@ -276,6 +276,13 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage
fileSearchTool
.
CallCount
,
dFileSearchQuota
.
String
())
fileSearchTool
.
CallCount
,
dFileSearchQuota
.
String
())
}
}
}
}
var
dImageGenerationCallQuota
decimal
.
Decimal
var
imageGenerationCallPrice
float64
if
ctx
.
GetBool
(
"image_generation_call"
)
{
imageGenerationCallPrice
=
operation_setting
.
GetGPTImage1PriceOnceCall
(
ctx
.
GetString
(
"image_generation_call_quality"
),
ctx
.
GetString
(
"image_generation_call_size"
))
dImageGenerationCallQuota
=
decimal
.
NewFromFloat
(
imageGenerationCallPrice
)
.
Mul
(
dGroupRatio
)
.
Mul
(
dQuotaPerUnit
)
extraContent
+=
fmt
.
Sprintf
(
"Image Generation Call 花费 %s"
,
dImageGenerationCallQuota
.
String
())
}
var
quotaCalculateDecimal
decimal
.
Decimal
var
quotaCalculateDecimal
decimal
.
Decimal
...
@@ -331,6 +338,8 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage
...
@@ -331,6 +338,8 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage
quotaCalculateDecimal
=
quotaCalculateDecimal
.
Add
(
dFileSearchQuota
)
quotaCalculateDecimal
=
quotaCalculateDecimal
.
Add
(
dFileSearchQuota
)
// 添加 audio input 独立计费
// 添加 audio input 独立计费
quotaCalculateDecimal
=
quotaCalculateDecimal
.
Add
(
audioInputQuota
)
quotaCalculateDecimal
=
quotaCalculateDecimal
.
Add
(
audioInputQuota
)
// 添加 image generation call 计费
quotaCalculateDecimal
=
quotaCalculateDecimal
.
Add
(
dImageGenerationCallQuota
)
quota
:=
int
(
quotaCalculateDecimal
.
Round
(
0
)
.
IntPart
())
quota
:=
int
(
quotaCalculateDecimal
.
Round
(
0
)
.
IntPart
())
totalTokens
:=
promptTokens
+
completionTokens
totalTokens
:=
promptTokens
+
completionTokens
...
@@ -429,6 +438,10 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage
...
@@ -429,6 +438,10 @@ func postConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage
other
[
"audio_input_token_count"
]
=
audioTokens
other
[
"audio_input_token_count"
]
=
audioTokens
other
[
"audio_input_price"
]
=
audioInputPrice
other
[
"audio_input_price"
]
=
audioInputPrice
}
}
if
!
dImageGenerationCallQuota
.
IsZero
()
{
other
[
"image_generation_call"
]
=
true
other
[
"image_generation_call_price"
]
=
imageGenerationCallPrice
}
model
.
RecordConsumeLog
(
ctx
,
relayInfo
.
UserId
,
model
.
RecordConsumeLogParams
{
model
.
RecordConsumeLog
(
ctx
,
relayInfo
.
UserId
,
model
.
RecordConsumeLogParams
{
ChannelId
:
relayInfo
.
ChannelId
,
ChannelId
:
relayInfo
.
ChannelId
,
PromptTokens
:
promptTokens
,
PromptTokens
:
promptTokens
,
...
...
setting/operation_setting/tools.go
View file @
23e5132e
...
@@ -11,6 +11,18 @@ const (
...
@@ -11,6 +11,18 @@ const (
)
)
const
(
const
(
GPTImage1Low1024x1024
=
0.011
GPTImage1Low1024x1536
=
0.016
GPTImage1Low1536x1024
=
0.016
GPTImage1Medium1024x1024
=
0.042
GPTImage1Medium1024x1536
=
0.063
GPTImage1Medium1536x1024
=
0.063
GPTImage1High1024x1024
=
0.167
GPTImage1High1024x1536
=
0.25
GPTImage1High1536x1024
=
0.25
)
const
(
// Gemini Audio Input Price
// Gemini Audio Input Price
Gemini25FlashPreviewInputAudioPrice
=
1.00
Gemini25FlashPreviewInputAudioPrice
=
1.00
Gemini25FlashProductionInputAudioPrice
=
1.00
// for `gemini-2.5-flash`
Gemini25FlashProductionInputAudioPrice
=
1.00
// for `gemini-2.5-flash`
...
@@ -65,3 +77,31 @@ func GetGeminiInputAudioPricePerMillionTokens(modelName string) float64 {
...
@@ -65,3 +77,31 @@ func GetGeminiInputAudioPricePerMillionTokens(modelName string) float64 {
}
}
return
0
return
0
}
}
func
GetGPTImage1PriceOnceCall
(
quality
string
,
size
string
)
float64
{
prices
:=
map
[
string
]
map
[
string
]
float64
{
"low"
:
{
"1024x1024"
:
GPTImage1Low1024x1024
,
"1024x1536"
:
GPTImage1Low1024x1536
,
"1536x1024"
:
GPTImage1Low1536x1024
,
},
"medium"
:
{
"1024x1024"
:
GPTImage1Medium1024x1024
,
"1024x1536"
:
GPTImage1Medium1024x1536
,
"1536x1024"
:
GPTImage1Medium1536x1024
,
},
"high"
:
{
"1024x1024"
:
GPTImage1High1024x1024
,
"1024x1536"
:
GPTImage1High1024x1536
,
"1536x1024"
:
GPTImage1High1536x1024
,
},
}
if
qualityMap
,
exists
:=
prices
[
quality
];
exists
{
if
price
,
exists
:=
qualityMap
[
size
];
exists
{
return
price
}
}
return
GPTImage1High1024x1024
}
web/src/helpers/render.jsx
View file @
23e5132e
...
@@ -1027,6 +1027,8 @@ export function renderModelPrice(
...
@@ -1027,6 +1027,8 @@ export function renderModelPrice(
audioInputSeperatePrice
=
false
,
audioInputSeperatePrice
=
false
,
audioInputTokens
=
0
,
audioInputTokens
=
0
,
audioInputPrice
=
0
,
audioInputPrice
=
0
,
imageGenerationCall
=
false
,
imageGenerationCallPrice
=
0
,
)
{
)
{
const
{
ratio
:
effectiveGroupRatio
,
label
:
ratioLabel
}
=
getEffectiveRatio
(
const
{
ratio
:
effectiveGroupRatio
,
label
:
ratioLabel
}
=
getEffectiveRatio
(
groupRatio
,
groupRatio
,
...
@@ -1069,7 +1071,8 @@ export function renderModelPrice(
...
@@ -1069,7 +1071,8 @@ export function renderModelPrice(
(
audioInputTokens
/
1000000
)
*
audioInputPrice
*
groupRatio
+
(
audioInputTokens
/
1000000
)
*
audioInputPrice
*
groupRatio
+
(
completionTokens
/
1000000
)
*
completionRatioPrice
*
groupRatio
+
(
completionTokens
/
1000000
)
*
completionRatioPrice
*
groupRatio
+
(
webSearchCallCount
/
1000
)
*
webSearchPrice
*
groupRatio
+
(
webSearchCallCount
/
1000
)
*
webSearchPrice
*
groupRatio
+
(
fileSearchCallCount
/
1000
)
*
fileSearchPrice
*
groupRatio
;
(
fileSearchCallCount
/
1000
)
*
fileSearchPrice
*
groupRatio
+
(
imageGenerationCallPrice
*
groupRatio
);
return
(
return
(
<>
<>
...
@@ -1131,7 +1134,13 @@ export function renderModelPrice(
...
@@ -1131,7 +1134,13 @@ export function renderModelPrice(
})
}
})
}
</
p
>
</
p
>
)
}
)
}
<
p
></
p
>
{
imageGenerationCall
&&
imageGenerationCallPrice
>
0
&&
(
<
p
>
{
i18next
.
t
(
'图片生成调用:
$
{{
price
}}
/ 1次', {
price
:
imageGenerationCallPrice
,
})
}
</
p
>
)
}
<
p
>
<
p
>
{
(()
=>
{
{
(()
=>
{
// 构建输入部分描述
// 构建输入部分描述
...
@@ -1211,6 +1220,16 @@ export function renderModelPrice(
...
@@ -1211,6 +1220,16 @@ export function renderModelPrice(
},
},
)
)
:
''
,
:
''
,
imageGenerationCall
&&
imageGenerationCallPrice
>
0
?
i18next
.
t
(
'
+
图片生成调用
$
{{
price
}}
/ 1次 * {{ratioType}} {{ratio}}'
,
{
price
:
imageGenerationCallPrice
,
ratio
:
groupRatio
,
ratioType
:
ratioLabel
,
},
)
:
''
,
].
join
(
''
);
].
join
(
''
);
return
i18next
.
t
(
return
i18next
.
t
(
...
...
web/src/hooks/usage-logs/useUsageLogsData.jsx
View file @
23e5132e
...
@@ -447,6 +447,8 @@ export const useLogsData = () => {
...
@@ -447,6 +447,8 @@ export const useLogsData = () => {
other
?.
audio_input_seperate_price
||
false
,
other
?.
audio_input_seperate_price
||
false
,
other
?.
audio_input_token_count
||
0
,
other
?.
audio_input_token_count
||
0
,
other
?.
audio_input_price
||
0
,
other
?.
audio_input_price
||
0
,
other
?.
image_generation_call
||
false
,
other
?.
image_generation_call_price
||
0
,
);
);
}
}
expandDataLocal
.
push
({
expandDataLocal
.
push
({
...
...
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