🐛 fix: multi-key channel sync and Vertex-AI key-upload edge cases

Backend
1. controller/channel.go
   • Always hydrate `ChannelInfo` from DB in `UpdateChannel`, keeping `IsMultiKey` true so `MultiKeySize` is recalculated.

2. model/channel.go
   • getKeys(): accept both newline-separated keys and JSON array (`[ {...}, {...} ]`).
   • Update(): reuse new parser-logic to recalc `MultiKeySize`; prune stale indices in `MultiKeyStatusList`.

Frontend
1. pages/Channel/EditChannel.js
   • `handleVertexUploadChange`
     – Reset `vertexErroredNames` on every change so the “ignored files” prompt always re-appears.
     – In single-key mode keep only the last file; in batch mode keep all valid files.
     – Parse files, display “以下文件解析失败,已忽略:…”.
   • Batch-toggle checkbox
     – When switching from batch→single while multiple files are present, show a confirm dialog and retain only the first file (synchronises state, form and local caches).
   • On opening the “new channel” side-sheet, clear `vertexErroredNames` to restore error prompts.

Result
• “已启用 x/x” count updates immediately after editing multi-key channels.
• Vertex-AI key upload works intuitively: proper error feedback, no duplicated files, and safe down-switch from batch to single mode.
This commit is contained in:
t0ng7u
2025-07-15 12:02:04 +08:00
parent 9326bf96fc
commit 06ad5e3f8c
5 changed files with 100 additions and 29 deletions

View File

@@ -987,7 +987,6 @@ const ChannelsTable = () => {
};
useEffect(() => {
// console.log('default effect')
const localIdSort = localStorage.getItem('id-sort') === 'true';
const localPageSize =
parseInt(localStorage.getItem('page-size')) || ITEMS_PER_PAGE;

View File

@@ -1770,10 +1770,15 @@
"轮询": "Polling",
"密钥文件 (.json)": "Key file (.json)",
"点击上传文件或拖拽文件到这里": "Click to upload file or drag and drop file here",
"仅支持 JSON 文件": "Only JSON files are supported",
"仅支持 JSON 文件,支持多文件": "Only JSON files are supported, multiple files are supported",
"请上传密钥文件": "Please upload the key file",
"请填写部署地区": "Please fill in the deployment region",
"请输入部署地区例如us-central1\n支持使用模型映射格式\n{\n \"default\": \"us-central1\",\n \"claude-3-5-sonnet-20240620\": \"europe-west1\"\n}": "Please enter the deployment region, for example: us-central1\nSupports using model mapping format\n{\n \"default\": \"us-central1\",\n \"claude-3-5-sonnet-20240620\": \"europe-west1\"\n}",
"其他": "Other",
"未知渠道": "Unknown channel"
"未知渠道": "Unknown channel",
"切换为单密钥模式": "Switch to single key mode",
"将仅保留第一个密钥文件,其余文件将被移除,是否继续?": "Only the first key file will be retained, and the remaining files will be removed. Continue?",
"自定义模型名称": "Custom model name",
"启用全部密钥": "Enable all keys"
}

View File

@@ -26,7 +26,6 @@ import {
Form,
Row,
Col,
Upload,
} from '@douyinfe/semi-ui';
import { getChannelModels, copy, getChannelIcon, getModelCategories } from '../../helpers';
import {
@@ -424,9 +423,10 @@ const EditChannel = (props) => {
}, [props.visible, channelId]);
const handleVertexUploadChange = ({ fileList }) => {
vertexErroredNames.current.clear();
(async () => {
const validFiles = [];
const keys = [];
let validFiles = [];
let keys = [];
const errorNames = [];
for (const item of fileList) {
const fileObj = item.fileInstance;
@@ -434,7 +434,7 @@ const EditChannel = (props) => {
try {
const txt = await fileObj.text();
keys.push(JSON.parse(txt));
validFiles.push(item); // 仅合法文件加入列表
validFiles.push(item);
} catch (err) {
if (!vertexErroredNames.current.has(item.name)) {
errorNames.push(item.name);
@@ -443,6 +443,12 @@ const EditChannel = (props) => {
}
}
// 非批量模式下只保留一个文件(最新选择的),避免重复叠加
if (!batch && validFiles.length > 1) {
validFiles = [validFiles[validFiles.length - 1]];
keys = [keys[keys.length - 1]];
}
setVertexKeys(keys);
setVertexFileList(validFiles);
if (formApiRef.current) {
@@ -603,13 +609,45 @@ const EditChannel = (props) => {
const batchAllowed = !isEdit || isMultiKeyChannel;
const batchExtra = batchAllowed ? (
<Space>
<Checkbox disabled={isEdit} checked={batch} onChange={() => {
setBatch(!batch);
if (batch) {
setMultiToSingle(false);
setMultiKeyMode('random');
}
}}>{t('批量创建')}</Checkbox>
<Checkbox
disabled={isEdit}
checked={batch}
onChange={(e) => {
const checked = e.target.checked;
if (!checked && vertexFileList.length > 1) {
Modal.confirm({
title: t('切换为单密钥模式'),
content: t('将仅保留第一个密钥文件,其余文件将被移除,是否继续?'),
onOk: () => {
const firstFile = vertexFileList[0];
const firstKey = vertexKeys[0] ? [vertexKeys[0]] : [];
setVertexFileList([firstFile]);
setVertexKeys(firstKey);
formApiRef.current?.setValue('vertex_files', [firstFile]);
setInputs((prev) => ({ ...prev, vertex_files: [firstFile] }));
setBatch(false);
setMultiToSingle(false);
setMultiKeyMode('random');
},
onCancel: () => {
setBatch(true);
},
centered: true,
});
return;
}
setBatch(checked);
if (!checked) {
setMultiToSingle(false);
setMultiKeyMode('random');
}
}}
>{t('批量创建')}</Checkbox>
{batch && (
<Checkbox disabled={isEdit} checked={multiToSingle} onChange={() => {
setMultiToSingle(prev => !prev);