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
3fc9182d
authored
Nov 22, 2025
by
Seefs
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 重定向后的模型视为已有的模型,附带特殊提示
parent
d6939d2f
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
85 additions
and
6 deletions
+85
-6
web/src/components/table/channels/modals/EditChannelModal.jsx
+25
-0
web/src/components/table/channels/modals/ModelSelectModal.jsx
+60
-6
No files found.
web/src/components/table/channels/modals/EditChannelModal.jsx
View file @
3fc9182d
...
@@ -190,6 +190,30 @@ const EditChannelModal = (props) => {
...
@@ -190,6 +190,30 @@ const EditChannelModal = (props) => {
const
[
keyMode
,
setKeyMode
]
=
useState
(
'append'
);
// 密钥模式:replace(覆盖)或 append(追加)
const
[
keyMode
,
setKeyMode
]
=
useState
(
'append'
);
// 密钥模式:replace(覆盖)或 append(追加)
const
[
isEnterpriseAccount
,
setIsEnterpriseAccount
]
=
useState
(
false
);
// 是否为企业账户
const
[
isEnterpriseAccount
,
setIsEnterpriseAccount
]
=
useState
(
false
);
// 是否为企业账户
const
[
doubaoApiEditUnlocked
,
setDoubaoApiEditUnlocked
]
=
useState
(
false
);
// 豆包渠道自定义 API 地址隐藏入口
const
[
doubaoApiEditUnlocked
,
setDoubaoApiEditUnlocked
]
=
useState
(
false
);
// 豆包渠道自定义 API 地址隐藏入口
const
redirectModelList
=
useMemo
(()
=>
{
const
mapping
=
inputs
.
model_mapping
;
if
(
typeof
mapping
!==
'string'
)
return
[];
const
trimmed
=
mapping
.
trim
();
if
(
!
trimmed
)
return
[];
try
{
const
parsed
=
JSON
.
parse
(
trimmed
);
if
(
!
parsed
||
typeof
parsed
!==
'object'
||
Array
.
isArray
(
parsed
)
)
{
return
[];
}
const
values
=
Object
.
values
(
parsed
)
.
map
((
value
)
=>
typeof
value
===
'string'
?
value
.
trim
()
:
undefined
,
)
.
filter
((
value
)
=>
value
);
return
Array
.
from
(
new
Set
(
values
));
}
catch
(
error
)
{
return
[];
}
},
[
inputs
.
model_mapping
]);
// 密钥显示状态
// 密钥显示状态
const
[
keyDisplayState
,
setKeyDisplayState
]
=
useState
({
const
[
keyDisplayState
,
setKeyDisplayState
]
=
useState
({
...
@@ -3044,6 +3068,7 @@ const EditChannelModal = (props) => {
...
@@ -3044,6 +3068,7 @@ const EditChannelModal = (props) => {
visible=
{
modelModalVisible
}
visible=
{
modelModalVisible
}
models=
{
fetchedModels
}
models=
{
fetchedModels
}
selected=
{
inputs
.
models
}
selected=
{
inputs
.
models
}
redirectModels=
{
redirectModelList
}
onConfirm=
{
(
selectedModels
)
=>
{
onConfirm=
{
(
selectedModels
)
=>
{
handleInputChange
(
'models'
,
selectedModels
);
handleInputChange
(
'models'
,
selectedModels
);
showSuccess
(
t
(
'模型列表已更新'
));
showSuccess
(
t
(
'模型列表已更新'
));
...
...
web/src/components/table/channels/modals/ModelSelectModal.jsx
View file @
3fc9182d
...
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
...
@@ -17,7 +17,7 @@ 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
React
,
{
useState
,
useEffect
}
from
'react'
;
import
React
,
{
useState
,
useEffect
,
useMemo
}
from
'react'
;
import
{
useIsMobile
}
from
'../../../../hooks/common/useIsMobile'
;
import
{
useIsMobile
}
from
'../../../../hooks/common/useIsMobile'
;
import
{
import
{
Modal
,
Modal
,
...
@@ -28,12 +28,13 @@ import {
...
@@ -28,12 +28,13 @@ import {
Empty
,
Empty
,
Tabs
,
Tabs
,
Collapse
,
Collapse
,
Tooltip
,
}
from
'@douyinfe/semi-ui'
;
}
from
'@douyinfe/semi-ui'
;
import
{
import
{
IllustrationNoResult
,
IllustrationNoResult
,
IllustrationNoResultDark
,
IllustrationNoResultDark
,
}
from
'@douyinfe/semi-illustrations'
;
}
from
'@douyinfe/semi-illustrations'
;
import
{
IconSearch
}
from
'@douyinfe/semi-icons'
;
import
{
IconSearch
,
IconInfoCircle
}
from
'@douyinfe/semi-icons'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
useTranslation
}
from
'react-i18next'
;
import
{
getModelCategories
}
from
'../../../../helpers/render'
;
import
{
getModelCategories
}
from
'../../../../helpers/render'
;
...
@@ -41,6 +42,7 @@ const ModelSelectModal = ({
...
@@ -41,6 +42,7 @@ const ModelSelectModal = ({
visible
,
visible
,
models
=
[],
models
=
[],
selected
=
[],
selected
=
[],
redirectModels
=
[],
onConfirm
,
onConfirm
,
onCancel
,
onCancel
,
})
=>
{
})
=>
{
...
@@ -50,15 +52,54 @@ const ModelSelectModal = ({
...
@@ -50,15 +52,54 @@ const ModelSelectModal = ({
const
[
activeTab
,
setActiveTab
]
=
useState
(
'new'
);
const
[
activeTab
,
setActiveTab
]
=
useState
(
'new'
);
const
isMobile
=
useIsMobile
();
const
isMobile
=
useIsMobile
();
const
normalizeModelName
=
(
model
)
=>
typeof
model
===
'string'
?
model
.
trim
()
:
''
;
const
normalizedRedirectModels
=
useMemo
(
()
=>
Array
.
from
(
new
Set
(
(
redirectModels
||
[])
.
map
((
model
)
=>
normalizeModelName
(
model
))
.
filter
(
Boolean
),
),
),
[
redirectModels
],
);
const
normalizedSelectedSet
=
useMemo
(()
=>
{
const
set
=
new
Set
();
(
selected
||
[]).
forEach
((
model
)
=>
{
const
normalized
=
normalizeModelName
(
model
);
if
(
normalized
)
{
set
.
add
(
normalized
);
}
});
return
set
;
},
[
selected
]);
const
classificationSet
=
useMemo
(()
=>
{
const
set
=
new
Set
(
normalizedSelectedSet
);
normalizedRedirectModels
.
forEach
((
model
)
=>
set
.
add
(
model
));
return
set
;
},
[
normalizedSelectedSet
,
normalizedRedirectModels
]);
const
redirectOnlySet
=
useMemo
(()
=>
{
const
set
=
new
Set
();
normalizedRedirectModels
.
forEach
((
model
)
=>
{
if
(
!
normalizedSelectedSet
.
has
(
model
))
{
set
.
add
(
model
);
}
});
return
set
;
},
[
normalizedRedirectModels
,
normalizedSelectedSet
]);
const
filteredModels
=
models
.
filter
((
m
)
=>
const
filteredModels
=
models
.
filter
((
m
)
=>
m
.
toLowerCase
().
includes
(
keyword
.
toLowerCase
()),
String
(
m
||
''
)
.
toLowerCase
().
includes
(
keyword
.
toLowerCase
()),
);
);
// 分类模型:新获取的模型和已有模型
// 分类模型:新获取的模型和已有模型
const
newModels
=
filteredModels
.
filter
((
model
)
=>
!
selected
.
includes
(
model
));
const
isExistingModel
=
(
model
)
=>
classificationSet
.
has
(
normalizeModelName
(
model
));
const
newModels
=
filteredModels
.
filter
((
model
)
=>
!
isExistingModel
(
model
));
const
existingModels
=
filteredModels
.
filter
((
model
)
=>
const
existingModels
=
filteredModels
.
filter
((
model
)
=>
selected
.
includes
(
model
),
isExistingModel
(
model
),
);
);
// 同步外部选中值
// 同步外部选中值
...
@@ -228,7 +269,20 @@ const ModelSelectModal = ({
...
@@ -228,7 +269,20 @@ const ModelSelectModal = ({
<
div
className=
'grid grid-cols-2 gap-x-4'
>
<
div
className=
'grid grid-cols-2 gap-x-4'
>
{
categoryData
.
models
.
map
((
model
)
=>
(
{
categoryData
.
models
.
map
((
model
)
=>
(
<
Checkbox
key=
{
model
}
value=
{
model
}
className=
'my-1'
>
<
Checkbox
key=
{
model
}
value=
{
model
}
className=
'my-1'
>
{
model
}
<
span
className=
'flex items-center gap-2'
>
<
span
>
{
model
}
</
span
>
{
redirectOnlySet
.
has
(
normalizeModelName
(
model
))
&&
(
<
Tooltip
position=
'top'
content=
{
t
(
'来自模型重定向,尚未加入模型列表'
)
}
>
<
IconInfoCircle
size=
'small'
className=
'text-amber-500 cursor-help'
/>
</
Tooltip
>
)
}
</
span
>
</
Checkbox
>
</
Checkbox
>
))
}
))
}
</
div
>
</
div
>
...
...
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