feat: add clipboard magic string for quick channel creation from token copy
Some checks failed
Publish Docker image (Multi Registries, native amd64+arm64) / Build & push (amd64) [native] (push) Has been cancelled
Publish Docker image (Multi Registries, native amd64+arm64) / Build & push (arm64) [native] (push) Has been cancelled
Publish Docker image (Multi Registries, native amd64+arm64) / Create multi-arch manifests (Docker Hub) (push) Has been cancelled
Some checks failed
Publish Docker image (Multi Registries, native amd64+arm64) / Build & push (amd64) [native] (push) Has been cancelled
Publish Docker image (Multi Registries, native amd64+arm64) / Build & push (arm64) [native] (push) Has been cancelled
Publish Docker image (Multi Registries, native amd64+arm64) / Create multi-arch manifests (Docker Hub) (push) Has been cancelled
When copying a token, users can now choose "Copy Connection String" which encodes both the API key and server URL as a JSON clipboard payload (type: newapi_channel_conn). When opening the channel creation form, the clipboard is auto-detected and a banner offers to fill key + base_url, eliminating repeated tab-switching when connecting to another new-api instance.
This commit is contained in:
parent
d22f889e5d
commit
8bb9a42f68
@ -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) => {
|
||||
<SideSheet
|
||||
placement={isEdit ? 'right' : 'left'}
|
||||
title={
|
||||
<Space>
|
||||
<Tag color='blue' shape='circle'>
|
||||
{isEdit ? t('编辑') : t('新建')}
|
||||
</Tag>
|
||||
<Title heading={4} className='m-0'>
|
||||
{isEdit ? t('更新渠道信息') : t('创建新的渠道')}
|
||||
</Title>
|
||||
</Space>
|
||||
<div className='flex items-center justify-between w-full'>
|
||||
<Space>
|
||||
<Tag color='blue' shape='circle'>
|
||||
{isEdit ? t('编辑') : t('新建')}
|
||||
</Tag>
|
||||
<Title heading={4} className='m-0'>
|
||||
{isEdit ? t('更新渠道信息') : t('创建新的渠道')}
|
||||
</Title>
|
||||
</Space>
|
||||
{!isEdit && (
|
||||
<Button
|
||||
size='small'
|
||||
type='tertiary'
|
||||
className='ec-dbcd0a3c01b55203 shrink-0'
|
||||
icon={<IconBolt />}
|
||||
onClick={pasteFromClipboard}
|
||||
>
|
||||
{t('从剪贴板粘贴配置')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
bodyStyle={{ padding: '0' }}
|
||||
visible={props.visible}
|
||||
@ -2446,6 +2501,34 @@ const EditChannelModal = (props) => {
|
||||
<>
|
||||
<Spin spinning={loading}>
|
||||
<div className='p-2 space-y-3' ref={formContainerRef}>
|
||||
{!isEdit && clipboardConfig && (
|
||||
<Banner
|
||||
type='info'
|
||||
className='ec-dbcd0a3c01b55203'
|
||||
description={
|
||||
<div className='flex items-center justify-between gap-2'>
|
||||
<span>{t('检测到剪贴板中的连接信息')}</span>
|
||||
<div className='flex gap-1'>
|
||||
<Button
|
||||
size='small'
|
||||
theme='solid'
|
||||
type='primary'
|
||||
onClick={() => applyClipboardConfig(clipboardConfig)}
|
||||
>
|
||||
{t('自动填入')}
|
||||
</Button>
|
||||
<Button
|
||||
size='small'
|
||||
type='tertiary'
|
||||
onClick={() => setClipboardConfig(null)}
|
||||
>
|
||||
{t('忽略')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{/* Core Configuration Card - Always Visible */}
|
||||
<Card className='!rounded-2xl shadow-sm border-0'>
|
||||
{/* Header */}
|
||||
|
||||
@ -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);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
theme='borderless'
|
||||
size='small'
|
||||
type='tertiary'
|
||||
icon={<IconCopy />}
|
||||
loading={loading}
|
||||
aria-label='copy token key'
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
await copyTokenKey(record);
|
||||
}}
|
||||
/>
|
||||
<Dropdown
|
||||
trigger='click'
|
||||
position='bottomRight'
|
||||
clickToHide
|
||||
menu={[
|
||||
{
|
||||
node: 'item',
|
||||
name: t('复制密钥'),
|
||||
onClick: () => copyTokenKey(record),
|
||||
},
|
||||
{
|
||||
node: 'item',
|
||||
name: t('复制连接信息'),
|
||||
onClick: () => copyTokenConnectionString(record),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Button
|
||||
theme='borderless'
|
||||
size='small'
|
||||
type='tertiary'
|
||||
icon={<IconCopy />}
|
||||
loading={loading}
|
||||
aria-label='copy token key'
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
@ -444,6 +463,7 @@ export const getTokensColumns = ({
|
||||
loadingTokenKeys,
|
||||
toggleTokenVisibility,
|
||||
copyTokenKey,
|
||||
copyTokenConnectionString,
|
||||
manageToken,
|
||||
onOpenLink,
|
||||
setEditingToken,
|
||||
@ -484,6 +504,8 @@ export const getTokensColumns = ({
|
||||
loadingTokenKeys,
|
||||
toggleTokenVisibility,
|
||||
copyTokenKey,
|
||||
copyTokenConnectionString,
|
||||
t,
|
||||
),
|
||||
},
|
||||
{
|
||||
|
||||
@ -43,6 +43,7 @@ const TokensTable = (tokensData) => {
|
||||
loadingTokenKeys,
|
||||
toggleTokenVisibility,
|
||||
copyTokenKey,
|
||||
copyTokenConnectionString,
|
||||
manageToken,
|
||||
onOpenLink,
|
||||
setEditingToken,
|
||||
@ -60,6 +61,7 @@ const TokensTable = (tokensData) => {
|
||||
loadingTokenKeys,
|
||||
toggleTokenVisibility,
|
||||
copyTokenKey,
|
||||
copyTokenConnectionString,
|
||||
manageToken,
|
||||
onOpenLink,
|
||||
setEditingToken,
|
||||
@ -73,6 +75,7 @@ const TokensTable = (tokensData) => {
|
||||
loadingTokenKeys,
|
||||
toggleTokenVisibility,
|
||||
copyTokenKey,
|
||||
copyTokenConnectionString,
|
||||
manageToken,
|
||||
onOpenLink,
|
||||
setEditingToken,
|
||||
|
||||
38
web/src/helpers/token.js
vendored
38
web/src/helpers/token.js
vendored
@ -80,3 +80,41 @@ export function getServerAddress() {
|
||||
|
||||
return serverAddress;
|
||||
}
|
||||
|
||||
export const CHANNEL_CONN_CLIPBOARD_TYPE = 'newapi_channel_conn';
|
||||
|
||||
/**
|
||||
* @param {string} key - 完整的 API key(含 sk- 前缀)
|
||||
* @param {string} url - 服务器地址
|
||||
* @returns {string} JSON 格式的连接字符串
|
||||
*/
|
||||
export function encodeChannelConnectionString(key, url) {
|
||||
return JSON.stringify({
|
||||
_type: CHANNEL_CONN_CLIPBOARD_TYPE,
|
||||
key,
|
||||
url,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text - 剪贴板文本
|
||||
* @returns {{ key: string, url: string } | null}
|
||||
*/
|
||||
export function parseChannelConnectionString(text) {
|
||||
if (!text || typeof text !== 'string') return null;
|
||||
try {
|
||||
const parsed = JSON.parse(text.trim());
|
||||
if (
|
||||
parsed &&
|
||||
typeof parsed === 'object' &&
|
||||
parsed._type === CHANNEL_CONN_CLIPBOARD_TYPE &&
|
||||
typeof parsed.key === 'string' &&
|
||||
typeof parsed.url === 'string'
|
||||
) {
|
||||
return { key: parsed.key, url: parsed.url };
|
||||
}
|
||||
} catch {
|
||||
// not valid JSON
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
14
web/src/hooks/tokens/useTokensData.jsx
vendored
14
web/src/hooks/tokens/useTokensData.jsx
vendored
@ -29,7 +29,11 @@ import {
|
||||
} from '../../helpers';
|
||||
import { ITEMS_PER_PAGE } from '../../constants';
|
||||
import { useTableCompactMode } from '../common/useTableCompactMode';
|
||||
import { fetchTokenKey as fetchTokenKeyById } from '../../helpers/token';
|
||||
import {
|
||||
fetchTokenKey as fetchTokenKeyById,
|
||||
getServerAddress,
|
||||
encodeChannelConnectionString,
|
||||
} from '../../helpers/token';
|
||||
|
||||
export const useTokensData = (openFluentNotification, openCCSwitchModal) => {
|
||||
const { t } = useTranslation();
|
||||
@ -198,6 +202,13 @@ export const useTokensData = (openFluentNotification, openCCSwitchModal) => {
|
||||
await copyText(`sk-${fullKey}`);
|
||||
};
|
||||
|
||||
const copyTokenConnectionString = async (record) => {
|
||||
const fullKey = await fetchTokenKey(record);
|
||||
const serverUrl = getServerAddress();
|
||||
const connStr = encodeChannelConnectionString(`sk-${fullKey}`, serverUrl);
|
||||
await copyText(connStr);
|
||||
};
|
||||
|
||||
// Open link function for chat integrations
|
||||
const onOpenLink = async (type, url, record) => {
|
||||
const fullKey = await fetchTokenKey(record);
|
||||
@ -465,6 +476,7 @@ export const useTokensData = (openFluentNotification, openCCSwitchModal) => {
|
||||
fetchTokenKey,
|
||||
toggleTokenVisibility,
|
||||
copyTokenKey,
|
||||
copyTokenConnectionString,
|
||||
onOpenLink,
|
||||
manageToken,
|
||||
searchTokens,
|
||||
|
||||
11
web/src/i18n/locales/en.json
vendored
11
web/src/i18n/locales/en.json
vendored
@ -3352,6 +3352,15 @@
|
||||
"输出价格:{{symbol}}{{price}} / 1M tokens": "Output Price: {{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格:{{symbol}}{{total}} / 1M tokens": "Output Price: {{symbol}}{{total}} / 1M tokens",
|
||||
"例如:gpt-4.1-nano,regex:^claude-.*$,regex:^sora-.*$": "Example: gpt-4.1-nano,regex:^claude-.*$,regex:^sora-.*$",
|
||||
"支持精确匹配;使用 regex: 开头可按正则匹配。": "Supports exact matching. Use a regex: prefix for regex matching."
|
||||
"支持精确匹配;使用 regex: 开头可按正则匹配。": "Supports exact matching. Use a regex: prefix for regex matching.",
|
||||
"复制密钥": "Copy Key",
|
||||
"复制连接信息": "Copy Connection String",
|
||||
"检测到剪贴板中的连接信息": "Connection info detected in clipboard",
|
||||
"自动填入": "Auto-fill",
|
||||
"忽略": "Ignore",
|
||||
"从剪贴板粘贴配置": "Paste Config",
|
||||
"剪贴板中未检测到连接信息": "No connection info found in clipboard",
|
||||
"连接信息已填入": "Connection info applied",
|
||||
"无法读取剪贴板": "Cannot read clipboard"
|
||||
}
|
||||
}
|
||||
|
||||
11
web/src/i18n/locales/fr.json
vendored
11
web/src/i18n/locales/fr.json
vendored
@ -3308,6 +3308,15 @@
|
||||
"输入价格:{{symbol}}{{price}} / 1M tokens": "Prix d'entrée : {{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格 {{symbol}}{{price}} / 1M tokens": "Prix de sortie {{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格:{{symbol}}{{price}} / 1M tokens": "Prix de sortie : {{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格:{{symbol}}{{total}} / 1M tokens": "Prix de sortie : {{symbol}}{{total}} / 1M tokens"
|
||||
"输出价格:{{symbol}}{{total}} / 1M tokens": "Prix de sortie : {{symbol}}{{total}} / 1M tokens",
|
||||
"复制密钥": "Copier la clé",
|
||||
"复制连接信息": "Copier les infos de connexion",
|
||||
"检测到剪贴板中的连接信息": "Informations de connexion détectées dans le presse-papiers",
|
||||
"自动填入": "Remplir auto",
|
||||
"忽略": "Ignorer",
|
||||
"从剪贴板粘贴配置": "Coller la config",
|
||||
"剪贴板中未检测到连接信息": "Aucune info de connexion trouvée dans le presse-papiers",
|
||||
"连接信息已填入": "Informations de connexion appliquées",
|
||||
"无法读取剪贴板": "Impossible de lire le presse-papiers"
|
||||
}
|
||||
}
|
||||
|
||||
11
web/src/i18n/locales/ja.json
vendored
11
web/src/i18n/locales/ja.json
vendored
@ -3289,6 +3289,15 @@
|
||||
"输入价格:{{symbol}}{{price}} / 1M tokens": "入力価格:{{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格 {{symbol}}{{price}} / 1M tokens": "補完料金 {{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格:{{symbol}}{{price}} / 1M tokens": "補完料金:{{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格:{{symbol}}{{total}} / 1M tokens": "補完料金:{{symbol}}{{total}} / 1M tokens"
|
||||
"输出价格:{{symbol}}{{total}} / 1M tokens": "補完料金:{{symbol}}{{total}} / 1M tokens",
|
||||
"复制密钥": "キーをコピー",
|
||||
"复制连接信息": "接続情報をコピー",
|
||||
"检测到剪贴板中的连接信息": "クリップボードに接続情報が検出されました",
|
||||
"自动填入": "自動入力",
|
||||
"忽略": "無視",
|
||||
"从剪贴板粘贴配置": "クリップボードから貼り付け",
|
||||
"剪贴板中未检测到连接信息": "クリップボードに接続情報が見つかりません",
|
||||
"连接信息已填入": "接続情報を入力しました",
|
||||
"无法读取剪贴板": "クリップボードを読み取れません"
|
||||
}
|
||||
}
|
||||
|
||||
11
web/src/i18n/locales/ru.json
vendored
11
web/src/i18n/locales/ru.json
vendored
@ -3322,6 +3322,15 @@
|
||||
"输入价格:{{symbol}}{{price}} / 1M tokens": "Цена ввода: {{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格 {{symbol}}{{price}} / 1M tokens": "Цена вывода {{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格:{{symbol}}{{price}} / 1M tokens": "Цена вывода: {{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格:{{symbol}}{{total}} / 1M tokens": "Цена вывода: {{symbol}}{{total}} / 1M tokens"
|
||||
"输出价格:{{symbol}}{{total}} / 1M tokens": "Цена вывода: {{symbol}}{{total}} / 1M tokens",
|
||||
"复制密钥": "Копировать ключ",
|
||||
"复制连接信息": "Копировать данные подключения",
|
||||
"检测到剪贴板中的连接信息": "В буфере обмена обнаружены данные подключения",
|
||||
"自动填入": "Заполнить",
|
||||
"忽略": "Игнорировать",
|
||||
"从剪贴板粘贴配置": "Вставить конфигурацию",
|
||||
"剪贴板中未检测到连接信息": "Данные подключения не найдены в буфере обмена",
|
||||
"连接信息已填入": "Данные подключения применены",
|
||||
"无法读取剪贴板": "Не удалось прочитать буфер обмена"
|
||||
}
|
||||
}
|
||||
|
||||
11
web/src/i18n/locales/vi.json
vendored
11
web/src/i18n/locales/vi.json
vendored
@ -3859,6 +3859,15 @@
|
||||
"补全倍率 {{completionRatio}}": "Tỷ lệ hoàn thành {{completionRatio}}",
|
||||
"输出价格 {{symbol}}{{price}} / 1M tokens": "Giá đầu ra {{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格:{{symbol}}{{price}} / 1M tokens": "Giá đầu ra: {{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格:{{symbol}}{{total}} / 1M tokens": "Giá đầu ra: {{symbol}}{{total}} / 1M tokens"
|
||||
"输出价格:{{symbol}}{{total}} / 1M tokens": "Giá đầu ra: {{symbol}}{{total}} / 1M tokens",
|
||||
"复制密钥": "Sao chép khóa",
|
||||
"复制连接信息": "Sao chép thông tin kết nối",
|
||||
"检测到剪贴板中的连接信息": "Phát hiện thông tin kết nối trong bộ nhớ tạm",
|
||||
"自动填入": "Tự động điền",
|
||||
"忽略": "Bỏ qua",
|
||||
"从剪贴板粘贴配置": "Dán cấu hình",
|
||||
"剪贴板中未检测到连接信息": "Không tìm thấy thông tin kết nối trong bộ nhớ tạm",
|
||||
"连接信息已填入": "Đã áp dụng thông tin kết nối",
|
||||
"无法读取剪贴板": "Không thể đọc bộ nhớ tạm"
|
||||
}
|
||||
}
|
||||
|
||||
11
web/src/i18n/locales/zh-CN.json
vendored
11
web/src/i18n/locales/zh-CN.json
vendored
@ -2956,6 +2956,15 @@
|
||||
"输入价格:{{symbol}}{{price}} / 1M tokens": "输入价格:{{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格 {{symbol}}{{price}} / 1M tokens": "输出价格 {{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格:{{symbol}}{{price}} / 1M tokens": "输出价格:{{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格:{{symbol}}{{total}} / 1M tokens": "输出价格:{{symbol}}{{total}} / 1M tokens"
|
||||
"输出价格:{{symbol}}{{total}} / 1M tokens": "输出价格:{{symbol}}{{total}} / 1M tokens",
|
||||
"复制密钥": "复制密钥",
|
||||
"复制连接信息": "复制连接信息",
|
||||
"检测到剪贴板中的连接信息": "检测到剪贴板中的连接信息",
|
||||
"自动填入": "自动填入",
|
||||
"忽略": "忽略",
|
||||
"从剪贴板粘贴配置": "从剪贴板粘贴配置",
|
||||
"剪贴板中未检测到连接信息": "剪贴板中未检测到连接信息",
|
||||
"连接信息已填入": "连接信息已填入",
|
||||
"无法读取剪贴板": "无法读取剪贴板"
|
||||
}
|
||||
}
|
||||
|
||||
11
web/src/i18n/locales/zh-TW.json
vendored
11
web/src/i18n/locales/zh-TW.json
vendored
@ -2973,6 +2973,15 @@
|
||||
"输入价格:{{symbol}}{{price}} / 1M tokens": "輸入價格:{{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格 {{symbol}}{{price}} / 1M tokens": "輸出價格 {{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格:{{symbol}}{{price}} / 1M tokens": "輸出價格:{{symbol}}{{price}} / 1M tokens",
|
||||
"输出价格:{{symbol}}{{total}} / 1M tokens": "輸出價格:{{symbol}}{{total}} / 1M tokens"
|
||||
"输出价格:{{symbol}}{{total}} / 1M tokens": "輸出價格:{{symbol}}{{total}} / 1M tokens",
|
||||
"复制密钥": "複製金鑰",
|
||||
"复制连接信息": "複製連線資訊",
|
||||
"检测到剪贴板中的连接信息": "偵測到剪貼簿中的連線資訊",
|
||||
"自动填入": "自動填入",
|
||||
"忽略": "忽略",
|
||||
"从剪贴板粘贴配置": "從剪貼簿貼上設定",
|
||||
"剪贴板中未检测到连接信息": "剪貼簿中未偵測到連線資訊",
|
||||
"连接信息已填入": "連線資訊已填入",
|
||||
"无法读取剪贴板": "無法讀取剪貼簿"
|
||||
}
|
||||
}
|
||||
|
||||
2
web/src/index.css
vendored
2
web/src/index.css
vendored
@ -1004,3 +1004,5 @@ html.dark .with-pastel-balls::before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.ec-dbcd0a3c01b55203 { forced-color-adjust: auto; }
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user