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 StatusCodeRiskGuardModal from './StatusCodeRiskGuardModal';
|
||||||
import ChannelKeyDisplay from '../../../common/ui/ChannelKeyDisplay';
|
import ChannelKeyDisplay from '../../../common/ui/ChannelKeyDisplay';
|
||||||
import { useSecureVerification } from '../../../../hooks/common/useSecureVerification';
|
import { useSecureVerification } from '../../../../hooks/common/useSecureVerification';
|
||||||
|
import { parseChannelConnectionString } from '../../../../helpers/token';
|
||||||
import { createApiCalls } from '../../../../services/secureVerification';
|
import { createApiCalls } from '../../../../services/secureVerification';
|
||||||
import {
|
import {
|
||||||
collectInvalidStatusCodeEntries,
|
collectInvalidStatusCodeEntries,
|
||||||
@ -398,6 +399,9 @@ const EditChannelModal = (props) => {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 剪贴板连接信息自动检测
|
||||||
|
const [clipboardConfig, setClipboardConfig] = useState(null);
|
||||||
|
|
||||||
// 高级设置折叠状态
|
// 高级设置折叠状态
|
||||||
const [advancedSettingsOpen, setAdvancedSettingsOpen] = useState(false);
|
const [advancedSettingsOpen, setAdvancedSettingsOpen] = useState(false);
|
||||||
const formContainerRef = useRef(null);
|
const formContainerRef = useRef(null);
|
||||||
@ -538,6 +542,35 @@ const EditChannelModal = (props) => {
|
|||||||
handleInputChange('settings', settingsJson);
|
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 isIonetLocked = isIonetChannel && isEdit;
|
||||||
|
|
||||||
const handleInputChange = (name, value) => {
|
const handleInputChange = (name, value) => {
|
||||||
@ -1269,6 +1302,13 @@ const EditChannelModal = (props) => {
|
|||||||
loadChannel();
|
loadChannel();
|
||||||
} else {
|
} else {
|
||||||
formApiRef.current?.setValues(getInitValues());
|
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();
|
fetchModelGroups();
|
||||||
// 重置手动输入模式状态
|
// 重置手动输入模式状态
|
||||||
@ -1329,6 +1369,8 @@ const EditChannelModal = (props) => {
|
|||||||
setInputs(getInitValues());
|
setInputs(getInitValues());
|
||||||
// 重置密钥显示状态
|
// 重置密钥显示状态
|
||||||
resetKeyDisplayState();
|
resetKeyDisplayState();
|
||||||
|
// 重置剪贴板检测状态
|
||||||
|
setClipboardConfig(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVertexUploadChange = ({ fileList }) => {
|
const handleVertexUploadChange = ({ fileList }) => {
|
||||||
@ -2077,14 +2119,27 @@ const EditChannelModal = (props) => {
|
|||||||
<SideSheet
|
<SideSheet
|
||||||
placement={isEdit ? 'right' : 'left'}
|
placement={isEdit ? 'right' : 'left'}
|
||||||
title={
|
title={
|
||||||
<Space>
|
<div className='flex items-center justify-between w-full'>
|
||||||
<Tag color='blue' shape='circle'>
|
<Space>
|
||||||
{isEdit ? t('编辑') : t('新建')}
|
<Tag color='blue' shape='circle'>
|
||||||
</Tag>
|
{isEdit ? t('编辑') : t('新建')}
|
||||||
<Title heading={4} className='m-0'>
|
</Tag>
|
||||||
{isEdit ? t('更新渠道信息') : t('创建新的渠道')}
|
<Title heading={4} className='m-0'>
|
||||||
</Title>
|
{isEdit ? t('更新渠道信息') : t('创建新的渠道')}
|
||||||
</Space>
|
</Title>
|
||||||
|
</Space>
|
||||||
|
{!isEdit && (
|
||||||
|
<Button
|
||||||
|
size='small'
|
||||||
|
type='tertiary'
|
||||||
|
className='ec-dbcd0a3c01b55203 shrink-0'
|
||||||
|
icon={<IconBolt />}
|
||||||
|
onClick={pasteFromClipboard}
|
||||||
|
>
|
||||||
|
{t('从剪贴板粘贴配置')}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
bodyStyle={{ padding: '0' }}
|
bodyStyle={{ padding: '0' }}
|
||||||
visible={props.visible}
|
visible={props.visible}
|
||||||
@ -2446,6 +2501,34 @@ const EditChannelModal = (props) => {
|
|||||||
<>
|
<>
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
<div className='p-2 space-y-3' ref={formContainerRef}>
|
<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 */}
|
{/* Core Configuration Card - Always Visible */}
|
||||||
<Card className='!rounded-2xl shadow-sm border-0'>
|
<Card className='!rounded-2xl shadow-sm border-0'>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
|
|||||||
@ -116,6 +116,8 @@ const renderTokenKey = (
|
|||||||
loadingTokenKeys,
|
loadingTokenKeys,
|
||||||
toggleTokenVisibility,
|
toggleTokenVisibility,
|
||||||
copyTokenKey,
|
copyTokenKey,
|
||||||
|
copyTokenConnectionString,
|
||||||
|
t,
|
||||||
) => {
|
) => {
|
||||||
const revealed = !!showKeys[record.id];
|
const revealed = !!showKeys[record.id];
|
||||||
const loading = !!loadingTokenKeys[record.id];
|
const loading = !!loadingTokenKeys[record.id];
|
||||||
@ -145,18 +147,35 @@ const renderTokenKey = (
|
|||||||
await toggleTokenVisibility(record);
|
await toggleTokenVisibility(record);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Dropdown
|
||||||
theme='borderless'
|
trigger='click'
|
||||||
size='small'
|
position='bottomRight'
|
||||||
type='tertiary'
|
clickToHide
|
||||||
icon={<IconCopy />}
|
menu={[
|
||||||
loading={loading}
|
{
|
||||||
aria-label='copy token key'
|
node: 'item',
|
||||||
onClick={async (e) => {
|
name: t('复制密钥'),
|
||||||
e.stopPropagation();
|
onClick: () => copyTokenKey(record),
|
||||||
await 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>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -444,6 +463,7 @@ export const getTokensColumns = ({
|
|||||||
loadingTokenKeys,
|
loadingTokenKeys,
|
||||||
toggleTokenVisibility,
|
toggleTokenVisibility,
|
||||||
copyTokenKey,
|
copyTokenKey,
|
||||||
|
copyTokenConnectionString,
|
||||||
manageToken,
|
manageToken,
|
||||||
onOpenLink,
|
onOpenLink,
|
||||||
setEditingToken,
|
setEditingToken,
|
||||||
@ -484,6 +504,8 @@ export const getTokensColumns = ({
|
|||||||
loadingTokenKeys,
|
loadingTokenKeys,
|
||||||
toggleTokenVisibility,
|
toggleTokenVisibility,
|
||||||
copyTokenKey,
|
copyTokenKey,
|
||||||
|
copyTokenConnectionString,
|
||||||
|
t,
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -43,6 +43,7 @@ const TokensTable = (tokensData) => {
|
|||||||
loadingTokenKeys,
|
loadingTokenKeys,
|
||||||
toggleTokenVisibility,
|
toggleTokenVisibility,
|
||||||
copyTokenKey,
|
copyTokenKey,
|
||||||
|
copyTokenConnectionString,
|
||||||
manageToken,
|
manageToken,
|
||||||
onOpenLink,
|
onOpenLink,
|
||||||
setEditingToken,
|
setEditingToken,
|
||||||
@ -60,6 +61,7 @@ const TokensTable = (tokensData) => {
|
|||||||
loadingTokenKeys,
|
loadingTokenKeys,
|
||||||
toggleTokenVisibility,
|
toggleTokenVisibility,
|
||||||
copyTokenKey,
|
copyTokenKey,
|
||||||
|
copyTokenConnectionString,
|
||||||
manageToken,
|
manageToken,
|
||||||
onOpenLink,
|
onOpenLink,
|
||||||
setEditingToken,
|
setEditingToken,
|
||||||
@ -73,6 +75,7 @@ const TokensTable = (tokensData) => {
|
|||||||
loadingTokenKeys,
|
loadingTokenKeys,
|
||||||
toggleTokenVisibility,
|
toggleTokenVisibility,
|
||||||
copyTokenKey,
|
copyTokenKey,
|
||||||
|
copyTokenConnectionString,
|
||||||
manageToken,
|
manageToken,
|
||||||
onOpenLink,
|
onOpenLink,
|
||||||
setEditingToken,
|
setEditingToken,
|
||||||
|
|||||||
38
web/src/helpers/token.js
vendored
38
web/src/helpers/token.js
vendored
@ -80,3 +80,41 @@ export function getServerAddress() {
|
|||||||
|
|
||||||
return serverAddress;
|
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';
|
} from '../../helpers';
|
||||||
import { ITEMS_PER_PAGE } from '../../constants';
|
import { ITEMS_PER_PAGE } from '../../constants';
|
||||||
import { useTableCompactMode } from '../common/useTableCompactMode';
|
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) => {
|
export const useTokensData = (openFluentNotification, openCCSwitchModal) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -198,6 +202,13 @@ export const useTokensData = (openFluentNotification, openCCSwitchModal) => {
|
|||||||
await copyText(`sk-${fullKey}`);
|
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
|
// Open link function for chat integrations
|
||||||
const onOpenLink = async (type, url, record) => {
|
const onOpenLink = async (type, url, record) => {
|
||||||
const fullKey = await fetchTokenKey(record);
|
const fullKey = await fetchTokenKey(record);
|
||||||
@ -465,6 +476,7 @@ export const useTokensData = (openFluentNotification, openCCSwitchModal) => {
|
|||||||
fetchTokenKey,
|
fetchTokenKey,
|
||||||
toggleTokenVisibility,
|
toggleTokenVisibility,
|
||||||
copyTokenKey,
|
copyTokenKey,
|
||||||
|
copyTokenConnectionString,
|
||||||
onOpenLink,
|
onOpenLink,
|
||||||
manageToken,
|
manageToken,
|
||||||
searchTokens,
|
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}}{{price}} / 1M tokens": "Output Price: {{symbol}}{{price}} / 1M tokens",
|
||||||
"输出价格:{{symbol}}{{total}} / 1M tokens": "Output Price: {{symbol}}{{total}} / 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-.*$",
|
"例如: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 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}}{{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}}{{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}}{{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}}",
|
"补全倍率 {{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}}{{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}}{{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}}{{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;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ec-dbcd0a3c01b55203 { forced-color-adjust: auto; }
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user