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

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 (234) 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/declarations.server.js +7 -4
  43. package/src/lib/declarations.server.js.map +1 -1
  44. package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
  45. package/src/lib/jobs/ai-build-capabilities.js +109 -9
  46. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  47. package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
  48. package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
  49. package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
  50. package/src/lib/jobs/ai-job-build-step.js +3 -1
  51. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  52. package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
  53. package/src/lib/jobs/ai-job-edit-step.js +631 -0
  54. package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
  55. package/src/lib/jobs/ai-job-insight-step.js +21 -4
  56. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  57. package/src/lib/jobs/ai-job-layout-language.d.ts +8 -3
  58. package/src/lib/jobs/ai-job-layout-language.js +8 -2
  59. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  60. package/src/lib/jobs/ai-job-layout-step.js +7 -6
  61. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  62. package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
  63. package/src/lib/jobs/ai-job-logic-step.js +87 -6
  64. package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
  65. package/src/lib/jobs/ai-job-page-language.d.ts +52 -7
  66. package/src/lib/jobs/ai-job-page-language.js +72 -9
  67. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  68. package/src/lib/jobs/ai-job-page-step.js +12 -2
  69. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  70. package/src/lib/jobs/ai-job-plan-step.d.ts +24 -5
  71. package/src/lib/jobs/ai-job-plan-step.js +68 -21
  72. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  73. package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
  74. package/src/lib/jobs/ai-job-site-content.js +443 -0
  75. package/src/lib/jobs/ai-job-site-content.js.map +1 -0
  76. package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
  77. package/src/lib/jobs/ai-job-site-look.js +322 -0
  78. package/src/lib/jobs/ai-job-site-look.js.map +1 -0
  79. package/src/lib/jobs/ai-job-site-step.d.ts +26 -5
  80. package/src/lib/jobs/ai-job-site-step.js +163 -24
  81. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  82. package/src/lib/jobs/ai-jobs-notify.d.ts +16 -1
  83. package/src/lib/jobs/ai-jobs-notify.js +81 -3
  84. package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
  85. package/src/lib/jobs/ai-jobs.d.ts +14 -0
  86. package/src/lib/jobs/ai-jobs.js +28 -10
  87. package/src/lib/jobs/ai-jobs.js.map +1 -1
  88. package/src/lib/layout-language/ai-layout-compiler.d.ts +9 -0
  89. package/src/lib/layout-language/ai-layout-compiler.js +120 -33
  90. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  91. package/src/lib/layout-language/ai-layout-frame.js +5 -4
  92. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  93. package/src/lib/layout-language/ai-layout-language.d.ts +12 -5
  94. package/src/lib/layout-language/ai-layout-language.js +92 -9
  95. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  96. package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
  97. package/src/lib/layout-language/ai-layout-pictures.js +214 -0
  98. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
  99. package/src/lib/layout-language/ai-layout-store.d.ts +2 -0
  100. package/src/lib/layout-language/ai-layout-store.js +7 -1
  101. package/src/lib/layout-language/ai-layout-store.js.map +1 -1
  102. package/src/lib/model/ai-build-progress.js +4 -1
  103. package/src/lib/model/ai-build-progress.js.map +1 -1
  104. package/src/lib/model/ai-edit-job.d.ts +109 -0
  105. package/src/lib/model/ai-edit-job.js +157 -0
  106. package/src/lib/model/ai-edit-job.js.map +1 -0
  107. package/src/lib/model/ai-job-activity.js +1 -0
  108. package/src/lib/model/ai-job-activity.js.map +1 -1
  109. package/src/lib/model/ai-jobs.types.d.ts +3 -1
  110. package/src/lib/model/ai-jobs.types.js.map +1 -1
  111. package/src/lib/model/ai-logic-job.d.ts +18 -1
  112. package/src/lib/model/ai-logic-job.js +24 -3
  113. package/src/lib/model/ai-logic-job.js.map +1 -1
  114. package/src/lib/model/ai-media-credits.d.ts +8 -4
  115. package/src/lib/model/ai-media-credits.js +11 -4
  116. package/src/lib/model/ai-media-credits.js.map +1 -1
  117. package/src/lib/model/ai-media-kinds.d.ts +68 -0
  118. package/src/lib/model/ai-media-kinds.js +98 -0
  119. package/src/lib/model/ai-media-kinds.js.map +1 -0
  120. package/src/lib/model/ai-plan-capabilities.d.ts +16 -0
  121. package/src/lib/model/ai-plan-capabilities.js +5 -0
  122. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  123. package/src/lib/model/ai-site-build-progress.d.ts +4 -0
  124. package/src/lib/model/ai-site-build-progress.js +28 -11
  125. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  126. package/src/lib/model/ai-site-context.d.ts +111 -0
  127. package/src/lib/model/ai-site-context.js +222 -0
  128. package/src/lib/model/ai-site-context.js.map +1 -0
  129. package/src/lib/model/ai-site-job.d.ts +10 -2
  130. package/src/lib/model/ai-site-job.js +12 -5
  131. package/src/lib/model/ai-site-job.js.map +1 -1
  132. package/src/lib/model/ai-site-kinds.d.ts +164 -0
  133. package/src/lib/model/ai-site-kinds.js +1942 -0
  134. package/src/lib/model/ai-site-kinds.js.map +1 -0
  135. package/src/lib/model/ai-site-look.d.ts +150 -0
  136. package/src/lib/model/ai-site-look.js +1236 -0
  137. package/src/lib/model/ai-site-look.js.map +1 -0
  138. package/src/lib/model/ai-site-memory.d.ts +80 -0
  139. package/src/lib/model/ai-site-memory.js +171 -0
  140. package/src/lib/model/ai-site-memory.js.map +1 -0
  141. package/src/lib/model/ai-site-start.d.ts +10 -1
  142. package/src/lib/model/ai-site-start.js +16 -2
  143. package/src/lib/model/ai-site-start.js.map +1 -1
  144. package/src/lib/model/assist-follow-up.d.ts +92 -0
  145. package/src/lib/model/assist-follow-up.js +128 -0
  146. package/src/lib/model/assist-follow-up.js.map +1 -0
  147. package/src/lib/operator-alerts.d.ts +18 -0
  148. package/src/lib/operator-alerts.js +42 -0
  149. package/src/lib/operator-alerts.js.map +1 -1
  150. package/src/lib/plugin-config.js +2 -0
  151. package/src/lib/plugin-config.js.map +1 -1
  152. package/src/lib/plugin.js +20 -1
  153. package/src/lib/plugin.js.map +1 -1
  154. package/src/lib/providers/anthropic.d.ts +9 -1
  155. package/src/lib/providers/anthropic.js +143 -9
  156. package/src/lib/providers/anthropic.js.map +1 -1
  157. package/src/lib/providers/catalog.d.ts +37 -7
  158. package/src/lib/providers/catalog.js +103 -11
  159. package/src/lib/providers/catalog.js.map +1 -1
  160. package/src/lib/providers/contract.d.ts +29 -0
  161. package/src/lib/providers/contract.js.map +1 -1
  162. package/src/lib/providers/image-contract.d.ts +22 -1
  163. package/src/lib/providers/image-contract.js +40 -1
  164. package/src/lib/providers/image-contract.js.map +1 -1
  165. package/src/lib/providers/image-endpoints.d.ts +24 -0
  166. package/src/lib/providers/image-endpoints.js +28 -0
  167. package/src/lib/providers/image-endpoints.js.map +1 -0
  168. package/src/lib/providers/model-choice.js +23 -2
  169. package/src/lib/providers/model-choice.js.map +1 -1
  170. package/src/lib/providers/routing.js +18 -0
  171. package/src/lib/providers/routing.js.map +1 -1
  172. package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
  173. package/src/lib/providers/vertex-image-endpoint.js +80 -0
  174. package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
  175. package/src/lib/providers/vertex-image.d.ts +13 -27
  176. package/src/lib/providers/vertex-image.js +15 -42
  177. package/src/lib/providers/vertex-image.js.map +1 -1
  178. package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
  179. package/src/lib/runtime/ai-blog-post-generation.js +89 -0
  180. package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
  181. package/src/lib/runtime/ai-dev-env.d.ts +53 -0
  182. package/src/lib/runtime/ai-dev-env.js +72 -0
  183. package/src/lib/runtime/ai-dev-env.js.map +1 -0
  184. package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
  185. package/src/lib/runtime/ai-dev-replay.js +313 -0
  186. package/src/lib/runtime/ai-dev-replay.js.map +1 -0
  187. package/src/lib/runtime/ai-doctrine-validators.d.ts +7 -0
  188. package/src/lib/runtime/ai-doctrine-validators.js +9 -2
  189. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  190. package/src/lib/runtime/ai-doctrine.js +5 -1
  191. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  192. package/src/lib/runtime/ai-eval-live.js +4 -0
  193. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  194. package/src/lib/runtime/ai-eval.d.ts +13 -1
  195. package/src/lib/runtime/ai-eval.js +84 -1
  196. package/src/lib/runtime/ai-eval.js.map +1 -1
  197. package/src/lib/runtime/ai-live-batch.d.ts +45 -0
  198. package/src/lib/runtime/ai-live-batch.js +89 -0
  199. package/src/lib/runtime/ai-live-batch.js.map +1 -0
  200. package/src/lib/runtime/ai-runtime.js +46 -2
  201. package/src/lib/runtime/ai-runtime.js.map +1 -1
  202. package/src/lib/runtime/site-context.d.ts +58 -0
  203. package/src/lib/runtime/site-context.js +127 -0
  204. package/src/lib/runtime/site-context.js.map +1 -0
  205. package/src/lib/server/ai-admin-conversations.d.ts +22 -0
  206. package/src/lib/server/ai-admin-conversations.js +197 -0
  207. package/src/lib/server/ai-admin-conversations.js.map +1 -0
  208. package/src/lib/server/ai-media-image.d.ts +5 -5
  209. package/src/lib/server/ai-media-image.js +25 -11
  210. package/src/lib/server/ai-media-image.js.map +1 -1
  211. package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
  212. package/src/lib/server/ai-media-raster-prompt.js +53 -0
  213. package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
  214. package/src/lib/server/assist-chat.d.ts +11 -0
  215. package/src/lib/server/assist-chat.js +57 -18
  216. package/src/lib/server/assist-chat.js.map +1 -1
  217. package/src/lib/server/assist-edit-applied.js +16 -3
  218. package/src/lib/server/assist-edit-applied.js.map +1 -1
  219. package/src/lib/server/assist-view-context.d.ts +19 -1
  220. package/src/lib/server/assist-view-context.js +38 -1
  221. package/src/lib/server/assist-view-context.js.map +1 -1
  222. package/src/lib/server.js +10 -0
  223. package/src/lib/server.js.map +1 -1
  224. package/src/lib/subprocessors.js +17 -1
  225. package/src/lib/subprocessors.js.map +1 -1
  226. package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
  227. package/src/lib/tools/ai-blog-post-tool.js +175 -0
  228. package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
  229. package/src/lib/usage/assist-usage.d.ts +3 -0
  230. package/src/lib/usage/assist-usage.js +4 -2
  231. package/src/lib/usage/assist-usage.js.map +1 -1
  232. package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
  233. package/src/lib/usage/staff-org-ai-conversations.js +152 -0
  234. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
@@ -0,0 +1,322 @@
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_THEME } from "@aglyn/aglyn/app-utils/default-site";
18
+ import { diffOverride, overrideWriteValue } from "@aglyn/aglyn/app-utils/artifact-overrides";
19
+ import { themeArtifactContent } from "@aglyn/aglyn/app-utils/site-theme";
20
+ import { readThemeSelection, SITE_THEME_ENTRY_ID } from "@aglyn/aglyn/app-utils/theme-library";
21
+ import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cache";
22
+ import { listServerThemePresets } from "@aglyn/aglyn/plugin-manager/plugin-theme-presets";
23
+ import { runThemeLibraryAction } from "@aglyn/tenant-data-admin/server/theme-library-write";
24
+ import { AI_SITE_FONT_PAIRINGS, aiSiteKindFor, aiSiteKindOfInputs } from "../model/ai-site-kinds.js";
25
+ import { AI_SITE_LOOK_TOOL, AI_SITE_LOOK_TOOL_NAME, aiReadSiteLook, aiSiteSeed, aiSiteStyleFor, aiSiteTheme } from "../model/ai-site-look.js";
26
+ import { aiCatalogEntry, aiDefaultModelFor } from "../providers/catalog.js";
27
+ import { aiModelForStep } from "../providers/routing.js";
28
+ import { runValidatedGeneration } from "../runtime/ai-doctrine.js";
29
+ import { aiJobStepBudget } from "./ai-job-budget.js";
30
+ import { aiGenerationSpent, aiUnspentOutcome } from "./ai-job-generation.js";
31
+ import { AI_JOB_BRIEF_MAX_CHARS } from "./ai-job-text-step.js";
32
+ /**
33
+ * A site's look, designed first (AGL-3660): the scaffold's theme unit.
34
+ *
35
+ * One short answer from the fast tier — the base theme, two hues, a ground,
36
+ * a font pairing and the building blocks' styles, chosen for this business
37
+ * within its kind — then code builds the theme (`ai-site-look.ts`) and saves
38
+ * it the way Setup → Theme saves a pick and its edits: the base theme
39
+ * selected, the site's own look as the override over it, and the style
40
+ * tokens on the site for later edits to keep. The header, the footer and
41
+ * every page are built after it, so they render in it from their first draft.
42
+ *
43
+ * The look never fails a site. An answer that could not be read leaves the
44
+ * kind and the seed to choose; a site whose owner already changed its theme
45
+ * keeps it.
46
+ */ /** The answer is a dozen short values, about 150 tokens; the ceiling leaves room to spare. */ export const AI_SITE_LOOK_MAX_TOKENS = 600;
47
+ /** A look pass's time, on the fast tier the look is asked on. */ export const AI_SITE_LOOK_BUDGET = aiJobStepBudget({
48
+ tier: 'fast',
49
+ maxTokens: AI_SITE_LOOK_MAX_TOKENS
50
+ });
51
+ /**
52
+ * The generation kind a look is asked under: values code builds a theme
53
+ * from, composing nothing, so the doctrine's short fields block leads it.
54
+ */ export const AI_SITE_LOOK_KIND = 'site-look';
55
+ /** The base themes as the model reads them; `starter` is the one a new site is born with. */ const STARTER_WORDS = 'the starter theme a new site is born with: a soft neutral ground, rounder corners and calm buttons';
56
+ /** The look door's cached instructions. */ export const AI_SITE_LOOK_INSTRUCTIONS = [
57
+ {
58
+ text: [
59
+ "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.",
60
+ `Answer by calling ${AI_SITE_LOOK_TOOL_NAME} exactly once, at once, and write nothing else: no reasoning, no prose.`,
61
+ '- 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.',
62
+ '- 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.',
63
+ '- 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.',
64
+ '- A hex color written in the brief is the brand color: put it in brand. Otherwise brand is null.',
65
+ '- 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.'
66
+ ].join('\n'),
67
+ cacheBreakpoint: true
68
+ }
69
+ ];
70
+ /** A list in a seed's order (Fisher–Yates over a small LCG). */ function shuffled(list, seed) {
71
+ const out = [
72
+ ...list
73
+ ];
74
+ let state = seed >>> 0 || 1;
75
+ for(let index = out.length - 1; index > 0; index -= 1){
76
+ state = Math.imul(state, 1664525) + 1013904223 >>> 0;
77
+ const other = state % (index + 1);
78
+ [out[index], out[other]] = [
79
+ out[other],
80
+ out[index]
81
+ ];
82
+ }
83
+ return out;
84
+ }
85
+ /** The look's user turn: the site, its kind and what suits it, and the seed. */ export function aiSiteLookPrompt(input) {
86
+ const { job, kind } = input;
87
+ const text = (key)=>{
88
+ var _job_inputs;
89
+ return typeof ((_job_inputs = job.inputs) == null ? void 0 : _job_inputs[key]) === 'string' ? job.inputs[key].trim() : '';
90
+ };
91
+ const pairings = AI_SITE_FONT_PAIRINGS.filter((pairing)=>kind.look.fonts.includes(pairing.id));
92
+ // The lists in the job's own order: a model reads the first of a list as
93
+ // the answer, and every site of a kind would get the same base and type.
94
+ const seed = aiSiteSeed(job.$id);
95
+ return [
96
+ `Kind of site: ${kind.label}. ${kind.design}`,
97
+ text('businessName') ? `Business name: ${text('businessName')}` : '',
98
+ text('businessType') ? `Business: ${text('businessType')}` : '',
99
+ text('audience') ? `For: ${text('audience')}` : '',
100
+ text('brand') ? `Brand: ${text('brand')}` : '',
101
+ `Brief: ${job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`,
102
+ `Base themes that suit it: ${shuffled(input.bases, seed).map((entry)=>`${entry.base} (${entry.words})`).join('; ')}.`,
103
+ `Font pairings that suit it: ${shuffled(pairings, seed + 1).map((pairing)=>`${pairing.id} (${pairing.feel})`).join('; ')}.`,
104
+ `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}.`,
105
+ `Design seed: ${job.$id.slice(-6)}.`
106
+ ].filter(Boolean).join('\n');
107
+ }
108
+ /** The answer, read leniently; a call with nothing usable is still an answer, and the seed fills it. */ export const aiSiteLookCheck = (answer)=>({
109
+ value: aiReadSiteLook(answer),
110
+ violations: []
111
+ });
112
+ /** The model a look is asked on: the fast tier of the job's provider, unless the creator picked a model. */ export function aiSiteLookModel(job, resolved) {
113
+ var _ref;
114
+ const fallback = resolved != null ? resolved : aiModelForStep('job.theme');
115
+ const picked = typeof job.model === 'string' ? job.model.trim() : '';
116
+ if (picked && picked !== 'auto') return fallback;
117
+ const entry = aiCatalogEntry(fallback);
118
+ return (_ref = entry ? aiDefaultModelFor(entry.provider, 'fast') : undefined) != null ? _ref : fallback;
119
+ }
120
+ /** The preset id a base names. */ export const aiSiteBasePresetId = (base)=>`theme-presets.${base}`;
121
+ /** The bases a kind suits that this deployment has, best first, with what each looks like. */ export function aiSiteLookBases(kind) {
122
+ const presets = listServerThemePresets();
123
+ return kind.look.bases.flatMap((base)=>{
124
+ var _preset_description;
125
+ if (base === 'starter') return [
126
+ {
127
+ base,
128
+ words: STARTER_WORDS
129
+ }
130
+ ];
131
+ const preset = presets.find((entry)=>entry.id === aiSiteBasePresetId(base));
132
+ return preset ? [
133
+ {
134
+ base,
135
+ words: (_preset_description = preset.description) != null ? _preset_description : preset.name
136
+ }
137
+ ] : [];
138
+ });
139
+ }
140
+ /** Plain JSON, deep-sorted, for comparing two themes as stored. */ function stable(value) {
141
+ return JSON.stringify(value, (_key, inner)=>inner && typeof inner === 'object' && !Array.isArray(inner) ? Object.fromEntries(Object.entries(inner).sort(([a], [b])=>a.localeCompare(b))) : inner);
142
+ }
143
+ /**
144
+ * Whether a site still has the theme it was born with (AGL-3497), or none:
145
+ * the only theme a look may replace. A site whose owner picked a theme, edited
146
+ * one, or installed one keeps it.
147
+ */ export function aiSiteThemeUntouched(host) {
148
+ if (host.themeOverride) return false;
149
+ const selection = readThemeSelection(host);
150
+ if (selection.kind === 'default') return true;
151
+ if (selection.kind !== 'custom' || selection.id !== SITE_THEME_ENTRY_ID) return false;
152
+ return stable(themeArtifactContent(host.theme)) === stable(themeArtifactContent(DEFAULT_SITE_THEME));
153
+ }
154
+ /**
155
+ * Saves a look on its site in one transaction: the base theme picked through
156
+ * the theme library (so the site's starter theme is filed in its library, as
157
+ * a person's switch files it), the look as the override over the base, and
158
+ * the style tokens. Again for the same job is a no-op.
159
+ */ export async function aiSaveSiteTheme(firestore, input) {
160
+ var _listServerThemePresets_find, _ref;
161
+ const hostRef = firestore.collection('hosts').doc(input.hostId);
162
+ const preset = input.style.base === 'starter' ? null : (_listServerThemePresets_find = listServerThemePresets().find((entry)=>entry.id === aiSiteBasePresetId(input.style.base))) != null ? _listServerThemePresets_find : null;
163
+ const base = preset ? themeArtifactContent(preset.theme) : DEFAULT_SITE_THEME;
164
+ const baseName = (_ref = preset == null ? void 0 : preset.name) != null ? _ref : 'Site theme';
165
+ const edited = aiSiteTheme(_extends({}, input.style, {
166
+ base: preset ? input.style.base : 'starter'
167
+ }), base);
168
+ const override = overrideWriteValue(diffOverride(base, edited), null);
169
+ const siteStyle = _extends({}, input.style, {
170
+ base: preset ? input.style.base : 'starter',
171
+ jobId: input.jobId
172
+ });
173
+ const write = await firestore.runTransaction(async (tx)=>{
174
+ const snapshot = await tx.get(hostRef);
175
+ if (!snapshot.exists) return 'missing';
176
+ const host = snapshot.data();
177
+ const stored = host['siteStyle'];
178
+ if ((stored == null ? void 0 : stored.jobId) === input.jobId) return 'applied';
179
+ if (!aiSiteThemeUntouched(host)) return 'kept';
180
+ const extra = {
181
+ themeOverride: override,
182
+ siteStyle
183
+ };
184
+ if (preset) {
185
+ const plan = await runThemeLibraryAction(tx, hostRef, {
186
+ action: 'select',
187
+ target: {
188
+ kind: 'preset',
189
+ id: preset.id,
190
+ name: preset.name,
191
+ theme: base
192
+ }
193
+ }, extra);
194
+ if (plan.ok === false) throw new Error(`the site's base theme could not be picked: ${plan.error}`);
195
+ } else {
196
+ tx.update(hostRef, _extends({
197
+ theme: DEFAULT_SITE_THEME
198
+ }, extra));
199
+ }
200
+ return 'applied';
201
+ });
202
+ if (write === 'applied') {
203
+ // The theme is live the moment it is written; cached pages go now.
204
+ await dropPluginSiteCache({
205
+ hostIds: [
206
+ input.hostId
207
+ ],
208
+ reason: 'ai site look'
209
+ });
210
+ }
211
+ return {
212
+ write,
213
+ baseName
214
+ };
215
+ }
216
+ /** The sentence a look's row and output carry. */ function lookLabel(style, baseName) {
217
+ const pairing = AI_SITE_FONT_PAIRINGS.find((entry)=>entry.id === style.fonts);
218
+ return `Your look: ${baseName} base, ${pairing ? `${pairing.heading.family} type` : 'its own type'}, ${style.cards} cards`;
219
+ }
220
+ /** The most of a workspace's sites a look is drawn apart from. */ export const AI_SITE_LOOK_SIBLINGS = 50;
221
+ /**
222
+ * The looks of a workspace's other sites (AGL-3660), so a new one is drawn
223
+ * apart from each: none shares their base, hue family, heading font and
224
+ * buttons. A read that fails draws apart from nothing.
225
+ */ export async function aiWorkspaceSiteStyles(firestore, input) {
226
+ try {
227
+ const snapshot = await firestore.collection('hosts').where('orgId', '==', input.orgId).limit(AI_SITE_LOOK_SIBLINGS).get();
228
+ return snapshot.docs.flatMap((doc)=>{
229
+ const style = doc.get('siteStyle');
230
+ return doc.id !== input.hostId && style && typeof style.base === 'string' ? [
231
+ style
232
+ ] : [];
233
+ });
234
+ } catch (error) {
235
+ console.warn('ai site look: the workspace sites could not be read; drawing apart from none', {
236
+ orgId: input.orgId,
237
+ error
238
+ });
239
+ return [];
240
+ }
241
+ }
242
+ /**
243
+ * The theme unit's pass: ask for the look, build it, save it. `job` is the
244
+ * unit's derived job, carrying the scaffold's inputs and its origin job id,
245
+ * which is what the seed is drawn from — so a resumed pass draws the same.
246
+ */ export async function aiRunSiteLook(context, job, deps = {}) {
247
+ var _deps_save, _deps_generate, _aiSiteKindOfInputs, _deps_others;
248
+ var _job_inputs, _job_inputs1;
249
+ const save = (_deps_save = deps.save) != null ? _deps_save : aiSaveSiteTheme;
250
+ const generate = (_deps_generate = deps.generate) != null ? _deps_generate : runValidatedGeneration;
251
+ const model = aiSiteLookModel(job, context.modelFor == null ? void 0 : context.modelFor.call(context, 'job.theme'));
252
+ if (!job.hostId) return _extends({}, aiUnspentOutcome(model), {
253
+ failure: 'This site job names no site.'
254
+ });
255
+ const kind = (_aiSiteKindOfInputs = aiSiteKindOfInputs(job.inputs)) != null ? _aiSiteKindOfInputs : aiSiteKindFor(job.brief);
256
+ const origin = typeof ((_job_inputs = job.inputs) == null ? void 0 : _job_inputs['originJobId']) === 'string' ? job.inputs['originJobId'] : job.$id;
257
+ const result = await generate(AI_SITE_LOOK_KIND, _extends({
258
+ step: 'job.theme',
259
+ model,
260
+ instructions: AI_SITE_LOOK_INSTRUCTIONS,
261
+ messages: [
262
+ {
263
+ role: 'user',
264
+ content: aiSiteLookPrompt({
265
+ job: _extends({}, job, {
266
+ $id: origin
267
+ }),
268
+ kind,
269
+ bases: aiSiteLookBases(kind)
270
+ })
271
+ }
272
+ ],
273
+ tool: AI_SITE_LOOK_TOOL,
274
+ maxTokens: AI_SITE_LOOK_BUDGET.maxTokens(model),
275
+ thinking: 'off',
276
+ check: aiSiteLookCheck
277
+ }, context.signal ? {
278
+ signal: context.signal
279
+ } : {}));
280
+ const spent = aiGenerationSpent(result);
281
+ if (result.status === 'refused') return _extends({}, spent, {
282
+ refused: true
283
+ });
284
+ // An answer that could not be used leaves the kind and the seed to choose.
285
+ const answer = result.status === 'ok' ? result.value : {};
286
+ const brand = typeof ((_job_inputs1 = job.inputs) == null ? void 0 : _job_inputs1['brand']) === 'string' ? job.inputs['brand'] : null;
287
+ const others = (_deps_others = deps.others) != null ? _deps_others : aiWorkspaceSiteStyles;
288
+ const avoid = await others(context.firestore, {
289
+ orgId: job.orgId,
290
+ hostId: job.hostId
291
+ });
292
+ const style = aiSiteStyleFor({
293
+ kind,
294
+ answer,
295
+ seed: aiSiteSeed(origin),
296
+ brand,
297
+ avoid
298
+ });
299
+ const saved = await save(context.firestore, {
300
+ hostId: job.hostId,
301
+ jobId: origin,
302
+ style
303
+ });
304
+ if (saved.write === 'missing') throw new Error(`site ${job.hostId} vanished while its look was chosen`);
305
+ const output = {
306
+ resource: 'theme',
307
+ id: 'look',
308
+ hostId: job.hostId,
309
+ label: saved.write === 'kept' ? 'Kept the theme you chose for this site' : lookLabel(style, saved.baseName),
310
+ proposal: JSON.parse(JSON.stringify({
311
+ style,
312
+ write: saved.write
313
+ }))
314
+ };
315
+ return _extends({}, spent, {
316
+ outputs: [
317
+ output
318
+ ]
319
+ });
320
+ }
321
+
322
+ //# sourceMappingURL=ai-job-site-look.js.map
@@ -0,0 +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 { 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 and the building blocks' styles, 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 ].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 `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","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","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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"}
@@ -21,6 +21,8 @@ import { type AiSiteJobInputs } from '../model/ai-site-job';
21
21
  import { type AiJobAdmission } from './ai-job-admission';
22
22
  import { readAiDraftNodes } from './ai-job-drafts';
23
23
  import { aiPublishGuidedSite } from './ai-site-publish';
24
+ import { aiPublishSitePosts, aiSiteContentRefusal } from './ai-job-site-content';
25
+ import { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache';
24
26
  import { type AiJobStepContext, type AiJobStepOutcome, type AiJobStepRunner } from './ai-job-text-step';
25
27
  import { aiJobStepRunnerFor } from './ai-jobs';
26
28
  /**
@@ -76,8 +78,13 @@ import { aiJobStepRunnerFor } from './ai-jobs';
76
78
  * `new:<name>` references resolved to what was built (`aiSiteBuiltRefs`,
77
79
  * `aiSiteUnitJob`). There is one loop, and it is the machine's.
78
80
  */
79
- /** What a scaffold's units are, in the order it builds them; a component is a page job's. */
80
- export type AiSiteUnitKind = 'theme' | 'layout' | 'form' | 'component' | 'page' | 'email';
81
+ /**
82
+ * What a scaffold's units are, in the order it builds them; a component is a
83
+ * page job's. A blog's first posts and a store's first products (AGL-3676)
84
+ * are built after the layout and the form and before the pages, which are
85
+ * told what they are.
86
+ */
87
+ export type AiSiteUnitKind = 'theme' | 'layout' | 'form' | 'component' | 'posts' | 'products' | 'page' | 'email';
81
88
  export interface AiSiteUnit {
82
89
  kind: AiSiteUnitKind;
83
90
  /** The job kind whose registered runner builds it. */
@@ -98,6 +105,8 @@ export interface AiSiteUnit {
98
105
  * are addressed by; `null` for a creation no unit builds.
99
106
  */
100
107
  export declare function aiCreationUnit(creation: AiBuildPlanCreate, slot: string): AiSiteUnit | null;
108
+ /** What the look's unit is called on the ledger. */
109
+ export declare const AI_SITE_LOOK_LABEL = "Your look";
101
110
  export declare const AI_SITE_NO_PLAN_COPY = "This site job has no confirmed plan to build.";
102
111
  /** What a scaffold answers where the step that builds a page is not loaded. */
103
112
  export declare const AI_SITE_NO_PAGE_STEP_COPY = "Building a whole site is not available yet.";
@@ -105,9 +114,10 @@ export declare const AI_SITE_NO_PAGE_STEP_COPY = "Building a whole site is not a
105
114
  export declare const AI_SITE_UNIT_EMPTY_COPY = "Part of this site could not be built. Try the site brief again.";
106
115
  /**
107
116
  * The most passes a scaffold's step may take: the largest plan it admits —
108
- * eight pages of eight sections, its three creations and a welcome email —
109
- * with nothing to spare, so a runner that never finishes is still bounded
110
- * while a real site is not.
117
+ * eight pages of eight sections, its three creations, a blog's first posts
118
+ * (one a pass, the largest part) and a welcome email — with nothing to
119
+ * spare, so a runner that never finishes is still bounded while a real site
120
+ * is not.
111
121
  */
112
122
  export declare const AI_SITE_MAX_PASSES: number;
113
123
  /**
@@ -119,6 +129,7 @@ export declare const AI_SITE_MAX_PASSES: number;
119
129
  */
120
130
  export declare function aiSiteJobUnits(plan: Pick<AiJobPlan, 'create' | 'screens'>, options?: {
121
131
  welcomeEmail?: boolean;
132
+ content?: 'posts' | 'products' | null;
122
133
  }): AiSiteUnit[];
123
134
  /**
124
135
  * The units a scaffold still owes after the outputs recorded so far. Each
@@ -250,6 +261,16 @@ export interface AiJobSiteStepDeps {
250
261
  readNodes?: typeof readAiDraftNodes;
251
262
  /** The guided start's publish; specs hand in a fake. */
252
263
  publish?: typeof aiPublishGuidedSite;
264
+ /** The look's pass (AGL-3660); specs hand in a fake. */
265
+ look?: (context: AiJobStepContext, job: AiJob) => Promise<AiJobStepOutcome>;
266
+ /** The first posts' and first products' passes (AGL-3676); specs hand in fakes. */
267
+ posts?: AiJobStepRunner;
268
+ products?: AiJobStepRunner;
269
+ /** Whether a part may be built for this member, before its first pass. */
270
+ contentRefusal?: typeof aiSiteContentRefusal;
271
+ /** The posts' publish once the pages are live, and the cache drop after it. */
272
+ publishPosts?: typeof aiPublishSitePosts;
273
+ dropCache?: typeof dropPluginSiteCache;
253
274
  }
254
275
  /** A page the scaffold reported built that holds none of its plan's sections. */
255
276
  export declare const AI_SITE_PAGE_NOT_WRITTEN_COPY = "A page of this site was not written from its plan. Try the site brief again.";