diff --git a/web/src/components/table/channels/modals/EditChannelModal.jsx b/web/src/components/table/channels/modals/EditChannelModal.jsx index 6d9388be..25e0f95b 100644 --- a/web/src/components/table/channels/modals/EditChannelModal.jsx +++ b/web/src/components/table/channels/modals/EditChannelModal.jsx @@ -220,6 +220,8 @@ const EditChannelModal = (props) => { ]; const formContainerRef = useRef(null); const doubaoApiClickCountRef = useRef(0); + const initialModelsRef = useRef([]); + const initialModelMappingRef = useRef(''); // 2FA状态更新辅助函数 const updateTwoFAState = (updates) => { @@ -595,6 +597,10 @@ const EditChannelModal = (props) => { system_prompt: data.system_prompt, system_prompt_override: data.system_prompt_override || false, }); + initialModelsRef.current = (data.models || []) + .map((model) => (model || '').trim()) + .filter(Boolean); + initialModelMappingRef.current = data.model_mapping || ''; // console.log(data); } else { showError(message); @@ -830,6 +836,13 @@ const EditChannelModal = (props) => { } }, [props.visible, channelId]); + useEffect(() => { + if (!isEdit) { + initialModelsRef.current = []; + initialModelMappingRef.current = ''; + } + }, [isEdit, props.visible]); + // 统一的模态框重置函数 const resetModalState = () => { formApiRef.current?.reset(); @@ -903,6 +916,80 @@ const EditChannelModal = (props) => { })(); }; + const confirmMissingModelMappings = (missingModels) => + new Promise((resolve) => { + const modal = Modal.confirm({ + title: t('模型未加入列表,可能无法调用'), + content: ( +
+
+ {t( + '模型重定向里的下列模型尚未添加到“模型”列表,调用时会因为缺少可用模型而失败:', + )} +
+
+ {missingModels.join(', ')} +
+
+ {t( + '你可以在“自定义模型名称”处手动添加它们,然后点击填入后再提交,或者直接使用下方操作自动处理。', + )} +
+
+ ), + centered: true, + footer: ( + + + + + + ), + }); + }); + + const hasModelConfigChanged = (normalizedModels, modelMappingStr) => { + if (!isEdit) return true; + const initialModels = initialModelsRef.current; + if (normalizedModels.length !== initialModels.length) { + return true; + } + for (let i = 0; i < normalizedModels.length; i++) { + if (normalizedModels[i] !== initialModels[i]) { + return true; + } + } + const normalizedMapping = (modelMappingStr || '').trim(); + const initialMapping = (initialModelMappingRef.current || '').trim(); + return normalizedMapping !== initialMapping; + }; + const submit = async () => { const formValues = formApiRef.current ? formApiRef.current.getValues() : {}; let localInputs = { ...formValues }; @@ -986,14 +1073,55 @@ const EditChannelModal = (props) => { showInfo(t('请输入API地址!')); return; } - if ( - localInputs.model_mapping && - localInputs.model_mapping !== '' && - !verifyJSON(localInputs.model_mapping) - ) { - showInfo(t('模型映射必须是合法的 JSON 格式!')); - return; + const hasModelMapping = + typeof localInputs.model_mapping === 'string' && + localInputs.model_mapping.trim() !== ''; + let parsedModelMapping = null; + if (hasModelMapping) { + if (!verifyJSON(localInputs.model_mapping)) { + showInfo(t('模型映射必须是合法的 JSON 格式!')); + return; + } + try { + parsedModelMapping = JSON.parse(localInputs.model_mapping); + } catch (error) { + showInfo(t('模型映射必须是合法的 JSON 格式!')); + return; + } } + + const normalizedModels = (localInputs.models || []) + .map((model) => (model || '').trim()) + .filter(Boolean); + localInputs.models = normalizedModels; + + if ( + parsedModelMapping && + typeof parsedModelMapping === 'object' && + !Array.isArray(parsedModelMapping) + ) { + const modelSet = new Set(normalizedModels); + const missingModels = Object.keys(parsedModelMapping) + .map((key) => (key || '').trim()) + .filter((key) => key && !modelSet.has(key)); + const shouldPromptMissing = + missingModels.length > 0 && + hasModelConfigChanged(normalizedModels, localInputs.model_mapping); + if (shouldPromptMissing) { + const confirmAction = await confirmMissingModelMappings(missingModels); + if (confirmAction === 'cancel') { + return; + } + if (confirmAction === 'add') { + const updatedModels = Array.from( + new Set([...normalizedModels, ...missingModels]), + ); + localInputs.models = updatedModels; + handleInputChange('models', updatedModels); + } + } + } + if (localInputs.base_url && localInputs.base_url.endsWith('/')) { localInputs.base_url = localInputs.base_url.slice( 0,