feat(frontend): add auto test functionality to debug drawer

Introduce a new auto test mode in the debug drawer, allowing users to run automated tests with mock data. This includes the addition of AutoTestPanel, AutoTestLivePanel, and AutoTestRunBar components for managing test cases and displaying results. The debug mode can now switch between manual and auto testing, enhancing the debugging experience.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Xin Wang
2026-08-07 16:09:18 +08:00
parent 36117b976e
commit 2f755969d3
2 changed files with 1292 additions and 26 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -23,6 +23,12 @@ import {
X, X,
} from "lucide-react"; } from "lucide-react";
import {
AutoTestLivePanel,
AutoTestRunBar,
DebugModeTabs,
useAutoTestRunner,
} from "@/components/assistant-editor/debug-auto-test";
import { NetworkQualityIndicator } from "@/components/network-quality-indicator"; import { NetworkQualityIndicator } from "@/components/network-quality-indicator";
import { ClientMessageDialog } from "@/components/client-message-dialog"; import { ClientMessageDialog } from "@/components/client-message-dialog";
import { AuraVisualizer } from "@/components/ui/aura-visualizer"; import { AuraVisualizer } from "@/components/ui/aura-visualizer";
@@ -67,6 +73,7 @@ type VizStyle = "aura" | "nebula" | "bars" | "wave";
// 调试面板顶部主视图:聊天记录 / 视频流 // 调试面板顶部主视图:聊天记录 / 视频流
type DebugView = "chat" | "video"; type DebugView = "chat" | "video";
type DebugInputMode = "mic" | "text"; type DebugInputMode = "mic" | "text";
type DebugMode = "manual" | "auto";
type PendingDebugImage = { type PendingDebugImage = {
file: File; file: File;
previewUrl: string; previewUrl: string;
@@ -175,6 +182,9 @@ export function DebugDrawer({
}) { }) {
const preview = useVoicePreview(assistantId, onNodeActive); const preview = useVoicePreview(assistantId, onNodeActive);
const camera = useCameraPreview(); const camera = useCameraPreview();
const autoTest = useAutoTestRunner();
const stopAutoTest = autoTest.stop;
const [debugMode, setDebugMode] = useState<DebugMode>("manual");
const [showTranscript, setShowTranscript] = useState(false); const [showTranscript, setShowTranscript] = useState(false);
const [vizStyle, setVizStyle] = useState<VizStyle>("aura"); const [vizStyle, setVizStyle] = useState<VizStyle>("aura");
const [view, setView] = useState<DebugView>("chat"); const [view, setView] = useState<DebugView>("chat");
@@ -218,6 +228,20 @@ export function DebugDrawer({
[camera, preview], [camera, preview],
); );
// 切到自动测试时停掉手动会话,避免两边抢占麦克风/对话区
const handleModeChange = useCallback(
(mode: DebugMode) => {
setDebugMode(mode);
if (mode === "auto" && recording) {
preview.disconnect();
}
if (mode === "manual") {
stopAutoTest();
}
},
[preview, recording, stopAutoTest],
);
return ( return (
<aside <aside
className={overlay className={overlay
@@ -240,14 +264,18 @@ export function DebugDrawer({
<div className="shrink-0 text-sm font-medium text-foreground"> <div className="shrink-0 text-sm font-medium text-foreground">
</div> </div>
<NetworkQualityIndicator {debugMode === "manual" && (
quality={preview.networkQuality} <>
status={preview.status} <NetworkQualityIndicator
/> quality={preview.networkQuality}
<DebugConnectionStatus status={preview.status}
status={preview.status} />
micWarning={preview.micWarning} <DebugConnectionStatus
/> status={preview.status}
micWarning={preview.micWarning}
/>
</>
)}
</div> </div>
<div className="flex shrink-0 items-center gap-2"> <div className="flex shrink-0 items-center gap-2">
<ClientToolsPopover tools={preview.clientTools} /> <ClientToolsPopover tools={preview.clientTools} />
@@ -261,7 +289,7 @@ export function DebugDrawer({
onChange={setDynamicVariableValues} onChange={setDynamicVariableValues}
/> />
)} )}
{SHOW_VOICE_VIZ && view === "chat" && ( {debugMode === "manual" && SHOW_VOICE_VIZ && view === "chat" && (
<> <>
{!showTranscript && ( {!showTranscript && (
<SegmentedIconGroup label="可视化样式"> <SegmentedIconGroup label="可视化样式">
@@ -297,28 +325,72 @@ export function DebugDrawer({
)} )}
</div> </div>
</div> </div>
<div className="shrink-0 border-b border-hairline px-5 py-2.5"> <div className="shrink-0 space-y-2.5 border-b border-hairline px-5 py-2.5">
<div className="flex h-10 min-w-0 items-center rounded-[1.4rem] border border-hairline-strong bg-background px-2"> <DebugModeTabs mode={debugMode} onChange={handleModeChange} />
<CameraDeviceField camera={camera} onSelect={selectCamera} /> {debugMode === "manual" && (
</div> <div className="flex h-10 min-w-0 items-center rounded-[1.4rem] border border-hairline-strong bg-background px-2">
<CameraDeviceField camera={camera} onSelect={selectCamera} />
</div>
)}
</div> </div>
<DebugVoicePanel {debugMode === "auto" ? (
view={view} <AutoTestPanel assistantId={assistantId} autoTest={autoTest} />
onViewChange={setView} ) : (
showTranscript={showTranscript} <DebugVoicePanel
vizStyle={vizStyle} view={view}
assistantId={assistantId} onViewChange={setView}
preview={preview} showTranscript={showTranscript}
camera={camera} vizStyle={vizStyle}
hasUnsavedChanges={hasUnsavedChanges} assistantId={assistantId}
vision={vision} preview={preview}
dynamicVariables={resolvedDynamicVariables} camera={camera}
dynamicVariablesError={dynamicVariablesError} hasUnsavedChanges={hasUnsavedChanges}
/> vision={vision}
dynamicVariables={resolvedDynamicVariables}
dynamicVariablesError={dynamicVariablesError}
/>
)}
</aside> </aside>
); );
} }
function AutoTestPanel({
assistantId,
autoTest,
}: {
assistantId: string | null;
autoTest: ReturnType<typeof useAutoTestRunner>;
}) {
const {
state,
selectedCase,
selectCase,
clearCase,
start,
stop,
rerun,
} = autoTest;
return (
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
<AutoTestLivePanel testCase={selectedCase} state={state} />
</div>
<div className="shrink-0 border-t border-hairline bg-card p-3">
<AutoTestRunBar
state={state}
assistantId={assistantId}
onSelectCase={selectCase}
onClearCase={clearCase}
onStart={start}
onStop={stop}
onRerun={rerun}
/>
</div>
</div>
);
}
function ClientToolsPopover({ tools }: { tools: ClientToolDefinition[] }) { function ClientToolsPopover({ tools }: { tools: ClientToolDefinition[] }) {
const [copiedToolName, setCopiedToolName] = useState<string | null>(null); const [copiedToolName, setCopiedToolName] = useState<string | null>(null);