Initial MVP for QA asset backend
This commit is contained in:
13
hy_qa_asset_backend/frontend/components/Layout.tsx
Normal file
13
hy_qa_asset_backend/frontend/components/Layout.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { ReactNode } from "react";
|
||||
import Sidebar from "./Sidebar";
|
||||
|
||||
export default function Layout({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<div className="min-h-screen bg-[#f5f7fa]">
|
||||
<Sidebar />
|
||||
<main className="min-h-screen px-4 py-5 md:ml-64 md:px-8 md:py-7">{children}</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
138
hy_qa_asset_backend/frontend/components/QAReviewCard.tsx
Normal file
138
hy_qa_asset_backend/frontend/components/QAReviewCard.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
20
hy_qa_asset_backend/frontend/components/RiskBadge.tsx
Normal file
20
hy_qa_asset_backend/frontend/components/RiskBadge.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
export default function RiskBadge({ level }: { level: string }) {
|
||||
const styles: Record<string, string> = {
|
||||
low: "border-emerald-200 bg-emerald-50 text-emerald-700",
|
||||
medium: "border-amber-200 bg-amber-50 text-amber-700",
|
||||
high: "border-orange-200 bg-orange-50 text-orange-700",
|
||||
forbidden: "border-rose-200 bg-rose-50 text-rose-700"
|
||||
};
|
||||
const labels: Record<string, string> = {
|
||||
low: "低风险",
|
||||
medium: "中风险",
|
||||
high: "高风险",
|
||||
forbidden: "禁止"
|
||||
};
|
||||
return (
|
||||
<span className={`inline-flex rounded-md border px-2 py-1 text-xs font-medium ${styles[level] ?? styles.medium}`}>
|
||||
{labels[level] ?? level}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
91
hy_qa_asset_backend/frontend/components/SessionTable.tsx
Normal file
91
hy_qa_asset_backend/frontend/components/SessionTable.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { FileText, Play, RotateCw } from "lucide-react";
|
||||
import { api, formatDateTime } from "@/lib/api";
|
||||
import type { FeishuSession, TaskRun } from "@/lib/types";
|
||||
import StatusBadge from "./StatusBadge";
|
||||
|
||||
export default function SessionTable({ sessions, onChanged }: { sessions: FeishuSession[]; onChanged: () => void }) {
|
||||
async function run(id: number, reprocess = false) {
|
||||
await api.post<TaskRun>(`/sessions/${id}/${reprocess ? "reprocess" : "process"}`, {});
|
||||
onChanged();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="table-wrap rounded-md border border-line bg-white">
|
||||
<table className="min-w-[1100px] w-full border-collapse text-left text-sm">
|
||||
<thead className="bg-panel text-xs uppercase text-slate-500">
|
||||
<tr>
|
||||
<th className="px-4 py-3">日期</th>
|
||||
<th className="px-4 py-3">标题</th>
|
||||
<th className="px-4 py-3">老师</th>
|
||||
<th className="px-4 py-3">飞书资料</th>
|
||||
<th className="px-4 py-3">状态</th>
|
||||
<th className="px-4 py-3">问答</th>
|
||||
<th className="px-4 py-3">待审</th>
|
||||
<th className="px-4 py-3">入库</th>
|
||||
<th className="px-4 py-3">失败原因</th>
|
||||
<th className="px-4 py-3">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sessions.map((session) => (
|
||||
<tr key={session.id} className="border-t border-line align-top">
|
||||
<td className="px-4 py-3">{session.date ?? "-"}</td>
|
||||
<td className="px-4 py-3 font-medium text-ink">{session.title}</td>
|
||||
<td className="px-4 py-3">{session.teachers ?? "-"}</td>
|
||||
<td className="px-4 py-3">
|
||||
{session.feishu_doc_url ? (
|
||||
<span className="text-jade">{session.feishu_doc_url}</span>
|
||||
) : (
|
||||
<span className="text-slate-400">-</span>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-3">
|
||||
<StatusBadge status={session.process_status} />
|
||||
</td>
|
||||
<td className="px-4 py-3">{session.qa_count}</td>
|
||||
<td className="px-4 py-3">{session.pending_review_count}</td>
|
||||
<td className="px-4 py-3">{session.standard_qa_count}</td>
|
||||
<td className="max-w-xs px-4 py-3 text-rose-700">{session.failed_reason ?? "-"}</td>
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<Link
|
||||
href={`/sessions/${session.id}`}
|
||||
className="inline-flex h-8 items-center gap-1 rounded-md border border-line px-2 text-xs hover:bg-slate-50"
|
||||
>
|
||||
<FileText className="h-3.5 w-3.5" aria-hidden />
|
||||
详情
|
||||
</Link>
|
||||
<button
|
||||
onClick={() => run(session.id)}
|
||||
className="inline-flex h-8 items-center gap-1 rounded-md border border-line px-2 text-xs hover:bg-slate-50"
|
||||
>
|
||||
<Play className="h-3.5 w-3.5" aria-hidden />
|
||||
处理
|
||||
</button>
|
||||
<button
|
||||
onClick={() => run(session.id, true)}
|
||||
className="inline-flex h-8 items-center gap-1 rounded-md border border-line px-2 text-xs hover:bg-slate-50"
|
||||
>
|
||||
<RotateCw className="h-3.5 w-3.5" aria-hidden />
|
||||
重跑
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{sessions.length === 0 ? (
|
||||
<tr>
|
||||
<td className="px-4 py-8 text-center text-slate-500" colSpan={10}>
|
||||
暂无场次
|
||||
</td>
|
||||
</tr>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
60
hy_qa_asset_backend/frontend/components/Sidebar.tsx
Normal file
60
hy_qa_asset_backend/frontend/components/Sidebar.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
import {
|
||||
BookOpenCheck,
|
||||
CalendarDays,
|
||||
ClipboardCheck,
|
||||
DatabaseZap,
|
||||
Gauge,
|
||||
Library,
|
||||
ListChecks,
|
||||
Settings,
|
||||
ShieldAlert
|
||||
} from "lucide-react";
|
||||
|
||||
const navItems = [
|
||||
{ href: "/dashboard", label: "仪表盘", icon: Gauge },
|
||||
{ href: "/sessions", label: "答疑场次", icon: CalendarDays },
|
||||
{ href: "/review", label: "待审核", icon: ClipboardCheck },
|
||||
{ href: "/high-risk", label: "高风险", icon: ShieldAlert },
|
||||
{ href: "/standard-qa", label: "标准库", icon: Library },
|
||||
{ href: "/callable-qa", label: "可调用库", icon: DatabaseZap },
|
||||
{ href: "/logs", label: "运行日志", icon: ListChecks },
|
||||
{ href: "/settings", label: "系统设置", icon: Settings }
|
||||
];
|
||||
|
||||
export default function Sidebar() {
|
||||
const pathname = usePathname();
|
||||
|
||||
return (
|
||||
<aside className="z-20 flex w-full flex-col border-b border-line bg-white md:fixed md:inset-y-0 md:left-0 md:w-64 md:border-b-0 md:border-r">
|
||||
<div className="flex h-16 shrink-0 items-center gap-3 border-b border-line px-5">
|
||||
<BookOpenCheck className="h-6 w-6 text-jade" aria-hidden />
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-ink">大本营答疑资产</div>
|
||||
<div className="text-xs text-slate-500">后台系统 MVP</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav className="flex gap-1 overflow-x-auto px-3 py-3 md:block md:flex-1 md:space-y-1 md:overflow-visible md:py-4">
|
||||
{navItems.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const active = pathname === item.href || pathname.startsWith(`${item.href}/`);
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={`flex h-10 shrink-0 items-center gap-3 rounded-md px-3 text-sm transition ${
|
||||
active ? "bg-teal-50 text-jade" : "text-slate-600 hover:bg-slate-100 hover:text-ink"
|
||||
}`}
|
||||
>
|
||||
<Icon className="h-4 w-4" aria-hidden />
|
||||
<span>{item.label}</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
126
hy_qa_asset_backend/frontend/components/StandardQATable.tsx
Normal file
126
hy_qa_asset_backend/frontend/components/StandardQATable.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
"use client";
|
||||
|
||||
import { Ban, CheckCircle2, PauseCircle, Pencil, RotateCw } from "lucide-react";
|
||||
import { api, formatDateTime } from "@/lib/api";
|
||||
import type { StandardQAItem } from "@/lib/types";
|
||||
import RiskBadge from "./RiskBadge";
|
||||
import StatusBadge from "./StatusBadge";
|
||||
|
||||
export default function StandardQATable({ items, onChanged }: { items: StandardQAItem[]; onChanged: () => void }) {
|
||||
async function action(id: number, path: string) {
|
||||
try {
|
||||
await api.post(`/standard-qa/${id}/${path}`, {});
|
||||
onChanged();
|
||||
} catch (error) {
|
||||
alert(error instanceof Error ? error.message : "操作失败");
|
||||
}
|
||||
}
|
||||
|
||||
async function edit(item: StandardQAItem) {
|
||||
const standardQuestion = window.prompt("标准问题", item.standard_question);
|
||||
if (standardQuestion === null) return;
|
||||
const standardAnswer = window.prompt("标准回答", item.standard_answer);
|
||||
if (standardAnswer === null) return;
|
||||
await api.patch(`/standard-qa/${item.id}`, {
|
||||
standard_question: standardQuestion,
|
||||
standard_answer: standardAnswer
|
||||
});
|
||||
onChanged();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="table-wrap rounded-md border border-line bg-white">
|
||||
<table className="min-w-[1350px] w-full border-collapse text-left text-sm">
|
||||
<thead className="bg-panel text-xs uppercase text-slate-500">
|
||||
<tr>
|
||||
<th className="px-4 py-3">标准问题</th>
|
||||
<th className="px-4 py-3">标准回答</th>
|
||||
<th className="px-4 py-3">相似问法</th>
|
||||
<th className="px-4 py-3">主题</th>
|
||||
<th className="px-4 py-3">标签</th>
|
||||
<th className="px-4 py-3">阶段</th>
|
||||
<th className="px-4 py-3">适用人群</th>
|
||||
<th className="px-4 py-3">边界</th>
|
||||
<th className="px-4 py-3">禁止表达</th>
|
||||
<th className="px-4 py-3">来源</th>
|
||||
<th className="px-4 py-3">状态</th>
|
||||
<th className="px-4 py-3">更新</th>
|
||||
<th className="px-4 py-3">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item) => (
|
||||
<tr key={item.id} className="border-t border-line align-top">
|
||||
<td className="max-w-sm px-4 py-3 font-medium text-ink">{item.standard_question}</td>
|
||||
<td className="max-w-md whitespace-pre-wrap px-4 py-3">{item.standard_answer}</td>
|
||||
<td className="px-4 py-3">{item.similar_questions.join("、") || "-"}</td>
|
||||
<td className="px-4 py-3">{item.primary_topic}</td>
|
||||
<td className="px-4 py-3">{item.problem_tags.join("、") || "-"}</td>
|
||||
<td className="px-4 py-3">{item.course_stage}</td>
|
||||
<td className="px-4 py-3">{item.audience_tags.join("、") || "-"}</td>
|
||||
<td className="max-w-xs px-4 py-3">{item.answer_boundary ?? "-"}</td>
|
||||
<td className="px-4 py-3">{item.forbidden_expressions.join("、") || "-"}</td>
|
||||
<td className="px-4 py-3">
|
||||
QA #{item.source_raw_qa_id}
|
||||
<br />
|
||||
场次 #{item.session_id}
|
||||
</td>
|
||||
<td className="space-y-2 px-4 py-3">
|
||||
<StatusBadge status={item.audit_status} />
|
||||
<StatusBadge status={item.call_status} />
|
||||
<RiskBadge level={item.risk_level} />
|
||||
</td>
|
||||
<td className="px-4 py-3">{formatDateTime(item.updated_at)}</td>
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
onClick={() => edit(item)}
|
||||
className="inline-flex h-8 items-center gap-1 rounded-md border border-line px-2 text-xs hover:bg-slate-50"
|
||||
>
|
||||
<Pencil className="h-3.5 w-3.5" aria-hidden />
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
onClick={() => action(item.id, "mark-callable")}
|
||||
className="inline-flex h-8 items-center gap-1 rounded-md border border-emerald-200 px-2 text-xs text-emerald-700 hover:bg-emerald-50"
|
||||
>
|
||||
<CheckCircle2 className="h-3.5 w-3.5" aria-hidden />
|
||||
可调用
|
||||
</button>
|
||||
<button
|
||||
onClick={() => action(item.id, "mark-not-callable")}
|
||||
className="inline-flex h-8 items-center gap-1 rounded-md border border-line px-2 text-xs hover:bg-slate-50"
|
||||
>
|
||||
<PauseCircle className="h-3.5 w-3.5" aria-hidden />
|
||||
暂不调用
|
||||
</button>
|
||||
<button
|
||||
onClick={() => action(item.id, "need-review")}
|
||||
className="inline-flex h-8 items-center gap-1 rounded-md border border-line px-2 text-xs hover:bg-slate-50"
|
||||
>
|
||||
<RotateCw className="h-3.5 w-3.5" aria-hidden />
|
||||
复审
|
||||
</button>
|
||||
<button
|
||||
onClick={() => action(item.id, "disable")}
|
||||
className="inline-flex h-8 items-center gap-1 rounded-md border border-rose-200 px-2 text-xs text-rose-700 hover:bg-rose-50"
|
||||
>
|
||||
<Ban className="h-3.5 w-3.5" aria-hidden />
|
||||
禁用
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{items.length === 0 ? (
|
||||
<tr>
|
||||
<td className="px-4 py-8 text-center text-slate-500" colSpan={13}>
|
||||
暂无标准问答
|
||||
</td>
|
||||
</tr>
|
||||
) : null}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
33
hy_qa_asset_backend/frontend/components/StatCard.tsx
Normal file
33
hy_qa_asset_backend/frontend/components/StatCard.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
export default function StatCard({
|
||||
title,
|
||||
value,
|
||||
icon,
|
||||
tone = "teal"
|
||||
}: {
|
||||
title: string;
|
||||
value: string | number;
|
||||
icon: ReactNode;
|
||||
tone?: "teal" | "amber" | "rose" | "indigo";
|
||||
}) {
|
||||
const tones = {
|
||||
teal: "bg-teal-50 text-jade",
|
||||
amber: "bg-amber-50 text-amber-700",
|
||||
rose: "bg-rose-50 text-rose-700",
|
||||
indigo: "bg-indigo-50 text-indigo-700"
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="rounded-md border border-line bg-white p-4">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<div className="text-sm text-slate-500">{title}</div>
|
||||
<div className="mt-2 text-2xl font-semibold text-ink">{value}</div>
|
||||
</div>
|
||||
<div className={`flex h-10 w-10 items-center justify-center rounded-md ${tones[tone]}`}>{icon}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
46
hy_qa_asset_backend/frontend/components/StatusBadge.tsx
Normal file
46
hy_qa_asset_backend/frontend/components/StatusBadge.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
export default function StatusBadge({ status }: { status: string }) {
|
||||
const styles: Record<string, string> = {
|
||||
unprocessed: "border-slate-200 bg-slate-50 text-slate-700",
|
||||
processing: "border-cyan-200 bg-cyan-50 text-cyan-700",
|
||||
parsed: "border-indigo-200 bg-indigo-50 text-indigo-700",
|
||||
pending_review: "border-amber-200 bg-amber-50 text-amber-700",
|
||||
completed: "border-emerald-200 bg-emerald-50 text-emerald-700",
|
||||
failed: "border-rose-200 bg-rose-50 text-rose-700",
|
||||
pending: "border-amber-200 bg-amber-50 text-amber-700",
|
||||
approved: "border-emerald-200 bg-emerald-50 text-emerald-700",
|
||||
rejected: "border-slate-200 bg-slate-50 text-slate-700",
|
||||
forbidden: "border-rose-200 bg-rose-50 text-rose-700",
|
||||
callable: "border-emerald-200 bg-emerald-50 text-emerald-700",
|
||||
not_callable: "border-slate-200 bg-slate-50 text-slate-700",
|
||||
need_review: "border-amber-200 bg-amber-50 text-amber-700",
|
||||
success: "border-emerald-200 bg-emerald-50 text-emerald-700",
|
||||
partial_success: "border-orange-200 bg-orange-50 text-orange-700",
|
||||
running: "border-cyan-200 bg-cyan-50 text-cyan-700"
|
||||
};
|
||||
const labels: Record<string, string> = {
|
||||
unprocessed: "未处理",
|
||||
processing: "处理中",
|
||||
parsed: "已拆解",
|
||||
pending_review: "待审核",
|
||||
completed: "已完成",
|
||||
failed: "失败",
|
||||
pending: "待审核",
|
||||
reviewing: "审核中",
|
||||
approved: "已通过",
|
||||
needs_revision: "需修改",
|
||||
rejected: "不入库",
|
||||
forbidden: "禁止",
|
||||
callable: "可调用",
|
||||
not_callable: "暂不调用",
|
||||
need_review: "需复审",
|
||||
success: "成功",
|
||||
partial_success: "部分成功",
|
||||
running: "运行中"
|
||||
};
|
||||
return (
|
||||
<span className={`inline-flex rounded-md border px-2 py-1 text-xs font-medium ${styles[status] ?? styles.pending}`}>
|
||||
{labels[status] ?? status}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user