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
325de987
authored
Nov 19, 2024
by
CalciumIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 渠道标签分组
parent
55f9a48f
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
1067 additions
and
591 deletions
+1067
-591
controller/channel.go
+97
-1
model/ability.go
+26
-6
model/channel.go
+54
-0
router/api-router.go
+3
-0
web/src/components/ChannelsTable.js
+553
-400
web/src/components/TextInput.js
+22
-0
web/src/helpers/render.js
+2
-0
web/src/pages/Channel/EditChannel.js
+217
-184
web/src/pages/Channel/EditTagModal.js
+93
-0
No files found.
controller/channel.go
View file @
325de987
...
...
@@ -57,10 +57,24 @@ func GetAllChannels(c *gin.Context) {
})
return
}
tags
:=
make
(
map
[
string
]
bool
)
channelData
:=
make
([]
*
model
.
Channel
,
0
,
len
(
channels
))
for
_
,
channel
:=
range
channels
{
channelTag
:=
channel
.
GetTag
()
if
channelTag
!=
""
&&
!
tags
[
channelTag
]
{
tags
[
channelTag
]
=
true
tagChannels
,
err
:=
model
.
GetChannelsByTag
(
channelTag
)
if
err
==
nil
{
channelData
=
append
(
channelData
,
tagChannels
...
)
}
}
else
{
channelData
=
append
(
channelData
,
channel
)
}
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
""
,
"data"
:
channel
s
,
"data"
:
channel
Data
,
})
return
}
...
...
@@ -279,6 +293,88 @@ func DeleteDisabledChannel(c *gin.Context) {
return
}
type
ChannelTag
struct
{
Tag
string
`json:"tag"`
NewTag
*
string
`json:"newTag"`
Priority
*
int64
`json:"priority"`
Weight
*
uint
`json:"weight"`
}
func
DisableTagChannels
(
c
*
gin
.
Context
)
{
channelTag
:=
ChannelTag
{}
err
:=
c
.
ShouldBindJSON
(
&
channelTag
)
if
err
!=
nil
||
channelTag
.
Tag
==
""
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"参数错误"
,
})
return
}
err
=
model
.
DisableChannelByTag
(
channelTag
.
Tag
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
err
.
Error
(),
})
return
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
""
,
})
return
}
func
EnableTagChannels
(
c
*
gin
.
Context
)
{
channelTag
:=
ChannelTag
{}
err
:=
c
.
ShouldBindJSON
(
&
channelTag
)
if
err
!=
nil
||
channelTag
.
Tag
==
""
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"参数错误"
,
})
return
}
err
=
model
.
EnableChannelByTag
(
channelTag
.
Tag
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
err
.
Error
(),
})
return
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
""
,
})
return
}
func
EditTagChannels
(
c
*
gin
.
Context
)
{
channelTag
:=
ChannelTag
{}
err
:=
c
.
ShouldBindJSON
(
&
channelTag
)
if
err
!=
nil
||
channelTag
.
Tag
==
""
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"参数错误"
,
})
return
}
err
=
model
.
EditChannelByTag
(
channelTag
.
Tag
,
channelTag
.
NewTag
,
channelTag
.
Priority
,
channelTag
.
Weight
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
err
.
Error
(),
})
return
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
""
,
})
return
}
type
ChannelBatch
struct
{
Ids
[]
int
`json:"ids"`
}
...
...
model/ability.go
View file @
325de987
...
...
@@ -10,12 +10,13 @@ import (
)
type
Ability
struct
{
Group
string
`json:"group" gorm:"type:varchar(64);primaryKey;autoIncrement:false"`
Model
string
`json:"model" gorm:"type:varchar(64);primaryKey;autoIncrement:false"`
ChannelId
int
`json:"channel_id" gorm:"primaryKey;autoIncrement:false;index"`
Enabled
bool
`json:"enabled"`
Priority
*
int64
`json:"priority" gorm:"bigint;default:0;index"`
Weight
uint
`json:"weight" gorm:"default:0;index"`
Group
string
`json:"group" gorm:"type:varchar(64);primaryKey;autoIncrement:false"`
Model
string
`json:"model" gorm:"type:varchar(64);primaryKey;autoIncrement:false"`
ChannelId
int
`json:"channel_id" gorm:"primaryKey;autoIncrement:false;index"`
Enabled
bool
`json:"enabled"`
Priority
*
int64
`json:"priority" gorm:"bigint;default:0;index"`
Weight
uint
`json:"weight" gorm:"default:0;index"`
Tag
*
string
`json:"tag" gorm:"index"`
}
func
GetGroupModels
(
group
string
)
[]
string
{
...
...
@@ -149,6 +150,7 @@ func (channel *Channel) AddAbilities() error {
Enabled
:
channel
.
Status
==
common
.
ChannelStatusEnabled
,
Priority
:
channel
.
Priority
,
Weight
:
uint
(
channel
.
GetWeight
()),
Tag
:
channel
.
Tag
,
}
abilities
=
append
(
abilities
,
ability
)
}
...
...
@@ -190,6 +192,24 @@ func UpdateAbilityStatus(channelId int, status bool) error {
return
DB
.
Model
(
&
Ability
{})
.
Where
(
"channel_id = ?"
,
channelId
)
.
Select
(
"enabled"
)
.
Update
(
"enabled"
,
status
)
.
Error
}
func
UpdateAbilityStatusByTag
(
tag
string
,
status
bool
)
error
{
return
DB
.
Model
(
&
Ability
{})
.
Where
(
"tag = ?"
,
tag
)
.
Select
(
"enabled"
)
.
Update
(
"enabled"
,
status
)
.
Error
}
func
UpdateAbilityByTag
(
tag
string
,
newTag
*
string
,
priority
*
int64
,
weight
*
uint
)
error
{
ability
:=
Ability
{}
if
newTag
!=
nil
{
ability
.
Tag
=
newTag
}
if
priority
!=
nil
{
ability
.
Priority
=
priority
}
if
weight
!=
nil
{
ability
.
Weight
=
*
weight
}
return
DB
.
Model
(
&
Ability
{})
.
Where
(
"tag = ?"
,
tag
)
.
Updates
(
ability
)
.
Error
}
func
FixAbility
()
(
int
,
error
)
{
var
channelIds
[]
int
count
:=
0
...
...
model/channel.go
View file @
325de987
...
...
@@ -32,6 +32,7 @@ type Channel struct {
Priority
*
int64
`json:"priority" gorm:"bigint;default:0"`
AutoBan
*
int
`json:"auto_ban" gorm:"default:1"`
OtherInfo
string
`json:"other_info"`
Tag
*
string
`json:"tag" gorm:"index"`
}
func
(
channel
*
Channel
)
GetModels
()
[]
string
{
...
...
@@ -61,6 +62,17 @@ func (channel *Channel) SetOtherInfo(otherInfo map[string]interface{}) {
channel
.
OtherInfo
=
string
(
otherInfoBytes
)
}
func
(
channel
*
Channel
)
GetTag
()
string
{
if
channel
.
Tag
==
nil
{
return
""
}
return
*
channel
.
Tag
}
func
(
channel
*
Channel
)
SetTag
(
tag
string
)
{
channel
.
Tag
=
&
tag
}
func
(
channel
*
Channel
)
GetAutoBan
()
bool
{
if
channel
.
AutoBan
==
nil
{
return
false
...
...
@@ -87,6 +99,12 @@ func GetAllChannels(startIdx int, num int, selectAll bool, idSort bool) ([]*Chan
return
channels
,
err
}
func
GetChannelsByTag
(
tag
string
)
([]
*
Channel
,
error
)
{
var
channels
[]
*
Channel
err
:=
DB
.
Where
(
"tag = ?"
,
tag
)
.
Find
(
&
channels
)
.
Error
return
channels
,
err
}
func
SearchChannels
(
keyword
string
,
group
string
,
model
string
)
([]
*
Channel
,
error
)
{
var
channels
[]
*
Channel
keyCol
:=
"`key`"
...
...
@@ -288,6 +306,42 @@ func UpdateChannelStatusById(id int, status int, reason string) {
}
func
EnableChannelByTag
(
tag
string
)
error
{
err
:=
DB
.
Model
(
&
Channel
{})
.
Where
(
"tag = ?"
,
tag
)
.
Update
(
"status"
,
common
.
ChannelStatusEnabled
)
.
Error
if
err
!=
nil
{
return
err
}
err
=
UpdateAbilityStatusByTag
(
tag
,
true
)
return
err
}
func
DisableChannelByTag
(
tag
string
)
error
{
err
:=
DB
.
Model
(
&
Channel
{})
.
Where
(
"tag = ?"
,
tag
)
.
Update
(
"status"
,
common
.
ChannelStatusManuallyDisabled
)
.
Error
if
err
!=
nil
{
return
err
}
err
=
UpdateAbilityStatusByTag
(
tag
,
false
)
return
err
}
func
EditChannelByTag
(
tag
string
,
newTag
*
string
,
priority
*
int64
,
weight
*
uint
)
error
{
updateData
:=
Channel
{}
if
newTag
!=
nil
{
updateData
.
Tag
=
newTag
}
if
priority
!=
nil
{
updateData
.
Priority
=
priority
}
if
weight
!=
nil
{
updateData
.
Weight
=
weight
}
err
:=
DB
.
Model
(
&
Channel
{})
.
Where
(
"tag = ?"
,
tag
)
.
Updates
(
updateData
)
.
Error
if
err
!=
nil
{
return
err
}
return
UpdateAbilityByTag
(
tag
,
newTag
,
priority
,
weight
)
}
func
UpdateChannelUsedQuota
(
id
int
,
quota
int
)
{
if
common
.
BatchUpdateEnabled
{
addNewRecord
(
BatchUpdateTypeChannelUsedQuota
,
id
,
quota
)
...
...
router/api-router.go
View file @
325de987
...
...
@@ -91,6 +91,9 @@ func SetApiRouter(router *gin.Engine) {
channelRoute
.
POST
(
"/"
,
controller
.
AddChannel
)
channelRoute
.
PUT
(
"/"
,
controller
.
UpdateChannel
)
channelRoute
.
DELETE
(
"/disabled"
,
controller
.
DeleteDisabledChannel
)
channelRoute
.
POST
(
"/tag/disabled"
,
controller
.
DisableTagChannels
)
channelRoute
.
POST
(
"/tag/enabled"
,
controller
.
EnableTagChannels
)
channelRoute
.
PUT
(
"/tag"
,
controller
.
EditTagChannels
)
channelRoute
.
DELETE
(
"/:id"
,
controller
.
DeleteChannel
)
channelRoute
.
POST
(
"/batch"
,
controller
.
DeleteChannelBatch
)
channelRoute
.
POST
(
"/fix"
,
controller
.
FixChannelsAbilities
)
...
...
web/src/components/ChannelsTable.js
View file @
325de987
...
...
@@ -7,14 +7,14 @@ import {
showInfo
,
showSuccess
,
showWarning
,
timestamp2string
,
timestamp2string
}
from
'../helpers'
;
import
{
CHANNEL_OPTIONS
,
ITEMS_PER_PAGE
}
from
'../constants'
;
import
{
renderGroup
,
renderNumberWithPoint
,
renderQuota
,
renderQuota
}
from
'../helpers/render'
;
import
{
Button
,
Divider
,
...
...
@@ -28,11 +28,12 @@ import {
Table
,
Tag
,
Tooltip
,
Typography
,
Typography
}
from
'@douyinfe/semi-ui'
;
import
EditChannel
from
'../pages/Channel/EditChannel'
;
import
{
IconTreeTriangleDown
}
from
'@douyinfe/semi-icons'
;
import
{
loadChannelModels
}
from
'./utils.js'
;
import
EditTagModal
from
'../pages/Channel/EditTagModal.js'
;
function
renderTimestamp
(
timestamp
)
{
return
<>
{
timestamp2string
(
timestamp
)}
<
/>
;
...
...
@@ -49,7 +50,7 @@ function renderType(type) {
type2label
[
0
]
=
{
value
:
0
,
text
:
'未知类型'
,
color
:
'grey'
};
}
return
(
<
Tag
size
=
'large'
color
=
{
type2label
[
type
]?.
color
}
>
<
Tag
size
=
"large"
color
=
{
type2label
[
type
]?.
color
}
>
{
type2label
[
type
]?.
text
}
<
/Tag
>
);
...
...
@@ -64,11 +65,11 @@ const ChannelsTable = () => {
// },
{
title
:
'ID'
,
dataIndex
:
'id'
,
dataIndex
:
'id'
},
{
title
:
'名称'
,
dataIndex
:
'name'
,
dataIndex
:
'name'
},
{
title
:
'分组'
,
...
...
@@ -77,20 +78,20 @@ const ChannelsTable = () => {
return
(
<
div
>
<
Space
spacing
=
{
2
}
>
{
text
.
split
(
','
).
map
((
item
,
index
)
=>
{
{
text
?
.
split
(
','
).
map
((
item
,
index
)
=>
{
return
renderGroup
(
item
);
})}
<
/Space
>
<
/div
>
);
}
,
}
},
{
title
:
'类型'
,
dataIndex
:
'type'
,
render
:
(
text
,
record
,
index
)
=>
{
return
<
div
>
{
renderType
(
text
)}
<
/div>
;
}
,
}
},
{
title
:
'状态'
,
...
...
@@ -98,7 +99,7 @@ const ChannelsTable = () => {
render
:
(
text
,
record
,
index
)
=>
{
if
(
text
===
3
)
{
if
(
record
.
other_info
===
''
)
{
record
.
other_info
=
'{}'
record
.
other_info
=
'{}'
;
}
let
otherInfo
=
JSON
.
parse
(
record
.
other_info
);
let
reason
=
otherInfo
[
'status_reason'
];
...
...
@@ -113,181 +114,246 @@ const ChannelsTable = () => {
}
else
{
return
renderStatus
(
text
);
}
}
,
}
},
{
title
:
'响应时间'
,
dataIndex
:
'response_time'
,
render
:
(
text
,
record
,
index
)
=>
{
return
<
div
>
{
renderResponseTime
(
text
)}
<
/div>
;
}
,
}
},
{
title
:
'已用/剩余'
,
dataIndex
:
'expired_time'
,
render
:
(
text
,
record
,
index
)
=>
{
return
(
<
div
>
<
Space
spacing
=
{
1
}
>
<
Tooltip
content
=
{
'已用额度'
}
>
<
Tag
color
=
'white'
type
=
'ghost'
size
=
'large'
>
{
renderQuota
(
record
.
used_quota
)}
<
/Tag
>
<
/Tooltip
>
<
Tooltip
content
=
{
'剩余额度'
+
record
.
balance
+
',点击更新'
}
>
<
Tag
color
=
'white'
type
=
'ghost'
size
=
'large'
onClick
=
{()
=>
{
updateChannelBalance
(
record
);
}}
>
$
{
renderNumberWithPoint
(
record
.
balance
)}
<
/Tag
>
<
/Tooltip
>
<
/Space
>
<
/div
>
);
},
if
(
record
.
children
===
undefined
)
{
return
(
<
div
>
<
Space
spacing
=
{
1
}
>
<
Tooltip
content
=
{
'已用额度'
}
>
<
Tag
color
=
"white"
type
=
"ghost"
size
=
"large"
>
{
renderQuota
(
record
.
used_quota
)}
<
/Tag
>
<
/Tooltip
>
<
Tooltip
content
=
{
'剩余额度'
+
record
.
balance
+
',点击更新'
}
>
<
Tag
color
=
"white"
type
=
"ghost"
size
=
"large"
onClick
=
{()
=>
{
updateChannelBalance
(
record
);
}}
>
$
{
renderNumberWithPoint
(
record
.
balance
)}
<
/Tag
>
<
/Tooltip
>
<
/Space
>
<
/div
>
);
}
else
{
return
<
Tooltip
content
=
{
'已用额度'
}
>
<
Tag
color
=
"white"
type
=
"ghost"
size
=
"large"
>
{
renderQuota
(
record
.
used_quota
)}
<
/Tag
>
<
/Tooltip>
;
}
}
},
{
title
:
'优先级'
,
dataIndex
:
'priority'
,
render
:
(
text
,
record
,
index
)
=>
{
return
(
<
div
>
<
InputNumber
style
=
{{
width
:
70
}}
name
=
'priority'
onBlur
=
{(
e
)
=>
{
manageChannel
(
record
.
id
,
'priority'
,
record
,
e
.
target
.
value
);
}}
keepFocus
=
{
true
}
innerButtons
defaultValue
=
{
record
.
priority
}
min
=
{
-
999
}
/
>
<
/div
>
);
},
if
(
record
.
children
===
undefined
)
{
return
(
<
div
>
<
InputNumber
style
=
{{
width
:
70
}}
name
=
"priority"
onBlur
=
{(
e
)
=>
{
manageChannel
(
record
.
id
,
'priority'
,
record
,
e
.
target
.
value
);
}}
keepFocus
=
{
true
}
innerButtons
defaultValue
=
{
record
.
priority
}
min
=
{
-
999
}
/
>
<
/div
>
);
}
else
{
return
<>
<
Button
theme
=
"outline"
type
=
"primary"
>
修改
<
/Button
>
<
/>
;
}
}
},
{
title
:
'权重'
,
dataIndex
:
'weight'
,
render
:
(
text
,
record
,
index
)
=>
{
return
(
<
div
>
<
InputNumber
style
=
{{
width
:
70
}}
name
=
'weight'
onBlur
=
{(
e
)
=>
{
manageChannel
(
record
.
id
,
'weight'
,
record
,
e
.
target
.
value
);
}}
keepFocus
=
{
true
}
innerButtons
defaultValue
=
{
record
.
weight
}
min
=
{
0
}
/
>
<
/div
>
);
},
if
(
record
.
children
===
undefined
)
{
return
(
<
div
>
<
InputNumber
style
=
{{
width
:
70
}}
name
=
"weight"
onBlur
=
{(
e
)
=>
{
manageChannel
(
record
.
id
,
'weight'
,
record
,
e
.
target
.
value
);
}}
keepFocus
=
{
true
}
innerButtons
defaultValue
=
{
record
.
weight
}
min
=
{
0
}
/
>
<
/div
>
);
}
else
{
return
(
<
Button
theme
=
"outline"
type
=
"primary"
>
修改
<
/Button
>
);
}
}
},
{
title
:
''
,
dataIndex
:
'operate'
,
render
:
(
text
,
record
,
index
)
=>
(
<
div
>
<
SplitButtonGroup
style
=
{{
marginRight
:
1
}}
aria
-
label
=
'测试操作项目组'
>
<
Button
theme
=
'light'
onClick
=
{()
=>
{
testChannel
(
record
,
''
);
}}
>
测试
<
/Button
>
<
Dropdown
trigger
=
'click'
position
=
'bottomRight'
menu
=
{
record
.
test_models
}
>
render
:
(
text
,
record
,
index
)
=>
{
if
(
record
.
children
===
undefined
)
{
return
(
<
div
>
<
SplitButtonGroup
style
=
{{
marginRight
:
1
}}
aria
-
label
=
"测试单个渠道操作项目组"
>
<
Button
theme
=
"light"
onClick
=
{()
=>
{
testChannel
(
record
,
''
);
}}
>
测试
<
/Button
>
<
Dropdown
trigger
=
"click"
position
=
"bottomRight"
menu
=
{
record
.
test_models
}
>
<
Button
style
=
{{
padding
:
'8px 4px'
}}
type
=
"primary"
icon
=
{
<
IconTreeTriangleDown
/>
}
><
/Button
>
<
/Dropdown
>
<
/SplitButtonGroup
>
{
/*<Button theme='light' type='primary' style={{marginRight: 1}} onClick={()=>testChannel(record)}>测试</Button>*/
}
<
Popconfirm
title
=
"确定是否要删除此渠道?"
content
=
"此修改将不可逆"
okType
=
{
'danger'
}
position
=
{
'left'
}
onConfirm
=
{()
=>
{
manageChannel
(
record
.
id
,
'delete'
,
record
).
then
(()
=>
{
removeRecord
(
record
.
id
);
});
}}
>
<
Button
theme
=
"light"
type
=
"danger"
style
=
{{
marginRight
:
1
}}
>
删除
<
/Button
>
<
/Popconfirm
>
{
record
.
status
===
1
?
(
<
Button
theme
=
"light"
type
=
"warning"
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
()
=>
{
manageChannel
(
record
.
id
,
'disable'
,
record
);
}}
>
禁用
<
/Button
>
)
:
(
<
Button
theme
=
"light"
type
=
"secondary"
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
()
=>
{
manageChannel
(
record
.
id
,
'enable'
,
record
);
}}
>
启用
<
/Button
>
)}
<
Button
style
=
{{
padding
:
'8px 4px'
}}
type
=
'primary'
icon
=
{
<
IconTreeTriangleDown
/>
}
><
/Button
>
<
/Dropdown
>
<
/SplitButtonGroup
>
{
/*<Button theme='light' type='primary' style={{marginRight: 1}} onClick={()=>testChannel(record)}>测试</Button>*/
}
<
Popconfirm
title
=
'确定是否要删除此渠道?'
content
=
'此修改将不可逆'
okType
=
{
'danger'
}
position
=
{
'left'
}
onConfirm
=
{()
=>
{
manageChannel
(
record
.
id
,
'delete'
,
record
).
then
(()
=>
{
removeRecord
(
record
.
id
);
});
}}
>
<
Button
theme
=
'light'
type
=
'danger'
style
=
{{
marginRight
:
1
}}
>
删除
<
/Button
>
<
/Popconfirm
>
{
record
.
status
===
1
?
(
<
Button
theme
=
'light'
type
=
'warning'
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
()
=>
{
manageChannel
(
record
.
id
,
'disable'
,
record
);
}}
>
禁用
<
/Button
>
)
:
(
<
Button
theme
=
'light'
type
=
'secondary'
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
()
=>
{
manageChannel
(
record
.
id
,
'enable'
,
record
);
}}
>
启用
<
/Button
>
)}
<
Button
theme
=
'light'
type
=
'tertiary'
style
=
{{
marginRight
:
1
}}
onClick
=
{()
=>
{
setEditingChannel
(
record
);
setShowEdit
(
true
);
}}
>
编辑
<
/Button
>
<
Popconfirm
title
=
'确定是否要复制此渠道?'
content
=
'复制渠道的所有信息'
okType
=
{
'danger'
}
position
=
{
'left'
}
onConfirm
=
{
async
()
=>
{
copySelectedChannel
(
record
.
id
);
}}
>
<
Button
theme
=
'light'
type
=
'primary'
style
=
{{
marginRight
:
1
}}
>
复制
<
/Button
>
<
/Popconfirm
>
<
/div
>
),
},
theme
=
"light"
type
=
"tertiary"
style
=
{{
marginRight
:
1
}}
onClick
=
{()
=>
{
setEditingChannel
(
record
);
setShowEdit
(
true
);
}}
>
编辑
<
/Button
>
<
Popconfirm
title
=
"确定是否要复制此渠道?"
content
=
"复制渠道的所有信息"
okType
=
{
'danger'
}
position
=
{
'left'
}
onConfirm
=
{
async
()
=>
{
copySelectedChannel
(
record
.
id
);
}}
>
<
Button
theme
=
"light"
type
=
"primary"
style
=
{{
marginRight
:
1
}}
>
复制
<
/Button
>
<
/Popconfirm
>
<
/div
>
);
}
else
{
return
(
<>
<
Button
theme
=
"light"
type
=
"secondary"
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
()
=>
{
manageTag
(
record
.
key
,
'enable'
);
}}
>
启用全部
<
/Button
>
<
Button
theme
=
"light"
type
=
"warning"
style
=
{{
marginRight
:
1
}}
onClick
=
{
async
()
=>
{
manageTag
(
record
.
key
,
'disable'
);
}}
>
禁用全部
<
/Button
>
<
Button
theme
=
"light"
type
=
"tertiary"
style
=
{{
marginRight
:
1
}}
onClick
=
{()
=>
{
setShowEditTag
(
true
);
setEditingTag
(
record
.
key
);
}}
>
编辑
<
/Button
>
<
/
>
);
}
}
}
];
const
[
channels
,
setChannels
]
=
useState
([]);
...
...
@@ -301,15 +367,17 @@ const ChannelsTable = () => {
const
[
updatingBalance
,
setUpdatingBalance
]
=
useState
(
false
);
const
[
pageSize
,
setPageSize
]
=
useState
(
ITEMS_PER_PAGE
);
const
[
showPrompt
,
setShowPrompt
]
=
useState
(
shouldShowPrompt
(
'channel-test'
)
,
shouldShowPrompt
(
'channel-test'
)
);
const
[
channelCount
,
setChannelCount
]
=
useState
(
pageSize
);
const
[
groupOptions
,
setGroupOptions
]
=
useState
([]);
const
[
showEdit
,
setShowEdit
]
=
useState
(
false
);
const
[
enableBatchDelete
,
setEnableBatchDelete
]
=
useState
(
false
);
const
[
editingChannel
,
setEditingChannel
]
=
useState
({
id
:
undefined
,
id
:
undefined
});
const
[
showEditTag
,
setShowEditTag
]
=
useState
(
false
);
const
[
editingTag
,
setEditingTag
]
=
useState
(
''
);
const
[
selectedChannels
,
setSelectedChannels
]
=
useState
([]);
const
removeRecord
=
(
id
)
=>
{
...
...
@@ -325,39 +393,82 @@ const ChannelsTable = () => {
};
const
setChannelFormat
=
(
channels
)
=>
{
let
channelDates
=
[];
let
channelTags
=
{};
for
(
let
i
=
0
;
i
<
channels
.
length
;
i
++
)
{
// if (channels[i].type === 8) {
// showWarning(
// '检测到您使用了“自定义渠道”类型,请更换为“OpenAI”渠道类型!',
// );
// showWarning('下个版本将不再支持“自定义渠道”类型!');
// }
channels
[
i
].
key
=
''
+
channels
[
i
].
id
;
let
test_models
=
[];
channels
[
i
].
models
.
split
(
','
).
forEach
((
item
,
index
)
=>
{
test_models
.
push
({
node
:
'item'
,
name
:
item
,
onClick
:
()
=>
{
testChannel
(
channels
[
i
],
item
);
},
if
(
channels
[
i
].
tag
===
''
||
channels
[
i
].
tag
===
null
)
{
let
test_models
=
[];
channels
[
i
].
models
.
split
(
','
).
forEach
((
item
,
index
)
=>
{
test_models
.
push
({
node
:
'item'
,
name
:
item
,
onClick
:
()
=>
{
testChannel
(
channels
[
i
],
item
);
}
});
});
});
channels
[
i
].
test_models
=
test_models
;
channels
[
i
].
test_models
=
test_models
;
channelDates
.
push
(
channels
[
i
]);
}
else
{
let
tag
=
channels
[
i
].
tag
;
// find from channelTags
let
tagIndex
=
channelTags
[
tag
];
let
tagChannelDates
=
undefined
;
if
(
tagIndex
===
undefined
)
{
// not found, create a new tag
channelTags
[
tag
]
=
1
;
tagChannelDates
=
{
key
:
tag
,
id
:
tag
,
tag
:
tag
,
name
:
'标签:'
+
tag
,
group
:
''
,
used_quota
:
0
,
response_time
:
0
};
tagChannelDates
.
children
=
[];
channelDates
.
push
(
tagChannelDates
);
}
else
{
// found, add to the tag
tagChannelDates
=
channelDates
.
find
((
item
)
=>
item
.
key
===
tag
);
}
if
(
tagChannelDates
.
group
===
''
)
{
tagChannelDates
.
group
=
channels
[
i
].
group
;
}
else
{
let
channelGroupsStr
=
channels
[
i
].
group
;
channelGroupsStr
.
split
(
','
).
forEach
((
item
,
index
)
=>
{
if
(
tagChannelDates
.
group
.
indexOf
(
item
)
===
-
1
)
{
tagChannelDates
.
group
+=
item
+
','
;
}
});
}
tagChannelDates
.
children
.
push
(
channels
[
i
]);
if
(
channels
[
i
].
status
===
1
)
{
tagChannelDates
.
status
=
1
;
}
tagChannelDates
.
used_quota
+=
channels
[
i
].
used_quota
;
tagChannelDates
.
response_time
+=
channels
[
i
].
response_time
;
tagChannelDates
.
response_time
=
tagChannelDates
.
response_time
/
2
;
}
}
// data.key = '' + data.id
setChannels
(
channels
);
if
(
channels
.
length
>=
pageSize
)
{
setChannelCount
(
channels
.
length
+
pageSize
);
setChannels
(
channel
Date
s
);
if
(
channel
Date
s
.
length
>=
pageSize
)
{
setChannelCount
(
channel
Date
s
.
length
+
pageSize
);
}
else
{
setChannelCount
(
channels
.
length
);
setChannelCount
(
channel
Date
s
.
length
);
}
};
const
loadChannels
=
async
(
startIdx
,
pageSize
,
idSort
)
=>
{
setLoading
(
true
);
const
res
=
await
API
.
get
(
`/api/channel/?p=
${
startIdx
}
&page_size=
${
pageSize
}
&id_sort=
${
idSort
}
`
,
`/api/channel/?p=
${
startIdx
}
&page_size=
${
pageSize
}
&id_sort=
${
idSort
}
`
);
if
(
res
===
undefined
)
{
return
;
...
...
@@ -379,7 +490,7 @@ const ChannelsTable = () => {
const
copySelectedChannel
=
async
(
id
)
=>
{
const
channelToCopy
=
channels
.
find
(
(
channel
)
=>
String
(
channel
.
id
)
===
String
(
id
)
,
(
channel
)
=>
String
(
channel
.
id
)
===
String
(
id
)
);
console
.
log
(
channelToCopy
);
channelToCopy
.
name
+=
'_复制'
;
...
...
@@ -472,29 +583,63 @@ const ChannelsTable = () => {
}
};
const
manageTag
=
async
(
tag
,
action
)
=>
{
console
.
log
(
tag
,
action
);
let
res
;
switch
(
action
)
{
case
'enable'
:
res
=
await
API
.
post
(
'/api/channel/tag/enabled'
,
{
tag
:
tag
});
break
;
case
'disable'
:
res
=
await
API
.
post
(
'/api/channel/tag/disabled'
,
{
tag
:
tag
});
break
;
}
const
{
success
,
message
}
=
res
.
data
;
if
(
success
)
{
showSuccess
(
'操作成功完成!'
);
let
newChannels
=
[...
channels
];
for
(
let
i
=
0
;
i
<
newChannels
.
length
;
i
++
)
{
if
(
newChannels
[
i
].
tag
===
tag
)
{
let
status
=
action
===
'enable'
?
1
:
2
;
newChannels
[
i
]?.
children
?.
forEach
((
channel
)
=>
{
channel
.
status
=
status
;
});
newChannels
[
i
].
status
=
status
;
}
}
setChannels
(
newChannels
);
}
else
{
showError
(
message
);
}
};
const
renderStatus
=
(
status
)
=>
{
switch
(
status
)
{
case
1
:
return
(
<
Tag
size
=
'large'
color
=
'green'
>
<
Tag
size
=
"large"
color
=
"green"
>
已启用
<
/Tag
>
);
case
2
:
return
(
<
Tag
size
=
'large'
color
=
'yellow'
>
<
Tag
size
=
"large"
color
=
"yellow"
>
已禁用
<
/Tag
>
);
case
3
:
return
(
<
Tag
size
=
'large'
color
=
'yellow'
>
<
Tag
size
=
"large"
color
=
"yellow"
>
自动禁用
<
/Tag
>
);
default
:
return
(
<
Tag
size
=
'large'
color
=
'grey'
>
<
Tag
size
=
"large"
color
=
"grey"
>
未知状态
<
/Tag
>
);
...
...
@@ -506,31 +651,31 @@ const ChannelsTable = () => {
time
=
time
.
toFixed
(
2
)
+
' 秒'
;
if
(
responseTime
===
0
)
{
return
(
<
Tag
size
=
'large'
color
=
'grey'
>
<
Tag
size
=
"large"
color
=
"grey"
>
未测试
<
/Tag
>
);
}
else
if
(
responseTime
<=
1000
)
{
return
(
<
Tag
size
=
'large'
color
=
'green'
>
<
Tag
size
=
"large"
color
=
"green"
>
{
time
}
<
/Tag
>
);
}
else
if
(
responseTime
<=
3000
)
{
return
(
<
Tag
size
=
'large'
color
=
'lime'
>
<
Tag
size
=
"large"
color
=
"lime"
>
{
time
}
<
/Tag
>
);
}
else
if
(
responseTime
<=
5000
)
{
return
(
<
Tag
size
=
'large'
color
=
'yellow'
>
<
Tag
size
=
"large"
color
=
"yellow"
>
{
time
}
<
/Tag
>
);
}
else
{
return
(
<
Tag
size
=
'large'
color
=
'red'
>
<
Tag
size
=
"large"
color
=
"red"
>
{
time
}
<
/Tag
>
);
...
...
@@ -546,7 +691,7 @@ const ChannelsTable = () => {
}
setSearching
(
true
);
const
res
=
await
API
.
get
(
`/api/channel/search?keyword=
${
searchKeyword
}
&group=
${
searchGroup
}
&model=
${
searchModel
}
`
,
`/api/channel/search?keyword=
${
searchKeyword
}
&group=
${
searchGroup
}
&model=
${
searchModel
}
`
);
const
{
success
,
message
,
data
}
=
res
.
data
;
if
(
success
)
{
...
...
@@ -649,14 +794,15 @@ const ChannelsTable = () => {
let
pageData
=
channels
.
slice
(
(
activePage
-
1
)
*
pageSize
,
activePage
*
pageSize
,
activePage
*
pageSize
);
const
handlePageChange
=
(
page
)
=>
{
setActivePage
(
page
);
if
(
page
===
Math
.
ceil
(
channels
.
length
/
pageSize
)
+
1
)
{
// In this case we have to load more data and then append them.
loadChannels
(
page
-
1
,
pageSize
,
idSort
).
then
((
r
)
=>
{});
loadChannels
(
page
-
1
,
pageSize
,
idSort
).
then
((
r
)
=>
{
});
}
};
...
...
@@ -682,8 +828,8 @@ const ChannelsTable = () => {
setGroupOptions
(
res
.
data
.
data
.
map
((
group
)
=>
({
label
:
group
,
value
:
group
,
}))
,
value
:
group
}))
);
}
catch
(
error
)
{
showError
(
error
.
message
);
...
...
@@ -698,8 +844,8 @@ const ChannelsTable = () => {
if
(
record
.
status
!==
1
)
{
return
{
style
:
{
background
:
'var(--semi-color-disabled-border)'
,
}
,
background
:
'var(--semi-color-disabled-border)'
}
};
}
else
{
return
{};
...
...
@@ -707,217 +853,224 @@ const ChannelsTable = () => {
};
return
(
<>
<
EditChannel
refresh
=
{
refresh
}
visible
=
{
showEdit
}
handleClose
=
{
closeEdit
}
editingChannel
=
{
editingChannel
}
/
>
<
Form
onSubmit
=
{()
=>
{
searchChannels
(
searchKeyword
,
searchGroup
,
searchModel
);
}}
labelPosition
=
'left'
<>
<
EditTagModal
visible
=
{
showEditTag
}
tag
=
{
editingTag
}
handleClose
=
{()
=>
setShowEditTag
(
false
)}
refresh
=
{
refresh
}
/
>
<
EditChannel
refresh
=
{
refresh
}
visible
=
{
showEdit
}
handleClose
=
{
closeEdit
}
editingChannel
=
{
editingChannel
}
/
>
<
Form
onSubmit
=
{()
=>
{
searchChannels
(
searchKeyword
,
searchGroup
,
searchModel
);
}}
labelPosition
=
"left"
>
<
div
style
=
{{
display
:
'flex'
}}
>
<
Space
>
<
Form
.
Input
field
=
"search_keyword"
label
=
"搜索渠道关键词"
placeholder
=
"ID,名称和密钥 ..."
value
=
{
searchKeyword
}
loading
=
{
searching
}
onChange
=
{(
v
)
=>
{
setSearchKeyword
(
v
.
trim
());
}}
/
>
<
Form
.
Input
field
=
"search_model"
label
=
"模型"
placeholder
=
"模型关键字"
value
=
{
searchModel
}
loading
=
{
searching
}
onChange
=
{(
v
)
=>
{
setSearchModel
(
v
.
trim
());
}}
/
>
<
Form
.
Select
field
=
"group"
label
=
"分组"
optionList
=
{[{
label
:
'选择分组'
,
value
:
null
},
...
groupOptions
]}
initValue
=
{
null
}
onChange
=
{(
v
)
=>
{
setSearchGroup
(
v
);
searchChannels
(
searchKeyword
,
v
,
searchModel
);
}}
/
>
<
Button
label
=
"查询"
type
=
"primary"
htmlType
=
"submit"
className
=
"btn-margin-right"
style
=
{{
marginRight
:
8
}}
>
查询
<
/Button
>
<
/Space
>
<
/div
>
<
/Form
>
<
Divider
style
=
{{
marginBottom
:
15
}}
/
>
<
div
style
=
{{
display
:
isMobile
()
?
''
:
'flex'
,
marginTop
:
isMobile
()
?
0
:
-
45
,
zIndex
:
999
,
pointerEvents
:
'none'
}}
>
<
Space
style
=
{{
pointerEvents
:
'auto'
,
marginTop
:
isMobile
()
?
0
:
45
}}
>
<
div
style
=
{{
display
:
'flex'
}}
>
<
Space
>
<
Form
.
Input
field
=
'search_keyword'
label
=
'搜索渠道关键词'
placeholder
=
'ID,名称和密钥 ...'
value
=
{
searchKeyword
}
loading
=
{
searching
}
onChange
=
{(
v
)
=>
{
setSearchKeyword
(
v
.
trim
());
}}
/
>
<
Form
.
Input
field
=
'search_model'
label
=
'模型'
placeholder
=
'模型关键字'
value
=
{
searchModel
}
loading
=
{
searching
}
onChange
=
{(
v
)
=>
{
setSearchModel
(
v
.
trim
());
}}
/
>
<
Form
.
Select
field
=
'group'
label
=
'分组'
optionList
=
{[{
label
:
'选择分组'
,
value
:
null
},
...
groupOptions
]}
initValue
=
{
null
}
onChange
=
{(
v
)
=>
{
setSearchGroup
(
v
);
searchChannels
(
searchKeyword
,
v
,
searchModel
);
}}
/
>
<
Button
label
=
'查询'
type
=
'primary'
htmlType
=
'submit'
className
=
'btn-margin-right'
style
=
{{
marginRight
:
8
}}
>
查询
<
/Button
>
<
/Space
>
<
/div
>
<
/Form
>
<
Divider
style
=
{{
marginBottom
:
15
}}
/
>
<
div
style
=
{{
display
:
isMobile
()
?
''
:
'flex'
,
marginTop
:
isMobile
()
?
0
:
-
45
,
zIndex
:
999
,
pointerEvents
:
'none'
,
<
Typography
.
Text
strong
>
使用
ID
排序
<
/Typography.Text
>
<
Switch
checked
=
{
idSort
}
label
=
"使用ID排序"
uncheckedText
=
"关"
aria
-
label
=
"是否用ID排序"
onChange
=
{(
v
)
=>
{
localStorage
.
setItem
(
'id-sort'
,
v
+
''
);
setIdSort
(
v
);
loadChannels
(
0
,
pageSize
,
v
)
.
then
()
.
catch
((
reason
)
=>
{
showError
(
reason
);
});
}}
><
/Switch
>
<
Button
theme
=
"light"
type
=
"primary"
style
=
{{
marginRight
:
8
}}
onClick
=
{()
=>
{
setEditingChannel
({
id
:
undefined
});
setShowEdit
(
true
);
}}
>
<
Space
style
=
{{
pointerEvents
:
'auto'
,
marginTop
:
isMobile
()
?
0
:
45
}}
>
<
Typography
.
Text
strong
>
使用
ID
排序
<
/Typography.Text
>
<
Switch
checked
=
{
idSort
}
label
=
'使用ID排序'
uncheckedText
=
'关'
aria
-
label
=
'是否用ID排序'
onChange
=
{(
v
)
=>
{
localStorage
.
setItem
(
'id-sort'
,
v
+
''
);
setIdSort
(
v
);
loadChannels
(
0
,
pageSize
,
v
)
.
then
()
.
catch
((
reason
)
=>
{
showError
(
reason
);
});
}}
><
/Switch
>
<
Button
theme
=
'light'
type
=
'primary'
style
=
{{
marginRight
:
8
}}
onClick
=
{()
=>
{
setEditingChannel
({
id
:
undefined
,
});
setShowEdit
(
true
);
}}
>
添加渠道
添加渠道
<
/Button
>
<
Popconfirm
title
=
"确定?"
okType
=
{
'warning'
}
onConfirm
=
{
testAllChannels
}
position
=
{
isMobile
()
?
'top'
:
'top'
}
>
<
Button
theme
=
"light"
type
=
"warning"
style
=
{{
marginRight
:
8
}}
>
测试所有通道
<
/Button
>
<
Popconfirm
title
=
'确定?'
okType
=
{
'warning'
}
onConfirm
=
{
testAllChannels
}
position
=
{
isMobile
()
?
'top'
:
'top'
}
>
<
Button
theme
=
'light'
type
=
'warning'
style
=
{{
marginRight
:
8
}}
>
测试所有通道
<
/Button
>
<
/Popconfirm
>
<
Popconfirm
title
=
'确定?'
okType
=
{
'secondary'
}
onConfirm
=
{
updateAllChannelsBalance
}
>
<
Button
theme
=
'light'
type
=
'secondary'
style
=
{{
marginRight
:
8
}}
>
更新所有已启用通道余额
<
/Button
>
<
/Popconfirm
>
<
Popconfirm
title
=
'确定是否要删除禁用通道?'
content
=
'此修改将不可逆'
okType
=
{
'danger'
}
onConfirm
=
{
deleteAllDisabledChannels
}
>
<
Button
theme
=
'light'
type
=
'danger'
style
=
{{
marginRight
:
8
}}
>
删除禁用通道
<
/Button
>
<
/Popconfirm
>
<
/Popconfirm
>
<
Popconfirm
title
=
"确定?"
okType
=
{
'secondary'
}
onConfirm
=
{
updateAllChannelsBalance
}
>
<
Button
theme
=
"light"
type
=
"secondary"
style
=
{{
marginRight
:
8
}}
>
更新所有已启用通道余额
<
/Button
>
<
/Popconfirm
>
<
Popconfirm
title
=
"确定是否要删除禁用通道?"
content
=
"此修改将不可逆"
okType
=
{
'danger'
}
onConfirm
=
{
deleteAllDisabledChannels
}
>
<
Button
theme
=
"light"
type
=
"danger"
style
=
{{
marginRight
:
8
}}
>
删除禁用通道
<
/Button
>
<
/Popconfirm
>
<
Button
theme
=
"light"
type
=
"primary"
style
=
{{
marginRight
:
8
}}
onClick
=
{
refresh
}
>
刷新
<
/Button
>
<
/Space
>
<
/div
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Space
>
<
Typography
.
Text
strong
>
开启批量删除
<
/Typography.Text
>
<
Switch
label
=
"开启批量删除"
uncheckedText
=
"关"
aria
-
label
=
"是否开启批量删除"
onChange
=
{(
v
)
=>
{
setEnableBatchDelete
(
v
);
}}
><
/Switch
>
<
Popconfirm
title
=
"确定是否要删除所选通道?"
content
=
"此修改将不可逆"
okType
=
{
'danger'
}
onConfirm
=
{
batchDeleteChannels
}
disabled
=
{
!
enableBatchDelete
}
position
=
{
'top'
}
>
<
Button
theme
=
'light'
type
=
'primary'
style
=
{{
marginRight
:
8
}}
onClick
=
{
refresh
}
disabled
=
{
!
enableBatchDelete
}
theme
=
"light"
type
=
"danger"
style
=
{{
marginRight
:
8
}
}
>
刷新
删除所选通道
<
/Button
>
<
/Space
>
<
/div
>
<
div
style
=
{{
marginTop
:
20
}}
>
<
Space
>
<
Typography
.
Text
strong
>
开启批量删除
<
/Typography.Text
>
<
Switch
label
=
'开启批量删除'
uncheckedText
=
'关'
aria
-
label
=
'是否开启批量删除'
onChange
=
{(
v
)
=>
{
setEnableBatchDelete
(
v
);
}}
><
/Switch
>
<
Popconfirm
title
=
'确定是否要删除所选通道?'
content
=
'此修改将不可逆'
okType
=
{
'danger'
}
onConfirm
=
{
batchDeleteChannels
}
disabled
=
{
!
enableBatchDelete
}
position
=
{
'top'
}
>
<
Button
disabled
=
{
!
enableBatchDelete
}
theme
=
'light'
type
=
'danger'
style
=
{{
marginRight
:
8
}}
>
删除所选通道
<
/Button
>
<
/Popconfirm
>
<
Popconfirm
title
=
'确定是否要修复数据库一致性?'
content
=
'进行该操作时,可能导致渠道访问错误,请仅在数据库出现问题时使用'
okType
=
{
'warning'
}
onConfirm
=
{
fixChannelsAbilities
}
position
=
{
'top'
}
>
<
Button
theme
=
'light'
type
=
'secondary'
style
=
{{
marginRight
:
8
}}
>
修复数据库一致性
<
/Button
>
<
/Popconfirm
>
<
/Space
>
<
/div
>
<
/Popconfirm
>
<
Popconfirm
title
=
"确定是否要修复数据库一致性?"
content
=
"进行该操作时,可能导致渠道访问错误,请仅在数据库出现问题时使用"
okType
=
{
'warning'
}
onConfirm
=
{
fixChannelsAbilities
}
position
=
{
'top'
}
>
<
Button
theme
=
"light"
type
=
"secondary"
style
=
{{
marginRight
:
8
}}
>
修复数据库一致性
<
/Button
>
<
/Popconfirm
>
<
/Space
>
<
/div
>
<
Table
className
=
{
'channel-table'
}
style
=
{{
marginTop
:
15
}}
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
currentPage
:
activePage
,
pageSize
:
pageSize
,
total
:
channelCount
,
pageSizeOpts
:
[
10
,
20
,
50
,
100
],
showSizeChanger
:
true
,
formatPageText
:
(
page
)
=>
''
,
onPageSizeChange
:
(
size
)
=>
{
handlePageSizeChange
(
size
).
then
();
},
onPageChange
:
handlePageChange
,
}}
loading
=
{
loading
}
onRow
=
{
handleRow
}
rowSelection
=
{
enableBatchDelete
?
{
onChange
:
(
selectedRowKeys
,
selectedRows
)
=>
{
// console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
setSelectedChannels
(
selectedRows
);
},
}
:
null
<
Table
className
=
{
'channel-table'
}
style
=
{{
marginTop
:
15
}}
columns
=
{
columns
}
dataSource
=
{
pageData
}
pagination
=
{{
currentPage
:
activePage
,
pageSize
:
pageSize
,
total
:
channelCount
,
pageSizeOpts
:
[
10
,
20
,
50
,
100
],
showSizeChanger
:
true
,
formatPageText
:
(
page
)
=>
''
,
onPageSizeChange
:
(
size
)
=>
{
handlePageSizeChange
(
size
).
then
();
},
onPageChange
:
handlePageChange
}}
loading
=
{
loading
}
onRow
=
{
handleRow
}
rowSelection
=
{
enableBatchDelete
?
{
onChange
:
(
selectedRowKeys
,
selectedRows
)
=>
{
// console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows);
setSelectedChannels
(
selectedRows
);
}
}
/
>
<
/
>
:
null
}
/
>
<
/
>
);
};
...
...
web/src/components/TextInput.js
0 → 100644
View file @
325de987
import
{
Input
,
Typography
}
from
'@douyinfe/semi-ui'
;
import
React
from
'react'
;
const
TextInput
=
({
label
,
name
,
value
,
onChange
,
placeholder
,
type
=
'text'
})
=>
{
return
(
<>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
{
label
}
<
/Typography.Text
>
<
/div
>
<
Input
name
=
{
name
}
placeholder
=
{
placeholder
}
onChange
=
{(
value
)
=>
onChange
(
value
)}
value
=
{
value
}
autoComplete
=
"new-password"
/>
<
/
>
);
}
export
default
TextInput
;
\ No newline at end of file
web/src/helpers/render.js
View file @
325de987
...
...
@@ -67,6 +67,8 @@ export function renderQuotaNumberWithDigit(num, digits = 2) {
}
export
function
renderNumberWithPoint
(
num
)
{
if
(
num
===
undefined
)
return
''
;
num
=
num
.
toFixed
(
2
);
if
(
num
>=
100000
)
{
// Convert number to string to manipulate it
...
...
web/src/pages/Channel/EditChannel.js
View file @
325de987
...
...
@@ -6,7 +6,7 @@ import {
showError
,
showInfo
,
showSuccess
,
verifyJSON
,
verifyJSON
}
from
'../../helpers'
;
import
{
CHANNEL_OPTIONS
}
from
'../../constants'
;
import
Title
from
'@douyinfe/semi-ui/lib/es/typography/title'
;
...
...
@@ -21,7 +21,7 @@ import {
Select
,
TextArea
,
Checkbox
,
Banner
,
Banner
}
from
'@douyinfe/semi-ui'
;
import
{
Divider
}
from
'semantic-ui-react'
;
import
{
getChannelModels
,
loadChannelModels
}
from
'../../components/utils.js'
;
...
...
@@ -30,19 +30,19 @@ import axios from 'axios';
const
MODEL_MAPPING_EXAMPLE
=
{
'gpt-3.5-turbo-0301'
:
'gpt-3.5-turbo'
,
'gpt-4-0314'
:
'gpt-4'
,
'gpt-4-32k-0314'
:
'gpt-4-32k'
,
'gpt-4-32k-0314'
:
'gpt-4-32k'
};
const
STATUS_CODE_MAPPING_EXAMPLE
=
{
400
:
'500'
,
400
:
'500'
};
const
REGION_EXAMPLE
=
{
"default"
:
"us-central1"
,
"claude-3-5-sonnet-20240620"
:
"europe-west1"
}
'default'
:
'us-central1'
,
'claude-3-5-sonnet-20240620'
:
'europe-west1'
}
;
const
fetchButtonTips
=
"1. 新建渠道时,请求通过当前浏览器发出;2. 编辑已有渠道,请求通过后端服务器发出"
const
fetchButtonTips
=
'1. 新建渠道时,请求通过当前浏览器发出;2. 编辑已有渠道,请求通过后端服务器发出'
;
function
type2secretPrompt
(
type
)
{
// inputs.type === 15 ? '按照如下格式输入:APIKey|SecretKey' : (inputs.type === 18 ? '按照如下格式输入:APPID|APISecret|APIKey' : '请输入渠道对应的鉴权密钥')
...
...
@@ -84,6 +84,9 @@ const EditChannel = (props) => {
auto_ban
:
1
,
test_model
:
''
,
groups
:
[
'default'
],
priority
:
0
,
weight
:
0
,
tag
:
''
};
const
[
batch
,
setBatch
]
=
useState
(
false
);
const
[
autoBan
,
setAutoBan
]
=
useState
(
true
);
...
...
@@ -108,7 +111,7 @@ const EditChannel = (props) => {
'mj_blend'
,
'mj_upscale'
,
'mj_describe'
,
'mj_uploads'
,
'mj_uploads'
];
break
;
case
5
:
...
...
@@ -128,13 +131,13 @@ const EditChannel = (props) => {
'mj_high_variation'
,
'mj_low_variation'
,
'mj_pan'
,
'mj_uploads'
,
'mj_uploads'
];
break
;
case
36
:
localModels
=
[
'suno_music'
,
'suno_lyrics'
,
'suno_lyrics'
];
break
;
default
:
...
...
@@ -171,7 +174,7 @@ const EditChannel = (props) => {
data
.
model_mapping
=
JSON
.
stringify
(
JSON
.
parse
(
data
.
model_mapping
),
null
,
2
,
2
);
}
setInputs
(
data
);
...
...
@@ -190,61 +193,60 @@ const EditChannel = (props) => {
const
fetchUpstreamModelList
=
async
(
name
)
=>
{
if
(
inputs
[
"type"
]
!==
1
)
{
showError
(
"仅支持 OpenAI 接口格式"
)
if
(
inputs
[
'type'
]
!==
1
)
{
showError
(
'仅支持 OpenAI 接口格式'
);
return
;
}
setLoading
(
true
)
const
models
=
inputs
[
"models"
]
||
[]
setLoading
(
true
)
;
const
models
=
inputs
[
'models'
]
||
[];
let
err
=
false
;
if
(
isEdit
)
{
const
res
=
await
API
.
get
(
"/api/channel/fetch_models/"
+
channelId
)
const
res
=
await
API
.
get
(
'/api/channel/fetch_models/'
+
channelId
);
if
(
res
.
data
&&
res
.
data
?.
success
)
{
models
.
push
(...
res
.
data
.
data
)
models
.
push
(...
res
.
data
.
data
)
;
}
else
{
err
=
true
err
=
true
;
}
}
else
{
if
(
!
inputs
?.[
"key"
])
{
showError
(
"请填写密钥"
)
err
=
true
if
(
!
inputs
?.[
'key'
])
{
showError
(
'请填写密钥'
);
err
=
true
;
}
else
{
try
{
const
host
=
new
URL
((
inputs
[
"base_url"
]
||
"https://api.openai.com"
))
const
host
=
new
URL
((
inputs
[
'base_url'
]
||
'https://api.openai.com'
));
const
url
=
`https://
${
host
.
hostname
}
/v1/models`
;
const
key
=
inputs
[
"key"
];
const
key
=
inputs
[
'key'
];
const
res
=
await
axios
.
get
(
url
,
{
headers
:
{
'Authorization'
:
`Bearer
${
key
}
`
}
})
})
;
if
(
res
.
data
&&
res
.
data
?.
success
)
{
models
.
push
(...
res
.
data
.
data
.
map
((
model
)
=>
model
.
id
))
models
.
push
(...
res
.
data
.
data
.
map
((
model
)
=>
model
.
id
))
;
}
else
{
err
=
true
err
=
true
;
}
}
catch
(
error
)
{
err
=
true
}
catch
(
error
)
{
err
=
true
;
}
}
}
if
(
!
err
)
{
handleInputChange
(
name
,
Array
.
from
(
new
Set
(
models
)));
showSuccess
(
"获取模型列表成功"
);
showSuccess
(
'获取模型列表成功'
);
}
else
{
showError
(
'获取模型列表失败'
);
}
setLoading
(
false
);
}
}
;
const
fetchModels
=
async
()
=>
{
try
{
let
res
=
await
API
.
get
(
`/api/channel/models`
);
let
localModelOptions
=
res
.
data
.
data
.
map
((
model
)
=>
({
label
:
model
.
id
,
value
:
model
.
id
,
value
:
model
.
id
}));
setOriginModelOptions
(
localModelOptions
);
setFullModels
(
res
.
data
.
data
.
map
((
model
)
=>
model
.
id
));
...
...
@@ -253,7 +255,7 @@ const EditChannel = (props) => {
.
filter
((
model
)
=>
{
return
model
.
id
.
startsWith
(
'gpt-3'
)
||
model
.
id
.
startsWith
(
'text-'
);
})
.
map
((
model
)
=>
model
.
id
)
,
.
map
((
model
)
=>
model
.
id
)
);
}
catch
(
error
)
{
showError
(
error
.
message
);
...
...
@@ -269,8 +271,8 @@ const EditChannel = (props) => {
setGroupOptions
(
res
.
data
.
data
.
map
((
group
)
=>
({
label
:
group
,
value
:
group
,
}))
,
value
:
group
}))
);
}
catch
(
error
)
{
showError
(
error
.
message
);
...
...
@@ -283,7 +285,7 @@ const EditChannel = (props) => {
if
(
!
localModelOptions
.
find
((
option
)
=>
option
.
key
===
model
))
{
localModelOptions
.
push
({
label
:
model
,
value
:
model
,
value
:
model
});
}
});
...
...
@@ -294,7 +296,8 @@ const EditChannel = (props) => {
fetchModels
().
then
();
fetchGroups
().
then
();
if
(
isEdit
)
{
loadChannel
().
then
(()
=>
{});
loadChannel
().
then
(()
=>
{
});
}
else
{
setInputs
(
originInputs
);
let
localModels
=
getChannelModels
(
inputs
.
type
);
...
...
@@ -320,7 +323,7 @@ const EditChannel = (props) => {
if
(
localInputs
.
base_url
&&
localInputs
.
base_url
.
endsWith
(
'/'
))
{
localInputs
.
base_url
=
localInputs
.
base_url
.
slice
(
0
,
localInputs
.
base_url
.
length
-
1
,
localInputs
.
base_url
.
length
-
1
);
}
if
(
localInputs
.
type
===
3
&&
localInputs
.
other
===
''
)
{
...
...
@@ -341,7 +344,7 @@ const EditChannel = (props) => {
if
(
isEdit
)
{
res
=
await
API
.
put
(
`/api/channel/`
,
{
...
localInputs
,
id
:
parseInt
(
channelId
)
,
id
:
parseInt
(
channelId
)
});
}
else
{
res
=
await
API
.
post
(
`/api/channel/`
,
localInputs
);
...
...
@@ -378,7 +381,7 @@ const EditChannel = (props) => {
// 添加到下拉选项
key
:
model
,
text
:
model
,
value
:
model
,
value
:
model
});
}
else
if
(
model
)
{
showError
(
'某些模型已存在!'
);
...
...
@@ -409,11 +412,11 @@ const EditChannel = (props) => {
footer
=
{
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'flex-end'
}}
>
<
Space
>
<
Button
theme
=
'solid'
size
=
{
'large'
}
onClick
=
{
submit
}
>
<
Button
theme
=
"solid"
size
=
{
'large'
}
onClick
=
{
submit
}
>
提交
<
/Button
>
<
Button
theme
=
'solid'
theme
=
"solid"
size
=
{
'large'
}
type
=
{
'tertiary'
}
onClick
=
{
handleCancel
}
...
...
@@ -432,7 +435,7 @@ const EditChannel = (props) => {
<
Typography
.
Text
strong
>
类型:
<
/Typography.Text
>
<
/div
>
<
Select
name
=
'type'
name
=
"type"
required
optionList
=
{
CHANNEL_OPTIONS
}
value
=
{
inputs
.
type
}
...
...
@@ -450,8 +453,8 @@ const EditChannel = (props) => {
,因为
One
API
会把请求体中的
model
参数替换为你的部署名称(模型名称中的点会被剔除),
<
a
target
=
'_blank'
href
=
'https://github.com/songquanpeng/one-api/issues/133?notification_referrer_id=NT_kwDOAmJSYrM2NjIwMzI3NDgyOjM5OTk4MDUw#issuecomment-1571602271'
target
=
"_blank"
href
=
"https://github.com/songquanpeng/one-api/issues/133?notification_referrer_id=NT_kwDOAmJSYrM2NjIwMzI3NDgyOjM5OTk4MDUw#issuecomment-1571602271"
>
图片演示
<
/a
>
...
...
@@ -466,8 +469,8 @@ const EditChannel = (props) => {
<
/Typography.Text
>
<
/div
>
<
Input
label
=
'AZURE_OPENAI_ENDPOINT'
name
=
'azure_base_url'
label
=
"AZURE_OPENAI_ENDPOINT"
name
=
"azure_base_url"
placeholder
=
{
'请输入 AZURE_OPENAI_ENDPOINT,例如:https://docs-test-001.openai.azure.com'
}
...
...
@@ -475,14 +478,14 @@ const EditChannel = (props) => {
handleInputChange
(
'base_url'
,
value
);
}}
value
=
{
inputs
.
base_url
}
autoComplete
=
'new-password'
autoComplete
=
"new-password"
/>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
默认
API
版本:
<
/Typography.Text
>
<
/div
>
<
Input
label
=
'默认 API 版本'
name
=
'azure_other'
label
=
"默认 API 版本"
name
=
"azure_other"
placeholder
=
{
'请输入默认 API 版本,例如:2023-06-01-preview,该配置可以被实际的请求查询参数所覆盖'
}
...
...
@@ -490,7 +493,7 @@ const EditChannel = (props) => {
handleInputChange
(
'other'
,
value
);
}}
value
=
{
inputs
.
other
}
autoComplete
=
'new-password'
autoComplete
=
"new-password"
/>
<
/
>
)}
...
...
@@ -512,7 +515,7 @@ const EditChannel = (props) => {
<
/Typography.Text
>
<
/div
>
<
Input
name
=
'base_url'
name
=
"base_url"
placeholder
=
{
'请输入完整的URL,例如:https://api.openai.com/v1/chat/completions'
}
...
...
@@ -520,49 +523,84 @@ const EditChannel = (props) => {
handleInputChange
(
'base_url'
,
value
);
}}
value
=
{
inputs
.
base_url
}
autoComplete
=
'new-password'
autoComplete
=
"new-password"
/>
<
/
>
)}
{
inputs
.
type
!==
3
&&
inputs
.
type
!==
8
&&
inputs
.
type
!==
22
&&
inputs
.
type
!==
36
&&
(
<>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
代理:
<
/Typography.Text
>
<
/div
>
<
Input
label
=
"代理"
name
=
"base_url"
placeholder
=
{
'此项可选,用于通过代理站来进行 API 调用'
}
onChange
=
{(
value
)
=>
{
handleInputChange
(
'base_url'
,
value
);
}}
value
=
{
inputs
.
base_url
}
autoComplete
=
"new-password"
/>
<
/
>
)}
{
inputs
.
type
===
22
&&
(
<>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
私有部署地址:
<
/Typography.Text
>
<
/div
>
<
Input
name
=
"base_url"
placeholder
=
{
'请输入私有部署地址,格式为:https://fastgpt.run/api/openapi'
}
onChange
=
{(
value
)
=>
{
handleInputChange
(
'base_url'
,
value
);
}}
value
=
{
inputs
.
base_url
}
autoComplete
=
"new-password"
/>
<
/
>
)}
{
inputs
.
type
===
36
&&
(
<>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
注意非
Chat
API
,请务必填写正确的
API
地址,否则可能导致无法使用
<
/Typography.Text
>
<
/div
>
<
Input
name
=
'base_url'
placeholder
=
{
'请输入到 /suno 前的路径,通常就是域名,例如:https://api.example.com '
}
onChange
=
{(
value
)
=>
{
handleInputChange
(
'base_url'
,
value
);
}}
value
=
{
inputs
.
base_url
}
autoComplete
=
'new-password'
/>
<
/
>
<>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
注意非
Chat
API
,请务必填写正确的
API
地址,否则可能导致无法使用
<
/Typography.Text
>
<
/div
>
<
Input
name
=
"base_url"
placeholder
=
{
'请输入到 /suno 前的路径,通常就是域名,例如:https://api.example.com '
}
onChange
=
{(
value
)
=>
{
handleInputChange
(
'base_url'
,
value
);
}}
value
=
{
inputs
.
base_url
}
autoComplete
=
"new-password"
/>
<
/
>
)}
<
div
style
=
{{
marginTop
:
10
}}
>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
名称:
<
/Typography.Text
>
<
/div
>
<
Input
required
name
=
'name'
required
name
=
"name"
placeholder
=
{
'请为渠道命名'
}
onChange
=
{(
value
)
=>
{
handleInputChange
(
'name'
,
value
);
}}
value
=
{
inputs
.
name
}
autoComplete
=
'new-password'
autoComplete
=
"new-password"
/>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
分组:
<
/Typography.Text
>
<
/div
>
<
Select
placeholder
=
{
'请选择可以使用该渠道的分组'
}
name
=
'groups'
name
=
"groups"
required
multiple
selection
...
...
@@ -572,7 +610,7 @@ const EditChannel = (props) => {
handleInputChange
(
'groups'
,
value
);
}}
value
=
{
inputs
.
groups
}
autoComplete
=
'new-password'
autoComplete
=
"new-password"
optionList
=
{
groupOptions
}
/
>
{
inputs
.
type
===
18
&&
(
...
...
@@ -581,7 +619,7 @@ const EditChannel = (props) => {
<
Typography
.
Text
strong
>
模型版本:
<
/Typography.Text
>
<
/div
>
<
Input
name
=
'other'
name
=
"other"
placeholder
=
{
'请输入星火大模型版本,注意是接口地址中的版本号,例如:v2.1'
}
...
...
@@ -589,7 +627,7 @@ const EditChannel = (props) => {
handleInputChange
(
'other'
,
value
);
}}
value
=
{
inputs
.
other
}
autoComplete
=
'new-password'
autoComplete
=
"new-password"
/>
<
/
>
)}
...
...
@@ -599,7 +637,7 @@ const EditChannel = (props) => {
<
Typography
.
Text
strong
>
部署地区:
<
/Typography.Text
>
<
/div
>
<
TextArea
name
=
'other'
name
=
"other"
placeholder
=
{
'请输入部署地区,例如:us-central1\n支持使用模型映射格式\n'
+
'{\n'
+
...
...
@@ -612,18 +650,18 @@ const EditChannel = (props) => {
handleInputChange
(
'other'
,
value
);
}}
value
=
{
inputs
.
other
}
autoComplete
=
'new-password'
autoComplete
=
"new-password"
/>
<
Typography
.
Text
style
=
{{
color
:
'rgba(var(--semi-blue-5), 1)'
,
userSelect
:
'none'
,
cursor
:
'pointer'
,
cursor
:
'pointer'
}}
onClick
=
{()
=>
{
handleInputChange
(
'other'
,
JSON
.
stringify
(
REGION_EXAMPLE
,
null
,
2
)
,
JSON
.
stringify
(
REGION_EXAMPLE
,
null
,
2
)
);
}}
>
...
...
@@ -637,14 +675,14 @@ const EditChannel = (props) => {
<
Typography
.
Text
strong
>
知识库
ID
:
<
/Typography.Text
>
<
/div
>
<
Input
label
=
'知识库 ID'
name
=
'other'
label
=
"知识库 ID"
name
=
"other"
placeholder
=
{
'请输入知识库 ID,例如:123456'
}
onChange
=
{(
value
)
=>
{
handleInputChange
(
'other'
,
value
);
}}
value
=
{
inputs
.
other
}
autoComplete
=
'new-password'
autoComplete
=
"new-password"
/>
<
/
>
)}
...
...
@@ -654,7 +692,7 @@ const EditChannel = (props) => {
<
Typography
.
Text
strong
>
Account
ID
:
<
/Typography.Text
>
<
/div
>
<
Input
name
=
'other'
name
=
"other"
placeholder
=
{
'请输入Account ID,例如:d6b5da8hk1awo8nap34ube6gh'
}
...
...
@@ -662,7 +700,7 @@ const EditChannel = (props) => {
handleInputChange
(
'other'
,
value
);
}}
value
=
{
inputs
.
other
}
autoComplete
=
'new-password'
autoComplete
=
"new-password"
/>
<
/
>
)}
...
...
@@ -671,7 +709,7 @@ const EditChannel = (props) => {
<
/div
>
<
Select
placeholder
=
{
'请选择该渠道所支持的模型'
}
name
=
'models'
name
=
"models"
required
multiple
selection
...
...
@@ -679,13 +717,13 @@ const EditChannel = (props) => {
handleInputChange
(
'models'
,
value
);
}}
value
=
{
inputs
.
models
}
autoComplete
=
'new-password'
autoComplete
=
"new-password"
optionList
=
{
modelOptions
}
/
>
<
div
style
=
{{
lineHeight
:
'40px'
,
marginBottom
:
'12px'
}}
>
<
Space
>
<
Button
type
=
'primary'
type
=
"primary"
onClick
=
{()
=>
{
handleInputChange
(
'models'
,
basicModels
);
}}
...
...
@@ -693,7 +731,7 @@ const EditChannel = (props) => {
填入相关模型
<
/Button
>
<
Button
type
=
'secondary'
type
=
"secondary"
onClick
=
{()
=>
{
handleInputChange
(
'models'
,
fullModels
);
}}
...
...
@@ -702,7 +740,7 @@ const EditChannel = (props) => {
<
/Button
>
<
Tooltip
content
=
{
fetchButtonTips
}
>
<
Button
type
=
'tertiary'
type
=
"tertiary"
onClick
=
{()
=>
{
fetchUpstreamModelList
(
'models'
);
}}
...
...
@@ -711,7 +749,7 @@ const EditChannel = (props) => {
<
/Button
>
<
/Tooltip
>
<
Button
type
=
'warning'
type
=
"warning"
onClick
=
{()
=>
{
handleInputChange
(
'models'
,
[]);
}}
...
...
@@ -721,11 +759,11 @@ const EditChannel = (props) => {
<
/Space
>
<
Input
addonAfter
=
{
<
Button
type
=
'primary'
onClick
=
{
addCustomModels
}
>
<
Button
type
=
"primary"
onClick
=
{
addCustomModels
}
>
填入
<
/Button
>
}
placeholder
=
'输入自定义模型名称'
placeholder
=
"输入自定义模型名称"
value
=
{
customModel
}
onChange
=
{(
value
)
=>
{
setCustomModel
(
value
.
trim
());
...
...
@@ -737,24 +775,24 @@ const EditChannel = (props) => {
<
/div
>
<
TextArea
placeholder
=
{
`此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称,例如:\n
${
JSON
.
stringify
(
MODEL_MAPPING_EXAMPLE
,
null
,
2
)}
`
}
name
=
'model_mapping'
name
=
"model_mapping"
onChange
=
{(
value
)
=>
{
handleInputChange
(
'model_mapping'
,
value
);
}}
autosize
value
=
{
inputs
.
model_mapping
}
autoComplete
=
'new-password'
autoComplete
=
"new-password"
/>
<
Typography
.
Text
style
=
{{
color
:
'rgba(var(--semi-blue-5), 1)'
,
userSelect
:
'none'
,
cursor
:
'pointer'
,
cursor
:
'pointer'
}}
onClick
=
{()
=>
{
handleInputChange
(
'model_mapping'
,
JSON
.
stringify
(
MODEL_MAPPING_EXAMPLE
,
null
,
2
)
,
JSON
.
stringify
(
MODEL_MAPPING_EXAMPLE
,
null
,
2
)
);
}}
>
...
...
@@ -765,8 +803,8 @@ const EditChannel = (props) => {
<
/div
>
{
batch
?
(
<
TextArea
label
=
'密钥'
name
=
'key'
label
=
"密钥"
name
=
"key"
required
placeholder
=
{
'请输入密钥,一行一个'
}
onChange
=
{(
value
)
=>
{
...
...
@@ -774,14 +812,14 @@ const EditChannel = (props) => {
}}
value
=
{
inputs
.
key
}
style
=
{{
minHeight
:
150
,
fontFamily
:
'JetBrains Mono, Consolas'
}}
autoComplete
=
'new-password'
autoComplete
=
"new-password"
/>
)
:
(
<>
{
inputs
.
type
===
41
?
(
<
TextArea
label
=
'鉴权json'
name
=
'key'
label
=
"鉴权json"
name
=
"key"
required
placeholder
=
{
'{\n'
+
' "type": "service_account",\n'
+
...
...
@@ -801,23 +839,36 @@ const EditChannel = (props) => {
}}
autosize
=
{{
minRows
:
10
}}
value
=
{
inputs
.
key
}
autoComplete
=
'new-password'
autoComplete
=
"new-password"
/>
)
:
(
<
Input
label
=
'密钥'
name
=
'key'
label
=
"密钥"
name
=
"key"
required
placeholder
=
{
type2secretPrompt
(
inputs
.
type
)}
onChange
=
{(
value
)
=>
{
handleInputChange
(
'key'
,
value
);
}}
value
=
{
inputs
.
key
}
autoComplete
=
'new-password'
autoComplete
=
"new-password"
/>
)
}
<
/
>
<
/
>
)}
{
!
isEdit
&&
(
<
div
style
=
{{
marginTop
:
10
,
display
:
'flex'
}}
>
<
Space
>
<
Checkbox
checked
=
{
batch
}
label
=
"批量创建"
name
=
"batch"
onChange
=
{()
=>
setBatch
(
!
batch
)}
/
>
<
Typography
.
Text
strong
>
批量创建
<
/Typography.Text
>
<
/Space
>
<
/div
>
)}
{
inputs
.
type
===
1
&&
(
<>
...
...
@@ -825,9 +876,9 @@ const EditChannel = (props) => {
<
Typography
.
Text
strong
>
组织:
<
/Typography.Text
>
<
/div
>
<
Input
label
=
'组织,可选,不填则为默认组织'
name
=
'openai_organization'
placeholder
=
'请输入组织org-xxx'
label
=
"组织,可选,不填则为默认组织"
name
=
"openai_organization"
placeholder
=
"请输入组织org-xxx"
onChange
=
{(
value
)
=>
{
handleInputChange
(
'openai_organization'
,
value
);
}}
...
...
@@ -839,8 +890,8 @@ const EditChannel = (props) => {
<
Typography
.
Text
strong
>
默认测试模型:
<
/Typography.Text
>
<
/div
>
<
Input
name
=
'test_model'
placeholder
=
'不填则为模型列表第一个'
name
=
"test_model"
placeholder
=
"不填则为模型列表第一个"
onChange
=
{(
value
)
=>
{
handleInputChange
(
'test_model'
,
value
);
}}
...
...
@@ -849,7 +900,7 @@ const EditChannel = (props) => {
<
div
style
=
{{
marginTop
:
10
,
display
:
'flex'
}}
>
<
Space
>
<
Checkbox
name
=
'auto_ban'
name
=
"auto_ban"
checked
=
{
autoBan
}
onChange
=
{()
=>
{
setAutoBan
(
!
autoBan
);
...
...
@@ -861,55 +912,6 @@ const EditChannel = (props) => {
<
/Typography.Text
>
<
/Space
>
<
/div
>
{
!
isEdit
&&
(
<
div
style
=
{{
marginTop
:
10
,
display
:
'flex'
}}
>
<
Space
>
<
Checkbox
checked
=
{
batch
}
label
=
'批量创建'
name
=
'batch'
onChange
=
{()
=>
setBatch
(
!
batch
)}
/
>
<
Typography
.
Text
strong
>
批量创建
<
/Typography.Text
>
<
/Space
>
<
/div
>
)}
{
inputs
.
type
!==
3
&&
inputs
.
type
!==
8
&&
inputs
.
type
!==
22
&&
inputs
.
type
!==
36
&&
(
<>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
代理:
<
/Typography.Text
>
<
/div
>
<
Input
label
=
'代理'
name
=
'base_url'
placeholder
=
{
'此项可选,用于通过代理站来进行 API 调用'
}
onChange
=
{(
value
)
=>
{
handleInputChange
(
'base_url'
,
value
);
}}
value
=
{
inputs
.
base_url
}
autoComplete
=
'new-password'
/>
<
/
>
)}
{
inputs
.
type
===
22
&&
(
<>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
私有部署地址:
<
/Typography.Text
>
<
/div
>
<
Input
name
=
'base_url'
placeholder
=
{
'请输入私有部署地址,格式为:https://fastgpt.run/api/openapi'
}
onChange
=
{(
value
)
=>
{
handleInputChange
(
'base_url'
,
value
);
}}
value
=
{
inputs
.
base_url
}
autoComplete
=
'new-password'
/>
<
/
>
)}
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
状态码复写(仅影响本地判断,不修改返回到上游的状态码):
...
...
@@ -917,43 +919,74 @@ const EditChannel = (props) => {
<
/div
>
<
TextArea
placeholder
=
{
`此项可选,用于复写返回的状态码,比如将claude渠道的400错误复写为500(用于重试),请勿滥用该功能,例如:\n
${
JSON
.
stringify
(
STATUS_CODE_MAPPING_EXAMPLE
,
null
,
2
)}
`
}
name
=
'status_code_mapping'
name
=
"status_code_mapping"
onChange
=
{(
value
)
=>
{
handleInputChange
(
'status_code_mapping'
,
value
);
}}
autosize
value
=
{
inputs
.
status_code_mapping
}
autoComplete
=
'new-password'
autoComplete
=
"new-password"
/>
<
Typography
.
Text
style
=
{{
color
:
'rgba(var(--semi-blue-5), 1)'
,
userSelect
:
'none'
,
cursor
:
'pointer'
,
cursor
:
'pointer'
}}
onClick
=
{()
=>
{
handleInputChange
(
'status_code_mapping'
,
JSON
.
stringify
(
STATUS_CODE_MAPPING_EXAMPLE
,
null
,
2
)
,
JSON
.
stringify
(
STATUS_CODE_MAPPING_EXAMPLE
,
null
,
2
)
);
}}
>
填入模板
<
/Typography.Text
>
{
/*<div style={{ marginTop: 10 }}>*/
}
{
/* <Typography.Text strong>*/
}
{
/* 最大请求token(0表示不限制):*/
}
{
/* </Typography.Text>*/
}
{
/*</div>*/
}
{
/*<Input*/
}
{
/* label='最大请求token'*/
}
{
/* name='max_input_tokens'*/
}
{
/* placeholder='默认为0,表示不限制'*/
}
{
/* onChange={(value) => {*/
}
{
/* handleInputChange('max_input_tokens', value);*/
}
{
/* }}*/
}
{
/* value={inputs.max_input_tokens}*/
}
{
/*/>*/
}
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
渠道标签
<
/Typography.Text
>
<
/div
>
<
Input
label
=
"渠道标签"
name
=
"tag"
placeholder
=
{
'渠道标签'
}
onChange
=
{(
value
)
=>
{
handleInputChange
(
'tag'
,
value
);
}}
value
=
{
inputs
.
tag
}
autoComplete
=
"new-password"
/>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
渠道优先级
<
/Typography.Text
>
<
/div
>
<
Input
label
=
"渠道优先级"
name
=
"priority"
placeholder
=
{
'渠道优先级'
}
onChange
=
{(
value
)
=>
{
handleInputChange
(
'priority'
,
parseInt
(
value
));
}}
value
=
{
inputs
.
priority
}
autoComplete
=
"new-password"
/>
<
div
style
=
{{
marginTop
:
10
}}
>
<
Typography
.
Text
strong
>
渠道权重
<
/Typography.Text
>
<
/div
>
<
Input
label
=
"渠道权重"
name
=
"weight"
placeholder
=
{
'渠道权重'
}
onChange
=
{(
value
)
=>
{
handleInputChange
(
'weight'
,
parseInt
(
value
));
}}
value
=
{
inputs
.
weight
}
autoComplete
=
"new-password"
/>
<
/Spin
>
<
/SideSheet
>
<
/
>
...
...
web/src/pages/Channel/EditTagModal.js
0 → 100644
View file @
325de987
import
React
,
{
useState
,
useEffect
}
from
'react'
;
import
{
API
,
showError
,
showSuccess
}
from
'../../helpers'
;
import
{
SideSheet
,
Space
,
Button
,
Input
,
Typography
,
Spin
,
Modal
}
from
'@douyinfe/semi-ui'
;
import
TextInput
from
'../../components/TextInput.js'
;
const
EditTagModal
=
(
props
)
=>
{
const
{
visible
,
tag
,
handleClose
,
refresh
}
=
props
;
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
originInputs
=
{
tag
:
''
,
newTag
:
null
,
}
const
[
inputs
,
setInputs
]
=
useState
(
originInputs
);
const
handleSave
=
async
()
=>
{
setLoading
(
true
);
let
data
=
{
tag
:
tag
,
}
let
shouldSave
=
true
;
if
(
inputs
.
newTag
===
tag
)
{
setLoading
(
false
);
return
;
}
data
.
newTag
=
inputs
.
newTag
;
if
(
data
.
newTag
===
''
)
{
Modal
.
confirm
({
title
:
'解散标签'
,
content
:
'确定要解散标签吗?'
,
onCancel
:
()
=>
{
setLoading
(
false
);
},
onOk
:
async
()
=>
{
await
submit
(
data
);
}
});
}
else
{
await
submit
(
data
);
}
setLoading
(
false
);
};
const
submit
=
async
(
data
)
=>
{
try
{
const
res
=
await
API
.
put
(
'/api/channel/tag'
,
data
);
if
(
res
?.
data
?.
success
)
{
showSuccess
(
'标签更新成功!'
);
refresh
();
handleClose
();
}
}
catch
(
error
)
{
showError
(
error
);
}
}
useEffect
(()
=>
{
setInputs
({
...
originInputs
,
tag
:
tag
,
newTag
:
tag
,
})
},
[
visible
]);
return
(
<
SideSheet
title
=
"编辑标签"
visible
=
{
visible
}
onCancel
=
{
handleClose
}
footer
=
{
<
div
style
=
{{
display
:
'flex'
,
justifyContent
:
'flex-end'
}}
>
<
Space
>
<
Button
onClick
=
{
handleClose
}
>
取消
<
/Button
>
<
Button
type
=
"primary"
onClick
=
{
handleSave
}
loading
=
{
loading
}
>
保存
<
/Button
>
<
/Space
>
<
/div
>
}
>
<
Spin
spinning
=
{
loading
}
>
<
TextInput
label
=
"新标签(留空则解散标签,不会删除标签下的渠道)"
name
=
"newTag"
value
=
{
inputs
.
newTag
}
onChange
=
{(
value
)
=>
setInputs
({
...
inputs
,
newTag
:
value
})}
placeholder
=
"请输入新标签"
/>
<
/Spin
>
<
/SideSheet
>
);
};
export
default
EditTagModal
;
\ No newline at end of file
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