mirror of
https://github.com/CJackHwang/ds2api.git
synced 2026-05-04 16:35:27 +08:00
feat: add account editing functionality with UI modal and backend handler
This commit is contained in:
11
API.en.md
11
API.en.md
@@ -140,6 +140,7 @@ Gemini-compatible clients can also send `x-goog-api-key`, `?key=`, or `?api_key=
|
||||
| POST | `/admin/proxies/test` | Admin | Test proxy connectivity |
|
||||
| GET | `/admin/accounts` | Admin | Paginated account list |
|
||||
| POST | `/admin/accounts` | Admin | Add account |
|
||||
| PUT | `/admin/accounts/{identifier}` | Admin | Update account name/remark |
|
||||
| DELETE | `/admin/accounts/{identifier}` | Admin | Delete account |
|
||||
| PUT | `/admin/accounts/{identifier}/proxy` | Admin | Bind/unbind proxy for an account |
|
||||
| GET | `/admin/queue/status` | Admin | Account queue status |
|
||||
@@ -843,6 +844,16 @@ Returned items also include `test_status`, usually `ok` or `failed`.
|
||||
|
||||
**Response**: `{"success": true, "total_accounts": 6}`
|
||||
|
||||
### `PUT /admin/accounts/{identifier}`
|
||||
|
||||
Updates the `name` / `remark` of the specified account. The path `identifier` can be email or mobile and cannot be changed.
|
||||
|
||||
```json
|
||||
{"name": "Primary account", "remark": "Shared with the team"}
|
||||
```
|
||||
|
||||
**Response**: `{"success": true, "total_accounts": 6}`
|
||||
|
||||
### `DELETE /admin/accounts/{identifier}`
|
||||
|
||||
`identifier` can be email, mobile, or the synthetic id for token-only accounts (`token:<hash>`).
|
||||
|
||||
11
API.md
11
API.md
@@ -140,6 +140,7 @@ Gemini 兼容客户端还可以使用 `x-goog-api-key`、`?key=` 或 `?api_key=`
|
||||
| POST | `/admin/proxies/test` | Admin | 测试代理连通性 |
|
||||
| GET | `/admin/accounts` | Admin | 分页账号列表 |
|
||||
| POST | `/admin/accounts` | Admin | 添加账号 |
|
||||
| PUT | `/admin/accounts/{identifier}` | Admin | 更新账号 name/remark |
|
||||
| DELETE | `/admin/accounts/{identifier}` | Admin | 删除账号 |
|
||||
| PUT | `/admin/accounts/{identifier}/proxy` | Admin | 为账号绑定/解绑代理 |
|
||||
| GET | `/admin/queue/status` | Admin | 账号队列状态 |
|
||||
@@ -842,6 +843,16 @@ data: {"type":"message_stop"}
|
||||
|
||||
**响应**:`{"success": true, "total_accounts": 6}`
|
||||
|
||||
### `PUT /admin/accounts/{identifier}`
|
||||
|
||||
更新指定账号的 `name` / `remark`。路径参数中的 `identifier` 可以是 email 或 mobile,且不可修改。
|
||||
|
||||
```json
|
||||
{"name": "主账号", "remark": "团队共享"}
|
||||
```
|
||||
|
||||
**响应**:`{"success": true, "total_accounts": 6}`
|
||||
|
||||
### `DELETE /admin/accounts/{identifier}`
|
||||
|
||||
`identifier` 可为 email、mobile,或 token-only 账号的合成标识(`token:<hash>`)。
|
||||
|
||||
@@ -37,6 +37,7 @@ func RegisterRoutes(r chi.Router, h *Handler) {
|
||||
pr.Post("/proxies/test", h.testProxy)
|
||||
pr.Get("/accounts", h.listAccounts)
|
||||
pr.Post("/accounts", h.addAccount)
|
||||
pr.Put("/accounts/{identifier}", h.updateAccount)
|
||||
pr.Delete("/accounts/{identifier}", h.deleteAccount)
|
||||
pr.Put("/accounts/{identifier}/proxy", h.updateAccountProxy)
|
||||
pr.Get("/queue/status", h.queueStatus)
|
||||
|
||||
@@ -116,6 +116,46 @@ func (h *Handler) addAccount(w http.ResponseWriter, r *http.Request) {
|
||||
writeJSON(w, http.StatusOK, map[string]any{"success": true, "total_accounts": len(h.Store.Snapshot().Accounts)})
|
||||
}
|
||||
|
||||
func (h *Handler) updateAccount(w http.ResponseWriter, r *http.Request) {
|
||||
identifier := chi.URLParam(r, "identifier")
|
||||
if decoded, err := url.PathUnescape(identifier); err == nil {
|
||||
identifier = decoded
|
||||
}
|
||||
|
||||
var req map[string]any
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]any{"detail": "invalid json"})
|
||||
return
|
||||
}
|
||||
name, nameOK := fieldStringOptional(req, "name")
|
||||
remark, remarkOK := fieldStringOptional(req, "remark")
|
||||
|
||||
err := h.Store.Update(func(c *config.Config) error {
|
||||
for i, acc := range c.Accounts {
|
||||
if !accountMatchesIdentifier(acc, identifier) {
|
||||
continue
|
||||
}
|
||||
if nameOK {
|
||||
c.Accounts[i].Name = name
|
||||
}
|
||||
if remarkOK {
|
||||
c.Accounts[i].Remark = remark
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return newRequestError("账号不存在")
|
||||
})
|
||||
if err != nil {
|
||||
if detail, ok := requestErrorDetail(err); ok {
|
||||
writeJSON(w, http.StatusNotFound, map[string]any{"detail": detail})
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusBadRequest, map[string]any{"detail": err.Error()})
|
||||
return
|
||||
}
|
||||
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")
|
||||
if decoded, err := url.PathUnescape(identifier); err == nil {
|
||||
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func TestListAccountsPageSizeCapIs5000(t *testing.T) {
|
||||
@@ -51,3 +53,36 @@ func TestListAccountsPageSizeAbove5000ClampedTo5000(t *testing.T) {
|
||||
t.Fatalf("expected page_size clamped to 5000, got %v", payload["page_size"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateAccountMetadataPreservesCredentials(t *testing.T) {
|
||||
h := newAdminTestHandler(t, `{
|
||||
"accounts":[{"email":"u@example.com","name":"old name","remark":"old remark","password":"secret"}]
|
||||
}`)
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Put("/admin/accounts/{identifier}", h.updateAccount)
|
||||
|
||||
body := []byte(`{"name":"new name","remark":"new remark"}`)
|
||||
req := httptest.NewRequest(http.MethodPut, "/admin/accounts/u@example.com", strings.NewReader(string(body)))
|
||||
rec := httptest.NewRecorder()
|
||||
r.ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("unexpected status: %d body=%s", rec.Code, rec.Body.String())
|
||||
}
|
||||
|
||||
snap := h.Store.Snapshot()
|
||||
if len(snap.Accounts) != 1 {
|
||||
t.Fatalf("unexpected accounts after update: %#v", snap.Accounts)
|
||||
}
|
||||
acc := snap.Accounts[0]
|
||||
if acc.Email != "u@example.com" {
|
||||
t.Fatalf("identifier changed unexpectedly: %#v", acc)
|
||||
}
|
||||
if acc.Name != "new name" || acc.Remark != "new remark" {
|
||||
t.Fatalf("metadata update did not persist: %#v", acc)
|
||||
}
|
||||
if acc.Password != "secret" {
|
||||
t.Fatalf("password should be preserved, got %#v", acc)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import ApiKeysPanel from './ApiKeysPanel'
|
||||
import AccountsTable from './AccountsTable'
|
||||
import AddKeyModal from './AddKeyModal'
|
||||
import AddAccountModal from './AddAccountModal'
|
||||
import EditAccountModal from './EditAccountModal'
|
||||
|
||||
export default function AccountManagerContainer({ config, onRefresh, onMessage, authFetch }) {
|
||||
const { t } = useI18n()
|
||||
@@ -35,7 +36,14 @@ export default function AccountManagerContainer({ config, onRefresh, onMessage,
|
||||
closeKeyModal,
|
||||
editingKey,
|
||||
showAddAccount,
|
||||
setShowAddAccount,
|
||||
openAddAccount,
|
||||
closeAddAccount,
|
||||
showEditAccount,
|
||||
editingAccount,
|
||||
editAccount,
|
||||
setEditAccount,
|
||||
openEditAccount,
|
||||
closeEditAccount,
|
||||
newKey,
|
||||
setNewKey,
|
||||
copiedKey,
|
||||
@@ -52,6 +60,7 @@ export default function AccountManagerContainer({ config, onRefresh, onMessage,
|
||||
addKey,
|
||||
deleteKey,
|
||||
addAccount,
|
||||
updateAccount,
|
||||
deleteAccount,
|
||||
testAccount,
|
||||
testAllAccounts,
|
||||
@@ -121,7 +130,8 @@ export default function AccountManagerContainer({ config, onRefresh, onMessage,
|
||||
resolveAccountIdentifier={resolveAccountIdentifier}
|
||||
proxies={config?.proxies || []}
|
||||
onTestAll={testAllAccounts}
|
||||
onShowAddAccount={() => setShowAddAccount(true)}
|
||||
onShowAddAccount={openAddAccount}
|
||||
onEditAccount={openEditAccount}
|
||||
onTestAccount={testAccount}
|
||||
onDeleteAccount={deleteAccount}
|
||||
onDeleteAllSessions={deleteAllSessions}
|
||||
@@ -151,9 +161,20 @@ export default function AccountManagerContainer({ config, onRefresh, onMessage,
|
||||
newAccount={newAccount}
|
||||
setNewAccount={setNewAccount}
|
||||
loading={loading}
|
||||
onClose={() => setShowAddAccount(false)}
|
||||
onClose={closeAddAccount}
|
||||
onAdd={addAccount}
|
||||
/>
|
||||
|
||||
<EditAccountModal
|
||||
show={showEditAccount}
|
||||
t={t}
|
||||
editingAccount={editingAccount}
|
||||
editAccount={editAccount}
|
||||
setEditAccount={setEditAccount}
|
||||
loading={loading}
|
||||
onClose={closeEditAccount}
|
||||
onSave={updateAccount}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import { ChevronLeft, ChevronRight, Check, Copy, Play, Plus, Trash2, FolderX } from 'lucide-react'
|
||||
import { ChevronLeft, ChevronRight, Check, Copy, Pencil, Play, Plus, Trash2, FolderX } from 'lucide-react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
export default function AccountsTable({
|
||||
@@ -20,6 +20,7 @@ export default function AccountsTable({
|
||||
proxies,
|
||||
onTestAll,
|
||||
onShowAddAccount,
|
||||
onEditAccount,
|
||||
onTestAccount,
|
||||
onDeleteAccount,
|
||||
onDeleteAllSessions,
|
||||
@@ -180,6 +181,14 @@ export default function AccountsTable({
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
onClick={() => onEditAccount(acc)}
|
||||
disabled={!id}
|
||||
className="p-1 lg:p-1.5 text-muted-foreground hover:text-primary hover:bg-primary/10 rounded-md transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
title={id ? t('accountManager.editAccountTitle') : t('accountManager.invalidIdentifier')}
|
||||
>
|
||||
<Pencil className="w-3.5 h-3.5 lg:w-4 lg:h-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onTestAccount(id)}
|
||||
disabled={testing[id]}
|
||||
|
||||
65
webui/src/features/account/EditAccountModal.jsx
Normal file
65
webui/src/features/account/EditAccountModal.jsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { X } from 'lucide-react'
|
||||
|
||||
export default function EditAccountModal({
|
||||
show,
|
||||
t,
|
||||
editingAccount,
|
||||
editAccount,
|
||||
setEditAccount,
|
||||
loading,
|
||||
onClose,
|
||||
onSave,
|
||||
}) {
|
||||
if (!show || !editingAccount) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<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-start gap-4">
|
||||
<div className="min-w-0">
|
||||
<h3 className="font-semibold">{t('accountManager.modalEditAccountTitle')}</h3>
|
||||
<p className="mt-1 text-xs text-muted-foreground">{t('accountManager.editAccountHint')}</p>
|
||||
</div>
|
||||
<button onClick={onClose} className="text-muted-foreground hover:text-foreground">
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="p-6 space-y-4">
|
||||
<div className="rounded-lg border border-border bg-muted/20 px-3 py-2">
|
||||
<div className="text-xs font-medium text-muted-foreground mb-1">{t('accountManager.accountIdentifierLabel')}</div>
|
||||
<code className="text-sm font-mono text-foreground break-all">{editingAccount.identifier}</code>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1.5">{t('accountManager.nameOptional')}</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input-field"
|
||||
placeholder={t('accountManager.namePlaceholder')}
|
||||
value={editAccount.name}
|
||||
onChange={e => setEditAccount({ ...editAccount, name: e.target.value })}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium mb-1.5">{t('accountManager.remarkOptional')}</label>
|
||||
<input
|
||||
type="text"
|
||||
className="input-field"
|
||||
placeholder={t('accountManager.remarkPlaceholder')}
|
||||
value={editAccount.remark}
|
||||
onChange={e => setEditAccount({ ...editAccount, remark: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<button onClick={onClose} className="px-4 py-2 rounded-lg border border-border hover:bg-secondary transition-colors text-sm font-medium">{t('actions.cancel')}</button>
|
||||
<button onClick={onSave} 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 ? t('accountManager.editAccountLoading') : t('accountManager.editAccountAction')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -4,9 +4,12 @@ export function useAccountActions({ apiFetch, t, onMessage, onRefresh, config, f
|
||||
const [showAddKey, setShowAddKey] = useState(false)
|
||||
const [editingKey, setEditingKey] = useState(null)
|
||||
const [showAddAccount, setShowAddAccount] = useState(false)
|
||||
const [showEditAccount, setShowEditAccount] = useState(false)
|
||||
const [editingAccount, setEditingAccount] = useState(null)
|
||||
const [newKey, setNewKey] = useState({ key: '', name: '', remark: '' })
|
||||
const [copiedKey, setCopiedKey] = useState(null)
|
||||
const [newAccount, setNewAccount] = useState({ name: '', remark: '', email: '', mobile: '', password: '' })
|
||||
const [editAccount, setEditAccount] = useState({ name: '', remark: '' })
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [testing, setTesting] = useState({})
|
||||
const [testingAll, setTestingAll] = useState(false)
|
||||
@@ -38,6 +41,42 @@ export function useAccountActions({ apiFetch, t, onMessage, onRefresh, config, f
|
||||
setNewKey({ key: '', name: '', remark: '' })
|
||||
}
|
||||
|
||||
const openAddAccount = () => {
|
||||
setShowEditAccount(false)
|
||||
setEditingAccount(null)
|
||||
setEditAccount({ name: '', remark: '' })
|
||||
setNewAccount({ name: '', remark: '', email: '', mobile: '', password: '' })
|
||||
setShowAddAccount(true)
|
||||
}
|
||||
|
||||
const closeAddAccount = () => {
|
||||
setShowAddAccount(false)
|
||||
setNewAccount({ name: '', remark: '', email: '', mobile: '', password: '' })
|
||||
}
|
||||
|
||||
const openEditAccount = (account) => {
|
||||
const identifier = resolveAccountIdentifier(account)
|
||||
if (!identifier) {
|
||||
onMessage('error', t('accountManager.invalidIdentifier'))
|
||||
return
|
||||
}
|
||||
setShowAddAccount(false)
|
||||
setEditingAccount({
|
||||
identifier,
|
||||
})
|
||||
setEditAccount({
|
||||
name: account?.name || '',
|
||||
remark: account?.remark || '',
|
||||
})
|
||||
setShowEditAccount(true)
|
||||
}
|
||||
|
||||
const closeEditAccount = () => {
|
||||
setShowEditAccount(false)
|
||||
setEditingAccount(null)
|
||||
setEditAccount({ name: '', remark: '' })
|
||||
}
|
||||
|
||||
const addKey = async () => {
|
||||
const isEditing = Boolean(editingKey?.key)
|
||||
if (!isEditing && !newKey.key.trim()) {
|
||||
@@ -104,8 +143,7 @@ export function useAccountActions({ apiFetch, t, onMessage, onRefresh, config, f
|
||||
})
|
||||
if (res.ok) {
|
||||
onMessage('success', t('accountManager.addAccountSuccess'))
|
||||
setNewAccount({ name: '', remark: '', email: '', mobile: '', password: '' })
|
||||
setShowAddAccount(false)
|
||||
closeAddAccount()
|
||||
fetchAccounts(1)
|
||||
onRefresh()
|
||||
} else {
|
||||
@@ -119,6 +157,35 @@ export function useAccountActions({ apiFetch, t, onMessage, onRefresh, config, f
|
||||
}
|
||||
}
|
||||
|
||||
const updateAccount = async () => {
|
||||
const identifier = String(editingAccount?.identifier || '').trim()
|
||||
if (!identifier) {
|
||||
onMessage('error', t('accountManager.invalidIdentifier'))
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await apiFetch(`/admin/accounts/${encodeURIComponent(identifier)}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(editAccount),
|
||||
})
|
||||
if (res.ok) {
|
||||
onMessage('success', t('accountManager.updateAccountSuccess'))
|
||||
closeEditAccount()
|
||||
fetchAccounts()
|
||||
onRefresh()
|
||||
} else {
|
||||
const data = await res.json()
|
||||
onMessage('error', data.detail || t('messages.requestFailed'))
|
||||
}
|
||||
} catch (e) {
|
||||
onMessage('error', t('messages.networkError'))
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const deleteAccount = async (id) => {
|
||||
const identifier = String(id || '').trim()
|
||||
if (!identifier) {
|
||||
@@ -285,7 +352,14 @@ export function useAccountActions({ apiFetch, t, onMessage, onRefresh, config, f
|
||||
closeKeyModal,
|
||||
editingKey,
|
||||
showAddAccount,
|
||||
setShowAddAccount,
|
||||
openAddAccount,
|
||||
closeAddAccount,
|
||||
showEditAccount,
|
||||
editingAccount,
|
||||
editAccount,
|
||||
setEditAccount,
|
||||
openEditAccount,
|
||||
closeEditAccount,
|
||||
newKey,
|
||||
setNewKey,
|
||||
copiedKey,
|
||||
@@ -302,6 +376,7 @@ export function useAccountActions({ apiFetch, t, onMessage, onRefresh, config, f
|
||||
addKey,
|
||||
deleteKey,
|
||||
addAccount,
|
||||
updateAccount,
|
||||
deleteAccount,
|
||||
testAccount,
|
||||
testAllAccounts,
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
"addKeySuccess": "API key added successfully.",
|
||||
"updateKeySuccess": "API key updated successfully.",
|
||||
"addAccountSuccess": "Account added successfully.",
|
||||
"updateAccountSuccess": "Account metadata updated 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?",
|
||||
@@ -111,16 +112,17 @@
|
||||
"accountsUnit": "accounts",
|
||||
"threadsUnit": "threads",
|
||||
"apiKeysTitle": "API Keys",
|
||||
"apiKeysDesc": "Manage the API access key pool",
|
||||
"apiKeysDesc": "Manage the API access key pool. Click the pencil icon on each row to edit name and remark.",
|
||||
"addKey": "Add key",
|
||||
"editKeyTitle": "Edit key",
|
||||
"editAccountTitle": "Edit account",
|
||||
"copied": "Copied",
|
||||
"copyFailed": "Copy failed",
|
||||
"copyKeyTitle": "Copy key",
|
||||
"deleteKeyTitle": "Delete key",
|
||||
"noApiKeys": "No API keys found.",
|
||||
"accountsTitle": "DeepSeek Accounts",
|
||||
"accountsDesc": "Manage the DeepSeek account pool",
|
||||
"accountsDesc": "Manage the DeepSeek account pool and edit name/remark.",
|
||||
"testAll": "Refresh all tokens",
|
||||
"addAccount": "Add account",
|
||||
"testingAllAccounts": "Refreshing tokens for all accounts...",
|
||||
@@ -131,6 +133,7 @@
|
||||
"noAccounts": "No accounts found.",
|
||||
"modalAddKeyTitle": "Add API key",
|
||||
"modalEditKeyTitle": "Edit API key",
|
||||
"modalEditAccountTitle": "Edit account details",
|
||||
"newKeyLabel": "New key value",
|
||||
"newKeyPlaceholder": "Enter a custom API key",
|
||||
"keyLabel": "Key value",
|
||||
@@ -142,6 +145,10 @@
|
||||
"addKeyAction": "Add key",
|
||||
"editKeyLoading": "Saving...",
|
||||
"editKeyAction": "Save changes",
|
||||
"editAccountHint": "Only name and remark can be changed here. The account identifier stays the same.",
|
||||
"accountIdentifierLabel": "Account identifier",
|
||||
"editAccountLoading": "Saving...",
|
||||
"editAccountAction": "Save changes",
|
||||
"modalAddAccountTitle": "Add DeepSeek account",
|
||||
"nameOptional": "Name (optional)",
|
||||
"namePlaceholder": "e.g. Primary Account A",
|
||||
|
||||
@@ -98,6 +98,7 @@
|
||||
"addKeySuccess": "API 密钥添加成功",
|
||||
"updateKeySuccess": "API 密钥更新成功",
|
||||
"addAccountSuccess": "账号添加成功",
|
||||
"updateAccountSuccess": "账号信息更新成功",
|
||||
"requiredFields": "需要填写密码以及邮箱或手机号",
|
||||
"deleteKeyConfirm": "确定要删除此 API 密钥吗?",
|
||||
"deleteAccountConfirm": "确定要删除此账号吗?",
|
||||
@@ -111,16 +112,17 @@
|
||||
"accountsUnit": "个账号",
|
||||
"threadsUnit": "线程",
|
||||
"apiKeysTitle": "API 密钥",
|
||||
"apiKeysDesc": "管理 API 访问密钥池",
|
||||
"apiKeysDesc": "管理 API 访问密钥池,点每行右侧铅笔可修改名称和备注",
|
||||
"addKey": "添加密钥",
|
||||
"editKeyTitle": "编辑密钥",
|
||||
"editAccountTitle": "编辑账号",
|
||||
"copied": "已复制",
|
||||
"copyFailed": "复制失败",
|
||||
"copyKeyTitle": "复制密钥",
|
||||
"deleteKeyTitle": "删除密钥",
|
||||
"noApiKeys": "未找到 API 密钥",
|
||||
"accountsTitle": "DeepSeek 账号",
|
||||
"accountsDesc": "管理 DeepSeek 账号池",
|
||||
"accountsDesc": "管理 DeepSeek 账号池,支持修改名称和备注",
|
||||
"testAll": "刷新全部 Token",
|
||||
"addAccount": "添加账号",
|
||||
"testingAllAccounts": "正在刷新所有账号 Token...",
|
||||
@@ -131,6 +133,7 @@
|
||||
"noAccounts": "未找到任何账号",
|
||||
"modalAddKeyTitle": "添加 API 密钥",
|
||||
"modalEditKeyTitle": "编辑 API 密钥",
|
||||
"modalEditAccountTitle": "编辑账号信息",
|
||||
"newKeyLabel": "新密钥值",
|
||||
"newKeyPlaceholder": "输入自定义 API 密钥",
|
||||
"keyLabel": "密钥值",
|
||||
@@ -142,6 +145,10 @@
|
||||
"addKeyAction": "添加密钥",
|
||||
"editKeyLoading": "保存中...",
|
||||
"editKeyAction": "保存修改",
|
||||
"editAccountHint": "这里只能修改名称和备注,账号标识保持不变。",
|
||||
"accountIdentifierLabel": "账号标识",
|
||||
"editAccountLoading": "保存中...",
|
||||
"editAccountAction": "保存修改",
|
||||
"modalAddAccountTitle": "添加 DeepSeek 账号",
|
||||
"nameOptional": "名称(可选)",
|
||||
"namePlaceholder": "例如:主账号 A",
|
||||
|
||||
Reference in New Issue
Block a user