Expand test case fixed-input editor and batch case detail split view.
Unify multi-turn behaviors (reply/tool), overall expectation, and input-mode picker while keeping non-text modes as coming soon; batch completed/running use a half-and-half detail panel below the top bar. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
185
frontend/src/components/test-cases/input-mode-picker.tsx
Normal file
185
frontend/src/components/test-cases/input-mode-picker.tsx
Normal file
@@ -0,0 +1,185 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* 测试用例顶部紧凑模式选择器:固定脚本 / 用户模拟。
|
||||
*/
|
||||
|
||||
import {
|
||||
AudioLines,
|
||||
Check,
|
||||
ChevronDown,
|
||||
MessageSquareText,
|
||||
Mic,
|
||||
Type,
|
||||
Volume2,
|
||||
} from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
TEST_CASE_INPUT_MODE_LABEL,
|
||||
type TestCaseInputMode,
|
||||
} from "@/data/test-suites";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const FIXED_SCRIPT_OPTIONS: {
|
||||
value: TestCaseInputMode;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: typeof Type;
|
||||
available: boolean;
|
||||
}[] = [
|
||||
{
|
||||
value: "fixed_script_text",
|
||||
title: "文字",
|
||||
description: "使用固定文字逐轮测试",
|
||||
icon: Type,
|
||||
available: true,
|
||||
},
|
||||
{
|
||||
value: "fixed_script_turn_voice",
|
||||
title: "逐轮语音",
|
||||
description: "固定文字自动合成为语音",
|
||||
icon: Volume2,
|
||||
available: false,
|
||||
},
|
||||
{
|
||||
value: "fixed_script_continuous_voice",
|
||||
title: "连续语音",
|
||||
description: "按指定时序连续发送语音",
|
||||
icon: AudioLines,
|
||||
available: false,
|
||||
},
|
||||
];
|
||||
|
||||
const USER_SIM_OPTIONS: {
|
||||
value: TestCaseInputMode;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: typeof MessageSquareText;
|
||||
available: boolean;
|
||||
}[] = [
|
||||
{
|
||||
value: "user_sim_text",
|
||||
title: "文字",
|
||||
description: "使用模型模拟文字用户",
|
||||
icon: MessageSquareText,
|
||||
available: false,
|
||||
},
|
||||
{
|
||||
value: "user_sim_voice",
|
||||
title: "语音",
|
||||
description: "使用模型模拟语音用户",
|
||||
icon: Mic,
|
||||
available: false,
|
||||
},
|
||||
];
|
||||
|
||||
export function InputModePicker({
|
||||
value,
|
||||
onChange,
|
||||
}: {
|
||||
value: TestCaseInputMode;
|
||||
onChange: (mode: TestCaseInputMode) => void;
|
||||
}) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-9 max-w-[220px] shrink-0 gap-1.5 rounded-full border-hairline-strong bg-background px-3 text-xs font-normal text-foreground"
|
||||
>
|
||||
<span className="truncate">{TEST_CASE_INPUT_MODE_LABEL[value]}</span>
|
||||
<ChevronDown size={14} className="shrink-0 text-muted-soft" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start" className="w-72 p-1.5">
|
||||
<DropdownMenuLabel className="px-2.5 py-1.5 text-[11px] font-medium tracking-wide text-muted-soft uppercase">
|
||||
固定脚本
|
||||
</DropdownMenuLabel>
|
||||
{FIXED_SCRIPT_OPTIONS.map((option) => (
|
||||
<ModeMenuItem
|
||||
key={option.value}
|
||||
selected={value === option.value}
|
||||
title={option.title}
|
||||
description={option.description}
|
||||
icon={option.icon}
|
||||
available={option.available}
|
||||
onSelect={() => onChange(option.value)}
|
||||
/>
|
||||
))}
|
||||
|
||||
<DropdownMenuSeparator className="my-1.5" />
|
||||
|
||||
<DropdownMenuLabel className="px-2.5 py-1.5 text-[11px] font-medium tracking-wide text-muted-soft uppercase">
|
||||
用户模拟
|
||||
</DropdownMenuLabel>
|
||||
{USER_SIM_OPTIONS.map((option) => (
|
||||
<ModeMenuItem
|
||||
key={option.value}
|
||||
selected={value === option.value}
|
||||
title={option.title}
|
||||
description={option.description}
|
||||
icon={option.icon}
|
||||
available={option.available}
|
||||
onSelect={() => onChange(option.value)}
|
||||
/>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
function ModeMenuItem({
|
||||
selected,
|
||||
title,
|
||||
description,
|
||||
icon: Icon,
|
||||
available,
|
||||
onSelect,
|
||||
}: {
|
||||
selected: boolean;
|
||||
title: string;
|
||||
description: string;
|
||||
icon: typeof Type;
|
||||
available: boolean;
|
||||
onSelect: () => void;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onSelect}
|
||||
className={cn(
|
||||
"flex w-full items-start gap-2.5 rounded-lg px-2.5 py-2 text-left transition-colors",
|
||||
available
|
||||
? "hover:bg-surface-strong"
|
||||
: "opacity-70 hover:bg-surface-strong/60",
|
||||
selected && "bg-surface-strong/80",
|
||||
)}
|
||||
>
|
||||
<Icon size={15} className="mt-0.5 shrink-0 text-muted-foreground" />
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="flex items-center gap-1.5 text-sm font-medium text-foreground">
|
||||
{title}
|
||||
{selected && <Check size={13} className="text-muted-foreground" />}
|
||||
{!available && (
|
||||
<span className="ml-auto text-[11px] font-normal text-muted-soft">
|
||||
即将支持
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="mt-0.5 block text-xs leading-4 text-muted-soft">
|
||||
{description}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user