mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-09 18:57:43 +08:00
feat: Translate UI texts and messages to Chinese across various components.
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet">
|
||||
<title>DS2API Admin</title>
|
||||
<title>DS2API 管理面板</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -180,9 +180,9 @@ export default function App() {
|
||||
<div className="p-6 border-b border-border">
|
||||
<div className="flex items-center gap-2 font-bold text-xl text-primary">
|
||||
<LayoutDashboard className="w-6 h-6" />
|
||||
<span>DS2API Admin</span>
|
||||
<span>DS2API 管理面板</span>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground mt-1 ml-8">V1.0.0 Control Panel</p>
|
||||
<p className="text-xs text-muted-foreground mt-1 ml-8">V1.0.0 控制面板</p>
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 p-4 space-y-1 overflow-y-auto">
|
||||
@@ -213,19 +213,19 @@ export default function App() {
|
||||
<div className="p-4 border-t border-border bg-card/50">
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between text-sm">
|
||||
<span className="text-muted-foreground">API Status</span>
|
||||
<span className="text-muted-foreground">API 状态</span>
|
||||
<span className="flex items-center gap-1.5 text-xs font-medium text-green-500 bg-green-500/10 px-2 py-0.5 rounded-full">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-green-500 animate-pulse"></span>
|
||||
Online
|
||||
在线
|
||||
</span>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="bg-background rounded p-2 border border-border">
|
||||
<div className="text-xs text-muted-foreground">Accounts</div>
|
||||
<div className="text-xs text-muted-foreground">账号</div>
|
||||
<div className="text-lg font-bold">{config.accounts?.length || 0}</div>
|
||||
</div>
|
||||
<div className="bg-background rounded p-2 border border-border">
|
||||
<div className="text-xs text-muted-foreground">Api Keys</div>
|
||||
<div className="text-xs text-muted-foreground">API 密钥</div>
|
||||
<div className="text-lg font-bold">{config.keys?.length || 0}</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -234,7 +234,7 @@ export default function App() {
|
||||
className="w-full flex items-center justify-center gap-2 px-3 py-2 rounded-md border border-border text-sm font-medium text-muted-foreground hover:bg-destructive/10 hover:text-destructive hover:border-destructive/20 transition-colors"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
Sign Out
|
||||
退出登录
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -280,7 +280,7 @@ export default function App() {
|
||||
{loading ? (
|
||||
<div className="flex flex-col items-center justify-center py-20 text-muted-foreground">
|
||||
<div className="w-8 h-8 border-4 border-primary border-t-transparent rounded-full animate-spin mb-4"></div>
|
||||
<p>Please wait while loading data...</p>
|
||||
<p>正在加载数据,请稍候...</p>
|
||||
</div>
|
||||
) : (
|
||||
renderTab()
|
||||
|
||||
@@ -249,7 +249,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
<CheckCircle2 className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">Available</p>
|
||||
<p className="text-sm font-medium text-muted-foreground">可用账号</p>
|
||||
<p className="text-2xl font-bold">{queueStatus.available}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -260,7 +260,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
<Server className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">In Use</p>
|
||||
<p className="text-sm font-medium text-muted-foreground">使用中</p>
|
||||
<p className="text-2xl font-bold">{queueStatus.in_use}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -271,7 +271,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
<ShieldCheck className="w-5 h-5" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-muted-foreground">Total Accounts</p>
|
||||
<p className="text-sm font-medium text-muted-foreground">账号总数</p>
|
||||
<p className="text-2xl font-bold">{queueStatus.total}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -283,15 +283,15 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
<div className="bg-card border border-border rounded-xl overflow-hidden shadow-sm">
|
||||
<div className="p-6 border-b border-border flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">API Keys</h2>
|
||||
<p className="text-sm text-muted-foreground">Manage access keys for the API</p>
|
||||
<h2 className="text-lg font-semibold">API 密钥</h2>
|
||||
<p className="text-sm text-muted-foreground">管理 API 访问密钥池</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowAddKey(true)}
|
||||
className="btn btn-primary flex items-center gap-2"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
New Key
|
||||
新增密钥
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -311,7 +311,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="p-8 text-center text-muted-foreground">No API keys found</div>
|
||||
<div className="p-8 text-center text-muted-foreground">未找到 API 密钥</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -320,8 +320,8 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
<div className="bg-card border border-border rounded-xl overflow-hidden shadow-sm">
|
||||
<div className="p-6 border-b border-border flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">DeepSeek Accounts</h2>
|
||||
<p className="text-sm text-muted-foreground">Manage your account pool</p>
|
||||
<h2 className="text-lg font-semibold">DeepSeek 账号</h2>
|
||||
<p className="text-sm text-muted-foreground">管理 DeepSeek 账号池</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
@@ -330,7 +330,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
className="btn btn-secondary text-xs"
|
||||
>
|
||||
{testingAll ? <span className="animate-spin mr-2">⟳</span> : <Play className="w-3 h-3 mr-2" />}
|
||||
Test All
|
||||
测试全部
|
||||
</button>
|
||||
<button
|
||||
onClick={validateAllAccounts}
|
||||
@@ -338,14 +338,14 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
className="btn btn-secondary text-xs"
|
||||
>
|
||||
{validatingAll ? <span className="animate-spin mr-2">⟳</span> : <CheckCircle2 className="w-3 h-3 mr-2" />}
|
||||
Validate All
|
||||
校验全部
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowAddAccount(true)}
|
||||
className="btn btn-primary flex items-center gap-2"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Add Account
|
||||
添加账号
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -354,7 +354,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
{(testingAll || validatingAll) && batchProgress.total > 0 && (
|
||||
<div className="p-4 border-b border-border bg-muted/30">
|
||||
<div className="flex items-center justify-between text-sm mb-2">
|
||||
<span className="font-medium">{testingAll ? 'Testing all accounts...' : 'Validating all accounts...'}</span>
|
||||
<span className="font-medium">{testingAll ? '正在测试所有账号...' : '正在校验所有账号...'}</span>
|
||||
<span className="text-muted-foreground">{batchProgress.current} / {batchProgress.total}</span>
|
||||
</div>
|
||||
<div className="w-full bg-muted rounded-full h-2 overflow-hidden mb-4">
|
||||
@@ -392,7 +392,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
<div className="min-w-0">
|
||||
<div className="font-medium truncate">{id}</div>
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground mt-0.5">
|
||||
<span>{acc.has_token ? 'Active Session' : 'Login Required'}</span>
|
||||
<span>{acc.has_token ? '已建立会话' : '需重新登录'}</span>
|
||||
{acc.token_preview && (
|
||||
<span className="font-mono bg-muted px-1.5 py-0.5 rounded text-[10px]">
|
||||
{acc.token_preview}
|
||||
@@ -407,14 +407,14 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
disabled={testing[id]}
|
||||
className="px-3 py-1.5 text-xs font-medium border border-border rounded-md hover:bg-secondary transition-colors disabled:opacity-50"
|
||||
>
|
||||
{testing[id] ? 'Testing...' : 'Test'}
|
||||
{testing[id] ? '正在测试...' : '测试'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => validateAccount(id)}
|
||||
disabled={validating[id]}
|
||||
className="px-3 py-1.5 text-xs font-medium border border-border rounded-md hover:bg-secondary transition-colors disabled:opacity-50"
|
||||
>
|
||||
{validating[id] ? 'Validating...' : 'Validate'}
|
||||
{validating[id] ? '正在校验...' : '校验'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => deleteAccount(id)}
|
||||
@@ -427,7 +427,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
)
|
||||
})
|
||||
) : (
|
||||
<div className="p-8 text-center text-muted-foreground">No accounts found</div>
|
||||
<div className="p-8 text-center text-muted-foreground">未找到任何账号</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -437,27 +437,27 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4 animate-in fade-in">
|
||||
<div className="bg-card w-full max-w-md rounded-xl border border-border shadow-2xl overflow-hidden animate-in zoom-in-95">
|
||||
<div className="p-4 border-b border-border flex justify-between items-center">
|
||||
<h3 className="font-semibold">Add API Key</h3>
|
||||
<h3 className="font-semibold">添加 API 密钥</h3>
|
||||
<button onClick={() => setShowAddKey(false)} className="text-muted-foreground hover:text-foreground">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-6 space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1.5">New Key value</label>
|
||||
<label className="block text-sm font-medium mb-1.5">新密钥值</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input-field"
|
||||
placeholder="Enter custom API key"
|
||||
placeholder="输入自定义 API 密钥"
|
||||
value={newKey}
|
||||
onChange={e => setNewKey(e.target.value)}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<button onClick={() => setShowAddKey(false)} className="btn btn-secondary">Cancel</button>
|
||||
<button onClick={() => setShowAddKey(false)} className="btn btn-secondary">取消</button>
|
||||
<button onClick={addKey} disabled={loading} className="btn btn-primary">
|
||||
{loading ? 'Adding...' : 'Add Key'}
|
||||
{loading ? '添加中...' : '添加'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -469,14 +469,14 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4 animate-in fade-in">
|
||||
<div className="bg-card w-full max-w-md rounded-xl border border-border shadow-2xl overflow-hidden animate-in zoom-in-95">
|
||||
<div className="p-4 border-b border-border flex justify-between items-center">
|
||||
<h3 className="font-semibold">Add DeepSeek Account</h3>
|
||||
<h3 className="font-semibold">添加 DeepSeek 账号</h3>
|
||||
<button onClick={() => setShowAddAccount(false)} className="text-muted-foreground hover:text-foreground">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-6 space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1.5">Email (Optional)</label>
|
||||
<label className="block text-sm font-medium mb-1.5">邮箱 (可选)</label>
|
||||
<input
|
||||
type="email"
|
||||
className="input-field"
|
||||
@@ -486,7 +486,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1.5">Mobile (Optional)</label>
|
||||
<label className="block text-sm font-medium mb-1.5">手机号 (可选)</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input-field"
|
||||
@@ -496,19 +496,19 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1.5">Password <span className="text-destructive">*</span></label>
|
||||
<label className="block text-sm font-medium mb-1.5">密码 <span className="text-destructive">*</span></label>
|
||||
<input
|
||||
type="password"
|
||||
className="input-field"
|
||||
placeholder="Account password"
|
||||
placeholder="账号密码"
|
||||
value={newAccount.password}
|
||||
onChange={e => setNewAccount({ ...newAccount, password: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<button onClick={() => setShowAddAccount(false)} className="btn btn-secondary">Cancel</button>
|
||||
<button onClick={() => setShowAddAccount(false)} className="btn btn-secondary">取消</button>
|
||||
<button onClick={addAccount} disabled={loading} className="btn btn-primary">
|
||||
{loading ? 'Adding...' : 'Add Account'}
|
||||
{loading ? '添加中...' : '添加账号'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
import clsx from 'clsx'
|
||||
|
||||
const MODELS = [
|
||||
{ id: 'deepseek-chat', name: 'DeepSeek Chat', icon: MessageSquare, desc: 'General purpose chat model' },
|
||||
{ id: 'deepseek-reasoner', name: 'DeepSeek Reasoner', icon: Cpu, desc: 'Optimized for reasoning tasks' },
|
||||
{ id: 'deepseek-chat', name: 'DeepSeek Chat', icon: MessageSquare, desc: '通用聊天模型' },
|
||||
{ id: 'deepseek-reasoner', name: 'DeepSeek Reasoner', icon: Cpu, desc: '推理能力增强模型' },
|
||||
// Removed search models as they might be deprecated or identical to chat with search tool
|
||||
]
|
||||
|
||||
@@ -58,7 +58,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
try {
|
||||
const key = apiKey || (config.keys?.[0] || '')
|
||||
if (!key) {
|
||||
onMessage('error', 'Please provide an API Key')
|
||||
onMessage('error', '请提供 API 密钥')
|
||||
setLoading(false)
|
||||
setIsStreaming(false)
|
||||
return
|
||||
@@ -80,8 +80,8 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
|
||||
if (!res.ok) {
|
||||
const data = await res.json()
|
||||
setResponse({ success: false, error: data.error?.message || 'Request failed' })
|
||||
onMessage('error', data.error?.message || 'Request failed')
|
||||
setResponse({ success: false, error: data.error?.message || '请求失败' })
|
||||
onMessage('error', data.error?.message || '请求失败')
|
||||
setLoading(false)
|
||||
setIsStreaming(false)
|
||||
return
|
||||
@@ -127,7 +127,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.name === 'AbortError') {
|
||||
onMessage('info', 'Generation stopped')
|
||||
onMessage('info', '已停止生成')
|
||||
} else {
|
||||
onMessage('error', 'Network error: ' + e.message)
|
||||
setResponse({ error: e.message, success: false })
|
||||
@@ -184,11 +184,11 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
<div className="bg-card border border-border rounded-xl p-5 shadow-sm space-y-5">
|
||||
<h3 className="font-semibold flex items-center gap-2">
|
||||
<Sparkles className="w-4 h-4 text-primary" />
|
||||
Configuration
|
||||
模型选项
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
<label className="text-sm font-medium text-muted-foreground">Model</label>
|
||||
<label className="text-sm font-medium text-muted-foreground">模型</label>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
{MODELS.map(m => {
|
||||
const Icon = m.icon
|
||||
@@ -217,13 +217,13 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-muted-foreground">Account Strategy</label>
|
||||
<label className="text-sm font-medium text-muted-foreground">账号策略</label>
|
||||
<select
|
||||
className="input-field"
|
||||
value={selectedAccount}
|
||||
onChange={e => setSelectedAccount(e.target.value)}
|
||||
>
|
||||
<option value="">🎲 Random (Streaming)</option>
|
||||
<option value="">🎲 随机切换 (支持流式预览)</option>
|
||||
{accounts.map((acc, i) => (
|
||||
<option key={i} value={acc.email || acc.mobile}>
|
||||
👤 {acc.email || acc.mobile}
|
||||
@@ -233,11 +233,11 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-muted-foreground">API Key (Optional)</label>
|
||||
<label className="text-sm font-medium text-muted-foreground">API 密钥 (可选)</label>
|
||||
<input
|
||||
type="password"
|
||||
className="input-field font-mono text-xs"
|
||||
placeholder={config.keys?.[0] ? `Default: ${config.keys[0].slice(0, 8)}...` : 'Enter custom API Key'}
|
||||
placeholder={config.keys?.[0] ? `默认: ${config.keys[0].slice(0, 8)}...` : '输入自定义 API 密钥'}
|
||||
value={apiKey}
|
||||
onChange={e => setApiKey(e.target.value)}
|
||||
/>
|
||||
@@ -256,7 +256,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
</div>
|
||||
<div className="space-y-1 flew-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="font-medium text-sm">You</span>
|
||||
<span className="font-medium text-sm">用户</span>
|
||||
</div>
|
||||
<div className="bg-secondary/50 rounded-2xl rounded-tl-none px-4 py-3 text-sm border border-border">
|
||||
{message}
|
||||
@@ -290,7 +290,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
<div className="text-xs text-muted-foreground bg-muted/30 border border-border rounded-lg p-3 space-y-1">
|
||||
<div className="flex items-center gap-1.5 opacity-70 mb-1">
|
||||
<Sparkles className="w-3 h-3" />
|
||||
<span>Reasoning Process</span>
|
||||
<span>思维链/推理过程</span>
|
||||
</div>
|
||||
<div className="whitespace-pre-wrap leading-relaxed opacity-90 font-mono">
|
||||
{streamingThinking || response?.response?.thinking}
|
||||
@@ -316,7 +316,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
|
||||
<div className="max-w-3xl mx-auto relative">
|
||||
<textarea
|
||||
className="w-full bg-secondary/30 border border-border rounded-xl pl-4 pr-14 py-3 text-sm focus:bg-background focus:ring-1 focus:ring-primary focus:border-primary transition-all resize-none custom-scrollbar"
|
||||
placeholder="Type your message here..."
|
||||
placeholder="输入消息..."
|
||||
rows={3}
|
||||
value={message}
|
||||
onChange={e => setMessage(e.target.value)}
|
||||
|
||||
@@ -4,8 +4,8 @@ import clsx from 'clsx'
|
||||
|
||||
const TEMPLATES = {
|
||||
full: {
|
||||
name: 'Full Configuration',
|
||||
desc: 'Includes keys, accounts, and model mappings',
|
||||
name: '全量配置模板',
|
||||
desc: '包含密钥、账号及模型映射',
|
||||
config: {
|
||||
keys: ["your-api-key-1", "your-api-key-2"],
|
||||
accounts: [
|
||||
@@ -20,8 +20,8 @@ const TEMPLATES = {
|
||||
}
|
||||
},
|
||||
email_only: {
|
||||
name: 'Email Only',
|
||||
desc: 'Batch import email accounts',
|
||||
name: '仅邮箱账号',
|
||||
desc: '批量导入邮箱格式账号',
|
||||
config: {
|
||||
keys: ["your-api-key"],
|
||||
accounts: [
|
||||
@@ -32,8 +32,8 @@ const TEMPLATES = {
|
||||
}
|
||||
},
|
||||
mobile_only: {
|
||||
name: 'Mobile Only',
|
||||
desc: 'Batch import mobile number accounts',
|
||||
name: '仅手机号账号',
|
||||
desc: '批量导入手机号格式账号',
|
||||
config: {
|
||||
keys: ["your-api-key"],
|
||||
accounts: [
|
||||
@@ -44,8 +44,8 @@ const TEMPLATES = {
|
||||
}
|
||||
},
|
||||
keys_only: {
|
||||
name: 'API Keys Only',
|
||||
desc: 'Just adding API access keys',
|
||||
name: '仅 API 密钥',
|
||||
desc: '仅添加 API 访问密钥',
|
||||
config: {
|
||||
keys: ["key-1", "key-2", "key-3"]
|
||||
}
|
||||
@@ -62,7 +62,7 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
|
||||
|
||||
const handleImport = async () => {
|
||||
if (!jsonInput.trim()) {
|
||||
onMessage('error', 'Please enter JSON configuration')
|
||||
onMessage('error', '请输入 JSON 配置内容')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
|
||||
try {
|
||||
config = JSON.parse(jsonInput)
|
||||
} catch (e) {
|
||||
onMessage('error', 'Invalid JSON format')
|
||||
onMessage('error', '无效的 JSON 格式')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -85,10 +85,10 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
|
||||
const data = await res.json()
|
||||
if (res.ok) {
|
||||
setResult(data)
|
||||
onMessage('success', `Imported: ${data.imported_keys} Keys, ${data.imported_accounts} Accounts`)
|
||||
onMessage('success', `导入成功: ${data.imported_keys} 个密钥, ${data.imported_accounts} 个账号`)
|
||||
onRefresh()
|
||||
} else {
|
||||
onMessage('error', data.detail || 'Import failed')
|
||||
onMessage('error', data.detail || '导入失败')
|
||||
}
|
||||
} catch (e) {
|
||||
onMessage('error', 'Network error')
|
||||
@@ -101,7 +101,7 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
|
||||
const tpl = TEMPLATES[key]
|
||||
if (tpl) {
|
||||
setJsonInput(JSON.stringify(tpl.config, null, 2))
|
||||
onMessage('info', `Loaded template: ${tpl.name}`)
|
||||
onMessage('info', `已加载模板: ${tpl.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,10 +111,10 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
setJsonInput(JSON.stringify(JSON.parse(data.json), null, 2))
|
||||
onMessage('success', 'Configuration loaded')
|
||||
onMessage('success', '当前配置已加载')
|
||||
}
|
||||
} catch (e) {
|
||||
onMessage('error', 'Failed to fetch config')
|
||||
onMessage('error', '获取配置失败')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,10 +126,10 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
|
||||
await navigator.clipboard.writeText(data.base64)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
onMessage('success', 'Base64 copied to clipboard')
|
||||
onMessage('success', 'Base64 配置已复制到剪贴板')
|
||||
}
|
||||
} catch (e) {
|
||||
onMessage('error', 'Copy failed')
|
||||
onMessage('error', '复制失败')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
|
||||
<div className="bg-card border border-border rounded-xl p-5 shadow-sm">
|
||||
<h3 className="font-semibold flex items-center gap-2 mb-4">
|
||||
<FileCode className="w-4 h-4 text-primary" />
|
||||
Quick Templates
|
||||
快速模板
|
||||
</h3>
|
||||
<div className="space-y-3">
|
||||
{Object.entries(TEMPLATES).map(([key, tpl]) => (
|
||||
@@ -159,20 +159,20 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
|
||||
<div className="bg-linear-to-br from-primary/10 to-transparent border border-primary/20 rounded-xl p-5 shadow-sm">
|
||||
<h3 className="font-semibold flex items-center gap-2 mb-2 text-primary">
|
||||
<Download className="w-4 h-4" />
|
||||
Export Data
|
||||
数据导出
|
||||
</h3>
|
||||
<p className="text-sm text-muted-foreground mb-4">
|
||||
Get your configuration as a Base64 string for Vercel environment variables.
|
||||
获取配置的 Base64 字符串,用于 Vercel 环境变量。
|
||||
</p>
|
||||
<button
|
||||
onClick={copyBase64}
|
||||
className="w-full btn btn-primary bg-primary/90 hover:bg-primary shadow-lg shadow-primary/20"
|
||||
>
|
||||
{copied ? <Check className="w-4 h-4 mr-2" /> : <Copy className="w-4 h-4 mr-2" />}
|
||||
{copied ? 'Copied!' : 'Copy Base64 Config'}
|
||||
{copied ? '已复制!' : '复制 Base64 配置'}
|
||||
</button>
|
||||
<p className="text-[10px] text-muted-foreground mt-2 text-center">
|
||||
Variable Name: <code className="bg-background px-1 py-0.5 rounded border border-border">DS2API_CONFIG_JSON</code>
|
||||
变量名: <code className="bg-background px-1 py-0.5 rounded border border-border">DS2API_CONFIG_JSON</code>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -182,14 +182,14 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
|
||||
<div className="p-4 border-b border-border flex items-center justify-between bg-muted/20">
|
||||
<h3 className="font-semibold flex items-center gap-2">
|
||||
<Upload className="w-4 h-4 text-primary" />
|
||||
JSON Editor
|
||||
JSON 编辑器
|
||||
</h3>
|
||||
<div className="flex gap-2">
|
||||
<button onClick={handleExport} className="btn btn-secondary text-xs h-8">
|
||||
Load Current
|
||||
加载当前配置
|
||||
</button>
|
||||
<button onClick={handleImport} disabled={loading} className="btn btn-primary text-xs h-8">
|
||||
{loading ? 'Importing...' : 'Simulate Import'}
|
||||
{loading ? '导入中...' : '模拟导入/应用'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -217,10 +217,10 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
|
||||
)}
|
||||
<div>
|
||||
<h4 className={clsx("font-medium", result.imported_keys || result.imported_accounts ? "text-emerald-500" : "text-destructive")}>
|
||||
Import Operation Completed
|
||||
导入操作已完成
|
||||
</h4>
|
||||
<p className="text-sm opacity-80 mt-1">
|
||||
Successfully imported {result.imported_keys} API keys and updated {result.imported_accounts} accounts.
|
||||
成功导入了 {result.imported_keys} 个 API 密钥,并更新了 {result.imported_accounts} 个账号。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,10 +49,10 @@ export default function Login({ onLogin, onMessage }) {
|
||||
<Lock className="w-8 h-8" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold tracking-tight text-white">
|
||||
Welcome Back
|
||||
欢迎回来
|
||||
</h1>
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Enter your admin key to access the dashboard
|
||||
请输入管理员密钥访问控制面板
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -60,7 +60,7 @@ export default function Login({ onLogin, onMessage }) {
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-xs font-medium text-muted-foreground ml-1 uppercase tracking-wider">
|
||||
Admin Key
|
||||
管理员密钥
|
||||
</label>
|
||||
<div className="relative group">
|
||||
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-muted-foreground group-focus-within:text-primary transition-colors">
|
||||
@@ -69,7 +69,7 @@ export default function Login({ onLogin, onMessage }) {
|
||||
<input
|
||||
type="password"
|
||||
className="block w-full pl-10 pr-3 py-2.5 bg-secondary/50 border border-border rounded-lg text-sm placeholder:text-muted-foreground/50 focus:border-primary/50 focus:ring-2 focus:ring-primary/20 focus:bg-secondary focus:outline-none transition-all duration-200"
|
||||
placeholder="Enter your DS2API_ADMIN_KEY"
|
||||
placeholder="输入您的 DS2API_ADMIN_KEY"
|
||||
value={adminKey}
|
||||
onChange={e => setAdminKey(e.target.value)}
|
||||
autoFocus
|
||||
@@ -94,7 +94,7 @@ export default function Login({ onLogin, onMessage }) {
|
||||
onClick={() => setRemember(!remember)}
|
||||
className="text-sm text-muted-foreground cursor-pointer select-none hover:text-foreground transition-colors"
|
||||
>
|
||||
Keep me signed in
|
||||
记住登录状态
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -108,7 +108,7 @@ export default function Login({ onLogin, onMessage }) {
|
||||
<div className="w-5 h-5 border-2 border-current border-t-transparent rounded-full animate-spin" />
|
||||
) : (
|
||||
<>
|
||||
<span>Access Dashboard</span>
|
||||
<span>进入控制面板</span>
|
||||
<ArrowRight className="w-4 h-4 ml-2" />
|
||||
</>
|
||||
)}
|
||||
@@ -118,7 +118,7 @@ export default function Login({ onLogin, onMessage }) {
|
||||
<div className="pt-6 border-t border-border/50 text-center">
|
||||
<div className="flex items-center justify-center gap-2 text-xs text-muted-foreground/70">
|
||||
<ShieldCheck className="w-3 h-3" />
|
||||
<span>Secure Session • 24h Expiration</span>
|
||||
<span>安全会话 • 24小时有效</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -32,11 +32,11 @@ export default function VercelSync({ onMessage, authFetch }) {
|
||||
const tokenToUse = preconfig?.has_token && !vercelToken ? '__USE_PRECONFIG__' : vercelToken
|
||||
|
||||
if (!tokenToUse && !preconfig?.has_token) {
|
||||
onMessage('error', 'Vercel Token is required')
|
||||
onMessage('error', '需要 Vercel 访问令牌')
|
||||
return
|
||||
}
|
||||
if (!projectId) {
|
||||
onMessage('error', 'Project ID is required')
|
||||
onMessage('error', '需要项目 ID')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ export default function VercelSync({ onMessage, authFetch }) {
|
||||
onMessage('success', data.message)
|
||||
} else {
|
||||
setResult({ ...data, success: false })
|
||||
onMessage('error', data.detail || 'Sync failed')
|
||||
onMessage('error', data.detail || '同步失败')
|
||||
}
|
||||
} catch (e) {
|
||||
onMessage('error', 'Network error')
|
||||
@@ -74,26 +74,26 @@ export default function VercelSync({ onMessage, authFetch }) {
|
||||
<div className="border-b border-border pb-6">
|
||||
<h2 className="text-xl font-semibold flex items-center gap-2">
|
||||
<Cloud className="w-6 h-6 text-primary" />
|
||||
Vercel Deployment
|
||||
Vercel 部署
|
||||
</h2>
|
||||
<p className="text-muted-foreground text-sm mt-1">
|
||||
Sync your current key and account configuration directly to Vercel environment variables.
|
||||
将当前密钥和账号配置直接同步到 Vercel 环境变量中。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium flex items-center justify-between">
|
||||
Vercel Token
|
||||
Vercel 访问令牌
|
||||
<a href="https://vercel.com/account/tokens" target="_blank" rel="noopener noreferrer" className="text-xs text-primary hover:underline flex items-center gap-1">
|
||||
Get Token <ExternalLink className="w-3 h-3" />
|
||||
获取令牌 <ExternalLink className="w-3 h-3" />
|
||||
</a>
|
||||
</label>
|
||||
<div className="relative">
|
||||
<input
|
||||
type="password"
|
||||
className="input-field pr-10"
|
||||
placeholder={preconfig?.has_token ? "Using pre-configured token" : "Enter Vercel Access Token"}
|
||||
placeholder={preconfig?.has_token ? "正在使用预配置令牌" : "输入 Vercel 访问令牌"}
|
||||
value={vercelToken}
|
||||
onChange={e => setVercelToken(e.target.value)}
|
||||
/>
|
||||
@@ -106,20 +106,20 @@ export default function VercelSync({ onMessage, authFetch }) {
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium">Project ID</label>
|
||||
<label className="text-sm font-medium">项目 ID</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input-field"
|
||||
placeholder="prj_xxxxxxxxxxxx or Project Name"
|
||||
placeholder="prj_xxxxxxxxxxxx 或项目名称"
|
||||
value={projectId}
|
||||
onChange={e => setProjectId(e.target.value)}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">Found in Project Settings → General</p>
|
||||
<p className="text-xs text-muted-foreground">可在项目设置 (Project Settings) → 常规 (General) 中找到</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium flex items-center gap-2">
|
||||
Team ID <span className="text-xs text-muted-foreground font-normal">(Optional)</span>
|
||||
团队 ID <span className="text-xs text-muted-foreground font-normal">(可选)</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -140,16 +140,16 @@ export default function VercelSync({ onMessage, authFetch }) {
|
||||
{loading ? (
|
||||
<span className="flex items-center gap-2">
|
||||
<span className="w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
||||
Syncing...
|
||||
同步中...
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center gap-2">
|
||||
Sync & Redeploy <ArrowRight className="w-5 h-5" />
|
||||
同步并重新部署 <ArrowRight className="w-5 h-5" />
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
<p className="text-xs text-center text-muted-foreground mt-4">
|
||||
This will trigger a new deployment on Vercel which takes about 30-60 seconds.
|
||||
这将触发 Vercel 的重新部署,大约需要 30-60 秒。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -170,14 +170,14 @@ export default function VercelSync({ onMessage, authFetch }) {
|
||||
)}
|
||||
<div className="space-y-1">
|
||||
<h3 className={`font-semibold text-lg ${result.success ? 'text-emerald-500' : 'text-destructive'}`}>
|
||||
{result.success ? 'Sync Successful' : 'Sync Failed'}
|
||||
{result.success ? '同步成功' : '同步失败'}
|
||||
</h3>
|
||||
<p className="text-sm opacity-90">{result.message}</p>
|
||||
|
||||
{result.deployment_url && (
|
||||
<div className="pt-3 mt-3 border-t border-emerald-500/20">
|
||||
<a href={`https://${result.deployment_url}`} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-sm font-medium hover:underline">
|
||||
Visit Deployment <ExternalLink className="w-3 h-3" />
|
||||
访问部署地址 <ExternalLink className="w-3 h-3" />
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
@@ -189,24 +189,24 @@ export default function VercelSync({ onMessage, authFetch }) {
|
||||
<div className="bg-secondary/20 border border-border rounded-xl p-6">
|
||||
<h3 className="font-semibold flex items-center gap-2 mb-4">
|
||||
<Info className="w-5 h-5 text-primary" />
|
||||
How it works
|
||||
工作原理
|
||||
</h3>
|
||||
<ul className="space-y-4">
|
||||
<li className="flex gap-3">
|
||||
<span className="shrink-0 w-6 h-6 rounded-full bg-background border border-border flex items-center justify-center text-xs font-bold text-muted-foreground">1</span>
|
||||
<p className="text-sm text-muted-foreground">Current configuration (Keys & Accounts) is exported to a JSON string.</p>
|
||||
<p className="text-sm text-muted-foreground">当前配置 (密钥和账号) 被导出为 JSON 字符串。</p>
|
||||
</li>
|
||||
<li className="flex gap-3">
|
||||
<span className="shrink-0 w-6 h-6 rounded-full bg-background border border-border flex items-center justify-center text-xs font-bold text-muted-foreground">2</span>
|
||||
<p className="text-sm text-muted-foreground">The JSON is encoded to Base64 to ensure format compatibility.</p>
|
||||
<p className="text-sm text-muted-foreground">JSON 被编码为 Base64 以确保格式兼容性。</p>
|
||||
</li>
|
||||
<li className="flex gap-3">
|
||||
<span className="shrink-0 w-6 h-6 rounded-full bg-background border border-border flex items-center justify-center text-xs font-bold text-muted-foreground">3</span>
|
||||
<p className="text-sm text-muted-foreground">We update the <code className="bg-background px-1 py-0.5 rounded border border-border text-xs">DS2API_CONFIG_JSON</code> env variable in your Vercel project.</p>
|
||||
<p className="text-sm text-muted-foreground">更新 Vercel 项目中的 <code className="bg-background px-1 py-0.5 rounded border border-border text-xs">DS2API_CONFIG_JSON</code> 环境变量。</p>
|
||||
</li>
|
||||
<li className="flex gap-3">
|
||||
<span className="shrink-0 w-6 h-6 rounded-full bg-background border border-border flex items-center justify-center text-xs font-bold text-muted-foreground">4</span>
|
||||
<p className="text-sm text-muted-foreground">A redeployment is triggered to apply the new environment variables.</p>
|
||||
<p className="text-sm text-muted-foreground">触发重新部署以应用新的环境变量。</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user