/** * HandTaskPanel - Hand 任务和结果面板 * * 显示选中 Hand 的任务清单、执行历史和结果。 * 使用真实 API 数据,移除了 Mock 数据。 */ import { useState, useEffect, useCallback } from 'react'; import { useHandStore, type Hand, type HandRun } from '../store/handStore'; import { Zap, Loader2, Clock, CheckCircle, XCircle, AlertCircle, ChevronRight, Play, ArrowLeft, RefreshCw, } from 'lucide-react'; import { useToast } from './ui/Toast'; interface HandTaskPanelProps { handId: string; onBack?: () => void; } // 任务状态配置 const RUN_STATUS_CONFIG: Record }> = { pending: { label: '等待中', className: 'text-gray-500 bg-gray-100', icon: Clock }, running: { label: '运行中', className: 'text-blue-600 bg-blue-100', icon: Loader2 }, completed: { label: '已完成', className: 'text-green-600 bg-green-100', icon: CheckCircle }, failed: { label: '失败', className: 'text-red-600 bg-red-100', icon: XCircle }, cancelled: { label: '已取消', className: 'text-gray-500 bg-gray-100', icon: XCircle }, needs_approval: { label: '待审批', className: 'text-yellow-600 bg-yellow-100', icon: AlertCircle }, success: { label: '成功', className: 'text-green-600 bg-green-100', icon: CheckCircle }, error: { label: '错误', className: 'text-red-600 bg-red-100', icon: XCircle }, }; export function HandTaskPanel({ handId, onBack }: HandTaskPanelProps) { const hands = useHandStore((s) => s.hands); const handRuns = useHandStore((s) => s.handRuns); const loadHands = useHandStore((s) => s.loadHands); const loadHandRuns = useHandStore((s) => s.loadHandRuns); const triggerHand = useHandStore((s) => s.triggerHand); const isLoading = useHandStore((s) => s.isLoading); const { toast } = useToast(); const [selectedHand, setSelectedHand] = useState(null); const [isActivating, setIsActivating] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); // Load hands on mount useEffect(() => { loadHands(); }, [loadHands]); // Find selected hand useEffect(() => { const hand = hands.find(h => h.id === handId || h.name === handId); setSelectedHand(hand || null); }, [hands, handId]); // Load task history when hand is selected useEffect(() => { if (selectedHand) { loadHandRuns(selectedHand.id, { limit: 50 }); } }, [selectedHand, loadHandRuns]); // Get runs for this hand from store const tasks: HandRun[] = selectedHand ? (handRuns[selectedHand.id] || []) : []; // Refresh task history const handleRefresh = useCallback(async () => { if (!selectedHand) return; setIsRefreshing(true); try { await loadHandRuns(selectedHand.id, { limit: 50 }); } finally { setIsRefreshing(false); } }, [selectedHand, loadHandRuns]); // Trigger hand execution const handleActivate = useCallback(async () => { if (!selectedHand) return; // Check if hand is already running if (selectedHand.status === 'running') { toast(`Hand "${selectedHand.name}" 正在运行中,请等待完成`, 'warning'); return; } setIsActivating(true); console.log(`[HandTaskPanel] Activating hand: ${selectedHand.id} (${selectedHand.name})`); try { const result = await triggerHand(selectedHand.id); console.log(`[HandTaskPanel] Activation result:`, result); if (result) { toast(`Hand "${selectedHand.name}" 已成功启动`, 'success'); // Refresh hands list and task history await Promise.all([ loadHands(), loadHandRuns(selectedHand.id, { limit: 50 }), ]); } else { // Check for specific error in store const storeError = useHandStore.getState().error; if (storeError?.includes('already active')) { toast(`Hand "${selectedHand.name}" 已在运行中`, 'warning'); } else { toast(`Hand "${selectedHand.name}" 启动失败: ${storeError || '未知错误'}`, 'error'); } } } catch (err) { const errorMsg = err instanceof Error ? err.message : String(err); console.error(`[HandTaskPanel] Activation error:`, errorMsg); if (errorMsg.includes('already active')) { toast(`Hand "${selectedHand.name}" 已在运行中`, 'warning'); } else { toast(`Hand "${selectedHand.name}" 启动异常: ${errorMsg}`, 'error'); } } finally { setIsActivating(false); } }, [selectedHand, triggerHand, loadHands, loadHandRuns, toast]); if (!selectedHand) { return (

请从左侧选择一个 Hand

); } const runningTasks = tasks.filter(t => t.status === 'running'); const completedTasks = tasks.filter(t => ['completed', 'success', 'failed', 'error', 'cancelled'].includes(t.status)); const pendingTasks = tasks.filter(t => ['pending', 'needs_approval'].includes(t.status)); return (
{/* 头部 */}
{onBack && ( )} {selectedHand.icon || '🤖'}

{selectedHand.name}

{selectedHand.description}

{/* 内容区域 */}
{/* 加载状态 */} {isLoading && tasks.length === 0 && (

加载任务历史中...

)} {/* 运行中的任务 */} {runningTasks.length > 0 && (

运行中 ({runningTasks.length})

{runningTasks.map(task => ( ))}
)} {/* 待处理任务 */} {pendingTasks.length > 0 && (

待处理 ({pendingTasks.length})

{pendingTasks.map(task => ( ))}
)} {/* 已完成任务 */} {completedTasks.length > 0 && (

历史记录 ({completedTasks.length})

{completedTasks.map(task => ( ))}
)} {/* 空状态 */} {!isLoading && tasks.length === 0 && (

暂无任务记录

点击"执行任务"按钮开始运行

)}
); } // 任务卡片组件 function TaskCard({ task, expanded = false }: { task: HandRun; expanded?: boolean }) { const [isExpanded, setIsExpanded] = useState(expanded); const config = RUN_STATUS_CONFIG[task.status] || RUN_STATUS_CONFIG.pending; const StatusIcon = config.icon; // Format result for display const resultText = task.result ? (typeof task.result === 'string' ? task.result : JSON.stringify(task.result, null, 2)) : undefined; return (
setIsExpanded(!isExpanded)} >
运行 #{task.runId.slice(0, 8)}
{config.label}
{/* 展开详情 */} {isExpanded && (
运行 ID {task.runId}
开始时间 {new Date(task.startedAt).toLocaleString()}
{task.completedAt && (
完成时间 {new Date(task.completedAt).toLocaleString()}
)} {resultText && (
{resultText}
)} {task.error && (
{task.error}
)}
)}
); } export default HandTaskPanel;