| 1 | import { useEffect, useState } from "react"; |
| 2 | import { app } from "../lib/bridge"; |
| 3 | import { useT } from "../lib/i18n"; |
| 4 | import { turnCheckText } from "../lib/turnResult"; |
| 5 | import type { WireCompletionSummary } from "../lib/types"; |
| 6 | |
| 7 | function CheckLog({ tabId, sessionPath, toolCallId, toolResultId, liveOutput }: { tabId: string; sessionPath: string; toolCallId?: string; toolResultId?: string; liveOutput?: string }) { |
| 8 | const t = useT(); |
| 9 | const [open, setOpen] = useState(false); |
| 10 | const [loaded, setLoaded] = useState<{ key: string; output?: string; loading: boolean; truncated?: boolean }>(); |
| 11 | const key = `${tabId}\u0000${sessionPath}\u0000${toolCallId ?? ""}\u0000${toolResultId ?? ""}`; |
| 12 | useEffect(() => { |
| 13 | if (!open || liveOutput !== undefined) return; |
| 14 | let active = true; |
| 15 | if (!toolCallId || !toolResultId || !sessionPath) return; |
| 16 | setLoaded({ key, loading: true }); |
| 17 | void app.TurnCheckLog(tabId, sessionPath, toolCallId, toolResultId).then(result => { |
| 18 | if (active) setLoaded({ key, loading: false, output: result?.output, truncated: result?.truncated }); |
| 19 | }).catch(() => { if (active) setLoaded({ key, loading: false }); }); |
| 20 | return () => { active = false; }; |
| 21 | }, [key, liveOutput, open, sessionPath, tabId, toolCallId, toolResultId]); |
| 22 | const value = liveOutput ?? (loaded?.key === key ? loaded.output : undefined); |
| 23 | const loading = liveOutput === undefined && loaded?.key === key && loaded.loading; |
| 24 | return <details className="turn-check-log" onToggle={e => setOpen(e.currentTarget.open)}> |
| 25 | <summary>{t("completion.viewLogs")}</summary> |
| 26 | {open && (loading ? <p role="status">{t("completion.loadingResult")}</p> : value !== undefined ? <>{loaded?.key === key && loaded.truncated && <p>{t("completion.logsTruncated")}</p>}<pre>{value}</pre></> : <p>{t("completion.logsUnavailable")}</p>)} |
| 27 | </details>; |
| 28 | } |
| 29 | |
| 30 | export function TurnCheckDetails({ summary, tabId = "", sessionPath = "" }: { summary: WireCompletionSummary; tabId?: string; sessionPath?: string }) { |
| 31 | const t = useT(); |
| 32 | const checks = summary.receipt?.verifications ?? []; |
| 33 | return <div className="turn-check-details"> |
| 34 | <p className="turn-check-details__status">{turnCheckText(summary, t)}</p> |
| 35 | {checks.map((check, index) => <div className="turn-check-details__entry" key={check.toolCallId ?? `${index}:${check.command}`}> |
| 36 | <div className="turn-check-details__head"><code>{check.command}</code><span className={check.interrupted || check.stale ? "turn-result-warning" : check.passed ? "turn-result-added" : "turn-result-removed"}> |
| 37 | {t(check.interrupted ? "completion.checkInterruptedItem" : check.passed ? "completion.checkPassedItem" : "completion.checkFailedItem")} |
| 38 | {check.stale && ` · ${t("completion.checkStaleItem")}`} |
| 39 | </span></div> |
| 40 | {check.exitCode !== undefined && <div>{t("completion.exitCode", { code: check.exitCode })}</div>} |
| 41 | <CheckLog tabId={tabId} sessionPath={sessionPath} toolCallId={check.toolCallId} toolResultId={check.toolResultId} /> |
| 42 | </div>)} |
| 43 | {summary.checking && summary.liveChecks?.map(check => <div className="turn-check-details__entry" key={check.toolCallId}> |
| 44 | <div className="turn-check-details__head"><code>{check.command}</code><span>{t("completion.checkRunning")}</span></div> |
| 45 | <CheckLog tabId={tabId} sessionPath={sessionPath} toolCallId={check.toolCallId} liveOutput={check.output} /> |
| 46 | </div>)} |
| 47 | {summary.receipt?.assessmentKind === "facts" && Boolean(summary.receipt.gaps?.length || summary.receipt.risks?.length) && <p>{t("completion.modelDeclarations")}</p>} |
| 48 | {(summary.receipt?.gaps?.length ?? 0) > 0 && <ul className="turn-check-details__gaps">{summary.receipt!.gaps!.map((gap, index) => <li key={`${index}:${gap.kind}`}>{gap.detail || t("completion.checkUnknown")}</li>)}</ul>} |
| 49 | {(summary.receipt?.risks?.length ?? 0) > 0 && <ul className="turn-check-details__gaps">{summary.receipt!.risks!.map((risk, index) => <li key={index}>{risk}</li>)}</ul>} |
| 50 | {checks.length > 0 && <p className="turn-check-details__footnote">{t("completion.checksOnly")}</p>} |
| 51 | </div>; |
| 52 | } |
| 53 |