| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- /*
- Copyright (C) 2025 QuantumNous
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
- For commercial licensing, please contact [email protected]
- */
- import React from 'react';
- import { useTranslation } from 'react-i18next';
- import { Card, Button, Typography, Tag } from '@douyinfe/semi-ui';
- import { copy, showSuccess } from '../../../helpers';
- /**
- * 解析密钥数据,支持多种格式
- * @param {string} keyData - 密钥数据
- * @param {Function} t - 翻译函数
- * @returns {Array} 解析后的密钥数组
- */
- const parseChannelKeys = (keyData, t) => {
- if (!keyData) return [];
-
- const trimmed = keyData.trim();
-
- // 检查是否是JSON数组格式(如Vertex AI)
- if (trimmed.startsWith('[')) {
- try {
- const parsed = JSON.parse(trimmed);
- if (Array.isArray(parsed)) {
- return parsed.map((item, index) => ({
- id: index,
- content: typeof item === 'string' ? item : JSON.stringify(item, null, 2),
- type: typeof item === 'string' ? 'text' : 'json',
- label: `${t('密钥')} ${index + 1}`
- }));
- }
- } catch (e) {
- // 如果解析失败,按普通文本处理
- console.warn('Failed to parse JSON keys:', e);
- }
- }
-
- // 检查是否是多行密钥(按换行符分割)
- const lines = trimmed.split('\n').filter(line => line.trim());
- if (lines.length > 1) {
- return lines.map((line, index) => ({
- id: index,
- content: line.trim(),
- type: 'text',
- label: `${t('密钥')} ${index + 1}`
- }));
- }
-
- // 单个密钥
- return [{
- id: 0,
- content: trimmed,
- type: trimmed.startsWith('{') ? 'json' : 'text',
- label: t('密钥')
- }];
- };
- /**
- * 可复用的密钥显示组件
- * @param {Object} props
- * @param {string} props.keyData - 密钥数据
- * @param {boolean} props.showSuccessIcon - 是否显示成功图标
- * @param {string} props.successText - 成功文本
- * @param {boolean} props.showWarning - 是否显示安全警告
- * @param {string} props.warningText - 警告文本
- */
- const ChannelKeyDisplay = ({
- keyData,
- showSuccessIcon = true,
- successText,
- showWarning = true,
- warningText
- }) => {
- const { t } = useTranslation();
- const parsedKeys = parseChannelKeys(keyData, t);
- const isMultipleKeys = parsedKeys.length > 1;
- const handleCopyAll = () => {
- copy(keyData);
- showSuccess(t('所有密钥已复制到剪贴板'));
- };
- const handleCopyKey = (content) => {
- copy(content);
- showSuccess(t('密钥已复制到剪贴板'));
- };
- return (
- <div className="space-y-4">
- {/* 成功状态 */}
- {showSuccessIcon && (
- <div className="flex items-center gap-2">
- <svg className="w-5 h-5 text-green-600" fill="currentColor" viewBox="0 0 20 20">
- <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
- </svg>
- <Typography.Text strong className="text-green-700">
- {successText || t('验证成功')}
- </Typography.Text>
- </div>
- )}
- {/* 密钥内容 */}
- <div className="space-y-3">
- <div className="flex items-center justify-between">
- <Typography.Text strong>
- {isMultipleKeys ? t('渠道密钥列表') : t('渠道密钥')}
- </Typography.Text>
- {isMultipleKeys && (
- <div className="flex items-center gap-2">
- <Typography.Text type="tertiary" size="small">
- {t('共 {{count}} 个密钥', { count: parsedKeys.length })}
- </Typography.Text>
- <Button
- size="small"
- type="primary"
- theme="outline"
- onClick={handleCopyAll}
- >
- {t('复制全部')}
- </Button>
- </div>
- )}
- </div>
-
- <div className="space-y-3 max-h-80 overflow-auto">
- {parsedKeys.map((keyItem) => (
- <Card key={keyItem.id} className="!rounded-lg !border !border-gray-200 dark:!border-gray-700">
- <div className="space-y-2">
- <div className="flex items-center justify-between">
- <Typography.Text strong size="small" className="text-gray-700 dark:text-gray-300">
- {keyItem.label}
- </Typography.Text>
- <div className="flex items-center gap-2">
- {keyItem.type === 'json' && (
- <Tag size="small" color="blue">{t('JSON')}</Tag>
- )}
- <Button
- size="small"
- type="primary"
- theme="outline"
- icon={
- <svg className="w-3 h-3" fill="currentColor" viewBox="0 0 20 20">
- <path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
- <path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
- </svg>
- }
- onClick={() => handleCopyKey(keyItem.content)}
- >
- {t('复制')}
- </Button>
- </div>
- </div>
-
- <div className="bg-gray-50 dark:bg-gray-800 rounded-lg p-3 max-h-40 overflow-auto">
- <Typography.Text
- code
- className="text-xs font-mono break-all whitespace-pre-wrap text-gray-800 dark:text-gray-200"
- >
- {keyItem.content}
- </Typography.Text>
- </div>
-
- {keyItem.type === 'json' && (
- <Typography.Text type="tertiary" size="small" className="block">
- {t('JSON格式密钥,请确保格式正确')}
- </Typography.Text>
- )}
- </div>
- </Card>
- ))}
- </div>
-
- {isMultipleKeys && (
- <div className="bg-blue-50 dark:bg-blue-900 rounded-lg p-3">
- <Typography.Text type="tertiary" size="small" className="text-blue-700 dark:text-blue-300">
- <svg className="w-4 h-4 inline mr-1" fill="currentColor" viewBox="0 0 20 20">
- <path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
- </svg>
- {t('检测到多个密钥,您可以单独复制每个密钥,或点击复制全部获取完整内容。')}
- </Typography.Text>
- </div>
- )}
- </div>
- {/* 安全警告 */}
- {showWarning && (
- <div className="bg-yellow-50 dark:bg-yellow-900 rounded-lg p-4">
- <div className="flex items-start">
- <svg className="w-5 h-5 text-yellow-600 dark:text-yellow-400 mt-0.5 mr-3 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
- <path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
- </svg>
- <div>
- <Typography.Text strong className="text-yellow-800 dark:text-yellow-200">
- {t('安全提醒')}
- </Typography.Text>
- <Typography.Text className="block text-yellow-700 dark:text-yellow-300 text-sm mt-1">
- {warningText || t('请妥善保管密钥信息,不要泄露给他人。如有安全疑虑,请及时更换密钥。')}
- </Typography.Text>
- </div>
- </div>
- </div>
- )}
- </div>
- );
- };
- export default ChannelKeyDisplay;
|