Spaces:
Sleeping
Sleeping
| /** | |
| * TaskManagerPanel — displays and manages background tasks. | |
| * Uses real JSON API endpoint /api/tasks/:sessionId | |
| */ | |
| import { useState, useEffect, useCallback } from "react"; | |
| import { Play, Square, RefreshCw, Clock, CheckCircle, XCircle, AlertCircle, Loader2 } from "lucide-react"; | |
| interface Task { | |
| id: string; | |
| description: string; | |
| command?: string; | |
| status: "running" | "completed" | "stopped" | "failed"; | |
| createdAt: number; | |
| output: string; | |
| } | |
| interface TaskManagerPanelProps { | |
| sessionId: number; | |
| onSlashCommand: (command: string, args?: string) => Promise<string>; | |
| } | |
| export function TaskManagerPanel({ sessionId }: TaskManagerPanelProps) { | |
| const [tasks, setTasks] = useState<Task[]>([]); | |
| const [loading, setLoading] = useState(false); | |
| const [expandedTask, setExpandedTask] = useState<string | null>(null); | |
| const refreshTasks = useCallback(async () => { | |
| setLoading(true); | |
| try { | |
| const res = await fetch(`/api/tasks/${sessionId}`); | |
| if (res.ok) { | |
| const data = await res.json(); | |
| if (Array.isArray(data)) setTasks(data); | |
| } | |
| } catch { | |
| // ignore fetch errors | |
| } finally { | |
| setLoading(false); | |
| } | |
| }, [sessionId]); | |
| useEffect(() => { | |
| refreshTasks(); | |
| // Auto-refresh every 3 seconds while panel is open | |
| const interval = setInterval(refreshTasks, 3000); | |
| return () => clearInterval(interval); | |
| }, [refreshTasks]); | |
| const statusIcon = (status: string) => { | |
| switch (status) { | |
| case "running": return <Loader2 className="w-4 h-4 text-blue-400 animate-spin" />; | |
| case "completed": return <CheckCircle className="w-4 h-4 text-green-400" />; | |
| case "stopped": return <Square className="w-4 h-4 text-yellow-400" />; | |
| case "failed": return <XCircle className="w-4 h-4 text-red-400" />; | |
| default: return <AlertCircle className="w-4 h-4 text-muted-foreground" />; | |
| } | |
| }; | |
| return ( | |
| <div className="flex flex-col h-full"> | |
| <div className="flex items-center justify-between px-4 py-3 border-b border-border"> | |
| <h3 className="text-sm font-semibold flex items-center gap-2"> | |
| <Play className="w-4 h-4" /> | |
| Background Tasks ({tasks.length}) | |
| </h3> | |
| <button | |
| onClick={refreshTasks} | |
| disabled={loading} | |
| className="p-1.5 rounded hover:bg-accent transition-colors" | |
| > | |
| <RefreshCw className={`w-4 h-4 ${loading ? "animate-spin" : ""}`} /> | |
| </button> | |
| </div> | |
| <div className="flex-1 overflow-y-auto"> | |
| {tasks.length === 0 ? ( | |
| <div className="flex flex-col items-center justify-center h-full text-muted-foreground text-sm gap-2 p-4"> | |
| <Clock className="w-8 h-8 opacity-40" /> | |
| <p>No background tasks</p> | |
| <p className="text-xs opacity-60">Tasks created via TaskCreate tool will appear here</p> | |
| </div> | |
| ) : ( | |
| <div className="divide-y divide-border"> | |
| {tasks.map((task) => ( | |
| <div key={task.id} className="p-3"> | |
| <button | |
| onClick={() => setExpandedTask(expandedTask === task.id ? null : task.id)} | |
| className="w-full flex items-start gap-2 text-left" | |
| > | |
| {statusIcon(task.status)} | |
| <div className="flex-1 min-w-0"> | |
| <p className="text-sm font-medium truncate">{task.description}</p> | |
| <p className="text-xs text-muted-foreground"> | |
| {task.id} · {new Date(task.createdAt).toLocaleTimeString()} | |
| {task.command && ` · ${task.command.substring(0, 40)}${task.command.length > 40 ? "..." : ""}`} | |
| </p> | |
| </div> | |
| <span className={`text-xs px-1.5 py-0.5 rounded ${ | |
| task.status === "running" ? "bg-blue-500/20 text-blue-400" : | |
| task.status === "completed" ? "bg-green-500/20 text-green-400" : | |
| task.status === "failed" ? "bg-red-500/20 text-red-400" : | |
| "bg-yellow-500/20 text-yellow-400" | |
| }`}> | |
| {task.status} | |
| </span> | |
| </button> | |
| {expandedTask === task.id && ( | |
| <pre className="mt-2 p-2 rounded bg-muted text-xs font-mono whitespace-pre-wrap max-h-40 overflow-y-auto"> | |
| {task.output || "(no output yet)"} | |
| </pre> | |
| )} | |
| </div> | |
| ))} | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |