SevdanurGenc's picture
Upload 300 files
9438a57 verified
Raw History Blame Contribute Delete
9.94 kB
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 (
<li className="tl-item phase">
<span className="tl-glyph" aria-hidden>{n || '•'}</span>
<div>
<div className="tl-title">{title}</div>
{d.agent && <div className="tl-meta">{t('flow.agent', { a: pick(d.agent_i18n, d.agent) })}</div>}
{d.reason && <div className="tl-meta">{t('flow.reason', { r: pick(d.reason_i18n, d.reason) })}</div>}
</div>
</li>
);
}
if (e.type === 'tool_call' && d.tool === 'write_tests') {
return (
<li className="tl-item">
<span className="tl-glyph" aria-hidden>✎</span>
<div>
<div className="tl-title">write_tests <span className="muted small">— {t('flow.wrote', { n: d.lines })}</span></div>
<details className="tl-detail"><summary>{t('flow.showCode')}</summary><pre>{d.code}</pre></details>
</div>
</li>
);
}
if (e.type === 'tool_call' && d.tool === 'report_bug') {
const ok = d.accepted !== false;
return (
<li className={`tl-item ${ok ? 'bad' : ''}`}>
<span className="tl-glyph" aria-hidden>{ok ? '!' : '⊘'}</span>
<div>
<div className="tl-title">report_bug <span className="muted small">— {ok ? t('flow.bugOk') : t('flow.bugRejected', { by: t(d.by === 'kural' ? 'flow.by.rule' : 'flow.by.judge') })}</span></div>
<div className="tl-meta">{d.test_name}: {d.reason}</div>
{d.verdict && <div className="tl-meta"><strong>{t('flow.verdict')}</strong> {d.verdict}</div>}
</div>
</li>
);
}
if (e.type === 'tool_call') {
const bad = d.tool === 'report_bug';
return (
<li className={`tl-item ${bad ? 'bad' : ''}`}>
<span className="tl-glyph" aria-hidden>{bad ? '!' : '›'}</span>
<div>
<div className="tl-title">{title}</div>
{bad && <div className="tl-meta">{d.test_name}: {d.reason}</div>}
{d.summary && <div className="tl-meta">{d.summary}</div>}
</div>
</li>
);
}
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 (
<li className={`tl-item ${d.accepted ? 'ok' : 'bad'}`}>
<span className="tl-glyph" aria-hidden>{d.accepted ? '✓' : '✕'}</span>
<div>
<div className="tl-title">{t('flow.roundResult', { n: d.iteration, summary: runSummary(r, t, pct) })}</div>
<div className="tl-meta">{t('flow.sandboxSec', { s: num(r.duration_s, 1) })}</div>
{(!r.compiled || failing.length > 0) && (
<details className="tl-detail">
<summary>{!r.compiled ? t('flow.compilerOut') : t('flow.nFailed', { n: failing.length })}</summary>
<pre>{!r.compiled ? r.compile_output : failing.map((c) => `✕ ${c.name}\n${c.message}`).join('\n\n')}</pre>
</details>
)}
</div>
</li>
);
}
if (e.type === 'thought') {
return (
<li className="tl-item">
<span className="tl-glyph" aria-hidden>…</span>
<div>
<div className="tl-title muted">{title}</div>
{d.text && <details className="tl-detail"><summary>{t('flow.showText')}</summary><pre>{d.text}</pre></details>}
</div>
</li>
);
}
if (e.type === 'result') {
return (
<li className="tl-item ok">
<span className="tl-glyph" aria-hidden>✓</span>
<div><div className="tl-title">{title}</div>
{d.analysis?.test_plan?.length > 0 && <div className="tl-meta">{t('flow.planItems', { n: d.analysis.test_plan.length })}</div>}</div>
</li>
);
}
if (e.type === 'llm_progress') {
return (
<li className="tl-item">
<span className="tl-glyph" aria-hidden>…</span>
<div>
<div className="tl-title">{t('flow.generating')}</div>
<div className="tl-meta">
{d.seconds ? t('flow.genProgress', { s: Math.round(d.seconds), c: d.chunks }) : t('flow.genLoading')}
</div>
</div>
</li>
);
}
if (e.type === 'mutant') {
return (
<li className="tl-item">
<span className="tl-glyph" aria-hidden>μ</span>
<div>
<div className="tl-title">{t('flow.mutants', { d: d.done, t: d.total })}</div>
<div className="meter na" style={{ marginTop: 6 }}><span style={{ width: `${(100 * d.done) / d.total}%`, background: 'var(--signal)' }} /></div>
</div>
</li>
);
}
if (e.type === 'error') {
return (
<li className="tl-item bad">
<span className="tl-glyph" aria-hidden>!</span>
<div><div className="tl-title">{t('flow.error')}</div><div className="tl-meta">{d.message}</div>
{d.trace && <details className="tl-detail"><summary>{t('flow.details')}</summary><pre>{d.trace}</pre></details>}</div>
</li>
);
}
if (e.type === 'done' || e.type === 'status') {
return (
<li className={`tl-item ${e.type === 'done' ? 'ok' : ''}`}>
<span className="tl-glyph" aria-hidden>{e.type === 'done' ? '■' : '▶'}</span>
<div><div className="tl-title">{title}</div>
<div className="tl-meta">{pick(d.stop_reason_i18n, d.stop_reason || '') || (d.model ? t('flow.model', { m: d.model }) : '')}</div></div>
</li>
);
}
return null;
}
export default function AgentFlow({ events, job, running }: { events: AgentEvent[]; job: JobState | null; running: boolean }) {
const { t, pct, num } = useI18n();
const endRef = useRef<HTMLDivElement>(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<string>();
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 (
<section className="panel" aria-labelledby="flow-h">
<div className="panel-head">
<h2 id="flow-h">{t('flow.title')}</h2>
<div className="spacer" />
{job && <span className={`tag ${running ? 'running' : job.status}`}>{t(`status.${running ? 'running' : job.status}` as MsgKey)}</span>}
</div>
<div className="loop" style={{ gridTemplateColumns: `repeat(${STEPS.length}, 1fr)` }} aria-label={t('flow.steps.aria')}>
{STEPS.map((s) => {
const isActive = running && active === s;
return (
<div key={s} className={`loop-step ${isActive ? 'active' : seen.has(s) ? 'done' : ''}`}>
<div className="bar" />{t(`flow.step.${s}` as MsgKey)}
</div>
);
})}
</div>
<div className="panel-body">
{events.length === 0 ? (
<div className="empty">
<div>
<h3>{t('flow.emptyTitle')}</h3>
<p>{t('flow.emptyText')}</p>
</div>
</div>
) : (
<ol className="timeline" aria-live="polite">
{visible.map((e) => <Item key={e.seq} e={e} />)}
</ol>
)}
<div ref={endRef} />
</div>
{runs.length > 0 && (
<div className="iter-strip" aria-label={t('flow.coverageByRound')}>
{runs.map(({ it, r }) => (
<div className="iter-row" key={it}>
<span className="muted">{t('flow.round', { n: it })}</span>
<div className={`meter ${r.coverage.percent == null || !r.compiled ? 'na' : ''}`}
title={runSummary(r, t, pct)}><span style={{ width: `${r.compiled ? r.coverage.percent ?? 0 : 0}%` }} /></div>
<span className="mono small" style={{ textAlign: 'right' }}>{r.compiled ? pct(r.coverage.percent) : t('flow.compileShort')}</span>
</div>
))}
{llm.calls > 0 && (
<div className="small muted">{t('flow.llmStats', { c: llm.calls, tin: num(llm.tin), tout: num(llm.tout), s: num(llm.secs) })}</div>
)}
</div>
)}
</section>
);
}