wizards 0.2.25 → 0.2.26
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/agents/architect.js +5 -0
- package/apps/runtime/dist/authoring/guide.js +55 -6
- package/apps/runtime/dist/connectors/builtin.js +17 -1
- package/apps/runtime/dist/connectors/index.js +43 -14
- package/apps/runtime/dist/connectors/via-account.js +63 -0
- package/apps/runtime/dist/credits/estimate.js +12 -2
- package/apps/runtime/dist/engine/decide.js +82 -0
- package/apps/runtime/dist/engine/input.js +12 -5
- package/apps/runtime/dist/engine/requirements.js +80 -15
- package/apps/runtime/dist/engine/runner.js +252 -6
- package/apps/runtime/dist/engine/steps.js +206 -18
- package/apps/runtime/dist/engine/surface.js +90 -0
- package/apps/runtime/dist/render/downloads.js +7 -0
- package/apps/runtime/dist/routes/runs.js +18 -5
- package/apps/runtime/dist/services/relevance.js +17 -49
- package/apps/runtime/dist/tools/pages.js +31 -6
- package/apps/web/dist/assets/{Account-CpxsROpe.js → Account-Cnksw5Ea.js} +1 -1
- package/apps/web/dist/assets/{ChangelogDialog-B7C0r9dF.js → ChangelogDialog-CdtQWq0m.js} +1 -1
- package/apps/web/dist/assets/{CreditsDialog-Com2Y3g8.js → CreditsDialog-Ti43boCN.js} +1 -1
- package/apps/web/dist/assets/EditorPage-N7EzlRSj.js +3 -0
- package/apps/web/dist/assets/{FlowPreview-D0m5Qk6S.js → FlowPreview-IZcNrBJi.js} +1 -1
- package/apps/web/dist/assets/{Harness-bIL3pHB_.js → Harness-Cj-k02TA.js} +1 -1
- package/apps/web/dist/assets/{HomePage-DPE_Z9HB.js → HomePage-bjdKkt84.js} +1 -1
- package/apps/web/dist/assets/{ImportWizard-Dz2uKznp.js → ImportWizard-DagFXYPM.js} +1 -1
- package/apps/web/dist/assets/{LocalRuntime-wMKUVZOu.js → LocalRuntime-Boi_zqQJ.js} +1 -1
- package/apps/web/dist/assets/NewWizardPage-DO5J8OpZ.js +2 -0
- package/apps/web/dist/assets/ProjectPage-DdG9wYsd.js +2 -0
- package/apps/web/dist/assets/{PublicRunner-BsrqVK0J.js → PublicRunner-B4ts9rT6.js} +1 -1
- package/apps/web/dist/assets/RunDrawer-LnVfCiYO.js +4 -0
- package/apps/web/dist/assets/RunnerView-DwffK7AA.js +70 -0
- package/apps/web/dist/assets/{SettingsPage-BkuUm-bf.js → SettingsPage-Cyx_jTlp.js} +1 -1
- package/apps/web/dist/assets/{SetupPage-CoyXpcvS.js → SetupPage-CBDD4rpC.js} +1 -1
- package/apps/web/dist/assets/{SharePage-BuR-he1L.js → SharePage-70niRKaO.js} +1 -1
- package/apps/web/dist/assets/{api-rJBQEu1G.js → api-C4M6cbpW.js} +1 -1
- package/apps/web/dist/assets/changelog-COyL1B5X.js +1 -0
- package/apps/web/dist/assets/definition-DoCLtZZL.js +55 -0
- package/apps/web/dist/assets/{esm-BrGYYg2h.js → esm-CfW8Rmtq.js} +1 -1
- package/apps/web/dist/assets/index-CJ0YJ5HC.css +1 -0
- package/apps/web/dist/assets/{index-Co5QOA9_.js → index-DMKXnSf-.js} +3 -3
- package/apps/web/dist/assets/meta-6jZygxJq.js +1 -0
- package/apps/web/dist/assets/{settings-menu-CCNUb0Fd.js → settings-menu-Dph4yXbO.js} +1 -1
- package/apps/web/dist/assets/{table-2-BdDuBcF2.js → star-UHmZXpjA.js} +1 -1
- package/apps/web/dist/assets/ui-BS348L4M.js +1 -0
- package/apps/web/dist/assets/wand-sparkles-IEd4jkyn.js +1 -0
- package/apps/web/dist/index.html +4 -4
- package/apps/web/dist/mcp/flow.html +60 -60
- package/node_modules/@engenty-wizards/shared/dist/decision.d.ts +76 -0
- package/node_modules/@engenty-wizards/shared/dist/decision.js +147 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +1048 -50
- package/node_modules/@engenty-wizards/shared/dist/definition.js +516 -43
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +19 -2
- package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +1 -1
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +55 -0
- package/node_modules/@engenty-wizards/shared/dist/run.js +3 -0
- package/node_modules/@engenty-wizards/shared/dist/surface.d.ts +89 -0
- package/node_modules/@engenty-wizards/shared/dist/surface.js +198 -0
- package/package.json +1 -1
- package/apps/web/dist/assets/EditorPage-BCagWjrz.js +0 -2
- package/apps/web/dist/assets/NewWizardPage-BOxU2jWq.js +0 -2
- package/apps/web/dist/assets/ProjectPage-BJp2wqIk.js +0 -5
- package/apps/web/dist/assets/RunDrawer-CLfJ0W_V.js +0 -1
- package/apps/web/dist/assets/RunnerView-BXMELFC4.js +0 -70
- package/apps/web/dist/assets/changelog-Bmw6dg-T.js +0 -1
- package/apps/web/dist/assets/definition-BznNdlhf.js +0 -55
- package/apps/web/dist/assets/index-BZs1IxAk.css +0 -1
- package/apps/web/dist/assets/meta-B3jzXP27.js +0 -1
- package/apps/web/dist/assets/ui-DJOZSSKo.js +0 -1
- package/apps/web/dist/assets/wand-sparkles-Clj3VRB5.js +0 -1
|
@@ -1,7 +1,18 @@
|
|
|
1
|
-
import { allFields, listRef } from "./definition.js";
|
|
1
|
+
import { allFields, conditionsOf, listRef, } from "./definition.js";
|
|
2
2
|
/** When a branch is taken, in words: the field's label stands for its id. */
|
|
3
3
|
function condition(definition, rule, lang) {
|
|
4
|
-
|
|
4
|
+
if (rule.ask) {
|
|
5
|
+
return rule.ask;
|
|
6
|
+
}
|
|
7
|
+
if (rule.when === undefined) {
|
|
8
|
+
return lang === "de" ? "sonst" : "otherwise";
|
|
9
|
+
}
|
|
10
|
+
return conditionsOf(rule.when)
|
|
11
|
+
.map((c) => conditionText(definition, c, lang))
|
|
12
|
+
.join(lang === "de" ? " und " : " and ");
|
|
13
|
+
}
|
|
14
|
+
function conditionText(definition, c, lang) {
|
|
15
|
+
const { field, op, value } = c;
|
|
5
16
|
const name = allFields(definition).find((f) => f.id === field)?.label ?? field.split(".").pop() ?? field;
|
|
6
17
|
const de = lang === "de";
|
|
7
18
|
// A toggle is often labelled "Mit …" already.
|
|
@@ -24,6 +35,12 @@ function condition(definition, rule, lang) {
|
|
|
24
35
|
return withIt;
|
|
25
36
|
case "empty":
|
|
26
37
|
return without;
|
|
38
|
+
case "gt":
|
|
39
|
+
return `${name} > ${shown}`;
|
|
40
|
+
case "lt":
|
|
41
|
+
return `${name} < ${shown}`;
|
|
42
|
+
case "contains":
|
|
43
|
+
return de ? `${name} enthält ${shown}` : `${name} contains ${shown}`;
|
|
27
44
|
}
|
|
28
45
|
}
|
|
29
46
|
export const outlineOf = (definition, lang) => definition.steps.map((s) => ({
|
|
@@ -199,7 +199,7 @@ export type CostTier = keyof typeof COST_TIERS;
|
|
|
199
199
|
export declare const ITEM_FORMAT_IDS: ("text" | "image" | "audio" | "video" | "document" | "dashboard" | "table")[];
|
|
200
200
|
export declare const INDUSTRY_IDS: ("any" | "trades" | "gastronomy" | "hospitality" | "realEstate" | "retail" | "health" | "professional" | "agency" | "events" | "publicSector")[];
|
|
201
201
|
export declare const USE_CASE_IDS: ("marketing" | "sales" | "accounting" | "service" | "research" | "hr" | "operations" | "website" | "visuals" | "inquiries" | "advice")[];
|
|
202
|
-
export declare const CAPABILITY_IDS: ("
|
|
202
|
+
export declare const CAPABILITY_IDS: ("code" | "text" | "image" | "browser" | "video" | "speech" | "film" | "web" | "documents" | "listening" | "connectors")[];
|
|
203
203
|
/** The formats a run hands over, read from what the result step offers. */
|
|
204
204
|
export declare function formatsOf(def: WizardDefinition): ItemFormat[];
|
|
205
205
|
/** The capabilities a definition needs, read from its steps. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Format, ModelClass, Step, WizardDefinition } from "./definition.js";
|
|
2
2
|
import type { ConnectionView, ListDef, ListRow } from "./store.js";
|
|
3
|
+
import type { Surface } from "./surface.js";
|
|
3
4
|
export type RunStatus = "waiting_input" | "running" | "done" | "failed" | "cancelled";
|
|
4
5
|
export interface AssetRef {
|
|
5
6
|
id: string;
|
|
@@ -16,6 +17,10 @@ export interface AssetRef {
|
|
|
16
17
|
export interface StepOutput {
|
|
17
18
|
text?: string;
|
|
18
19
|
json?: unknown;
|
|
20
|
+
/** A decision step: how probable each answer is (1 or 0 where a language model answered). */
|
|
21
|
+
decided?: Record<string, number>;
|
|
22
|
+
/** A view from the surface catalog and the data it is bound to. */
|
|
23
|
+
surface?: Surface;
|
|
19
24
|
assets?: AssetRef[];
|
|
20
25
|
/** Widgets: what loading it with this run's data showed. */
|
|
21
26
|
widget?: {
|
|
@@ -34,12 +39,39 @@ export interface RunState {
|
|
|
34
39
|
notes: Record<string, string>;
|
|
35
40
|
/** Steps that make several results: the entries (from 0) to make again; the others stay. */
|
|
36
41
|
redo?: Record<string, number[]>;
|
|
42
|
+
/** The step whose `ask` branches are being decided; the run waits on that decision. */
|
|
43
|
+
deciding?: string;
|
|
44
|
+
/** What decided `ask` branches answered, by step. */
|
|
45
|
+
decisions?: Record<string, BranchDecision>;
|
|
46
|
+
/** Pages whose fields a decision chose: the field ids kept, by page. */
|
|
47
|
+
pages?: Record<string, {
|
|
48
|
+
shown: string[];
|
|
49
|
+
}>;
|
|
50
|
+
/** How often each branch was taken, by "<step>→<goto>": loops stop at the rule's max. */
|
|
51
|
+
loops?: Record<string, number>;
|
|
37
52
|
/** File fields with `listen`: what is said in the recording, piece by piece with its times. */
|
|
38
53
|
heard?: Record<string, {
|
|
39
54
|
start: number;
|
|
40
55
|
end: number;
|
|
41
56
|
text: string;
|
|
42
57
|
}[]>;
|
|
58
|
+
/**
|
|
59
|
+
* A test run's own copy of the wizard's memory: the pages it wrote, by title. The wizard's
|
|
60
|
+
* pages stay as they are; the copy goes with the run.
|
|
61
|
+
*/
|
|
62
|
+
memory?: Record<string, string>;
|
|
63
|
+
}
|
|
64
|
+
/** What a decision over a step's `ask` branches found. */
|
|
65
|
+
export interface BranchDecision {
|
|
66
|
+
/** The index of the rule in the step's `next`, or null: none of them, go on. */
|
|
67
|
+
rule: number | null;
|
|
68
|
+
source: "systemone" | "llm";
|
|
69
|
+
probabilities?: Record<string, number>;
|
|
70
|
+
at: string;
|
|
71
|
+
}
|
|
72
|
+
/** A branch decision as the studio sees it: with the step's branches it was made over. */
|
|
73
|
+
export interface ShownDecision extends BranchDecision {
|
|
74
|
+
over: NonNullable<Step["next"]>;
|
|
43
75
|
}
|
|
44
76
|
interface AskBase {
|
|
45
77
|
id: string;
|
|
@@ -123,6 +155,18 @@ export interface ShownList {
|
|
|
123
155
|
* line into the run's log, a runner shows it in the language the person reads.
|
|
124
156
|
*/
|
|
125
157
|
export declare const RUN_NOTES: {
|
|
158
|
+
readonly entry: {
|
|
159
|
+
readonly de: "Eintrag {n} von {total}";
|
|
160
|
+
readonly en: "Entry {n} of {total}";
|
|
161
|
+
};
|
|
162
|
+
readonly deciding: {
|
|
163
|
+
readonly de: "Entscheidet, wie es weitergeht …";
|
|
164
|
+
readonly en: "Deciding how to go on …";
|
|
165
|
+
};
|
|
166
|
+
readonly choosingFields: {
|
|
167
|
+
readonly de: "Wählt die passenden Fragen …";
|
|
168
|
+
readonly en: "Choosing the right questions …";
|
|
169
|
+
};
|
|
126
170
|
readonly batchDone: {
|
|
127
171
|
readonly de: "{done} von {total} fertig";
|
|
128
172
|
readonly en: "{done} of {total} done";
|
|
@@ -310,6 +354,17 @@ export interface RunView {
|
|
|
310
354
|
values: Record<string, unknown>;
|
|
311
355
|
/** What fields of the current page start with, from earlier steps (`prefill`). */
|
|
312
356
|
prefill: Record<string, unknown>;
|
|
357
|
+
/** What the `when` of the current page's fields read from before the page, by reference. */
|
|
358
|
+
known?: Record<string, unknown>;
|
|
359
|
+
/** Choices of the current page's fields that come from earlier data (`optionsFrom`). */
|
|
360
|
+
options?: Record<string, string[]>;
|
|
361
|
+
/** The decided fields (`ask`, `group`) of the current page that a decision kept. */
|
|
362
|
+
decidedFields?: string[];
|
|
363
|
+
/**
|
|
364
|
+
* What decided branches answered so far, by step, with the branches decided over (test runs:
|
|
365
|
+
* the studio shows them while the draft's branches are still those).
|
|
366
|
+
*/
|
|
367
|
+
decisions?: Record<string, ShownDecision>;
|
|
313
368
|
/** Choices of the current page that lead to a step without a model here, by field id. */
|
|
314
369
|
closed: Record<string, ClosedChoice>;
|
|
315
370
|
outputs: Record<string, StepOutput>;
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* line into the run's log, a runner shows it in the language the person reads.
|
|
4
4
|
*/
|
|
5
5
|
export const RUN_NOTES = {
|
|
6
|
+
entry: { de: "Eintrag {n} von {total}", en: "Entry {n} of {total}" },
|
|
7
|
+
deciding: { de: "Entscheidet, wie es weitergeht …", en: "Deciding how to go on …" },
|
|
8
|
+
choosingFields: { de: "Wählt die passenden Fragen …", en: "Choosing the right questions …" },
|
|
6
9
|
batchDone: { de: "{done} von {total} fertig", en: "{done} of {total} done" },
|
|
7
10
|
editing: { de: "Bearbeitet {n} von {total}", en: "Editing {n} of {total}" },
|
|
8
11
|
filming: { de: "Dreht {n} von {total}", en: "Filming {n} of {total}" },
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Surfaces: views composed from a small catalog of native components, bound to a run's data and
|
|
4
|
+
* rendered by the runner itself — themed, on phones, no iframe and nothing to pay per run. A
|
|
5
|
+
* surface is data, not code: only catalog components render, so it needs no sandbox. Between the
|
|
6
|
+
* plain table a review shows and a widget, which is free HTML. As engenty-pro's A2UI catalog
|
|
7
|
+
* (`engenty:core/v1`), kept to what a wizard's result needs.
|
|
8
|
+
*
|
|
9
|
+
* A surface is a flat list of components with ids; the one with id "root" is drawn, containers
|
|
10
|
+
* name their children by id. A prop may be a literal or `{ "path": "/json/pointer" }` into the
|
|
11
|
+
* surface's data. A `List` or `Table` repeats over an array: inside a repeated row, a path
|
|
12
|
+
* without a leading slash reads the row ("name"), one with a slash the whole data.
|
|
13
|
+
*/
|
|
14
|
+
export declare const SURFACE_COMPONENTS: readonly ["Column", "Text", "List", "Row", "DetailGrid", "Badge", "Grid", "Metric", "Table", "Image", "BarChart", "LineChart", "DonutChart", "Actions", "Button"];
|
|
15
|
+
export type SurfaceComponentName = (typeof SURFACE_COMPONENTS)[number];
|
|
16
|
+
export declare const SURFACE_ACTIONS: readonly ["regenerate", "open"];
|
|
17
|
+
/** One component: its id, its kind, its children (ids, or a template repeated over an array). */
|
|
18
|
+
export declare const surfaceComponentSchema: z.ZodObject<{
|
|
19
|
+
id: z.ZodString;
|
|
20
|
+
component: z.ZodEnum<{
|
|
21
|
+
Column: "Column";
|
|
22
|
+
Text: "Text";
|
|
23
|
+
List: "List";
|
|
24
|
+
Row: "Row";
|
|
25
|
+
DetailGrid: "DetailGrid";
|
|
26
|
+
Badge: "Badge";
|
|
27
|
+
Grid: "Grid";
|
|
28
|
+
Metric: "Metric";
|
|
29
|
+
Table: "Table";
|
|
30
|
+
Image: "Image";
|
|
31
|
+
BarChart: "BarChart";
|
|
32
|
+
LineChart: "LineChart";
|
|
33
|
+
DonutChart: "DonutChart";
|
|
34
|
+
Actions: "Actions";
|
|
35
|
+
Button: "Button";
|
|
36
|
+
}>;
|
|
37
|
+
children: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodObject<{
|
|
38
|
+
path: z.ZodString;
|
|
39
|
+
componentId: z.ZodString;
|
|
40
|
+
}, z.core.$strip>]>>;
|
|
41
|
+
}, z.core.$loose>;
|
|
42
|
+
export type SurfaceComponent = z.infer<typeof surfaceComponentSchema>;
|
|
43
|
+
export declare const surfaceComponentsSchema: z.ZodArray<z.ZodObject<{
|
|
44
|
+
id: z.ZodString;
|
|
45
|
+
component: z.ZodEnum<{
|
|
46
|
+
Column: "Column";
|
|
47
|
+
Text: "Text";
|
|
48
|
+
List: "List";
|
|
49
|
+
Row: "Row";
|
|
50
|
+
DetailGrid: "DetailGrid";
|
|
51
|
+
Badge: "Badge";
|
|
52
|
+
Grid: "Grid";
|
|
53
|
+
Metric: "Metric";
|
|
54
|
+
Table: "Table";
|
|
55
|
+
Image: "Image";
|
|
56
|
+
BarChart: "BarChart";
|
|
57
|
+
LineChart: "LineChart";
|
|
58
|
+
DonutChart: "DonutChart";
|
|
59
|
+
Actions: "Actions";
|
|
60
|
+
Button: "Button";
|
|
61
|
+
}>;
|
|
62
|
+
children: z.ZodOptional<z.ZodUnion<readonly [z.ZodArray<z.ZodString>, z.ZodObject<{
|
|
63
|
+
path: z.ZodString;
|
|
64
|
+
componentId: z.ZodString;
|
|
65
|
+
}, z.core.$strip>]>>;
|
|
66
|
+
}, z.core.$loose>>;
|
|
67
|
+
/** What a run shows: the components and the data they are bound to. */
|
|
68
|
+
export interface Surface {
|
|
69
|
+
components: SurfaceComponent[];
|
|
70
|
+
data: Record<string, unknown>;
|
|
71
|
+
}
|
|
72
|
+
export interface SurfaceIssue {
|
|
73
|
+
message: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* What can be wrong with a surface: an unknown or missing component, no root, a child that is
|
|
77
|
+
* not there, an id twice, a loop, a prop missing, and — given the data keys a run will provide —
|
|
78
|
+
* an absolute path into data nothing provides.
|
|
79
|
+
*/
|
|
80
|
+
export declare function validateSurface(raw: unknown, dataKeys?: string[]): SurfaceIssue[];
|
|
81
|
+
/** A JSON pointer into data: "/suppliers/0/name". */
|
|
82
|
+
export declare function pointer(data: unknown, path: string): unknown;
|
|
83
|
+
/**
|
|
84
|
+
* A prop's value: a literal as it is, a path read from the data — or, without a leading slash,
|
|
85
|
+
* from the row a List or Table repeats over.
|
|
86
|
+
*/
|
|
87
|
+
export declare function bound(value: unknown, data: unknown, row?: unknown): unknown;
|
|
88
|
+
/** What a model needs to compose a surface: the catalog, briefly. */
|
|
89
|
+
export declare const SURFACE_GUIDE = "A surface is a flat JSON array of components; the one with id \"root\" is drawn. Containers list their children by id.\nComponents and props (a prop may be a literal or {\"path\": \"/key/...\"} into the data):\n- Column { children: [ids] } \u2014 vertical stack (use for root)\n- Text { text, variant?: \"h3\"|\"h4\"|\"body\"|\"muted\" }\n- List { children: {\"path\": \"/rows\", \"componentId\": \"<a Row id>\"} } \u2014 one Row per array entry; inside, paths without \"/\" read the entry (\"name\")\n- Row { title, subtitle?, meta?, badge?, image? }\n- DetailGrid { rows: [{ label, value }] } \u2014 label/value facts\n- Badge { label, tone?: \"default\"|\"success\"|\"warning\" }\n- Grid { children: [ids], columns?: 2|3|4 } \u2014 tiles side by side\n- Metric { label, value, caption? } \u2014 a key figure\n- Table { rows: {\"path\": \"/rows\"}, columns?: [{ key, label }] }\n- Image { src: an asset id or {\"path\": ...}, alt? }\n- BarChart / LineChart { title?, points: [{ label, value }] or {\"path\": ...}, labelKey?, valueKey? }\n- DonutChart { title?, slices: [{ label, value }] or {\"path\": ...}, labelKey?, valueKey? }\n (labelKey / valueKey: the columns of bound rows that hold the label and the number, e.g. \"name\", \"price\")\n- Actions { children: [Button ids] } / Button { label, action: \"regenerate\"|\"open\" }\nKeep it small: what the person needs to see first, then the details. Never invent numbers: bind them by path.";
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Surfaces: views composed from a small catalog of native components, bound to a run's data and
|
|
4
|
+
* rendered by the runner itself — themed, on phones, no iframe and nothing to pay per run. A
|
|
5
|
+
* surface is data, not code: only catalog components render, so it needs no sandbox. Between the
|
|
6
|
+
* plain table a review shows and a widget, which is free HTML. As engenty-pro's A2UI catalog
|
|
7
|
+
* (`engenty:core/v1`), kept to what a wizard's result needs.
|
|
8
|
+
*
|
|
9
|
+
* A surface is a flat list of components with ids; the one with id "root" is drawn, containers
|
|
10
|
+
* name their children by id. A prop may be a literal or `{ "path": "/json/pointer" }` into the
|
|
11
|
+
* surface's data. A `List` or `Table` repeats over an array: inside a repeated row, a path
|
|
12
|
+
* without a leading slash reads the row ("name"), one with a slash the whole data.
|
|
13
|
+
*/
|
|
14
|
+
export const SURFACE_COMPONENTS = [
|
|
15
|
+
"Column",
|
|
16
|
+
"Text",
|
|
17
|
+
"List",
|
|
18
|
+
"Row",
|
|
19
|
+
"DetailGrid",
|
|
20
|
+
"Badge",
|
|
21
|
+
"Grid",
|
|
22
|
+
"Metric",
|
|
23
|
+
"Table",
|
|
24
|
+
"Image",
|
|
25
|
+
"BarChart",
|
|
26
|
+
"LineChart",
|
|
27
|
+
"DonutChart",
|
|
28
|
+
"Actions",
|
|
29
|
+
"Button",
|
|
30
|
+
];
|
|
31
|
+
/** Components that hold others by id. */
|
|
32
|
+
const CONTAINERS = new Set(["Column", "List", "Grid", "Actions"]);
|
|
33
|
+
/** The props each component needs. */
|
|
34
|
+
const REQUIRED = {
|
|
35
|
+
Text: ["text"],
|
|
36
|
+
Row: ["title"],
|
|
37
|
+
DetailGrid: ["rows"],
|
|
38
|
+
Badge: ["label"],
|
|
39
|
+
Metric: ["label", "value"],
|
|
40
|
+
Table: ["rows"],
|
|
41
|
+
Image: ["src"],
|
|
42
|
+
BarChart: ["points"],
|
|
43
|
+
LineChart: ["points"],
|
|
44
|
+
DonutChart: ["slices"],
|
|
45
|
+
Button: ["label", "action"],
|
|
46
|
+
};
|
|
47
|
+
export const SURFACE_ACTIONS = ["regenerate", "open"];
|
|
48
|
+
const id = z.string().regex(/^[a-zA-Z][a-zA-Z0-9_-]{0,40}$/, "ids are letters, digits, _ and -");
|
|
49
|
+
/** One component: its id, its kind, its children (ids, or a template repeated over an array). */
|
|
50
|
+
export const surfaceComponentSchema = z.looseObject({
|
|
51
|
+
id,
|
|
52
|
+
component: z.enum(SURFACE_COMPONENTS),
|
|
53
|
+
children: z
|
|
54
|
+
.union([z.array(z.string()), z.object({ path: z.string(), componentId: z.string() })])
|
|
55
|
+
.optional(),
|
|
56
|
+
});
|
|
57
|
+
export const surfaceComponentsSchema = z.array(surfaceComponentSchema).min(1).max(200);
|
|
58
|
+
const isPath = (v) => Boolean(v && typeof v === "object" && !Array.isArray(v) && typeof v.path === "string");
|
|
59
|
+
/** Every `{ path }` in a component's props, with whether it reads a repeated row. */
|
|
60
|
+
function pathsOf(c) {
|
|
61
|
+
const out = [];
|
|
62
|
+
const walk = (v) => {
|
|
63
|
+
if (isPath(v)) {
|
|
64
|
+
out.push(v.path);
|
|
65
|
+
}
|
|
66
|
+
else if (Array.isArray(v)) {
|
|
67
|
+
v.forEach(walk);
|
|
68
|
+
}
|
|
69
|
+
else if (v && typeof v === "object") {
|
|
70
|
+
Object.values(v).forEach(walk);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
for (const [key, value] of Object.entries(c)) {
|
|
74
|
+
if (key !== "id" && key !== "component") {
|
|
75
|
+
walk(key === "children" && !Array.isArray(value) && isPath(value) ? { path: value.path } : value);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* What can be wrong with a surface: an unknown or missing component, no root, a child that is
|
|
82
|
+
* not there, an id twice, a loop, a prop missing, and — given the data keys a run will provide —
|
|
83
|
+
* an absolute path into data nothing provides.
|
|
84
|
+
*/
|
|
85
|
+
export function validateSurface(raw, dataKeys) {
|
|
86
|
+
const parsed = surfaceComponentsSchema.safeParse(raw);
|
|
87
|
+
if (!parsed.success) {
|
|
88
|
+
return parsed.error.issues.map((i) => ({ message: `${i.path.join(".")}: ${i.message}` }));
|
|
89
|
+
}
|
|
90
|
+
const components = parsed.data;
|
|
91
|
+
const issues = [];
|
|
92
|
+
const byId = new Map();
|
|
93
|
+
for (const c of components) {
|
|
94
|
+
if (byId.has(c.id)) {
|
|
95
|
+
issues.push({ message: `Duplicate component id "${c.id}".` });
|
|
96
|
+
}
|
|
97
|
+
byId.set(c.id, c);
|
|
98
|
+
}
|
|
99
|
+
if (!byId.has("root")) {
|
|
100
|
+
issues.push({ message: 'A surface has a component with id "root".' });
|
|
101
|
+
}
|
|
102
|
+
for (const c of components) {
|
|
103
|
+
for (const prop of REQUIRED[c.component] ?? []) {
|
|
104
|
+
if (c[prop] === undefined) {
|
|
105
|
+
issues.push({ message: `${c.component} "${c.id}" needs "${prop}".` });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const action = c.action;
|
|
109
|
+
if (action !== undefined && !SURFACE_ACTIONS.includes(action)) {
|
|
110
|
+
issues.push({
|
|
111
|
+
message: `"${c.id}": action is one of ${SURFACE_ACTIONS.join(", ")}.`,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const children = c.children;
|
|
115
|
+
if (children && !CONTAINERS.has(c.component) && c.component !== "Table") {
|
|
116
|
+
issues.push({ message: `${c.component} "${c.id}" holds no children.` });
|
|
117
|
+
}
|
|
118
|
+
const ids = Array.isArray(children) ? children : children ? [children.componentId] : [];
|
|
119
|
+
for (const child of ids) {
|
|
120
|
+
if (!byId.has(child)) {
|
|
121
|
+
issues.push({ message: `"${c.id}" names a child "${child}" that is not there.` });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (dataKeys) {
|
|
125
|
+
for (const path of pathsOf(c)) {
|
|
126
|
+
const key = path.startsWith("/") ? path.split("/")[1] : null;
|
|
127
|
+
if (key !== null && !dataKeys.includes(key)) {
|
|
128
|
+
issues.push({ message: `"${c.id}" reads "${path}", which no data provides.` });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// A child that holds its parent would draw forever.
|
|
134
|
+
const seen = new Set();
|
|
135
|
+
const loops = (cid, stack) => {
|
|
136
|
+
if (stack.has(cid)) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
if (seen.has(cid)) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
seen.add(cid);
|
|
143
|
+
const c = byId.get(cid);
|
|
144
|
+
const kids = Array.isArray(c?.children)
|
|
145
|
+
? c.children
|
|
146
|
+
: c?.children
|
|
147
|
+
? [c.children.componentId]
|
|
148
|
+
: [];
|
|
149
|
+
const next = new Set(stack).add(cid);
|
|
150
|
+
return kids.some((k) => loops(k, next));
|
|
151
|
+
};
|
|
152
|
+
if (byId.has("root") && loops("root", new Set())) {
|
|
153
|
+
issues.push({ message: "Components hold each other in a loop." });
|
|
154
|
+
}
|
|
155
|
+
return issues;
|
|
156
|
+
}
|
|
157
|
+
/** A JSON pointer into data: "/suppliers/0/name". */
|
|
158
|
+
export function pointer(data, path) {
|
|
159
|
+
let at = data;
|
|
160
|
+
for (const part of path.split("/").slice(1)) {
|
|
161
|
+
if (at === null || typeof at !== "object") {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
at = at[part.replace(/~1/g, "/").replace(/~0/g, "~")];
|
|
165
|
+
}
|
|
166
|
+
return at;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* A prop's value: a literal as it is, a path read from the data — or, without a leading slash,
|
|
170
|
+
* from the row a List or Table repeats over.
|
|
171
|
+
*/
|
|
172
|
+
export function bound(value, data, row) {
|
|
173
|
+
if (!isPath(value)) {
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
176
|
+
if (value.path.startsWith("/")) {
|
|
177
|
+
return pointer(data, value.path);
|
|
178
|
+
}
|
|
179
|
+
return row === undefined ? undefined : pointer(row, `/${value.path.replace(/\./g, "/")}`);
|
|
180
|
+
}
|
|
181
|
+
/** What a model needs to compose a surface: the catalog, briefly. */
|
|
182
|
+
export const SURFACE_GUIDE = `A surface is a flat JSON array of components; the one with id "root" is drawn. Containers list their children by id.
|
|
183
|
+
Components and props (a prop may be a literal or {"path": "/key/..."} into the data):
|
|
184
|
+
- Column { children: [ids] } — vertical stack (use for root)
|
|
185
|
+
- Text { text, variant?: "h3"|"h4"|"body"|"muted" }
|
|
186
|
+
- List { children: {"path": "/rows", "componentId": "<a Row id>"} } — one Row per array entry; inside, paths without "/" read the entry ("name")
|
|
187
|
+
- Row { title, subtitle?, meta?, badge?, image? }
|
|
188
|
+
- DetailGrid { rows: [{ label, value }] } — label/value facts
|
|
189
|
+
- Badge { label, tone?: "default"|"success"|"warning" }
|
|
190
|
+
- Grid { children: [ids], columns?: 2|3|4 } — tiles side by side
|
|
191
|
+
- Metric { label, value, caption? } — a key figure
|
|
192
|
+
- Table { rows: {"path": "/rows"}, columns?: [{ key, label }] }
|
|
193
|
+
- Image { src: an asset id or {"path": ...}, alt? }
|
|
194
|
+
- BarChart / LineChart { title?, points: [{ label, value }] or {"path": ...}, labelKey?, valueKey? }
|
|
195
|
+
- DonutChart { title?, slices: [{ label, value }] or {"path": ...}, labelKey?, valueKey? }
|
|
196
|
+
(labelKey / valueKey: the columns of bound rows that hold the label and the number, e.g. "name", "price")
|
|
197
|
+
- Actions { children: [Button ids] } / Button { label, action: "regenerate"|"open" }
|
|
198
|
+
Keep it small: what the person needs to see first, then the details. Never invent numbers: bind them by path.`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wizards",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.26",
|
|
4
4
|
"description": "Make a wish. Get your wizard. It guides you step by step. Let AI do the work. Runs on your machine, on the AI subscription you already have.",
|
|
5
5
|
"license": "FSL-1.1-MIT",
|
|
6
6
|
"homepage": "https://engenty.ai",
|