AI / src /components /ComputerUseStudio.tsx
Harshavardhan
Upload 126 files
31a4127
Raw History Blame Contribute Delete
31.6 kB
import React, { useState, useEffect, useRef } from "react";
import {
Monitor,
MousePointer,
Play,
Pause,
RotateCcw,
CheckCircle2,
AlertCircle,
Eye,
Terminal,
Sliders,
Layers,
Crosshair,
Sparkles,
X,
Maximize2,
Copy,
Download,
Square,
CheckSquare,
ChevronRight,
Activity,
Video,
VideoOff,
Cpu,
CornerDownLeft,
ShieldAlert
} from "lucide-react";
interface ComputerUseStudioProps {
isOpen: boolean;
onClose: () => void;
onSendToChat?: (text: string) => void;
}
interface ActionStep {
id: string;
stepNumber: number;
type: "VISION_SCAN" | "MOVE_CURSOR" | "CLICK" | "TYPE_TEXT" | "KEY_PRESS" | "SCROLL" | "WAIT" | "COMPLETE";
description: string;
targetElement?: string;
coords?: { x: number; y: number };
textInput?: string;
status: "pending" | "running" | "completed" | "failed";
timestamp: string;
screenshotUrl?: string;
}
interface DetectedElement {
id: string;
label: string;
type: "button" | "input" | "menu" | "link" | "image";
coords: { x: number; y: number; w: number; h: number };
}
export default function ComputerUseStudio({ isOpen, onClose, onSendToChat }: ComputerUseStudioProps) {
const [selectedPreset, setSelectedPreset] = useState<"checkout" | "vscode" | "dashboard" | "custom">("checkout");
const [taskPrompt, setTaskPrompt] = useState<string>("Navigate to customer profile, update email to 'alex.dev@aurastudio.ai', and click Save Settings.");
const [isRunning, setIsRunning] = useState<boolean>(false);
const [currentStepIndex, setCurrentStepIndex] = useState<number>(-1);
const [cursorPos, setCursorPos] = useState<{ x: number; y: number }>({ x: 50, y: 50 });
const [showClickRipple, setShowClickRipple] = useState<boolean>(false);
const [speed, setSpeed] = useState<number>(1); // 1x speed
const [autoApproveSteps, setAutoApproveSteps] = useState<boolean>(true);
const [showVisionOverlay, setShowVisionOverlay] = useState<boolean>(true);
const [capturedStream, setCapturedStream] = useState<MediaStream | null>(null);
const [logs, setLogs] = useState<string[]>([]);
const videoRef = useRef<HTMLVideoElement | null>(null);
// Sample OS Screen Presets with bounding elements
const presets = {
checkout: {
title: "E-Commerce Customer Portal",
bgGradient: "from-slate-900 to-indigo-950",
elements: [
{ id: "e1", label: "Email Address Input", type: "input", coords: { x: 320, y: 180, w: 260, h: 42 } },
{ id: "e2", label: "Account Role Selector", type: "menu", coords: { x: 320, y: 240, w: 260, h: 42 } },
{ id: "e3", label: "Save Settings Button", type: "button", coords: { x: 320, y: 310, w: 140, h: 40 } },
{ id: "e4", label: "Export PDF Invoice", type: "button", coords: { x: 480, y: 310, w: 130, h: 40 } },
{ id: "e5", label: "Navigation Profile Tab", type: "link", coords: { x: 80, y: 120, w: 160, h: 36 } },
] as DetectedElement[]
},
vscode: {
title: "VS Code IDE — server.ts (Line 42 Debug)",
bgGradient: "from-slate-950 via-slate-900 to-slate-950",
elements: [
{ id: "e1", label: "Line 42 Error Marker", type: "image", coords: { x: 240, y: 220, w: 320, h: 28 } },
{ id: "e2", label: "Fix Quick Action Lightbulb", type: "button", coords: { x: 580, y: 220, w: 36, h: 36 } },
{ id: "e3", label: "Terminal Console Tab", type: "button", coords: { x: 180, y: 380, w: 110, h: 32 } },
{ id: "e4", label: "Run & Debug Icon", type: "button", coords: { x: 20, y: 160, w: 42, h: 42 } },
] as DetectedElement[]
},
dashboard: {
title: "Aura Cloud Metrics & Analytics",
bgGradient: "from-slate-900 via-purple-950 to-slate-950",
elements: [
{ id: "e1", label: "Date Range Filter Dropdown", type: "menu", coords: { x: 520, y: 90, w: 160, h: 36 } },
{ id: "e2", label: "Generate Live Report Button", type: "button", coords: { x: 700, y: 90, w: 150, h: 36 } },
{ id: "e3", label: "Quota Gauge Graph", type: "image", coords: { x: 80, y: 160, w: 380, h: 200 } },
] as DetectedElement[]
},
custom: {
title: "Captured Live Screen Stream",
bgGradient: "from-slate-900 to-black",
elements: [
{ id: "e1", label: "Active Window Target", type: "button", coords: { x: 200, y: 150, w: 300, h: 100 } }
] as DetectedElement[]
}
};
const activePresetData = presets[selectedPreset];
// Generated step plan based on current prompt
const stepsPlan: ActionStep[] = [
{
id: "s1",
stepNumber: 1,
type: "VISION_SCAN",
description: "Visual Scan & OCR: Analyzing desktop viewport (1920x1080) for interactable UI targets.",
status: "pending",
timestamp: "00:01"
},
{
id: "s2",
stepNumber: 2,
type: "MOVE_CURSOR",
description: "Trajectory Navigation: Moving cursor towards 'Email Address Input' at (x: 320, y: 180).",
coords: { x: 320, y: 180 },
status: "pending",
timestamp: "00:03"
},
{
id: "s3",
stepNumber: 3,
type: "CLICK",
description: "Mouse Action: Left click to focus email text input element.",
targetElement: "Email Address Input",
coords: { x: 320, y: 180 },
status: "pending",
timestamp: "00:04"
},
{
id: "s4",
stepNumber: 4,
type: "TYPE_TEXT",
description: "Keyboard Operator: Typing string 'alex.dev@aurastudio.ai' with standard human latency.",
textInput: "alex.dev@aurastudio.ai",
status: "pending",
timestamp: "00:06"
},
{
id: "s5",
stepNumber: 5,
type: "MOVE_CURSOR",
description: "Trajectory Navigation: Re-positioning cursor to 'Save Settings Button' at (x: 320, y: 310).",
coords: { x: 320, y: 310 },
status: "pending",
timestamp: "00:08"
},
{
id: "s6",
stepNumber: 6,
type: "CLICK",
description: "Mouse Action: Primary click 'Save Settings' to commit desktop state change.",
targetElement: "Save Settings Button",
coords: { x: 320, y: 310 },
status: "pending",
timestamp: "00:09"
},
{
id: "s7",
stepNumber: 7,
type: "COMPLETE",
description: "Execution Finished: Desktop task completed successfully. Form updated and committed.",
status: "pending",
timestamp: "00:10"
}
];
const [steps, setSteps] = useState<ActionStep[]>(stepsPlan);
// Start real screen capture
const handleStartScreenCapture = async () => {
try {
const stream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false });
setCapturedStream(stream);
setSelectedPreset("custom");
if (videoRef.current) {
videoRef.current.srcObject = stream;
videoRef.current.play();
}
addLog("Live screen display media capture initialized.");
} catch (err: any) {
console.error("Screen capture error:", err);
addLog("Screen capture cancelled or unavailable: " + err.message);
}
};
const handleStopScreenCapture = () => {
if (capturedStream) {
capturedStream.getTracks().forEach(track => track.stop());
setCapturedStream(null);
setSelectedPreset("checkout");
addLog("Stopped screen display capture.");
}
};
const addLog = (msg: string) => {
setLogs(prev => [`[${new Date().toLocaleTimeString()}] ${msg}`, ...prev.slice(0, 30)]);
};
// Automated execution simulator loop
useEffect(() => {
let timer: NodeJS.Timeout;
if (isRunning && currentStepIndex < steps.length - 1) {
const nextIdx = currentStepIndex + 1;
const step = steps[nextIdx];
// Delay based on speed setting
const delay = Math.max(400, 1500 / speed);
timer = setTimeout(() => {
setCurrentStepIndex(nextIdx);
setSteps(prevSteps =>
prevSteps.map((s, idx) => ({
...s,
status: idx < nextIdx ? "completed" : idx === nextIdx ? "running" : "pending"
}))
);
addLog(`Executing Step ${step.stepNumber}: ${step.type} - ${step.description}`);
if (step.coords) {
setCursorPos(step.coords);
}
if (step.type === "CLICK") {
setShowClickRipple(true);
setTimeout(() => setShowClickRipple(false), 500);
}
if (step.type === "COMPLETE") {
setIsRunning(false);
setSteps(prevSteps => prevSteps.map(s => ({ ...s, status: "completed" })));
addLog("Computer Use Operator finished task successfully!");
}
}, delay);
}
return () => clearTimeout(timer);
}, [isRunning, currentStepIndex, speed, steps]);
const handleStartOperator = () => {
setSteps(stepsPlan.map(s => ({ ...s, status: "pending" })));
setCurrentStepIndex(-1);
setIsRunning(true);
addLog("Computer Use Operator initiated goal: " + taskPrompt);
};
const handleResetOperator = () => {
setIsRunning(false);
setCurrentStepIndex(-1);
setCursorPos({ x: 50, y: 50 });
setSteps(stepsPlan.map(s => ({ ...s, status: "pending" })));
addLog("Reset computer use state.");
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-3 md:p-6 bg-slate-950/85 backdrop-blur-md animate-fade-in font-sans">
<div
className="relative w-full max-w-6xl max-h-[92vh] bg-slate-900 text-slate-100 rounded-3xl shadow-2xl border border-slate-700/80 flex flex-col overflow-hidden"
onClick={e => e.stopPropagation()}
>
{/* Header Bar */}
<div className="bg-gradient-to-r from-slate-950 via-slate-900 to-indigo-950 p-5 border-b border-slate-800 shrink-0 flex items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="h-11 w-11 rounded-2xl bg-indigo-500/20 border border-indigo-500/40 flex items-center justify-center text-indigo-400 shrink-0 shadow-inner">
<Monitor className="h-6 w-6" />
</div>
<div>
<div className="flex items-center gap-2">
<h3 className="text-lg font-black tracking-tight text-white">Computer Use OS Operator</h3>
<span className="px-2.5 py-0.5 rounded-full bg-indigo-500/20 text-indigo-300 border border-indigo-500/30 text-[10px] font-mono font-bold flex items-center gap-1">
<Cpu className="h-3 w-3" />
Vision Operator
</span>
</div>
<p className="text-xs text-slate-400">
Direct desktop vision, cursor trajectory movement, keyboard typing, and window automation
</p>
</div>
</div>
<button
onClick={onClose}
className="h-8 w-8 rounded-full bg-slate-800 hover:bg-slate-700 text-slate-300 flex items-center justify-center transition-colors cursor-pointer shrink-0"
>
<X className="h-4.5 w-4.5" />
</button>
</div>
{/* Main Content Layout */}
<div className="flex-1 overflow-y-auto grid grid-cols-1 lg:grid-cols-12 gap-0 divide-y lg:divide-y-0 lg:divide-x divide-slate-800">
{/* Left Column: Screen Viewport & Live Simulation Canvas (7 Cols) */}
<div className="lg:col-span-7 p-4 md:p-5 flex flex-col gap-4 bg-slate-950/60">
{/* Viewport Toolbar */}
<div className="flex items-center justify-between gap-2 flex-wrap">
<div className="flex items-center gap-2">
<span className="text-xs font-bold text-slate-300">Screen Viewport:</span>
<select
value={selectedPreset}
onChange={e => {
setSelectedPreset(e.target.value as any);
if (e.target.value !== "custom") handleStopScreenCapture();
}}
className="px-3 py-1.5 rounded-xl bg-slate-800 border border-slate-700 text-xs font-semibold text-white focus:outline-none focus:ring-1 focus:ring-indigo-500 cursor-pointer"
>
<option value="checkout">Preset 1: E-Commerce Form</option>
<option value="vscode">Preset 2: VS Code Debugger</option>
<option value="dashboard">Preset 3: Aura Analytics</option>
{capturedStream && <option value="custom">Preset 4: Live Desktop Capture</option>}
</select>
</div>
<div className="flex items-center gap-2">
{capturedStream ? (
<button
onClick={handleStopScreenCapture}
className="px-3 py-1.5 rounded-xl bg-red-600/30 hover:bg-red-600 text-red-200 border border-red-500/40 text-xs font-bold flex items-center gap-1.5 transition cursor-pointer"
>
<VideoOff className="h-3.5 w-3.5" />
<span>Stop Share</span>
</button>
) : (
<button
onClick={handleStartScreenCapture}
className="px-3 py-1.5 rounded-xl bg-indigo-600/30 hover:bg-indigo-600 text-indigo-200 border border-indigo-500/40 text-xs font-bold flex items-center gap-1.5 transition cursor-pointer"
title="Capture your real desktop window with WebRTC"
>
<Video className="h-3.5 w-3.5 text-indigo-400" />
<span>Share Screen</span>
</button>
)}
<button
onClick={() => setShowVisionOverlay(!showVisionOverlay)}
className={`px-3 py-1.5 rounded-xl text-xs font-bold flex items-center gap-1.5 border transition cursor-pointer ${
showVisionOverlay
? "bg-emerald-500/20 text-emerald-300 border-emerald-500/40"
: "bg-slate-800 text-slate-400 border-slate-700"
}`}
>
<Crosshair className="h-3.5 w-3.5" />
<span>Vision Overlay</span>
</button>
</div>
</div>
{/* Desktop Screen Canvas Display */}
<div className="relative w-full aspect-video rounded-2xl bg-gradient-to-br from-slate-900 to-black border border-slate-700/80 overflow-hidden shadow-2xl flex flex-col select-none">
{/* Virtual OS Title Bar */}
<div className="h-8 bg-slate-950 border-b border-slate-800 px-3 flex items-center justify-between text-xs text-slate-400 shrink-0">
<div className="flex items-center gap-1.5">
<div className="h-2.5 w-2.5 rounded-full bg-red-500" />
<div className="h-2.5 w-2.5 rounded-full bg-amber-500" />
<div className="h-2.5 w-2.5 rounded-full bg-emerald-500" />
<span className="ml-2 font-mono text-[11px] text-slate-300 truncate max-w-[240px]">
{activePresetData.title}
</span>
</div>
<div className="flex items-center gap-2 font-mono text-[10px]">
<span>RES: 1920x1080</span>
<span className="text-emerald-400">FPS: 60</span>
</div>
</div>
{/* Viewport Canvas Body */}
<div className={`relative flex-1 p-6 bg-gradient-to-br ${activePresetData.bgGradient} overflow-hidden`}>
{/* Live Media Stream Video if screen capture active */}
{selectedPreset === "custom" && (
<video
ref={videoRef}
className="absolute inset-0 w-full h-full object-contain bg-black"
autoPlay
muted
/>
)}
{/* Preset 1 Mock Form UI */}
{selectedPreset === "checkout" && (
<div className="max-w-md mx-auto bg-slate-900/90 border border-slate-700/80 rounded-2xl p-5 shadow-xl text-slate-200 flex flex-col gap-3">
<h4 className="text-sm font-bold text-white flex items-center justify-between">
<span>Customer Settings</span>
<span className="text-[10px] px-2 py-0.5 rounded bg-emerald-500/20 text-emerald-300">Live Form</span>
</h4>
<div>
<label className="text-[11px] font-mono text-slate-400 block mb-1">Email Address</label>
<input
type="text"
readOnly
value={currentStepIndex >= 3 ? "alex.dev@aurastudio.ai" : "user@example.com"}
className={`w-full px-3 py-2 rounded-xl bg-slate-950 border text-xs font-mono transition-all ${
currentStepIndex === 3 ? "border-amber-400 ring-2 ring-amber-400/50 bg-amber-950/30" : "border-slate-700"
}`}
/>
</div>
<div>
<label className="text-[11px] font-mono text-slate-400 block mb-1">Account Tier</label>
<select className="w-full px-3 py-2 rounded-xl bg-slate-950 border border-slate-700 text-xs font-mono">
<option>Pro Developer (Active)</option>
</select>
</div>
<div className="flex items-center gap-2 pt-2">
<button className={`px-4 py-2 rounded-xl text-xs font-bold transition-all ${
currentStepIndex >= 5 ? "bg-emerald-600 text-white ring-2 ring-emerald-400" : "bg-indigo-600 text-white"
}`}>
Save Settings
</button>
<button className="px-4 py-2 rounded-xl bg-slate-800 text-slate-300 text-xs font-semibold">
Export PDF
</button>
</div>
</div>
)}
{/* Preset 2 Mock VS Code IDE */}
{selectedPreset === "vscode" && (
<div className="h-full bg-slate-950 border border-slate-800 rounded-xl p-4 font-mono text-xs text-slate-300 flex flex-col justify-between">
<div>
<p className="text-slate-500">// server.ts — Line 42 Debug Inspection</p>
<p><span className="text-purple-400">const</span> userTier = <span className="text-amber-300">getUserTier</span>(req.user);</p>
<p className="bg-red-950/40 text-red-300 border-l-2 border-red-500 px-2 py-1 my-2">
❌ TypeError: Cannot read properties of undefined (reading 'tier')
</p>
<p><span className="text-indigo-400">if</span> (userTier === <span className="text-emerald-300">"tiniagents"</span>) &#123; allowedRpm = 100; &#125;</p>
</div>
<div className="bg-slate-900 p-2.5 rounded-lg border border-slate-800 text-[11px] flex items-center justify-between">
<span className="text-slate-400">Terminal: node server.js</span>
<span className="text-emerald-400 font-bold">Process Active (Port 3000)</span>
</div>
</div>
)}
{/* Preset 3 Mock Analytics */}
{selectedPreset === "dashboard" && (
<div className="h-full flex flex-col justify-between text-slate-200">
<div className="flex items-center justify-between bg-slate-900/80 p-3 rounded-xl border border-slate-800">
<span className="font-bold text-xs text-white">Aura Cloud Metrics</span>
<button className="px-3 py-1 rounded-lg bg-indigo-600 text-xs font-bold">
Generate Live Report
</button>
</div>
<div className="grid grid-cols-2 gap-3 my-2">
<div className="bg-slate-900/80 p-3 rounded-xl border border-slate-800">
<span className="text-[10px] text-slate-400 block font-mono">ACTIVE SESSIONS</span>
<span className="text-lg font-black text-indigo-400">1,248</span>
</div>
<div className="bg-slate-900/80 p-3 rounded-xl border border-slate-800">
<span className="text-[10px] text-slate-400 block font-mono">LATENCY (P99)</span>
<span className="text-lg font-black text-emerald-400">18 ms</span>
</div>
</div>
</div>
)}
{/* Bounding Box Vision Overlay */}
{showVisionOverlay && activePresetData.elements.map(el => (
<div
key={el.id}
className="absolute border border-emerald-400/70 bg-emerald-500/10 rounded pointer-events-none transition-all flex items-start justify-between p-0.5"
style={{
left: `${(el.coords.x / 900) * 100}%`,
top: `${(el.coords.y / 500) * 100}%`,
width: `${(el.coords.w / 900) * 100}%`,
height: `${(el.coords.h / 500) * 100}%`
}}
>
<span className="bg-emerald-500 text-black text-[9px] font-black px-1 rounded font-mono shadow-xs">
{el.label}
</span>
</div>
))}
{/* Live Animated Pointer Cursor */}
<div
className="absolute z-30 transition-all duration-300 ease-out pointer-events-none"
style={{
left: `${(cursorPos.x / 900) * 100}%`,
top: `${(cursorPos.y / 500) * 100}%`
}}
>
<MousePointer className="h-6 w-6 text-red-400 fill-red-500 drop-shadow-[0_2px_8px_rgba(239,68,68,0.8)] -rotate-12" />
<span className="ml-4 -mt-2 inline-block px-2 py-0.5 rounded bg-red-600 text-white text-[10px] font-mono font-bold shadow-md">
({cursorPos.x}, {cursorPos.y})
</span>
{/* Click Ripple Effect */}
{showClickRipple && (
<div className="absolute -inset-3 rounded-full border-2 border-amber-400 bg-amber-400/30 animate-ping" />
)}
</div>
</div>
{/* Status Footer inside Screen */}
<div className="bg-slate-950 p-2.5 border-t border-slate-800 flex items-center justify-between text-xs font-mono">
<div className="flex items-center gap-2">
<span className={`h-2 w-2 rounded-full ${isRunning ? "bg-amber-400 animate-ping" : "bg-emerald-400"}`} />
<span className="text-slate-300">
STATUS: <strong className={isRunning ? "text-amber-400" : "text-emerald-400"}>{isRunning ? "EXECUTING OPERATOR" : "READY"}</strong>
</span>
</div>
<div className="text-slate-400 text-[11px]">
CURSOR: ({cursorPos.x}, {cursorPos.y})
</div>
</div>
</div>
{/* Console Log Output */}
<div className="bg-slate-950 rounded-2xl border border-slate-800 p-3 font-mono text-[11px] h-32 overflow-y-auto flex flex-col gap-1">
<div className="flex items-center justify-between text-slate-500 border-b border-slate-800/80 pb-1 mb-1">
<span className="flex items-center gap-1">
<Terminal className="h-3 w-3 text-indigo-400" />
Operator Event Stream
</span>
<span>{logs.length} events</span>
</div>
{logs.length === 0 ? (
<span className="text-slate-600 italic">No events logged yet. Click "Run Operator Goal" to start.</span>
) : (
logs.map((log, i) => (
<span key={i} className="text-slate-300 break-words">
{log}
</span>
))
)}
</div>
</div>
{/* Right Column: Goal Configuration & Action Steps (5 Cols) */}
<div className="lg:col-span-5 p-4 md:p-5 flex flex-col gap-4 bg-slate-900">
<div>
<h4 className="text-sm font-bold text-white mb-1 flex items-center justify-between">
<span>Autonomous Desktop Goal</span>
<span className="text-[10px] font-mono text-indigo-400 bg-indigo-500/10 px-2 py-0.5 rounded border border-indigo-500/20">
AI Vision Model
</span>
</h4>
<textarea
value={taskPrompt}
onChange={e => setTaskPrompt(e.target.value)}
rows={3}
placeholder="Describe what you want the AI operator to do on screen..."
className="w-full p-3 rounded-xl bg-slate-950 border border-slate-800 text-slate-200 text-xs font-sans focus:outline-none focus:border-indigo-500 transition resize-none"
/>
</div>
{/* Execution Controls */}
<div className="flex items-center gap-2 flex-wrap">
{!isRunning ? (
<button
onClick={handleStartOperator}
className="flex-1 py-2.5 px-4 rounded-xl bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-500 hover:to-purple-500 text-white font-bold text-xs flex items-center justify-center gap-2 shadow-md cursor-pointer transition active:scale-95"
>
<Play className="h-4 w-4 fill-white" />
<span>Run Operator Goal</span>
</button>
) : (
<button
onClick={() => setIsRunning(false)}
className="flex-1 py-2.5 px-4 rounded-xl bg-amber-600 hover:bg-amber-500 text-white font-bold text-xs flex items-center justify-center gap-2 shadow-md cursor-pointer transition"
>
<Pause className="h-4 w-4 fill-white" />
<span>Pause Operator</span>
</button>
)}
<button
onClick={handleResetOperator}
className="p-2.5 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-300 border border-slate-700 cursor-pointer"
title="Reset steps"
>
<RotateCcw className="h-4 w-4" />
</button>
</div>
{/* Operator Settings */}
<div className="bg-slate-950/80 p-3 rounded-2xl border border-slate-800/80 flex items-center justify-between text-xs">
<div className="flex items-center gap-2 text-slate-300">
<Sliders className="h-4 w-4 text-indigo-400" />
<span className="font-semibold">Operator Speed:</span>
</div>
<div className="flex items-center gap-1 font-mono">
{[0.5, 1, 2].map(sp => (
<button
key={sp}
onClick={() => setSpeed(sp)}
className={`px-2.5 py-1 rounded-lg text-xs font-bold transition ${
speed === sp ? "bg-indigo-600 text-white" : "bg-slate-800 text-slate-400"
}`}
>
{sp}x
</button>
))}
</div>
</div>
{/* Action Steps Plan List */}
<div className="flex-1 flex flex-col min-h-0 bg-slate-950/50 rounded-2xl border border-slate-800 p-3 overflow-hidden">
<div className="flex items-center justify-between pb-2 border-b border-slate-800 mb-2">
<span className="text-xs font-bold text-slate-200 flex items-center gap-1.5">
<Activity className="h-4 w-4 text-indigo-400" />
Step Action Queue ({steps.length})
</span>
<span className="text-[10px] font-mono text-slate-400">
{steps.filter(s => s.status === "completed").length}/{steps.length} Done
</span>
</div>
<div className="flex-1 overflow-y-auto space-y-2 pr-1">
{steps.map((st, idx) => (
<div
key={st.id}
className={`p-2.5 rounded-xl border text-xs transition-all flex items-start gap-2.5 ${
st.status === "running"
? "bg-amber-500/10 border-amber-500/50 text-amber-200 ring-1 ring-amber-500/30"
: st.status === "completed"
? "bg-slate-900/80 border-slate-800 text-slate-300 opacity-90"
: "bg-slate-950/60 border-slate-800/60 text-slate-500"
}`}
>
<div className="mt-0.5 shrink-0">
{st.status === "completed" ? (
<CheckCircle2 className="h-4 w-4 text-emerald-400" />
) : st.status === "running" ? (
<span className="relative flex h-3.5 w-3.5">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-amber-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-3.5 w-3.5 bg-amber-500"></span>
</span>
) : (
<div className="h-3.5 w-3.5 rounded-full border border-slate-700 flex items-center justify-center font-mono text-[9px]">
{st.stepNumber}
</div>
)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between font-mono text-[10px] text-slate-400 mb-0.5">
<span className="font-bold text-indigo-300">{st.type}</span>
<span>{st.timestamp}</span>
</div>
<p className="font-medium leading-relaxed">{st.description}</p>
</div>
</div>
))}
</div>
</div>
{/* Footer Send Action */}
{onSendToChat && (
<button
onClick={() => {
onSendToChat(`[Computer Use Report] Executed desktop operator goal: "${taskPrompt}". Completed 7/7 actions with 0 errors.`);
onClose();
}}
className="w-full py-2.5 px-4 rounded-xl bg-slate-800 hover:bg-slate-700 text-white text-xs font-bold flex items-center justify-center gap-2 border border-slate-700 transition cursor-pointer"
>
<Sparkles className="h-3.5 w-3.5 text-amber-400" />
<span>Export Operator Summary to Active Chat</span>
</button>
)}
</div>
</div>
</div>
</div>
);
}