139 lines
5.6 KiB
TypeScript
139 lines
5.6 KiB
TypeScript
"use client";
|
||
|
||
import { Check, EyeOff, FileX, Pencil, ShieldAlert, Sparkles, X } from "lucide-react";
|
||
import type { ReactNode } from "react";
|
||
import { api } from "@/lib/api";
|
||
import type { RawQAItem } from "@/lib/types";
|
||
import RiskBadge from "./RiskBadge";
|
||
import StatusBadge from "./StatusBadge";
|
||
|
||
function Field({ label, children }: { label: string; children: ReactNode }) {
|
||
return (
|
||
<div>
|
||
<div className="mb-1 text-xs font-medium text-slate-500">{label}</div>
|
||
<div className="whitespace-pre-wrap text-sm leading-6 text-ink">{children || "-"}</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default function QAReviewCard({ item, onChanged }: { item: RawQAItem; onChanged: () => void }) {
|
||
async function action(path: string) {
|
||
await api.post(`/raw-qa/${item.id}/${path}`, {});
|
||
onChanged();
|
||
}
|
||
|
||
async function editAndApprove() {
|
||
const standardQuestion = window.prompt("标准问题", item.suggested_standard_question ?? item.normalized_question ?? "");
|
||
if (standardQuestion === null) return;
|
||
const standardAnswer = window.prompt("标准回答", item.suggested_standard_answer ?? item.normalized_answer ?? "");
|
||
if (standardAnswer === null) return;
|
||
await api.patch(`/raw-qa/${item.id}`, {
|
||
suggested_standard_question: standardQuestion,
|
||
suggested_standard_answer: standardAnswer
|
||
});
|
||
await action("approve");
|
||
}
|
||
|
||
return (
|
||
<article className="rounded-md border border-line bg-white p-5">
|
||
<div className="mb-4 flex flex-wrap items-center justify-between gap-3">
|
||
<div className="flex flex-wrap items-center gap-2">
|
||
<span className="text-sm font-semibold text-ink">{item.qa_code}</span>
|
||
<RiskBadge level={item.risk_level} />
|
||
<StatusBadge status={item.review_status} />
|
||
{item.need_desensitization ? (
|
||
<span className="rounded-md border border-amber-200 bg-amber-50 px-2 py-1 text-xs font-medium text-amber-700">
|
||
需脱敏
|
||
</span>
|
||
) : null}
|
||
</div>
|
||
<div className="text-xs text-slate-500">场次 #{item.session_id}</div>
|
||
</div>
|
||
|
||
<div className="grid gap-5 lg:grid-cols-2">
|
||
<Field label="原始问题">{item.raw_question}</Field>
|
||
<Field label="问题整理版">{item.normalized_question}</Field>
|
||
<Field label="原始回答">{item.raw_answer}</Field>
|
||
<Field label="回答整理版">{item.normalized_answer}</Field>
|
||
<Field label="AI 建议标准问题">{item.suggested_standard_question}</Field>
|
||
<Field label="AI 建议标准回答">{item.suggested_standard_answer}</Field>
|
||
</div>
|
||
|
||
<div className="mt-5 grid gap-3 border-t border-line pt-4 text-sm md:grid-cols-2 xl:grid-cols-4">
|
||
<div>
|
||
<span className="text-slate-500">主题:</span>
|
||
{item.primary_topic}
|
||
</div>
|
||
<div>
|
||
<span className="text-slate-500">阶段:</span>
|
||
{item.course_stage}
|
||
</div>
|
||
<div>
|
||
<span className="text-slate-500">标签:</span>
|
||
{item.problem_tags.join("、") || "-"}
|
||
</div>
|
||
<div>
|
||
<span className="text-slate-500">来源时间:</span>
|
||
{item.source_timestamp || "-"}
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mt-4 rounded-md border border-amber-200 bg-amber-50 p-3 text-sm text-amber-900">
|
||
{item.risk_notes || "无风险说明"}
|
||
</div>
|
||
|
||
<div className="mt-5 flex flex-wrap gap-2">
|
||
<button
|
||
onClick={() => action("approve")}
|
||
className="inline-flex h-9 items-center gap-2 rounded-md bg-jade px-3 text-sm font-medium text-white hover:bg-teal-800"
|
||
>
|
||
<Check className="h-4 w-4" aria-hidden />
|
||
通过入库
|
||
</button>
|
||
<button
|
||
onClick={editAndApprove}
|
||
className="inline-flex h-9 items-center gap-2 rounded-md border border-line bg-white px-3 text-sm font-medium text-ink hover:bg-slate-50"
|
||
>
|
||
<Pencil className="h-4 w-4" aria-hidden />
|
||
修改后通过
|
||
</button>
|
||
<button
|
||
onClick={() => action("revise")}
|
||
className="inline-flex h-9 items-center gap-2 rounded-md border border-line bg-white px-3 text-sm font-medium text-ink hover:bg-slate-50"
|
||
>
|
||
<Sparkles className="h-4 w-4" aria-hidden />
|
||
需修改
|
||
</button>
|
||
<button
|
||
onClick={() => action("reject")}
|
||
className="inline-flex h-9 items-center gap-2 rounded-md border border-line bg-white px-3 text-sm font-medium text-ink hover:bg-slate-50"
|
||
>
|
||
<FileX className="h-4 w-4" aria-hidden />
|
||
不入库
|
||
</button>
|
||
<button
|
||
onClick={() => action("forbid")}
|
||
className="inline-flex h-9 items-center gap-2 rounded-md border border-rose-200 bg-white px-3 text-sm font-medium text-rose-700 hover:bg-rose-50"
|
||
>
|
||
<X className="h-4 w-4" aria-hidden />
|
||
禁止使用
|
||
</button>
|
||
<button
|
||
onClick={() => action("mark-high-risk")}
|
||
className="inline-flex h-9 items-center gap-2 rounded-md border border-orange-200 bg-white px-3 text-sm font-medium text-orange-700 hover:bg-orange-50"
|
||
>
|
||
<ShieldAlert className="h-4 w-4" aria-hidden />
|
||
标记高风险
|
||
</button>
|
||
<button
|
||
onClick={() => action("mark-desensitization")}
|
||
className="inline-flex h-9 items-center gap-2 rounded-md border border-amber-200 bg-white px-3 text-sm font-medium text-amber-700 hover:bg-amber-50"
|
||
>
|
||
<EyeOff className="h-4 w-4" aria-hidden />
|
||
标记需脱敏
|
||
</button>
|
||
</div>
|
||
</article>
|
||
);
|
||
}
|