@aglyn/plugins-ai 1.0.0-beta.236 → 1.0.0-beta.238
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/package.json +17 -17
- package/src/lib/components/ai-credits-prompt.component.d.ts +44 -0
- package/src/lib/components/ai-credits-prompt.component.js +117 -0
- package/src/lib/components/ai-credits-prompt.component.js.map +1 -0
- package/src/lib/components/ai-job-cancel.component.d.ts +38 -0
- package/src/lib/components/ai-job-cancel.component.js +156 -0
- package/src/lib/components/ai-job-cancel.component.js.map +1 -0
- package/src/lib/components/ai-job-plan.component.d.ts +5 -4
- package/src/lib/components/ai-job-plan.component.js +48 -12
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +10 -0
- package/src/lib/components/ai-job-requests.js +12 -4
- package/src/lib/components/ai-job-requests.js.map +1 -1
- package/src/lib/components/ai-jobs-list-page.component.js +141 -72
- package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +48 -9
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.js +68 -22
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-usage-strip.component.d.ts +19 -2
- package/src/lib/components/ai-usage-strip.component.js +40 -9
- package/src/lib/components/ai-usage-strip.component.js.map +1 -1
- package/src/lib/components/assist-build-card.component.d.ts +3 -1
- package/src/lib/components/assist-build-card.component.js +58 -7
- package/src/lib/components/assist-build-card.component.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +66 -22
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +89 -52
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/jobs/ai-job-admission.d.ts +22 -2
- package/src/lib/jobs/ai-job-admission.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.d.ts +24 -11
- package/src/lib/jobs/ai-job-build-step.js +54 -35
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-cancel.d.ts +71 -0
- package/src/lib/jobs/ai-job-cancel.js +102 -0
- package/src/lib/jobs/ai-job-cancel.js.map +1 -0
- package/src/lib/jobs/ai-job-component-step.js +4 -1
- package/src/lib/jobs/ai-job-component-step.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.js +2 -0
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-form-step.js +4 -1
- package/src/lib/jobs/ai-job-form-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +2 -0
- package/src/lib/jobs/ai-job-layout-language.js +13 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +6 -1
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.js +12 -0
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +13 -0
- package/src/lib/jobs/ai-job-page-step.js +46 -4
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +42 -0
- package/src/lib/jobs/ai-job-plan-step.js +118 -18
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +49 -3
- package/src/lib/jobs/ai-job-site-content.js +196 -37
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.js +49 -16
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-template-step.js +4 -1
- package/src/lib/jobs/ai-job-template-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.d.ts +8 -0
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +55 -13
- package/src/lib/jobs/ai-jobs.js +213 -41
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +194 -34
- package/src/lib/jobs/ai-layout-stock-photos.js +861 -146
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.d.ts +30 -0
- package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js +220 -0
- package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.d.ts +27 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js +151 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +33 -0
- package/src/lib/layout-language/ai-layout-compiler.js +381 -18
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
- package/src/lib/layout-language/ai-layout-design.js +5 -3
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
- package/src/lib/layout-language/ai-layout-frame.js +58 -3
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-gaps.js +3 -1
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.js +1 -1
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +49 -1
- package/src/lib/layout-language/ai-layout-listings.js +117 -47
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +10 -1
- package/src/lib/layout-language/ai-layout-pictures.js +5 -2
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/layout-language/ai-layout-storefront.d.ts +82 -0
- package/src/lib/layout-language/ai-layout-storefront.js +223 -0
- package/src/lib/layout-language/ai-layout-storefront.js.map +1 -0
- package/src/lib/model/ai-build-job.d.ts +39 -0
- package/src/lib/model/ai-build-job.js +100 -1
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +8 -0
- package/src/lib/model/ai-build-progress.js +35 -0
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +249 -0
- package/src/lib/model/ai-credit-estimate.js +278 -0
- package/src/lib/model/ai-credit-estimate.js.map +1 -0
- package/src/lib/model/ai-job-cancel-copy.d.ts +44 -0
- package/src/lib/model/ai-job-cancel-copy.js +53 -0
- package/src/lib/model/ai-job-cancel-copy.js.map +1 -0
- package/src/lib/model/ai-job-failure-copy.d.ts +4 -1
- package/src/lib/model/ai-job-failure-copy.js +8 -3
- package/src/lib/model/ai-job-failure-copy.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +35 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-build-progress.js +7 -4
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +76 -22
- package/src/lib/model/ai-site-job.js +125 -23
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.d.ts +4 -1
- package/src/lib/model/ai-site-kinds.js +35 -7
- package/src/lib/model/ai-site-kinds.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +37 -1
- package/src/lib/runtime/ai-doctrine-validators.js +161 -7
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +4 -3
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-node-tree.d.ts +11 -0
- package/src/lib/runtime/ai-node-tree.js +26 -0
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +592 -14
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/server/ai-jobs-cancel.d.ts +13 -5
- package/src/lib/server/ai-jobs-cancel.js +27 -9
- package/src/lib/server/ai-jobs-cancel.js.map +1 -1
- package/src/lib/server/ai-jobs-gate.d.ts +12 -2
- package/src/lib/server/ai-jobs-gate.js +20 -9
- package/src/lib/server/ai-jobs-gate.js.map +1 -1
- package/src/lib/server/ai-jobs-resume.js +93 -35
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/server/ai-jobs-route.js +45 -4
- package/src/lib/server/ai-jobs-route.js.map +1 -1
- package/src/lib/server/assist-chat.js +4 -1
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit.js +3 -1
- package/src/lib/server/assist-edit.js.map +1 -1
- package/src/lib/server/assist-music.d.ts +36 -0
- package/src/lib/server/assist-music.js +36 -0
- package/src/lib/server/assist-music.js.map +1 -0
- package/src/lib/server.js +6 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/usage/ai-usage-meter.d.ts +2 -1
- package/src/lib/usage/ai-usage-meter.js +17 -1
- package/src/lib/usage/ai-usage-meter.js.map +1 -1
- package/src/lib/usage/ai-usage-wire.d.ts +6 -0
- package/src/lib/usage/ai-usage-wire.js.map +1 -1
- package/src/lib/usage/assist-usage.d.ts +7 -0
- package/src/lib/usage/assist-usage.js +14 -4
- package/src/lib/usage/assist-usage.js.map +1 -1
- package/src/lib/usage/free-ai-credits-left.d.ts +7 -0
- package/src/lib/usage/free-ai-credits-left.js +4 -2
- package/src/lib/usage/free-ai-credits-left.js.map +1 -1
|
@@ -23,7 +23,7 @@ import { FREE_AI_TASTE_CREDITS_PER_MONTH } from "../plan-entitlements.js";
|
|
|
23
23
|
import { REUSABLE_INSTANCE_COMPONENT_ID, REUSABLE_INSTANCE_PROP_VALUES_KEY } from "@aglyn/aglyn/app-utils/reusable-component-keys";
|
|
24
24
|
import { parseBreakpointSpan } from "@aglyn/shared-data-enums/breakpoint-span";
|
|
25
25
|
import { renderEmailHtml } from "@aglyn/shared-util-email/email-render";
|
|
26
|
-
import { AI_BUILD_PLAN_CREATION_NOUNS, AI_BUILD_PLAN_EMBED_HOST_NAMES, AI_PLAN_ASKS_FOR_VIDEO, aiEmbedHostOf, aiEmbedVideoKey, aiPlanCreateFor, aiPlanSlugKey, aiPlanUndeclaredRefs, isAiPlanNewRef } from "../model/ai-build-plan.js";
|
|
26
|
+
import { AI_BUILD_PLAN_CREATION_NOUNS, AI_BUILD_PLAN_EMBED_HOST_NAMES, AI_BUILD_PLAN_LIMITS, AI_PLAN_ASKS_FOR_VIDEO, aiEmbedHostOf, aiEmbedVideoKey, aiPlanCreateFor, aiPlanItemFor, aiPlanSlugKey, aiPlanUndeclaredRefs, isAiPlanNewRef } from "../model/ai-build-plan.js";
|
|
27
27
|
import { aiCreationNoun, aiPlanCanPlaceForm, aiPlanUncreatable, aiPlanUncreatableKind } from "../model/ai-plan-capabilities.js";
|
|
28
28
|
import { aiFreeSiteSectionsWithin, aiSiteHomeMinSections, aiSitePlanIsHome } from "../model/ai-site-job.js";
|
|
29
29
|
import { AI_INSTANCE_REF_PROP, validateAiNodeTree } from "./ai-node-tree.js";
|
|
@@ -279,12 +279,15 @@ function listNames(ids, tree) {
|
|
|
279
279
|
const index = indexShapes(tree);
|
|
280
280
|
/**
|
|
281
281
|
* A compiled picture card (AGL-3660): a Stack that opens with its picture,
|
|
282
|
-
* in a cell of a grid — or that cell, holding only the card.
|
|
282
|
+
* in a cell of a grid — or that cell, holding only the card. A storefront's
|
|
283
|
+
* icon column (AGL-3676), a Stack that opens with its library icon, is the
|
|
284
|
+
* compiler's the same way: no model writes an Icon on a page.
|
|
283
285
|
*/ const card = (id)=>{
|
|
284
286
|
var _node_nodes, _tree_nodes_first;
|
|
285
287
|
const node = id === undefined ? undefined : tree.nodes[id];
|
|
286
288
|
const first = node == null ? void 0 : (_node_nodes = node.nodes) == null ? void 0 : _node_nodes[0];
|
|
287
|
-
|
|
289
|
+
const opens = first === undefined ? undefined : (_tree_nodes_first = tree.nodes[first]) == null ? void 0 : _tree_nodes_first.componentId;
|
|
290
|
+
return (node == null ? void 0 : node.componentId) === 'muiStack' && (opens === 'image' || opens === 'icon');
|
|
288
291
|
};
|
|
289
292
|
const pictureCard = (id)=>{
|
|
290
293
|
var _node_props, _node_nodes, _index_visits_find, _tree_nodes_parentId;
|
|
@@ -2351,6 +2354,13 @@ const DUPLICATE_SX_MIN_NODES = 4;
|
|
|
2351
2354
|
const key = aiEmbedVideoKey(url);
|
|
2352
2355
|
return key && planned.some((embed)=>embed.url && aiEmbedVideoKey(embed.url) === key) ? 'planned' : 'unplanned-link';
|
|
2353
2356
|
}
|
|
2357
|
+
/**
|
|
2358
|
+
* Containers whose empty state is something they draw themselves, so an empty
|
|
2359
|
+
* one is a placement and not a gap (AGL-3716): the Music player, placed with
|
|
2360
|
+
* no tracks, asks its owner to add their own; a model never fills it.
|
|
2361
|
+
*/ const OWNS_ITS_EMPTY_STATE = new Set([
|
|
2362
|
+
'musicPlayer'
|
|
2363
|
+
]);
|
|
2354
2364
|
/**
|
|
2355
2365
|
* Rule 16. The flattest tree that renders the design: no container that
|
|
2356
2366
|
* wraps one other container and adds nothing, no empty containers, no inline
|
|
@@ -2379,7 +2389,9 @@ const DUPLICATE_SX_MIN_NODES = 4;
|
|
|
2379
2389
|
wrappers.push(id);
|
|
2380
2390
|
}
|
|
2381
2391
|
const boundForm = node.componentId === 'form' && typeof ((_node_props1 = node.props) == null ? void 0 : _node_props1['formId']) === 'string';
|
|
2382
|
-
if ((entry == null ? void 0 : entry.acceptsChildren) && !children.length && !boundForm
|
|
2392
|
+
if ((entry == null ? void 0 : entry.acceptsChildren) && !children.length && !boundForm && !OWNS_ITS_EMPTY_STATE.has(node.componentId)) {
|
|
2393
|
+
empty.push(id);
|
|
2394
|
+
}
|
|
2383
2395
|
if (outputKind !== 'email' && ((_node_sx2 = node.sx) == null ? void 0 : _node_sx2['fontFamily']) !== undefined) fonts.push(id);
|
|
2384
2396
|
if (node.componentId === 'image') {
|
|
2385
2397
|
var _node_props6;
|
|
@@ -3277,11 +3289,18 @@ const FORM_SECTION_NAME = /\bform\b|\bsign[- ]?up\b|\bsubscribe\b|\bnewsletter\b
|
|
|
3277
3289
|
* creation before the rules read the plan, when exactly one creation matches
|
|
3278
3290
|
* it with case, spaces and punctuation ignored. Two that match, or none, are
|
|
3279
3291
|
* left for the rules to ask about.
|
|
3280
|
-
|
|
3292
|
+
*
|
|
3293
|
+
* So is a creation named without its prefix — `Contact form` in a section's
|
|
3294
|
+
* uses for the "Contact form" the plan creates — where the name is no record
|
|
3295
|
+
* the site has (AGL-3660). A re-asked plan told to place its form "by id"
|
|
3296
|
+
* can answer with the form's bare name, which is no reference at all.
|
|
3297
|
+
*/ export function aiSettlePlanRefs(plan, inventory = null) {
|
|
3281
3298
|
if (!plan.create.length) return plan;
|
|
3299
|
+
const kinds = inventoryKinds(inventory);
|
|
3282
3300
|
const resolve = (ref)=>{
|
|
3283
|
-
if (!
|
|
3284
|
-
const key = creationKey(ref.slice('new:'.length));
|
|
3301
|
+
if (!ref || kinds.has(ref) || aiPlanCreateFor(plan, ref)) return ref;
|
|
3302
|
+
const key = creationKey(isAiPlanNewRef(ref) ? ref.slice('new:'.length) : ref);
|
|
3303
|
+
if (!key) return ref;
|
|
3285
3304
|
const matches = plan.create.filter((entry)=>creationKey(entry.name) === key);
|
|
3286
3305
|
return matches.length === 1 ? `new:${matches[0].name}` : ref;
|
|
3287
3306
|
};
|
|
@@ -3357,6 +3376,140 @@ const FORM_SECTION_NAME = /\bform\b|\bsign[- ]?up\b|\bsubscribe\b|\bnewsletter\b
|
|
|
3357
3376
|
}))
|
|
3358
3377
|
});
|
|
3359
3378
|
}
|
|
3379
|
+
/** Every saved form a plan could place, the plan's own creations first. */ function planFormChoices(plan, inventory) {
|
|
3380
|
+
var _ref;
|
|
3381
|
+
return [
|
|
3382
|
+
...plan.create.filter((entry)=>entry.kind === 'form').map((entry)=>({
|
|
3383
|
+
ref: `new:${entry.name}`,
|
|
3384
|
+
name: entry.name,
|
|
3385
|
+
created: true
|
|
3386
|
+
})),
|
|
3387
|
+
...((_ref = inventory == null ? void 0 : inventory.forms) != null ? _ref : []).map((form)=>({
|
|
3388
|
+
ref: form.id,
|
|
3389
|
+
name: form.name,
|
|
3390
|
+
created: false
|
|
3391
|
+
}))
|
|
3392
|
+
];
|
|
3393
|
+
}
|
|
3394
|
+
/** The form a plan's own words would name for a section's reference, so placements count alike. */ function formChoiceKey(ref, plan) {
|
|
3395
|
+
const created = isAiPlanNewRef(ref) ? aiPlanCreateFor(plan, ref) : undefined;
|
|
3396
|
+
return created ? `new:${created.name}` : ref;
|
|
3397
|
+
}
|
|
3398
|
+
/**
|
|
3399
|
+
* The form a section that collects answers places: the one whose name shares
|
|
3400
|
+
* the most words with the section's ("Commission inquiry" → "Commission
|
|
3401
|
+
* inquiry form"), then the one the plan already places most, then the plan's
|
|
3402
|
+
* own creation over a form the site has. `null` when there is none.
|
|
3403
|
+
*/ function bestPlanForm(section, choices, placed) {
|
|
3404
|
+
var _ranked_;
|
|
3405
|
+
const words = new Set(nameTokens(section.name).filter((word)=>word !== 'form'));
|
|
3406
|
+
const score = (choice)=>nameTokens(choice.name).filter((word)=>word !== 'form' && words.has(word)).length;
|
|
3407
|
+
const ranked = [
|
|
3408
|
+
...choices
|
|
3409
|
+
].sort((a, b)=>{
|
|
3410
|
+
var _placed_get, _placed_get1;
|
|
3411
|
+
return score(b) - score(a) || ((_placed_get = placed.get(b.ref)) != null ? _placed_get : 0) - ((_placed_get1 = placed.get(a.ref)) != null ? _placed_get1 : 0) || Number(b.created) - Number(a.created);
|
|
3412
|
+
});
|
|
3413
|
+
return (_ranked_ = ranked[0]) != null ? _ranked_ : null;
|
|
3414
|
+
}
|
|
3415
|
+
/** The contact form a plan is given when a section collects answers and the plan makes no form. */ export const AI_PLAN_SETTLED_FORM = {
|
|
3416
|
+
name: 'Contact form',
|
|
3417
|
+
why: 'Visitors need a way to reach the owner, and the site has no saved form for it.',
|
|
3418
|
+
fields: [
|
|
3419
|
+
'name',
|
|
3420
|
+
'email',
|
|
3421
|
+
'message'
|
|
3422
|
+
]
|
|
3423
|
+
};
|
|
3424
|
+
/**
|
|
3425
|
+
* A section that collects answers and places no saved form is given one
|
|
3426
|
+
* before the rules read the plan (AGL-3660), rather than failing the build.
|
|
3427
|
+
* A production guided start for a ceramic artist's portfolio (2026-10-09,
|
|
3428
|
+
* five pages, 94 credits refunded) planned a section that asks for
|
|
3429
|
+
* commissions on its work page without placing the contact form the plan
|
|
3430
|
+
* made for its Contact page; the answer and its re-ask both did, and rule 3
|
|
3431
|
+
* failed the whole site for one section.
|
|
3432
|
+
*
|
|
3433
|
+
* The section places, in order of preference:
|
|
3434
|
+
* 1. the form whose name best matches it, among those the plan creates and
|
|
3435
|
+
* the site has, preferring one the plan already places — so a guided
|
|
3436
|
+
* start, which makes one form, places that same form again rather than a
|
|
3437
|
+
* second one;
|
|
3438
|
+
* 2. where there is no form at all and this job may create one, a form the
|
|
3439
|
+
* plan now creates: named for the `new:` reference the section already
|
|
3440
|
+
* holds when it names a form it never declared, the contact form
|
|
3441
|
+
* otherwise.
|
|
3442
|
+
*
|
|
3443
|
+
* Nothing is taken out of the plan: the section keeps its name, its other
|
|
3444
|
+
* uses and its items. Where the site has no form and the job may create none,
|
|
3445
|
+
* rule 3 asks for nothing (AGL-3596), and neither does this. A section whose
|
|
3446
|
+
* uses are already full is left for the rule to ask about.
|
|
3447
|
+
*/ export function aiSettlePlanForms(plan, inventory, capabilities = null) {
|
|
3448
|
+
if (!aiPlanCanPlaceForm(inventory, capabilities)) return plan;
|
|
3449
|
+
const kinds = inventoryKinds(inventory);
|
|
3450
|
+
const unbound = (current)=>sectionPaths(current).filter(({ section })=>FORM_SECTION_NAME.test(section.name) && section.uses.length < AI_BUILD_PLAN_LIMITS.uses && !section.uses.some((ref)=>refKind(ref, current, kinds) === 'form'));
|
|
3451
|
+
const first = unbound(plan);
|
|
3452
|
+
if (!first.length) return plan;
|
|
3453
|
+
let next = plan;
|
|
3454
|
+
if (!planFormChoices(next, inventory).length) {
|
|
3455
|
+
if (capabilities && !capabilities.create.form.allowed) return plan;
|
|
3456
|
+
// A form the section already names and the plan never declared is the form it meant.
|
|
3457
|
+
const named = first.flatMap(({ section })=>section.uses).find((ref)=>isAiPlanNewRef(ref) && !aiPlanCreateFor(next, ref) && !aiPlanItemFor(next, ref));
|
|
3458
|
+
const taken = new Set(next.create.map((entry)=>entry.name.toLowerCase()));
|
|
3459
|
+
const name = named ? named.slice('new:'.length).trim() : taken.has(AI_PLAN_SETTLED_FORM.name.toLowerCase()) ? 'Get in touch form' : AI_PLAN_SETTLED_FORM.name;
|
|
3460
|
+
next = _extends({}, next, {
|
|
3461
|
+
create: [
|
|
3462
|
+
...next.create,
|
|
3463
|
+
{
|
|
3464
|
+
kind: 'form',
|
|
3465
|
+
name,
|
|
3466
|
+
why: AI_PLAN_SETTLED_FORM.why,
|
|
3467
|
+
duplicateOf: null,
|
|
3468
|
+
fields: [
|
|
3469
|
+
...AI_PLAN_SETTLED_FORM.fields
|
|
3470
|
+
]
|
|
3471
|
+
}
|
|
3472
|
+
]
|
|
3473
|
+
});
|
|
3474
|
+
}
|
|
3475
|
+
const choices = planFormChoices(next, inventory);
|
|
3476
|
+
const placed = new Map();
|
|
3477
|
+
for (const { section } of sectionPaths(next)){
|
|
3478
|
+
for (const ref of section.uses){
|
|
3479
|
+
var _placed_get;
|
|
3480
|
+
if (refKind(ref, next, kinds) !== 'form') continue;
|
|
3481
|
+
const key = formChoiceKey(ref, next);
|
|
3482
|
+
placed.set(key, ((_placed_get = placed.get(key)) != null ? _placed_get : 0) + 1);
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
const settle = new Map();
|
|
3486
|
+
for (const { section, path } of unbound(next)){
|
|
3487
|
+
const form = bestPlanForm(section, choices, placed);
|
|
3488
|
+
if (form) settle.set(path, form.ref);
|
|
3489
|
+
}
|
|
3490
|
+
if (!settle.size) return next;
|
|
3491
|
+
return _extends({}, next, {
|
|
3492
|
+
screens: next.screens.map((screen, screenIndex)=>_extends({}, screen, {
|
|
3493
|
+
sections: screen.sections.map((section, sectionIndex)=>{
|
|
3494
|
+
const ref = settle.get(`screens[${screenIndex}].sections[${sectionIndex}]`);
|
|
3495
|
+
if (!ref) return section;
|
|
3496
|
+
// The undeclared reference the form was named for now names it; any other stays.
|
|
3497
|
+
const uses = section.uses.some((use)=>formChoiceKey(use, next) === ref) ? section.uses : [
|
|
3498
|
+
...section.uses,
|
|
3499
|
+
ref
|
|
3500
|
+
];
|
|
3501
|
+
return _extends({}, section, {
|
|
3502
|
+
uses
|
|
3503
|
+
});
|
|
3504
|
+
})
|
|
3505
|
+
}))
|
|
3506
|
+
});
|
|
3507
|
+
}
|
|
3508
|
+
/** The forms a section that collects answers can place, as the re-ask names them. */ function planFormChoicesText(plan, inventory) {
|
|
3509
|
+
const choices = planFormChoices(plan, inventory);
|
|
3510
|
+
if (!choices.length) return 'Plan one in create as a form, and put new:<its name> in the section\'s uses.';
|
|
3511
|
+
return `Put one of these in the section's uses: ${choices.map((choice)=>choice.created ? choice.ref : `${choice.ref} (${choice.name})`).join(', ')}.`;
|
|
3512
|
+
}
|
|
3360
3513
|
/**
|
|
3361
3514
|
* Rule 2 (plan): every screen declares a layout, no section is a copy of a
|
|
3362
3515
|
* layout region, and no section places a layout, which frames a whole screen
|
|
@@ -3417,6 +3570,7 @@ const FORM_SECTION_NAME = /\bform\b|\bsign[- ]?up\b|\bsubscribe\b|\bnewsletter\b
|
|
|
3417
3570
|
rule: 3,
|
|
3418
3571
|
code: 'plan-form-not-placed',
|
|
3419
3572
|
message: 'A section collects answers without a form. Reuse a form from the Forms page, or plan one there, and place it by id.',
|
|
3573
|
+
detail: planFormChoicesText(plan, inventory),
|
|
3420
3574
|
paths: unbound.map((entry)=>entry.path)
|
|
3421
3575
|
}
|
|
3422
3576
|
] : [];
|