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 (
)
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 && (
)}
)
})}
)}
)
}