From d97b86e0eef5c2542fdb58d0a752b0e32ef2aec0 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 28 Feb 2026 09:57:19 +0800 Subject: [PATCH] =?UTF-8?q?feat(webui):=20=E8=B4=A6=E5=8F=B7=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=B7=BB=E5=8A=A0=E6=90=9C=E7=B4=A2=E8=BF=87=E6=BB=A4?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端 GET /admin/accounts 支持 ?q= 参数,大小写不敏感匹配 identifier/email/mobile - 前端搜索框内嵌于标题栏按钮行(测试全部按钮前) - 搜索时重置到第 1 页,分页 total 反映过滤后数量 - 无匹配结果时显示专属提示文案(中英文) --- internal/admin/handler_accounts_crud.go | 243 +++---- .../account/AccountManagerContainer.jsx | 238 +++---- webui/src/features/account/AccountsTable.jsx | 373 +++++------ webui/src/features/account/useAccountsData.js | 161 ++--- webui/src/locales/en.json | 592 +++++++++--------- webui/src/locales/zh.json | 592 +++++++++--------- 6 files changed, 1120 insertions(+), 1079 deletions(-) diff --git a/internal/admin/handler_accounts_crud.go b/internal/admin/handler_accounts_crud.go index a0d64df..768e59e 100644 --- a/internal/admin/handler_accounts_crud.go +++ b/internal/admin/handler_accounts_crud.go @@ -1,115 +1,128 @@ -package admin - -import ( - "encoding/json" - "fmt" - "net/http" - "strings" - - "github.com/go-chi/chi/v5" - - "ds2api/internal/config" -) - -func (h *Handler) listAccounts(w http.ResponseWriter, r *http.Request) { - page := intFromQuery(r, "page", 1) - pageSize := intFromQuery(r, "page_size", 10) - if page < 1 { - page = 1 - } - if pageSize < 1 { - pageSize = 1 - } - if pageSize > 100 { - pageSize = 100 - } - accounts := h.Store.Snapshot().Accounts - total := len(accounts) - reverseAccounts(accounts) - totalPages := 1 - if total > 0 { - totalPages = (total + pageSize - 1) / pageSize - } - start := (page - 1) * pageSize - if start > total { - start = total - } - end := start + pageSize - if end > total { - end = total - } - items := make([]map[string]any, 0, end-start) - for _, acc := range accounts[start:end] { - token := strings.TrimSpace(acc.Token) - preview := "" - if token != "" { - if len(token) > 20 { - preview = token[:20] + "..." - } else { - preview = token - } - } - items = append(items, map[string]any{ - "identifier": acc.Identifier(), - "email": acc.Email, - "mobile": acc.Mobile, - "has_password": acc.Password != "", - "has_token": token != "", - "token_preview": preview, - "test_status": acc.TestStatus, - }) - } - writeJSON(w, http.StatusOK, map[string]any{"items": items, "total": total, "page": page, "page_size": pageSize, "total_pages": totalPages}) -} - -func (h *Handler) addAccount(w http.ResponseWriter, r *http.Request) { - var req map[string]any - _ = json.NewDecoder(r.Body).Decode(&req) - acc := toAccount(req) - if acc.Identifier() == "" { - writeJSON(w, http.StatusBadRequest, map[string]any{"detail": "需要 email 或 mobile"}) - return - } - err := h.Store.Update(func(c *config.Config) error { - for _, a := range c.Accounts { - if acc.Email != "" && a.Email == acc.Email { - return fmt.Errorf("邮箱已存在") - } - if acc.Mobile != "" && a.Mobile == acc.Mobile { - return fmt.Errorf("手机号已存在") - } - } - c.Accounts = append(c.Accounts, acc) - return nil - }) - if err != nil { - writeJSON(w, http.StatusBadRequest, map[string]any{"detail": err.Error()}) - return - } - h.Pool.Reset() - writeJSON(w, http.StatusOK, map[string]any{"success": true, "total_accounts": len(h.Store.Snapshot().Accounts)}) -} - -func (h *Handler) deleteAccount(w http.ResponseWriter, r *http.Request) { - identifier := chi.URLParam(r, "identifier") - err := h.Store.Update(func(c *config.Config) error { - idx := -1 - for i, a := range c.Accounts { - if accountMatchesIdentifier(a, identifier) { - idx = i - break - } - } - if idx < 0 { - return fmt.Errorf("账号不存在") - } - c.Accounts = append(c.Accounts[:idx], c.Accounts[idx+1:]...) - return nil - }) - if err != nil { - writeJSON(w, http.StatusNotFound, map[string]any{"detail": err.Error()}) - return - } - h.Pool.Reset() - writeJSON(w, http.StatusOK, map[string]any{"success": true, "total_accounts": len(h.Store.Snapshot().Accounts)}) -} +package admin + +import ( + "encoding/json" + "fmt" + "net/http" + "strings" + + "github.com/go-chi/chi/v5" + + "ds2api/internal/config" +) + +func (h *Handler) listAccounts(w http.ResponseWriter, r *http.Request) { + page := intFromQuery(r, "page", 1) + pageSize := intFromQuery(r, "page_size", 10) + if page < 1 { + page = 1 + } + if pageSize < 1 { + pageSize = 1 + } + if pageSize > 100 { + pageSize = 100 + } + accounts := h.Store.Snapshot().Accounts + reverseAccounts(accounts) + q := strings.TrimSpace(strings.ToLower(r.URL.Query().Get("q"))) + if q != "" { + filtered := make([]config.Account, 0, len(accounts)) + for _, acc := range accounts { + id := strings.ToLower(acc.Identifier()) + if strings.Contains(id, q) || + strings.Contains(strings.ToLower(acc.Email), q) || + strings.Contains(strings.ToLower(acc.Mobile), q) { + filtered = append(filtered, acc) + } + } + accounts = filtered + } + total := len(accounts) + totalPages := 1 + if total > 0 { + totalPages = (total + pageSize - 1) / pageSize + } + start := (page - 1) * pageSize + if start > total { + start = total + } + end := start + pageSize + if end > total { + end = total + } + items := make([]map[string]any, 0, end-start) + for _, acc := range accounts[start:end] { + token := strings.TrimSpace(acc.Token) + preview := "" + if token != "" { + if len(token) > 20 { + preview = token[:20] + "..." + } else { + preview = token + } + } + items = append(items, map[string]any{ + "identifier": acc.Identifier(), + "email": acc.Email, + "mobile": acc.Mobile, + "has_password": acc.Password != "", + "has_token": token != "", + "token_preview": preview, + "test_status": acc.TestStatus, + }) + } + writeJSON(w, http.StatusOK, map[string]any{"items": items, "total": total, "page": page, "page_size": pageSize, "total_pages": totalPages}) +} + +func (h *Handler) addAccount(w http.ResponseWriter, r *http.Request) { + var req map[string]any + _ = json.NewDecoder(r.Body).Decode(&req) + acc := toAccount(req) + if acc.Identifier() == "" { + writeJSON(w, http.StatusBadRequest, map[string]any{"detail": "需要 email 或 mobile"}) + return + } + err := h.Store.Update(func(c *config.Config) error { + for _, a := range c.Accounts { + if acc.Email != "" && a.Email == acc.Email { + return fmt.Errorf("邮箱已存在") + } + if acc.Mobile != "" && a.Mobile == acc.Mobile { + return fmt.Errorf("手机号已存在") + } + } + c.Accounts = append(c.Accounts, acc) + return nil + }) + if err != nil { + writeJSON(w, http.StatusBadRequest, map[string]any{"detail": err.Error()}) + return + } + h.Pool.Reset() + writeJSON(w, http.StatusOK, map[string]any{"success": true, "total_accounts": len(h.Store.Snapshot().Accounts)}) +} + +func (h *Handler) deleteAccount(w http.ResponseWriter, r *http.Request) { + identifier := chi.URLParam(r, "identifier") + err := h.Store.Update(func(c *config.Config) error { + idx := -1 + for i, a := range c.Accounts { + if accountMatchesIdentifier(a, identifier) { + idx = i + break + } + } + if idx < 0 { + return fmt.Errorf("账号不存在") + } + c.Accounts = append(c.Accounts[:idx], c.Accounts[idx+1:]...) + return nil + }) + if err != nil { + writeJSON(w, http.StatusNotFound, map[string]any{"detail": err.Error()}) + return + } + h.Pool.Reset() + writeJSON(w, http.StatusOK, map[string]any{"success": true, "total_accounts": len(h.Store.Snapshot().Accounts)}) +} diff --git a/webui/src/features/account/AccountManagerContainer.jsx b/webui/src/features/account/AccountManagerContainer.jsx index 558739d..9da3616 100644 --- a/webui/src/features/account/AccountManagerContainer.jsx +++ b/webui/src/features/account/AccountManagerContainer.jsx @@ -1,117 +1,121 @@ -import { useI18n } from '../../i18n' -import { useAccountsData } from './useAccountsData' -import { useAccountActions } from './useAccountActions' -import QueueCards from './QueueCards' -import ApiKeysPanel from './ApiKeysPanel' -import AccountsTable from './AccountsTable' -import AddKeyModal from './AddKeyModal' -import AddAccountModal from './AddAccountModal' - -export default function AccountManagerContainer({ config, onRefresh, onMessage, authFetch }) { - const { t } = useI18n() - const apiFetch = authFetch || fetch - - const { - queueStatus, - keysExpanded, - setKeysExpanded, - accounts, - page, - pageSize, - totalPages, - totalAccounts, - loadingAccounts, - fetchAccounts, - changePageSize, - resolveAccountIdentifier, - } = useAccountsData({ apiFetch }) - - const { - showAddKey, - setShowAddKey, - showAddAccount, - setShowAddAccount, - newKey, - setNewKey, - copiedKey, - setCopiedKey, - newAccount, - setNewAccount, - loading, - testing, - testingAll, - batchProgress, - addKey, - deleteKey, - addAccount, - deleteAccount, - testAccount, - testAllAccounts, - } = useAccountActions({ - apiFetch, - t, - onMessage, - onRefresh, - config, - fetchAccounts, - resolveAccountIdentifier, - }) - - return ( -
- - - - - setShowAddAccount(true)} - onTestAccount={testAccount} - onDeleteAccount={deleteAccount} - onPrevPage={() => fetchAccounts(page - 1)} - onNextPage={() => fetchAccounts(page + 1)} - onPageSizeChange={changePageSize} - /> - - setShowAddKey(false)} - onAdd={addKey} - /> - - setShowAddAccount(false)} - onAdd={addAccount} - /> -
- ) -} +import { useI18n } from '../../i18n' +import { useAccountsData } from './useAccountsData' +import { useAccountActions } from './useAccountActions' +import QueueCards from './QueueCards' +import ApiKeysPanel from './ApiKeysPanel' +import AccountsTable from './AccountsTable' +import AddKeyModal from './AddKeyModal' +import AddAccountModal from './AddAccountModal' + +export default function AccountManagerContainer({ config, onRefresh, onMessage, authFetch }) { + const { t } = useI18n() + const apiFetch = authFetch || fetch + + const { + queueStatus, + keysExpanded, + setKeysExpanded, + accounts, + page, + pageSize, + totalPages, + totalAccounts, + loadingAccounts, + fetchAccounts, + changePageSize, + resolveAccountIdentifier, + searchQuery, + handleSearchChange, + } = useAccountsData({ apiFetch }) + + const { + showAddKey, + setShowAddKey, + showAddAccount, + setShowAddAccount, + newKey, + setNewKey, + copiedKey, + setCopiedKey, + newAccount, + setNewAccount, + loading, + testing, + testingAll, + batchProgress, + addKey, + deleteKey, + addAccount, + deleteAccount, + testAccount, + testAllAccounts, + } = useAccountActions({ + apiFetch, + t, + onMessage, + onRefresh, + config, + fetchAccounts, + resolveAccountIdentifier, + }) + + return ( +
+ + + + + setShowAddAccount(true)} + onTestAccount={testAccount} + onDeleteAccount={deleteAccount} + onPrevPage={() => fetchAccounts(page - 1)} + onNextPage={() => fetchAccounts(page + 1)} + onPageSizeChange={changePageSize} + searchQuery={searchQuery} + onSearchChange={handleSearchChange} + /> + + setShowAddKey(false)} + onAdd={addKey} + /> + + setShowAddAccount(false)} + onAdd={addAccount} + /> +
+ ) +} diff --git a/webui/src/features/account/AccountsTable.jsx b/webui/src/features/account/AccountsTable.jsx index 7f7c960..d7be383 100644 --- a/webui/src/features/account/AccountsTable.jsx +++ b/webui/src/features/account/AccountsTable.jsx @@ -1,182 +1,191 @@ -import { useState } from 'react' -import { ChevronLeft, ChevronRight, Check, Copy, Play, Plus, Trash2 } from 'lucide-react' -import clsx from 'clsx' - -export default function AccountsTable({ - t, - accounts, - loadingAccounts, - testing, - testingAll, - batchProgress, - totalAccounts, - page, - pageSize, - totalPages, - resolveAccountIdentifier, - onTestAll, - onShowAddAccount, - onTestAccount, - onDeleteAccount, - onPrevPage, - onNextPage, - onPageSizeChange, -}) { - const [copiedId, setCopiedId] = useState(null) - - const copyId = (id) => { - navigator.clipboard.writeText(id).then(() => { - setCopiedId(id) - setTimeout(() => setCopiedId(null), 1500) - }) - } - return ( -
-
-
-

{t('accountManager.accountsTitle')}

-

{t('accountManager.accountsDesc')}

-
-
- - -
-
- - {testingAll && batchProgress.total > 0 && ( -
-
- {t('accountManager.testingAllAccounts')} - {batchProgress.current} / {batchProgress.total} -
-
-
-
- {batchProgress.results.length > 0 && ( -
- {batchProgress.results.map((r, i) => ( -
- {r.success ? '✓' : '✗'} {r.id} -
- ))} -
- )} -
- )} - -
- {loadingAccounts ? ( -
{t('actions.loading')}
- ) : accounts.length > 0 ? ( - accounts.map((acc, i) => { - const id = resolveAccountIdentifier(acc) - return ( -
-
-
-
-
copyId(id)} - > - {id || '-'} - {copiedId === id - ? - : - } -
-
- {acc.test_status === 'failed' ? t('accountManager.testStatusFailed') : (acc.test_status === 'ok' || acc.has_token) ? t('accountManager.sessionActive') : t('accountManager.reauthRequired')} - {acc.token_preview && ( - - {acc.token_preview} - - )} -
-
-
-
- - -
-
- ) - }) - ) : ( -
{t('accountManager.noAccounts')}
- )} -
- - {totalPages > 1 && ( -
-
-
- {t('accountManager.pageInfo', { current: page, total: totalPages, count: totalAccounts })} -
- -
-
- - {page} / {totalPages} - -
-
- )} -
- ) -} +import { useState } from 'react' +import { ChevronLeft, ChevronRight, Check, Copy, Play, Plus, Trash2 } from 'lucide-react' +import clsx from 'clsx' + +export default function AccountsTable({ + t, + accounts, + loadingAccounts, + testing, + testingAll, + batchProgress, + totalAccounts, + page, + pageSize, + totalPages, + resolveAccountIdentifier, + onTestAll, + onShowAddAccount, + onTestAccount, + onDeleteAccount, + onPrevPage, + onNextPage, + onPageSizeChange, + searchQuery, + onSearchChange, +}) { + const [copiedId, setCopiedId] = useState(null) + + const copyId = (id) => { + navigator.clipboard.writeText(id).then(() => { + setCopiedId(id) + setTimeout(() => setCopiedId(null), 1500) + }) + } + return ( +
+
+
+

{t('accountManager.accountsTitle')}

+

{t('accountManager.accountsDesc')}

+
+
+ onSearchChange(e.target.value)} + placeholder={t('accountManager.searchPlaceholder')} + className="px-3 py-1.5 text-sm bg-muted border border-border rounded-lg focus:outline-none focus:ring-1 focus:ring-ring placeholder:text-muted-foreground" + /> + + +
+
+ + {testingAll && batchProgress.total > 0 && ( +
+
+ {t('accountManager.testingAllAccounts')} + {batchProgress.current} / {batchProgress.total} +
+
+
+
+ {batchProgress.results.length > 0 && ( +
+ {batchProgress.results.map((r, i) => ( +
+ {r.success ? '✓' : '✗'} {r.id} +
+ ))} +
+ )} +
+ )} + +
+ {loadingAccounts ? ( +
{t('actions.loading')}
+ ) : accounts.length > 0 ? ( + accounts.map((acc, i) => { + const id = resolveAccountIdentifier(acc) + return ( +
+
+
+
+
copyId(id)} + > + {id || '-'} + {copiedId === id + ? + : + } +
+
+ {acc.test_status === 'failed' ? t('accountManager.testStatusFailed') : (acc.test_status === 'ok' || acc.has_token) ? t('accountManager.sessionActive') : t('accountManager.reauthRequired')} + {acc.token_preview && ( + + {acc.token_preview} + + )} +
+
+
+
+ + +
+
+ ) + }) + ) : ( +
{searchQuery ? t('accountManager.searchNoResults') : t('accountManager.noAccounts')}
+ )} +
+ + {totalPages > 1 && ( +
+
+
+ {t('accountManager.pageInfo', { current: page, total: totalPages, count: totalAccounts })} +
+ +
+
+ + {page} / {totalPages} + +
+
+ )} +
+ ) +} diff --git a/webui/src/features/account/useAccountsData.js b/webui/src/features/account/useAccountsData.js index 90c56e0..3c904f3 100644 --- a/webui/src/features/account/useAccountsData.js +++ b/webui/src/features/account/useAccountsData.js @@ -1,75 +1,86 @@ -import { useEffect, useState } from 'react' - -export function useAccountsData({ apiFetch }) { - const [queueStatus, setQueueStatus] = useState(null) - const [keysExpanded, setKeysExpanded] = useState(false) - - const [accounts, setAccounts] = useState([]) - const [page, setPage] = useState(1) - const [pageSize, setPageSize] = useState(10) - const [totalPages, setTotalPages] = useState(1) - const [totalAccounts, setTotalAccounts] = useState(0) - const [loadingAccounts, setLoadingAccounts] = useState(false) - - const resolveAccountIdentifier = (acc) => { - if (!acc || typeof acc !== 'object') return '' - return String(acc.identifier || acc.email || acc.mobile || '').trim() - } - - const fetchAccounts = async (targetPage = page, targetPageSize = pageSize) => { - setLoadingAccounts(true) - try { - const res = await apiFetch(`/admin/accounts?page=${targetPage}&page_size=${targetPageSize}`) - if (res.ok) { - const data = await res.json() - setAccounts(data.items || []) - setTotalPages(data.total_pages || 1) - setTotalAccounts(data.total || 0) - setPage(data.page || 1) - } - } catch (e) { - console.error('Failed to fetch accounts:', e) - } finally { - setLoadingAccounts(false) - } - } - - const changePageSize = (newSize) => { - setPageSize(newSize) - fetchAccounts(1, newSize) - } - - const fetchQueueStatus = async () => { - try { - const res = await apiFetch('/admin/queue/status') - if (res.ok) { - const data = await res.json() - setQueueStatus(data) - } - } catch (e) { - console.error('Failed to fetch queue status:', e) - } - } - - useEffect(() => { - fetchAccounts() - fetchQueueStatus() - const interval = setInterval(fetchQueueStatus, 5000) - return () => clearInterval(interval) - }, []) - - return { - queueStatus, - keysExpanded, - setKeysExpanded, - accounts, - page, - pageSize, - totalPages, - totalAccounts, - loadingAccounts, - fetchAccounts, - changePageSize, - resolveAccountIdentifier, - } -} +import { useEffect, useState } from 'react' + +export function useAccountsData({ apiFetch }) { + const [queueStatus, setQueueStatus] = useState(null) + const [keysExpanded, setKeysExpanded] = useState(false) + + const [accounts, setAccounts] = useState([]) + const [page, setPage] = useState(1) + const [pageSize, setPageSize] = useState(10) + const [totalPages, setTotalPages] = useState(1) + const [totalAccounts, setTotalAccounts] = useState(0) + const [loadingAccounts, setLoadingAccounts] = useState(false) + + const resolveAccountIdentifier = (acc) => { + if (!acc || typeof acc !== 'object') return '' + return String(acc.identifier || acc.email || acc.mobile || '').trim() + } + + const [searchQuery, setSearchQuery] = useState('') + + const fetchAccounts = async (targetPage = page, targetPageSize = pageSize, targetQuery = searchQuery) => { + setLoadingAccounts(true) + try { + let url = `/admin/accounts?page=${targetPage}&page_size=${targetPageSize}` + if (targetQuery.trim()) url += `&q=${encodeURIComponent(targetQuery.trim())}` + const res = await apiFetch(url) + if (res.ok) { + const data = await res.json() + setAccounts(data.items || []) + setTotalPages(data.total_pages || 1) + setTotalAccounts(data.total || 0) + setPage(data.page || 1) + } + } catch (e) { + console.error('Failed to fetch accounts:', e) + } finally { + setLoadingAccounts(false) + } + } + + const changePageSize = (newSize) => { + setPageSize(newSize) + fetchAccounts(1, newSize) + } + + const handleSearchChange = (query) => { + setSearchQuery(query) + fetchAccounts(1, pageSize, query) + } + + const fetchQueueStatus = async () => { + try { + const res = await apiFetch('/admin/queue/status') + if (res.ok) { + const data = await res.json() + setQueueStatus(data) + } + } catch (e) { + console.error('Failed to fetch queue status:', e) + } + } + + useEffect(() => { + fetchAccounts() + fetchQueueStatus() + const interval = setInterval(fetchQueueStatus, 5000) + return () => clearInterval(interval) + }, []) + + return { + queueStatus, + keysExpanded, + setKeysExpanded, + accounts, + page, + pageSize, + totalPages, + totalAccounts, + loadingAccounts, + fetchAccounts, + changePageSize, + resolveAccountIdentifier, + searchQuery, + handleSearchChange, + } +} diff --git a/webui/src/locales/en.json b/webui/src/locales/en.json index bd38fd0..499509e 100644 --- a/webui/src/locales/en.json +++ b/webui/src/locales/en.json @@ -1,295 +1,297 @@ -{ - "language": { - "label": "Language", - "english": "English", - "chinese": "中文" - }, - "nav": { - "accounts": { - "label": "Account Management", - "desc": "Manage the DeepSeek account pool" - }, - "test": { - "label": "API Test", - "desc": "Test API connectivity and responses" - }, - "import": { - "label": "Batch Import", - "desc": "Bulk import account configuration" - }, - "vercel": { - "label": "Vercel Sync", - "desc": "Sync configuration to Vercel" - }, - "settings": { - "label": "Settings", - "desc": "Edit runtime and security settings online" - } - }, - "sidebar": { - "onlineAdminConsole": "Online Admin Console", - "systemStatus": "System Status", - "statusOnline": "Online", - "accounts": "Accounts", - "keys": "Keys", - "signOut": "Sign out" - }, - "auth": { - "expired": "Authentication expired. Please sign in again.", - "checking": "Checking authentication status..." - }, - "errors": { - "fetchConfig": "Failed to fetch configuration: {error}" - }, - "actions": { - "cancel": "Cancel", - "add": "Add", - "delete": "Delete", - "copy": "Copy", - "generate": "Generate", - "test": "Test", - "testing": "Testing...", - "loading": "Loading..." - }, - "messages": { - "deleted": "Deleted successfully", - "deleteFailed": "Delete failed", - "failedToAdd": "Failed to add", - "networkError": "Network error.", - "requestFailed": "Request failed.", - "generationStopped": "Generation stopped.", - "invalidJson": "Invalid JSON format.", - "importFailed": "Import failed.", - "copyFailed": "Copy failed." - }, - "landing": { - "adminConsole": "Admin Console", - "apiStatus": "API Status", - "features": { - "compatibility": { - "title": "Full Compatibility", - "desc": "OpenAI & Claude format support" - }, - "loadBalancing": { - "title": "Load Balancing", - "desc": "Smart rotation with stable throughput" - }, - "reasoning": { - "title": "Deep Reasoning", - "desc": "Expose reasoning traces when enabled" - }, - "search": { - "title": "Web Search", - "desc": "Integrated native web search" - } - } - }, - "accountManager": { - "addKeySuccess": "API key added successfully.", - "addAccountSuccess": "Account added successfully.", - "requiredFields": "Password and email/mobile are required.", - "deleteKeyConfirm": "Are you sure you want to delete this API key?", - "deleteAccountConfirm": "Are you sure you want to delete this account?", - "invalidIdentifier": "Invalid account identifier. Operation aborted.", - "testAllConfirm": "Test API connectivity for all accounts?", - "testAllCompleted": "Completed: {success}/{total} available", - "testFailed": "Test failed: {error}", - "available": "Available", - "inUse": "In use", - "totalPool": "Total pool", - "accountsUnit": "accounts", - "threadsUnit": "threads", - "apiKeysTitle": "API Keys", - "apiKeysDesc": "Manage the API access key pool", - "addKey": "Add key", - "copied": "Copied", - "copyKeyTitle": "Copy key", - "deleteKeyTitle": "Delete key", - "noApiKeys": "No API keys found.", - "accountsTitle": "DeepSeek Accounts", - "accountsDesc": "Manage the DeepSeek account pool", - "testAll": "Test all", - "addAccount": "Add account", - "testingAllAccounts": "Testing all accounts...", - "sessionActive": "Session active", - "reauthRequired": "Re-auth required", - "testStatusFailed": "Last test failed", - "noAccounts": "No accounts found.", - "modalAddKeyTitle": "Add API key", - "newKeyLabel": "New key value", - "newKeyPlaceholder": "Enter a custom API key", - "generate": "Generate", - "generateHint": "Click Generate to create a random key.", - "addKeyLoading": "Adding...", - "addKeyAction": "Add key", - "modalAddAccountTitle": "Add DeepSeek account", - "emailOptional": "Email (optional)", - "mobileOptional": "Mobile (optional)", - "passwordLabel": "Password", - "passwordPlaceholder": "Account password", - "addAccountLoading": "Adding...", - "addAccountAction": "Add account", - "pageInfo": "Page {current}/{total}, {count} accounts total" - }, - "apiTester": { - "defaultMessage": "Hello, please introduce yourself in one sentence.", - "models": { - "chat": "Non-reasoning model", - "reasoner": "Reasoning model", - "chatSearch": "Non-reasoning model (with search)", - "reasonerSearch": "Reasoning model (with search)" - }, - "missingApiKey": "Please provide an API key.", - "requestFailed": "Request failed.", - "networkError": "Network error: {error}", - "testSuccess": "{account}: Test successful ({time}ms)", - "config": "Configuration", - "modelLabel": "Model", - "streamMode": "Streaming", - "accountSelector": "Account", - "autoRandom": "🤖 Auto / Random", - "apiKeyOptional": "API Key (optional)", - "apiKeyDefault": "Default: ...{suffix}", - "apiKeyPlaceholder": "Enter a custom key", - "modeManaged": "Managed key mode (uses account pool).", - "modeDirect": "Direct token mode (requires a valid DeepSeek token).", - "statusError": "Error", - "reasoningTrace": "Reasoning Trace", - "generating": "Generating response...", - "enterMessage": "Enter a message...", - "adminConsoleLabel": "DeepSeek admin console" - }, - "batchImport": { - "templates": { - "full": { - "name": "Full configuration template", - "desc": "Includes keys, accounts, and model mapping" - }, - "emailOnly": { - "name": "Email-only accounts", - "desc": "Batch import accounts using email login" - }, - "mobileOnly": { - "name": "Mobile-only accounts", - "desc": "Batch import accounts using mobile login" - }, - "keysOnly": { - "name": "API keys only", - "desc": "Add API access keys only" - } - }, - "enterJson": "Please provide JSON configuration content.", - "importSuccess": "Import successful: {keys} keys, {accounts} accounts", - "templateLoaded": "Template loaded: {name}", - "currentConfigLoaded": "Current configuration loaded.", - "fetchConfigFailed": "Failed to fetch configuration.", - "copySuccess": "Base64 configuration copied to clipboard.", - "quickTemplates": "Quick Templates", - "dataExport": "Data Export", - "dataExportDesc": "Copy the Base64-encoded configuration for Vercel environment variables.", - "copyBase64": "Copy Base64 config", - "copied": "Copied", - "variableName": "Variable name", - "jsonEditor": "JSON Editor", - "loadCurrentConfig": "Load current config", - "applyConfig": "Apply config", - "importing": "Importing...", - "importComplete": "Import complete", - "importSummary": "Imported {keys} API keys and updated {accounts} accounts." - }, - "settings": { - "loadFailed": "Failed to load settings.", - "nonJsonResponse": "Unexpected non-JSON response from server (status: {status}).", - "save": "Save settings", - "saving": "Saving...", - "saveSuccess": "Settings saved and hot reloaded.", - "saveFailed": "Failed to save settings.", - "securityTitle": "Security", - "jwtExpireHours": "JWT expiry (hours)", - "newPassword": "New admin password", - "newPasswordPlaceholder": "Enter new password (min 4 chars)", - "updatePassword": "Update password", - "updating": "Updating...", - "passwordTooShort": "Password must be at least 4 characters.", - "passwordUpdated": "Password updated. Please sign in again.", - "passwordUpdateFailed": "Failed to update password.", - "runtimeTitle": "Concurrency & Queue", - "accountMaxInflight": "Per-account max inflight", - "accountMaxQueue": "Account max queue size", - "globalMaxInflight": "Global max inflight", - "behaviorTitle": "Behavior", - "toolcallMode": "Toolcall mode", - "earlyEmitConfidence": "Early emit confidence", - "responsesTTL": "Responses store TTL (seconds)", - "embeddingsProvider": "Embeddings provider", - "modelTitle": "Model mapping", - "claudeMapping": "Claude mapping (JSON)", - "modelAliases": "Model aliases (JSON)", - "backupTitle": "Backup & Restore", - "loadExport": "Load current export", - "importModeMerge": "Merge import (default)", - "importModeReplace": "Replace all import", - "importNow": "Import now", - "importing": "Importing...", - "importPlaceholder": "Paste config JSON to import", - "importEmpty": "Please input import JSON.", - "importInvalidJson": "Import JSON is invalid.", - "importFailed": "Import failed.", - "importSuccess": "Config imported (mode: {mode}).", - "exportFailed": "Export failed.", - "exportLoaded": "Current export loaded.", - "exportJson": "Export JSON", - "invalidJsonField": "{field} is not a valid JSON object.", - "defaultPasswordWarning": "You are using the default admin password \"admin\". Please change it.", - "vercelSyncHint": "Configuration changed. For Vercel deployments, sync manually in Vercel Sync and redeploy.", - "autoFetchPaused": "Auto loading paused after {count} failures: {error}", - "retryLoad": "Retry now" - }, - "login": { - "welcome": "Welcome back", - "subtitle": "Enter your admin key to continue", - "adminKeyLabel": "Admin key", - "adminKeyPlaceholder": "Enter your admin key...", - "rememberSession": "Remember this session", - "signIn": "Sign in", - "secureConnection": "Secure connection", - "adminPortal": "DS2API admin portal", - "signInFailed": "Sign-in failed.", - "networkError": "Network error: {error}" - }, - "vercel": { - "tokenRequired": "Vercel access token is required.", - "projectRequired": "Project ID is required.", - "syncFailed": "Sync failed.", - "networkError": "Network error.", - "title": "Vercel Deployment", - "description": "Sync the current keys and accounts directly to Vercel environment variables.", - "tokenLabel": "Vercel Access Token", - "getToken": "Get token", - "tokenPlaceholderPreconfig": "Using preconfigured token", - "tokenPlaceholder": "Enter Vercel access token", - "projectIdLabel": "Project ID", - "projectIdHint": "Find it in Project Settings → General.", - "teamIdLabel": "Team ID", - "optional": "optional", - "syncing": "Syncing...", - "syncRedeploy": "Sync & redeploy", - "redeployHint": "This triggers a Vercel redeploy and usually takes 30–60 seconds.", - "syncSucceeded": "Sync succeeded", - "syncFailedLabel": "Sync failed", - "openDeployment": "Open deployment", - "statusSynced": "Synced", - "statusNotSynced": "Not synced", - "statusNeverSynced": "Never synced", - "lastSyncTime": "Last sync: {time}", - "pollPaused": "Status polling paused after {count} failures.", - "manualRefresh": "Refresh manually", - "howItWorks": "How it works", - "steps": { - "one": "The current configuration (keys and accounts) is exported as JSON.", - "two": "The JSON is Base64-encoded for safe formatting.", - "three": "Update the env var in Vercel:", - "four": "Trigger a redeploy to apply the updated environment variables." - } - } -} +{ + "language": { + "label": "Language", + "english": "English", + "chinese": "中文" + }, + "nav": { + "accounts": { + "label": "Account Management", + "desc": "Manage the DeepSeek account pool" + }, + "test": { + "label": "API Test", + "desc": "Test API connectivity and responses" + }, + "import": { + "label": "Batch Import", + "desc": "Bulk import account configuration" + }, + "vercel": { + "label": "Vercel Sync", + "desc": "Sync configuration to Vercel" + }, + "settings": { + "label": "Settings", + "desc": "Edit runtime and security settings online" + } + }, + "sidebar": { + "onlineAdminConsole": "Online Admin Console", + "systemStatus": "System Status", + "statusOnline": "Online", + "accounts": "Accounts", + "keys": "Keys", + "signOut": "Sign out" + }, + "auth": { + "expired": "Authentication expired. Please sign in again.", + "checking": "Checking authentication status..." + }, + "errors": { + "fetchConfig": "Failed to fetch configuration: {error}" + }, + "actions": { + "cancel": "Cancel", + "add": "Add", + "delete": "Delete", + "copy": "Copy", + "generate": "Generate", + "test": "Test", + "testing": "Testing...", + "loading": "Loading..." + }, + "messages": { + "deleted": "Deleted successfully", + "deleteFailed": "Delete failed", + "failedToAdd": "Failed to add", + "networkError": "Network error.", + "requestFailed": "Request failed.", + "generationStopped": "Generation stopped.", + "invalidJson": "Invalid JSON format.", + "importFailed": "Import failed.", + "copyFailed": "Copy failed." + }, + "landing": { + "adminConsole": "Admin Console", + "apiStatus": "API Status", + "features": { + "compatibility": { + "title": "Full Compatibility", + "desc": "OpenAI & Claude format support" + }, + "loadBalancing": { + "title": "Load Balancing", + "desc": "Smart rotation with stable throughput" + }, + "reasoning": { + "title": "Deep Reasoning", + "desc": "Expose reasoning traces when enabled" + }, + "search": { + "title": "Web Search", + "desc": "Integrated native web search" + } + } + }, + "accountManager": { + "addKeySuccess": "API key added successfully.", + "addAccountSuccess": "Account added successfully.", + "requiredFields": "Password and email/mobile are required.", + "deleteKeyConfirm": "Are you sure you want to delete this API key?", + "deleteAccountConfirm": "Are you sure you want to delete this account?", + "invalidIdentifier": "Invalid account identifier. Operation aborted.", + "testAllConfirm": "Test API connectivity for all accounts?", + "testAllCompleted": "Completed: {success}/{total} available", + "testFailed": "Test failed: {error}", + "available": "Available", + "inUse": "In use", + "totalPool": "Total pool", + "accountsUnit": "accounts", + "threadsUnit": "threads", + "apiKeysTitle": "API Keys", + "apiKeysDesc": "Manage the API access key pool", + "addKey": "Add key", + "copied": "Copied", + "copyKeyTitle": "Copy key", + "deleteKeyTitle": "Delete key", + "noApiKeys": "No API keys found.", + "accountsTitle": "DeepSeek Accounts", + "accountsDesc": "Manage the DeepSeek account pool", + "testAll": "Test all", + "addAccount": "Add account", + "testingAllAccounts": "Testing all accounts...", + "sessionActive": "Session active", + "reauthRequired": "Re-auth required", + "testStatusFailed": "Last test failed", + "noAccounts": "No accounts found.", + "modalAddKeyTitle": "Add API key", + "newKeyLabel": "New key value", + "newKeyPlaceholder": "Enter a custom API key", + "generate": "Generate", + "generateHint": "Click Generate to create a random key.", + "addKeyLoading": "Adding...", + "addKeyAction": "Add key", + "modalAddAccountTitle": "Add DeepSeek account", + "emailOptional": "Email (optional)", + "mobileOptional": "Mobile (optional)", + "passwordLabel": "Password", + "passwordPlaceholder": "Account password", + "addAccountLoading": "Adding...", + "addAccountAction": "Add account", + "pageInfo": "Page {current}/{total}, {count} accounts total", + "searchPlaceholder": "Search accounts...", + "searchNoResults": "No accounts match your search" + }, + "apiTester": { + "defaultMessage": "Hello, please introduce yourself in one sentence.", + "models": { + "chat": "Non-reasoning model", + "reasoner": "Reasoning model", + "chatSearch": "Non-reasoning model (with search)", + "reasonerSearch": "Reasoning model (with search)" + }, + "missingApiKey": "Please provide an API key.", + "requestFailed": "Request failed.", + "networkError": "Network error: {error}", + "testSuccess": "{account}: Test successful ({time}ms)", + "config": "Configuration", + "modelLabel": "Model", + "streamMode": "Streaming", + "accountSelector": "Account", + "autoRandom": "🤖 Auto / Random", + "apiKeyOptional": "API Key (optional)", + "apiKeyDefault": "Default: ...{suffix}", + "apiKeyPlaceholder": "Enter a custom key", + "modeManaged": "Managed key mode (uses account pool).", + "modeDirect": "Direct token mode (requires a valid DeepSeek token).", + "statusError": "Error", + "reasoningTrace": "Reasoning Trace", + "generating": "Generating response...", + "enterMessage": "Enter a message...", + "adminConsoleLabel": "DeepSeek admin console" + }, + "batchImport": { + "templates": { + "full": { + "name": "Full configuration template", + "desc": "Includes keys, accounts, and model mapping" + }, + "emailOnly": { + "name": "Email-only accounts", + "desc": "Batch import accounts using email login" + }, + "mobileOnly": { + "name": "Mobile-only accounts", + "desc": "Batch import accounts using mobile login" + }, + "keysOnly": { + "name": "API keys only", + "desc": "Add API access keys only" + } + }, + "enterJson": "Please provide JSON configuration content.", + "importSuccess": "Import successful: {keys} keys, {accounts} accounts", + "templateLoaded": "Template loaded: {name}", + "currentConfigLoaded": "Current configuration loaded.", + "fetchConfigFailed": "Failed to fetch configuration.", + "copySuccess": "Base64 configuration copied to clipboard.", + "quickTemplates": "Quick Templates", + "dataExport": "Data Export", + "dataExportDesc": "Copy the Base64-encoded configuration for Vercel environment variables.", + "copyBase64": "Copy Base64 config", + "copied": "Copied", + "variableName": "Variable name", + "jsonEditor": "JSON Editor", + "loadCurrentConfig": "Load current config", + "applyConfig": "Apply config", + "importing": "Importing...", + "importComplete": "Import complete", + "importSummary": "Imported {keys} API keys and updated {accounts} accounts." + }, + "settings": { + "loadFailed": "Failed to load settings.", + "nonJsonResponse": "Unexpected non-JSON response from server (status: {status}).", + "save": "Save settings", + "saving": "Saving...", + "saveSuccess": "Settings saved and hot reloaded.", + "saveFailed": "Failed to save settings.", + "securityTitle": "Security", + "jwtExpireHours": "JWT expiry (hours)", + "newPassword": "New admin password", + "newPasswordPlaceholder": "Enter new password (min 4 chars)", + "updatePassword": "Update password", + "updating": "Updating...", + "passwordTooShort": "Password must be at least 4 characters.", + "passwordUpdated": "Password updated. Please sign in again.", + "passwordUpdateFailed": "Failed to update password.", + "runtimeTitle": "Concurrency & Queue", + "accountMaxInflight": "Per-account max inflight", + "accountMaxQueue": "Account max queue size", + "globalMaxInflight": "Global max inflight", + "behaviorTitle": "Behavior", + "toolcallMode": "Toolcall mode", + "earlyEmitConfidence": "Early emit confidence", + "responsesTTL": "Responses store TTL (seconds)", + "embeddingsProvider": "Embeddings provider", + "modelTitle": "Model mapping", + "claudeMapping": "Claude mapping (JSON)", + "modelAliases": "Model aliases (JSON)", + "backupTitle": "Backup & Restore", + "loadExport": "Load current export", + "importModeMerge": "Merge import (default)", + "importModeReplace": "Replace all import", + "importNow": "Import now", + "importing": "Importing...", + "importPlaceholder": "Paste config JSON to import", + "importEmpty": "Please input import JSON.", + "importInvalidJson": "Import JSON is invalid.", + "importFailed": "Import failed.", + "importSuccess": "Config imported (mode: {mode}).", + "exportFailed": "Export failed.", + "exportLoaded": "Current export loaded.", + "exportJson": "Export JSON", + "invalidJsonField": "{field} is not a valid JSON object.", + "defaultPasswordWarning": "You are using the default admin password \"admin\". Please change it.", + "vercelSyncHint": "Configuration changed. For Vercel deployments, sync manually in Vercel Sync and redeploy.", + "autoFetchPaused": "Auto loading paused after {count} failures: {error}", + "retryLoad": "Retry now" + }, + "login": { + "welcome": "Welcome back", + "subtitle": "Enter your admin key to continue", + "adminKeyLabel": "Admin key", + "adminKeyPlaceholder": "Enter your admin key...", + "rememberSession": "Remember this session", + "signIn": "Sign in", + "secureConnection": "Secure connection", + "adminPortal": "DS2API admin portal", + "signInFailed": "Sign-in failed.", + "networkError": "Network error: {error}" + }, + "vercel": { + "tokenRequired": "Vercel access token is required.", + "projectRequired": "Project ID is required.", + "syncFailed": "Sync failed.", + "networkError": "Network error.", + "title": "Vercel Deployment", + "description": "Sync the current keys and accounts directly to Vercel environment variables.", + "tokenLabel": "Vercel Access Token", + "getToken": "Get token", + "tokenPlaceholderPreconfig": "Using preconfigured token", + "tokenPlaceholder": "Enter Vercel access token", + "projectIdLabel": "Project ID", + "projectIdHint": "Find it in Project Settings → General.", + "teamIdLabel": "Team ID", + "optional": "optional", + "syncing": "Syncing...", + "syncRedeploy": "Sync & redeploy", + "redeployHint": "This triggers a Vercel redeploy and usually takes 30–60 seconds.", + "syncSucceeded": "Sync succeeded", + "syncFailedLabel": "Sync failed", + "openDeployment": "Open deployment", + "statusSynced": "Synced", + "statusNotSynced": "Not synced", + "statusNeverSynced": "Never synced", + "lastSyncTime": "Last sync: {time}", + "pollPaused": "Status polling paused after {count} failures.", + "manualRefresh": "Refresh manually", + "howItWorks": "How it works", + "steps": { + "one": "The current configuration (keys and accounts) is exported as JSON.", + "two": "The JSON is Base64-encoded for safe formatting.", + "three": "Update the env var in Vercel:", + "four": "Trigger a redeploy to apply the updated environment variables." + } + } +} diff --git a/webui/src/locales/zh.json b/webui/src/locales/zh.json index 1206f89..27150b4 100644 --- a/webui/src/locales/zh.json +++ b/webui/src/locales/zh.json @@ -1,295 +1,297 @@ -{ - "language": { - "label": "语言", - "english": "English", - "chinese": "中文" - }, - "nav": { - "accounts": { - "label": "账号管理", - "desc": "管理 DeepSeek 账号池" - }, - "test": { - "label": "API 测试", - "desc": "测试 API 连接与响应" - }, - "import": { - "label": "批量导入", - "desc": "批量导入账号配置" - }, - "vercel": { - "label": "Vercel 同步", - "desc": "同步配置到 Vercel" - }, - "settings": { - "label": "设置中心", - "desc": "在线修改系统设置与配置" - } - }, - "sidebar": { - "onlineAdminConsole": "在线管理面板", - "systemStatus": "系统状态", - "statusOnline": "在线", - "accounts": "账号", - "keys": "密钥", - "signOut": "退出登录" - }, - "auth": { - "expired": "认证已过期,请重新登录", - "checking": "正在检查登录状态..." - }, - "errors": { - "fetchConfig": "获取配置失败: {error}" - }, - "actions": { - "cancel": "取消", - "add": "添加", - "delete": "删除", - "copy": "复制", - "generate": "生成", - "test": "测试", - "testing": "正在测试...", - "loading": "加载中..." - }, - "messages": { - "deleted": "删除成功", - "deleteFailed": "删除失败", - "failedToAdd": "添加失败", - "networkError": "网络错误", - "requestFailed": "请求失败", - "generationStopped": "已停止生成", - "invalidJson": "无效的 JSON 格式", - "importFailed": "导入失败", - "copyFailed": "复制失败" - }, - "landing": { - "adminConsole": "管理面板", - "apiStatus": "API 状态", - "features": { - "compatibility": { - "title": "全面兼容", - "desc": "适配 OpenAI 与 Claude 格式" - }, - "loadBalancing": { - "title": "负载均衡", - "desc": "智能轮询,稳定高效" - }, - "reasoning": { - "title": "深度思考", - "desc": "支持推理过程输出" - }, - "search": { - "title": "联网搜索", - "desc": "集成原生网页搜索能力" - } - } - }, - "accountManager": { - "addKeySuccess": "API 密钥添加成功", - "addAccountSuccess": "账号添加成功", - "requiredFields": "需要填写密码以及邮箱或手机号", - "deleteKeyConfirm": "确定要删除此 API 密钥吗?", - "deleteAccountConfirm": "确定要删除此账号吗?", - "invalidIdentifier": "账号标识无效,无法执行操作", - "testAllConfirm": "测试所有账号的 API 连通性?", - "testAllCompleted": "完成:{success}/{total} 可用", - "testFailed": "测试失败: {error}", - "available": "可用", - "inUse": "正在使用", - "totalPool": "账号池总数", - "accountsUnit": "个账号", - "threadsUnit": "线程", - "apiKeysTitle": "API 密钥", - "apiKeysDesc": "管理 API 访问密钥池", - "addKey": "添加密钥", - "copied": "已复制", - "copyKeyTitle": "复制密钥", - "deleteKeyTitle": "删除密钥", - "noApiKeys": "未找到 API 密钥", - "accountsTitle": "DeepSeek 账号", - "accountsDesc": "管理 DeepSeek 账号池", - "testAll": "测试全部", - "addAccount": "添加账号", - "testingAllAccounts": "正在测试所有账号...", - "sessionActive": "已建立会话", - "reauthRequired": "需重新登录", - "testStatusFailed": "上次测试失败", - "noAccounts": "未找到任何账号", - "modalAddKeyTitle": "添加 API 密钥", - "newKeyLabel": "新密钥值", - "newKeyPlaceholder": "输入自定义 API 密钥", - "generate": "生成", - "generateHint": "点击「生成」自动创建随机密钥", - "addKeyLoading": "添加中...", - "addKeyAction": "添加密钥", - "modalAddAccountTitle": "添加 DeepSeek 账号", - "emailOptional": "邮箱 (可选)", - "mobileOptional": "手机号 (可选)", - "passwordLabel": "密码", - "passwordPlaceholder": "账号密码", - "addAccountLoading": "添加中...", - "addAccountAction": "添加账号", - "pageInfo": "第 {current}/{total} 页,共 {count} 个账号" - }, - "apiTester": { - "defaultMessage": "你好,请用一句话介绍你自己。", - "models": { - "chat": "非思考模型", - "reasoner": "思考模型", - "chatSearch": "非思考模型 (带搜索)", - "reasonerSearch": "思考模型 (带搜索)" - }, - "missingApiKey": "请提供 API 密钥", - "requestFailed": "请求失败", - "networkError": "网络错误: {error}", - "testSuccess": "{account}: 测试成功 ({time}ms)", - "config": "配置", - "modelLabel": "模型", - "streamMode": "流式模式", - "accountSelector": "选择账号", - "autoRandom": "🤖 自动 / 随机", - "apiKeyOptional": "API 密钥 (可选)", - "apiKeyDefault": "默认: ...{suffix}", - "apiKeyPlaceholder": "输入自定义密钥", - "modeManaged": "当前使用托管 key 模式(会走账号池)。", - "modeDirect": "当前使用直通 token 模式(需填写有效 DeepSeek token)。", - "statusError": "错误", - "reasoningTrace": "思维链过程", - "generating": "正在生成响应...", - "enterMessage": "输入消息...", - "adminConsoleLabel": "DeepSeek 管理员界面" - }, - "batchImport": { - "templates": { - "full": { - "name": "全量配置模板", - "desc": "包含密钥、账号及模型映射" - }, - "emailOnly": { - "name": "仅邮箱账号", - "desc": "批量导入邮箱格式账号" - }, - "mobileOnly": { - "name": "仅手机号账号", - "desc": "批量导入手机号格式账号" - }, - "keysOnly": { - "name": "仅 API 密钥", - "desc": "仅添加 API 访问密钥" - } - }, - "enterJson": "请输入 JSON 配置内容", - "importSuccess": "导入成功: {keys} 个密钥, {accounts} 个账号", - "templateLoaded": "已加载模板: {name}", - "currentConfigLoaded": "当前配置已加载", - "fetchConfigFailed": "获取配置失败", - "copySuccess": "Base64 配置已复制到剪贴板", - "quickTemplates": "快速模板", - "dataExport": "数据导出", - "dataExportDesc": "获取配置的 Base64 字符串,用于 Vercel 环境变量。", - "copyBase64": "复制 Base64 配置", - "copied": "已复制", - "variableName": "变量名", - "jsonEditor": "JSON 编辑器", - "loadCurrentConfig": "加载当前配置", - "applyConfig": "应用配置", - "importing": "正在导入...", - "importComplete": "导入操作已完成", - "importSummary": "成功导入了 {keys} 个 API 密钥,并更新了 {accounts} 个账号。" - }, - "settings": { - "loadFailed": "加载设置失败", - "nonJsonResponse": "服务端返回了非 JSON 响应(状态码:{status})", - "save": "保存设置", - "saving": "保存中...", - "saveSuccess": "设置已保存并热更新生效", - "saveFailed": "保存设置失败", - "securityTitle": "安全设置", - "jwtExpireHours": "JWT 有效期(小时)", - "newPassword": "面板新密码", - "newPasswordPlaceholder": "输入新密码(至少 4 位)", - "updatePassword": "修改密码", - "updating": "更新中...", - "passwordTooShort": "新密码至少 4 位", - "passwordUpdated": "密码已更新,需重新登录", - "passwordUpdateFailed": "密码更新失败", - "runtimeTitle": "并发与队列", - "accountMaxInflight": "每账号并发上限", - "accountMaxQueue": "账号等待队列上限", - "globalMaxInflight": "全局并发上限", - "behaviorTitle": "行为设置", - "toolcallMode": "Toolcall 模式", - "earlyEmitConfidence": "早发置信度", - "responsesTTL": "Responses 缓存 TTL(秒)", - "embeddingsProvider": "Embeddings Provider", - "modelTitle": "模型映射", - "claudeMapping": "Claude 映射(JSON)", - "modelAliases": "模型别名(JSON)", - "backupTitle": "备份与恢复", - "loadExport": "加载当前导出", - "importModeMerge": "合并导入(默认)", - "importModeReplace": "全量覆盖导入", - "importNow": "立即导入", - "importing": "导入中...", - "importPlaceholder": "粘贴要导入的 JSON 配置", - "importEmpty": "请先输入导入 JSON", - "importInvalidJson": "导入 JSON 格式无效", - "importFailed": "导入失败", - "importSuccess": "配置导入成功(模式:{mode})", - "exportFailed": "导出失败", - "exportLoaded": "已加载当前配置导出", - "exportJson": "导出 JSON", - "invalidJsonField": "{field} 不是有效 JSON 对象", - "defaultPasswordWarning": "当前使用默认密码 admin,请尽快在此修改。", - "vercelSyncHint": "当前配置已更新。Vercel 部署请到 Vercel 同步页面手动同步并重部署。", - "autoFetchPaused": "自动加载已暂停:连续失败 {count} 次({error})", - "retryLoad": "立即重试" - }, - "login": { - "welcome": "欢迎回来", - "subtitle": "请输入管理员密钥以继续", - "adminKeyLabel": "管理员密钥", - "adminKeyPlaceholder": "输入您的管理员密钥...", - "rememberSession": "记住登录状态", - "signIn": "登录", - "secureConnection": "安全连接", - "adminPortal": "DS2API 管理员门户", - "signInFailed": "登录失败", - "networkError": "网络错误: {error}" - }, - "vercel": { - "tokenRequired": "需要 Vercel 访问令牌", - "projectRequired": "需要项目 ID", - "syncFailed": "同步失败", - "networkError": "网络错误", - "title": "Vercel 部署", - "description": "将当前密钥和账号配置直接同步到 Vercel 环境变量中。", - "tokenLabel": "Vercel 访问令牌", - "getToken": "获取令牌", - "tokenPlaceholderPreconfig": "正在使用预配置的令牌", - "tokenPlaceholder": "输入 Vercel 访问令牌", - "projectIdLabel": "项目 ID", - "projectIdHint": "可在项目设置 (Project Settings) → 常规 (General) 中找到", - "teamIdLabel": "团队 ID", - "optional": "可选", - "syncing": "正在同步...", - "syncRedeploy": "同步并重新部署", - "redeployHint": "这将触发 Vercel 的重新部署,大约需要 30-60 秒。", - "syncSucceeded": "同步成功", - "syncFailedLabel": "同步失败", - "openDeployment": "访问部署地址", - "statusSynced": "已同步", - "statusNotSynced": "未同步", - "statusNeverSynced": "从未同步", - "lastSyncTime": "上次同步: {time}", - "pollPaused": "状态轮询已暂停:连续失败 {count} 次。", - "manualRefresh": "手动刷新", - "howItWorks": "工作原理", - "steps": { - "one": "当前配置 (密钥和账号) 被导出为 JSON 字符串。", - "two": "JSON 被编码为 Base64 以确保格式兼容性。", - "three": "更新 Vercel 项目中的环境变量:", - "four": "触发重新部署以应用新的环境变量。" - } - } -} +{ + "language": { + "label": "语言", + "english": "English", + "chinese": "中文" + }, + "nav": { + "accounts": { + "label": "账号管理", + "desc": "管理 DeepSeek 账号池" + }, + "test": { + "label": "API 测试", + "desc": "测试 API 连接与响应" + }, + "import": { + "label": "批量导入", + "desc": "批量导入账号配置" + }, + "vercel": { + "label": "Vercel 同步", + "desc": "同步配置到 Vercel" + }, + "settings": { + "label": "设置中心", + "desc": "在线修改系统设置与配置" + } + }, + "sidebar": { + "onlineAdminConsole": "在线管理面板", + "systemStatus": "系统状态", + "statusOnline": "在线", + "accounts": "账号", + "keys": "密钥", + "signOut": "退出登录" + }, + "auth": { + "expired": "认证已过期,请重新登录", + "checking": "正在检查登录状态..." + }, + "errors": { + "fetchConfig": "获取配置失败: {error}" + }, + "actions": { + "cancel": "取消", + "add": "添加", + "delete": "删除", + "copy": "复制", + "generate": "生成", + "test": "测试", + "testing": "正在测试...", + "loading": "加载中..." + }, + "messages": { + "deleted": "删除成功", + "deleteFailed": "删除失败", + "failedToAdd": "添加失败", + "networkError": "网络错误", + "requestFailed": "请求失败", + "generationStopped": "已停止生成", + "invalidJson": "无效的 JSON 格式", + "importFailed": "导入失败", + "copyFailed": "复制失败" + }, + "landing": { + "adminConsole": "管理面板", + "apiStatus": "API 状态", + "features": { + "compatibility": { + "title": "全面兼容", + "desc": "适配 OpenAI 与 Claude 格式" + }, + "loadBalancing": { + "title": "负载均衡", + "desc": "智能轮询,稳定高效" + }, + "reasoning": { + "title": "深度思考", + "desc": "支持推理过程输出" + }, + "search": { + "title": "联网搜索", + "desc": "集成原生网页搜索能力" + } + } + }, + "accountManager": { + "addKeySuccess": "API 密钥添加成功", + "addAccountSuccess": "账号添加成功", + "requiredFields": "需要填写密码以及邮箱或手机号", + "deleteKeyConfirm": "确定要删除此 API 密钥吗?", + "deleteAccountConfirm": "确定要删除此账号吗?", + "invalidIdentifier": "账号标识无效,无法执行操作", + "testAllConfirm": "测试所有账号的 API 连通性?", + "testAllCompleted": "完成:{success}/{total} 可用", + "testFailed": "测试失败: {error}", + "available": "可用", + "inUse": "正在使用", + "totalPool": "账号池总数", + "accountsUnit": "个账号", + "threadsUnit": "线程", + "apiKeysTitle": "API 密钥", + "apiKeysDesc": "管理 API 访问密钥池", + "addKey": "添加密钥", + "copied": "已复制", + "copyKeyTitle": "复制密钥", + "deleteKeyTitle": "删除密钥", + "noApiKeys": "未找到 API 密钥", + "accountsTitle": "DeepSeek 账号", + "accountsDesc": "管理 DeepSeek 账号池", + "testAll": "测试全部", + "addAccount": "添加账号", + "testingAllAccounts": "正在测试所有账号...", + "sessionActive": "已建立会话", + "reauthRequired": "需重新登录", + "testStatusFailed": "上次测试失败", + "noAccounts": "未找到任何账号", + "modalAddKeyTitle": "添加 API 密钥", + "newKeyLabel": "新密钥值", + "newKeyPlaceholder": "输入自定义 API 密钥", + "generate": "生成", + "generateHint": "点击「生成」自动创建随机密钥", + "addKeyLoading": "添加中...", + "addKeyAction": "添加密钥", + "modalAddAccountTitle": "添加 DeepSeek 账号", + "emailOptional": "邮箱 (可选)", + "mobileOptional": "手机号 (可选)", + "passwordLabel": "密码", + "passwordPlaceholder": "账号密码", + "addAccountLoading": "添加中...", + "addAccountAction": "添加账号", + "pageInfo": "第 {current}/{total} 页,共 {count} 个账号", + "searchPlaceholder": "搜索账号...", + "searchNoResults": "未找到匹配的账号" + }, + "apiTester": { + "defaultMessage": "你好,请用一句话介绍你自己。", + "models": { + "chat": "非思考模型", + "reasoner": "思考模型", + "chatSearch": "非思考模型 (带搜索)", + "reasonerSearch": "思考模型 (带搜索)" + }, + "missingApiKey": "请提供 API 密钥", + "requestFailed": "请求失败", + "networkError": "网络错误: {error}", + "testSuccess": "{account}: 测试成功 ({time}ms)", + "config": "配置", + "modelLabel": "模型", + "streamMode": "流式模式", + "accountSelector": "选择账号", + "autoRandom": "🤖 自动 / 随机", + "apiKeyOptional": "API 密钥 (可选)", + "apiKeyDefault": "默认: ...{suffix}", + "apiKeyPlaceholder": "输入自定义密钥", + "modeManaged": "当前使用托管 key 模式(会走账号池)。", + "modeDirect": "当前使用直通 token 模式(需填写有效 DeepSeek token)。", + "statusError": "错误", + "reasoningTrace": "思维链过程", + "generating": "正在生成响应...", + "enterMessage": "输入消息...", + "adminConsoleLabel": "DeepSeek 管理员界面" + }, + "batchImport": { + "templates": { + "full": { + "name": "全量配置模板", + "desc": "包含密钥、账号及模型映射" + }, + "emailOnly": { + "name": "仅邮箱账号", + "desc": "批量导入邮箱格式账号" + }, + "mobileOnly": { + "name": "仅手机号账号", + "desc": "批量导入手机号格式账号" + }, + "keysOnly": { + "name": "仅 API 密钥", + "desc": "仅添加 API 访问密钥" + } + }, + "enterJson": "请输入 JSON 配置内容", + "importSuccess": "导入成功: {keys} 个密钥, {accounts} 个账号", + "templateLoaded": "已加载模板: {name}", + "currentConfigLoaded": "当前配置已加载", + "fetchConfigFailed": "获取配置失败", + "copySuccess": "Base64 配置已复制到剪贴板", + "quickTemplates": "快速模板", + "dataExport": "数据导出", + "dataExportDesc": "获取配置的 Base64 字符串,用于 Vercel 环境变量。", + "copyBase64": "复制 Base64 配置", + "copied": "已复制", + "variableName": "变量名", + "jsonEditor": "JSON 编辑器", + "loadCurrentConfig": "加载当前配置", + "applyConfig": "应用配置", + "importing": "正在导入...", + "importComplete": "导入操作已完成", + "importSummary": "成功导入了 {keys} 个 API 密钥,并更新了 {accounts} 个账号。" + }, + "settings": { + "loadFailed": "加载设置失败", + "nonJsonResponse": "服务端返回了非 JSON 响应(状态码:{status})", + "save": "保存设置", + "saving": "保存中...", + "saveSuccess": "设置已保存并热更新生效", + "saveFailed": "保存设置失败", + "securityTitle": "安全设置", + "jwtExpireHours": "JWT 有效期(小时)", + "newPassword": "面板新密码", + "newPasswordPlaceholder": "输入新密码(至少 4 位)", + "updatePassword": "修改密码", + "updating": "更新中...", + "passwordTooShort": "新密码至少 4 位", + "passwordUpdated": "密码已更新,需重新登录", + "passwordUpdateFailed": "密码更新失败", + "runtimeTitle": "并发与队列", + "accountMaxInflight": "每账号并发上限", + "accountMaxQueue": "账号等待队列上限", + "globalMaxInflight": "全局并发上限", + "behaviorTitle": "行为设置", + "toolcallMode": "Toolcall 模式", + "earlyEmitConfidence": "早发置信度", + "responsesTTL": "Responses 缓存 TTL(秒)", + "embeddingsProvider": "Embeddings Provider", + "modelTitle": "模型映射", + "claudeMapping": "Claude 映射(JSON)", + "modelAliases": "模型别名(JSON)", + "backupTitle": "备份与恢复", + "loadExport": "加载当前导出", + "importModeMerge": "合并导入(默认)", + "importModeReplace": "全量覆盖导入", + "importNow": "立即导入", + "importing": "导入中...", + "importPlaceholder": "粘贴要导入的 JSON 配置", + "importEmpty": "请先输入导入 JSON", + "importInvalidJson": "导入 JSON 格式无效", + "importFailed": "导入失败", + "importSuccess": "配置导入成功(模式:{mode})", + "exportFailed": "导出失败", + "exportLoaded": "已加载当前配置导出", + "exportJson": "导出 JSON", + "invalidJsonField": "{field} 不是有效 JSON 对象", + "defaultPasswordWarning": "当前使用默认密码 admin,请尽快在此修改。", + "vercelSyncHint": "当前配置已更新。Vercel 部署请到 Vercel 同步页面手动同步并重部署。", + "autoFetchPaused": "自动加载已暂停:连续失败 {count} 次({error})", + "retryLoad": "立即重试" + }, + "login": { + "welcome": "欢迎回来", + "subtitle": "请输入管理员密钥以继续", + "adminKeyLabel": "管理员密钥", + "adminKeyPlaceholder": "输入您的管理员密钥...", + "rememberSession": "记住登录状态", + "signIn": "登录", + "secureConnection": "安全连接", + "adminPortal": "DS2API 管理员门户", + "signInFailed": "登录失败", + "networkError": "网络错误: {error}" + }, + "vercel": { + "tokenRequired": "需要 Vercel 访问令牌", + "projectRequired": "需要项目 ID", + "syncFailed": "同步失败", + "networkError": "网络错误", + "title": "Vercel 部署", + "description": "将当前密钥和账号配置直接同步到 Vercel 环境变量中。", + "tokenLabel": "Vercel 访问令牌", + "getToken": "获取令牌", + "tokenPlaceholderPreconfig": "正在使用预配置的令牌", + "tokenPlaceholder": "输入 Vercel 访问令牌", + "projectIdLabel": "项目 ID", + "projectIdHint": "可在项目设置 (Project Settings) → 常规 (General) 中找到", + "teamIdLabel": "团队 ID", + "optional": "可选", + "syncing": "正在同步...", + "syncRedeploy": "同步并重新部署", + "redeployHint": "这将触发 Vercel 的重新部署,大约需要 30-60 秒。", + "syncSucceeded": "同步成功", + "syncFailedLabel": "同步失败", + "openDeployment": "访问部署地址", + "statusSynced": "已同步", + "statusNotSynced": "未同步", + "statusNeverSynced": "从未同步", + "lastSyncTime": "上次同步: {time}", + "pollPaused": "状态轮询已暂停:连续失败 {count} 次。", + "manualRefresh": "手动刷新", + "howItWorks": "工作原理", + "steps": { + "one": "当前配置 (密钥和账号) 被导出为 JSON 字符串。", + "two": "JSON 被编码为 Base64 以确保格式兼容性。", + "three": "更新 Vercel 项目中的环境变量:", + "four": "触发重新部署以应用新的环境变量。" + } + } +}