@aglyn/plugins-ai 1.0.0-beta.291 → 1.0.0-beta.293

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 (149) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-site-import-job.component.js +5 -4
  3. package/src/lib/components/ai-site-import-job.component.js.map +1 -1
  4. package/src/lib/components/ai-site-import-start.component.js +3 -3
  5. package/src/lib/components/ai-site-import-start.component.js.map +1 -1
  6. package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
  7. package/src/lib/components/ai-site-start-card.component.js +129 -29
  8. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  9. package/src/lib/components/ai-site-start-steps.component.d.ts +15 -3
  10. package/src/lib/components/ai-site-start-steps.component.js +42 -25
  11. package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
  12. package/src/lib/components/ai-site-style-previews.d.ts +17 -0
  13. package/src/lib/components/ai-site-style-previews.js +26 -95
  14. package/src/lib/components/ai-site-style-previews.js.map +1 -1
  15. package/src/lib/declarations.server.js +22 -0
  16. package/src/lib/declarations.server.js.map +1 -1
  17. package/src/lib/jobs/ai-job-import-step.d.ts +21 -2
  18. package/src/lib/jobs/ai-job-import-step.js +179 -49
  19. package/src/lib/jobs/ai-job-import-step.js.map +1 -1
  20. package/src/lib/jobs/ai-job-layout-language.d.ts +27 -3
  21. package/src/lib/jobs/ai-job-layout-language.js +156 -113
  22. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  23. package/src/lib/jobs/ai-job-layout-step.js +56 -17
  24. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  25. package/src/lib/jobs/ai-job-page-language.d.ts +25 -15
  26. package/src/lib/jobs/ai-job-page-language.js +120 -66
  27. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  28. package/src/lib/jobs/ai-job-page-step.js +27 -7
  29. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  30. package/src/lib/jobs/ai-job-site-datasets.d.ts +12 -4
  31. package/src/lib/jobs/ai-job-site-datasets.js +36 -16
  32. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  33. package/src/lib/jobs/ai-jobs.js +6 -2
  34. package/src/lib/jobs/ai-jobs.js.map +1 -1
  35. package/src/lib/jobs/ai-layout-stock-photos.d.ts +50 -1
  36. package/src/lib/jobs/ai-layout-stock-photos.js +586 -35
  37. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  38. package/src/lib/jobs/ai-site-import-compile.d.ts +147 -0
  39. package/src/lib/jobs/ai-site-import-compile.js +1143 -0
  40. package/src/lib/jobs/ai-site-import-compile.js.map +1 -0
  41. package/src/lib/jobs/ai-site-import-document.d.ts +9 -36
  42. package/src/lib/jobs/ai-site-import-document.js +23 -489
  43. package/src/lib/jobs/ai-site-import-document.js.map +1 -1
  44. package/src/lib/jobs/ai-site-import-look.d.ts +54 -7
  45. package/src/lib/jobs/ai-site-import-look.js +163 -52
  46. package/src/lib/jobs/ai-site-import-look.js.map +1 -1
  47. package/src/lib/jobs/ai-site-import-publish.d.ts +7 -1
  48. package/src/lib/jobs/ai-site-import-publish.js +19 -5
  49. package/src/lib/jobs/ai-site-import-publish.js.map +1 -1
  50. package/src/lib/jobs/ai-site-late-photos.js +4 -2
  51. package/src/lib/jobs/ai-site-late-photos.js.map +1 -1
  52. package/src/lib/jobs/ai-site-publish.d.ts +10 -1
  53. package/src/lib/jobs/ai-site-publish.js +8 -3
  54. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  55. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-1029-recordings.d.ts +23 -0
  56. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-1029-recordings.js +677 -0
  57. package/src/lib/jobs/fixtures/ai-stock-photo-agl4092-1029-recordings.js.map +1 -0
  58. package/src/lib/layout-language/ai-layout-compiler.d.ts +26 -0
  59. package/src/lib/layout-language/ai-layout-compiler.js +677 -33
  60. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  61. package/src/lib/layout-language/ai-layout-frame-hollows.js +5 -1
  62. package/src/lib/layout-language/ai-layout-frame-hollows.js.map +1 -1
  63. package/src/lib/layout-language/ai-layout-frame-source.d.ts +196 -0
  64. package/src/lib/layout-language/ai-layout-frame-source.js +726 -0
  65. package/src/lib/layout-language/ai-layout-frame-source.js.map +1 -0
  66. package/src/lib/layout-language/ai-layout-frame.d.ts +22 -0
  67. package/src/lib/layout-language/ai-layout-frame.js +121 -29
  68. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  69. package/src/lib/layout-language/ai-layout-language.d.ts +103 -37
  70. package/src/lib/layout-language/ai-layout-language.js +85 -120
  71. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  72. package/src/lib/model/ai-credit-estimate.d.ts +11 -7
  73. package/src/lib/model/ai-credit-estimate.js +11 -7
  74. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  75. package/src/lib/model/ai-site-import.d.ts +307 -13
  76. package/src/lib/model/ai-site-import.js +227 -19
  77. package/src/lib/model/ai-site-import.js.map +1 -1
  78. package/src/lib/model/ai-site-look.d.ts +65 -1
  79. package/src/lib/model/ai-site-look.js +141 -23
  80. package/src/lib/model/ai-site-look.js.map +1 -1
  81. package/src/lib/model/ai-site-photos.d.ts +2 -0
  82. package/src/lib/model/ai-site-photos.js.map +1 -1
  83. package/src/lib/model/ai-site-start-steps.d.ts +4 -3
  84. package/src/lib/model/ai-site-start-steps.js +11 -3
  85. package/src/lib/model/ai-site-start-steps.js.map +1 -1
  86. package/src/lib/model/ai-site-start.d.ts +23 -2
  87. package/src/lib/model/ai-site-start.js +25 -3
  88. package/src/lib/model/ai-site-start.js.map +1 -1
  89. package/src/lib/providers/routing.d.ts +11 -0
  90. package/src/lib/providers/routing.js +23 -0
  91. package/src/lib/providers/routing.js.map +1 -1
  92. package/src/lib/runtime/ai-doctrine-validators.d.ts +4 -2
  93. package/src/lib/runtime/ai-doctrine-validators.js +7 -2
  94. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  95. package/src/lib/runtime/ai-icon-library.d.ts +4 -0
  96. package/src/lib/runtime/ai-icon-library.js +4 -0
  97. package/src/lib/runtime/ai-icon-library.js.map +1 -1
  98. package/src/lib/runtime/ai-node-tree.js +5 -1
  99. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  100. package/src/lib/runtime/ai-palette.d.ts +2 -0
  101. package/src/lib/runtime/ai-palette.generated.js +19 -3
  102. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  103. package/src/lib/runtime/ai-palette.js +2 -1
  104. package/src/lib/runtime/ai-palette.js.map +1 -1
  105. package/src/lib/site-import/fixtures/site-layout-recordings.d.ts +50 -0
  106. package/src/lib/site-import/fixtures/site-layout-recordings.js +86 -0
  107. package/src/lib/site-import/fixtures/site-layout-recordings.js.map +1 -0
  108. package/src/lib/site-import/site-import-crawl.d.ts +18 -1
  109. package/src/lib/site-import/site-import-crawl.js +70 -10
  110. package/src/lib/site-import/site-import-crawl.js.map +1 -1
  111. package/src/lib/site-import/site-import-css.d.ts +110 -0
  112. package/src/lib/site-import/site-import-css.js +826 -0
  113. package/src/lib/site-import/site-import-css.js.map +1 -0
  114. package/src/lib/site-import/site-import-html.d.ts +116 -1
  115. package/src/lib/site-import/site-import-html.js +538 -46
  116. package/src/lib/site-import/site-import-html.js.map +1 -1
  117. package/src/lib/site-import/site-import-layout.d.ts +79 -0
  118. package/src/lib/site-import/site-import-layout.js +1608 -0
  119. package/src/lib/site-import/site-import-layout.js.map +1 -0
  120. package/src/lib/site-import/site-import-nav.d.ts +40 -0
  121. package/src/lib/site-import/site-import-nav.js +437 -0
  122. package/src/lib/site-import/site-import-nav.js.map +1 -0
  123. package/src/lib/site-import/site-import-page.d.ts +8 -1
  124. package/src/lib/site-import/site-import-page.js +24 -2
  125. package/src/lib/site-import/site-import-page.js.map +1 -1
  126. package/src/lib/assets/site-styles/beauty.webp +0 -0
  127. package/src/lib/assets/site-styles/blog.webp +0 -0
  128. package/src/lib/assets/site-styles/business.webp +0 -0
  129. package/src/lib/assets/site-styles/coming-soon.webp +0 -0
  130. package/src/lib/assets/site-styles/education.webp +0 -0
  131. package/src/lib/assets/site-styles/events.webp +0 -0
  132. package/src/lib/assets/site-styles/fitness.webp +0 -0
  133. package/src/lib/assets/site-styles/landing.webp +0 -0
  134. package/src/lib/assets/site-styles/music.webp +0 -0
  135. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  136. package/src/lib/assets/site-styles/personal.webp +0 -0
  137. package/src/lib/assets/site-styles/photography.webp +0 -0
  138. package/src/lib/assets/site-styles/portfolio.webp +0 -0
  139. package/src/lib/assets/site-styles/professional.webp +0 -0
  140. package/src/lib/assets/site-styles/realestate.webp +0 -0
  141. package/src/lib/assets/site-styles/restaurant.webp +0 -0
  142. package/src/lib/assets/site-styles/store.webp +0 -0
  143. package/src/lib/assets/site-styles/studio.webp +0 -0
  144. package/src/lib/assets/site-styles/trades.webp +0 -0
  145. package/src/lib/assets/site-styles/wellness.webp +0 -0
  146. package/src/lib/assets/site-styles/yoga.webp +0 -0
  147. package/src/lib/site-import/site-import-review.d.ts +0 -40
  148. package/src/lib/site-import/site-import-review.js +0 -149
  149. package/src/lib/site-import/site-import-review.js.map +0 -1
@@ -18,15 +18,21 @@
18
18
  *
19
19
  * The first wave's look pass, before the header and footer, with no model:
20
20
  *
21
- * - **The theme.** The owner's own home page is read once more as AglynBot
22
- * (with the edge headers a site this platform serves gets), and the
23
- * signals a guided start reads off the sites a person likes (AGL-3975:
24
- * colors, ground, type mood, corners, buttons, cards, header, density,
25
- * motion) pin the look: its strongest colorful color is the brand, its
26
- * second the accent. The look is drawn and saved exactly as a guided
27
- * start's (`aiSiteStyleFor`, `aiSaveSiteTheme`): a built-in base plus the
28
- * brand colors and a pairing of the source's type mood, never its font.
29
- * A site whose owner already chose a theme keeps it.
21
+ * - **The theme.** The owner's own home page is read once more as AglynBot,
22
+ * with the edge headers a site this platform serves gets whether or not a
23
+ * caller hands them in (AGL-4108: production handed none, our own edge
24
+ * challenged the read, and the 10/29 site kept the starter's indigo), and
25
+ * the signals a guided start reads off the sites a person likes
26
+ * (AGL-3975: colors, ground, type mood, corners, buttons, cards, header,
27
+ * density, motion) pin the look. What the reader found of the source's
28
+ * look (AGL-4106) pins it closer (AGL-4108): its buttons' fill is the
29
+ * brand and its links' color the accent, its page's ground is the site's,
30
+ * and its own font family is the site's wherever the theme font catalog
31
+ * has it, else a pairing of its type mood. The look is drawn and saved
32
+ * exactly as a guided start's (`aiSiteStyleFor`, `aiSaveSiteTheme`): a
33
+ * built-in base plus the brand colors and fonts, every color held to the
34
+ * theme's reading checks. A site whose owner already chose a theme keeps
35
+ * it.
30
36
  * - **The logo.** The source header's logo, as the source draws it, into the
31
37
  * site's media library once: an SVG is turned into a PNG 192 pixels tall
32
38
  * (the library takes pictures, not drawings), a picture is kept as it is.
@@ -39,11 +45,13 @@
39
45
  * site's theme, and a logo that cannot be brought in leaves the header its
40
46
  * name.
41
47
  *=========================================*/ import { _ as _extends } from "@swc/helpers/_/_extends";
48
+ import { themeFontFacts } from "@aglyn/aglyn/plugin-manager/plugin-theme-font-catalog";
42
49
  import { aiSiteListingSeeded } from "../model/ai-site-entity.js";
43
50
  import { aiSiteKindFor } from "../model/ai-site-kinds.js";
44
- import { aiSiteSeed, aiSiteStyleFor } from "../model/ai-site-look.js";
51
+ import { aiHexOklch, aiSiteSeed, aiSiteStyleFor } from "../model/ai-site-look.js";
45
52
  import { aiSiteIsStarterSrc } from "../model/ai-site-photos.js";
46
- import { AGLYN_BOT_USER_AGENT } from "../site-import/site-import-crawl.js";
53
+ import { AGLYN_BOT_USER_AGENT, siteImportOrigins } from "../site-import/site-import-crawl.js";
54
+ import { NO_SITE_IMPORT_HEADERS, siteImportEdgeHeadersFor } from "../site-import/site-import-edge.js";
47
55
  import { bringInSiteImportPhotos, siteImportImageKey, siteImportLogoUrl } from "../site-import/site-import-images.js";
48
56
  import { aiSaveSiteTheme, aiWorkspaceSiteStyles } from "./ai-job-site-look.js";
49
57
  import { aiSiteInspirationLookPins } from "./ai-site-inspiration.js";
@@ -52,36 +60,30 @@ import { AI_THEME_BRAND_COLORS_PER_SOURCE, AI_THEME_REFERENCE_MAX_BYTES, AI_THEM
52
60
  /** The height an SVG logo is drawn at as a picture: crisp at twice the header's 40. */ export const AI_SITE_IMPORT_LOGO_HEIGHT = 192;
53
61
  /** The largest logo file read. */ export const AI_SITE_IMPORT_LOGO_MAX_BYTES = 1024 * 1024;
54
62
  /** One read's own clock in the look pass. */ const LOOK_READ_MS = 8000;
55
- const sameHost = (a, b)=>{
56
- try {
57
- return new URL(a).host === new URL(b).host;
58
- } catch (unused) {
59
- return false;
60
- }
61
- };
62
63
  /**
63
- * The home page and its first stylesheets, read as AglynBot (AGL-4084), the
64
- * edge headers going with the page and with a stylesheet only on the page's
65
- * own host. `null` when the page cannot be read.
64
+ * The home page and its first stylesheets, read as AglynBot (AGL-4084).
65
+ * `headersFor` is asked for every request and every redirect hop, as the
66
+ * crawl asks it (`aiSiteImportLookHeaders`: the edge header only to the
67
+ * site's own origins). `null` when the page cannot be read.
66
68
  */ export async function readSiteImportLookPage(url, input) {
67
- var _input_fetch;
69
+ var _input_fetch, _input_headersFor;
68
70
  const fetchText = (_input_fetch = input.fetch) != null ? _input_fetch : fetchPublicTextDetailed;
69
- const headers = input.headers && Object.keys(input.headers).length ? input.headers : null;
70
- const read = (address, maxBytes, accept, own)=>fetchText(address, _extends({
71
+ const headersFor = (_input_headersFor = input.headersFor) != null ? _input_headersFor : null;
72
+ const read = (address, maxBytes, accept)=>fetchText(address, _extends({
71
73
  maxBytes,
72
74
  accept,
73
75
  userAgent: AGLYN_BOT_USER_AGENT,
74
76
  timeoutMs: LOOK_READ_MS
75
77
  }, input.signal ? {
76
78
  signal: input.signal
77
- } : {}, headers && own ? {
78
- headers
79
+ } : {}, headersFor ? {
80
+ headersFor
79
81
  } : {})).catch(()=>null);
80
- const page = await read(url, AI_THEME_REFERENCE_MAX_BYTES, /text\/html|application\/xhtml\+xml/i, true);
82
+ const page = await read(url, AI_THEME_REFERENCE_MAX_BYTES, /text\/html|application\/xhtml\+xml/i);
81
83
  if (!(page == null ? void 0 : page.ok)) return null;
82
84
  const stylesheets = [];
83
85
  for (const sheet of stylesheetUrlsOf(page.text, page.url).slice(0, AI_THEME_REFERENCE_STYLESHEETS)){
84
- const css = await read(sheet, AI_THEME_STYLESHEET_MAX_BYTES, /text\/css/i, sameHost(sheet, page.url));
86
+ const css = await read(sheet, AI_THEME_STYLESHEET_MAX_BYTES, /text\/css/i);
85
87
  if (css == null ? void 0 : css.ok) stylesheets.push(css.text);
86
88
  }
87
89
  return {
@@ -132,16 +134,40 @@ const hexOf = (value)=>{
132
134
  }
133
135
  return rankBrandColors(weights, AI_THEME_BRAND_COLORS_PER_SOURCE);
134
136
  }
137
+ /** How colorful a color must be to carry a brand: the inspiration reader's own floor. */ const COLORFUL_CHROMA = 0.04;
138
+ const colorful = (hex)=>!!hex && /^#[0-9a-f]{6}$/i.test(hex) && aiHexOklch(hex).C >= COLORFUL_CHROMA;
139
+ /**
140
+ * The brand and accent a source's look facts give (AGL-4108): the fill of
141
+ * its filled buttons as the brand, the color of its links as the accent
142
+ * where it is a color of its own, and the reverse where its buttons are not
143
+ * colorful. The theme draws the brand at the nearest shade of it that reads
144
+ * (`aiSiteColors`): aglyn.com's light blue buttons, under white words at
145
+ * 2.4:1, come out at the deeper blue of its own links.
146
+ */ export function aiSiteImportBrandOf(facts) {
147
+ const fill = colorful(facts == null ? void 0 : facts.buttonFill) ? (facts == null ? void 0 : facts.buttonFill).toLowerCase() : null;
148
+ const link = colorful(facts == null ? void 0 : facts.link) ? (facts == null ? void 0 : facts.link).toLowerCase() : null;
149
+ const brand = fill != null ? fill : link;
150
+ if (!brand) return null;
151
+ return {
152
+ brand,
153
+ accent: link && link !== brand ? link : null
154
+ };
155
+ }
135
156
  /**
136
157
  * The look a source's signals pin (AGL-4084), drawn the way a guided start's
137
158
  * is: the kind of site its own description says, the import's seed, its
138
- * strongest color as the brand. `null` where the source showed no color to
159
+ * strongest color as the brand. What the reader found of its look
160
+ * (`facts`, AGL-4108) pins it closer: its buttons' fill as the brand and its
161
+ * links' color as the accent, its page's ground, and its own fonts where the
162
+ * catalog has them (`fonts`). `null` where the source showed no color to
139
163
  * carry a brand, so the site keeps the theme it has.
140
164
  */ export function aiSiteImportStyle(site, input) {
141
- var _input_avoid;
142
- if (!site) return null;
165
+ var _input_facts, _input_avoid;
166
+ const facts = (_input_facts = input.facts) != null ? _input_facts : null;
167
+ const own = aiSiteImportBrandOf(facts);
168
+ if (!site && !own) return null;
143
169
  const kind = aiSiteKindFor(input.description);
144
- const pins = aiSiteInspirationLookPins({
170
+ const pins = site ? aiSiteInspirationLookPins({
145
171
  v: 1,
146
172
  sites: [
147
173
  site
@@ -150,9 +176,21 @@ const hexOf = (value)=>{
150
176
  }, {
151
177
  kind,
152
178
  seed: input.seed
153
- });
179
+ }) : {};
180
+ if (own) {
181
+ pins.brand = own.brand;
182
+ pins.hue = Math.round(aiHexOklch(own.brand).h) % 360;
183
+ if (own.accent) pins.accent = Math.round(aiHexOklch(own.accent).h) % 360;
184
+ }
185
+ // Its buttons' corners round the theme's shape where its stylesheets did not say (AGL-4108).
186
+ const radius = facts == null ? void 0 : facts.buttonRadius;
187
+ if (typeof radius === 'number' && Number.isFinite(radius) && radius >= 0) {
188
+ var _pins, _buttons, _pins1, _corners;
189
+ (_buttons = (_pins = pins).buttons) != null ? _buttons : _pins.buttons = radius >= 24 ? 'pill' : radius <= 2 ? 'square' : 'rounded';
190
+ (_corners = (_pins1 = pins).corners) != null ? _corners : _pins1.corners = radius <= 2 ? 'sharp' : radius <= 10 ? 'soft' : radius <= 20 ? 'round' : 'pill';
191
+ }
154
192
  if (!pins.brand) return null;
155
- return aiSiteStyleFor({
193
+ const style = aiSiteStyleFor({
156
194
  kind,
157
195
  answer: {},
158
196
  seed: input.seed,
@@ -160,6 +198,46 @@ const hexOf = (value)=>{
160
198
  avoid: (_input_avoid = input.avoid) != null ? _input_avoid : [],
161
199
  pins
162
200
  });
201
+ const page = (facts == null ? void 0 : facts.background) && /^#[0-9a-f]{6}$/i.test(facts.background) ? facts.background.toLowerCase() : null;
202
+ return _extends({}, style, page ? {
203
+ page
204
+ } : {}, input.fonts ? {
205
+ sourceFonts: input.fonts
206
+ } : {});
207
+ }
208
+ /**
209
+ * The source's own fonts (AGL-4108), each where the theme font catalog has
210
+ * the family (Google Fonts', as the fonts plugin registers it), or the
211
+ * source loads it from Google Fonts itself: its body's and its headings'.
212
+ * `null` where its body's family is neither, and the pairing of the source's
213
+ * type mood sets the site instead.
214
+ */ export async function aiSiteImportSourceFonts(facts, lookup = themeFontFacts) {
215
+ const fonts = facts == null ? void 0 : facts.fonts;
216
+ if (!(fonts == null ? void 0 : fonts.body)) return null;
217
+ const loaded = new Set(fonts.google.map((family)=>family.toLowerCase()));
218
+ const resolve = async (family)=>{
219
+ if (!family) return null;
220
+ const known = await lookup(family).catch(()=>undefined);
221
+ if (known) return {
222
+ family: known.family,
223
+ category: known.category,
224
+ weights: [
225
+ ...known.weights
226
+ ]
227
+ };
228
+ return loaded.has(family.toLowerCase()) ? {
229
+ family,
230
+ category: 'sans-serif',
231
+ weights: []
232
+ } : null;
233
+ };
234
+ const body = await resolve(fonts.body);
235
+ if (!body) return null;
236
+ const heading = fonts.headings && fonts.headings.toLowerCase() !== body.family.toLowerCase() ? await resolve(fonts.headings) : null;
237
+ return {
238
+ body,
239
+ heading
240
+ };
163
241
  }
164
242
  /**
165
243
  * The site's listing in the home page's own words (AGL-4084), as field
@@ -231,8 +309,8 @@ async function writeHostDoc(firestore, hostId, updates) {
231
309
  binary: true
232
310
  }, input.signal ? {
233
311
  signal: input.signal
234
- } : {}, input.headers && sameHost(url, `https://${input.domain}/`) ? {
235
- headers: input.headers
312
+ } : {}, input.headersFor ? {
313
+ headersFor: input.headersFor
236
314
  } : {}));
237
315
  if (!fetched.ok || !((_fetched_data = fetched.data) == null ? void 0 : _fetched_data.byteLength)) return null;
238
316
  const type = fetched.contentType.split(';')[0].trim().toLowerCase();
@@ -281,24 +359,14 @@ async function writeHostDoc(firestore, hostId, updates) {
281
359
  }
282
360
  }
283
361
  /** The look pass (AGL-4084): the theme, the logo and the listing, from the owner's own home page. No model. */ export async function aiRunSiteImportLook(input, deps = {}) {
284
- var _deps_others, _deps_ingest, _deps_readHost, _ref;
362
+ var _deps_others, _home_look, _deps_ingest, _deps_readHost, _ref;
285
363
  const { firestore, hostId, home } = input;
286
- let headers = null;
287
- if (deps.headersFor) {
288
- try {
289
- headers = await deps.headersFor(input.domain);
290
- } catch (error) {
291
- console.warn('[site-import] the edge headers could not be read; reading the look without them', {
292
- domain: input.domain,
293
- error
294
- });
295
- }
296
- }
364
+ const headersFor = await aiSiteImportLookHeaders(input.domain, deps);
297
365
  const seed = aiSiteSeed(input.jobId);
298
366
  const page = await readSiteImportLookPage(home.url, _extends({}, deps.fetch ? {
299
367
  fetch: deps.fetch
300
368
  } : {}, {
301
- headers
369
+ headersFor
302
370
  }, input.signal ? {
303
371
  signal: input.signal
304
372
  } : {}));
@@ -311,10 +379,15 @@ async function writeHostDoc(firestore, hostId, updates) {
311
379
  orgId: input.orgId,
312
380
  hostId
313
381
  });
382
+ // What the reader found of its look, its buttons, ground and fonts (AGL-4108).
383
+ const facts = (_home_look = home.look) != null ? _home_look : null;
384
+ const fonts = await aiSiteImportSourceFonts(facts, deps.fontFacts);
314
385
  const style = aiSiteImportStyle(site, {
315
386
  seed,
316
387
  description: `${home.siteName} ${home.description}`,
317
- avoid
388
+ avoid,
389
+ facts,
390
+ fonts
318
391
  });
319
392
  let theme = 'unread';
320
393
  if (style) {
@@ -334,7 +407,7 @@ async function writeHostDoc(firestore, hostId, updates) {
334
407
  domain: input.domain,
335
408
  logo: home.brand.logo,
336
409
  alt: home.siteName || home.brand.text,
337
- headers
410
+ headersFor
338
411
  }, input.signal ? {
339
412
  signal: input.signal
340
413
  } : {}), _extends({
@@ -388,15 +461,53 @@ async function writeHostDoc(firestore, hostId, updates) {
388
461
  } : {}, typeof style.motion === 'string' ? {
389
462
  motion: style.motion
390
463
  } : {}) : null;
464
+ const colors = [
465
+ ...new Set([
466
+ ...facts ? [
467
+ facts.buttonFill,
468
+ facts.link
469
+ ].filter(colorful).map((hex)=>hex.toLowerCase()) : [],
470
+ ...(_ref = site == null ? void 0 : site.colors) != null ? _ref : []
471
+ ])
472
+ ];
391
473
  return {
392
474
  at: input.at,
393
475
  theme,
394
- colors: (_ref = site == null ? void 0 : site.colors) != null ? _ref : [],
476
+ colors,
395
477
  logo,
396
478
  seo,
397
479
  seed: style ? style.seed : seed,
398
480
  style: drawn
399
481
  };
400
482
  }
483
+ /**
484
+ * The headers the look's reads of the source go with, per address (AGL-4108),
485
+ * exactly as the crawl's (`siteRequestHeaders`): the caller's where it hands
486
+ * them in, else the edge's own, so a site this platform serves is read past
487
+ * its challenge however the step was registered; and nothing for any address
488
+ * off the site's two origins, a redirect hop's included. Never throws.
489
+ *
490
+ * The 10/29 look read went without the header twice over: no caller handed
491
+ * one in, and the read passed headers the fetch does not take, which is why
492
+ * this is a function the fetch asks for every hop (`headersFor`).
493
+ */ export async function aiSiteImportLookHeaders(domain, deps) {
494
+ const origins = siteImportOrigins(domain);
495
+ let inner = NO_SITE_IMPORT_HEADERS;
496
+ try {
497
+ if (deps.headersFor) {
498
+ const headers = await deps.headersFor(domain);
499
+ if (headers && Object.keys(headers).length) inner = ()=>headers;
500
+ } else {
501
+ var _deps_edgeHeadersFor;
502
+ inner = await ((_deps_edgeHeadersFor = deps.edgeHeadersFor) != null ? _deps_edgeHeadersFor : siteImportEdgeHeadersFor)(origins);
503
+ }
504
+ } catch (error) {
505
+ console.warn('[site-import] the edge headers could not be read; reading the look without them', {
506
+ domain,
507
+ error
508
+ });
509
+ }
510
+ return (url)=>origins.includes(url.origin) ? inner(url) : {};
511
+ }
401
512
 
402
513
  //# sourceMappingURL=ai-site-import-look.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-site-import-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\n/*==========================================\n * THE SITE BROUGHT IN KEEPS ITS OWN LOOK (AGL-4084).\n *\n * The first wave's look pass, before the header and footer, with no model:\n *\n * - **The theme.** The owner's own home page is read once more as AglynBot\n * (with the edge headers a site this platform serves gets), and the\n * signals a guided start reads off the sites a person likes (AGL-3975:\n * colors, ground, type mood, corners, buttons, cards, header, density,\n * motion) pin the look: its strongest colorful color is the brand, its\n * second the accent. The look is drawn and saved exactly as a guided\n * start's (`aiSiteStyleFor`, `aiSaveSiteTheme`): a built-in base plus the\n * brand colors and a pairing of the source's type mood, never its font.\n * A site whose owner already chose a theme keeps it.\n * - **The logo.** The source header's logo, as the source draws it, into the\n * site's media library once: an SVG is turned into a PNG 192 pixels tall\n * (the library takes pictures, not drawings), a picture is kept as it is.\n * - **The listing.** The home page's own title, description and share\n * picture become the site's listing, only while the site still has the\n * listing it was created with: an owner's own words are never written\n * over.\n *\n * Nothing here fails a wave: a source whose look cannot be read keeps the\n * site's theme, and a logo that cannot be brought in leaves the header its\n * name.\n *=========================================*/\n\nimport type { PluginMediaIngest } from '@aglyn/aglyn/plugin-manager/plugin-media-ingest'\nimport { aiSiteListingSeeded } from '../model/ai-site-entity'\nimport type { AiSiteImportHome, AiSiteImportLook, AiSiteImportPhoto } from '../model/ai-site-import'\nimport { aiSiteKindFor } from '../model/ai-site-kinds'\nimport { aiSiteSeed, aiSiteStyleFor, type AiSiteStyle } from '../model/ai-site-look'\nimport { aiSiteIsStarterSrc } from '../model/ai-site-photos'\nimport { AGLYN_BOT_USER_AGENT, type SiteImportCrawlSeams } from '../site-import/site-import-crawl'\nimport { bringInSiteImportPhotos, siteImportImageKey, siteImportLogoUrl } from '../site-import/site-import-images'\nimport { aiSaveSiteTheme, aiWorkspaceSiteStyles } from './ai-job-site-look'\nimport { aiSiteInspirationLookPins } from './ai-site-inspiration'\nimport { aiSiteInspirationSignals, type AiSiteInspirationSite } from './ai-site-inspiration-signals'\nimport {\n AI_THEME_BRAND_COLORS_PER_SOURCE,\n AI_THEME_REFERENCE_MAX_BYTES,\n AI_THEME_REFERENCE_STYLESHEETS,\n AI_THEME_STYLESHEET_MAX_BYTES,\n AI_THEME_THEME_COLOR_WEIGHT,\n fetchPublicTextDetailed,\n rankBrandColors,\n stylesheetUrlsOf,\n type AiReferencePage,\n type PublicFetchOptions,\n type PublicFetchResult,\n} from './ai-theme-brand-inputs'\n\n/** The height an SVG logo is drawn at as a picture: crisp at twice the header's 40. */\nexport const AI_SITE_IMPORT_LOGO_HEIGHT = 192\n\n/** The largest logo file read. */\nexport const AI_SITE_IMPORT_LOGO_MAX_BYTES = 1024 * 1024\n\n/** One read's own clock in the look pass. */\nconst LOOK_READ_MS = 8_000\n\ntype Fetch = (url: string, options: PublicFetchOptions) => Promise<PublicFetchResult>\n\nconst sameHost = (a: string, b: string) => {\n try {\n return new URL(a).host === new URL(b).host\n } catch {\n return false\n }\n}\n\n/**\n * The home page and its first stylesheets, read as AglynBot (AGL-4084), the\n * edge headers going with the page and with a stylesheet only on the page's\n * own host. `null` when the page cannot be read.\n */\nexport async function readSiteImportLookPage(\n url: string,\n input: { fetch?: Fetch; headers?: Readonly<Record<string, string>> | null; signal?: AbortSignal },\n): Promise<AiReferencePage | null> {\n const fetchText = input.fetch ?? fetchPublicTextDetailed\n const headers = input.headers && Object.keys(input.headers).length ? input.headers : null\n const read = (address: string, maxBytes: number, accept: RegExp, own: boolean) =>\n fetchText(address, {\n maxBytes,\n accept,\n userAgent: AGLYN_BOT_USER_AGENT,\n timeoutMs: LOOK_READ_MS,\n ...(input.signal ? { signal: input.signal } : {}),\n ...(headers && own ? { headers } : {}),\n }).catch(() => null)\n const page = await read(url, AI_THEME_REFERENCE_MAX_BYTES, /text\\/html|application\\/xhtml\\+xml/i, true)\n if (!page?.ok) return null\n const stylesheets: string[] = []\n for (const sheet of stylesheetUrlsOf(page.text, page.url).slice(0, AI_THEME_REFERENCE_STYLESHEETS)) {\n const css = await read(sheet, AI_THEME_STYLESHEET_MAX_BYTES, /text\\/css/i, sameHost(sheet, page.url))\n if (css?.ok) stylesheets.push(css.text)\n }\n return { url: page.url, html: page.text, stylesheets }\n}\n\nconst SOLID_HEX = /(?<!&)#(?:[0-9a-f]{6}|[0-9a-f]{3})(?![0-9a-z])/gi\nconst SOLID_RGB = /rgba?\\(\\s*(\\d{1,3})\\s*[,\\s]\\s*(\\d{1,3})\\s*[,\\s]\\s*(\\d{1,3})\\s*(?:[,/]\\s*([\\d.]+%?)\\s*)?\\)/gi\nconst THEME_COLOR = /<meta\\b[^>]*\\bname\\s*=\\s*[\"']?theme-color\\b[^>]*\\bcontent\\s*=\\s*[\"']?\\s*(#[0-9a-f]{3,6})/gi\n\nconst hexOf = (value: string): string | null => {\n const raw = value.trim().toLowerCase()\n if (/^#[0-9a-f]{6}$/.test(raw)) return raw\n if (/^#[0-9a-f]{3}$/.test(raw)) return `#${raw[1]}${raw[1]}${raw[2]}${raw[2]}${raw[3]}${raw[3]}`\n return null\n}\n\n/**\n * A source's brand colors as it DRAWS them (AGL-4084): the colors its markup\n * and stylesheets use solid, most used first, the colorful before the greys.\n * A translucent color is a tint of one (a hover, a focus ring), never a use\n * of it, so it is not counted: aglyn.com's focus rings, a light blue at a\n * quarter strength, outnumbered the deep blue its buttons and links are\n * drawn in, which the theme reader would otherwise take as its brand.\n */\nexport function aiSiteImportBrandColors(page: Pick<AiReferencePage, 'html' | 'stylesheets'>): string[] {\n const weights = new Map<string, number>()\n const add = (hex: string | null, weight = 1) => {\n if (hex) weights.set(hex, (weights.get(hex) ?? 0) + weight)\n }\n for (const match of page.html.matchAll(THEME_COLOR)) add(hexOf(match[1] ?? ''), AI_THEME_THEME_COLOR_WEIGHT)\n for (const text of [page.html, ...page.stylesheets]) {\n for (const match of text.matchAll(SOLID_HEX)) add(hexOf(match[0]))\n for (const match of text.matchAll(SOLID_RGB)) {\n const alpha = match[4]\n if (alpha !== undefined && (alpha.endsWith('%') ? Number(alpha.slice(0, -1)) < 100 : Number(alpha) < 1)) continue\n const channels = [match[1], match[2], match[3]].map((value) => Math.min(255, Number(value)))\n add(`#${channels.map((value) => value.toString(16).padStart(2, '0')).join('')}`)\n }\n }\n return rankBrandColors(weights, AI_THEME_BRAND_COLORS_PER_SOURCE)\n}\n\n/**\n * The look a source's signals pin (AGL-4084), drawn the way a guided start's\n * is: the kind of site its own description says, the import's seed, its\n * strongest color as the brand. `null` where the source showed no color to\n * carry a brand, so the site keeps the theme it has.\n */\nexport function aiSiteImportStyle(\n site: AiSiteInspirationSite | null,\n input: { seed: number; description: string; avoid?: readonly AiSiteStyle[] },\n): AiSiteStyle | null {\n if (!site) return null\n const kind = aiSiteKindFor(input.description)\n const pins = aiSiteInspirationLookPins({ v: 1, sites: [site], unread: 0 }, { kind, seed: input.seed })\n if (!pins.brand) return null\n return aiSiteStyleFor({ kind, answer: {}, seed: input.seed, brand: pins.brand, avoid: input.avoid ?? [], pins })\n}\n\n/**\n * The site's listing in the home page's own words (AGL-4084), as field\n * paths: its title and description each only where the site still has the\n * one it was created with, so an owner's own listing is never written over.\n * `{}` with nothing to write.\n */\nexport function aiSiteImportListingUpdates(seo: unknown, displayName: unknown, home: Pick<AiSiteImportHome, 'title' | 'description'>): Record<string, string> {\n const seeded = aiSiteListingSeeded(seo, displayName)\n const current = (seo && typeof seo === 'object' ? seo : {}) as Record<string, unknown>\n const updates: Record<string, string> = {}\n const title = home.title.replace(/\\s+/g, ' ').trim()\n const description = home.description.replace(/\\s+/g, ' ').trim()\n if (title && seeded.title && title !== current['title']) updates['seo.title'] = title\n if (description && seeded.description && description !== current['description']) updates['seo.description'] = description\n return updates\n}\n\n/** Whether the site's share picture is still open: none, or the starter's (AGL-3988). */\nfunction shareOpen(seo: unknown): boolean {\n const image = (seo as { image?: unknown } | null | undefined)?.image\n if (image === undefined || image === null) return true\n return typeof image === 'string' && (!image.trim() || aiSiteIsStarterSrc(image))\n}\n\n/** Turns an SVG drawing into a PNG of a height; `null` where it cannot. */\nexport type AiSiteImportRasterize = (svg: Uint8Array, height: number) => Promise<Uint8Array | null>\n\nexport interface AiSiteImportLookDeps {\n fetch?: Fetch\n headersFor?: SiteImportCrawlSeams['headersFor']\n save?: typeof aiSaveSiteTheme\n others?: typeof aiWorkspaceSiteStyles\n ingest?: PluginMediaIngest | null\n rasterize?: AiSiteImportRasterize\n /** The site's document, as the listing reads it. */\n readHost?: (firestore: FirebaseFirestore.Firestore, hostId: string) => Promise<Record<string, unknown> | null>\n writeHost?: (firestore: FirebaseFirestore.Firestore, hostId: string, updates: Record<string, unknown>) => Promise<void>\n}\n\nexport interface AiSiteImportLookInput {\n firestore: FirebaseFirestore.Firestore\n hostId: string\n orgId: string\n /** The member the logo and the share picture are stored as. */\n uid: string\n /** The import job's id, which the look's seed is drawn from. */\n jobId: string\n domain: string\n home: AiSiteImportHome\n at: string\n signal?: AbortSignal\n}\n\nasync function readHostDoc(firestore: FirebaseFirestore.Firestore, hostId: string): Promise<Record<string, unknown> | null> {\n const snapshot = await firestore.collection('hosts').doc(hostId).get()\n return snapshot.exists ? ((snapshot.data() ?? {}) as Record<string, unknown>) : null\n}\n\nasync function writeHostDoc(firestore: FirebaseFirestore.Firestore, hostId: string, updates: Record<string, unknown>): Promise<void> {\n await firestore.collection('hosts').doc(hostId).update(updates)\n}\n\n/** Turns an SVG into a PNG in this process, where `sharp` is shipped; `null` where it is not. */\nasync function rasterizeHere(svg: Uint8Array, height: number): Promise<Uint8Array | null> {\n const images = await import('./ai-image-reads')\n const origin = images.aiImageReadsOrigin()\n if (origin) return images.requestAiLogoRaster(origin, svg, height)\n return images.rasterizeAiLogoSvg(svg, height)\n}\n\n/**\n * The source header's logo in the site's library (AGL-4084), once: the copy\n * a page already brought in is reused by its key. An SVG becomes a PNG.\n * `null` where it cannot be brought in, and the header then prints the name.\n */\nexport async function aiSiteImportLogo(\n input: { hostId: string; uid: string; domain: string; logo: NonNullable<AiSiteImportHome['brand']['logo']>; alt: string; headers?: Readonly<Record<string, string>> | null; signal?: AbortSignal },\n deps: { fetch?: Fetch; ingest: PluginMediaIngest | null; rasterize?: AiSiteImportRasterize },\n): Promise<AiSiteImportPhoto | null> {\n const ingest = deps.ingest\n const url = siteImportLogoUrl(input.logo.url)\n if (!ingest?.findImportedPhoto || !url) return null\n const sourceKey = siteImportImageKey(url)\n try {\n const held = await ingest.findImportedPhoto({ hostId: input.hostId, sourceKey })\n if (held) return { mediaId: held.mediaId, src: held.src, alt: input.alt, from: url, ...(held.width ? { width: held.width } : {}), ...(held.height ? { height: held.height } : {}) }\n const fetched = await (deps.fetch ?? fetchPublicTextDetailed)(url, {\n maxBytes: AI_SITE_IMPORT_LOGO_MAX_BYTES,\n accept: /^image\\//i,\n acceptHeader: 'image/svg+xml,image/png,image/webp,image/jpeg;q=0.9',\n userAgent: AGLYN_BOT_USER_AGENT,\n timeoutMs: LOOK_READ_MS,\n refuseOver: true,\n binary: true,\n ...(input.signal ? { signal: input.signal } : {}),\n ...(input.headers && sameHost(url, `https://${input.domain}/`) ? { headers: input.headers } : {}),\n })\n if (!fetched.ok || !fetched.data?.byteLength) return null\n const type = fetched.contentType.split(';')[0].trim().toLowerCase()\n let bytes: Uint8Array | null = fetched.data\n let contentType = type\n if (type === 'image/svg+xml' || input.logo.type === 'svg') {\n bytes = await (deps.rasterize ?? rasterizeHere)(fetched.data, AI_SITE_IMPORT_LOGO_HEIGHT).catch(() => null)\n contentType = 'image/png'\n } else if (!/^image\\/(?:png|jpeg|webp|gif)$/.test(type)) {\n return null\n }\n if (!bytes?.byteLength) return null\n const stored = await ingest.ingest({\n hostId: input.hostId,\n uid: input.uid,\n fileName: `logo.${contentType === 'image/jpeg' ? 'jpg' : contentType.split('/')[1]}`,\n contentType,\n bytes,\n ...(input.alt ? { alt: input.alt.slice(0, 250) } : {}),\n importedFrom: { key: sourceKey, url, domain: input.domain },\n })\n if (!stored.ok) return null\n return { mediaId: stored.mediaId, src: stored.src, alt: input.alt, from: url, ...(stored.width ? { width: stored.width } : {}), ...(stored.height ? { height: stored.height } : {}) }\n } catch (error) {\n console.warn('[site-import] the logo could not be brought in', { hostId: input.hostId, error })\n return null\n }\n}\n\n/** The look pass (AGL-4084): the theme, the logo and the listing, from the owner's own home page. No model. */\nexport async function aiRunSiteImportLook(input: AiSiteImportLookInput, deps: AiSiteImportLookDeps = {}): Promise<AiSiteImportLook> {\n const { firestore, hostId, home } = input\n let headers: Readonly<Record<string, string>> | null = null\n if (deps.headersFor) {\n try {\n headers = await deps.headersFor(input.domain)\n } catch (error) {\n console.warn('[site-import] the edge headers could not be read; reading the look without them', { domain: input.domain, error })\n }\n }\n const seed = aiSiteSeed(input.jobId)\n const page = await readSiteImportLookPage(home.url, { ...(deps.fetch ? { fetch: deps.fetch } : {}), headers, ...(input.signal ? { signal: input.signal } : {}) })\n // The source's own signals, its brand colors as it draws them solid.\n const signals = page ? aiSiteInspirationSignals(page) : null\n const site = signals && page ? { ...signals, colors: aiSiteImportBrandColors(page) } : signals\n const avoid = await (deps.others ?? aiWorkspaceSiteStyles)(firestore, { orgId: input.orgId, hostId })\n const style = aiSiteImportStyle(site, { seed, description: `${home.siteName} ${home.description}`, avoid })\n let theme: AiSiteImportLook['theme'] = 'unread'\n if (style) {\n const saved = await (deps.save ?? aiSaveSiteTheme)(firestore, { hostId, jobId: input.jobId, style })\n if (saved.write === 'missing') throw new Error(`site ${hostId} vanished while its look was brought in`)\n theme = saved.write === 'kept' ? 'kept' : 'applied'\n }\n const ingest = deps.ingest ?? null\n const logo = home.brand.logo\n ? await aiSiteImportLogo(\n { hostId, uid: input.uid, domain: input.domain, logo: home.brand.logo, alt: home.siteName || home.brand.text, headers, ...(input.signal ? { signal: input.signal } : {}) },\n { ingest, ...(deps.fetch ? { fetch: deps.fetch } : {}), ...(deps.rasterize ? { rasterize: deps.rasterize } : {}) },\n )\n : null\n // The site's listing in its own words, only over the one it was created with.\n let seo = false\n const host = await (deps.readHost ?? readHostDoc)(firestore, hostId)\n if (host) {\n const updates: Record<string, unknown> = aiSiteImportListingUpdates(host['seo'], host['displayName'], home)\n if (home.ogImage && shareOpen(host['seo'])) {\n const share = await bringInSiteImportPhotos(\n { hostId, uid: input.uid, domain: input.domain, images: [{ url: home.ogImage, alt: '' }], deadlineAt: Date.now() + LOOK_READ_MS, ...(input.signal ? { signal: input.signal } : {}) },\n { ingest, ...(deps.fetch ? { fetch: deps.fetch } : {}) },\n )\n const photo = share.assets[0]\n if (photo) {\n updates['seo.image'] = photo.src\n if (photo.width) updates['seo.imageWidth'] = photo.width\n if (photo.height) updates['seo.imageHeight'] = photo.height\n }\n }\n if (Object.keys(updates).length) {\n await (deps.writeHost ?? writeHostDoc)(firestore, hostId, updates)\n seo = true\n }\n }\n const drawn = style\n ? {\n ...(typeof style.headerAlign === 'string' ? { headerAlign: style.headerAlign } : {}),\n ...(typeof style.rhythm === 'string' ? { rhythm: style.rhythm } : {}),\n ...(typeof style.motion === 'string' ? { motion: style.motion } : {}),\n }\n : null\n return { at: input.at, theme, colors: site?.colors ?? [], logo, seo, seed: style ? style.seed : seed, style: drawn }\n}\n"],"names":["aiSiteListingSeeded","aiSiteKindFor","aiSiteSeed","aiSiteStyleFor","aiSiteIsStarterSrc","AGLYN_BOT_USER_AGENT","bringInSiteImportPhotos","siteImportImageKey","siteImportLogoUrl","aiSaveSiteTheme","aiWorkspaceSiteStyles","aiSiteInspirationLookPins","aiSiteInspirationSignals","AI_THEME_BRAND_COLORS_PER_SOURCE","AI_THEME_REFERENCE_MAX_BYTES","AI_THEME_REFERENCE_STYLESHEETS","AI_THEME_STYLESHEET_MAX_BYTES","AI_THEME_THEME_COLOR_WEIGHT","fetchPublicTextDetailed","rankBrandColors","stylesheetUrlsOf","AI_SITE_IMPORT_LOGO_HEIGHT","AI_SITE_IMPORT_LOGO_MAX_BYTES","LOOK_READ_MS","sameHost","a","b","URL","host","readSiteImportLookPage","url","input","fetchText","fetch","headers","Object","keys","length","read","address","maxBytes","accept","own","userAgent","timeoutMs","signal","catch","page","ok","stylesheets","sheet","text","slice","css","push","html","SOLID_HEX","SOLID_RGB","THEME_COLOR","hexOf","value","raw","trim","toLowerCase","test","aiSiteImportBrandColors","match","weights","Map","add","hex","weight","set","get","matchAll","alpha","undefined","endsWith","Number","channels","map","Math","min","toString","padStart","join","aiSiteImportStyle","site","kind","description","pins","v","sites","unread","seed","brand","answer","avoid","aiSiteImportListingUpdates","seo","displayName","home","seeded","current","updates","title","replace","shareOpen","image","readHostDoc","firestore","hostId","snapshot","collection","doc","exists","data","writeHostDoc","update","rasterizeHere","svg","height","images","origin","aiImageReadsOrigin","requestAiLogoRaster","rasterizeAiLogoSvg","aiSiteImportLogo","deps","ingest","logo","findImportedPhoto","sourceKey","fetched","held","mediaId","src","alt","from","width","acceptHeader","refuseOver","binary","domain","byteLength","type","contentType","split","bytes","rasterize","stored","uid","fileName","importedFrom","key","error","console","warn","aiRunSiteImportLook","headersFor","jobId","signals","colors","others","orgId","style","siteName","theme","saved","save","write","Error","readHost","ogImage","share","deadlineAt","Date","now","photo","assets","writeHost","drawn","headerAlign","rhythm","motion","at"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-site-import-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\n/*==========================================\n * THE SITE BROUGHT IN KEEPS ITS OWN LOOK (AGL-4084).\n *\n * The first wave's look pass, before the header and footer, with no model:\n *\n * - **The theme.** The owner's own home page is read once more as AglynBot,\n * with the edge headers a site this platform serves gets whether or not a\n * caller hands them in (AGL-4108: production handed none, our own edge\n * challenged the read, and the 10/29 site kept the starter's indigo), and\n * the signals a guided start reads off the sites a person likes\n * (AGL-3975: colors, ground, type mood, corners, buttons, cards, header,\n * density, motion) pin the look. What the reader found of the source's\n * look (AGL-4106) pins it closer (AGL-4108): its buttons' fill is the\n * brand and its links' color the accent, its page's ground is the site's,\n * and its own font family is the site's wherever the theme font catalog\n * has it, else a pairing of its type mood. The look is drawn and saved\n * exactly as a guided start's (`aiSiteStyleFor`, `aiSaveSiteTheme`): a\n * built-in base plus the brand colors and fonts, every color held to the\n * theme's reading checks. A site whose owner already chose a theme keeps\n * it.\n * - **The logo.** The source header's logo, as the source draws it, into the\n * site's media library once: an SVG is turned into a PNG 192 pixels tall\n * (the library takes pictures, not drawings), a picture is kept as it is.\n * - **The listing.** The home page's own title, description and share\n * picture become the site's listing, only while the site still has the\n * listing it was created with: an owner's own words are never written\n * over.\n *\n * Nothing here fails a wave: a source whose look cannot be read keeps the\n * site's theme, and a logo that cannot be brought in leaves the header its\n * name.\n *=========================================*/\n\nimport type { PluginMediaIngest } from '@aglyn/aglyn/plugin-manager/plugin-media-ingest'\nimport { themeFontFacts, type ThemeFontFacts } from '@aglyn/aglyn/plugin-manager/plugin-theme-font-catalog'\nimport { aiSiteListingSeeded } from '../model/ai-site-entity'\nimport type { AiSiteImportHome, AiSiteImportLook, AiSiteImportPhoto, SiteImportLookFacts } from '../model/ai-site-import'\nimport { aiSiteKindFor } from '../model/ai-site-kinds'\nimport { aiHexOklch, aiSiteSeed, aiSiteStyleFor, type AiSiteSourceFont, type AiSiteStyle } from '../model/ai-site-look'\nimport { aiSiteIsStarterSrc } from '../model/ai-site-photos'\nimport { AGLYN_BOT_USER_AGENT, siteImportOrigins, type SiteImportCrawlSeams } from '../site-import/site-import-crawl'\nimport { NO_SITE_IMPORT_HEADERS, siteImportEdgeHeadersFor, type SiteImportHeadersFor } from '../site-import/site-import-edge'\nimport { bringInSiteImportPhotos, siteImportImageKey, siteImportLogoUrl } from '../site-import/site-import-images'\nimport { aiSaveSiteTheme, aiWorkspaceSiteStyles } from './ai-job-site-look'\nimport { aiSiteInspirationLookPins } from './ai-site-inspiration'\nimport { aiSiteInspirationSignals, type AiSiteInspirationSite } from './ai-site-inspiration-signals'\nimport {\n AI_THEME_BRAND_COLORS_PER_SOURCE,\n AI_THEME_REFERENCE_MAX_BYTES,\n AI_THEME_REFERENCE_STYLESHEETS,\n AI_THEME_STYLESHEET_MAX_BYTES,\n AI_THEME_THEME_COLOR_WEIGHT,\n fetchPublicTextDetailed,\n rankBrandColors,\n stylesheetUrlsOf,\n type AiReferencePage,\n type PublicFetchOptions,\n type PublicFetchResult,\n} from './ai-theme-brand-inputs'\n\n/** The height an SVG logo is drawn at as a picture: crisp at twice the header's 40. */\nexport const AI_SITE_IMPORT_LOGO_HEIGHT = 192\n\n/** The largest logo file read. */\nexport const AI_SITE_IMPORT_LOGO_MAX_BYTES = 1024 * 1024\n\n/** One read's own clock in the look pass. */\nconst LOOK_READ_MS = 8_000\n\ntype Fetch = (url: string, options: PublicFetchOptions) => Promise<PublicFetchResult>\n\n/**\n * The home page and its first stylesheets, read as AglynBot (AGL-4084).\n * `headersFor` is asked for every request and every redirect hop, as the\n * crawl asks it (`aiSiteImportLookHeaders`: the edge header only to the\n * site's own origins). `null` when the page cannot be read.\n */\nexport async function readSiteImportLookPage(\n url: string,\n input: { fetch?: Fetch; headersFor?: SiteImportHeadersFor | null; signal?: AbortSignal },\n): Promise<AiReferencePage | null> {\n const fetchText = input.fetch ?? fetchPublicTextDetailed\n const headersFor = input.headersFor ?? null\n const read = (address: string, maxBytes: number, accept: RegExp) =>\n fetchText(address, {\n maxBytes,\n accept,\n userAgent: AGLYN_BOT_USER_AGENT,\n timeoutMs: LOOK_READ_MS,\n ...(input.signal ? { signal: input.signal } : {}),\n ...(headersFor ? { headersFor } : {}),\n }).catch(() => null)\n const page = await read(url, AI_THEME_REFERENCE_MAX_BYTES, /text\\/html|application\\/xhtml\\+xml/i)\n if (!page?.ok) return null\n const stylesheets: string[] = []\n for (const sheet of stylesheetUrlsOf(page.text, page.url).slice(0, AI_THEME_REFERENCE_STYLESHEETS)) {\n const css = await read(sheet, AI_THEME_STYLESHEET_MAX_BYTES, /text\\/css/i)\n if (css?.ok) stylesheets.push(css.text)\n }\n return { url: page.url, html: page.text, stylesheets }\n}\n\nconst SOLID_HEX = /(?<!&)#(?:[0-9a-f]{6}|[0-9a-f]{3})(?![0-9a-z])/gi\nconst SOLID_RGB = /rgba?\\(\\s*(\\d{1,3})\\s*[,\\s]\\s*(\\d{1,3})\\s*[,\\s]\\s*(\\d{1,3})\\s*(?:[,/]\\s*([\\d.]+%?)\\s*)?\\)/gi\nconst THEME_COLOR = /<meta\\b[^>]*\\bname\\s*=\\s*[\"']?theme-color\\b[^>]*\\bcontent\\s*=\\s*[\"']?\\s*(#[0-9a-f]{3,6})/gi\n\nconst hexOf = (value: string): string | null => {\n const raw = value.trim().toLowerCase()\n if (/^#[0-9a-f]{6}$/.test(raw)) return raw\n if (/^#[0-9a-f]{3}$/.test(raw)) return `#${raw[1]}${raw[1]}${raw[2]}${raw[2]}${raw[3]}${raw[3]}`\n return null\n}\n\n/**\n * A source's brand colors as it DRAWS them (AGL-4084): the colors its markup\n * and stylesheets use solid, most used first, the colorful before the greys.\n * A translucent color is a tint of one (a hover, a focus ring), never a use\n * of it, so it is not counted: aglyn.com's focus rings, a light blue at a\n * quarter strength, outnumbered the deep blue its buttons and links are\n * drawn in, which the theme reader would otherwise take as its brand.\n */\nexport function aiSiteImportBrandColors(page: Pick<AiReferencePage, 'html' | 'stylesheets'>): string[] {\n const weights = new Map<string, number>()\n const add = (hex: string | null, weight = 1) => {\n if (hex) weights.set(hex, (weights.get(hex) ?? 0) + weight)\n }\n for (const match of page.html.matchAll(THEME_COLOR)) add(hexOf(match[1] ?? ''), AI_THEME_THEME_COLOR_WEIGHT)\n for (const text of [page.html, ...page.stylesheets]) {\n for (const match of text.matchAll(SOLID_HEX)) add(hexOf(match[0]))\n for (const match of text.matchAll(SOLID_RGB)) {\n const alpha = match[4]\n if (alpha !== undefined && (alpha.endsWith('%') ? Number(alpha.slice(0, -1)) < 100 : Number(alpha) < 1)) continue\n const channels = [match[1], match[2], match[3]].map((value) => Math.min(255, Number(value)))\n add(`#${channels.map((value) => value.toString(16).padStart(2, '0')).join('')}`)\n }\n }\n return rankBrandColors(weights, AI_THEME_BRAND_COLORS_PER_SOURCE)\n}\n\n/** How colorful a color must be to carry a brand: the inspiration reader's own floor. */\nconst COLORFUL_CHROMA = 0.04\n\nconst colorful = (hex: string | null | undefined): hex is string => !!hex && /^#[0-9a-f]{6}$/i.test(hex) && aiHexOklch(hex).C >= COLORFUL_CHROMA\n\n/**\n * The brand and accent a source's look facts give (AGL-4108): the fill of\n * its filled buttons as the brand, the color of its links as the accent\n * where it is a color of its own, and the reverse where its buttons are not\n * colorful. The theme draws the brand at the nearest shade of it that reads\n * (`aiSiteColors`): aglyn.com's light blue buttons, under white words at\n * 2.4:1, come out at the deeper blue of its own links.\n */\nexport function aiSiteImportBrandOf(facts: Pick<SiteImportLookFacts, 'buttonFill' | 'link'> | null | undefined): { brand: string; accent: string | null } | null {\n const fill = colorful(facts?.buttonFill) ? (facts?.buttonFill as string).toLowerCase() : null\n const link = colorful(facts?.link) ? (facts?.link as string).toLowerCase() : null\n const brand = fill ?? link\n if (!brand) return null\n return { brand, accent: link && link !== brand ? link : null }\n}\n\n/**\n * The look a source's signals pin (AGL-4084), drawn the way a guided start's\n * is: the kind of site its own description says, the import's seed, its\n * strongest color as the brand. What the reader found of its look\n * (`facts`, AGL-4108) pins it closer: its buttons' fill as the brand and its\n * links' color as the accent, its page's ground, and its own fonts where the\n * catalog has them (`fonts`). `null` where the source showed no color to\n * carry a brand, so the site keeps the theme it has.\n */\nexport function aiSiteImportStyle(\n site: AiSiteInspirationSite | null,\n input: {\n seed: number\n description: string\n avoid?: readonly AiSiteStyle[]\n facts?: SiteImportLookFacts | null\n fonts?: AiSiteStyle['sourceFonts']\n },\n): AiSiteStyle | null {\n const facts = input.facts ?? null\n const own = aiSiteImportBrandOf(facts)\n if (!site && !own) return null\n const kind = aiSiteKindFor(input.description)\n const pins = site ? aiSiteInspirationLookPins({ v: 1, sites: [site], unread: 0 }, { kind, seed: input.seed }) : {}\n if (own) {\n pins.brand = own.brand\n pins.hue = Math.round(aiHexOklch(own.brand).h) % 360\n if (own.accent) pins.accent = Math.round(aiHexOklch(own.accent).h) % 360\n }\n // Its buttons' corners round the theme's shape where its stylesheets did not say (AGL-4108).\n const radius = facts?.buttonRadius\n if (typeof radius === 'number' && Number.isFinite(radius) && radius >= 0) {\n pins.buttons ??= radius >= 24 ? 'pill' : radius <= 2 ? 'square' : 'rounded'\n pins.corners ??= radius <= 2 ? 'sharp' : radius <= 10 ? 'soft' : radius <= 20 ? 'round' : 'pill'\n }\n if (!pins.brand) return null\n const style = aiSiteStyleFor({ kind, answer: {}, seed: input.seed, brand: pins.brand, avoid: input.avoid ?? [], pins })\n const page = facts?.background && /^#[0-9a-f]{6}$/i.test(facts.background) ? facts.background.toLowerCase() : null\n return {\n ...style,\n ...(page ? { page } : {}),\n ...(input.fonts ? { sourceFonts: input.fonts } : {}),\n }\n}\n\n/** A family's facts, as the theme font catalog answers them. */\nexport type AiSiteImportFontFacts = (family: string) => Promise<ThemeFontFacts | undefined>\n\n/**\n * The source's own fonts (AGL-4108), each where the theme font catalog has\n * the family (Google Fonts', as the fonts plugin registers it), or the\n * source loads it from Google Fonts itself: its body's and its headings'.\n * `null` where its body's family is neither, and the pairing of the source's\n * type mood sets the site instead.\n */\nexport async function aiSiteImportSourceFonts(\n facts: Pick<SiteImportLookFacts, 'fonts'> | null | undefined,\n lookup: AiSiteImportFontFacts = themeFontFacts,\n): Promise<AiSiteStyle['sourceFonts']> {\n const fonts = facts?.fonts\n if (!fonts?.body) return null\n const loaded = new Set(fonts.google.map((family) => family.toLowerCase()))\n const resolve = async (family: string | null): Promise<AiSiteSourceFont | null> => {\n if (!family) return null\n const known = await lookup(family).catch(() => undefined)\n if (known) return { family: known.family, category: known.category, weights: [...known.weights] }\n return loaded.has(family.toLowerCase()) ? { family, category: 'sans-serif', weights: [] } : null\n }\n const body = await resolve(fonts.body)\n if (!body) return null\n const heading = fonts.headings && fonts.headings.toLowerCase() !== body.family.toLowerCase() ? await resolve(fonts.headings) : null\n return { body, heading }\n}\n\n/**\n * The site's listing in the home page's own words (AGL-4084), as field\n * paths: its title and description each only where the site still has the\n * one it was created with, so an owner's own listing is never written over.\n * `{}` with nothing to write.\n */\nexport function aiSiteImportListingUpdates(seo: unknown, displayName: unknown, home: Pick<AiSiteImportHome, 'title' | 'description'>): Record<string, string> {\n const seeded = aiSiteListingSeeded(seo, displayName)\n const current = (seo && typeof seo === 'object' ? seo : {}) as Record<string, unknown>\n const updates: Record<string, string> = {}\n const title = home.title.replace(/\\s+/g, ' ').trim()\n const description = home.description.replace(/\\s+/g, ' ').trim()\n if (title && seeded.title && title !== current['title']) updates['seo.title'] = title\n if (description && seeded.description && description !== current['description']) updates['seo.description'] = description\n return updates\n}\n\n/** Whether the site's share picture is still open: none, or the starter's (AGL-3988). */\nfunction shareOpen(seo: unknown): boolean {\n const image = (seo as { image?: unknown } | null | undefined)?.image\n if (image === undefined || image === null) return true\n return typeof image === 'string' && (!image.trim() || aiSiteIsStarterSrc(image))\n}\n\n/** Turns an SVG drawing into a PNG of a height; `null` where it cannot. */\nexport type AiSiteImportRasterize = (svg: Uint8Array, height: number) => Promise<Uint8Array | null>\n\nexport interface AiSiteImportLookDeps {\n fetch?: Fetch\n /** The edge headers for the source, asked with its domain; absent, the edge is asked as the crawl asks it (AGL-4108). */\n headersFor?: SiteImportCrawlSeams['headersFor']\n /** The edge's own answer, per address (`siteImportEdgeHeadersFor`), where no `headersFor` is given. */\n edgeHeadersFor?: typeof siteImportEdgeHeadersFor\n /** A family's facts in the theme font catalog (`themeFontFacts`). */\n fontFacts?: AiSiteImportFontFacts\n save?: typeof aiSaveSiteTheme\n others?: typeof aiWorkspaceSiteStyles\n ingest?: PluginMediaIngest | null\n rasterize?: AiSiteImportRasterize\n /** The site's document, as the listing reads it. */\n readHost?: (firestore: FirebaseFirestore.Firestore, hostId: string) => Promise<Record<string, unknown> | null>\n writeHost?: (firestore: FirebaseFirestore.Firestore, hostId: string, updates: Record<string, unknown>) => Promise<void>\n}\n\nexport interface AiSiteImportLookInput {\n firestore: FirebaseFirestore.Firestore\n hostId: string\n orgId: string\n /** The member the logo and the share picture are stored as. */\n uid: string\n /** The import job's id, which the look's seed is drawn from. */\n jobId: string\n domain: string\n home: AiSiteImportHome\n at: string\n signal?: AbortSignal\n}\n\nasync function readHostDoc(firestore: FirebaseFirestore.Firestore, hostId: string): Promise<Record<string, unknown> | null> {\n const snapshot = await firestore.collection('hosts').doc(hostId).get()\n return snapshot.exists ? ((snapshot.data() ?? {}) as Record<string, unknown>) : null\n}\n\nasync function writeHostDoc(firestore: FirebaseFirestore.Firestore, hostId: string, updates: Record<string, unknown>): Promise<void> {\n await firestore.collection('hosts').doc(hostId).update(updates)\n}\n\n/** Turns an SVG into a PNG in this process, where `sharp` is shipped; `null` where it is not. */\nasync function rasterizeHere(svg: Uint8Array, height: number): Promise<Uint8Array | null> {\n const images = await import('./ai-image-reads')\n const origin = images.aiImageReadsOrigin()\n if (origin) return images.requestAiLogoRaster(origin, svg, height)\n return images.rasterizeAiLogoSvg(svg, height)\n}\n\n/**\n * The source header's logo in the site's library (AGL-4084), once: the copy\n * a page already brought in is reused by its key. An SVG becomes a PNG.\n * `null` where it cannot be brought in, and the header then prints the name.\n */\nexport async function aiSiteImportLogo(\n input: { hostId: string; uid: string; domain: string; logo: NonNullable<AiSiteImportHome['brand']['logo']>; alt: string; headersFor?: SiteImportHeadersFor | null; signal?: AbortSignal },\n deps: { fetch?: Fetch; ingest: PluginMediaIngest | null; rasterize?: AiSiteImportRasterize },\n): Promise<AiSiteImportPhoto | null> {\n const ingest = deps.ingest\n const url = siteImportLogoUrl(input.logo.url)\n if (!ingest?.findImportedPhoto || !url) return null\n const sourceKey = siteImportImageKey(url)\n try {\n const held = await ingest.findImportedPhoto({ hostId: input.hostId, sourceKey })\n if (held) return { mediaId: held.mediaId, src: held.src, alt: input.alt, from: url, ...(held.width ? { width: held.width } : {}), ...(held.height ? { height: held.height } : {}) }\n const fetched = await (deps.fetch ?? fetchPublicTextDetailed)(url, {\n maxBytes: AI_SITE_IMPORT_LOGO_MAX_BYTES,\n accept: /^image\\//i,\n acceptHeader: 'image/svg+xml,image/png,image/webp,image/jpeg;q=0.9',\n userAgent: AGLYN_BOT_USER_AGENT,\n timeoutMs: LOOK_READ_MS,\n refuseOver: true,\n binary: true,\n ...(input.signal ? { signal: input.signal } : {}),\n ...(input.headersFor ? { headersFor: input.headersFor } : {}),\n })\n if (!fetched.ok || !fetched.data?.byteLength) return null\n const type = fetched.contentType.split(';')[0].trim().toLowerCase()\n let bytes: Uint8Array | null = fetched.data\n let contentType = type\n if (type === 'image/svg+xml' || input.logo.type === 'svg') {\n bytes = await (deps.rasterize ?? rasterizeHere)(fetched.data, AI_SITE_IMPORT_LOGO_HEIGHT).catch(() => null)\n contentType = 'image/png'\n } else if (!/^image\\/(?:png|jpeg|webp|gif)$/.test(type)) {\n return null\n }\n if (!bytes?.byteLength) return null\n const stored = await ingest.ingest({\n hostId: input.hostId,\n uid: input.uid,\n fileName: `logo.${contentType === 'image/jpeg' ? 'jpg' : contentType.split('/')[1]}`,\n contentType,\n bytes,\n ...(input.alt ? { alt: input.alt.slice(0, 250) } : {}),\n importedFrom: { key: sourceKey, url, domain: input.domain },\n })\n if (!stored.ok) return null\n return { mediaId: stored.mediaId, src: stored.src, alt: input.alt, from: url, ...(stored.width ? { width: stored.width } : {}), ...(stored.height ? { height: stored.height } : {}) }\n } catch (error) {\n console.warn('[site-import] the logo could not be brought in', { hostId: input.hostId, error })\n return null\n }\n}\n\n/** The look pass (AGL-4084): the theme, the logo and the listing, from the owner's own home page. No model. */\nexport async function aiRunSiteImportLook(input: AiSiteImportLookInput, deps: AiSiteImportLookDeps = {}): Promise<AiSiteImportLook> {\n const { firestore, hostId, home } = input\n const headersFor = await aiSiteImportLookHeaders(input.domain, deps)\n const seed = aiSiteSeed(input.jobId)\n const page = await readSiteImportLookPage(home.url, { ...(deps.fetch ? { fetch: deps.fetch } : {}), headersFor, ...(input.signal ? { signal: input.signal } : {}) })\n // The source's own signals, its brand colors as it draws them solid.\n const signals = page ? aiSiteInspirationSignals(page) : null\n const site = signals && page ? { ...signals, colors: aiSiteImportBrandColors(page) } : signals\n const avoid = await (deps.others ?? aiWorkspaceSiteStyles)(firestore, { orgId: input.orgId, hostId })\n // What the reader found of its look, its buttons, ground and fonts (AGL-4108).\n const facts = home.look ?? null\n const fonts = await aiSiteImportSourceFonts(facts, deps.fontFacts)\n const style = aiSiteImportStyle(site, { seed, description: `${home.siteName} ${home.description}`, avoid, facts, fonts })\n let theme: AiSiteImportLook['theme'] = 'unread'\n if (style) {\n const saved = await (deps.save ?? aiSaveSiteTheme)(firestore, { hostId, jobId: input.jobId, style })\n if (saved.write === 'missing') throw new Error(`site ${hostId} vanished while its look was brought in`)\n theme = saved.write === 'kept' ? 'kept' : 'applied'\n }\n const ingest = deps.ingest ?? null\n const logo = home.brand.logo\n ? await aiSiteImportLogo(\n { hostId, uid: input.uid, domain: input.domain, logo: home.brand.logo, alt: home.siteName || home.brand.text, headersFor, ...(input.signal ? { signal: input.signal } : {}) },\n { ingest, ...(deps.fetch ? { fetch: deps.fetch } : {}), ...(deps.rasterize ? { rasterize: deps.rasterize } : {}) },\n )\n : null\n // The site's listing in its own words, only over the one it was created with.\n let seo = false\n const host = await (deps.readHost ?? readHostDoc)(firestore, hostId)\n if (host) {\n const updates: Record<string, unknown> = aiSiteImportListingUpdates(host['seo'], host['displayName'], home)\n if (home.ogImage && shareOpen(host['seo'])) {\n const share = await bringInSiteImportPhotos(\n { hostId, uid: input.uid, domain: input.domain, images: [{ url: home.ogImage, alt: '' }], deadlineAt: Date.now() + LOOK_READ_MS, ...(input.signal ? { signal: input.signal } : {}) },\n { ingest, ...(deps.fetch ? { fetch: deps.fetch } : {}) },\n )\n const photo = share.assets[0]\n if (photo) {\n updates['seo.image'] = photo.src\n if (photo.width) updates['seo.imageWidth'] = photo.width\n if (photo.height) updates['seo.imageHeight'] = photo.height\n }\n }\n if (Object.keys(updates).length) {\n await (deps.writeHost ?? writeHostDoc)(firestore, hostId, updates)\n seo = true\n }\n }\n const drawn = style\n ? {\n ...(typeof style.headerAlign === 'string' ? { headerAlign: style.headerAlign } : {}),\n ...(typeof style.rhythm === 'string' ? { rhythm: style.rhythm } : {}),\n ...(typeof style.motion === 'string' ? { motion: style.motion } : {}),\n }\n : null\n const colors = [...new Set([...(facts ? [facts.buttonFill, facts.link].filter(colorful).map((hex) => hex.toLowerCase()) : []), ...(site?.colors ?? [])])]\n return { at: input.at, theme, colors, logo, seo, seed: style ? style.seed : seed, style: drawn }\n}\n\n/**\n * The headers the look's reads of the source go with, per address (AGL-4108),\n * exactly as the crawl's (`siteRequestHeaders`): the caller's where it hands\n * them in, else the edge's own, so a site this platform serves is read past\n * its challenge however the step was registered; and nothing for any address\n * off the site's two origins, a redirect hop's included. Never throws.\n *\n * The 10/29 look read went without the header twice over: no caller handed\n * one in, and the read passed headers the fetch does not take, which is why\n * this is a function the fetch asks for every hop (`headersFor`).\n */\nexport async function aiSiteImportLookHeaders(\n domain: string,\n deps: Pick<AiSiteImportLookDeps, 'headersFor' | 'edgeHeadersFor'>,\n): Promise<SiteImportHeadersFor> {\n const origins = siteImportOrigins(domain)\n let inner: SiteImportHeadersFor = NO_SITE_IMPORT_HEADERS\n try {\n if (deps.headersFor) {\n const headers = await deps.headersFor(domain)\n if (headers && Object.keys(headers).length) inner = () => headers\n } else {\n inner = await (deps.edgeHeadersFor ?? siteImportEdgeHeadersFor)(origins)\n }\n } catch (error) {\n console.warn('[site-import] the edge headers could not be read; reading the look without them', { domain, error })\n }\n return (url: URL) => (origins.includes(url.origin) ? inner(url) : {})\n}\n"],"names":["themeFontFacts","aiSiteListingSeeded","aiSiteKindFor","aiHexOklch","aiSiteSeed","aiSiteStyleFor","aiSiteIsStarterSrc","AGLYN_BOT_USER_AGENT","siteImportOrigins","NO_SITE_IMPORT_HEADERS","siteImportEdgeHeadersFor","bringInSiteImportPhotos","siteImportImageKey","siteImportLogoUrl","aiSaveSiteTheme","aiWorkspaceSiteStyles","aiSiteInspirationLookPins","aiSiteInspirationSignals","AI_THEME_BRAND_COLORS_PER_SOURCE","AI_THEME_REFERENCE_MAX_BYTES","AI_THEME_REFERENCE_STYLESHEETS","AI_THEME_STYLESHEET_MAX_BYTES","AI_THEME_THEME_COLOR_WEIGHT","fetchPublicTextDetailed","rankBrandColors","stylesheetUrlsOf","AI_SITE_IMPORT_LOGO_HEIGHT","AI_SITE_IMPORT_LOGO_MAX_BYTES","LOOK_READ_MS","readSiteImportLookPage","url","input","fetchText","fetch","headersFor","read","address","maxBytes","accept","userAgent","timeoutMs","signal","catch","page","ok","stylesheets","sheet","text","slice","css","push","html","SOLID_HEX","SOLID_RGB","THEME_COLOR","hexOf","value","raw","trim","toLowerCase","test","aiSiteImportBrandColors","match","weights","Map","add","hex","weight","set","get","matchAll","alpha","undefined","endsWith","Number","channels","map","Math","min","toString","padStart","join","COLORFUL_CHROMA","colorful","C","aiSiteImportBrandOf","facts","fill","buttonFill","link","brand","accent","aiSiteImportStyle","site","own","kind","description","pins","v","sites","unread","seed","hue","round","h","radius","buttonRadius","isFinite","buttons","corners","style","answer","avoid","background","fonts","sourceFonts","aiSiteImportSourceFonts","lookup","body","loaded","Set","google","family","resolve","known","category","has","heading","headings","aiSiteImportListingUpdates","seo","displayName","home","seeded","current","updates","title","replace","shareOpen","image","readHostDoc","firestore","hostId","snapshot","collection","doc","exists","data","writeHostDoc","update","rasterizeHere","svg","height","images","origin","aiImageReadsOrigin","requestAiLogoRaster","rasterizeAiLogoSvg","aiSiteImportLogo","deps","ingest","logo","findImportedPhoto","sourceKey","fetched","held","mediaId","src","alt","from","width","acceptHeader","refuseOver","binary","byteLength","type","contentType","split","bytes","rasterize","stored","uid","fileName","importedFrom","key","domain","error","console","warn","aiRunSiteImportLook","aiSiteImportLookHeaders","jobId","signals","colors","others","orgId","look","fontFacts","siteName","theme","saved","save","write","Error","host","readHost","ogImage","share","deadlineAt","Date","now","photo","assets","Object","keys","length","writeHost","drawn","headerAlign","rhythm","motion","filter","at","origins","inner","headers","edgeHeadersFor","includes"],"mappings":"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"}
@@ -22,7 +22,13 @@ export interface AiSiteImportPublishDeps {
22
22
  /** Whether the site is in preview now. */
23
23
  inPreview?: (firestore: FirebaseFirestore.Firestore, hostId: string) => Promise<boolean>;
24
24
  }
25
- /** The pages of the import a link may not go to yet: every one not in, the wave's own failures included. */
25
+ /**
26
+ * The pages of the import that will never be in (AGL-4107): left out, or
27
+ * settled without coming in — a wave's own failures included. A page of a
28
+ * wave still to come is not one: a link to it keeps its place and shows its
29
+ * words, which the site's pages show for a page that is not live, and goes
30
+ * there once that page's wave lands.
31
+ */
26
32
  export declare function aiSiteImportUnbuilt(state: AiSiteImportState, items: readonly AiJobItemLedger[]): string[];
27
33
  /** The wave's pages that came in, as the outputs that publish them, in the wave's order. */
28
34
  export declare function aiSiteImportWaveOutputs(state: AiSiteImportState, wave: AiSiteImportWave, items: readonly AiJobItemLedger[], outputs: readonly AiJobOutput[]): AiJobOutput[];
@@ -21,8 +21,10 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
21
21
  * sees its notice, and its team sees the whole site. So when a wave closes,
22
22
  * its pages go live INTO THE PREVIEW through the same writes a guided
23
23
  * start's publish makes (`aiPublishGuidedSite`): each page takes its
24
- * address, Home takes `/`, the links to a page still a draft come out, and
25
- * the cached pages go. The team opens the site and sees the wave; the public
24
+ * address, Home takes `/`, a link to a page that will not come in goes
25
+ * nowhere with its words kept, and the cached pages go. A link to a page of a
26
+ * later wave keeps its place (AGL-4107): until that page is live a visitor
27
+ * sees its words, and once the page's wave lands it goes there. The team opens the site and sees the wave; the public
26
28
  * still sees the notice until the person makes the site public.
27
29
  *
28
30
  * Only into a preview. A site that is not in preview keeps the wave's pages
@@ -39,12 +41,22 @@ async function readInPreview(firestore, hostId) {
39
41
  });
40
42
  }
41
43
  const delivered = (row)=>(row == null ? void 0 : row.status) === 'succeeded' || (row == null ? void 0 : row.status) === 'degraded';
42
- /** The pages of the import a link may not go to yet: every one not in, the wave's own failures included. */ export function aiSiteImportUnbuilt(state, items) {
44
+ const open = (row)=>(row == null ? void 0 : row.status) === 'pending' || (row == null ? void 0 : row.status) === 'running';
45
+ /**
46
+ * The pages of the import that will never be in (AGL-4107): left out, or
47
+ * settled without coming in — a wave's own failures included. A page of a
48
+ * wave still to come is not one: a link to it keeps its place and shows its
49
+ * words, which the site's pages show for a page that is not live, and goes
50
+ * there once that page's wave lands.
51
+ */ export function aiSiteImportUnbuilt(state, items) {
43
52
  const rows = new Map(items.map((row)=>[
44
53
  row.slot,
45
54
  row
46
55
  ]));
47
- return state.pages.filter((page)=>!delivered(rows.get(page.slot))).map((page)=>page.draftId);
56
+ return state.pages.filter((page)=>{
57
+ const row = rows.get(page.slot);
58
+ return page.refused || !delivered(row) && !open(row);
59
+ }).map((page)=>page.draftId);
48
60
  }
49
61
  /** The wave's pages that came in, as the outputs that publish them, in the wave's order. */ export function aiSiteImportWaveOutputs(state, wave, items, outputs) {
50
62
  const rows = new Map(items.map((row)=>[
@@ -80,8 +92,10 @@ const delivered = (row)=>(row == null ? void 0 : row.status) === 'succeeded' ||
80
92
  job: input.job,
81
93
  outputs: pages,
82
94
  now: input.now,
83
- // The links to a page still to come, or one that did not come in, come out.
95
+ // The links to a page that did not come in go nowhere, their words kept;
96
+ // a page still to come keeps its links (AGL-4107).
84
97
  unbuiltScreenIds: aiSiteImportUnbuilt(input.state, input.items),
98
+ keepLinkWords: true,
85
99
  siteOrder: input.state.pages.map((page)=>page.draftId)
86
100
  }, (already == null ? void 0 : already.published.length) ? {
87
101
  retry: true
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-site-import-publish.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * EACH WAVE LANDS IN THE SITE'S PREVIEW (AGL-4084).\n *\n * A site brought in starts in preview (AGL-4086, `host.preview`): the public\n * sees its notice, and its team sees the whole site. So when a wave closes,\n * its pages go live INTO THE PREVIEW through the same writes a guided\n * start's publish makes (`aiPublishGuidedSite`): each page takes its\n * address, Home takes `/`, the links to a page still a draft come out, and\n * the cached pages go. The team opens the site and sees the wave; the public\n * still sees the notice until the person makes the site public.\n *\n * Only into a preview. A site that is not in preview keeps the wave's pages\n * as drafts, as every import did before: the public never sees a page no\n * person has looked at. And `site_published` is not sent here: the site is\n * published when its switch makes it public (AGL-4086), not when a wave\n * lands.\n *=========================================*/\n\nimport { isHostInPreview } from '@aglyn/aglyn/app-utils/site-preview'\nimport type { AiJob, AiJobItemLedger, AiJobOutput, AiJobSitePublish } from '../model/ai-jobs.types'\nimport type { AiSiteImportState, AiSiteImportWave } from '../model/ai-site-import'\nimport { aiMergeSitePublish, aiPublishGuidedSite, type AiSitePublishDeps } from './ai-site-publish'\n\nexport interface AiSiteImportPublishDeps {\n publish?: typeof aiPublishGuidedSite\n /** Whether the site is in preview now. */\n inPreview?: (firestore: FirebaseFirestore.Firestore, hostId: string) => Promise<boolean>\n}\n\nasync function readInPreview(firestore: FirebaseFirestore.Firestore, hostId: string): Promise<boolean> {\n const host = await firestore.collection('hosts').doc(hostId).get()\n return host.exists && isHostInPreview({ preview: host.get('preview') })\n}\n\nconst delivered = (row: AiJobItemLedger | undefined) => row?.status === 'succeeded' || row?.status === 'degraded'\n\n/** The pages of the import a link may not go to yet: every one not in, the wave's own failures included. */\nexport function aiSiteImportUnbuilt(state: AiSiteImportState, items: readonly AiJobItemLedger[]): string[] {\n const rows = new Map(items.map((row) => [row.slot, row]))\n return state.pages.filter((page) => !delivered(rows.get(page.slot))).map((page) => page.draftId)\n}\n\n/** The wave's pages that came in, as the outputs that publish them, in the wave's order. */\nexport function aiSiteImportWaveOutputs(\n state: AiSiteImportState,\n wave: AiSiteImportWave,\n items: readonly AiJobItemLedger[],\n outputs: readonly AiJobOutput[],\n): AiJobOutput[] {\n const rows = new Map(items.map((row) => [row.slot, row]))\n return wave.slots.flatMap((slot) => {\n const page = state.pages.find((entry) => entry.slot === slot)\n if (!page || !delivered(rows.get(slot))) return []\n const output = outputs.find((entry) => entry.resource === 'screen' && entry.id === page.draftId)\n return output ? [output] : []\n })\n}\n\n/**\n * A closed wave's pages, live in the site's preview (AGL-4084). `null` when\n * nothing went live: the site is not in preview, or none of the wave's pages\n * came in. The job's earlier publish is carried in the answer.\n */\nexport async function aiPublishImportedWave(\n firestore: FirebaseFirestore.Firestore,\n input: {\n job: Pick<AiJob, '$id' | 'orgId' | 'hostId'> & Partial<Pick<AiJob, 'inputs' | 'sitePublish'>>\n state: AiSiteImportState\n wave: AiSiteImportWave\n items: readonly AiJobItemLedger[]\n outputs: readonly AiJobOutput[]\n now: Date\n },\n deps: AiSiteImportPublishDeps = {},\n): Promise<AiJobSitePublish | null> {\n const hostId = input.job.hostId\n if (!hostId) return null\n const pages = aiSiteImportWaveOutputs(input.state, input.wave, input.items, input.outputs)\n if (!pages.length) return null\n if (!(await (deps.inPreview ?? readInPreview)(firestore, hostId))) return null\n const publishDeps: AiSitePublishDeps = {\n // Published when its switch makes it public (AGL-4086), never when a wave lands.\n sendSitePublished: async () => undefined,\n }\n const already = input.job.sitePublish ?? null\n const result = await (deps.publish ?? aiPublishGuidedSite)(\n firestore,\n {\n job: input.job,\n outputs: pages,\n now: input.now,\n // The links to a page still to come, or one that did not come in, come out.\n unbuiltScreenIds: aiSiteImportUnbuilt(input.state, input.items),\n siteOrder: input.state.pages.map((page) => page.draftId),\n // A later wave joins what is live: it never takes the home address again.\n ...(already?.published.length ? { retry: true } : {}),\n },\n publishDeps,\n )\n return already ? aiMergeSitePublish(already, result) : result\n}\n"],"names":["isHostInPreview","aiMergeSitePublish","aiPublishGuidedSite","readInPreview","firestore","hostId","host","collection","doc","get","exists","preview","delivered","row","status","aiSiteImportUnbuilt","state","items","rows","Map","map","slot","pages","filter","page","draftId","aiSiteImportWaveOutputs","wave","outputs","slots","flatMap","find","entry","output","resource","id","aiPublishImportedWave","input","deps","job","length","inPreview","publishDeps","sendSitePublished","undefined","already","sitePublish","result","publish","now","unbuiltScreenIds","siteOrder","published","retry"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-site-import-publish.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * EACH WAVE LANDS IN THE SITE'S PREVIEW (AGL-4084).\n *\n * A site brought in starts in preview (AGL-4086, `host.preview`): the public\n * sees its notice, and its team sees the whole site. So when a wave closes,\n * its pages go live INTO THE PREVIEW through the same writes a guided\n * start's publish makes (`aiPublishGuidedSite`): each page takes its\n * address, Home takes `/`, a link to a page that will not come in goes\n * nowhere with its words kept, and the cached pages go. A link to a page of a\n * later wave keeps its place (AGL-4107): until that page is live a visitor\n * sees its words, and once the page's wave lands it goes there. The team opens the site and sees the wave; the public\n * still sees the notice until the person makes the site public.\n *\n * Only into a preview. A site that is not in preview keeps the wave's pages\n * as drafts, as every import did before: the public never sees a page no\n * person has looked at. And `site_published` is not sent here: the site is\n * published when its switch makes it public (AGL-4086), not when a wave\n * lands.\n *=========================================*/\n\nimport { isHostInPreview } from '@aglyn/aglyn/app-utils/site-preview'\nimport type { AiJob, AiJobItemLedger, AiJobOutput, AiJobSitePublish } from '../model/ai-jobs.types'\nimport type { AiSiteImportState, AiSiteImportWave } from '../model/ai-site-import'\nimport { aiMergeSitePublish, aiPublishGuidedSite, type AiSitePublishDeps } from './ai-site-publish'\n\nexport interface AiSiteImportPublishDeps {\n publish?: typeof aiPublishGuidedSite\n /** Whether the site is in preview now. */\n inPreview?: (firestore: FirebaseFirestore.Firestore, hostId: string) => Promise<boolean>\n}\n\nasync function readInPreview(firestore: FirebaseFirestore.Firestore, hostId: string): Promise<boolean> {\n const host = await firestore.collection('hosts').doc(hostId).get()\n return host.exists && isHostInPreview({ preview: host.get('preview') })\n}\n\nconst delivered = (row: AiJobItemLedger | undefined) => row?.status === 'succeeded' || row?.status === 'degraded'\n\nconst open = (row: AiJobItemLedger | undefined) => row?.status === 'pending' || row?.status === 'running'\n\n/**\n * The pages of the import that will never be in (AGL-4107): left out, or\n * settled without coming in — a wave's own failures included. A page of a\n * wave still to come is not one: a link to it keeps its place and shows its\n * words, which the site's pages show for a page that is not live, and goes\n * there once that page's wave lands.\n */\nexport function aiSiteImportUnbuilt(state: AiSiteImportState, items: readonly AiJobItemLedger[]): string[] {\n const rows = new Map(items.map((row) => [row.slot, row]))\n return state.pages\n .filter((page) => {\n const row = rows.get(page.slot)\n return page.refused || (!delivered(row) && !open(row))\n })\n .map((page) => page.draftId)\n}\n\n/** The wave's pages that came in, as the outputs that publish them, in the wave's order. */\nexport function aiSiteImportWaveOutputs(\n state: AiSiteImportState,\n wave: AiSiteImportWave,\n items: readonly AiJobItemLedger[],\n outputs: readonly AiJobOutput[],\n): AiJobOutput[] {\n const rows = new Map(items.map((row) => [row.slot, row]))\n return wave.slots.flatMap((slot) => {\n const page = state.pages.find((entry) => entry.slot === slot)\n if (!page || !delivered(rows.get(slot))) return []\n const output = outputs.find((entry) => entry.resource === 'screen' && entry.id === page.draftId)\n return output ? [output] : []\n })\n}\n\n/**\n * A closed wave's pages, live in the site's preview (AGL-4084). `null` when\n * nothing went live: the site is not in preview, or none of the wave's pages\n * came in. The job's earlier publish is carried in the answer.\n */\nexport async function aiPublishImportedWave(\n firestore: FirebaseFirestore.Firestore,\n input: {\n job: Pick<AiJob, '$id' | 'orgId' | 'hostId'> & Partial<Pick<AiJob, 'inputs' | 'sitePublish'>>\n state: AiSiteImportState\n wave: AiSiteImportWave\n items: readonly AiJobItemLedger[]\n outputs: readonly AiJobOutput[]\n now: Date\n },\n deps: AiSiteImportPublishDeps = {},\n): Promise<AiJobSitePublish | null> {\n const hostId = input.job.hostId\n if (!hostId) return null\n const pages = aiSiteImportWaveOutputs(input.state, input.wave, input.items, input.outputs)\n if (!pages.length) return null\n if (!(await (deps.inPreview ?? readInPreview)(firestore, hostId))) return null\n const publishDeps: AiSitePublishDeps = {\n // Published when its switch makes it public (AGL-4086), never when a wave lands.\n sendSitePublished: async () => undefined,\n }\n const already = input.job.sitePublish ?? null\n const result = await (deps.publish ?? aiPublishGuidedSite)(\n firestore,\n {\n job: input.job,\n outputs: pages,\n now: input.now,\n // The links to a page that did not come in go nowhere, their words kept;\n // a page still to come keeps its links (AGL-4107).\n unbuiltScreenIds: aiSiteImportUnbuilt(input.state, input.items),\n keepLinkWords: true,\n siteOrder: input.state.pages.map((page) => page.draftId),\n // A later wave joins what is live: it never takes the home address again.\n ...(already?.published.length ? { retry: true } : {}),\n },\n publishDeps,\n )\n return already ? aiMergeSitePublish(already, result) : result\n}\n"],"names":["isHostInPreview","aiMergeSitePublish","aiPublishGuidedSite","readInPreview","firestore","hostId","host","collection","doc","get","exists","preview","delivered","row","status","open","aiSiteImportUnbuilt","state","items","rows","Map","map","slot","pages","filter","page","refused","draftId","aiSiteImportWaveOutputs","wave","outputs","slots","flatMap","find","entry","output","resource","id","aiPublishImportedWave","input","deps","job","length","inPreview","publishDeps","sendSitePublished","undefined","already","sitePublish","result","publish","now","unbuiltScreenIds","keepLinkWords","siteOrder","published","retry"],"mappings":"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"}