import { useEffect, useState } from 'react' import { useTaskStore } from '../../stores/taskStore' import { useChatStore } from '../../stores/chatStore' import { useTabStore } from '../../stores/tabStore' import { useTranslation } from '../../i18n' import { parseRunOutput } from '../../lib/parseRunOutput' import type { TaskRun } from '../../types/task' function RunOutput({ run }: { run: TaskRun }) { const t = useTranslation() // Show error prominently if present if (run.error) { return (
{run.error}
) } const text = parseRunOutput(run.output || '') if (!text) { return (
{run.sessionId ? t('tasks.outputHintSession') : t('tasks.noOutputText')}
) } // Render AI text response with proper formatting (not monospace
)
  return (
    
{text}
) } type Props = { taskId: string onClose: () => void refreshKey?: number } const STATUS_CONFIG: Record = { running: { icon: 'sync', color: 'var(--color-warning)' }, completed: { icon: 'check_circle', color: 'var(--color-success)' }, failed: { icon: 'error', color: 'var(--color-error)' }, timeout: { icon: 'timer_off', color: 'var(--color-error)' }, } export function TaskRunsPanel({ taskId, onClose, refreshKey }: Props) { const t = useTranslation() const { fetchTaskRuns } = useTaskStore() const connectToSession = useChatStore((s) => s.connectToSession) const openTab = useTabStore((s) => s.openTab) const [runs, setRuns] = useState([]) const [loading, setLoading] = useState(true) const [expandedId, setExpandedId] = useState(null) const openSession = (sessionId: string, taskName?: string) => { openTab(sessionId, taskName || 'Task Run') connectToSession(sessionId) } const refresh = () => { fetchTaskRuns(taskId).then((r) => { setRuns(r) setLoading(false) }).catch(() => setLoading(false)) } // Initial fetch + re-fetch when refreshKey changes useEffect(() => { setLoading(true) refresh() }, [taskId, fetchTaskRuns, refreshKey]) // Auto-poll while any run is "running" or shortly after a manual trigger. // Uses faster 1s polling for the first 10s after refreshKey changes, then 3s. const hasRunning = runs.some((r) => r.status === 'running') useEffect(() => { if (!hasRunning && refreshKey === 0) return // no reason to poll initially // Start with fast polling (1s) to give snappy feedback after "Run Now" let interval = 1000 let timer = setInterval(refresh, interval) // After 10s, switch to slower 3s polling if still running const slowDown = setTimeout(() => { clearInterval(timer) if (hasRunning) { timer = setInterval(refresh, 3000) } }, 10000) // If nothing is running and initial window passes, stop entirely const stopTimer = hasRunning ? undefined : setTimeout(() => clearInterval(timer), 12000) return () => { clearInterval(timer) clearTimeout(slowDown) if (stopTimer) clearTimeout(stopTimer) } }, [hasRunning, taskId, refreshKey]) return (
{/* Header */}
{t('tasks.logsTitle')}
{/* Content */}
{loading ? (
) : runs.length === 0 ? (
{t('tasks.noLogs')}
) : (
{runs.map((run) => { const cfg = STATUS_CONFIG[run.status] || STATUS_CONFIG.failed! const isExpanded = expandedId === run.id return (
{/* Status icon */} {cfg.icon} {/* Status text */} {t(`tasks.runStatus.${run.status}` as any)} {/* dynamic key */} {/* Time */} {new Date(run.startedAt).toLocaleString()} {/* Duration */} {run.durationMs != null && ( {t('tasks.duration', { s: Math.round(run.durationMs / 1000) })} )}
{/* Open session — only after run completes (session is empty while running) */} {run.sessionId && run.status !== 'running' && ( )} {/* Summary toggle */} {(run.output || run.error) && ( )}
{/* Expanded output */} {isExpanded && ( )}
) })}
)}
) }