feat: Add token preview and batch progress to admin account management, and expand API tests for admin endpoints.

This commit is contained in:
CJACK
2026-02-01 03:31:01 +08:00
parent a6d89876ba
commit 23bd4970d9
5 changed files with 239 additions and 99 deletions

View File

@@ -62,11 +62,13 @@ async def get_config(_: bool = Depends(verify_admin)):
"claude_model_mapping": CONFIG.get("claude_model_mapping", {}),
}
for acc in CONFIG.get("accounts", []):
token = acc.get("token", "")
safe_acc = {
"email": acc.get("email", ""),
"mobile": acc.get("mobile", ""),
"has_password": bool(acc.get("password")),
"has_token": bool(acc.get("token")),
"has_token": bool(token),
"token_preview": token[:12] + "..." if len(token) > 12 else "" if token else "",
}
safe_config["accounts"].append(safe_acc)
return JSONResponse(content=safe_config)
@@ -288,14 +290,9 @@ async def validate_all_accounts(_: bool = Depends(verify_admin)):
# 账号 API 测试(实际发送请求)
# ----------------------------------------------------------------------
async def test_account_api(account: dict, model: str = "deepseek-chat", message: str = "") -> dict:
"""测试单个账号的 API 调用能力
如果提供了 message会发送实际请求并返回 AI 回复;
否则只测试创建会话(快速测试)。
"""
"""测试单个账号的 API 调用能力(通过创建会话验证)"""
from curl_cffi import requests as cffi_requests
from core.deepseek import DEEPSEEK_CREATE_SESSION_URL, DEEPSEEK_CHAT_COMPLETION_URL, BASE_HEADERS
from core.pow import get_pow_response_direct
from core.deepseek import DEEPSEEK_CREATE_SESSION_URL, BASE_HEADERS
acc_id = get_account_identifier(account)
result = {
@@ -304,7 +301,6 @@ async def test_account_api(account: dict, model: str = "deepseek-chat", message:
"response_time": 0,
"message": "",
"model": model,
"reply": "", # AI 回复内容
}
import time
@@ -323,7 +319,7 @@ async def test_account_api(account: dict, model: str = "deepseek-chat", message:
headers = {**BASE_HEADERS, "authorization": f"Bearer {token}"}
# 1. 创建会话
# 创建会话来测试 API 可用性
session_resp = cffi_requests.post(
DEEPSEEK_CREATE_SESSION_URL,
headers=headers,
@@ -343,70 +339,8 @@ async def test_account_api(account: dict, model: str = "deepseek-chat", message:
account["token"] = ""
return result
session_id = session_data["data"]["biz_data"]["id"]
# 如果没有消息,只测试会话创建
if not message:
result["success"] = True
result["message"] = "API 测试成功"
result["response_time"] = round((time.time() - start_time) * 1000)
return result
# 2. 发送实际请求
from core.pow import get_pow_response_direct
pow_resp = get_pow_response_direct(headers)
if not pow_resp:
result["message"] = "获取 PoW 失败"
return result
chat_headers = {**headers, "x-ds-pow-response": pow_resp}
thinking_enabled = "reasoner" in model.lower()
search_enabled = "search" in model.lower()
payload = {
"chat_session_id": session_id,
"parent_message_id": None,
"prompt": message,
"ref_file_ids": [],
"thinking_enabled": thinking_enabled,
"search_enabled": search_enabled,
}
chat_resp = cffi_requests.post(
DEEPSEEK_CHAT_COMPLETION_URL,
headers=chat_headers,
json=payload,
impersonate="safari15_3",
timeout=60,
stream=True,
)
if chat_resp.status_code != 200:
result["message"] = f"对话请求失败: HTTP {chat_resp.status_code}"
return result
# 解析流式响应获取回复
reply_text = ""
for line in chat_resp.iter_lines():
try:
line_str = line.decode("utf-8")
if line_str.startswith("data:"):
data_str = line_str[5:].strip()
if data_str == "[DONE]":
break
import json
chunk = json.loads(data_str)
choices = chunk.get("choices", [])
if choices:
delta = choices[0].get("delta", {})
if delta.get("type") == "text":
reply_text += delta.get("content", "")
except:
pass
result["success"] = True
result["message"] = "API 测试成功"
result["reply"] = reply_text[:500] # 限制长度
result["response_time"] = round((time.time() - start_time) * 1000)
except Exception as e:

View File

@@ -558,6 +558,80 @@ class TestRunner:
"details": {"input_length": len(long_text)}
}
# =====================================================================
# 管理 API 测试
# =====================================================================
def test_admin_config(self) -> dict:
"""测试管理配置 API"""
resp = requests.get(
f"{self.endpoint}/admin/config",
timeout=10
)
if resp.status_code != 200:
return {"success": False, "message": f"状态码: {resp.status_code}"}
data = resp.json()
# 验证返回结构
if "accounts" not in data:
return {"success": False, "message": "响应缺少 accounts 字段"}
# 验证 token_preview 字段存在
accounts = data.get("accounts", [])
if accounts:
first_acc = accounts[0]
if "token_preview" not in first_acc:
return {"success": False, "message": "响应缺少 token_preview 字段"}
return {
"success": True,
"message": f"获取配置成功,{len(accounts)} 个账号",
"details": {"account_count": len(accounts)}
}
def test_admin_account_test(self) -> dict:
"""测试单账号 API 测试端点"""
# 先获取配置以获取账号
config_resp = requests.get(f"{self.endpoint}/admin/config", timeout=10)
if config_resp.status_code != 200:
return {"success": False, "message": "获取配置失败"}
accounts = config_resp.json().get("accounts", [])
if not accounts:
return {"success": False, "message": "没有可测试的账号"}
# 测试第一个账号
first_acc = accounts[0]
identifier = first_acc.get("email") or first_acc.get("mobile")
resp = requests.post(
f"{self.endpoint}/admin/accounts/test",
json={"identifier": identifier},
timeout=30
)
if resp.status_code != 200:
return {"success": False, "message": f"状态码: {resp.status_code}"}
data = resp.json()
# 验证返回结构
required_fields = ["account", "success", "response_time", "message"]
for field in required_fields:
if field not in data:
return {"success": False, "message": f"响应缺少 {field} 字段"}
if not data["success"]:
return {"success": False, "message": f"账号测试失败: {data['message']}"}
return {
"success": True,
"message": f"账号 {identifier} 测试成功 ({data['response_time']}ms)",
"details": {"response_time": data["response_time"]}
}
# =====================================================================
# 运行测试
# =====================================================================
@@ -599,6 +673,10 @@ class TestRunner:
self.run_test("多轮对话", self.test_multi_turn_conversation)
self.run_test("长输入处理", self.test_long_input)
# 管理 API 测试
self.run_test("管理配置 API", self.test_admin_config)
self.run_test("账号测试 API", self.test_admin_account_test)
# 输出测试报告
self.print_report()

View File

@@ -1 +1 @@
1.3.4
1.6.11

View File

@@ -10,6 +10,7 @@ export default function AccountManager({ config, onRefresh, onMessage }) {
const [validatingAll, setValidatingAll] = useState(false)
const [testing, setTesting] = useState({}) // 单个账号测试状态
const [testingAll, setTestingAll] = useState(false)
const [batchProgress, setBatchProgress] = useState({ current: 0, total: 0, results: [] })
const [queueStatus, setQueueStatus] = useState(null)
// 获取队列状态
@@ -137,20 +138,41 @@ export default function AccountManager({ config, onRefresh, onMessage }) {
}
}
// 批量验证所有账号
// 批量验证所有账号(带进度)
const validateAllAccounts = async () => {
if (!confirm('确定要验证所有账号?这可能需要一些时间。')) return
if (!confirm('确定要验证所有账号?')) return
const accounts = config.accounts || []
if (accounts.length === 0) return
setValidatingAll(true)
try {
const res = await fetch('/admin/accounts/validate-all', { method: 'POST' })
const data = await res.json()
onMessage('success', `验证完成: ${data.valid}/${data.total} 个账号有效`)
onRefresh()
} catch (e) {
onMessage('error', '批量验证失败: ' + e.message)
} finally {
setValidatingAll(false)
setBatchProgress({ current: 0, total: accounts.length, results: [] })
let validCount = 0
const results = []
for (let i = 0; i < accounts.length; i++) {
const acc = accounts[i]
const id = acc.email || acc.mobile
try {
const res = await fetch('/admin/accounts/validate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ identifier: id }),
})
const data = await res.json()
results.push({ id, success: data.valid, message: data.message })
if (data.valid) validCount++
} catch (e) {
results.push({ id, success: false, message: e.message })
}
setBatchProgress({ current: i + 1, total: accounts.length, results: [...results] })
}
onMessage('success', `验证完成: ${validCount}/${accounts.length} 个账号有效`)
onRefresh()
setValidatingAll(false)
}
// 测试单个账号 API
@@ -176,24 +198,41 @@ export default function AccountManager({ config, onRefresh, onMessage }) {
}
}
// 批量测试所有账号 API
// 批量测试所有账号 API(带进度)
const testAllAccounts = async () => {
if (!confirm('确定要测试所有账号的 API这可能需要较长时间。')) return
if (!confirm('确定要测试所有账号的 API')) return
const accounts = config.accounts || []
if (accounts.length === 0) return
setTestingAll(true)
try {
const res = await fetch('/admin/accounts/test-all', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({}),
})
const data = await res.json()
onMessage('success', `API 测试完成: ${data.success}/${data.total} 个账号可用`)
onRefresh()
} catch (e) {
onMessage('error', '批量 API 测试失败: ' + e.message)
} finally {
setTestingAll(false)
setBatchProgress({ current: 0, total: accounts.length, results: [] })
let successCount = 0
const results = []
for (let i = 0; i < accounts.length; i++) {
const acc = accounts[i]
const id = acc.email || acc.mobile
try {
const res = await fetch('/admin/accounts/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ identifier: id }),
})
const data = await res.json()
results.push({ id, success: data.success, message: data.message, time: data.response_time })
if (data.success) successCount++
} catch (e) {
results.push({ id, success: false, message: e.message })
}
setBatchProgress({ current: i + 1, total: accounts.length, results: [...results] })
}
onMessage('success', `API 测试完成: ${successCount}/${accounts.length} 个账号可用`)
onRefresh()
setTestingAll(false)
}
return (
@@ -267,6 +306,31 @@ export default function AccountManager({ config, onRefresh, onMessage }) {
</div>
</div>
{/* 批量操作进度条 */}
{(testingAll || validatingAll) && batchProgress.total > 0 && (
<div className="batch-progress">
<div className="progress-header">
<span>{testingAll ? '🧪 批量测试中...' : '✅ 批量验证中...'}</span>
<span>{batchProgress.current}/{batchProgress.total}</span>
</div>
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${(batchProgress.current / batchProgress.total) * 100}%` }}
/>
</div>
{batchProgress.results.length > 0 && (
<div className="progress-results">
{batchProgress.results.map((r, i) => (
<div key={i} className={`progress-result ${r.success ? 'success' : 'failed'}`}>
{r.success ? '✓' : '✗'} {r.id} {r.time ? `(${r.time}ms)` : ''}
</div>
))}
</div>
)}
</div>
)}
{config.accounts?.length > 0 ? (
<div className="list">
{config.accounts.map((acc, i) => {
@@ -278,6 +342,11 @@ export default function AccountManager({ config, onRefresh, onMessage }) {
<span className={`badge ${acc.has_token ? 'badge-success' : 'badge-warning'}`}>
{acc.has_token ? '已登录' : '未登录'}
</span>
{acc.token_preview && (
<span className="token-preview" title="Token 预览">
🔑 {acc.token_preview}
</span>
)}
</div>
<div className="btn-group-inline">
<button

View File

@@ -446,6 +446,65 @@ textarea.form-input {
font-size: 0.8rem;
}
/* Token Preview */
.token-preview {
font-size: 0.75rem;
color: var(--text-secondary);
font-family: 'Monaco', 'Menlo', monospace;
background: var(--bg-tertiary);
padding: 0.2rem 0.5rem;
border-radius: var(--radius);
margin-left: 0.5rem;
}
/* Batch Progress */
.batch-progress {
margin: 1rem 0;
padding: 1rem;
background: var(--bg-tertiary);
border-radius: var(--radius);
}
.progress-header {
display: flex;
justify-content: space-between;
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.progress-bar {
height: 8px;
background: var(--bg-secondary);
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--primary), var(--primary-hover));
transition: width 0.3s ease;
}
.progress-results {
margin-top: 0.75rem;
max-height: 150px;
overflow-y: auto;
font-size: 0.85rem;
}
.progress-result {
padding: 0.25rem 0;
font-family: 'Monaco', 'Menlo', monospace;
}
.progress-result.success {
color: var(--success);
}
.progress-result.failed {
color: var(--error);
}
@media (max-width: 640px) {
.app {
padding: 1rem;