Spaces:
Configuration error
Configuration error
| 'use client' | |
| import { useEffect, useState } from 'react' | |
| import { useAgentStore } from '@/hooks/useAgentStore' | |
| import { getTasks, fetchAPI } from '@/lib/api' | |
| const cancelTask = (id: string) => fetchAPI('/api/v1/tasks/' + id + '/cancel', { method: 'POST' }) | |
| const retryTask = (id: string) => fetchAPI('/api/v1/tasks/' + id + '/retry', { method: 'POST' }) | |
| import { ListTodo, Play, Square, RefreshCw, Clock, CheckCircle2, XCircle, Loader2, Zap } from 'lucide-react' | |
| import { formatDistanceToNow } from 'date-fns' | |
| const STATUS_META: Record<string, { icon: React.ElementType; color: string; label: string }> = { | |
| queued: { icon: Clock, color: '#94a3b8', label: 'Queued' }, | |
| executing: { icon: Loader2, color: '#6366f1', label: 'Running' }, | |
| completed: { icon: CheckCircle2, color: '#22c55e', label: 'Done' }, | |
| failed: { icon: XCircle, color: '#ef4444', label: 'Failed' }, | |
| cancelled: { icon: Square, color: '#64748b', label: 'Cancelled' }, | |
| } | |
| export default function TasksPanel() { | |
| const { sessionId, locale, setActiveTaskId, activeTaskId } = useAgentStore() | |
| const [tasks, setTasks] = useState<any[]>([]) | |
| const [loading, setLoading] = useState(true) | |
| const load = async () => { | |
| setLoading(true) | |
| try { | |
| const data = await getTasks() | |
| setTasks(Array.isArray(data) ? data : data.tasks || []) | |
| } catch {} | |
| setLoading(false) | |
| } | |
| useEffect(() => { load() }, [sessionId]) | |
| useEffect(() => { | |
| const id = setInterval(load, 5000) | |
| return () => clearInterval(id) | |
| }, [sessionId]) | |
| const handleCancel = async (taskId: string, e: React.MouseEvent) => { | |
| e.stopPropagation() | |
| await cancelTask(taskId) | |
| load() | |
| } | |
| const handleRetry = async (taskId: string, e: React.MouseEvent) => { | |
| e.stopPropagation() | |
| await retryTask(taskId) | |
| load() | |
| } | |
| return ( | |
| <div className="flex flex-col h-full" style={{ background: 'var(--bg-2)' }}> | |
| <div className="flex items-center justify-between px-4 py-2.5 border-b shrink-0" | |
| style={{ borderColor: 'var(--border)', background: 'var(--bg-3)' }}> | |
| <div className="flex items-center gap-2"> | |
| <ListTodo size={14} className="text-indigo-400" /> | |
| <span className="text-sm font-semibold" style={{ color: 'var(--text-primary)' }}> | |
| {locale === 'my' ? 'လုပ်ငန်းများ' : 'Tasks'} | |
| </span> | |
| {tasks.length > 0 && ( | |
| <span className="text-[10px] px-1.5 py-0.5 rounded-full" | |
| style={{ background: 'rgba(99,102,241,0.15)', color: '#818cf8', border: '1px solid rgba(99,102,241,0.3)' }}> | |
| {tasks.length} | |
| </span> | |
| )} | |
| </div> | |
| <button onClick={load} className="p-1.5 rounded-lg hover:bg-white/5 transition-colors"> | |
| <RefreshCw size={12} style={{ color: 'var(--text-muted)' }} className={loading ? 'animate-spin' : ''} /> | |
| </button> | |
| </div> | |
| <div className="flex-1 overflow-y-auto p-3 space-y-2"> | |
| {loading && tasks.length === 0 ? ( | |
| <div className="space-y-2"> | |
| {[...Array(3)].map((_, i) => ( | |
| <div key={i} className="h-20 rounded-xl shimmer" /> | |
| ))} | |
| </div> | |
| ) : tasks.length === 0 ? ( | |
| <div className="flex flex-col items-center justify-center h-full gap-3 py-8"> | |
| <div className="w-12 h-12 rounded-xl flex items-center justify-center" | |
| style={{ background: 'var(--bg-3)', border: '1px solid var(--border)' }}> | |
| <ListTodo size={20} style={{ color: 'var(--text-muted)' }} /> | |
| </div> | |
| <p className="text-sm" style={{ color: 'var(--text-secondary)' }}> | |
| {locale === 'my' ? 'လုပ်ငန်းမရှá€á€žá€±á€¸á€•ါ' : 'No tasks yet'} | |
| </p> | |
| </div> | |
| ) : ( | |
| tasks.map(task => { | |
| const sm = STATUS_META[task.status] || STATUS_META.queued | |
| const Icon = sm.icon | |
| const isActive = task.id === activeTaskId | |
| const isRunning = task.status === 'executing' || task.status === 'queued' | |
| return ( | |
| <button key={task.id} onClick={() => setActiveTaskId(isActive ? null : task.id)} | |
| className="w-full rounded-xl p-3 text-left transition-all hover:scale-[1.01] active:scale-[0.99]" | |
| style={{ | |
| background: isActive ? 'rgba(99,102,241,0.1)' : 'var(--bg-3)', | |
| border: `1px solid ${isActive ? 'rgba(99,102,241,0.4)' : 'var(--border)'}`, | |
| }}> | |
| <div className="flex items-start gap-2"> | |
| <div className="mt-0.5 flex-shrink-0"> | |
| <Icon size={13} style={{ | |
| color: sm.color, | |
| animation: task.status === 'executing' ? 'spin 1.5s linear infinite' : 'none', | |
| }} /> | |
| </div> | |
| <div className="flex-1 min-w-0"> | |
| <p className="text-xs font-medium truncate" style={{ color: 'var(--text-primary)' }}> | |
| {task.goal} | |
| </p> | |
| <div className="flex items-center gap-2 mt-1"> | |
| <span className="text-[9px] px-1.5 py-0.5 rounded-full font-medium" | |
| style={{ background: `${sm.color}15`, color: sm.color, border: `1px solid ${sm.color}30` }}> | |
| {sm.label} | |
| </span> | |
| <span className="text-[9px]" style={{ color: 'var(--text-muted)' }}> | |
| {formatDistanceToNow(new Date(task.created_at * 1000), { addSuffix: true })} | |
| </span> | |
| </div> | |
| <p className="text-[9px] mt-1 font-mono truncate" style={{ color: 'var(--text-muted)' }}> | |
| {task.id} | |
| </p> | |
| </div> | |
| {/* Actions */} | |
| <div className="flex gap-1 ml-1"> | |
| {isRunning && ( | |
| <button onClick={e => handleCancel(task.id, e)} | |
| className="p-1 rounded-lg hover:bg-red-500/10 transition-colors" title="Cancel"> | |
| <Square size={10} className="text-red-400" /> | |
| </button> | |
| )} | |
| {task.status === 'failed' && ( | |
| <button onClick={e => handleRetry(task.id, e)} | |
| className="p-1 rounded-lg hover:bg-indigo-500/10 transition-colors" title="Retry"> | |
| <RefreshCw size={10} className="text-indigo-400" /> | |
| </button> | |
| )} | |
| </div> | |
| </div> | |
| {/* Progress bar for running tasks */} | |
| {isRunning && ( | |
| <div className="mt-2 h-0.5 rounded-full overflow-hidden" style={{ background: 'var(--border)' }}> | |
| <div className="h-full rounded-full animate-pulse" style={{ background: sm.color, width: '60%' }} /> | |
| </div> | |
| )} | |
| </button> | |
| ) | |
| }) | |
| )} | |
| </div> | |
| </div> | |
| ) | |
| } | |