import { get } from "./api.js"; import { ago, errorText, h, replace } from "./dom.js"; import { markdown } from "./markdown.js"; import { onEach, state } from "./state.js"; const severities = ["critical", "high", "medium", "low", "info"]; export function isReviewEvent(ev) { return ev.kind.startsWith("review."); } export function reviewCounts(r) { const res = (r.result ?? {}); const findings = Array.isArray(res["findings"]) ? res["findings"] : []; const parts = severities.map((s) => [s, findings.filter((f) => f["severity"] === s).length]).filter(([, n]) => n > 0); return parts.length > 0 ? parts.map(([s, n]) => `${n} ${s}`).join(", ") : "no findings"; } function stateText(r) { switch (r.state) { case "running": return `reviewing on ${r.vendor} (${r.model})…`; case "done": return reviewCounts(r); case "failed": return "failed"; } } export function reviewCard(r, taskId) { return h("article", { class: `review ${r.state}` }, h("header", {}, h("span", { class: "review-snapshot" }, `snapshot #${r.snapshot_n}`), h("span", { class: `review-state ${r.state}` }, stateText(r)), h("time", { dateTime: r.created_at, title: r.created_at }, ago(r.created_at))), r.state === "failed" ? h("p", { class: "error-text" }, r.error) : null, r.state === "done" ? reviewResult(r, taskId) : null); } function reviewResult(r, taskId) { const res = (r.result ?? {}); const findings = Array.isArray(res["findings"]) ? res["findings"] : []; const md = { taskId }; return h("div", { class: "review-result" }, markdown(String(res["summary"] ?? ""), "md review-summary", md), findings.length > 0 ? h("ol", { class: "findings" }, ...findings.map((f) => { const severity = String(f["severity"] ?? ""); const where = typeof f["path"] === "string" && f["path"] ? `${f["path"]}${typeof f["line"] === "number" && f["line"] > 0 ? `:${f["line"]}` : ""}` : ""; return h("li", { class: `finding ${severity}` }, h("div", { class: "finding-head" }, h("span", { class: `severity ${severity}` }, severity), typeof f["category"] === "string" && f["category"] ? h("span", { class: "finding-category" }, f["category"]) : null, h("span", { class: "finding-title" }, String(f["title"] ?? "")), where ? h("code", { class: "finding-where" }, where) : null), markdown(String(f["detail"] ?? ""), "md finding-detail", md)); })) : null); } export function mountReviews(root) { const list = h("div", { class: "review-list" }); let reviews; let error = ""; let target = 0; let disposed = false; const load = async () => { const taskId = state.selectedTaskId; if (!taskId) { render(); return; } const started = ++target; try { const fresh = await get("/api/tasks/{taskId}/reviews", { path: { taskId } }); if (started === target) { reviews = fresh; error = ""; } } catch (err) { if (started === target) { reviews = undefined; error = errorText(err); } } if (!disposed && started === target) { render(); } }; const render = () => { const taskId = state.selectedTaskId; if (!taskId) { replace(list, h("div", { class: "empty" }, "No task selected.")); return; } if (error) { replace(list, h("div", { class: "empty error-text" }, error)); return; } if (!reviews) { replace(list, h("div", { class: "empty" }, "Loading…")); return; } if (reviews.length === 0) { replace(list, h("div", { class: "empty" }, "No reviews yet. Each new snapshot of the task is reviewed by an agent from the other vendor.")); return; } replace(list, ...[...reviews].reverse().map((r) => reviewCard(r, taskId))); }; const offs = [ onEach(["selection"], () => { target++; reviews = undefined; error = ""; render(); void load(); }), onEach(["transcript"], (fresh) => { if (fresh.some(isReviewEvent)) { void load(); } }), ]; replace(root, h("div", { class: "reviews-view" }, list)); render(); void load(); return () => { disposed = true; for (const off of offs) { off(); } }; } //# sourceMappingURL=reviews.js.map