evals-lab 0.12.0 → 0.13.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.
- package/CHANGELOG.md +49 -0
- package/lab/VERSION +1 -1
- package/lab/evals-core.mjs +170 -41
- package/lab/flows/wdl.mjs +19 -0
- package/lab/kinds/list.mjs +5 -2
- package/lab/metrics/builtin.mjs +34 -7
- package/lab/run-evals.js +30 -11
- package/lab/server.py +137 -28
- package/lab/web/dist/assets/gallery-DpaqOg9Z.js +5 -0
- package/lab/web/dist/assets/main-BSw-jyYD.css +1 -0
- package/lab/web/dist/assets/main-CrB8wPZn.js +17 -0
- package/lab/web/dist/assets/tokens-CrjDMCbz.js +60 -0
- package/lab/web/dist/assets/tokens-DDOONQon.css +1 -0
- package/lab/web/dist/gallery.html +3 -3
- package/lab/web/dist/index.html +4 -4
- package/package.json +1 -1
- package/lab/web/dist/assets/gallery-CFntjQFG.js +0 -5
- package/lab/web/dist/assets/main-BlTG2i20.css +0 -1
- package/lab/web/dist/assets/main-D6MNWqak.js +0 -17
- package/lab/web/dist/assets/tokens-DwzzdSIF.css +0 -1
- package/lab/web/dist/assets/tokens-bvuNFuYg.js +0 -60
package/lab/metrics/builtin.mjs
CHANGED
|
@@ -139,9 +139,9 @@ function verdictOf(said )
|
|
|
139
139
|
can hold one. A reply that still cannot be read is asked for once more;
|
|
140
140
|
a second, or a grader that failed, is the grader's error -- said as one,
|
|
141
141
|
so it is not read as the reply failing. [ok] says whether a verdict read
|
|
142
|
-
is one this metric can use. */
|
|
142
|
+
is one this metric can use: true, or why not. */
|
|
143
143
|
async function graded(ctx , prompt , schema ,
|
|
144
|
-
ok
|
|
144
|
+
ok = () => true) {
|
|
145
145
|
if (!ctx.ask) throw new Error("a model-graded metric needs a grader, and this run has none");
|
|
146
146
|
let last = "";
|
|
147
147
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
@@ -149,14 +149,21 @@ async function graded(ctx , prompt , schema
|
|
|
149
149
|
try { said = await ctx.ask(prompt, schema); } catch (e) { throw new Error(`grader error: ${e instanceof Error ? e.message : String(e)}`); }
|
|
150
150
|
try {
|
|
151
151
|
const v = verdictOf(said);
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
const usable = ok(v);
|
|
153
|
+
if (usable === true) return v;
|
|
154
|
+
last = typeof usable === "string" ? usable : `the grader said ${short(JSON.stringify(v))}`;
|
|
154
155
|
} catch (e) { last = e instanceof Error ? e.message : String(e); }
|
|
155
156
|
}
|
|
156
157
|
throw new Error(`grader error: ${last}`);
|
|
157
158
|
}
|
|
159
|
+
/** Whether a pass-or-fail verdict's score is on the scale it was asked for.
|
|
160
|
+
One off it -- a 4 echoing a 1-to-5 rating -- is not clamped to a score the
|
|
161
|
+
grader never gave: it is read as unreadable, asked for again, then the
|
|
162
|
+
grader's error (#514). */
|
|
163
|
+
const onScale = (v ) =>
|
|
164
|
+
typeof v.score !== "number" || (v.score >= 0 && v.score <= 1) || `the grader scored ${v.score}, off the 0 to 1 scale`;
|
|
158
165
|
const gradedPass = (v , threshold ) => {
|
|
159
|
-
const score = typeof v.score === "number" ?
|
|
166
|
+
const score = typeof v.score === "number" ? v.score : v.pass === true ? 1 : 0;
|
|
160
167
|
const pass = typeof v.pass === "boolean" ? v.pass && score >= threshold : score >= threshold;
|
|
161
168
|
return { pass, score, reason: str(v.reason) || (pass ? "the grader passed it" : "the grader failed it") };
|
|
162
169
|
};
|
|
@@ -339,6 +346,7 @@ const metrics = {
|
|
|
339
346
|
// rules discarded is the finding.
|
|
340
347
|
discarded: {
|
|
341
348
|
family: RESULT,
|
|
349
|
+
readsResult: true,
|
|
342
350
|
label: "Discarded",
|
|
343
351
|
description: "Passes only when the job's rules discarded the reply.",
|
|
344
352
|
options: [],
|
|
@@ -348,8 +356,26 @@ const metrics = {
|
|
|
348
356
|
return ok(thrown, thrown ? "discarded" : input.error ? input.error : `kept ${input.terms.length} items`);
|
|
349
357
|
},
|
|
350
358
|
},
|
|
359
|
+
// A case that expects no answer at all: the connection answering an error
|
|
360
|
+
// -- a 400 for a file it can't read -- is the finding. A discard is
|
|
361
|
+
// Discarded's, at whichever stage it was.
|
|
362
|
+
refused: {
|
|
363
|
+
family: RESULT,
|
|
364
|
+
readsResult: true,
|
|
365
|
+
readsRefusal: true,
|
|
366
|
+
label: "Refused",
|
|
367
|
+
description: "Passes only when the target's call was refused: an error from the connection, not a reply.",
|
|
368
|
+
options: [],
|
|
369
|
+
defaults: () => ({}),
|
|
370
|
+
score: (input) => {
|
|
371
|
+
if (!input.error) return ok(false, "replied");
|
|
372
|
+
const refused = !/^(stage \d+: )?discarded: /.test(input.error);
|
|
373
|
+
return ok(refused, refused ? `refused: ${input.error}` : input.error);
|
|
374
|
+
},
|
|
375
|
+
},
|
|
351
376
|
"item-count": {
|
|
352
377
|
family: RESULT,
|
|
378
|
+
readsResult: true,
|
|
353
379
|
label: "Item count",
|
|
354
380
|
description: "Passes when the number of items is within the bounds.",
|
|
355
381
|
options: [{ key: "min", label: "At least", type: "number" }, { key: "max", label: "At most", type: "number" }],
|
|
@@ -385,6 +411,7 @@ const metrics = {
|
|
|
385
411
|
},
|
|
386
412
|
latency: {
|
|
387
413
|
family: RESULT,
|
|
414
|
+
readsResult: true,
|
|
388
415
|
label: "Latency",
|
|
389
416
|
description: "Passes when the reply came back within the time set.",
|
|
390
417
|
options: [{ key: "max", label: "At most (ms)", type: "number" }],
|
|
@@ -472,7 +499,7 @@ const metrics = {
|
|
|
472
499
|
validate: (m, at, bad) => needs(m, ["rubric"], at, bad),
|
|
473
500
|
score: async (input, m, ctx) => gradedPass(await graded(ctx,
|
|
474
501
|
`You are grading an output against a rubric.\n\n${taskOf(input, "")}Rubric:\n${str(m.rubric)}\n\nOutput:\n${input.text}\n\n${REPLY}`,
|
|
475
|
-
VERDICT_SCHEMA), num(m.threshold) ?? 0.5),
|
|
502
|
+
VERDICT_SCHEMA, onScale), num(m.threshold) ?? 0.5),
|
|
476
503
|
},
|
|
477
504
|
factuality: {
|
|
478
505
|
family: GRADED,
|
|
@@ -488,7 +515,7 @@ const metrics = {
|
|
|
488
515
|
score: async (input, m, ctx) => gradedPass(await graded(ctx,
|
|
489
516
|
`You are checking an output for factual consistency with a reference. Differences in wording or detail are `
|
|
490
517
|
+ `fine; a contradiction, or a fact the reference does not support, is not.\n\n${taskOf(input, "")}Reference:\n`
|
|
491
|
-
+ `${str(m.reference).trim() || recorded(input)}\n\nOutput:\n${input.text}\n\n${REPLY}`, VERDICT_SCHEMA), num(m.threshold) ?? 0.5),
|
|
518
|
+
+ `${str(m.reference).trim() || recorded(input)}\n\nOutput:\n${input.text}\n\n${REPLY}`, VERDICT_SCHEMA, onScale), num(m.threshold) ?? 0.5),
|
|
492
519
|
},
|
|
493
520
|
"judge-vs-production": {
|
|
494
521
|
family: GRADED,
|
package/lab/run-evals.js
CHANGED
|
@@ -479,20 +479,24 @@ function prepare(bin, file, c) {
|
|
|
479
479
|
|
|
480
480
|
// ---- The type → handler registry ------------------------------------------
|
|
481
481
|
// Design §4. What a file *is* to a run is decided here and only here, so the
|
|
482
|
-
// queue and the page never learn about file types, and
|
|
483
|
-
//
|
|
482
|
+
// queue and the page never learn about file types, and xlsx is a later
|
|
483
|
+
// entry that touches nothing but this map.
|
|
484
484
|
//
|
|
485
485
|
// A file with no mapper is passed through anyway -- decoded as UTF-8 with
|
|
486
486
|
// replacement and joined at {text} like the text handler, and flagged in the
|
|
487
|
-
// transcript. Stating the failure mode beats hiding it: a
|
|
488
|
-
//
|
|
489
|
-
//
|
|
487
|
+
// transcript. Stating the failure mode beats hiding it: a binary format's
|
|
488
|
+
// bytes arrive as mojibake, and a run that read them "well" would be a
|
|
489
|
+
// finding about nothing.
|
|
490
490
|
const HANDLERS = new Map();
|
|
491
491
|
[
|
|
492
492
|
// Images: prepared to the profile's pixel budget by ImageMagick and sent
|
|
493
493
|
// as the image content part. The formats are server.py's FILE_TYPES
|
|
494
494
|
// image half, so nothing accepted for upload has no executor to run it.
|
|
495
495
|
["image", "jpg", "jpeg", "png", "heic", "heif", "dng", "tif", "tiff"],
|
|
496
|
+
// Documents: the file's bytes, unprocessed, handed to an API that reads
|
|
497
|
+
// them whole (core.DOCUMENT_TYPES); a connection that cannot is refused
|
|
498
|
+
// before the run, by validatePipeline.
|
|
499
|
+
["document", ...Object.keys(core.DOCUMENT_TYPES).map(e => e.slice(1))],
|
|
496
500
|
// Text-ish: decoded UTF-8, joining the stage at {text} -- the text-content
|
|
497
501
|
// semantics the canvas already has (core.textPrompt).
|
|
498
502
|
["text", "txt", "md", "csv"],
|
|
@@ -511,8 +515,8 @@ const UNMAPPED = "the file has no handler, so it was passed through as raw "
|
|
|
511
515
|
|
|
512
516
|
/**
|
|
513
517
|
* One file of the Source made into what the job consumes: an image as the
|
|
514
|
-
* dataUrl at [conn]'s budget, or text at {text}. A
|
|
515
|
-
* still runs, and says so.
|
|
518
|
+
* dataUrl at [conn]'s budget, a document as its bytes, or text at {text}. A
|
|
519
|
+
* file with no mapper still runs, and says so.
|
|
516
520
|
*/
|
|
517
521
|
function itemContent(bin, file, conn) {
|
|
518
522
|
const kind = HANDLERS.get(extOf(file));
|
|
@@ -520,6 +524,7 @@ function itemContent(bin, file, conn) {
|
|
|
520
524
|
const { dataUrl, dims } = prepare(bin, file, conn);
|
|
521
525
|
return { kind, dataUrl, text: null, flags: [] };
|
|
522
526
|
}
|
|
527
|
+
if (kind === "document") return { kind, dataUrl: documentOf(file), text: null, flags: [] };
|
|
523
528
|
// TextDecoder with fatal unset replaces malformed bytes with U+FFFD --
|
|
524
529
|
// what "decoded as UTF-8 with replacement" means, for a mapped-text file
|
|
525
530
|
// and an unmapped one alike.
|
|
@@ -527,6 +532,13 @@ function itemContent(bin, file, conn) {
|
|
|
527
532
|
return { kind: kind ?? "unmapped", dataUrl: null, text, flags: kind ? [] : [UNMAPPED] };
|
|
528
533
|
}
|
|
529
534
|
|
|
535
|
+
/** A document file as the attachment a stage sends: its bytes, its media
|
|
536
|
+
type and its name. */
|
|
537
|
+
function documentOf(file) {
|
|
538
|
+
const name = path.basename(file);
|
|
539
|
+
return core.documentUrl(name, core.documentTypeOf(name).media, fs.readFileSync(file).toString("base64"));
|
|
540
|
+
}
|
|
541
|
+
|
|
530
542
|
// ---- Reaching a model ----------------------------------------------------
|
|
531
543
|
|
|
532
544
|
/**
|
|
@@ -737,9 +749,10 @@ async function ask(to, c, prompt, dataUrl, schema = null) {
|
|
|
737
749
|
// this invocation did, so resume and re-run item agree with a fresh run.
|
|
738
750
|
|
|
739
751
|
// What a file *is* to a run, the same registry §4 gives the graded set: an
|
|
740
|
-
// image is prepared to the profile's pixel budget; a
|
|
741
|
-
// UTF-8 and joined at {text}; anything else is passed
|
|
742
|
-
// replacement and flagged, so the failure mode is
|
|
752
|
+
// image is prepared to the profile's pixel budget; a document is sent whole;
|
|
753
|
+
// a text file is decoded UTF-8 and joined at {text}; anything else is passed
|
|
754
|
+
// through as UTF-8 with replacement and flagged, so the failure mode is
|
|
755
|
+
// stated rather than hidden.
|
|
743
756
|
const RUN_IMAGE_TYPES = new Set([".jpg", ".jpeg", ".png", ".heic", ".heif", ".dng", ".tif", ".tiff"]);
|
|
744
757
|
const RUN_TEXT_TYPES = new Set([".txt", ".md", ".csv"]);
|
|
745
758
|
|
|
@@ -916,6 +929,7 @@ async function runSnapshot(o) {
|
|
|
916
929
|
: files.map(name => {
|
|
917
930
|
const ext = path.extname(name).toLowerCase();
|
|
918
931
|
if (RUN_IMAGE_TYPES.has(ext)) return { name, kind: "image" };
|
|
932
|
+
if (core.documentTypeOf(name)) return { name, kind: "document" };
|
|
919
933
|
// A record (flows/record.ts): one call of a Power Automate step, what
|
|
920
934
|
// an HTTP Request is rebuilt from.
|
|
921
935
|
if (ext === ".json") return { name, kind: "record" };
|
|
@@ -981,6 +995,11 @@ async function runSnapshot(o) {
|
|
|
981
995
|
break;
|
|
982
996
|
}
|
|
983
997
|
}
|
|
998
|
+
if (item.kind === "document") {
|
|
999
|
+
const file = path.join(o.source, item.name);
|
|
1000
|
+
if (!fs.existsSync(file)) { failed = `the file ${item.name} is not in the source`; break; }
|
|
1001
|
+
dataUrl = documentOf(file);
|
|
1002
|
+
}
|
|
984
1003
|
let record = null;
|
|
985
1004
|
if (item.kind === "record") {
|
|
986
1005
|
const file = path.join(o.source, item.name);
|
|
@@ -1450,7 +1469,7 @@ async function main() {
|
|
|
1450
1469
|
found: s.found, missed: s.missed, invented: s.invented, count: s.count,
|
|
1451
1470
|
metrics: s.metrics, watch: s.watch,
|
|
1452
1471
|
terms: res.terms || [], ms: res.ms,
|
|
1453
|
-
// What a file type was to this run -- "image", "text", "unmapped".
|
|
1472
|
+
// What a file type was to this run -- "image", "document", "text", "unmapped".
|
|
1454
1473
|
type: item?.kind ?? null,
|
|
1455
1474
|
// What the model actually said. #248 wants a failure written out as a
|
|
1456
1475
|
// task an agent can act on, and "missed water" without the reply that
|
package/lab/server.py
CHANGED
|
@@ -50,6 +50,7 @@ from pathlib import Path
|
|
|
50
50
|
# Ollama's own address on this machine, unless OLLAMA_URL names another: the
|
|
51
51
|
# deployed lab's is set in the Dockerfile, and no default names a host of its.
|
|
52
52
|
OLLAMA = os.environ.get("OLLAMA_URL", "http://127.0.0.1:11434")
|
|
53
|
+
|
|
53
54
|
# The password in front of everything but /healthz (#463). Unset means open,
|
|
54
55
|
# as a lab run on a workstation always has been; the deployed one sets it,
|
|
55
56
|
# because it keeps setups and API keys and can spend GPU time.
|
|
@@ -145,6 +146,16 @@ def host_of(url: str) -> str:
|
|
|
145
146
|
return (urllib.parse.urlsplit(url).hostname or "").lower()
|
|
146
147
|
|
|
147
148
|
|
|
149
|
+
def ollama_shown():
|
|
150
|
+
"""OLLAMA as the page may show it, where a connection with no address
|
|
151
|
+
goes: no user or password, should OLLAMA_URL carry one."""
|
|
152
|
+
try:
|
|
153
|
+
u = urllib.parse.urlsplit(OLLAMA)
|
|
154
|
+
return urllib.parse.urlunsplit(u._replace(netloc=u.netloc.rpartition("@")[2]))
|
|
155
|
+
except ValueError:
|
|
156
|
+
return ""
|
|
157
|
+
|
|
158
|
+
|
|
148
159
|
# The chat endpoint each connection type's request reaches, off the /v1
|
|
149
160
|
# base -- evals-core.ts's CONNECTION_TYPES, mirrored because the server is
|
|
150
161
|
# stdlib-only and cannot load it. Ollama's native /api/chat sits at the root;
|
|
@@ -311,6 +322,8 @@ FILE_TYPES = {
|
|
|
311
322
|
".dng": "image/x-adobe-dng",
|
|
312
323
|
".tif": "image/tiff",
|
|
313
324
|
".tiff": "image/tiff",
|
|
325
|
+
# Handed to the model whole, as a document (evals-core.ts DOCUMENT_TYPES).
|
|
326
|
+
".pdf": "application/pdf",
|
|
314
327
|
".txt": "text/plain; charset=utf-8",
|
|
315
328
|
".md": "text/markdown; charset=utf-8",
|
|
316
329
|
".csv": "text/csv; charset=utf-8",
|
|
@@ -1212,9 +1225,20 @@ def workspace_of(store) -> str:
|
|
|
1212
1225
|
|
|
1213
1226
|
|
|
1214
1227
|
# The `connection_shares` sentinels (phase 4 fills and enforces the table;
|
|
1215
|
-
# phase 1 only creates it empty): every workspace,
|
|
1228
|
+
# phase 1 only creates it empty): every workspace, future ones, and none.
|
|
1229
|
+
# No rows is All, so a set that loses its last workspace (a purge, the Share
|
|
1230
|
+
# link's Undo, a stale id) holds SHARE_NONE rather than nothing (#506).
|
|
1216
1231
|
SHARE_ALL = "*all"
|
|
1217
1232
|
SHARE_NEW = "*new"
|
|
1233
|
+
SHARE_NONE = "*none"
|
|
1234
|
+
|
|
1235
|
+
def hold_nowhere(db, conns) -> None:
|
|
1236
|
+
"""SHARE_NONE for each (kind, id) in [conns] left with no share row, which
|
|
1237
|
+
would otherwise read as shared with every workspace."""
|
|
1238
|
+
db.executemany("INSERT INTO connection_shares (kind, id, workspace) SELECT ?, ?, ? "
|
|
1239
|
+
"WHERE NOT EXISTS (SELECT 1 FROM connection_shares WHERE kind = ? AND id = ?)",
|
|
1240
|
+
[(k, i, SHARE_NONE, k, i) for k, i in conns])
|
|
1241
|
+
|
|
1218
1242
|
|
|
1219
1243
|
# How the `docs` table scopes by key (docs/workspaces.md): workflows are
|
|
1220
1244
|
# per-workspace, profiles/tokens global, and promptlab.versions is split --
|
|
@@ -1519,7 +1543,8 @@ class Store:
|
|
|
1519
1543
|
# An AI Connection or an Account (kind "profile"/"account", id the
|
|
1520
1544
|
# connection's -- the Target profile's it kept -- or the account's) is
|
|
1521
1545
|
# usable in a workspace by its share set in `connection_shares`: a row for
|
|
1522
|
-
# that workspace, or the SHARE_ALL sentinel.
|
|
1546
|
+
# that workspace, or the SHARE_ALL sentinel. SHARE_NONE is a row that
|
|
1547
|
+
# matches no workspace, so a set holding only it is usable nowhere.
|
|
1523
1548
|
# No row at all is the migration and new-connection default -- shared with
|
|
1524
1549
|
# every workspace, so nothing stops running the moment workspaces exist
|
|
1525
1550
|
# (open question 2, resolved All); narrowing a connection is adding the
|
|
@@ -1540,7 +1565,7 @@ class Store:
|
|
|
1540
1565
|
"""Every connection's share set, for the Connections menus: keyed by
|
|
1541
1566
|
"<kind>:<id>", the workspace values as stored (workspace ids and the
|
|
1542
1567
|
sentinels). A connection with no row is absent, which the page reads as
|
|
1543
|
-
shared with All. No key is anywhere in this (#258)."""
|
|
1568
|
+
shared with All; one with SHARE_NONE is shared with no workspace. No key is anywhere in this (#258)."""
|
|
1544
1569
|
out: dict = {}
|
|
1545
1570
|
with self.lock, closing(sqlite3.connect(self.path)) as db:
|
|
1546
1571
|
for kind, cid, ws in db.execute("SELECT kind, id, workspace FROM connection_shares"):
|
|
@@ -1548,15 +1573,20 @@ class Store:
|
|
|
1548
1573
|
return out
|
|
1549
1574
|
|
|
1550
1575
|
def set_shares(self, kind: str, cid: str, workspaces) -> None:
|
|
1551
|
-
"""Replace a connection's share set. SHARE_ALL means every workspace
|
|
1552
|
-
so
|
|
1553
|
-
in, so a stale id cannot linger in the table.
|
|
1576
|
+
"""Replace a connection's share set. SHARE_ALL means every workspace
|
|
1577
|
+
and SHARE_NONE none, so each is kept alone; only the sentinels and live
|
|
1578
|
+
workspace ids are let in, so a stale id cannot linger in the table. A
|
|
1579
|
+
set named but left empty by that is SHARE_NONE, not the All an empty
|
|
1580
|
+
set reads as; an empty set asked for is a plain delete, so a failed
|
|
1581
|
+
lab import's rollback leaves no row for a connection it never wrote."""
|
|
1554
1582
|
with self.lock, closing(sqlite3.connect(self.path)) as db, db:
|
|
1555
1583
|
valid = {r[0] for r in db.execute("SELECT id FROM workspaces WHERE trash IS NULL")}
|
|
1556
1584
|
chosen = [w for w in dict.fromkeys(workspaces or [])
|
|
1557
|
-
if w in (SHARE_ALL, SHARE_NEW) or w in valid]
|
|
1585
|
+
if w in (SHARE_ALL, SHARE_NEW, SHARE_NONE) or w in valid]
|
|
1558
1586
|
if SHARE_ALL in chosen:
|
|
1559
1587
|
chosen = [SHARE_ALL]
|
|
1588
|
+
elif SHARE_NONE in chosen or (workspaces and not chosen):
|
|
1589
|
+
chosen = [SHARE_NONE]
|
|
1560
1590
|
db.execute("DELETE FROM connection_shares WHERE kind = ? AND id = ?", (kind, cid))
|
|
1561
1591
|
db.executemany("INSERT INTO connection_shares (kind, id, workspace) VALUES (?, ?, ?)",
|
|
1562
1592
|
[(kind, cid, w) for w in chosen])
|
|
@@ -1565,14 +1595,18 @@ class Store:
|
|
|
1565
1595
|
"""Add or remove one workspace from a connection's share set -- the Run
|
|
1566
1596
|
bar's Share link and its Undo (docs/workspaces.md). The link is offered
|
|
1567
1597
|
only where the connection is actually narrowed, so adding a row is what
|
|
1568
|
-
grants the blocked workspace its use and Undo takes it back
|
|
1598
|
+
grants the blocked workspace its use and Undo takes it back -- to
|
|
1599
|
+
SHARE_NONE when it was the last, as a set shared nowhere was."""
|
|
1569
1600
|
with self.lock, closing(sqlite3.connect(self.path)) as db, db:
|
|
1570
1601
|
if on:
|
|
1571
1602
|
db.execute("INSERT OR IGNORE INTO connection_shares (kind, id, workspace) "
|
|
1572
1603
|
"VALUES (?, ?, ?)", (kind, cid, ws))
|
|
1604
|
+
db.execute("DELETE FROM connection_shares WHERE kind = ? AND id = ? AND workspace = ?",
|
|
1605
|
+
(kind, cid, SHARE_NONE))
|
|
1573
1606
|
else:
|
|
1574
1607
|
db.execute("DELETE FROM connection_shares WHERE kind = ? AND id = ? AND workspace = ?",
|
|
1575
1608
|
(kind, cid, ws))
|
|
1609
|
+
hold_nowhere(db, [(kind, cid)])
|
|
1576
1610
|
|
|
1577
1611
|
def workspace_name(self, ws) -> str:
|
|
1578
1612
|
"""A workspace's name, for the refusal sentence the Run bar shows."""
|
|
@@ -1702,8 +1736,11 @@ class Workspaces:
|
|
|
1702
1736
|
if isinstance(shares, list)
|
|
1703
1737
|
else list(db.execute("SELECT kind, id FROM connection_shares WHERE workspace = ?",
|
|
1704
1738
|
(SHARE_NEW,))))
|
|
1739
|
+
grant = [(k, i) for k, i in grant if k and i]
|
|
1705
1740
|
db.executemany("INSERT OR IGNORE INTO connection_shares (kind, id, workspace) VALUES (?, ?, ?)",
|
|
1706
|
-
[(k, i, wid) for k, i in grant
|
|
1741
|
+
[(k, i, wid) for k, i in grant])
|
|
1742
|
+
db.executemany("DELETE FROM connection_shares WHERE kind = ? AND id = ? AND workspace = ?",
|
|
1743
|
+
[(k, i, SHARE_NONE) for k, i in grant])
|
|
1707
1744
|
return wid, None
|
|
1708
1745
|
|
|
1709
1746
|
def rename(self, wid, name) -> tuple:
|
|
@@ -1843,7 +1880,11 @@ class Workspaces:
|
|
|
1843
1880
|
db.execute("DELETE FROM queue WHERE workspace = ?", (ws,))
|
|
1844
1881
|
db.execute("DELETE FROM runs WHERE workspace = ?", (ws,))
|
|
1845
1882
|
db.execute("DELETE FROM docs WHERE workspace = ?", (ws,))
|
|
1883
|
+
# A connection shared only here is shared nowhere after, not with
|
|
1884
|
+
# All, which is what no rows would read as (#506).
|
|
1885
|
+
held = list(db.execute("SELECT kind, id FROM connection_shares WHERE workspace = ?", (ws,)))
|
|
1846
1886
|
db.execute("DELETE FROM connection_shares WHERE workspace = ?", (ws,))
|
|
1887
|
+
hold_nowhere(db, held)
|
|
1847
1888
|
db.execute("DELETE FROM workspaces WHERE id = ?", (ws,))
|
|
1848
1889
|
return sids, rids
|
|
1849
1890
|
|
|
@@ -4411,8 +4452,8 @@ class Datasets:
|
|
|
4411
4452
|
|
|
4412
4453
|
def save(self, did, version, body):
|
|
4413
4454
|
"""The body, written at [version] -- the one it began from. Returns
|
|
4414
|
-
({version}, None), or (None, error); a stale version's error
|
|
4415
|
-
the current row."""
|
|
4455
|
+
({version, versions}, None), or (None, error); a stale version's error
|
|
4456
|
+
carries the current row."""
|
|
4416
4457
|
if type(version) is not int:
|
|
4417
4458
|
return None, (400, "a save names the version it began from")
|
|
4418
4459
|
# A body of an earlier version -- from a page loaded before this one --
|
|
@@ -4431,7 +4472,9 @@ class Datasets:
|
|
|
4431
4472
|
self._keep(db, r, text)
|
|
4432
4473
|
db.execute("UPDATE datasets SET body = ?, version = ?, updated_at = ? WHERE id = ?",
|
|
4433
4474
|
(text, version + 1, self._now(), did))
|
|
4434
|
-
|
|
4475
|
+
# The save may have cut a version: a link pins the newest (#547).
|
|
4476
|
+
newest = self._head(db, did)["n"]
|
|
4477
|
+
return {"version": version + 1, "versions": newest}, None
|
|
4435
4478
|
|
|
4436
4479
|
def remove(self, did):
|
|
4437
4480
|
"""Into the trash, at once; Undo restores it. Returns (token, None)."""
|
|
@@ -4863,8 +4906,8 @@ class FilterSets:
|
|
|
4863
4906
|
|
|
4864
4907
|
def save(self, fid, version, body):
|
|
4865
4908
|
"""The body, written at [version] -- the one it began from. Returns
|
|
4866
|
-
({version}, None), or (None, error); a stale version's error
|
|
4867
|
-
the current row."""
|
|
4909
|
+
({version, versions}, None), or (None, error); a stale version's error
|
|
4910
|
+
carries the current row."""
|
|
4868
4911
|
if type(version) is not int:
|
|
4869
4912
|
return None, (400, "a save names the version it began from")
|
|
4870
4913
|
body = upgrade_filter_set_body(body)
|
|
@@ -4881,7 +4924,9 @@ class FilterSets:
|
|
|
4881
4924
|
self._keep(db, r, text)
|
|
4882
4925
|
db.execute("UPDATE filter_sets SET body = ?, version = ?, updated_at = ? WHERE id = ?",
|
|
4883
4926
|
(text, version + 1, self._now(), fid))
|
|
4884
|
-
|
|
4927
|
+
# The save may have cut a version: a link pins the newest (#547).
|
|
4928
|
+
newest = self._head(db, fid)["n"]
|
|
4929
|
+
return {"version": version + 1, "versions": newest}, None
|
|
4885
4930
|
|
|
4886
4931
|
def remove(self, fid):
|
|
4887
4932
|
"""Into the trash, at once; Undo restores it. Returns (token, None)."""
|
|
@@ -5141,6 +5186,43 @@ def demo_pack() -> bytes:
|
|
|
5141
5186
|
return buf.getvalue()
|
|
5142
5187
|
|
|
5143
5188
|
|
|
5189
|
+
# The connection types a prompt cannot be asked of (evals-core.ts: their
|
|
5190
|
+
# `answers` lacks "prompt", or they answer from the item and are no model).
|
|
5191
|
+
NO_WORDS = {"echo", "recorded", "http"}
|
|
5192
|
+
|
|
5193
|
+
|
|
5194
|
+
def pack_connection(ws):
|
|
5195
|
+
"""The AI Connection a pack's target answers on where it names one this
|
|
5196
|
+
lab lacks (#550): the lab's grader, or else the first connection that is
|
|
5197
|
+
asked words, either shared with workspace [ws]; None where there is none,
|
|
5198
|
+
so the target keeps its placeholder and the Run bar says to add one."""
|
|
5199
|
+
body = ((STORE.all().get("promptlab.connections") or {}).get("body") or {}) if STORE is not None else {}
|
|
5200
|
+
words = [c for c in body.get("list") or []
|
|
5201
|
+
if isinstance(c, dict) and isinstance(c.get("id"), str) and c["id"]
|
|
5202
|
+
and c.get("type") not in NO_WORDS and STORE.shared("profile", c["id"], ws)]
|
|
5203
|
+
return next((c for c in words if c["id"] == body.get("grader")), words[0] if words else None)
|
|
5204
|
+
|
|
5205
|
+
|
|
5206
|
+
def repoint_targets(doc: dict, ws) -> None:
|
|
5207
|
+
"""A pack pipeline's targets that name a connection this lab does not have,
|
|
5208
|
+
pointed at the one pack_connection picks, its profile dropped with it."""
|
|
5209
|
+
targets = doc.get("targets")
|
|
5210
|
+
if not isinstance(targets, list) or STORE is None:
|
|
5211
|
+
return
|
|
5212
|
+
have = {c.get("id") for c in ((STORE.all().get("promptlab.connections") or {}).get("body") or {}).get("list") or []
|
|
5213
|
+
if isinstance(c, dict)}
|
|
5214
|
+
pick = None
|
|
5215
|
+
for t in targets:
|
|
5216
|
+
ref = t.get("connection") if isinstance(t, dict) else None
|
|
5217
|
+
if not isinstance(ref, dict) or ref.get("id") in have:
|
|
5218
|
+
continue
|
|
5219
|
+
pick = pick or pack_connection(ws)
|
|
5220
|
+
if pick is None:
|
|
5221
|
+
return
|
|
5222
|
+
t["connection"] = {"id": pick["id"], "name": pick.get("name") or pick["id"]}
|
|
5223
|
+
t["profile"] = None
|
|
5224
|
+
|
|
5225
|
+
|
|
5144
5226
|
def pack_requires_problem(manifest: dict, plugins: set) -> str:
|
|
5145
5227
|
"""Why this lab cannot take a pack its manifest describes, or ""."""
|
|
5146
5228
|
req = manifest.get("requires") or {}
|
|
@@ -5533,6 +5615,7 @@ class Packs:
|
|
|
5533
5615
|
sid = src_ids.get(content["ref"].get("id")) or src_ids.get(content["ref"].get("name"))
|
|
5534
5616
|
if sid:
|
|
5535
5617
|
content["ref"] = {"id": sid, "name": SOURCES.get(sid)["name"]}
|
|
5618
|
+
repoint_targets(doc, ws)
|
|
5536
5619
|
new_work.append((key, doc))
|
|
5537
5620
|
def place(body):
|
|
5538
5621
|
body.setdefault("list", [])
|
|
@@ -7518,10 +7601,12 @@ CONNECTION_TYPES = {
|
|
|
7518
7601
|
"presencePenalty", "dryMultiplier", "dryBase", "dryAllowedLength", "grammar",
|
|
7519
7602
|
"imagePosition", "cachePrompt", "thinking", "rejectLoops"),
|
|
7520
7603
|
}
|
|
7521
|
-
# Types that answer from the item and send no request (Echo, #97
|
|
7522
|
-
# address to reach, no key to carry, so the destination rules have
|
|
7523
|
-
# say about them. A
|
|
7524
|
-
|
|
7604
|
+
# Types that answer from the item and send no request (Echo, #97; Recorded,
|
|
7605
|
+
# #299): no address to reach, no key to carry, so the destination rules have
|
|
7606
|
+
# nothing to say about them. A target step stands in for each now, so a stored
|
|
7607
|
+
# row of one is never exported (lab_export). A plugin's type joins with
|
|
7608
|
+
# `"local": true`.
|
|
7609
|
+
LOCAL_CONNECTIONS = {"echo", "recorded"}
|
|
7525
7610
|
# The built-in types, kept apart so a plugin's can be taken away again.
|
|
7526
7611
|
BUILTIN_CONNECTION_TYPES = dict(CONNECTION_TYPES)
|
|
7527
7612
|
BUILTIN_LOCAL = set(LOCAL_CONNECTIONS)
|
|
@@ -7967,6 +8052,11 @@ class NoRedirects(urllib.request.HTTPRedirectHandler):
|
|
|
7967
8052
|
# -- a host nobody put on the list -- and the relay reported 200. An
|
|
7968
8053
|
# allow-list that an environment variable can route around is not one.
|
|
7969
8054
|
OPENER = urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirects)
|
|
8055
|
+
# urllib's own "Python-urllib/3.x" is refused outright by many public APIs
|
|
8056
|
+
# behind Cloudflare (#512), so every outbound request names the lab instead --
|
|
8057
|
+
# honestly, never a browser. A request's own User-Agent, such as an API call
|
|
8058
|
+
# Source's Headers, wins: urllib adds this only where none is set.
|
|
8059
|
+
OPENER.addheaders = [("User-Agent", "evals-lab")]
|
|
7970
8060
|
|
|
7971
8061
|
|
|
7972
8062
|
class Handler(BaseHTTPRequestHandler):
|
|
@@ -8158,7 +8248,8 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
8158
8248
|
# its own, and only in a lab with a store to keep it in.
|
|
8159
8249
|
return self._json(200, {"targetCap": TARGET_CAP, "microsoft": microsoft_config(),
|
|
8160
8250
|
"microsoftEditable": MICROSOFT is not None and not M365_CLIENT_ID,
|
|
8161
|
-
"dynamicProviders": dynamic_providers_config()
|
|
8251
|
+
"dynamicProviders": dynamic_providers_config(),
|
|
8252
|
+
"ollama": ollama_shown()})
|
|
8162
8253
|
if path == "/api/state":
|
|
8163
8254
|
if STORE is None:
|
|
8164
8255
|
return self._send(404, b"not found", "text/plain")
|
|
@@ -8970,7 +9061,11 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
8970
9061
|
if err:
|
|
8971
9062
|
return self._json(err[0], {"error": err[1]})
|
|
8972
9063
|
content["live"] = True
|
|
8973
|
-
|
|
9064
|
+
# The one capture, looped as a snapshot's files are
|
|
9065
|
+
# (evals-core.ts expand): Run first cannot cut below
|
|
9066
|
+
# one item, and Loop repeats it for reply variance (#513).
|
|
9067
|
+
loops = content.get("loops")
|
|
9068
|
+
content["files"] = [name] * (loops if type(loops) is int and loops >= 1 else 1)
|
|
8974
9069
|
content["revs"] = {name: "live:" + hashlib.sha256(
|
|
8975
9070
|
produced[name].encode("utf-8")).hexdigest()}
|
|
8976
9071
|
else:
|
|
@@ -9702,8 +9797,10 @@ def lab_export(only=None) -> dict:
|
|
|
9702
9797
|
the profiles over them (or over none) and the grader if it is one of them,
|
|
9703
9798
|
each share set narrowed to that workspace, the one the file names, and
|
|
9704
9799
|
*new kept where the connection has it. A connection shared only with
|
|
9705
|
-
workspaces in the trash is usable nowhere, and an empty share set would
|
|
9706
|
-
import as All, so it is left out, and out as the grader too.
|
|
9800
|
+
workspaces in the trash, or with none, is usable nowhere, and an empty share set would
|
|
9801
|
+
import as All, so it is left out, and out as the grader too. So is a
|
|
9802
|
+
connection of a local type (Echo): a target step stands in for it, and the
|
|
9803
|
+
lab it lands in offers that step already."""
|
|
9707
9804
|
body = (STORE.all().get("promptlab.connections") or {}).get("body") or {}
|
|
9708
9805
|
profiles = (STORE.all().get("promptlab.connectionProfiles") or {}).get("body") or {}
|
|
9709
9806
|
every = WORKSPACES.list()
|
|
@@ -9716,6 +9813,9 @@ def lab_export(only=None) -> dict:
|
|
|
9716
9813
|
for c in body.get("list") or []:
|
|
9717
9814
|
if not isinstance(c, dict) or (only and not STORE.shared("profile", str(c.get("id") or ""), spaces[0]["id"])):
|
|
9718
9815
|
continue
|
|
9816
|
+
if c.get("type") in LOCAL_CONNECTIONS:
|
|
9817
|
+
nowhere.add(c.get("id"))
|
|
9818
|
+
continue
|
|
9719
9819
|
entry = {k: v for k, v in c.items() if k not in ("key", "shares")}
|
|
9720
9820
|
held = rows.get(f"profile:{c.get('id')}")
|
|
9721
9821
|
if held and SHARE_ALL not in held:
|
|
@@ -9827,6 +9927,11 @@ def lab_problems(doc) -> str:
|
|
|
9827
9927
|
if not c["shares"]:
|
|
9828
9928
|
return (f"connection {c['id']}'s shares is empty; leave shares out for All, "
|
|
9829
9929
|
"or name a workspace or *new")
|
|
9930
|
+
# *none is the store's own mark for a set that lost its last workspace;
|
|
9931
|
+
# an export leaves such a connection out, so a file never carries it.
|
|
9932
|
+
if SHARE_NONE in c["shares"]:
|
|
9933
|
+
return (f"connection {c['id']}'s shares names *none; a lab file holds no connection "
|
|
9934
|
+
"shared with no workspace -- leave it out, or name a workspace or *new")
|
|
9830
9935
|
lost = [s for s in c["shares"] if s not in (SHARE_ALL, SHARE_NEW) and s not in known]
|
|
9831
9936
|
if lost:
|
|
9832
9937
|
return f"connection {c['id']} is shared with {', '.join(lost)}, which neither the file nor this lab has"
|
|
@@ -9893,9 +9998,7 @@ def lab_import(doc, keys) -> tuple:
|
|
|
9893
9998
|
lines.append(f"grader set to {doc['grader']}")
|
|
9894
9999
|
# Only an added connection takes the file's share set: one the lab had
|
|
9895
10000
|
# keeps its own, as everything else it had does. Set before the write,
|
|
9896
|
-
# so the connection is never there and shared with All in between
|
|
9897
|
-
# failed write puts back what the rows were, since its ids never landed.
|
|
9898
|
-
before = STORE.shares()
|
|
10001
|
+
# so the connection is never there and shared with All in between.
|
|
9899
10002
|
for c in added:
|
|
9900
10003
|
if "shares" in c:
|
|
9901
10004
|
STORE.set_shares("profile", c["id"], [s if s in (SHARE_ALL, SHARE_NEW) else wid_of[s]
|
|
@@ -9912,9 +10015,15 @@ def lab_import(doc, keys) -> tuple:
|
|
|
9912
10015
|
ok, _ = STORE.write(writes, STORE.default_ws)
|
|
9913
10016
|
if ok:
|
|
9914
10017
|
break
|
|
10018
|
+
# A failed write takes back the rows of each id that still never
|
|
10019
|
+
# landed, as no rows at all (an empty set is a delete, never *none).
|
|
10020
|
+
# One another writer landed in between keeps the rows it has now:
|
|
10021
|
+
# taking them would leave it shared with All.
|
|
10022
|
+
landed = {x.get("id") for x in ((STORE.all().get("promptlab.connections") or {}).get("body")
|
|
10023
|
+
or {}).get("list") or [] if isinstance(x, dict)}
|
|
9915
10024
|
for c in added:
|
|
9916
|
-
if "shares" in c:
|
|
9917
|
-
STORE.set_shares("profile", c["id"],
|
|
10025
|
+
if "shares" in c and c["id"] not in landed:
|
|
10026
|
+
STORE.set_shares("profile", c["id"], [])
|
|
9918
10027
|
else:
|
|
9919
10028
|
return None, (409, "the lab's connections kept changing while they were imported; import again")
|
|
9920
10029
|
said += lines
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{$t as e,An as t,Bt as n,Cn as r,Ct as i,Dt as a,En as o,Ft as s,Ht as c,It as l,Jt as u,Kt as d,Nt as f,Pt as p,Qt as m,St as ee,Tn as te,Tt as h,Ut as ne,Vt as g,Xt as _,Yt as v,_t as re,an as ie,at as ae,bn as oe,bt as se,cr as ce,fn as y,gn as le,gt as ue,kn as de,kt as b,l as fe,ln as pe,lt as x,mn as S,n as me,nn as C,on as he,pn as ge,qt as _e,s as w,sr as ve,tn as ye,vn as be,vt as T,wn as xe,wt as Se,xt as Ce,yn as we}from"./tokens-CrjDMCbz.js";var E=ce(),Te=ve(),D=[{id:`text`,label:`Plain text`,settings:[]},{id:`list`,label:`List`,settings:[{key:`parse`,label:`Parse as`,type:`select`,choices:[{value:`csv`,label:`CSV`},{value:`lines`,label:`One a line`},{value:`json`,label:`JSON array`}]}]}],O=[{id:`case`,label:`Case`,accepts:[`list`],options:[{key:`mode`,label:`Mode`,type:`select`,choices:[{value:`LOWER`,label:`lower`},{value:`SENTENCE`,label:`Sentence`},{value:`TITLE`,label:`Title`},{value:`UPPER`,label:`UPPER`},{value:`NONE`,label:`As replied`}]}],defaults:()=>({type:`case`,mode:`LOWER`})},{id:`join`,label:`Join`,accepts:[`list`],options:[{key:`with`,label:`Separator`,type:`text`}],defaults:()=>({type:`join`,with:`, `})}],Ee=[{name:`language`,type:`value`,value:`English UK`},{name:`geo-location`,type:`value`,value:`Hamilton, Waikato, New Zealand`},{name:`geo`,type:`block`,enabled:!1}],De=[{path:`group/open`,title:`Group, open`},{path:`group/closed`,title:`Group, closed with a note`},{path:`group/no-menu`,title:`Group without actions`},{path:`group/sub`,title:`Sub-group, level 3`},{path:`group/depth`,title:`Group, three levels deep`},{path:`group/renaming`,title:`Group, renaming`},{path:`menu/disabled`,title:`Menu with a disabled item`},{path:`row/button`,title:`Row actions, one action a button`},{path:`row/menu`,title:`Row actions, two or more in the menu`},{path:`actionbar/states`,title:`Action bar, docked under a header, in its four states`},{path:`tabs/main`,title:`Tab strip, the lab's tabs`},{path:`tabs/section`,title:`Tab strip, a section strip under the lab's`},{path:`header/pipeline`,title:`Context header, a Pipeline`},{path:`header/dataset`,title:`Context header, a Dataset`},{path:`header/source`,title:`Context header, a Source`},{path:`header/switcher`,title:`Switcher, recent and more`},{path:`header/switcher-detail`,title:`Switcher, with a detail`},{path:`picker/fields`,title:`Picker, a profile and a Source`},{path:`multi/closed`,title:`Multi-select, two of three ticked`},{path:`multi/open`,title:`Multi-select, its list open`},{path:`chiplist/words`,title:`Chip-list, words with the input to add more`},{path:`chiplist/empty`,title:`Chip-list, empty with its placeholder`},{path:`workspace/menu`,title:`Workspace trigger and its three-part menu`},{path:`copymove/choose`,title:`Copy or move, a destination chosen`},{path:`copymove/collision`,title:`Copy or move, the name is already taken`},{path:`copymove/confirm`,title:`Copy or move, a Move that breaks a dependency`},{path:`copymove/confirm-pipeline`,title:`Copy or move, a pipeline Move that leaves its data behind`},{path:`share/menu`,title:`Share menu, All / some / New workspaces`},{path:`header/long`,title:`Context header, a long name`},{path:`menu/busy`,title:`Menu with a busy item`},{path:`loading/group`,title:`Loading, inside a group`},{path:`loading/error`,title:`Loading failed, with Retry`},{path:`loading/busy-button`,title:`Busy buttons`},{path:`repeater/empty`,title:`Repeater, empty`},{path:`repeater/min`,title:`Repeater at min 1`},{path:`repeater/between`,title:`Repeater, two of four`},{path:`repeater/max`,title:`Repeater at max`},{path:`repeater/locked`,title:`Repeater, a locked row`},{path:`props/controls`,title:`Property table, every control`},{path:`props/required`,title:`Property table, required only`},{path:`props/long`,title:`Property table, long values`},{path:`props/cards`,title:`Property table, a kind chosen by card`},{path:`props/widths`,title:`Property table, a width per value`},{path:`props/affix`,title:`Property table, a fixed suffix and a field's error`},{path:`props/secret`,title:`Property table, a key held and not held`},{path:`props/code`,title:`Property table, an endpoint and a payload`},{path:`table/sortable`,title:`Table, sortable headers and a row menu`},{path:`table/paged`,title:`Table, paged ten at a time`},{path:`table/select`,title:`DataTable with selection`},{path:`table/select-on`,title:`Table, two rows selected`},{path:`table/filter`,title:`Table with a filter bar`},{path:`table/filter-on`,title:`Table, filtered, with a scope`},{path:`edit/form`,title:`Edit modal, a profile`},{path:`edit/plain`,title:`Edit modal, one section and no menu`},{path:`edit/long`,title:`Edit modal, a form that scrolls`},{path:`edit/status`,title:`Edit modal, a status that is a refusal`},{path:`view/tokens`,title:`Token mappings`},{path:`view/output`,title:`Output with Case`},{path:`view/output-text`,title:`Output, plain text`},{path:`view/output-two`,title:`Output with two modifiers`},{path:`view/output-twice`,title:`Output with the same modifier twice`},{path:`view/output-none`,title:`Responses, read as text`},{path:`view/output-flow`,title:`Responses with Read as`},{path:`view/field-chip`,title:`Field chips in a target's words, with a changed word`},{path:`view/field-edit`,title:`The Edited target's editable prompt, with Insert field and a highlighted diff`},{path:`view/insert-field`,title:`The Edited target's Insert field menu, open`},{path:`wizard/pill`,title:`Wizard pill, two of four steps done`},{path:`wizard/pill-open`,title:`Wizard pill, its checklist open on the current step`},{path:`switch/off`,title:`Switch, off`},{path:`switch/on`,title:`Switch, on`},{path:`switch/disabled`,title:`Switch, disabled`}],k=e(),A=()=>{},j=(...e)=>e.map(e=>({label:e,icon:e===`Remove`?m.remove:e===`Clone`?m.clone:m.rename,onSelect:A,destructive:e===`Remove`}));function M({start:e,min:t,max:n,locked:r}){let[i,a]=(0,E.useState)(e);return(0,k.jsx)(re,{noun:`scenario`,items:i,...t===void 0?{}:{min:t},...n===void 0?{}:{max:n},...r?{locked:r}:{},onAdd:()=>a([...i,`Target ${String.fromCharCode(65+i.length)}`]),onRemove:e=>a(i.filter((t,n)=>n!==e)),onClone:e=>a([...i.slice(0,e+1),`${i[e]} copy`,...i.slice(e+1)]),onRename:A,empty:`No targets yet.`,renderItem:(e,t,n)=>(0,k.jsx)(d,{id:`scenario-${t}`,title:e,level:3,actions:n,children:(0,k.jsx)(`p`,{className:`note`,children:`A connection, a profile and a prompt for each job.`})},`${e}-${t}`)})}function Oe(){let[e,t]=(0,E.useState)(Ee),n=(n,r)=>t(e.map(e=>e.name===n?{...e,...r}:e));return(0,k.jsx)(fe,{mappings:e,onValue:(e,t)=>n(e,{value:t}),onEnabled:(e,t)=>n(e,{enabled:t}),onEdit:A,onRemove:n=>t(e.filter(e=>e.name!==n)),onAdd:()=>t([...e,{name:`token${e.length+1}`,type:`value`,value:``}])})}function N({start:e,format:t=!0,readAs:n}){let[r,i]=(0,E.useState)(e),[a,o]=(0,E.useState)(t),[s,c]=(0,E.useState)(n);return(0,k.jsx)(ae,{id:`output`,out:r,format:a,readAs:s,outputKinds:D,modifiers:O,onFormat:e=>{o(e!=null);let t=e??`text`;i({kind:t,modifiers:r.modifiers.filter(e=>O.find(t=>t.id===e.type)?.accepts.includes(t))})},onReadAs:c,onModifier:(e,t)=>i({...r,modifiers:r.modifiers.map((n,r)=>r===e?t:n)}),onAddModifier:e=>i({...r,modifiers:[...r.modifiers,e]}),onRemoveModifier:e=>i({...r,modifiers:r.modifiers.filter((t,n)=>n!==e)})})}function ke(){let[e,t]=(0,E.useState)(`Job 1`),[n,r]=(0,E.useState)(!0);return(0,k.jsx)(d,{id:`renaming`,title:e,actions:[{label:`Rename…`,icon:m.rename,onSelect:()=>r(!0)}],...n?{rename:{value:e,onCommit:n=>{t(n||e),r(!1)},onCancel:()=>r(!1)}}:{},children:(0,k.jsx)(`p`,{className:`note`,children:`Enter or leaving keeps the name; Escape cancels.`})})}function Ae(){let[e,t]=(0,E.useState)(`system`);return(0,k.jsx)(`div`,{className:`gallery-row`,style:{justifyContent:`flex-end`},children:(0,k.jsx)(i,{current:{id:`w1`,slug:`work`,name:`Work`},workspaces:[{id:`w1`,slug:`work`,name:`Work`},{id:`w2`,slug:`side-project`,name:`Side project`},{id:`w3`,slug:`client-demos`,name:`Client demos`}],onSwitch:A,onNew:A,onManage:A,theme:e,onTheme:t})})}var je=[{id:`cur`,slug:`work`,name:`Work`,archived:!1,isDefault:!0,lastUsed:`2026-10-08T09:30:00Z`,createdAt:null,pipelines:4,runs:18},{id:`side`,slug:`side-project`,name:`Side project`,archived:!1,isDefault:!1,lastUsed:`2026-10-07T09:30:00Z`,createdAt:null,pipelines:2,runs:6},{id:`demos`,slug:`client-demos`,name:`Client demos`,archived:!1,isDefault:!1,lastUsed:`2026-10-06T09:30:00Z`,createdAt:null,pipelines:1,runs:0}],Me={id:`cur`,slug:`work`,name:`Work`},Ne={undo:null,offer:A,undoNow:A,dismiss:A},Pe=e=>({copyMovePreflight:async()=>({nameTaken:e,suggestedName:`Daily regressions (copy)`}),copyAsset:async()=>({id:`copy`}),moveAsset:async()=>({id:`same`}),forWorkspace:()=>({})});function P({nameTaken:e=!1,confirm:t=!1,subject:n,dependents:r=[]}){return(0,k.jsx)(ue,{api:Pe(e),subject:n??{kind:`filter-set`,id:`f1`,name:`Daily regressions`},workspaces:je,current:Me,dependents:r,undo:Ne,onClose:A,defaultDest:`side`,...t?{defaultConfirm:!0}:{}})}function Fe(){let e=[{id:`w1`,name:`Work`},{id:`w2`,name:`Side project`},{id:`w3`,name:`Client demos`}];return(0,k.jsx)(`div`,{style:{display:`grid`,gap:`var(--s3)`,maxWidth:`20rem`},children:[{label:`All workspaces`,shares:void 0},{label:`Some workspaces`,shares:[`w2`]},{label:`New workspaces`,shares:[se]},{label:`No workspaces`,shares:[Ce]}].map(t=>(0,k.jsx)(ee,{label:t.label,workspaces:e,shares:t.shares,onChange:A},t.label))})}function F({sections:e,menu:t,status:n,statusBad:r,outcome:i}){return(0,k.jsx)(f,{title:`Lab Ollama`,...n?{status:n}:{},...r?{statusBad:!0}:{},...t?{menu:t}:{},sections:e,actions:[{label:`Test connection`,icon:ge,onSelect:A,outcome:i?{label:i,onOpen:A}:void 0}],onClose:A})}var I=()=>[{key:`name`,label:`Name`,width:`full`,required:!0,control:{type:`text`,value:`Lab Ollama`,onChange:A}},{key:`slug`,label:`Slug`,width:`full`,required:!0,control:{type:`text`,value:`lab-ollama`,onChange:A}},{key:`type`,label:`Type`,required:!0,control:{type:`select`,value:`ollama`,onChange:A,options:[{value:`ollama`,label:`Ollama (Local)`},{value:`anthropic`,label:`Anthropic`}]}},{key:`url`,label:`Address`,width:`full`,control:{type:`text`,value:``,placeholder:`http://127.0.0.1:11434`,onChange:A}},{key:`key`,label:`Key`,control:{type:`secret`,value:``,onChange:A}},{key:`model`,label:`Model`,width:`full`,control:{type:`text`,value:`minicpm-v4.6:latest`,onChange:A}}],L=()=>[{key:`seed`,label:`Seed`,control:{type:`text`,value:``,placeholder:`1234`,onChange:A}},{key:`nPredict`,label:`Reply tokens`,width:`quarter`,control:{type:`text`,value:``,onChange:A}},{key:`temperature`,label:`Temperature`,control:{type:`text`,value:`0.3`,onChange:A}},{key:`repeatPenalty`,label:`Repeat penalty`,width:`quarter`,control:{type:`text`,value:``,onChange:A}}],R=()=>[{key:`endpoint`,label:`Endpoint`,required:!0,control:{type:`code`,text:`http://127.0.0.1:11434/api/chat`}},{key:`prompt`,label:`Prompt`,required:!0,control:{type:`static`,text:`(prompt)`}},{key:`model`,label:`model`,control:{type:`code`,text:`minicpm-v4.6:latest`}},{key:`options`,label:`options`,control:{type:`code`,text:`{"seed":1234,"temperature":0.3}`}}],z={key:`kind`,label:`Response Format`,required:!0,control:{type:`select`,value:`list`,onChange:A,options:D.map(e=>({value:e.id,label:e.label}))}};function B({actions:e}){return(0,k.jsx)(d,{id:`g`,title:`Actions`,children:(0,k.jsx)(b,{label:`Actions`,rows:[{name:`Ask_Claude`}],rowKey:e=>e.name,columns:[{key:`name`,header:`Name`,role:`title`,cell:e=>e.name},{key:`menu`,header:``,role:`menu`,sort:null,cell:()=>(0,k.jsx)(_e,{label:`Ask_Claude actions`,actions:e})}]})})}var Ie=[[`home`,`Home`,we],[`runs`,`Runs`,te],[`history`,`History`,oe],[`library`,`Library`,be],[`setup`,`Setup`,pe]],Le=[[`datasets`,`Datasets`,t],[`prompts`,`Prompts`,le],[`sources`,`Sources`,xe]],V=e=>(0,k.jsx)(g,{label:`Lab`,current:e,items:Ie.map(([e,t,n])=>({key:e,label:t,icon:n,href:`#/${e}`}))}),H=e=>(0,k.jsx)(g,{label:`Library`,level:`section`,current:e,items:Le.map(([e,t,n])=>({key:e,label:t,icon:n,href:`#/library/${e}`}))}),U=({section:e,children:t})=>(0,k.jsxs)(`div`,{children:[V(e?`library`:`runs`),e&&H(e),t]}),W=(...e)=>e.map(e=>({label:e,icon:e===`Delete`?m.remove:e===`Clone`?m.clone:m.rename,onSelect:A,destructive:e===`Delete`})),Re={"header/pipeline":()=>(0,k.jsx)(U,{children:(0,k.jsx)(n,{kind:`Pipeline`,name:`Pipeline 1`,onPick:A,meta:`2 jobs, 3 targets`,primary:{label:`New pipeline`,icon:y,onSelect:A},actions:W(`Rename…`,`Clone`,`Export YAML`,`Import YAML…`,`Restore…`,`Delete`)})}),"header/dataset":()=>(0,k.jsx)(U,{section:`datasets`,children:(0,k.jsx)(n,{kind:`Dataset`,name:`Demo 1`,onPick:A,meta:`62 cases, 19 graded`,primary:{label:`New dataset`,icon:y,onSelect:A},actions:W(`Rename…`,`Clone`,`Import JSON…`,`Export JSON`,`Restore…`,`Delete`)})}),"header/switcher":()=>(0,k.jsx)(h,{kind:`Pipeline`,value:`p5`,onPick:A,onClose:A,recent:[`p7`,`p6`,`p5`,`p4`,`p3`],options:Array.from({length:13},(e,t)=>({id:`p${t}`,label:[`Demo 1`,`Demo 2`,`Receipts`,`Menus`,`Street signs`,`Labels`,`Plates`,`Posters`,`Packaging`,`Whiteboards`,`Book spines`,`Shop fronts`,`Timetables`][t],used:t<8?{at:`2026-10-0${t+1}T09:30:00Z`,label:x(`2026-10-0${t+1}T09:30:00Z`)}:null}))}),"header/switcher-detail":()=>(0,k.jsx)(h,{kind:`Connection`,value:`c1`,onPick:A,onClose:A,detailLabel:`Model`,recent:[`c3`,`c1`],options:[[`Lab Ollama gemma3 4b`,`gemma3:4b`],[`Lab Ollama minicpm`,`minicpm-v`],[`Anthropic`,`claude-sonnet-4-5`],[`Dell llama`,`qwen2.5vl:7b`],[`OpenAI`,`gpt-5`],[`Docker llama`,`qwen2.5vl:7b`]].map(([e,t],n)=>({id:`c${n}`,label:e,detail:t,used:n<4?{at:`2026-10-1${n}T09:30:00Z`,label:x(`2026-10-1${n}T09:30:00Z`)}:null}))}),"picker/fields":()=>(0,k.jsx)(d,{id:`g`,title:`Target A`,children:(0,k.jsx)(p,{label:`Target A setup`,rows:[{key:`profile`,label:`Profile`,control:{type:`picker`,kind:`Profile`,value:`p2`,usedLabel:`Last changed`,onChange:A,recent:[`p5`,`p4`,`p3`,`p2`,`p1`],options:[{id:`#echo`,label:`Echo`,fixed:!0},...[`Lab Ollama`,`Anthropic`,`Cloud vision`,`Local llama`,`OpenRouter`,`Bench`,`Vision`].map((e,t)=>({id:`p${t}`,label:e,used:t<6?{at:`2026-10-0${t+1}T09:30:00Z`,label:x(`2026-10-0${t+1}T09:30:00Z`)}:null}))]}},{key:`source`,label:`Source`,control:{type:`picker`,kind:`Source`,value:`gone`,usedLabel:`Last changed`,onChange:A,options:[{id:``,label:`Any`,fixed:!0},{id:`gone`,label:`Shoot 3 (missing)`,missing:!0},{id:`s1`,label:`Receipts`}]}}]})}),"header/source":()=>(0,k.jsx)(U,{section:`sources`,children:(0,k.jsx)(n,{kind:`Source`,name:`Demo 1 images`,onPick:A,meta:`File Library. 63 files, 48 MB`,primary:{label:`Upload…`,icon:C,onSelect:A},actions:W(`Rename…`,`Delete`)})}),"workspace/menu":()=>(0,k.jsx)(Ae,{}),"copymove/choose":()=>(0,k.jsx)(P,{}),"copymove/collision":()=>(0,k.jsx)(P,{nameTaken:!0}),"copymove/confirm":()=>(0,k.jsx)(P,{confirm:!0,dependents:[{kind:`pipeline`,id:`w1`,name:`Nightly regression`,href:`#/runs`}]}),"copymove/confirm-pipeline":()=>(0,k.jsx)(P,{confirm:!0,subject:{kind:`pipeline`,id:`w1`,name:`Nightly regression`},dependents:[{kind:`eval-group`,id:`g1`,name:`Receipts, 62 cases`,href:`#/library/datasets/g1`},{kind:`source`,id:`s1`,name:`Receipt images`,href:`#/library/sources`}]}),"share/menu":()=>(0,k.jsx)(Fe,{}),"header/long":()=>(0,k.jsx)(U,{children:(0,k.jsx)(n,{kind:`Pipeline`,name:`Receipts and handwritten menus, second pass with the tighter prompt and a Contains metric`,onPick:A,meta:`12 jobs, 8 targets`,primary:{label:`New pipeline`,icon:y,onSelect:A},actions:W(`Rename…`,`Clone`,`Delete`)})}),"actionbar/states":()=>(0,k.jsxs)(`div`,{children:[V(`runs`),(0,k.jsx)(n,{kind:`Pipeline`,name:`Demo 1`,onPick:A,meta:`1 job, 2 targets`,docked:!0,primary:{label:`New pipeline`,icon:y,onSelect:A},actions:W(`Rename…`,`Delete`)}),(0,k.jsx)(T,{primary:{label:`Run`,icon:S,onSelect:A},status:(0,k.jsx)(`b`,{children:`Ready to run`})}),(0,k.jsxs)(`div`,{style:{display:`grid`,gap:`var(--s4)`,marginTop:`var(--s5)`},children:[(0,k.jsx)(T,{primary:{label:`Run`,icon:S,onSelect:A,disabled:!0},status:(0,k.jsxs)(k.Fragment,{children:[`Connection "Vision" not found. `,(0,k.jsxs)(`a`,{href:`#/setup`,style:{whiteSpace:`nowrap`},children:[`Open`,`\xA0`,`Setup`]})]})}),(0,k.jsx)(T,{primary:{label:`Run`,icon:S,onSelect:A,disabled:!0},status:(0,k.jsxs)(k.Fragment,{children:[(0,k.jsx)(`b`,{children:`Running`}),` 12 of 42`]}),secondary:{label:`Stop`,icon:he,onSelect:A},progress:28,progressLabel:`Run progress`}),(0,k.jsx)(T,{primary:{label:`Run`,icon:S,onSelect:A},status:(0,k.jsxs)(k.Fragment,{children:[(0,k.jsx)(`b`,{children:`Done`}),` 42 in 2m`]})})]})]}),"tabs/main":()=>V(`runs`),"tabs/section":()=>(0,k.jsxs)(`div`,{children:[V(`library`),H(`datasets`)]}),"group/open":()=>(0,k.jsx)(d,{id:`g`,title:`Evals`,actions:j(`Change…`,`Remove`),children:(0,k.jsx)(`p`,{className:`note`,children:`The graded set.`})}),"group/closed":()=>(0,k.jsx)(d,{id:`g`,title:`Content`,open:!1,note:`Sample library, 63 files`,actions:j(`Change…`,`Remove`)}),"group/no-menu":()=>(0,k.jsx)(d,{id:`g`,title:`Mapping`,children:(0,k.jsx)(`p`,{className:`note`,children:`Nothing to do to it.`})}),"group/sub":()=>(0,k.jsx)(d,{id:`g`,title:`Job 1`,actions:j(`Rename…`,`Remove`),children:(0,k.jsx)(d,{id:`g-sub`,title:`Scenarios`,level:3,actions:j(`Clear`),children:(0,k.jsx)(`p`,{className:`note`,children:`Inside a pane.`})})}),"group/depth":()=>(0,k.jsxs)(d,{id:`g`,title:`Job 1`,actions:j(`Rename…`,`Remove`),children:[(0,k.jsxs)(d,{id:`g-scenarios`,title:`Scenarios`,level:3,children:[(0,k.jsx)(d,{id:`g-plain`,title:`Plain`,level:3,actions:j(`Rename…`,`Remove`),children:(0,k.jsx)(`p`,{className:`note`,children:`A pane, a section of it, and a thing with a name inside that section.`})}),(0,k.jsx)(d,{id:`g-strict`,title:`Strict`,level:3,actions:j(`Rename…`,`Remove`)})]}),(0,k.jsx)(d,{id:`g-output`,title:`Output`,level:3,children:(0,k.jsx)(`p`,{className:`note`,children:`The next section of the same pane.`})})]}),"group/renaming":()=>(0,k.jsx)(ke,{}),"menu/disabled":()=>(0,k.jsx)(d,{id:`g`,title:`Target D`,actions:[{label:`Rename…`,icon:m.rename,onSelect:A},{label:`Clone`,icon:m.clone,onSelect:A,disabled:!0},{label:`Remove`,icon:m.remove,onSelect:A,destructive:!0}]}),"row/button":()=>(0,k.jsx)(B,{actions:[{label:`New pipeline`,icon:S,onSelect:A}]}),"row/menu":()=>(0,k.jsx)(B,{actions:[{label:`View…`,icon:o,onSelect:A},{label:`Download`,icon:de,onSelect:A},{label:`Remove`,icon:ie,onSelect:A,destructive:!0}]}),"menu/busy":()=>(0,k.jsx)(d,{id:`g`,title:`History`,actions:[{label:`Export JSON`,icon:m.export,onSelect:A,busy:!0},{label:`Clear`,icon:m.clear,onSelect:A,destructive:!0}]}),"loading/group":()=>(0,k.jsx)(d,{id:`g`,title:`History`,actions:j(`Rename…`),children:(0,k.jsx)(v,{label:`Runs`,delay:0})}),"loading/error":()=>(0,k.jsx)(d,{id:`g`,title:`History`,children:(0,k.jsx)(v,{label:`Runs`,error:`Runs not loaded: server unreachable.`,onRetry:A})}),"loading/busy-button":()=>(0,k.jsx)(d,{id:`g`,title:`Run`,children:(0,k.jsxs)(`div`,{className:`gallery-row`,children:[(0,k.jsxs)(`button`,{type:`button`,className:`run`,disabled:!0,"aria-busy":!0,children:[(0,k.jsx)(_,{size:16}),`Run`]}),(0,k.jsxs)(`button`,{type:`button`,className:`ghost`,disabled:!0,"aria-busy":!0,children:[(0,k.jsx)(_,{}),`Older`]})]})}),"repeater/empty":()=>(0,k.jsx)(M,{start:[]}),"repeater/min":()=>(0,k.jsx)(M,{start:[`Target A`],min:1}),"repeater/between":()=>(0,k.jsx)(M,{start:[`Target A`,`Target B`],min:1,max:4}),"repeater/max":()=>(0,k.jsx)(M,{start:[`Target A`,`Target B`,`Target C`,`Target D`],min:1,max:4}),"repeater/locked":()=>(0,k.jsx)(M,{start:[`Reply`,`Target A`],locked:(e,t)=>t===0}),"props/controls":()=>(0,k.jsx)(p,{label:`Controls`,onRename:A,onRemove:A,onAdd:A,addLabel:`Add row`,rows:[z,{key:`language`,label:`{language}`,code:!0,control:{type:`text`,value:`English UK`,onChange:A}},{key:`geo`,label:`{geo}…{/geo}`,code:!0,control:{type:`static`,text:`Block`},enabled:{checked:!0,onChange:A}},{key:`image`,label:`Image`,control:{type:`checkbox`,checked:!0,label:`Send`,onChange:A}}]}),"props/required":()=>(0,k.jsx)(p,{label:`Output`,onRemove:A,rows:[z]}),"props/long":()=>(0,k.jsx)(p,{label:`Long`,onRemove:A,rows:[{key:`geo-location`,label:`{geo-location}`,code:!0,control:{type:`text`,value:`Hamilton, Waikato, New Zealand, Southern Hemisphere, Earth`,onChange:A}},{key:`averylongtokennamethatdoesnotbreak`,label:`{averylongtokennamethatdoesnotbreak}`,code:!0,control:{type:`text`,value:``,onChange:A}}]}),"props/cards":()=>(0,k.jsx)(He,{}),"props/widths":()=>(0,k.jsx)(p,{label:`Widths`,rows:[{key:`type`,label:`Type`,required:!0,control:{type:`select`,value:`ollama`,onChange:A,options:[{value:`ollama`,label:`Ollama (Local)`},{value:`anthropic`,label:`Anthropic`}]}},{key:`url`,label:`Address`,width:`full`,control:{type:`text`,value:``,placeholder:`http://127.0.0.1:11434`,onChange:A}},{key:`model`,label:`Model`,width:`full`,control:{type:`text`,value:`minicpm-v4.6:latest`,onChange:A}},{key:`seed`,label:`Seed`,control:{type:`text`,value:``,placeholder:`1234`,onChange:A}},{key:`nPredict`,label:`Reply tokens`,width:`quarter`,control:{type:`text`,value:``,onChange:A}},{key:`temperature`,label:`Temperature`,control:{type:`text`,value:`0.2`,onChange:A}},{key:`grammar`,label:`Grammar`,control:{type:`textarea`,value:`root ::= item (", " item)*
|
|
2
|
+
item ::= [a-z ]+`,onChange:A}}]}),"props/affix":()=>(0,k.jsx)(p,{label:`Call`,rows:[{key:`name`,label:`Name`,control:{type:`text`,value:`open-meteo-forecast`,suffix:`.txt`,onChange:A}},{key:`taken`,label:`Name`,error:`open-meteo-forecast-storm.txt is taken.`,control:{type:`text`,value:`open-meteo-forecast-storm`,suffix:`.txt`,onChange:A}},{key:`status`,label:`Status`,width:`quarter`,control:{type:`text`,value:`200`,numeric:!0,onChange:A}},{key:`body`,label:`Body`,control:{type:`textarea`,mono:!0,value:`{
|
|
3
|
+
"category": "utilities"
|
|
4
|
+
}`,onChange:A}}]}),"props/secret":()=>(0,k.jsx)(p,{label:`Keys`,rows:[{key:`held`,label:`Key`,control:{type:`secret`,value:`sk-not-a-real-key-0000`,onChange:A}},{key:`empty`,label:`Key`,control:{type:`secret`,value:``,onChange:A}},{key:`server`,label:`Key`,control:{type:`secret`,value:``,held:!0,onChange:A}}]}),"props/code":()=>(0,k.jsx)(p,{label:`Debug`,rows:[{key:`endpoint`,label:`Endpoint`,required:!0,control:{type:`code`,text:`https://api.anthropic.com/v1/messages`}},{key:`prompt`,label:`Prompt`,required:!0,control:{type:`static`,text:`(prompt)`}},{key:`model`,label:`model`,control:{type:`code`,text:`claude-sonnet-4-5-20250929`}},{key:`options`,label:`options`,control:{type:`code`,text:`{"seed":1234,"temperature":0}`}},{key:`stream`,label:`stream`,control:{type:`code`,text:`false`}}]}),"table/sortable":()=>(0,k.jsx)($,{count:4}),"table/paged":()=>(0,k.jsx)($,{count:35,pageSize:10}),"table/select":()=>(0,k.jsx)($,{count:5,picked:[]}),"table/select-on":()=>(0,k.jsx)($,{count:5,picked:[1,3]}),"table/filter":()=>(0,k.jsx)(Ve,{}),"table/filter-on":()=>(0,k.jsx)(Ve,{search:`IMG_40`,size:`large`}),"edit/form":()=>(0,k.jsx)(F,{outcome:`Connected`,menu:[{label:`Restore…`,icon:m.restore,onSelect:A},{label:`Apply preset: repeat 1.3`,icon:m.preset,onSelect:A}],sections:[{key:`identity`,rows:I()},{key:`settings`,title:`Settings`,rows:L()},{key:`debug`,title:`Debug`,rows:R(),folded:!0}]}),"edit/plain":()=>(0,k.jsx)(f,{title:`A mapping`,sections:[{key:`one`,rows:[z,{key:`with`,label:`Separator`,control:{type:`text`,value:`, `,onChange:A}}]}],onClose:A}),"edit/long":()=>(0,k.jsx)(F,{menu:[{label:`Restore…`,icon:m.restore,onSelect:A,disabled:!0}],sections:[{key:`identity`,rows:I()},{key:`settings`,title:`Settings`,rows:[...L(),{key:`grammar`,label:`Grammar`,control:{type:`textarea`,value:`root ::= item (", " item)*
|
|
5
|
+
item ::= [a-z ]+`,onChange:A}},{key:`imagePosition`,label:`Image position`,control:{type:`select`,value:`after`,onChange:A,options:[{value:`before`,label:`before the prompt`},{value:`after`,label:`after the prompt`}]}},{key:`cachePrompt`,label:`Cache prompt`,control:{type:`select`,value:`off`,onChange:A,options:[{value:`on`,label:`on`},{value:`off`,label:`off`}]}}]},{key:`debug`,title:`Debug`,rows:R(),folded:!0}]}),"edit/status":()=>(0,k.jsx)(F,{status:`the relay could not reach http://127.0.0.1:11434`,statusBad:!0,sections:[{key:`identity`,rows:I()}]}),"view/tokens":()=>(0,k.jsx)(d,{id:`g`,title:`Token mappings`,level:3,children:(0,k.jsx)(Oe,{})}),"view/output":()=>(0,k.jsx)(N,{start:{kind:`list`,parse:`csv`,modifiers:[{type:`case`,mode:`LOWER`}]}}),"view/output-text":()=>(0,k.jsx)(N,{start:{kind:`text`,modifiers:[]}}),"view/output-two":()=>(0,k.jsx)(N,{start:{kind:`list`,parse:`csv`,modifiers:[{type:`case`,mode:`TITLE`},{type:`join`,with:` | `}]}}),"view/output-twice":()=>(0,k.jsx)(N,{start:{kind:`list`,parse:`csv`,modifiers:[{type:`case`,mode:`TITLE`},{type:`case`,mode:`LOWER`}]}}),"view/output-none":()=>(0,k.jsx)(N,{start:{kind:`text`,modifiers:[]},format:!1}),"view/output-flow":()=>(0,k.jsx)(N,{start:{kind:`text`,modifiers:[]},readAs:`@body('Ask')?['content']?[0]?['text']`}),"view/field-chip":()=>(0,k.jsx)(d,{id:`g`,title:`Edited`,level:3,children:(0,k.jsxs)(`div`,{className:`prompt`,children:[(0,k.jsx)(`span`,{className:`prop-name`,children:`Prompt`}),(0,k.jsx)(w,{words:`Summarise @{triggerBody()?['text']} in two short sentences for @{variables('Audience')}.`,was:`Summarise @{triggerBody()?['text']} in one sentence for @{variables('Audience')}.`})]})}),"view/field-edit":()=>(0,k.jsx)(ze,{}),"view/insert-field":()=>(0,k.jsx)(d,{id:`g`,title:`Edited`,level:3,children:(0,k.jsxs)(`div`,{className:`prompt`,children:[(0,k.jsxs)(`div`,{className:`pe-head`,children:[(0,k.jsx)(`span`,{className:`prop-name`,children:`Prompt`}),(0,k.jsx)(ne,{fields:J,onInsert:A,label:`Edited`,defaultOpen:!0})]}),(0,k.jsx)(`textarea`,{"aria-label":`Edited, prompt`,rows:4,defaultValue:q}),(0,k.jsx)(w,{className:`pe-words`,words:q,was:K,showExprs:!1})]})}),"multi/closed":()=>(0,k.jsx)(Y,{}),"multi/open":()=>(0,k.jsx)(Y,{open:!0}),"chiplist/words":()=>(0,k.jsx)(X,{start:[`clearly`,`obviously`,`as an AI`]}),"chiplist/empty":()=>(0,k.jsx)(X,{start:[]}),"wizard/pill":()=>(0,k.jsx)(Z,{}),"wizard/pill-open":()=>(0,k.jsx)(Z,{open:!0}),"switch/off":()=>(0,k.jsx)(G,{}),"switch/on":()=>(0,k.jsx)(G,{start:!0}),"switch/disabled":()=>(0,k.jsx)(s,{checked:!0,disabled:!0,label:`Snapshot now`,onChange:A})};function G({start:e}){let[t,n]=(0,E.useState)(!!e);return(0,k.jsx)(s,{checked:t,onChange:n,label:`Snapshot now`})}var K=`Tell me about this location in 1 sentence: @{variables('LocationVar')}`,q=`Tell me about this location in 2 sentences, for someone visiting for the first time: @{variables('LocationVar')}`,J=[{name:`LocationVar`,expr:`@{variables('LocationVar')}`},{name:`text`,expr:`@{triggerBody()?['text']}`}];function ze(){let[e,t]=(0,E.useState)(q);return(0,k.jsx)(d,{id:`g`,title:`Edited`,level:3,children:(0,k.jsx)(me,{label:`Edited`,asks:[{key:`connection`,label:`Connection`,control:{type:`static`,text:`Anthropic Direct`}}],prompt:e,onPrompt:t,rows:[{key:`model`,label:`Model`,control:{type:`static`,text:`claude-haiku-4-5`}}],request:[{key:`request`,label:`Request`,control:{type:`code`,text:`POST /v1/messages`}},{key:`body`,label:`Body`,control:{type:`code`,text:JSON.stringify({model:`claude-haiku-4-5`,max_tokens:1024,messages:[{role:`user`,content:e}]},null,2)}}],requestId:`story-edited-request`,words:{text:e,was:K},fields:J})})}function Y({open:e=!1}){let[t,n]=(0,E.useState)([`better`,`same`]),r=[{value:`better`,label:`Better`},{value:`same`,label:`Same`},{value:`worse`,label:`Worse`}];return e?(0,k.jsx)(`div`,{style:{minHeight:180},children:(0,k.jsx)(l,{value:t,options:r,onChange:n,defaultOpen:!0})}):(0,k.jsx)(p,{label:`Judged against recorded`,rows:[{key:`expected`,label:`Expected`,required:!0,control:{type:`multi`,value:t,options:r,onChange:n}}]})}function X({start:e}){let[t,n]=(0,E.useState)(e);return(0,k.jsx)(`div`,{style:{maxWidth:360},children:(0,k.jsx)(Se,{value:t,onChange:n})})}function Z({open:e}){return(0,k.jsx)(`div`,{style:{display:`flex`,justifyContent:`flex-end`,padding:`var(--s3)`},children:(0,k.jsx)(c,{name:`Test a workflow`,done:2,total:4,current:2,steps:[{title:`Add a workflow Source`,done:!0},{title:`Test an action`,done:!0},{title:`Run the test`,done:!1},{title:`Compare the replies`,done:!1}],onOpen:A,onCancel:A,...e?{defaultOpen:!0}:{}})})}var Q=De.map(e=>({...e,render:Re[e.path]})),Be=e=>Array.from({length:e},(e,t)=>({name:`IMG_${String(4e3+t*7919%97).padStart(4,`0`)}.jpg`,kb:t*37%900+40}));function Ve({search:e=``,size:t=`all`}){let[n,r]=(0,E.useState)(e),[i,o]=(0,E.useState)(t),[s,c]=(0,E.useState)(`shoot`),l=Be(12),u=l.filter(e=>e.name.toLowerCase().includes(n.trim().toLowerCase())&&(i===`all`||i===`large`==e.kb>=400));return(0,k.jsxs)(d,{id:`files`,title:`Files`,children:[(0,k.jsx)(a,{noun:`files`,search:{label:`Name`,placeholder:`Search names`,value:n,onChange:r},count:{shown:u.length,total:l.length},filters:[{key:`source`,label:`Source`,value:s,neutral:null,onChange:c,options:[{value:`shoot`,label:`Tuesday shoot`},{value:`samples`,label:`Sample library`}]},{key:`size`,label:`Size`,value:i,onChange:o,options:[{value:`all`,label:`All`},{value:`small`,label:`Small`},{value:`large`,label:`Large`}]}]}),u.length?(0,k.jsx)(b,{label:`Files`,rows:u,rowKey:e=>e.name,columns:[{key:`name`,header:`Name`,cell:e=>e.name},{key:`size`,header:`Size`,sort:e=>e.kb,cell:e=>`${e.kb} KB`}]}):(0,k.jsx)(`p`,{className:`note`,children:`No files match these filters.`})]})}function $({count:e,pageSize:t,picked:n}){let r=Be(e),[i,a]=(0,E.useState)(r),[o,s]=(0,E.useState)(()=>new Set((n??[]).map(e=>r[e].name)));return(0,k.jsx)(d,{id:`files`,title:`Files`,children:(0,k.jsx)(b,{label:`Files`,cards:!0,rows:i,rowKey:e=>e.name,pageSize:t??null,select:n&&{noun:`file`,selected:o,onChange:s,actions:e=>[{label:`Move…`,icon:m.rename,onSelect:A},{label:`Remove`,icon:m.remove,destructive:!0,onSelect:()=>{a(t=>t.filter(t=>!e.includes(t))),s(new Set)}},{label:`Export`,icon:C,onSelect:A}]},columns:[{key:`name`,header:`Name`,role:`title`,cell:e=>e.name},{key:`size`,header:`Size`,className:`num`,sort:e=>e.kb,cell:e=>`${e.kb} KB`},{key:`menu`,header:``,role:`menu`,sort:null,cell:e=>(0,k.jsx)(u,{label:`${e.name} actions`,actions:[{label:`Rename…`,icon:m.rename,onSelect:A},{label:`Remove`,icon:m.remove,destructive:!0,onSelect:A}]})}]})})}function He(){let[e,t]=(0,E.useState)(`files`);return(0,k.jsx)(p,{label:`New Source`,rows:[{key:`type`,label:`Type`,control:{type:`cards`,value:e,onChange:t,options:[{value:`files`,label:`File Library`,icon:(0,k.jsx)(r,{size:16,fill:`currentColor`,"aria-hidden":`true`})},{value:`flow`,label:`Power Automate flow`,icon:(0,k.jsx)(ye,{size:16,fill:`currentColor`,"aria-hidden":`true`})}]}},{key:`name`,label:`Name`,control:{type:`text`,value:``,onChange:A}}]})}var Ue=()=>decodeURIComponent(location.hash.replace(/^#\/?/,``));function We(){let[e,t]=(0,E.useState)(Ue);(0,E.useEffect)(()=>{let e=()=>t(Ue());return window.addEventListener(`hashchange`,e),()=>window.removeEventListener(`hashchange`,e)},[]);let n=Q.find(t=>t.path===e);return n?(0,k.jsxs)(`main`,{className:`gallery-one`,"data-route":n.path,children:[(0,k.jsxs)(`nav`,{className:`gallery-nav`,children:[(0,k.jsx)(`a`,{href:`#/`,children:`All`}),(0,k.jsx)(`span`,{children:n.title})]}),n.render()]}):(0,k.jsxs)(`main`,{className:`gallery-index`,"data-route":``,children:[(0,k.jsx)(`h1`,{children:`Components`}),(0,k.jsx)(`ul`,{children:Q.map(e=>(0,k.jsxs)(`li`,{children:[(0,k.jsx)(`a`,{href:`#/${e.path}`,children:e.title}),` `,(0,k.jsx)(`code`,{children:e.path})]},e.path))})]})}(0,Te.createRoot)(document.getElementById(`root`)).render((0,k.jsx)(E.StrictMode,{children:(0,k.jsx)(We,{})}));
|