dsh-model-arena 0.2.0 → 0.3.0

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.
@@ -1,6 +1,8 @@
1
- import type { PanelPayload, ArenaRun } from './types.js';
1
+ import type { PanelPayload, RunSummary, LeaderboardEntry, ArenaStats } from './types.js';
2
2
  /** Route paths shared between host and client. */
3
3
  export declare const ARENA_PANEL_PATH = "/api/arena.panel";
4
4
  export declare const ARENA_RUN_PATH = "/api/arena.run";
5
5
  export declare const ARENA_RATE_PATH = "/api/arena.rate";
6
- export declare function panelState(runs: ArenaRun[]): PanelPayload;
6
+ export declare const ARENA_COMPARE_PATH = "/api/arena.compare";
7
+ export declare const ARENA_DELETE_PATH = "/api/arena.delete";
8
+ export declare function panelState(runs: RunSummary[], leaderboard: LeaderboardEntry[], stats: ArenaStats): PanelPayload;
package/lib/arena-view.js CHANGED
@@ -2,16 +2,8 @@
2
2
  export const ARENA_PANEL_PATH = '/api/arena.panel';
3
3
  export const ARENA_RUN_PATH = '/api/arena.run';
4
4
  export const ARENA_RATE_PATH = '/api/arena.rate';
5
- export function panelState(runs) {
6
- return {
7
- recentRuns: runs
8
- .sort((a, b) => b.timestamp - a.timestamp)
9
- .slice(0, 20)
10
- .map((r) => ({
11
- id: r.id,
12
- prompt: r.prompt,
13
- timestamp: r.timestamp,
14
- modelCount: r.results.length,
15
- })),
16
- };
5
+ export const ARENA_COMPARE_PATH = '/api/arena.compare';
6
+ export const ARENA_DELETE_PATH = '/api/arena.delete';
7
+ export function panelState(runs, leaderboard, stats) {
8
+ return { recentRuns: runs.slice(0, 20), leaderboard, stats };
17
9
  }
package/lib/arena.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ArenaRun, ModelResult, Suite, RegressionReport, Score } from './types.js';
1
+ import type { ArenaRun, ModelResult, Suite, RegressionReport, LeaderboardEntry, ArenaStats, RunSummary, ComparePayload } from './types.js';
2
2
  export declare class Arena {
3
3
  private projectDir;
4
4
  private store;
@@ -6,31 +6,21 @@ export declare class Arena {
6
6
  /** Save a completed run (results already collected from model calls). */
7
7
  saveRun(prompt: string, results: ModelResult[], reference?: string): ArenaRun;
8
8
  getRun(id: string): ArenaRun | null;
9
- listRuns(): {
10
- id: string;
11
- prompt: string;
12
- timestamp: number;
13
- modelCount: number;
14
- }[];
9
+ listRuns(): RunSummary[];
15
10
  /** Compare two models' outputs within a run. */
16
- compare(runId: string, modelA: string, modelB: string): {
17
- modelA: string;
18
- modelB: string;
19
- outputA: string;
20
- outputB: string;
21
- diff: import("./diff.js").DiffLine[];
22
- summary: {
23
- added: number;
24
- removed: number;
25
- changed: number;
26
- };
27
- scoreA: Score;
28
- scoreB: Score;
29
- } | null;
11
+ compare(runId: string, modelA: string, modelB: string): ComparePayload | null;
30
12
  /** Set a user rating for a model in a run. */
31
13
  rate(runId: string, model: string, rating: number): ArenaRun | null;
14
+ /** Delete a run. */
15
+ deleteRun(runId: string): boolean;
32
16
  /** Save a benchmark suite. */
33
17
  saveSuite(suite: Suite): void;
34
18
  /** Generate a regression report between two runs of the same suite. */
35
19
  regression(suiteName: string, baselineRunId: string, currentRunId: string): RegressionReport | null;
20
+ /** Elo leaderboard from all runs. */
21
+ leaderboard(): LeaderboardEntry[];
22
+ /** Aggregate stats. */
23
+ stats(): ArenaStats;
24
+ /** Pick the best model in a run by composite score. */
25
+ private pickWinner;
36
26
  }
package/lib/arena.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ArenaStore } from './store.js';
2
- import { autoScore } from './scoring.js';
2
+ import { autoScore, buildLeaderboard } from './scoring.js';
3
3
  import { lineDiff, diffSummary } from './diff.js';
4
4
  export class Arena {
5
5
  projectDir;
@@ -13,7 +13,7 @@ export class Arena {
13
13
  const models = results.map((r) => r.model);
14
14
  const id = ArenaStore.generateId(prompt, models);
15
15
  const scores = autoScore(results, reference);
16
- const run = { id, prompt, timestamp: Date.now(), results, scores };
16
+ const run = { id, prompt, timestamp: Date.now(), results, scores, reference };
17
17
  this.store.saveRun(run);
18
18
  return run;
19
19
  }
@@ -21,12 +21,16 @@ export class Arena {
21
21
  return this.store.getRun(id);
22
22
  }
23
23
  listRuns() {
24
- return this.store.listRuns().map((r) => ({
25
- id: r.id,
26
- prompt: r.prompt,
27
- timestamp: r.timestamp,
28
- modelCount: r.results.length,
29
- }));
24
+ return this.store.listRuns().map((r) => {
25
+ const winner = this.pickWinner(r);
26
+ return {
27
+ id: r.id,
28
+ prompt: r.prompt,
29
+ timestamp: r.timestamp,
30
+ modelCount: r.results.length,
31
+ winner,
32
+ };
33
+ });
30
34
  }
31
35
  /** Compare two models' outputs within a run. */
32
36
  compare(runId, modelA, modelB) {
@@ -38,14 +42,15 @@ export class Arena {
38
42
  if (!a || !b)
39
43
  return null;
40
44
  return {
45
+ runId,
41
46
  modelA: a.model,
42
47
  modelB: b.model,
43
48
  outputA: a.output,
44
49
  outputB: b.output,
45
50
  diff: lineDiff(a.output, b.output),
46
51
  summary: diffSummary(a.output, b.output),
47
- scoreA: run.scores[modelA],
48
- scoreB: run.scores[modelB],
52
+ scoreA: run.scores[modelA] ?? {},
53
+ scoreB: run.scores[modelB] ?? {},
49
54
  };
50
55
  }
51
56
  /** Set a user rating for a model in a run. */
@@ -59,6 +64,10 @@ export class Arena {
59
64
  this.store.saveRun(run);
60
65
  return run;
61
66
  }
67
+ /** Delete a run. */
68
+ deleteRun(runId) {
69
+ return this.store.deleteRun(runId);
70
+ }
62
71
  /** Save a benchmark suite. */
63
72
  saveSuite(suite) {
64
73
  this.store.saveSuite(suite);
@@ -84,4 +93,51 @@ export class Arena {
84
93
  }
85
94
  return { suiteName, baselineRunId, currentRunId, changes };
86
95
  }
96
+ /** Elo leaderboard from all runs. */
97
+ leaderboard() {
98
+ const allRuns = this.store.listRuns();
99
+ return buildLeaderboard(allRuns);
100
+ }
101
+ /** Aggregate stats. */
102
+ stats() {
103
+ const allRuns = this.store.listRuns();
104
+ const allModels = new Set();
105
+ const allPrompts = new Set();
106
+ for (const run of allRuns) {
107
+ allPrompts.add(run.prompt);
108
+ for (const r of run.results)
109
+ allModels.add(r.model);
110
+ }
111
+ let totalRatings = 0;
112
+ for (const run of allRuns) {
113
+ for (const [, score] of Object.entries(run.scores)) {
114
+ if (score.userRating !== undefined)
115
+ totalRatings++;
116
+ }
117
+ }
118
+ return {
119
+ totalRuns: allRuns.length,
120
+ totalModels: allModels.size,
121
+ uniqueModels: allModels.size,
122
+ uniquePrompts: allPrompts.size,
123
+ totalRatings,
124
+ avgModelsPerRun: allRuns.length > 0
125
+ ? Math.round((allRuns.reduce((s, r) => s + r.results.length, 0) / allRuns.length) * 10) / 10
126
+ : 0,
127
+ };
128
+ }
129
+ /** Pick the best model in a run by composite score. */
130
+ pickWinner(run) {
131
+ let best;
132
+ let bestScore = -Infinity;
133
+ for (const [model, score] of Object.entries(run.scores)) {
134
+ const s = (score.userRating !== undefined ? score.userRating / 5 : 0)
135
+ + (score.referenceMatch ?? 0) + (score.latencyScore ?? 0);
136
+ if (s > bestScore) {
137
+ bestScore = s;
138
+ best = model;
139
+ }
140
+ }
141
+ return best;
142
+ }
87
143
  }
package/lib/arena.web.js CHANGED
@@ -26,65 +26,291 @@ __export(index_exports, {
26
26
  inject: () => inject
27
27
  });
28
28
  module.exports = __toCommonJS(index_exports);
29
- var import_react = require("react");
30
29
 
31
- // src/client/view.tsx
32
- function renderPanel(payload) {
33
- const runRows = payload.recentRuns.map((r) => `<tr><td>${r.id}</td><td>${r.modelCount}</td><td>${new Date(r.timestamp).toLocaleString()}</td><td>${r.prompt.slice(0, 60)}...</td></tr>`).join("");
34
- return `<div class="arena-panel">
35
- <h2>Model Arena</h2>
36
- ${runRows ? `<table class="arena-table"><thead><tr><th>Run</th><th>Models</th><th>Date</th><th>Prompt</th></tr></thead><tbody>${runRows}</tbody></table>` : "<p>No runs yet.</p>"}
37
- </div>`;
38
- }
30
+ // src/client/locales.ts
31
+ var NS = "modelArena";
32
+ var zh = {
33
+ "nav": "\u6A21\u578B\u7ADE\u6280\u573A",
34
+ "title": "\u6A21\u578B\u7ADE\u6280\u573A",
35
+ "run": "\u8FD0\u884C",
36
+ "models": "\u6A21\u578B\u6570",
37
+ "model": "\u6A21\u578B",
38
+ "date": "\u65F6\u95F4",
39
+ "prompt": "\u63D0\u793A\u8BCD",
40
+ "empty": "\u8FD8\u6CA1\u6709\u8FD0\u884C\u8BB0\u5F55\u3002\u7528\u540C\u4E00\u4E2A\u63D0\u793A\u8BCD\u8DD1\u591A\u4E2A\u6A21\u578B\u540E\uFF0C\u7ED3\u679C\u4F1A\u663E\u793A\u5728\u8FD9\u91CC\u3002",
41
+ "refresh": "\u5237\u65B0",
42
+ "loading": "\u6B63\u5728\u52A0\u8F7D\u2026",
43
+ "failed": "\u52A0\u8F7D\u5931\u8D25",
44
+ "retry": "\u91CD\u8BD5",
45
+ "leaderboard": "Elo \u6392\u884C\u699C",
46
+ "wins": "\u80DC",
47
+ "avgRating": "\u5E73\u5747\u8BC4\u5206",
48
+ "runs": "\u6B21\u6570",
49
+ "totalRuns": "\u603B\u8FD0\u884C\u6570",
50
+ "uniqueModels": "\u6A21\u578B\u79CD\u7C7B",
51
+ "totalRatings": "\u603B\u8BC4\u5206\u6570",
52
+ "recentRuns": "\u6700\u8FD1\u8FD0\u884C",
53
+ "winner": "\u80DC\u51FA",
54
+ "reference": "\u53C2\u8003\u7B54\u6848",
55
+ "error": "\u9519\u8BEF",
56
+ "delete": "\u5220\u9664",
57
+ "confirmDelete": "\u786E\u5B9A\u5220\u9664\u8FD9\u6761\u8FD0\u884C\u8BB0\u5F55\uFF1F",
58
+ "notFound": "\u672A\u627E\u5230\u8FD0\u884C\u8BB0\u5F55"
59
+ };
60
+ var en = {
61
+ "nav": "Model Arena",
62
+ "title": "Model Arena",
63
+ "run": "Run",
64
+ "models": "Models",
65
+ "model": "Model",
66
+ "date": "Date",
67
+ "prompt": "Prompt",
68
+ "empty": "No runs yet. Run one prompt through several models and the comparison appears here.",
69
+ "refresh": "Refresh",
70
+ "loading": "Loading\u2026",
71
+ "failed": "Failed to load",
72
+ "retry": "Retry",
73
+ "leaderboard": "Elo Leaderboard",
74
+ "wins": "Wins",
75
+ "avgRating": "Avg Rating",
76
+ "runs": "Runs",
77
+ "totalRuns": "Total Runs",
78
+ "uniqueModels": "Models",
79
+ "totalRatings": "Ratings",
80
+ "recentRuns": "Recent Runs",
81
+ "winner": "Winner",
82
+ "reference": "Reference",
83
+ "error": "Error",
84
+ "delete": "Delete",
85
+ "confirmDelete": "Delete this run?",
86
+ "notFound": "Run not found"
87
+ };
39
88
 
40
- // src/client/index.tsx
89
+ // src/client/view.tsx
90
+ var import_react = require("react");
41
91
  var import_jsx_runtime = require("react/jsx-runtime");
42
92
  var PANEL_PATH = "/api/arena.panel";
93
+ var RUN_PATH = "/api/arena.run";
94
+ var RATE_PATH = "/api/arena.rate";
95
+ var DELETE_PATH = "/api/arena.delete";
96
+ var wrap = { display: "flex", flexDirection: "column", gap: 14, maxWidth: 820, fontFamily: "inherit" };
97
+ var head = { display: "flex", alignItems: "baseline", gap: 12, flexWrap: "wrap" };
98
+ var section = { marginTop: 8 };
99
+ var sectionTitle = { fontSize: 13, fontWeight: 600, marginBottom: 6 };
100
+ var muted = { fontSize: 12, opacity: 0.75 };
101
+ var table = { borderCollapse: "collapse", width: "100%" };
102
+ var th = { textAlign: "left", padding: "4px 10px 4px 0", fontWeight: 600, fontSize: 12, opacity: 0.8, borderBottom: "0.5px solid rgba(128,128,128,0.4)" };
103
+ var td = { padding: "6px 10px 6px 0", fontSize: 13, borderBottom: "0.5px solid rgba(128,128,128,0.18)" };
104
+ var card = { padding: "8px 12px", borderRadius: 6, border: "1px solid rgba(128,128,128,0.2)", background: "rgba(128,128,128,0.04)", fontSize: 13 };
105
+ var badge = { display: "inline-block", padding: "1px 6px", borderRadius: 9, fontSize: 11, fontWeight: 600 };
106
+ var statRow = { display: "flex", gap: 16, flexWrap: "wrap" };
107
+ var statBox = { ...card, flex: 1, minWidth: 100, textAlign: "center" };
108
+ var starBtn = { cursor: "pointer", background: "none", border: "none", fontSize: 16, padding: "0 1px" };
109
+ var clickRow = { cursor: "pointer" };
110
+ var preBlock = { whiteSpace: "pre-wrap", wordBreak: "break-word", fontSize: 12, maxHeight: 200, overflow: "auto", padding: 8, borderRadius: 4, background: "rgba(128,128,128,0.06)", border: "1px solid rgba(128,128,128,0.15)" };
111
+ var dangerBtn = { fontSize: 11, color: "#e55", background: "none", border: "1px solid #e55", borderRadius: 4, padding: "2px 8px", cursor: "pointer" };
43
112
  function usePanel() {
44
- const [html, setHtml] = (0, import_react.useState)(null);
45
- const [error, setError] = (0, import_react.useState)(null);
113
+ const [state, setState] = (0, import_react.useState)({ payload: null, error: null });
46
114
  const [tick, setTick] = (0, import_react.useState)(0);
47
- const reload = (0, import_react.useCallback)(() => setTick((t) => t + 1), []);
115
+ const reload = (0, import_react.useCallback)(() => setTick((v) => v + 1), []);
48
116
  (0, import_react.useEffect)(() => {
49
- const controller = new AbortController();
50
- setError(null);
51
- fetch(PANEL_PATH, { signal: controller.signal }).then(async (response) => {
52
- if (!response.ok) throw new Error(`panel request failed with ${response.status}`);
53
- return response.json();
117
+ const c = new AbortController();
118
+ setState((p) => ({ ...p, error: null }));
119
+ fetch(PANEL_PATH, { signal: c.signal }).then(async (r) => {
120
+ if (!r.ok) throw new Error(String(r.status));
121
+ return r.json();
54
122
  }).then((payload) => {
55
- if (!controller.signal.aborted) setHtml(renderPanel(payload));
56
- }).catch((cause) => {
57
- if (controller.signal.aborted) return;
58
- setError(cause instanceof Error ? cause.message : String(cause));
123
+ if (!c.signal.aborted) setState({ payload, error: null });
124
+ }).catch((e) => {
125
+ if (!c.signal.aborted) setState({ payload: null, error: e instanceof Error ? e.message : String(e) });
59
126
  });
60
- return () => controller.abort();
127
+ return () => c.abort();
61
128
  }, [tick]);
62
- return { html, error, reload };
129
+ return { ...state, reload };
130
+ }
131
+ function Stars({ rating, onRate }) {
132
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: [1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", style: starBtn, onClick: () => onRate(n), title: `${n}/5`, children: n <= rating ? "\u2605" : "\u2606" }, n)) });
133
+ }
134
+ function Leaderboard({ entries, t }) {
135
+ if (entries.length === 0) return null;
136
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: section, children: [
137
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: sectionTitle, children: [
138
+ "\u{1F3C6} ",
139
+ t("leaderboard")
140
+ ] }),
141
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { style: table, children: [
142
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
143
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: "#" }),
144
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("model") }),
145
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: "Elo" }),
146
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("wins") }),
147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("avgRating") }),
148
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("runs") })
149
+ ] }) }),
150
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: entries.map((e, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
151
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: i + 1 }),
152
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: e.model }) }),
153
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: e.elo }),
154
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: e.wins }),
155
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: e.avgRating > 0 ? e.avgRating.toFixed(1) : "\u2014" }),
156
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: e.totalRuns })
157
+ ] }, e.model)) })
158
+ ] })
159
+ ] });
160
+ }
161
+ function StatsSummary({ stats, t }) {
162
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: statRow, children: [
163
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: statBox, children: [
164
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 20, fontWeight: 700 }, children: stats.totalRuns }),
165
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: muted, children: t("totalRuns") })
166
+ ] }),
167
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: statBox, children: [
168
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 20, fontWeight: 700 }, children: stats.uniqueModels }),
169
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: muted, children: t("uniqueModels") })
170
+ ] }),
171
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: statBox, children: [
172
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 20, fontWeight: 700 }, children: stats.totalRatings }),
173
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: muted, children: t("totalRatings") })
174
+ ] })
175
+ ] });
63
176
  }
64
- function ModelArenaPage() {
65
- const { html, error, reload } = usePanel();
177
+ function RunDetail({ runId, t, onDelete }) {
178
+ const [run, setRun] = (0, import_react.useState)(null);
179
+ const [loading, setLoading] = (0, import_react.useState)(true);
180
+ (0, import_react.useEffect)(() => {
181
+ setLoading(true);
182
+ fetch(`${RUN_PATH}?id=${runId}`).then((r) => r.json()).then(setRun).catch(() => setRun(null)).finally(() => setLoading(false));
183
+ }, [runId]);
184
+ const handleRate = (0, import_react.useCallback)((model, rating) => {
185
+ fetch(RATE_PATH, {
186
+ method: "POST",
187
+ headers: { "Content-Type": "application/json" },
188
+ body: JSON.stringify({ runId, model, rating })
189
+ }).then((r) => r.json()).then(setRun).catch(() => {
190
+ });
191
+ }, [runId]);
192
+ const handleDelete = (0, import_react.useCallback)(() => {
193
+ if (!confirm(t("confirmDelete"))) return;
194
+ fetch(DELETE_PATH, {
195
+ method: "POST",
196
+ headers: { "Content-Type": "application/json" },
197
+ body: JSON.stringify({ runId })
198
+ }).then(() => onDelete()).catch(() => {
199
+ });
200
+ }, [runId, t, onDelete]);
201
+ if (loading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: muted, children: t("loading") });
202
+ if (!run) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: muted, children: t("notFound") });
203
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { ...card, marginTop: 4 }, children: [
204
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }, children: [
205
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { children: [
206
+ t("prompt"),
207
+ ": "
208
+ ] }),
209
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", style: dangerBtn, onClick: handleDelete, children: t("delete") })
210
+ ] }),
211
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: preBlock, children: run.prompt }),
212
+ run.reference && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { marginTop: 6 }, children: [
213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { fontSize: 12, fontWeight: 600, marginBottom: 2 }, children: [
214
+ t("reference"),
215
+ ":"
216
+ ] }),
217
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: preBlock, children: run.reference })
218
+ ] }),
219
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { marginTop: 8 }, children: run.results.map((r) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { ...card, marginTop: 6 }, children: [
220
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" }, children: [
221
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: r.model }),
222
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: r.error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...badge, background: "rgba(255,80,80,0.15)", color: "#e55" }, children: t("error") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: { ...badge, background: "rgba(80,200,120,0.15)", color: "#4a4" }, children: [
223
+ r.latencyMs,
224
+ "ms"
225
+ ] }) })
226
+ ] }),
227
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: preBlock, children: r.error ?? r.output }),
228
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 4 }, children: [
229
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: muted, children: [
230
+ r.promptTokens + r.completionTokens,
231
+ " tokens (",
232
+ r.promptTokens,
233
+ "\u2191 ",
234
+ r.completionTokens,
235
+ "\u2193)"
236
+ ] }),
237
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Stars, { rating: r.rating ?? 0, onRate: (n) => handleRate(r.model, n) })
238
+ ] })
239
+ ] }, r.model)) })
240
+ ] });
241
+ }
242
+ function ArenaPanel({ t }) {
243
+ const { payload, error, reload } = usePanel();
244
+ const [expandedRun, setExpandedRun] = (0, import_react.useState)(null);
245
+ const header = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { style: head, children: [
246
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { style: { fontSize: 14 }, children: [
247
+ "\u2694\uFE0F ",
248
+ t("title")
249
+ ] }),
250
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { flex: 1 } }),
251
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: reload, style: { fontSize: 12 }, children: t("refresh") })
252
+ ] });
66
253
  if (error !== null) {
67
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { children: [
68
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { role: "alert", children: [
69
- "Model Arena panel failed to load: ",
254
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: wrap, children: [
255
+ header,
256
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { role: "alert", style: { margin: 0, fontSize: 13 }, children: [
257
+ t("failed"),
258
+ ": ",
70
259
  error
71
260
  ] }),
72
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: reload, children: "Retry" })
261
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: reload, style: { alignSelf: "flex-start", fontSize: 12 }, children: t("retry") })
73
262
  ] });
74
263
  }
75
- if (html === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { "aria-live": "polite", children: "Loading the Model Arena panel\u2026" });
76
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { dangerouslySetInnerHTML: { __html: html } });
264
+ if (payload === null) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: muted, "aria-live": "polite", children: t("loading") });
265
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: wrap, children: [
266
+ header,
267
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StatsSummary, { stats: payload.stats, t }),
268
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Leaderboard, { entries: payload.leaderboard, t }),
269
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: section, children: [
270
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: sectionTitle, children: [
271
+ "\u{1F4CB} ",
272
+ t("recentRuns")
273
+ ] }),
274
+ payload.recentRuns.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: { margin: 0, fontSize: 13, opacity: 0.8 }, children: t("empty") }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", { style: table, children: [
275
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { children: [
276
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("run") }),
277
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("models") }),
278
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("winner") }),
279
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("th", { style: th, children: t("date") })
280
+ ] }) }),
281
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tbody", { children: payload.recentRuns.map((row) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
282
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", { style: clickRow, onClick: () => setExpandedRun(expandedRun === row.id ? null : row.id), children: [
283
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("td", { style: td, children: [
284
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { style: { fontSize: 11 }, children: row.id }),
285
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { marginLeft: 6, fontSize: 10 }, children: expandedRun === row.id ? "\u25BC" : "\u25B6" })
286
+ ] }),
287
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: row.modelCount }),
288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: row.winner ?? "\u2014" }),
289
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { style: td, children: new Date(row.timestamp).toLocaleString() })
290
+ ] }, row.id),
291
+ expandedRun === row.id && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("td", { colSpan: 4, style: { padding: "4px 0" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RunDetail, { runId: row.id, t, onDelete: () => {
292
+ setExpandedRun(null);
293
+ reload();
294
+ } }) }) }, `${row.id}-detail`)
295
+ ] })) })
296
+ ] })
297
+ ] })
298
+ ] });
77
299
  }
78
- var inject = ["slots"];
300
+
301
+ // src/client/index.tsx
302
+ var inject = ["slots", "locale"];
79
303
  function apply(ctx) {
304
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-model-arena: dictionaries");
80
305
  ctx.slots.inject("settings.section", () => ctx.slots.register(
81
306
  {
82
307
  name: "settings.section",
83
308
  id: "model-arena",
84
309
  order: 42,
85
- label: () => "Model Arena"
310
+ label: () => ctx.locale.bind(NS)("nav"),
311
+ locale: NS
86
312
  },
87
- ModelArenaPage
313
+ ArenaPanel
88
314
  ));
89
315
  }
90
316
  return module.exports; } });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/client/index.tsx", "../src/client/view.tsx"],
4
- "sourcesContent": ["/**\n * Browser half of dsh-model-arena: the Model Arena page inside Settings.\n *\n * Services are named by their runtime identity, not by the package that provides them:\n * the settings shell is the `slots` service and the host bridge is `connection`. A\n * fiber that injects package names never resolves and the plugin stays pending, which\n * the loader reports as \"did not activate\".\n *\n * @module client\n */\n\nimport { useCallback, useEffect, useState } from 'react'\nimport type { ComponentType } from 'react'\nimport type { PanelPayload } from '../types.js'\nimport { renderPanel } from './view.js'\n\n/** Path the host half registers on the web connection. */\nconst PANEL_PATH = '/api/arena.panel'\n\ninterface SlotsService {\n inject(name: string, register: () => void): void\n register(options: { name: string; id: string; order: number; label: () => string }, component: ComponentType): unknown\n}\n\n/** Fetch the panel payload; `reload` re-runs the request. */\nfunction usePanel() {\n const [html, setHtml] = useState<string | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [tick, setTick] = useState(0)\n const reload = useCallback(() => setTick((t) => t + 1), [])\n\n useEffect(() => {\n const controller = new AbortController()\n setError(null)\n fetch(PANEL_PATH, { signal: controller.signal })\n .then(async (response) => {\n if (!response.ok) throw new Error(`panel request failed with ${response.status}`)\n return response.json() as Promise<PanelPayload>\n })\n .then((payload) => {\n if (!controller.signal.aborted) setHtml(renderPanel(payload))\n })\n .catch((cause: unknown) => {\n if (controller.signal.aborted) return\n // Say what failed rather than rendering an empty panel, which would read as\n // \"nothing recorded\" \u2014 a different and wrong answer.\n setError(cause instanceof Error ? cause.message : String(cause))\n })\n return () => controller.abort()\n }, [tick])\n\n return { html, error, reload }\n}\n\nfunction ModelArenaPage() {\n const { html, error, reload } = usePanel()\n if (error !== null) {\n return (\n <section>\n <p role=\"alert\">Model Arena panel failed to load: {error}</p>\n <button type=\"button\" onClick={reload}>Retry</button>\n </section>\n )\n }\n if (html === null) return <p aria-live=\"polite\">Loading the Model Arena panel\u2026</p>\n // The markup is produced by `renderPanel`, which escapes every interpolated value.\n return <div dangerouslySetInnerHTML={{ __html: html }} />\n}\n\n/** The slots service is what the settings shell exposes. */\nexport const inject = ['slots']\n\nexport function apply(ctx: { slots: SlotsService }): void {\n ctx.slots.inject('settings.section', () => ctx.slots.register(\n {\n name: 'settings.section',\n id: 'model-arena',\n order: 42,\n label: () => 'Model Arena',\n },\n ModelArenaPage,\n ))\n}\n", "import type { PanelPayload } from '../types.js';\n\nexport function renderPanel(payload: PanelPayload): string {\n const runRows = payload.recentRuns\n .map((r) => `<tr><td>${r.id}</td><td>${r.modelCount}</td><td>${new Date(r.timestamp).toLocaleString()}</td><td>${r.prompt.slice(0, 60)}...</td></tr>`)\n .join('');\n\n return `<div class=\"arena-panel\">\n <h2>Model Arena</h2>\n ${runRows ? `<table class=\"arena-table\"><thead><tr><th>Run</th><th>Models</th><th>Date</th><th>Prompt</th></tr></thead><tbody>${runRows}</tbody></table>` : '<p>No runs yet.</p>'}\n </div>`;\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA,mBAAiD;;;ACT1C,SAAS,YAAY,SAA+B;AACzD,QAAM,UAAU,QAAQ,WACrB,IAAI,CAAC,MAAM,WAAW,EAAE,EAAE,YAAY,EAAE,UAAU,YAAY,IAAI,KAAK,EAAE,SAAS,EAAE,eAAe,CAAC,YAAY,EAAE,OAAO,MAAM,GAAG,EAAE,CAAC,eAAe,EACpJ,KAAK,EAAE;AAEV,SAAO;AAAA;AAAA,MAEH,UAAU,oHAAoH,OAAO,qBAAqB,qBAAqB;AAAA;AAErL;;;ADgDQ;AA1CR,IAAM,aAAa;AAQnB,SAAS,WAAW;AAClB,QAAM,CAAC,MAAM,OAAO,QAAI,uBAAwB,IAAI;AACpD,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAwB,IAAI;AACtD,QAAM,CAAC,MAAM,OAAO,QAAI,uBAAS,CAAC;AAClC,QAAM,aAAS,0BAAY,MAAM,QAAQ,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;AAE1D,8BAAU,MAAM;AACd,UAAM,aAAa,IAAI,gBAAgB;AACvC,aAAS,IAAI;AACb,UAAM,YAAY,EAAE,QAAQ,WAAW,OAAO,CAAC,EAC5C,KAAK,OAAO,aAAa;AACxB,UAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,6BAA6B,SAAS,MAAM,EAAE;AAChF,aAAO,SAAS,KAAK;AAAA,IACvB,CAAC,EACA,KAAK,CAAC,YAAY;AACjB,UAAI,CAAC,WAAW,OAAO,QAAS,SAAQ,YAAY,OAAO,CAAC;AAAA,IAC9D,CAAC,EACA,MAAM,CAAC,UAAmB;AACzB,UAAI,WAAW,OAAO,QAAS;AAG/B,eAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,IACjE,CAAC;AACH,WAAO,MAAM,WAAW,MAAM;AAAA,EAChC,GAAG,CAAC,IAAI,CAAC;AAET,SAAO,EAAE,MAAM,OAAO,OAAO;AAC/B;AAEA,SAAS,iBAAiB;AACxB,QAAM,EAAE,MAAM,OAAO,OAAO,IAAI,SAAS;AACzC,MAAI,UAAU,MAAM;AAClB,WACE,6CAAC,aACC;AAAA,mDAAC,OAAE,MAAK,SAAQ;AAAA;AAAA,QAAmC;AAAA,SAAM;AAAA,MACzD,4CAAC,YAAO,MAAK,UAAS,SAAS,QAAQ,mBAAK;AAAA,OAC9C;AAAA,EAEJ;AACA,MAAI,SAAS,KAAM,QAAO,4CAAC,OAAE,aAAU,UAAS,iDAA8B;AAE9E,SAAO,4CAAC,SAAI,yBAAyB,EAAE,QAAQ,KAAK,GAAG;AACzD;AAGO,IAAM,SAAS,CAAC,OAAO;AAEvB,SAAS,MAAM,KAAoC;AACxD,MAAI,MAAM,OAAO,oBAAoB,MAAM,IAAI,MAAM;AAAA,IACnD;AAAA,MACE,MAAM;AAAA,MACN,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,OAAO,MAAM;AAAA,IACf;AAAA,IACA;AAAA,EACF,CAAC;AACH;",
3
+ "sources": ["../src/client/index.tsx", "../src/client/locales.ts", "../src/client/view.tsx"],
4
+ "sourcesContent": ["/**\n * Browser half of dsh-model-arena: its page inside Settings.\n *\n * Two services are injected by their runtime identity, not by the packages that provide\n * them: the settings shell is `slots`, the dictionary registry is `locale`. The label and\n * every string inside the component are translated through this plugin's own namespace,\n * which is why the page follows the UI language instead of the plugin author's.\n *\n * @module client\n */\n\nimport type { ComponentType } from 'react'\n\nimport { NS, en, zh } from './locales.js'\nimport { ArenaPanel } from './view.js'\n\nexport const inject = ['slots', 'locale']\n\ninterface SlotsService {\n inject(name: string, register: () => void): void\n register(\n options: { name: string; id: string; order: number; label: () => string; locale: string },\n component: ComponentType<{ t: (key: string, params?: Record<string, unknown>) => string }>,\n ): unknown\n}\n\ninterface ClientContext {\n slots: SlotsService\n locale: {\n register(ns: string, dicts: { zh: unknown; en: unknown }): () => void\n bind(ns: string): (key: string, params?: Record<string, unknown>) => string\n }\n effect(callback: () => unknown, label?: string): unknown\n}\n\nexport function apply(ctx: ClientContext): void {\n // `zh` is the key-set source of truth, matching the official client plugins.\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-model-arena: dictionaries')\n\n ctx.slots.inject('settings.section', () => ctx.slots.register(\n {\n name: 'settings.section',\n id: 'model-arena',\n order: 42,\n label: () => ctx.locale.bind(NS)('nav'),\n locale: NS,\n },\n ArenaPanel,\n ))\n}\n", "/**\n * Dictionaries for the Model Arena page.\n *\n * `zh` is the key-set source of truth, as in the official client plugins, and `en` is\n * typed against it: a key translated in one language but not the other fails the build\n * instead of silently rendering the raw key.\n *\n * @module client/locales\n */\n\n/** Dictionary namespace owned by this plugin. */\nexport const NS = 'modelArena'\n\n/** Simplified Chinese dictionary (the key-set source of truth). */\nexport const zh = {\n 'nav': '\u6A21\u578B\u7ADE\u6280\u573A',\n 'title': '\u6A21\u578B\u7ADE\u6280\u573A',\n 'run': '\u8FD0\u884C',\n 'models': '\u6A21\u578B\u6570',\n 'model': '\u6A21\u578B',\n 'date': '\u65F6\u95F4',\n 'prompt': '\u63D0\u793A\u8BCD',\n 'empty': '\u8FD8\u6CA1\u6709\u8FD0\u884C\u8BB0\u5F55\u3002\u7528\u540C\u4E00\u4E2A\u63D0\u793A\u8BCD\u8DD1\u591A\u4E2A\u6A21\u578B\u540E\uFF0C\u7ED3\u679C\u4F1A\u663E\u793A\u5728\u8FD9\u91CC\u3002',\n 'refresh': '\u5237\u65B0',\n 'loading': '\u6B63\u5728\u52A0\u8F7D\u2026',\n 'failed': '\u52A0\u8F7D\u5931\u8D25',\n 'retry': '\u91CD\u8BD5',\n 'leaderboard': 'Elo \u6392\u884C\u699C',\n 'wins': '\u80DC',\n 'avgRating': '\u5E73\u5747\u8BC4\u5206',\n 'runs': '\u6B21\u6570',\n 'totalRuns': '\u603B\u8FD0\u884C\u6570',\n 'uniqueModels': '\u6A21\u578B\u79CD\u7C7B',\n 'totalRatings': '\u603B\u8BC4\u5206\u6570',\n 'recentRuns': '\u6700\u8FD1\u8FD0\u884C',\n 'winner': '\u80DC\u51FA',\n 'reference': '\u53C2\u8003\u7B54\u6848',\n 'error': '\u9519\u8BEF',\n 'delete': '\u5220\u9664',\n 'confirmDelete': '\u786E\u5B9A\u5220\u9664\u8FD9\u6761\u8FD0\u884C\u8BB0\u5F55\uFF1F',\n 'notFound': '\u672A\u627E\u5230\u8FD0\u884C\u8BB0\u5F55',\n}\n\n/** English dictionary, checked complete against the zh key set. */\nexport const en: typeof zh = {\n 'nav': 'Model Arena',\n 'title': 'Model Arena',\n 'run': 'Run',\n 'models': 'Models',\n 'model': 'Model',\n 'date': 'Date',\n 'prompt': 'Prompt',\n 'empty': 'No runs yet. Run one prompt through several models and the comparison appears here.',\n 'refresh': 'Refresh',\n 'loading': 'Loading\u2026',\n 'failed': 'Failed to load',\n 'retry': 'Retry',\n 'leaderboard': 'Elo Leaderboard',\n 'wins': 'Wins',\n 'avgRating': 'Avg Rating',\n 'runs': 'Runs',\n 'totalRuns': 'Total Runs',\n 'uniqueModels': 'Models',\n 'totalRatings': 'Ratings',\n 'recentRuns': 'Recent Runs',\n 'winner': 'Winner',\n 'reference': 'Reference',\n 'error': 'Error',\n 'delete': 'Delete',\n 'confirmDelete': 'Delete this run?',\n 'notFound': 'Run not found',\n}\n", "/**\n * Pure rendering half of the modelArena page.\n *\n * Separate from `index.tsx` so a static render can assert in Node what the page draws \u2014\n * the shipped bundle is a loader factory only a browser can run. Every user-visible\n * string comes from the `t` seat the renderer binds from this plugin's namespace, so the\n * page follows the UI language; no copy is hardcoded here.\n *\n * @module client/view\n */\n\nimport { useCallback, useEffect, useState } from 'react'\nimport type { CSSProperties } from 'react'\n\nimport type { PanelPayload, LeaderboardEntry, ArenaStats, RunSummary, ArenaRun } from '../types.js'\n\nexport type Translate = (key: string, params?: Record<string, unknown>) => string\n\nexport interface PanelProps {\n t: Translate\n}\n\nconst PANEL_PATH = '/api/arena.panel'\nconst RUN_PATH = '/api/arena.run'\nconst RATE_PATH = '/api/arena.rate'\nconst DELETE_PATH = '/api/arena.delete'\n\n// --- Styles ---\nconst wrap: CSSProperties = { display: 'flex', flexDirection: 'column', gap: 14, maxWidth: 820, fontFamily: 'inherit' }\nconst head: CSSProperties = { display: 'flex', alignItems: 'baseline', gap: 12, flexWrap: 'wrap' }\nconst section: CSSProperties = { marginTop: 8 }\nconst sectionTitle: CSSProperties = { fontSize: 13, fontWeight: 600, marginBottom: 6 }\nconst muted: CSSProperties = { fontSize: 12, opacity: 0.75 }\nconst table: CSSProperties = { borderCollapse: 'collapse', width: '100%' }\nconst th: CSSProperties = { textAlign: 'left', padding: '4px 10px 4px 0', fontWeight: 600, fontSize: 12, opacity: 0.8, borderBottom: '0.5px solid rgba(128,128,128,0.4)' }\nconst td: CSSProperties = { padding: '6px 10px 6px 0', fontSize: 13, borderBottom: '0.5px solid rgba(128,128,128,0.18)' }\nconst card: CSSProperties = { padding: '8px 12px', borderRadius: 6, border: '1px solid rgba(128,128,128,0.2)', background: 'rgba(128,128,128,0.04)', fontSize: 13 }\nconst badge: CSSProperties = { display: 'inline-block', padding: '1px 6px', borderRadius: 9, fontSize: 11, fontWeight: 600 }\nconst statRow: CSSProperties = { display: 'flex', gap: 16, flexWrap: 'wrap' }\nconst statBox: CSSProperties = { ...card, flex: 1, minWidth: 100, textAlign: 'center' as const }\nconst starBtn: CSSProperties = { cursor: 'pointer', background: 'none', border: 'none', fontSize: 16, padding: '0 1px' }\nconst clickRow: CSSProperties = { cursor: 'pointer' }\nconst preBlock: CSSProperties = { whiteSpace: 'pre-wrap', wordBreak: 'break-word', fontSize: 12, maxHeight: 200, overflow: 'auto', padding: 8, borderRadius: 4, background: 'rgba(128,128,128,0.06)', border: '1px solid rgba(128,128,128,0.15)' }\nconst dangerBtn: CSSProperties = { fontSize: 11, color: '#e55', background: 'none', border: '1px solid #e55', borderRadius: 4, padding: '2px 8px', cursor: 'pointer' }\n\ninterface PanelState {\n payload: PanelPayload | null\n error: string | null\n}\n\nexport function usePanel(): PanelState & { reload: () => void } {\n const [state, setState] = useState<PanelState>({ payload: null, error: null })\n const [tick, setTick] = useState(0)\n const reload = useCallback(() => setTick((v) => v + 1), [])\n useEffect(() => {\n const c = new AbortController()\n setState((p) => ({ ...p, error: null }))\n fetch(PANEL_PATH, { signal: c.signal })\n .then(async (r) => {\n if (!r.ok) throw new Error(String(r.status))\n return r.json() as Promise<PanelPayload>\n })\n .then((payload) => { if (!c.signal.aborted) setState({ payload, error: null }) })\n .catch((e: unknown) => { if (!c.signal.aborted) setState({ payload: null, error: e instanceof Error ? e.message : String(e) }) })\n return () => c.abort()\n }, [tick])\n return { ...state, reload }\n}\n\n// --- Stars ---\nfunction Stars({ rating, onRate }: { rating: number; onRate: (n: number) => void }) {\n return (\n <span>\n {[1, 2, 3, 4, 5].map((n) => (\n <button key={n} type=\"button\" style={starBtn} onClick={() => onRate(n)} title={`${n}/5`}>\n {n <= rating ? '\u2605' : '\u2606'}\n </button>\n ))}\n </span>\n )\n}\n\n// --- Leaderboard ---\nfunction Leaderboard({ entries, t }: { entries: LeaderboardEntry[]; t: Translate }) {\n if (entries.length === 0) return null\n return (\n <div style={section}>\n <div style={sectionTitle}>\uD83C\uDFC6 {t('leaderboard')}</div>\n <table style={table}>\n <thead>\n <tr>\n <th style={th}>#</th>\n <th style={th}>{t('model')}</th>\n <th style={th}>Elo</th>\n <th style={th}>{t('wins')}</th>\n <th style={th}>{t('avgRating')}</th>\n <th style={th}>{t('runs')}</th>\n </tr>\n </thead>\n <tbody>\n {entries.map((e, i) => (\n <tr key={e.model}>\n <td style={td}>{i + 1}</td>\n <td style={td}><strong>{e.model}</strong></td>\n <td style={td}>{e.elo}</td>\n <td style={td}>{e.wins}</td>\n <td style={td}>{e.avgRating > 0 ? e.avgRating.toFixed(1) : '\u2014'}</td>\n <td style={td}>{e.totalRuns}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )\n}\n\n// --- Stats summary ---\nfunction StatsSummary({ stats, t }: { stats: ArenaStats; t: Translate }) {\n return (\n <div style={statRow}>\n <div style={statBox}><div style={{ fontSize: 20, fontWeight: 700 }}>{stats.totalRuns}</div><div style={muted}>{t('totalRuns')}</div></div>\n <div style={statBox}><div style={{ fontSize: 20, fontWeight: 700 }}>{stats.uniqueModels}</div><div style={muted}>{t('uniqueModels')}</div></div>\n <div style={statBox}><div style={{ fontSize: 20, fontWeight: 700 }}>{stats.totalRatings}</div><div style={muted}>{t('totalRatings')}</div></div>\n </div>\n )\n}\n\n// --- Run detail expansion ---\nfunction RunDetail({ runId, t, onDelete }: { runId: string; t: Translate; onDelete: () => void }) {\n const [run, setRun] = useState<ArenaRun | null>(null)\n const [loading, setLoading] = useState(true)\n useEffect(() => {\n setLoading(true)\n fetch(`${RUN_PATH}?id=${runId}`)\n .then((r) => r.json() as Promise<ArenaRun>)\n .then(setRun)\n .catch(() => setRun(null))\n .finally(() => setLoading(false))\n }, [runId])\n\n const handleRate = useCallback((model: string, rating: number) => {\n fetch(RATE_PATH, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ runId, model, rating }),\n })\n .then((r) => r.json() as Promise<ArenaRun>)\n .then(setRun)\n .catch(() => {})\n }, [runId])\n\n const handleDelete = useCallback(() => {\n if (!confirm(t('confirmDelete'))) return\n fetch(DELETE_PATH, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ runId }),\n }).then(() => onDelete()).catch(() => {})\n }, [runId, t, onDelete])\n\n if (loading) return <p style={muted}>{t('loading')}</p>\n if (!run) return <p style={muted}>{t('notFound')}</p>\n return (\n <div style={{ ...card, marginTop: 4 }}>\n <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>\n <strong>{t('prompt')}: </strong>\n <button type=\"button\" style={dangerBtn} onClick={handleDelete}>{t('delete')}</button>\n </div>\n <div style={preBlock}>{run.prompt}</div>\n {run.reference && (\n <div style={{ marginTop: 6 }}>\n <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 2 }}>{t('reference')}:</div>\n <div style={preBlock}>{run.reference}</div>\n </div>\n )}\n <div style={{ marginTop: 8 }}>\n {run.results.map((r) => (\n <div key={r.model} style={{ ...card, marginTop: 6 }}>\n <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>\n <strong>{r.model}</strong>\n <span>\n {r.error ? (\n <span style={{ ...badge, background: 'rgba(255,80,80,0.15)', color: '#e55' }}>{t('error')}</span>\n ) : (\n <span style={{ ...badge, background: 'rgba(80,200,120,0.15)', color: '#4a4' }}>{r.latencyMs}ms</span>\n )}\n </span>\n </div>\n <div style={preBlock}>{r.error ?? r.output}</div>\n <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 4 }}>\n <span style={muted}>{r.promptTokens + r.completionTokens} tokens ({r.promptTokens}\u2191 {r.completionTokens}\u2193)</span>\n <Stars rating={r.rating ?? 0} onRate={(n) => handleRate(r.model, n)} />\n </div>\n </div>\n ))}\n </div>\n </div>\n )\n}\n\n// --- Main Panel ---\nexport function ArenaPanel({ t }: PanelProps) {\n const { payload, error, reload } = usePanel()\n const [expandedRun, setExpandedRun] = useState<string | null>(null)\n\n const header = (\n <header style={head}>\n <strong style={{ fontSize: 14 }}>\u2694\uFE0F {t('title')}</strong>\n <span style={{ flex: 1 }} />\n <button type=\"button\" onClick={reload} style={{ fontSize: 12 }}>{t('refresh')}</button>\n </header>\n )\n if (error !== null) {\n return (\n <div style={wrap}>\n {header}\n <p role=\"alert\" style={{ margin: 0, fontSize: 13 }}>{t('failed')}: {error}</p>\n <button type=\"button\" onClick={reload} style={{ alignSelf: 'flex-start', fontSize: 12 }}>{t('retry')}</button>\n </div>\n )\n }\n if (payload === null) return <p style={muted} aria-live=\"polite\">{t('loading')}</p>\n return (\n <div style={wrap}>\n {header}\n\n {/* Stats */}\n <StatsSummary stats={payload.stats} t={t} />\n\n {/* Leaderboard */}\n <Leaderboard entries={payload.leaderboard} t={t} />\n\n {/* Recent runs */}\n <div style={section}>\n <div style={sectionTitle}>\uD83D\uDCCB {t('recentRuns')}</div>\n {payload.recentRuns.length === 0 ? (\n <p style={{ margin: 0, fontSize: 13, opacity: 0.8 }}>{t('empty')}</p>\n ) : (\n <table style={table}>\n <thead>\n <tr><th style={th}>{t('run')}</th><th style={th}>{t('models')}</th><th style={th}>{t('winner')}</th><th style={th}>{t('date')}</th></tr>\n </thead>\n <tbody>\n {payload.recentRuns.map((row) => (\n <>\n <tr key={row.id} style={clickRow} onClick={() => setExpandedRun(expandedRun === row.id ? null : row.id)}>\n <td style={td}>\n <code style={{ fontSize: 11 }}>{row.id}</code>\n <span style={{ marginLeft: 6, fontSize: 10 }}>{expandedRun === row.id ? '\u25BC' : '\u25B6'}</span>\n </td>\n <td style={td}>{row.modelCount}</td>\n <td style={td}>{row.winner ?? '\u2014'}</td>\n <td style={td}>{new Date(row.timestamp).toLocaleString()}</td>\n </tr>\n {expandedRun === row.id && (\n <tr key={`${row.id}-detail`}>\n <td colSpan={4} style={{ padding: '4px 0' }}>\n <RunDetail runId={row.id} t={t} onDelete={() => { setExpandedRun(null); reload() }} />\n </td>\n </tr>\n )}\n </>\n ))}\n </tbody>\n </table>\n )}\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACWO,IAAM,KAAK;AAGX,IAAM,KAAK;AAAA,EAChB,OAAO;AAAA,EACP,SAAS;AAAA,EACT,OAAO;AAAA,EACP,UAAU;AAAA,EACV,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT,WAAW;AAAA,EACX,WAAW;AAAA,EACX,UAAU;AAAA,EACV,SAAS;AAAA,EACT,eAAe;AAAA,EACf,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,YAAY;AACd;AAGO,IAAM,KAAgB;AAAA,EAC3B,OAAO;AAAA,EACP,SAAS;AAAA,EACT,OAAO;AAAA,EACP,UAAU;AAAA,EACV,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,SAAS;AAAA,EACT,WAAW;AAAA,EACX,WAAW;AAAA,EACX,UAAU;AAAA,EACV,SAAS;AAAA,EACT,eAAe;AAAA,EACf,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,cAAc;AAAA,EACd,UAAU;AAAA,EACV,aAAa;AAAA,EACb,SAAS;AAAA,EACT,UAAU;AAAA,EACV,iBAAiB;AAAA,EACjB,YAAY;AACd;;;AC5DA,mBAAiD;AA+DzC;AApDR,IAAM,aAAa;AACnB,IAAM,WAAW;AACjB,IAAM,YAAY;AAClB,IAAM,cAAc;AAGpB,IAAM,OAAsB,EAAE,SAAS,QAAQ,eAAe,UAAU,KAAK,IAAI,UAAU,KAAK,YAAY,UAAU;AACtH,IAAM,OAAsB,EAAE,SAAS,QAAQ,YAAY,YAAY,KAAK,IAAI,UAAU,OAAO;AACjG,IAAM,UAAyB,EAAE,WAAW,EAAE;AAC9C,IAAM,eAA8B,EAAE,UAAU,IAAI,YAAY,KAAK,cAAc,EAAE;AACrF,IAAM,QAAuB,EAAE,UAAU,IAAI,SAAS,KAAK;AAC3D,IAAM,QAAuB,EAAE,gBAAgB,YAAY,OAAO,OAAO;AACzE,IAAM,KAAoB,EAAE,WAAW,QAAQ,SAAS,kBAAkB,YAAY,KAAK,UAAU,IAAI,SAAS,KAAK,cAAc,oCAAoC;AACzK,IAAM,KAAoB,EAAE,SAAS,kBAAkB,UAAU,IAAI,cAAc,qCAAqC;AACxH,IAAM,OAAsB,EAAE,SAAS,YAAY,cAAc,GAAG,QAAQ,mCAAmC,YAAY,0BAA0B,UAAU,GAAG;AAClK,IAAM,QAAuB,EAAE,SAAS,gBAAgB,SAAS,WAAW,cAAc,GAAG,UAAU,IAAI,YAAY,IAAI;AAC3H,IAAM,UAAyB,EAAE,SAAS,QAAQ,KAAK,IAAI,UAAU,OAAO;AAC5E,IAAM,UAAyB,EAAE,GAAG,MAAM,MAAM,GAAG,UAAU,KAAK,WAAW,SAAkB;AAC/F,IAAM,UAAyB,EAAE,QAAQ,WAAW,YAAY,QAAQ,QAAQ,QAAQ,UAAU,IAAI,SAAS,QAAQ;AACvH,IAAM,WAA0B,EAAE,QAAQ,UAAU;AACpD,IAAM,WAA0B,EAAE,YAAY,YAAY,WAAW,cAAc,UAAU,IAAI,WAAW,KAAK,UAAU,QAAQ,SAAS,GAAG,cAAc,GAAG,YAAY,0BAA0B,QAAQ,mCAAmC;AACjP,IAAM,YAA2B,EAAE,UAAU,IAAI,OAAO,QAAQ,YAAY,QAAQ,QAAQ,kBAAkB,cAAc,GAAG,SAAS,WAAW,QAAQ,UAAU;AAO9J,SAAS,WAAgD;AAC9D,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAqB,EAAE,SAAS,MAAM,OAAO,KAAK,CAAC;AAC7E,QAAM,CAAC,MAAM,OAAO,QAAI,uBAAS,CAAC;AAClC,QAAM,aAAS,0BAAY,MAAM,QAAQ,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1D,8BAAU,MAAM;AACd,UAAM,IAAI,IAAI,gBAAgB;AAC9B,aAAS,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,KAAK,EAAE;AACvC,UAAM,YAAY,EAAE,QAAQ,EAAE,OAAO,CAAC,EACnC,KAAK,OAAO,MAAM;AACjB,UAAI,CAAC,EAAE,GAAI,OAAM,IAAI,MAAM,OAAO,EAAE,MAAM,CAAC;AAC3C,aAAO,EAAE,KAAK;AAAA,IAChB,CAAC,EACA,KAAK,CAAC,YAAY;AAAE,UAAI,CAAC,EAAE,OAAO,QAAS,UAAS,EAAE,SAAS,OAAO,KAAK,CAAC;AAAA,IAAE,CAAC,EAC/E,MAAM,CAAC,MAAe;AAAE,UAAI,CAAC,EAAE,OAAO,QAAS,UAAS,EAAE,SAAS,MAAM,OAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,EAAE,CAAC;AAAA,IAAE,CAAC;AAClI,WAAO,MAAM,EAAE,MAAM;AAAA,EACvB,GAAG,CAAC,IAAI,CAAC;AACT,SAAO,EAAE,GAAG,OAAO,OAAO;AAC5B;AAGA,SAAS,MAAM,EAAE,QAAQ,OAAO,GAAoD;AAClF,SACE,4CAAC,UACE,WAAC,GAAG,GAAG,GAAG,GAAG,CAAC,EAAE,IAAI,CAAC,MACpB,4CAAC,YAAe,MAAK,UAAS,OAAO,SAAS,SAAS,MAAM,OAAO,CAAC,GAAG,OAAO,GAAG,CAAC,MAChF,eAAK,SAAS,WAAM,YADV,CAEb,CACD,GACH;AAEJ;AAGA,SAAS,YAAY,EAAE,SAAS,EAAE,GAAkD;AAClF,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,SACE,6CAAC,SAAI,OAAO,SACV;AAAA,iDAAC,SAAI,OAAO,cAAc;AAAA;AAAA,MAAI,EAAE,aAAa;AAAA,OAAE;AAAA,IAC/C,6CAAC,WAAM,OAAO,OACZ;AAAA,kDAAC,WACC,uDAAC,QACC;AAAA,oDAAC,QAAG,OAAO,IAAI,eAAC;AAAA,QAChB,4CAAC,QAAG,OAAO,IAAK,YAAE,OAAO,GAAE;AAAA,QAC3B,4CAAC,QAAG,OAAO,IAAI,iBAAG;AAAA,QAClB,4CAAC,QAAG,OAAO,IAAK,YAAE,MAAM,GAAE;AAAA,QAC1B,4CAAC,QAAG,OAAO,IAAK,YAAE,WAAW,GAAE;AAAA,QAC/B,4CAAC,QAAG,OAAO,IAAK,YAAE,MAAM,GAAE;AAAA,SAC5B,GACF;AAAA,MACA,4CAAC,WACE,kBAAQ,IAAI,CAAC,GAAG,MACf,6CAAC,QACC;AAAA,oDAAC,QAAG,OAAO,IAAK,cAAI,GAAE;AAAA,QACtB,4CAAC,QAAG,OAAO,IAAI,sDAAC,YAAQ,YAAE,OAAM,GAAS;AAAA,QACzC,4CAAC,QAAG,OAAO,IAAK,YAAE,KAAI;AAAA,QACtB,4CAAC,QAAG,OAAO,IAAK,YAAE,MAAK;AAAA,QACvB,4CAAC,QAAG,OAAO,IAAK,YAAE,YAAY,IAAI,EAAE,UAAU,QAAQ,CAAC,IAAI,UAAI;AAAA,QAC/D,4CAAC,QAAG,OAAO,IAAK,YAAE,WAAU;AAAA,WANrB,EAAE,KAOX,CACD,GACH;AAAA,OACF;AAAA,KACF;AAEJ;AAGA,SAAS,aAAa,EAAE,OAAO,EAAE,GAAwC;AACvE,SACE,6CAAC,SAAI,OAAO,SACV;AAAA,iDAAC,SAAI,OAAO,SAAS;AAAA,kDAAC,SAAI,OAAO,EAAE,UAAU,IAAI,YAAY,IAAI,GAAI,gBAAM,WAAU;AAAA,MAAM,4CAAC,SAAI,OAAO,OAAQ,YAAE,WAAW,GAAE;AAAA,OAAM;AAAA,IACpI,6CAAC,SAAI,OAAO,SAAS;AAAA,kDAAC,SAAI,OAAO,EAAE,UAAU,IAAI,YAAY,IAAI,GAAI,gBAAM,cAAa;AAAA,MAAM,4CAAC,SAAI,OAAO,OAAQ,YAAE,cAAc,GAAE;AAAA,OAAM;AAAA,IAC1I,6CAAC,SAAI,OAAO,SAAS;AAAA,kDAAC,SAAI,OAAO,EAAE,UAAU,IAAI,YAAY,IAAI,GAAI,gBAAM,cAAa;AAAA,MAAM,4CAAC,SAAI,OAAO,OAAQ,YAAE,cAAc,GAAE;AAAA,OAAM;AAAA,KAC5I;AAEJ;AAGA,SAAS,UAAU,EAAE,OAAO,GAAG,SAAS,GAA0D;AAChG,QAAM,CAAC,KAAK,MAAM,QAAI,uBAA0B,IAAI;AACpD,QAAM,CAAC,SAAS,UAAU,QAAI,uBAAS,IAAI;AAC3C,8BAAU,MAAM;AACd,eAAW,IAAI;AACf,UAAM,GAAG,QAAQ,OAAO,KAAK,EAAE,EAC5B,KAAK,CAAC,MAAM,EAAE,KAAK,CAAsB,EACzC,KAAK,MAAM,EACX,MAAM,MAAM,OAAO,IAAI,CAAC,EACxB,QAAQ,MAAM,WAAW,KAAK,CAAC;AAAA,EACpC,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,iBAAa,0BAAY,CAAC,OAAe,WAAmB;AAChE,UAAM,WAAW;AAAA,MACf,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,OAAO,OAAO,CAAC;AAAA,IAC/C,CAAC,EACE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAsB,EACzC,KAAK,MAAM,EACX,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EACnB,GAAG,CAAC,KAAK,CAAC;AAEV,QAAM,mBAAe,0BAAY,MAAM;AACrC,QAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,EAAG;AAClC,UAAM,aAAa;AAAA,MACjB,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,MAC9C,MAAM,KAAK,UAAU,EAAE,MAAM,CAAC;AAAA,IAChC,CAAC,EAAE,KAAK,MAAM,SAAS,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC1C,GAAG,CAAC,OAAO,GAAG,QAAQ,CAAC;AAEvB,MAAI,QAAS,QAAO,4CAAC,OAAE,OAAO,OAAQ,YAAE,SAAS,GAAE;AACnD,MAAI,CAAC,IAAK,QAAO,4CAAC,OAAE,OAAO,OAAQ,YAAE,UAAU,GAAE;AACjD,SACE,6CAAC,SAAI,OAAO,EAAE,GAAG,MAAM,WAAW,EAAE,GAClC;AAAA,iDAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,gBAAgB,iBAAiB,YAAY,UAAU,cAAc,EAAE,GACpG;AAAA,mDAAC,YAAQ;AAAA,UAAE,QAAQ;AAAA,QAAE;AAAA,SAAE;AAAA,MACvB,4CAAC,YAAO,MAAK,UAAS,OAAO,WAAW,SAAS,cAAe,YAAE,QAAQ,GAAE;AAAA,OAC9E;AAAA,IACA,4CAAC,SAAI,OAAO,UAAW,cAAI,QAAO;AAAA,IACjC,IAAI,aACH,6CAAC,SAAI,OAAO,EAAE,WAAW,EAAE,GACzB;AAAA,mDAAC,SAAI,OAAO,EAAE,UAAU,IAAI,YAAY,KAAK,cAAc,EAAE,GAAI;AAAA,UAAE,WAAW;AAAA,QAAE;AAAA,SAAC;AAAA,MACjF,4CAAC,SAAI,OAAO,UAAW,cAAI,WAAU;AAAA,OACvC;AAAA,IAEF,4CAAC,SAAI,OAAO,EAAE,WAAW,EAAE,GACxB,cAAI,QAAQ,IAAI,CAAC,MAChB,6CAAC,SAAkB,OAAO,EAAE,GAAG,MAAM,WAAW,EAAE,GAChD;AAAA,mDAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,gBAAgB,iBAAiB,YAAY,SAAS,GACnF;AAAA,oDAAC,YAAQ,YAAE,OAAM;AAAA,QACjB,4CAAC,UACE,YAAE,QACD,4CAAC,UAAK,OAAO,EAAE,GAAG,OAAO,YAAY,wBAAwB,OAAO,OAAO,GAAI,YAAE,OAAO,GAAE,IAE1F,6CAAC,UAAK,OAAO,EAAE,GAAG,OAAO,YAAY,yBAAyB,OAAO,OAAO,GAAI;AAAA,YAAE;AAAA,UAAU;AAAA,WAAE,GAElG;AAAA,SACF;AAAA,MACA,4CAAC,SAAI,OAAO,UAAW,YAAE,SAAS,EAAE,QAAO;AAAA,MAC3C,6CAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,gBAAgB,iBAAiB,YAAY,UAAU,WAAW,EAAE,GACjG;AAAA,qDAAC,UAAK,OAAO,OAAQ;AAAA,YAAE,eAAe,EAAE;AAAA,UAAiB;AAAA,UAAU,EAAE;AAAA,UAAa;AAAA,UAAG,EAAE;AAAA,UAAiB;AAAA,WAAE;AAAA,QAC1G,4CAAC,SAAM,QAAQ,EAAE,UAAU,GAAG,QAAQ,CAAC,MAAM,WAAW,EAAE,OAAO,CAAC,GAAG;AAAA,SACvE;AAAA,SAfQ,EAAE,KAgBZ,CACD,GACH;AAAA,KACF;AAEJ;AAGO,SAAS,WAAW,EAAE,EAAE,GAAe;AAC5C,QAAM,EAAE,SAAS,OAAO,OAAO,IAAI,SAAS;AAC5C,QAAM,CAAC,aAAa,cAAc,QAAI,uBAAwB,IAAI;AAElE,QAAM,SACJ,6CAAC,YAAO,OAAO,MACb;AAAA,iDAAC,YAAO,OAAO,EAAE,UAAU,GAAG,GAAG;AAAA;AAAA,MAAI,EAAE,OAAO;AAAA,OAAE;AAAA,IAChD,4CAAC,UAAK,OAAO,EAAE,MAAM,EAAE,GAAG;AAAA,IAC1B,4CAAC,YAAO,MAAK,UAAS,SAAS,QAAQ,OAAO,EAAE,UAAU,GAAG,GAAI,YAAE,SAAS,GAAE;AAAA,KAChF;AAEF,MAAI,UAAU,MAAM;AAClB,WACE,6CAAC,SAAI,OAAO,MACT;AAAA;AAAA,MACD,6CAAC,OAAE,MAAK,SAAQ,OAAO,EAAE,QAAQ,GAAG,UAAU,GAAG,GAAI;AAAA,UAAE,QAAQ;AAAA,QAAE;AAAA,QAAG;AAAA,SAAM;AAAA,MAC1E,4CAAC,YAAO,MAAK,UAAS,SAAS,QAAQ,OAAO,EAAE,WAAW,cAAc,UAAU,GAAG,GAAI,YAAE,OAAO,GAAE;AAAA,OACvG;AAAA,EAEJ;AACA,MAAI,YAAY,KAAM,QAAO,4CAAC,OAAE,OAAO,OAAO,aAAU,UAAU,YAAE,SAAS,GAAE;AAC/E,SACE,6CAAC,SAAI,OAAO,MACT;AAAA;AAAA,IAGD,4CAAC,gBAAa,OAAO,QAAQ,OAAO,GAAM;AAAA,IAG1C,4CAAC,eAAY,SAAS,QAAQ,aAAa,GAAM;AAAA,IAGjD,6CAAC,SAAI,OAAO,SACV;AAAA,mDAAC,SAAI,OAAO,cAAc;AAAA;AAAA,QAAI,EAAE,YAAY;AAAA,SAAE;AAAA,MAC7C,QAAQ,WAAW,WAAW,IAC7B,4CAAC,OAAE,OAAO,EAAE,QAAQ,GAAG,UAAU,IAAI,SAAS,IAAI,GAAI,YAAE,OAAO,GAAE,IAEjE,6CAAC,WAAM,OAAO,OACZ;AAAA,oDAAC,WACC,uDAAC,QAAG;AAAA,sDAAC,QAAG,OAAO,IAAK,YAAE,KAAK,GAAE;AAAA,UAAK,4CAAC,QAAG,OAAO,IAAK,YAAE,QAAQ,GAAE;AAAA,UAAK,4CAAC,QAAG,OAAO,IAAK,YAAE,QAAQ,GAAE;AAAA,UAAK,4CAAC,QAAG,OAAO,IAAK,YAAE,MAAM,GAAE;AAAA,WAAK,GACrI;AAAA,QACA,4CAAC,WACE,kBAAQ,WAAW,IAAI,CAAC,QACvB,4EACE;AAAA,uDAAC,QAAgB,OAAO,UAAU,SAAS,MAAM,eAAe,gBAAgB,IAAI,KAAK,OAAO,IAAI,EAAE,GACpG;AAAA,yDAAC,QAAG,OAAO,IACT;AAAA,0DAAC,UAAK,OAAO,EAAE,UAAU,GAAG,GAAI,cAAI,IAAG;AAAA,cACvC,4CAAC,UAAK,OAAO,EAAE,YAAY,GAAG,UAAU,GAAG,GAAI,0BAAgB,IAAI,KAAK,WAAM,UAAI;AAAA,eACpF;AAAA,YACA,4CAAC,QAAG,OAAO,IAAK,cAAI,YAAW;AAAA,YAC/B,4CAAC,QAAG,OAAO,IAAK,cAAI,UAAU,UAAI;AAAA,YAClC,4CAAC,QAAG,OAAO,IAAK,cAAI,KAAK,IAAI,SAAS,EAAE,eAAe,GAAE;AAAA,eAPlD,IAAI,EAQb;AAAA,UACC,gBAAgB,IAAI,MACnB,4CAAC,QACC,sDAAC,QAAG,SAAS,GAAG,OAAO,EAAE,SAAS,QAAQ,GACxC,sDAAC,aAAU,OAAO,IAAI,IAAI,GAAM,UAAU,MAAM;AAAE,2BAAe,IAAI;AAAG,mBAAO;AAAA,UAAE,GAAG,GACtF,KAHO,GAAG,IAAI,EAAE,SAIlB;AAAA,WAEJ,CACD,GACH;AAAA,SACF;AAAA,OAEJ;AAAA,KACF;AAEJ;;;AF7PO,IAAM,SAAS,CAAC,SAAS,QAAQ;AAmBjC,SAAS,MAAM,KAA0B;AAE9C,MAAI,OAAO,MAAM,IAAI,OAAO,SAAS,IAAI,EAAE,IAAI,GAAG,CAAC,GAAG,+BAA+B;AAErF,MAAI,MAAM,OAAO,oBAAoB,MAAM,IAAI,MAAM;AAAA,IACnD;AAAA,MACE,MAAM;AAAA,MACN,IAAI;AAAA,MACJ,OAAO;AAAA,MACP,OAAO,MAAM,IAAI,OAAO,KAAK,EAAE,EAAE,KAAK;AAAA,MACtC,QAAQ;AAAA,IACV;AAAA,IACA;AAAA,EACF,CAAC;AACH;",
6
6
  "names": []
7
7
  }