import { useEffect, useMemo, useRef } from 'react'; import type { AgentEvent, JobState, RunResult } from '../types'; import { useI18n, type MsgKey, type TFn } from '../i18n'; const STEPS = ['plan', 'implement', 'verify', 'fix', 'evaluate'] as const; function runSummary(r: RunResult, t: TFn, pct: (v: number | null | undefined) => string): string { if (r.timed_out) return t('flow.timeout'); if (!r.compiled) return t('flow.compileErr'); return t('flow.runSummary', { p: r.passed, t: r.total, cov: pct(r.coverage.percent) }); } function Item({ e }: { e: AgentEvent }) { const { t, pct, num, evTitle, pick } = useI18n(); const d = e.data; const title = evTitle(e); if (e.type === 'phase') { const n = STEPS.findIndex((s) => s === e.phase) + 1; return (
  • {n || '•'}
    {title}
    {d.agent &&
    {t('flow.agent', { a: pick(d.agent_i18n, d.agent) })}
    } {d.reason &&
    {t('flow.reason', { r: pick(d.reason_i18n, d.reason) })}
    }
  • ); } if (e.type === 'tool_call' && d.tool === 'write_tests') { return (
  • ✎
    write_tests — {t('flow.wrote', { n: d.lines })}
    {t('flow.showCode')}
    {d.code}
  • ); } if (e.type === 'tool_call' && d.tool === 'report_bug') { const ok = d.accepted !== false; return (
  • {ok ? '!' : '⊘'}
    report_bug — {ok ? t('flow.bugOk') : t('flow.bugRejected', { by: t(d.by === 'kural' ? 'flow.by.rule' : 'flow.by.judge') })}
    {d.test_name}: {d.reason}
    {d.verdict &&
    {t('flow.verdict')} {d.verdict}
    }
  • ); } if (e.type === 'tool_call') { const bad = d.tool === 'report_bug'; return (
  • {bad ? '!' : '›'}
    {title}
    {bad &&
    {d.test_name}: {d.reason}
    } {d.summary &&
    {d.summary}
    }
  • ); } if (e.type === 'tool_result' && d.run) { const r: RunResult = d.run; const failing = r.cases.filter((c) => c.status === 'failed' || c.status === 'error'); return (
  • {d.accepted ? '✓' : '✕'}
    {t('flow.roundResult', { n: d.iteration, summary: runSummary(r, t, pct) })}
    {t('flow.sandboxSec', { s: num(r.duration_s, 1) })}
    {(!r.compiled || failing.length > 0) && (
    {!r.compiled ? t('flow.compilerOut') : t('flow.nFailed', { n: failing.length })}
    {!r.compiled ? r.compile_output : failing.map((c) => `✕ ${c.name}\n${c.message}`).join('\n\n')}
    )}
  • ); } if (e.type === 'thought') { return (
  • …
    {title}
    {d.text &&
    {t('flow.showText')}
    {d.text}
    }
  • ); } if (e.type === 'result') { return (
  • ✓
    {title}
    {d.analysis?.test_plan?.length > 0 &&
    {t('flow.planItems', { n: d.analysis.test_plan.length })}
    }
  • ); } if (e.type === 'llm_progress') { return (
  • …
    {t('flow.generating')}
    {d.seconds ? t('flow.genProgress', { s: Math.round(d.seconds), c: d.chunks }) : t('flow.genLoading')}
  • ); } if (e.type === 'mutant') { return (
  • μ
    {t('flow.mutants', { d: d.done, t: d.total })}
  • ); } if (e.type === 'error') { return (
  • !
    {t('flow.error')}
    {d.message}
    {d.trace &&
    {t('flow.details')}
    {d.trace}
    }
  • ); } if (e.type === 'done' || e.type === 'status') { return (
  • {e.type === 'done' ? '■' : '▶'}
    {title}
    {pick(d.stop_reason_i18n, d.stop_reason || '') || (d.model ? t('flow.model', { m: d.model }) : '')}
  • ); } return null; } export default function AgentFlow({ events, job, running }: { events: AgentEvent[]; job: JobState | null; running: boolean }) { const { t, pct, num } = useI18n(); const endRef = useRef(null); const { visible, active, seen, llm, runs } = useMemo(() => { let lastMutant = -1, lastProgress = -1, lastLlm = -1; events.forEach((e, i) => { if (e.type === 'mutant') lastMutant = i; if (e.type === 'llm_progress') lastProgress = i; if (e.type === 'llm' || e.type === 'error' || e.type === 'done') lastLlm = i; }); // Model hâlâ yanıt üretiyorsa yalnızca en son ilerleme satırını göster const showProgress = lastProgress > lastLlm ? lastProgress : -1; const seen = new Set(); let active: string | null = null; const runs: Array<{ it: number; r: RunResult }> = []; let llm = { calls: 0, tin: 0, tout: 0, secs: 0 }; for (const e of events) { if (e.type === 'phase' && e.phase) { seen.add(e.phase); active = e.phase; } if (e.type === 'llm') llm = { calls: llm.calls + 1, tin: llm.tin + (e.data.tokens_in || 0), tout: llm.tout + (e.data.tokens_out || 0), secs: llm.secs + (e.data.seconds || 0) }; if (e.type === 'tool_result' && e.data.run) runs.push({ it: e.data.iteration, r: e.data.run }); } const visible = events.filter((e, i) => e.type !== 'llm' && (e.type !== 'mutant' || i === lastMutant) && (e.type !== 'llm_progress' || i === showProgress)); return { visible, active, seen, llm, runs }; }, [events]); useEffect(() => { if (running) endRef.current?.scrollIntoView({ block: 'end' }); }, [visible.length, running]); return (

    {t('flow.title')}

    {job && {t(`status.${running ? 'running' : job.status}` as MsgKey)}}
    {STEPS.map((s) => { const isActive = running && active === s; return (
    {t(`flow.step.${s}` as MsgKey)}
    ); })}
    {events.length === 0 ? (

    {t('flow.emptyTitle')}

    {t('flow.emptyText')}

    ) : (
      {visible.map((e) => )}
    )}
    {runs.length > 0 && (
    {runs.map(({ it, r }) => (
    {t('flow.round', { n: it })}
    {r.compiled ? pct(r.coverage.percent) : t('flow.compileShort')}
    ))} {llm.calls > 0 && (
    {t('flow.llmStats', { c: llm.calls, tin: num(llm.tin), tout: num(llm.tout), s: num(llm.secs) })}
    )}
    )}
    ); }