static-fire 0.44.0 → 0.54.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/web/tasks.tsx ADDED
@@ -0,0 +1,122 @@
1
+ // The Tasks page (#79): each suite and its tasks, from the project's own files. For each task, what the agent gets,
2
+ // where it writes, and how the output is graded. The server sends only what core/task-page.ts allows, so the graders'
3
+ // own material (references, sources, the held-out file) never reaches this page. Each suite and task has an address
4
+ // of its own (web/pages.ts: suiteHref, taskHref), so another page can link to it.
5
+ import { useEffect, useState } from "react";
6
+ import type { TaskListing } from "../src/core/authored";
7
+ import type { TaskPage, TaskPageCriterion } from "../src/core/task-page";
8
+ import type { Suite } from "../src/core/types";
9
+ import { api } from "./api";
10
+ import { LevelTag } from "./levels";
11
+ import { suiteAnchor, taskAnchor, taskHref } from "./pages";
12
+
13
+ const GRADER_TEXT: Record<TaskPageCriterion["grader"], string> = { check: "Check", judge: "Judge", human: "Human" };
14
+
15
+ /** A task as the page has it: its details, or the reason it has none (an invalid task file). */
16
+ type Loaded = { page: TaskPage } | { error: string };
17
+
18
+ export function TasksPage() {
19
+ const [suites, setSuites] = useState<Suite[] | null>(null);
20
+ const [listing, setListing] = useState<TaskListing[]>([]);
21
+ const [tasks, setTasks] = useState<Record<string, Loaded>>({});
22
+ const [error, setError] = useState("");
23
+
24
+ useEffect(() => {
25
+ let live = true;
26
+ Promise.all([api<Suite[]>("/api/suites"), api<TaskListing[]>("/api/tasks")])
27
+ .then(async ([s, l]) => {
28
+ const loaded = await Promise.all(l.map((t) =>
29
+ api<TaskPage>(`/api/tasks/${encodeURIComponent(t.id)}`).then((page): Loaded => ({ page }), (e: Error): Loaded => ({ error: e.message }))));
30
+ if (!live) return;
31
+ setSuites(s); setListing(l);
32
+ setTasks(Object.fromEntries(l.map((t, i) => [t.id, loaded[i]!])));
33
+ })
34
+ .catch((e: Error) => { if (live) setError(e.message); });
35
+ return () => { live = false; };
36
+ }, []);
37
+ // A link to one suite or task opens the page there, once the entries exist.
38
+ useEffect(() => {
39
+ if (!suites || !location.hash) return;
40
+ document.getElementById(decodeURIComponent(location.hash.slice(1)))?.scrollIntoView();
41
+ }, [suites]);
42
+
43
+ if (error) return <div className="empty card"><div className="kicker">Could not load the tasks</div><p className="error" style={{ whiteSpace: "pre-wrap" }}>{error}</p></div>;
44
+ if (!suites) return <div className="empty card"><div className="kicker">Tasks</div><h1>Loading…</h1></div>;
45
+
46
+ // Each task is shown once, under the first suite that lists it; a later suite links to it.
47
+ const shown = new Set<string>();
48
+ const title = (id: string) => listing.find((t) => t.id === id)?.title ?? id;
49
+ const groups = suites.map((s) => ({ suite: s, tasks: s.tasks.map((id) => { const first = !shown.has(id); shown.add(id); return { id, first }; }) }));
50
+ const loose = listing.filter((t) => !shown.has(t.id)).map((t) => t.id);
51
+
52
+ return (
53
+ <>
54
+ <section className="hero">
55
+ <div className="kicker">Tasks</div>
56
+ <h1>Suites and tasks</h1>
57
+ <p className="page-sub">What each task asks of the agent and how its output is graded, from the project's task files. Graders' own material (references, sources, the held-out file) is not shown.</p>
58
+ </section>
59
+ {groups.map(({ suite, tasks: ids }) => (
60
+ <section key={suite.id} id={suiteAnchor(suite.id)} className="card task-suite">
61
+ <div className="kicker">Suite</div>
62
+ <h2>{suite.title}</h2>
63
+ {suite.description && <p className="sub">{suite.description}</p>}
64
+ {ids.length === 0 && <p className="note">This suite lists no tasks.</p>}
65
+ {ids.map(({ id, first }) => first ? <TaskEntry key={id} id={id} title={title(id)} loaded={tasks[id]} />
66
+ : <p key={id} className="note task-ref">{title(id)}: <a href={taskHref(id)}>shown above</a>.</p>)}
67
+ </section>
68
+ ))}
69
+ {loose.length > 0 && (
70
+ <section className="card task-suite">
71
+ <div className="kicker">Not in a suite</div>
72
+ <h2>Other tasks</h2>
73
+ <p className="sub">These tasks are in no suite, so the Results page has no suite index for them.</p>
74
+ {loose.map((id) => <TaskEntry key={id} id={id} title={title(id)} loaded={tasks[id]} />)}
75
+ </section>
76
+ )}
77
+ </>
78
+ );
79
+ }
80
+
81
+ function TaskEntry({ id, title, loaded }: { id: string; title: string; loaded?: Loaded }) {
82
+ if (!loaded || "error" in loaded) {
83
+ return (
84
+ <article id={taskAnchor(id)} className="task-entry">
85
+ <h3>{title}</h3>
86
+ <p className="error" style={{ whiteSpace: "pre-wrap" }}>This task file has errors, so it cannot run:{"\n"}{loaded?.error ?? "not found"}</p>
87
+ </article>
88
+ );
89
+ }
90
+ const t = loaded.page;
91
+ const groups = [{ id: "gates", label: "Gates", note: "If a gate fails, the task score is 0.", criteria: t.gates }, ...t.measures.map((m) => ({ ...m, note: m.weight !== 1 ? `Weight ${m.weight}` : "" }))]
92
+ .filter((g) => g.criteria.length);
93
+ return (
94
+ <article id={taskAnchor(id)} className="task-entry">
95
+ <h3>{t.title}{t.version != null && <span className="muted task-version"> v{t.version}</span>}</h3>
96
+ {t.description && <p className="task-desc">{t.description}</p>}
97
+ <h4>What the agent gets</h4>
98
+ <pre className="task-prompt">{t.prompt}</pre>
99
+ <dl className="task-facts">
100
+ <dt>Input files</dt><dd>{t.inputFiles.length ? t.inputFiles.map((f) => <code key={f}>{f}</code>) : <span className="muted">None</span>}</dd>
101
+ {t.projectFiles && <><dt>Project files</dt><dd>{t.projectFiles.length ? t.projectFiles.map((f) => <code key={f}>{f}</code>) : <span className="muted">None</span>}</dd></>}
102
+ <dt>Output</dt><dd><code>{t.output}</code></dd>
103
+ </dl>
104
+ <h4>How it is graded</h4>
105
+ <div className="table-wrap"><table className="criteria task-criteria">
106
+ <thead><tr><th>Question</th><th>Level</th><th>Grader</th></tr></thead>
107
+ <tbody>
108
+ {groups.map((g) => [
109
+ <tr key={g.id} className="group"><td colSpan={3}>{g.label}{g.note && <span className="group-note"> · {g.note}</span>}</td></tr>,
110
+ ...g.criteria.map((c) => (
111
+ <tr key={`${g.id}-${c.id}`}>
112
+ <td className="question">{c.question}</td>
113
+ <td><LevelTag level={c.level} /></td>
114
+ <td className="muted">{GRADER_TEXT[c.grader]}</td>
115
+ </tr>
116
+ )),
117
+ ])}
118
+ </tbody>
119
+ </table></div>
120
+ </article>
121
+ );
122
+ }
@@ -1,20 +0,0 @@
1
- // The suite descriptions the Results page shows with a chart. A suite can state facts that must stand next to any
2
- // chart of it (for example where its tasks and criteria come from), so every chart that draws a suite shows them.
3
- import type { Suite } from "../src/core/types";
4
- import type { Scope } from "../src/core/view-query";
5
-
6
- export interface SuiteNote { title: string; description: string }
7
-
8
- /**
9
- * The described suites a chart draws: the chosen suite, every suite in the view for the Workflow Index, or each
10
- * suite that holds the chosen task (a task's view has no suites of its own). Suites without a description are left out.
11
- */
12
- export function suiteNotes(sel: Scope, suiteIds: string[], suites: Map<string, Suite>): SuiteNote[] {
13
- const ids = sel.kind === "suite" ? [sel.id]
14
- : sel.kind === "index" ? suiteIds
15
- : [...suites.keys()].filter((id) => suites.get(id)!.tasks.includes(sel.id));
16
- return ids.flatMap((id) => {
17
- const s = suites.get(id), description = s?.description?.trim();
18
- return s && description ? [{ title: s.title, description }] : [];
19
- });
20
- }