Download frontend/src/components/AgentFlow.tsx from SevdanurGenc/LLM-Automated-Test-Generation-Agent: direct link, hf CLI and curl.
- Browser
- Download file 9.94 kB
-
https://huggingface.co/SevdanurGenc/LLM-Automated-Test-Generation-Agent/resolve/main/frontend/src/components/AgentFlow.tsx
- Command line
-
hf download hf://SevdanurGenc/LLM-Automated-Test-Generation-Agent/frontend/src/components/AgentFlow.tsx
-
curl -L -o AgentFlow.tsx https://huggingface.co/SevdanurGenc/LLM-Automated-Test-Generation-Agent/resolve/main/frontend/src/components/AgentFlow.tsx
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> | |
| ); | |
| } | |