@aglyn/plugins-ai 1.0.0-beta.270 → 1.0.0-beta.272

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 (281) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +4 -3
  3. package/src/lib/activity/ai-activity-actions.js +4 -0
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/assets/site-styles/beauty.webp +0 -0
  6. package/src/lib/assets/site-styles/blog.webp +0 -0
  7. package/src/lib/assets/site-styles/business.webp +0 -0
  8. package/src/lib/assets/site-styles/coming-soon.webp +0 -0
  9. package/src/lib/assets/site-styles/education.webp +0 -0
  10. package/src/lib/assets/site-styles/events.webp +0 -0
  11. package/src/lib/assets/site-styles/fitness.webp +0 -0
  12. package/src/lib/assets/site-styles/landing.webp +0 -0
  13. package/src/lib/assets/site-styles/music.webp +0 -0
  14. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  15. package/src/lib/assets/site-styles/personal.webp +0 -0
  16. package/src/lib/assets/site-styles/photography.webp +0 -0
  17. package/src/lib/assets/site-styles/portfolio.webp +0 -0
  18. package/src/lib/assets/site-styles/professional.webp +0 -0
  19. package/src/lib/assets/site-styles/realestate.webp +0 -0
  20. package/src/lib/assets/site-styles/restaurant.webp +0 -0
  21. package/src/lib/assets/site-styles/store.webp +0 -0
  22. package/src/lib/assets/site-styles/studio.webp +0 -0
  23. package/src/lib/assets/site-styles/trades.webp +0 -0
  24. package/src/lib/assets/site-styles/wellness.webp +0 -0
  25. package/src/lib/assets/site-styles/yoga.webp +0 -0
  26. package/src/lib/components/ai-credits-card.component.d.ts +4 -3
  27. package/src/lib/components/ai-credits-card.component.js +15 -15
  28. package/src/lib/components/ai-credits-card.component.js.map +1 -1
  29. package/src/lib/components/ai-job-input-form.component.d.ts +36 -0
  30. package/src/lib/components/ai-job-input-form.component.js +100 -0
  31. package/src/lib/components/ai-job-input-form.component.js.map +1 -0
  32. package/src/lib/components/ai-job-requests.d.ts +4 -0
  33. package/src/lib/components/ai-job-requests.js +4 -0
  34. package/src/lib/components/ai-job-requests.js.map +1 -1
  35. package/src/lib/components/ai-site-build-page.component.js +18 -0
  36. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  37. package/src/lib/components/ai-site-import-job.component.d.ts +41 -0
  38. package/src/lib/components/ai-site-import-job.component.js +707 -0
  39. package/src/lib/components/ai-site-import-job.component.js.map +1 -0
  40. package/src/lib/components/ai-site-import-start.component.d.ts +64 -0
  41. package/src/lib/components/ai-site-import-start.component.js +335 -0
  42. package/src/lib/components/ai-site-import-start.component.js.map +1 -0
  43. package/src/lib/components/ai-site-ownership-proof.component.d.ts +42 -0
  44. package/src/lib/components/ai-site-ownership-proof.component.js +353 -0
  45. package/src/lib/components/ai-site-ownership-proof.component.js.map +1 -0
  46. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  47. package/src/lib/components/ai-site-start-card.component.js +261 -119
  48. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  49. package/src/lib/components/ai-site-start-steps.component.d.ts +43 -2
  50. package/src/lib/components/ai-site-start-steps.component.js +651 -15
  51. package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
  52. package/src/lib/components/ai-site-start-tracking.d.ts +10 -0
  53. package/src/lib/components/ai-site-start-tracking.js +14 -0
  54. package/src/lib/components/ai-site-start-tracking.js.map +1 -1
  55. package/src/lib/components/ai-site-style-previews.js +26 -22
  56. package/src/lib/components/ai-site-style-previews.js.map +1 -1
  57. package/src/lib/components/ai-upsell-dialog.component.d.ts +7 -5
  58. package/src/lib/components/ai-upsell-dialog.component.js +8 -6
  59. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  60. package/src/lib/components/assist-build-card.component.js +11 -1
  61. package/src/lib/components/assist-build-card.component.js.map +1 -1
  62. package/src/lib/components/assist-jobs-drawer.component.js +9 -1
  63. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  64. package/src/lib/components/assist-panel.component.js +8 -4
  65. package/src/lib/components/assist-panel.component.js.map +1 -1
  66. package/src/lib/components/staff-org-ai-card.component.js +4 -7
  67. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  68. package/src/lib/declarations.d.ts +2 -2
  69. package/src/lib/declarations.js +9 -5
  70. package/src/lib/declarations.js.map +1 -1
  71. package/src/lib/jobs/ai-build-capabilities.js +3 -1
  72. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  73. package/src/lib/jobs/ai-business-profile-prefill.d.ts +1 -1
  74. package/src/lib/jobs/ai-business-profile-prefill.js +14 -3
  75. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -1
  76. package/src/lib/jobs/ai-job-build-step.js +5 -3
  77. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  78. package/src/lib/jobs/ai-job-drafts.d.ts +14 -0
  79. package/src/lib/jobs/ai-job-drafts.js +24 -0
  80. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  81. package/src/lib/jobs/ai-job-form-step.d.ts +15 -1
  82. package/src/lib/jobs/ai-job-form-step.js +75 -8
  83. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  84. package/src/lib/jobs/ai-job-import-step.d.ts +136 -0
  85. package/src/lib/jobs/ai-job-import-step.js +809 -0
  86. package/src/lib/jobs/ai-job-import-step.js.map +1 -0
  87. package/src/lib/jobs/ai-job-page-language.d.ts +52 -1
  88. package/src/lib/jobs/ai-job-page-language.js +99 -5
  89. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  90. package/src/lib/jobs/ai-job-page-step.d.ts +19 -2
  91. package/src/lib/jobs/ai-job-page-step.js +42 -18
  92. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  93. package/src/lib/jobs/ai-job-plan-step.d.ts +25 -2
  94. package/src/lib/jobs/ai-job-plan-step.js +103 -29
  95. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  96. package/src/lib/jobs/ai-job-settings-step.js +4 -3
  97. package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
  98. package/src/lib/jobs/ai-job-site-datasets.d.ts +27 -0
  99. package/src/lib/jobs/ai-job-site-datasets.js +140 -14
  100. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  101. package/src/lib/jobs/ai-job-site-look.js +18 -5
  102. package/src/lib/jobs/ai-job-site-look.js.map +1 -1
  103. package/src/lib/jobs/ai-job-site-step.d.ts +16 -1
  104. package/src/lib/jobs/ai-job-site-step.js +95 -22
  105. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  106. package/src/lib/jobs/ai-job-text-step.d.ts +15 -1
  107. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  108. package/src/lib/jobs/ai-job-tracking-step.d.ts +14 -3
  109. package/src/lib/jobs/ai-job-tracking-step.js +28 -5
  110. package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
  111. package/src/lib/jobs/ai-jobs.d.ts +13 -1
  112. package/src/lib/jobs/ai-jobs.js +77 -10
  113. package/src/lib/jobs/ai-jobs.js.map +1 -1
  114. package/src/lib/jobs/ai-layout-owner-photos.d.ts +16 -1
  115. package/src/lib/jobs/ai-layout-owner-photos.js +47 -0
  116. package/src/lib/jobs/ai-layout-owner-photos.js.map +1 -1
  117. package/src/lib/jobs/ai-layout-stock-photos.d.ts +27 -2
  118. package/src/lib/jobs/ai-layout-stock-photos.js +190 -19
  119. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  120. package/src/lib/jobs/ai-site-inspiration-signals.d.ts +98 -0
  121. package/src/lib/jobs/ai-site-inspiration-signals.js +585 -0
  122. package/src/lib/jobs/ai-site-inspiration-signals.js.map +1 -0
  123. package/src/lib/jobs/ai-site-inspiration.d.ts +157 -0
  124. package/src/lib/jobs/ai-site-inspiration.js +346 -0
  125. package/src/lib/jobs/ai-site-inspiration.js.map +1 -0
  126. package/src/lib/jobs/ai-site-publish.d.ts +26 -1
  127. package/src/lib/jobs/ai-site-publish.js +162 -13
  128. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  129. package/src/lib/jobs/ai-theme-brand-inputs.d.ts +77 -3
  130. package/src/lib/jobs/ai-theme-brand-inputs.js +135 -31
  131. package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
  132. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.d.ts +97 -0
  133. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js +137 -0
  134. package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js.map +1 -0
  135. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.d.ts +38 -0
  136. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js +103 -0
  137. package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js.map +1 -0
  138. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.d.ts +25 -0
  139. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js +174 -0
  140. package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js.map +1 -0
  141. package/src/lib/layout-language/ai-layout-compiler.d.ts +1 -1
  142. package/src/lib/layout-language/ai-layout-compiler.js +438 -25
  143. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  144. package/src/lib/layout-language/ai-layout-contact.d.ts +57 -0
  145. package/src/lib/layout-language/ai-layout-contact.js +137 -0
  146. package/src/lib/layout-language/ai-layout-contact.js.map +1 -0
  147. package/src/lib/layout-language/ai-layout-copy.d.ts +12 -0
  148. package/src/lib/layout-language/ai-layout-copy.js +34 -2
  149. package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
  150. package/src/lib/layout-language/ai-layout-design.d.ts +65 -8
  151. package/src/lib/layout-language/ai-layout-design.js +411 -97
  152. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  153. package/src/lib/layout-language/ai-layout-frame.js +23 -0
  154. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  155. package/src/lib/layout-language/ai-layout-labels.d.ts +2 -0
  156. package/src/lib/layout-language/ai-layout-labels.js +12 -6
  157. package/src/lib/layout-language/ai-layout-labels.js.map +1 -1
  158. package/src/lib/layout-language/ai-layout-language.d.ts +3 -2
  159. package/src/lib/layout-language/ai-layout-language.js +4 -3
  160. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  161. package/src/lib/layout-language/ai-layout-links.d.ts +13 -0
  162. package/src/lib/layout-language/ai-layout-links.js +30 -0
  163. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  164. package/src/lib/layout-language/ai-layout-pictures.d.ts +49 -6
  165. package/src/lib/layout-language/ai-layout-pictures.js +145 -22
  166. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  167. package/src/lib/model/ai-build-job.d.ts +16 -1
  168. package/src/lib/model/ai-build-job.js +40 -0
  169. package/src/lib/model/ai-build-job.js.map +1 -1
  170. package/src/lib/model/ai-credit-estimate.d.ts +20 -0
  171. package/src/lib/model/ai-credit-estimate.js +13 -0
  172. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  173. package/src/lib/model/ai-job-activity.d.ts +2 -2
  174. package/src/lib/model/ai-job-activity.js +6 -4
  175. package/src/lib/model/ai-job-activity.js.map +1 -1
  176. package/src/lib/model/ai-job-input.d.ts +33 -0
  177. package/src/lib/model/ai-job-input.js +59 -0
  178. package/src/lib/model/ai-job-input.js.map +1 -0
  179. package/src/lib/model/ai-job-notice.d.ts +4 -0
  180. package/src/lib/model/ai-job-notice.js +28 -4
  181. package/src/lib/model/ai-job-notice.js.map +1 -1
  182. package/src/lib/model/ai-jobs.types.d.ts +69 -3
  183. package/src/lib/model/ai-jobs.types.js +2 -1
  184. package/src/lib/model/ai-jobs.types.js.map +1 -1
  185. package/src/lib/model/ai-site-details.d.ts +122 -0
  186. package/src/lib/model/ai-site-details.js +300 -0
  187. package/src/lib/model/ai-site-details.js.map +1 -0
  188. package/src/lib/model/ai-site-entity.d.ts +181 -0
  189. package/src/lib/model/ai-site-entity.js +390 -0
  190. package/src/lib/model/ai-site-entity.js.map +1 -0
  191. package/src/lib/model/ai-site-import-view.d.ts +130 -0
  192. package/src/lib/model/ai-site-import-view.js +182 -0
  193. package/src/lib/model/ai-site-import-view.js.map +1 -0
  194. package/src/lib/model/ai-site-import.d.ts +302 -0
  195. package/src/lib/model/ai-site-import.js +526 -0
  196. package/src/lib/model/ai-site-import.js.map +1 -0
  197. package/src/lib/model/ai-site-inspiration.d.ts +72 -0
  198. package/src/lib/model/ai-site-inspiration.js +109 -0
  199. package/src/lib/model/ai-site-inspiration.js.map +1 -0
  200. package/src/lib/model/ai-site-job.d.ts +13 -1
  201. package/src/lib/model/ai-site-job.js +15 -2
  202. package/src/lib/model/ai-site-job.js.map +1 -1
  203. package/src/lib/model/ai-site-kinds.js +109 -21
  204. package/src/lib/model/ai-site-kinds.js.map +1 -1
  205. package/src/lib/model/ai-site-look.d.ts +10 -0
  206. package/src/lib/model/ai-site-look.js +24 -0
  207. package/src/lib/model/ai-site-look.js.map +1 -1
  208. package/src/lib/model/ai-site-start-seo.d.ts +19 -5
  209. package/src/lib/model/ai-site-start-seo.js +25 -7
  210. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  211. package/src/lib/model/ai-site-start-steps.d.ts +9 -3
  212. package/src/lib/model/ai-site-start-steps.js +67 -5
  213. package/src/lib/model/ai-site-start-steps.js.map +1 -1
  214. package/src/lib/model/ai-site-start.d.ts +81 -4
  215. package/src/lib/model/ai-site-start.js +207 -13
  216. package/src/lib/model/ai-site-start.js.map +1 -1
  217. package/src/lib/model/ai-upsell-copy.js +4 -1
  218. package/src/lib/model/ai-upsell-copy.js.map +1 -1
  219. package/src/lib/plan-entitlements.d.ts +4 -3
  220. package/src/lib/plan-entitlements.js +3 -8
  221. package/src/lib/plan-entitlements.js.map +1 -1
  222. package/src/lib/runtime/ai-dataset-generation.d.ts +8 -1
  223. package/src/lib/runtime/ai-dataset-generation.js +12 -2
  224. package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
  225. package/src/lib/runtime/ai-doctrine-validators.d.ts +26 -33
  226. package/src/lib/runtime/ai-doctrine-validators.js +54 -44
  227. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  228. package/src/lib/runtime/ai-doctrine.js +1 -1
  229. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  230. package/src/lib/runtime/ai-node-tree.d.ts +7 -0
  231. package/src/lib/runtime/ai-node-tree.js +9 -1
  232. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  233. package/src/lib/server/ai-create-route.d.ts +1 -1
  234. package/src/lib/server/ai-create-route.js +3 -4
  235. package/src/lib/server/ai-create-route.js.map +1 -1
  236. package/src/lib/server/ai-jobs-resume.js +27 -3
  237. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  238. package/src/lib/server/ai-jobs-route.js +18 -0
  239. package/src/lib/server/ai-jobs-route.js.map +1 -1
  240. package/src/lib/server/ai-site-import-route.d.ts +89 -0
  241. package/src/lib/server/ai-site-import-route.js +356 -0
  242. package/src/lib/server/ai-site-import-route.js.map +1 -0
  243. package/src/lib/server.js +14 -0
  244. package/src/lib/server.js.map +1 -1
  245. package/src/lib/site-import/fixtures/site-import-crawl-recordings.d.ts +45 -0
  246. package/src/lib/site-import/fixtures/site-import-crawl-recordings.js +313 -0
  247. package/src/lib/site-import/fixtures/site-import-crawl-recordings.js.map +1 -0
  248. package/src/lib/site-import/fixtures/site-import-wave-recordings.d.ts +50 -0
  249. package/src/lib/site-import/fixtures/site-import-wave-recordings.js +217 -0
  250. package/src/lib/site-import/fixtures/site-import-wave-recordings.js.map +1 -0
  251. package/src/lib/site-import/site-import-crawl.d.ts +163 -0
  252. package/src/lib/site-import/site-import-crawl.js +467 -0
  253. package/src/lib/site-import/site-import-crawl.js.map +1 -0
  254. package/src/lib/site-import/site-import-flag.d.ts +23 -0
  255. package/src/lib/site-import/site-import-flag.js +23 -0
  256. package/src/lib/site-import/site-import-flag.js.map +1 -0
  257. package/src/lib/site-import/site-import-html.d.ts +94 -0
  258. package/src/lib/site-import/site-import-html.js +462 -0
  259. package/src/lib/site-import/site-import-html.js.map +1 -0
  260. package/src/lib/site-import/site-import-images.d.ts +54 -0
  261. package/src/lib/site-import/site-import-images.js +204 -0
  262. package/src/lib/site-import/site-import-images.js.map +1 -0
  263. package/src/lib/site-import/site-import-page.d.ts +48 -0
  264. package/src/lib/site-import/site-import-page.js +150 -0
  265. package/src/lib/site-import/site-import-page.js.map +1 -0
  266. package/src/lib/site-import/site-import-review.d.ts +40 -0
  267. package/src/lib/site-import/site-import-review.js +149 -0
  268. package/src/lib/site-import/site-import-review.js.map +1 -0
  269. package/src/lib/site-import/site-import-robots.d.ts +52 -0
  270. package/src/lib/site-import/site-import-robots.js +126 -0
  271. package/src/lib/site-import/site-import-robots.js.map +1 -0
  272. package/src/lib/usage/assist-credits.d.ts +4 -4
  273. package/src/lib/usage/assist-credits.js +4 -4
  274. package/src/lib/usage/assist-credits.js.map +1 -1
  275. package/src/lib/usage/staff-org-ai-view.js +6 -3
  276. package/src/lib/usage/staff-org-ai-view.js.map +1 -1
  277. package/src/lib/usage/staff-org-ai.d.ts +4 -2
  278. package/src/lib/usage/staff-org-ai.js +7 -7
  279. package/src/lib/usage/staff-org-ai.js.map +1 -1
  280. package/src/lib/usage/staff-org-usage-ai.js +2 -2
  281. package/src/lib/usage/staff-org-usage-ai.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-site-look.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_THEME } from '@aglyn/aglyn/app-utils/default-site'\nimport { diffOverride, overrideWriteValue } from '@aglyn/aglyn/app-utils/artifact-overrides'\nimport { themeArtifactContent, type ThemeHostDocument } from '@aglyn/aglyn/app-utils/site-theme'\nimport { readThemeSelection, SITE_THEME_ENTRY_ID } from '@aglyn/aglyn/app-utils/theme-library'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport { listServerThemePresets } from '@aglyn/aglyn/plugin-manager/plugin-theme-presets'\nimport type { HostTheme } from '@aglyn/shared-data-types'\nimport { runThemeLibraryAction } from '@aglyn/tenant-data-admin/server/theme-library-write'\nimport type { AiJob, AiJobOutput } from '../model/ai-jobs.types'\nimport { AI_SITE_FONT_PAIRINGS, aiSiteKindFor, aiSiteKindOfInputs, type AiSiteBase, type AiSiteKind } from '../model/ai-site-kinds'\nimport {\n AI_SITE_LOOK_TOOL,\n AI_SITE_LOOK_TOOL_NAME,\n aiReadSiteLook,\n aiSiteSeed,\n aiSiteStyleFor,\n aiSiteTheme,\n type AiSiteLookAnswer,\n type AiSiteStyle,\n} from '../model/ai-site-look'\nimport { AI_SITE_MOTION_STYLE_RANGES, aiSiteMotionStylesFor } from '../layout-language/ai-layout-motion'\nimport { aiCatalogEntry, aiDefaultModelFor } from '../providers/catalog'\nimport { aiModelForStep } from '../providers/routing'\nimport { runValidatedGeneration, type AiGenerationCheck } from '../runtime/ai-doctrine'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiGenerationSpent, aiUnspentOutcome } from './ai-job-generation'\nimport { AI_JOB_BRIEF_MAX_CHARS, type AiJobStepContext, type AiJobStepOutcome } from './ai-job-text-step'\n\n/**\n * A site's look, designed first (AGL-3660): the scaffold's theme unit.\n *\n * One short answer from the fast tier — the base theme, two hues, a ground,\n * a font pairing, the building blocks' styles and how the pages move\n * (AGL-3941), chosen for this business\n * within its kind — then code builds the theme (`ai-site-look.ts`) and saves\n * it the way Setup → Theme saves a pick and its edits: the base theme\n * selected, the site's own look as the override over it, and the style\n * tokens on the site for later edits to keep. The header, the footer and\n * every page are built after it, so they render in it from their first draft.\n *\n * The look never fails a site. An answer that could not be read leaves the\n * kind and the seed to choose; a site whose owner already changed its theme\n * keeps it.\n */\n\n/** The answer is a dozen short values, about 150 tokens; the ceiling leaves room to spare. */\nexport const AI_SITE_LOOK_MAX_TOKENS = 600\n\n/** A look pass's time, on the fast tier the look is asked on. */\nexport const AI_SITE_LOOK_BUDGET = aiJobStepBudget({ tier: 'fast', maxTokens: AI_SITE_LOOK_MAX_TOKENS })\n\n/**\n * The generation kind a look is asked under: values code builds a theme\n * from, composing nothing, so the doctrine's short fields block leads it.\n */\nexport const AI_SITE_LOOK_KIND = 'site-look'\n\n/** The base themes as the model reads them; `starter` is the one a new site is born with. */\nconst STARTER_WORDS = 'the starter theme a new site is born with: a soft neutral ground, rounder corners and calm buttons'\n\n/** The look door's cached instructions. */\nexport const AI_SITE_LOOK_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n \"You choose the look of a new small-business website: the platform's base theme it starts from, and the choices that make it this business's own.\",\n `Answer by calling ${AI_SITE_LOOK_TOOL_NAME} exactly once, at once, and write nothing else: no reasoning, no prose.`,\n '- Choose for this business, its audience and its kind of site, never a default: a roofer and a florist should not share a color, a law firm and a yoga studio should not share a typeface.',\n '- Prefer a base theme, font pairing and corners from the lists the request gives for this kind of site; the lists are in no order, so weigh each for this business.',\n '- hue is the brand color and accent sits beside it; pick colors people would expect of this business, and let the design seed tip a close call.',\n '- A hex color written in the brief is the brand color: put it in brand. Otherwise brand is null.',\n '- Choose the building blocks to suit the kind: quiet, flat cards and small eyebrows for minimal sites; raised cards and capitals for bold ones; soft tinted cards for calm ones.',\n '- motion is how the pages arrive as a visitor scrolls: choose from the motion list the request gives, for this business, never by habit; a quiet practice suits little motion, a lively studio more.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/** A list in a seed's order (Fisher–Yates over a small LCG). */\nfunction shuffled<T>(list: readonly T[], seed: number): T[] {\n const out = [...list]\n let state = seed >>> 0 || 1\n for (let index = out.length - 1; index > 0; index -= 1) {\n state = (Math.imul(state, 1664525) + 1013904223) >>> 0\n const other = state % (index + 1)\n ;[out[index], out[other]] = [out[other] as T, out[index] as T]\n }\n return out\n}\n\n/** The look's user turn: the site, its kind and what suits it, and the seed. */\nexport function aiSiteLookPrompt(input: {\n job: Pick<AiJob, 'brief' | '$id' | 'inputs'>\n kind: AiSiteKind\n bases: ReadonlyArray<{ base: AiSiteBase; words: string }>\n}): string {\n const { job, kind } = input\n const text = (key: string) => (typeof job.inputs?.[key] === 'string' ? (job.inputs[key] as string).trim() : '')\n const pairings = AI_SITE_FONT_PAIRINGS.filter((pairing) => kind.look.fonts.includes(pairing.id))\n // The lists in the job's own order: a model reads the first of a list as\n // the answer, and every site of a kind would get the same base and type.\n const seed = aiSiteSeed(job.$id)\n return [\n `Kind of site: ${kind.label}. ${kind.design}`,\n text('businessName') ? `Business name: ${text('businessName')}` : '',\n text('businessType') ? `Business: ${text('businessType')}` : '',\n text('audience') ? `For: ${text('audience')}` : '',\n text('brand') ? `Brand: ${text('brand')}` : '',\n `Brief: ${job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`,\n `Base themes that suit it: ${shuffled(input.bases, seed).map((entry) => `${entry.base} (${entry.words})`).join('; ')}.`,\n `Font pairings that suit it: ${shuffled(pairings, seed + 1).map((pairing) => `${pairing.id} (${pairing.feel})`).join('; ')}.`,\n `Corners that suit it: ${kind.look.corners.join(', ')}. Its palette runs ${kind.look.chroma === 'neutral' ? 'neutral, ink and paper with one accent color' : kind.look.chroma}.`,\n `Motion that suits it: ${shuffled(aiSiteMotionStylesFor(kind.id), seed + 2).map((motion) => `${motion} (${AI_SITE_MOTION_STYLE_RANGES[motion].words})`).join('; ')}.`,\n `Design seed: ${job.$id.slice(-6)}.`,\n ]\n .filter(Boolean)\n .join('\\n')\n}\n\n/** The answer, read leniently; a call with nothing usable is still an answer, and the seed fills it. */\nexport const aiSiteLookCheck: AiGenerationCheck<AiSiteLookAnswer> = (answer) => ({\n value: aiReadSiteLook(answer),\n violations: [],\n})\n\n/** The model a look is asked on: the fast tier of the job's provider, unless the creator picked a model. */\nexport function aiSiteLookModel(job: Pick<AiJob, 'model'>, resolved: string | undefined): string {\n const fallback = resolved ?? aiModelForStep('job.theme')\n const picked = typeof job.model === 'string' ? job.model.trim() : ''\n if (picked && picked !== 'auto') return fallback\n const entry = aiCatalogEntry(fallback)\n return (entry ? aiDefaultModelFor(entry.provider, 'fast') : undefined) ?? fallback\n}\n\n/** The preset id a base names. */\nexport const aiSiteBasePresetId = (base: AiSiteBase) => `theme-presets.${base}`\n\n/** The bases a kind suits that this deployment has, best first, with what each looks like. */\nexport function aiSiteLookBases(kind: AiSiteKind): Array<{ base: AiSiteBase; words: string }> {\n const presets = listServerThemePresets()\n return kind.look.bases.flatMap((base): Array<{ base: AiSiteBase; words: string }> => {\n if (base === 'starter') return [{ base, words: STARTER_WORDS }]\n const preset = presets.find((entry) => entry.id === aiSiteBasePresetId(base))\n return preset ? [{ base, words: preset.description ?? preset.name }] : []\n })\n}\n\n/** Plain JSON, deep-sorted, for comparing two themes as stored. */\nfunction stable(value: unknown): string {\n return JSON.stringify(value, (_key, inner) =>\n inner && typeof inner === 'object' && !Array.isArray(inner)\n ? Object.fromEntries(Object.entries(inner as Record<string, unknown>).sort(([a], [b]) => a.localeCompare(b)))\n : inner,\n )\n}\n\n/**\n * Whether a site still has the theme it was born with (AGL-3497), or none:\n * the only theme a look may replace. A site whose owner picked a theme, edited\n * one, or installed one keeps it.\n */\nexport function aiSiteThemeUntouched(host: ThemeHostDocument & Record<string, unknown>): boolean {\n if (host.themeOverride) return false\n const selection = readThemeSelection(host)\n if (selection.kind === 'default') return true\n if (selection.kind !== 'custom' || selection.id !== SITE_THEME_ENTRY_ID) return false\n return stable(themeArtifactContent(host.theme)) === stable(themeArtifactContent(DEFAULT_SITE_THEME))\n}\n\nexport type AiSiteThemeWrite = 'applied' | 'kept' | 'missing'\n\n/**\n * Saves a look on its site in one transaction: the base theme picked through\n * the theme library (so the site's starter theme is filed in its library, as\n * a person's switch files it), the look as the override over the base, and\n * the style tokens. Again for the same job is a no-op.\n */\nexport async function aiSaveSiteTheme(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; jobId: string; style: AiSiteStyle },\n): Promise<{ write: AiSiteThemeWrite; baseName: string }> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const preset =\n input.style.base === 'starter'\n ? null\n : (listServerThemePresets().find((entry) => entry.id === aiSiteBasePresetId(input.style.base)) ?? null)\n const base: HostTheme = preset ? themeArtifactContent(preset.theme) : DEFAULT_SITE_THEME\n const baseName = preset?.name ?? 'Site theme'\n const edited = aiSiteTheme({ ...input.style, base: preset ? input.style.base : 'starter' }, base)\n const override = overrideWriteValue(diffOverride(base, edited), null)\n const siteStyle = { ...input.style, base: preset ? input.style.base : 'starter', jobId: input.jobId }\n const write = await firestore.runTransaction(async (tx): Promise<AiSiteThemeWrite> => {\n const snapshot = await tx.get(hostRef)\n if (!snapshot.exists) return 'missing'\n const host = snapshot.data() as ThemeHostDocument & Record<string, unknown>\n const stored = host['siteStyle'] as { jobId?: unknown } | undefined\n if (stored?.jobId === input.jobId) return 'applied'\n if (!aiSiteThemeUntouched(host)) return 'kept'\n const extra = { themeOverride: override, siteStyle }\n if (preset) {\n const plan = await runThemeLibraryAction(\n tx,\n hostRef,\n { action: 'select', target: { kind: 'preset', id: preset.id, name: preset.name, theme: base } },\n extra,\n )\n if (plan.ok === false) throw new Error(`the site's base theme could not be picked: ${plan.error}`)\n } else {\n tx.update(hostRef, { theme: DEFAULT_SITE_THEME, ...extra })\n }\n return 'applied'\n })\n if (write === 'applied') {\n // The theme is live the moment it is written; cached pages go now.\n await dropPluginSiteCache({ hostIds: [input.hostId], reason: 'ai site look' })\n }\n return { write, baseName }\n}\n\n/** The sentence a look's row and output carry. */\nfunction lookLabel(style: AiSiteStyle, baseName: string): string {\n const pairing = AI_SITE_FONT_PAIRINGS.find((entry) => entry.id === style.fonts)\n return `Your look: ${baseName} base, ${pairing ? `${pairing.heading.family} type` : 'its own type'}, ${style.cards} cards`\n}\n\nexport interface AiSiteLookDeps {\n save?: typeof aiSaveSiteTheme\n generate?: typeof runValidatedGeneration\n others?: typeof aiWorkspaceSiteStyles\n}\n\n/** The most of a workspace's sites a look is drawn apart from. */\nexport const AI_SITE_LOOK_SIBLINGS = 50\n\n/**\n * The looks of a workspace's other sites (AGL-3660), so a new one is drawn\n * apart from each: none shares their base, hue family, heading font and\n * buttons. A read that fails draws apart from nothing.\n */\nexport async function aiWorkspaceSiteStyles(\n firestore: FirebaseFirestore.Firestore,\n input: { orgId: string; hostId: string },\n): Promise<AiSiteStyle[]> {\n try {\n const snapshot = await firestore\n .collection('hosts')\n .where('orgId', '==', input.orgId)\n .limit(AI_SITE_LOOK_SIBLINGS)\n .get()\n return snapshot.docs.flatMap((doc) => {\n const style = doc.get('siteStyle') as AiSiteStyle | undefined\n return doc.id !== input.hostId && style && typeof style.base === 'string' ? [style] : []\n })\n } catch (error) {\n console.warn('ai site look: the workspace sites could not be read; drawing apart from none', { orgId: input.orgId, error })\n return []\n }\n}\n\n/**\n * The theme unit's pass: ask for the look, build it, save it. `job` is the\n * unit's derived job, carrying the scaffold's inputs and its origin job id,\n * which is what the seed is drawn from — so a resumed pass draws the same.\n */\nexport async function aiRunSiteLook(\n context: AiJobStepContext,\n job: AiJob,\n deps: AiSiteLookDeps = {},\n): Promise<AiJobStepOutcome> {\n const save = deps.save ?? aiSaveSiteTheme\n const generate = deps.generate ?? runValidatedGeneration\n const model = aiSiteLookModel(job, context.modelFor?.('job.theme'))\n if (!job.hostId) return { ...aiUnspentOutcome(model), failure: 'This site job names no site.' }\n const kind = aiSiteKindOfInputs(job.inputs) ?? aiSiteKindFor(job.brief)\n const origin = typeof job.inputs?.['originJobId'] === 'string' ? (job.inputs['originJobId'] as string) : job.$id\n const result = await generate<AiSiteLookAnswer>(AI_SITE_LOOK_KIND, {\n step: 'job.theme',\n model,\n instructions: AI_SITE_LOOK_INSTRUCTIONS as AiSystemBlock[],\n messages: [{ role: 'user', content: aiSiteLookPrompt({ job: { ...job, $id: origin }, kind, bases: aiSiteLookBases(kind) }) }],\n tool: AI_SITE_LOOK_TOOL,\n maxTokens: AI_SITE_LOOK_BUDGET.maxTokens(model),\n thinking: 'off',\n check: aiSiteLookCheck,\n ...(context.signal ? { signal: context.signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n // An answer that could not be used leaves the kind and the seed to choose.\n const answer = result.status === 'ok' ? result.value : {}\n const brand = typeof job.inputs?.['brand'] === 'string' ? (job.inputs['brand'] as string) : null\n const others = deps.others ?? aiWorkspaceSiteStyles\n const avoid = await others(context.firestore, { orgId: job.orgId, hostId: job.hostId })\n const style = aiSiteStyleFor({ kind, answer, seed: aiSiteSeed(origin), brand, avoid })\n const saved = await save(context.firestore, { hostId: job.hostId, jobId: origin, style })\n if (saved.write === 'missing') throw new Error(`site ${job.hostId} vanished while its look was chosen`)\n const output: AiJobOutput = {\n resource: 'theme',\n id: 'look',\n hostId: job.hostId,\n label:\n saved.write === 'kept'\n ? 'Kept the theme you chose for this site'\n : lookLabel(style, saved.baseName),\n proposal: JSON.parse(JSON.stringify({ style, write: saved.write })) as Record<string, unknown>,\n }\n return { ...spent, outputs: [output] }\n}\n"],"names":["DEFAULT_SITE_THEME","diffOverride","overrideWriteValue","themeArtifactContent","readThemeSelection","SITE_THEME_ENTRY_ID","dropPluginSiteCache","listServerThemePresets","runThemeLibraryAction","AI_SITE_FONT_PAIRINGS","aiSiteKindFor","aiSiteKindOfInputs","AI_SITE_LOOK_TOOL","AI_SITE_LOOK_TOOL_NAME","aiReadSiteLook","aiSiteSeed","aiSiteStyleFor","aiSiteTheme","AI_SITE_MOTION_STYLE_RANGES","aiSiteMotionStylesFor","aiCatalogEntry","aiDefaultModelFor","aiModelForStep","runValidatedGeneration","aiJobStepBudget","aiGenerationSpent","aiUnspentOutcome","AI_JOB_BRIEF_MAX_CHARS","AI_SITE_LOOK_MAX_TOKENS","AI_SITE_LOOK_BUDGET","tier","maxTokens","AI_SITE_LOOK_KIND","STARTER_WORDS","AI_SITE_LOOK_INSTRUCTIONS","text","join","cacheBreakpoint","shuffled","list","seed","out","state","index","length","Math","imul","other","aiSiteLookPrompt","input","job","kind","key","inputs","trim","pairings","filter","pairing","look","fonts","includes","id","$id","label","design","brief","slice","bases","map","entry","base","words","feel","corners","chroma","motion","Boolean","aiSiteLookCheck","answer","value","violations","aiSiteLookModel","resolved","fallback","picked","model","provider","undefined","aiSiteBasePresetId","aiSiteLookBases","presets","flatMap","preset","find","description","name","stable","JSON","stringify","_key","inner","Array","isArray","Object","fromEntries","entries","sort","a","b","localeCompare","aiSiteThemeUntouched","host","themeOverride","selection","theme","aiSaveSiteTheme","firestore","hostRef","collection","doc","hostId","style","baseName","edited","override","siteStyle","jobId","write","runTransaction","tx","snapshot","get","exists","data","stored","extra","plan","action","target","ok","Error","error","update","hostIds","reason","lookLabel","heading","family","cards","AI_SITE_LOOK_SIBLINGS","aiWorkspaceSiteStyles","where","orgId","limit","docs","console","warn","aiRunSiteLook","context","deps","save","generate","modelFor","failure","origin","result","step","instructions","messages","role","content","tool","thinking","check","signal","spent","status","refused","brand","others","avoid","saved","output","resource","proposal","parse","outputs"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-site-look.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_THEME } from '@aglyn/aglyn/app-utils/default-site'\nimport { diffOverride, overrideWriteValue } from '@aglyn/aglyn/app-utils/artifact-overrides'\nimport { themeArtifactContent, type ThemeHostDocument } from '@aglyn/aglyn/app-utils/site-theme'\nimport { readThemeSelection, SITE_THEME_ENTRY_ID } from '@aglyn/aglyn/app-utils/theme-library'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport { listServerThemePresets } from '@aglyn/aglyn/plugin-manager/plugin-theme-presets'\nimport type { HostTheme } from '@aglyn/shared-data-types'\nimport { runThemeLibraryAction } from '@aglyn/tenant-data-admin/server/theme-library-write'\nimport type { AiJob, AiJobOutput } from '../model/ai-jobs.types'\nimport { AI_SITE_FONT_PAIRINGS, aiSiteKindFor, aiSiteKindOfInputs, type AiSiteBase, type AiSiteKind } from '../model/ai-site-kinds'\nimport {\n AI_SITE_LOOK_TOOL,\n AI_SITE_LOOK_TOOL_NAME,\n aiReadSiteLook,\n aiSiteSeed,\n aiSiteStyleFor,\n aiSiteTheme,\n type AiSiteLookAnswer,\n type AiSiteStyle,\n} from '../model/ai-site-look'\nimport { AI_SITE_MOTION_STYLE_RANGES, aiSiteMotionStylesFor } from '../layout-language/ai-layout-motion'\nimport { aiCatalogEntry, aiDefaultModelFor } from '../providers/catalog'\nimport { aiModelForStep } from '../providers/routing'\nimport { runValidatedGeneration, type AiGenerationCheck } from '../runtime/ai-doctrine'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { AI_SITE_INSPIRATION_SIGNALS_INPUT, aiSiteInspirationLookPins } from './ai-site-inspiration'\nimport { aiReadSiteInspiration } from './ai-site-inspiration-signals'\nimport { aiGenerationSpent, aiUnspentOutcome } from './ai-job-generation'\nimport { AI_JOB_BRIEF_MAX_CHARS, type AiJobStepContext, type AiJobStepOutcome } from './ai-job-text-step'\n\n/**\n * A site's look, designed first (AGL-3660): the scaffold's theme unit.\n *\n * One short answer from the fast tier — the base theme, two hues, a ground,\n * a font pairing, the building blocks' styles and how the pages move\n * (AGL-3941), chosen for this business\n * within its kind — then code builds the theme (`ai-site-look.ts`) and saves\n * it the way Setup → Theme saves a pick and its edits: the base theme\n * selected, the site's own look as the override over it, and the style\n * tokens on the site for later edits to keep. The header, the footer and\n * every page are built after it, so they render in it from their first draft.\n *\n * The look never fails a site. An answer that could not be read leaves the\n * kind and the seed to choose; a site whose owner already changed its theme\n * keeps it.\n */\n\n/** The answer is a dozen short values, about 150 tokens; the ceiling leaves room to spare. */\nexport const AI_SITE_LOOK_MAX_TOKENS = 600\n\n/** A look pass's time, on the fast tier the look is asked on. */\nexport const AI_SITE_LOOK_BUDGET = aiJobStepBudget({ tier: 'fast', maxTokens: AI_SITE_LOOK_MAX_TOKENS })\n\n/**\n * The generation kind a look is asked under: values code builds a theme\n * from, composing nothing, so the doctrine's short fields block leads it.\n */\nexport const AI_SITE_LOOK_KIND = 'site-look'\n\n/** The base themes as the model reads them; `starter` is the one a new site is born with. */\nconst STARTER_WORDS = 'the starter theme a new site is born with: a soft neutral ground, rounder corners and calm buttons'\n\n/** The look door's cached instructions. */\nexport const AI_SITE_LOOK_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n \"You choose the look of a new small-business website: the platform's base theme it starts from, and the choices that make it this business's own.\",\n `Answer by calling ${AI_SITE_LOOK_TOOL_NAME} exactly once, at once, and write nothing else: no reasoning, no prose.`,\n '- Choose for this business, its audience and its kind of site, never a default: a roofer and a florist should not share a color, a law firm and a yoga studio should not share a typeface.',\n '- Prefer a base theme, font pairing and corners from the lists the request gives for this kind of site; the lists are in no order, so weigh each for this business.',\n '- hue is the brand color and accent sits beside it; pick colors people would expect of this business, and let the design seed tip a close call.',\n '- A hex color written in the brief is the brand color: put it in brand. Otherwise brand is null.',\n '- Choose the building blocks to suit the kind: quiet, flat cards and small eyebrows for minimal sites; raised cards and capitals for bold ones; soft tinted cards for calm ones.',\n '- motion is how the pages arrive as a visitor scrolls: choose from the motion list the request gives, for this business, never by habit; a quiet practice suits little motion, a lively studio more.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/** A list in a seed's order (Fisher–Yates over a small LCG). */\nfunction shuffled<T>(list: readonly T[], seed: number): T[] {\n const out = [...list]\n let state = seed >>> 0 || 1\n for (let index = out.length - 1; index > 0; index -= 1) {\n state = (Math.imul(state, 1664525) + 1013904223) >>> 0\n const other = state % (index + 1)\n ;[out[index], out[other]] = [out[other] as T, out[index] as T]\n }\n return out\n}\n\n/** The look's user turn: the site, its kind and what suits it, and the seed. */\nexport function aiSiteLookPrompt(input: {\n job: Pick<AiJob, 'brief' | '$id' | 'inputs'>\n kind: AiSiteKind\n bases: ReadonlyArray<{ base: AiSiteBase; words: string }>\n}): string {\n const { job, kind } = input\n const text = (key: string) => (typeof job.inputs?.[key] === 'string' ? (job.inputs[key] as string).trim() : '')\n const pairings = AI_SITE_FONT_PAIRINGS.filter((pairing) => kind.look.fonts.includes(pairing.id))\n // The lists in the job's own order: a model reads the first of a list as\n // the answer, and every site of a kind would get the same base and type.\n const seed = aiSiteSeed(job.$id)\n return [\n `Kind of site: ${kind.label}. ${kind.design}`,\n text('businessName') ? `Business name: ${text('businessName')}` : '',\n text('businessType') ? `Business: ${text('businessType')}` : '',\n text('audience') ? `For: ${text('audience')}` : '',\n text('brand') ? `Brand: ${text('brand')}` : '',\n `Brief: ${job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`,\n `Base themes that suit it: ${shuffled(input.bases, seed).map((entry) => `${entry.base} (${entry.words})`).join('; ')}.`,\n `Font pairings that suit it: ${shuffled(pairings, seed + 1).map((pairing) => `${pairing.id} (${pairing.feel})`).join('; ')}.`,\n `Corners that suit it: ${kind.look.corners.join(', ')}. Its palette runs ${kind.look.chroma === 'neutral' ? 'neutral, ink and paper with one accent color' : kind.look.chroma}.`,\n `Motion that suits it: ${shuffled(aiSiteMotionStylesFor(kind.id), seed + 2).map((motion) => `${motion} (${AI_SITE_MOTION_STYLE_RANGES[motion].words})`).join('; ')}.`,\n `Design seed: ${job.$id.slice(-6)}.`,\n ]\n .filter(Boolean)\n .join('\\n')\n}\n\n/** The answer, read leniently; a call with nothing usable is still an answer, and the seed fills it. */\nexport const aiSiteLookCheck: AiGenerationCheck<AiSiteLookAnswer> = (answer) => ({\n value: aiReadSiteLook(answer),\n violations: [],\n})\n\n/** The model a look is asked on: the fast tier of the job's provider, unless the creator picked a model. */\nexport function aiSiteLookModel(job: Pick<AiJob, 'model'>, resolved: string | undefined): string {\n const fallback = resolved ?? aiModelForStep('job.theme')\n const picked = typeof job.model === 'string' ? job.model.trim() : ''\n if (picked && picked !== 'auto') return fallback\n const entry = aiCatalogEntry(fallback)\n return (entry ? aiDefaultModelFor(entry.provider, 'fast') : undefined) ?? fallback\n}\n\n/** The preset id a base names. */\nexport const aiSiteBasePresetId = (base: AiSiteBase) => `theme-presets.${base}`\n\n/** The bases a kind suits that this deployment has, best first, with what each looks like. */\nexport function aiSiteLookBases(kind: AiSiteKind): Array<{ base: AiSiteBase; words: string }> {\n const presets = listServerThemePresets()\n return kind.look.bases.flatMap((base): Array<{ base: AiSiteBase; words: string }> => {\n if (base === 'starter') return [{ base, words: STARTER_WORDS }]\n const preset = presets.find((entry) => entry.id === aiSiteBasePresetId(base))\n return preset ? [{ base, words: preset.description ?? preset.name }] : []\n })\n}\n\n/** Plain JSON, deep-sorted, for comparing two themes as stored. */\nfunction stable(value: unknown): string {\n return JSON.stringify(value, (_key, inner) =>\n inner && typeof inner === 'object' && !Array.isArray(inner)\n ? Object.fromEntries(Object.entries(inner as Record<string, unknown>).sort(([a], [b]) => a.localeCompare(b)))\n : inner,\n )\n}\n\n/**\n * Whether a site still has the theme it was born with (AGL-3497), or none:\n * the only theme a look may replace. A site whose owner picked a theme, edited\n * one, or installed one keeps it.\n */\nexport function aiSiteThemeUntouched(host: ThemeHostDocument & Record<string, unknown>): boolean {\n if (host.themeOverride) return false\n const selection = readThemeSelection(host)\n if (selection.kind === 'default') return true\n if (selection.kind !== 'custom' || selection.id !== SITE_THEME_ENTRY_ID) return false\n return stable(themeArtifactContent(host.theme)) === stable(themeArtifactContent(DEFAULT_SITE_THEME))\n}\n\nexport type AiSiteThemeWrite = 'applied' | 'kept' | 'missing'\n\n/**\n * Saves a look on its site in one transaction: the base theme picked through\n * the theme library (so the site's starter theme is filed in its library, as\n * a person's switch files it), the look as the override over the base, and\n * the style tokens. Again for the same job is a no-op.\n */\nexport async function aiSaveSiteTheme(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; jobId: string; style: AiSiteStyle },\n): Promise<{ write: AiSiteThemeWrite; baseName: string }> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const preset =\n input.style.base === 'starter'\n ? null\n : (listServerThemePresets().find((entry) => entry.id === aiSiteBasePresetId(input.style.base)) ?? null)\n const base: HostTheme = preset ? themeArtifactContent(preset.theme) : DEFAULT_SITE_THEME\n const baseName = preset?.name ?? 'Site theme'\n const edited = aiSiteTheme({ ...input.style, base: preset ? input.style.base : 'starter' }, base)\n const override = overrideWriteValue(diffOverride(base, edited), null)\n const siteStyle = { ...input.style, base: preset ? input.style.base : 'starter', jobId: input.jobId }\n const write = await firestore.runTransaction(async (tx): Promise<AiSiteThemeWrite> => {\n const snapshot = await tx.get(hostRef)\n if (!snapshot.exists) return 'missing'\n const host = snapshot.data() as ThemeHostDocument & Record<string, unknown>\n const stored = host['siteStyle'] as { jobId?: unknown } | undefined\n if (stored?.jobId === input.jobId) return 'applied'\n if (!aiSiteThemeUntouched(host)) return 'kept'\n const extra = { themeOverride: override, siteStyle }\n if (preset) {\n const plan = await runThemeLibraryAction(\n tx,\n hostRef,\n { action: 'select', target: { kind: 'preset', id: preset.id, name: preset.name, theme: base } },\n extra,\n )\n if (plan.ok === false) throw new Error(`the site's base theme could not be picked: ${plan.error}`)\n } else {\n tx.update(hostRef, { theme: DEFAULT_SITE_THEME, ...extra })\n }\n return 'applied'\n })\n if (write === 'applied') {\n // The theme is live the moment it is written; cached pages go now.\n await dropPluginSiteCache({ hostIds: [input.hostId], reason: 'ai site look' })\n }\n return { write, baseName }\n}\n\n/** The sentence a look's row and output carry. */\nfunction lookLabel(style: AiSiteStyle, baseName: string): string {\n const pairing = AI_SITE_FONT_PAIRINGS.find((entry) => entry.id === style.fonts)\n return `Your look: ${baseName} base, ${pairing ? `${pairing.heading.family} type` : 'its own type'}, ${style.cards} cards`\n}\n\nexport interface AiSiteLookDeps {\n save?: typeof aiSaveSiteTheme\n generate?: typeof runValidatedGeneration\n others?: typeof aiWorkspaceSiteStyles\n}\n\n/** The most of a workspace's sites a look is drawn apart from. */\nexport const AI_SITE_LOOK_SIBLINGS = 50\n\n/**\n * The looks of a workspace's other sites (AGL-3660), so a new one is drawn\n * apart from each: none shares their base, hue family, heading font and\n * buttons. A read that fails draws apart from nothing.\n */\nexport async function aiWorkspaceSiteStyles(\n firestore: FirebaseFirestore.Firestore,\n input: { orgId: string; hostId: string },\n): Promise<AiSiteStyle[]> {\n try {\n const snapshot = await firestore\n .collection('hosts')\n .where('orgId', '==', input.orgId)\n .limit(AI_SITE_LOOK_SIBLINGS)\n .get()\n return snapshot.docs.flatMap((doc) => {\n const style = doc.get('siteStyle') as AiSiteStyle | undefined\n return doc.id !== input.hostId && style && typeof style.base === 'string' ? [style] : []\n })\n } catch (error) {\n console.warn('ai site look: the workspace sites could not be read; drawing apart from none', { orgId: input.orgId, error })\n return []\n }\n}\n\n/**\n * The theme unit's pass: ask for the look, build it, save it. `job` is the\n * unit's derived job, carrying the scaffold's inputs and its origin job id,\n * which is what the seed is drawn from — so a resumed pass draws the same.\n */\nexport async function aiRunSiteLook(\n context: AiJobStepContext,\n job: AiJob,\n deps: AiSiteLookDeps = {},\n): Promise<AiJobStepOutcome> {\n const save = deps.save ?? aiSaveSiteTheme\n const generate = deps.generate ?? runValidatedGeneration\n const model = aiSiteLookModel(job, context.modelFor?.('job.theme'))\n if (!job.hostId) return { ...aiUnspentOutcome(model), failure: 'This site job names no site.' }\n const kind = aiSiteKindOfInputs(job.inputs) ?? aiSiteKindFor(job.brief)\n const origin = typeof job.inputs?.['originJobId'] === 'string' ? (job.inputs['originJobId'] as string) : job.$id\n const result = await generate<AiSiteLookAnswer>(AI_SITE_LOOK_KIND, {\n step: 'job.theme',\n model,\n instructions: AI_SITE_LOOK_INSTRUCTIONS as AiSystemBlock[],\n messages: [{ role: 'user', content: aiSiteLookPrompt({ job: { ...job, $id: origin }, kind, bases: aiSiteLookBases(kind) }) }],\n tool: AI_SITE_LOOK_TOOL,\n maxTokens: AI_SITE_LOOK_BUDGET.maxTokens(model),\n thinking: 'off',\n check: aiSiteLookCheck,\n ...(context.signal ? { signal: context.signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n // An answer that could not be used leaves the kind and the seed to choose.\n const answer = result.status === 'ok' ? result.value : {}\n const brand = typeof job.inputs?.['brand'] === 'string' ? (job.inputs['brand'] as string) : null\n const others = deps.others ?? aiWorkspaceSiteStyles\n const avoid = await others(context.firestore, { orgId: job.orgId, hostId: job.hostId })\n // What the sites the person likes pin (AGL-3975), read off the plan's\n // signals the scaffold hands its look unit; none on a start without them.\n const pins = aiSiteInspirationLookPins(aiReadSiteInspiration(job.inputs?.[AI_SITE_INSPIRATION_SIGNALS_INPUT]), {\n kind,\n seed: aiSiteSeed(origin),\n })\n const pinned = Object.keys(pins)\n const style = aiSiteStyleFor({ kind, answer, seed: aiSiteSeed(origin), brand, avoid, ...(pinned.length ? { pins } : {}) })\n const saved = await save(context.firestore, { hostId: job.hostId, jobId: origin, style })\n if (saved.write === 'missing') throw new Error(`site ${job.hostId} vanished while its look was chosen`)\n const output: AiJobOutput = {\n resource: 'theme',\n id: 'look',\n hostId: job.hostId,\n label:\n saved.write === 'kept'\n ? 'Kept the theme you chose for this site'\n : lookLabel(style, saved.baseName),\n proposal: JSON.parse(JSON.stringify({ style, write: saved.write, ...(pinned.length ? { inspired: pinned } : {}) })) as Record<string, unknown>,\n }\n return { ...spent, outputs: [output] }\n}\n"],"names":["DEFAULT_SITE_THEME","diffOverride","overrideWriteValue","themeArtifactContent","readThemeSelection","SITE_THEME_ENTRY_ID","dropPluginSiteCache","listServerThemePresets","runThemeLibraryAction","AI_SITE_FONT_PAIRINGS","aiSiteKindFor","aiSiteKindOfInputs","AI_SITE_LOOK_TOOL","AI_SITE_LOOK_TOOL_NAME","aiReadSiteLook","aiSiteSeed","aiSiteStyleFor","aiSiteTheme","AI_SITE_MOTION_STYLE_RANGES","aiSiteMotionStylesFor","aiCatalogEntry","aiDefaultModelFor","aiModelForStep","runValidatedGeneration","aiJobStepBudget","AI_SITE_INSPIRATION_SIGNALS_INPUT","aiSiteInspirationLookPins","aiReadSiteInspiration","aiGenerationSpent","aiUnspentOutcome","AI_JOB_BRIEF_MAX_CHARS","AI_SITE_LOOK_MAX_TOKENS","AI_SITE_LOOK_BUDGET","tier","maxTokens","AI_SITE_LOOK_KIND","STARTER_WORDS","AI_SITE_LOOK_INSTRUCTIONS","text","join","cacheBreakpoint","shuffled","list","seed","out","state","index","length","Math","imul","other","aiSiteLookPrompt","input","job","kind","key","inputs","trim","pairings","filter","pairing","look","fonts","includes","id","$id","label","design","brief","slice","bases","map","entry","base","words","feel","corners","chroma","motion","Boolean","aiSiteLookCheck","answer","value","violations","aiSiteLookModel","resolved","fallback","picked","model","provider","undefined","aiSiteBasePresetId","aiSiteLookBases","presets","flatMap","preset","find","description","name","stable","JSON","stringify","_key","inner","Array","isArray","Object","fromEntries","entries","sort","a","b","localeCompare","aiSiteThemeUntouched","host","themeOverride","selection","theme","aiSaveSiteTheme","firestore","hostRef","collection","doc","hostId","style","baseName","edited","override","siteStyle","jobId","write","runTransaction","tx","snapshot","get","exists","data","stored","extra","plan","action","target","ok","Error","error","update","hostIds","reason","lookLabel","heading","family","cards","AI_SITE_LOOK_SIBLINGS","aiWorkspaceSiteStyles","where","orgId","limit","docs","console","warn","aiRunSiteLook","context","deps","save","generate","modelFor","failure","origin","result","step","instructions","messages","role","content","tool","thinking","check","signal","spent","status","refused","brand","others","avoid","pins","pinned","keys","saved","output","resource","proposal","parse","inspired","outputs"],"mappings":"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"}
@@ -162,7 +162,11 @@ export declare function aiSitePendingUnits(units: readonly AiSiteUnit[], outputs
162
162
  * output by `aiSitePendingUnits` and cannot move where the scaffold thinks
163
163
  * it is.
164
164
  */
165
- export declare function aiSiteSeoOutputs(job: AiJob): AiJobOutput[];
165
+ export declare function aiSiteSeoOutputs(job: AiJob, siteName?: string | null): AiJobOutput[];
166
+ /** Whether the scaffold has yet to report the site's listing. */
167
+ export declare function aiSiteSeoOutputOwed(job: Pick<AiJob, 'outputs'>): boolean;
168
+ /** The site's own name, which its listing leads with (AGL-3979); `null` when it cannot be read. */
169
+ export declare function aiSiteDisplayName(firestore: FirebaseFirestore.Firestore, hostId: string | null | undefined): Promise<string | null>;
166
170
  /** A record an earlier unit created, as a later one references it. */
167
171
  export interface AiSiteBuiltRef {
168
172
  id: string;
@@ -193,6 +197,13 @@ export declare function aiSiteResolvedRef(ref: string | null, built: BuiltRefs):
193
197
  * name of its own in the header and the footer.
194
198
  */
195
199
  export declare function aiSiteBriefLines(brief: string, inputs: AiSiteJobInputs): string[];
200
+ /**
201
+ * What a guided start's welcome email is called in Marketing (AGL-3988): the
202
+ * business's welcome email, else "Welcome email". The beta.270 Sugar Hollow
203
+ * start's was listed as "New email", the email step's name for one nothing
204
+ * named.
205
+ */
206
+ export declare function aiSiteWelcomeEmailName(inputs: Readonly<Record<string, unknown>> | null | undefined): string;
196
207
  /**
197
208
  * What the welcome email is told (AGL-2918), beyond that it is a welcome
198
209
  * email.
@@ -263,6 +274,8 @@ export declare function aiSiteSellsProducts(job: Pick<AiJob, 'items'>): boolean;
263
274
  export declare function createAiSiteJobAdmission(deps?: {
264
275
  freeCreditsLeft?: typeof readFreeAiCreditsLeft;
265
276
  now?: () => Date;
277
+ /** Whether `release_ai_site_inspiration` is on for a workspace (AGL-3975). */
278
+ inspirationReleased?: (orgId: string) => Promise<boolean>;
266
279
  }): AiJobAdmission;
267
280
  export declare const aiSiteJobAdmission: AiJobAdmission;
268
281
  /** Whether a scaffold's workspace spends the Free taste (AGL-3594); a missing org reads as paid, as the band has always been. */
@@ -301,6 +314,8 @@ export interface AiJobSiteStepDeps {
301
314
  readNodes?: typeof readAiDraftNodes;
302
315
  /** The guided start's publish; specs hand in a fake. */
303
316
  publish?: typeof aiPublishGuidedSite;
317
+ /** The site's own name, which its listing leads with (AGL-3979); specs hand in a fake. */
318
+ siteName?: typeof aiSiteDisplayName;
304
319
  /** The look's pass (AGL-3660); specs hand in a fake. */
305
320
  look?: (context: AiJobStepContext, job: AiJob) => Promise<AiJobStepOutcome>;
306
321
  /** The first posts' and first products' passes (AGL-3676); specs hand in fakes. */
@@ -24,11 +24,12 @@ import { AI_SITE_EMAIL_TYPE, AI_SITE_MAX_SECTIONS, AI_SITE_PAGES, aiFreeCreditsN
24
24
  import { readFreeAiCreditsLeft } from "../usage/free-ai-credits-left.js";
25
25
  import { AI_CREDITS_CONFIRM_CODE, aiCreditsPromptText } from "../model/ai-credit-estimate.js";
26
26
  import { AI_SITE_SEO_OUTPUT_ID, aiSiteSeoProposalForInputs } from "../model/ai-site-start-seo.js";
27
+ import { aiSiteDetailsBriefLines } from "../model/ai-site-details.js";
27
28
  import { aiModelForStep } from "../providers/routing.js";
28
29
  import { registerAiJobAdmission } from "./ai-job-admission.js";
29
30
  import { aiOriginJobId, aiRecordedJobDraftId } from "./ai-job-draft-ids.js";
30
31
  import { readAiDraftNodes } from "./ai-job-drafts.js";
31
- import { AI_STOCK_PHOTO_AVOID_INPUT, AI_STOCK_PHOTO_PAGES_INPUT } from "./ai-layout-stock-photos.js";
32
+ import { AI_STOCK_PHOTO_AVOID_INPUT, AI_STOCK_PHOTO_JOB_PHOTOS_INPUT, AI_STOCK_PHOTO_PAGES_INPUT } from "./ai-layout-stock-photos.js";
32
33
  import { AI_LAYOUT_SITE_ALIASES_INPUT, AI_LAYOUT_SITE_PAGES_INPUT, AI_LAYOUT_SITE_PAGES_MAX, aiLayoutIsHomeSlug, aiLayoutSitePagesOfPlan } from "./ai-job-layout-site-pages.js";
33
34
  import { aiPageSectionNodeId } from "./ai-job-page-sections.js";
34
35
  import { AI_LAYOUT_LISTINGS_INPUT, aiLayoutIsShopPage } from "../layout-language/ai-layout-listings.js";
@@ -37,7 +38,7 @@ import { AI_LAYOUT_FORM_PAGE_INPUT, AI_LAYOUT_LANGUAGE_INPUT, aiJobUsesLayoutLan
37
38
  import { aiJobPublishesSite, aiMergeSitePublish, aiPublishGuidedSite, aiRestoreGuidedBlogEntry } from "./ai-site-publish.js";
38
39
  import { AI_SITE_CONTENT_INPUT, AI_SITE_FREE_POSTS, AI_SITE_POST_BUDGET, AI_SITE_POST_LABEL, AI_SITE_POSTS, AI_SITE_POSTS_LABEL, AI_SITE_PRODUCTS_LABEL, AI_SITE_PRODUCTS_PRICE_NOTE, aiPublishSitePosts, aiSiteContentBriefLines, aiSiteContentPart, aiSiteContentRefusal, aiSiteListings, aiSiteProductsBriefLine, createAiSiteProductsRunner, runAiSitePostsUnit } from "./ai-job-site-content.js";
39
40
  import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cache";
40
- import { AI_SITE_DATASET_BUDGET, AI_SITE_DATASET_INPUT, AI_SITE_DATASETS_MAX, aiSiteDatasetBriefLines, aiSiteDatasetInputOf, aiSiteDatasetListings, aiSiteDatasetPlacedPhotos, aiSiteDatasetRefusal, aiSitePlanDatasets, aiSiteReusedDatasetListings, aiSiteRecordTemplateOf, runAiSiteDatasetUnit } from "./ai-job-site-datasets.js";
41
+ import { AI_SITE_DATASET_BUDGET, AI_SITE_DATASET_INPUT, AI_SITE_DATASETS_MAX, aiSiteDatasetBriefLines, aiSiteDatasetInputOf, aiSiteDatasetListings, aiSiteDatasetPlacedPhotoDetails, aiSiteDatasetPlacedPhotos, aiSiteDatasetRefusal, aiSitePlanDatasets, aiSiteReusedDatasetListings, aiSiteRecordTemplateOf, runAiSiteDatasetUnit } from "./ai-job-site-datasets.js";
41
42
  import { AI_SITE_EVENT_INPUT, AI_SITE_EVENT_LABEL, AI_SITE_EVENT_MINIMUM_MS, AI_SITE_EVENTS_MAX, aiSiteEventListings, aiSiteEventRefusal, aiSitePlanEvents, runAiSiteEventUnit } from "./ai-job-site-events.js";
42
43
  import { AI_SITE_LOOK_BUDGET, aiRunSiteLook } from "./ai-job-site-look.js";
43
44
  import { AI_SITE_STORE_PAGES_INPUT, aiSiteStorePagesRefusal, runAiSiteStorePagesUnit } from "./ai-job-site-store-pages.js";
@@ -46,6 +47,8 @@ import { AI_FORM_DATASET_MADE_INPUT } from "./ai-job-form-dataset.js";
46
47
  import { aiConfirmedPlan, aiUnspentOutcome } from "./ai-job-generation.js";
47
48
  import { AI_JOB_BRIEF_MAX_CHARS } from "./ai-job-text-step.js";
48
49
  import { AI_JOB_PAGE_STEP_MINIMUM_MS } from "./ai-job-page-budget.js";
50
+ import { AI_SITE_INSPIRATION_SIGNALS_INPUT, aiSiteInspirationReleased } from "./ai-site-inspiration.js";
51
+ import { aiSiteInspirationRefusal } from "../model/ai-site-inspiration.js";
49
52
  import { aiJobStepRunMinimumMs, aiJobStepRunnerFor, registerAiJobStep, registerAiJobStepPasses } from "./ai-jobs.js";
50
53
  /** The job kind and output resource each unit is built through. */ const UNIT_KINDS = {
51
54
  theme: {
@@ -276,14 +279,13 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
276
279
  * is `seo`, which no unit reports, so it cannot be mistaken for a unit's
277
280
  * output by `aiSitePendingUnits` and cannot move where the scaffold thinks
278
281
  * it is.
279
- */ export function aiSiteSeoOutputs(job) {
280
- var _job_outputs, _job_hostId;
282
+ */ export function aiSiteSeoOutputs(job, siteName) {
283
+ var _job_hostId;
281
284
  var _job_plan;
282
- const outputs = (_job_outputs = job.outputs) != null ? _job_outputs : [];
283
- if (outputs.some((output)=>output.resource === 'seo' && output.id === AI_SITE_SEO_OUTPUT_ID)) {
284
- return [];
285
- }
286
- const proposal = aiSiteSeoProposalForInputs(job.inputs, (_job_plan = job.plan) == null ? void 0 : _job_plan.language);
285
+ if (!aiSiteSeoOutputOwed(job)) return [];
286
+ // The site's own name leads it where the start named no business (AGL-3979):
287
+ // the same listing the publish writes over the seeded default.
288
+ const proposal = aiSiteSeoProposalForInputs(job.inputs, (_job_plan = job.plan) == null ? void 0 : _job_plan.language, siteName);
287
289
  if (!proposal) return [];
288
290
  return [
289
291
  {
@@ -295,6 +297,20 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
295
297
  }
296
298
  ];
297
299
  }
300
+ /** Whether the scaffold has yet to report the site's listing. */ export function aiSiteSeoOutputOwed(job) {
301
+ var _job_outputs;
302
+ return !((_job_outputs = job.outputs) != null ? _job_outputs : []).some((output)=>output.resource === 'seo' && output.id === AI_SITE_SEO_OUTPUT_ID);
303
+ }
304
+ /** The site's own name, which its listing leads with (AGL-3979); `null` when it cannot be read. */ export async function aiSiteDisplayName(firestore, hostId) {
305
+ if (!hostId) return null;
306
+ try {
307
+ const name = (await firestore.collection('hosts').doc(hostId).get()).get('displayName');
308
+ return typeof name === 'string' && name.trim() ? name.trim() : null;
309
+ } catch (unused) {
310
+ // A listing without the name is the listing every start made before.
311
+ return null;
312
+ }
313
+ }
298
314
  /**
299
315
  * What the scaffold has already created, as the plan's `new:<name>`
300
316
  * references resolve to: the id of the record each completed creation wrote.
@@ -361,8 +377,23 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
361
377
  ].filter(Boolean);
362
378
  lines.push(`Site — ${site.join('; ')}.`);
363
379
  if (inputs.businessName) lines.push(aiSiteNameSentence(inputs.businessName));
380
+ // The business details the person gave (AGL-3979): how to reach it, where
381
+ // and when, and that the pages show the phone and email as written.
382
+ lines.push(...aiSiteDetailsBriefLines(inputs, {
383
+ city: inputs.city
384
+ }));
364
385
  return lines;
365
386
  }
387
+ /**
388
+ * What a guided start's welcome email is called in Marketing (AGL-3988): the
389
+ * business's welcome email, else "Welcome email". The beta.270 Sugar Hollow
390
+ * start's was listed as "New email", the email step's name for one nothing
391
+ * named.
392
+ */ export function aiSiteWelcomeEmailName(inputs) {
393
+ const raw = inputs == null ? void 0 : inputs['businessName'];
394
+ const business = typeof raw === 'string' ? raw.replace(/\s+/g, ' ').trim().slice(0, 100) : '';
395
+ return business ? `${business} welcome email` : 'Welcome email';
396
+ }
366
397
  /**
367
398
  * What the welcome email is told (AGL-2918), beyond that it is a welcome
368
399
  * email.
@@ -498,21 +529,29 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
498
529
  const unitInputs = _extends({}, job.inputs, {
499
530
  originJobId: aiOriginJobId(job)
500
531
  });
532
+ // The look pins what the sites the person likes say of style (AGL-3975):
533
+ // the signals the plan step read, never the sites themselves.
534
+ if (unit.kind === 'theme' && plan.inspiration) unitInputs[AI_SITE_INSPIRATION_SIGNALS_INPUT] = plan.inspiration;
535
+ // The welcome email is named for the business in Marketing, never "New email" (AGL-3988).
536
+ if (unit.kind === 'email' && typeof unitInputs['name'] !== 'string') unitInputs['name'] = aiSiteWelcomeEmailName(job.inputs);
501
537
  // A page is told the pages built before it, whose photos it does not place
502
538
  // again (AGL-3660): the maker's portrait in About is never a vase in Work.
503
539
  if (unit.screen) {
504
- var _job_outputs2, _job_outputs3;
540
+ var _job_outputs2, _job_outputs3, _job_outputs4;
505
541
  const pages = ((_job_outputs2 = job.outputs) != null ? _job_outputs2 : []).filter((output)=>output.resource === 'screen' && output.hostId === job.hostId && output.id).map((output)=>output.id);
506
542
  if (pages.length) unitInputs[AI_STOCK_PHOTO_PAGES_INPUT] = pages;
507
543
  // Nor a photo the site's datasets' records already show (AGL-3616).
508
544
  const records = aiSiteDatasetPlacedPhotos((_job_outputs3 = job.outputs) != null ? _job_outputs3 : []);
509
545
  if (records.length) unitInputs[AI_STOCK_PHOTO_AVOID_INPUT] = records;
546
+ // Which a picture the library fails takes before any starter (AGL-3988).
547
+ const recordPhotos = aiSiteDatasetPlacedPhotoDetails((_job_outputs4 = job.outputs) != null ? _job_outputs4 : []);
548
+ if (recordPhotos.length) unitInputs[AI_STOCK_PHOTO_JOB_PHOTOS_INPUT] = recordPhotos;
510
549
  }
511
550
  // The layout is built before the pages, so it is told them (AGL-3596): their
512
551
  // ids are minted on the plan, and the platform writes the header's links.
513
552
  // A page is told them too, so its buttons may go to a page built after it.
514
553
  if (job.kind === 'site' && (unit.kind === 'layout' || unit.kind === 'page')) {
515
- var _job_outputs4, _job_outputs5, _job_outputs6;
554
+ var _job_outputs5, _job_outputs6, _job_outputs7;
516
555
  var _aiSiteKindOfInputs, _aiSiteKindOfInputs1;
517
556
  // A guided start links every page the person asked for (AGL-3660), and
518
557
  // the blog its first posts are written into, by its path, second after
@@ -551,7 +590,7 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
551
590
  // The store's catalog and the blog, listed by the sections that show them,
552
591
  // and the cart in a selling site's header (AGL-3676).
553
592
  const listings = aiSiteListings({
554
- outputs: (_job_outputs4 = job.outputs) != null ? _job_outputs4 : [],
593
+ outputs: (_job_outputs5 = job.outputs) != null ? _job_outputs5 : [],
555
594
  screens: merged.screens,
556
595
  sells: aiSiteSellsProducts(job),
557
596
  // A store lists its catalog on its Shop page even when its first
@@ -568,13 +607,13 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
568
607
  entry.id
569
608
  ]));
570
609
  listings.push(...aiSiteDatasetListings({
571
- outputs: (_job_outputs5 = job.outputs) != null ? _job_outputs5 : [],
610
+ outputs: (_job_outputs6 = job.outputs) != null ? _job_outputs6 : [],
572
611
  datasets: aiSitePlanDatasets(plan),
573
612
  screens: merged.screens,
574
613
  delivered
575
614
  }), // The events this start drafted, in the section that names them (AGL-3755).
576
615
  ...aiSiteEventListings({
577
- outputs: (_job_outputs6 = job.outputs) != null ? _job_outputs6 : [],
616
+ outputs: (_job_outputs7 = job.outputs) != null ? _job_outputs7 : [],
578
617
  items: aiSitePlanEvents(plan),
579
618
  screens: merged.screens
580
619
  }));
@@ -597,11 +636,11 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
597
636
  // A site's pages and its layout are designed in the layout language and
598
637
  // compiled (AGL-3660), and a page is told which page places the site's form.
599
638
  if (unit.kind === 'layout' || unit.kind === 'page') {
600
- var _job_outputs7;
639
+ var _job_outputs8;
601
640
  var _look_proposal;
602
641
  // The look designed first (AGL-3660): its header arrangement, band rhythm
603
642
  // and, where it chose one, how its pages move (AGL-3941).
604
- const look = ((_job_outputs7 = job.outputs) != null ? _job_outputs7 : []).find((output)=>output.resource === 'theme' && output.id === 'look');
643
+ const look = ((_job_outputs8 = job.outputs) != null ? _job_outputs8 : []).find((output)=>output.resource === 'theme' && output.id === 'look');
605
644
  const style = look == null ? void 0 : (_look_proposal = look.proposal) == null ? void 0 : _look_proposal['style'];
606
645
  if (style) {
607
646
  unitInputs['siteStyle'] = _extends({
@@ -617,11 +656,11 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
617
656
  if (formPage) unitInputs[AI_LAYOUT_FORM_PAGE_INPUT] = formPage;
618
657
  }
619
658
  if (unit.kind === 'posts') {
620
- var _job_outputs8, _unit_posts, _inputs_businessName;
659
+ var _job_outputs9, _unit_posts, _inputs_businessName;
621
660
  // The posts already written (one a pass), the addresses the site's own
622
661
  // pages answer at, and the byline the person gave (AGL-3676).
623
662
  const posts = {
624
- written: ((_job_outputs8 = job.outputs) != null ? _job_outputs8 : []).filter((output)=>output.resource === 'entry').map((output)=>({
663
+ written: ((_job_outputs9 = job.outputs) != null ? _job_outputs9 : []).filter((output)=>output.resource === 'entry').map((output)=>({
625
664
  id: output.id,
626
665
  title: output.label
627
666
  })),
@@ -706,10 +745,12 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
706
745
  * org, and only where this deployment has loaded the step that builds a page:
707
746
  * a scaffold whose pages nothing can build is not a scaffold.
708
747
  */ export function createAiSiteJobAdmission(deps = {}) {
709
- var _deps_freeCreditsLeft, _deps_now;
748
+ var _deps_freeCreditsLeft, _deps_inspirationReleased, _deps_now;
710
749
  const freeCreditsLeft = (_deps_freeCreditsLeft = deps.freeCreditsLeft) != null ? _deps_freeCreditsLeft : readFreeAiCreditsLeft;
750
+ const inspirationReleased = (_deps_inspirationReleased = deps.inspirationReleased) != null ? _deps_inspirationReleased : aiSiteInspirationReleased;
711
751
  const now = (_deps_now = deps.now) != null ? _deps_now : ()=>new Date();
712
752
  return async (context)=>{
753
+ var _inputs_inspiration;
713
754
  const inputs = parseAiSiteJobInputs(context.inputs);
714
755
  if (typeof inputs === 'string') return {
715
756
  status: 400,
@@ -723,6 +764,16 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
723
764
  status: 400,
724
765
  error: pages
725
766
  };
767
+ // The sites the person likes (AGL-3975): one on the Free taste, three on
768
+ // a paid plan — asked only while the release flag is on, since with it
769
+ // off they are never read.
770
+ if (((_inputs_inspiration = inputs.inspiration) == null ? void 0 : _inputs_inspiration.length) && await inspirationReleased(context.orgId)) {
771
+ const inspiration = aiSiteInspirationRefusal(inputs.inspiration, freeTaste);
772
+ if (inspiration) return {
773
+ status: 400,
774
+ error: inspiration
775
+ };
776
+ }
726
777
  // A Free start is admitted on its measured p90 (AGL-3722), not its worst
727
778
  // case: one that fits what is left of the month starts with no prompt;
728
779
  // one that does not is answered with the prompt — what it is likely to
@@ -953,10 +1004,11 @@ export const aiSiteJobAdmission = createAiSiteJobAdmission();
953
1004
  }).filter((unit)=>unit.kind === 'store' && ledgered('store') || unit.kind === 'theme' || unit.kind === 'posts' && ledgered('posts') || unit.kind === 'dataset' || unit.kind === 'event' || unit.kind !== 'store' && runnerFor(unit.jobKind));
954
1005
  }
955
1006
  export function createAiJobSiteStep(deps = {}) {
956
- var _deps_runnerFor, _deps_readNodes, _deps_publish, _deps_look, _deps_contentRefusal, _deps_datasetRefusal, _deps_eventRefusal, _deps_storePagesRefusal, _deps_publishPosts, _deps_dropCache, _deps_restoreBlogEntry;
1007
+ var _deps_runnerFor, _deps_readNodes, _deps_publish, _deps_siteName, _deps_look, _deps_contentRefusal, _deps_datasetRefusal, _deps_eventRefusal, _deps_storePagesRefusal, _deps_publishPosts, _deps_dropCache, _deps_restoreBlogEntry;
957
1008
  const runnerFor = (_deps_runnerFor = deps.runnerFor) != null ? _deps_runnerFor : aiJobStepRunnerFor;
958
1009
  const readNodes = (_deps_readNodes = deps.readNodes) != null ? _deps_readNodes : readAiDraftNodes;
959
1010
  const publish = (_deps_publish = deps.publish) != null ? _deps_publish : aiPublishGuidedSite;
1011
+ const siteName = (_deps_siteName = deps.siteName) != null ? _deps_siteName : aiSiteDisplayName;
960
1012
  const look = (_deps_look = deps.look) != null ? _deps_look : aiRunSiteLook;
961
1013
  const contentRefusal = (_deps_contentRefusal = deps.contentRefusal) != null ? _deps_contentRefusal : aiSiteContentRefusal;
962
1014
  const datasetRefusal = (_deps_datasetRefusal = deps.datasetRefusal) != null ? _deps_datasetRefusal : aiSiteDatasetRefusal;
@@ -1040,7 +1092,14 @@ export function createAiJobSiteStep(deps = {}) {
1040
1092
  blogUnwritten: !aiBuildItemDelivered(postsRow) || !((_postsRow_outputs = postsRow.outputs) == null ? void 0 : _postsRow_outputs.length)
1041
1093
  } : {}, rows.get('store') ? {
1042
1094
  unwrittenHrefs: unwrittenStoreHrefs(pages)
1043
- } : {}, wasLive ? {
1095
+ } : {}, {
1096
+ // The pages it planned and did not build, which nothing it publishes links (AGL-3988).
1097
+ unbuiltScreenIds: unbuiltPages(),
1098
+ // And the order the site lists its pages in, which Pages and the 404 page follow.
1099
+ siteOrder: plan.screens.flatMap((screen)=>screen.id ? [
1100
+ screen.id
1101
+ ] : [])
1102
+ }, wasLive ? {
1044
1103
  retry: true
1045
1104
  } : {}, plan.language ? {
1046
1105
  language: plan.language
@@ -1167,6 +1226,20 @@ export function createAiJobSiteStep(deps = {}) {
1167
1226
  ...extra
1168
1227
  ];
1169
1228
  };
1229
+ /**
1230
+ * The pages the start planned and has not built (AGL-3988): a page whose
1231
+ * part failed or was refused. The beta.270 Sugar Hollow Galleries page
1232
+ * failed its checks, and its home page, header and footer linked it
1233
+ * anyway, to a 404.
1234
+ */ const unbuiltPages = ()=>units.flatMap((unit)=>{
1235
+ var _rows_get;
1236
+ var _unit_screen;
1237
+ return unit.kind === 'page' && !templates.has(unit.slot) && ((_unit_screen = unit.screen) == null ? void 0 : _unit_screen.id) && !aiBuildItemDelivered((_rows_get = rows.get(unit.slot)) != null ? _rows_get : {
1238
+ status: 'pending'
1239
+ }) ? [
1240
+ unit.screen.id
1241
+ ] : [];
1242
+ });
1170
1243
  if (!next) return finish(_extends({}, aiUnspentOutcome(model), init), builtPages());
1171
1244
  const unit = units.find((one)=>one.slot === next.slot);
1172
1245
  // The look is the scaffold's own pass (AGL-3660), not the theme job's proposal.
@@ -1184,7 +1257,7 @@ export function createAiJobSiteStep(deps = {}) {
1184
1257
  // The site's own listing rides out beside the first unit's output, once
1185
1258
  // (AGL-2918): it is derived from the answers rather than generated, so it
1186
1259
  // is ready before anything is built and costs the pass nothing.
1187
- const listing = aiSiteSeoOutputs(job);
1260
+ const listing = aiSiteSeoOutputOwed(job) ? aiSiteSeoOutputs(job, await siteName(context.firestore, job.hostId)) : [];
1188
1261
  const settle = (item, spent = aiUnspentOutcome(model))=>_extends({}, spent, {
1189
1262
  outputs: [
1190
1263
  ...listing,