wizards 0.5.13 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/runtime/dist/authoring/guide.js +63 -7
- package/apps/runtime/dist/credits/estimate.js +1 -1
- package/apps/runtime/dist/engine/requirements.js +1 -1
- package/apps/runtime/dist/engine/runner.js +53 -4
- package/apps/runtime/dist/engine/steps.js +155 -123
- package/apps/runtime/dist/render/downloads.js +16 -3
- package/apps/runtime/dist/render/shaped.js +137 -0
- package/apps/runtime/dist/routes/runs.js +58 -2
- package/apps/runtime/dist/services/brand.js +2 -1
- package/apps/runtime/dist/widgets/runtime.js +25 -2
- package/apps/runtime/dist/widgets/step.js +2 -0
- package/apps/web/dist/assets/AboutDialog-CTuI-l3p.js +1 -0
- package/apps/web/dist/assets/{ChangelogDialog-BCS3WIRA.js → ChangelogDialog-jQk_bQpP.js} +1 -1
- package/apps/web/dist/assets/ChatView-9TLi0B7K.js +10 -0
- package/apps/web/dist/assets/EditorPage-BVrzOfF3.js +3 -0
- package/apps/web/dist/assets/{FlowPreview-D2haAZwo.js → FlowPreview-DA0sHtr7.js} +1 -1
- package/apps/web/dist/assets/{Harness-CGI6ZMTA.js → Harness-DmYkwU4x.js} +2 -2
- package/apps/web/dist/assets/{NewWizardChannels-C4b_phHw.js → NewWizardChannels-BLS9sKhT.js} +1 -1
- package/apps/web/dist/assets/NewWizardPage-DWYZYiBF.js +7 -0
- package/apps/web/dist/assets/{OnboardingPage-DgySdBgo.js → OnboardingPage-CREvCCdX.js} +1 -1
- package/apps/web/dist/assets/ProjectPage-BQkd55xR.js +1 -0
- package/apps/web/dist/assets/PublicRunner-sOlOC0zD.js +1 -0
- package/apps/web/dist/assets/{ReadOnly-B-4l3bVt.js → ReadOnly-b7TjjRAk.js} +1 -1
- package/apps/web/dist/assets/RunDrawer-BPIVaan7.js +4 -0
- package/apps/web/dist/assets/{RunnerView-BZ83HUnO.js → RunnerView-CLfZzbKn.js} +1 -1
- package/apps/web/dist/assets/SettingsPage-BO6TPmoj.js +1 -0
- package/apps/web/dist/assets/SetupPage-DlJHBYWH.js +1 -0
- package/apps/web/dist/assets/SharePage-B21QI97K.js +1 -0
- package/apps/web/dist/assets/changelog-DeSoCsai.js +1 -0
- package/apps/web/dist/assets/{esm-BqbWrbsa.js → esm-DqRvG-PU.js} +1 -1
- package/apps/web/dist/assets/index-BhVJwBz7.css +1 -0
- package/apps/web/dist/assets/index-CAUeJXCg.js +14 -0
- package/apps/web/dist/assets/{lock-BkN0Fai6.js → lock-D_KjsGfU.js} +1 -1
- package/apps/web/dist/assets/{phone-Dip_wSUZ.js → phone-YRxkPQk6.js} +1 -1
- package/apps/web/dist/assets/{settings-menu-B7eXkvba.js → settings-menu-CQgehwEX.js} +1 -1
- package/apps/web/dist/assets/ui-DDUCc_4v.js +137 -0
- package/apps/web/dist/assets/{upload-DPQ4pwnl.js → upload-DDo14e2p.js} +1 -1
- package/apps/web/dist/index.html +3 -3
- package/apps/web/dist/mcp/flow.html +58 -55
- package/node_modules/@engenty-wizards/plugin-sdk/dist/index.d.ts +5 -2
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +187 -3
- package/node_modules/@engenty-wizards/shared/dist/definition.js +89 -18
- package/node_modules/@engenty-wizards/shared/dist/json-schema.js +4 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +11 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.js +54 -0
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +22 -0
- package/node_modules/@engenty-wizards/shared/dist/run.js +3 -0
- package/node_modules/@engenty-wizards/shared/dist/runner-texts.d.ts +41 -1
- package/node_modules/@engenty-wizards/shared/dist/runner-texts.js +40 -0
- package/node_modules/@engenty-wizards/shared/dist/runners.js +1 -1
- package/node_modules/@engenty-wizards/shared/dist/shape.d.ts +102 -0
- package/node_modules/@engenty-wizards/shared/dist/shape.js +318 -0
- package/node_modules/@engenty-wizards/shared/dist/surface.d.ts +33 -7
- package/node_modules/@engenty-wizards/shared/dist/surface.js +263 -20
- package/package.json +1 -1
- package/apps/web/dist/assets/AboutDialog-CkE7uZNL.js +0 -1
- package/apps/web/dist/assets/ChatView-DClGzMte.js +0 -10
- package/apps/web/dist/assets/EditorPage-Dg27sIZ7.js +0 -3
- package/apps/web/dist/assets/NewWizardPage-DhDXrjKX.js +0 -7
- package/apps/web/dist/assets/ProjectPage-dvtJn_3c.js +0 -1
- package/apps/web/dist/assets/PublicRunner-lP4sVF1q.js +0 -1
- package/apps/web/dist/assets/RunDrawer-B2bsB9Z6.js +0 -4
- package/apps/web/dist/assets/SettingsPage-C0_wAa_j.js +0 -1
- package/apps/web/dist/assets/SetupPage-kuvBk0G0.js +0 -1
- package/apps/web/dist/assets/SharePage-COLVxXMI.js +0 -1
- package/apps/web/dist/assets/changelog-CIYdRaa5.js +0 -1
- package/apps/web/dist/assets/index-Da9Pvzpj.js +0 -14
- package/apps/web/dist/assets/index-DaKjB676.css +0 -1
- package/apps/web/dist/assets/ui-B9AOCT0v.js +0 -134
|
@@ -23,6 +23,11 @@ export interface StepOutput {
|
|
|
23
23
|
/** A view from the surface catalog and the data it is bound to. */
|
|
24
24
|
surface?: Surface;
|
|
25
25
|
assets?: AssetRef[];
|
|
26
|
+
/**
|
|
27
|
+
* What the person did in the step's view, kept with the run: a checklist's ticks by component
|
|
28
|
+
* id, a widget's own state (`wizard.save`).
|
|
29
|
+
*/
|
|
30
|
+
view?: Record<string, unknown>;
|
|
26
31
|
/** Widgets: what loading it with this run's data showed. */
|
|
27
32
|
widget?: {
|
|
28
33
|
/** Seconds of animation the widget registered for video export; null = a still widget. */
|
|
@@ -177,6 +182,7 @@ declare const NOTES_EN: {
|
|
|
177
182
|
};
|
|
178
183
|
readonly videoRendering: "Rendering the video – this usually takes 1–3 minutes.";
|
|
179
184
|
readonly photo: "Looking at photo {n} of {total}";
|
|
185
|
+
readonly found: "{label}: {name}";
|
|
180
186
|
readonly listen: "Listening to “{label}”…";
|
|
181
187
|
readonly listenFailed: "“{label}” couldn’t be transcribed.";
|
|
182
188
|
readonly filmCutting: "Cutting the film – this takes about a minute.";
|
|
@@ -584,6 +590,22 @@ export interface BrandView {
|
|
|
584
590
|
name: string;
|
|
585
591
|
accent: string | null;
|
|
586
592
|
logoUrl: string | null;
|
|
593
|
+
/** How the pages look: the run page sets its tokens from it. */
|
|
594
|
+
theme: RunTheme;
|
|
595
|
+
}
|
|
596
|
+
/** How a wizard's pages look, taken from its space's brand (`runTheme`). Null: the runner's own. */
|
|
597
|
+
export interface RunTheme {
|
|
598
|
+
/** Buttons and marks. */
|
|
599
|
+
accent: string | null;
|
|
600
|
+
/** What is there, done or good to know: ticks, tips, group headings. */
|
|
601
|
+
second: string | null;
|
|
602
|
+
/** The ground the pages stand on; its hue tints paper and ink. */
|
|
603
|
+
background: string | null;
|
|
604
|
+
/** Typefaces as CSS names them. */
|
|
605
|
+
display: string | null;
|
|
606
|
+
body: string | null;
|
|
607
|
+
/** The brand stands on a dark ground. */
|
|
608
|
+
dark: boolean;
|
|
587
609
|
}
|
|
588
610
|
export interface PublicWizard {
|
|
589
611
|
token: string;
|
|
@@ -20,6 +20,8 @@ const NOTES_EN = {
|
|
|
20
20
|
},
|
|
21
21
|
videoRendering: "Rendering the video – this usually takes 1–3 minutes.",
|
|
22
22
|
photo: "Looking at photo {n} of {total}",
|
|
23
|
+
// An entry a step has just written, while it writes the rest: "Rezepte: Shakshuka mit Feta".
|
|
24
|
+
found: "{label}: {name}",
|
|
23
25
|
listen: "Listening to “{label}”…",
|
|
24
26
|
listenFailed: "“{label}” couldn’t be transcribed.",
|
|
25
27
|
filmCutting: "Cutting the film – this takes about a minute.",
|
|
@@ -308,6 +310,7 @@ const NOTES_DE = {
|
|
|
308
310
|
},
|
|
309
311
|
videoRendering: "Das Video wird gerendert – das dauert meist 1–3 Minuten.",
|
|
310
312
|
photo: "Sieht sich Foto {n} von {total} an",
|
|
313
|
+
found: "{label}: {name}",
|
|
311
314
|
listen: "Hört „{label}“ an …",
|
|
312
315
|
listenFailed: "„{label}“ konnte nicht verschriftlicht werden.",
|
|
313
316
|
filmCutting: "Der Film wird geschnitten – das dauert etwa eine Minute.",
|
|
@@ -8,6 +8,26 @@ declare const en: {
|
|
|
8
8
|
"translation.showTranslated": string;
|
|
9
9
|
"translation.failed": string;
|
|
10
10
|
"share.copied": string;
|
|
11
|
+
"surface.done": string;
|
|
12
|
+
"surface.allDone": string;
|
|
13
|
+
"surface.close": string;
|
|
14
|
+
"data.view": string;
|
|
15
|
+
"data.edit": string;
|
|
16
|
+
"data.json": string;
|
|
17
|
+
"data.modes": string;
|
|
18
|
+
"data.add": string;
|
|
19
|
+
"data.remove": string;
|
|
20
|
+
"data.missing": string;
|
|
21
|
+
"data.yes": string;
|
|
22
|
+
"data.no": string;
|
|
23
|
+
"data.menu": string;
|
|
24
|
+
"data.open": string;
|
|
25
|
+
"data.close": string;
|
|
26
|
+
"data.save": string;
|
|
27
|
+
"data.saved": string;
|
|
28
|
+
"data.schema": string;
|
|
29
|
+
"data.copy": string;
|
|
30
|
+
"data.download": string;
|
|
11
31
|
"run.closed": string;
|
|
12
32
|
"slot.later": string;
|
|
13
33
|
"slot.none": string;
|
|
@@ -261,6 +281,26 @@ export declare const RUNNER_TEXTS: {
|
|
|
261
281
|
"translation.showTranslated": string;
|
|
262
282
|
"translation.failed": string;
|
|
263
283
|
"share.copied": string;
|
|
284
|
+
"surface.done": string;
|
|
285
|
+
"surface.allDone": string;
|
|
286
|
+
"surface.close": string;
|
|
287
|
+
"data.view": string;
|
|
288
|
+
"data.edit": string;
|
|
289
|
+
"data.json": string;
|
|
290
|
+
"data.modes": string;
|
|
291
|
+
"data.add": string;
|
|
292
|
+
"data.remove": string;
|
|
293
|
+
"data.missing": string;
|
|
294
|
+
"data.yes": string;
|
|
295
|
+
"data.no": string;
|
|
296
|
+
"data.menu": string;
|
|
297
|
+
"data.open": string;
|
|
298
|
+
"data.close": string;
|
|
299
|
+
"data.save": string;
|
|
300
|
+
"data.saved": string;
|
|
301
|
+
"data.schema": string;
|
|
302
|
+
"data.copy": string;
|
|
303
|
+
"data.download": string;
|
|
264
304
|
"run.closed": string;
|
|
265
305
|
"slot.later": string;
|
|
266
306
|
"slot.none": string;
|
|
@@ -501,6 +541,6 @@ export declare const RUNNER_TEXTS: {
|
|
|
501
541
|
"video.you": string;
|
|
502
542
|
"nav.language": string;
|
|
503
543
|
};
|
|
504
|
-
de: Record<"ai.generated" | "ai.modified" | "ai.publish" | "ask.allow" | "ask.allowRest" | "ask.deny" | "ask.done" | "ask.down" | "ask.hide" | "ask.pictureHint" | "ask.private" | "ask.remember" | "ask.signIn" | "ask.skip" | "ask.typeInto" | "ask.up" | "ask.wants" | "browser.driving" | "browser.handBack" | "browser.takeOver" | "camera.asking" | "camera.clipHint" | "camera.denied" | "camera.done" | "camera.flip" | "camera.hint" | "camera.hintMany" | "camera.native" | "camera.photo" | "camera.pick" | "camera.record" | "camera.recording" | "camera.shoot" | "camera.stop" | "camera.taken" | "camera.title" | "camera.torch" | "camera.video" | "cards.open" | "cards.pick" | "cards.picked" | "chat.above" | "chat.badEmail" | "chat.badNumber" | "chat.badUrl" | "chat.change" | "chat.checked" | "chat.connected" | "chat.dictate" | "chat.dictateStop" | "chat.file" | "chat.files" | "chat.listening" | "chat.micDenied" | "chat.placeholder" | "chat.readAloud" | "chat.readAloudOff" | "chat.required" | "chat.rows" | "chat.send" | "chat.skip" | "chat.skipped" | "chat.transcribing" | "chat.voice" | "check.noFile" | "check.noPreview" | "check.onlyOpen" | "check.progress" | "check.reopen" | "common.cancel" | "common.close" | "common.offline" | "common.retry" | "connect.connected" | "connect.disconnect" | "connect.go" | "connect.none" | "connect.openLink" | "install.add" | "install.app" | "install.bookmark" | "install.bookmark.mac" | "install.bookmark.pc" | "install.ios" | "list.delete" | "list.empty" | "location.accuracy" | "location.denied" | "location.label" | "location.locating" | "location.manual" | "location.map" | "location.refining" | "location.remove" | "location.use" | "location.why" | "nav.language" | "notify.ask" | "notify.blocked" | "notify.done" | "notify.needsYou" | "notify.next" | "notify.on" | "notify.onHint" | "notify.quiet" | "notify.why" | "output.preview" | "package.badJson" | "package.newer" | "package.noManifest" | "package.unreadable" | "result.translate" | "run.accept" | "run.addRow" | "run.again" | "run.awake" | "run.back" | "run.camera" | "run.cancelled" | "run.closed" | "run.cls.account" | "run.cls.audio" | "run.cls.image" | "run.cls.speech" | "run.cls.text" | "run.cls.video" | "run.copy" | "run.edit" | "run.elapsed" | "run.failed" | "run.feedbackPick" | "run.feedbackPlaceholder" | "run.feedbackTarget" | "run.filesMin" | "run.filesOf" | "run.madeWith" | "run.maxFiles" | "run.moveFile" | "run.moveFileHint" | "run.net" | "run.next" | "run.no" | "run.notFound" | "run.openFull" | "run.orAs" | "run.pickItem" | "run.pickedItem" | "run.print" | "run.regenerateAll" | "run.regenerateGo" | "run.regeneratePicked" | "run.removeFile" | "run.resume" | "run.retry" | "run.share" | "run.shareFile" | "run.shareNow" | "run.start" | "run.total" | "run.unavailable" | "run.upload" | "run.uploadMore" | "run.uploadTouch" | "run.uploading" | "run.vat" | "run.working" | "run.yes" | "scan.asking" | "scan.button" | "scan.fromPhoto" | "scan.hint" | "scan.hintQr" | "scan.miss" | "scan.title" | "scan.typeInstead" | "share.copied" | "sign.accept" | "sign.again" | "sign.clear" | "sign.hint" | "sign.open" | "sign.pad" | "sign.remove" | "slot.later" | "slot.none" | "store.confirmDelete" | "store.delete" | "store.empty" | "store.explain" | "store.files" | "store.open" | "store.rows" | "store.signIns" | "store.title" | "talk.camera" | "talk.cameraDenied" | "talk.cameraOff" | "talk.connecting" | "talk.failed" | "talk.hint" | "talk.joinHint" | "talk.listening" | "talk.live" | "talk.speaking" | "talk.start" | "talk.stop" | "translation.failed" | "translation.machine" | "translation.showOriginal" | "translation.showTranslated" | "translation.working" | "video.cameraOff" | "video.ended" | "video.flip" | "video.hint" | "video.join" | "video.leave" | "video.mute" | "video.rejoin" | "video.unmute" | "video.you" | "voice.again" | "voice.denied" | "voice.left" | "voice.noRecorder" | "voice.pick" | "voice.recording" | "voice.remove" | "voice.start" | "voice.stop" | "voice.why", Line>;
|
|
544
|
+
de: Record<"ai.generated" | "ai.modified" | "ai.publish" | "ask.allow" | "ask.allowRest" | "ask.deny" | "ask.done" | "ask.down" | "ask.hide" | "ask.pictureHint" | "ask.private" | "ask.remember" | "ask.signIn" | "ask.skip" | "ask.typeInto" | "ask.up" | "ask.wants" | "browser.driving" | "browser.handBack" | "browser.takeOver" | "camera.asking" | "camera.clipHint" | "camera.denied" | "camera.done" | "camera.flip" | "camera.hint" | "camera.hintMany" | "camera.native" | "camera.photo" | "camera.pick" | "camera.record" | "camera.recording" | "camera.shoot" | "camera.stop" | "camera.taken" | "camera.title" | "camera.torch" | "camera.video" | "cards.open" | "cards.pick" | "cards.picked" | "chat.above" | "chat.badEmail" | "chat.badNumber" | "chat.badUrl" | "chat.change" | "chat.checked" | "chat.connected" | "chat.dictate" | "chat.dictateStop" | "chat.file" | "chat.files" | "chat.listening" | "chat.micDenied" | "chat.placeholder" | "chat.readAloud" | "chat.readAloudOff" | "chat.required" | "chat.rows" | "chat.send" | "chat.skip" | "chat.skipped" | "chat.transcribing" | "chat.voice" | "check.noFile" | "check.noPreview" | "check.onlyOpen" | "check.progress" | "check.reopen" | "common.cancel" | "common.close" | "common.offline" | "common.retry" | "connect.connected" | "connect.disconnect" | "connect.go" | "connect.none" | "connect.openLink" | "data.add" | "data.close" | "data.copy" | "data.download" | "data.edit" | "data.json" | "data.menu" | "data.missing" | "data.modes" | "data.no" | "data.open" | "data.remove" | "data.save" | "data.saved" | "data.schema" | "data.view" | "data.yes" | "install.add" | "install.app" | "install.bookmark" | "install.bookmark.mac" | "install.bookmark.pc" | "install.ios" | "list.delete" | "list.empty" | "location.accuracy" | "location.denied" | "location.label" | "location.locating" | "location.manual" | "location.map" | "location.refining" | "location.remove" | "location.use" | "location.why" | "nav.language" | "notify.ask" | "notify.blocked" | "notify.done" | "notify.needsYou" | "notify.next" | "notify.on" | "notify.onHint" | "notify.quiet" | "notify.why" | "output.preview" | "package.badJson" | "package.newer" | "package.noManifest" | "package.unreadable" | "result.translate" | "run.accept" | "run.addRow" | "run.again" | "run.awake" | "run.back" | "run.camera" | "run.cancelled" | "run.closed" | "run.cls.account" | "run.cls.audio" | "run.cls.image" | "run.cls.speech" | "run.cls.text" | "run.cls.video" | "run.copy" | "run.edit" | "run.elapsed" | "run.failed" | "run.feedbackPick" | "run.feedbackPlaceholder" | "run.feedbackTarget" | "run.filesMin" | "run.filesOf" | "run.madeWith" | "run.maxFiles" | "run.moveFile" | "run.moveFileHint" | "run.net" | "run.next" | "run.no" | "run.notFound" | "run.openFull" | "run.orAs" | "run.pickItem" | "run.pickedItem" | "run.print" | "run.regenerateAll" | "run.regenerateGo" | "run.regeneratePicked" | "run.removeFile" | "run.resume" | "run.retry" | "run.share" | "run.shareFile" | "run.shareNow" | "run.start" | "run.total" | "run.unavailable" | "run.upload" | "run.uploadMore" | "run.uploadTouch" | "run.uploading" | "run.vat" | "run.working" | "run.yes" | "scan.asking" | "scan.button" | "scan.fromPhoto" | "scan.hint" | "scan.hintQr" | "scan.miss" | "scan.title" | "scan.typeInstead" | "share.copied" | "sign.accept" | "sign.again" | "sign.clear" | "sign.hint" | "sign.open" | "sign.pad" | "sign.remove" | "slot.later" | "slot.none" | "store.confirmDelete" | "store.delete" | "store.empty" | "store.explain" | "store.files" | "store.open" | "store.rows" | "store.signIns" | "store.title" | "surface.allDone" | "surface.close" | "surface.done" | "talk.camera" | "talk.cameraDenied" | "talk.cameraOff" | "talk.connecting" | "talk.failed" | "talk.hint" | "talk.joinHint" | "talk.listening" | "talk.live" | "talk.speaking" | "talk.start" | "talk.stop" | "translation.failed" | "translation.machine" | "translation.showOriginal" | "translation.showTranslated" | "translation.working" | "video.cameraOff" | "video.ended" | "video.flip" | "video.hint" | "video.join" | "video.leave" | "video.mute" | "video.rejoin" | "video.unmute" | "video.you" | "voice.again" | "voice.denied" | "voice.left" | "voice.noRecorder" | "voice.pick" | "voice.recording" | "voice.remove" | "voice.start" | "voice.stop" | "voice.why", Line>;
|
|
505
545
|
};
|
|
506
546
|
export {};
|
|
@@ -12,6 +12,26 @@ const de = {
|
|
|
12
12
|
"translation.showTranslated": "Übersetzung zeigen",
|
|
13
13
|
"translation.failed": "Dieser Wizard lässt sich gerade nicht übersetzen.",
|
|
14
14
|
"share.copied": "Kopiert",
|
|
15
|
+
"surface.done": "{done} von {total}",
|
|
16
|
+
"surface.allDone": "Alles erledigt",
|
|
17
|
+
"surface.close": "Schließen",
|
|
18
|
+
"data.view": "Ansicht",
|
|
19
|
+
"data.edit": "Bearbeiten",
|
|
20
|
+
"data.json": "JSON",
|
|
21
|
+
"data.modes": "Daten zeigen als",
|
|
22
|
+
"data.add": "{what} hinzufügen",
|
|
23
|
+
"data.remove": "Entfernen",
|
|
24
|
+
"data.missing": "{id} fehlt",
|
|
25
|
+
"data.yes": "Ja",
|
|
26
|
+
"data.no": "Nein",
|
|
27
|
+
"data.menu": "Mehr",
|
|
28
|
+
"data.open": "Daten ansehen und bearbeiten",
|
|
29
|
+
"data.close": "Daten schließen",
|
|
30
|
+
"data.save": "Speichern",
|
|
31
|
+
"data.saved": "Gespeichert",
|
|
32
|
+
"data.schema": "JSON Schema",
|
|
33
|
+
"data.copy": "Kopieren",
|
|
34
|
+
"data.download": "Herunterladen",
|
|
15
35
|
"run.closed": "Hier gerade nicht verfügbar, dafür fehlt: {list}",
|
|
16
36
|
"slot.later": "Spätere Termine zeigen",
|
|
17
37
|
"slot.none": "Gerade sind keine Termine frei.",
|
|
@@ -248,6 +268,26 @@ const en = {
|
|
|
248
268
|
"translation.showTranslated": "Show translation",
|
|
249
269
|
"translation.failed": "This wizard can’t be translated right now.",
|
|
250
270
|
"share.copied": "Copied",
|
|
271
|
+
"surface.done": "{done} of {total}",
|
|
272
|
+
"surface.allDone": "All done",
|
|
273
|
+
"surface.close": "Close",
|
|
274
|
+
"data.view": "View",
|
|
275
|
+
"data.edit": "Edit",
|
|
276
|
+
"data.json": "JSON",
|
|
277
|
+
"data.modes": "Show the data as",
|
|
278
|
+
"data.add": "Add {what}",
|
|
279
|
+
"data.remove": "Remove",
|
|
280
|
+
"data.missing": "{id} is missing",
|
|
281
|
+
"data.yes": "Yes",
|
|
282
|
+
"data.no": "No",
|
|
283
|
+
"data.menu": "More",
|
|
284
|
+
"data.open": "View and edit the data",
|
|
285
|
+
"data.close": "Close the data",
|
|
286
|
+
"data.save": "Save",
|
|
287
|
+
"data.saved": "Saved",
|
|
288
|
+
"data.schema": "JSON Schema",
|
|
289
|
+
"data.copy": "Copy",
|
|
290
|
+
"data.download": "Download",
|
|
251
291
|
"run.closed": "Not available here right now. It needs {list}.",
|
|
252
292
|
"slot.later": "Show later times",
|
|
253
293
|
"slot.none": "No times are free right now.",
|
|
@@ -68,7 +68,7 @@ export const DEFAULT_RUNNER_SETTINGS = {
|
|
|
68
68
|
export function outputKindOf(step) {
|
|
69
69
|
switch (step.type) {
|
|
70
70
|
case "agent":
|
|
71
|
-
return step.output?.fields?.length ? "data" : "text";
|
|
71
|
+
return step.output?.fields?.length || step.output?.schema ? "data" : "text";
|
|
72
72
|
case "generate":
|
|
73
73
|
return step.asset;
|
|
74
74
|
case "widget":
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* What an agent step's json output holds, in the kinds wizards already use: `output.schema`.
|
|
4
|
+
* Text, numbers, yes/no, a choice, an entry of another list by its id (`ref`), and lists — of
|
|
5
|
+
* these or of objects (entries) with lists of their own — every one with a label. The runtime builds the model's JSON Schema from
|
|
6
|
+
* it, the admin shows and edits the data by it, downloads and views read it.
|
|
7
|
+
*
|
|
8
|
+
* `output.fields`, the short form, compiles to the same shape (`shapeOfFields`); two kinds exist
|
|
9
|
+
* only there: `score` (a decision's levels) and `table` (rows of text or numbers, columns named
|
|
10
|
+
* or picked by the model).
|
|
11
|
+
*/
|
|
12
|
+
/** Objects nest at most this deep, the step's own object counted. */
|
|
13
|
+
export declare const MAX_SHAPE_DEPTH = 4;
|
|
14
|
+
/** At most this many keys in all objects together. */
|
|
15
|
+
export declare const MAX_SHAPE_KEYS = 100;
|
|
16
|
+
interface Named {
|
|
17
|
+
/** What people read: a column's head, a part's title, "add …". Without one, the key. */
|
|
18
|
+
label?: string;
|
|
19
|
+
/** What the model is to put there. */
|
|
20
|
+
description?: string;
|
|
21
|
+
}
|
|
22
|
+
export type TextShape = Named & {
|
|
23
|
+
kind: "text";
|
|
24
|
+
long?: boolean;
|
|
25
|
+
};
|
|
26
|
+
export type NumberShape = Named & {
|
|
27
|
+
kind: "number";
|
|
28
|
+
unit?: string;
|
|
29
|
+
};
|
|
30
|
+
export type YesNoShape = Named & {
|
|
31
|
+
kind: "yesno";
|
|
32
|
+
yes?: string;
|
|
33
|
+
no?: string;
|
|
34
|
+
};
|
|
35
|
+
export type ChoiceShape = Named & {
|
|
36
|
+
kind: "choice";
|
|
37
|
+
options: string[];
|
|
38
|
+
};
|
|
39
|
+
/** An entry of a top-level list of the same output, by its `id`. */
|
|
40
|
+
export type RefShape = Named & {
|
|
41
|
+
kind: "ref";
|
|
42
|
+
to: string;
|
|
43
|
+
};
|
|
44
|
+
export type LeafShape = TextShape | NumberShape | YesNoShape | ChoiceShape | RefShape;
|
|
45
|
+
export type ListShape = Named & {
|
|
46
|
+
kind: "list";
|
|
47
|
+
of: LeafShape | ObjectShape;
|
|
48
|
+
};
|
|
49
|
+
export interface ObjectShape extends Named {
|
|
50
|
+
kind: "object";
|
|
51
|
+
fields: Record<string, FieldShape>;
|
|
52
|
+
}
|
|
53
|
+
/** `fields` only: a decision's levels, lowest first. */
|
|
54
|
+
export type ScoreShape = Named & {
|
|
55
|
+
kind: "score";
|
|
56
|
+
options: string[];
|
|
57
|
+
};
|
|
58
|
+
/** `fields` only: rows of text or numbers; without columns the model picks them. */
|
|
59
|
+
export type TableShape = Named & {
|
|
60
|
+
kind: "table";
|
|
61
|
+
columns?: string[];
|
|
62
|
+
};
|
|
63
|
+
export type FieldShape = LeafShape | ListShape | ScoreShape | TableShape;
|
|
64
|
+
/** `output.schema`: what the step returns. */
|
|
65
|
+
export declare const outputShapeSchema: z.ZodType<ObjectShape, unknown, z.core.$ZodTypeInternals<ObjectShape, unknown>>;
|
|
66
|
+
/** An output field as `output.fields` writes it. */
|
|
67
|
+
interface ShortField {
|
|
68
|
+
id: string;
|
|
69
|
+
kind: string;
|
|
70
|
+
description?: string;
|
|
71
|
+
columns?: string[];
|
|
72
|
+
options?: string[];
|
|
73
|
+
}
|
|
74
|
+
/** The short form as a shape: a field per key, a list of text, a table as it is. */
|
|
75
|
+
export declare function shapeOfFields(fields: ShortField[]): ObjectShape;
|
|
76
|
+
/** The key of an entry's title: its first short text other than its `id`. */
|
|
77
|
+
export declare function titleKey(shape: ObjectShape): string | undefined;
|
|
78
|
+
/** What people read for a key. */
|
|
79
|
+
export declare function labelOf(key: string, shape: FieldShape | ListShape["of"]): string;
|
|
80
|
+
/** The data a shape describes, as Zod: what a model's answer is checked against. */
|
|
81
|
+
export declare function shapeZod(shape: FieldShape | ListShape["of"], k?: string): z.ZodType;
|
|
82
|
+
/**
|
|
83
|
+
* The data as JSON Schema (draft 2020-12): every key required, no other keys — what a model's
|
|
84
|
+
* structured output takes, and what developers read.
|
|
85
|
+
*/
|
|
86
|
+
export declare function shapeJsonSchema(shape: ObjectShape, title?: string): Record<string, unknown>;
|
|
87
|
+
/**
|
|
88
|
+
* Data a model wrote itself, fitted to the shape before it is checked: a number where text
|
|
89
|
+
* belongs as text, a number written as text as a number; in a table a cell's list joined and a
|
|
90
|
+
* missing column empty. Lists stay lists everywhere else.
|
|
91
|
+
*/
|
|
92
|
+
export declare function fitData(shape: FieldShape | ListShape["of"], value: unknown): unknown;
|
|
93
|
+
/** Tables with free columns come back as columns + rows and are stored as records. */
|
|
94
|
+
export declare function recordsOf(shape: FieldShape | ListShape["of"], value: unknown): unknown;
|
|
95
|
+
/** The shape as lines of paths a model binds to: "/recipes[]/title (text): Titel". */
|
|
96
|
+
export declare function shapeLines(shape: ObjectShape, at?: string): string[];
|
|
97
|
+
/**
|
|
98
|
+
* The shape at a path: keys of objects, an index (digits) into a list. `any` where the data can
|
|
99
|
+
* hold anything (a table's row), undefined where the shape has nothing.
|
|
100
|
+
*/
|
|
101
|
+
export declare function shapeAt(shape: FieldShape | ListShape["of"], path: string[]): FieldShape | ListShape["of"] | "any" | undefined;
|
|
102
|
+
export {};
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// SPDX-License-Identifier: FSL-1.1-MIT OR MIT
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
/**
|
|
4
|
+
* What an agent step's json output holds, in the kinds wizards already use: `output.schema`.
|
|
5
|
+
* Text, numbers, yes/no, a choice, an entry of another list by its id (`ref`), and lists — of
|
|
6
|
+
* these or of objects (entries) with lists of their own — every one with a label. The runtime builds the model's JSON Schema from
|
|
7
|
+
* it, the admin shows and edits the data by it, downloads and views read it.
|
|
8
|
+
*
|
|
9
|
+
* `output.fields`, the short form, compiles to the same shape (`shapeOfFields`); two kinds exist
|
|
10
|
+
* only there: `score` (a decision's levels) and `table` (rows of text or numbers, columns named
|
|
11
|
+
* or picked by the model).
|
|
12
|
+
*/
|
|
13
|
+
/** Objects nest at most this deep, the step's own object counted. */
|
|
14
|
+
export const MAX_SHAPE_DEPTH = 4;
|
|
15
|
+
/** At most this many keys in all objects together. */
|
|
16
|
+
export const MAX_SHAPE_KEYS = 100;
|
|
17
|
+
const key = z
|
|
18
|
+
.string()
|
|
19
|
+
.regex(/^[a-zA-Z][a-zA-Z0-9_]{0,40}$/, "keys are letters, digits and _ and start with a letter");
|
|
20
|
+
const named = {
|
|
21
|
+
label: z.string().min(1).optional(),
|
|
22
|
+
description: z.string().optional(),
|
|
23
|
+
};
|
|
24
|
+
const leaves = [
|
|
25
|
+
z.object({ kind: z.literal("text"), ...named, long: z.boolean().optional() }),
|
|
26
|
+
z.object({ kind: z.literal("number"), ...named, unit: z.string().optional() }),
|
|
27
|
+
z.object({
|
|
28
|
+
kind: z.literal("yesno"),
|
|
29
|
+
...named,
|
|
30
|
+
yes: z.string().optional(),
|
|
31
|
+
no: z.string().optional(),
|
|
32
|
+
}),
|
|
33
|
+
z.object({
|
|
34
|
+
kind: z.literal("choice"),
|
|
35
|
+
...named,
|
|
36
|
+
options: z.array(z.string().min(1)).min(2).max(40),
|
|
37
|
+
}),
|
|
38
|
+
z.object({ kind: z.literal("ref"), ...named, to: key }),
|
|
39
|
+
];
|
|
40
|
+
const LEAF_KINDS = "text, number, yesno, choice, ref";
|
|
41
|
+
/**
|
|
42
|
+
* The shape of an object `depth` deep, built level by level instead of recursively: JSON Schema
|
|
43
|
+
* of the definition takes no $ref, and the depth is part of the schema. Objects are the entries
|
|
44
|
+
* of lists (and the step's own); a field holding one object would only add a level.
|
|
45
|
+
*/
|
|
46
|
+
function objectAt(depth) {
|
|
47
|
+
const deeper = depth < MAX_SHAPE_DEPTH ? objectAt(depth + 1) : undefined;
|
|
48
|
+
const of = deeper
|
|
49
|
+
? z.discriminatedUnion("kind", [...leaves, deeper], {
|
|
50
|
+
error: `A list holds ${LEAF_KINDS} or object.`,
|
|
51
|
+
})
|
|
52
|
+
: z.discriminatedUnion("kind", [...leaves], {
|
|
53
|
+
error: `A list this deep holds ${LEAF_KINDS}: objects nest at most ${MAX_SHAPE_DEPTH} deep.`,
|
|
54
|
+
});
|
|
55
|
+
const list = z.object({ kind: z.literal("list"), ...named, of });
|
|
56
|
+
const field = z.discriminatedUnion("kind", [...leaves, list], {
|
|
57
|
+
error: `"kind" is one of ${LEAF_KINDS}, list.`,
|
|
58
|
+
});
|
|
59
|
+
return z.object({
|
|
60
|
+
kind: z.literal("object"),
|
|
61
|
+
...named,
|
|
62
|
+
fields: z
|
|
63
|
+
.record(key, field)
|
|
64
|
+
.refine((f) => Object.keys(f).length > 0, { message: "An object has fields." }),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/** Every object of a shape with where it sits, the shape's own first. */
|
|
68
|
+
function objectsOf(shape, at = []) {
|
|
69
|
+
const out = [{ shape, at }];
|
|
70
|
+
for (const [k, f] of Object.entries(shape.fields)) {
|
|
71
|
+
if (f.kind === "list" && f.of.kind === "object") {
|
|
72
|
+
out.push(...objectsOf(f.of, [...at, "fields", k, "of"]));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
/** The list a `ref` points into: a top-level list of objects with an `id`. Null when it is fine. */
|
|
78
|
+
function refProblem(root, to) {
|
|
79
|
+
const target = root.fields[to];
|
|
80
|
+
if (target?.kind !== "list" || target.of.kind !== "object") {
|
|
81
|
+
return `"ref" to "${to}": that is no list of objects at the top of the schema.`;
|
|
82
|
+
}
|
|
83
|
+
return target.of.fields.id?.kind === "text"
|
|
84
|
+
? null
|
|
85
|
+
: `"ref" to "${to}": its entries need an "id" text field to be pointed at.`;
|
|
86
|
+
}
|
|
87
|
+
/** `output.schema`: what the step returns. */
|
|
88
|
+
export const outputShapeSchema = objectAt(1).superRefine((shape, ctx) => {
|
|
89
|
+
const objects = objectsOf(shape);
|
|
90
|
+
const keys = objects.reduce((n, o) => n + Object.keys(o.shape.fields).length, 0);
|
|
91
|
+
if (keys > MAX_SHAPE_KEYS) {
|
|
92
|
+
ctx.addIssue({
|
|
93
|
+
code: "custom",
|
|
94
|
+
message: `A schema has at most ${MAX_SHAPE_KEYS} keys in all; this one has ${keys}.`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
for (const o of objects) {
|
|
98
|
+
for (const [k, f] of Object.entries(o.shape.fields)) {
|
|
99
|
+
const ref = f.kind === "ref" ? f : f.kind === "list" && f.of.kind === "ref" ? f.of : null;
|
|
100
|
+
const problem = ref ? refProblem(shape, ref.to) : null;
|
|
101
|
+
if (problem) {
|
|
102
|
+
ctx.addIssue({ code: "custom", message: problem, path: [...o.at, "fields", k] });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
/** The short form as a shape: a field per key, a list of text, a table as it is. */
|
|
108
|
+
export function shapeOfFields(fields) {
|
|
109
|
+
const one = (f) => {
|
|
110
|
+
const d = f.description ? { description: f.description } : {};
|
|
111
|
+
switch (f.kind) {
|
|
112
|
+
case "number":
|
|
113
|
+
return { kind: "number", ...d };
|
|
114
|
+
case "list":
|
|
115
|
+
return { kind: "list", ...d, of: { kind: "text" } };
|
|
116
|
+
case "yesno":
|
|
117
|
+
return { kind: "yesno", ...d };
|
|
118
|
+
case "choice":
|
|
119
|
+
return { kind: "choice", ...d, options: f.options ?? [] };
|
|
120
|
+
case "score":
|
|
121
|
+
return { kind: "score", ...d, options: f.options ?? [] };
|
|
122
|
+
case "table":
|
|
123
|
+
return { kind: "table", ...d, ...(f.columns?.length ? { columns: f.columns } : {}) };
|
|
124
|
+
default:
|
|
125
|
+
return { kind: "text", ...d };
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
return { kind: "object", fields: Object.fromEntries(fields.map((f) => [f.id, one(f)])) };
|
|
129
|
+
}
|
|
130
|
+
/** The key of an entry's title: its first short text other than its `id`. */
|
|
131
|
+
export function titleKey(shape) {
|
|
132
|
+
return Object.entries(shape.fields).find(([k, f]) => k !== "id" && f.kind === "text" && !f.long)?.[0];
|
|
133
|
+
}
|
|
134
|
+
/** What people read for a key. */
|
|
135
|
+
export function labelOf(key, shape) {
|
|
136
|
+
return shape.label ?? key;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* What the model reads about a value beside its title: its description (without label: its key),
|
|
140
|
+
* its unit, a score's order, where a ref points. Undefined where the title says it all.
|
|
141
|
+
*/
|
|
142
|
+
function about(k, s) {
|
|
143
|
+
const base = s.description ?? (s.label ? "" : k);
|
|
144
|
+
const more = s.kind === "number" && s.unit
|
|
145
|
+
? `in ${s.unit}`
|
|
146
|
+
: s.kind === "score"
|
|
147
|
+
? "lowest to highest"
|
|
148
|
+
: s.kind === "ref"
|
|
149
|
+
? `the id of an entry of "${s.to}"`
|
|
150
|
+
: "";
|
|
151
|
+
return (base && more ? `${base} (${more})` : base || more) || undefined;
|
|
152
|
+
}
|
|
153
|
+
const CELL = z.union([z.string(), z.number(), z.null()]);
|
|
154
|
+
/** The data a shape describes, as Zod: what a model's answer is checked against. */
|
|
155
|
+
export function shapeZod(shape, k = "data") {
|
|
156
|
+
const description = about(k, shape);
|
|
157
|
+
const meta = {
|
|
158
|
+
...(shape.label ? { title: shape.label } : {}),
|
|
159
|
+
...(description ? { description } : {}),
|
|
160
|
+
};
|
|
161
|
+
switch (shape.kind) {
|
|
162
|
+
case "text":
|
|
163
|
+
case "ref":
|
|
164
|
+
return z.string().meta(meta);
|
|
165
|
+
case "number":
|
|
166
|
+
return z.number().meta(meta);
|
|
167
|
+
case "yesno":
|
|
168
|
+
return z.boolean().meta(meta);
|
|
169
|
+
case "choice":
|
|
170
|
+
case "score":
|
|
171
|
+
return z.enum(shape.options).meta(meta);
|
|
172
|
+
case "list":
|
|
173
|
+
return z.array(shapeZod(shape.of, k)).meta(meta);
|
|
174
|
+
case "object":
|
|
175
|
+
return z
|
|
176
|
+
.object(Object.fromEntries(Object.entries(shape.fields).map(([n, f]) => [n, shapeZod(f, n)])))
|
|
177
|
+
.meta(meta);
|
|
178
|
+
case "table":
|
|
179
|
+
return (shape.columns?.length
|
|
180
|
+
? z.array(z.object(Object.fromEntries(shape.columns.map((c) => [c, CELL]))))
|
|
181
|
+
: // Free columns come as columns + rows, which a strict schema takes; stored as records.
|
|
182
|
+
z.object({ columns: z.array(z.string()), rows: z.array(z.array(CELL)) })).meta(meta);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The data as JSON Schema (draft 2020-12): every key required, no other keys — what a model's
|
|
187
|
+
* structured output takes, and what developers read.
|
|
188
|
+
*/
|
|
189
|
+
export function shapeJsonSchema(shape, title) {
|
|
190
|
+
const schema = z.toJSONSchema(shapeZod(shape), { target: "draft-2020-12" });
|
|
191
|
+
return title && !shape.label ? { ...schema, title } : schema;
|
|
192
|
+
}
|
|
193
|
+
/** A table cell as a table takes it: a list joined by spaces, anything else as text. */
|
|
194
|
+
function fitCell(value) {
|
|
195
|
+
if (value === null || value === undefined) {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
201
|
+
if (Array.isArray(value) && value.every((v) => typeof v === "string" || typeof v === "number")) {
|
|
202
|
+
return value.join(" ");
|
|
203
|
+
}
|
|
204
|
+
return typeof value === "boolean" ? String(value) : JSON.stringify(value);
|
|
205
|
+
}
|
|
206
|
+
const isRecord = (v) => Boolean(v) && typeof v === "object" && !Array.isArray(v);
|
|
207
|
+
/**
|
|
208
|
+
* Data a model wrote itself, fitted to the shape before it is checked: a number where text
|
|
209
|
+
* belongs as text, a number written as text as a number; in a table a cell's list joined and a
|
|
210
|
+
* missing column empty. Lists stay lists everywhere else.
|
|
211
|
+
*/
|
|
212
|
+
export function fitData(shape, value) {
|
|
213
|
+
switch (shape.kind) {
|
|
214
|
+
case "text":
|
|
215
|
+
case "ref":
|
|
216
|
+
return typeof value === "number" ? String(value) : value;
|
|
217
|
+
case "number":
|
|
218
|
+
return typeof value === "string" && value.trim() && Number.isFinite(Number(value))
|
|
219
|
+
? Number(value)
|
|
220
|
+
: value;
|
|
221
|
+
case "list":
|
|
222
|
+
return Array.isArray(value) ? value.map((v) => fitData(shape.of, v)) : value;
|
|
223
|
+
case "object":
|
|
224
|
+
if (!isRecord(value)) {
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
...value,
|
|
229
|
+
...Object.fromEntries(Object.entries(shape.fields)
|
|
230
|
+
.filter(([k]) => k in value)
|
|
231
|
+
.map(([k, f]) => [k, fitData(f, value[k])])),
|
|
232
|
+
};
|
|
233
|
+
case "table": {
|
|
234
|
+
const columns = shape.columns;
|
|
235
|
+
return columns?.length && Array.isArray(value)
|
|
236
|
+
? value.map((row) => isRecord(row) ? Object.fromEntries(columns.map((c) => [c, fitCell(row[c])])) : row)
|
|
237
|
+
: value;
|
|
238
|
+
}
|
|
239
|
+
default:
|
|
240
|
+
return value;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/** Tables with free columns come back as columns + rows and are stored as records. */
|
|
244
|
+
export function recordsOf(shape, value) {
|
|
245
|
+
if (shape.kind === "table" && !shape.columns?.length && isRecord(value)) {
|
|
246
|
+
const t = value;
|
|
247
|
+
if (t.columns && t.rows) {
|
|
248
|
+
const columns = t.columns;
|
|
249
|
+
return t.rows.map((row) => Object.fromEntries(columns.map((c, i) => [c, row[i] ?? null])));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (shape.kind === "object" && isRecord(value)) {
|
|
253
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => {
|
|
254
|
+
const f = shape.fields[k];
|
|
255
|
+
return [k, f ? recordsOf(f, v) : v];
|
|
256
|
+
}));
|
|
257
|
+
}
|
|
258
|
+
if (shape.kind === "list" && Array.isArray(value)) {
|
|
259
|
+
return value.map((v) => recordsOf(shape.of, v));
|
|
260
|
+
}
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
263
|
+
/** What a value of a shape is, in a word or two: "list of Rezept", "choice: a | b". */
|
|
264
|
+
function kindLine(s) {
|
|
265
|
+
switch (s.kind) {
|
|
266
|
+
case "choice":
|
|
267
|
+
case "score":
|
|
268
|
+
return `${s.kind}: ${s.options.join(" | ")}`;
|
|
269
|
+
case "ref":
|
|
270
|
+
return `ref → /${s.to}[].id`;
|
|
271
|
+
case "table":
|
|
272
|
+
return s.columns?.length ? `table: ${s.columns.join(", ")}` : "table";
|
|
273
|
+
case "list":
|
|
274
|
+
return s.of.kind === "object" ? "list of entries" : `list of ${s.of.kind}`;
|
|
275
|
+
default:
|
|
276
|
+
return s.kind;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/** The shape as lines of paths a model binds to: "/recipes[]/title (text): Titel". */
|
|
280
|
+
export function shapeLines(shape, at = "") {
|
|
281
|
+
const out = [];
|
|
282
|
+
for (const [k, f] of Object.entries(shape.fields)) {
|
|
283
|
+
const path = `${at}/${k}`;
|
|
284
|
+
const words = [f.label, f.description].filter(Boolean).join(" — ");
|
|
285
|
+
out.push(`- ${path} (${kindLine(f)})${words ? `: ${words}` : ""}`);
|
|
286
|
+
if (f.kind === "list" && f.of.kind === "object") {
|
|
287
|
+
out.push(...shapeLines(f.of, `${path}[]`));
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return out;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* The shape at a path: keys of objects, an index (digits) into a list. `any` where the data can
|
|
294
|
+
* hold anything (a table's row), undefined where the shape has nothing.
|
|
295
|
+
*/
|
|
296
|
+
export function shapeAt(shape, path) {
|
|
297
|
+
let at = shape;
|
|
298
|
+
for (const part of path) {
|
|
299
|
+
if (at.kind === "object") {
|
|
300
|
+
const next = at.fields[part];
|
|
301
|
+
if (!next) {
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
304
|
+
at = next;
|
|
305
|
+
}
|
|
306
|
+
else if (at.kind === "list" && /^\d+$/.test(part)) {
|
|
307
|
+
at = at.of;
|
|
308
|
+
}
|
|
309
|
+
else if (at.kind === "table") {
|
|
310
|
+
// A row, and in it whatever columns the model wrote.
|
|
311
|
+
return /^\d+$/.test(part) ? "any" : undefined;
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
return undefined;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return at;
|
|
318
|
+
}
|