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
23206840
authored
Jan 26, 2026
by
Seefs
Committed by
GitHub
Jan 26, 2026
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #2610 from Bliod-Cook/main
parents
5e23d5d6
67d6699c
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
201 additions
and
2 deletions
+201
-2
web/src/components/common/ui/JSONEditor.jsx
+6
-2
web/src/components/table/channels/modals/EditChannelModal.jsx
+0
-0
web/src/components/table/channels/modals/SingleModelSelectModal.jsx
+195
-0
No files found.
web/src/components/common/ui/JSONEditor.jsx
View file @
23206840
...
...
@@ -60,6 +60,7 @@ const JSONEditor = ({
editorType
=
'keyValue'
,
rules
=
[],
formApi
=
null
,
renderStringValueSuffix
,
...
props
})
=>
{
const
{
t
}
=
useTranslation
();
...
...
@@ -335,7 +336,7 @@ const JSONEditor = ({
]);
// 渲染值输入控件(支持嵌套)
const
renderValueInput
=
(
pairId
,
value
)
=>
{
const
renderValueInput
=
(
pairId
,
pairKey
,
value
)
=>
{
const
valueType
=
typeof
value
;
if
(
valueType
===
'boolean'
)
{
...
...
@@ -387,6 +388,7 @@ const JSONEditor = ({
<
Input
placeholder=
{
t
(
'参数值'
)
}
value=
{
String
(
value
)
}
suffix=
{
renderStringValueSuffix
?.({
pairId
,
pairKey
,
value
})
}
onChange=
{
(
newValue
)
=>
{
let
convertedValue
=
newValue
;
if
(
newValue
===
'true'
)
convertedValue
=
true
;
...
...
@@ -470,7 +472,9 @@ const JSONEditor = ({
)
}
</
div
>
</
Col
>
<
Col
span=
{
12
}
>
{
renderValueInput
(
pair
.
id
,
pair
.
value
)
}
</
Col
>
<
Col
span=
{
12
}
>
{
renderValueInput
(
pair
.
id
,
pair
.
key
,
pair
.
value
)
}
</
Col
>
<
Col
span=
{
2
}
>
<
Button
icon=
{
<
IconDelete
/>
}
...
...
web/src/components/table/channels/modals/EditChannelModal.jsx
View file @
23206840
This diff is collapsed.
Click to expand it.
web/src/components/table/channels/modals/SingleModelSelectModal.jsx
0 → 100644
View file @
23206840
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import
React
,
{
useEffect
,
useMemo
,
useState
}
from
'react'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
useIsMobile
}
from
'../../../../hooks/common/useIsMobile'
;
import
{
Collapse
,
Empty
,
Input
,
Modal
,
Radio
,
Typography
,
}
from
'@douyinfe/semi-ui'
;
import
{
IllustrationNoResult
,
IllustrationNoResultDark
,
}
from
'@douyinfe/semi-illustrations'
;
import
{
IconSearch
}
from
'@douyinfe/semi-icons'
;
import
{
getModelCategories
}
from
'../../../../helpers/render'
;
const
SingleModelSelectModal
=
({
visible
,
models
=
[],
selected
=
''
,
onConfirm
,
onCancel
,
})
=>
{
const
{
t
}
=
useTranslation
();
const
isMobile
=
useIsMobile
();
const
normalizeModelName
=
(
model
)
=>
String
(
model
??
''
).
trim
();
const
normalizedModels
=
useMemo
(()
=>
{
const
list
=
Array
.
isArray
(
models
)
?
models
:
[];
return
Array
.
from
(
new
Set
(
list
.
map
(
normalizeModelName
).
filter
(
Boolean
)));
},
[
models
]);
const
[
keyword
,
setKeyword
]
=
useState
(
''
);
const
[
selectedModel
,
setSelectedModel
]
=
useState
(
''
);
useEffect
(()
=>
{
if
(
visible
)
{
setKeyword
(
''
);
setSelectedModel
(
normalizeModelName
(
selected
));
}
},
[
visible
,
selected
]);
const
filteredModels
=
useMemo
(()
=>
{
const
lower
=
keyword
.
trim
().
toLowerCase
();
if
(
!
lower
)
return
normalizedModels
;
return
normalizedModels
.
filter
((
m
)
=>
m
.
toLowerCase
().
includes
(
lower
));
},
[
normalizedModels
,
keyword
]);
const
modelsByCategory
=
useMemo
(()
=>
{
const
categories
=
getModelCategories
(
t
);
const
categorized
=
{};
const
uncategorized
=
[];
filteredModels
.
forEach
((
model
)
=>
{
let
foundCategory
=
false
;
for
(
const
[
key
,
category
]
of
Object
.
entries
(
categories
))
{
if
(
key
!==
'all'
&&
category
.
filter
({
model_name
:
model
}))
{
if
(
!
categorized
[
key
])
{
categorized
[
key
]
=
{
label
:
category
.
label
,
icon
:
category
.
icon
,
models
:
[],
};
}
categorized
[
key
].
models
.
push
(
model
);
foundCategory
=
true
;
break
;
}
}
if
(
!
foundCategory
)
{
uncategorized
.
push
(
model
);
}
});
if
(
uncategorized
.
length
>
0
)
{
categorized
.
other
=
{
label
:
t
(
'其他'
),
icon
:
null
,
models
:
uncategorized
,
};
}
return
categorized
;
},
[
filteredModels
,
t
]);
const
categoryEntries
=
useMemo
(
()
=>
Object
.
entries
(
modelsByCategory
),
[
modelsByCategory
],
);
return
(
<
Modal
header=
{
<
div
className=
'flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 sm:gap-4 py-4'
>
<
Typography
.
Title
heading=
{
5
}
className=
'm-0'
>
{
t
(
'选择模型'
)
}
</
Typography
.
Title
>
</
div
>
}
visible=
{
visible
}
onOk=
{
()
=>
onConfirm
?.(
selectedModel
)
}
onCancel=
{
onCancel
}
okText=
{
t
(
'确定'
)
}
cancelText=
{
t
(
'取消'
)
}
okButtonProps=
{
{
disabled
:
!
selectedModel
}
}
size=
{
isMobile
?
'full-width'
:
'large'
}
closeOnEsc
maskClosable
centered
>
<
Input
prefix=
{
<
IconSearch
size=
{
14
}
/>
}
placeholder=
{
t
(
'搜索模型'
)
}
value=
{
keyword
}
onChange=
{
(
v
)
=>
setKeyword
(
v
)
}
showClear
/>
<
div
style=
{
{
maxHeight
:
400
,
overflowY
:
'auto'
,
paddingRight
:
8
}
}
>
{
filteredModels
.
length
===
0
?
(
<
Empty
image=
{
<
IllustrationNoResult
style=
{
{
width
:
150
,
height
:
150
}
}
/>
}
darkModeImage=
{
<
IllustrationNoResultDark
style=
{
{
width
:
150
,
height
:
150
}
}
/>
}
description=
{
t
(
'暂无匹配模型'
)
}
style=
{
{
padding
:
30
}
}
/>
)
:
(
<
Radio
.
Group
className=
'w-full'
style=
{
{
width
:
'100%'
}
}
value=
{
selectedModel
}
onChange=
{
(
val
)
=>
{
const
next
=
val
&&
val
.
target
?
val
.
target
.
value
:
val
;
setSelectedModel
(
next
);
}
}
>
<
Collapse
className=
'w-full'
style=
{
{
width
:
'100%'
}
}
defaultActiveKey=
{
[]
}
>
{
categoryEntries
.
map
(([
key
,
categoryData
],
index
)
=>
(
<
Collapse
.
Panel
key=
{
`${key}_${index}`
}
itemKey=
{
`${key}_${index}`
}
header=
{
<
span
className=
'flex items-center gap-2'
>
{
categoryData
.
icon
}
<
span
>
{
categoryData
.
label
}
(
{
categoryData
.
models
.
length
}
)
</
span
>
</
span
>
}
>
<
div
className=
'grid grid-cols-2 gap-x-4'
>
{
categoryData
.
models
.
map
((
model
)
=>
(
<
Radio
key=
{
model
}
value=
{
model
}
className=
'my-1'
>
{
model
}
</
Radio
>
))
}
</
div
>
</
Collapse
.
Panel
>
))
}
</
Collapse
>
</
Radio
.
Group
>
)
}
</
div
>
</
Modal
>
);
};
export
default
SingleModelSelectModal
;
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