"use client"; import { useEffect, useState } from "react"; import { getUsageSummary, listAuditLogs, listTtsTasks } from "@/lib/admin"; import { BrandIcon } from "@/components/brand-icon"; import { StatusCard } from "@/components/ui"; type Task = { id: string; user_id: string; user_email: string; status: string; text_length: number; provider_voice_id: string; error_code: string | null; attempt_count: number; reserved_amount: number; created_at: string; started_at: string | null; finished_at: string | null; duration_ms: number | null; settlement: string; reserved: number; used: number; }; type Audit = { id: string; actor_email: string | null; action: string; target_type: string; target_id: string; before_value: Record | null; after_value: Record | null; reason: string; created_at: string; }; type Summary = { tasks: { total: number; by_status: Record; failed: number }; quota: { used: number; reserved: number; adjustment: number }; ledger_total: number }; const statusLabel: Record = { queued: "排队", running: "进行中", succeeded: "成功", failed: "失败" }; const settleLabel: Record = { consumed: "已消费", released: "已释放", pending: "待结算" }; const actionLabel: Record = { user_status: "用户状态变更", membership: "授予/变更会员", membership_revoke: "撤销会员", quota_adjustment: "额度调整", }; export function TtsAuditPanel() { const [tab, setTab] = useState<"tasks" | "audit">("tasks"); const [status, setStatus] = useState(""); const [taskPage, setTaskPage] = useState(1); const [tasks, setTasks] = useState([]); const [taskTotal, setTaskTotal] = useState(0); const [taskPages, setTaskPages] = useState(1); const [auditPage, setAuditPage] = useState(1); const [audits, setAudits] = useState([]); const [auditTotal, setAuditTotal] = useState(0); const [auditPages, setAuditPages] = useState(1); const [summary, setSummary] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); async function loadTasks(p = taskPage) { setLoading(true); setError(""); try { const res = await listTtsTasks({ status, page: p, limit: 20 }); setTasks(res.items as unknown as Task[]); setTaskTotal(res.total); setTaskPages(res.pages); setTaskPage(res.page); } catch (e) { setError(e instanceof Error ? e.message : "加载失败"); } finally { setLoading(false); } } async function loadAudit(p = auditPage) { setLoading(true); setError(""); try { const res = await listAuditLogs({ page: p, limit: 30 }); setAudits(res.items as unknown as Audit[]); setAuditTotal(res.total); setAuditPages(res.pages); setAuditPage(res.page); } catch (e) { setError(e instanceof Error ? e.message : "加载失败"); } finally { setLoading(false); } } useEffect(() => { void loadTasks(1); void getUsageSummary().then(setSummary).catch(() => undefined); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); function switchTab(next: "tasks" | "audit") { setTab(next); if (next === "tasks") void loadTasks(1); else void loadAudit(1); } return (
{summary ? (
) : null}
{error ?
{error}
: null} {tab === "tasks" ? (
{loading ? ( ) : tasks.length === 0 ? ( ) : ( tasks.map((task) => ( )) )}
用户 状态 计量 耗时 结算 错误
加载中…
无匹配任务

{task.user_email}

{new Date(task.created_at).toLocaleString()}

{statusLabel[task.status] ?? task.status} {task.text_length} 字符 {task.duration_ms != null ? `${(task.duration_ms / 1000).toFixed(1)}s` : "—"} {settleLabel[task.settlement] ?? task.settlement} {task.error_code ?? "—"} {task.id.slice(0, 8)}
第 {taskPage} / {taskPages} 页
) : (

审计记录默认不含完整用户输入文本,也不显示任何密钥或令牌。账号禁用、会员变更、VIP 撤销和额度调整均记录操作者、前后值与原因。

{audits.length === 0 ? ( ) : ( audits.map((a) => ( )) )}
操作者 动作 目标 原因 时间
暂无审计记录
{a.actor_email ?? "—"} {actionLabel[a.action] ?? a.action} {a.target_type} · {String(a.target_id).slice(0, 8)} {a.reason} {new Date(a.created_at).toLocaleString()}
第 {auditPage} / {auditPages} 页
)}
); }