"use client"; import React, { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { Play, SkipForward, CheckCircle, X, Clock, AlertCircle, Bot, User, Activity, Zap, Settings, } from "lucide-react"; import { Button } from "~/components/ui/button"; import { Badge } from "~/components/ui/badge"; import { Progress } from "~/components/ui/progress"; import { Alert, AlertDescription } from "~/components/ui/alert"; import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import { Separator } from "~/components/ui/separator"; import { PageHeader } from "~/components/ui/page-header"; import { useBreadcrumbsEffect } from "~/components/ui/breadcrumb-provider"; import { PanelsContainer } from "~/components/experiments/designer/layout/PanelsContainer"; import { ActionControls } from "./ActionControls"; import { RobotStatus } from "./RobotStatus"; import { ParticipantInfo } from "./ParticipantInfo"; import { EventsLogSidebar } from "./EventsLogSidebar"; import { api } from "~/trpc/react"; import { useTrialWebSocket } from "~/hooks/useWebSocket"; interface WizardInterfaceProps { trial: { id: string; status: "scheduled" | "in_progress" | "completed" | "aborted" | "failed"; scheduledAt: Date | null; startedAt: Date | null; completedAt: Date | null; duration: number | null; sessionNumber: number | null; notes: string | null; metadata: Record | null; experimentId: string; participantId: string | null; wizardId: string | null; experiment: { id: string; name: string; description: string | null; studyId: string; }; participant: { id: string; participantCode: string; demographics: Record | null; }; }; userRole: string; } interface StepData { id: string; name: string; description: string | null; type: | "wizard_action" | "robot_action" | "parallel_steps" | "conditional_branch"; parameters: Record; order: number; } export function WizardInterface({ trial: initialTrial, userRole: _userRole, }: WizardInterfaceProps) { const router = useRouter(); const [trial, setTrial] = useState(initialTrial); const [currentStepIndex, setCurrentStepIndex] = useState(0); const [trialStartTime, setTrialStartTime] = useState( initialTrial.startedAt ? new Date(initialTrial.startedAt) : null, ); const [elapsedTime, setElapsedTime] = useState(0); // Get experiment steps from API const { data: experimentSteps } = api.experiments.getSteps.useQuery( { experimentId: trial.experimentId }, { enabled: !!trial.experimentId, staleTime: 30000, }, ); // Get study data for breadcrumbs const { data: studyData } = api.studies.get.useQuery( { id: trial.experiment.studyId }, { enabled: !!trial.experiment.studyId }, ); // Set breadcrumbs useBreadcrumbsEffect([ { label: "Dashboard", href: "/dashboard" }, { label: "Studies", href: "/studies" }, ...(studyData ? [ { label: studyData.name, href: `/studies/${studyData.id}` }, { label: "Trials", href: `/studies/${studyData.id}/trials` }, ] : []), { label: `Trial ${trial.participant.participantCode}`, href: `/trials/${trial.id}`, }, { label: "Wizard Control" }, ]); // Map database step types to component step types const mapStepType = (dbType: string) => { switch (dbType) { case "wizard": return "wizard_action" as const; case "robot": return "robot_action" as const; case "parallel": return "parallel_steps" as const; case "conditional": return "conditional_branch" as const; default: return "wizard_action" as const; } }; // Real-time WebSocket connection const { isConnected: wsConnected, isConnecting: wsConnecting, connectionError: wsError, trialEvents, executeTrialAction, transitionStep, } = useTrialWebSocket(trial.id); // Fallback polling for trial updates when WebSocket is not available api.trials.get.useQuery( { id: trial.id }, { refetchInterval: wsConnected ? 10000 : 2000, refetchOnWindowFocus: true, enabled: !wsConnected, }, ); // Mutations for trial control const startTrialMutation = api.trials.start.useMutation({ onSuccess: (data) => { setTrial((prev) => ({ ...prev, status: data.status, startedAt: data.startedAt, })); setTrialStartTime(new Date()); }, }); const completeTrialMutation = api.trials.complete.useMutation({ onSuccess: (data) => { if (data) { setTrial((prev) => ({ ...prev, status: data.status, completedAt: data.completedAt, })); } router.push(`/trials/${trial.id}/analysis`); }, }); const abortTrialMutation = api.trials.abort.useMutation({ onSuccess: (data) => { if (data) { setTrial((prev) => ({ ...prev, status: data.status, completedAt: data.completedAt, })); } router.push(`/trials/${trial.id}`); }, }); // Process steps from API response const steps: StepData[] = React.useMemo(() => { if (!experimentSteps) return []; return experimentSteps.map((step) => ({ id: step.id, name: step.name, description: step.description, type: mapStepType(step.type), parameters: typeof step.parameters === "object" && step.parameters !== null ? step.parameters : {}, order: step.order, })); }, [experimentSteps]); const currentStep = steps[currentStepIndex] ?? null; const progress = steps.length > 0 ? ((currentStepIndex + 1) / steps.length) * 100 : 0; // Update elapsed time useEffect(() => { if (!trialStartTime || trial.status !== "in_progress") return; const interval = setInterval(() => { setElapsedTime( Math.floor((Date.now() - trialStartTime.getTime()) / 1000), ); }, 1000); return () => clearInterval(interval); }, [trialStartTime, trial.status]); // Format elapsed time const formatElapsedTime = (seconds: number) => { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`; }; // Trial control handlers const handleStartTrial = async () => { try { await startTrialMutation.mutateAsync({ id: trial.id }); } catch (error) { console.error("Failed to start trial:", error); } }; const handleCompleteTrial = async () => { try { await completeTrialMutation.mutateAsync({ id: trial.id }); } catch (error) { console.error("Failed to complete trial:", error); } }; const handleAbortTrial = async () => { if (window.confirm("Are you sure you want to abort this trial?")) { try { await abortTrialMutation.mutateAsync({ id: trial.id }); } catch (error) { console.error("Failed to abort trial:", error); } } }; const handleNextStep = () => { if (currentStepIndex < steps.length - 1) { setCurrentStepIndex(currentStepIndex + 1); if (transitionStep) { void transitionStep({ to_step: currentStepIndex + 1, from_step: currentStepIndex, step_name: steps[currentStepIndex + 1]?.name, }); } } }; const handlePreviousStep = () => { if (currentStepIndex > 0) { setCurrentStepIndex(currentStepIndex - 1); if (transitionStep) { void transitionStep({ to_step: currentStepIndex - 1, from_step: currentStepIndex, step_name: steps[currentStepIndex - 1]?.name, }); } } }; const handleCompleteWizardAction = ( actionId: string, actionData: Record, ) => { if (executeTrialAction) { void executeTrialAction(actionId, actionData); } }; // Left panel - Trial controls and step navigation const leftPanel = (
{/* Trial Status */} Trial Status {trial.status.replace("_", " ")} {trial.status === "in_progress" && (
Elapsed {formatElapsedTime(elapsedTime)}
Step {currentStepIndex + 1} of {steps.length}
)} {trial.status === "scheduled" && ( )}
{/* Trial Controls */} {trial.status === "in_progress" && ( Trial Controls )} {/* Step List */} {steps.length > 0 && ( Experiment Steps
{steps.map((step, index) => (
{index + 1}
{step.name}
{step.description && (
{step.description}
)}
))}
)}
); // Center panel - Main execution area const centerPanel = (
{/* Connection Status Alert */} {wsError && wsError.length > 0 && !wsConnecting && ( {wsError.includes("polling mode") ? "Real-time connection unavailable - using polling for updates" : wsError} )} {trial.status === "scheduled" ? ( // Trial not started

Trial Scheduled

This trial is scheduled and ready to begin. Click "Start Trial" in the left panel to begin execution.

) : trial.status === "in_progress" ? ( // Trial in progress - Current step and controls
{/* Current Step */} {currentStep && ( Current Step: {currentStep.name}

{currentStep.description}

{currentStepIndex > 0 && ( )}
)} {/* Wizard Actions */} Wizard Actions
) : ( // Trial completed/aborted

Trial {trial.status === "completed" ? "Completed" : "Ended"}

This trial has{" "} {trial.status === "completed" ? "completed successfully" : "ended"} . You can view the results and analysis data.

)}
); // Right panel - Monitoring and context const rightPanel = (
{/* Robot Status */} Robot Status {/* Participant Info */} Participant {/* Live Events */} Live Events {/* Connection Status */} Connection
Status {wsConnected ? "Connected" : "Polling"}
Trial ID: {trial.id.slice(-8)}
Experiment: {trial.experiment.name}
Participant: {trial.participant.participantCode}
{trialStartTime && (
Started: {trialStartTime.toLocaleTimeString()}
)}
); return (
{/* Page Header */} {/* Main Panel Layout */}
); }