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,