Add per-snapshot review link and snapshot picker to Reviews tab
This commit is contained in:
+27
-8
@@ -1,7 +1,7 @@
|
||||
import { get, type ByreEvent, type Schemas } from "./api.js";
|
||||
import { ago, errorText, h, replace } from "./dom.js";
|
||||
import { markdown } from "./markdown.js";
|
||||
import { onEach, state } from "./state.js";
|
||||
import { onEach, pickReview, state } from "./state.js";
|
||||
|
||||
type Review = Schemas["Review"];
|
||||
|
||||
@@ -80,7 +80,8 @@ function reviewResult(r: Review, taskId: string | undefined): HTMLElement {
|
||||
}
|
||||
|
||||
export function mountReviews(root: HTMLElement): () => void {
|
||||
const list = h("div", { class: "review-list" });
|
||||
const head = h("header", { class: "reviews-header" });
|
||||
const body = h("div", { class: "review-list" });
|
||||
let reviews: Review[] | undefined;
|
||||
let error = "";
|
||||
let target = 0;
|
||||
@@ -112,23 +113,40 @@ export function mountReviews(root: HTMLElement): () => void {
|
||||
|
||||
const render = () => {
|
||||
const taskId = state.selectedTaskId;
|
||||
replace(head);
|
||||
head.hidden = true;
|
||||
if (!taskId) {
|
||||
replace(list, h("div", { class: "empty" }, "No task selected."));
|
||||
replace(body, h("div", { class: "empty" }, "No task selected."));
|
||||
return;
|
||||
}
|
||||
if (error) {
|
||||
replace(list, h("div", { class: "empty error-text" }, error));
|
||||
replace(body, h("div", { class: "empty error-text" }, error));
|
||||
return;
|
||||
}
|
||||
if (!reviews) {
|
||||
replace(list, h("div", { class: "empty" }, "Loading…"));
|
||||
replace(body, 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."));
|
||||
replace(body, 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 n = state.reviewed ?? reviews.at(-1)!.snapshot_n;
|
||||
const shown = reviews.findLast((r) => r.snapshot_n === n);
|
||||
const ns = [...new Set([n, ...reviews.map((r) => r.snapshot_n)])].sort((a, b) => b - a);
|
||||
const picker = h("select", { title: "Snapshot whose review to show" }, ...ns.map((s) => h("option", { value: String(s) }, `snapshot #${s}`)));
|
||||
picker.value = String(n);
|
||||
picker.onchange = () => pickReview(Number(picker.value));
|
||||
const step = (to: number, label: string) => {
|
||||
const b = h("button", { type: "button", class: "snap-step", disabled: !ns.includes(to) }, label);
|
||||
b.onclick = () => pickReview(to);
|
||||
return b;
|
||||
};
|
||||
const next = ns.filter((s) => s > n).at(-1);
|
||||
const prev = ns.find((s) => s < n);
|
||||
head.hidden = false;
|
||||
replace(head, picker, step(prev ?? n - 1, `← #${prev ?? n - 1}`), step(next ?? n + 1, `#${next ?? n + 1} →`));
|
||||
replace(body, shown ? reviewCard(shown, taskId) : h("div", { class: "empty" }, `No review of snapshot #${n} yet.`));
|
||||
};
|
||||
|
||||
const offs = [
|
||||
@@ -144,9 +162,10 @@ export function mountReviews(root: HTMLElement): () => void {
|
||||
void load();
|
||||
}
|
||||
}),
|
||||
onEach(["review"], render),
|
||||
];
|
||||
|
||||
replace(root, h("div", { class: "reviews-view" }, list));
|
||||
replace(root, h("div", { class: "reviews-view" }, head, body));
|
||||
render();
|
||||
void load();
|
||||
return () => {
|
||||
|
||||
@@ -12,6 +12,7 @@ export type Topic =
|
||||
| "connection"
|
||||
| "event"
|
||||
| "pin"
|
||||
| "review"
|
||||
| "focus"
|
||||
| "lead.sessions"
|
||||
| "lead.transcript"
|
||||
@@ -28,6 +29,7 @@ export const state = {
|
||||
selectedTaskId: undefined as string | undefined,
|
||||
transcript: [] as ByreEvent[],
|
||||
pinned: undefined as number | undefined,
|
||||
reviewed: undefined as number | undefined,
|
||||
connected: false,
|
||||
focus: { tabs: ["transcript"], tab: "transcript" } as Focus,
|
||||
leadSessions: [] as AgentSession[],
|
||||
@@ -92,6 +94,16 @@ export function pinSnapshot(n: number | undefined): void {
|
||||
notify("pin");
|
||||
}
|
||||
|
||||
export function pickReview(n: number): void {
|
||||
state.reviewed = n;
|
||||
notify("review");
|
||||
}
|
||||
|
||||
export function showReview(n: number): void {
|
||||
pickReview(n);
|
||||
focusTab("reviews");
|
||||
}
|
||||
|
||||
export async function loadMe(): Promise<void> {
|
||||
state.me = await get("/api/me");
|
||||
notify("me");
|
||||
@@ -254,6 +266,7 @@ export function selectTask(taskId: string | undefined): void {
|
||||
taskFeed.reset();
|
||||
state.selectedTaskId = taskId;
|
||||
state.pinned = undefined;
|
||||
state.reviewed = undefined;
|
||||
notify("selection");
|
||||
|
||||
if (!taskId) {
|
||||
|
||||
+5
-5
@@ -3,7 +3,7 @@ import { ago, copyable, errorText, h, replace, showAlert, showConfirm } from "./
|
||||
import { markdown } from "./markdown.js";
|
||||
import { previewURL } from "./preview.js";
|
||||
import { reviewCounts } from "./reviews.js";
|
||||
import { currentSession, findTask, focusTab, focusTask, onEach, pinSnapshot, setFocus, state, type Topic } from "./state.js";
|
||||
import { currentSession, findTask, focusTab, focusTask, onEach, pinSnapshot, setFocus, showReview, state, type Topic } from "./state.js";
|
||||
import { taskStyle } from "./taskcolor.js";
|
||||
|
||||
export type Usage = { used: number; window: number; model: string };
|
||||
@@ -907,8 +907,10 @@ function snapRow(snap: Snapshot, taskId: string | undefined): HTMLElement {
|
||||
void showAlert(errorText(err));
|
||||
}
|
||||
};
|
||||
const review = h("button", { type: "button", title: `Show the review of snapshot ${snap.n} in the Reviews tab` }, "review");
|
||||
review.onclick = () => showReview(snap.n);
|
||||
const time = new Date(snap.created_at).toLocaleTimeString([], { hour12: false });
|
||||
return h("div", { class: "snap-row", title: `${snap.trigger.replace("_", " ")} · ${time}` }, `snapshot #${snap.n}`, changes, fork);
|
||||
return h("div", { class: "snap-row", title: `${snap.trigger.replace("_", " ")} · ${time}` }, `snapshot #${snap.n}`, changes, fork, review);
|
||||
}
|
||||
|
||||
function runSummary(calls: Call[]): HTMLElement {
|
||||
@@ -1052,10 +1054,8 @@ function renderEvent(ev: ByreEvent, ctx: Ctx): HTMLElement | null {
|
||||
|
||||
case "review.finished": {
|
||||
const r = p as unknown as Review;
|
||||
const open = h("button", { type: "button", title: "Show the task's reviews" }, "reviews");
|
||||
open.onclick = () => focusTab("reviews");
|
||||
const outcome = r.state === "failed" ? `failed: ${r.error}` : reviewCounts(r);
|
||||
return h("div", { class: `note${r.state === "failed" ? " error" : ""}` }, `review of snapshot #${r.snapshot_n}: ${outcome} `, when, " ", open);
|
||||
return h("div", { class: `note${r.state === "failed" ? " error" : ""}` }, `review of snapshot #${r.snapshot_n}: ${outcome} `, when);
|
||||
}
|
||||
|
||||
case "task.merge_queued":
|
||||
|
||||
Reference in New Issue
Block a user