Initial MVP for QA asset backend

This commit is contained in:
2026-07-05 17:44:15 +08:00
commit 95b5e09fd4
71 changed files with 6546 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
"use client";
import Link from "next/link";
import { useEffect, useState } from "react";
import { AlertTriangle, CheckCircle2, Database, FileQuestion, Library, Play, Rows3, Timer } from "lucide-react";
import { api, formatDateTime } from "@/lib/api";
import type { DashboardSummary, TaskRun } from "@/lib/types";
import StatCard from "@/components/StatCard";
import StatusBadge from "@/components/StatusBadge";
export default function DashboardPage() {
const [summary, setSummary] = useState<DashboardSummary | null>(null);
const [loading, setLoading] = useState(true);
const [running, setRunning] = useState(false);
async function load() {
setLoading(true);
const data = await api.get<DashboardSummary>("/dashboard/summary");
setSummary(data);
setLoading(false);
}
async function runTask() {
setRunning(true);
await api.post<TaskRun>("/tasks/run-now", {});
await load();
setRunning(false);
}
useEffect(() => {
load().catch(() => setLoading(false));
}, []);
if (loading || !summary) {
return <div className="text-sm text-slate-500">...</div>;
}
const actions = [
{ href: "/review", label: "去审核" },
{ href: "/high-risk", label: "查看高风险" },
{ href: "/standard-qa", label: "查看标准问答库" },
{ href: "/logs", label: "查看运行日志" }
];
return (
<div className="space-y-6">
<div className="flex flex-wrap items-center justify-between gap-3">
<div>
<h1 className="text-2xl font-semibold text-ink"></h1>
<p className="mt-1 text-sm text-slate-500">{summary.last_task_time ? formatDateTime(summary.last_task_time) : "-"}</p>
</div>
<button
onClick={runTask}
disabled={running}
className="inline-flex h-10 items-center gap-2 rounded-md bg-jade px-4 text-sm font-medium text-white hover:bg-teal-800 disabled:opacity-60"
>
<Play className="h-4 w-4" aria-hidden />
{running ? "处理中" : "手动触发处理任务"}
</button>
</div>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<StatCard title="昨晚处理答疑场次数" value={summary.processed_sessions} icon={<Timer className="h-5 w-5" />} />
<StatCard title="已拆解问答总数" value={summary.pending_review_count + summary.approved_qa_count} icon={<Rows3 className="h-5 w-5" />} tone="indigo" />
<StatCard title="待审核问答数" value={summary.pending_review_count} icon={<FileQuestion className="h-5 w-5" />} tone="amber" />
<StatCard title="高风险问答数" value={summary.high_risk_count} icon={<AlertTriangle className="h-5 w-5" />} tone="rose" />
<StatCard title="已审核问答数" value={summary.approved_qa_count} icon={<CheckCircle2 className="h-5 w-5" />} />
<StatCard title="标准问答数" value={summary.standard_qa_count} icon={<Library className="h-5 w-5" />} tone="indigo" />
<StatCard title="可被智能体调用问答数" value={summary.callable_qa_count} icon={<Database className="h-5 w-5" />} />
<div className="rounded-md border border-line bg-white p-4">
<div className="text-sm text-slate-500"></div>
<div className="mt-3">
{summary.last_task_status ? <StatusBadge status={summary.last_task_status} /> : <span className="text-sm text-slate-400"></span>}
</div>
{summary.last_task_error ? <p className="mt-3 text-sm text-rose-700">{summary.last_task_error}</p> : null}
</div>
</div>
<div className="flex flex-wrap gap-2">
{actions.map((action) => (
<Link
key={action.href}
href={action.href}
className="inline-flex h-10 items-center rounded-md border border-line bg-white px-4 text-sm font-medium text-ink hover:bg-slate-50"
>
{action.label}
</Link>
))}
</div>
</div>
);
}