@aglyn/plugins-ai 1.0.0-beta.229 → 1.0.0-beta.230

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 (248) hide show
  1. package/README.md +3 -3
  2. package/package.json +17 -17
  3. package/src/lib/activity/ai-activity-actions.js +7 -1
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/components/ai-automation-zones.d.ts +38 -0
  6. package/src/lib/components/ai-automation-zones.js +5 -1
  7. package/src/lib/components/ai-automation-zones.js.map +1 -1
  8. package/src/lib/components/ai-brief-dialog.component.d.ts +20 -2
  9. package/src/lib/components/ai-brief-dialog.component.js +87 -8
  10. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  11. package/src/lib/components/ai-campaign-create.component.d.ts +54 -0
  12. package/src/lib/components/ai-campaign-create.component.js +270 -0
  13. package/src/lib/components/ai-campaign-create.component.js.map +1 -0
  14. package/src/lib/components/ai-describe-automation.component.d.ts +8 -3
  15. package/src/lib/components/ai-describe-automation.component.js +15 -6
  16. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  17. package/src/lib/components/ai-describe-button.component.d.ts +20 -8
  18. package/src/lib/components/ai-describe-button.component.js +19 -36
  19. package/src/lib/components/ai-describe-button.component.js.map +1 -1
  20. package/src/lib/components/ai-describe-email.component.d.ts +31 -0
  21. package/src/lib/components/ai-describe-email.component.js +22 -0
  22. package/src/lib/components/ai-describe-email.component.js.map +1 -0
  23. package/src/lib/components/ai-describe-org-automation.component.d.ts +36 -0
  24. package/src/lib/components/ai-describe-org-automation.component.js +238 -0
  25. package/src/lib/components/ai-describe-org-automation.component.js.map +1 -0
  26. package/src/lib/components/ai-experiment-cards.component.d.ts +9 -1
  27. package/src/lib/components/ai-experiment-cards.component.js +104 -18
  28. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  29. package/src/lib/components/ai-explain-automation.component.js +1 -5
  30. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  31. package/src/lib/components/ai-funnel-zones.component.d.ts +61 -0
  32. package/src/lib/components/ai-funnel-zones.component.js +178 -0
  33. package/src/lib/components/ai-funnel-zones.component.js.map +1 -0
  34. package/src/lib/components/ai-insight-card.component.d.ts +42 -0
  35. package/src/lib/components/ai-insight-card.component.js +137 -0
  36. package/src/lib/components/ai-insight-card.component.js.map +1 -0
  37. package/src/lib/components/ai-insight-dialog.component.d.ts +6 -0
  38. package/src/lib/components/ai-insight-dialog.component.js +9 -3
  39. package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
  40. package/src/lib/components/ai-job-plan.component.d.ts +7 -2
  41. package/src/lib/components/ai-job-plan.component.js +35 -5
  42. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  43. package/src/lib/components/ai-job-requests.d.ts +17 -1
  44. package/src/lib/components/ai-job-requests.js +39 -3
  45. package/src/lib/components/ai-job-requests.js.map +1 -1
  46. package/src/lib/components/ai-logic-zones.d.ts +64 -0
  47. package/src/lib/components/ai-logic-zones.js +18 -0
  48. package/src/lib/components/ai-logic-zones.js.map +1 -0
  49. package/src/lib/components/ai-logic.component.d.ts +77 -0
  50. package/src/lib/components/ai-logic.component.js +553 -0
  51. package/src/lib/components/ai-logic.component.js.map +1 -0
  52. package/src/lib/components/ai-marketing-insight.component.d.ts +30 -0
  53. package/src/lib/components/ai-marketing-insight.component.js +80 -0
  54. package/src/lib/components/ai-marketing-insight.component.js.map +1 -0
  55. package/src/lib/components/ai-media-create.component.d.ts +55 -0
  56. package/src/lib/components/ai-media-create.component.js +433 -0
  57. package/src/lib/components/ai-media-create.component.js.map +1 -0
  58. package/src/lib/components/ai-overlay-cards.component.d.ts +83 -0
  59. package/src/lib/components/ai-overlay-cards.component.js +505 -0
  60. package/src/lib/components/ai-overlay-cards.component.js.map +1 -0
  61. package/src/lib/components/ai-product-zones.d.ts +5 -0
  62. package/src/lib/components/ai-product-zones.js +1 -1
  63. package/src/lib/components/ai-product-zones.js.map +1 -1
  64. package/src/lib/components/ai-products-create-button.component.d.ts +42 -0
  65. package/src/lib/components/ai-products-create-button.component.js +67 -0
  66. package/src/lib/components/ai-products-create-button.component.js.map +1 -0
  67. package/src/lib/components/ai-products-hub-card.component.js +100 -81
  68. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  69. package/src/lib/components/ai-revise-automation.component.d.ts +16 -0
  70. package/src/lib/components/ai-revise-automation.component.js +207 -0
  71. package/src/lib/components/ai-revise-automation.component.js.map +1 -0
  72. package/src/lib/components/ai-site-build-page.component.js +140 -16
  73. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  74. package/src/lib/components/ai-upsell-dialog.component.d.ts +53 -0
  75. package/src/lib/components/ai-upsell-dialog.component.js +177 -0
  76. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -0
  77. package/src/lib/components/assist-build-card.component.d.ts +42 -0
  78. package/src/lib/components/assist-build-card.component.js +183 -0
  79. package/src/lib/components/assist-build-card.component.js.map +1 -0
  80. package/src/lib/components/assist-edit-canvas.d.ts +2 -0
  81. package/src/lib/components/assist-edit-canvas.js +56 -1
  82. package/src/lib/components/assist-edit-canvas.js.map +1 -1
  83. package/src/lib/components/assist-jobs-drawer.component.js +48 -3
  84. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  85. package/src/lib/components/assist-panel.component.js +51 -2
  86. package/src/lib/components/assist-panel.component.js.map +1 -1
  87. package/src/lib/components/use-ai-job-run.d.ts +3 -14
  88. package/src/lib/components/use-ai-job-run.js +0 -50
  89. package/src/lib/components/use-ai-job-run.js.map +1 -1
  90. package/src/lib/jobs/ai-build-capabilities.d.ts +58 -0
  91. package/src/lib/jobs/ai-build-capabilities.js +256 -0
  92. package/src/lib/jobs/ai-build-capabilities.js.map +1 -0
  93. package/src/lib/jobs/ai-build-unit-outcome.d.ts +52 -0
  94. package/src/lib/jobs/ai-build-unit-outcome.js +138 -0
  95. package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -0
  96. package/src/lib/jobs/ai-job-build-step.d.ts +144 -0
  97. package/src/lib/jobs/ai-job-build-step.js +657 -0
  98. package/src/lib/jobs/ai-job-build-step.js.map +1 -0
  99. package/src/lib/jobs/ai-job-draft-ids.js +9 -1
  100. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  101. package/src/lib/jobs/ai-job-drafts.d.ts +14 -4
  102. package/src/lib/jobs/ai-job-drafts.js +25 -4
  103. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  104. package/src/lib/jobs/ai-job-logic-step.d.ts +107 -0
  105. package/src/lib/jobs/ai-job-logic-step.js +395 -0
  106. package/src/lib/jobs/ai-job-logic-step.js.map +1 -0
  107. package/src/lib/jobs/ai-job-overlay-copy.d.ts +70 -0
  108. package/src/lib/jobs/ai-job-overlay-copy.js +178 -0
  109. package/src/lib/jobs/ai-job-overlay-copy.js.map +1 -0
  110. package/src/lib/jobs/ai-job-page-step.js +6 -6
  111. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  112. package/src/lib/jobs/ai-job-plan-step.d.ts +26 -1
  113. package/src/lib/jobs/ai-job-plan-step.js +79 -11
  114. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  115. package/src/lib/jobs/ai-job-site-step.d.ts +15 -1
  116. package/src/lib/jobs/ai-job-site-step.js +200 -58
  117. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  118. package/src/lib/jobs/ai-job-text-step.d.ts +22 -1
  119. package/src/lib/jobs/ai-job-text-step.js +12 -0
  120. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  121. package/src/lib/jobs/ai-job-workflow-step.d.ts +54 -4
  122. package/src/lib/jobs/ai-job-workflow-step.js +318 -13
  123. package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
  124. package/src/lib/jobs/ai-jobs.d.ts +80 -2
  125. package/src/lib/jobs/ai-jobs.js +377 -13
  126. package/src/lib/jobs/ai-jobs.js.map +1 -1
  127. package/src/lib/jobs/ai-logic-records.d.ts +40 -0
  128. package/src/lib/jobs/ai-logic-records.js +55 -0
  129. package/src/lib/jobs/ai-logic-records.js.map +1 -0
  130. package/src/lib/jobs/ai-site-publish.d.ts +7 -0
  131. package/src/lib/jobs/ai-site-publish.js +9 -0
  132. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  133. package/src/lib/jobs/ai-workflow-records.d.ts +4 -0
  134. package/src/lib/jobs/ai-workflow-records.js +40 -1
  135. package/src/lib/jobs/ai-workflow-records.js.map +1 -1
  136. package/src/lib/model/ai-automation-draft.d.ts +18 -0
  137. package/src/lib/model/ai-automation-draft.js +86 -0
  138. package/src/lib/model/ai-automation-draft.js.map +1 -1
  139. package/src/lib/model/ai-build-job.d.ts +201 -0
  140. package/src/lib/model/ai-build-job.js +471 -0
  141. package/src/lib/model/ai-build-job.js.map +1 -0
  142. package/src/lib/model/ai-build-plan.d.ts +61 -0
  143. package/src/lib/model/ai-build-plan.js +154 -9
  144. package/src/lib/model/ai-build-plan.js.map +1 -1
  145. package/src/lib/model/ai-build-progress.d.ts +56 -0
  146. package/src/lib/model/ai-build-progress.js +128 -0
  147. package/src/lib/model/ai-build-progress.js.map +1 -0
  148. package/src/lib/model/ai-insight.d.ts +20 -10
  149. package/src/lib/model/ai-insight.js +72 -9
  150. package/src/lib/model/ai-insight.js.map +1 -1
  151. package/src/lib/model/ai-job-activity.js +3 -1
  152. package/src/lib/model/ai-job-activity.js.map +1 -1
  153. package/src/lib/model/ai-jobs.types.d.ts +80 -2
  154. package/src/lib/model/ai-jobs.types.js +3 -1
  155. package/src/lib/model/ai-jobs.types.js.map +1 -1
  156. package/src/lib/model/ai-logic-job.d.ts +108 -0
  157. package/src/lib/model/ai-logic-job.js +327 -0
  158. package/src/lib/model/ai-logic-job.js.map +1 -0
  159. package/src/lib/model/ai-media-credits.d.ts +52 -0
  160. package/src/lib/model/ai-media-credits.js +61 -0
  161. package/src/lib/model/ai-media-credits.js.map +1 -0
  162. package/src/lib/model/ai-overlay-copy.d.ts +114 -0
  163. package/src/lib/model/ai-overlay-copy.js +175 -0
  164. package/src/lib/model/ai-overlay-copy.js.map +1 -0
  165. package/src/lib/model/ai-site-build-progress.d.ts +20 -3
  166. package/src/lib/model/ai-site-build-progress.js +170 -10
  167. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  168. package/src/lib/model/ai-svg.d.ts +69 -0
  169. package/src/lib/model/ai-svg.js +265 -0
  170. package/src/lib/model/ai-svg.js.map +1 -0
  171. package/src/lib/model/ai-usage-by-user.d.ts +3 -2
  172. package/src/lib/model/ai-usage-by-user.js.map +1 -1
  173. package/src/lib/model/ai-workflow-job.d.ts +92 -4
  174. package/src/lib/model/ai-workflow-job.js +124 -7
  175. package/src/lib/model/ai-workflow-job.js.map +1 -1
  176. package/src/lib/model/assist-build.d.ts +58 -0
  177. package/src/lib/model/assist-build.js +52 -0
  178. package/src/lib/model/assist-build.js.map +1 -0
  179. package/src/lib/model/assist-edit.d.ts +48 -3
  180. package/src/lib/model/assist-edit.js +48 -4
  181. package/src/lib/model/assist-edit.js.map +1 -1
  182. package/src/lib/plugin-config.js +2 -0
  183. package/src/lib/plugin-config.js.map +1 -1
  184. package/src/lib/plugin.js +240 -11
  185. package/src/lib/plugin.js.map +1 -1
  186. package/src/lib/providers/catalog.d.ts +81 -4
  187. package/src/lib/providers/catalog.js +80 -9
  188. package/src/lib/providers/catalog.js.map +1 -1
  189. package/src/lib/providers/image-contract.d.ts +116 -0
  190. package/src/lib/providers/image-contract.js +74 -0
  191. package/src/lib/providers/image-contract.js.map +1 -0
  192. package/src/lib/providers/model-choice.js +11 -0
  193. package/src/lib/providers/model-choice.js.map +1 -1
  194. package/src/lib/providers/routing.js +18 -0
  195. package/src/lib/providers/routing.js.map +1 -1
  196. package/src/lib/providers/vertex-image.d.ts +121 -0
  197. package/src/lib/providers/vertex-image.js +312 -0
  198. package/src/lib/providers/vertex-image.js.map +1 -0
  199. package/src/lib/runtime/ai-doctrine-validators.d.ts +19 -0
  200. package/src/lib/runtime/ai-doctrine-validators.js +89 -0
  201. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  202. package/src/lib/runtime/ai-doctrine.js +11 -5
  203. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  204. package/src/lib/runtime/ai-eval-live.js +4 -0
  205. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  206. package/src/lib/runtime/ai-eval.d.ts +15 -1
  207. package/src/lib/runtime/ai-eval.js +52 -1
  208. package/src/lib/runtime/ai-eval.js.map +1 -1
  209. package/src/lib/runtime/experiment-variant-copy.d.ts +34 -0
  210. package/src/lib/runtime/experiment-variant-copy.js +102 -0
  211. package/src/lib/runtime/experiment-variant-copy.js.map +1 -0
  212. package/src/lib/server/ai-experiment-versions.d.ts +51 -0
  213. package/src/lib/server/ai-experiment-versions.js +270 -0
  214. package/src/lib/server/ai-experiment-versions.js.map +1 -0
  215. package/src/lib/server/ai-jobs-resume.js +74 -4
  216. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  217. package/src/lib/server/ai-media-image.d.ts +66 -0
  218. package/src/lib/server/ai-media-image.js +521 -0
  219. package/src/lib/server/ai-media-image.js.map +1 -0
  220. package/src/lib/server/ai-media-svg.d.ts +91 -0
  221. package/src/lib/server/ai-media-svg.js +186 -0
  222. package/src/lib/server/ai-media-svg.js.map +1 -0
  223. package/src/lib/server/assist-build.d.ts +54 -0
  224. package/src/lib/server/assist-build.js +121 -0
  225. package/src/lib/server/assist-build.js.map +1 -0
  226. package/src/lib/server/assist-chat.js +108 -12
  227. package/src/lib/server/assist-chat.js.map +1 -1
  228. package/src/lib/server/assist-edit.js +213 -3
  229. package/src/lib/server/assist-edit.js.map +1 -1
  230. package/src/lib/server/assist-view-context.js +242 -4
  231. package/src/lib/server/assist-view-context.js.map +1 -1
  232. package/src/lib/server.js +26 -0
  233. package/src/lib/server.js.map +1 -1
  234. package/src/lib/tools/ai-logic-tool.d.ts +27 -0
  235. package/src/lib/tools/ai-logic-tool.js +207 -0
  236. package/src/lib/tools/ai-logic-tool.js.map +1 -0
  237. package/src/lib/tools/ai-overlay-tool.d.ts +35 -0
  238. package/src/lib/tools/ai-overlay-tool.js +107 -0
  239. package/src/lib/tools/ai-overlay-tool.js.map +1 -0
  240. package/src/lib/tools/ai-workflow-tool.d.ts +11 -0
  241. package/src/lib/tools/ai-workflow-tool.js +31 -0
  242. package/src/lib/tools/ai-workflow-tool.js.map +1 -1
  243. package/src/lib/usage/assist-job-refund.d.ts +28 -1
  244. package/src/lib/usage/assist-job-refund.js +44 -9
  245. package/src/lib/usage/assist-job-refund.js.map +1 -1
  246. package/src/lib/usage/assist-usage.d.ts +5 -0
  247. package/src/lib/usage/assist-usage.js +2 -0
  248. package/src/lib/usage/assist-usage.js.map +1 -1
@@ -0,0 +1,175 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ /**
18
+ * Overlay copy by AI (AGL-3603): what a `text` job asked for an announcement
19
+ * bar's or a popup's copy answers, in the shape the step writes and the
20
+ * console reads. Pure data, so the widgets and the step share one reading.
21
+ *
22
+ * The overlay is the marketing plugin's, and neither plugin imports the other:
23
+ * the limits and the trigger catalog are handed to the widget by the zone,
24
+ * and the widget passes them on as the job's inputs. The step then holds the
25
+ * answer to the TIGHTER of those and its own ceilings below, so a job started
26
+ * by anything else is still bounded, and drops a trigger the caller did not
27
+ * name rather than inventing one.
28
+ */ /** The `text` job's `inputs.task` that asks for overlay copy. */ export const AI_OVERLAY_COPY_TASK = 'overlay';
29
+ /** The step's own ceilings: no zone may widen a field past these. */ export const AI_OVERLAY_COPY_CEILINGS = {
30
+ name: 80,
31
+ text: 200,
32
+ headline: 120,
33
+ body: 600,
34
+ ctaLabel: 60
35
+ };
36
+ /** The triggers a popup may be proposed to open on, as the site runtime names them. */ export const AI_OVERLAY_TRIGGERS = [
37
+ 'delay',
38
+ 'scroll',
39
+ 'exit'
40
+ ];
41
+ /** The longest description of the current copy a job sends. */ export const AI_OVERLAY_CURRENT_MAX_CHARS = 1200;
42
+ const str = (value)=>typeof value === 'string' ? value.trim() : '';
43
+ /** The kind a job's inputs name, or `null`. */ export function aiOverlayKind(inputs) {
44
+ const kind = inputs == null ? void 0 : inputs['overlayKind'];
45
+ return kind === 'bar' || kind === 'popup' ? kind : null;
46
+ }
47
+ /** Whether a `text` job's inputs ask for overlay copy. */ export function aiOverlayCopyRequested(inputs) {
48
+ return (inputs == null ? void 0 : inputs['task']) === AI_OVERLAY_COPY_TASK;
49
+ }
50
+ /**
51
+ * The limit each field is held to: the caller's, where it named one, never
52
+ * past the step's ceiling. Inputs carry them as `limit_<field>`.
53
+ */ export function aiOverlayLimits(inputs) {
54
+ const limits = _extends({}, AI_OVERLAY_COPY_CEILINGS);
55
+ for (const field of Object.keys(limits)){
56
+ const asked = Number(inputs == null ? void 0 : inputs[`limit_${field}`]);
57
+ if (Number.isFinite(asked) && asked > 0) limits[field] = Math.min(limits[field], Math.floor(asked));
58
+ }
59
+ return limits;
60
+ }
61
+ /**
62
+ * The triggers the caller allows, with their ranges. Inputs carry them as
63
+ * `triggers` (`delay:0-120,scroll:1-100,exit`); an id the runtime does not
64
+ * know is dropped, and with no list a popup is proposed no trigger at all.
65
+ */ export function aiOverlayTriggerRanges(inputs) {
66
+ const ranges = new Map();
67
+ for (const part of str(inputs == null ? void 0 : inputs['triggers']).split(',')){
68
+ const [id, range] = part.trim().split(':');
69
+ if (!AI_OVERLAY_TRIGGERS.includes(id)) continue;
70
+ const [min, max] = String(range != null ? range : '').split('-').map(Number);
71
+ ranges.set(id, Number.isFinite(min) && Number.isFinite(max) && max >= min ? {
72
+ min,
73
+ max
74
+ } : null);
75
+ }
76
+ return ranges;
77
+ }
78
+ /** The zone's trigger catalog as the job's `triggers` input. */ export function aiOverlayTriggersInput(rules) {
79
+ return rules.filter((rule)=>AI_OVERLAY_TRIGGERS.includes(rule.id)).map((rule)=>rule.unit ? `${rule.id}:${rule.min}-${rule.max}` : rule.id).join(',');
80
+ }
81
+ /** The zone's limits as the job's `limit_<field>` inputs. */ export function aiOverlayLimitInputs(limits) {
82
+ const inputs = {};
83
+ for (const field of Object.keys(AI_OVERLAY_COPY_CEILINGS)){
84
+ const limit = limits[field];
85
+ if (typeof limit === 'number' && limit > 0) inputs[`limit_${field}`] = limit;
86
+ }
87
+ return inputs;
88
+ }
89
+ /** One field on one line, or a body with its paragraphs, cut to its limit. */ function fit(value, limit, multiline = false) {
90
+ const folded = multiline ? value.replace(/\r\n?/g, '\n').replace(/[^\S\n]+/g, ' ').replace(/\n{3,}/g, '\n\n') : value.replace(/\s+/g, ' ');
91
+ return folded.trim().slice(0, limit).trim();
92
+ }
93
+ /** Markup, a fence or a link: things a bar or a popup shows as typed. */ const MARKUP = /<\/?[a-z][^>]*>|```|\[[^\]]*\]\([^)]*\)|https?:\/\//i;
94
+ /**
95
+ * An answer held to the overlay's rules, or `null` when nothing usable is
96
+ * left: a bar with no text, a popup with no body, or copy carrying markup or
97
+ * a link. A field over its limit is cut rather than refused, and a trigger
98
+ * outside the caller's catalog is dropped, with each recorded as a finding.
99
+ */ export function checkAiOverlayCopy(answer, kind, limits, triggers) {
100
+ if (!answer) return null;
101
+ const findings = [];
102
+ const field = (name, multiline = false)=>{
103
+ const raw = answer[name];
104
+ const value = fit(raw, limits[name], multiline);
105
+ if (fit(raw, Number.MAX_SAFE_INTEGER, multiline).length > limits[name]) {
106
+ findings.push(`${name} cut to ${limits[name]} characters`);
107
+ }
108
+ return value;
109
+ };
110
+ const proposal = {
111
+ kind,
112
+ name: field('name'),
113
+ text: kind === 'bar' ? field('text') : '',
114
+ headline: kind === 'popup' ? field('headline') : '',
115
+ body: kind === 'popup' ? field('body', true) : '',
116
+ ctaLabel: kind === 'popup' ? field('ctaLabel') : '',
117
+ trigger: null,
118
+ triggerValue: null,
119
+ rationale: fit(answer.rationale, 200)
120
+ };
121
+ if ([
122
+ proposal.text,
123
+ proposal.headline,
124
+ proposal.body,
125
+ proposal.ctaLabel
126
+ ].some((value)=>MARKUP.test(value))) {
127
+ return null;
128
+ }
129
+ if (kind === 'bar' ? !proposal.text : !proposal.body) return null;
130
+ if (kind === 'popup' && answer.trigger) {
131
+ const trigger = answer.trigger;
132
+ if (triggers.has(trigger)) {
133
+ var _triggers_get;
134
+ proposal.trigger = trigger;
135
+ const range = (_triggers_get = triggers.get(trigger)) != null ? _triggers_get : null;
136
+ if (range && answer.triggerValue !== null && Number.isFinite(answer.triggerValue)) {
137
+ proposal.triggerValue = Math.min(range.max, Math.max(range.min, Math.round(answer.triggerValue)));
138
+ }
139
+ } else if (answer.trigger !== 'none') {
140
+ findings.push(`trigger "${answer.trigger}" is not one the site offers`);
141
+ }
142
+ }
143
+ return {
144
+ proposal,
145
+ findings
146
+ };
147
+ }
148
+ /** The proposal a finished overlay job's output carries, or `null` for anything else. */ export function readAiOverlayCopy(proposal) {
149
+ if (!proposal || proposal['task'] !== AI_OVERLAY_COPY_TASK) return null;
150
+ const kind = proposal['kind'];
151
+ if (kind !== 'bar' && kind !== 'popup') return null;
152
+ const trigger = proposal['trigger'];
153
+ const value = proposal['triggerValue'];
154
+ return {
155
+ kind,
156
+ name: str(proposal['name']),
157
+ text: str(proposal['text']),
158
+ headline: str(proposal['headline']),
159
+ body: str(proposal['body']),
160
+ ctaLabel: str(proposal['ctaLabel']),
161
+ trigger: AI_OVERLAY_TRIGGERS.includes(trigger) ? trigger : null,
162
+ triggerValue: typeof value === 'number' && Number.isFinite(value) ? value : null,
163
+ rationale: str(proposal['rationale'])
164
+ };
165
+ }
166
+ /** A proposal as the plain text a `text` output shows on the AI jobs page. */ export function aiOverlayCopyText(proposal) {
167
+ if (proposal.kind === 'bar') return proposal.text;
168
+ return [
169
+ proposal.headline,
170
+ proposal.body,
171
+ proposal.ctaLabel ? `Button: ${proposal.ctaLabel}` : ''
172
+ ].filter(Boolean).join('\n\n');
173
+ }
174
+
175
+ //# sourceMappingURL=ai-overlay-copy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-overlay-copy.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Overlay copy by AI (AGL-3603): what a `text` job asked for an announcement\n * bar's or a popup's copy answers, in the shape the step writes and the\n * console reads. Pure data, so the widgets and the step share one reading.\n *\n * The overlay is the marketing plugin's, and neither plugin imports the other:\n * the limits and the trigger catalog are handed to the widget by the zone,\n * and the widget passes them on as the job's inputs. The step then holds the\n * answer to the TIGHTER of those and its own ceilings below, so a job started\n * by anything else is still bounded, and drops a trigger the caller did not\n * name rather than inventing one.\n */\n\n/** The `text` job's `inputs.task` that asks for overlay copy. */\nexport const AI_OVERLAY_COPY_TASK = 'overlay'\n\n/** What the copy is for. */\nexport type AiOverlayKind = 'bar' | 'popup'\n\n/** The fields an answer fills. */\nexport type AiOverlayCopyField = 'name' | 'text' | 'headline' | 'body' | 'ctaLabel'\n\n/** The step's own ceilings: no zone may widen a field past these. */\nexport const AI_OVERLAY_COPY_CEILINGS: Readonly<Record<AiOverlayCopyField, number>> = {\n name: 80,\n text: 200,\n headline: 120,\n body: 600,\n ctaLabel: 60,\n}\n\n/** The triggers a popup may be proposed to open on, as the site runtime names them. */\nexport const AI_OVERLAY_TRIGGERS = ['delay', 'scroll', 'exit'] as const\nexport type AiOverlayTrigger = (typeof AI_OVERLAY_TRIGGERS)[number]\n\n/** One trigger as the zone hands it over: its id and the range of its value. */\nexport interface AiOverlayTriggerRule {\n id: string\n label: string\n unit: 'seconds' | 'percent' | null\n min: number\n max: number\n}\n\n/** The longest description of the current copy a job sends. */\nexport const AI_OVERLAY_CURRENT_MAX_CHARS = 1_200\n\n/** What a checked answer proposes. Absent fields propose nothing. */\nexport interface AiOverlayCopyProposal {\n kind: AiOverlayKind\n name: string\n text: string\n headline: string\n body: string\n ctaLabel: string\n trigger: AiOverlayTrigger | null\n triggerValue: number | null\n /** One sentence on what the copy leans on; shown, never written. */\n rationale: string\n}\n\n/** What the model answered, before the check. */\nexport interface AiOverlayCopyAnswer {\n name: string\n text: string\n headline: string\n body: string\n ctaLabel: string\n trigger: string\n triggerValue: number | null\n rationale: string\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** The kind a job's inputs name, or `null`. */\nexport function aiOverlayKind(inputs: Readonly<Record<string, unknown>> | null | undefined): AiOverlayKind | null {\n const kind = inputs?.['overlayKind']\n return kind === 'bar' || kind === 'popup' ? kind : null\n}\n\n/** Whether a `text` job's inputs ask for overlay copy. */\nexport function aiOverlayCopyRequested(inputs: Readonly<Record<string, unknown>> | null | undefined): boolean {\n return inputs?.['task'] === AI_OVERLAY_COPY_TASK\n}\n\n/**\n * The limit each field is held to: the caller's, where it named one, never\n * past the step's ceiling. Inputs carry them as `limit_<field>`.\n */\nexport function aiOverlayLimits(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): Record<AiOverlayCopyField, number> {\n const limits = { ...AI_OVERLAY_COPY_CEILINGS }\n for (const field of Object.keys(limits) as AiOverlayCopyField[]) {\n const asked = Number(inputs?.[`limit_${field}`])\n if (Number.isFinite(asked) && asked > 0) limits[field] = Math.min(limits[field], Math.floor(asked))\n }\n return limits\n}\n\n/**\n * The triggers the caller allows, with their ranges. Inputs carry them as\n * `triggers` (`delay:0-120,scroll:1-100,exit`); an id the runtime does not\n * know is dropped, and with no list a popup is proposed no trigger at all.\n */\nexport function aiOverlayTriggerRanges(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): Map<AiOverlayTrigger, { min: number; max: number } | null> {\n const ranges = new Map<AiOverlayTrigger, { min: number; max: number } | null>()\n for (const part of str(inputs?.['triggers']).split(',')) {\n const [id, range] = part.trim().split(':')\n if (!(AI_OVERLAY_TRIGGERS as readonly string[]).includes(id)) continue\n const [min, max] = String(range ?? '').split('-').map(Number)\n ranges.set(\n id as AiOverlayTrigger,\n Number.isFinite(min) && Number.isFinite(max) && max >= min ? { min, max } : null,\n )\n }\n return ranges\n}\n\n/** The zone's trigger catalog as the job's `triggers` input. */\nexport function aiOverlayTriggersInput(rules: readonly AiOverlayTriggerRule[]): string {\n return rules\n .filter((rule) => (AI_OVERLAY_TRIGGERS as readonly string[]).includes(rule.id))\n .map((rule) => (rule.unit ? `${rule.id}:${rule.min}-${rule.max}` : rule.id))\n .join(',')\n}\n\n/** The zone's limits as the job's `limit_<field>` inputs. */\nexport function aiOverlayLimitInputs(\n limits: Readonly<Partial<Record<AiOverlayCopyField, number>>>,\n): Record<string, number> {\n const inputs: Record<string, number> = {}\n for (const field of Object.keys(AI_OVERLAY_COPY_CEILINGS) as AiOverlayCopyField[]) {\n const limit = limits[field]\n if (typeof limit === 'number' && limit > 0) inputs[`limit_${field}`] = limit\n }\n return inputs\n}\n\n/** One field on one line, or a body with its paragraphs, cut to its limit. */\nfunction fit(value: string, limit: number, multiline = false): string {\n const folded = multiline\n ? value.replace(/\\r\\n?/g, '\\n').replace(/[^\\S\\n]+/g, ' ').replace(/\\n{3,}/g, '\\n\\n')\n : value.replace(/\\s+/g, ' ')\n return folded.trim().slice(0, limit).trim()\n}\n\n/** Markup, a fence or a link: things a bar or a popup shows as typed. */\nconst MARKUP = /<\\/?[a-z][^>]*>|```|\\[[^\\]]*\\]\\([^)]*\\)|https?:\\/\\//i\n\nexport interface AiOverlayCopyCheck {\n proposal: AiOverlayCopyProposal\n /** What was cut or dropped, for the log. */\n findings: string[]\n}\n\n/**\n * An answer held to the overlay's rules, or `null` when nothing usable is\n * left: a bar with no text, a popup with no body, or copy carrying markup or\n * a link. A field over its limit is cut rather than refused, and a trigger\n * outside the caller's catalog is dropped, with each recorded as a finding.\n */\nexport function checkAiOverlayCopy(\n answer: AiOverlayCopyAnswer | null,\n kind: AiOverlayKind,\n limits: Readonly<Record<AiOverlayCopyField, number>>,\n triggers: ReadonlyMap<AiOverlayTrigger, { min: number; max: number } | null>,\n): AiOverlayCopyCheck | null {\n if (!answer) return null\n const findings: string[] = []\n const field = (name: AiOverlayCopyField, multiline = false): string => {\n const raw = answer[name]\n const value = fit(raw, limits[name], multiline)\n if (fit(raw, Number.MAX_SAFE_INTEGER, multiline).length > limits[name]) {\n findings.push(`${name} cut to ${limits[name]} characters`)\n }\n return value\n }\n const proposal: AiOverlayCopyProposal = {\n kind,\n name: field('name'),\n text: kind === 'bar' ? field('text') : '',\n headline: kind === 'popup' ? field('headline') : '',\n body: kind === 'popup' ? field('body', true) : '',\n ctaLabel: kind === 'popup' ? field('ctaLabel') : '',\n trigger: null,\n triggerValue: null,\n rationale: fit(answer.rationale, 200),\n }\n if ([proposal.text, proposal.headline, proposal.body, proposal.ctaLabel].some((value) => MARKUP.test(value))) {\n return null\n }\n if (kind === 'bar' ? !proposal.text : !proposal.body) return null\n if (kind === 'popup' && answer.trigger) {\n const trigger = answer.trigger as AiOverlayTrigger\n if (triggers.has(trigger)) {\n proposal.trigger = trigger\n const range = triggers.get(trigger) ?? null\n if (range && answer.triggerValue !== null && Number.isFinite(answer.triggerValue)) {\n proposal.triggerValue = Math.min(range.max, Math.max(range.min, Math.round(answer.triggerValue)))\n }\n } else if (answer.trigger !== 'none') {\n findings.push(`trigger \"${answer.trigger}\" is not one the site offers`)\n }\n }\n return { proposal, findings }\n}\n\n/** The proposal a finished overlay job's output carries, or `null` for anything else. */\nexport function readAiOverlayCopy(proposal: Record<string, unknown> | null | undefined): AiOverlayCopyProposal | null {\n if (!proposal || proposal['task'] !== AI_OVERLAY_COPY_TASK) return null\n const kind = proposal['kind']\n if (kind !== 'bar' && kind !== 'popup') return null\n const trigger = proposal['trigger']\n const value = proposal['triggerValue']\n return {\n kind,\n name: str(proposal['name']),\n text: str(proposal['text']),\n headline: str(proposal['headline']),\n body: str(proposal['body']),\n ctaLabel: str(proposal['ctaLabel']),\n trigger: (AI_OVERLAY_TRIGGERS as readonly unknown[]).includes(trigger) ? (trigger as AiOverlayTrigger) : null,\n triggerValue: typeof value === 'number' && Number.isFinite(value) ? value : null,\n rationale: str(proposal['rationale']),\n }\n}\n\n/** A proposal as the plain text a `text` output shows on the AI jobs page. */\nexport function aiOverlayCopyText(proposal: AiOverlayCopyProposal): string {\n if (proposal.kind === 'bar') return proposal.text\n return [\n proposal.headline,\n proposal.body,\n proposal.ctaLabel ? `Button: ${proposal.ctaLabel}` : '',\n ]\n .filter(Boolean)\n .join('\\n\\n')\n}\n"],"names":["AI_OVERLAY_COPY_TASK","AI_OVERLAY_COPY_CEILINGS","name","text","headline","body","ctaLabel","AI_OVERLAY_TRIGGERS","AI_OVERLAY_CURRENT_MAX_CHARS","str","value","trim","aiOverlayKind","inputs","kind","aiOverlayCopyRequested","aiOverlayLimits","limits","field","Object","keys","asked","Number","isFinite","Math","min","floor","aiOverlayTriggerRanges","ranges","Map","part","split","id","range","includes","max","String","map","set","aiOverlayTriggersInput","rules","filter","rule","unit","join","aiOverlayLimitInputs","limit","fit","multiline","folded","replace","slice","MARKUP","checkAiOverlayCopy","answer","triggers","findings","raw","MAX_SAFE_INTEGER","length","push","proposal","trigger","triggerValue","rationale","some","test","has","get","round","readAiOverlayCopy","aiOverlayCopyText","Boolean"],"mappings":"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"}
@@ -27,11 +27,21 @@ import type { AiJobSummary } from './ai-jobs.types';
27
27
  *
28
28
  * Pure: no React, no request. The page and its spec read the same rows.
29
29
  */
30
- export type AiSiteBuildRowState = 'done' | 'active' | 'waiting' | 'failed';
30
+ export type AiSiteBuildRowState = 'done' | 'active' | 'waiting' | 'failed' | 'skipped';
31
31
  export interface AiSiteBuildRow {
32
32
  id: string;
33
33
  label: string;
34
34
  state: AiSiteBuildRowState;
35
+ /** What the person should know about this row (AGL-3616): a build item's failure, note or refund. */
36
+ detail?: string | null;
37
+ /** What the row is doing while it is the active one, in a sentence. */
38
+ hint?: string | null;
39
+ /** A page's planned sections, in order, so the person sees what is being written. */
40
+ sections?: string[];
41
+ /** The credits this row's work used, once it has finished; absent while it runs. */
42
+ credits?: number;
43
+ /** When this row's work started, for the elapsed time beside the active row. */
44
+ startedAt?: string | null;
35
45
  }
36
46
  /** Where the whole job stands, as the page's header and actions read it. */
37
47
  export type AiSiteBuildPhase = 'working' | 'done' | 'stopped' | 'failed' | 'canceled';
@@ -44,7 +54,14 @@ export declare function aiSiteBuildPhase(job: Pick<AiJobSummary, 'status'>): AiS
44
54
  * where a failure stopped it: a layout that broke is the layout's row, never
45
55
  * the first page's.
46
56
  */
47
- export declare function aiSiteBuildRows(job: Pick<AiJobSummary, 'status' | 'steps' | 'plan' | 'outputs' | 'review'>): AiSiteBuildRow[];
57
+ export declare function aiSiteBuildRows(job: Pick<AiJobSummary, 'status' | 'steps' | 'plan' | 'outputs' | 'review'> & Partial<Pick<AiJobSummary, 'items' | 'kind' | 'siteInputs' | 'sitePublish'>>): AiSiteBuildRow[];
58
+ /** What the planning row says while it runs. */
59
+ export declare const AI_SITE_PLAN_HINT = "Reading your answers and choosing your pages, what each one says, and the forms and layout they need. This usually takes under a minute.";
60
+ /**
61
+ * How far the job is, from 0 to 1, by its rows: a finished row counts whole
62
+ * and the active one half. `null` before there is more than one row to count.
63
+ */
64
+ export declare function aiSiteBuildFraction(rows: readonly AiSiteBuildRow[]): number | null;
48
65
  /**
49
66
  * The job's one price (AGL-3594), never a plan's on its own: what it has used
50
67
  * so far while it works, what it used once done, and, on a failure we gave
@@ -66,4 +83,4 @@ export interface AiJobPageCopy {
66
83
  * done site job without that record — an older job, or one that was not a
67
84
  * guided start — built drafts, and says that instead.
68
85
  */
69
- export declare function aiJobPageCopy(job: Pick<AiJobSummary, 'kind' | 'status' | 'review' | 'error'> & Partial<Pick<AiJobSummary, 'sitePublish'>>, brand: string): AiJobPageCopy;
86
+ export declare function aiJobPageCopy(job: Pick<AiJobSummary, 'kind' | 'status' | 'review' | 'error'> & Partial<Pick<AiJobSummary, 'sitePublish' | 'items'>>, brand: string): AiJobPageCopy;
@@ -1,3 +1,4 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
1
2
  /**
2
3
  * @license
3
4
  * Copyright 2026 Aglyn LLC
@@ -15,6 +16,7 @@
15
16
  * limitations under the License.
16
17
  */ import { aiJobKindNoun } from "./ai-job-activity.js";
17
18
  import { aiJobRefundCopy } from "./ai-job-failure-copy.js";
19
+ import { aiBuildItemRows, aiBuildOutcomeLine, aiSitePartialCopy } from "./ai-build-progress.js";
18
20
  export function aiSiteBuildPhase(job) {
19
21
  switch(job.status){
20
22
  case 'done':
@@ -55,19 +57,79 @@ export function aiSiteBuildPhase(job) {
55
57
  * where a failure stopped it: a layout that broke is the layout's row, never
56
58
  * the first page's.
57
59
  */ export function aiSiteBuildRows(job) {
58
- var _built_get, _ref;
59
- var _job_plan;
60
+ var _ref, _planStep_creditsSpent, _built_get, _ref1;
61
+ var _job_items, _job_plan;
60
62
  const phase = aiSiteBuildPhase(job);
63
+ // A build is read off its item ledger (AGL-3616): one row an item, each with
64
+ // its own state and what became of its credits.
65
+ if ((_job_items = job.items) == null ? void 0 : _job_items.length) {
66
+ var _job_items1;
67
+ const site = job.kind !== 'build';
68
+ const pages = ((_job_items1 = job.items) != null ? _job_items1 : []).filter((row)=>row.op === 'page');
69
+ return [
70
+ {
71
+ id: 'plan',
72
+ label: site ? 'Planning your pages' : 'Planning what to build',
73
+ state: 'done'
74
+ },
75
+ ...aiBuildItemRows(job).map((row, index)=>{
76
+ var _job_items, _ledger_creditsSpent, _ledger_creditsRefunded;
77
+ const ledger = ((_job_items = job.items) != null ? _job_items : [])[index];
78
+ // A site keeps the scaffold's own words for each stage (AGL-3596).
79
+ const label = !site || !ledger ? row.label : ledger.op === 'theme' ? 'Choosing your colors' : ledger.op === 'layout' ? `Building the header and footer: ${ledger.label}` : ledger.op === 'form' ? `Building the form: ${ledger.label}` : ledger.op === 'page' ? `Writing page ${pages.indexOf(ledger) + 1} of ${pages.length}: ${ledger.label}` : ledger.op === 'email' ? 'Writing your welcome email' : row.label;
80
+ const screen = (ledger == null ? void 0 : ledger.op) === 'page' ? aiPlanScreenFor(job.plan, ledger.label, pages.indexOf(ledger)) : null;
81
+ const finished = row.state === 'done' || row.state === 'failed';
82
+ const net = ledger ? Math.max(0, Math.floor(((_ledger_creditsSpent = ledger.creditsSpent) != null ? _ledger_creditsSpent : 0) - ((_ledger_creditsRefunded = ledger.creditsRefunded) != null ? _ledger_creditsRefunded : 0))) : 0;
83
+ return _extends({
84
+ id: row.slot,
85
+ label,
86
+ state: row.state,
87
+ detail: row.detail
88
+ }, (screen == null ? void 0 : screen.sections.length) ? {
89
+ sections: screen.sections.map((section)=>section.name)
90
+ } : {}, finished && ledger ? {
91
+ credits: net
92
+ } : {});
93
+ }),
94
+ ...aiSitePublishRow(job, phase)
95
+ ];
96
+ }
61
97
  const planStep = job.steps.find((step)=>step.name === 'plan');
62
98
  const planDone = (planStep == null ? void 0 : planStep.status) === 'done' || Boolean(job.plan);
63
99
  const stopped = phase === 'failed' || phase === 'stopped';
100
+ const planState = planDone ? 'done' : stopped ? 'failed' : phase === 'working' ? 'active' : 'waiting';
64
101
  const rows = [
65
- {
102
+ _extends({
66
103
  id: 'plan',
67
104
  label: 'Planning your pages',
68
- state: planDone ? 'done' : stopped ? 'failed' : phase === 'working' ? 'active' : 'waiting'
69
- }
105
+ state: planState,
106
+ startedAt: (_ref = planStep == null ? void 0 : planStep.startedAt) != null ? _ref : null,
107
+ hint: planState === 'active' ? AI_SITE_PLAN_HINT : null
108
+ }, planDone && planStep ? {
109
+ credits: Math.max(0, Math.floor((_planStep_creditsSpent = planStep.creditsSpent) != null ? _planStep_creditsSpent : 0))
110
+ } : {})
70
111
  ];
112
+ // Before there is a plan, the stages a guided start always goes through
113
+ // are named from its answers, so the page shows the whole way from the
114
+ // start rather than one row.
115
+ if (!job.plan && job.kind !== 'build') {
116
+ var _job_siteInputs;
117
+ const pagesAsked = typeof ((_job_siteInputs = job.siteInputs) == null ? void 0 : _job_siteInputs['pages']) === 'number' ? Math.round(job.siteInputs['pages']) : 0;
118
+ if (job.siteInputs) {
119
+ rows.push({
120
+ id: 'layout',
121
+ label: 'Building the header and footer',
122
+ state: 'waiting'
123
+ });
124
+ rows.push({
125
+ id: 'pages',
126
+ label: pagesAsked > 1 ? `Writing your ${pagesAsked} pages` : 'Writing your pages',
127
+ state: 'waiting'
128
+ });
129
+ rows.push(...aiSitePublishRow(job, phase));
130
+ }
131
+ return rows;
132
+ }
71
133
  const built = new Map();
72
134
  for (const output of job.outputs)built.set(output.resource, ((_built_get = built.get(output.resource)) != null ? _built_get : 0) + 1);
73
135
  const take = (resource)=>{
@@ -87,27 +149,64 @@ export function aiSiteBuildPhase(job) {
87
149
  resource: row.resource
88
150
  });
89
151
  }
90
- const pages = (_ref = (_job_plan = job.plan) == null ? void 0 : _job_plan.screens) != null ? _ref : [];
152
+ const pages = (_ref1 = (_job_plan = job.plan) == null ? void 0 : _job_plan.screens) != null ? _ref1 : [];
91
153
  pages.forEach((page, index)=>{
92
154
  stages.push({
93
155
  id: `page-${index}`,
94
156
  label: `Writing page ${index + 1} of ${pages.length}: ${page.title}`,
95
- resource: 'screen'
157
+ resource: 'screen',
158
+ sections: page.sections.map((section)=>section.name)
96
159
  });
97
160
  });
98
161
  let reached = false;
99
162
  for (const stage of stages){
163
+ var _stage_sections;
100
164
  const done = phase === 'done' || take(stage.resource);
101
165
  const current = !done && !reached && planDone;
102
166
  if (current) reached = true;
103
- rows.push({
167
+ rows.push(_extends({
104
168
  id: stage.id,
105
169
  label: stage.label,
106
170
  state: done ? 'done' : current && stopped ? 'failed' : current && phase === 'working' ? 'active' : 'waiting'
107
- });
171
+ }, ((_stage_sections = stage.sections) == null ? void 0 : _stage_sections.length) ? {
172
+ sections: stage.sections
173
+ } : {}));
108
174
  }
175
+ rows.push(...aiSitePublishRow(job, phase));
109
176
  return rows;
110
177
  }
178
+ /** What the planning row says while it runs. */ export const AI_SITE_PLAN_HINT = 'Reading your answers and choosing your pages, what each one says, and the forms and layout they need. This usually takes under a minute.';
179
+ /** The plan's page an item builds: by its title, else by its place among the pages. */ function aiPlanScreenFor(plan, title, index) {
180
+ var _ref, _screens_find, _screens_index;
181
+ const screens = (_ref = plan == null ? void 0 : plan.screens) != null ? _ref : [];
182
+ return (_screens_find = screens.find((screen)=>screen.title === title)) != null ? _screens_find : index >= 0 ? (_screens_index = screens[index]) != null ? _screens_index : null : null;
183
+ }
184
+ /**
185
+ * A guided start ends by putting its pages live (AGL-3596): the last row, so
186
+ * the person sees that step coming and when it is done. Only a site job
187
+ * publishes on its own; a build publishes when asked, and says so in its plan.
188
+ */ function aiSitePublishRow(job, phase) {
189
+ var _ref;
190
+ var _job_sitePublish;
191
+ if (job.kind !== 'site') return [];
192
+ const published = ((_ref = (_job_sitePublish = job.sitePublish) == null ? void 0 : _job_sitePublish.published.length) != null ? _ref : 0) > 0;
193
+ return [
194
+ {
195
+ id: 'publish',
196
+ label: 'Publishing your site',
197
+ // A job that stopped or failed publishes nothing, so the row is passed over.
198
+ state: phase === 'done' ? published ? 'done' : 'skipped' : phase === 'working' ? 'waiting' : 'skipped'
199
+ }
200
+ ];
201
+ }
202
+ /**
203
+ * How far the job is, from 0 to 1, by its rows: a finished row counts whole
204
+ * and the active one half. `null` before there is more than one row to count.
205
+ */ export function aiSiteBuildFraction(rows) {
206
+ if (rows.length < 2) return null;
207
+ const counted = rows.reduce((sum, row)=>sum + (row.state === 'active' ? 0.5 : row.state === 'waiting' ? 0 : 1), 0);
208
+ return Math.min(1, counted / rows.length);
209
+ }
111
210
  /**
112
211
  * The job's one price (AGL-3594), never a plan's on its own: what it has used
113
212
  * so far while it works, what it used once done, and, on a failure we gave
@@ -142,6 +241,8 @@ export function aiSiteBuildPhase(job) {
142
241
  var _ref, _ref1;
143
242
  var _job_review;
144
243
  const phase = aiSiteBuildPhase(job);
244
+ // A build is what the person asked for, item by item (AGL-3616).
245
+ if (job.kind === 'build') return aiBuildPageCopy(job, brand);
145
246
  const noun = job.kind === 'site' ? 'site' : aiJobKindNoun(job.kind);
146
247
  const generic = noun === 'AI job';
147
248
  // `products` is the one plural noun.
@@ -157,13 +258,25 @@ export function aiSiteBuildPhase(job) {
157
258
  var _ref2;
158
259
  var _job_sitePublish;
159
260
  const published = (_ref2 = (_job_sitePublish = job.sitePublish) == null ? void 0 : _job_sitePublish.published.length) != null ? _ref2 : 0;
261
+ // Part of the site was built (AGL-3616): what was, what was not, and its credits.
262
+ const partial = job.kind === 'site' ? aiSitePartialCopy({
263
+ items: job.items,
264
+ status: job.status
265
+ }) : null;
160
266
  if (job.kind === 'site' && published > 0) {
161
267
  var _job_sitePublish1;
162
268
  return {
163
269
  heading: 'Your site is live',
164
- lede: ((_job_sitePublish1 = job.sitePublish) == null ? void 0 : _job_sitePublish1.drafts.length) ? 'Your pages are published, except the ones listed below, which stayed drafts.' : 'Your pages are published, and anyone can visit your site now.'
270
+ lede: [
271
+ partial,
272
+ ((_job_sitePublish1 = job.sitePublish) == null ? void 0 : _job_sitePublish1.drafts.length) ? 'Your pages are published, except the ones listed below, which stayed drafts.' : 'Your pages are published, and anyone can visit your site now.'
273
+ ].filter(Boolean).join(' ')
165
274
  };
166
275
  }
276
+ if (partial) return {
277
+ heading: 'Your site is ready',
278
+ lede: `${partial} Your new pages are drafts. Publish them when you’re happy.`
279
+ };
167
280
  if (job.kind === 'site' && job.sitePublish) {
168
281
  return {
169
282
  heading: 'Your site is ready',
@@ -186,5 +299,52 @@ export function aiSiteBuildPhase(job) {
186
299
  lede: why != null ? why : generic ? 'Something went wrong running this job.' : `Something went wrong building your ${noun}.`
187
300
  };
188
301
  }
302
+ /** A build's page heading and sentence (AGL-3616). */ function aiBuildPageCopy(job, brand) {
303
+ var _job_items, _job_error;
304
+ const phase = aiSiteBuildPhase(job);
305
+ const outcome = aiBuildOutcomeLine({
306
+ items: (_job_items = job.items) != null ? _job_items : [],
307
+ status: job.status
308
+ });
309
+ if (phase === 'working') {
310
+ return {
311
+ heading: 'Building what you asked for',
312
+ lede: `${brand} AI is building each part in turn. You can leave this page; it keeps going.`
313
+ };
314
+ }
315
+ if (phase === 'stopped') {
316
+ var _ref, _ref1;
317
+ var _job_review, _job_review1;
318
+ return {
319
+ heading: ((_job_review = job.review) == null ? void 0 : _job_review.reason) === 'plan' ? 'Your plan is ready' : 'Your build needs you',
320
+ lede: (_ref = (_ref1 = (_job_review1 = job.review) == null ? void 0 : _job_review1.message) != null ? _ref1 : job.error) != null ? _ref : 'It waits for your decision.'
321
+ };
322
+ }
323
+ if (phase === 'done') {
324
+ var _job_items1, _ref2;
325
+ var _job_sitePublish;
326
+ const failed = ((_job_items1 = job.items) != null ? _job_items1 : []).some((row)=>row.status === 'failed' || row.status === 'skipped');
327
+ const live = (_ref2 = (_job_sitePublish = job.sitePublish) == null ? void 0 : _job_sitePublish.published.length) != null ? _ref2 : 0;
328
+ return {
329
+ heading: failed ? 'Most of it is ready' : 'Everything you asked for is ready',
330
+ lede: [
331
+ outcome,
332
+ live ? 'Your new pages are published.' : 'Everything it built is an unpublished draft until you publish it.',
333
+ failed ? 'Try again builds only what failed.' : ''
334
+ ].filter(Boolean).join(' ')
335
+ };
336
+ }
337
+ if (phase === 'canceled') return {
338
+ heading: 'Your build was canceled',
339
+ lede: 'The job was canceled.'
340
+ };
341
+ return {
342
+ heading: 'Nothing could be built',
343
+ lede: [
344
+ (_job_error = job.error) != null ? _job_error : 'Something went wrong building what you asked for.',
345
+ outcome
346
+ ].filter(Boolean).join(' ')
347
+ };
348
+ }
189
349
 
190
350
  //# sourceMappingURL=ai-site-build-progress.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-build-progress.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { aiJobKindNoun } from './ai-job-activity'\nimport { aiJobRefundCopy } from './ai-job-failure-copy'\nimport type { AiJobSummary } from './ai-jobs.types'\n\n/**\n * What the \"Building your site\" page shows of a site job (AGL-3594): one row\n * a stage — planning the pages, then each page — with its state, the state\n * the whole job is in, and the job's one price.\n *\n * Read off the job summary every surface already follows: the plan step's\n * status, the plan's pages, and the pages the build has reported as outputs.\n * A scaffold builds its units in plan order and reports each page when it is\n * written, so the pages written so far are the first pages of the plan.\n *\n * Pure: no React, no request. The page and its spec read the same rows.\n */\n\nexport type AiSiteBuildRowState = 'done' | 'active' | 'waiting' | 'failed'\n\nexport interface AiSiteBuildRow {\n id: string\n label: string\n state: AiSiteBuildRowState\n}\n\n/** Where the whole job stands, as the page's header and actions read it. */\nexport type AiSiteBuildPhase = 'working' | 'done' | 'stopped' | 'failed' | 'canceled'\n\nexport function aiSiteBuildPhase(job: Pick<AiJobSummary, 'status'>): AiSiteBuildPhase {\n switch (job.status) {\n case 'done':\n return 'done'\n case 'failed':\n return 'failed'\n case 'canceled':\n return 'canceled'\n case 'needs_review':\n case 'needs_input':\n return 'stopped'\n default:\n return 'working'\n }\n}\n\n/** The creations a scaffold builds before its pages, in its build order, and what each row says. */\nconst CREATION_ROWS: ReadonlyArray<{ kind: string; resource: string; label: (name: string) => string }> = [\n { kind: 'theme-change', resource: 'theme', label: () => 'Choosing your colors' },\n { kind: 'layout', resource: 'layout', label: (name) => `Building the header and footer: ${name}` },\n { kind: 'form', resource: 'form', label: (name) => `Building the form: ${name}` },\n]\n\n/**\n * One row a stage: planning, then each creation the plan builds before its\n * pages — the colors, the layout, the form — then each page (AGL-3596). A\n * scaffold builds them in that order and reports each as an output when it\n * is written, so the first row without its output is where the build is, and\n * where a failure stopped it: a layout that broke is the layout's row, never\n * the first page's.\n */\nexport function aiSiteBuildRows(\n job: Pick<AiJobSummary, 'status' | 'steps' | 'plan' | 'outputs' | 'review'>,\n): AiSiteBuildRow[] {\n const phase = aiSiteBuildPhase(job)\n const planStep = job.steps.find((step) => step.name === 'plan')\n const planDone = planStep?.status === 'done' || Boolean(job.plan)\n const stopped = phase === 'failed' || phase === 'stopped'\n const rows: AiSiteBuildRow[] = [\n {\n id: 'plan',\n label: 'Planning your pages',\n state: planDone ? 'done' : stopped ? 'failed' : phase === 'working' ? 'active' : 'waiting',\n },\n ]\n const built = new Map<string, number>()\n for (const output of job.outputs) built.set(output.resource, (built.get(output.resource) ?? 0) + 1)\n const take = (resource: string): boolean => {\n const left = built.get(resource) ?? 0\n if (left <= 0) return false\n built.set(resource, left - 1)\n return true\n }\n const stages: Array<{ id: string; label: string; resource: string }> = []\n for (const row of CREATION_ROWS) {\n const creation = job.plan?.create.find((entry) => entry.kind === row.kind)\n if (creation) stages.push({ id: row.resource, label: row.label(creation.name), resource: row.resource })\n }\n const pages = job.plan?.screens ?? []\n pages.forEach((page, index) => {\n stages.push({ id: `page-${index}`, label: `Writing page ${index + 1} of ${pages.length}: ${page.title}`, resource: 'screen' })\n })\n let reached = false\n for (const stage of stages) {\n const done = phase === 'done' || take(stage.resource)\n const current = !done && !reached && planDone\n if (current) reached = true\n rows.push({\n id: stage.id,\n label: stage.label,\n state: done ? 'done' : current && stopped ? 'failed' : current && phase === 'working' ? 'active' : 'waiting',\n })\n }\n return rows\n}\n\n/**\n * The job's one price (AGL-3594), never a plan's on its own: what it has used\n * so far while it works, what it used once done, and, on a failure we gave\n * back, that nothing was charged.\n */\nexport function aiSiteBuildCreditsLine(\n job: Pick<AiJobSummary, 'status' | 'creditsSpent' | 'refundedCredits'> & Partial<Pick<AiJobSummary, 'kind'>>,\n): string | null {\n const spent = Math.max(0, job.creditsSpent ?? 0)\n const refunded = Math.max(0, job.refundedCredits ?? 0)\n const net = Math.max(0, spent - refunded)\n const phase = aiSiteBuildPhase(job)\n // What the job made, by its kind; a job that names none is a site's.\n const subject = !job.kind || job.kind === 'site' ? 'This site' : aiJobKindNoun(job.kind) === 'AI job' ? 'This AI job' : `This ${aiJobKindNoun(job.kind)}`\n // A failure on our side says so in the words every surface uses (AGL-3596),\n // from the job's recorded give-back.\n const refund = phase === 'working' || phase === 'done' ? null : aiJobRefundCopy(job)\n if (refund && refunded > 0) return refund\n if (phase === 'done') return `${subject} used ${net} credits.`\n if (phase === 'working') return `Credits used so far: ${net}`\n if (refund) return `${subject} used ${net} credits. ${refund}`\n return net > 0 ? `${subject} used ${net} credits.` : null\n}\n\n/** The heading and the sentence under it, for one job's page in its current phase. */\nexport interface AiJobPageCopy {\n heading: string\n lede: string\n}\n\n/**\n * What one job's page says at the top (AGL-3594, AGL-3596). A site job reads\n * as \"Building your site\"; any other kind names what it makes — \"Building\n * your page\" — or, for a kind with no noun, the job itself.\n *\n * A guided site start publishes its pages when it finishes, and says so\n * (`sitePublish`): with a page live the heading is \"Your site is live\". A\n * done site job without that record — an older job, or one that was not a\n * guided start — built drafts, and says that instead.\n */\nexport function aiJobPageCopy(\n job: Pick<AiJobSummary, 'kind' | 'status' | 'review' | 'error'> & Partial<Pick<AiJobSummary, 'sitePublish'>>,\n brand: string,\n): AiJobPageCopy {\n const phase = aiSiteBuildPhase(job)\n const noun = job.kind === 'site' ? 'site' : aiJobKindNoun(job.kind)\n const generic = noun === 'AI job'\n // `products` is the one plural noun.\n const isAre = noun.endsWith('s') ? 'are' : 'is'\n const why = job.review?.message ?? job.error ?? null\n if (phase === 'working') {\n return {\n heading: generic ? 'Your AI job is running' : `Building your ${noun}`,\n lede:\n job.kind === 'site'\n ? `${brand} AI is planning your pages and writing each one. You can leave this page; it keeps going.`\n : `${brand} AI is working on it. You can leave this page; it keeps going.`,\n }\n }\n if (phase === 'done') {\n const published = job.sitePublish?.published.length ?? 0\n if (job.kind === 'site' && published > 0) {\n return {\n heading: 'Your site is live',\n lede: job.sitePublish?.drafts.length\n ? 'Your pages are published, except the ones listed below, which stayed drafts.'\n : 'Your pages are published, and anyone can visit your site now.',\n }\n }\n if (job.kind === 'site' && job.sitePublish) {\n return {\n heading: 'Your site is ready',\n lede: 'None of your new pages could be published, so they are drafts. The reasons are below.',\n }\n }\n return {\n heading: generic ? 'Your AI job finished' : `Your ${noun} ${isAre} ready`,\n lede:\n job.kind === 'site'\n ? 'Your new pages are drafts. Publish them when you’re happy.'\n : 'Everything it built is an unpublished draft until you publish it.',\n }\n }\n if (phase === 'canceled') {\n return { heading: generic ? 'Your AI job was canceled' : `Your ${noun} was not built`, lede: 'The job was canceled.' }\n }\n return {\n heading: generic ? 'Your AI job stopped' : `Your ${noun} ${noun.endsWith('s') ? 'were' : 'was'} not built`,\n lede: why ?? (generic ? 'Something went wrong running this job.' : `Something went wrong building your ${noun}.`),\n }\n}\n"],"names":["aiJobKindNoun","aiJobRefundCopy","aiSiteBuildPhase","job","status","CREATION_ROWS","kind","resource","label","name","aiSiteBuildRows","built","phase","planStep","steps","find","step","planDone","Boolean","plan","stopped","rows","id","state","Map","output","outputs","set","get","take","left","stages","row","creation","create","entry","push","pages","screens","forEach","page","index","length","title","reached","stage","done","current","aiSiteBuildCreditsLine","spent","Math","max","creditsSpent","refunded","refundedCredits","net","subject","refund","aiJobPageCopy","brand","noun","generic","isAre","endsWith","why","review","message","error","heading","lede","published","sitePublish","drafts"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-build-progress.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { aiJobKindNoun } from './ai-job-activity'\nimport { aiJobRefundCopy } from './ai-job-failure-copy'\nimport { aiBuildItemRows, aiBuildOutcomeLine, aiSitePartialCopy } from './ai-build-progress'\nimport type { AiJobSummary } from './ai-jobs.types'\n\n/**\n * What the \"Building your site\" page shows of a site job (AGL-3594): one row\n * a stage — planning the pages, then each page — with its state, the state\n * the whole job is in, and the job's one price.\n *\n * Read off the job summary every surface already follows: the plan step's\n * status, the plan's pages, and the pages the build has reported as outputs.\n * A scaffold builds its units in plan order and reports each page when it is\n * written, so the pages written so far are the first pages of the plan.\n *\n * Pure: no React, no request. The page and its spec read the same rows.\n */\n\nexport type AiSiteBuildRowState = 'done' | 'active' | 'waiting' | 'failed' | 'skipped'\n\nexport interface AiSiteBuildRow {\n id: string\n label: string\n state: AiSiteBuildRowState\n /** What the person should know about this row (AGL-3616): a build item's failure, note or refund. */\n detail?: string | null\n /** What the row is doing while it is the active one, in a sentence. */\n hint?: string | null\n /** A page's planned sections, in order, so the person sees what is being written. */\n sections?: string[]\n /** The credits this row's work used, once it has finished; absent while it runs. */\n credits?: number\n /** When this row's work started, for the elapsed time beside the active row. */\n startedAt?: string | null\n}\n\n/** Where the whole job stands, as the page's header and actions read it. */\nexport type AiSiteBuildPhase = 'working' | 'done' | 'stopped' | 'failed' | 'canceled'\n\nexport function aiSiteBuildPhase(job: Pick<AiJobSummary, 'status'>): AiSiteBuildPhase {\n switch (job.status) {\n case 'done':\n return 'done'\n case 'failed':\n return 'failed'\n case 'canceled':\n return 'canceled'\n case 'needs_review':\n case 'needs_input':\n return 'stopped'\n default:\n return 'working'\n }\n}\n\n/** The creations a scaffold builds before its pages, in its build order, and what each row says. */\nconst CREATION_ROWS: ReadonlyArray<{ kind: string; resource: string; label: (name: string) => string }> = [\n { kind: 'theme-change', resource: 'theme', label: () => 'Choosing your colors' },\n { kind: 'layout', resource: 'layout', label: (name) => `Building the header and footer: ${name}` },\n { kind: 'form', resource: 'form', label: (name) => `Building the form: ${name}` },\n]\n\n/**\n * One row a stage: planning, then each creation the plan builds before its\n * pages — the colors, the layout, the form — then each page (AGL-3596). A\n * scaffold builds them in that order and reports each as an output when it\n * is written, so the first row without its output is where the build is, and\n * where a failure stopped it: a layout that broke is the layout's row, never\n * the first page's.\n */\nexport function aiSiteBuildRows(\n job: Pick<AiJobSummary, 'status' | 'steps' | 'plan' | 'outputs' | 'review'> &\n Partial<Pick<AiJobSummary, 'items' | 'kind' | 'siteInputs' | 'sitePublish'>>,\n): AiSiteBuildRow[] {\n const phase = aiSiteBuildPhase(job)\n // A build is read off its item ledger (AGL-3616): one row an item, each with\n // its own state and what became of its credits.\n if (job.items?.length) {\n const site = job.kind !== 'build'\n const pages = (job.items ?? []).filter((row) => row.op === 'page')\n return [\n { id: 'plan', label: site ? 'Planning your pages' : 'Planning what to build', state: 'done' },\n ...aiBuildItemRows(job).map((row, index) => {\n const ledger = (job.items ?? [])[index]\n // A site keeps the scaffold's own words for each stage (AGL-3596).\n const label =\n !site || !ledger\n ? row.label\n : ledger.op === 'theme'\n ? 'Choosing your colors'\n : ledger.op === 'layout'\n ? `Building the header and footer: ${ledger.label}`\n : ledger.op === 'form'\n ? `Building the form: ${ledger.label}`\n : ledger.op === 'page'\n ? `Writing page ${pages.indexOf(ledger) + 1} of ${pages.length}: ${ledger.label}`\n : ledger.op === 'email'\n ? 'Writing your welcome email'\n : row.label\n const screen = ledger?.op === 'page' ? aiPlanScreenFor(job.plan, ledger.label, pages.indexOf(ledger)) : null\n const finished = row.state === 'done' || row.state === 'failed'\n const net = ledger ? Math.max(0, Math.floor((ledger.creditsSpent ?? 0) - (ledger.creditsRefunded ?? 0))) : 0\n return {\n id: row.slot,\n label,\n state: row.state,\n detail: row.detail,\n ...(screen?.sections.length ? { sections: screen.sections.map((section) => section.name) } : {}),\n ...(finished && ledger ? { credits: net } : {}),\n }\n }),\n ...aiSitePublishRow(job, phase),\n ]\n }\n const planStep = job.steps.find((step) => step.name === 'plan')\n const planDone = planStep?.status === 'done' || Boolean(job.plan)\n const stopped = phase === 'failed' || phase === 'stopped'\n const planState: AiSiteBuildRowState = planDone ? 'done' : stopped ? 'failed' : phase === 'working' ? 'active' : 'waiting'\n const rows: AiSiteBuildRow[] = [\n {\n id: 'plan',\n label: 'Planning your pages',\n state: planState,\n startedAt: planStep?.startedAt ?? null,\n hint: planState === 'active' ? AI_SITE_PLAN_HINT : null,\n ...(planDone && planStep ? { credits: Math.max(0, Math.floor(planStep.creditsSpent ?? 0)) } : {}),\n },\n ]\n // Before there is a plan, the stages a guided start always goes through\n // are named from its answers, so the page shows the whole way from the\n // start rather than one row.\n if (!job.plan && job.kind !== 'build') {\n const pagesAsked = typeof job.siteInputs?.['pages'] === 'number' ? Math.round(job.siteInputs['pages'] as number) : 0\n if (job.siteInputs) {\n rows.push({ id: 'layout', label: 'Building the header and footer', state: 'waiting' })\n rows.push({\n id: 'pages',\n label: pagesAsked > 1 ? `Writing your ${pagesAsked} pages` : 'Writing your pages',\n state: 'waiting',\n })\n rows.push(...aiSitePublishRow(job, phase))\n }\n return rows\n }\n const built = new Map<string, number>()\n for (const output of job.outputs) built.set(output.resource, (built.get(output.resource) ?? 0) + 1)\n const take = (resource: string): boolean => {\n const left = built.get(resource) ?? 0\n if (left <= 0) return false\n built.set(resource, left - 1)\n return true\n }\n const stages: Array<{ id: string; label: string; resource: string; sections?: string[] }> = []\n for (const row of CREATION_ROWS) {\n const creation = job.plan?.create.find((entry) => entry.kind === row.kind)\n if (creation) stages.push({ id: row.resource, label: row.label(creation.name), resource: row.resource })\n }\n const pages = job.plan?.screens ?? []\n pages.forEach((page, index) => {\n stages.push({\n id: `page-${index}`,\n label: `Writing page ${index + 1} of ${pages.length}: ${page.title}`,\n resource: 'screen',\n sections: page.sections.map((section) => section.name),\n })\n })\n let reached = false\n for (const stage of stages) {\n const done = phase === 'done' || take(stage.resource)\n const current = !done && !reached && planDone\n if (current) reached = true\n rows.push({\n id: stage.id,\n label: stage.label,\n state: done ? 'done' : current && stopped ? 'failed' : current && phase === 'working' ? 'active' : 'waiting',\n ...(stage.sections?.length ? { sections: stage.sections } : {}),\n })\n }\n rows.push(...aiSitePublishRow(job, phase))\n return rows\n}\n\n/** What the planning row says while it runs. */\nexport const AI_SITE_PLAN_HINT =\n 'Reading your answers and choosing your pages, what each one says, and the forms and layout they need. This usually takes under a minute.'\n\n/** The plan's page an item builds: by its title, else by its place among the pages. */\nfunction aiPlanScreenFor(\n plan: AiJobSummary['plan'],\n title: string,\n index: number,\n): NonNullable<AiJobSummary['plan']>['screens'][number] | null {\n const screens = plan?.screens ?? []\n return screens.find((screen) => screen.title === title) ?? (index >= 0 ? (screens[index] ?? null) : null)\n}\n\n/**\n * A guided start ends by putting its pages live (AGL-3596): the last row, so\n * the person sees that step coming and when it is done. Only a site job\n * publishes on its own; a build publishes when asked, and says so in its plan.\n */\nfunction aiSitePublishRow(\n job: Pick<AiJobSummary, 'status'> & Partial<Pick<AiJobSummary, 'kind' | 'sitePublish'>>,\n phase: AiSiteBuildPhase,\n): AiSiteBuildRow[] {\n if (job.kind !== 'site') return []\n const published = (job.sitePublish?.published.length ?? 0) > 0\n return [\n {\n id: 'publish',\n label: 'Publishing your site',\n // A job that stopped or failed publishes nothing, so the row is passed over.\n state: phase === 'done' ? (published ? 'done' : 'skipped') : phase === 'working' ? 'waiting' : 'skipped',\n },\n ]\n}\n\n/**\n * How far the job is, from 0 to 1, by its rows: a finished row counts whole\n * and the active one half. `null` before there is more than one row to count.\n */\nexport function aiSiteBuildFraction(rows: readonly AiSiteBuildRow[]): number | null {\n if (rows.length < 2) return null\n const counted = rows.reduce(\n (sum, row) => sum + (row.state === 'active' ? 0.5 : row.state === 'waiting' ? 0 : 1),\n 0,\n )\n return Math.min(1, counted / rows.length)\n}\n\n/**\n * The job's one price (AGL-3594), never a plan's on its own: what it has used\n * so far while it works, what it used once done, and, on a failure we gave\n * back, that nothing was charged.\n */\nexport function aiSiteBuildCreditsLine(\n job: Pick<AiJobSummary, 'status' | 'creditsSpent' | 'refundedCredits'> & Partial<Pick<AiJobSummary, 'kind'>>,\n): string | null {\n const spent = Math.max(0, job.creditsSpent ?? 0)\n const refunded = Math.max(0, job.refundedCredits ?? 0)\n const net = Math.max(0, spent - refunded)\n const phase = aiSiteBuildPhase(job)\n // What the job made, by its kind; a job that names none is a site's.\n const subject = !job.kind || job.kind === 'site' ? 'This site' : aiJobKindNoun(job.kind) === 'AI job' ? 'This AI job' : `This ${aiJobKindNoun(job.kind)}`\n // A failure on our side says so in the words every surface uses (AGL-3596),\n // from the job's recorded give-back.\n const refund = phase === 'working' || phase === 'done' ? null : aiJobRefundCopy(job)\n if (refund && refunded > 0) return refund\n if (phase === 'done') return `${subject} used ${net} credits.`\n if (phase === 'working') return `Credits used so far: ${net}`\n if (refund) return `${subject} used ${net} credits. ${refund}`\n return net > 0 ? `${subject} used ${net} credits.` : null\n}\n\n/** The heading and the sentence under it, for one job's page in its current phase. */\nexport interface AiJobPageCopy {\n heading: string\n lede: string\n}\n\n/**\n * What one job's page says at the top (AGL-3594, AGL-3596). A site job reads\n * as \"Building your site\"; any other kind names what it makes — \"Building\n * your page\" — or, for a kind with no noun, the job itself.\n *\n * A guided site start publishes its pages when it finishes, and says so\n * (`sitePublish`): with a page live the heading is \"Your site is live\". A\n * done site job without that record — an older job, or one that was not a\n * guided start — built drafts, and says that instead.\n */\nexport function aiJobPageCopy(\n job: Pick<AiJobSummary, 'kind' | 'status' | 'review' | 'error'> & Partial<Pick<AiJobSummary, 'sitePublish' | 'items'>>,\n brand: string,\n): AiJobPageCopy {\n const phase = aiSiteBuildPhase(job)\n // A build is what the person asked for, item by item (AGL-3616).\n if (job.kind === 'build') return aiBuildPageCopy(job, brand)\n const noun = job.kind === 'site' ? 'site' : aiJobKindNoun(job.kind)\n const generic = noun === 'AI job'\n // `products` is the one plural noun.\n const isAre = noun.endsWith('s') ? 'are' : 'is'\n const why = job.review?.message ?? job.error ?? null\n if (phase === 'working') {\n return {\n heading: generic ? 'Your AI job is running' : `Building your ${noun}`,\n lede:\n job.kind === 'site'\n ? `${brand} AI is planning your pages and writing each one. You can leave this page; it keeps going.`\n : `${brand} AI is working on it. You can leave this page; it keeps going.`,\n }\n }\n if (phase === 'done') {\n const published = job.sitePublish?.published.length ?? 0\n // Part of the site was built (AGL-3616): what was, what was not, and its credits.\n const partial = job.kind === 'site' ? aiSitePartialCopy({ items: job.items, status: job.status }) : null\n if (job.kind === 'site' && published > 0) {\n return {\n heading: 'Your site is live',\n lede: [\n partial,\n job.sitePublish?.drafts.length\n ? 'Your pages are published, except the ones listed below, which stayed drafts.'\n : 'Your pages are published, and anyone can visit your site now.',\n ]\n .filter(Boolean)\n .join(' '),\n }\n }\n if (partial) return { heading: 'Your site is ready', lede: `${partial} Your new pages are drafts. Publish them when you’re happy.` }\n if (job.kind === 'site' && job.sitePublish) {\n return {\n heading: 'Your site is ready',\n lede: 'None of your new pages could be published, so they are drafts. The reasons are below.',\n }\n }\n return {\n heading: generic ? 'Your AI job finished' : `Your ${noun} ${isAre} ready`,\n lede:\n job.kind === 'site'\n ? 'Your new pages are drafts. Publish them when you’re happy.'\n : 'Everything it built is an unpublished draft until you publish it.',\n }\n }\n if (phase === 'canceled') {\n return { heading: generic ? 'Your AI job was canceled' : `Your ${noun} was not built`, lede: 'The job was canceled.' }\n }\n return {\n heading: generic ? 'Your AI job stopped' : `Your ${noun} ${noun.endsWith('s') ? 'were' : 'was'} not built`,\n lede: why ?? (generic ? 'Something went wrong running this job.' : `Something went wrong building your ${noun}.`),\n }\n}\n\n/** A build's page heading and sentence (AGL-3616). */\nfunction aiBuildPageCopy(\n job: Pick<AiJobSummary, 'status' | 'review' | 'error'> & Partial<Pick<AiJobSummary, 'items' | 'sitePublish'>>,\n brand: string,\n): AiJobPageCopy {\n const phase = aiSiteBuildPhase(job)\n const outcome = aiBuildOutcomeLine({ items: job.items ?? [], status: job.status })\n if (phase === 'working') {\n return {\n heading: 'Building what you asked for',\n lede: `${brand} AI is building each part in turn. You can leave this page; it keeps going.`,\n }\n }\n if (phase === 'stopped') {\n return {\n heading: job.review?.reason === 'plan' ? 'Your plan is ready' : 'Your build needs you',\n lede: job.review?.message ?? job.error ?? 'It waits for your decision.',\n }\n }\n if (phase === 'done') {\n const failed = (job.items ?? []).some((row) => row.status === 'failed' || row.status === 'skipped')\n const live = job.sitePublish?.published.length ?? 0\n return {\n heading: failed ? 'Most of it is ready' : 'Everything you asked for is ready',\n lede: [\n outcome,\n live ? 'Your new pages are published.' : 'Everything it built is an unpublished draft until you publish it.',\n failed ? 'Try again builds only what failed.' : '',\n ]\n .filter(Boolean)\n .join(' '),\n }\n }\n if (phase === 'canceled') return { heading: 'Your build was canceled', lede: 'The job was canceled.' }\n return {\n heading: 'Nothing could be built',\n lede: [job.error ?? 'Something went wrong building what you asked for.', outcome].filter(Boolean).join(' '),\n }\n}\n"],"names":["aiJobKindNoun","aiJobRefundCopy","aiBuildItemRows","aiBuildOutcomeLine","aiSitePartialCopy","aiSiteBuildPhase","job","status","CREATION_ROWS","kind","resource","label","name","aiSiteBuildRows","planStep","built","phase","items","length","site","pages","filter","row","op","id","state","map","index","ledger","indexOf","screen","aiPlanScreenFor","plan","finished","net","Math","max","floor","creditsSpent","creditsRefunded","slot","detail","sections","section","credits","aiSitePublishRow","steps","find","step","planDone","Boolean","stopped","planState","rows","startedAt","hint","AI_SITE_PLAN_HINT","pagesAsked","siteInputs","round","push","Map","output","outputs","set","get","take","left","stages","creation","create","entry","screens","forEach","page","title","reached","stage","done","current","published","sitePublish","aiSiteBuildFraction","counted","reduce","sum","min","aiSiteBuildCreditsLine","spent","refunded","refundedCredits","subject","refund","aiJobPageCopy","brand","aiBuildPageCopy","noun","generic","isAre","endsWith","why","review","message","error","heading","lede","partial","drafts","join","outcome","reason","failed","some","live"],"mappings":"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"}