add help mode

This commit is contained in:
2026-02-01 23:27:00 -05:00
parent 5b7d4e79fe
commit 54c34b6f7d
14 changed files with 543 additions and 92 deletions

View File

@@ -1,7 +1,7 @@
"use client";
import React, { useState, useEffect, useCallback, useMemo } from "react";
import { Play, CheckCircle, X, Clock, AlertCircle } from "lucide-react";
import { Play, CheckCircle, X, Clock, AlertCircle, HelpCircle } from "lucide-react";
import { useRouter } from "next/navigation";
import { Badge } from "~/components/ui/badge";
import { Progress } from "~/components/ui/progress";
@@ -19,6 +19,7 @@ import {
import { api } from "~/trpc/react";
import { useWizardRos } from "~/hooks/useWizardRos";
import { toast } from "sonner";
import { useTour } from "~/components/onboarding/TourProvider";
interface WizardInterfaceProps {
trial: {
@@ -77,6 +78,7 @@ export const WizardInterface = React.memo(function WizardInterface({
trial: initialTrial,
userRole: _userRole,
}: WizardInterfaceProps) {
const { startTour } = useTour();
const [trial, setTrial] = useState(initialTrial);
const [currentStepIndex, setCurrentStepIndex] = useState(0);
const [trialStartTime, setTrialStartTime] = useState<Date | null>(
@@ -654,6 +656,13 @@ export const WizardInterface = React.memo(function WizardInterface({
>
{rosConnected ? "ROS Connected" : "ROS Offline"}
</Badge>
<button
onClick={() => startTour("wizard")}
className="hover:bg-muted p-1 rounded-full transition-colors"
title="Start Tour"
>
<HelpCircle className="h-4 w-4" />
</button>
</div>
</div>
</div>
@@ -664,59 +673,65 @@ export const WizardInterface = React.memo(function WizardInterface({
<ResizablePanel defaultSize={75} minSize={30}>
<PanelsContainer
left={
<WizardControlPanel
trial={trial}
currentStep={currentStep}
steps={steps}
currentStepIndex={currentStepIndex}
onStartTrial={handleStartTrial}
onPauseTrial={handlePauseTrial}
onNextStep={handleNextStep}
onCompleteTrial={handleCompleteTrial}
onAbortTrial={handleAbortTrial}
onExecuteAction={handleExecuteAction}
onExecuteRobotAction={handleExecuteRobotAction}
studyId={trial.experiment.studyId}
_isConnected={rosConnected}
activeTab={controlPanelTab}
onTabChange={setControlPanelTab}
isStarting={startTrialMutation.isPending}
onSetAutonomousLife={setAutonomousLife}
readOnly={trial.status === 'completed' || _userRole === 'observer'}
/>
<div id="tour-wizard-controls" className="h-full">
<WizardControlPanel
trial={trial}
currentStep={currentStep}
steps={steps}
currentStepIndex={currentStepIndex}
onStartTrial={handleStartTrial}
onPauseTrial={handlePauseTrial}
onNextStep={handleNextStep}
onCompleteTrial={handleCompleteTrial}
onAbortTrial={handleAbortTrial}
onExecuteAction={handleExecuteAction}
onExecuteRobotAction={handleExecuteRobotAction}
studyId={trial.experiment.studyId}
_isConnected={rosConnected}
activeTab={controlPanelTab}
onTabChange={setControlPanelTab}
isStarting={startTrialMutation.isPending}
onSetAutonomousLife={setAutonomousLife}
readOnly={trial.status === 'completed' || _userRole === 'observer'}
/>
</div>
}
center={
<WizardExecutionPanel
trial={trial}
currentStep={currentStep}
steps={steps}
currentStepIndex={currentStepIndex}
trialEvents={trialEvents}
onStepSelect={(index: number) => setCurrentStepIndex(index)}
onExecuteAction={handleExecuteAction}
onExecuteRobotAction={handleExecuteRobotAction}
activeTab={executionPanelTab}
onTabChange={setExecutionPanelTab}
onSkipAction={handleSkipAction}
isExecuting={isExecutingAction}
onNextStep={handleNextStep}
completedActionsCount={completedActionsCount}
onActionCompleted={() => setCompletedActionsCount(c => c + 1)}
onCompleteTrial={handleCompleteTrial}
readOnly={trial.status === 'completed' || _userRole === 'observer'}
/>
<div id="tour-wizard-timeline" className="h-full">
<WizardExecutionPanel
trial={trial}
currentStep={currentStep}
steps={steps}
currentStepIndex={currentStepIndex}
trialEvents={trialEvents}
onStepSelect={(index: number) => setCurrentStepIndex(index)}
onExecuteAction={handleExecuteAction}
onExecuteRobotAction={handleExecuteRobotAction}
activeTab={executionPanelTab}
onTabChange={setExecutionPanelTab}
onSkipAction={handleSkipAction}
isExecuting={isExecutingAction}
onNextStep={handleNextStep}
completedActionsCount={completedActionsCount}
onActionCompleted={() => setCompletedActionsCount(c => c + 1)}
onCompleteTrial={handleCompleteTrial}
readOnly={trial.status === 'completed' || _userRole === 'observer'}
/>
</div>
}
right={
<WizardMonitoringPanel
rosConnected={rosConnected}
rosConnecting={rosConnecting}
rosError={rosError ?? undefined}
robotStatus={robotStatus}
connectRos={connectRos}
disconnectRos={disconnectRos}
executeRosAction={executeRosAction}
readOnly={trial.status === 'completed' || _userRole === 'observer'}
/>
<div id="tour-wizard-robot-status" className="h-full">
<WizardMonitoringPanel
rosConnected={rosConnected}
rosConnecting={rosConnecting}
rosError={rosError ?? undefined}
robotStatus={robotStatus}
connectRos={connectRos}
disconnectRos={disconnectRos}
executeRosAction={executeRosAction}
readOnly={trial.status === 'completed' || _userRole === 'observer'}
/>
</div>
}
showDividers={true}
className="h-full"

View File

@@ -30,12 +30,14 @@ interface WizardObservationPaneProps {
tags?: string[],
) => Promise<void>;
isSubmitting?: boolean;
readOnly?: boolean;
}
export function WizardObservationPane({
onAddAnnotation,
isSubmitting = false,
trialEvents = [],
readOnly = false,
}: WizardObservationPaneProps & { trialEvents?: TrialEvent[] }) {
const [note, setNote] = useState("");
const [category, setCategory] = useState("observation");
@@ -82,15 +84,16 @@ export function WizardObservationPane({
<TabsContent value="notes" className="flex-1 flex flex-col p-4 m-0 data-[state=inactive]:hidden">
<div className="flex flex-1 flex-col gap-2">
<Textarea
placeholder="Type your observation here..."
placeholder={readOnly ? "Session is read-only" : "Type your observation here..."}
className="flex-1 resize-none font-mono text-sm"
value={note}
onChange={(e) => setNote(e.target.value)}
onKeyDown={handleKeyDown}
disabled={readOnly}
/>
<div className="flex items-center gap-2">
<Select value={category} onValueChange={setCategory}>
<Select value={category} onValueChange={setCategory} disabled={readOnly}>
<SelectTrigger className="w-[140px] h-8 text-xs">
<SelectValue placeholder="Category" />
</SelectTrigger>
@@ -104,11 +107,11 @@ export function WizardObservationPane({
</Select>
<div className="flex flex-1 items-center gap-2 rounded-md border px-2 h-8">
<Tag className="h-3 w-3 text-muted-foreground" />
<Tag className={`h-3 w-3 ${readOnly ? "text-muted-foreground/50" : "text-muted-foreground"}`} />
<input
type="text"
placeholder="Add tags..."
className="flex-1 bg-transparent text-xs outline-none placeholder:text-muted-foreground"
placeholder={readOnly ? "" : "Add tags..."}
className="flex-1 bg-transparent text-xs outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed"
value={currentTag}
onChange={(e) => setCurrentTag(e.target.value)}
onKeyDown={(e) => {
@@ -118,13 +121,14 @@ export function WizardObservationPane({
}
}}
onBlur={addTag}
disabled={readOnly}
/>
</div>
<Button
size="sm"
onClick={handleSubmit}
disabled={isSubmitting || !note.trim()}
disabled={isSubmitting || !note.trim() || readOnly}
className="h-8"
>
<Send className="mr-2 h-3 w-3" />