@aglyn/plugins-ai 1.0.0-beta.237 → 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.
Files changed (128) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-credits-prompt.component.d.ts +11 -4
  3. package/src/lib/components/ai-credits-prompt.component.js +84 -34
  4. package/src/lib/components/ai-credits-prompt.component.js.map +1 -1
  5. package/src/lib/components/ai-job-cancel.component.d.ts +38 -0
  6. package/src/lib/components/ai-job-cancel.component.js +156 -0
  7. package/src/lib/components/ai-job-cancel.component.js.map +1 -0
  8. package/src/lib/components/ai-job-plan.component.js +12 -1
  9. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  10. package/src/lib/components/ai-jobs-list-page.component.js +141 -72
  11. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  12. package/src/lib/components/ai-site-build-page.component.js +22 -2
  13. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  14. package/src/lib/components/ai-usage-strip.component.d.ts +19 -2
  15. package/src/lib/components/ai-usage-strip.component.js +40 -9
  16. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  17. package/src/lib/components/assist-jobs-drawer.component.js +12 -16
  18. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  19. package/src/lib/components/assist-panel.component.js +87 -51
  20. package/src/lib/components/assist-panel.component.js.map +1 -1
  21. package/src/lib/jobs/ai-job-build-step.js +4 -1
  22. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  23. package/src/lib/jobs/ai-job-cancel.d.ts +71 -0
  24. package/src/lib/jobs/ai-job-cancel.js +102 -0
  25. package/src/lib/jobs/ai-job-cancel.js.map +1 -0
  26. package/src/lib/jobs/ai-job-component-step.js +4 -1
  27. package/src/lib/jobs/ai-job-component-step.js.map +1 -1
  28. package/src/lib/jobs/ai-job-drafts.js +2 -0
  29. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  30. package/src/lib/jobs/ai-job-form-step.js +4 -1
  31. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  32. package/src/lib/jobs/ai-job-layout-language.d.ts +2 -0
  33. package/src/lib/jobs/ai-job-layout-language.js +13 -1
  34. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  35. package/src/lib/jobs/ai-job-layout-step.js +6 -1
  36. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-page-language.js +8 -0
  38. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  39. package/src/lib/jobs/ai-job-page-step.js +12 -1
  40. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  41. package/src/lib/jobs/ai-job-plan-step.d.ts +20 -1
  42. package/src/lib/jobs/ai-job-plan-step.js +49 -9
  43. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  44. package/src/lib/jobs/ai-job-site-content.d.ts +15 -0
  45. package/src/lib/jobs/ai-job-site-content.js +66 -7
  46. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  47. package/src/lib/jobs/ai-job-site-step.js +4 -1
  48. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  49. package/src/lib/jobs/ai-job-template-step.js +4 -1
  50. package/src/lib/jobs/ai-job-template-step.js.map +1 -1
  51. package/src/lib/jobs/ai-jobs.d.ts +37 -12
  52. package/src/lib/jobs/ai-jobs.js +184 -28
  53. package/src/lib/jobs/ai-jobs.js.map +1 -1
  54. package/src/lib/jobs/ai-layout-stock-photos.d.ts +143 -70
  55. package/src/lib/jobs/ai-layout-stock-photos.js +649 -176
  56. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  57. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.d.ts +30 -0
  58. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js +220 -0
  59. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js.map +1 -0
  60. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.d.ts +27 -0
  61. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js +151 -0
  62. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js.map +1 -0
  63. package/src/lib/layout-language/ai-layout-compiler.d.ts +6 -0
  64. package/src/lib/layout-language/ai-layout-compiler.js +304 -8
  65. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  66. package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
  67. package/src/lib/layout-language/ai-layout-design.js +5 -3
  68. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  69. package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
  70. package/src/lib/layout-language/ai-layout-frame.js +58 -3
  71. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  72. package/src/lib/layout-language/ai-layout-listings.d.ts +13 -1
  73. package/src/lib/layout-language/ai-layout-listings.js +61 -46
  74. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  75. package/src/lib/layout-language/ai-layout-pictures.d.ts +8 -0
  76. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  77. package/src/lib/layout-language/ai-layout-storefront.d.ts +82 -0
  78. package/src/lib/layout-language/ai-layout-storefront.js +223 -0
  79. package/src/lib/layout-language/ai-layout-storefront.js.map +1 -0
  80. package/src/lib/model/ai-build-job.d.ts +9 -1
  81. package/src/lib/model/ai-build-job.js +19 -5
  82. package/src/lib/model/ai-build-job.js.map +1 -1
  83. package/src/lib/model/ai-build-progress.d.ts +2 -2
  84. package/src/lib/model/ai-build-progress.js +6 -4
  85. package/src/lib/model/ai-build-progress.js.map +1 -1
  86. package/src/lib/model/ai-credit-estimate.d.ts +69 -2
  87. package/src/lib/model/ai-credit-estimate.js +91 -3
  88. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  89. package/src/lib/model/ai-job-cancel-copy.d.ts +44 -0
  90. package/src/lib/model/ai-job-cancel-copy.js +53 -0
  91. package/src/lib/model/ai-job-cancel-copy.js.map +1 -0
  92. package/src/lib/model/ai-job-failure-copy.d.ts +4 -1
  93. package/src/lib/model/ai-job-failure-copy.js +8 -3
  94. package/src/lib/model/ai-job-failure-copy.js.map +1 -1
  95. package/src/lib/model/ai-jobs.types.d.ts +20 -0
  96. package/src/lib/model/ai-jobs.types.js.map +1 -1
  97. package/src/lib/model/ai-site-build-progress.js +7 -4
  98. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  99. package/src/lib/model/ai-site-job.d.ts +19 -2
  100. package/src/lib/model/ai-site-job.js +29 -8
  101. package/src/lib/model/ai-site-job.js.map +1 -1
  102. package/src/lib/model/ai-site-kinds.d.ts +4 -1
  103. package/src/lib/model/ai-site-kinds.js +34 -6
  104. package/src/lib/model/ai-site-kinds.js.map +1 -1
  105. package/src/lib/runtime/ai-doctrine-validators.d.ts +37 -1
  106. package/src/lib/runtime/ai-doctrine-validators.js +151 -6
  107. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  108. package/src/lib/runtime/ai-doctrine.js +4 -3
  109. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  110. package/src/lib/runtime/ai-palette.generated.js +53 -7
  111. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  112. package/src/lib/server/ai-jobs-cancel.d.ts +13 -5
  113. package/src/lib/server/ai-jobs-cancel.js +27 -9
  114. package/src/lib/server/ai-jobs-cancel.js.map +1 -1
  115. package/src/lib/server/ai-jobs-gate.d.ts +12 -2
  116. package/src/lib/server/ai-jobs-gate.js +20 -9
  117. package/src/lib/server/ai-jobs-gate.js.map +1 -1
  118. package/src/lib/usage/ai-usage-meter.d.ts +2 -1
  119. package/src/lib/usage/ai-usage-meter.js +17 -1
  120. package/src/lib/usage/ai-usage-meter.js.map +1 -1
  121. package/src/lib/usage/ai-usage-wire.d.ts +6 -0
  122. package/src/lib/usage/ai-usage-wire.js.map +1 -1
  123. package/src/lib/usage/assist-usage.d.ts +7 -0
  124. package/src/lib/usage/assist-usage.js +14 -4
  125. package/src/lib/usage/assist-usage.js.map +1 -1
  126. package/src/lib/usage/free-ai-credits-left.d.ts +7 -0
  127. package/src/lib/usage/free-ai-credits-left.js +4 -2
  128. 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
- return (node == null ? void 0 : node.componentId) === 'muiStack' && first !== undefined && ((_tree_nodes_first = tree.nodes[first]) == null ? void 0 : _tree_nodes_first.componentId) === 'image';
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;
@@ -3286,11 +3289,18 @@ const FORM_SECTION_NAME = /\bform\b|\bsign[- ]?up\b|\bsubscribe\b|\bnewsletter\b
3286
3289
  * creation before the rules read the plan, when exactly one creation matches
3287
3290
  * it with case, spaces and punctuation ignored. Two that match, or none, are
3288
3291
  * left for the rules to ask about.
3289
- */ export function aiSettlePlanRefs(plan) {
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) {
3290
3298
  if (!plan.create.length) return plan;
3299
+ const kinds = inventoryKinds(inventory);
3291
3300
  const resolve = (ref)=>{
3292
- if (!isAiPlanNewRef(ref) || aiPlanCreateFor(plan, ref)) return ref;
3293
- 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;
3294
3304
  const matches = plan.create.filter((entry)=>creationKey(entry.name) === key);
3295
3305
  return matches.length === 1 ? `new:${matches[0].name}` : ref;
3296
3306
  };
@@ -3366,6 +3376,140 @@ const FORM_SECTION_NAME = /\bform\b|\bsign[- ]?up\b|\bsubscribe\b|\bnewsletter\b
3366
3376
  }))
3367
3377
  });
3368
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
+ }
3369
3513
  /**
3370
3514
  * Rule 2 (plan): every screen declares a layout, no section is a copy of a
3371
3515
  * layout region, and no section places a layout, which frames a whole screen
@@ -3426,6 +3570,7 @@ const FORM_SECTION_NAME = /\bform\b|\bsign[- ]?up\b|\bsubscribe\b|\bnewsletter\b
3426
3570
  rule: 3,
3427
3571
  code: 'plan-form-not-placed',
3428
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),
3429
3574
  paths: unbound.map((entry)=>entry.path)
3430
3575
  }
3431
3576
  ] : [];