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
Unverified
Commit
1d166532
authored
Jun 28, 2026
by
CaIon
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix: update section titles and improve layout in channel components
parent
2d5a0416
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
642 additions
and
204 deletions
+642
-204
web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
+630
-194
web/default/src/features/channels/components/drawers/sections/channel-advanced-section.tsx
+6
-3
web/default/src/features/channels/components/drawers/sections/channel-api-access-section.tsx
+5
-6
web/default/src/features/channels/components/drawers/sections/channel-auth-section.tsx
+1
-1
No files found.
web/default/src/features/channels/components/drawers/channel-mutate-drawer.tsx
View file @
1d166532
...
@@ -29,8 +29,14 @@ import { zodResolver } from '@hookform/resolvers/zod'
...
@@ -29,8 +29,14 @@ import { zodResolver } from '@hookform/resolvers/zod'
import
{
useQuery
,
useQueryClient
}
from
'@tanstack/react-query'
import
{
useQuery
,
useQueryClient
}
from
'@tanstack/react-query'
import
{
import
{
ArrowRight
,
ArrowRight
,
AlertCircle
,
Boxes
,
CheckCircle2
,
Circle
,
HelpCircle
,
HelpCircle
,
KeyRound
,
Loader2
,
Loader2
,
Server
,
Sparkles
,
Sparkles
,
Trash2
,
Trash2
,
Copy
,
Copy
,
...
@@ -47,15 +53,17 @@ import {
...
@@ -47,15 +53,17 @@ import {
}
from
'lucide-react'
}
from
'lucide-react'
import
{
useTranslation
}
from
'react-i18next'
import
{
useTranslation
}
from
'react-i18next'
import
{
toast
}
from
'sonner'
import
{
toast
}
from
'sonner'
import
{
import
{
ADMIN_PERMISSION_ACTIONS
,
sideDrawerContentClassName
,
ADMIN_PERMISSION_RESOURCES
,
sideDrawerFooterClassName
,
hasPermission
,
sideDrawerFormClassName
,
}
from
'@/lib/admin-permissions'
sideDrawerHeaderClassName
,
import
{
getLobeIcon
}
from
'@/lib/lobe-icon'
sideDrawerSectionClassName
,
import
{
ROLE
}
from
'@/lib/roles'
sideDrawerSwitchItemClassName
,
import
{
useCopyToClipboard
}
from
'@/hooks/use-copy-to-clipboard'
}
from
'@/components/drawer-layout'
import
{
useHiddenClickUnlock
}
from
'@/hooks/use-hidden-click-unlock'
import
{
JsonEditor
}
from
'@/components/json-editor'
import
{
MultiSelect
}
from
'@/components/multi-select'
import
{
Alert
,
AlertDescription
}
from
'@/components/ui/alert'
import
{
Alert
,
AlertDescription
}
from
'@/components/ui/alert'
import
{
Badge
}
from
'@/components/ui/badge'
import
{
Badge
}
from
'@/components/ui/badge'
import
{
Button
}
from
'@/components/ui/button'
import
{
Button
}
from
'@/components/ui/button'
...
@@ -97,20 +105,21 @@ import {
...
@@ -97,20 +105,21 @@ import {
TooltipTrigger
,
TooltipTrigger
,
}
from
'@/components/ui/tooltip'
}
from
'@/components/ui/tooltip'
import
{
import
{
sideDrawerContentClassName
,
sideDrawerFooterClassName
,
sideDrawerFormClassName
,
sideDrawerHeaderClassName
,
sideDrawerSectionClassName
,
sideDrawerSwitchItemClassName
,
}
from
'@/components/drawer-layout'
import
{
JsonEditor
}
from
'@/components/json-editor'
import
{
MultiSelect
}
from
'@/components/multi-select'
import
{
SecureVerificationDialog
,
SecureVerificationDialog
,
useSecureVerification
,
useSecureVerification
,
}
from
'@/features/auth/secure-verification'
}
from
'@/features/auth/secure-verification'
import
{
useCopyToClipboard
}
from
'@/hooks/use-copy-to-clipboard'
import
{
useHiddenClickUnlock
}
from
'@/hooks/use-hidden-click-unlock'
import
{
ADMIN_PERMISSION_ACTIONS
,
ADMIN_PERMISSION_RESOURCES
,
hasPermission
,
}
from
'@/lib/admin-permissions'
import
{
getLobeIcon
}
from
'@/lib/lobe-icon'
import
{
ROLE
}
from
'@/lib/roles'
import
{
cn
}
from
'@/lib/utils'
import
{
useAuthStore
}
from
'@/stores/auth-store'
import
{
useAuthStore
}
from
'@/stores/auth-store'
import
{
import
{
fetchModels
,
fetchModels
,
getAllModels
,
getAllModels
,
...
@@ -122,6 +131,7 @@ import {
...
@@ -122,6 +131,7 @@ import {
}
from
'../../api'
}
from
'../../api'
import
{
import
{
ADD_MODE_OPTIONS
,
ADD_MODE_OPTIONS
,
CHANNEL_STATUS_LABELS
,
CHANNEL_TYPE_OPTIONS
,
CHANNEL_TYPE_OPTIONS
,
CHANNEL_TYPE_WARNINGS
,
CHANNEL_TYPE_WARNINGS
,
ERROR_MESSAGES
,
ERROR_MESSAGES
,
...
@@ -187,6 +197,17 @@ type ModelMappingGuardrail = {
...
@@ -187,6 +197,17 @@ type ModelMappingGuardrail = {
exposedTargetModels
:
string
[]
exposedTargetModels
:
string
[]
}
}
type
ChannelEditorSectionStatus
=
'complete'
|
'configured'
|
'error'
|
'idle'
type
ChannelEditorNavItem
=
{
id
:
string
title
:
string
description
?:
string
statusLabel
:
string
status
:
ChannelEditorSectionStatus
icon
:
ReactNode
}
// Helper functions
// Helper functions
const
createEmptyModelMappingGuardrail
=
():
ModelMappingGuardrail
=>
({
const
createEmptyModelMappingGuardrail
=
():
ModelMappingGuardrail
=>
({
invalidJson
:
false
,
invalidJson
:
false
,
...
@@ -313,6 +334,150 @@ function SubHeading({ title, icon }: { title: string; icon?: ReactNode }) {
...
@@ -313,6 +334,150 @@ function SubHeading({ title, icon }: { title: string; icon?: ReactNode }) {
)
)
}
}
function
ChannelTypeLogo
(
props
:
{
type
:
number
size
?:
number
className
?:
string
})
{
const
isKnownType
=
CHANNEL_TYPE_OPTIONS
.
some
(
(
option
)
=>
option
.
value
===
props
.
type
)
if
(
!
isKnownType
)
{
return
(
<
Server
className=
{
cn
(
'text-muted-foreground shrink-0'
,
props
.
className
)
}
style=
{
{
width
:
props
.
size
??
16
,
height
:
props
.
size
??
16
,
}
}
aria
-
hidden=
'true'
/>
)
}
return
(
<
span
className=
{
cn
(
'inline-flex shrink-0'
,
props
.
className
)
}
>
{
getLobeIcon
(
`${getChannelTypeIcon(props.type)}.Color`
,
props
.
size
??
16
)
}
</
span
>
)
}
function
getSectionStatusIcon
(
status
:
ChannelEditorSectionStatus
):
ReactNode
{
if
(
status
===
'error'
)
{
return
<
AlertCircle
className=
'h-3.5 w-3.5'
aria
-
hidden=
'true'
/>
}
if
(
status
===
'complete'
||
status
===
'configured'
)
{
return
<
CheckCircle2
className=
'h-3.5 w-3.5'
aria
-
hidden=
'true'
/>
}
return
<
Circle
className=
'h-3.5 w-3.5'
aria
-
hidden=
'true'
/>
}
function
getCompletionStatus
(
hasErrors
:
boolean
,
isComplete
:
boolean
):
ChannelEditorSectionStatus
{
if
(
hasErrors
)
return
'error'
if
(
isComplete
)
return
'complete'
return
'idle'
}
function
getSectionStatusLabel
(
status
:
ChannelEditorSectionStatus
,
t
:
(
key
:
string
)
=>
string
):
string
{
if
(
status
===
'error'
)
return
t
(
'Error'
)
if
(
status
===
'complete'
||
status
===
'configured'
)
return
t
(
'Ready'
)
return
t
(
'Incomplete'
)
}
function
ChannelEditorNav
(
props
:
{
providerLogo
:
ReactNode
providerLabel
:
string
statusLabel
:
string
progressLabel
:
string
navigationLabel
:
string
items
:
ChannelEditorNavItem
[]
})
{
return
(
<
aside
className=
'hidden self-start lg:sticky lg:top-4 lg:z-20 lg:block'
>
<
div
className=
'flex max-h-[calc(100dvh-12rem)] flex-col gap-3 overflow-y-auto overscroll-contain pr-1'
>
<
div
className=
'border-border/60 bg-muted/20 rounded-lg border p-3'
>
<
div
className=
'flex min-w-0 items-center gap-2'
>
<
span
className=
'bg-background flex size-8 shrink-0 items-center justify-center rounded-md border'
>
{
props
.
providerLogo
}
</
span
>
<
div
className=
'min-w-0'
>
<
p
className=
'truncate text-sm font-medium'
>
{
props
.
providerLabel
}
</
p
>
<
p
className=
'text-muted-foreground truncate text-xs'
>
{
props
.
statusLabel
}
·
{
props
.
progressLabel
}
</
p
>
</
div
>
</
div
>
</
div
>
<
nav
className=
'border-border/60 bg-background rounded-lg border p-1'
aria
-
label=
{
props
.
navigationLabel
}
>
{
props
.
items
.
map
((
item
)
=>
{
const
isError
=
item
.
status
===
'error'
const
isDone
=
item
.
status
===
'complete'
||
item
.
status
===
'configured'
return
(
<
button
key=
{
item
.
id
}
type=
'button'
className=
{
cn
(
'hover:bg-muted/60 flex w-full items-start gap-2 rounded-md px-2 py-2 text-left transition-colors'
,
isError
&&
'text-destructive hover:bg-destructive/10'
)
}
onClick=
{
()
=>
{
document
.
querySelector
<
HTMLElement
>
(
`#${item.id}`
)
?.
scrollIntoView
({
behavior
:
'smooth'
,
block
:
'start'
})
}
}
>
<
span
className=
{
cn
(
'bg-muted text-muted-foreground mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-md'
,
isError
&&
'bg-destructive/10 text-destructive'
,
isDone
&&
!
isError
&&
'text-primary'
)
}
>
{
item
.
icon
}
</
span
>
<
span
className=
'min-w-0 flex-1'
>
<
span
className=
'block truncate text-sm font-medium'
>
{
item
.
title
}
</
span
>
{
item
.
description
&&
(
<
span
className=
'text-muted-foreground block truncate text-xs'
>
{
item
.
description
}
</
span
>
)
}
</
span
>
<
span
className=
{
cn
(
'text-muted-foreground mt-1 shrink-0'
,
isError
&&
'text-destructive'
,
isDone
&&
!
isError
&&
'text-primary'
)
}
aria
-
label=
{
item
.
statusLabel
}
>
{
getSectionStatusIcon
(
item
.
status
)
}
</
span
>
</
button
>
)
})
}
</
nav
>
</
div
>
</
aside
>
)
}
export
function
ChannelMutateDrawer
({
export
function
ChannelMutateDrawer
({
open
,
open
,
onOpenChange
,
onOpenChange
,
...
@@ -359,9 +524,9 @@ export function ChannelMutateDrawer({
...
@@ -359,9 +524,9 @@ export function ChannelMutateDrawer({
// Fetch channel details if editing
// Fetch channel details if editing
const
{
data
:
channelData
,
isLoading
:
isChannelLoading
}
=
useQuery
({
const
{
data
:
channelData
,
isLoading
:
isChannelLoading
}
=
useQuery
({
queryKey
:
channelsQueryKeys
.
detail
(
c
urrentRow
?.
i
d
||
0
),
queryKey
:
channelsQueryKeys
.
detail
(
c
hannelI
d
||
0
),
queryFn
:
()
=>
getChannel
(
c
urrentRow
!
.
id
),
queryFn
:
()
=>
getChannel
(
c
hannelId
||
0
),
enabled
:
isEditing
&&
Boolean
(
c
urrentRow
?.
i
d
),
enabled
:
isEditing
&&
Boolean
(
c
hannelI
d
),
})
})
// Fetch available groups
// Fetch available groups
...
@@ -420,7 +585,10 @@ export function ChannelMutateDrawer({
...
@@ -420,7 +585,10 @@ export function ChannelMutateDrawer({
const
keyMode
=
form
.
watch
(
'key_mode'
)
const
keyMode
=
form
.
watch
(
'key_mode'
)
const
currentGroups
=
form
.
watch
(
'group'
)
const
currentGroups
=
form
.
watch
(
'group'
)
const
currentType
=
form
.
watch
(
'type'
)
const
currentType
=
form
.
watch
(
'type'
)
const
currentStatus
=
form
.
watch
(
'status'
)
const
currentBaseUrl
=
form
.
watch
(
'base_url'
)
const
currentBaseUrl
=
form
.
watch
(
'base_url'
)
const
currentKey
=
form
.
watch
(
'key'
)
const
currentOther
=
form
.
watch
(
'other'
)
const
currentModels
=
form
.
watch
(
'models'
)
const
currentModels
=
form
.
watch
(
'models'
)
const
currentName
=
form
.
watch
(
'name'
)
const
currentName
=
form
.
watch
(
'name'
)
const
currentModelMapping
=
form
.
watch
(
'model_mapping'
)
const
currentModelMapping
=
form
.
watch
(
'model_mapping'
)
...
@@ -496,7 +664,7 @@ export function ChannelMutateDrawer({
...
@@ -496,7 +664,7 @@ export function ChannelMutateDrawer({
const
groupOptions
=
useMemo
(()
=>
{
const
groupOptions
=
useMemo
(()
=>
{
if
(
!
groupsData
?.
data
)
return
[]
if
(
!
groupsData
?.
data
)
return
[]
const
allGroups
=
new
Set
([...
groupsData
.
data
,
...(
currentGroups
||
[])])
const
allGroups
=
new
Set
([...
groupsData
.
data
,
...(
currentGroups
||
[])])
return
Array
.
from
(
allGroups
)
.
map
((
group
)
=>
({
return
[...
allGroups
]
.
map
((
group
)
=>
({
value
:
group
,
value
:
group
,
label
:
group
,
label
:
group
,
}))
}))
...
@@ -519,18 +687,110 @@ export function ChannelMutateDrawer({
...
@@ -519,18 +687,110 @@ export function ChannelMutateDrawer({
const
options
=
CHANNEL_TYPE_OPTIONS
.
map
((
option
)
=>
({
const
options
=
CHANNEL_TYPE_OPTIONS
.
map
((
option
)
=>
({
value
:
String
(
option
.
value
),
value
:
String
(
option
.
value
),
label
:
t
(
option
.
label
),
label
:
t
(
option
.
label
),
icon
:
getLobeIcon
(
`
${
getChannelTypeIcon
(
option
.
value
)}
.Color`
,
16
)
,
icon
:
<
ChannelTypeLogo
type=
{
option
.
value
}
size=
{
16
}
/>
,
}))
}))
if
(
!
options
.
some
((
option
)
=>
Number
(
option
.
value
)
===
currentType
))
{
if
(
!
options
.
some
((
option
)
=>
Number
(
option
.
value
)
===
currentType
))
{
options
.
push
({
options
.
push
({
value
:
String
(
currentType
),
value
:
String
(
currentType
),
label
:
`#
${
currentType
}
`
,
label
:
`#
${
currentType
}
`
,
icon
:
getLobeIcon
(
`
${
getChannelTypeIcon
(
currentType
)}
.Color`
,
16
)
,
icon
:
<
ChannelTypeLogo
type=
{
currentType
}
size=
{
16
}
/>
,
})
})
}
}
return
options
return
options
},
[
currentType
,
t
])
},
[
currentType
,
t
])
const
formErrors
=
form
.
formState
.
errors
const
identityHasErrors
=
Boolean
(
formErrors
.
name
||
formErrors
.
type
||
formErrors
.
status
||
formErrors
.
openai_organization
)
const
credentialsHaveErrors
=
Boolean
(
formErrors
.
key
||
formErrors
.
base_url
||
formErrors
.
other
||
formErrors
.
multi_key_mode
||
formErrors
.
multi_key_type
||
formErrors
.
key_mode
||
formErrors
.
vertex_key_type
||
formErrors
.
aws_key_type
||
formErrors
.
azure_responses_version
)
const
modelsHaveErrors
=
Boolean
(
formErrors
.
models
||
formErrors
.
group
||
formErrors
.
model_mapping
)
const
advancedHaveErrors
=
hasAdvancedSettingsErrors
(
formErrors
)
||
Boolean
(
formErrors
.
advanced_custom
)
const
providerRequiresBaseUrl
=
[
3
,
8
,
36
,
45
].
includes
(
currentType
)
const
providerRequiresOther
=
[
3
,
18
,
21
,
39
,
41
,
49
].
includes
(
currentType
)
const
identityComplete
=
Boolean
(
currentName
?.
trim
()
&&
currentType
>
0
)
const
credentialsComplete
=
Boolean
(
(
isEditing
||
currentKey
?.
trim
())
&&
(
!
providerRequiresBaseUrl
||
currentBaseUrl
?.
trim
())
&&
(
!
providerRequiresOther
||
currentOther
?.
trim
())
)
const
modelsComplete
=
Boolean
(
currentModelsArray
.
length
>
0
&&
currentGroups
?.
length
)
const
requiredCompletedCount
=
[
identityComplete
,
credentialsComplete
,
modelsComplete
,
].
filter
(
Boolean
).
length
const
currentStatusLabel
=
CHANNEL_STATUS_LABELS
[
currentStatus
as
keyof
typeof
CHANNEL_STATUS_LABELS
]
||
'Unknown'
const
progressLabel
=
`
${
requiredCompletedCount
}
/3`
const
identityStatus
=
getCompletionStatus
(
identityHasErrors
,
identityComplete
)
const
credentialsStatus
=
getCompletionStatus
(
credentialsHaveErrors
,
credentialsComplete
)
const
modelsStatus
=
getCompletionStatus
(
modelsHaveErrors
,
modelsComplete
)
const
advancedStatus
:
ChannelEditorSectionStatus
=
advancedHaveErrors
?
'error'
:
'idle'
const
advancedSummary
=
advancedHaveErrors
?
t
(
'Error'
)
:
undefined
const
editorNavItems
:
ChannelEditorNavItem
[]
=
[
{
id
:
'channel-section-identity'
,
title
:
t
(
'Basic Information'
),
description
:
getSectionStatusLabel
(
identityStatus
,
t
),
statusLabel
:
getSectionStatusLabel
(
identityStatus
,
t
),
status
:
identityStatus
,
icon
:
<
Server
className=
'h-4 w-4'
aria
-
hidden=
'true'
/>,
},
{
id
:
'channel-section-credentials'
,
title
:
t
(
'Credentials'
),
description
:
getSectionStatusLabel
(
credentialsStatus
,
t
),
statusLabel
:
getSectionStatusLabel
(
credentialsStatus
,
t
),
status
:
credentialsStatus
,
icon
:
<
KeyRound
className=
'h-4 w-4'
aria
-
hidden=
'true'
/>,
},
{
id
:
'channel-section-models'
,
title
:
t
(
'Models & Groups'
),
description
:
getSectionStatusLabel
(
modelsStatus
,
t
),
statusLabel
:
getSectionStatusLabel
(
modelsStatus
,
t
),
status
:
modelsStatus
,
icon
:
<
Boxes
className=
'h-4 w-4'
aria
-
hidden=
'true'
/>,
},
{
id
:
'channel-section-advanced'
,
title
:
t
(
'Advanced Settings'
),
description
:
advancedSummary
,
statusLabel
:
advancedSummary
??
t
(
'Advanced Settings'
),
status
:
advancedStatus
,
icon
:
<
Settings
className=
'h-4 w-4'
aria
-
hidden=
'true'
/>,
},
]
// Extract redirect models from model_mapping (target values)
// Extract redirect models from model_mapping (target values)
const
redirectModelList
=
useMemo
(
const
redirectModelList
=
useMemo
(
()
=>
extractRedirectModels
(
currentModelMapping
||
''
),
()
=>
extractRedirectModels
(
currentModelMapping
||
''
),
...
@@ -546,7 +806,7 @@ export function ChannelMutateDrawer({
...
@@ -546,7 +806,7 @@ export function ChannelMutateDrawer({
// Transform models to multi-select options
// Transform models to multi-select options
const
modelOptions
=
useMemo
(()
=>
{
const
modelOptions
=
useMemo
(()
=>
{
const
allModels
=
new
Set
([...
allModelsList
,
...
currentModelsArray
])
const
allModels
=
new
Set
([...
allModelsList
,
...
currentModelsArray
])
return
Array
.
from
(
allModels
)
.
map
((
model
)
=>
({
return
[...
allModels
]
.
map
((
model
)
=>
({
value
:
model
,
value
:
model
,
label
:
model
,
label
:
model
,
}))
}))
...
@@ -577,8 +837,8 @@ export function ChannelMutateDrawer({
...
@@ -577,8 +837,8 @@ export function ChannelMutateDrawer({
return
acc
return
acc
},
[])
},
[])
const
missingSourceModels
=
Array
.
from
(
const
missingSourceModels
=
[
new
Set
(
...
new
Set
(
entries
entries
.
filter
(
.
filter
(
(
entry
)
=>
(
entry
)
=>
...
@@ -586,11 +846,11 @@ export function ChannelMutateDrawer({
...
@@ -586,11 +846,11 @@ export function ChannelMutateDrawer({
!
currentModelsArray
.
includes
(
entry
.
source
)
!
currentModelsArray
.
includes
(
entry
.
source
)
)
)
.
map
((
entry
)
=>
entry
.
source
)
.
map
((
entry
)
=>
entry
.
source
)
)
)
,
)
]
const
exposedTargetModels
=
Array
.
from
(
const
exposedTargetModels
=
[
new
Set
(
...
new
Set
(
entries
entries
.
filter
(
.
filter
(
(
entry
)
=>
(
entry
)
=>
...
@@ -598,8 +858,8 @@ export function ChannelMutateDrawer({
...
@@ -598,8 +858,8 @@ export function ChannelMutateDrawer({
currentModelsArray
.
includes
(
entry
.
target
)
currentModelsArray
.
includes
(
entry
.
target
)
)
)
.
map
((
entry
)
=>
entry
.
target
)
.
map
((
entry
)
=>
entry
.
target
)
)
)
,
)
]
return
{
return
{
invalidJson
:
false
,
invalidJson
:
false
,
...
@@ -633,7 +893,7 @@ export function ChannelMutateDrawer({
...
@@ -633,7 +893,7 @@ export function ChannelMutateDrawer({
return
{
return
{
lastCheckTime
:
settings
.
upstream_model_update_last_check_time
,
lastCheckTime
:
settings
.
upstream_model_update_last_check_time
,
detectedModels
:
Array
.
from
(
new
Set
(
detectedModels
))
,
detectedModels
:
[...
new
Set
(
detectedModels
)]
,
}
}
},
[
currentSettings
])
},
[
currentSettings
])
...
@@ -1063,9 +1323,10 @@ export function ChannelMutateDrawer({
...
@@ -1063,9 +1323,10 @@ export function ChannelMutateDrawer({
const
hasModelMapping
=
const
hasModelMapping
=
typeof
data
.
model_mapping
===
'string'
&&
typeof
data
.
model_mapping
===
'string'
&&
data
.
model_mapping
.
trim
()
!==
''
data
.
model_mapping
.
trim
()
!==
''
const
modelMappingValue
=
data
.
model_mapping
||
''
if
(
hasModelMapping
)
{
if
(
hasModelMapping
)
{
const
validation
=
validateModelMappingJson
(
data
.
model_mapping
!
)
const
validation
=
validateModelMappingJson
(
modelMappingValue
)
if
(
!
validation
.
valid
)
{
if
(
!
validation
.
valid
)
{
toast
.
error
(
t
(
validation
.
error
||
'Invalid model mapping'
))
toast
.
error
(
t
(
validation
.
error
||
'Invalid model mapping'
))
return
return
...
@@ -1078,7 +1339,7 @@ export function ChannelMutateDrawer({
...
@@ -1078,7 +1339,7 @@ export function ChannelMutateDrawer({
// Check for missing models in model_mapping
// Check for missing models in model_mapping
if
(
hasModelMapping
)
{
if
(
hasModelMapping
)
{
const
missingModels
=
findMissingModelsInMapping
(
const
missingModels
=
findMissingModelsInMapping
(
data
.
model_mapping
!
,
modelMappingValue
,
normalizedModels
normalizedModels
)
)
...
@@ -1097,9 +1358,9 @@ export function ChannelMutateDrawer({
...
@@ -1097,9 +1358,9 @@ export function ChannelMutateDrawer({
return
return
}
}
if
(
confirmAction
===
'add'
)
{
if
(
confirmAction
===
'add'
)
{
const
updatedModels
=
Array
.
from
(
const
updatedModels
=
[
new
Set
([...
normalizedModels
,
...
missingModels
])
...
new
Set
([...
normalizedModels
,
...
missingModels
]),
)
]
data
.
models
=
formatModelsArray
(
updatedModels
)
data
.
models
=
formatModelsArray
(
updatedModels
)
form
.
setValue
(
'models'
,
data
.
models
)
form
.
setValue
(
'models'
,
data
.
models
)
}
}
...
@@ -1154,11 +1415,11 @@ export function ChannelMutateDrawer({
...
@@ -1154,11 +1415,11 @@ export function ChannelMutateDrawer({
return
(
return
(
<>
<>
<
Sheet
open=
{
open
}
onOpenChange=
{
handleOpenChange
}
>
<
Sheet
open=
{
open
}
onOpenChange=
{
handleOpenChange
}
>
<
SheetContent
className=
{
sideDrawerContentClassName
(
'sm:max-w-
3
xl'
)
}
>
<
SheetContent
className=
{
sideDrawerContentClassName
(
'sm:max-w-
5
xl'
)
}
>
<
SheetHeader
className=
{
sideDrawerHeaderClassName
()
}
>
<
SheetHeader
className=
{
sideDrawerHeaderClassName
()
}
>
<
SheetTitle
className=
'flex items-center gap-3'
>
<
SheetTitle
className=
'flex items-center gap-3'
>
<
span
className=
'bg-muted flex size-9 shrink-0 items-center justify-center rounded-md'
>
<
span
className=
'bg-muted flex size-9 shrink-0 items-center justify-center rounded-md'
>
{
getLobeIcon
(
`${getChannelTypeIcon(currentType)}.Color`
,
22
)
}
<
ChannelTypeLogo
type=
{
currentType
}
size=
{
22
}
/>
</
span
>
</
span
>
<
span
>
<
span
>
{
isEditing
?
t
(
'Edit Channel'
)
:
t
(
'Create Channel'
)
}
{
isEditing
?
t
(
'Edit Channel'
)
:
t
(
'Create Channel'
)
}
...
@@ -1181,7 +1442,9 @@ export function ChannelMutateDrawer({
...
@@ -1181,7 +1442,9 @@ export function ChannelMutateDrawer({
{
sensitiveLocked
&&
(
{
sensitiveLocked
&&
(
<
Alert
className=
'border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-50'
>
<
Alert
className=
'border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-50'
>
<
AlertDescription
>
<
AlertDescription
>
{
t
(
'Sensitive channel settings are read-only for your account.'
)
}{
' '
}
{
t
(
'Sensitive channel settings are read-only for your account.'
)
}{
' '
}
{
t
(
{
t
(
'You can still edit non-sensitive operations fields such as models, groups, priority, and weight.'
'You can still edit non-sensitive operations fields such as models, groups, priority, and weight.'
)
}
)
}
...
@@ -1198,27 +1461,22 @@ export function ChannelMutateDrawer({
...
@@ -1198,27 +1461,22 @@ export function ChannelMutateDrawer({
{
isChannelDetailLoading
?
(
{
isChannelDetailLoading
?
(
<
ChannelEditorLoadingState
/>
<
ChannelEditorLoadingState
/>
)
:
(
)
:
(
<>
<
div
className=
'grid gap-5 lg:grid-cols-[13rem_minmax(0,1fr)] lg:items-start'
>
<
ChannelEditorNav
providerLogo=
{
<
ChannelTypeLogo
type=
{
currentType
}
size=
{
18
}
/>
}
providerLabel=
{
t
(
currentTypeLabel
)
}
statusLabel=
{
t
(
currentStatusLabel
)
}
progressLabel=
{
progressLabel
}
navigationLabel=
{
t
(
'Channels'
)
}
items=
{
editorNavItems
}
/>
<
div
className=
'flex min-w-0 flex-col gap-5'
>
{
/* ── Basic Information ── */
}
{
/* ── Basic Information ── */
}
<
div
id=
'channel-section-identity'
className=
'scroll-mt-4'
>
<
ChannelBasicSection
>
<
ChannelBasicSection
>
<
div
className=
'grid gap-4 sm:grid-cols-2'
>
<
div
className=
'grid gap-4 sm:grid-cols-2'
>
<
FormField
control=
{
form
.
control
}
name=
'name'
render=
{
({
field
})
=>
(
<
FormItem
>
<
FormLabel
>
{
t
(
'Name *'
)
}
</
FormLabel
>
<
FormControl
>
<
Input
placeholder=
{
t
(
FIELD_PLACEHOLDERS
.
NAME
)
}
{
...
field
}
/>
</
FormControl
>
<
FormMessage
/>
</
FormItem
>
)
}
/>
<
fieldset
<
fieldset
disabled=
{
sensitiveLocked
}
disabled=
{
sensitiveLocked
}
className=
'min-w-0 disabled:opacity-60'
className=
'min-w-0 disabled:opacity-60'
...
@@ -1230,6 +1488,13 @@ export function ChannelMutateDrawer({
...
@@ -1230,6 +1488,13 @@ export function ChannelMutateDrawer({
<
FormItem
>
<
FormItem
>
<
FormLabel
>
{
t
(
'Type *'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'Type *'
)
}
</
FormLabel
>
<
FormControl
>
<
FormControl
>
<
div
className=
'relative'
>
<
span
className=
'pointer-events-none absolute top-1/2 left-3 z-10 flex -translate-y-1/2'
>
<
ChannelTypeLogo
type=
{
Number
(
field
.
value
)
}
size=
{
18
}
/>
</
span
>
<
Combobox
<
Combobox
options=
{
channelTypeOptions
}
options=
{
channelTypeOptions
}
value=
{
String
(
field
.
value
)
}
value=
{
String
(
field
.
value
)
}
...
@@ -1247,8 +1512,10 @@ export function ChannelMutateDrawer({
...
@@ -1247,8 +1512,10 @@ export function ChannelMutateDrawer({
'Search channel type...'
'Search channel type...'
)
}
)
}
emptyText=
{
t
(
'No channel type found.'
)
}
emptyText=
{
t
(
'No channel type found.'
)
}
className=
'pl-10'
allowCustomValue
allowCustomValue
/>
/>
</
div
>
</
FormControl
>
</
FormControl
>
{
sensitiveLocked
&&
(
{
sensitiveLocked
&&
(
<
FormDescription
>
<
FormDescription
>
...
@@ -1262,6 +1529,23 @@ export function ChannelMutateDrawer({
...
@@ -1262,6 +1529,23 @@ export function ChannelMutateDrawer({
)
}
)
}
/>
/>
</
fieldset
>
</
fieldset
>
<
FormField
control=
{
form
.
control
}
name=
'name'
render=
{
({
field
})
=>
(
<
FormItem
>
<
FormLabel
>
{
t
(
'Name *'
)
}
</
FormLabel
>
<
FormControl
>
<
Input
placeholder=
{
t
(
FIELD_PLACEHOLDERS
.
NAME
)
}
{
...
field
}
/>
</
FormControl
>
<
FormMessage
/>
</
FormItem
>
)
}
/>
</
div
>
</
div
>
{
!
isEditing
&&
(
{
!
isEditing
&&
(
...
@@ -1269,7 +1553,9 @@ export function ChannelMutateDrawer({
...
@@ -1269,7 +1553,9 @@ export function ChannelMutateDrawer({
control=
{
form
.
control
}
control=
{
form
.
control
}
name=
'status'
name=
'status'
render=
{
({
field
})
=>
(
render=
{
({
field
})
=>
(
<
FormItem
className=
{
sideDrawerSwitchItemClassName
()
}
>
<
FormItem
className=
{
sideDrawerSwitchItemClassName
()
}
>
<
div
className=
'flex flex-col gap-0.5'
>
<
div
className=
'flex flex-col gap-0.5'
>
<
FormLabel
>
{
t
(
'Enabled'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'Enabled'
)
}
</
FormLabel
>
<
FormDescription
className=
'text-xs'
>
<
FormDescription
className=
'text-xs'
>
...
@@ -1299,9 +1585,14 @@ export function ChannelMutateDrawer({
...
@@ -1299,9 +1585,14 @@ export function ChannelMutateDrawer({
name=
'openai_organization'
name=
'openai_organization'
render=
{
({
field
})
=>
(
render=
{
({
field
})
=>
(
<
FormItem
>
<
FormItem
>
<
FormLabel
>
{
t
(
'OpenAI Organization'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'OpenAI Organization'
)
}
</
FormLabel
>
<
FormControl
>
<
FormControl
>
<
Input
placeholder=
{
t
(
'org-...'
)
}
{
...
field
}
/>
<
Input
placeholder=
{
t
(
'org-...'
)
}
{
...
field
}
/>
</
FormControl
>
</
FormControl
>
<
FormDescription
>
<
FormDescription
>
{
sensitiveLocked
{
sensitiveLocked
...
@@ -1317,8 +1608,13 @@ export function ChannelMutateDrawer({
...
@@ -1317,8 +1608,13 @@ export function ChannelMutateDrawer({
</
fieldset
>
</
fieldset
>
)
}
)
}
</
ChannelBasicSection
>
</
ChannelBasicSection
>
</
div
>
{
/* ── API Access ── */
}
{
/* ── API Access ── */
}
<
div
id=
'channel-section-credentials'
className=
'scroll-mt-4'
>
<
ChannelApiAccessSection
>
<
ChannelApiAccessSection
>
{
CHANNEL_TYPE_WARNINGS
[
currentType
]
&&
(
{
CHANNEL_TYPE_WARNINGS
[
currentType
]
&&
(
<
Alert
>
<
Alert
>
...
@@ -1331,13 +1627,12 @@ export function ChannelMutateDrawer({
...
@@ -1331,13 +1627,12 @@ export function ChannelMutateDrawer({
{
sensitiveLocked
&&
(
{
sensitiveLocked
&&
(
<
Alert
className=
'border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-50'
>
<
Alert
className=
'border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-50'
>
<
AlertDescription
>
<
AlertDescription
>
{
t
(
{
t
(
'No permission to perform this action'
)
}
'No permission to perform this action'
)
}
</
AlertDescription
>
</
AlertDescription
>
</
Alert
>
</
Alert
>
)
}
)
}
<
div
className=
'border-border/60 bg-muted/10 rounded-lg border p-4'
>
<
fieldset
<
fieldset
disabled=
{
sensitiveLocked
}
disabled=
{
sensitiveLocked
}
className=
'space-y-4 disabled:opacity-60'
className=
'space-y-4 disabled:opacity-60'
...
@@ -1378,12 +1673,16 @@ export function ChannelMutateDrawer({
...
@@ -1378,12 +1673,16 @@ export function ChannelMutateDrawer({
</
FormLabel
>
</
FormLabel
>
<
FormControl
>
<
FormControl
>
<
Input
<
Input
placeholder=
{
t
(
'e.g., 2025-04-01-preview'
)
}
placeholder=
{
t
(
'e.g., 2025-04-01-preview'
)
}
{
...
field
}
{
...
field
}
/>
/>
</
FormControl
>
</
FormControl
>
<
FormDescription
>
<
FormDescription
>
{
t
(
'Default API version for this channel'
)
}
{
t
(
'Default API version for this channel'
)
}
</
FormDescription
>
</
FormDescription
>
<
FormMessage
/>
<
FormMessage
/>
</
FormItem
>
</
FormItem
>
...
@@ -1436,7 +1735,8 @@ export function ChannelMutateDrawer({
...
@@ -1436,7 +1735,8 @@ export function ChannelMutateDrawer({
/>
/>
</
FormControl
>
</
FormControl
>
<
FormDescription
>
<
FormDescription
>
{
t
(
'Enter the complete URL, supports'
)
}
{
'
{
'
}
{
t
(
'Enter the complete URL, supports'
)
}{
' '
}
{
'
{
'
}
{
t
(
'model'
)}
{
t
(
'model'
)}
{
'
}
'
}
{
t
(
'variable'
)
}
{
'
}
'
}
{
t
(
'variable'
)
}
</
FormDescription
>
</
FormDescription
>
...
@@ -1453,9 +1753,14 @@ export function ChannelMutateDrawer({
...
@@ -1453,9 +1753,14 @@ export function ChannelMutateDrawer({
name=
'other'
name=
'other'
render=
{
({
field
})
=>
(
render=
{
({
field
})
=>
(
<
FormItem
>
<
FormItem
>
<
FormLabel
>
{
t
(
'Model Version *'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'Model Version *'
)
}
</
FormLabel
>
<
FormControl
>
<
FormControl
>
<
Input
placeholder=
{
t
(
'e.g., v2.1'
)
}
{
...
field
}
/>
<
Input
placeholder=
{
t
(
'e.g., v2.1'
)
}
{
...
field
}
/>
</
FormControl
>
</
FormControl
>
<
FormDescription
>
<
FormDescription
>
{
t
(
{
t
(
...
@@ -1476,7 +1781,9 @@ export function ChannelMutateDrawer({
...
@@ -1476,7 +1781,9 @@ export function ChannelMutateDrawer({
render=
{
({
field
})
=>
(
render=
{
({
field
})
=>
(
<
FormItem
className=
'flex items-center justify-between'
>
<
FormItem
className=
'flex items-center justify-between'
>
<
div
className=
'space-y-0.5'
>
<
div
className=
'space-y-0.5'
>
<
FormLabel
>
{
t
(
'Enterprise Account'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'Enterprise Account'
)
}
</
FormLabel
>
<
FormDescription
>
<
FormDescription
>
{
t
(
{
t
(
'Enable if this is an OpenRouter enterprise account with special response format'
'Enable if this is an OpenRouter enterprise account with special response format'
...
@@ -1506,9 +1813,14 @@ export function ChannelMutateDrawer({
...
@@ -1506,9 +1813,14 @@ export function ChannelMutateDrawer({
items=
{
[
items=
{
[
{
{
value
:
'ak_sk'
,
value
:
'ak_sk'
,
label
:
t
(
'AccessKey / SecretAccessKey'
),
label
:
t
(
'AccessKey / SecretAccessKey'
),
},
{
value
:
'api_key'
,
label
:
t
(
'API Key'
),
},
},
{
value
:
'api_key'
,
label
:
t
(
'API Key'
)
},
]
}
]
}
onValueChange=
{
field
.
onChange
}
onValueChange=
{
field
.
onChange
}
value=
{
field
.
value
}
value=
{
field
.
value
}
...
@@ -1520,7 +1832,9 @@ export function ChannelMutateDrawer({
...
@@ -1520,7 +1832,9 @@ export function ChannelMutateDrawer({
/>
/>
</
SelectTrigger
>
</
SelectTrigger
>
</
FormControl
>
</
FormControl
>
<
SelectContent
alignItemWithTrigger=
{
false
}
>
<
SelectContent
alignItemWithTrigger=
{
false
}
>
<
SelectGroup
>
<
SelectGroup
>
<
SelectItem
value=
'ak_sk'
>
<
SelectItem
value=
'ak_sk'
>
{
t
(
'AccessKey / SecretAccessKey'
)
}
{
t
(
'AccessKey / SecretAccessKey'
)
}
...
@@ -1551,7 +1865,9 @@ export function ChannelMutateDrawer({
...
@@ -1551,7 +1865,9 @@ export function ChannelMutateDrawer({
name=
'other'
name=
'other'
render=
{
({
field
})
=>
(
render=
{
({
field
})
=>
(
<
FormItem
>
<
FormItem
>
<
FormLabel
>
{
t
(
'Knowledge Base ID *'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'Knowledge Base ID *'
)
}
</
FormLabel
>
<
FormControl
>
<
FormControl
>
<
Input
<
Input
placeholder=
{
t
(
'e.g., 123456'
)
}
placeholder=
{
t
(
'e.g., 123456'
)
}
...
@@ -1574,7 +1890,9 @@ export function ChannelMutateDrawer({
...
@@ -1574,7 +1890,9 @@ export function ChannelMutateDrawer({
name=
'base_url'
name=
'base_url'
render=
{
({
field
})
=>
(
render=
{
({
field
})
=>
(
<
FormItem
>
<
FormItem
>
<
FormLabel
>
{
t
(
'Private Deployment URL'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'Private Deployment URL'
)
}
</
FormLabel
>
<
FormControl
>
<
FormControl
>
<
Input
<
Input
placeholder=
{
t
(
placeholder=
{
t
(
...
@@ -1602,7 +1920,9 @@ export function ChannelMutateDrawer({
...
@@ -1602,7 +1920,9 @@ export function ChannelMutateDrawer({
render=
{
({
field
})
=>
(
render=
{
({
field
})
=>
(
<
FormItem
>
<
FormItem
>
<
FormLabel
>
<
FormLabel
>
{
t
(
'API Base URL (Important: Not Chat API) *'
)
}
{
t
(
'API Base URL (Important: Not Chat API) *'
)
}
</
FormLabel
>
</
FormLabel
>
<
FormControl
>
<
FormControl
>
<
Input
<
Input
...
@@ -1659,7 +1979,9 @@ export function ChannelMutateDrawer({
...
@@ -1659,7 +1979,9 @@ export function ChannelMutateDrawer({
rel=
'noopener noreferrer'
rel=
'noopener noreferrer'
className=
'text-primary underline'
className=
'text-primary underline'
>
>
{
t
(
'https://cloud.siliconflow.cn/i/hij0YNTZ'
)
}
{
t
(
'https://cloud.siliconflow.cn/i/hij0YNTZ'
)
}
</
a
>
</
a
>
</
AlertDescription
>
</
AlertDescription
>
</
Alert
>
</
Alert
>
...
@@ -1673,11 +1995,16 @@ export function ChannelMutateDrawer({
...
@@ -1673,11 +1995,16 @@ export function ChannelMutateDrawer({
name=
'vertex_key_type'
name=
'vertex_key_type'
render=
{
({
field
})
=>
(
render=
{
({
field
})
=>
(
<
FormItem
>
<
FormItem
>
<
FormLabel
>
{
t
(
'Vertex AI Key Format'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'Vertex AI Key Format'
)
}
</
FormLabel
>
<
Select
<
Select
items=
{
[
items=
{
[
{
value
:
'json'
,
label
:
t
(
'JSON'
)
},
{
value
:
'json'
,
label
:
t
(
'JSON'
)
},
{
value
:
'api_key'
,
label
:
t
(
'API Key'
)
},
{
value
:
'api_key'
,
label
:
t
(
'API Key'
),
},
]
}
]
}
onValueChange=
{
field
.
onChange
}
onValueChange=
{
field
.
onChange
}
value=
{
field
.
value
}
value=
{
field
.
value
}
...
@@ -1687,7 +2014,9 @@ export function ChannelMutateDrawer({
...
@@ -1687,7 +2014,9 @@ export function ChannelMutateDrawer({
<
SelectValue
/>
<
SelectValue
/>
</
SelectTrigger
>
</
SelectTrigger
>
</
FormControl
>
</
FormControl
>
<
SelectContent
alignItemWithTrigger=
{
false
}
>
<
SelectContent
alignItemWithTrigger=
{
false
}
>
<
SelectGroup
>
<
SelectGroup
>
<
SelectItem
value=
'json'
>
<
SelectItem
value=
'json'
>
{
t
(
'JSON'
)
}
{
t
(
'JSON'
)
}
...
@@ -1724,13 +2053,15 @@ export function ChannelMutateDrawer({
...
@@ -1724,13 +2053,15 @@ export function ChannelMutateDrawer({
onChange=
{
async
(
e
)
=>
{
onChange=
{
async
(
e
)
=>
{
const
fileList
=
e
.
target
.
files
const
fileList
=
e
.
target
.
files
const
files
=
fileList
const
files
=
fileList
?
Array
.
from
(
fileList
)
?
[...
fileList
]
:
[]
:
[]
// allow re-selecting the same file
// allow re-selecting the same file
e
.
target
.
value
=
''
e
.
target
.
value
=
''
if
(
files
.
length
===
0
)
{
if
(
files
.
length
===
0
)
{
toast
.
info
(
t
(
'Please upload key file(s)'
))
toast
.
info
(
t
(
'Please upload key file(s)'
)
)
return
return
}
}
...
@@ -1753,7 +2084,9 @@ export function ChannelMutateDrawer({
...
@@ -1753,7 +2084,9 @@ export function ChannelMutateDrawer({
}
}
if
(
keys
.
length
===
0
)
{
if
(
keys
.
length
===
0
)
{
toast
.
info
(
t
(
'Please upload key file(s)'
))
toast
.
info
(
t
(
'Please upload key file(s)'
)
)
return
return
}
}
...
@@ -1779,7 +2112,9 @@ export function ChannelMutateDrawer({
...
@@ -1779,7 +2112,9 @@ export function ChannelMutateDrawer({
</
FormControl
>
</
FormControl
>
<
FormDescription
>
<
FormDescription
>
{
isBatchMode
{
isBatchMode
?
t
(
'Upload multiple JSON files in batch modes'
)
?
t
(
'Upload multiple JSON files in batch modes'
)
:
t
(
:
t
(
'Upload a single service account JSON file'
'Upload a single service account JSON file'
)
}
)
}
...
@@ -1792,7 +2127,9 @@ export function ChannelMutateDrawer({
...
@@ -1792,7 +2127,9 @@ export function ChannelMutateDrawer({
name=
'other'
name=
'other'
render=
{
({
field
})
=>
(
render=
{
({
field
})
=>
(
<
FormItem
>
<
FormItem
>
<
FormLabel
>
{
t
(
'Deployment Region *'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'Deployment Region *'
)
}
</
FormLabel
>
<
FormControl
>
<
FormControl
>
<
Textarea
<
Textarea
placeholder=
{
t
(
placeholder=
{
t
(
...
@@ -1803,7 +2140,9 @@ export function ChannelMutateDrawer({
...
@@ -1803,7 +2140,9 @@ export function ChannelMutateDrawer({
/>
/>
</
FormControl
>
</
FormControl
>
<
FormDescription
>
<
FormDescription
>
{
t
(
'Enter deployment region or JSON mapping:'
)
}{
' '
}
{
t
(
'Enter deployment region or JSON mapping:'
)
}{
' '
}
{
'
{
'
}
{
'
{
'
}
{
t
(
{
t
(
'"default": "us-central1", "claude-3-5-sonnet-20240620": "europe-west1"'
'"default": "us-central1", "claude-3-5-sonnet-20240620": "europe-west1"'
...
@@ -1833,8 +2172,11 @@ export function ChannelMutateDrawer({
...
@@ -1833,8 +2172,11 @@ export function ChannelMutateDrawer({
<
Select
<
Select
items=
{
[
items=
{
[
{
{
value
:
'https://ark.cn-beijing.volces.com'
,
value
:
label
:
t
(
'https://ark.cn-beijing.volces.com'
),
'https://ark.cn-beijing.volces.com'
,
label
:
t
(
'https://ark.cn-beijing.volces.com'
),
},
},
{
{
value
:
value
:
...
@@ -1857,10 +2199,14 @@ export function ChannelMutateDrawer({
...
@@ -1857,10 +2199,14 @@ export function ChannelMutateDrawer({
<
SelectValue
/>
<
SelectValue
/>
</
SelectTrigger
>
</
SelectTrigger
>
</
FormControl
>
</
FormControl
>
<
SelectContent
alignItemWithTrigger=
{
false
}
>
<
SelectContent
alignItemWithTrigger=
{
false
}
>
<
SelectGroup
>
<
SelectGroup
>
<
SelectItem
value=
'https://ark.cn-beijing.volces.com'
>
<
SelectItem
value=
'https://ark.cn-beijing.volces.com'
>
{
t
(
'https://ark.cn-beijing.volces.com'
)
}
{
t
(
'https://ark.cn-beijing.volces.com'
)
}
</
SelectItem
>
</
SelectItem
>
<
SelectItem
value=
'https://ark.ap-southeast.bytepluses.com'
>
<
SelectItem
value=
'https://ark.ap-southeast.bytepluses.com'
>
{
t
(
{
t
(
...
@@ -1937,7 +2283,9 @@ export function ChannelMutateDrawer({
...
@@ -1937,7 +2283,9 @@ export function ChannelMutateDrawer({
<
FormLabel
>
{
t
(
'Base URL'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'Base URL'
)
}
</
FormLabel
>
<
FormControl
>
<
FormControl
>
<
Input
<
Input
placeholder=
{
t
(
FIELD_PLACEHOLDERS
.
BASE_URL
)
}
placeholder=
{
t
(
FIELD_PLACEHOLDERS
.
BASE_URL
)
}
{
...
field
}
{
...
field
}
/>
/>
</
FormControl
>
</
FormControl
>
...
@@ -2002,24 +2350,29 @@ export function ChannelMutateDrawer({
...
@@ -2002,24 +2350,29 @@ export function ChannelMutateDrawer({
control=
{
form
.
control
}
control=
{
form
.
control
}
name=
'multi_key_mode'
name=
'multi_key_mode'
render=
{
({
field
})
=>
(
render=
{
({
field
})
=>
(
<
FormItem
>
<
FormItem
className=
'flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between'
>
<
FormLabel
>
{
t
(
'Add Mode'
)
}
</
FormLabel
>
<
FormLabel
className=
'text-muted-foreground text-xs font-medium'
>
{
t
(
'Add Mode'
)
}
</
FormLabel
>
<
Select
<
Select
items=
{
[
items=
{
addModeOptions
.
map
((
option
)
=>
({
...
addModeOptions
.
map
((
option
)
=>
({
value
:
option
.
value
,
value
:
option
.
value
,
label
:
t
(
option
.
label
),
label
:
t
(
option
.
label
),
})),
}))
}
]
}
onValueChange=
{
field
.
onChange
}
onValueChange=
{
field
.
onChange
}
value=
{
field
.
value
}
value=
{
field
.
value
}
>
>
<
FormControl
>
<
FormControl
>
<
SelectTrigger
>
<
SelectTrigger
size=
'sm'
className=
'w-full sm:w-56'
>
<
SelectValue
/>
<
SelectValue
/>
</
SelectTrigger
>
</
SelectTrigger
>
</
FormControl
>
</
FormControl
>
<
SelectContent
alignItemWithTrigger=
{
false
}
>
<
SelectContent
alignItemWithTrigger=
{
false
}
>
<
SelectGroup
>
<
SelectGroup
>
{
addModeOptions
.
map
((
option
)
=>
(
{
addModeOptions
.
map
((
option
)
=>
(
<
SelectItem
<
SelectItem
...
@@ -2032,13 +2385,6 @@ export function ChannelMutateDrawer({
...
@@ -2032,13 +2385,6 @@ export function ChannelMutateDrawer({
</
SelectGroup
>
</
SelectGroup
>
</
SelectContent
>
</
SelectContent
>
</
Select
>
</
Select
>
<
FormDescription
>
{
t
(
supportsMultiKeyAddMode
?
FIELD_DESCRIPTIONS
.
BATCH_ADD
:
FIELD_DESCRIPTIONS
.
KEY
)
}
</
FormDescription
>
<
FormMessage
/>
<
FormMessage
/>
</
FormItem
>
</
FormItem
>
)
}
)
}
...
@@ -2049,33 +2395,74 @@ export function ChannelMutateDrawer({
...
@@ -2049,33 +2395,74 @@ export function ChannelMutateDrawer({
control=
{
form
.
control
}
control=
{
form
.
control
}
name=
'key'
name=
'key'
render=
{
({
field
})
=>
{
render=
{
({
field
})
=>
{
const
keyPlaceholder
=
(()
=>
{
let
keyPlaceholder
=
t
(
getKeyPromptForType
(
currentType
)
)
if
(
isEditing
)
{
if
(
isEditing
)
{
return
t
(
'Leave empty to keep existing key'
)
keyPlaceholder
=
t
(
}
'Leave empty to keep existing key'
if
(
currentType
===
33
)
{
)
if
(
awsKeyType
===
'api_key'
)
{
}
else
if
(
return
isBatchMode
currentType
===
33
&&
?
t
(
awsKeyType
===
'api_key'
&&
isBatchMode
)
{
keyPlaceholder
=
t
(
'Enter API Key, one per line, format: APIKey|Region'
'Enter API Key, one per line, format: APIKey|Region'
)
)
:
t
(
'Enter API Key, format: APIKey|Region'
)
}
else
if
(
}
currentType
===
33
&&
return
isBatchMode
awsKeyType
===
'api_key'
?
t
(
)
{
keyPlaceholder
=
t
(
'Enter API Key, format: APIKey|Region'
)
}
else
if
(
currentType
===
33
&&
isBatchMode
)
{
keyPlaceholder
=
t
(
'Enter key, one per line, format: AccessKey|SecretAccessKey|Region'
'Enter key, one per line, format: AccessKey|SecretAccessKey|Region'
)
)
:
t
(
}
else
if
(
currentType
===
33
)
{
keyPlaceholder
=
t
(
'Enter key, format: AccessKey|SecretAccessKey|Region'
'Enter key, format: AccessKey|SecretAccessKey|Region'
)
)
}
}
else
if
(
isBatchMode
)
{
if
(
isBatchMode
)
{
keyPlaceholder
=
t
(
return
t
(
'Enter one key per line for batch creation'
'Enter one key per line for batch creation'
)
)
}
}
return
t
(
getKeyPromptForType
(
currentType
))
})()
let
keyDescription
:
ReactNode
=
t
(
FIELD_DESCRIPTIONS
.
KEY
)
if
(
isEditing
)
{
let
keyModeDescription
=
t
(
'Append mode: New keys will be added to the end of the existing key list'
)
if
(
keyMode
===
'replace'
)
{
keyModeDescription
=
t
(
'Replace mode: Will completely replace all existing keys'
)
}
keyDescription
=
(
<>
{
t
(
'Enter new key to update, or leave empty to keep current key'
)
}
{
isMultiKeyChannel
&&
(
<
span
className=
'text-warning mt-1 block'
>
{
keyModeDescription
}
</
span
>
)
}
</>
)
}
else
if
(
isBatchMode
)
{
keyDescription
=
t
(
'Enter one API key per line for batch creation'
)
}
return
(
return
(
<
FormItem
>
<
FormItem
>
<
FormLabel
>
{
t
(
'API Key *'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'API Key *'
)
}
</
FormLabel
>
...
@@ -2088,31 +2475,7 @@ export function ChannelMutateDrawer({
...
@@ -2088,31 +2475,7 @@ export function ChannelMutateDrawer({
</
FormControl
>
</
FormControl
>
<
FormDescription
>
<
FormDescription
>
<
div
className=
'flex flex-col gap-2'
>
<
div
className=
'flex flex-col gap-2'
>
<
span
>
<
span
>
{
keyDescription
}
</
span
>
{
isEditing
?
(
<>
{
t
(
'Enter new key to update, or leave empty to keep current key'
)
}
{
isMultiKeyChannel
&&
(
<
span
className=
'text-warning mt-1 block'
>
{
t
(
'Multi-key channel: Keys will be'
)
}{
' '
}
{
keyMode
===
'replace'
?
t
(
'replaced'
)
:
t
(
'appended'
)
}
</
span
>
)
}
</>
)
:
isBatchMode
?
(
t
(
'Enter one API key per line for batch creation'
)
)
:
(
t
(
FIELD_DESCRIPTIONS
.
KEY
)
)
}
</
span
>
{
isBatchMode
&&
(
{
isBatchMode
&&
(
<
Button
<
Button
type=
'button'
type=
'button'
...
@@ -2165,7 +2528,9 @@ export function ChannelMutateDrawer({
...
@@ -2165,7 +2528,9 @@ export function ChannelMutateDrawer({
size=
'sm'
size=
'sm'
onClick=
{
async
()
=>
{
onClick=
{
async
()
=>
{
if
(
channelKey
)
{
if
(
channelKey
)
{
await
copyToClipboard
(
channelKey
)
await
copyToClipboard
(
channelKey
)
}
}
}
}
}
}
disabled=
{
!
channelKey
}
disabled=
{
!
channelKey
}
...
@@ -2178,7 +2543,9 @@ export function ChannelMutateDrawer({
...
@@ -2178,7 +2543,9 @@ export function ChannelMutateDrawer({
<
Input
<
Input
readOnly
readOnly
value=
{
channelKey
??
''
}
value=
{
channelKey
??
''
}
placeholder=
{
t
(
'Hidden — verify to reveal'
)
}
placeholder=
{
t
(
'Hidden — verify to reveal'
)
}
className=
'font-mono'
className=
'font-mono'
/>
/>
</
div
>
</
div
>
...
@@ -2237,7 +2604,9 @@ export function ChannelMutateDrawer({
...
@@ -2237,7 +2604,9 @@ export function ChannelMutateDrawer({
name=
'key_mode'
name=
'key_mode'
render=
{
({
field
})
=>
(
render=
{
({
field
})
=>
(
<
FormItem
>
<
FormItem
>
<
FormLabel
>
{
t
(
'Key Update Mode'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'Key Update Mode'
)
}
</
FormLabel
>
<
Select
<
Select
items=
{
[
items=
{
[
{
{
...
@@ -2246,7 +2615,9 @@ export function ChannelMutateDrawer({
...
@@ -2246,7 +2615,9 @@ export function ChannelMutateDrawer({
},
},
{
{
value
:
'replace'
,
value
:
'replace'
,
label
:
t
(
'Replace all existing keys'
),
label
:
t
(
'Replace all existing keys'
),
},
},
]
}
]
}
onValueChange=
{
field
.
onChange
}
onValueChange=
{
field
.
onChange
}
...
@@ -2257,7 +2628,9 @@ export function ChannelMutateDrawer({
...
@@ -2257,7 +2628,9 @@ export function ChannelMutateDrawer({
<
SelectValue
/>
<
SelectValue
/>
</
SelectTrigger
>
</
SelectTrigger
>
</
FormControl
>
</
FormControl
>
<
SelectContent
alignItemWithTrigger=
{
false
}
>
<
SelectContent
alignItemWithTrigger=
{
false
}
>
<
SelectGroup
>
<
SelectGroup
>
<
SelectItem
value=
'append'
>
<
SelectItem
value=
'append'
>
{
t
(
'Append to existing keys'
)
}
{
t
(
'Append to existing keys'
)
}
...
@@ -2283,17 +2656,26 @@ export function ChannelMutateDrawer({
...
@@ -2283,17 +2656,26 @@ export function ChannelMutateDrawer({
/>
/>
)
}
)
}
{
!
isEditing
&&
multiKeyMode
===
'multi_to_single'
&&
(
{
!
isEditing
&&
multiKeyMode
===
'multi_to_single'
&&
(
<
FormField
<
FormField
control=
{
form
.
control
}
control=
{
form
.
control
}
name=
'multi_key_type'
name=
'multi_key_type'
render=
{
({
field
})
=>
(
render=
{
({
field
})
=>
(
<
FormItem
>
<
FormItem
>
<
FormLabel
>
{
t
(
'Multi-Key Strategy'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'Multi-Key Strategy'
)
}
</
FormLabel
>
<
Select
<
Select
items=
{
[
items=
{
[
{
value
:
'random'
,
label
:
t
(
'Random'
)
},
{
{
value
:
'polling'
,
label
:
t
(
'Polling'
)
},
value
:
'random'
,
label
:
t
(
'Random'
),
},
{
value
:
'polling'
,
label
:
t
(
'Polling'
),
},
]
}
]
}
onValueChange=
{
field
.
onChange
}
onValueChange=
{
field
.
onChange
}
value=
{
field
.
value
}
value=
{
field
.
value
}
...
@@ -2303,7 +2685,9 @@ export function ChannelMutateDrawer({
...
@@ -2303,7 +2685,9 @@ export function ChannelMutateDrawer({
<
SelectValue
/>
<
SelectValue
/>
</
SelectTrigger
>
</
SelectTrigger
>
</
FormControl
>
</
FormControl
>
<
SelectContent
alignItemWithTrigger=
{
false
}
>
<
SelectContent
alignItemWithTrigger=
{
false
}
>
<
SelectGroup
>
<
SelectGroup
>
<
SelectItem
value=
'random'
>
<
SelectItem
value=
'random'
>
{
t
(
'Random'
)
}
{
t
(
'Random'
)
}
...
@@ -2334,9 +2718,12 @@ export function ChannelMutateDrawer({
...
@@ -2334,9 +2718,12 @@ export function ChannelMutateDrawer({
)
}
)
}
</
ChannelAuthSection
>
</
ChannelAuthSection
>
</
fieldset
>
</
fieldset
>
</
div
>
</
ChannelApiAccessSection
>
</
ChannelApiAccessSection
>
</
div
>
{
/* ── Models & Groups ── */
}
{
/* ── Models & Groups ── */
}
<
div
id=
'channel-section-models'
className=
'scroll-mt-4'
>
<
ChannelModelsSection
>
<
ChannelModelsSection
>
<
div
className=
'space-y-5'
>
<
div
className=
'space-y-5'
>
<
div
className=
'border-border/60 bg-muted/10 rounded-lg border p-4'
>
<
div
className=
'border-border/60 bg-muted/10 rounded-lg border p-4'
>
...
@@ -2395,7 +2782,8 @@ export function ChannelMutateDrawer({
...
@@ -2395,7 +2782,8 @@ export function ChannelMutateDrawer({
)
)
updateModels
(
updateModels
(
currentModelsArray
.
filter
(
currentModelsArray
.
filter
(
(
model
)
=>
!
hiddenTargets
.
has
(
model
)
(
model
)
=>
!
hiddenTargets
.
has
(
model
)
)
)
)
)
}
}
}
}
...
@@ -2512,7 +2900,9 @@ export function ChannelMutateDrawer({
...
@@ -2512,7 +2900,9 @@ export function ChannelMutateDrawer({
type=
'button'
type=
'button'
variant=
'secondary'
variant=
'secondary'
size=
'sm'
size=
'sm'
onClick=
{
()
=>
handleAddPrefillGroup
(
group
)
}
onClick=
{
()
=>
handleAddPrefillGroup
(
group
)
}
>
>
{
group
.
name
}
{
group
.
name
}
</
Button
>
</
Button
>
...
@@ -2562,7 +2952,8 @@ export function ChannelMutateDrawer({
...
@@ -2562,7 +2952,8 @@ export function ChannelMutateDrawer({
{
t
(
'Request flow'
)
}
{
t
(
'Request flow'
)
}
</
p
>
</
p
>
<
div
className=
'space-y-1 font-mono text-xs'
>
<
div
className=
'space-y-1 font-mono text-xs'
>
{
mappingPreviewPairs
.
map
((
pair
)
=>
(
{
mappingPreviewPairs
.
map
(
(
pair
)
=>
(
<
div
<
div
key=
{
`${pair.source}-${pair.target}`
}
key=
{
`${pair.source}-${pair.target}`
}
className=
'flex items-center gap-1'
className=
'flex items-center gap-1'
...
@@ -2574,7 +2965,8 @@ export function ChannelMutateDrawer({
...
@@ -2574,7 +2965,8 @@ export function ChannelMutateDrawer({
/>
/>
<
span
>
{
pair
.
target
}
</
span
>
<
span
>
{
pair
.
target
}
</
span
>
</
div
>
</
div
>
))
}
)
)
}
{
remainingMappingCount
>
0
&&
(
{
remainingMappingCount
>
0
&&
(
<
div
className=
'text-[11px] opacity-70'
>
<
div
className=
'text-[11px] opacity-70'
>
+
{
remainingMappingCount
}{
' '
}
+
{
remainingMappingCount
}{
' '
}
...
@@ -2618,7 +3010,9 @@ export function ChannelMutateDrawer({
...
@@ -2618,7 +3010,9 @@ export function ChannelMutateDrawer({
<
code
className=
'font-mono'
>
<
code
className=
'font-mono'
>
{
'
{
"gpt-4"
:
"Azure-GPT4"
}
'
}
{
'
{
"gpt-4"
:
"Azure-GPT4"
}
'
}
</
code
>
</
code
>
{
t
(
'. Please fix the JSON before saving.'
)
}
{
t
(
'. Please fix the JSON before saving.'
)
}
</
AlertDescription
>
</
AlertDescription
>
</
Alert
>
</
Alert
>
)
}
)
}
...
@@ -2677,7 +3071,9 @@ export function ChannelMutateDrawer({
...
@@ -2677,7 +3071,9 @@ export function ChannelMutateDrawer({
options=
{
groupOptions
}
options=
{
groupOptions
}
selected=
{
field
.
value
}
selected=
{
field
.
value
}
onChange=
{
field
.
onChange
}
onChange=
{
field
.
onChange
}
placeholder=
{
t
(
FIELD_PLACEHOLDERS
.
GROUP
)
}
placeholder=
{
t
(
FIELD_PLACEHOLDERS
.
GROUP
)
}
/>
/>
)
}
)
}
</
FormControl
>
</
FormControl
>
...
@@ -2688,10 +3084,13 @@ export function ChannelMutateDrawer({
...
@@ -2688,10 +3084,13 @@ export function ChannelMutateDrawer({
</
div
>
</
div
>
</
div
>
</
div
>
</
ChannelModelsSection
>
</
ChannelModelsSection
>
</
div
>
<
div
id=
'channel-section-advanced'
className=
'scroll-mt-4'
>
<
ChannelAdvancedSection
<
ChannelAdvancedSection
open=
{
advancedSettingsOpen
}
open=
{
advancedSettingsOpen
}
onOpenChange=
{
handleAdvancedSettingsOpenChange
}
onOpenChange=
{
handleAdvancedSettingsOpenChange
}
summary=
{
advancedSummary
}
>
>
{
/* ── Routing & Overrides ── */
}
{
/* ── Routing & Overrides ── */
}
<
div
className=
{
sideDrawerSectionClassName
()
}
>
<
div
className=
{
sideDrawerSectionClassName
()
}
>
...
@@ -2762,7 +3161,9 @@ export function ChannelMutateDrawer({
...
@@ -2762,7 +3161,9 @@ export function ChannelMutateDrawer({
<
FormLabel
>
{
t
(
'Test Model'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'Test Model'
)
}
</
FormLabel
>
<
FormControl
>
<
FormControl
>
<
Input
<
Input
placeholder=
{
t
(
FIELD_PLACEHOLDERS
.
TEST_MODEL
)
}
placeholder=
{
t
(
FIELD_PLACEHOLDERS
.
TEST_MODEL
)
}
{
...
field
}
{
...
field
}
/>
/>
</
FormControl
>
</
FormControl
>
...
@@ -2832,7 +3233,9 @@ export function ChannelMutateDrawer({
...
@@ -2832,7 +3233,9 @@ export function ChannelMutateDrawer({
<
FormLabel
>
{
t
(
'Remark'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'Remark'
)
}
</
FormLabel
>
<
FormControl
>
<
FormControl
>
<
Textarea
<
Textarea
placeholder=
{
t
(
FIELD_PLACEHOLDERS
.
REMARK
)
}
placeholder=
{
t
(
FIELD_PLACEHOLDERS
.
REMARK
)
}
rows=
{
2
}
rows=
{
2
}
{
...
field
}
{
...
field
}
/>
/>
...
@@ -2973,7 +3376,9 @@ export function ChannelMutateDrawer({
...
@@ -2973,7 +3376,9 @@ export function ChannelMutateDrawer({
<
Textarea
<
Textarea
value=
{
field
.
value
||
''
}
value=
{
field
.
value
||
''
}
onChange=
{
field
.
onChange
}
onChange=
{
field
.
onChange
}
disabled=
{
sensitiveLocked
||
isSubmitting
}
disabled=
{
sensitiveLocked
||
isSubmitting
}
rows=
{
8
}
rows=
{
8
}
placeholder=
{
t
(
placeholder=
{
t
(
'Override request parameters. Cannot override stream parameter.'
'Override request parameters. Cannot override stream parameter.'
...
@@ -3011,8 +3416,10 @@ export function ChannelMutateDrawer({
...
@@ -3011,8 +3416,10 @@ export function ChannelMutateDrawer({
{
{
'*'
:
true
,
'*'
:
true
,
're:^X-Trace-.*$'
:
true
,
're:^X-Trace-.*$'
:
true
,
'X-Foo'
:
'
{
client_header
:
X
-
Foo
}
',
'X-Foo'
:
Authorization: '
Bearer
{
api_key
}
'
,
'
{
client_header
:
X
-
Foo
}
',
Authorization:
'
Bearer
{
api_key
}
'
,
},
},
null
,
null
,
2
2
...
@@ -3028,7 +3435,11 @@ export function ChannelMutateDrawer({
...
@@ -3028,7 +3435,11 @@ export function ChannelMutateDrawer({
size=
'sm'
size=
'sm'
onClick=
{
()
=>
onClick=
{
()
=>
field
.
onChange
(
field
.
onChange
(
JSON
.
stringify
({
'*'
:
true
},
null
,
2
)
JSON
.
stringify
(
{
'*'
:
true
},
null
,
2
)
)
)
}
}
>
>
...
@@ -3046,7 +3457,7 @@ export function ChannelMutateDrawer({
...
@@ -3046,7 +3457,7 @@ export function ChannelMutateDrawer({
field
.
onChange
(
field
.
onChange
(
JSON
.
stringify
(
parsed
,
null
,
2
)
JSON
.
stringify
(
parsed
,
null
,
2
)
)
)
}
catch
(
_e
)
{
}
catch
{
/* ignore invalid JSON */
/* ignore invalid JSON */
}
}
}
}
}
}
...
@@ -3069,7 +3480,9 @@ export function ChannelMutateDrawer({
...
@@ -3069,7 +3480,9 @@ export function ChannelMutateDrawer({
rows=
{
6
}
rows=
{
6
}
value=
{
field
.
value
||
''
}
value=
{
field
.
value
||
''
}
onChange=
{
field
.
onChange
}
onChange=
{
field
.
onChange
}
disabled=
{
sensitiveLocked
||
isSubmitting
}
disabled=
{
sensitiveLocked
||
isSubmitting
}
placeholder=
{
t
(
placeholder=
{
t
(
'Enter JSON to override request headers'
'Enter JSON to override request headers'
)
}
)
}
...
@@ -3103,9 +3516,7 @@ export function ChannelMutateDrawer({
...
@@ -3103,9 +3516,7 @@ export function ChannelMutateDrawer({
{
sensitiveLocked
&&
(
{
sensitiveLocked
&&
(
<
Alert
className=
'border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-50'
>
<
Alert
className=
'border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-50'
>
<
AlertDescription
>
<
AlertDescription
>
{
t
(
{
t
(
'No permission to perform this action'
)
}
'No permission to perform this action'
)
}
</
AlertDescription
>
</
AlertDescription
>
</
Alert
>
</
Alert
>
)
}
)
}
...
@@ -3117,7 +3528,9 @@ export function ChannelMutateDrawer({
...
@@ -3117,7 +3528,9 @@ export function ChannelMutateDrawer({
<
div
className=
'border-border/60 flex flex-col gap-3 border-y py-4'
>
<
div
className=
'border-border/60 flex flex-col gap-3 border-y py-4'
>
<
SubHeading
<
SubHeading
title=
{
t
(
'Field passthrough controls'
)
}
title=
{
t
(
'Field passthrough controls'
)
}
icon=
{
<
SlidersHorizontal
className=
'h-3.5 w-3.5'
/>
}
icon=
{
<
SlidersHorizontal
className=
'h-3.5 w-3.5'
/>
}
/>
/>
<
div
className=
'divide-border space-y-0 divide-y border-y'
>
<
div
className=
'divide-border space-y-0 divide-y border-y'
>
...
@@ -3128,10 +3541,14 @@ export function ChannelMutateDrawer({
...
@@ -3128,10 +3541,14 @@ export function ChannelMutateDrawer({
<
FormItem
className=
'flex items-center justify-between gap-3 px-4 py-3'
>
<
FormItem
className=
'flex items-center justify-between gap-3 px-4 py-3'
>
<
div
className=
'space-y-0.5'
>
<
div
className=
'space-y-0.5'
>
<
FormLabel
className=
'text-sm'
>
<
FormLabel
className=
'text-sm'
>
{
t
(
'Allow service_tier passthrough'
)
}
{
t
(
'Allow service_tier passthrough'
)
}
</
FormLabel
>
</
FormLabel
>
<
FormDescription
>
<
FormDescription
>
{
t
(
'Pass through the service_tier field'
)
}
{
t
(
'Pass through the service_tier field'
)
}
</
FormDescription
>
</
FormDescription
>
</
div
>
</
div
>
<
FormControl
>
<
FormControl
>
...
@@ -3263,7 +3680,9 @@ export function ChannelMutateDrawer({
...
@@ -3263,7 +3680,9 @@ export function ChannelMutateDrawer({
<
FormItem
className=
'flex items-center justify-between gap-3 px-4 py-3'
>
<
FormItem
className=
'flex items-center justify-between gap-3 px-4 py-3'
>
<
div
className=
'space-y-0.5'
>
<
div
className=
'space-y-0.5'
>
<
FormLabel
className=
'text-sm'
>
<
FormLabel
className=
'text-sm'
>
{
t
(
'Allow inference_geo passthrough'
)
}
{
t
(
'Allow inference_geo passthrough'
)
}
</
FormLabel
>
</
FormLabel
>
<
FormDescription
>
<
FormDescription
>
{
t
(
{
t
(
...
@@ -3346,7 +3765,9 @@ export function ChannelMutateDrawer({
...
@@ -3346,7 +3765,9 @@ export function ChannelMutateDrawer({
render=
{
({
field
})
=>
(
render=
{
({
field
})
=>
(
<
FormItem
className=
'flex items-center justify-between px-4 py-3'
>
<
FormItem
className=
'flex items-center justify-between px-4 py-3'
>
<
div
className=
'space-y-0.5'
>
<
div
className=
'space-y-0.5'
>
<
FormLabel
>
{
t
(
'Force Format'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'Force Format'
)
}
</
FormLabel
>
<
FormDescription
>
<
FormDescription
>
{
t
(
{
t
(
'Force format response to OpenAI standard (OpenAI channel only)'
'Force format response to OpenAI standard (OpenAI channel only)'
...
@@ -3395,9 +3816,13 @@ export function ChannelMutateDrawer({
...
@@ -3395,9 +3816,13 @@ export function ChannelMutateDrawer({
render=
{
({
field
})
=>
(
render=
{
({
field
})
=>
(
<
FormItem
className=
'flex items-center justify-between px-4 py-3'
>
<
FormItem
className=
'flex items-center justify-between px-4 py-3'
>
<
div
className=
'space-y-0.5'
>
<
div
className=
'space-y-0.5'
>
<
FormLabel
>
{
t
(
'Pass Through Body'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'Pass Through Body'
)
}
</
FormLabel
>
<
FormDescription
>
<
FormDescription
>
{
t
(
'Pass request body directly to upstream'
)
}
{
t
(
'Pass request body directly to upstream'
)
}
</
FormDescription
>
</
FormDescription
>
</
div
>
</
div
>
<
FormControl
>
<
FormControl
>
...
@@ -3444,7 +3869,9 @@ export function ChannelMutateDrawer({
...
@@ -3444,7 +3869,9 @@ export function ChannelMutateDrawer({
<
FormLabel
>
{
t
(
'Proxy Address'
)
}
</
FormLabel
>
<
FormLabel
>
{
t
(
'Proxy Address'
)
}
</
FormLabel
>
<
FormControl
>
<
FormControl
>
<
Input
<
Input
placeholder=
{
t
(
'socks5://user:pass@host:port'
)
}
placeholder=
{
t
(
'socks5://user:pass@host:port'
)
}
{
...
field
}
{
...
field
}
/>
/>
</
FormControl
>
</
FormControl
>
...
@@ -3474,7 +3901,9 @@ export function ChannelMutateDrawer({
...
@@ -3474,7 +3901,9 @@ export function ChannelMutateDrawer({
/>
/>
</
FormControl
>
</
FormControl
>
<
FormDescription
>
<
FormDescription
>
{
t
(
'Default system prompt for this channel'
)
}
{
t
(
'Default system prompt for this channel'
)
}
</
FormDescription
>
</
FormDescription
>
<
FormMessage
/>
<
FormMessage
/>
</
FormItem
>
</
FormItem
>
...
@@ -3595,28 +4024,33 @@ export function ChannelMutateDrawer({
...
@@ -3595,28 +4024,33 @@ export function ChannelMutateDrawer({
<
span
className=
'text-foreground font-medium'
>
<
span
className=
'text-foreground font-medium'
>
{
t
(
'Last check time'
)
}
:
{
t
(
'Last check time'
)
}
:
</
span
>
{
' '
}
</
span
>
{
' '
}
{
formatUnixTime
(
upstreamUpdateMeta
.
lastCheckTime
)
}
{
formatUnixTime
(
upstreamUpdateMeta
.
lastCheckTime
)
}
</
div
>
</
div
>
<
div
>
<
div
>
<
span
className=
'text-foreground font-medium'
>
<
span
className=
'text-foreground font-medium'
>
{
t
(
'Last detected addable models'
)
}
:
{
t
(
'Last detected addable models'
)
}
:
</
span
>
{
' '
}
</
span
>
{
' '
}
{
upstreamUpdateMeta
.
detectedModels
.
length
===
{
upstreamUpdateMeta
.
detectedModels
0
?
(
.
length
===
0
?
(
t
(
'None'
)
t
(
'None'
)
)
:
(
)
:
(
<>
<>
<
span
className=
'break-all'
>
<
span
className=
'break-all'
>
{
upstreamDetectedModelsPreview
.
join
(
', '
)
}
{
upstreamDetectedModelsPreview
.
join
(
', '
)
}
</
span
>
</
span
>
{
upstreamDetectedModelsOmittedCount
>
0
&&
(
{
upstreamDetectedModelsOmittedCount
>
0
&&
(
<
span
className=
'ml-1'
>
<
span
className=
'ml-1'
>
{
t
(
{
t
(
'
({{
total
}}
total
,
{{
omit
}}
omitted
)
'
,
'
({{
total
}}
total
,
{{
omit
}}
omitted
)
'
,
{
{
total
:
total
:
upstreamUpdateMeta
.
detectedModels
upstreamUpdateMeta
.
length
,
.
detectedModels
.
length
,
omit
:
upstreamDetectedModelsOmittedCount
,
omit
:
upstreamDetectedModelsOmittedCount
,
}
}
)
}
)
}
...
@@ -3631,7 +4065,9 @@ export function ChannelMutateDrawer({
...
@@ -3631,7 +4065,9 @@ export function ChannelMutateDrawer({
</
fieldset
>
</
fieldset
>
</
div
>
</
div
>
</
ChannelAdvancedSection
>
</
ChannelAdvancedSection
>
</>
</
div
>
</
div
>
</
div
>
)
}
)
}
</
form
>
</
form
>
</
Form
>
</
Form
>
...
...
web/default/src/features/channels/components/drawers/sections/channel-advanced-section.tsx
View file @
1d166532
import
{
ChevronDown
,
Settings
}
from
'lucide-react'
/*
/*
Copyright (C) 2023-2026 QuantumNous
Copyright (C) 2023-2026 QuantumNous
...
@@ -17,19 +18,20 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
...
@@ -17,19 +18,20 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact support@quantumnous.com
*/
*/
import
type
{
ReactNode
}
from
'react'
import
type
{
ReactNode
}
from
'react'
import
{
ChevronDown
,
Settings
}
from
'lucide-react'
import
{
useTranslation
}
from
'react-i18next'
import
{
useTranslation
}
from
'react-i18next'
import
{
cn
}
from
'@/lib/utils'
import
{
import
{
Collapsible
,
Collapsible
,
CollapsibleContent
,
CollapsibleContent
,
CollapsibleTrigger
,
CollapsibleTrigger
,
}
from
'@/components/ui/collapsible'
}
from
'@/components/ui/collapsible'
import
{
cn
}
from
'@/lib/utils'
type
ChannelAdvancedSectionProps
=
{
type
ChannelAdvancedSectionProps
=
{
children
:
ReactNode
children
:
ReactNode
open
:
boolean
open
:
boolean
onOpenChange
:
(
open
:
boolean
)
=>
void
onOpenChange
:
(
open
:
boolean
)
=>
void
summary
?:
ReactNode
}
}
export
function
ChannelAdvancedSection
(
props
:
ChannelAdvancedSectionProps
)
{
export
function
ChannelAdvancedSection
(
props
:
ChannelAdvancedSectionProps
)
{
...
@@ -55,7 +57,8 @@ export function ChannelAdvancedSection(props: ChannelAdvancedSectionProps) {
...
@@ -55,7 +57,8 @@ export function ChannelAdvancedSection(props: ChannelAdvancedSectionProps) {
{
t
(
'Advanced Settings'
)
}
{
t
(
'Advanced Settings'
)
}
</
div
>
</
div
>
<
div
className=
'text-muted-foreground text-xs'
>
<
div
className=
'text-muted-foreground text-xs'
>
{
t
(
{
props
.
summary
??
t
(
'Request overrides, routing behavior, and upstream model automation'
'Request overrides, routing behavior, and upstream model automation'
)
}
)
}
</
div
>
</
div
>
...
...
web/default/src/features/channels/components/drawers/sections/channel-api-access-section.tsx
View file @
1d166532
...
@@ -17,8 +17,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
...
@@ -17,8 +17,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact support@quantumnous.com
*/
*/
import
type
{
ReactNode
}
from
'react'
import
type
{
ReactNode
}
from
'react'
import
{
Link2
}
from
'lucide-react'
import
{
KeyRound
}
from
'lucide-react'
import
{
useTranslation
}
from
'react-i18next'
import
{
useTranslation
}
from
'react-i18next'
import
{
import
{
SideDrawerSection
,
SideDrawerSection
,
SideDrawerSectionHeader
,
SideDrawerSectionHeader
,
...
@@ -34,11 +35,9 @@ export function ChannelApiAccessSection(props: ChannelApiAccessSectionProps) {
...
@@ -34,11 +35,9 @@ export function ChannelApiAccessSection(props: ChannelApiAccessSectionProps) {
return
(
return
(
<
SideDrawerSection
>
<
SideDrawerSection
>
<
SideDrawerSectionHeader
<
SideDrawerSectionHeader
title=
{
t
(
'API Access'
)
}
title=
{
t
(
'Credentials'
)
}
description=
{
t
(
description=
{
t
(
'Authentication'
)
}
'Endpoint, provider-specific settings, and credentials.'
icon=
{
<
KeyRound
className=
'h-4 w-4'
aria
-
hidden=
'true'
/>
}
)
}
icon=
{
<
Link2
className=
'h-4 w-4'
aria
-
hidden=
'true'
/>
}
/>
/>
{
props
.
children
}
{
props
.
children
}
</
SideDrawerSection
>
</
SideDrawerSection
>
...
...
web/default/src/features/channels/components/drawers/sections/channel-auth-section.tsx
View file @
1d166532
...
@@ -28,7 +28,7 @@ export function ChannelAuthSection(props: ChannelAuthSectionProps) {
...
@@ -28,7 +28,7 @@ export function ChannelAuthSection(props: ChannelAuthSectionProps) {
const
{
t
}
=
useTranslation
()
const
{
t
}
=
useTranslation
()
return
(
return
(
<
div
className=
'border-border/60 flex flex-col gap-
4
border-t pt-4'
>
<
div
className=
'border-border/60 flex flex-col gap-
3
border-t pt-4'
>
<
div
className=
'flex items-center gap-2'
>
<
div
className=
'flex items-center gap-2'
>
<
KeyRound
<
KeyRound
className=
'text-muted-foreground h-3.5 w-3.5'
className=
'text-muted-foreground h-3.5 w-3.5'
...
...
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