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
0c0b69a3
authored
Feb 06, 2026
by
Seefs
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: channel test stream
parent
cb34e239
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
214 additions
and
40 deletions
+214
-40
controller/channel-test.go
+129
-17
web/src/components/table/channels/modals/ModelTestModal.jsx
+60
-21
web/src/hooks/channels/useChannelsData.jsx
+19
-2
web/src/i18n/locales/en.json
+1
-0
web/src/i18n/locales/fr.json
+1
-0
web/src/i18n/locales/ja.json
+1
-0
web/src/i18n/locales/ru.json
+1
-0
web/src/i18n/locales/vi.json
+1
-0
web/src/i18n/locales/zh.json
+1
-0
No files found.
controller/channel-test.go
View file @
0c0b69a3
...
@@ -31,6 +31,7 @@ import (
...
@@ -31,6 +31,7 @@ import (
"github.com/bytedance/gopkg/util/gopool"
"github.com/bytedance/gopkg/util/gopool"
"github.com/samber/lo"
"github.com/samber/lo"
"github.com/tidwall/gjson"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin"
)
)
...
@@ -41,7 +42,7 @@ type testResult struct {
...
@@ -41,7 +42,7 @@ type testResult struct {
newAPIError
*
types
.
NewAPIError
newAPIError
*
types
.
NewAPIError
}
}
func
testChannel
(
channel
*
model
.
Channel
,
testModel
string
,
endpointType
string
)
testResult
{
func
testChannel
(
channel
*
model
.
Channel
,
testModel
string
,
endpointType
string
,
isStream
bool
)
testResult
{
tik
:=
time
.
Now
()
tik
:=
time
.
Now
()
var
unsupportedTestChannelTypes
=
[]
int
{
var
unsupportedTestChannelTypes
=
[]
int
{
constant
.
ChannelTypeMidjourney
,
constant
.
ChannelTypeMidjourney
,
...
@@ -200,7 +201,7 @@ func testChannel(channel *model.Channel, testModel string, endpointType string)
...
@@ -200,7 +201,7 @@ func testChannel(channel *model.Channel, testModel string, endpointType string)
}
}
}
}
request
:=
buildTestRequest
(
testModel
,
endpointType
,
channel
)
request
:=
buildTestRequest
(
testModel
,
endpointType
,
channel
,
isStream
)
info
,
err
:=
relaycommon
.
GenRelayInfo
(
c
,
relayFormat
,
request
,
nil
)
info
,
err
:=
relaycommon
.
GenRelayInfo
(
c
,
relayFormat
,
request
,
nil
)
...
@@ -418,16 +419,16 @@ func testChannel(channel *model.Channel, testModel string, endpointType string)
...
@@ -418,16 +419,16 @@ func testChannel(channel *model.Channel, testModel string, endpointType string)
newAPIError
:
respErr
,
newAPIError
:
respErr
,
}
}
}
}
if
usageA
==
nil
{
usage
,
usageErr
:=
coerceTestUsage
(
usageA
,
isStream
,
info
.
GetEstimatePromptTokens
())
if
usageErr
!=
nil
{
return
testResult
{
return
testResult
{
context
:
c
,
context
:
c
,
localErr
:
errors
.
New
(
"usage is nil"
)
,
localErr
:
usageErr
,
newAPIError
:
types
.
NewOpenAIError
(
errors
.
New
(
"usage is nil"
)
,
types
.
ErrorCodeBadResponseBody
,
http
.
StatusInternalServerError
),
newAPIError
:
types
.
NewOpenAIError
(
usageErr
,
types
.
ErrorCodeBadResponseBody
,
http
.
StatusInternalServerError
),
}
}
}
}
usage
:=
usageA
.
(
*
dto
.
Usage
)
result
:=
w
.
Result
()
result
:=
w
.
Result
()
respBody
,
err
:=
io
.
ReadAll
(
result
.
Body
)
respBody
,
err
:=
readTestResponseBody
(
result
.
Body
,
isStream
)
if
err
!=
nil
{
if
err
!=
nil
{
return
testResult
{
return
testResult
{
context
:
c
,
context
:
c
,
...
@@ -435,6 +436,13 @@ func testChannel(channel *model.Channel, testModel string, endpointType string)
...
@@ -435,6 +436,13 @@ func testChannel(channel *model.Channel, testModel string, endpointType string)
newAPIError
:
types
.
NewOpenAIError
(
err
,
types
.
ErrorCodeReadResponseBodyFailed
,
http
.
StatusInternalServerError
),
newAPIError
:
types
.
NewOpenAIError
(
err
,
types
.
ErrorCodeReadResponseBodyFailed
,
http
.
StatusInternalServerError
),
}
}
}
}
if
bodyErr
:=
detectErrorFromTestResponseBody
(
respBody
);
bodyErr
!=
nil
{
return
testResult
{
context
:
c
,
localErr
:
bodyErr
,
newAPIError
:
types
.
NewOpenAIError
(
bodyErr
,
types
.
ErrorCodeBadResponseBody
,
http
.
StatusInternalServerError
),
}
}
info
.
SetEstimatePromptTokens
(
usage
.
PromptTokens
)
info
.
SetEstimatePromptTokens
(
usage
.
PromptTokens
)
quota
:=
0
quota
:=
0
...
@@ -473,7 +481,101 @@ func testChannel(channel *model.Channel, testModel string, endpointType string)
...
@@ -473,7 +481,101 @@ func testChannel(channel *model.Channel, testModel string, endpointType string)
}
}
}
}
func
buildTestRequest
(
model
string
,
endpointType
string
,
channel
*
model
.
Channel
)
dto
.
Request
{
func
coerceTestUsage
(
usageAny
any
,
isStream
bool
,
estimatePromptTokens
int
)
(
*
dto
.
Usage
,
error
)
{
switch
u
:=
usageAny
.
(
type
)
{
case
*
dto
.
Usage
:
return
u
,
nil
case
dto
.
Usage
:
return
&
u
,
nil
case
nil
:
if
!
isStream
{
return
nil
,
errors
.
New
(
"usage is nil"
)
}
usage
:=
&
dto
.
Usage
{
PromptTokens
:
estimatePromptTokens
,
}
usage
.
TotalTokens
=
usage
.
PromptTokens
return
usage
,
nil
default
:
if
!
isStream
{
return
nil
,
fmt
.
Errorf
(
"invalid usage type: %T"
,
usageAny
)
}
usage
:=
&
dto
.
Usage
{
PromptTokens
:
estimatePromptTokens
,
}
usage
.
TotalTokens
=
usage
.
PromptTokens
return
usage
,
nil
}
}
func
readTestResponseBody
(
body
io
.
ReadCloser
,
isStream
bool
)
([]
byte
,
error
)
{
defer
func
()
{
_
=
body
.
Close
()
}()
const
maxStreamLogBytes
=
8
<<
10
if
isStream
{
return
io
.
ReadAll
(
io
.
LimitReader
(
body
,
maxStreamLogBytes
))
}
return
io
.
ReadAll
(
body
)
}
func
detectErrorFromTestResponseBody
(
respBody
[]
byte
)
error
{
b
:=
bytes
.
TrimSpace
(
respBody
)
if
len
(
b
)
==
0
{
return
nil
}
if
message
:=
detectErrorMessageFromJSONBytes
(
b
);
message
!=
""
{
return
fmt
.
Errorf
(
"upstream error: %s"
,
message
)
}
for
_
,
line
:=
range
bytes
.
Split
(
b
,
[]
byte
{
'\n'
})
{
line
=
bytes
.
TrimSpace
(
line
)
if
len
(
line
)
==
0
{
continue
}
if
!
bytes
.
HasPrefix
(
line
,
[]
byte
(
"data:"
))
{
continue
}
payload
:=
bytes
.
TrimSpace
(
bytes
.
TrimPrefix
(
line
,
[]
byte
(
"data:"
)))
if
len
(
payload
)
==
0
||
bytes
.
Equal
(
payload
,
[]
byte
(
"[DONE]"
))
{
continue
}
if
message
:=
detectErrorMessageFromJSONBytes
(
payload
);
message
!=
""
{
return
fmt
.
Errorf
(
"upstream error: %s"
,
message
)
}
}
return
nil
}
func
detectErrorMessageFromJSONBytes
(
jsonBytes
[]
byte
)
string
{
if
len
(
jsonBytes
)
==
0
{
return
""
}
if
jsonBytes
[
0
]
!=
'{'
&&
jsonBytes
[
0
]
!=
'['
{
return
""
}
errVal
:=
gjson
.
GetBytes
(
jsonBytes
,
"error"
)
if
!
errVal
.
Exists
()
||
errVal
.
Type
==
gjson
.
Null
{
return
""
}
message
:=
gjson
.
GetBytes
(
jsonBytes
,
"error.message"
)
.
String
()
if
message
==
""
{
message
=
gjson
.
GetBytes
(
jsonBytes
,
"error.error.message"
)
.
String
()
}
if
message
==
""
&&
errVal
.
Type
==
gjson
.
String
{
message
=
errVal
.
String
()
}
if
message
==
""
{
message
=
errVal
.
Raw
}
message
=
strings
.
TrimSpace
(
message
)
if
message
==
""
{
return
"upstream returned error payload"
}
return
message
}
func
buildTestRequest
(
model
string
,
endpointType
string
,
channel
*
model
.
Channel
,
isStream
bool
)
dto
.
Request
{
testResponsesInput
:=
json
.
RawMessage
(
`[{"role":"user","content":"hi"}]`
)
testResponsesInput
:=
json
.
RawMessage
(
`[{"role":"user","content":"hi"}]`
)
// 根据端点类型构建不同的测试请求
// 根据端点类型构建不同的测试请求
...
@@ -504,8 +606,9 @@ func buildTestRequest(model string, endpointType string, channel *model.Channel)
...
@@ -504,8 +606,9 @@ func buildTestRequest(model string, endpointType string, channel *model.Channel)
case
constant
.
EndpointTypeOpenAIResponse
:
case
constant
.
EndpointTypeOpenAIResponse
:
// 返回 OpenAIResponsesRequest
// 返回 OpenAIResponsesRequest
return
&
dto
.
OpenAIResponsesRequest
{
return
&
dto
.
OpenAIResponsesRequest
{
Model
:
model
,
Model
:
model
,
Input
:
json
.
RawMessage
(
`[{"role":"user","content":"hi"}]`
),
Input
:
json
.
RawMessage
(
`[{"role":"user","content":"hi"}]`
),
Stream
:
isStream
,
}
}
case
constant
.
EndpointTypeOpenAIResponseCompact
:
case
constant
.
EndpointTypeOpenAIResponseCompact
:
// 返回 OpenAIResponsesCompactionRequest
// 返回 OpenAIResponsesCompactionRequest
...
@@ -519,9 +622,9 @@ func buildTestRequest(model string, endpointType string, channel *model.Channel)
...
@@ -519,9 +622,9 @@ func buildTestRequest(model string, endpointType string, channel *model.Channel)
if
constant
.
EndpointType
(
endpointType
)
==
constant
.
EndpointTypeGemini
{
if
constant
.
EndpointType
(
endpointType
)
==
constant
.
EndpointTypeGemini
{
maxTokens
=
3000
maxTokens
=
3000
}
}
re
turn
&
dto
.
GeneralOpenAIRequest
{
re
q
:=
&
dto
.
GeneralOpenAIRequest
{
Model
:
model
,
Model
:
model
,
Stream
:
false
,
Stream
:
isStream
,
Messages
:
[]
dto
.
Message
{
Messages
:
[]
dto
.
Message
{
{
{
Role
:
"user"
,
Role
:
"user"
,
...
@@ -530,6 +633,10 @@ func buildTestRequest(model string, endpointType string, channel *model.Channel)
...
@@ -530,6 +633,10 @@ func buildTestRequest(model string, endpointType string, channel *model.Channel)
},
},
MaxTokens
:
maxTokens
,
MaxTokens
:
maxTokens
,
}
}
if
isStream
{
req
.
StreamOptions
=
&
dto
.
StreamOptions
{
IncludeUsage
:
true
}
}
return
req
}
}
}
}
...
@@ -565,15 +672,16 @@ func buildTestRequest(model string, endpointType string, channel *model.Channel)
...
@@ -565,15 +672,16 @@ func buildTestRequest(model string, endpointType string, channel *model.Channel)
// Responses-only models (e.g. codex series)
// Responses-only models (e.g. codex series)
if
strings
.
Contains
(
strings
.
ToLower
(
model
),
"codex"
)
{
if
strings
.
Contains
(
strings
.
ToLower
(
model
),
"codex"
)
{
return
&
dto
.
OpenAIResponsesRequest
{
return
&
dto
.
OpenAIResponsesRequest
{
Model
:
model
,
Model
:
model
,
Input
:
json
.
RawMessage
(
`[{"role":"user","content":"hi"}]`
),
Input
:
json
.
RawMessage
(
`[{"role":"user","content":"hi"}]`
),
Stream
:
isStream
,
}
}
}
}
// Chat/Completion 请求 - 返回 GeneralOpenAIRequest
// Chat/Completion 请求 - 返回 GeneralOpenAIRequest
testRequest
:=
&
dto
.
GeneralOpenAIRequest
{
testRequest
:=
&
dto
.
GeneralOpenAIRequest
{
Model
:
model
,
Model
:
model
,
Stream
:
false
,
Stream
:
isStream
,
Messages
:
[]
dto
.
Message
{
Messages
:
[]
dto
.
Message
{
{
{
Role
:
"user"
,
Role
:
"user"
,
...
@@ -581,6 +689,9 @@ func buildTestRequest(model string, endpointType string, channel *model.Channel)
...
@@ -581,6 +689,9 @@ func buildTestRequest(model string, endpointType string, channel *model.Channel)
},
},
},
},
}
}
if
isStream
{
testRequest
.
StreamOptions
=
&
dto
.
StreamOptions
{
IncludeUsage
:
true
}
}
if
strings
.
HasPrefix
(
model
,
"o"
)
{
if
strings
.
HasPrefix
(
model
,
"o"
)
{
testRequest
.
MaxCompletionTokens
=
16
testRequest
.
MaxCompletionTokens
=
16
...
@@ -618,8 +729,9 @@ func TestChannel(c *gin.Context) {
...
@@ -618,8 +729,9 @@ func TestChannel(c *gin.Context) {
//}()
//}()
testModel
:=
c
.
Query
(
"model"
)
testModel
:=
c
.
Query
(
"model"
)
endpointType
:=
c
.
Query
(
"endpoint_type"
)
endpointType
:=
c
.
Query
(
"endpoint_type"
)
isStream
,
_
:=
strconv
.
ParseBool
(
c
.
Query
(
"stream"
))
tik
:=
time
.
Now
()
tik
:=
time
.
Now
()
result
:=
testChannel
(
channel
,
testModel
,
endpointType
)
result
:=
testChannel
(
channel
,
testModel
,
endpointType
,
isStream
)
if
result
.
localErr
!=
nil
{
if
result
.
localErr
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"success"
:
false
,
...
@@ -678,7 +790,7 @@ func testAllChannels(notify bool) error {
...
@@ -678,7 +790,7 @@ func testAllChannels(notify bool) error {
for
_
,
channel
:=
range
channels
{
for
_
,
channel
:=
range
channels
{
isChannelEnabled
:=
channel
.
Status
==
common
.
ChannelStatusEnabled
isChannelEnabled
:=
channel
.
Status
==
common
.
ChannelStatusEnabled
tik
:=
time
.
Now
()
tik
:=
time
.
Now
()
result
:=
testChannel
(
channel
,
""
,
""
)
result
:=
testChannel
(
channel
,
""
,
""
,
false
)
tok
:=
time
.
Now
()
tok
:=
time
.
Now
()
milliseconds
:=
tok
.
Sub
(
tik
)
.
Milliseconds
()
milliseconds
:=
tok
.
Sub
(
tik
)
.
Milliseconds
()
...
...
web/src/components/table/channels/modals/ModelTestModal.jsx
View file @
0c0b69a3
...
@@ -26,8 +26,10 @@ import {
...
@@ -26,8 +26,10 @@ import {
Tag
,
Tag
,
Typography
,
Typography
,
Select
,
Select
,
Switch
,
Banner
,
}
from
'@douyinfe/semi-ui'
;
}
from
'@douyinfe/semi-ui'
;
import
{
IconSearch
}
from
'@douyinfe/semi-icons'
;
import
{
IconSearch
,
IconInfoCircle
}
from
'@douyinfe/semi-icons'
;
import
{
copy
,
showError
,
showInfo
,
showSuccess
}
from
'../../../../helpers'
;
import
{
copy
,
showError
,
showInfo
,
showSuccess
}
from
'../../../../helpers'
;
import
{
MODEL_TABLE_PAGE_SIZE
}
from
'../../../../constants'
;
import
{
MODEL_TABLE_PAGE_SIZE
}
from
'../../../../constants'
;
...
@@ -48,11 +50,25 @@ const ModelTestModal = ({
...
@@ -48,11 +50,25 @@ const ModelTestModal = ({
setModelTablePage
,
setModelTablePage
,
selectedEndpointType
,
selectedEndpointType
,
setSelectedEndpointType
,
setSelectedEndpointType
,
isStreamTest
,
setIsStreamTest
,
allSelectingRef
,
allSelectingRef
,
isMobile
,
isMobile
,
t
,
t
,
})
=>
{
})
=>
{
const
hasChannel
=
Boolean
(
currentTestChannel
);
const
hasChannel
=
Boolean
(
currentTestChannel
);
const
streamToggleDisabled
=
[
'embeddings'
,
'image-generation'
,
'jina-rerank'
,
'openai-response-compact'
,
].
includes
(
selectedEndpointType
);
React
.
useEffect
(()
=>
{
if
(
streamToggleDisabled
&&
isStreamTest
)
{
setIsStreamTest
(
false
);
}
},
[
streamToggleDisabled
,
isStreamTest
,
setIsStreamTest
]);
const
filteredModels
=
hasChannel
const
filteredModels
=
hasChannel
?
currentTestChannel
.
models
?
currentTestChannel
.
models
...
@@ -181,6 +197,7 @@ const ModelTestModal = ({
...
@@ -181,6 +197,7 @@ const ModelTestModal = ({
currentTestChannel
,
currentTestChannel
,
record
.
model
,
record
.
model
,
selectedEndpointType
,
selectedEndpointType
,
isStreamTest
,
)
)
}
}
loading=
{
isTesting
}
loading=
{
isTesting
}
...
@@ -258,25 +275,46 @@ const ModelTestModal = ({
...
@@ -258,25 +275,46 @@ const ModelTestModal = ({
>
>
{
hasChannel
&&
(
{
hasChannel
&&
(
<
div
className=
'model-test-scroll'
>
<
div
className=
'model-test-scroll'
>
{
/* 端点类型选择器 */
}
{
/* Endpoint toolbar */
}
<
div
className=
'flex items-center gap-2 w-full mb-2'
>
<
div
className=
'flex flex-col sm:flex-row sm:items-center gap-2 w-full mb-2'
>
<
Typography
.
Text
strong
>
{
t
(
'端点类型'
)
}
:
</
Typography
.
Text
>
<
div
className=
'flex items-center gap-2 flex-1 min-w-0'
>
<
Select
<
Typography
.
Text
strong
className=
'shrink-0'
>
value=
{
selectedEndpointType
}
{
t
(
'端点类型'
)
}
:
onChange=
{
setSelectedEndpointType
}
</
Typography
.
Text
>
optionList=
{
endpointTypeOptions
}
<
Select
className=
'!w-full'
value=
{
selectedEndpointType
}
placeholder=
{
t
(
'选择端点类型'
)
}
onChange=
{
setSelectedEndpointType
}
/>
optionList=
{
endpointTypeOptions
}
className=
'!w-full min-w-0'
placeholder=
{
t
(
'选择端点类型'
)
}
/>
</
div
>
<
div
className=
'flex items-center justify-between sm:justify-end gap-2 shrink-0'
>
<
Typography
.
Text
strong
className=
'shrink-0'
>
{
t
(
'流式'
)
}
:
</
Typography
.
Text
>
<
Switch
checked=
{
isStreamTest
}
onChange=
{
setIsStreamTest
}
size=
'small'
disabled=
{
streamToggleDisabled
}
aria
-
label=
{
t
(
'流式'
)
}
/>
</
div
>
</
div
>
</
div
>
<
Typography
.
Text
type=
'tertiary'
size=
'small'
className=
'block mb-2'
>
{
t
(
<
Banner
type=
'info'
closeIcon=
{
null
}
icon=
{
<
IconInfoCircle
/>
}
className=
'!rounded-lg mb-2'
description=
{
t
(
'说明:本页测试为非流式请求;若渠道仅支持流式返回,可能出现测试失败,请以实际使用为准。'
,
'说明:本页测试为非流式请求;若渠道仅支持流式返回,可能出现测试失败,请以实际使用为准。'
,
)
}
)
}
</
Typography
.
Text
>
/
>
{
/* 搜索与操作按钮 */
}
{
/* 搜索与操作按钮 */
}
<
div
className=
'flex
items-center justify-end
gap-2 w-full mb-2'
>
<
div
className=
'flex
flex-col sm:flex-row sm:items-center
gap-2 w-full mb-2'
>
<
Input
<
Input
placeholder=
{
t
(
'搜索模型...'
)
}
placeholder=
{
t
(
'搜索模型...'
)
}
value=
{
modelSearchKeyword
}
value=
{
modelSearchKeyword
}
...
@@ -284,16 +322,17 @@ const ModelTestModal = ({
...
@@ -284,16 +322,17 @@ const ModelTestModal = ({
setModelSearchKeyword
(
v
);
setModelSearchKeyword
(
v
);
setModelTablePage
(
1
);
setModelTablePage
(
1
);
}
}
}
}
className=
'!w-full'
className=
'!w-full
sm:!flex-1
'
prefix=
{
<
IconSearch
/>
}
prefix=
{
<
IconSearch
/>
}
showClear
showClear
/>
/>
<
Button
onClick=
{
handleCopySelected
}
>
{
t
(
'复制已选'
)
}
</
Button
>
<
div
className=
'flex items-center justify-end gap-2'
>
<
Button
onClick=
{
handleCopySelected
}
>
{
t
(
'复制已选'
)
}
</
Button
>
<
Button
type=
'tertiary'
onClick=
{
handleSelectSuccess
}
>
<
Button
type=
'tertiary'
onClick=
{
handleSelectSuccess
}
>
{
t
(
'选择成功'
)
}
{
t
(
'选择成功'
)
}
</
Button
>
</
Button
>
</
div
>
</
div
>
</
div
>
<
Table
<
Table
...
...
web/src/hooks/channels/useChannelsData.jsx
View file @
0c0b69a3
...
@@ -87,6 +87,7 @@ export const useChannelsData = () => {
...
@@ -87,6 +87,7 @@ export const useChannelsData = () => {
const
[
isBatchTesting
,
setIsBatchTesting
]
=
useState
(
false
);
const
[
isBatchTesting
,
setIsBatchTesting
]
=
useState
(
false
);
const
[
modelTablePage
,
setModelTablePage
]
=
useState
(
1
);
const
[
modelTablePage
,
setModelTablePage
]
=
useState
(
1
);
const
[
selectedEndpointType
,
setSelectedEndpointType
]
=
useState
(
''
);
const
[
selectedEndpointType
,
setSelectedEndpointType
]
=
useState
(
''
);
const
[
isStreamTest
,
setIsStreamTest
]
=
useState
(
false
);
const
[
globalPassThroughEnabled
,
setGlobalPassThroughEnabled
]
=
const
[
globalPassThroughEnabled
,
setGlobalPassThroughEnabled
]
=
useState
(
false
);
useState
(
false
);
...
@@ -851,7 +852,12 @@ export const useChannelsData = () => {
...
@@ -851,7 +852,12 @@ export const useChannelsData = () => {
};
};
// Test channel - 单个模型测试,参考旧版实现
// Test channel - 单个模型测试,参考旧版实现
const
testChannel
=
async
(
record
,
model
,
endpointType
=
''
)
=>
{
const
testChannel
=
async
(
record
,
model
,
endpointType
=
''
,
stream
=
false
,
)
=>
{
const
testKey
=
`
${
record
.
id
}
-
${
model
}
`
;
const
testKey
=
`
${
record
.
id
}
-
${
model
}
`
;
// 检查是否应该停止批量测试
// 检查是否应该停止批量测试
...
@@ -867,6 +873,9 @@ export const useChannelsData = () => {
...
@@ -867,6 +873,9 @@ export const useChannelsData = () => {
if
(
endpointType
)
{
if
(
endpointType
)
{
url
+=
`&endpoint_type=
${
endpointType
}
`
;
url
+=
`&endpoint_type=
${
endpointType
}
`
;
}
}
if
(
stream
)
{
url
+=
`&stream=true`
;
}
const
res
=
await
API
.
get
(
url
);
const
res
=
await
API
.
get
(
url
);
// 检查是否在请求期间被停止
// 检查是否在请求期间被停止
...
@@ -995,7 +1004,12 @@ export const useChannelsData = () => {
...
@@ -995,7 +1004,12 @@ export const useChannelsData = () => {
);
);
const
batchPromises
=
batch
.
map
((
model
)
=>
const
batchPromises
=
batch
.
map
((
model
)
=>
testChannel
(
currentTestChannel
,
model
,
selectedEndpointType
),
testChannel
(
currentTestChannel
,
model
,
selectedEndpointType
,
isStreamTest
,
),
);
);
const
batchResults
=
await
Promise
.
allSettled
(
batchPromises
);
const
batchResults
=
await
Promise
.
allSettled
(
batchPromises
);
results
.
push
(...
batchResults
);
results
.
push
(...
batchResults
);
...
@@ -1080,6 +1094,7 @@ export const useChannelsData = () => {
...
@@ -1080,6 +1094,7 @@ export const useChannelsData = () => {
setSelectedModelKeys
([]);
setSelectedModelKeys
([]);
setModelTablePage
(
1
);
setModelTablePage
(
1
);
setSelectedEndpointType
(
''
);
setSelectedEndpointType
(
''
);
setIsStreamTest
(
false
);
// 可选择性保留测试结果,这里不清空以便用户查看
// 可选择性保留测试结果,这里不清空以便用户查看
};
};
...
@@ -1170,6 +1185,8 @@ export const useChannelsData = () => {
...
@@ -1170,6 +1185,8 @@ export const useChannelsData = () => {
setModelTablePage
,
setModelTablePage
,
selectedEndpointType
,
selectedEndpointType
,
setSelectedEndpointType
,
setSelectedEndpointType
,
isStreamTest
,
setIsStreamTest
,
allSelectingRef
,
allSelectingRef
,
// Multi-key management states
// Multi-key management states
...
...
web/src/i18n/locales/en.json
View file @
0c0b69a3
...
@@ -1547,6 +1547,7 @@
...
@@ -1547,6 +1547,7 @@
"流"
:
"stream"
,
"流"
:
"stream"
,
"流式响应完成"
:
"Streaming response completed"
,
"流式响应完成"
:
"Streaming response completed"
,
"流式输出"
:
"Streaming Output"
,
"流式输出"
:
"Streaming Output"
,
"流式"
:
"Streaming"
,
"流量端口"
:
"Traffic Port"
,
"流量端口"
:
"Traffic Port"
,
"浅色"
:
"Light"
,
"浅色"
:
"Light"
,
"浅色模式"
:
"Light Mode"
,
"浅色模式"
:
"Light Mode"
,
...
...
web/src/i18n/locales/fr.json
View file @
0c0b69a3
...
@@ -1557,6 +1557,7 @@
...
@@ -1557,6 +1557,7 @@
"流"
:
"Flux"
,
"流"
:
"Flux"
,
"流式响应完成"
:
"Flux terminé"
,
"流式响应完成"
:
"Flux terminé"
,
"流式输出"
:
"Sortie en flux"
,
"流式输出"
:
"Sortie en flux"
,
"流式"
:
"Streaming"
,
"流量端口"
:
"Traffic Port"
,
"流量端口"
:
"Traffic Port"
,
"浅色"
:
"Clair"
,
"浅色"
:
"Clair"
,
"浅色模式"
:
"Mode clair"
,
"浅色模式"
:
"Mode clair"
,
...
...
web/src/i18n/locales/ja.json
View file @
0c0b69a3
...
@@ -1542,6 +1542,7 @@
...
@@ -1542,6 +1542,7 @@
"流"
:
"ストリーム"
,
"流"
:
"ストリーム"
,
"流式响应完成"
:
"ストリーム完了"
,
"流式响应完成"
:
"ストリーム完了"
,
"流式输出"
:
"ストリーム出力"
,
"流式输出"
:
"ストリーム出力"
,
"流式"
:
"ストリーミング"
,
"流量端口"
:
"Traffic Port"
,
"流量端口"
:
"Traffic Port"
,
"浅色"
:
"ライト"
,
"浅色"
:
"ライト"
,
"浅色模式"
:
"ライトモード"
,
"浅色模式"
:
"ライトモード"
,
...
...
web/src/i18n/locales/ru.json
View file @
0c0b69a3
...
@@ -1568,6 +1568,7 @@
...
@@ -1568,6 +1568,7 @@
"流"
:
"Поток"
,
"流"
:
"Поток"
,
"流式响应完成"
:
"Поток завершён"
,
"流式响应完成"
:
"Поток завершён"
,
"流式输出"
:
"Потоковый вывод"
,
"流式输出"
:
"Потоковый вывод"
,
"流式"
:
"Стриминг"
,
"流量端口"
:
"Traffic Port"
,
"流量端口"
:
"Traffic Port"
,
"浅色"
:
"Светлая"
,
"浅色"
:
"Светлая"
,
"浅色模式"
:
"Светлый режим"
,
"浅色模式"
:
"Светлый режим"
,
...
...
web/src/i18n/locales/vi.json
View file @
0c0b69a3
...
@@ -1596,6 +1596,7 @@
...
@@ -1596,6 +1596,7 @@
"流"
:
"luồng"
,
"流"
:
"luồng"
,
"流式响应完成"
:
"Luồng hoàn tất"
,
"流式响应完成"
:
"Luồng hoàn tất"
,
"流式输出"
:
"Đầu ra luồng"
,
"流式输出"
:
"Đầu ra luồng"
,
"流式"
:
"Streaming"
,
"流量端口"
:
"Traffic Port"
,
"流量端口"
:
"Traffic Port"
,
"浅色"
:
"Sáng"
,
"浅色"
:
"Sáng"
,
"浅色模式"
:
"Chế độ sáng"
,
"浅色模式"
:
"Chế độ sáng"
,
...
...
web/src/i18n/locales/zh.json
View file @
0c0b69a3
...
@@ -1537,6 +1537,7 @@
...
@@ -1537,6 +1537,7 @@
"流"
:
"流"
,
"流"
:
"流"
,
"流式响应完成"
:
"流式响应完成"
,
"流式响应完成"
:
"流式响应完成"
,
"流式输出"
:
"流式输出"
,
"流式输出"
:
"流式输出"
,
"流式"
:
"流式"
,
"流量端口"
:
"流量端口"
,
"流量端口"
:
"流量端口"
,
"浅色"
:
"浅色"
,
"浅色"
:
"浅色"
,
"浅色模式"
:
"浅色模式"
,
"浅色模式"
:
"浅色模式"
,
...
...
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