nao6 ros2 integration updated

This commit is contained in:
2025-11-13 10:58:45 -05:00
parent 70882b9dbb
commit 86b5ed80c4
276 changed files with 4288 additions and 1552 deletions

0
src/components/admin/AdminContent.tsx Normal file → Executable file
View File

0
src/components/admin/admin-user-table.tsx Normal file → Executable file
View File

0
src/components/admin/repositories-columns.tsx Normal file → Executable file
View File

0
src/components/admin/repositories-data-table.tsx Normal file → Executable file
View File

0
src/components/admin/role-management.tsx Normal file → Executable file
View File

0
src/components/admin/system-stats.tsx Normal file → Executable file
View File

0
src/components/dashboard/DashboardContent.tsx Normal file → Executable file
View File

0
src/components/dashboard/app-sidebar.tsx Normal file → Executable file
View File

0
src/components/dashboard/study-guard.tsx Normal file → Executable file
View File

0
src/components/experiments/ExperimentForm.tsx Normal file → Executable file
View File

0
src/components/experiments/ExperimentsGrid.tsx Normal file → Executable file
View File

0
src/components/experiments/ExperimentsTable.tsx Normal file → Executable file
View File

0
src/components/experiments/designer/ActionRegistry.ts Normal file → Executable file
View File

View File

0
src/components/experiments/designer/DesignerRoot.tsx Normal file → Executable file
View File

View File

0
src/components/experiments/designer/StepPreview.tsx Normal file → Executable file
View File

View File

View File

View File

View File

View File

View File

0
src/components/experiments/designer/state/hashing.ts Normal file → Executable file
View File

0
src/components/experiments/designer/state/store.ts Normal file → Executable file
View File

View File

0
src/components/experiments/experiments-columns.tsx Normal file → Executable file
View File

0
src/components/experiments/experiments-data-table.tsx Normal file → Executable file
View File

0
src/components/participants/ParticipantForm.tsx Normal file → Executable file
View File

0
src/components/participants/ParticipantsTable.tsx Normal file → Executable file
View File

0
src/components/participants/ParticipantsView.tsx Normal file → Executable file
View File

0
src/components/plugins/plugin-store-browse.tsx Normal file → Executable file
View File

0
src/components/plugins/plugins-columns.tsx Normal file → Executable file
View File

0
src/components/plugins/plugins-data-table.tsx Normal file → Executable file
View File

0
src/components/profile/password-change-form.tsx Normal file → Executable file
View File

0
src/components/profile/profile-edit-form.tsx Normal file → Executable file
View File

0
src/components/studies/InviteMemberDialog.tsx Normal file → Executable file
View File

0
src/components/studies/StudiesGrid.tsx Normal file → Executable file
View File

0
src/components/studies/StudiesTable.tsx Normal file → Executable file
View File

0
src/components/studies/StudyCard.tsx Normal file → Executable file
View File

0
src/components/studies/StudyForm.tsx Normal file → Executable file
View File

0
src/components/studies/studies-columns.tsx Normal file → Executable file
View File

0
src/components/studies/studies-data-table.tsx Normal file → Executable file
View File

0
src/components/theme/index.ts Normal file → Executable file
View File

0
src/components/theme/theme-provider.tsx Normal file → Executable file
View File

0
src/components/theme/theme-script.tsx Normal file → Executable file
View File

0
src/components/theme/theme-toggle.tsx Normal file → Executable file
View File

0
src/components/theme/toaster.tsx Normal file → Executable file
View File

0
src/components/trials/TrialForm.tsx Normal file → Executable file
View File

0
src/components/trials/TrialsGrid.tsx Normal file → Executable file
View File

0
src/components/trials/TrialsTable.tsx Normal file → Executable file
View File

0
src/components/trials/execution/EventsLog.tsx Normal file → Executable file
View File

0
src/components/trials/views/ObserverView.tsx Normal file → Executable file
View File

0
src/components/trials/views/ParticipantView.tsx Normal file → Executable file
View File

0
src/components/trials/views/WizardView.tsx Normal file → Executable file
View File

0
src/components/trials/wizard/ActionControls.tsx Normal file → Executable file
View File

0
src/components/trials/wizard/EventsLogSidebar.tsx Normal file → Executable file
View File

0
src/components/trials/wizard/ExecutionStepDisplay.tsx Normal file → Executable file
View File

0
src/components/trials/wizard/ParticipantInfo.tsx Normal file → Executable file
View File

0
src/components/trials/wizard/RobotActionsPanel.tsx Normal file → Executable file
View File

0
src/components/trials/wizard/RobotStatus.tsx Normal file → Executable file
View File

0
src/components/trials/wizard/StepDisplay.tsx Normal file → Executable file
View File

0
src/components/trials/wizard/TrialProgress.tsx Normal file → Executable file
View File

64
src/components/trials/wizard/WizardInterface.tsx Normal file → Executable file
View File

@@ -1,6 +1,6 @@
"use client";
import React, { useState, useEffect } from "react";
import React, { useState, useEffect, useCallback, useMemo } from "react";
import { Play, CheckCircle, X, Clock, AlertCircle } from "lucide-react";
import { Badge } from "~/components/ui/badge";
import { Progress } from "~/components/ui/progress";
@@ -55,7 +55,7 @@ interface StepData {
order: number;
}
export function WizardInterface({
export const WizardInterface = React.memo(function WizardInterface({
trial: initialTrial,
userRole: _userRole,
}: WizardInterfaceProps) {
@@ -120,34 +120,54 @@ export function WizardInterface({
const { data: pollingData } = api.trials.get.useQuery(
{ id: trial.id },
{
refetchInterval: 2000, // Poll every 2 seconds
refetchInterval: trial.status === "in_progress" ? 10000 : 30000, // Poll less frequently
staleTime: 5000, // Consider data fresh for 5 seconds
refetchOnWindowFocus: false, // Don't refetch on window focus
},
);
// Mock trial events for now (can be populated from database later)
const trialEvents: Array<{
type: string;
timestamp: Date;
data?: unknown;
message?: string;
}> = [];
// Memoized trial events to prevent re-creation on every render
const trialEvents = useMemo<
Array<{
type: string;
timestamp: Date;
data?: unknown;
message?: string;
}>
>(() => [], []);
// Update trial data from polling
React.useEffect(() => {
if (pollingData) {
setTrial({
...pollingData,
metadata: pollingData.metadata as Record<string, unknown> | null,
// Update trial data from polling (optimized to prevent unnecessary re-renders)
const updateTrial = useCallback((newTrialData: typeof pollingData) => {
if (!newTrialData) return;
setTrial((prevTrial) => {
// Only update if data actually changed
if (
prevTrial.id === newTrialData.id &&
prevTrial.status === newTrialData.status &&
prevTrial.startedAt === newTrialData.startedAt &&
prevTrial.completedAt === newTrialData.completedAt
) {
return prevTrial; // No changes, keep existing state
}
return {
...newTrialData,
metadata: newTrialData.metadata as Record<string, unknown> | null,
participant: {
...pollingData.participant,
demographics: pollingData.participant.demographics as Record<
...newTrialData.participant,
demographics: newTrialData.participant.demographics as Record<
string,
unknown
> | null,
},
});
}
}, [pollingData]);
};
});
}, []);
useEffect(() => {
updateTrial(pollingData);
}, [pollingData, updateTrial]);
// Transform experiment steps to component format
const steps: StepData[] =
@@ -438,6 +458,6 @@ export function WizardInterface({
</div>
</div>
);
}
});
export default WizardInterface;

0
src/components/trials/wizard/panels/ExecutionPanel.tsx Normal file → Executable file
View File

View File

View File

View File

View File

View File

@@ -1,6 +1,6 @@
"use client";
import React from "react";
import React, { useState, useRef } from "react";
import {
Bot,
User,
@@ -21,7 +21,6 @@ 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";
// import { useRosBridge } from "~/hooks/useRosBridge"; // Removed ROS dependency
interface TrialData {
id: string;
@@ -64,7 +63,7 @@ interface WizardMonitoringPanelProps {
onTabChange: (tab: "status" | "robot" | "events") => void;
}
export function WizardMonitoringPanel({
const WizardMonitoringPanel = React.memo(function WizardMonitoringPanel({
trial,
trialEvents,
isConnected,
@@ -72,32 +71,340 @@ export function WizardMonitoringPanel({
activeTab,
onTabChange,
}: WizardMonitoringPanelProps) {
// Mock robot status for development (ROS bridge removed for now)
const mockRobotStatus = {
// ROS Bridge connection state
const [rosConnected, setRosConnected] = useState(false);
const [rosConnecting, setRosConnecting] = useState(false);
const [rosError, setRosError] = useState<string | null>(null);
const [rosSocket, setRosSocket] = useState<WebSocket | null>(null);
const [robotStatus, setRobotStatus] = useState({
connected: false,
battery: 85,
battery: 0,
position: { x: 0, y: 0, theta: 0 },
joints: {},
sensors: {},
lastUpdate: new Date(),
});
const ROS_BRIDGE_URL = "ws://134.82.159.25:9090";
// Use refs to persist connection state across re-renders
const connectionAttemptRef = useRef(false);
const socketRef = useRef<WebSocket | null>(null);
const connectRos = () => {
// Prevent multiple connection attempts
if (connectionAttemptRef.current) {
console.log("Connection already in progress, skipping");
return;
}
if (
rosSocket?.readyState === WebSocket.OPEN ||
socketRef.current?.readyState === WebSocket.OPEN
) {
console.log("Already connected, skipping");
return;
}
// Prevent rapid reconnection attempts
if (rosConnecting) {
console.log("Connection in progress, please wait");
return;
}
connectionAttemptRef.current = true;
setRosConnecting(true);
setRosError(null);
console.log("🔌 Connecting to ROS Bridge:", ROS_BRIDGE_URL);
const socket = new WebSocket(ROS_BRIDGE_URL);
socketRef.current = socket;
// Add connection timeout
const connectionTimeout = setTimeout(() => {
if (socket.readyState === WebSocket.CONNECTING) {
socket.close();
connectionAttemptRef.current = false;
setRosConnecting(false);
setRosError("Connection timeout (10s) - ROS Bridge not responding");
}
}, 10000);
socket.onopen = () => {
clearTimeout(connectionTimeout);
connectionAttemptRef.current = false;
console.log("Connected to ROS Bridge successfully");
setRosConnected(true);
setRosConnecting(false);
setRosSocket(socket);
setRosError(null);
// Just log connection success - no auto actions
console.log("WebSocket connected successfully to ROS Bridge");
setRobotStatus((prev) => ({
...prev,
connected: true,
lastUpdate: new Date(),
}));
};
socket.onmessage = (event) => {
try {
const data = JSON.parse(event.data as string) as {
topic?: string;
msg?: Record<string, unknown>;
op?: string;
level?: string;
};
// Handle status messages
if (data.op === "status") {
console.log("ROS Bridge status:", data.msg, "Level:", data.level);
return;
}
// Handle topic messages
if (data.topic === "/joint_states" && data.msg) {
setRobotStatus((prev) => ({
...prev,
joints: data.msg ?? {},
lastUpdate: new Date(),
}));
} else if (data.topic === "/naoqi_driver/battery" && data.msg) {
const batteryPercent = (data.msg.percentage as number) || 0;
setRobotStatus((prev) => ({
...prev,
battery: Math.round(batteryPercent),
lastUpdate: new Date(),
}));
} else if (data.topic === "/diagnostics" && data.msg) {
// Handle diagnostic messages for battery
console.log("Diagnostics received:", data.msg);
}
} catch (error) {
console.error("Error parsing ROS message:", error);
}
};
socket.onclose = (event) => {
clearTimeout(connectionTimeout);
connectionAttemptRef.current = false;
setRosConnected(false);
setRosConnecting(false);
setRosSocket(null);
socketRef.current = null;
setRobotStatus((prev) => ({
...prev,
connected: false,
battery: 0,
joints: {},
sensors: {},
}));
// Only show error if it wasn't a normal closure (code 1000)
if (event.code !== 1000) {
let errorMsg = "Connection lost";
if (event.code === 1006) {
errorMsg =
"ROS Bridge not responding - check if rosbridge_server is running";
} else if (event.code === 1011) {
errorMsg = "Server error in ROS Bridge";
} else if (event.code === 1002) {
errorMsg = "Protocol error - check ROS Bridge version";
} else if (event.code === 1001) {
errorMsg = "Server going away - ROS Bridge may have restarted";
}
console.log(
`🔌 Connection closed - Code: ${event.code}, Reason: ${event.reason}`,
);
setRosError(`${errorMsg} (${event.code})`);
}
};
socket.onerror = (error) => {
clearTimeout(connectionTimeout);
connectionAttemptRef.current = false;
console.error("ROS Bridge WebSocket error:", error);
setRosConnected(false);
setRosConnecting(false);
setRosError(
"Failed to connect to ROS bridge - check if rosbridge_server is running",
);
setRobotStatus((prev) => ({ ...prev, connected: false }));
};
};
const disconnectRos = () => {
console.log("Manually disconnecting from ROS Bridge");
connectionAttemptRef.current = false;
if (rosSocket) {
// Close with normal closure code to avoid error messages
rosSocket.close(1000, "User disconnected");
}
if (socketRef.current) {
socketRef.current.close(1000, "User disconnected");
}
// Clear all state
setRosSocket(null);
socketRef.current = null;
setRosConnected(false);
setRosConnecting(false);
setRosError(null);
setRobotStatus({
connected: false,
battery: 0,
position: { x: 0, y: 0, theta: 0 },
joints: {},
sensors: {},
lastUpdate: new Date(),
});
};
const rosConnected = false;
const rosConnecting = false;
const rosError = null;
const robotStatus = mockRobotStatus;
// const connectRos = () => console.log("ROS connection not implemented yet");
const disconnectRos = () =>
console.log("ROS disconnection not implemented yet");
const executeRobotAction = (
action: string,
parameters?: Record<string, unknown>,
) => console.log("Robot action:", action, parameters);
) => {
if (!rosSocket || !rosConnected) {
setRosError("Robot not connected");
return;
}
const formatTimestamp = (timestamp: Date) => {
return new Date(timestamp).toLocaleTimeString();
let message: {
op: string;
topic: string;
type: string;
msg: Record<string, unknown>;
};
switch (action) {
case "say_text":
const speechText = parameters?.text ?? "Hello from wizard interface!";
console.log("🔊 Preparing speech command:", speechText);
message = {
op: "publish",
topic: "/speech",
type: "std_msgs/String",
msg: { data: speechText },
};
console.log(
"📤 Speech message constructed:",
JSON.stringify(message, null, 2),
);
break;
case "move_forward":
case "move_backward":
case "turn_left":
case "turn_right":
const speed = (parameters?.speed as number) || 0.1;
const linear = action.includes("forward")
? speed
: action.includes("backward")
? -speed
: 0;
const angular = action.includes("left")
? speed
: action.includes("right")
? -speed
: 0;
message = {
op: "publish",
topic: "/cmd_vel",
type: "geometry_msgs/Twist",
msg: {
linear: { x: linear, y: 0, z: 0 },
angular: { x: 0, y: 0, z: angular },
},
};
break;
case "stop_movement":
message = {
op: "publish",
topic: "/cmd_vel",
type: "geometry_msgs/Twist",
msg: {
linear: { x: 0, y: 0, z: 0 },
angular: { x: 0, y: 0, z: 0 },
},
};
break;
case "head_movement":
case "turn_head":
const yaw = (parameters?.yaw as number) || 0;
const pitch = (parameters?.pitch as number) || 0;
const headSpeed = (parameters?.speed as number) || 0.3;
message = {
op: "publish",
topic: "/joint_angles",
type: "naoqi_bridge_msgs/JointAnglesWithSpeed",
msg: {
joint_names: ["HeadYaw", "HeadPitch"],
joint_angles: [yaw, pitch],
speed: headSpeed,
},
};
break;
case "play_animation":
const animation = (parameters?.animation as string) ?? "Hello";
message = {
op: "publish",
topic: "/naoqi_driver/animation",
type: "std_msgs/String",
msg: { data: animation },
};
break;
default:
setRosError(`Unknown action: ${String(action)}`);
return;
}
try {
const messageStr = JSON.stringify(message);
console.log("📡 Sending to ROS Bridge:", messageStr);
rosSocket.send(messageStr);
console.log(`✅ Sent robot action: ${action}`, parameters);
} catch (error) {
console.error("❌ Failed to send command:", error);
setRosError(`Failed to send command: ${String(error)}`);
}
};
const subscribeToTopic = (topic: string, messageType: string) => {
if (!rosSocket || !rosConnected) {
setRosError("Cannot subscribe - not connected");
return;
}
try {
const subscribeMsg = {
op: "subscribe",
topic: topic,
type: messageType,
};
rosSocket.send(JSON.stringify(subscribeMsg));
console.log(`Manually subscribed to ${topic}`);
} catch (error) {
setRosError(`Failed to subscribe to ${topic}: ${String(error)}`);
}
};
// 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":
@@ -370,16 +677,36 @@ export function WizardMonitoringPanel({
<span className="text-muted-foreground text-xs">
ROS Bridge
</span>
<Badge
variant={rosConnected ? "default" : "outline"}
className="text-xs"
>
{rosConnecting
? "Connecting..."
: rosConnected
? "Connected"
: "Offline"}
</Badge>
<div className="flex items-center gap-1">
<Badge
variant={
rosConnected
? "default"
: rosError
? "destructive"
: "outline"
}
className="text-xs"
>
{rosConnecting
? "Connecting..."
: rosConnected
? "Ready"
: rosError
? "Failed"
: "Offline"}
</Badge>
{rosConnected && (
<span className="animate-pulse text-xs text-green-600">
</span>
)}
{rosConnecting && (
<span className="animate-spin text-xs text-blue-600">
</span>
)}
</div>
</div>
<div className="flex items-center justify-between">
<span className="text-muted-foreground text-xs">
@@ -387,12 +714,18 @@ export function WizardMonitoringPanel({
</span>
<div className="flex items-center gap-1">
<span className="text-xs">
{robotStatus
? `${Math.round(robotStatus.battery * 100)}%`
: "--"}
{robotStatus && robotStatus.battery > 0
? `${Math.round(robotStatus.battery)}%`
: rosConnected
? "Reading..."
: "No data"}
</span>
<Progress
value={robotStatus ? robotStatus.battery * 100 : 0}
value={
robotStatus && robotStatus.battery > 0
? robotStatus.battery
: 0
}
className="h-1 w-8"
/>
</div>
@@ -426,13 +759,15 @@ export function WizardMonitoringPanel({
size="sm"
variant="outline"
className="w-full text-xs"
onClick={() =>
console.log("Connect robot (not implemented)")
}
disabled={true}
onClick={connectRos}
disabled={rosConnecting || rosConnected}
>
<Bot className="mr-1 h-3 w-3" />
Connect Robot (Coming Soon)
{rosConnecting
? "Connecting..."
: rosConnected
? "Connected ✓"
: "Connect to NAO6"}
</Button>
) : (
<Button
@@ -442,7 +777,7 @@ export function WizardMonitoringPanel({
onClick={disconnectRos}
>
<PowerOff className="mr-1 h-3 w-3" />
Disconnect Robot
Disconnect
</Button>
)}
</div>
@@ -451,7 +786,29 @@ export function WizardMonitoringPanel({
<Alert variant="destructive" className="mt-2">
<AlertCircle className="h-4 w-4" />
<AlertDescription className="text-xs">
ROS Error: {rosError}
{rosError}
</AlertDescription>
</Alert>
)}
{/* Connection Help */}
{!rosConnected && !rosConnecting && (
<Alert className="mt-2">
<AlertCircle className="h-4 w-4" />
<AlertDescription className="text-xs">
<div className="space-y-1">
<div className="font-medium">Troubleshooting:</div>
<div>
1. Check ROS Bridge:{" "}
<code className="bg-muted rounded px-1 text-xs">
telnet 134.82.159.25 9090
</code>
</div>
<div>2. NAO6 must be awake and connected</div>
<div>
3. Try: Click Connect Wait 2s Test Speech
</div>
</div>
</AlertDescription>
</Alert>
)}
@@ -538,48 +895,119 @@ export function WizardMonitoringPanel({
</div>
</div>
{/* Quick Robot Actions */}
{/* Manual Subscription Controls */}
{rosConnected && (
<div className="space-y-2">
<div className="text-sm font-medium">Quick Actions</div>
<div className="grid grid-cols-2 gap-1">
<div className="text-sm font-medium">Manual Controls</div>
{/* Connection Test */}
<div className="grid grid-cols-1 gap-1">
<Button
size="sm"
variant="outline"
className="text-xs"
onClick={() =>
executeRobotAction("say_text", {
text: "Hello from wizard!",
text: "Connection test - can you hear me?",
})
}
disabled={!rosConnected}
>
Say Hello
{rosConnected ? "🔊 Test Speech" : "🔊 Not Ready"}
</Button>
</div>
{/* Topic Subscriptions */}
<div className="space-y-1">
<div className="text-muted-foreground text-xs font-medium">
Subscribe to Topics:
</div>
<div className="grid grid-cols-1 gap-1">
<Button
size="sm"
variant="ghost"
className="justify-start text-xs"
onClick={() =>
subscribeToTopic(
"/naoqi_driver/battery",
"naoqi_bridge_msgs/Battery",
)
}
>
🔋 Battery Status
</Button>
<Button
size="sm"
variant="ghost"
className="justify-start text-xs"
onClick={() =>
subscribeToTopic(
"/naoqi_driver/joint_states",
"sensor_msgs/JointState",
)
}
>
🤖 Joint States
</Button>
<Button
size="sm"
variant="ghost"
className="justify-start text-xs"
onClick={() =>
subscribeToTopic(
"/naoqi_driver/bumper",
"naoqi_bridge_msgs/Bumper",
)
}
>
👟 Bumper Sensors
</Button>
</div>
</div>
</div>
)}
{/* Quick Robot Actions */}
{rosConnected && (
<div className="space-y-2">
<div className="text-sm font-medium">Robot Actions</div>
{/* Movement Controls */}
<div className="grid grid-cols-3 gap-1">
<Button
size="sm"
variant="outline"
className="text-xs"
onClick={() =>
executeRobotAction("move_forward", { speed: 0.05 })
}
>
Forward
</Button>
<Button
size="sm"
variant="outline"
className="text-xs"
onClick={() =>
executeRobotAction("play_animation", {
animation: "Hello",
})
executeRobotAction("turn_left", { speed: 0.3 })
}
>
Wave
Turn Left
</Button>
<Button
size="sm"
variant="outline"
className="text-xs"
onClick={() =>
executeRobotAction("set_led_color", {
color: "blue",
intensity: 1.0,
})
executeRobotAction("turn_right", { speed: 0.3 })
}
>
Blue LEDs
Turn Right
</Button>
</div>
{/* Head Controls */}
<div className="grid grid-cols-3 gap-1">
<Button
size="sm"
variant="outline"
@@ -594,18 +1022,88 @@ export function WizardMonitoringPanel({
>
Center Head
</Button>
<Button
size="sm"
variant="outline"
className="text-xs"
onClick={() =>
executeRobotAction("turn_head", {
yaw: 0.5,
pitch: 0,
speed: 0.3,
})
}
>
Look Left
</Button>
<Button
size="sm"
variant="outline"
className="text-xs"
onClick={() =>
executeRobotAction("turn_head", {
yaw: -0.5,
pitch: 0,
speed: 0.3,
})
}
>
Look Right
</Button>
</div>
{/* Animation & LED Controls */}
<div className="grid grid-cols-2 gap-1">
<Button
size="sm"
variant="outline"
className="text-xs"
onClick={() =>
executeRobotAction("play_animation", {
animation: "Hello",
})
}
>
Wave Hello
</Button>
<Button
size="sm"
variant="outline"
className="text-xs"
onClick={() =>
executeRobotAction("say_text", {
text: "Experiment ready!",
})
}
>
Say Ready
</Button>
</div>
{/* Emergency Controls */}
<div className="grid grid-cols-1 gap-1">
<Button
size="sm"
variant="destructive"
className="text-xs"
onClick={() => executeRobotAction("stop_movement", {})}
>
🛑 Emergency Stop
</Button>
</div>
</div>
)}
{!rosConnected && !rosConnecting && (
<Alert className="mt-4">
<AlertCircle className="h-4 w-4" />
<AlertDescription className="text-xs">
Connect to ROS bridge for live robot monitoring and
control
</AlertDescription>
</Alert>
<div className="mt-4">
<Alert>
<AlertCircle className="h-4 w-4" />
<AlertDescription className="text-xs">
Connect to ROS bridge for live robot monitoring and
control
</AlertDescription>
</Alert>
</div>
)}
</div>
</ScrollArea>
@@ -669,4 +1167,6 @@ export function WizardMonitoringPanel({
</Tabs>
</div>
);
}
});
export { WizardMonitoringPanel };

0
src/components/ui/accordion.tsx Normal file → Executable file
View File

0
src/components/ui/alert-dialog.tsx Normal file → Executable file
View File

0
src/components/ui/alert.tsx Normal file → Executable file
View File

0
src/components/ui/avatar.tsx Normal file → Executable file
View File

0
src/components/ui/badge.tsx Normal file → Executable file
View File

0
src/components/ui/breadcrumb-provider.tsx Normal file → Executable file
View File

0
src/components/ui/breadcrumb.tsx Normal file → Executable file
View File

0
src/components/ui/button.tsx Normal file → Executable file
View File

0
src/components/ui/card.tsx Normal file → Executable file
View File

0
src/components/ui/checkbox.tsx Normal file → Executable file
View File

0
src/components/ui/collapsible.tsx Normal file → Executable file
View File

0
src/components/ui/command.tsx Normal file → Executable file
View File

0
src/components/ui/data-table-column-header.tsx Normal file → Executable file
View File

0
src/components/ui/data-table-pagination.tsx Normal file → Executable file
View File

0
src/components/ui/data-table-view-options.tsx Normal file → Executable file
View File

0
src/components/ui/data-table.tsx Normal file → Executable file
View File

0
src/components/ui/dialog.tsx Normal file → Executable file
View File

0
src/components/ui/dropdown-menu.tsx Normal file → Executable file
View File

0
src/components/ui/entity-form.tsx Normal file → Executable file
View File

0
src/components/ui/entity-view.tsx Normal file → Executable file
View File

0
src/components/ui/file-upload.tsx Normal file → Executable file
View File

0
src/components/ui/form.tsx Normal file → Executable file
View File

0
src/components/ui/input.tsx Normal file → Executable file
View File

0
src/components/ui/label.tsx Normal file → Executable file
View File

0
src/components/ui/logo.tsx Normal file → Executable file
View File

0
src/components/ui/page-header.tsx Normal file → Executable file
View File

0
src/components/ui/page-layout.tsx Normal file → Executable file
View File

0
src/components/ui/progress.tsx Normal file → Executable file
View File

0
src/components/ui/resizable.tsx Normal file → Executable file
View File

0
src/components/ui/scroll-area.tsx Normal file → Executable file
View File

Some files were not shown because too many files have changed in this diff Show More