@@ -190,6 +190,30 @@ const EditChannelModal = (props) => {
190190 const [ keyMode , setKeyMode ] = useState ( 'append' ) ; // 密钥模式:replace(覆盖)或 append(追加)
191191 const [ isEnterpriseAccount , setIsEnterpriseAccount ] = useState ( false ) ; // 是否为企业账户
192192 const [ doubaoApiEditUnlocked , setDoubaoApiEditUnlocked ] = useState ( false ) ; // 豆包渠道自定义 API 地址隐藏入口
193+ const redirectModelList = useMemo ( ( ) => {
194+ const mapping = inputs . model_mapping ;
195+ if ( typeof mapping !== 'string' ) return [ ] ;
196+ const trimmed = mapping . trim ( ) ;
197+ if ( ! trimmed ) return [ ] ;
198+ try {
199+ const parsed = JSON . parse ( trimmed ) ;
200+ if (
201+ ! parsed ||
202+ typeof parsed !== 'object' ||
203+ Array . isArray ( parsed )
204+ ) {
205+ return [ ] ;
206+ }
207+ const values = Object . values ( parsed )
208+ . map ( ( value ) =>
209+ typeof value === 'string' ? value . trim ( ) : undefined ,
210+ )
211+ . filter ( ( value ) => value ) ;
212+ return Array . from ( new Set ( values ) ) ;
213+ } catch ( error ) {
214+ return [ ] ;
215+ }
216+ } , [ inputs . model_mapping ] ) ;
193217
194218 // 密钥显示状态
195219 const [ keyDisplayState , setKeyDisplayState ] = useState ( {
@@ -220,6 +244,8 @@ const EditChannelModal = (props) => {
220244 ] ;
221245 const formContainerRef = useRef ( null ) ;
222246 const doubaoApiClickCountRef = useRef ( 0 ) ;
247+ const initialModelsRef = useRef ( [ ] ) ;
248+ const initialModelMappingRef = useRef ( '' ) ;
223249
224250 // 2FA状态更新辅助函数
225251 const updateTwoFAState = ( updates ) => {
@@ -595,6 +621,10 @@ const EditChannelModal = (props) => {
595621 system_prompt : data . system_prompt ,
596622 system_prompt_override : data . system_prompt_override || false ,
597623 } ) ;
624+ initialModelsRef . current = ( data . models || [ ] )
625+ . map ( ( model ) => ( model || '' ) . trim ( ) )
626+ . filter ( Boolean ) ;
627+ initialModelMappingRef . current = data . model_mapping || '' ;
598628 // console.log(data);
599629 } else {
600630 showError ( message ) ;
@@ -830,6 +860,13 @@ const EditChannelModal = (props) => {
830860 }
831861 } , [ props . visible , channelId ] ) ;
832862
863+ useEffect ( ( ) => {
864+ if ( ! isEdit ) {
865+ initialModelsRef . current = [ ] ;
866+ initialModelMappingRef . current = '' ;
867+ }
868+ } , [ isEdit , props . visible ] ) ;
869+
833870 // 统一的模态框重置函数
834871 const resetModalState = ( ) => {
835872 formApiRef . current ?. reset ( ) ;
@@ -903,6 +940,80 @@ const EditChannelModal = (props) => {
903940 } ) ( ) ;
904941 } ;
905942
943+ const confirmMissingModelMappings = ( missingModels ) =>
944+ new Promise ( ( resolve ) => {
945+ const modal = Modal . confirm ( {
946+ title : t ( '模型未加入列表,可能无法调用' ) ,
947+ content : (
948+ < div className = 'text-sm leading-6' >
949+ < div >
950+ { t (
951+ '模型重定向里的下列模型尚未添加到“模型”列表,调用时会因为缺少可用模型而失败:' ,
952+ ) }
953+ </ div >
954+ < div className = 'font-mono text-xs break-all text-red-600 mt-1' >
955+ { missingModels . join ( ', ' ) }
956+ </ div >
957+ < div className = 'mt-2' >
958+ { t (
959+ '你可以在“自定义模型名称”处手动添加它们,然后点击填入后再提交,或者直接使用下方操作自动处理。' ,
960+ ) }
961+ </ div >
962+ </ div >
963+ ) ,
964+ centered : true ,
965+ footer : (
966+ < Space align = 'center' className = 'w-full justify-end' >
967+ < Button
968+ type = 'tertiary'
969+ onClick = { ( ) => {
970+ modal . destroy ( ) ;
971+ resolve ( 'cancel' ) ;
972+ } }
973+ >
974+ { t ( '返回修改' ) }
975+ </ Button >
976+ < Button
977+ type = 'primary'
978+ theme = 'light'
979+ onClick = { ( ) => {
980+ modal . destroy ( ) ;
981+ resolve ( 'submit' ) ;
982+ } }
983+ >
984+ { t ( '直接提交' ) }
985+ </ Button >
986+ < Button
987+ type = 'primary'
988+ theme = 'solid'
989+ onClick = { ( ) => {
990+ modal . destroy ( ) ;
991+ resolve ( 'add' ) ;
992+ } }
993+ >
994+ { t ( '添加后提交' ) }
995+ </ Button >
996+ </ Space >
997+ ) ,
998+ } ) ;
999+ } ) ;
1000+
1001+ const hasModelConfigChanged = ( normalizedModels , modelMappingStr ) => {
1002+ if ( ! isEdit ) return true ;
1003+ const initialModels = initialModelsRef . current ;
1004+ if ( normalizedModels . length !== initialModels . length ) {
1005+ return true ;
1006+ }
1007+ for ( let i = 0 ; i < normalizedModels . length ; i ++ ) {
1008+ if ( normalizedModels [ i ] !== initialModels [ i ] ) {
1009+ return true ;
1010+ }
1011+ }
1012+ const normalizedMapping = ( modelMappingStr || '' ) . trim ( ) ;
1013+ const initialMapping = ( initialModelMappingRef . current || '' ) . trim ( ) ;
1014+ return normalizedMapping !== initialMapping ;
1015+ } ;
1016+
9061017 const submit = async ( ) => {
9071018 const formValues = formApiRef . current ? formApiRef . current . getValues ( ) : { } ;
9081019 let localInputs = { ...formValues } ;
@@ -986,14 +1097,55 @@ const EditChannelModal = (props) => {
9861097 showInfo ( t ( '请输入API地址!' ) ) ;
9871098 return ;
9881099 }
1100+ const hasModelMapping =
1101+ typeof localInputs . model_mapping === 'string' &&
1102+ localInputs . model_mapping . trim ( ) !== '' ;
1103+ let parsedModelMapping = null ;
1104+ if ( hasModelMapping ) {
1105+ if ( ! verifyJSON ( localInputs . model_mapping ) ) {
1106+ showInfo ( t ( '模型映射必须是合法的 JSON 格式!' ) ) ;
1107+ return ;
1108+ }
1109+ try {
1110+ parsedModelMapping = JSON . parse ( localInputs . model_mapping ) ;
1111+ } catch ( error ) {
1112+ showInfo ( t ( '模型映射必须是合法的 JSON 格式!' ) ) ;
1113+ return ;
1114+ }
1115+ }
1116+
1117+ const normalizedModels = ( localInputs . models || [ ] )
1118+ . map ( ( model ) => ( model || '' ) . trim ( ) )
1119+ . filter ( Boolean ) ;
1120+ localInputs . models = normalizedModels ;
1121+
9891122 if (
990- localInputs . model_mapping &&
991- localInputs . model_mapping !== ' ' &&
992- ! verifyJSON ( localInputs . model_mapping )
1123+ parsedModelMapping &&
1124+ typeof parsedModelMapping === 'object ' &&
1125+ ! Array . isArray ( parsedModelMapping )
9931126 ) {
994- showInfo ( t ( '模型映射必须是合法的 JSON 格式!' ) ) ;
995- return ;
1127+ const modelSet = new Set ( normalizedModels ) ;
1128+ const missingModels = Object . keys ( parsedModelMapping )
1129+ . map ( ( key ) => ( key || '' ) . trim ( ) )
1130+ . filter ( ( key ) => key && ! modelSet . has ( key ) ) ;
1131+ const shouldPromptMissing =
1132+ missingModels . length > 0 &&
1133+ hasModelConfigChanged ( normalizedModels , localInputs . model_mapping ) ;
1134+ if ( shouldPromptMissing ) {
1135+ const confirmAction = await confirmMissingModelMappings ( missingModels ) ;
1136+ if ( confirmAction === 'cancel' ) {
1137+ return ;
1138+ }
1139+ if ( confirmAction === 'add' ) {
1140+ const updatedModels = Array . from (
1141+ new Set ( [ ...normalizedModels , ...missingModels ] ) ,
1142+ ) ;
1143+ localInputs . models = updatedModels ;
1144+ handleInputChange ( 'models' , updatedModels ) ;
1145+ }
1146+ }
9961147 }
1148+
9971149 if ( localInputs . base_url && localInputs . base_url . endsWith ( '/' ) ) {
9981150 localInputs . base_url = localInputs . base_url . slice (
9991151 0 ,
@@ -2916,6 +3068,7 @@ const EditChannelModal = (props) => {
29163068 visible = { modelModalVisible }
29173069 models = { fetchedModels }
29183070 selected = { inputs . models }
3071+ redirectModels = { redirectModelList }
29193072 onConfirm = { ( selectedModels ) => {
29203073 handleInputChange ( 'models' , selectedModels ) ;
29213074 showSuccess ( t ( '模型列表已更新' ) ) ;
0 commit comments