diff --git a/web/src/components/table/channels/modals/EditChannelModal.jsx b/web/src/components/table/channels/modals/EditChannelModal.jsx index 17175669..8558b734 100644 --- a/web/src/components/table/channels/modals/EditChannelModal.jsx +++ b/web/src/components/table/channels/modals/EditChannelModal.jsx @@ -67,6 +67,7 @@ import SecureVerificationModal from '../../../common/modals/SecureVerificationMo import StatusCodeRiskGuardModal from './StatusCodeRiskGuardModal'; import ChannelKeyDisplay from '../../../common/ui/ChannelKeyDisplay'; import { useSecureVerification } from '../../../../hooks/common/useSecureVerification'; +import { parseChannelConnectionString } from '../../../../helpers/token'; import { createApiCalls } from '../../../../services/secureVerification'; import { collectInvalidStatusCodeEntries, @@ -398,6 +399,9 @@ const EditChannelModal = (props) => { [], ); + // 剪贴板连接信息自动检测 + const [clipboardConfig, setClipboardConfig] = useState(null); + // 高级设置折叠状态 const [advancedSettingsOpen, setAdvancedSettingsOpen] = useState(false); const formContainerRef = useRef(null); @@ -538,6 +542,35 @@ const EditChannelModal = (props) => { handleInputChange('settings', settingsJson); }; + const applyClipboardConfig = (config) => { + if (!config) return; + setInputs((prev) => ({ + ...prev, + key: config.key, + base_url: config.url, + })); + if (formApiRef.current) { + formApiRef.current.setValue('key', config.key); + formApiRef.current.setValue('base_url', config.url); + } + setClipboardConfig(null); + showSuccess(t('连接信息已填入')); + }; + + const pasteFromClipboard = async () => { + try { + const text = await navigator.clipboard.readText(); + const parsed = parseChannelConnectionString(text); + if (parsed) { + applyClipboardConfig(parsed); + } else { + showInfo(t('剪贴板中未检测到连接信息')); + } + } catch { + showError(t('无法读取剪贴板')); + } + }; + const isIonetLocked = isIonetChannel && isEdit; const handleInputChange = (name, value) => { @@ -1269,6 +1302,13 @@ const EditChannelModal = (props) => { loadChannel(); } else { formApiRef.current?.setValues(getInitValues()); + // best-effort clipboard auto-detect for new channels + navigator.clipboard.readText().then((text) => { + const parsed = parseChannelConnectionString(text); + if (parsed) { + setClipboardConfig(parsed); + } + }).catch(() => {}); } fetchModelGroups(); // 重置手动输入模式状态 @@ -1329,6 +1369,8 @@ const EditChannelModal = (props) => { setInputs(getInitValues()); // 重置密钥显示状态 resetKeyDisplayState(); + // 重置剪贴板检测状态 + setClipboardConfig(null); }; const handleVertexUploadChange = ({ fileList }) => { @@ -2077,14 +2119,27 @@ const EditChannelModal = (props) => { - - {isEdit ? t('编辑') : t('新建')} - - - {isEdit ? t('更新渠道信息') : t('创建新的渠道')} - - +
+ + + {isEdit ? t('编辑') : t('新建')} + + + {isEdit ? t('更新渠道信息') : t('创建新的渠道')} + + + {!isEdit && ( + + )} +
} bodyStyle={{ padding: '0' }} visible={props.visible} @@ -2446,6 +2501,34 @@ const EditChannelModal = (props) => { <>
+ {!isEdit && clipboardConfig && ( + + {t('检测到剪贴板中的连接信息')} +
+ + +
+
+ } + /> + )} {/* Core Configuration Card - Always Visible */} {/* Header */} diff --git a/web/src/components/table/tokens/TokensColumnDefs.jsx b/web/src/components/table/tokens/TokensColumnDefs.jsx index 86c4545a..d78f7545 100644 --- a/web/src/components/table/tokens/TokensColumnDefs.jsx +++ b/web/src/components/table/tokens/TokensColumnDefs.jsx @@ -116,6 +116,8 @@ const renderTokenKey = ( loadingTokenKeys, toggleTokenVisibility, copyTokenKey, + copyTokenConnectionString, + t, ) => { const revealed = !!showKeys[record.id]; const loading = !!loadingTokenKeys[record.id]; @@ -145,18 +147,35 @@ const renderTokenKey = ( await toggleTokenVisibility(record); }} /> -