From 617c8e8f4fdf45ef7cb4f35bc0823c7c64935b77 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Mon, 16 Jun 2025 01:47:41 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(channel-ui):=20support=20multi?= =?UTF-8?q?-JSON=20batch=20creation=20for=20Vertex=20AI=20&=20multi-to-sin?= =?UTF-8?q?gle=20mode?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WHY • Backend (0089157) now accepts structured request `{ mode, channel }`, including new `multi_to_single`. • Need front-end to upload multiple service-account JSON files and generate correct `channel.key`. • Improve UX: avoid red “uploadFail” state and offer drag-and-drop UI. WHAT 1. EditChannel.js • Added Upload drag-area with IconBolt; `uploadTrigger="custom"`. • `handleJsonFileUpload` reads file, pushes content to `jsonFiles`, returns `{ shouldUpload:false, status:'success' }`. • New states: `batch`, `mergeToSingle`, `jsonFiles`. • Dynamic mode resolver: `single` | `batch` | `multi_to_single`. • Builds `channel.key` as JSON-object whose keys are the raw credential texts. • UI: – “Batch create” checkbox (new build only). – Nested “Merge to single channel (multi-key mode)” checkbox enabled when batch=true. – Real-time file count display. 2. Upload UX • Drag-and-drop, accepts `.json,application/json`. • Custom texts: “Click or drop files here” / “JSON credentials only”. • Eliminated mandatory `action` warning (`action="#"`). 3. Misc • Included IconBolt import. • Safeguard toggles reset logic to prevent stale state. RESULT Front-end now fully aligns with enhanced AddChannel API: • Supports Vertex AI multi JSON batch creation. • Supports new `multi_to_single` flow. • Clean user feedback with successful file status. --- web/src/pages/Channel/EditChannel.js | 212 ++++++++++++++++++--------- 1 file changed, 146 insertions(+), 66 deletions(-) diff --git a/web/src/pages/Channel/EditChannel.js b/web/src/pages/Channel/EditChannel.js index 8bfe5812..fa77be81 100644 --- a/web/src/pages/Channel/EditChannel.js +++ b/web/src/pages/Channel/EditChannel.js @@ -25,6 +25,7 @@ import { ImagePreview, Card, Tag, + Upload, } from '@douyinfe/semi-ui'; import { getChannelModels } from '../../helpers'; import { @@ -34,6 +35,7 @@ import { IconSetting, IconCode, IconGlobe, + IconBolt, } from '@douyinfe/semi-icons'; const { Text, Title } = Typography; @@ -97,8 +99,9 @@ const EditChannel = (props) => { tag: '', }; const [batch, setBatch] = useState(false); + const [mergeToSingle, setMergeToSingle] = useState(false); const [autoBan, setAutoBan] = useState(true); - // const [autoBan, setAutoBan] = useState(true); + const [jsonFiles, setJsonFiles] = useState([]); const [inputs, setInputs] = useState(originInputs); const [originModelOptions, setOriginModelOptions] = useState([]); const [modelOptions, setModelOptions] = useState([]); @@ -325,9 +328,20 @@ const EditChannel = (props) => { }, [props.editingChannel.id]); const submit = async () => { - if (!isEdit && (inputs.name === '' || inputs.key === '')) { - showInfo(t('请填写渠道名称和渠道密钥!')); - return; + if (!isEdit) { + if (inputs.name === '') { + showInfo(t('请填写渠道名称!')); + return; + } + if (inputs.type === 41 && batch) { + if (jsonFiles.length === 0) { + showInfo(t('请至少选择一个 JSON 凭证文件!')); + return; + } + } else if (inputs.key === '') { + showInfo(t('请填写渠道密钥!')); + return; + } } if (inputs.models.length === 0) { showInfo(t('请至少选择一个模型!')); @@ -356,13 +370,32 @@ const EditChannel = (props) => { localInputs.auto_ban = autoBan ? 1 : 0; localInputs.models = localInputs.models.join(','); localInputs.group = localInputs.groups.join(','); + + if (inputs.type === 41 && batch) { + const keyObj = {}; + jsonFiles.forEach((content, idx) => { + keyObj[content] = idx; + }); + localInputs.key = JSON.stringify(keyObj); + } + + let mode = 'single'; + if (batch) { + mode = mergeToSingle ? 'multi_to_single' : 'batch'; + } + + const payload = { + mode, + channel: localInputs, + }; + if (isEdit) { res = await API.put(`/api/channel/`, { ...localInputs, id: parseInt(channelId), }); } else { - res = await API.post(`/api/channel/`, localInputs); + res = await API.post(`/api/channel/`, payload); } const { success, message } = res.data; if (success) { @@ -415,6 +448,18 @@ const EditChannel = (props) => { } }; + const handleJsonFileUpload = (file) => { + return new Promise((resolve) => { + const reader = new FileReader(); + reader.onload = (e) => { + const content = e.target.result; + setJsonFiles((prev) => [...prev, content]); + resolve({ shouldUpload: false, status: 'success' }); + }; + reader.readAsText(file); + }); + }; + return ( <> {
{t('密钥')} {batch ? ( -