Files
ai-video-fullstack/frontend/src/components/batch-test/batch-case-detail-drawer.tsx
T
2026-08-08 19:08:48 +08:00

212 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
/**
* 批量测试用例详情抽屉。
* 当前展示前端 mock 数据,后续可直接替换为真实执行事件与校验结果。
*/
import {
CheckCircle2,
Circle,
Loader2,
MessageSquareText,
Target,
TriangleAlert,
} from "lucide-react";
import { BatchCaseStatusBadge } from "@/components/batch-test/batch-case-status";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
} from "@/components/ui/sheet";
import type { BatchRunCase } from "@/data/batch-run";
import { cn } from "@/lib/utils";
type BatchCaseDetailDrawerProps = {
item: BatchRunCase | null;
assistantName: string;
onClose: () => void;
};
export function BatchCaseDetailDrawer({
item,
assistantName,
onClose,
}: BatchCaseDetailDrawerProps) {
return (
<Sheet
open={Boolean(item)}
onOpenChange={(open) => {
if (!open) onClose();
}}
>
<SheetContent
side="right"
className="w-[min(92vw,620px)] border-hairline bg-card p-0 sm:max-w-[620px]"
>
{item && (
<>
<SheetHeader className="border-b border-hairline px-6 py-5 pr-16">
<p className="text-xs font-medium tracking-[0.08em] text-muted-soft uppercase">
用例详情
</p>
<SheetTitle className="text-lg leading-7">
{item.name}
</SheetTitle>
<SheetDescription>
被测助手:{assistantName} · 当前为前端 Mock 结果
</SheetDescription>
</SheetHeader>
<div className="min-h-0 flex-1 overflow-y-auto px-6 py-6">
<div className="space-y-6">
<section className="flex items-center justify-between rounded-2xl border border-hairline bg-canvas-soft px-4 py-3.5">
<div>
<p className="text-xs text-muted-soft">当前状态</p>
<p className="mt-1 text-sm text-muted-foreground">
{statusDescription(item)}
</p>
</div>
<BatchCaseStatusBadge status={item.status} />
</section>
<DetailSection
icon={<Target size={16} />}
title="预期结果"
value={item.expected}
/>
{(item.status === "waiting" || item.status === "running") && (
<ExecutionTimeline status={item.status} />
)}
{(item.status === "pass" || item.status === "fail") && (
<DetailSection
icon={<MessageSquareText size={16} />}
title="实际回复"
value={item.actual}
/>
)}
{item.status === "fail" && item.failReason && (
<DetailSection
icon={<TriangleAlert size={16} />}
title="失败原因(LLM 判断)"
value={item.failReason}
tone="destructive"
/>
)}
{item.status === "skipped" && (
<div className="rounded-2xl border border-dashed border-hairline-strong px-4 py-4 text-sm leading-6 text-muted-foreground">
该用例未执行,因此没有实际回复与判断结果。
</div>
)}
</div>
</div>
</>
)}
</SheetContent>
</Sheet>
);
}
function statusDescription(item: BatchRunCase): string {
const descriptions = {
waiting: "已加入执行队列,正在等待可用并发。",
running: "正在请求助手响应,结果尚未生成。",
pass: "执行与结果判断均已完成。",
fail: "执行完成,但实际回复未达到预期。",
skipped: "本轮运行已结束,该用例未执行。",
} as const;
return descriptions[item.status];
}
function DetailSection({
icon,
title,
value,
tone = "default",
}: {
icon: React.ReactNode;
title: string;
value: string;
tone?: "default" | "destructive";
}) {
return (
<section>
<div
className={cn(
"flex items-center gap-2 text-sm font-medium text-foreground",
tone === "destructive" && "text-destructive",
)}
>
{icon}
<h3>{title}</h3>
</div>
<p
className={cn(
"mt-2.5 rounded-2xl border border-hairline bg-canvas-soft px-4 py-3.5 text-sm leading-6 text-muted-foreground",
tone === "destructive" &&
"border-destructive/20 bg-destructive/5 text-foreground",
)}
>
{value}
</p>
</section>
);
}
function ExecutionTimeline({ status }: { status: "waiting" | "running" }) {
const isRunning = status === "running";
const steps = [
{
label: "载入测试上下文",
detail: isRunning ? "已完成" : "等待开始",
state: isRunning ? "done" : "pending",
},
{
label: "请求助手响应",
detail: isRunning ? "生成中(Mock)" : "尚未开始",
state: isRunning ? "active" : "pending",
},
{
label: "执行结果判断",
detail: "尚未开始",
state: "pending",
},
] as const;
return (
<section>
<div className="flex items-center gap-2 text-sm font-medium text-foreground">
<Loader2 size={16} className={cn(isRunning && "animate-spin")} />
<h3>执行进度</h3>
</div>
<ol className="mt-3 rounded-2xl border border-hairline px-4 py-1">
{steps.map((step) => (
<li
key={step.label}
className="flex items-center gap-3 border-b border-hairline py-3.5 last:border-b-0"
>
{step.state === "done" ? (
<CheckCircle2 size={17} className="shrink-0 text-success" />
) : step.state === "active" ? (
<Loader2 size={17} className="shrink-0 animate-spin text-primary" />
) : (
<Circle size={17} className="shrink-0 text-muted-soft" />
)}
<span className="min-w-0 flex-1 text-sm text-foreground">
{step.label}
</span>
<span className="text-xs text-muted-soft">{step.detail}</span>
</li>
))}
</ol>
</section>
);
}