feat: Localize UI strings to Chinese and add new API documentation.

This commit is contained in:
CJACK
2026-02-01 07:09:07 +08:00
parent ef11d9cf13
commit 0dc83912bc
9 changed files with 540 additions and 130 deletions

View File

@@ -133,7 +133,7 @@ export default function App() {
<div className="min-h-screen flex items-center justify-center bg-background">
<div className="flex flex-col items-center gap-4">
<div className="w-8 h-8 border-4 border-primary border-t-transparent rounded-full animate-spin"></div>
<p className="text-muted-foreground animate-pulse">检查登录状态...</p>
<p className="text-muted-foreground animate-pulse">正在检查登录状态...</p>
</div>
</div>
)
@@ -184,7 +184,7 @@ export default function App() {
</div>
<span>DS2API</span>
</div>
<p className="text-[10px] text-muted-foreground mt-2 font-semibold tracking-[0.1em] uppercase opacity-60 px-1">V1.0.0 Admin Panel</p>
<p className="text-[10px] text-muted-foreground mt-2 font-semibold tracking-[0.1em] uppercase opacity-60 px-1">V1.0.0 管理面板</p>
</div>
<nav className="flex-1 px-3 space-y-1 overflow-y-auto pt-2">
@@ -216,20 +216,20 @@ export default function App() {
<div className="p-4 border-t border-border bg-card">
<div className="space-y-4">
<div className="flex items-center justify-between text-sm px-1">
<span className="text-muted-foreground font-semibold text-[10px] uppercase tracking-wider">System Status</span>
<span className="text-muted-foreground font-semibold text-[10px] uppercase tracking-wider">系统状态</span>
<span className="flex items-center gap-1.5 text-[10px] font-bold text-emerald-500 bg-emerald-500/10 px-2 py-0.5 rounded-full border border-emerald-500/20">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse"></span>
ONLINE
在线
</span>
</div>
<div className="grid grid-cols-2 gap-2">
<div className="bg-background rounded-lg p-3 border border-border shadow-sm">
<div className="text-[9px] text-muted-foreground font-bold uppercase tracking-wider mb-0.5 opacity-70">Accounts</div>
<div className="text-[9px] text-muted-foreground font-bold uppercase tracking-wider mb-0.5 opacity-70">账号</div>
<div className="text-lg font-bold text-foreground leading-tight">{config.accounts?.length || 0}</div>
</div>
<div className="bg-background rounded-lg p-3 border border-border shadow-sm">
<div className="text-[9px] text-muted-foreground font-bold uppercase tracking-wider mb-0.5 opacity-70">API Keys</div>
<div className="text-lg font-bold text-foreground leading-tight">{config.keys?.length || 0}</div>
<div className="text-[9px] text-muted-foreground font-bold uppercase tracking-wider mb-0.5 opacity-70">密钥</div>
<div className="text-lg font-bold text-foreground">{config.keys?.length || 0}</div>
</div>
</div>
<button

View File

@@ -57,7 +57,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
body: JSON.stringify({ key: newKey.trim() }),
})
if (res.ok) {
onMessage('success', 'API Key added successfully')
onMessage('success', 'API 密钥添加成功')
setNewKey('')
setShowAddKey(false)
onRefresh()
@@ -66,14 +66,14 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
onMessage('error', data.detail || 'Failed to add')
}
} catch (e) {
onMessage('error', 'Network error')
onMessage('error', '网络错误')
} finally {
setLoading(false)
}
}
const deleteKey = async (key) => {
if (!confirm('Are you sure you want to delete this API Key?')) return
if (!confirm('确定要删除此 API 密钥吗?')) return
try {
const res = await apiFetch(`/admin/keys/${encodeURIComponent(key)}`, { method: 'DELETE' })
if (res.ok) {
@@ -100,7 +100,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
body: JSON.stringify(newAccount),
})
if (res.ok) {
onMessage('success', 'Account added successfully')
onMessage('success', '账号添加成功')
setNewAccount({ email: '', mobile: '', password: '' })
setShowAddAccount(false)
onRefresh()
@@ -109,14 +109,14 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
onMessage('error', data.detail || 'Failed to add')
}
} catch (e) {
onMessage('error', 'Network error')
onMessage('error', '网络错误')
} finally {
setLoading(false)
}
}
const deleteAccount = async (id) => {
if (!confirm('Are you sure you want to delete this account?')) return
if (!confirm('确定要删除此账号吗?')) return
try {
const res = await apiFetch(`/admin/accounts/${encodeURIComponent(id)}`, { method: 'DELETE' })
if (res.ok) {
@@ -149,7 +149,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
}
const validateAllAccounts = async () => {
if (!confirm('Validate ALL accounts? This might take a while.')) return
if (!confirm('校验所有账号?这可能需要一些时间。')) return
const accounts = config.accounts || []
if (accounts.length === 0) return
@@ -203,7 +203,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
}
const testAllAccounts = async () => {
if (!confirm('Test API connectivity for ALL accounts?')) return
if (!confirm('测试所有账号的 API 连通性?')) return
const accounts = config.accounts || []
if (accounts.length === 0) return
@@ -248,30 +248,30 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
<div className="absolute right-0 top-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
<CheckCircle2 className="w-16 h-16" />
</div>
<p className="text-xs font-medium text-muted-foreground uppercase tracking-widest">Available</p>
<p className="text-xs font-medium text-muted-foreground uppercase tracking-widest">可用</p>
<div className="mt-2 flex items-baseline gap-2">
<span className="text-3xl font-bold text-foreground">{queueStatus.available}</span>
<span className="text-xs text-muted-foreground">accounts</span>
<span className="text-xs text-muted-foreground">个账号</span>
</div>
</div>
<div className="bg-card border border-border rounded-xl p-4 flex flex-col justify-between shadow-sm relative overflow-hidden group">
<div className="absolute right-0 top-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
<Server className="w-16 h-16" />
</div>
<p className="text-xs font-medium text-muted-foreground uppercase tracking-widest">In Use</p>
<p className="text-xs font-medium text-muted-foreground uppercase tracking-widest">正在使用</p>
<div className="mt-2 flex items-baseline gap-2">
<span className="text-3xl font-bold text-foreground">{queueStatus.in_use}</span>
<span className="text-xs text-muted-foreground">threads</span>
<span className="text-xs text-muted-foreground">线程</span>
</div>
</div>
<div className="bg-card border border-border rounded-xl p-4 flex flex-col justify-between shadow-sm relative overflow-hidden group">
<div className="absolute right-0 top-0 p-4 opacity-5 group-hover:opacity-10 transition-opacity">
<ShieldCheck className="w-16 h-16" />
</div>
<p className="text-xs font-medium text-muted-foreground uppercase tracking-widest">Total Pool</p>
<p className="text-xs font-medium text-muted-foreground uppercase tracking-widest">账号池总数</p>
<div className="mt-2 flex items-baseline gap-2">
<span className="text-3xl font-bold text-foreground">{queueStatus.total}</span>
<span className="text-xs text-muted-foreground">accounts</span>
<span className="text-xs text-muted-foreground">个账号</span>
</div>
</div>
</div>
@@ -290,7 +290,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
className="flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors font-medium text-sm shadow-sm"
>
<Plus className="w-4 h-4" />
Add Key
添加密钥
</button>
</div>
@@ -329,7 +329,7 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
className="flex items-center px-3 py-2 bg-secondary text-secondary-foreground rounded-lg hover:bg-secondary/80 transition-colors text-xs font-medium border border-border disabled:opacity-50"
>
{testingAll ? <span className="animate-spin mr-2"></span> : <Play className="w-3 h-3 mr-2" />}
Test All
测试全部
</button>
<button
onClick={validateAllAccounts}
@@ -337,14 +337,14 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
className="flex items-center px-3 py-2 bg-secondary text-secondary-foreground rounded-lg hover:bg-secondary/80 transition-colors text-xs font-medium border border-border disabled:opacity-50"
>
{validatingAll ? <span className="animate-spin mr-2"></span> : <CheckCircle2 className="w-3 h-3 mr-2" />}
Validate All
校验全部
</button>
<button
onClick={() => setShowAddAccount(true)}
className="flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors font-medium text-sm shadow-sm"
>
<Plus className="w-4 h-4" />
Add Account
添加账号
</button>
</div>
</div>
@@ -455,9 +455,9 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
/>
</div>
<div className="flex justify-end gap-2 pt-2">
<button onClick={() => setShowAddKey(false)} className="px-4 py-2 rounded-lg border border-border hover:bg-secondary transition-colors text-sm font-medium">Cancel</button>
<button onClick={() => setShowAddKey(false)} className="px-4 py-2 rounded-lg border border-border hover:bg-secondary transition-colors text-sm font-medium">取消</button>
<button onClick={addKey} disabled={loading} className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors text-sm font-medium disabled:opacity-50">
{loading ? 'Adding...' : 'Add Key'}
{loading ? '添加中...' : '添加密钥'}
</button>
</div>
</div>
@@ -508,9 +508,9 @@ export default function AccountManager({ config, onRefresh, onMessage, authFetch
/>
</div>
<div className="flex justify-end gap-2 pt-2">
<button onClick={() => setShowAddAccount(false)} className="px-4 py-2 rounded-lg border border-border hover:bg-secondary transition-colors text-sm font-medium">Cancel</button>
<button onClick={() => setShowAddAccount(false)} className="px-4 py-2 rounded-lg border border-border hover:bg-secondary transition-colors text-sm font-medium">取消</button>
<button onClick={addAccount} disabled={loading} className="px-4 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors text-sm font-medium disabled:opacity-50">
{loading ? 'Adding...' : 'Add Account'}
{loading ? '添加中...' : '添加账号'}
</button>
</div>
</div>

View File

@@ -19,8 +19,10 @@ import {
import clsx from 'clsx'
const MODELS = [
{ id: "deepseek-chat", name: "deepseek-chat", icon: MessageSquare, desc: "通用高智商模型 (V3)", color: "text-amber-500" },
{ id: "deepseek-reasoner", name: "deepseek-reasoner", icon: Cpu, desc: "深度推理/思维链 (R1)", color: "text-amber-600" },
{ id: "deepseek-chat", name: "deepseek-chat", icon: MessageSquare, desc: "非思考模型", color: "text-amber-500" },
{ id: "deepseek-reasoner", name: "deepseek-reasoner", icon: Cpu, desc: "思考模型", color: "text-amber-600" },
{ id: "deepseek-chat-search", name: "deepseek-chat-search", icon: SearchIcon, desc: "非思考模型 (带搜索)", color: "text-cyan-500" },
{ id: "deepseek-reasoner-search", name: "deepseek-reasoner-search", icon: SearchIcon, desc: "思考模型 (带搜索)", color: "text-cyan-600" },
];
export default function ApiTester({ config, onMessage, authFetch }) {
@@ -116,13 +118,16 @@ export default function ApiTester({ config, onMessage, authFetch }) {
try {
const json = JSON.parse(dataStr)
console.log('[ApiTester] Parsed JSON:', json)
const choice = json.choices?.[0]
if (choice?.delta) {
const delta = choice.delta
console.log('[ApiTester] Delta:', delta)
if (delta.reasoning_content) {
setStreamingThinking(prev => prev + delta.reasoning_content)
}
if (delta.content) {
console.log('[ApiTester] Content:', delta.content)
setStreamingContent(prev => prev + delta.content)
}
}
@@ -135,7 +140,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
if (e.name === 'AbortError') {
onMessage('info', '已停止生成')
} else {
onMessage('error', 'Network error: ' + e.message)
onMessage('error', '网络错误: ' + e.message)
setResponse({ error: e.message, success: false })
}
} finally {
@@ -167,12 +172,12 @@ export default function ApiTester({ config, onMessage, authFetch }) {
account: selectedAccount,
})
if (data.success) {
onMessage('success', `${selectedAccount}: Test Success (${data.response_time}ms)`)
onMessage('success', `${selectedAccount}: 测试成功 (${data.response_time}ms)`)
} else {
onMessage('error', `${selectedAccount}: ${data.message}`)
}
} catch (e) {
onMessage('error', 'Network error: ' + e.message)
onMessage('error', '网络错误: ' + e.message)
setResponse({ error: e.message })
} finally {
setLoading(false)
@@ -229,7 +234,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
>
<div className={clsx(
"p-1.5 rounded-md shrink-0 transition-colors",
model === m.id ? "text-primary" : "text-muted-foreground group-hover:text-foreground"
model === m.id ? m.color : "text-muted-foreground group-hover:text-foreground"
)}>
<Icon className="w-4 h-4" />
</div>
@@ -240,7 +245,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
<div className="text-[11px] text-muted-foreground mt-0.5">{m.desc}</div>
</div>
{model === m.id && (
<div className="absolute top-3 right-3 text-primary">
<div className={clsx("absolute top-3 right-3", m.color)}>
<div className="w-1.5 h-1.5 rounded-full bg-current" />
</div>
)}
@@ -274,7 +279,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
<input
type="password"
className="w-full h-10 px-3 bg-muted/30 border border-border rounded-lg text-sm font-mono placeholder:text-muted-foreground/40 focus:outline-none focus:ring-1 focus:ring-ring focus:border-ring transition-all"
placeholder={config.keys?.[0] ? `默认: ...${config.keys[0].slice(-6)}` : '输入自定义 Key'}
placeholder={config.keys?.[0] ? `默认: ...${config.keys[0].slice(-6)}` : '输入自定义密钥'}
value={apiKey}
onChange={e => setApiKey(e.target.value)}
/>
@@ -319,7 +324,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
"text-[10px] px-1.5 py-0.5 rounded-sm border uppercase font-medium tracking-wider",
response.success ? "border-emerald-500/20 text-emerald-500 bg-emerald-500/10" : "border-destructive/20 text-destructive bg-destructive/10"
)}>
{response.status_code || 'ERR'}
{response.status_code || '错误'}
</span>
)}
</div>
@@ -328,7 +333,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
<div className="text-xs bg-secondary/50 border border-border rounded-lg p-3 space-y-1.5">
<div className="flex items-center gap-1.5 text-muted-foreground">
<Zap className="w-3.5 h-3.5" />
<span className="font-medium">Thinking Process</span>
<span className="font-medium">思维链过程</span>
</div>
<div className="whitespace-pre-wrap leading-relaxed text-muted-foreground font-mono text-[11px] max-h-60 overflow-y-auto custom-scrollbar pl-5 border-l-2 border-border/50">
{streamingThinking || response?.response?.thinking}
@@ -340,7 +345,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
{!selectedAccount ? (
streamingContent || (response?.error && <span className="text-destructive font-medium">{response.error}</span>)
) : (
response?.response?.message || <span className="text-muted-foreground italic">Generating response...</span>
response?.response?.message || <span className="text-muted-foreground italic">正在生成响应...</span>
)}
{isStreaming && <span className="inline-block w-1.5 h-4 bg-primary ml-1 align-middle animate-pulse" />}
</div>
@@ -354,7 +359,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
<div className="max-w-4xl mx-auto relative group">
<textarea
className="w-full bg-[#09090b] border border-border rounded-xl pl-4 pr-12 py-3 text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all resize-none custom-scrollbar placeholder:text-muted-foreground/50 text-foreground shadow-inner"
placeholder="Type a message..."
placeholder="输入消息..."
rows={1}
style={{ minHeight: '52px' }}
value={message}
@@ -386,7 +391,7 @@ export default function ApiTester({ config, onMessage, authFetch }) {
</div>
</div>
<div className="max-w-4xl mx-auto mt-3 flex justify-center">
<span className="text-[10px] text-muted-foreground/40 font-medium">DeepSeek Admin Interface</span>
<span className="text-[10px] text-muted-foreground/40 font-medium">DeepSeek 管理员界面</span>
</div>
</div>
</div>

View File

@@ -91,7 +91,7 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
onMessage('error', data.detail || '导入失败')
}
} catch (e) {
onMessage('error', 'Network error')
onMessage('error', '网络错误')
} finally {
setLoading(false)
}
@@ -169,7 +169,7 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
className="w-full flex items-center justify-center gap-2 py-2.5 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-all font-medium text-sm shadow-sm"
>
{copied ? <Check className="w-4 h-4" /> : <Copy className="w-4 h-4" />}
{copied ? 'Copied' : 'Copy Base64 Config'}
{copied ? '已复制' : '复制 Base64 配置'}
</button>
<p className="text-[10px] text-muted-foreground mt-2 text-center">
变量名: <code className="bg-background px-1 py-0.5 rounded border border-border">DS2API_CONFIG_JSON</code>
@@ -186,10 +186,10 @@ export default function BatchImport({ onRefresh, onMessage, authFetch }) {
</h3>
<div className="flex gap-2">
<button onClick={handleExport} className="px-3 py-1.5 bg-secondary text-secondary-foreground rounded-lg hover:bg-secondary/80 transition-colors text-xs font-medium border border-border">
Load Current
加载当前配置
</button>
<button onClick={handleImport} disabled={loading} className="px-3 py-1.5 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 transition-colors text-xs font-medium disabled:opacity-50">
{loading ? 'Importing...' : 'Apply Config'}
{loading ? '正在导入...' : '应用配置'}
</button>
</div>
</div>

View File

@@ -1,5 +1,5 @@
import { useState } from 'react'
import { Key, ArrowRight, ShieldCheck, Lock } from 'lucide-react'
import { Key, ArrowRight, ShieldCheck, Lock, Check } from 'lucide-react'
import clsx from 'clsx'
export default function Login({ onLogin, onMessage }) {
@@ -46,69 +46,55 @@ export default function Login({ onLogin, onMessage }) {
<div className="w-full max-w-[400px] relative z-10 animate-in fade-in zoom-in-95 duration-200">
<div className="w-full bg-card border border-border rounded-xl p-8 shadow-sm">
<div className="text-center space-y-2 mb-8">
<div className="text-center space-y-2 mb-8 animate-in fade-in slide-in-from-top-4 duration-500">
<div className="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-primary/10 text-primary mb-2">
<Lock className="w-6 h-6" />
</div>
<h1 className="text-xl font-semibold tracking-tight text-foreground">
欢迎回来
</h1>
<p className="text-muted-foreground text-sm">
请输入管理员密钥继续
</p>
<h1 className="text-3xl font-bold tracking-tight text-foreground">欢迎回来</h1>
<p className="text-sm text-muted-foreground/80">请输入管理员密钥以继续</p>
</div>
<form onSubmit={handleLogin} className="space-y-4">
<div className="space-y-4">
<div className="space-y-2">
<label className="text-xs font-medium text-muted-foreground ml-0.5">
管理员密钥
</label>
<div className="relative group">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none text-muted-foreground/50 transition-colors">
<Key className="w-4 h-4" />
</div>
<input
type="password"
id="admin_key"
className="w-full h-12 px-4 bg-[#09090b] border border-border rounded-lg text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all text-foreground font-mono"
placeholder="••••••••••••••••"
value={adminKey}
onChange={(e) => setAdminKey(e.target.value)}
onKeyDown={(e) => e.key === 'Enter' && handleLogin()}
/>
<form onSubmit={handleLogin} className="space-y-5 animate-in fade-in slide-in-from-bottom-4 duration-700 delay-150">
<div className="space-y-2">
<label className="text-xs font-semibold text-muted-foreground uppercase tracking-widest ml-1">管理员密钥</label>
<div className="relative group">
<div className="absolute inset-y-0 left-0 pl-3.5 flex items-center pointer-events-none text-muted-foreground group-focus-within:text-primary transition-colors">
<Key className="w-4 h-4" />
</div>
<input
type="password"
className="w-full bg-[#09090b] border border-border rounded-xl pl-10 pr-4 py-3 text-sm focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all placeholder:text-muted-foreground/30 text-foreground"
placeholder="输入您的管理员密钥..."
value={adminKey}
onChange={e => setAdminKey(e.target.value)}
autoFocus
/>
</div>
</div>
<div className="flex items-center space-x-2.5">
<button
type="button"
role="checkbox"
aria-checked={remember}
onClick={() => setRemember(!remember)}
className={clsx(
"w-4 h-4 rounded border flex items-center justify-center transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-ring/40",
remember ? "bg-primary border-primary text-primary-foreground" : "border-muted-foreground/40 bg-transparent hover:border-muted-foreground"
)}
>
{remember && <div className="w-2 h-2 rounded-[1px] bg-current" />}
</button>
<span
onClick={() => setRemember(!remember)}
className="text-xs text-muted-foreground cursor-pointer select-none hover:text-foreground transition-colors"
>
记住登录状态
</span>
</div>
<div className="flex items-center justify-between px-1">
<label className="flex items-center gap-2.5 cursor-pointer group">
<div className="relative flex items-center">
<input
type="checkbox"
className="peer sr-only"
checked={remember}
onChange={e => setRemember(e.target.checked)}
/>
<div className="w-4.5 h-4.5 bg-secondary border border-border rounded-md peer-checked:bg-primary peer-checked:border-primary transition-all shadow-sm"></div>
<Check className="absolute w-3 h-3 text-primary-foreground opacity-0 peer-checked:opacity-100 left-0.5 transition-opacity" />
</div>
<span className="text-xs font-medium text-muted-foreground group-hover:text-foreground transition-colors">记住登录状态</span>
</label>
</div>
<button
type="submit"
disabled={loading}
className="w-full flex items-center justify-center py-2.5 px-4 rounded-lg bg-primary hover:bg-primary/90 text-primary-foreground font-medium text-sm transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-ring disabled:opacity-50 disabled:cursor-not-allowed"
className="w-full h-12 flex items-center justify-center gap-2 bg-primary text-primary-foreground rounded-xl hover:bg-primary/90 transition-all font-semibold text-sm shadow-lg shadow-primary/20 hover:shadow-primary/30 disabled:opacity-50 disabled:shadow-none"
>
{loading ? (
<div className="w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin" />
<div className="w-5 h-5 border-2 border-primary-foreground/30 border-t-primary-foreground rounded-full animate-spin" />
) : (
<div className="flex items-center gap-2">
<span>登录</span>
@@ -121,13 +107,13 @@ export default function Login({ onLogin, onMessage }) {
<div className="mt-6 pt-6 border-t border-border flex justify-center">
<div className="flex items-center gap-1.5 text-[10px] text-muted-foreground/60 font-medium tracking-wide uppercase">
<ShieldCheck className="w-3 h-3" />
<span>Secured Connection</span>
<span>安全连接</span>
</div>
</div>
</div>
<div className="mt-8 text-center">
<p className="text-[10px] text-muted-foreground/30 font-mono">DS2API Admin Portal</p>
<p className="text-[10px] text-muted-foreground/30 font-mono text-center">DS2API 管理员门户</p>
</div>
</div>
</div>

View File

@@ -61,7 +61,7 @@ export default function VercelSync({ onMessage, authFetch }) {
onMessage('error', data.detail || '同步失败')
}
} catch (e) {
onMessage('error', 'Network error')
onMessage('error', '网络错误')
} finally {
setLoading(false)
}
@@ -93,7 +93,7 @@ export default function VercelSync({ onMessage, authFetch }) {
<input
type="password"
className="w-full h-10 px-3 bg-background border border-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-ring focus:border-ring transition-all 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)}
/>
@@ -140,11 +140,11 @@ export default function VercelSync({ onMessage, authFetch }) {
{loading ? (
<span className="flex items-center gap-2">
<span className="w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin" />
Syncing...
正在同步...
</span>
) : (
<span className="flex items-center gap-2">
Sync & Redeploy <ArrowRight className="w-4 h-4" />
同步并重新部署 <ArrowRight className="w-4 h-4" />
</span>
)}
</button>