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,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { tableColumnsSchema } from "./engenty/data-tables/index.js";
|
|
3
3
|
import { CONNECTION_KINDS } from "./store.js";
|
|
4
|
+
import { surfaceComponentsSchema, validateSurface } from "./surface.js";
|
|
4
5
|
/** The engenty avatars a wizard can wear. Same cast as engenty's ui-core. */
|
|
5
6
|
export const ENGENTY_KINDS = [
|
|
6
7
|
"round",
|
|
@@ -117,6 +118,27 @@ export const itemColumnSchema = z.object({
|
|
|
117
118
|
label: z.string().min(1),
|
|
118
119
|
kind: z.enum(["text", "number", "money"]),
|
|
119
120
|
});
|
|
121
|
+
export const CONDITION_OPS = [
|
|
122
|
+
"equals",
|
|
123
|
+
"notEquals",
|
|
124
|
+
"in",
|
|
125
|
+
"notEmpty",
|
|
126
|
+
"empty",
|
|
127
|
+
"gt",
|
|
128
|
+
"lt",
|
|
129
|
+
"contains",
|
|
130
|
+
];
|
|
131
|
+
/**
|
|
132
|
+
* One test on what the run knows: an answer by field id, an output of an agent step as
|
|
133
|
+
* "steps.<step>.<field>", or how many rows a stored list has as "lists.<list>.count".
|
|
134
|
+
*/
|
|
135
|
+
export const conditionSchema = z.object({
|
|
136
|
+
field: z.string(),
|
|
137
|
+
op: z.enum(CONDITION_OPS),
|
|
138
|
+
value: z.union([z.string(), z.number(), z.boolean(), z.array(z.string())]).optional(),
|
|
139
|
+
});
|
|
140
|
+
/** One condition, or several that must all hold. */
|
|
141
|
+
export const whenSchema = z.union([conditionSchema, z.array(conditionSchema).min(1)]);
|
|
120
142
|
export const fieldSchema = z.object({
|
|
121
143
|
id,
|
|
122
144
|
label: z.string().min(1),
|
|
@@ -157,15 +179,54 @@ export const fieldSchema = z.object({
|
|
|
157
179
|
* `items` field takes a table whose columns are its column ids; the person corrects it.
|
|
158
180
|
*/
|
|
159
181
|
prefill: z.string().optional(),
|
|
182
|
+
/**
|
|
183
|
+
* The field is shown only while this holds. It reads an answer of this page (decided as the
|
|
184
|
+
* person types) or anything known before the page. A hidden field is not asked and not kept.
|
|
185
|
+
*/
|
|
186
|
+
when: whenSchema.optional(),
|
|
187
|
+
/**
|
|
188
|
+
* select / multiselect: the choices come from earlier data — a list output
|
|
189
|
+
* ("steps.<id>.<key>"), a column of a table output ("steps.<id>.<key>.<column>") or a column
|
|
190
|
+
* of a stored list ("lists.<id>.<column>"). `options` stand in while that data is empty.
|
|
191
|
+
*/
|
|
192
|
+
optionsFrom: z.string().optional(),
|
|
193
|
+
/**
|
|
194
|
+
* The field is shown when a decision, made as the run reaches the page, finds this statement
|
|
195
|
+
* true of the run ("The complaint is about a damaged article").
|
|
196
|
+
*/
|
|
197
|
+
ask: z.string().min(1).optional(),
|
|
198
|
+
/** The id of one of the page's `groups`: of a group's fields a decision shows one (or none). */
|
|
199
|
+
group: z.string().optional(),
|
|
160
200
|
});
|
|
161
|
-
export const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
201
|
+
export const fieldGroupSchema = z.object({
|
|
202
|
+
id,
|
|
203
|
+
/** What the decision picks: "How should we reach the person?" */
|
|
204
|
+
instructions: z.string().min(1),
|
|
205
|
+
/** True when showing none of the group's fields is a valid answer. */
|
|
206
|
+
optional: z.boolean().optional(),
|
|
207
|
+
});
|
|
208
|
+
export const nextRuleSchema = z
|
|
209
|
+
.object({
|
|
210
|
+
/** The rule holds when this holds … */
|
|
211
|
+
when: whenSchema.optional(),
|
|
212
|
+
/**
|
|
213
|
+
* … or, instead, when a decision finds this statement true of the run ("The person wants a
|
|
214
|
+
* refund"). All `ask` rules of a step are decided together, after no `when` rule matched.
|
|
215
|
+
*/
|
|
216
|
+
ask: z.string().min(1).optional(),
|
|
217
|
+
/**
|
|
218
|
+
* A step id, or "end". A rule with neither `when` nor `ask` is the step's "otherwise": where
|
|
219
|
+
* the run goes when no other rule holds, in place of the next step in the list.
|
|
220
|
+
*/
|
|
168
221
|
goto: z.string(),
|
|
222
|
+
/**
|
|
223
|
+
* How often this rule may send the run on in one run; afterwards it is skipped. Meant for a
|
|
224
|
+
* branch back to an earlier step (a loop), which without it is taken at most ten times.
|
|
225
|
+
*/
|
|
226
|
+
max: z.number().int().min(1).max(50).optional(),
|
|
227
|
+
})
|
|
228
|
+
.refine((rule) => rule.when === undefined || rule.ask === undefined, {
|
|
229
|
+
message: 'A branch has "when" or "ask", not both.',
|
|
169
230
|
});
|
|
170
231
|
const stepBase = {
|
|
171
232
|
id,
|
|
@@ -176,22 +237,25 @@ const stepBase = {
|
|
|
176
237
|
export const outputFieldSchema = z
|
|
177
238
|
.object({
|
|
178
239
|
id,
|
|
179
|
-
kind: z.enum(["text", "number", "list", "table", "yesno", "choice"]),
|
|
180
|
-
/** What the field holds; for `yesno` and `
|
|
240
|
+
kind: z.enum(["text", "number", "list", "table", "yesno", "choice", "score"]),
|
|
241
|
+
/** What the field holds; for `yesno`, `choice` and `score` the question it answers. */
|
|
181
242
|
description: z.string().optional(),
|
|
182
243
|
/** Table only: the exact column keys every row has — what a widget reads. */
|
|
183
244
|
columns: z.array(z.string().min(1)).optional(),
|
|
184
|
-
/** Choice
|
|
245
|
+
/** Choice: the options, one of which is the answer. Score: the levels, lowest first. */
|
|
185
246
|
options: z.array(z.string().min(1)).min(2).max(40).optional(),
|
|
186
247
|
})
|
|
187
|
-
.refine((field) => field.kind !== "choice" || Boolean(field.options?.length), {
|
|
188
|
-
|
|
248
|
+
.refine((field) => (field.kind !== "choice" && field.kind !== "score") || Boolean(field.options?.length), { message: "A choice or score field lists its options.", path: ["options"] })
|
|
249
|
+
.refine((field) => field.kind !== "score" || (field.options?.length ?? 0) <= 10, {
|
|
250
|
+
message: "A score has at most ten levels.",
|
|
189
251
|
path: ["options"],
|
|
190
252
|
});
|
|
191
253
|
export const pageStepSchema = z.object({
|
|
192
254
|
...stepBase,
|
|
193
255
|
type: z.literal("page"),
|
|
194
256
|
fields: z.array(fieldSchema).min(1),
|
|
257
|
+
/** Fields of which a decision shows one: see `group` on a field. */
|
|
258
|
+
groups: z.array(fieldGroupSchema).max(6).optional(),
|
|
195
259
|
cta: z.string().optional(),
|
|
196
260
|
});
|
|
197
261
|
export const agentStepSchema = z.object({
|
|
@@ -213,12 +277,24 @@ export const agentStepSchema = z.object({
|
|
|
213
277
|
.object({
|
|
214
278
|
format: z.enum(["text", "markdown", "json"]),
|
|
215
279
|
fields: z.array(outputFieldSchema).optional(),
|
|
280
|
+
/**
|
|
281
|
+
* json only: the step also composes a view of its result from the surface catalog, bound
|
|
282
|
+
* to its own fields; reviews and the result show that view instead of a plain table.
|
|
283
|
+
*/
|
|
284
|
+
surface: z.boolean().optional(),
|
|
216
285
|
})
|
|
217
286
|
.default({ format: "markdown" }),
|
|
218
287
|
/** The kind of model the step needs; default `high`. The model itself is bound elsewhere. */
|
|
219
288
|
model: z.enum(TEXT_CLASSES).optional(),
|
|
220
289
|
effort: z.enum(EFFORTS).optional(),
|
|
221
290
|
/** Shown to the person while it works. */
|
|
291
|
+
/**
|
|
292
|
+
* One run of the step per entry, at most twenty: the rows of a table output
|
|
293
|
+
* ("steps.<id>.<key>"), the entries of a list output, or the rows of a stored list
|
|
294
|
+
* ("lists.<id>"). The instructions read the entry as {{item.<column>}}, {{index}}, {{count}}.
|
|
295
|
+
* The output is a table: the entry's columns and the step's output fields, one row per entry.
|
|
296
|
+
*/
|
|
297
|
+
each: z.string().optional(),
|
|
222
298
|
working: z.string().optional(),
|
|
223
299
|
});
|
|
224
300
|
export const generateStepSchema = z.object({
|
|
@@ -319,6 +395,48 @@ export const filmStepSchema = z.object({
|
|
|
319
395
|
effort: z.enum(EFFORTS).optional(),
|
|
320
396
|
working: z.string().optional(),
|
|
321
397
|
});
|
|
398
|
+
/** A piece a decided surface may show: its components (the first has the piece's id) and what it shows. */
|
|
399
|
+
export const surfaceCandidateSchema = z.object({
|
|
400
|
+
id,
|
|
401
|
+
/** What this piece shows, for the decision: "A chart of prices per supplier". */
|
|
402
|
+
description: z.string().min(1),
|
|
403
|
+
components: surfaceComponentsSchema,
|
|
404
|
+
/** Pieces of a group exclude each other: the decision shows one (or none, if optional). */
|
|
405
|
+
group: z.string().optional(),
|
|
406
|
+
/** Always shown: a title, the main list. */
|
|
407
|
+
required: z.boolean().optional(),
|
|
408
|
+
});
|
|
409
|
+
export const surfaceStepSchema = z
|
|
410
|
+
.object({
|
|
411
|
+
...stepBase,
|
|
412
|
+
type: z.literal("surface"),
|
|
413
|
+
/** A view from the surface catalog, written once like a widget; every run brings new data. */
|
|
414
|
+
components: surfaceComponentsSchema.optional(),
|
|
415
|
+
/**
|
|
416
|
+
* Instead of `components`: pieces a decision picks from per run (no chart when there is
|
|
417
|
+
* nothing to chart), drawn in this order under one column.
|
|
418
|
+
*/
|
|
419
|
+
candidates: z.array(surfaceCandidateSchema).max(16).optional(),
|
|
420
|
+
groups: z.array(fieldGroupSchema).max(6).optional(),
|
|
421
|
+
/** What the surface reads as data: key → a field id, steps.id, steps.id.key, lists.id, brand.name or today. */
|
|
422
|
+
data: z.record(z.string(), z.string()).default({}),
|
|
423
|
+
working: z.string().optional(),
|
|
424
|
+
})
|
|
425
|
+
.refine((step) => Boolean(step.components) !== Boolean(step.candidates), {
|
|
426
|
+
message: 'A surface step has either "components" or "candidates".',
|
|
427
|
+
});
|
|
428
|
+
/**
|
|
429
|
+
* The components of a decided surface for the pieces kept: one root column holding them in
|
|
430
|
+
* their order. Every piece kept is the same as all of them for the validator.
|
|
431
|
+
*/
|
|
432
|
+
export function assembleSurface(candidates, kept) {
|
|
433
|
+
const keep = new Set(kept);
|
|
434
|
+
const chosen = candidates.filter((c) => keep.has(c.id));
|
|
435
|
+
return [
|
|
436
|
+
{ id: "root", component: "Column", children: chosen.map((c) => c.components[0]?.id ?? c.id) },
|
|
437
|
+
...chosen.flatMap((c) => c.components),
|
|
438
|
+
];
|
|
439
|
+
}
|
|
322
440
|
export const reviewStepSchema = z.object({
|
|
323
441
|
...stepBase,
|
|
324
442
|
type: z.literal("review"),
|
|
@@ -343,6 +461,7 @@ export const stepSchema = z.discriminatedUnion("type", [
|
|
|
343
461
|
agentStepSchema,
|
|
344
462
|
generateStepSchema,
|
|
345
463
|
widgetStepSchema,
|
|
464
|
+
surfaceStepSchema,
|
|
346
465
|
filmStepSchema,
|
|
347
466
|
reviewStepSchema,
|
|
348
467
|
resultStepSchema,
|
|
@@ -357,7 +476,8 @@ export function isDecisionStep(step) {
|
|
|
357
476
|
return (step.model === "classifier" &&
|
|
358
477
|
step.output.format === "json" &&
|
|
359
478
|
fields.length > 0 &&
|
|
360
|
-
fields.every((f) => f.kind === "yesno" || f.kind === "choice") &&
|
|
479
|
+
fields.every((f) => f.kind === "yesno" || f.kind === "choice" || f.kind === "score") &&
|
|
480
|
+
!step.each &&
|
|
361
481
|
step.tools.length === 0 &&
|
|
362
482
|
!step.mcp?.length &&
|
|
363
483
|
!step.connections?.length);
|
|
@@ -444,6 +564,9 @@ export function formatsFor(step) {
|
|
|
444
564
|
if (step.type === "widget") {
|
|
445
565
|
return step.video ? ["mp4", "png"] : ["html", "png", "pdf", "mp4", "json"];
|
|
446
566
|
}
|
|
567
|
+
if (step.type === "surface") {
|
|
568
|
+
return ["json"];
|
|
569
|
+
}
|
|
447
570
|
if (step.type === "agent") {
|
|
448
571
|
if (step.output.format === "json") {
|
|
449
572
|
return ["json", "csv", "xlsx", "md", "zip"];
|
|
@@ -536,7 +659,9 @@ export function validateWizard(def, files) {
|
|
|
536
659
|
issues.push({ stepId: step.id, message: `Duplicate field id "${field.id}".` });
|
|
537
660
|
}
|
|
538
661
|
fieldIds.add(field.id);
|
|
539
|
-
if ((field.kind === "select" || field.kind === "multiselect") &&
|
|
662
|
+
if ((field.kind === "select" || field.kind === "multiselect") &&
|
|
663
|
+
!field.options?.length &&
|
|
664
|
+
!field.optionsFrom) {
|
|
540
665
|
issues.push({ stepId: step.id, message: `Field "${field.id}" needs options.` });
|
|
541
666
|
}
|
|
542
667
|
if (field.kind === "items" && !field.columns?.length) {
|
|
@@ -614,10 +739,10 @@ export function validateWizard(def, files) {
|
|
|
614
739
|
// provided by the runner
|
|
615
740
|
}
|
|
616
741
|
else if (head === "item" || head === "index" || head === "count") {
|
|
617
|
-
if (step.type !== "generate" || !step.each) {
|
|
742
|
+
if ((step.type !== "generate" && step.type !== "agent") || !step.each) {
|
|
618
743
|
issues.push({
|
|
619
744
|
stepId: step.id,
|
|
620
|
-
message: `"{{${ref}}}" only exists in a generate step with "each".`,
|
|
745
|
+
message: `"{{${ref}}}" only exists in a generate or agent step with "each".`,
|
|
621
746
|
});
|
|
622
747
|
}
|
|
623
748
|
}
|
|
@@ -634,10 +759,134 @@ export function validateWizard(def, files) {
|
|
|
634
759
|
}
|
|
635
760
|
}
|
|
636
761
|
};
|
|
762
|
+
/**
|
|
763
|
+
* What a condition may read before `step`: an answer asked earlier, an output field of an
|
|
764
|
+
* earlier agent step ("steps.<step>.<field>", the step itself for a branch), or how many rows
|
|
765
|
+
* a stored list has ("lists.<list>.count").
|
|
766
|
+
*/
|
|
767
|
+
const knownBefore = (step, ref, self = false) => {
|
|
768
|
+
if (seenFields.has(ref)) {
|
|
769
|
+
return true;
|
|
770
|
+
}
|
|
771
|
+
const [head, from, key, ...more] = ref.split(".");
|
|
772
|
+
if (head === "lists") {
|
|
773
|
+
return key === "count" && more.length === 0 && listIds.has(from ?? "");
|
|
774
|
+
}
|
|
775
|
+
// `steps.<step>.<field>.p`: how probable the decision's answer is.
|
|
776
|
+
const probability = more.length === 1 && more[0] === "p";
|
|
777
|
+
if (head !== "steps" || !key || (more.length > 0 && !probability)) {
|
|
778
|
+
return false;
|
|
779
|
+
}
|
|
780
|
+
if (!(seenSteps.has(from) || (self && from === step.id))) {
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
const source = def.steps.find((s) => s.id === from);
|
|
784
|
+
return (source?.type === "agent" &&
|
|
785
|
+
(!probability || isDecisionStep(source)) &&
|
|
786
|
+
((source.output.fields ?? []).some((f) => f.id === key) ||
|
|
787
|
+
(Boolean(source.each) && key === "rows" && !probability)));
|
|
788
|
+
};
|
|
789
|
+
const conditionIssues = (stepId, c) => {
|
|
790
|
+
if ((c.op === "gt" || c.op === "lt") && typeof c.value !== "number") {
|
|
791
|
+
return [{ stepId, message: `"${c.op}" on "${c.field}" compares with a number value.` }];
|
|
792
|
+
}
|
|
793
|
+
if (c.op === "in" && !Array.isArray(c.value)) {
|
|
794
|
+
return [{ stepId, message: `"in" on "${c.field}" needs a list of values.` }];
|
|
795
|
+
}
|
|
796
|
+
if (c.op !== "empty" && c.op !== "notEmpty" && c.value === undefined) {
|
|
797
|
+
return [{ stepId, message: `"${c.op}" on "${c.field}" needs a value.` }];
|
|
798
|
+
}
|
|
799
|
+
return [];
|
|
800
|
+
};
|
|
801
|
+
/** Why an `optionsFrom` reference finds nothing, or null when it is fine. */
|
|
802
|
+
const optionsFromProblem = (raw) => {
|
|
803
|
+
const [head, from, key, column, ...more] = dataRef(raw).split(".");
|
|
804
|
+
if (head === "lists" && from && key && !column) {
|
|
805
|
+
const list = def.lists?.find((l) => l.id === from);
|
|
806
|
+
if (!list) {
|
|
807
|
+
return `"optionsFrom" names an unknown list "${from}".`;
|
|
808
|
+
}
|
|
809
|
+
if (sharedLists.has(from)) {
|
|
810
|
+
return notShown(from);
|
|
811
|
+
}
|
|
812
|
+
return list.columns.some((c) => c.id === key)
|
|
813
|
+
? null
|
|
814
|
+
: `"optionsFrom": list "${from}" has no column "${key}".`;
|
|
815
|
+
}
|
|
816
|
+
if (head === "steps" && from && key && more.length === 0) {
|
|
817
|
+
const source = seenSteps.has(from) ? def.steps.find((s) => s.id === from) : undefined;
|
|
818
|
+
// An agent step with `each` hands on one table, `rows`: the entry's columns and its fields.
|
|
819
|
+
if (source?.type === "agent" && source.each && key === "rows") {
|
|
820
|
+
return column ? null : `"optionsFrom": "${from}.rows" is a table; name one of its columns.`;
|
|
821
|
+
}
|
|
822
|
+
const out = source?.type === "agent"
|
|
823
|
+
? (source.output.fields ?? []).find((f) => f.id === key)
|
|
824
|
+
: undefined;
|
|
825
|
+
if (!out) {
|
|
826
|
+
return `"optionsFrom" names "${from}.${key}", which is not an output of an earlier agent step.`;
|
|
827
|
+
}
|
|
828
|
+
if (column) {
|
|
829
|
+
return out.kind === "table" && (!out.columns?.length || out.columns.includes(column))
|
|
830
|
+
? null
|
|
831
|
+
: `"optionsFrom": "${from}.${key}" is not a table with the column "${column}".`;
|
|
832
|
+
}
|
|
833
|
+
return out.kind === "list" ? null : `"optionsFrom": "${from}.${key}" is not a list.`;
|
|
834
|
+
}
|
|
835
|
+
return `"optionsFrom" is "steps.<id>.<key>", "steps.<id>.<key>.<column>" or "lists.<id>.<column>".`;
|
|
836
|
+
};
|
|
637
837
|
for (const step of def.steps) {
|
|
638
838
|
switch (step.type) {
|
|
639
|
-
case "page":
|
|
839
|
+
case "page": {
|
|
840
|
+
const groups = new Set();
|
|
841
|
+
for (const g of step.groups ?? []) {
|
|
842
|
+
if (groups.has(g.id)) {
|
|
843
|
+
issues.push({ stepId: step.id, message: `Duplicate field group "${g.id}".` });
|
|
844
|
+
}
|
|
845
|
+
groups.add(g.id);
|
|
846
|
+
if (!step.fields.some((f) => f.group === g.id)) {
|
|
847
|
+
issues.push({ stepId: step.id, message: `Field group "${g.id}" has no fields.` });
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
const decided = step.fields.some((f) => f.ask || f.group);
|
|
851
|
+
if (decided && step.fields.length > MAX_DECIDED_FIELDS) {
|
|
852
|
+
issues.push({
|
|
853
|
+
stepId: step.id,
|
|
854
|
+
message: `A page whose fields are decided has at most ${MAX_DECIDED_FIELDS} fields.`,
|
|
855
|
+
});
|
|
856
|
+
}
|
|
640
857
|
for (const field of step.fields) {
|
|
858
|
+
if (field.ask && field.group) {
|
|
859
|
+
issues.push({
|
|
860
|
+
stepId: step.id,
|
|
861
|
+
message: `Field "${field.id}" has "ask" or "group", not both.`,
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
if (field.group && !groups.has(field.group)) {
|
|
865
|
+
issues.push({
|
|
866
|
+
stepId: step.id,
|
|
867
|
+
message: `Field "${field.id}" names an unknown group "${field.group}".`,
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
for (const field of step.fields) {
|
|
872
|
+
for (const c of conditionsOf(field.when)) {
|
|
873
|
+
const own = step.fields.some((f) => f.id === c.field && f.id !== field.id);
|
|
874
|
+
if (!own && !knownBefore(step, c.field)) {
|
|
875
|
+
issues.push({
|
|
876
|
+
stepId: step.id,
|
|
877
|
+
message: `Field "${field.id}": "when" reads "${c.field}", which is neither on this page nor known before it.`,
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
issues.push(...conditionIssues(step.id, c));
|
|
881
|
+
}
|
|
882
|
+
if (field.optionsFrom) {
|
|
883
|
+
const problem = field.kind !== "select" && field.kind !== "multiselect"
|
|
884
|
+
? `"optionsFrom" fills the choices of a select or multiselect; kind is "${field.kind}".`
|
|
885
|
+
: optionsFromProblem(field.optionsFrom);
|
|
886
|
+
if (problem) {
|
|
887
|
+
issues.push({ stepId: step.id, message: `Field "${field.id}": ${problem}` });
|
|
888
|
+
}
|
|
889
|
+
}
|
|
641
890
|
if (field.prefill) {
|
|
642
891
|
const ref = dataRef(field.prefill);
|
|
643
892
|
if (ref.split(".")[0] !== "steps") {
|
|
@@ -653,6 +902,7 @@ export function validateWizard(def, files) {
|
|
|
653
902
|
seenFields.add(field.id);
|
|
654
903
|
}
|
|
655
904
|
break;
|
|
905
|
+
}
|
|
656
906
|
case "agent":
|
|
657
907
|
checkTemplate(step, step.instructions);
|
|
658
908
|
for (const c of step.connections ?? []) {
|
|
@@ -660,6 +910,30 @@ export function validateWizard(def, files) {
|
|
|
660
910
|
issues.push({ stepId: step.id, message: `Step uses unknown connection "${c}".` });
|
|
661
911
|
}
|
|
662
912
|
}
|
|
913
|
+
if (step.output.surface && step.output.format !== "json") {
|
|
914
|
+
issues.push({
|
|
915
|
+
stepId: step.id,
|
|
916
|
+
message: 'A step composes a view ("surface") of json output only.',
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
if (step.each) {
|
|
920
|
+
const ref = dataRef(step.each);
|
|
921
|
+
const list = ref.match(/^lists\.([a-zA-Z][a-zA-Z0-9_]*)$/)?.[1];
|
|
922
|
+
if (list) {
|
|
923
|
+
if (!listIds.has(list)) {
|
|
924
|
+
issues.push({ stepId: step.id, message: `each names an unknown list "${list}".` });
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
else if (ref.startsWith("steps.") && ref.split(".").length === 3) {
|
|
928
|
+
checkTemplate(step, `{{${ref}}}`);
|
|
929
|
+
}
|
|
930
|
+
else {
|
|
931
|
+
issues.push({
|
|
932
|
+
stepId: step.id,
|
|
933
|
+
message: `each "${step.each}" is "steps.<id>.<key>" (a table or list output) or "lists.<id>".`,
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
}
|
|
663
937
|
break;
|
|
664
938
|
case "generate":
|
|
665
939
|
checkTemplate(step, step.prompt);
|
|
@@ -711,6 +985,49 @@ export function validateWizard(def, files) {
|
|
|
711
985
|
});
|
|
712
986
|
}
|
|
713
987
|
break;
|
|
988
|
+
case "surface": {
|
|
989
|
+
for (const [key, ref] of Object.entries(step.data)) {
|
|
990
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
991
|
+
issues.push({
|
|
992
|
+
stepId: step.id,
|
|
993
|
+
message: `Surface data key "${key}" is not an identifier.`,
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
const list = listRef(dataRef(ref));
|
|
997
|
+
if (list) {
|
|
998
|
+
if (!listIds.has(list)) {
|
|
999
|
+
issues.push({
|
|
1000
|
+
stepId: step.id,
|
|
1001
|
+
message: `Surface data reads an unknown list "${list}".`,
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
else {
|
|
1006
|
+
checkTemplate(step, `{{${dataRef(ref)}}}`);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
const groups = new Set((step.groups ?? []).map((g) => g.id));
|
|
1010
|
+
for (const c of step.candidates ?? []) {
|
|
1011
|
+
if (c.components[0]?.id !== c.id) {
|
|
1012
|
+
issues.push({
|
|
1013
|
+
stepId: step.id,
|
|
1014
|
+
message: `Surface piece "${c.id}": its first component carries the piece's id.`,
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
if (c.group && !groups.has(c.group)) {
|
|
1018
|
+
issues.push({
|
|
1019
|
+
stepId: step.id,
|
|
1020
|
+
message: `Surface piece "${c.id}" names an unknown group "${c.group}".`,
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
const components = step.components ??
|
|
1025
|
+
assembleSurface(step.candidates ?? [], (step.candidates ?? []).map((c) => c.id));
|
|
1026
|
+
for (const issue of validateSurface(components, Object.keys(step.data))) {
|
|
1027
|
+
issues.push({ stepId: step.id, message: `Surface: ${issue.message}` });
|
|
1028
|
+
}
|
|
1029
|
+
break;
|
|
1030
|
+
}
|
|
714
1031
|
case "film":
|
|
715
1032
|
checkTemplate(step, step.brief);
|
|
716
1033
|
for (const [name, ref] of Object.entries(step.inputs)) {
|
|
@@ -778,23 +1095,23 @@ export function validateWizard(def, files) {
|
|
|
778
1095
|
}
|
|
779
1096
|
break;
|
|
780
1097
|
}
|
|
1098
|
+
if ((step.next ?? []).filter(isOtherwise).length > 1) {
|
|
1099
|
+
issues.push({
|
|
1100
|
+
stepId: step.id,
|
|
1101
|
+
message: 'A step has at most one "otherwise" branch (one without "when" and "ask").',
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
781
1104
|
for (const rule of step.next ?? []) {
|
|
782
1105
|
if (rule.goto !== "end" && !stepIds.has(rule.goto)) {
|
|
783
1106
|
issues.push({ stepId: step.id, message: `Branch goes to unknown step "${rule.goto}".` });
|
|
784
1107
|
}
|
|
785
|
-
// A branch reads what the person answered,
|
|
786
|
-
// agent step
|
|
787
|
-
const
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
(source?.type === "agent" && (source.output.fields ?? []).some((f) => f.id === key));
|
|
793
|
-
if (!known) {
|
|
794
|
-
issues.push({
|
|
795
|
-
stepId: step.id,
|
|
796
|
-
message: `Branch reads unknown field "${rule.when.field}".`,
|
|
797
|
-
});
|
|
1108
|
+
// A branch reads what the person answered (on any page), an output field of this or an
|
|
1109
|
+
// earlier agent step, or the row count of a stored list.
|
|
1110
|
+
for (const c of conditionsOf(rule.when)) {
|
|
1111
|
+
if (!fieldIds.has(c.field) && !knownBefore(step, c.field, true)) {
|
|
1112
|
+
issues.push({ stepId: step.id, message: `Branch reads unknown field "${c.field}".` });
|
|
1113
|
+
}
|
|
1114
|
+
issues.push(...conditionIssues(step.id, c));
|
|
798
1115
|
}
|
|
799
1116
|
}
|
|
800
1117
|
seenSteps.add(step.id);
|
|
@@ -817,48 +1134,204 @@ export function parseWizard(input, files) {
|
|
|
817
1134
|
* What branch rules can read in a run: the person's answers by field id, and the structured
|
|
818
1135
|
* output of the steps run so far as `steps.<step>.<field>`.
|
|
819
1136
|
*/
|
|
820
|
-
export function branchValues(values, outputs
|
|
1137
|
+
export function branchValues(values, outputs,
|
|
1138
|
+
/** Rows per stored list, read as `lists.<list>.count`. */
|
|
1139
|
+
listCounts = {}) {
|
|
821
1140
|
const out = { ...values };
|
|
1141
|
+
for (const [list, count] of Object.entries(listCounts)) {
|
|
1142
|
+
out[`lists.${list}.count`] = count;
|
|
1143
|
+
}
|
|
822
1144
|
for (const [stepId, output] of Object.entries(outputs)) {
|
|
823
1145
|
if (output.json && typeof output.json === "object" && !Array.isArray(output.json)) {
|
|
824
1146
|
for (const [key, value] of Object.entries(output.json)) {
|
|
825
1147
|
out[`steps.${stepId}.${key}`] = value;
|
|
826
1148
|
}
|
|
827
1149
|
}
|
|
1150
|
+
for (const [key, p] of Object.entries(output.decided ?? {})) {
|
|
1151
|
+
out[`steps.${stepId}.${key}.p`] = p;
|
|
1152
|
+
}
|
|
828
1153
|
}
|
|
829
1154
|
return out;
|
|
830
1155
|
}
|
|
831
|
-
/**
|
|
832
|
-
export
|
|
1156
|
+
/** A branch back to an earlier step without `max` is taken at most this often in one run. */
|
|
1157
|
+
export const DEFAULT_LOOP_MAX = 10;
|
|
1158
|
+
/** How a run counts the times it took a branch: "<step>→<goto>". */
|
|
1159
|
+
export function loopKey(stepId, goto) {
|
|
1160
|
+
return `${stepId}→${goto}`;
|
|
1161
|
+
}
|
|
1162
|
+
/** Whether a rule may still send the run on: one with `max`, or one back, stops after so many. */
|
|
1163
|
+
export function ruleOpen(def, stepId, rule, loops = {}) {
|
|
1164
|
+
const from = def.steps.findIndex((s) => s.id === stepId);
|
|
1165
|
+
const to = rule.goto === "end" ? def.steps.length : def.steps.findIndex((s) => s.id === rule.goto);
|
|
1166
|
+
const max = rule.max ?? (to >= 0 && to <= from ? DEFAULT_LOOP_MAX : Number.POSITIVE_INFINITY);
|
|
1167
|
+
return (loops[loopKey(stepId, rule.goto)] ?? 0) < max;
|
|
1168
|
+
}
|
|
1169
|
+
export function followRules(def, stepId, values, loops = {}) {
|
|
833
1170
|
const index = def.steps.findIndex((s) => s.id === stepId);
|
|
834
1171
|
const step = def.steps[index];
|
|
835
1172
|
if (!step) {
|
|
836
|
-
return null;
|
|
1173
|
+
return { kind: "go", cursor: null, rule: null };
|
|
837
1174
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1175
|
+
const rules = step.next ?? [];
|
|
1176
|
+
for (const [i, rule] of rules.entries()) {
|
|
1177
|
+
if (ruleOpen(def, stepId, rule, loops) && ruleMatches(rule, values)) {
|
|
1178
|
+
return { kind: "go", cursor: rule.goto === "end" ? null : rule.goto, rule: i };
|
|
841
1179
|
}
|
|
842
1180
|
}
|
|
1181
|
+
const asks = rules.flatMap((rule, i) => rule.ask && ruleOpen(def, stepId, rule, loops) ? [i] : []);
|
|
1182
|
+
if (asks.length) {
|
|
1183
|
+
return { kind: "decide", rules: asks };
|
|
1184
|
+
}
|
|
1185
|
+
return { kind: "go", ...otherwiseOf(def, stepId, loops) };
|
|
1186
|
+
}
|
|
1187
|
+
/** A rule with neither a condition nor a statement: where the run goes when no other rule holds. */
|
|
1188
|
+
export function isOtherwise(rule) {
|
|
1189
|
+
return rule.when === undefined && rule.ask === undefined;
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* Where a step goes when none of its rules holds: its "otherwise" rule (`rule` is its index in
|
|
1193
|
+
* `next`), else the next step in the list. `cursor` null = the end.
|
|
1194
|
+
*/
|
|
1195
|
+
export function otherwiseOf(def, stepId, loops = {}) {
|
|
1196
|
+
const index = def.steps.findIndex((s) => s.id === stepId);
|
|
1197
|
+
const rules = def.steps[index]?.next ?? [];
|
|
1198
|
+
const i = rules.findIndex((r) => isOtherwise(r) && ruleOpen(def, stepId, r, loops));
|
|
1199
|
+
if (i >= 0) {
|
|
1200
|
+
return { cursor: rules[i].goto === "end" ? null : rules[i].goto, rule: i };
|
|
1201
|
+
}
|
|
1202
|
+
return { cursor: def.steps[index + 1]?.id ?? null, rule: null };
|
|
1203
|
+
}
|
|
1204
|
+
/** The step after `step` by its `when` rules alone. `null` = the end. */
|
|
1205
|
+
export function nextStepId(def, stepId, values) {
|
|
1206
|
+
const outcome = followRules(def, stepId, values);
|
|
1207
|
+
if (outcome.kind === "go") {
|
|
1208
|
+
return outcome.cursor;
|
|
1209
|
+
}
|
|
1210
|
+
const index = def.steps.findIndex((s) => s.id === stepId);
|
|
843
1211
|
return def.steps[index + 1]?.id ?? null;
|
|
844
1212
|
}
|
|
845
1213
|
function isEmptyValue(v) {
|
|
846
1214
|
return v === undefined || v === null || v === "" || (Array.isArray(v) && v.length === 0);
|
|
847
1215
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
1216
|
+
/** The conditions of a rule or a field, as a list: one, several, or none. */
|
|
1217
|
+
export function conditionsOf(when) {
|
|
1218
|
+
return when === undefined ? [] : Array.isArray(when) ? when : [when];
|
|
1219
|
+
}
|
|
1220
|
+
/** A typed number as a number: "89,90" from a German number pad counts as 89.9. */
|
|
1221
|
+
function numberOf(v) {
|
|
1222
|
+
if (typeof v === "number") {
|
|
1223
|
+
return Number.isFinite(v) ? v : null;
|
|
1224
|
+
}
|
|
1225
|
+
if (typeof v !== "string" || !v.trim()) {
|
|
1226
|
+
return null;
|
|
1227
|
+
}
|
|
1228
|
+
const n = Number(v.replace(/\s/g, "").replace(",", "."));
|
|
1229
|
+
return Number.isFinite(n) ? n : null;
|
|
1230
|
+
}
|
|
1231
|
+
export function conditionMatches(c, values) {
|
|
1232
|
+
const v = values[c.field];
|
|
1233
|
+
switch (c.op) {
|
|
851
1234
|
case "empty":
|
|
852
1235
|
return isEmptyValue(v);
|
|
853
1236
|
case "notEmpty":
|
|
854
1237
|
return !isEmptyValue(v);
|
|
855
1238
|
case "equals":
|
|
856
|
-
return String(v) === String(
|
|
1239
|
+
return String(v) === String(c.value);
|
|
857
1240
|
case "notEquals":
|
|
858
|
-
return String(v) !== String(
|
|
1241
|
+
return String(v) !== String(c.value);
|
|
859
1242
|
case "in":
|
|
860
|
-
return Array.isArray(
|
|
1243
|
+
return Array.isArray(c.value) && c.value.map(String).includes(String(v));
|
|
1244
|
+
case "gt":
|
|
1245
|
+
case "lt": {
|
|
1246
|
+
const a = numberOf(v);
|
|
1247
|
+
const b = numberOf(c.value);
|
|
1248
|
+
return a !== null && b !== null && (c.op === "gt" ? a > b : a < b);
|
|
1249
|
+
}
|
|
1250
|
+
case "contains":
|
|
1251
|
+
if (Array.isArray(v)) {
|
|
1252
|
+
return v.map(String).includes(String(c.value));
|
|
1253
|
+
}
|
|
1254
|
+
return (typeof v === "string" &&
|
|
1255
|
+
c.value !== undefined &&
|
|
1256
|
+
v.toLowerCase().includes(String(c.value).toLowerCase()));
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
/** Whether every condition holds; no conditions hold trivially. */
|
|
1260
|
+
export function whenHolds(when, values) {
|
|
1261
|
+
return conditionsOf(when).every((c) => conditionMatches(c, values));
|
|
1262
|
+
}
|
|
1263
|
+
/** Whether a `when` rule holds. An `ask` rule never holds here: a decision takes it (see decide). */
|
|
1264
|
+
export function ruleMatches(rule, values) {
|
|
1265
|
+
return rule.when !== undefined && whenHolds(rule.when, values);
|
|
1266
|
+
}
|
|
1267
|
+
/** A page with decided fields has at most this many; it shows at most MAX_SHOWN_FIELDS of them. */
|
|
1268
|
+
export const MAX_DECIDED_FIELDS = 12;
|
|
1269
|
+
export const MAX_SHOWN_FIELDS = 5;
|
|
1270
|
+
/** Whether a field is shown only when a decision keeps it. */
|
|
1271
|
+
export function isDecidedField(field) {
|
|
1272
|
+
return Boolean(field.ask || field.group);
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* The fields of a page that are shown: those whose `when` holds on what was known before the
|
|
1276
|
+
* page (`known`) and what the page holds so far (`page`). A hidden field's value does not count
|
|
1277
|
+
* for the fields after it.
|
|
1278
|
+
*/
|
|
1279
|
+
export function shownFields(fields, known, page) {
|
|
1280
|
+
const values = { ...known, ...page };
|
|
1281
|
+
const out = [];
|
|
1282
|
+
for (const field of fields) {
|
|
1283
|
+
if (whenHolds(field.when, values)) {
|
|
1284
|
+
out.push(field);
|
|
1285
|
+
}
|
|
1286
|
+
else {
|
|
1287
|
+
delete values[field.id];
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
return out;
|
|
1291
|
+
}
|
|
1292
|
+
/** The references the `when` of a page's fields read outside the page: what the page must know. */
|
|
1293
|
+
export function pageNeeds(fields) {
|
|
1294
|
+
const own = new Set(fields.map((f) => f.id));
|
|
1295
|
+
return [
|
|
1296
|
+
...new Set(fields.flatMap((f) => conditionsOf(f.when).map((c) => c.field)).filter((r) => !own.has(r))),
|
|
1297
|
+
];
|
|
1298
|
+
}
|
|
1299
|
+
/** At most this many choices come from data. */
|
|
1300
|
+
export const MAX_DATA_OPTIONS = 100;
|
|
1301
|
+
/**
|
|
1302
|
+
* The choices an `optionsFrom` reference finds in a run: the entries of a list output, one
|
|
1303
|
+
* column of a table output, or one column of a stored list — as text, without duplicates and
|
|
1304
|
+
* empties. Empty when the data is not there (yet).
|
|
1305
|
+
*/
|
|
1306
|
+
export function optionsFromData(ref, outputs, lists = {}) {
|
|
1307
|
+
const [head, id, key, column, ...more] = dataRef(ref).split(".");
|
|
1308
|
+
let raw = [];
|
|
1309
|
+
if (head === "lists" && id && key && !column) {
|
|
1310
|
+
raw = (lists[id]?.rows ?? []).map((r) => r.cells[key]);
|
|
1311
|
+
}
|
|
1312
|
+
else if (head === "steps" && id && key && more.length === 0) {
|
|
1313
|
+
const json = outputs[id]?.json;
|
|
1314
|
+
const value = json && typeof json === "object" ? json[key] : undefined;
|
|
1315
|
+
if (Array.isArray(value)) {
|
|
1316
|
+
raw = column
|
|
1317
|
+
? value.map((row) => row && typeof row === "object" ? row[column] : undefined)
|
|
1318
|
+
: value;
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
const seen = new Set();
|
|
1322
|
+
for (const v of raw) {
|
|
1323
|
+
if (v === null || v === undefined || typeof v === "object") {
|
|
1324
|
+
continue;
|
|
1325
|
+
}
|
|
1326
|
+
const text = String(v).trim().slice(0, 200);
|
|
1327
|
+
if (text) {
|
|
1328
|
+
seen.add(text);
|
|
1329
|
+
}
|
|
1330
|
+
if (seen.size >= MAX_DATA_OPTIONS) {
|
|
1331
|
+
break;
|
|
1332
|
+
}
|
|
861
1333
|
}
|
|
1334
|
+
return [...seen];
|
|
862
1335
|
}
|
|
863
1336
|
export function itemsTotals(field, value, values) {
|
|
864
1337
|
const rows = Array.isArray(value) ? value : [];
|