import { useEffect, useMemo, useState } from 'react'; import { api } from '../api'; import { useI18n, type MsgKey } from '../i18n'; import type { Experiment, ExpPlan, ExpStep, Health } from '../types'; import { allModels, openConnections, providerOf } from './ModelSelect'; import { isNum, usePref } from '../settings'; function useDuration() { const { t } = useI18n(); return (s: number) => { if (!s || s < 60) return t('exp.dur.min', { m: s ? 1 : 0 }); const h = Math.floor(s / 3600), m = Math.round((s % 3600) / 60); return h ? t('exp.dur.hm', { h, m }) : t('exp.dur.min', { m }); }; } function stepName(s: ExpStep, t: (k: MsgKey, p?: Record) => string) { if (s.kind === 'judge') return t('exp.step.judge', { mode: s.rule_mode }); if (s.kind === 'xmut') return t('exp.step.xmut'); if (s.ablate.length) return t('exp.step.abl', { a: s.ablate.join(', ') }); return t('exp.step.eval', { k: s.max_iterations, r: s.repeats }); } const PROFILES = ['deneme', 'hizli', 'tam', 'karsilastirma'] as const; type Profile = typeof PROFILES[number]; const MAX_MODELS = 8; export default function ExperimentsPage({ health }: { health: Health | null }) { const { t, pct, date, lang } = useI18n(); const dur = useDuration(); const [profile, setProfile] = usePref('exp.profile', 'deneme', (v) => (PROFILES as readonly string[]).includes(v as string)); const [savedModels, setModels] = usePref('exp.models', [], (v) => Array.isArray(v)); const [maxMutants, setMaxMutants] = usePref('exp.maxMutants', 30, isNum); const [plan, setPlan] = useState(null); const [list, setList] = useState([]); const [sel, setSel] = useState(null); const [err, setErr] = useState(null); const [busy, setBusy] = useState(false); const [repLang, setRepLang] = useState<'tr' | 'en'>(lang); useEffect(() => { setRepLang(lang); }, [lang]); const options = allModels(health); const available = options.map((m) => m.id); // Kayıtlı seçimden bu bilgisayarda bulunmayan modeller çıkarılır (ayar dosyası başka bilgisayardan gelmiş olabilir) const kept = available.length ? savedModels.filter((m) => available.includes(m)) : savedModels; const defaults = (() => { const pref = ['qwen2.5-coder:7b', 'qwen2.5-coder:14b'].filter((m) => available.includes(m)); return pref.length ? pref : available.length ? [health?.default_model && available.includes(health.default_model) ? health.default_model : available[0]] : []; })(); const models = kept.length || savedModels.length && !available.length ? kept : defaults; useEffect(() => { if (!models.length) return; api.expPlan(profile, models).then(setPlan).catch(() => setPlan(null)); }, [profile, models.join(',')]); // eslint-disable-line react-hooks/exhaustive-deps const load = () => api.experiments().then((l) => { setList(l); setSel((s) => s ?? (l[0]?.id || null)); }).catch((e) => setErr(String(e.message || e))); useEffect(() => { load(); const h = setInterval(load, 5000); return () => clearInterval(h); }, []); // eslint-disable-line react-hooks/exhaustive-deps const cur = useMemo(() => list.find((e) => e.id === sel) || null, [list, sel]); const active = list.find((e) => e.status === 'running'); const toggleModel = (m: string) => setModels(models.includes(m) ? models.filter((x) => x !== m) : [...models, m]); const start = async () => { setErr(null); setBusy(true); try { const { id } = await api.startExperiment({ profile, models, max_mutants: maxMutants }); setSel(id); await load(); } catch (e: any) { setErr(String(e.message) === 'busy' ? t('exp.err.busy') : String(e.message || e)); } setBusy(false); }; const act = async (fn: () => Promise) => { setErr(null); try { await fn(); await load(); } catch (e: any) { setErr(String(e.message) === 'busy' ? t('exp.err.busy') : String(e.message || e)); } }; return (

{t('exp.title')}

{t('exp.lead')}

{t('exp.new')}

{PROFILES.map((p) => ( ))}
{t('exp.models')}
{(['ollama', 'openai', 'anthropic'] as const).map((p) => { const items = options.filter((m) => m.provider === p); return items.length ? (
{t(`model.group.${p}` as MsgKey)}
{items.map((m) => ( ))}
) : null; })} {!available.length &&
{t('exp.noModels')}
}
{models.some((m) => providerOf(m) !== 'ollama') &&
{t('ev.cloudCost')}
}
{plan && (
{t('exp.planSummary', { s: plan.steps.length, r: plan.item_runs, n: plan.items })}
{t('exp.estimate', { d: dur(plan.eta_s) })} · {t('exp.estimateHint')}
)}
{t('exp.resumeHint')}
{err &&
{err}
} {list.length > 0 && (
{t('exp.previous')} {list.map((e) => ( setSel(e.id)}> ))}
{t(`exp.profile.${e.profile}` as MsgKey)} · {e.models.join(', ')}
{date(e.created_at)}
{t(`exp.status.${e.status}` as MsgKey)}
{pct(e.percent, 0)}
)}

{cur ? <>{t(`exp.profile.${cur.profile}` as MsgKey)} · {cur.models.join(', ')} : t('exp.progress')}

{cur && cur.status === 'running' && } {cur && ['running', 'paused', 'failed'].includes(cur.status) && } {cur && (cur.status === 'paused' || cur.status === 'failed') && } {cur && (cur.status === 'done' || cur.status === 'failed') && !!cur.retryable && (cur.retryable.items + cur.retryable.xmut) > 0 && } {cur && (<>
{(['tr', 'en'] as const).map((l) => ( ))}
{t('exp.reportHtml')} PDF {t('exp.download')} )}
{!cur ? (

{t('exp.emptyTitle')}

{t('exp.emptyText')}

) : (<>
{t(`exp.status.${cur.status}` as MsgKey)}{' '} {pct(cur.percent, 1)} · {t('exp.items', { d: cur.items_done, n: cur.items_total })} {cur.judge_total > 0 && ` · ${t('exp.judges', { d: cur.judge_done, n: cur.judge_total })}`} {cur.status !== 'done' && {t('exp.eta', { d: dur(cur.eta_s), a: Math.round(cur.avg_item_s) })}}
{cur.status === 'running' &&
{t('exp.sleepHint')}
} {cur.status === 'done' &&
{t('exp.doneHint')}
} {cur.error &&
{cur.error}
}
{cur.steps.map((s) => ( ))}
{t('exp.col.step')}{t('exp.col.model')}{t('exp.col.status')} {t('exp.col.progress')}{t('ev.col.cov')}{t('ev.col.mut')}
{stepName(s, t)}
{s.label}
{s.error &&
{s.error.slice(0, 160)}
}
{s.model} {t(`exp.status.${s.status}` as MsgKey)} {s.kind === 'eval' ? `${s.done}/${s.total}` : s.kind === 'xmut' && s.done ? s.done : '—'} {s.summary ? `${pct(s.summary.first_coverage)} → ${pct(s.summary.final_coverage)}` : '—'} {s.summary ? pct(s.summary.mutation_score_pooled) : '—'}
)}
); }