From 5be4ff037275688d7a72a25bb15c35f06fd9c432 Mon Sep 17 00:00:00 2001 From: Sean O'Connor Date: Thu, 20 Nov 2025 14:52:08 -0500 Subject: [PATCH] refactor: simplify wizard UI by removing trial monitoring and robot control tabs, and streamlining monitoring panel props. --- .../trials/wizard/WizardInterface.tsx | 6 - .../wizard/panels/WizardControlPanel.tsx | 35 +- .../wizard/panels/WizardMonitoringPanel.tsx | 1105 +++++------------ src/hooks/useWizardRos.ts | 9 +- 4 files changed, 306 insertions(+), 849 deletions(-) diff --git a/src/components/trials/wizard/WizardInterface.tsx b/src/components/trials/wizard/WizardInterface.tsx index 8e9084f..42fd255 100755 --- a/src/components/trials/wizard/WizardInterface.tsx +++ b/src/components/trials/wizard/WizardInterface.tsx @@ -491,12 +491,6 @@ export const WizardInterface = React.memo(function WizardInterface({ } right={ ; order: number; } @@ -173,29 +174,25 @@ export function WizardControlPanel({ value === "actions" || value === "robot" ) { - onTabChange(value as "control" | "step" | "actions" | "robot"); + onTabChange(value as "control" | "step" | "actions"); } }} className="flex min-h-0 flex-1 flex-col" >
- + - + Control - + Step Actions - - - Robot -
@@ -270,13 +267,13 @@ export function WizardControlPanel({ {(trial.status === "completed" || trial.status === "aborted") && ( - - - - Trial has ended. All controls are disabled. - - - )} + + + + Trial has ended. All controls are disabled. + + + )} {/* Connection Status */} diff --git a/src/components/trials/wizard/panels/WizardMonitoringPanel.tsx b/src/components/trials/wizard/panels/WizardMonitoringPanel.tsx index 0fa1fb1..fd8324f 100755 --- a/src/components/trials/wizard/panels/WizardMonitoringPanel.tsx +++ b/src/components/trials/wizard/panels/WizardMonitoringPanel.tsx @@ -1,70 +1,20 @@ "use client"; -import React, { useState, useRef } from "react"; +import React from "react"; import { Bot, - User, - Activity, - Wifi, - WifiOff, - AlertCircle, - CheckCircle, - Clock, Power, PowerOff, - Eye, - Volume2, - Move, - Hand, + AlertCircle, } from "lucide-react"; import { Badge } from "~/components/ui/badge"; import { Separator } from "~/components/ui/separator"; import { ScrollArea } from "~/components/ui/scroll-area"; import { Alert, AlertDescription } from "~/components/ui/alert"; -import { Tabs, TabsList, TabsTrigger, TabsContent } from "~/components/ui/tabs"; import { Progress } from "~/components/ui/progress"; import { Button } from "~/components/ui/button"; -interface TrialData { - 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; - 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; - }; -} - -interface TrialEvent { - type: string; - timestamp: Date; - data?: unknown; - message?: string; -} - interface WizardMonitoringPanelProps { - trial: TrialData; - trialEvents: TrialEvent[]; - isConnected: boolean; - wsError?: string; - activeTab: "status" | "robot" | "events"; - onTabChange: (tab: "status" | "robot" | "events") => void; - // ROS connection props rosConnected: boolean; rosConnecting: boolean; rosError?: string; @@ -85,13 +35,7 @@ interface WizardMonitoringPanelProps { ) => Promise; } -const WizardMonitoringPanel = React.memo(function WizardMonitoringPanel({ - trial, - trialEvents, - isConnected, - wsError, - activeTab, - onTabChange, +const WizardMonitoringPanel = function WizardMonitoringPanel({ rosConnected, rosConnecting, rosError, @@ -100,778 +44,297 @@ const WizardMonitoringPanel = React.memo(function WizardMonitoringPanel({ disconnectRos, executeRosAction, }: WizardMonitoringPanelProps) { - // ROS connection is now passed as props, no need for separate hook - - // Don't close connection on unmount to prevent disconnection issues - // Connection will persist across component re-renders - - // Removed auto-reconnect to prevent interference with manual connections - - const formatTimestamp = React.useCallback((timestamp: Date) => { - return new Date(timestamp).toLocaleTimeString(); - }, []); - - const getEventIcon = (eventType: string) => { - switch (eventType.toLowerCase()) { - case "trial_started": - case "trial_resumed": - return CheckCircle; - case "trial_paused": - case "trial_stopped": - return AlertCircle; - case "step_completed": - case "action_completed": - return CheckCircle; - case "robot_action": - case "robot_status": - return Bot; - case "wizard_action": - case "wizard_intervention": - return User; - case "system_error": - case "connection_error": - return AlertCircle; - default: - return Activity; - } - }; - - const getEventColor = (eventType: string) => { - switch (eventType.toLowerCase()) { - case "trial_started": - case "trial_resumed": - case "step_completed": - case "action_completed": - return "text-green-600"; - case "trial_paused": - case "trial_stopped": - return "text-yellow-600"; - case "system_error": - case "connection_error": - case "trial_failed": - return "text-red-600"; - case "robot_action": - case "robot_status": - return "text-blue-600"; - case "wizard_action": - case "wizard_intervention": - return "text-purple-600"; - default: - return "text-muted-foreground"; - } - }; - return (
{/* Header */} -
-
-

Monitoring

-
- {isConnected ? ( - - ) : ( - - )} - - {isConnected ? "Live" : "Offline"} - -
-
- {wsError && ( - - - {wsError} - - )} +
+

Robot Control

- {/* Tabbed Content */} - { - if (value === "status" || value === "robot" || value === "events") { - onTabChange(value); - } - }} - className="flex min-h-0 flex-1 flex-col" - > -
- - - - Status - - - - Robot - - - - Events - {trialEvents.length > 0 && ( - - {trialEvents.length} - - )} - - -
- -
- {/* Status Tab */} - - -
- {/* Connection Status */} -
-
Connection
-
-
- - ROS Bridge - - - {isConnected ? "Connected" : "Offline"} - -
-
- - Data Mode - - - {isConnected ? "Real-time" : "Polling"} - -
-
-
- - - - {/* Trial Information */} -
-
Trial Info
-
-
- ID - - {trial.id.slice(-8)} - -
-
- - Session - - #{trial.sessionNumber} -
-
- - Status - - - {trial.status.replace("_", " ")} - -
- {trial.startedAt && ( -
- - Started - - - {formatTimestamp(new Date(trial.startedAt))} - -
- )} -
-
- - - - {/* Participant Information */} -
-
Participant
-
-
- - Code - - - {trial.participant.participantCode} - -
-
- - Session - - #{trial.sessionNumber} -
- {trial.participant.demographics && ( -
- - Demographics - - - {Object.keys(trial.participant.demographics).length}{" "} - fields - -
- )} -
-
- - - - {/* System Information */} -
-
System
-
-
- - Experiment - - - {trial.experiment.name} - -
-
- - Study - - - {trial.experiment.studyId.slice(-8)} - -
-
- - Platform - - HRIStudio -
-
-
-
-
-
- - {/* Robot Tab */} - - -
- {/* Robot Status */} -
-
-
Robot Status
-
- {rosConnected ? ( - - ) : ( - - )} -
-
-
-
- - ROS Bridge - -
- - {rosConnecting - ? "Connecting..." - : rosConnected - ? "Ready" - : rosError - ? "Failed" - : "Offline"} - - {rosConnected && ( - - ● - - )} - {rosConnecting && ( - - ⟳ - - )} -
-
-
- - Battery - -
- - {robotStatus && robotStatus.battery > 0 - ? `${Math.round(robotStatus.battery)}%` - : rosConnected - ? "Reading..." - : "No data"} - - 0 - ? robotStatus.battery - : 0 - } - className="h-1 w-8" - /> -
-
-
- - Position - - - {robotStatus - ? `(${robotStatus.position.x.toFixed(1)}, ${robotStatus.position.y.toFixed(1)})` - : "--"} - -
-
- - Last Update - - - {robotStatus - ? robotStatus.lastUpdate.toLocaleTimeString() - : "--"} - -
-
- - {/* ROS Connection Controls */} -
- {!rosConnected ? ( - - ) : ( - - )} -
- - {rosError && ( - - - - {rosError} - - - )} - - {/* Connection Help */} - {!rosConnected && !rosConnecting && ( - - - -
-
Troubleshooting:
-
- 1. Check ROS Bridge:{" "} - - telnet localhost 9090 - -
-
2. NAO6 must be awake and connected
-
- 3. Try: Click Connect → Wait 2s → Test Speech -
-
-
-
- )} -
- - - - {/* Robot Actions */} -
-
Active Actions
-
-
- No active actions -
-
-
- - - - {/* Recent Trial Events */} -
-
Recent Events
-
- {trialEvents - .filter((e) => e.type.includes("robot")) - .slice(-2) - .map((event, index) => ( -
- - {event.type.replace(/_/g, " ")} - - - {formatTimestamp(event.timestamp)} - -
- ))} - {trialEvents.filter((e) => e.type.includes("robot")) - .length === 0 && ( -
- No robot events yet -
- )} -
-
- - - - {/* Robot Configuration */} -
-
Configuration
-
-
- - Type - - NAO6 -
-
- - ROS Bridge - - localhost:9090 -
-
- - Platform - - NAOqi -
- {robotStatus && - Object.keys(robotStatus.joints).length > 0 && ( -
- - Joints - - - {Object.keys(robotStatus.joints).length} active - -
- )} -
-
- - {/* Manual Subscription Controls */} - {rosConnected && ( -
-
Manual Controls
- - {/* Connection Test */} -
- -
- - {/* Topic Subscriptions */} -
-
- Subscribe to Topics: -
-
-
- Subscriptions managed automatically -
-
-
-
- )} - - {/* Quick Robot Actions */} - {rosConnected && ( -
-
Robot Actions
- - {/* Movement Controls */} -
- - - -
- - {/* Head Controls */} -
- - - -
- - {/* Animation & LED Controls */} -
- - -
- - {/* Emergency Controls */} -
- -
-
- )} - - {!rosConnected && !rosConnecting && ( -
- - - - Connect to ROS bridge for live robot monitoring and - control - - -
- )} -
-
-
- - {/* Events Tab */} - - -
- {trialEvents.length === 0 ? ( -
- No events recorded yet -
+ {/* Robot Status and Controls */} + +
+ {/* Robot Status */} +
+
+
Robot Status
+
+ {rosConnected ? ( + ) : ( -
-
- Live Events - - {trialEvents.length} - -
- - {trialEvents - .slice() - .reverse() - .map((event, index) => { - const EventIcon = getEventIcon(event.type); - const eventColor = getEventColor(event.type); - - return ( -
-
- -
-
-
- {event.type.replace(/_/g, " ")} -
- {event.message && ( -
- {event.message} -
- )} -
- - {formatTimestamp(event.timestamp)} -
-
-
- ); - })} -
+ )}
- - +
+
+
+ + ROS Bridge + +
+ + {rosConnecting + ? "Connecting..." + : rosConnected + ? "Ready" + : rosError + ? "Failed" + : "Offline"} + + {rosConnected && ( + + ● + + )} + {rosConnecting && ( + + ⟳ + + )} +
+
+
+ + {/* ROS Connection Controls */} +
+ {!rosConnected ? ( + + ) : ( + + )} +
+ + {rosError && ( + + + + {rosError} + + + )} + + {!rosConnected && !rosConnecting && ( +
+ + + + Connect to ROS bridge for live robot monitoring and + control. + + +
+ )} +
+ + + + {/* Movement Controls */} + {rosConnected && ( +
+
Movement
+
+ {/* Row 1: Turn Left, Forward, Turn Right */} + + + + + {/* Row 2: Left, Stop, Right */} + + + + + {/* Row 3: Empty, Back, Empty */} +
+ +
+
+
+ )} + + + + {/* Quick Actions */} + {rosConnected && ( +
+
Quick Actions
+ + {/* TTS Input */} +
+ { + if (e.key === "Enter" && e.currentTarget.value.trim()) { + executeRosAction("nao6-ros2", "say_text", { + text: e.currentTarget.value.trim(), + }).catch(console.error); + e.currentTarget.value = ""; + } + }} + /> + +
+ + {/* Preset Actions */} +
+ + +
+
+ )}
- +
); -}); +}; export { WizardMonitoringPanel }; diff --git a/src/hooks/useWizardRos.ts b/src/hooks/useWizardRos.ts index 99c3f01..e94d82a 100644 --- a/src/hooks/useWizardRos.ts +++ b/src/hooks/useWizardRos.ts @@ -107,12 +107,15 @@ export function useWizardRos( if (!service) return; const handleConnected = () => { - if (!mountedRef.current) return; - console.log("[useWizardRos] Connected to ROS bridge"); + console.log("[useWizardRos] handleConnected called, mountedRef:", mountedRef.current); + // Set state immediately, before checking mounted status setIsConnected(true); setIsConnecting(false); setConnectionError(null); - onConnectedRef.current?.(); + + if (mountedRef.current) { + onConnectedRef.current?.(); + } }; const handleDisconnected = () => {