@aglyn/plugins-ai 1.0.0-beta.231 → 1.0.0-beta.232

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 (220) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.js +8 -4
  3. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  4. package/src/lib/components/ai-job-follow.component.js +1 -0
  5. package/src/lib/components/ai-job-follow.component.js.map +1 -1
  6. package/src/lib/components/ai-job-links.js +13 -0
  7. package/src/lib/components/ai-job-links.js.map +1 -1
  8. package/src/lib/components/ai-jobs-list-page.component.js +1 -0
  9. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  10. package/src/lib/components/ai-media-create.component.d.ts +20 -7
  11. package/src/lib/components/ai-media-create.component.js +107 -55
  12. package/src/lib/components/ai-media-create.component.js.map +1 -1
  13. package/src/lib/components/ai-seo-fields-card.component.js +1 -0
  14. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  15. package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
  16. package/src/lib/components/ai-site-memory-card.component.js +187 -0
  17. package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
  18. package/src/lib/components/ai-site-start-card.component.js +80 -77
  19. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  20. package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
  21. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  22. package/src/lib/components/ai-usage-strip.component.js +1 -0
  23. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  24. package/src/lib/components/assist-panel.component.d.ts +10 -0
  25. package/src/lib/components/assist-panel.component.js +119 -9
  26. package/src/lib/components/assist-panel.component.js.map +1 -1
  27. package/src/lib/components/assist-signals-page.component.js +2 -0
  28. package/src/lib/components/assist-signals-page.component.js.map +1 -1
  29. package/src/lib/components/billing-ai-allotments.component.js +3 -0
  30. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  31. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  32. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  33. package/src/lib/components/billing-assist-overage-card.component.js +2 -0
  34. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  35. package/src/lib/components/member-ai-usage-card.component.js +10 -0
  36. package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
  37. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  38. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  39. package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
  40. package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
  41. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
  42. package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
  43. package/src/lib/jobs/ai-build-capabilities.js +101 -7
  44. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  45. package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
  46. package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
  47. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
  48. package/src/lib/jobs/ai-job-build-step.js +3 -1
  49. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  50. package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
  51. package/src/lib/jobs/ai-job-edit-step.js +631 -0
  52. package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
  53. package/src/lib/jobs/ai-job-insight-step.js +21 -4
  54. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  55. package/src/lib/jobs/ai-job-layout-language.d.ts +8 -3
  56. package/src/lib/jobs/ai-job-layout-language.js +8 -2
  57. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  58. package/src/lib/jobs/ai-job-layout-step.js +7 -6
  59. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  60. package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
  61. package/src/lib/jobs/ai-job-logic-step.js +87 -6
  62. package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
  63. package/src/lib/jobs/ai-job-page-language.d.ts +52 -7
  64. package/src/lib/jobs/ai-job-page-language.js +72 -9
  65. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  66. package/src/lib/jobs/ai-job-page-step.js +12 -2
  67. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  68. package/src/lib/jobs/ai-job-plan-step.d.ts +19 -5
  69. package/src/lib/jobs/ai-job-plan-step.js +54 -20
  70. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  71. package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
  72. package/src/lib/jobs/ai-job-site-content.js +443 -0
  73. package/src/lib/jobs/ai-job-site-content.js.map +1 -0
  74. package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
  75. package/src/lib/jobs/ai-job-site-look.js +322 -0
  76. package/src/lib/jobs/ai-job-site-look.js.map +1 -0
  77. package/src/lib/jobs/ai-job-site-step.d.ts +26 -5
  78. package/src/lib/jobs/ai-job-site-step.js +163 -24
  79. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  80. package/src/lib/jobs/ai-jobs-notify.d.ts +6 -1
  81. package/src/lib/jobs/ai-jobs-notify.js +15 -3
  82. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  83. package/src/lib/layout-language/ai-layout-compiler.d.ts +9 -0
  84. package/src/lib/layout-language/ai-layout-compiler.js +120 -33
  85. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  86. package/src/lib/layout-language/ai-layout-frame.js +5 -4
  87. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  88. package/src/lib/layout-language/ai-layout-language.d.ts +12 -5
  89. package/src/lib/layout-language/ai-layout-language.js +92 -9
  90. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  91. package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
  92. package/src/lib/layout-language/ai-layout-pictures.js +214 -0
  93. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
  94. package/src/lib/layout-language/ai-layout-store.d.ts +2 -0
  95. package/src/lib/layout-language/ai-layout-store.js +7 -1
  96. package/src/lib/layout-language/ai-layout-store.js.map +1 -1
  97. package/src/lib/model/ai-build-progress.js +4 -1
  98. package/src/lib/model/ai-build-progress.js.map +1 -1
  99. package/src/lib/model/ai-edit-job.d.ts +109 -0
  100. package/src/lib/model/ai-edit-job.js +157 -0
  101. package/src/lib/model/ai-edit-job.js.map +1 -0
  102. package/src/lib/model/ai-job-activity.js +1 -0
  103. package/src/lib/model/ai-job-activity.js.map +1 -1
  104. package/src/lib/model/ai-jobs.types.d.ts +3 -1
  105. package/src/lib/model/ai-jobs.types.js.map +1 -1
  106. package/src/lib/model/ai-logic-job.d.ts +18 -1
  107. package/src/lib/model/ai-logic-job.js +24 -3
  108. package/src/lib/model/ai-logic-job.js.map +1 -1
  109. package/src/lib/model/ai-media-credits.d.ts +8 -4
  110. package/src/lib/model/ai-media-credits.js +11 -4
  111. package/src/lib/model/ai-media-credits.js.map +1 -1
  112. package/src/lib/model/ai-media-kinds.d.ts +68 -0
  113. package/src/lib/model/ai-media-kinds.js +98 -0
  114. package/src/lib/model/ai-media-kinds.js.map +1 -0
  115. package/src/lib/model/ai-site-build-progress.d.ts +4 -0
  116. package/src/lib/model/ai-site-build-progress.js +28 -11
  117. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  118. package/src/lib/model/ai-site-context.d.ts +111 -0
  119. package/src/lib/model/ai-site-context.js +222 -0
  120. package/src/lib/model/ai-site-context.js.map +1 -0
  121. package/src/lib/model/ai-site-job.d.ts +10 -2
  122. package/src/lib/model/ai-site-job.js +12 -5
  123. package/src/lib/model/ai-site-job.js.map +1 -1
  124. package/src/lib/model/ai-site-kinds.d.ts +164 -0
  125. package/src/lib/model/ai-site-kinds.js +1942 -0
  126. package/src/lib/model/ai-site-kinds.js.map +1 -0
  127. package/src/lib/model/ai-site-look.d.ts +150 -0
  128. package/src/lib/model/ai-site-look.js +1236 -0
  129. package/src/lib/model/ai-site-look.js.map +1 -0
  130. package/src/lib/model/ai-site-memory.d.ts +80 -0
  131. package/src/lib/model/ai-site-memory.js +171 -0
  132. package/src/lib/model/ai-site-memory.js.map +1 -0
  133. package/src/lib/model/ai-site-start.d.ts +10 -1
  134. package/src/lib/model/ai-site-start.js +16 -2
  135. package/src/lib/model/ai-site-start.js.map +1 -1
  136. package/src/lib/model/assist-follow-up.d.ts +92 -0
  137. package/src/lib/model/assist-follow-up.js +128 -0
  138. package/src/lib/model/assist-follow-up.js.map +1 -0
  139. package/src/lib/plugin-config.js +2 -0
  140. package/src/lib/plugin-config.js.map +1 -1
  141. package/src/lib/plugin.js +20 -1
  142. package/src/lib/plugin.js.map +1 -1
  143. package/src/lib/providers/anthropic.d.ts +9 -1
  144. package/src/lib/providers/anthropic.js +143 -9
  145. package/src/lib/providers/anthropic.js.map +1 -1
  146. package/src/lib/providers/catalog.d.ts +37 -7
  147. package/src/lib/providers/catalog.js +103 -11
  148. package/src/lib/providers/catalog.js.map +1 -1
  149. package/src/lib/providers/contract.d.ts +29 -0
  150. package/src/lib/providers/contract.js.map +1 -1
  151. package/src/lib/providers/image-contract.d.ts +22 -1
  152. package/src/lib/providers/image-contract.js +40 -1
  153. package/src/lib/providers/image-contract.js.map +1 -1
  154. package/src/lib/providers/image-endpoints.d.ts +24 -0
  155. package/src/lib/providers/image-endpoints.js +28 -0
  156. package/src/lib/providers/image-endpoints.js.map +1 -0
  157. package/src/lib/providers/model-choice.js +23 -2
  158. package/src/lib/providers/model-choice.js.map +1 -1
  159. package/src/lib/providers/routing.js +18 -0
  160. package/src/lib/providers/routing.js.map +1 -1
  161. package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
  162. package/src/lib/providers/vertex-image-endpoint.js +80 -0
  163. package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
  164. package/src/lib/providers/vertex-image.d.ts +13 -27
  165. package/src/lib/providers/vertex-image.js +15 -42
  166. package/src/lib/providers/vertex-image.js.map +1 -1
  167. package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
  168. package/src/lib/runtime/ai-blog-post-generation.js +89 -0
  169. package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
  170. package/src/lib/runtime/ai-dev-env.d.ts +53 -0
  171. package/src/lib/runtime/ai-dev-env.js +72 -0
  172. package/src/lib/runtime/ai-dev-env.js.map +1 -0
  173. package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
  174. package/src/lib/runtime/ai-dev-replay.js +313 -0
  175. package/src/lib/runtime/ai-dev-replay.js.map +1 -0
  176. package/src/lib/runtime/ai-doctrine.js +5 -1
  177. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  178. package/src/lib/runtime/ai-eval-live.js +4 -0
  179. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  180. package/src/lib/runtime/ai-eval.d.ts +13 -1
  181. package/src/lib/runtime/ai-eval.js +84 -1
  182. package/src/lib/runtime/ai-eval.js.map +1 -1
  183. package/src/lib/runtime/ai-live-batch.d.ts +45 -0
  184. package/src/lib/runtime/ai-live-batch.js +89 -0
  185. package/src/lib/runtime/ai-live-batch.js.map +1 -0
  186. package/src/lib/runtime/ai-runtime.js +46 -2
  187. package/src/lib/runtime/ai-runtime.js.map +1 -1
  188. package/src/lib/runtime/site-context.d.ts +58 -0
  189. package/src/lib/runtime/site-context.js +127 -0
  190. package/src/lib/runtime/site-context.js.map +1 -0
  191. package/src/lib/server/ai-admin-conversations.d.ts +22 -0
  192. package/src/lib/server/ai-admin-conversations.js +197 -0
  193. package/src/lib/server/ai-admin-conversations.js.map +1 -0
  194. package/src/lib/server/ai-media-image.d.ts +5 -5
  195. package/src/lib/server/ai-media-image.js +25 -11
  196. package/src/lib/server/ai-media-image.js.map +1 -1
  197. package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
  198. package/src/lib/server/ai-media-raster-prompt.js +53 -0
  199. package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
  200. package/src/lib/server/assist-chat.d.ts +11 -0
  201. package/src/lib/server/assist-chat.js +57 -18
  202. package/src/lib/server/assist-chat.js.map +1 -1
  203. package/src/lib/server/assist-edit-applied.js +16 -3
  204. package/src/lib/server/assist-edit-applied.js.map +1 -1
  205. package/src/lib/server/assist-view-context.d.ts +19 -1
  206. package/src/lib/server/assist-view-context.js +38 -1
  207. package/src/lib/server/assist-view-context.js.map +1 -1
  208. package/src/lib/server.js +10 -0
  209. package/src/lib/server.js.map +1 -1
  210. package/src/lib/subprocessors.js +17 -1
  211. package/src/lib/subprocessors.js.map +1 -1
  212. package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
  213. package/src/lib/tools/ai-blog-post-tool.js +175 -0
  214. package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
  215. package/src/lib/usage/assist-usage.d.ts +3 -0
  216. package/src/lib/usage/assist-usage.js +4 -2
  217. package/src/lib/usage/assist-usage.js.map +1 -1
  218. package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
  219. package/src/lib/usage/staff-org-ai-conversations.js +152 -0
  220. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
@@ -0,0 +1,214 @@
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
+ */ import { DEFAULT_SITE_IMAGES } from "@aglyn/aglyn/app-utils/default-site";
18
+ /** Words that say a picture shows people, or a section is about them. */ const PEOPLE = /\b(about|team|story|owner|owners|founder|founders|staff|crew|people|person|portrait|meet|family|families|who we are|our (?:team|crew|people|story)|therapist|counsel(?:l)?or|coach|doctor|dentist|stylist|trainer|smil(?:e|es|ing)|headshot)\b/i;
19
+ /** A frame's `aspectRatio` (`'16 / 9'`) as a number. */ function aspectOf(value) {
20
+ const match = /^\s*(\d+(?:\.\d+)?)\s*\/\s*(\d+(?:\.\d+)?)\s*$/.exec(String(value != null ? value : ''));
21
+ if (!match) return 1;
22
+ const ratio = Number(match[1]) / Number(match[2]);
23
+ return Number.isFinite(ratio) && ratio > 0 ? ratio : 1;
24
+ }
25
+ /** A slot's role: the first section's picture is the hero; people make an about picture. */ export function aiLayoutPictureRole(input) {
26
+ if (input.sectionIndex === 0) return 'hero';
27
+ if (PEOPLE.test(input.sectionName) || PEOPLE.test(input.alt)) return 'about';
28
+ return 'gallery';
29
+ }
30
+ /**
31
+ * Every empty picture slot of a stored page, walked from its root in
32
+ * document order. A slot that already has a source — one the owner placed, or
33
+ * an earlier pass filled — is not a slot.
34
+ */ export function aiLayoutPictureSlots(nodes, rootId, sections) {
35
+ const map = nodes;
36
+ const slots = [];
37
+ const seen = new Set();
38
+ const visit = (id, parentId, sectionIndex)=>{
39
+ var _node_nodes;
40
+ if (seen.has(id)) return;
41
+ seen.add(id);
42
+ const node = map[id];
43
+ if (!node) return;
44
+ const index = sections.ids.indexOf(id);
45
+ const here = index >= 0 ? index : sectionIndex;
46
+ if (node.componentId === 'image') {
47
+ var _node_props, _node_props1;
48
+ const src = (_node_props = node.props) == null ? void 0 : _node_props['src'];
49
+ const alt = typeof ((_node_props1 = node.props) == null ? void 0 : _node_props1['alt']) === 'string' ? node.props['alt'].trim() : '';
50
+ if ((typeof src !== 'string' || !src.trim()) && alt) {
51
+ var _ref, _sections_names_here;
52
+ var _frame_nodes, _frame_sx;
53
+ const frame = parentId ? map[parentId] : undefined;
54
+ const iconId = (_ref = frame == null ? void 0 : (_frame_nodes = frame.nodes) == null ? void 0 : _frame_nodes.find((child)=>{
55
+ var _map_child;
56
+ return child !== id && ((_map_child = map[child]) == null ? void 0 : _map_child.componentId) === 'icon';
57
+ })) != null ? _ref : null;
58
+ slots.push({
59
+ imageId: id,
60
+ frameId: frame ? parentId : null,
61
+ iconId,
62
+ alt,
63
+ aspect: aspectOf(frame == null ? void 0 : (_frame_sx = frame.sx) == null ? void 0 : _frame_sx['aspectRatio']),
64
+ sectionIndex: here,
65
+ role: aiLayoutPictureRole({
66
+ sectionIndex: here,
67
+ sectionName: here >= 0 ? (_sections_names_here = sections.names[here]) != null ? _sections_names_here : '' : '',
68
+ alt
69
+ })
70
+ });
71
+ }
72
+ }
73
+ for (const child of (_node_nodes = node.nodes) != null ? _node_nodes : [])visit(child, id, here);
74
+ };
75
+ visit(rootId, null, -1);
76
+ return slots;
77
+ }
78
+ /** The starter photos, by name. */ export const AI_LAYOUT_STARTER_PHOTOS = DEFAULT_SITE_IMAGES;
79
+ /**
80
+ * The starter photos each role draws from, best first. A hero draws from the
81
+ * wide photos that read as a page's opening, an about picture leads with the
82
+ * owner, and the rest are the gallery's.
83
+ */ const STARTER_POOLS = {
84
+ hero: [
85
+ 'hero',
86
+ 'desk',
87
+ 'craft'
88
+ ],
89
+ about: [
90
+ 'about',
91
+ 'hero'
92
+ ],
93
+ gallery: [
94
+ 'desk',
95
+ 'plants',
96
+ 'craft'
97
+ ]
98
+ };
99
+ const ALL_STARTERS = Object.keys(AI_LAYOUT_STARTER_PHOTOS);
100
+ /** A small stable number from a seed, so the same job always picks the same photos. */ export function aiLayoutSeedNumber(seed) {
101
+ let hash = 2166136261;
102
+ for(let index = 0; index < seed.length; index += 1){
103
+ hash ^= seed.charCodeAt(index);
104
+ hash = Math.imul(hash, 16777619);
105
+ }
106
+ return hash >>> 0;
107
+ }
108
+ function rotate(list, by) {
109
+ if (!list.length) return [];
110
+ const start = by % list.length;
111
+ return [
112
+ ...list.slice(start),
113
+ ...list.slice(0, start)
114
+ ];
115
+ }
116
+ /**
117
+ * A starter photo for each slot. The hero's and the gallery's pools are
118
+ * rotated by the seed, so sites do not all open with the same photo, while an
119
+ * about picture always leads with the owner; no photo repeats on a page
120
+ * while one is still unused; and a page with more slots than photos starts
121
+ * the round again from its role's pool.
122
+ */ export function aiLayoutStarterPhotos(slots, seed) {
123
+ const turn = aiLayoutSeedNumber(seed);
124
+ const used = new Set();
125
+ return slots.map((slot)=>{
126
+ var _find;
127
+ // An about picture leads with the owner on every site; the others turn.
128
+ const pool = slot.role === 'about' ? [
129
+ ...STARTER_POOLS.about
130
+ ] : rotate(STARTER_POOLS[slot.role], turn);
131
+ const rest = rotate(ALL_STARTERS.filter((name)=>!pool.includes(name)), turn);
132
+ if (used.size >= ALL_STARTERS.length) used.clear();
133
+ const name = (_find = [
134
+ ...pool,
135
+ ...rest
136
+ ].find((candidate)=>!used.has(candidate))) != null ? _find : pool[0];
137
+ used.add(name);
138
+ const photo = AI_LAYOUT_STARTER_PHOTOS[name];
139
+ return {
140
+ src: photo.src,
141
+ width: photo.width,
142
+ height: photo.height
143
+ };
144
+ });
145
+ }
146
+ /**
147
+ * The page with each slot filled by its photo: the `image` given its source,
148
+ * its intrinsic size and how it loads (the hero eagerly, as the starter home
149
+ * page loads its own), and the placeholder icon taken out of the frame. The
150
+ * alt text is kept as the model wrote it. A slot given no photo is left as
151
+ * the compiler wrote it.
152
+ */ export function aiLayoutPlacePictures(nodes, slots, photos) {
153
+ const next = _extends({}, nodes);
154
+ slots.forEach((slot, index)=>{
155
+ var _node_props;
156
+ const photo = photos[index];
157
+ const node = next[slot.imageId];
158
+ if (!photo || !node) return;
159
+ next[slot.imageId] = _extends({}, node, {
160
+ props: _extends({}, (_node_props = node['props']) != null ? _node_props : {}, {
161
+ src: photo.src,
162
+ intrinsicWidth: photo.width,
163
+ intrinsicHeight: photo.height,
164
+ loading: slot.role === 'hero' ? 'eager' : 'lazy'
165
+ })
166
+ });
167
+ if (slot.iconId && slot.frameId && next[slot.frameId]) {
168
+ var _frame_nodes;
169
+ const frame = next[slot.frameId];
170
+ next[slot.frameId] = _extends({}, frame, {
171
+ nodes: ((_frame_nodes = frame['nodes']) != null ? _frame_nodes : []).filter((child)=>child !== slot.iconId)
172
+ });
173
+ delete next[slot.iconId];
174
+ }
175
+ });
176
+ return next;
177
+ }
178
+ /**
179
+ * Fills a stored page's empty picture slots (AGL-3660): from `source` where
180
+ * one is given and it answers, else from the starter photos. Never throws,
181
+ * and never leaves a page worse than it came: a picture is decoration, and a
182
+ * job never fails over one.
183
+ */ export async function aiResolveLayoutPictures(nodes, input) {
184
+ try {
185
+ const slots = aiLayoutPictureSlots(nodes, input.rootId, {
186
+ ids: input.sectionIds,
187
+ names: input.sectionNames
188
+ });
189
+ if (!slots.length) return nodes;
190
+ let found = [];
191
+ if (input.source) {
192
+ try {
193
+ found = await input.source(slots);
194
+ } catch (error) {
195
+ console.warn('ai layout pictures: the stock source failed; starter photos fill the page', {
196
+ error
197
+ });
198
+ found = [];
199
+ }
200
+ }
201
+ const starters = aiLayoutStarterPhotos(slots, input.seed);
202
+ return aiLayoutPlacePictures(nodes, slots, slots.map((_slot, index)=>{
203
+ var _found_index;
204
+ return (_found_index = found[index]) != null ? _found_index : starters[index];
205
+ }));
206
+ } catch (error) {
207
+ console.error('ai layout pictures: the page keeps its empty picture slots', {
208
+ error
209
+ });
210
+ return nodes;
211
+ }
212
+ }
213
+
214
+ //# sourceMappingURL=ai-layout-pictures.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-pictures.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 { DEFAULT_SITE_IMAGES } from '@aglyn/aglyn/app-utils/default-site'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\n\n/**\n * THE PICTURES OF A LANGUAGE PAGE (AGL-3660).\n *\n * The compiler writes a picture slot as a frame holding an icon and an\n * `image` with alt text and no source (`ai-layout-compiler.ts`), and stays\n * pure: it never looks a photo up. This module is what runs after it, on the\n * stored page — it finds each empty slot, decides what kind of picture the\n * slot is, and fills it with a photo the site can serve from its own origin.\n *\n * Two sources fill a slot, in order:\n *\n * 1. A stock photo searched for by the slot's own description and copied into\n * the site's media library (`jobs/ai-layout-stock-photos.ts`), when the\n * deployment has a stock-photo key.\n * 2. Otherwise, or when the search finds nothing fitting, one of the STARTER\n * PHOTOS every new site is born with (`DEFAULT_SITE_IMAGES`, served at\n * `/_static/starter/` by the tenant and the console alike, credited in the\n * CREDITS file beside them). They are the platform's own files, so a\n * visitor's browser asks no other host for them.\n *\n * The slot keeps the alt text the model wrote whichever source fills it.\n */\n\n/** What a picture slot is for, read from where it sits and what it says. */\nexport type AiLayoutPictureRole = 'hero' | 'about' | 'gallery'\n\n/** One empty picture slot of a stored page, in document order. */\nexport interface AiLayoutPictureSlot {\n /** The `image` node. */\n imageId: string\n /** The frame around it, which sets the slot's shape. */\n frameId: string | null\n /** The placeholder icon sharing the frame, taken out once a photo fills it. */\n iconId: string | null\n alt: string\n /** The frame's shape as width over height; 1 when it names none. */\n aspect: number\n /** The plan index of the section it sits in, or -1. */\n sectionIndex: number\n role: AiLayoutPictureRole\n}\n\n/** A photo a slot is filled with. */\nexport interface AiLayoutPicturePhoto {\n /** The site-served address: a starter path or the media library's own. */\n src: string\n width: number\n height: number\n}\n\n/** Words that say a picture shows people, or a section is about them. */\nconst PEOPLE =\n /\\b(about|team|story|owner|owners|founder|founders|staff|crew|people|person|portrait|meet|family|families|who we are|our (?:team|crew|people|story)|therapist|counsel(?:l)?or|coach|doctor|dentist|stylist|trainer|smil(?:e|es|ing)|headshot)\\b/i\n\n/** A frame's `aspectRatio` (`'16 / 9'`) as a number. */\nfunction aspectOf(value: unknown): number {\n const match = /^\\s*(\\d+(?:\\.\\d+)?)\\s*\\/\\s*(\\d+(?:\\.\\d+)?)\\s*$/.exec(String(value ?? ''))\n if (!match) return 1\n const ratio = Number(match[1]) / Number(match[2])\n return Number.isFinite(ratio) && ratio > 0 ? ratio : 1\n}\n\n/** A slot's role: the first section's picture is the hero; people make an about picture. */\nexport function aiLayoutPictureRole(input: {\n sectionIndex: number\n sectionName: string\n alt: string\n}): AiLayoutPictureRole {\n if (input.sectionIndex === 0) return 'hero'\n if (PEOPLE.test(input.sectionName) || PEOPLE.test(input.alt)) return 'about'\n return 'gallery'\n}\n\n/**\n * Every empty picture slot of a stored page, walked from its root in\n * document order. A slot that already has a source — one the owner placed, or\n * an earlier pass filled — is not a slot.\n */\nexport function aiLayoutPictureSlots(\n nodes: NodesMap,\n rootId: string,\n sections: { ids: readonly string[]; names: readonly string[] },\n): AiLayoutPictureSlot[] {\n const map = nodes as unknown as Record<\n string,\n { componentId?: string; props?: Record<string, unknown>; sx?: Record<string, unknown>; nodes?: string[] }\n >\n const slots: AiLayoutPictureSlot[] = []\n const seen = new Set<string>()\n const visit = (id: string, parentId: string | null, sectionIndex: number) => {\n if (seen.has(id)) return\n seen.add(id)\n const node = map[id]\n if (!node) return\n const index = sections.ids.indexOf(id)\n const here = index >= 0 ? index : sectionIndex\n if (node.componentId === 'image') {\n const src = node.props?.['src']\n const alt = typeof node.props?.['alt'] === 'string' ? (node.props['alt'] as string).trim() : ''\n if ((typeof src !== 'string' || !src.trim()) && alt) {\n const frame = parentId ? map[parentId] : undefined\n const iconId =\n frame?.nodes?.find((child) => child !== id && map[child]?.componentId === 'icon') ?? null\n slots.push({\n imageId: id,\n frameId: frame ? parentId : null,\n iconId,\n alt,\n aspect: aspectOf(frame?.sx?.['aspectRatio']),\n sectionIndex: here,\n role: aiLayoutPictureRole({\n sectionIndex: here,\n sectionName: here >= 0 ? (sections.names[here] ?? '') : '',\n alt,\n }),\n })\n }\n }\n for (const child of node.nodes ?? []) visit(child, id, here)\n }\n visit(rootId, null, -1)\n return slots\n}\n\n/** The starter photos, by name. */\nexport const AI_LAYOUT_STARTER_PHOTOS = DEFAULT_SITE_IMAGES\n\ntype StarterName = keyof typeof AI_LAYOUT_STARTER_PHOTOS\n\n/**\n * The starter photos each role draws from, best first. A hero draws from the\n * wide photos that read as a page's opening, an about picture leads with the\n * owner, and the rest are the gallery's.\n */\nconst STARTER_POOLS: Readonly<Record<AiLayoutPictureRole, readonly StarterName[]>> = {\n hero: ['hero', 'desk', 'craft'],\n about: ['about', 'hero'],\n gallery: ['desk', 'plants', 'craft'],\n}\n\nconst ALL_STARTERS = Object.keys(AI_LAYOUT_STARTER_PHOTOS) as StarterName[]\n\n/** A small stable number from a seed, so the same job always picks the same photos. */\nexport function aiLayoutSeedNumber(seed: string): number {\n let hash = 2166136261\n for (let index = 0; index < seed.length; index += 1) {\n hash ^= seed.charCodeAt(index)\n hash = Math.imul(hash, 16777619)\n }\n return hash >>> 0\n}\n\nfunction rotate<T>(list: readonly T[], by: number): T[] {\n if (!list.length) return []\n const start = by % list.length\n return [...list.slice(start), ...list.slice(0, start)]\n}\n\n/**\n * A starter photo for each slot. The hero's and the gallery's pools are\n * rotated by the seed, so sites do not all open with the same photo, while an\n * about picture always leads with the owner; no photo repeats on a page\n * while one is still unused; and a page with more slots than photos starts\n * the round again from its role's pool.\n */\nexport function aiLayoutStarterPhotos(\n slots: readonly AiLayoutPictureSlot[],\n seed: string,\n): AiLayoutPicturePhoto[] {\n const turn = aiLayoutSeedNumber(seed)\n const used = new Set<StarterName>()\n return slots.map((slot) => {\n // An about picture leads with the owner on every site; the others turn.\n const pool =\n slot.role === 'about' ? [...STARTER_POOLS.about] : rotate(STARTER_POOLS[slot.role], turn)\n const rest = rotate(\n ALL_STARTERS.filter((name) => !pool.includes(name)),\n turn,\n )\n if (used.size >= ALL_STARTERS.length) used.clear()\n const name = [...pool, ...rest].find((candidate) => !used.has(candidate)) ?? pool[0]\n used.add(name)\n const photo = AI_LAYOUT_STARTER_PHOTOS[name]\n return { src: photo.src, width: photo.width, height: photo.height }\n })\n}\n\n/**\n * The page with each slot filled by its photo: the `image` given its source,\n * its intrinsic size and how it loads (the hero eagerly, as the starter home\n * page loads its own), and the placeholder icon taken out of the frame. The\n * alt text is kept as the model wrote it. A slot given no photo is left as\n * the compiler wrote it.\n */\nexport function aiLayoutPlacePictures(\n nodes: NodesMap,\n slots: readonly AiLayoutPictureSlot[],\n photos: ReadonlyArray<AiLayoutPicturePhoto | null>,\n): NodesMap {\n const next = { ...(nodes as unknown as Record<string, Record<string, unknown>>) }\n slots.forEach((slot, index) => {\n const photo = photos[index]\n const node = next[slot.imageId]\n if (!photo || !node) return\n next[slot.imageId] = {\n ...node,\n props: {\n ...((node['props'] as Record<string, unknown> | undefined) ?? {}),\n src: photo.src,\n intrinsicWidth: photo.width,\n intrinsicHeight: photo.height,\n loading: slot.role === 'hero' ? 'eager' : 'lazy',\n },\n }\n if (slot.iconId && slot.frameId && next[slot.frameId]) {\n const frame = next[slot.frameId]\n next[slot.frameId] = {\n ...frame,\n nodes: ((frame['nodes'] as string[] | undefined) ?? []).filter((child) => child !== slot.iconId),\n }\n delete next[slot.iconId]\n }\n })\n return next as unknown as NodesMap\n}\n\n/**\n * A source of photos found for the slots themselves — a stock search copied\n * into the site's library — answering one photo or `null` per slot, in order.\n * It may throw; a slot it leaves unfilled takes a starter photo.\n */\nexport type AiLayoutPictureSource = (\n slots: readonly AiLayoutPictureSlot[],\n) => Promise<ReadonlyArray<AiLayoutPicturePhoto | null>>\n\n/**\n * Fills a stored page's empty picture slots (AGL-3660): from `source` where\n * one is given and it answers, else from the starter photos. Never throws,\n * and never leaves a page worse than it came: a picture is decoration, and a\n * job never fails over one.\n */\nexport async function aiResolveLayoutPictures(\n nodes: NodesMap,\n input: {\n rootId: string\n sectionIds: readonly string[]\n sectionNames: readonly string[]\n /** A per-job word, so two sites do not open with the same photo. */\n seed: string\n source?: AiLayoutPictureSource | null\n },\n): Promise<NodesMap> {\n try {\n const slots = aiLayoutPictureSlots(nodes, input.rootId, {\n ids: input.sectionIds,\n names: input.sectionNames,\n })\n if (!slots.length) return nodes\n let found: ReadonlyArray<AiLayoutPicturePhoto | null> = []\n if (input.source) {\n try {\n found = await input.source(slots)\n } catch (error) {\n console.warn('ai layout pictures: the stock source failed; starter photos fill the page', { error })\n found = []\n }\n }\n const starters = aiLayoutStarterPhotos(slots, input.seed)\n return aiLayoutPlacePictures(\n nodes,\n slots,\n slots.map((_slot, index) => found[index] ?? starters[index]),\n )\n } catch (error) {\n console.error('ai layout pictures: the page keeps its empty picture slots', { error })\n return nodes\n }\n}\n"],"names":["DEFAULT_SITE_IMAGES","PEOPLE","aspectOf","value","match","exec","String","ratio","Number","isFinite","aiLayoutPictureRole","input","sectionIndex","test","sectionName","alt","aiLayoutPictureSlots","nodes","rootId","sections","map","slots","seen","Set","visit","id","parentId","node","has","add","index","ids","indexOf","here","componentId","src","props","trim","frame","undefined","iconId","find","child","push","imageId","frameId","aspect","sx","role","names","AI_LAYOUT_STARTER_PHOTOS","STARTER_POOLS","hero","about","gallery","ALL_STARTERS","Object","keys","aiLayoutSeedNumber","seed","hash","length","charCodeAt","Math","imul","rotate","list","by","start","slice","aiLayoutStarterPhotos","turn","used","slot","pool","rest","filter","name","includes","size","clear","candidate","photo","width","height","aiLayoutPlacePictures","photos","next","forEach","intrinsicWidth","intrinsicHeight","loading","aiResolveLayoutPictures","sectionIds","sectionNames","found","source","error","console","warn","starters","_slot"],"mappings":"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"}
@@ -32,6 +32,8 @@ export type AiLayoutStored = {
32
32
  nodes: NodesMap;
33
33
  rootId: string;
34
34
  repairs: string[];
35
+ /** Each compiled id → the id its node is stored under, for a node the validator kept. */
36
+ storedIds: Record<string, string>;
35
37
  } | {
36
38
  ok: false;
37
39
  error: string;
@@ -58,11 +58,17 @@ export function aiLayoutStoredTree(compiled, surface, context, keepIds = []) {
58
58
  interactions: source.interactions
59
59
  } : {});
60
60
  }
61
+ const storedIds = {};
62
+ for (const minted of Object.keys(validated.nodes)){
63
+ const source = validated.sourceIds[minted];
64
+ if (source) storedIds[source] = storedId(minted);
65
+ }
61
66
  return {
62
67
  ok: true,
63
68
  nodes: nodes,
64
69
  rootId: storedId(validated.rootId),
65
- repairs: validated.repairs
70
+ repairs: validated.repairs,
71
+ storedIds
66
72
  };
67
73
  }
68
74
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-store.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 type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport {\n validateAiNodeTree,\n type AiNodeTreeContext,\n} from '../runtime/ai-node-tree'\nimport type { AiSurface } from '../runtime/ai-palette'\nimport type { AiLayoutRawTree } from './ai-layout-tree'\n\n/**\n * A compiled tree as a document stores it (AGL-3660): read by the same\n * palette validator every generated tree passes, as a code-built tree, so\n * what is stored is exactly what any other generated document would store.\n * The validator mints fresh ids; the ids a caller names (a page's section\n * roots, which its plan minted) keep their own, and the interactions code\n * wrote — which the validator strips from anything it stores — are carried\n * over onto the stored nodes.\n */\n\nexport type AiLayoutStored =\n | { ok: true; nodes: NodesMap; rootId: string; repairs: string[] }\n | { ok: false; error: string }\n\ntype StoredNode = Record<string, unknown> & {\n $id: string\n parentId: string | null\n nodes?: string[]\n}\n\nexport function aiLayoutStoredTree(\n compiled: AiLayoutRawTree,\n surface: AiSurface,\n context: AiNodeTreeContext,\n keepIds: readonly string[] = [],\n): AiLayoutStored {\n // The interactions are code's, carried over below; the validator is shown the rest.\n const bare = {\n rootId: compiled.rootId,\n nodes: Object.fromEntries(\n Object.entries(compiled.nodes).map(\n ([id, { interactions: _interactions, ...node }]) => [id, node],\n ),\n ),\n }\n const validated = validateAiNodeTree(bare, surface, {\n ...context,\n codeBuilt: true,\n })\n if (validated.ok === false) return { ok: false, error: validated.error }\n const keep = new Set(keepIds)\n // Minted → the id it is stored under: its own, or the one the caller keeps.\n const storedId = (minted: string): string => {\n const source = validated.sourceIds[minted]\n return source && keep.has(source) ? source : minted\n }\n const nodes: Record<string, StoredNode> = {}\n for (const [minted, raw] of Object.entries(\n validated.nodes as unknown as Record<string, StoredNode>,\n )) {\n const id = storedId(minted)\n const source = compiled.nodes[validated.sourceIds[minted] ?? '']\n nodes[id] = {\n ...raw,\n $id: id,\n parentId: raw.parentId === null ? null : storedId(raw.parentId),\n ...(Array.isArray(raw.nodes) ? { nodes: raw.nodes.map(storedId) } : {}),\n ...(source?.interactions?.length\n ? { interactions: source.interactions }\n : {}),\n }\n }\n return {\n ok: true,\n nodes: nodes as unknown as NodesMap,\n rootId: storedId(validated.rootId),\n repairs: validated.repairs,\n }\n}\n"],"names":["validateAiNodeTree","aiLayoutStoredTree","compiled","surface","context","keepIds","bare","rootId","nodes","Object","fromEntries","entries","map","id","interactions","_interactions","node","validated","codeBuilt","ok","error","keep","Set","storedId","minted","source","sourceIds","has","raw","$id","parentId","Array","isArray","length","repairs"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;;AAGD,SACEA,kBAAkB,QAEb,6BAAyB;AAwBhC,OAAO,SAASC,mBACdC,QAAyB,EACzBC,OAAkB,EAClBC,OAA0B,EAC1BC,UAA6B,EAAE;IAE/B,oFAAoF;IACpF,MAAMC,OAAO;QACXC,QAAQL,SAASK,MAAM;QACvBC,OAAOC,OAAOC,WAAW,CACvBD,OAAOE,OAAO,CAACT,SAASM,KAAK,EAAEI,GAAG,CAChC;gBAAC,CAACC,SAA6C,OAAzC,EAAEC,cAAcC,aAAa,EAAW,SAANC;;;mBAAY;gBAACH;gBAAIG;aAAK;;IAGpE;IACA,MAAMC,YAAYjB,mBAAmBM,MAAMH,SAAS,aAC/CC;QACHc,WAAW;;IAEb,IAAID,UAAUE,EAAE,KAAK,OAAO,OAAO;QAAEA,IAAI;QAAOC,OAAOH,UAAUG,KAAK;IAAC;IACvE,MAAMC,OAAO,IAAIC,IAAIjB;IACrB,4EAA4E;IAC5E,MAAMkB,WAAW,CAACC;QAChB,MAAMC,SAASR,UAAUS,SAAS,CAACF,OAAO;QAC1C,OAAOC,UAAUJ,KAAKM,GAAG,CAACF,UAAUA,SAASD;IAC/C;IACA,MAAMhB,QAAoC,CAAC;IAC3C,KAAK,MAAM,CAACgB,QAAQI,IAAI,IAAInB,OAAOE,OAAO,CACxCM,UAAUT,KAAK,EACd;YAE6BS;YAMxBQ;QAPN,MAAMZ,KAAKU,SAASC;QACpB,MAAMC,SAASvB,SAASM,KAAK,EAACS,8BAAAA,UAAUS,SAAS,CAACF,OAAO,YAA3BP,8BAA+B,GAAG;QAChET,KAAK,CAACK,GAAG,GAAG,aACPe;YACHC,KAAKhB;YACLiB,UAAUF,IAAIE,QAAQ,KAAK,OAAO,OAAOP,SAASK,IAAIE,QAAQ;WAC1DC,MAAMC,OAAO,CAACJ,IAAIpB,KAAK,IAAI;YAAEA,OAAOoB,IAAIpB,KAAK,CAACI,GAAG,CAACW;QAAU,IAAI,CAAC,GACjEE,CAAAA,2BAAAA,uBAAAA,OAAQX,YAAY,qBAApBW,qBAAsBQ,MAAM,IAC5B;YAAEnB,cAAcW,OAAOX,YAAY;QAAC,IACpC,CAAC;IAET;IACA,OAAO;QACLK,IAAI;QACJX,OAAOA;QACPD,QAAQgB,SAASN,UAAUV,MAAM;QACjC2B,SAASjB,UAAUiB,OAAO;IAC5B;AACF"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-store.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 type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport {\n validateAiNodeTree,\n type AiNodeTreeContext,\n} from '../runtime/ai-node-tree'\nimport type { AiSurface } from '../runtime/ai-palette'\nimport type { AiLayoutRawTree } from './ai-layout-tree'\n\n/**\n * A compiled tree as a document stores it (AGL-3660): read by the same\n * palette validator every generated tree passes, as a code-built tree, so\n * what is stored is exactly what any other generated document would store.\n * The validator mints fresh ids; the ids a caller names (a page's section\n * roots, which its plan minted) keep their own, and the interactions code\n * wrote — which the validator strips from anything it stores — are carried\n * over onto the stored nodes.\n */\n\nexport type AiLayoutStored =\n | {\n ok: true\n nodes: NodesMap\n rootId: string\n repairs: string[]\n /** Each compiled id → the id its node is stored under, for a node the validator kept. */\n storedIds: Record<string, string>\n }\n | { ok: false; error: string }\n\ntype StoredNode = Record<string, unknown> & {\n $id: string\n parentId: string | null\n nodes?: string[]\n}\n\nexport function aiLayoutStoredTree(\n compiled: AiLayoutRawTree,\n surface: AiSurface,\n context: AiNodeTreeContext,\n keepIds: readonly string[] = [],\n): AiLayoutStored {\n // The interactions are code's, carried over below; the validator is shown the rest.\n const bare = {\n rootId: compiled.rootId,\n nodes: Object.fromEntries(\n Object.entries(compiled.nodes).map(\n ([id, { interactions: _interactions, ...node }]) => [id, node],\n ),\n ),\n }\n const validated = validateAiNodeTree(bare, surface, {\n ...context,\n codeBuilt: true,\n })\n if (validated.ok === false) return { ok: false, error: validated.error }\n const keep = new Set(keepIds)\n // Minted → the id it is stored under: its own, or the one the caller keeps.\n const storedId = (minted: string): string => {\n const source = validated.sourceIds[minted]\n return source && keep.has(source) ? source : minted\n }\n const nodes: Record<string, StoredNode> = {}\n for (const [minted, raw] of Object.entries(\n validated.nodes as unknown as Record<string, StoredNode>,\n )) {\n const id = storedId(minted)\n const source = compiled.nodes[validated.sourceIds[minted] ?? '']\n nodes[id] = {\n ...raw,\n $id: id,\n parentId: raw.parentId === null ? null : storedId(raw.parentId),\n ...(Array.isArray(raw.nodes) ? { nodes: raw.nodes.map(storedId) } : {}),\n ...(source?.interactions?.length\n ? { interactions: source.interactions }\n : {}),\n }\n }\n const storedIds: Record<string, string> = {}\n for (const minted of Object.keys(validated.nodes)) {\n const source = validated.sourceIds[minted]\n if (source) storedIds[source] = storedId(minted)\n }\n return {\n ok: true,\n nodes: nodes as unknown as NodesMap,\n rootId: storedId(validated.rootId),\n repairs: validated.repairs,\n storedIds,\n }\n}\n"],"names":["validateAiNodeTree","aiLayoutStoredTree","compiled","surface","context","keepIds","bare","rootId","nodes","Object","fromEntries","entries","map","id","interactions","_interactions","node","validated","codeBuilt","ok","error","keep","Set","storedId","minted","source","sourceIds","has","raw","$id","parentId","Array","isArray","length","storedIds","keys","repairs"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;;AAGD,SACEA,kBAAkB,QAEb,6BAAyB;AA+BhC,OAAO,SAASC,mBACdC,QAAyB,EACzBC,OAAkB,EAClBC,OAA0B,EAC1BC,UAA6B,EAAE;IAE/B,oFAAoF;IACpF,MAAMC,OAAO;QACXC,QAAQL,SAASK,MAAM;QACvBC,OAAOC,OAAOC,WAAW,CACvBD,OAAOE,OAAO,CAACT,SAASM,KAAK,EAAEI,GAAG,CAChC;gBAAC,CAACC,SAA6C,OAAzC,EAAEC,cAAcC,aAAa,EAAW,SAANC;;;mBAAY;gBAACH;gBAAIG;aAAK;;IAGpE;IACA,MAAMC,YAAYjB,mBAAmBM,MAAMH,SAAS,aAC/CC;QACHc,WAAW;;IAEb,IAAID,UAAUE,EAAE,KAAK,OAAO,OAAO;QAAEA,IAAI;QAAOC,OAAOH,UAAUG,KAAK;IAAC;IACvE,MAAMC,OAAO,IAAIC,IAAIjB;IACrB,4EAA4E;IAC5E,MAAMkB,WAAW,CAACC;QAChB,MAAMC,SAASR,UAAUS,SAAS,CAACF,OAAO;QAC1C,OAAOC,UAAUJ,KAAKM,GAAG,CAACF,UAAUA,SAASD;IAC/C;IACA,MAAMhB,QAAoC,CAAC;IAC3C,KAAK,MAAM,CAACgB,QAAQI,IAAI,IAAInB,OAAOE,OAAO,CACxCM,UAAUT,KAAK,EACd;YAE6BS;YAMxBQ;QAPN,MAAMZ,KAAKU,SAASC;QACpB,MAAMC,SAASvB,SAASM,KAAK,EAACS,8BAAAA,UAAUS,SAAS,CAACF,OAAO,YAA3BP,8BAA+B,GAAG;QAChET,KAAK,CAACK,GAAG,GAAG,aACPe;YACHC,KAAKhB;YACLiB,UAAUF,IAAIE,QAAQ,KAAK,OAAO,OAAOP,SAASK,IAAIE,QAAQ;WAC1DC,MAAMC,OAAO,CAACJ,IAAIpB,KAAK,IAAI;YAAEA,OAAOoB,IAAIpB,KAAK,CAACI,GAAG,CAACW;QAAU,IAAI,CAAC,GACjEE,CAAAA,2BAAAA,uBAAAA,OAAQX,YAAY,qBAApBW,qBAAsBQ,MAAM,IAC5B;YAAEnB,cAAcW,OAAOX,YAAY;QAAC,IACpC,CAAC;IAET;IACA,MAAMoB,YAAoC,CAAC;IAC3C,KAAK,MAAMV,UAAUf,OAAO0B,IAAI,CAAClB,UAAUT,KAAK,EAAG;QACjD,MAAMiB,SAASR,UAAUS,SAAS,CAACF,OAAO;QAC1C,IAAIC,QAAQS,SAAS,CAACT,OAAO,GAAGF,SAASC;IAC3C;IACA,OAAO;QACLL,IAAI;QACJX,OAAOA;QACPD,QAAQgB,SAASN,UAAUV,MAAM;QACjC6B,SAASnB,UAAUmB,OAAO;QAC1BF;IACF;AACF"}
@@ -24,7 +24,10 @@ const OP_NOUNS = {
24
24
  email: 'Email design',
25
25
  template: 'Page template',
26
26
  campaign: 'Email campaign',
27
- workflow: 'Automation'
27
+ workflow: 'Automation',
28
+ // A guided start's first posts and products (AGL-3676).
29
+ posts: 'Blog',
30
+ products: 'Store'
28
31
  };
29
32
  /** What an operation is called at the start of a row: its own noun, or the op spelled as words. */ export function aiBuildOpNoun(op) {
30
33
  const known = OP_NOUNS[op];
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-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 { aiCustomerSafeItem } from './ai-job-failure-copy'\nimport { AI_JOB_PLAN_STEP, type AiJobItemLedger, type AiJobSummary } from './ai-jobs.types'\n\n/**\n * What a `build` job shows of each item (AGL-3616), wherever it is shown —\n * the AI jobs drawer, the job's own page, the chat card: the item, where it\n * stands, what it cost, and what became of its credits when it failed.\n *\n * Pure: no React, no request.\n */\n\n/** Where one item stands, as a row's icon reads it. */\nexport type AiBuildItemRowState = 'done' | 'active' | 'waiting' | 'failed' | 'skipped'\n\nexport interface AiBuildItemRow {\n slot: string\n /** \"Page: About\", \"Form: Contact\". */\n label: string\n state: AiBuildItemRowState\n /** What the person should know: the failure, the note, what was given back. */\n detail: string | null\n /** When the active item started, for the elapsed time beside it; absent on every other row. */\n startedAt?: string | null\n}\n\nconst OP_NOUNS: Readonly<Record<string, string>> = {\n page: 'Page',\n layout: 'Layout',\n form: 'Form',\n component: 'Component',\n email: 'Email design',\n template: 'Page template',\n campaign: 'Email campaign',\n workflow: 'Automation',\n}\n\n/** What an operation is called at the start of a row: its own noun, or the op spelled as words. */\nexport function aiBuildOpNoun(op: string): string {\n const known = OP_NOUNS[op]\n if (known) return known\n const words = op.replace(/[-_]+/g, ' ').trim()\n return words ? `${words[0].toUpperCase()}${words.slice(1)}` : 'Item'\n}\n\n/**\n * What became of a failed item's credits (AGL-3616), in the words every\n * surface uses for a failure on our side: \"This one’s on us — you weren’t\n * charged.\" A failure that was not ours — the model declining, a site at its\n * allowance — says what it used, which was charged. `null` for an item that\n * did not fail.\n */\nexport function aiBuildItemRefundCopy(\n row: Pick<AiJobItemLedger, 'status' | 'failure' | 'creditsRefunded' | 'creditsSpent'>,\n): string | null {\n if (row.status !== 'failed') return null\n if (row.failure?.ours) {\n const refunded = Math.max(0, Math.floor(row.creditsRefunded ?? 0))\n if (refunded > 0) {\n return `This one’s on us — you weren’t charged. The ${refunded} ${refunded === 1 ? 'credit' : 'credits'} it used ${refunded === 1 ? 'is' : 'are'} back in your AI credits.`\n }\n return 'This one’s on us — you weren’t charged.'\n }\n const spent = Math.max(0, Math.floor(row.creditsSpent ?? 0))\n return spent > 0 ? `It used ${spent} ${spent === 1 ? 'credit' : 'credits'}.` : null\n}\n\nconst STATES: Readonly<Record<AiJobItemLedger['status'], AiBuildItemRowState>> = {\n pending: 'waiting',\n running: 'active',\n succeeded: 'done',\n degraded: 'done',\n failed: 'failed',\n skipped: 'skipped',\n}\n\n/**\n * The item the build is on (AGL-3596): the one its ledger marks running, else\n * — while the job moves — the first still pending, which is the one its runner\n * builds next. The runner settles an item in the write that records its pass,\n * so the item it is writing reads `pending` until it is done; read as such,\n * every row waited and the build looked stalled. `null` when the job is not\n * moving or nothing is left.\n */\nexport function aiBuildActiveSlot(\n job: Pick<AiJobSummary, 'items'> & Partial<Pick<AiJobSummary, 'status'>>,\n): string | null {\n const items = job.items ?? []\n const running = items.find((row) => row.status === 'running')\n if (running) return running.slot\n if (job.status !== 'queued' && job.status !== 'running') return null\n return items.find((row) => row.status === 'pending')?.slot ?? null\n}\n\n/**\n * When the active item started: when the last item before it settled, which\n * the ledger records, else when the build step started — the first item's\n * start, and the best a ledger written before settling was recorded has.\n */\nexport function aiBuildActiveStartedAt(\n job: Pick<AiJobSummary, 'items'> & Partial<Pick<AiJobSummary, 'steps'>>,\n): string | null {\n let latest: { at: number; iso: string } | null = null\n for (const row of job.items ?? []) {\n const at = row.settledAt ? Date.parse(row.settledAt) : NaN\n if (Number.isFinite(at) && (!latest || at > latest.at)) latest = { at, iso: row.settledAt as string }\n }\n if (latest) return latest.iso\n const step = (job.steps ?? []).find((one) => one.name !== AI_JOB_PLAN_STEP && one.startedAt)\n return step?.startedAt ?? null\n}\n\n/** One row an item, in the order the build planned them; the item it is on is the active one. */\nexport function aiBuildItemRows(\n job: Pick<AiJobSummary, 'items'> & Partial<Pick<AiJobSummary, 'status' | 'steps'>>,\n): AiBuildItemRow[] {\n const active = aiBuildActiveSlot(job)\n return (job.items ?? []).map((ledger) => {\n // A building rule's own words never reach a customer (AGL-3596).\n const row = aiCustomerSafeItem(ledger)\n const parts: string[] = []\n if (row.status === 'failed' && row.failure?.message) parts.push(row.failure.message)\n if (row.note) parts.push(row.note)\n const refund = aiBuildItemRefundCopy(row)\n if (refund) parts.push(refund)\n return {\n slot: row.slot,\n label: `${aiBuildOpNoun(row.op)}: ${row.label}`,\n state: row.slot === active ? 'active' : (STATES[row.status] ?? 'waiting'),\n detail: parts.length ? parts.join(' ') : null,\n ...(row.slot === active ? { startedAt: aiBuildActiveStartedAt(job) } : {}),\n }\n })\n}\n\n/** How a build came out, in one line: \"4 of 5 built; 1 failed.\" `null` while it runs. */\nexport function aiBuildOutcomeLine(job: Pick<AiJobSummary, 'items' | 'status'>): string | null {\n const items = job.items ?? []\n if (!items.length || (job.status !== 'done' && job.status !== 'failed')) return null\n const built = items.filter((row) => row.status === 'succeeded' || row.status === 'degraded').length\n const failed = items.filter((row) => row.status === 'failed').length\n const skipped = items.filter((row) => row.status === 'skipped').length\n const tail = [failed ? `${failed} failed` : '', skipped ? `${skipped} not built` : ''].filter(Boolean)\n return `${built} of ${items.length} built${tail.length ? `; ${tail.join(', ')}` : ''}.`\n}\n\n/** Whether a finished build — or a site that built part of itself — has failed items to try again. */\nexport function aiBuildCanRetry(job: Pick<AiJobSummary, 'kind' | 'status' | 'items'>): boolean {\n // A site that built nothing starts over from its own answers instead.\n const finished = job.kind === 'build' ? job.status === 'done' || job.status === 'failed' : job.kind === 'site' && job.status === 'done'\n return (\n finished &&\n (job.items ?? []).some((row) => row.status === 'failed' || (row.status === 'skipped' && !row.degradedBy?.length))\n )\n}\n\n/**\n * What a guided start that built part of its site says (AGL-3616), in the\n * words every surface uses: \"Built 1 of 2 pages. Home couldn’t be built —\n * that one’s on us…\". `null` when every page was built, or none was.\n */\nexport function aiSitePartialCopy(job: Pick<AiJobSummary, 'items' | 'status'>): string | null {\n const pages = (job.items ?? []).filter((row) => row.op === 'page')\n const built = pages.filter((row) => row.status === 'succeeded' || row.status === 'degraded')\n const unbuilt = (job.items ?? []).filter((row) => row.status === 'failed' || row.status === 'skipped')\n if (job.status !== 'done' || !unbuilt.length || !built.length) return null\n const names = unbuilt.map((row) => (row.op === 'page' ? row.label : `The ${aiBuildOpNoun(row.op).toLowerCase()} “${row.label}”`))\n const list = names.length === 1 ? names[0] : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`\n const refunded = unbuilt.reduce((total, row) => total + Math.max(0, Math.floor(row.creditsRefunded ?? 0)), 0)\n const back = refunded > 0 ? ` The ${refunded} ${refunded === 1 ? 'credit' : 'credits'} it used ${refunded === 1 ? 'is' : 'are'} back in your AI credits.` : ''\n // Only a failure on our side says so; a part the AI declined or an allowance\n // stopped was charged for what it used, and the row says that.\n const ours = unbuilt.every((row) => row.status === 'skipped' || row.failure?.ours === true)\n const why = ours ? ` — that one’s on us, you weren’t charged for ${unbuilt.length === 1 ? 'it' : 'them'}.` : '.'\n return `Built ${built.length} of ${pages.length} ${pages.length === 1 ? 'page' : 'pages'}. ${list} couldn’t be built${why}${back} Try again builds only what failed.`\n}\n"],"names":["aiCustomerSafeItem","AI_JOB_PLAN_STEP","OP_NOUNS","page","layout","form","component","email","template","campaign","workflow","aiBuildOpNoun","op","known","words","replace","trim","toUpperCase","slice","aiBuildItemRefundCopy","row","status","failure","ours","refunded","Math","max","floor","creditsRefunded","spent","creditsSpent","STATES","pending","running","succeeded","degraded","failed","skipped","aiBuildActiveSlot","job","items","find","slot","aiBuildActiveStartedAt","latest","at","settledAt","Date","parse","NaN","Number","isFinite","iso","step","steps","one","name","startedAt","aiBuildItemRows","active","map","ledger","parts","message","push","note","refund","label","state","detail","length","join","aiBuildOutcomeLine","built","filter","tail","Boolean","aiBuildCanRetry","finished","kind","some","degradedBy","aiSitePartialCopy","pages","unbuilt","names","toLowerCase","list","reduce","total","back","every","why"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-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 { aiCustomerSafeItem } from './ai-job-failure-copy'\nimport { AI_JOB_PLAN_STEP, type AiJobItemLedger, type AiJobSummary } from './ai-jobs.types'\n\n/**\n * What a `build` job shows of each item (AGL-3616), wherever it is shown —\n * the AI jobs drawer, the job's own page, the chat card: the item, where it\n * stands, what it cost, and what became of its credits when it failed.\n *\n * Pure: no React, no request.\n */\n\n/** Where one item stands, as a row's icon reads it. */\nexport type AiBuildItemRowState = 'done' | 'active' | 'waiting' | 'failed' | 'skipped'\n\nexport interface AiBuildItemRow {\n slot: string\n /** \"Page: About\", \"Form: Contact\". */\n label: string\n state: AiBuildItemRowState\n /** What the person should know: the failure, the note, what was given back. */\n detail: string | null\n /** When the active item started, for the elapsed time beside it; absent on every other row. */\n startedAt?: string | null\n}\n\nconst OP_NOUNS: Readonly<Record<string, string>> = {\n page: 'Page',\n layout: 'Layout',\n form: 'Form',\n component: 'Component',\n email: 'Email design',\n template: 'Page template',\n campaign: 'Email campaign',\n workflow: 'Automation',\n // A guided start's first posts and products (AGL-3676).\n posts: 'Blog',\n products: 'Store',\n}\n\n/** What an operation is called at the start of a row: its own noun, or the op spelled as words. */\nexport function aiBuildOpNoun(op: string): string {\n const known = OP_NOUNS[op]\n if (known) return known\n const words = op.replace(/[-_]+/g, ' ').trim()\n return words ? `${words[0].toUpperCase()}${words.slice(1)}` : 'Item'\n}\n\n/**\n * What became of a failed item's credits (AGL-3616), in the words every\n * surface uses for a failure on our side: \"This one’s on us — you weren’t\n * charged.\" A failure that was not ours — the model declining, a site at its\n * allowance — says what it used, which was charged. `null` for an item that\n * did not fail.\n */\nexport function aiBuildItemRefundCopy(\n row: Pick<AiJobItemLedger, 'status' | 'failure' | 'creditsRefunded' | 'creditsSpent'>,\n): string | null {\n if (row.status !== 'failed') return null\n if (row.failure?.ours) {\n const refunded = Math.max(0, Math.floor(row.creditsRefunded ?? 0))\n if (refunded > 0) {\n return `This one’s on us — you weren’t charged. The ${refunded} ${refunded === 1 ? 'credit' : 'credits'} it used ${refunded === 1 ? 'is' : 'are'} back in your AI credits.`\n }\n return 'This one’s on us — you weren’t charged.'\n }\n const spent = Math.max(0, Math.floor(row.creditsSpent ?? 0))\n return spent > 0 ? `It used ${spent} ${spent === 1 ? 'credit' : 'credits'}.` : null\n}\n\nconst STATES: Readonly<Record<AiJobItemLedger['status'], AiBuildItemRowState>> = {\n pending: 'waiting',\n running: 'active',\n succeeded: 'done',\n degraded: 'done',\n failed: 'failed',\n skipped: 'skipped',\n}\n\n/**\n * The item the build is on (AGL-3596): the one its ledger marks running, else\n * — while the job moves — the first still pending, which is the one its runner\n * builds next. The runner settles an item in the write that records its pass,\n * so the item it is writing reads `pending` until it is done; read as such,\n * every row waited and the build looked stalled. `null` when the job is not\n * moving or nothing is left.\n */\nexport function aiBuildActiveSlot(\n job: Pick<AiJobSummary, 'items'> & Partial<Pick<AiJobSummary, 'status'>>,\n): string | null {\n const items = job.items ?? []\n const running = items.find((row) => row.status === 'running')\n if (running) return running.slot\n if (job.status !== 'queued' && job.status !== 'running') return null\n return items.find((row) => row.status === 'pending')?.slot ?? null\n}\n\n/**\n * When the active item started: when the last item before it settled, which\n * the ledger records, else when the build step started — the first item's\n * start, and the best a ledger written before settling was recorded has.\n */\nexport function aiBuildActiveStartedAt(\n job: Pick<AiJobSummary, 'items'> & Partial<Pick<AiJobSummary, 'steps'>>,\n): string | null {\n let latest: { at: number; iso: string } | null = null\n for (const row of job.items ?? []) {\n const at = row.settledAt ? Date.parse(row.settledAt) : NaN\n if (Number.isFinite(at) && (!latest || at > latest.at)) latest = { at, iso: row.settledAt as string }\n }\n if (latest) return latest.iso\n const step = (job.steps ?? []).find((one) => one.name !== AI_JOB_PLAN_STEP && one.startedAt)\n return step?.startedAt ?? null\n}\n\n/** One row an item, in the order the build planned them; the item it is on is the active one. */\nexport function aiBuildItemRows(\n job: Pick<AiJobSummary, 'items'> & Partial<Pick<AiJobSummary, 'status' | 'steps'>>,\n): AiBuildItemRow[] {\n const active = aiBuildActiveSlot(job)\n return (job.items ?? []).map((ledger) => {\n // A building rule's own words never reach a customer (AGL-3596).\n const row = aiCustomerSafeItem(ledger)\n const parts: string[] = []\n if (row.status === 'failed' && row.failure?.message) parts.push(row.failure.message)\n if (row.note) parts.push(row.note)\n const refund = aiBuildItemRefundCopy(row)\n if (refund) parts.push(refund)\n return {\n slot: row.slot,\n label: `${aiBuildOpNoun(row.op)}: ${row.label}`,\n state: row.slot === active ? 'active' : (STATES[row.status] ?? 'waiting'),\n detail: parts.length ? parts.join(' ') : null,\n ...(row.slot === active ? { startedAt: aiBuildActiveStartedAt(job) } : {}),\n }\n })\n}\n\n/** How a build came out, in one line: \"4 of 5 built; 1 failed.\" `null` while it runs. */\nexport function aiBuildOutcomeLine(job: Pick<AiJobSummary, 'items' | 'status'>): string | null {\n const items = job.items ?? []\n if (!items.length || (job.status !== 'done' && job.status !== 'failed')) return null\n const built = items.filter((row) => row.status === 'succeeded' || row.status === 'degraded').length\n const failed = items.filter((row) => row.status === 'failed').length\n const skipped = items.filter((row) => row.status === 'skipped').length\n const tail = [failed ? `${failed} failed` : '', skipped ? `${skipped} not built` : ''].filter(Boolean)\n return `${built} of ${items.length} built${tail.length ? `; ${tail.join(', ')}` : ''}.`\n}\n\n/** Whether a finished build — or a site that built part of itself — has failed items to try again. */\nexport function aiBuildCanRetry(job: Pick<AiJobSummary, 'kind' | 'status' | 'items'>): boolean {\n // A site that built nothing starts over from its own answers instead.\n const finished = job.kind === 'build' ? job.status === 'done' || job.status === 'failed' : job.kind === 'site' && job.status === 'done'\n return (\n finished &&\n (job.items ?? []).some((row) => row.status === 'failed' || (row.status === 'skipped' && !row.degradedBy?.length))\n )\n}\n\n/**\n * What a guided start that built part of its site says (AGL-3616), in the\n * words every surface uses: \"Built 1 of 2 pages. Home couldn’t be built —\n * that one’s on us…\". `null` when every page was built, or none was.\n */\nexport function aiSitePartialCopy(job: Pick<AiJobSummary, 'items' | 'status'>): string | null {\n const pages = (job.items ?? []).filter((row) => row.op === 'page')\n const built = pages.filter((row) => row.status === 'succeeded' || row.status === 'degraded')\n const unbuilt = (job.items ?? []).filter((row) => row.status === 'failed' || row.status === 'skipped')\n if (job.status !== 'done' || !unbuilt.length || !built.length) return null\n const names = unbuilt.map((row) => (row.op === 'page' ? row.label : `The ${aiBuildOpNoun(row.op).toLowerCase()} “${row.label}”`))\n const list = names.length === 1 ? names[0] : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`\n const refunded = unbuilt.reduce((total, row) => total + Math.max(0, Math.floor(row.creditsRefunded ?? 0)), 0)\n const back = refunded > 0 ? ` The ${refunded} ${refunded === 1 ? 'credit' : 'credits'} it used ${refunded === 1 ? 'is' : 'are'} back in your AI credits.` : ''\n // Only a failure on our side says so; a part the AI declined or an allowance\n // stopped was charged for what it used, and the row says that.\n const ours = unbuilt.every((row) => row.status === 'skipped' || row.failure?.ours === true)\n const why = ours ? ` — that one’s on us, you weren’t charged for ${unbuilt.length === 1 ? 'it' : 'them'}.` : '.'\n return `Built ${built.length} of ${pages.length} ${pages.length === 1 ? 'page' : 'pages'}. ${list} couldn’t be built${why}${back} Try again builds only what failed.`\n}\n"],"names":["aiCustomerSafeItem","AI_JOB_PLAN_STEP","OP_NOUNS","page","layout","form","component","email","template","campaign","workflow","posts","products","aiBuildOpNoun","op","known","words","replace","trim","toUpperCase","slice","aiBuildItemRefundCopy","row","status","failure","ours","refunded","Math","max","floor","creditsRefunded","spent","creditsSpent","STATES","pending","running","succeeded","degraded","failed","skipped","aiBuildActiveSlot","job","items","find","slot","aiBuildActiveStartedAt","latest","at","settledAt","Date","parse","NaN","Number","isFinite","iso","step","steps","one","name","startedAt","aiBuildItemRows","active","map","ledger","parts","message","push","note","refund","label","state","detail","length","join","aiBuildOutcomeLine","built","filter","tail","Boolean","aiBuildCanRetry","finished","kind","some","degradedBy","aiSitePartialCopy","pages","unbuilt","names","toLowerCase","list","reduce","total","back","every","why"],"mappings":"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"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * An `edit` job (AGL-3616): a change to a page or a layout the site already
19
+ * has, asked for in words, made without the Besigner open.
20
+ *
21
+ * It speaks the Assist edit rung's own protocol — the same tool, the same
22
+ * closed world of element ids, the same validators, the same canvas mutators
23
+ * — against the stored document instead of an open canvas, and the change is
24
+ * saved where no visitor sees it:
25
+ *
26
+ * - on a plan with version history, as a NEW version beside the one it
27
+ * started from, which becomes the page's current version only when the
28
+ * page is not published (no visitor reaches it); a published page's and a
29
+ * layout's current version never move;
30
+ * - on a plan without it, in place, and only on a version no visitor
31
+ * reaches — an unpublished page's, or a version that is not the current
32
+ * one. Anything else is refused, before anything is spent.
33
+ *
34
+ * Pure: the inputs, the sentences a person reads, and the outline the model
35
+ * is shown. The runner is `jobs/ai-job-edit-step.ts`.
36
+ */
37
+ /** The documents an edit job changes: those that keep versions and render on a site. */
38
+ export declare const AI_EDIT_TARGET_KINDS: readonly ["screen", "layout"];
39
+ export type AiEditTargetKind = (typeof AI_EDIT_TARGET_KINDS)[number];
40
+ export interface AiEditJobInputs {
41
+ /** The page's or layout's document id, on the job's site. */
42
+ target: string;
43
+ /** Which collection the id is in; both are looked in, a page's first, when absent. */
44
+ targetKind: AiEditTargetKind | null;
45
+ /** The version to start from; the document's current version when absent. */
46
+ versionId: string | null;
47
+ }
48
+ export declare const AI_EDIT_TARGET_REQUIRED_COPY = "Name the page or layout to change.";
49
+ export declare const AI_EDIT_TARGET_KIND_COPY = "inputs.targetKind must be screen or layout";
50
+ export declare const AI_EDIT_VERSION_ID_COPY = "inputs.versionId must name a version of the page or layout";
51
+ /** A job's inputs read, or the sentence that says what is wrong with them. */
52
+ export declare function parseAiEditJobInputs(inputs: Readonly<Record<string, unknown>> | null | undefined): AiEditJobInputs | string;
53
+ export declare const AI_EDIT_NO_SITE_COPY = "Open the site the change is for before starting the job";
54
+ export declare const AI_EDIT_GONE_COPY = "The page or layout to change is not on the site.";
55
+ export declare const AI_EDIT_VERSION_GONE_COPY = "The version to start from is no longer on the page or layout.";
56
+ export declare const AI_EDIT_EMAIL_COPY = "That is an email design, not a page \u2014 change it from Emails \u2192 Templates.";
57
+ export declare const AI_EDIT_NO_NODES_COPY = "That version has nothing on it to change.";
58
+ /**
59
+ * A change that would land on a version visitors see, on a plan without
60
+ * version history: the versions route's own sentence, after what it means
61
+ * here.
62
+ */
63
+ export declare const AI_EDIT_NEEDS_VERSIONING_COPY = "Visitors see that version, so the change has to go into a new version beside it. Version history requires a Pro plan \u2014 see Billing to upgrade.";
64
+ /** The model answered and no change survived its checks, even after its re-ask. */
65
+ export declare const AI_EDIT_NOTHING_COPY = "No change could be made from that request. Say which part of the page to change, the way it reads on the page, and ask again.";
66
+ /** The stored document moved under the change between the read and the write. */
67
+ export declare const AI_EDIT_STALE_COPY = "The page changed while this edit was being written, so nothing was saved. Ask again.";
68
+ /** The canvas's own guards refused part of the change. */
69
+ export declare const AI_EDIT_REFUSED_COPY = "The editor refused part of this change, so none of it was saved.";
70
+ /** What a version an edit job makes is called, from the change's one-line summary. */
71
+ export declare const AI_EDIT_VERSION_PREFIX = "AI edit";
72
+ /** The longest label a version takes, as the versions route cuts one. */
73
+ export declare const AI_EDIT_VERSION_NAME_MAX_CHARS = 200;
74
+ export declare function aiEditVersionName(summary: string): string;
75
+ /** Where the change landed, which decides what the output's note says. */
76
+ export type AiEditPlacement =
77
+ /** A new version, now the unpublished page's current one. */
78
+ 'new-current'
79
+ /** A new version beside the one it started from, which is unchanged. */
80
+ | 'new-beside'
81
+ /** The version it started from, changed in place: one no visitor reaches. */
82
+ | 'in-place';
83
+ /** The output's note: where the change is, that nothing visitors see moved, and what was left out. */
84
+ export declare function aiEditOutputNote(input: {
85
+ kind: AiEditTargetKind;
86
+ placement: AiEditPlacement;
87
+ leftOut: readonly string[];
88
+ }): string;
89
+ /** What the output says of search fields a published page keeps on the page itself. */
90
+ export declare const AI_EDIT_SEO_LEFT_OUT = "the search title and description, which a published page uses as soon as they are saved \u2014 change them in Page properties";
91
+ /** What the output says of a part a job cannot save on its own. */
92
+ export declare const AI_EDIT_COMPONENT_LEFT_OUT = "saving part of the page as a reusable component, which is done in the editor";
93
+ /**
94
+ * The stored document as the outline the edit rung's protocol reads
95
+ * (`parseAssistEditContext` holds it to its limits): the document root, then
96
+ * every element level by level — each after its parent, so every ancestor
97
+ * chain the check walks is whole — until the rung's cap.
98
+ *
99
+ * In the editor, an outline is the neighborhood of what the author selected.
100
+ * A job has no selection, so it is the TOP of the document, read breadth
101
+ * first: every section, and as deep inside each as the cap reaches. Each
102
+ * element keeps its real child count and the document its real size, so the
103
+ * prompt can say how much the outline leaves out. `null` without a root.
104
+ */
105
+ export declare function aiEditJobOutline(nodes: Readonly<Record<string, unknown>>): {
106
+ selectedId: null;
107
+ total: number;
108
+ nodes: Array<Record<string, unknown>>;
109
+ } | null;