@aglyn/plugins-ai 1.0.0-beta.240 → 1.0.0-beta.241

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 (87) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-job-links.d.ts +17 -0
  3. package/src/lib/components/ai-job-links.js +63 -0
  4. package/src/lib/components/ai-job-links.js.map +1 -1
  5. package/src/lib/components/ai-job-plan.component.js +8 -1
  6. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  7. package/src/lib/components/ai-site-build-page.component.js +7 -1
  8. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  9. package/src/lib/components/ai-store-finish-card.component.d.ts +28 -0
  10. package/src/lib/components/ai-store-finish-card.component.js +116 -0
  11. package/src/lib/components/ai-store-finish-card.component.js.map +1 -0
  12. package/src/lib/components/assist-build-card.component.js +12 -1
  13. package/src/lib/components/assist-build-card.component.js.map +1 -1
  14. package/src/lib/jobs/ai-job-build-step.d.ts +45 -1
  15. package/src/lib/jobs/ai-job-build-step.js +283 -7
  16. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  17. package/src/lib/jobs/ai-job-drafts.d.ts +9 -1
  18. package/src/lib/jobs/ai-job-drafts.js +4 -1
  19. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  20. package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
  21. package/src/lib/jobs/ai-job-layout-language.js +4 -2
  22. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  23. package/src/lib/jobs/ai-job-layout-step.js +3 -1
  24. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  25. package/src/lib/jobs/ai-job-page-language.d.ts +2 -1
  26. package/src/lib/jobs/ai-job-page-language.js +19 -7
  27. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  28. package/src/lib/jobs/ai-job-page-sections.d.ts +11 -0
  29. package/src/lib/jobs/ai-job-page-sections.js +40 -3
  30. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  31. package/src/lib/jobs/ai-job-page-step.d.ts +2 -1
  32. package/src/lib/jobs/ai-job-page-step.js +19 -8
  33. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  34. package/src/lib/jobs/ai-job-plan-step.d.ts +12 -0
  35. package/src/lib/jobs/ai-job-plan-step.js +41 -7
  36. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-site-datasets.d.ts +76 -2
  38. package/src/lib/jobs/ai-job-site-datasets.js +206 -13
  39. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  40. package/src/lib/jobs/ai-job-site-step.d.ts +18 -2
  41. package/src/lib/jobs/ai-job-site-step.js +109 -22
  42. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  43. package/src/lib/jobs/ai-job-site-store-pages.d.ts +75 -0
  44. package/src/lib/jobs/ai-job-site-store-pages.js +195 -0
  45. package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -0
  46. package/src/lib/jobs/ai-job-store-links-layout.d.ts +71 -0
  47. package/src/lib/jobs/ai-job-store-links-layout.js +95 -0
  48. package/src/lib/jobs/ai-job-store-links-layout.js.map +1 -0
  49. package/src/lib/jobs/ai-layout-stock-photos.d.ts +69 -3
  50. package/src/lib/jobs/ai-layout-stock-photos.js +603 -42
  51. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  52. package/src/lib/jobs/ai-site-publish.d.ts +18 -0
  53. package/src/lib/jobs/ai-site-publish.js +76 -15
  54. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  55. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.d.ts +43 -0
  56. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.js +61 -0
  57. package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.js.map +1 -0
  58. package/src/lib/layout-language/ai-layout-compiler.js +25 -2
  59. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  60. package/src/lib/layout-language/ai-layout-frame.d.ts +10 -0
  61. package/src/lib/layout-language/ai-layout-frame.js +58 -4
  62. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  63. package/src/lib/layout-language/ai-layout-listings.d.ts +6 -0
  64. package/src/lib/layout-language/ai-layout-listings.js +3 -0
  65. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  66. package/src/lib/model/ai-build-job.d.ts +2 -0
  67. package/src/lib/model/ai-build-job.js +23 -3
  68. package/src/lib/model/ai-build-job.js.map +1 -1
  69. package/src/lib/model/ai-build-plan.d.ts +7 -0
  70. package/src/lib/model/ai-build-plan.js.map +1 -1
  71. package/src/lib/model/ai-build-progress.js +3 -1
  72. package/src/lib/model/ai-build-progress.js.map +1 -1
  73. package/src/lib/model/ai-build-store-pages.d.ts +133 -0
  74. package/src/lib/model/ai-build-store-pages.js +209 -0
  75. package/src/lib/model/ai-build-store-pages.js.map +1 -0
  76. package/src/lib/model/ai-layout-store-links.d.ts +64 -0
  77. package/src/lib/model/ai-layout-store-links.js +165 -0
  78. package/src/lib/model/ai-layout-store-links.js.map +1 -0
  79. package/src/lib/model/ai-site-inventory.d.ts +5 -0
  80. package/src/lib/model/ai-site-inventory.js.map +1 -1
  81. package/src/lib/model/ai-site-store-pages.d.ts +192 -0
  82. package/src/lib/model/ai-site-store-pages.js +383 -0
  83. package/src/lib/model/ai-site-store-pages.js.map +1 -0
  84. package/src/lib/runtime/site-inventory.js +8 -3
  85. package/src/lib/runtime/site-inventory.js.map +1 -1
  86. package/src/lib/testing/stand-in-dataset-index.js +8 -4
  87. package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-layout-stock-photos.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 {\n pluginMediaIngest,\n type PluginMediaIngest,\n type PluginMediaIngestResult,\n} from '@aglyn/aglyn/plugin-manager/plugin-media-ingest'\nimport {\n STOCK_PHOTO_QUERY_MAX_CHARS,\n stockPhotoProvider,\n stockPhotoSourceKey,\n type StockPhoto,\n type StockPhotoOrientation,\n type StockPhotoProvider,\n type StockPhotoSearchRequest,\n} from '@aglyn/aglyn/plugin-manager/stock-photo-provider'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport {\n aiLayoutSeedNumber,\n type AiLayoutPicturePhoto,\n type AiLayoutPictureSlot,\n type AiLayoutPictureSource,\n} from '../layout-language/ai-layout-pictures'\n\n/**\n * STOCK PHOTOS FOR A LANGUAGE PAGE'S PICTURE SLOTS (AGL-3660).\n *\n * The source `aiResolveLayoutPictures` asks before it falls back to the\n * starter photos. It names no library: it asks core for the deployment's\n * stock photo provider (`core.stock-photos`) and for the media library's\n * server door (`core.media-ingest`), and builds no source at all when either\n * is missing, so a deployment without a key behaves exactly as before.\n *\n * What the live starts of 2026-10-10 taught (Juniper Clay Works, a ceramic\n * artist's portfolio, and Willow Wick Candles, a candle store), each photo\n * traced through the library's cached answers:\n *\n * - The site's words were read as \"portfolio\" and \"online\" — the kind of\n * SITE, not the craft — so every search said \"serving bowl portfolio\" and\n * the hero asked for \"portfolio\": a wallet, fashion models, and a blank\n * paper mockup that read as an empty panel beside the brushes.\n * - A hit naming the object anywhere won: a robin tagged \"food bowl\" filled\n * \"Speckled serving bowl\", a lilac branch tagged \"bud, vase\" filled \"Tall\n * bud vase\", letterpress type tagged \"set\" filled \"Nesting bowl set\" (its\n * head noun was read as \"set\"), and an antique tea set tagged \"brown\n * candle\" filled \"Candle Gift Set\".\n * - The page's 20 seconds ran out after four photos (each copy took ~5s, one\n * after another), and the slots left took the starter photos: the laptop\n * desk with pink roses under \"Candle Wick Trimmer\" and a work card, and the\n * windowsill plants under another.\n *\n * So, for each slot:\n *\n * 1. **The site's terms** ({@link aiStockSiteTerms}): the business is the\n * first clause of its type that says more than what kind of site it is\n * (\"ceramic artist\" from \"a portfolio for a ceramic artist who makes\n * stoneware bowls\"), its craft one word (\"ceramic\"), and its DOMAIN the\n * words a photo of its world names: the craft, the business type's own\n * nouns (\"stoneware\", \"soy\") and, for common crafts, their kin (\"pottery\",\n * \"clay\", \"wax\"). Its broad searches ask for the world itself (\"handmade\n * ceramics\", \"pottery\").\n * 2. **Search.** A picture of a thing asks for its subject — the noun phrase\n * of the caption, filler dropped — qualified by the craft where it does\n * not already name the domain (\"ceramic serving bowl\"), then the craft and\n * the object alone (\"ceramic bowl\"), then the broad searches. A hero asks\n * for the business, then broad; an about picture for the business's\n * people, then broad. The orientation and a minimum size come from the\n * slot's frame.\n * 3. **Accept, or reject.** A hit tagged as a mockup, a blank or a template\n * never fills anything. A picture of a thing takes only a hit naming its\n * OBJECT (the head noun, past \"set\", \"gift\", \"kit\": \"bowl\" in \"nesting\n * bowl set\", \"candle\" in \"candle gift set\") AND either the site's domain\n * or the subject's own two-word phrase (\"bud vase\"). A broad, hero or\n * about search takes only a hit naming the domain. The accepted are\n * ranked: the object in the hit's first tags (what the photo is OF) over\n * a mention in its last, the subject's other words, the domain. Among\n * equals the job's seed picks, so two sites differ and one job repeats.\n * 4. **Never twice.** A photo is placed once per job: not twice on a page,\n * and not on a page when another page (or another pass, in this process)\n * of the same job already placed it. The caller hands in what the job's\n * other pages hold (`avoid`); a pass repeated over its own page keeps its\n * own.\n * 5. **Keep, all at once.** The choosing is done first, slot by slot; the\n * copies then run side by side, a few at a time, so a page of eight\n * pictures takes about as long as two. A photo the site's library already\n * holds (by its source key) is reused; otherwise its bytes are copied\n * into the site's own library as the member who started the job, credited\n * to its photographer. A copy that fails tries the slot's next accepted\n * hit.\n *\n * A slot nothing acceptable answered — the library down, refusing, or out\n * of time — is left `null` for the starter photos. A named object is never\n * given one while the library answers anything of the site's world: the\n * broad searches come first. Nothing here throws, and none of it is an AI\n * model call: photos cost no AI credits.\n */\n\n/** Wall clock the whole page's photos may take, inside the step's own signal. */\nexport const AI_LAYOUT_STOCK_PHOTOS_BUDGET_MS = 30_000\n\n/** Searches one page may send to the provider; a repeated query is answered from the page's own memory. */\nexport const AI_LAYOUT_STOCK_SEARCHES_PER_PAGE = 18\n\n/** The hits a pick is made among. */\nexport const AI_LAYOUT_STOCK_PICK_WINDOW = 40\n\n/** The accepted hits a slot keeps in hand, in case a copy fails. */\nconst AI_LAYOUT_STOCK_TRIES_PER_SLOT = 3\n\n/** Copies made side by side. */\nconst AI_LAYOUT_STOCK_COPIES_AT_ONCE = 4\n\n/** The largest photo copied: inside one direct upload. */\nexport const AI_LAYOUT_STOCK_MAX_BYTES = 3 * 1024 * 1024\n\n/**\n * The unit job input naming the screen drafts the job's other pages were\n * written to, whose photos a page does not place again (AGL-3660).\n */\nexport const AI_STOCK_PHOTO_PAGES_INPUT = 'stockPhotoPages'\n\nconst words = (list: string) => new Set(list.trim().split(/\\s+/))\n\n/** Words that carry no subject. */\nconst STOPWORDS = words(\n 'a an the and or of in on at to for with from by into onto over under near our your their my his her its ' +\n 'this that these those is are be being was were who whom which where while as about than then very just ' +\n 'small local best new top quality premium family-owned family owned independent professional ' +\n 'business company services service page photo picture image showing shows show ' +\n 'someone people person one two three some many several',\n)\n\n/**\n * Words that describe a picture's look, not what it shows, dropped from a\n * subject. A word that is as often a noun (\"light\", \"set\") is not one.\n */\nconst FILLER = words(\n 'beautiful beautifully stunning gorgeous lovely pretty elegant modern contemporary rustic cozy cosy warm cool ' +\n 'soft bright natural simple minimal minimalist delicate unique special artisan artisanal handmade ' +\n 'hand-made hand-thrown handthrown hand-crafted handcrafted crafted hand-built handbuilt hand-poured ' +\n 'hand-painted hand-dyed hand-stitched poured finished styled perfect ' +\n 'fresh clean calm serene quiet peaceful vibrant colorful colourful sunlit sunny golden glossy matte textured ' +\n 'close-up closeup up view views detail details detailed shot shots overhead flat lay flatlay flat-lay ' +\n 'top-down wide angle featuring featured displayed arranged sitting resting placed standing lined ' +\n 'single pair collection assortment selection group few various array range large tiny big little ' +\n 'tall short wide narrow everyday classic signature original favorite favourite bestselling best-selling ' +\n 'small-batch batch limited edition custom bespoke ' +\n 'white black grey gray beige cream ivory brown tan blue green red pink yellow orange purple speckled ' +\n 'gentle airy inviting welcoming cheerful happy smiling friendly',\n)\n\n/** The nouns that name a place of business, not its craft: \"studio\" in \"ceramics studio\". */\nconst VENUES = words(\n 'studio studios shop shops store stores boutique company co workshop gallery atelier agency brand practice ' +\n 'salon house collective lab market services service firm clinic center centre parlor parlour bar ' +\n 'class classes lessons school academy',\n)\n\n/** The person who does the craft, not the craft: \"artist\" in \"ceramic artist\". */\nconst ROLES = words(\n 'artist artists maker makers artisan artisans owner owners founder founders creator creators',\n)\n\n/** Words naming what kind of SITE it is, never what it shows: \"portfolio\" in \"a portfolio for a ceramic artist\". */\nconst SITE_KINDS = words(\n 'portfolio portfolios website websites site sites blog blogs online web webshop ecommerce e-commerce ' +\n 'landing personal homepage storefront',\n)\n\n/** Verbs a business type uses to say what it does; what follows them is its domain, never its business. */\nconst DOING =\n /(?<![\\w-])(?:makes?|making|made|sells?|selling|sold|offers?|offering|creates?|creating|specializ(?:es|ing)|specialis(?:es|ing)|provides?|providing|featuring|showcasing)(?![\\w-])/g\n\n/** Where a business type's own clause ends. */\nconst BUSINESS_BREAK =\n /[,;:.()]|\\s[-–—]\\s|\\b(?:in|for|near|serving|based|located|that|which|who|with|from|since|and|&)\\b/\n\n/** A clause after one of these says where or for whom, never what: \"in Austin\", \"for busy parents\". */\nconst PLACE_OR_AUDIENCE =\n /^(?:in|near|serving|based|located|across|around|throughout|for)$/\n\n/**\n * Words naming a set or a wrapper of a thing, never the thing: the OBJECT\n * of \"nesting bowl set\" is the bowl, of \"candle gift set\" the candle.\n */\nconst WRAPPERS = words(\n 'set sets kit kits gift gifts bundle bundles pack packs box boxes trio duo pair collection sampler assortment ' +\n 'series range line group lot bunch',\n)\n\n/**\n * A hit tagged with any of these is never placed: a mockup's blank panel is\n * an empty frame on the page (the Juniper Clay hero), a template or a\n * graphic is not a photo of anything.\n */\nconst REJECTED_TAGS = words(\n 'mockup mock-up mock blank template templates clipart clip-art illustration vector logo icon ' +\n 'ai-generated generated render rendering 3d',\n)\n\n/**\n * The kin of a few common crafts: the words a photo of that world is\n * tagged with, and the broad searches that find it. A craft not listed\n * still has its own words; this only widens what counts as its world.\n */\nconst CRAFT_KIN: ReadonlyArray<{\n match: readonly string[]\n kin: readonly string[]\n broad: readonly string[]\n}> = [\n {\n match: [\n 'ceramic',\n 'ceramics',\n 'pottery',\n 'potter',\n 'stoneware',\n 'porcelain',\n 'earthenware',\n 'clay',\n ],\n kin: [\n 'ceramic',\n 'ceramics',\n 'pottery',\n 'potter',\n 'stoneware',\n 'porcelain',\n 'earthenware',\n 'clay',\n 'glaze',\n 'kiln',\n ],\n broad: ['handmade ceramics', 'pottery'],\n },\n {\n match: ['candle', 'candlemaker', 'candlemaking'],\n kin: [\n 'candle',\n 'candlelight',\n 'wax',\n 'soy',\n 'wick',\n 'scented',\n 'votive',\n 'candlestick',\n ],\n broad: ['handmade candles', 'scented candles'],\n },\n {\n match: [\n 'jewelry',\n 'jewellery',\n 'jeweler',\n 'jeweller',\n 'silversmith',\n 'goldsmith',\n ],\n kin: [\n 'jewelry',\n 'jewellery',\n 'ring',\n 'necklace',\n 'earring',\n 'bracelet',\n 'silver',\n 'gold',\n 'gemstone',\n ],\n broad: ['handmade jewelry', 'jewelry'],\n },\n {\n match: ['soap', 'soapmaker', 'skincare'],\n kin: ['soap', 'skincare', 'lotion', 'bath', 'spa', 'natural', 'cosmetic'],\n broad: ['handmade soap', 'natural skincare'],\n },\n {\n match: [\n 'wood',\n 'woodwork',\n 'woodworking',\n 'woodworker',\n 'carpenter',\n 'carpentry',\n 'furniture',\n 'joinery',\n ],\n kin: [\n 'wood',\n 'wooden',\n 'woodwork',\n 'timber',\n 'furniture',\n 'carpentry',\n 'oak',\n 'walnut',\n ],\n broad: ['woodworking', 'wooden furniture'],\n },\n {\n match: ['bakery', 'baker', 'bread', 'pastry', 'cake', 'patisserie'],\n kin: [\n 'bakery',\n 'bread',\n 'pastry',\n 'cake',\n 'baking',\n 'baked',\n 'dough',\n 'croissant',\n ],\n broad: ['bakery', 'fresh bread'],\n },\n {\n match: ['coffee', 'cafe', 'café', 'roaster', 'espresso'],\n kin: [\n 'coffee',\n 'cafe',\n 'espresso',\n 'latte',\n 'cappuccino',\n 'barista',\n 'beans',\n ],\n broad: ['coffee shop', 'coffee'],\n },\n {\n match: ['florist', 'flower', 'floral'],\n kin: ['flower', 'floral', 'bouquet', 'florist', 'bloom', 'blossom'],\n broad: ['florist', 'flower bouquet'],\n },\n {\n match: [\n 'knit',\n 'knitting',\n 'yarn',\n 'weaving',\n 'weaver',\n 'textile',\n 'textiles',\n 'quilt',\n 'quilting',\n 'sewing',\n ],\n kin: [\n 'knit',\n 'knitting',\n 'yarn',\n 'wool',\n 'weaving',\n 'textile',\n 'fabric',\n 'quilt',\n 'sewing',\n ],\n broad: ['handmade textiles', 'yarn'],\n },\n {\n match: ['leather', 'leatherwork', 'leathercraft'],\n kin: ['leather', 'wallet', 'bag', 'belt', 'stitching'],\n broad: ['leather craft', 'leather goods'],\n },\n]\n\n/** Lowercase words, letters only, without stopwords. */\nfunction contentWords(text: string): string[] {\n return text\n .toLowerCase()\n .replace(/https?:\\/\\/\\S+|\\S+@\\S+|\\+?\\d[\\d\\s().-]{5,}\\d/g, ' ')\n .replace(/[^a-zÀ-ɏ\\s-]/g, ' ')\n .split(/\\s+/)\n .map((word) => word.replace(/^-+|-+$/g, ''))\n .filter((word) => word.length > 1 && !STOPWORDS.has(word))\n}\n\n/**\n * A word's singular, for matching only: \"bowls\" and \"bowl\", \"vases\" and\n * \"vase\", \"berries\" and \"berry\", \"dishes\" and \"dish\" meet. Both sides of a\n * comparison are reduced the same way, so an odd stem still matches itself.\n */\nexport function aiStockStem(word: string): string {\n const lower = word.toLowerCase()\n if (lower.length > 4 && lower.endsWith('ies')) return `${lower.slice(0, -3)}y`\n if (lower.length > 4 && /(?:ches|shes|sses|xes|zes)$/.test(lower))\n return lower.slice(0, -2)\n if (lower.length > 3 && lower.endsWith('s') && !/(?:ss|us|is)$/.test(lower))\n return lower.slice(0, -1)\n return lower\n}\n\n/** A text's words as stems, in order; a hyphenated word counts as its parts. */\nfunction stemList(text: string): string[] {\n return text\n .toLowerCase()\n .split(/[^a-zÀ-ɏ]+/)\n .filter((word) => word.length > 1)\n .map(aiStockStem)\n}\n\n/** Where a noun phrase ends: punctuation, a preposition, a conjunction. */\nconst CLAUSE_BREAK =\n /[,;:.()]|\\s[-–—]\\s|\\b(?:with|on|in|at|against|beside|next|near|under|over|by|from|for|during|while|atop|inside|across|beneath|through|into|onto|amid|among|and|of|that|which|who)\\b|&/\n\n/** A business type's clauses, each with the word that opened it. */\nfunction businessClauses(\n businessType: string,\n): Array<{ opener: string; words: string[] }> {\n const text = businessType.toLowerCase().replace(DOING, ' | ')\n const clauses: Array<{ opener: string; words: string[] }> = []\n const breaker = new RegExp(`${BUSINESS_BREAK.source}|\\\\|`, 'g')\n let at = 0\n let opener = ''\n for (const match of text.matchAll(breaker)) {\n clauses.push({ opener, words: contentWords(text.slice(at, match.index)) })\n opener = match[0].trim()\n at = (match.index ?? 0) + match[0].length\n }\n clauses.push({ opener, words: contentWords(text.slice(at)) })\n return clauses\n}\n\n/** A clause's words that name the business: no site kind, no filler. */\nconst businessOf = (clause: readonly string[]) =>\n clause.filter((word) => !SITE_KINDS.has(word) && !FILLER.has(word))\n\n/**\n * The kind of business in a few words: the first clause of the business\n * type that says more than what kind of site it is, before a place, an\n * audience or what it makes, filler dropped, its last three words (the head\n * of an English noun phrase is at its end). \"ceramic artist\" from \"a\n * portfolio for a ceramic artist who makes stoneware bowls\"; \"candle shop\"\n * from \"a small-batch candle shop selling hand-poured soy candles online\".\n */\nexport function aiStockBusinessWords(businessType: string): string {\n for (const { words: clause } of businessClauses(businessType)) {\n const named = businessOf(clause)\n if (named.length) return named.slice(-3).join(' ')\n }\n return ''\n}\n\n/**\n * The business's craft in one word: the last of its words that is not the\n * place it is done in nor the person doing it, singular — \"ceramic\" from\n * \"ceramics studio\" or \"ceramic artist\", \"yoga\" from \"yoga studio\". Empty\n * when the business is only a place (\"a shop\").\n */\nexport function aiStockCraftWords(business: string): string {\n const named = business\n .split(/\\s+/)\n .filter(\n (word) =>\n word &&\n !VENUES.has(word) &&\n !ROLES.has(word) &&\n !FILLER.has(word) &&\n !SITE_KINDS.has(word),\n )\n const craft = named[named.length - 1]\n return craft ? aiStockStem(craft) : ''\n}\n\n/** What a site's photos are searched and judged by (AGL-3660). */\nexport interface AiStockSiteTerms {\n /** The kind of business, in a few words: \"ceramic artist\". */\n business: string\n /** Its craft, one singular word: \"ceramic\". */\n craft: string\n /**\n * The stems a photo of the site's world names: the business's own words,\n * what the things it makes are made of, and the craft's kin. Empty for a\n * site whose words name no world (a type left blank), and then no hit is\n * held to it.\n */\n domain: string[]\n /** The searches for the site's world itself, broadest last. */\n broad: string[]\n}\n\n/** The site's terms, read from its business type. */\nexport function aiStockSiteTerms(businessType: string): AiStockSiteTerms {\n const business = aiStockBusinessWords(businessType)\n const craft = aiStockCraftWords(business)\n const domain = new Set<string>()\n /** The things it makes, by their head nouns. */\n const made = new Set<string>()\n let named = false\n let making = false\n for (const { opener, words: clause } of businessClauses(businessType)) {\n if (opener === '|') making = true\n // Where it is, and who it is for, are not its world — once something has named it.\n if (named && PLACE_OR_AUDIENCE.test(opener)) {\n making = false\n continue\n }\n const kept = businessOf(clause).filter(\n (word) => !VENUES.has(word) && !ROLES.has(word),\n )\n if (kept.length) named = true\n // What it makes names its world by what the things are made of, never\n // the things: \"stoneware\" of \"stoneware bowls\", \"soy\" of \"soy candles\".\n // A bowl is not a ceramic world; a robin's food bowl is not pottery.\n const world = making ? kept.slice(0, -1) : kept\n for (const word of world)\n for (const stem of stemList(word)) domain.add(stem)\n if (making)\n for (const word of kept.slice(-1))\n for (const stem of stemList(word)) made.add(stem)\n }\n if (craft) domain.add(craft)\n // A business named only by what it sells (\"a shop selling pottery\") is of that world.\n if (!domain.size) for (const stem of made) domain.add(stem)\n const broad: string[] = []\n for (const group of CRAFT_KIN) {\n if (\n !group.match.some(\n (word) => domain.has(aiStockStem(word)) || made.has(aiStockStem(word)),\n )\n )\n continue\n for (const word of group.kin) domain.add(aiStockStem(word))\n broad.push(...group.broad)\n }\n if (!broad.length) broad.push(business, craft)\n return {\n business,\n craft,\n domain: [...domain].filter((stem) => stem.length > 1),\n broad: [...new Set(broad.map((query) => query.trim()).filter(Boolean))],\n }\n}\n\n/** A phrase's content words without its filler; all filler, and nothing is left. */\nfunction subjectOf(list: readonly string[]): string[] {\n return list.filter((word) => !FILLER.has(word))\n}\n\n/**\n * A picture's subject in a few words (AGL-3660): the noun phrase its alt\n * text opens with — its first clause holding more than filler, cut where a\n * preposition starts the setting, filler dropped, its last three words (the\n * head noun is the last) — else its section's name.\n */\nexport function aiStockSubjectWords(alt: string, sectionName: string): string {\n for (const clause of alt.toLowerCase().split(CLAUSE_BREAK)) {\n const found = subjectOf(contentWords(clause ?? ''))\n if (found.length) return found.slice(-3).join(' ')\n }\n return subjectOf(contentWords(sectionName)).slice(0, 2).join(' ')\n}\n\n/**\n * The thing a subject shows, one singular word: its last word that is not\n * a set or a wrapper of it — \"bowl\" from \"nesting bowl set\", \"candle\" from\n * \"candle gift set\", \"vase\" from \"bud vase\". Its last word when every word\n * is one (\"gift set\").\n */\nexport function aiStockObjectWord(subject: string): string {\n const list = subject.split(/\\s+/).filter(Boolean)\n const object =\n [...list].reverse().find((word) => !WRAPPERS.has(word)) ??\n list[list.length - 1] ??\n ''\n return object ? aiStockStem(object) : ''\n}\n\nconst clip = (query: string) =>\n query.replace(/\\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUERY_MAX_CHARS).trim()\n\n/** The frame's orientation: a wide frame wants a landscape photo, a tall one a portrait. */\nexport function aiStockOrientation(aspect: number): StockPhotoOrientation {\n if (aspect > 1.15) return 'horizontal'\n if (aspect < 0.87) return 'vertical'\n return 'any'\n}\n\n/**\n * One search a slot tries. A `broad` one asks for the site's world and\n * takes any hit of it; any other picture of a thing takes only a hit naming\n * the thing.\n */\nexport interface AiStockSearch extends StockPhotoSearchRequest {\n broad?: boolean\n /** The words its hits are scored by, where they are not the slot's own subject (a product's searches, AGL-3676). */\n subject?: string\n /** A word every hit must name: a product's photo names the shop's category (AGL-3676). */\n requires?: string\n}\n\n/**\n * A product's searches (AGL-3676), most specific first: each of its subjects\n * — its name's noun phrase, then its photo's — with the shop's category\n * where the phrase does not already say it (\"candle gift box\"), then the\n * category alone, a plain photo of what the shop sells. Every one must find a\n * hit naming the category, so no product is ever filled with a lifestyle shot\n * of something else: the beta.237 Willow Wick start put a laptop and roses\n * under \"Candle Wick Trimmer\" and an antique tea set under \"Candle Gift Set\".\n */\nexport function aiStockProductSearches(\n slot: Pick<AiLayoutPictureSlot, 'aspect' | 'product'>,\n craft: string,\n): AiStockSearch[] {\n const orientation = aiStockOrientation(slot.aspect)\n const size = orientation === 'vertical' ? { minHeight: 900 } : { minWidth: 900 }\n const craftStems = stemList(craft)\n const queries: Array<{ query: string; subject: string }> = []\n for (const raw of slot.product?.subjects ?? []) {\n const subject = aiStockSubjectWords(raw, '')\n if (!subject) continue\n const says = craftStems.every((stem) => stemList(subject).includes(stem))\n queries.push({ query: craft && !says ? `${craft} ${subject}` : subject, subject })\n }\n if (craft) queries.push({ query: craft, subject: craft })\n const seen = new Set<string>()\n const searches: AiStockSearch[] = []\n for (const entry of queries) {\n const query = clip(entry.query)\n if (!query || seen.has(query)) continue\n seen.add(query)\n searches.push({ query, orientation, ...size, subject: entry.subject, ...(craft ? { requires: craft } : {}) })\n }\n return searches\n}\n\n/** The searches a slot tries, in order, most specific first, each distinct. */\nexport function aiStockSearchesFor(\n slot: Pick<AiLayoutPictureSlot, 'role' | 'alt' | 'aspect'>,\n terms: Pick<AiStockSiteTerms, 'business' | 'craft' | 'domain' | 'broad'>,\n subject: string,\n): AiStockSearch[] {\n const orientation = aiStockOrientation(slot.aspect)\n const size =\n slot.role === 'hero'\n ? { minWidth: 1600 }\n : orientation === 'vertical'\n ? { minHeight: 900 }\n : { minWidth: 900 }\n const domain = new Set(terms.domain)\n const object = aiStockObjectWord(subject)\n const subjectStems = stemList(subject)\n // The craft qualifies a subject that does not already name the site's world.\n const namesWorld = subjectStems.some((stem) => domain.has(stem))\n const qualify = (phrase: string) =>\n terms.craft && !namesWorld && !stemList(phrase).includes(terms.craft)\n ? `${terms.craft} ${phrase}`\n : phrase\n const broad = terms.broad.map((query) => ({ query, broad: true }))\n const queries: Array<{ query: string; broad?: boolean; people?: boolean }> =\n slot.role === 'hero'\n ? [{ query: terms.business, broad: true }, ...broad]\n : slot.role === 'about'\n ? [\n {\n query: `${terms.business} ${subject}`,\n broad: true,\n people: true,\n },\n { query: terms.business, broad: true, people: true },\n ...broad,\n ]\n : subject\n ? [{ query: qualify(subject) }, { query: qualify(object) }, ...broad]\n : broad\n const seen = new Set<string>()\n const searches: AiStockSearch[] = []\n for (const raw of queries) {\n const query = clip(raw.query)\n const key = `${query}|${raw.people === true}`\n if (!query || seen.has(key)) continue\n seen.add(key)\n searches.push({\n query,\n orientation,\n ...size,\n ...(raw.people ? { people: true } : {}),\n ...(raw.broad ? { broad: true } : {}),\n })\n }\n return searches\n}\n\n/** What a hit is judged by. */\nexport interface AiStockJudgement {\n /** The words the picture is of; empty for a picture of the site's world. */\n subject?: string\n /** The site's domain stems ({@link AiStockSiteTerms.domain}). */\n domain?: readonly string[]\n /**\n * Whether a hit must name the subject's object. A broad search, a hero\n * and an about picture need only name the domain.\n */\n strict?: boolean\n /** A word every hit must name: a product's photo names the shop's category (AGL-3676). */\n requires?: string\n}\n\n/** The words of a photo's page address at its library, its id dropped: \"robin-bird-songbird-garden-winter\". */\nfunction pageSlug(pageUrl: string): string {\n try {\n const last = decodeURIComponent(new URL(pageUrl).pathname)\n .split('/')\n .filter(Boolean)\n .pop()\n return last?.replace(/-?\\d+$/, '') ?? ''\n } catch {\n return ''\n }\n}\n\n/** A hit's words: the first few (what it is OF), every one, and each tag's phrase. */\nfunction hitWords(photo: Pick<StockPhoto, 'tags' | 'alt' | 'pageUrl'>) {\n const slug = pageSlug(photo.pageUrl)\n const tags = (photo.tags ?? [])\n .map((tag) => tag.toLowerCase().trim())\n .filter(Boolean)\n const lead = new Set(stemList([...tags.slice(0, 3), slug].join(' ')))\n const all = new Set(stemList([...tags, photo.alt ?? '', slug].join(' ')))\n const phrases = [\n ...tags.map((tag) => stemList(tag).join(' ')),\n stemList(photo.alt ?? '').join(' '),\n stemList(slug).join(' '),\n ]\n const rejected = tags.some(\n (tag) =>\n REJECTED_TAGS.has(tag) ||\n tag.split(/\\s+/).some((word) => REJECTED_TAGS.has(word)),\n )\n return { lead, all, phrases, rejected }\n}\n\n/**\n * How well a hit shows what a slot needs (AGL-3660), or 0 when it must not\n * fill it: a mockup or a blank never; a picture of a thing only when the hit\n * names its object AND either the site's world or the subject's own\n * two-word phrase (\"bud vase\", \"wick trimmer\"); anything else only when it\n * names the site's world (when the site names one). Among the accepted, the\n * object among the hit's first tags counts 4 (a mention among its last, 2),\n * the subject's two-word phrase 3, each of its other words 1, and the\n * site's world 1, plus a quarter when it is among the first tags.\n */\nexport function aiStockRelevance(\n photo: Pick<StockPhoto, 'tags' | 'alt' | 'pageUrl'>,\n judgement: AiStockJudgement,\n): number {\n const hit = hitWords(photo)\n if (hit.rejected) return 0\n const required = stemList(judgement.requires ?? '')\n if (required.some((stem) => !hit.all.has(stem))) return 0\n const subject = stemList(judgement.subject ?? '')\n const object = judgement.subject ? aiStockObjectWord(judgement.subject) : ''\n const domain = (judgement.domain ?? []).filter((stem) => stem !== object)\n // A hit naming the word it must name is of the site's world: a product's\n // category is that world even where it is the product's own object.\n const world =\n required.length > 0 || domain.some((stem) => hit.all.has(stem))\n const worldLead =\n (required.length > 0 && required.every((stem) => hit.lead.has(stem))) ||\n domain.some((stem) => hit.lead.has(stem))\n const pair = subject.length >= 2 ? subject.slice(-2).join(' ') : ''\n const phrased =\n Boolean(pair) &&\n hit.phrases.some((phrase) => ` ${phrase} `.includes(` ${pair} `))\n let score = 0\n if (judgement.strict && object) {\n if (!hit.all.has(object)) return 0\n if (domain.length && !world && !phrased) return 0\n score += hit.lead.has(object) ? 4 : 2\n } else if (domain.length) {\n if (!world) return 0\n }\n if (phrased) score += 3\n for (const word of new Set(subject))\n if (word !== object && hit.all.has(word)) score += 1\n if (world) score += worldLead ? 1.25 : 1\n return Math.max(score, 0.5)\n}\n\n/**\n * A search's acceptable hits in the order they are tried (AGL-3660): those\n * not placed yet, among the first few the library returned, those it must\n * not take dropped ({@link aiStockRelevance}), best first; the seed turns\n * the hits that score alike, so two jobs differ and one job repeats itself.\n */\nexport function aiStockRank(\n photos: readonly StockPhoto[],\n used: ReadonlySet<string>,\n seed: string,\n judgement: AiStockJudgement = {},\n): StockPhoto[] {\n const scored = photos\n .filter((photo) => !used.has(stockPhotoSourceKey(photo)))\n .slice(0, AI_LAYOUT_STOCK_PICK_WINDOW)\n .map((photo) => ({ photo, score: aiStockRelevance(photo, judgement) }))\n .filter((entry) => entry.score > 0)\n if (!scored.length) return []\n const turn = aiLayoutSeedNumber(seed)\n const ranked: StockPhoto[] = []\n for (const score of [...new Set(scored.map((entry) => entry.score))].sort(\n (a, b) => b - a,\n )) {\n const tier = scored.filter((entry) => entry.score === score)\n const start = turn % tier.length\n for (const entry of [...tier.slice(start), ...tier.slice(0, start)])\n ranked.push(entry.photo)\n }\n return ranked\n}\n\n/** The pick among a search's hits: the first {@link aiStockRank} would try, or `null`. */\nexport function aiStockPick(\n photos: readonly StockPhoto[],\n used: ReadonlySet<string>,\n seed: string,\n judgement: AiStockJudgement = {},\n): StockPhoto | null {\n return aiStockRank(photos, used, seed, judgement)[0] ?? null\n}\n\n/**\n * The library photos a stored page already shows: every `image` whose\n * source is the site's media reference. Starter photos are the platform's,\n * not the job's, and are not listed.\n */\nexport function aiStockPlacedSrcs(\n nodes: NodesMap | null | undefined,\n): string[] {\n const map = (nodes ?? {}) as unknown as Record<\n string,\n { componentId?: string; props?: Record<string, unknown> }\n >\n const out: string[] = []\n for (const node of Object.values(map)) {\n const src = node?.componentId === 'image' ? node.props?.['src'] : null\n if (typeof src === 'string' && src.startsWith('media:')) out.push(src)\n }\n return out\n}\n\n/**\n * What this process has placed for each job, by source key and asset src,\n * with the scope (the page's seed) that placed it (AGL-3660). It spans the\n * passes of one job that run here, so a page, the products or a post built\n * later does not place what an earlier one did, while a pass repeated over\n * its own page keeps its own photos. Bounded; the oldest job goes first.\n */\nconst JOB_PHOTOS = new Map<string, Map<string, string>>()\nconst JOB_PHOTOS_MAX = 200\n\nfunction jobPhotos(key: string | null): Map<string, string> | null {\n if (!key) return null\n let placed = JOB_PHOTOS.get(key)\n if (!placed) {\n placed = new Map()\n JOB_PHOTOS.set(key, placed)\n while (JOB_PHOTOS.size > JOB_PHOTOS_MAX) {\n const oldest = JOB_PHOTOS.keys().next().value\n if (oldest === undefined) break\n JOB_PHOTOS.delete(oldest)\n }\n }\n return placed\n}\n\n/** Forgets every job's placed photos; for specs. */\nexport function aiStockForgetJobPhotos(): void {\n JOB_PHOTOS.clear()\n}\n\nconst EXTENSIONS: Record<string, string> = {\n 'image/jpeg': 'jpg',\n 'image/png': 'png',\n 'image/webp': 'webp',\n}\n\nexport interface AiLayoutStockPhotoSourceInput {\n hostId: string\n /** The member the photos are stored as: the job's creator. */\n uid: string\n /** The job's seed, as the starter photos turn by; also the scope a photo is placed under. */\n seed: string\n /** The site's business type, else the job's brief. */\n business: string\n /** The page's section names, by plan index. */\n sectionNames: readonly string[]\n /**\n * The job the member started (`aiOriginJobId`), so a photo is placed once\n * across its pages. Absent, only the page itself is kept free of repeats.\n */\n jobId?: string\n /** Asset srcs or source keys the job's other pages already show. */\n avoid?: readonly string[]\n /** The most searches this source sends; a page's own bound where absent. A store's products search more (AGL-3676). */\n searches?: number\n signal?: AbortSignal\n}\n\nexport interface AiLayoutStockPhotoDeps {\n provider?: () => StockPhotoProvider | null\n ingest?: () => PluginMediaIngest | null\n budgetMs?: number\n}\n\n/** A slot's choice: the accepted hits it may be filled from, best first, and the query that found them. */\ninterface AiStockChoice {\n candidates: StockPhoto[]\n query: string\n}\n\n/**\n * The source for one page, or `null` when this deployment has no stock\n * photo library or no media door, which leaves every slot to the starters.\n */\nexport function aiLayoutStockPhotoSource(\n input: AiLayoutStockPhotoSourceInput,\n deps: AiLayoutStockPhotoDeps = {},\n): AiLayoutPictureSource | null {\n const provider = (deps.provider ?? stockPhotoProvider)()\n const ingest = (deps.ingest ?? pluginMediaIngest)()\n if (!provider || !ingest || !input.hostId || !input.uid) return null\n const terms = aiStockSiteTerms(input.business)\n return async (slots) => {\n const timeout = AbortSignal.timeout(\n deps.budgetMs ?? AI_LAYOUT_STOCK_PHOTOS_BUDGET_MS,\n )\n const signal = input.signal\n ? AbortSignal.any([input.signal, timeout])\n : timeout\n const scope = input.seed\n const job = jobPhotos(input.jobId ? `${input.hostId}:${input.jobId}` : null)\n const avoid = new Set(input.avoid ?? [])\n /** Placed elsewhere in the job: by another page in this process, or one the caller read. */\n const elsewhere = (key: string) =>\n avoid.has(key) || (job?.has(key) === true && job.get(key) !== scope)\n /** Source keys this page has claimed for a slot, or turned down. */\n const claimed = new Set<string>()\n /** Asset srcs this page has placed. */\n const placedSrcs = new Set<string>()\n const answers = new Map<string, Promise<StockPhoto[] | null>>()\n let searches = 0\n let storing = true\n const remember = (sourceKey: string, src: string) => {\n placedSrcs.add(src)\n job?.set(sourceKey, scope)\n job?.set(src, scope)\n }\n /** A search's hits; the same words twice are answered from memory, and `null` once the page's searches are spent. */\n const search = (wanted: AiStockSearch): Promise<StockPhoto[] | null> => {\n const request: StockPhotoSearchRequest = { ...wanted }\n delete (request as AiStockSearch).broad\n delete (request as AiStockSearch).subject\n delete (request as AiStockSearch).requires\n const key = JSON.stringify([\n request.query,\n request.orientation,\n request.people === true,\n ])\n const known = answers.get(key)\n if (known) return known\n if (searches >= (input.searches ?? AI_LAYOUT_STOCK_SEARCHES_PER_PAGE))\n return Promise.resolve(null)\n searches += 1\n const asked = provider\n .search(request, { signal })\n .then((found) => found?.photos ?? [])\n .catch(() => [] as StockPhoto[])\n answers.set(key, asked)\n return asked\n }\n /** The photo placed in the site's library, a copy it holds or a new one; `null` when it cannot be placed. */\n const keep = async (\n photo: StockPhoto,\n slot: AiLayoutPictureSlot,\n query: string,\n ): Promise<AiLayoutPicturePhoto | null> => {\n const sourceKey = stockPhotoSourceKey(photo)\n try {\n const kept = await ingest.findStockPhoto({\n hostId: input.hostId,\n sourceKey,\n })\n if (kept) {\n // Held because a slot of this page or another page of the job shows it.\n if (placedSrcs.has(kept.src) || elsewhere(kept.src)) return null\n remember(sourceKey, kept.src)\n return {\n src: kept.src,\n width: kept.width ?? photo.width,\n height: kept.height ?? photo.height,\n }\n }\n // A refusal to store (the storage band, a lockdown) holds for the\n // whole page, so the rest of it reuses what it can and copies nothing.\n if (!storing || signal.aborted) return null\n const bytes = await provider.download(photo, {\n maxBytes: AI_LAYOUT_STOCK_MAX_BYTES,\n signal,\n })\n if (!bytes || signal.aborted || !storing) return null\n const credit = provider.credit(photo)\n const stored = await ingest.ingest({\n hostId: input.hostId,\n uid: input.uid,\n fileName: `${photo.provider}-${photo.id}.${EXTENSIONS[bytes.contentType] ?? 'jpg'}`,\n contentType: bytes.contentType,\n bytes: bytes.bytes,\n alt: slot.alt,\n description: credit.text,\n stockPhoto: {\n key: sourceKey,\n provider: photo.provider,\n providerLabel: credit.providerLabel,\n id: photo.id,\n pageUrl: photo.pageUrl,\n photographer: photo.photographer,\n ...(photo.photographerUrl\n ? { photographerUrl: photo.photographerUrl }\n : {}),\n license: credit.license,\n licenseUrl: credit.licenseUrl,\n attributionRequired: credit.attributionRequired,\n query,\n },\n })\n if (stored.ok) {\n remember(sourceKey, stored.src)\n return {\n src: stored.src,\n width: stored.width ?? photo.width,\n height: stored.height ?? photo.height,\n }\n }\n storing = false\n const refusal = stored as Extract<\n PluginMediaIngestResult,\n { ok: false }\n >\n console.warn(\n 'ai layout pictures: the media library refused a stock photo',\n {\n status: refusal.status,\n reason: refusal.reason,\n },\n )\n } catch (error) {\n console.warn('ai layout pictures: a stock photo could not be kept', {\n error: String(error),\n })\n }\n return null\n }\n\n // 1. Choose, slot by slot, so the page's choices never collide: each\n // slot claims its best accepted hit before the next slot looks.\n const choices: Array<AiStockChoice | null> = []\n for (const [index, slot] of slots.entries()) {\n if (signal.aborted) {\n choices.push(null)\n continue\n }\n const subject = aiStockSubjectWords(\n slot.alt,\n input.sectionNames[slot.sectionIndex] ?? '',\n )\n let choice: AiStockChoice | null = null\n const requests = slot.product\n ? aiStockProductSearches(slot, terms.craft)\n : aiStockSearchesFor(slot, terms, subject)\n for (const request of requests) {\n if (choice || signal.aborted) break\n const found = await search(request)\n if (found === null) break\n const blocked = new Set(claimed)\n for (const hit of found) {\n const key = stockPhotoSourceKey(hit)\n if (elsewhere(key)) blocked.add(key)\n }\n const ranked = aiStockRank(\n found,\n blocked,\n `${input.seed}:${index}:${request.query}`,\n {\n subject: request.subject ?? (request.broad ? '' : subject),\n domain: terms.domain,\n strict:\n (slot.role === 'gallery' && !request.broad) || !!request.requires,\n ...(request.requires ? { requires: request.requires } : {}),\n },\n )\n if (!ranked.length) continue\n const first = ranked[0] as StockPhoto\n claimed.add(stockPhotoSourceKey(first))\n choice = {\n candidates: ranked.slice(0, AI_LAYOUT_STOCK_TRIES_PER_SLOT),\n query: request.query,\n }\n }\n choices.push(choice)\n }\n\n // 2. Keep, a few at a time; a copy that fails tries the slot's next\n // accepted hit no other slot has claimed.\n const photos: Array<AiLayoutPicturePhoto | null> = slots.map(() => null)\n const fill = async (index: number) => {\n const choice = choices[index]\n const slot = slots[index]\n if (!choice || !slot) return\n for (const [rank, candidate] of choice.candidates.entries()) {\n if (signal.aborted) return\n const key = stockPhotoSourceKey(candidate)\n if (rank > 0) {\n if (claimed.has(key)) continue\n claimed.add(key)\n }\n const placed = await keep(candidate, slot, choice.query)\n if (placed) {\n photos[index] = placed\n return\n }\n }\n }\n let next = 0\n const worker = async () => {\n while (next < slots.length) {\n const index = next\n next += 1\n await fill(index)\n }\n }\n await Promise.all(\n Array.from(\n { length: Math.min(AI_LAYOUT_STOCK_COPIES_AT_ONCE, slots.length) },\n worker,\n ),\n )\n return photos\n }\n}\n"],"names":["pluginMediaIngest","STOCK_PHOTO_QUERY_MAX_CHARS","stockPhotoProvider","stockPhotoSourceKey","aiLayoutSeedNumber","AI_LAYOUT_STOCK_PHOTOS_BUDGET_MS","AI_LAYOUT_STOCK_SEARCHES_PER_PAGE","AI_LAYOUT_STOCK_PICK_WINDOW","AI_LAYOUT_STOCK_TRIES_PER_SLOT","AI_LAYOUT_STOCK_COPIES_AT_ONCE","AI_LAYOUT_STOCK_MAX_BYTES","AI_STOCK_PHOTO_PAGES_INPUT","words","list","Set","trim","split","STOPWORDS","FILLER","VENUES","ROLES","SITE_KINDS","DOING","BUSINESS_BREAK","PLACE_OR_AUDIENCE","WRAPPERS","REJECTED_TAGS","CRAFT_KIN","match","kin","broad","contentWords","text","toLowerCase","replace","map","word","filter","length","has","aiStockStem","lower","endsWith","slice","test","stemList","CLAUSE_BREAK","businessClauses","businessType","clauses","breaker","RegExp","source","at","opener","matchAll","push","index","businessOf","clause","aiStockBusinessWords","named","join","aiStockCraftWords","business","craft","aiStockSiteTerms","domain","made","making","kept","world","stem","add","size","group","some","query","Boolean","subjectOf","aiStockSubjectWords","alt","sectionName","found","aiStockObjectWord","subject","object","reverse","find","clip","aiStockOrientation","aspect","aiStockProductSearches","slot","orientation","minHeight","minWidth","craftStems","queries","raw","product","subjects","says","every","includes","seen","searches","entry","requires","aiStockSearchesFor","terms","role","subjectStems","namesWorld","qualify","phrase","people","key","pageSlug","pageUrl","last","decodeURIComponent","URL","pathname","pop","hitWords","photo","slug","tags","tag","lead","all","phrases","rejected","aiStockRelevance","judgement","hit","required","worldLead","pair","phrased","score","strict","Math","max","aiStockRank","photos","used","seed","scored","turn","ranked","sort","a","b","tier","start","aiStockPick","aiStockPlacedSrcs","nodes","out","node","Object","values","src","componentId","props","startsWith","JOB_PHOTOS","Map","JOB_PHOTOS_MAX","jobPhotos","placed","get","set","oldest","keys","next","value","undefined","delete","aiStockForgetJobPhotos","clear","EXTENSIONS","aiLayoutStockPhotoSource","input","deps","provider","ingest","hostId","uid","slots","timeout","AbortSignal","budgetMs","signal","any","scope","job","jobId","avoid","elsewhere","claimed","placedSrcs","answers","storing","remember","sourceKey","search","wanted","request","JSON","stringify","known","Promise","resolve","asked","then","catch","keep","findStockPhoto","width","height","aborted","bytes","download","maxBytes","credit","stored","fileName","id","contentType","description","stockPhoto","providerLabel","photographer","photographerUrl","license","licenseUrl","attributionRequired","ok","refusal","console","warn","status","reason","error","String","choices","entries","sectionNames","sectionIndex","choice","requests","blocked","first","candidates","fill","rank","candidate","worker","Array","from","min"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,iBAAiB,QAGZ,kDAAiD;AACxD,SACEC,2BAA2B,EAC3BC,kBAAkB,EAClBC,mBAAmB,QAKd,mDAAkD;AAEzD,SACEC,kBAAkB,QAIb,2CAAuC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuEC,GAED,+EAA+E,GAC/E,OAAO,MAAMC,mCAAmC,MAAM;AAEtD,yGAAyG,GACzG,OAAO,MAAMC,oCAAoC,GAAE;AAEnD,mCAAmC,GACnC,OAAO,MAAMC,8BAA8B,GAAE;AAE7C,kEAAkE,GAClE,MAAMC,iCAAiC;AAEvC,8BAA8B,GAC9B,MAAMC,iCAAiC;AAEvC,wDAAwD,GACxD,OAAO,MAAMC,4BAA4B,IAAI,OAAO,KAAI;AAExD;;;CAGC,GACD,OAAO,MAAMC,6BAA6B,kBAAiB;AAE3D,MAAMC,QAAQ,CAACC,OAAiB,IAAIC,IAAID,KAAKE,IAAI,GAAGC,KAAK,CAAC;AAE1D,iCAAiC,GACjC,MAAMC,YAAYL,MAChB,6GACE,4GACA,iGACA,mFACA;AAGJ;;;CAGC,GACD,MAAMM,SAASN,MACb,kHACE,sGACA,wGACA,yEACA,iHACA,0GACA,qGACA,qGACA,4GACA,sDACA,yGACA;AAGJ,2FAA2F,GAC3F,MAAMO,SAASP,MACb,+GACE,qGACA;AAGJ,gFAAgF,GAChF,MAAMQ,QAAQR,MACZ;AAGF,kHAAkH,GAClH,MAAMS,aAAaT,MACjB,yGACE;AAGJ,yGAAyG,GACzG,MAAMU,QACJ;AAEF,6CAA6C,GAC7C,MAAMC,iBACJ;AAEF,qGAAqG,GACrG,MAAMC,oBACJ;AAEF;;;CAGC,GACD,MAAMC,WAAWb,MACf,kHACE;AAGJ;;;;CAIC,GACD,MAAMc,gBAAgBd,MACpB,iGACE;AAGJ;;;;CAIC,GACD,MAAMe,YAID;IACH;QACEC,OAAO;YACL;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,KAAK;YACH;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,OAAO;YAAC;YAAqB;SAAU;IACzC;IACA;QACEF,OAAO;YAAC;YAAU;YAAe;SAAe;QAChDC,KAAK;YACH;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,OAAO;YAAC;YAAoB;SAAkB;IAChD;IACA;QACEF,OAAO;YACL;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,KAAK;YACH;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,OAAO;YAAC;YAAoB;SAAU;IACxC;IACA;QACEF,OAAO;YAAC;YAAQ;YAAa;SAAW;QACxCC,KAAK;YAAC;YAAQ;YAAY;YAAU;YAAQ;YAAO;YAAW;SAAW;QACzEC,OAAO;YAAC;YAAiB;SAAmB;IAC9C;IACA;QACEF,OAAO;YACL;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,KAAK;YACH;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,OAAO;YAAC;YAAe;SAAmB;IAC5C;IACA;QACEF,OAAO;YAAC;YAAU;YAAS;YAAS;YAAU;YAAQ;SAAa;QACnEC,KAAK;YACH;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,OAAO;YAAC;YAAU;SAAc;IAClC;IACA;QACEF,OAAO;YAAC;YAAU;YAAQ;YAAQ;YAAW;SAAW;QACxDC,KAAK;YACH;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,OAAO;YAAC;YAAe;SAAS;IAClC;IACA;QACEF,OAAO;YAAC;YAAW;YAAU;SAAS;QACtCC,KAAK;YAAC;YAAU;YAAU;YAAW;YAAW;YAAS;SAAU;QACnEC,OAAO;YAAC;YAAW;SAAiB;IACtC;IACA;QACEF,OAAO;YACL;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,KAAK;YACH;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,OAAO;YAAC;YAAqB;SAAO;IACtC;IACA;QACEF,OAAO;YAAC;YAAW;YAAe;SAAe;QACjDC,KAAK;YAAC;YAAW;YAAU;YAAO;YAAQ;SAAY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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-layout-stock-photos.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 {\n pluginMediaIngest,\n type PluginMediaIngest,\n type PluginMediaIngestResult,\n} from '@aglyn/aglyn/plugin-manager/plugin-media-ingest'\nimport {\n STOCK_PHOTO_QUERY_MAX_CHARS,\n stockPhotoProvider,\n stockPhotoSourceKey,\n type StockPhoto,\n type StockPhotoOrientation,\n type StockPhotoProvider,\n type StockPhotoSearchRequest,\n} from '@aglyn/aglyn/plugin-manager/stock-photo-provider'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport {\n aiLayoutSeedNumber,\n type AiLayoutPicturePhoto,\n type AiLayoutPictureSlot,\n type AiLayoutPictureSource,\n} from '../layout-language/ai-layout-pictures'\nimport { aiStorefrontSays } from '../layout-language/ai-layout-storefront'\n\n/**\n * STOCK PHOTOS FOR A LANGUAGE PAGE'S PICTURE SLOTS (AGL-3660).\n *\n * The source `aiResolveLayoutPictures` asks before it falls back to the\n * starter photos. It names no library: it asks core for the deployment's\n * stock photo provider (`core.stock-photos`) and for the media library's\n * server door (`core.media-ingest`), and builds no source at all when either\n * is missing, so a deployment without a key behaves exactly as before.\n *\n * What the live starts of 2026-10-10 taught (Juniper Clay Works, a ceramic\n * artist's portfolio, and Willow Wick Candles, a candle store), each photo\n * traced through the library's cached answers:\n *\n * - The site's words were read as \"portfolio\" and \"online\" — the kind of\n * SITE, not the craft — so every search said \"serving bowl portfolio\" and\n * the hero asked for \"portfolio\": a wallet, fashion models, and a blank\n * paper mockup that read as an empty panel beside the brushes.\n * - A hit naming the object anywhere won: a robin tagged \"food bowl\" filled\n * \"Speckled serving bowl\", a lilac branch tagged \"bud, vase\" filled \"Tall\n * bud vase\", letterpress type tagged \"set\" filled \"Nesting bowl set\" (its\n * head noun was read as \"set\"), and an antique tea set tagged \"brown\n * candle\" filled \"Candle Gift Set\".\n * - The page's 20 seconds ran out after four photos (each copy took ~5s, one\n * after another), and the slots left took the starter photos: the laptop\n * desk with pink roses under \"Candle Wick Trimmer\" and a work card, and the\n * windowsill plants under another.\n *\n * So, for each slot:\n *\n * 1. **The site's terms** ({@link aiStockSiteTerms}): the business is the\n * first clause of its type that says more than what kind of site it is\n * (\"ceramic artist\" from \"a portfolio for a ceramic artist who makes\n * stoneware bowls\"), its craft one word (\"ceramic\"), and its DOMAIN the\n * words a photo of its world names: the craft, the business type's own\n * nouns (\"stoneware\", \"soy\") and, for common crafts, their kin (\"pottery\",\n * \"clay\", \"wax\"). Its broad searches ask for the world itself (\"handmade\n * ceramics\", \"pottery\").\n * 2. **Search.** A picture of a thing asks for its subject — the noun phrase\n * of the caption, filler dropped — qualified by the craft where it does\n * not already name the domain (\"ceramic serving bowl\"), then the craft and\n * the object alone (\"ceramic bowl\"), then the broad searches. A hero asks\n * for the business, then broad; an about picture for the business's\n * people, then broad. The orientation and a minimum size come from the\n * slot's frame.\n * 3. **Accept, or reject.** A hit tagged as a mockup, a blank or a template\n * never fills anything. A picture of a thing takes only a hit naming its\n * OBJECT (the head noun, past \"set\", \"gift\", \"kit\": \"bowl\" in \"nesting\n * bowl set\", \"candle\" in \"candle gift set\") AND either the site's domain\n * or the subject's own two-word phrase (\"bud vase\"). A broad, hero or\n * about search takes only a hit naming the domain. The accepted are\n * ranked: the object in the hit's first tags (what the photo is OF) over\n * a mention in its last, the subject's other words, the domain. Among\n * equals the job's seed picks, so two sites differ and one job repeats.\n * What the beta.239 Ember & Oak start added: \"hand-poured\", \"handmade\"\n * and \"by hand\" never make \"hands\" a subject, and a story of the craft\n * asks for the maker at work (\"candle making\"); a hit carrying a topic\n * the brief never named (a cross, a grave, a flag, a gun, wine, a child,\n * a mannequin) never fills anything; a product, and a collection tile,\n * take only a hit naming its head noun among the first tags, held to the\n * shop's category; and an object whose word is not enough (\"melt\") is\n * named and searched by its kin (\"wax melt\", \"melt warmer\"), never by the\n * category alone. And from the Kiln & Clover portfolio: nudity never fills\n * anything, for any business; a hit naming a rival craft (glass, for a\n * potter) counts only when it names the craft's own words too.\n * 4. **Never twice.** A photo is placed once per job: not twice on a page,\n * and not on a page when another page (or another pass, in this process)\n * of the same job already placed it. The caller hands in what the job's\n * other pages hold (`avoid`); a pass repeated over its own page keeps its\n * own.\n * 5. **Keep, all at once.** The choosing is done first, slot by slot; the\n * copies then run side by side, a few at a time, so a page of eight\n * pictures takes about as long as two. A photo the site's library already\n * holds (by its source key) is reused; otherwise its bytes are copied\n * into the site's own library as the member who started the job, credited\n * to its photographer. A copy that fails tries the slot's next accepted\n * hit.\n *\n * A slot nothing acceptable answered — the library down, refusing, or out\n * of time — is left `null` for the starter photos. A named object is never\n * given one while the library answers anything of the site's world: the\n * broad searches come first. Nothing here throws, and none of it is an AI\n * model call: photos cost no AI credits.\n */\n\n/** Wall clock the whole page's photos may take, inside the step's own signal. */\nexport const AI_LAYOUT_STOCK_PHOTOS_BUDGET_MS = 30_000\n\n/** Searches one page may send to the provider; a repeated query is answered from the page's own memory. */\nexport const AI_LAYOUT_STOCK_SEARCHES_PER_PAGE = 18\n\n/** The hits a pick is made among. */\nexport const AI_LAYOUT_STOCK_PICK_WINDOW = 40\n\n/** The accepted hits a slot keeps in hand, in case a copy fails. */\nconst AI_LAYOUT_STOCK_TRIES_PER_SLOT = 3\n\n/** Copies made side by side. */\nconst AI_LAYOUT_STOCK_COPIES_AT_ONCE = 4\n\n/** The largest photo copied: inside one direct upload. */\nexport const AI_LAYOUT_STOCK_MAX_BYTES = 3 * 1024 * 1024\n\n/**\n * The unit job input naming the screen drafts the job's other pages were\n * written to, whose photos a page does not place again (AGL-3660).\n */\nexport const AI_STOCK_PHOTO_PAGES_INPUT = 'stockPhotoPages'\n\n/**\n * The unit job input naming library photos (`media:` references) another\n * part of the job already shows outside its pages — a dataset's records\n * (AGL-3616) — which a page does not place again.\n */\nexport const AI_STOCK_PHOTO_AVOID_INPUT = 'stockPhotoAvoid'\n\nconst words = (list: string) => new Set(list.trim().split(/\\s+/))\n\n/** Words that carry no subject. */\nconst STOPWORDS = words(\n 'a an the and or of in on at to for with from by into onto over under near our your their my his her its ' +\n 'this that these those is are be being was were who whom which where while as about than then very just ' +\n 'small local best new top quality premium family-owned family owned independent professional ' +\n 'business company services service page photo picture image showing shows show ' +\n 'someone people person one two three some many several',\n)\n\n/**\n * Words that describe a picture's look, not what it shows, dropped from a\n * subject. A word that is as often a noun (\"light\", \"set\") is not one.\n */\nconst FILLER = words(\n 'beautiful beautifully stunning gorgeous lovely pretty elegant modern contemporary rustic cozy cosy warm cool ' +\n 'soft bright natural simple minimal minimalist delicate unique special artisan artisanal handmade ' +\n 'hand-made hand-thrown handthrown hand-crafted handcrafted crafted hand-built handbuilt hand-poured ' +\n 'hand-painted hand-dyed hand-stitched poured finished styled perfect wheel-thrown wheelthrown thrown ' +\n 'fresh clean calm serene quiet peaceful vibrant colorful colourful sunlit sunny golden glossy matte textured ' +\n 'close-up closeup up view views detail details detailed shot shots overhead flat lay flatlay flat-lay ' +\n 'top-down wide angle featuring featured displayed arranged sitting resting placed standing lined ' +\n 'single pair collection assortment selection group few various array range large tiny big little ' +\n 'tall short wide narrow everyday classic signature original favorite favourite bestselling best-selling ' +\n 'small-batch batch limited edition custom bespoke ' +\n 'white black grey gray beige cream ivory brown tan blue green red pink yellow orange purple speckled ' +\n 'gentle airy inviting welcoming cheerful happy smiling friendly',\n)\n\n/** The nouns that name a place of business, not its craft: \"studio\" in \"ceramics studio\". */\nconst VENUES = words(\n 'studio studios shop shops store stores boutique company co workshop gallery atelier agency brand practice ' +\n 'salon house collective lab market services service firm clinic center centre parlor parlour bar ' +\n 'class classes lessons school academy',\n)\n\n/** The person who does the craft, not the craft: \"artist\" in \"ceramic artist\". */\nconst ROLES = words(\n 'artist artists maker makers artisan artisans owner owners founder founders creator creators',\n)\n\n/** Words naming what kind of SITE it is, never what it shows: \"portfolio\" in \"a portfolio for a ceramic artist\". */\nconst SITE_KINDS = words(\n 'portfolio portfolios website websites site sites blog blogs online web webshop ecommerce e-commerce ' +\n 'landing personal homepage storefront',\n)\n\n/** Verbs a business type uses to say what it does; what follows them is its domain, never its business. */\nconst DOING =\n /(?<![\\w-])(?:makes?|making|made|sells?|selling|sold|offers?|offering|creates?|creating|specializ(?:es|ing)|specialis(?:es|ing)|provides?|providing|featuring|showcasing)(?![\\w-])/g\n\n/** Where a business type's own clause ends. */\nconst BUSINESS_BREAK =\n /[,;:.()]|\\s[-–—]\\s|\\b(?:in|for|near|serving|based|located|that|which|who|with|from|since|and|&)\\b/\n\n/** A clause after one of these says where or for whom, never what: \"in Austin\", \"for busy parents\". */\nconst PLACE_OR_AUDIENCE =\n /^(?:in|near|serving|based|located|across|around|throughout|for)$/\n\n/**\n * Words naming a set or a wrapper of a thing, never the thing: the OBJECT\n * of \"nesting bowl set\" is the bowl, of \"candle gift set\" the candle.\n */\nconst WRAPPERS = words(\n 'set sets kit kits gift gifts bundle bundles pack packs box boxes trio duo pair collection sampler assortment ' +\n 'series range line group lot bunch',\n)\n\n/**\n * A hit tagged with any of these is never placed: a mockup's blank panel is\n * an empty frame on the page (the Juniper Clay hero), a template or a\n * graphic is not a photo of anything.\n */\nconst REJECTED_TAGS = words(\n 'mockup mock-up mock blank template templates clipart clip-art illustration vector logo icon ' +\n 'ai-generated generated render rendering 3d',\n)\n\n/**\n * Topics a business's photos never carry unless its own words name them\n * (AGL-3660, the beta.239 Ember & Oak start, whose \"Gifts for melt fans\" tile\n * showed devotional candles with crosses and names). A hit tagged with any\n * word of a topic is rejected unless the site's business type or brief says\n * one of the topic's words or its `allow` words. The last topic is not\n * sensitive but is never the thing: the same start's story band showed bronze\n * mannequin hands tagged \"wax\".\n */\nconst SENSITIVE_TOPICS: ReadonlyArray<{\n words: readonly string[]\n allow?: readonly string[]\n /** Rejected whatever the brief says. */\n always?: boolean\n}> = [\n {\n // Religion.\n words: [\n 'cross',\n 'crosses',\n 'crucifix',\n 'church',\n 'chapel',\n 'cathedral',\n 'jesus',\n 'christ',\n 'christian',\n 'christianity',\n 'god',\n 'bible',\n 'rosary',\n 'prayer',\n 'pray',\n 'praying',\n 'worship',\n 'worships',\n 'religion',\n 'religious',\n 'faith',\n 'islam',\n 'islamic',\n 'muslim',\n 'mosque',\n 'quran',\n 'temple',\n 'buddha',\n 'buddhism',\n 'buddhist',\n 'hindu',\n 'hinduism',\n 'synagogue',\n 'jewish',\n 'judaism',\n 'ecclesiastical',\n 'saint',\n 'altar',\n 'sacrificial',\n 'nativity',\n 'priest',\n 'nun',\n 'monk',\n ],\n allow: ['ministry', 'parish', 'congregation', 'wedding'],\n },\n {\n // Death and mourning.\n words: [\n 'funeral',\n 'memorial',\n 'grave',\n 'gravestone',\n 'cemetery',\n 'tomb',\n 'death',\n 'dead',\n 'grief',\n 'mourning',\n 'condolence',\n 'coffin',\n 'skull',\n ],\n allow: ['hospice', 'mortuary', 'cremation', 'obituary', 'halloween'],\n },\n {\n // Politics.\n words: [\n 'political',\n 'politics',\n 'protest',\n 'protester',\n 'demonstration',\n 'rally',\n 'flag',\n 'election',\n 'vote',\n 'voting',\n 'parliament',\n 'government',\n ],\n allow: ['advocacy', 'campaign', 'candidate', 'council', 'nonprofit'],\n },\n {\n // Weapons and war.\n words: [\n 'gun',\n 'rifle',\n 'pistol',\n 'weapon',\n 'firearm',\n 'ammunition',\n 'bullet',\n 'sword',\n 'war',\n 'military',\n 'army',\n 'soldier',\n ],\n allow: ['hunting', 'archery', 'martial', 'fencing', 'veteran'],\n },\n {\n // Alcohol, for a business that does not serve it.\n words: [\n 'alcohol',\n 'wine',\n 'beer',\n 'whiskey',\n 'whisky',\n 'vodka',\n 'cocktail',\n 'liquor',\n 'rum',\n 'gin',\n 'champagne',\n 'drunk',\n ],\n allow: [\n 'winery',\n 'vineyard',\n 'brewery',\n 'distillery',\n 'pub',\n 'tavern',\n 'taproom',\n 'bartender',\n 'sommelier',\n 'spirits',\n ],\n },\n {\n // Tobacco and drugs.\n words: ['cigarette', 'smoking', 'tobacco', 'cannabis', 'marijuana', 'drug', 'drugs'],\n allow: ['dispensary', 'vape', 'cigar'],\n },\n {\n // Children, for a business that is not about them.\n words: ['child', 'children', 'kid', 'kids', 'baby', 'babies', 'toddler', 'infant'],\n allow: [\n 'nursery',\n 'daycare',\n 'preschool',\n 'kindergarten',\n 'school',\n 'tutor',\n 'tutoring',\n 'parent',\n 'parents',\n 'parenting',\n 'family',\n 'families',\n 'pediatric',\n 'paediatric',\n 'newborn',\n 'maternity',\n ],\n },\n {\n // Nudity, for every business: no word of a brief lets it in (Kiln &\n // Clover, 2026-10-10: a nude torso sculpture filled \"About the artist\").\n // Never bare \"body\": a skincare shop's body lotion is its product.\n words: [\n 'nude',\n 'nudes',\n 'nudity',\n 'naked',\n 'torso',\n 'breast',\n 'breasts',\n 'erotic',\n 'sensual',\n 'lingerie',\n 'bikini',\n ],\n allow: [],\n always: true,\n },\n {\n // Figures, never the maker: mannequin hands are not a candlemaker's.\n words: [\n 'mannequin',\n 'mannequins',\n 'dummy',\n 'statue',\n 'sculpture',\n 'figurine',\n 'doll',\n 'dolls',\n 'waxwork',\n ],\n allow: ['sculptor', 'museum', 'fashion', 'boutique', 'clothing', 'apparel', 'toy', 'toys'],\n },\n]\n\n/** Each topic's words and the words that allow it, as stems. */\nconst SENSITIVE_STEMS = SENSITIVE_TOPICS.map((topic) => ({\n words: new Set(topic.words.map(aiStockStem)),\n allow: new Set(\n topic.always\n ? []\n : [...topic.words, ...(topic.allow ?? [])].map(aiStockStem),\n ),\n}))\n\n/**\n * Hands are never a picture's subject (AGL-3660): \"hand-poured\", \"handmade\"\n * and \"by hand\" say how a thing was made, and a search for \"hands\" found\n * bronze mannequin hands for the beta.239 Ember & Oak story band. Each of\n * these is dropped from a picture's words; a hand only stays where it names\n * a thing sold for hands (\"hand cream\").\n */\nconst HAND_IDIOMS: readonly RegExp[] = [\n /\\b(?:made\\s+|poured\\s+|crafted\\s+|finished\\s+|done\\s+)?by\\s+(?:our\\s+|my\\s+|their\\s+)?(?:own\\s+)?hands?\\b/g,\n /\\bwith\\s+(?:our|my|their)\\s+(?:own\\s+)?(?:two\\s+)?hands\\b/g,\n /\\bhand[- ]?(?:poured|made|crafted|thrown|built|painted|dyed|stitched|sewn|knit|knitted|carved|tied|lettered|blown|bound|rolled|dipped|picked|forged|woven|cut|finished|mixed|cast|formed|shaped|pressed|printed|wrapped|selected|blended|turned|glazed)\\b/g,\n /\\bhands?\\b(?!\\s+(?:creams?|soaps?|lotions?|wash|towels?|saniti[sz]ers?|salves?|balms?|tools?|saws?|planes?|drills?|mixers?|puppets?|bags?|warmers?))/g,\n]\n\n/** A text without its hands. */\nfunction withoutHands(text: string): string {\n return HAND_IDIOMS.reduce(\n (out, idiom) => out.replace(idiom, ' '),\n text.toLowerCase(),\n )\n}\n\n/** Whether a text says how a thing was made by hand: a maker at work. */\nfunction saysMadeByHand(text: string): boolean {\n const lower = text.toLowerCase()\n return HAND_IDIOMS.some((idiom) => {\n idiom.lastIndex = 0\n const found = idiom.test(lower)\n idiom.lastIndex = 0\n return found\n })\n}\n\n/**\n * The kin of a few objects whose own word is not enough (AGL-3660): a \"melt\"\n * is any melting thing (the beta.239 \"Wax melts\" tile showed resin fluid\n * art), so a wax melt names one of these phrases. `within` is the world they\n * are of; `qualifier` the word a search adds; `queries` the searches for the\n * object itself, which stand in for the shop's category alone: a candle is\n * not a wax melt.\n */\nconst OBJECT_KIN: ReadonlyArray<{\n objects: readonly string[]\n within: readonly string[]\n qualifier: string\n kin: readonly string[]\n queries: readonly string[]\n}> = [\n {\n objects: ['melt', 'tart', 'cube'],\n within: ['wax', 'candle', 'soy'],\n qualifier: 'wax',\n kin: [\n 'wax melt',\n 'wax melts',\n 'wax cube',\n 'wax cubes',\n 'wax tart',\n 'wax tarts',\n 'melt warmer',\n 'wax warmer',\n 'tart warmer',\n 'oil burner',\n 'oil warmer',\n 'aroma burner',\n 'scent warmer',\n 'fragrance warmer',\n ],\n queries: ['wax melts', 'wax melt warmer', 'scented wax cubes'],\n },\n]\n\n/** An object's kin where the site or the subject is of its world, else `null`. */\nfunction objectKin(\n object: string,\n world: readonly string[],\n): (typeof OBJECT_KIN)[number] | null {\n return (\n OBJECT_KIN.find(\n (entry) =>\n entry.objects.some((word) => aiStockStem(word) === object) &&\n entry.within.some((word) => world.includes(aiStockStem(word))),\n ) ?? null\n )\n}\n\n/**\n * The kin of a few common crafts: the words a photo of that world is\n * tagged with, and the broad searches that find it. A craft not listed\n * still has its own words; this only widens what counts as its world.\n */\nconst CRAFT_KIN: ReadonlyArray<{\n match: readonly string[]\n kin: readonly string[]\n broad: readonly string[]\n /** The maker at work: what a story of the craft shows, never \"hands\" (AGL-3660). */\n making: readonly string[]\n /**\n * Crafts a photo of this one is mistaken for, and the words only this\n * craft's photos say: a hit naming a rival counts as of this world only\n * when it names one of them too (Kiln & Clover, 2026-10-10: red and yellow\n * glass art filled a ceramic artist's Work hero, glass being fired and\n * \"vase\" too).\n */\n rivals?: { words: readonly string[]; core: readonly string[] }\n}> = [\n {\n match: [\n 'ceramic',\n 'ceramics',\n 'pottery',\n 'potter',\n 'stoneware',\n 'porcelain',\n 'earthenware',\n 'clay',\n ],\n kin: [\n 'ceramic',\n 'ceramics',\n 'pottery',\n 'potter',\n 'stoneware',\n 'porcelain',\n 'earthenware',\n 'clay',\n 'glaze',\n 'kiln',\n ],\n broad: ['handmade ceramics', 'pottery'],\n rivals: {\n words: ['glass', 'glassware', 'glasswork', 'glassblowing', 'blown', 'stained', 'murano', 'crystal'],\n core: ['ceramic', 'ceramics', 'pottery', 'potter', 'stoneware', 'porcelain', 'earthenware', 'clay', 'terracotta'],\n },\n making: ['pottery wheel', 'potter at work'],\n },\n {\n match: ['candle', 'candlemaker', 'candlemaking'],\n kin: [\n 'candle',\n 'candlelight',\n 'wax',\n 'soy',\n 'wick',\n 'scented',\n 'votive',\n 'candlestick',\n ],\n broad: ['handmade candles', 'scented candles'],\n making: ['candle making', 'pouring wax'],\n },\n {\n match: [\n 'jewelry',\n 'jewellery',\n 'jeweler',\n 'jeweller',\n 'silversmith',\n 'goldsmith',\n ],\n kin: [\n 'jewelry',\n 'jewellery',\n 'ring',\n 'necklace',\n 'earring',\n 'bracelet',\n 'silver',\n 'gold',\n 'gemstone',\n ],\n broad: ['handmade jewelry', 'jewelry'],\n making: ['jewelry making', 'jeweler at work'],\n },\n {\n match: ['soap', 'soapmaker', 'skincare'],\n kin: ['soap', 'skincare', 'lotion', 'bath', 'spa', 'natural', 'cosmetic'],\n broad: ['handmade soap', 'natural skincare'],\n making: ['soap making'],\n },\n {\n match: [\n 'wood',\n 'woodwork',\n 'woodworking',\n 'woodworker',\n 'carpenter',\n 'carpentry',\n 'furniture',\n 'joinery',\n ],\n kin: [\n 'wood',\n 'wooden',\n 'woodwork',\n 'timber',\n 'furniture',\n 'carpentry',\n 'oak',\n 'walnut',\n ],\n broad: ['woodworking', 'wooden furniture'],\n making: ['woodworking workshop', 'carpenter at work'],\n },\n {\n match: ['bakery', 'baker', 'bread', 'pastry', 'cake', 'patisserie'],\n kin: [\n 'bakery',\n 'bread',\n 'pastry',\n 'cake',\n 'baking',\n 'baked',\n 'dough',\n 'croissant',\n ],\n broad: ['bakery', 'fresh bread'],\n making: ['baker kneading dough', 'baking bread'],\n },\n {\n match: ['coffee', 'cafe', 'café', 'roaster', 'espresso'],\n kin: [\n 'coffee',\n 'cafe',\n 'espresso',\n 'latte',\n 'cappuccino',\n 'barista',\n 'beans',\n ],\n broad: ['coffee shop', 'coffee'],\n making: ['barista making coffee'],\n },\n {\n match: ['florist', 'flower', 'floral'],\n kin: ['flower', 'floral', 'bouquet', 'florist', 'bloom', 'blossom'],\n broad: ['florist', 'flower bouquet'],\n making: ['florist arranging flowers'],\n },\n {\n match: [\n 'knit',\n 'knitting',\n 'yarn',\n 'weaving',\n 'weaver',\n 'textile',\n 'textiles',\n 'quilt',\n 'quilting',\n 'sewing',\n ],\n kin: [\n 'knit',\n 'knitting',\n 'yarn',\n 'wool',\n 'weaving',\n 'textile',\n 'fabric',\n 'quilt',\n 'sewing',\n ],\n broad: ['handmade textiles', 'yarn'],\n making: ['knitting', 'weaving loom'],\n },\n {\n match: ['leather', 'leatherwork', 'leathercraft'],\n kin: ['leather', 'wallet', 'bag', 'belt', 'stitching'],\n broad: ['leather craft', 'leather goods'],\n making: ['leather crafting'],\n },\n]\n\n/** Lowercase words, letters only, without stopwords. */\nfunction contentWords(text: string): string[] {\n return text\n .toLowerCase()\n .replace(/https?:\\/\\/\\S+|\\S+@\\S+|\\+?\\d[\\d\\s().-]{5,}\\d/g, ' ')\n .replace(/[^a-zÀ-ɏ\\s-]/g, ' ')\n .split(/\\s+/)\n .map((word) => word.replace(/^-+|-+$/g, ''))\n .filter((word) => word.length > 1 && !STOPWORDS.has(word))\n}\n\n/**\n * A word's singular, for matching only: \"bowls\" and \"bowl\", \"vases\" and\n * \"vase\", \"berries\" and \"berry\", \"dishes\" and \"dish\" meet. Both sides of a\n * comparison are reduced the same way, so an odd stem still matches itself.\n */\nexport function aiStockStem(word: string): string {\n const lower = word.toLowerCase()\n if (lower.length > 4 && lower.endsWith('ies')) return `${lower.slice(0, -3)}y`\n if (lower.length > 4 && /(?:ches|shes|sses|xes|zes)$/.test(lower))\n return lower.slice(0, -2)\n if (lower.length > 3 && lower.endsWith('s') && !/(?:ss|us|is)$/.test(lower))\n return lower.slice(0, -1)\n return lower\n}\n\n/** A text's words as stems, in order; a hyphenated word counts as its parts. */\nfunction stemList(text: string): string[] {\n return text\n .toLowerCase()\n .split(/[^a-zÀ-ɏ]+/)\n .filter((word) => word.length > 1)\n .map(aiStockStem)\n}\n\n/** Where a noun phrase ends: punctuation, a preposition, a conjunction. */\nconst CLAUSE_BREAK =\n /[,;:.()]|\\s[-–—]\\s|\\b(?:with|on|in|at|against|beside|next|near|under|over|by|from|for|during|while|atop|inside|across|beneath|through|into|onto|amid|among|and|of|that|which|who)\\b|&/\n\n/** A business type's clauses, each with the word that opened it. */\nfunction businessClauses(\n businessType: string,\n): Array<{ opener: string; words: string[] }> {\n const text = businessType.toLowerCase().replace(DOING, ' | ')\n const clauses: Array<{ opener: string; words: string[] }> = []\n const breaker = new RegExp(`${BUSINESS_BREAK.source}|\\\\|`, 'g')\n let at = 0\n let opener = ''\n for (const match of text.matchAll(breaker)) {\n clauses.push({ opener, words: contentWords(text.slice(at, match.index)) })\n opener = match[0].trim()\n at = (match.index ?? 0) + match[0].length\n }\n clauses.push({ opener, words: contentWords(text.slice(at)) })\n return clauses\n}\n\n/** A clause's words that name the business: no site kind, no filler. */\nconst businessOf = (clause: readonly string[]) =>\n clause.filter((word) => !SITE_KINDS.has(word) && !FILLER.has(word))\n\n/**\n * The kind of business in a few words: the first clause of the business\n * type that says more than what kind of site it is, before a place, an\n * audience or what it makes, filler dropped, its last three words (the head\n * of an English noun phrase is at its end). \"ceramic artist\" from \"a\n * portfolio for a ceramic artist who makes stoneware bowls\"; \"candle shop\"\n * from \"a small-batch candle shop selling hand-poured soy candles online\".\n */\nexport function aiStockBusinessWords(businessType: string): string {\n for (const { words: clause } of businessClauses(businessType)) {\n const named = businessOf(clause)\n if (named.length) return named.slice(-3).join(' ')\n }\n return ''\n}\n\n/**\n * The business's craft in one word: the last of its words that is not the\n * place it is done in nor the person doing it, singular — \"ceramic\" from\n * \"ceramics studio\" or \"ceramic artist\", \"yoga\" from \"yoga studio\". Empty\n * when the business is only a place (\"a shop\").\n */\nexport function aiStockCraftWords(business: string): string {\n const named = business\n .split(/\\s+/)\n .filter(\n (word) =>\n word &&\n !VENUES.has(word) &&\n !ROLES.has(word) &&\n !FILLER.has(word) &&\n !SITE_KINDS.has(word),\n )\n const craft = named[named.length - 1]\n return craft ? aiStockStem(craft) : ''\n}\n\n/** What a site's photos are searched and judged by (AGL-3660). */\nexport interface AiStockSiteTerms {\n /** The kind of business, in a few words: \"ceramic artist\". */\n business: string\n /** Its craft, one singular word: \"ceramic\". */\n craft: string\n /**\n * The stems a photo of the site's world names: the business's own words,\n * what the things it makes are made of, and the craft's kin. Empty for a\n * site whose words name no world (a type left blank), and then no hit is\n * held to it.\n */\n domain: string[]\n /** The searches for the site's world itself, broadest last. */\n broad: string[]\n /** The searches for its maker at work, for a story of the craft (AGL-3660); empty for a craft not listed. */\n making: string[]\n /** Every stem of the business type or brief: what lets a sensitive topic in ({@link SENSITIVE_TOPICS}). */\n named: string[]\n /** Rival crafts' words, each with the words that must then be named too ({@link CRAFT_KIN}). */\n rivals: Array<{ words: string[]; core: string[] }>\n}\n\n/** The site's terms, read from its business type. */\nexport function aiStockSiteTerms(businessType: string): AiStockSiteTerms {\n const business = aiStockBusinessWords(businessType)\n const craft = aiStockCraftWords(business)\n const domain = new Set<string>()\n /** The things it makes, by their head nouns. */\n const made = new Set<string>()\n let named = false\n let making = false\n for (const { opener, words: clause } of businessClauses(businessType)) {\n if (opener === '|') making = true\n // Where it is, and who it is for, are not its world — once something has named it.\n if (named && PLACE_OR_AUDIENCE.test(opener)) {\n making = false\n continue\n }\n const kept = businessOf(clause).filter(\n (word) => !VENUES.has(word) && !ROLES.has(word),\n )\n if (kept.length) named = true\n // What it makes names its world by what the things are made of, never\n // the things: \"stoneware\" of \"stoneware bowls\", \"soy\" of \"soy candles\".\n // A bowl is not a ceramic world; a robin's food bowl is not pottery.\n // A set or a gift is never a world: \"gift\" of \"gift sets\" is not one.\n const world = (making ? kept.slice(0, -1) : kept).filter(\n (word) => !WRAPPERS.has(word),\n )\n for (const word of world)\n for (const stem of stemList(word)) domain.add(stem)\n if (making)\n for (const word of kept.slice(-1))\n for (const stem of stemList(word)) made.add(stem)\n }\n if (craft) domain.add(craft)\n // A business named only by what it sells (\"a shop selling pottery\") is of that world.\n if (!domain.size) for (const stem of made) domain.add(stem)\n const broad: string[] = []\n const makingSearches: string[] = []\n const rivals: Array<{ words: string[]; core: string[] }> = []\n for (const group of CRAFT_KIN) {\n if (\n !group.match.some(\n (word) => domain.has(aiStockStem(word)) || made.has(aiStockStem(word)),\n )\n )\n continue\n for (const word of group.kin) domain.add(aiStockStem(word))\n broad.push(...group.broad)\n makingSearches.push(...group.making)\n if (group.rivals)\n rivals.push({\n words: group.rivals.words.map(aiStockStem),\n core: group.rivals.core.map(aiStockStem),\n })\n }\n if (!broad.length) broad.push(business, craft)\n return {\n business,\n craft,\n domain: [...domain].filter((stem) => stem.length > 1),\n broad: [...new Set(broad.map((query) => query.trim()).filter(Boolean))],\n making: [...new Set(makingSearches)],\n named: [...new Set(stemList(businessType))],\n rivals,\n }\n}\n\n/** A phrase's content words without its filler; all filler, and nothing is left. */\nfunction subjectOf(list: readonly string[]): string[] {\n return list.filter((word) => !FILLER.has(word))\n}\n\n/**\n * A picture's subject in a few words (AGL-3660): the noun phrase its alt\n * text opens with — its first clause holding more than filler, cut where a\n * preposition starts the setting, filler dropped, its last three words (the\n * head noun is the last) — else its section's name.\n */\nexport function aiStockSubjectWords(alt: string, sectionName: string): string {\n // \"Hand-poured\", \"by hand\" and \"hands\" say how it was made, never what it shows.\n for (const clause of withoutHands(alt).split(CLAUSE_BREAK)) {\n const found = subjectOf(contentWords(clause ?? ''))\n if (found.length) return found.slice(-3).join(' ')\n }\n return subjectOf(contentWords(withoutHands(sectionName))).slice(0, 2).join(' ')\n}\n\n/**\n * The thing a subject shows, one singular word: its last word that is not\n * a set or a wrapper of it — \"bowl\" from \"nesting bowl set\", \"candle\" from\n * \"candle gift set\", \"vase\" from \"bud vase\". Its last word when every word\n * is one (\"gift set\").\n */\nexport function aiStockObjectWord(subject: string): string {\n const list = subject.split(/\\s+/).filter(Boolean)\n const object =\n [...list].reverse().find((word) => !WRAPPERS.has(word)) ??\n list[list.length - 1] ??\n ''\n return object ? aiStockStem(object) : ''\n}\n\nconst clip = (query: string) =>\n query.replace(/\\s+/g, ' ').trim().slice(0, STOCK_PHOTO_QUERY_MAX_CHARS).trim()\n\n/** The frame's orientation: a wide frame wants a landscape photo, a tall one a portrait. */\nexport function aiStockOrientation(aspect: number): StockPhotoOrientation {\n if (aspect > 1.15) return 'horizontal'\n if (aspect < 0.87) return 'vertical'\n return 'any'\n}\n\n/**\n * One search a slot tries. A `broad` one asks for the site's world and\n * takes any hit of it; any other picture of a thing takes only a hit naming\n * the thing.\n */\nexport interface AiStockSearch extends StockPhotoSearchRequest {\n broad?: boolean\n /** The words its hits are scored by, where they are not the slot's own subject (a product's searches, AGL-3676). */\n subject?: string\n /** A word every hit must name: a product's photo names the shop's category (AGL-3676). */\n requires?: string\n /** The thing every hit must name, where it is not the subject's own: a product's head noun (AGL-3660). */\n object?: string\n /** Phrases that name the object where its own word is not enough: \"wax melt\" ({@link OBJECT_KIN}). */\n kin?: readonly string[]\n /** Whether the object must be among the hit's first tags, what the photo is OF (AGL-3660). */\n lead?: boolean\n}\n\n/** A slot's part of a page, where it asks for more than its role: a collection tile or a story of the craft (AGL-3660). */\nexport type AiStockSlotPart = 'collection' | 'story'\n\n/**\n * A slot's part (AGL-3660): a tile of a collections band is product-like,\n * held to the shop's category and its own object; a picture of a story of\n * the craft, or of a thing made by hand, shows the maker at work.\n */\nexport function aiStockSlotPart(\n slot: Pick<AiLayoutPictureSlot, 'role' | 'alt'>,\n sectionName: string,\n): AiStockSlotPart | undefined {\n if (slot.role === 'hero') return undefined\n if (slot.role === 'gallery' && aiStorefrontSays('collections', sectionName))\n return 'collection'\n if (\n aiStorefrontSays('story', sectionName) ||\n saysMadeByHand(slot.alt) ||\n saysMadeByHand(sectionName)\n )\n return 'story'\n return undefined\n}\n\n/** An object's kin phrases as stems, or none. */\nconst kinStems = (entry: (typeof OBJECT_KIN)[number] | null): string[] =>\n entry ? [...new Set(entry.kin.map((phrase) => stemList(phrase).join(' ')))] : []\n\n/**\n * A product's searches (AGL-3676), most specific first: each of its subjects\n * — its name's noun phrase, then its photo's — with the shop's category\n * where the phrase does not already say it (\"candle gift box\"), then the\n * category alone, a plain photo of what the shop sells. Every one must find a\n * hit naming the category, so no product is ever filled with a lifestyle shot\n * of something else: the beta.237 Willow Wick start put a laptop and roses\n * under \"Candle Wick Trimmer\" and an antique tea set under \"Candle Gift Set\".\n *\n * And every one must find a hit naming the product's own head noun among its\n * first tags (AGL-3660): the beta.239 Ember & Oak start put a bed with a book,\n * a mug and a camera (its candle a last tag) under \"Hand-Poured Soy Candle\".\n * An object whose own word is not enough ({@link OBJECT_KIN}) is searched by\n * its kin instead of the category alone, and named by them: a single taper\n * candle filled \"Wax Melt Gift Set\".\n */\nexport function aiStockProductSearches(\n slot: Pick<AiLayoutPictureSlot, 'aspect' | 'product'>,\n craft: string,\n domain: readonly string[] = [],\n): AiStockSearch[] {\n const orientation = aiStockOrientation(slot.aspect)\n const size = orientation === 'vertical' ? { minHeight: 900 } : { minWidth: 900 }\n const craftStems = stemList(craft)\n const subjects = (slot.product?.subjects ?? [])\n .map((raw) => aiStockSubjectWords(raw, ''))\n .filter(Boolean)\n // The product's head noun is its name's: \"candle\" of \"Hand-Poured Soy Candle\".\n const object = aiStockObjectWord(subjects[0] ?? '')\n const kin = object\n ? objectKin(object, [...craftStems, ...domain, ...stemList(subjects.join(' '))])\n : null\n const kinPhrases = kinStems(kin)\n const queries: Array<{ query: string; subject: string; object: string }> = []\n for (const subject of subjects) {\n const stems = stemList(subject)\n const query = kin\n ? stems.includes(aiStockStem(kin.qualifier))\n ? subject\n : `${kin.qualifier} ${subject}`\n : craft && !craftStems.every((stem) => stems.includes(stem))\n ? `${craft} ${subject}`\n : subject\n queries.push({ query, subject, object })\n }\n // The category alone stands in only for a thing that is no more than of it.\n // Its hits are still scored by the product's own words: a soy candle over a taper.\n const own = subjects[0] ?? ''\n if (kin) for (const query of kin.queries) queries.push({ query, subject: own || query, object })\n else if (craft) queries.push({ query: craft, subject: own || craft, object: craft })\n const seen = new Set<string>()\n const searches: AiStockSearch[] = []\n for (const entry of queries) {\n const query = clip(entry.query)\n if (!query || seen.has(query)) continue\n seen.add(query)\n searches.push({\n query,\n orientation,\n ...size,\n subject: entry.subject,\n lead: true,\n ...(entry.object ? { object: entry.object } : {}),\n ...(kinPhrases.length ? { kin: kinPhrases } : {}),\n ...(craft ? { requires: craft } : {}),\n })\n }\n return searches\n}\n\n/** The searches a slot tries, in order, most specific first, each distinct. */\nexport function aiStockSearchesFor(\n slot: Pick<AiLayoutPictureSlot, 'role' | 'alt' | 'aspect'>,\n terms: Pick<AiStockSiteTerms, 'business' | 'craft' | 'domain' | 'broad'> &\n Partial<Pick<AiStockSiteTerms, 'making'>>,\n subject: string,\n part?: AiStockSlotPart,\n): AiStockSearch[] {\n const orientation = aiStockOrientation(slot.aspect)\n const size =\n slot.role === 'hero'\n ? { minWidth: 1600 }\n : orientation === 'vertical'\n ? { minHeight: 900 }\n : { minWidth: 900 }\n const domain = new Set(terms.domain)\n const object = aiStockObjectWord(subject)\n const subjectStems = stemList(subject)\n const kin = object ? objectKin(object, [...terms.domain, ...subjectStems]) : null\n const kinPhrases = kinStems(kin)\n // The craft qualifies a subject that does not already name the site's world.\n const namesWorld = subjectStems.some((stem) => domain.has(stem))\n const qualify = (phrase: string) =>\n terms.craft && !namesWorld && !stemList(phrase).includes(terms.craft)\n ? `${terms.craft} ${phrase}`\n : phrase\n type Query = {\n query: string\n broad?: boolean\n people?: boolean\n held?: boolean\n category?: boolean\n }\n // A collection tile is product-like: held to the shop's category and its\n // own object among the first tags, and searched by its object's kin, never\n // the category's world alone (AGL-3660).\n const collection = part === 'collection' && Boolean(terms.craft)\n const broad: Query[] = kin && collection\n ? []\n : terms.broad.map((query) => ({ query, broad: true, category: collection }))\n // The maker at work, held to the craft: never \"hands\" (AGL-3660).\n const making: Query[] = (terms.making ?? []).map((query) => ({\n query,\n broad: true,\n category: true,\n }))\n const thing: Query[] = subject\n ? [\n { query: qualify(subject), held: true },\n ...(kin\n ? kin.queries.map((query) => ({ query, held: true }))\n : [{ query: qualify(object), held: true }]),\n ]\n : []\n const queries: Query[] =\n slot.role === 'hero'\n ? [{ query: terms.business, broad: true }, ...broad]\n : slot.role === 'about'\n ? [\n ...(part === 'story' ? making : []),\n {\n query: `${terms.business} ${subject}`,\n broad: true,\n people: true,\n },\n { query: terms.business, broad: true, people: true },\n ...(part === 'story' ? [] : making),\n ...broad,\n ]\n : [...(part === 'story' ? making : []), ...thing, ...broad]\n const seen = new Set<string>()\n const searches: AiStockSearch[] = []\n for (const raw of queries) {\n const query = clip(raw.query)\n const key = `${query}|${raw.people === true}`\n if (!query || seen.has(key)) continue\n seen.add(key)\n const held = raw.held === true && (collection || kinPhrases.length > 0)\n searches.push({\n query,\n orientation,\n ...size,\n ...(raw.people ? { people: true } : {}),\n ...(raw.broad ? { broad: true } : {}),\n ...(held && kinPhrases.length ? { kin: kinPhrases } : {}),\n ...(held && collection ? { lead: true } : {}),\n ...(terms.craft && (raw.category || (raw.held && collection))\n ? { requires: terms.craft }\n : {}),\n })\n }\n return searches\n}\n\n/** What a hit is judged by. */\nexport interface AiStockJudgement {\n /** The words the picture is of; empty for a picture of the site's world. */\n subject?: string\n /** The site's domain stems ({@link AiStockSiteTerms.domain}). */\n domain?: readonly string[]\n /**\n * Whether a hit must name the subject's object. A broad search, a hero\n * and an about picture need only name the domain.\n */\n strict?: boolean\n /** A word every hit must name: a product's photo names the shop's category (AGL-3676). */\n requires?: string\n /** The thing a hit must name, where it is not the subject's own (AGL-3660). */\n object?: string\n /** Phrases naming the object where its word is not enough; one of them is then required, and stands for the category. */\n kin?: readonly string[]\n /** Whether the object must be among the hit's first tags (AGL-3660). */\n lead?: boolean\n /** The stems of the site's business type or brief, which let a sensitive topic in ({@link AiStockSiteTerms.named}). */\n named?: readonly string[]\n /** Rival crafts: a hit naming one is rejected unless it names the site's core words ({@link AiStockSiteTerms.rivals}). */\n rivals?: ReadonlyArray<{ words: readonly string[]; core: readonly string[] }>\n}\n\n/** The words of a photo's page address at its library, its id dropped: \"robin-bird-songbird-garden-winter\". */\nfunction pageSlug(pageUrl: string): string {\n try {\n const last = decodeURIComponent(new URL(pageUrl).pathname)\n .split('/')\n .filter(Boolean)\n .pop()\n return last?.replace(/-?\\d+$/, '') ?? ''\n } catch {\n return ''\n }\n}\n\n/** A hit's words: the first few (what it is OF), every one, and each tag's phrase. */\nfunction hitWords(photo: Pick<StockPhoto, 'tags' | 'alt' | 'pageUrl'>) {\n const slug = pageSlug(photo.pageUrl)\n const tags = (photo.tags ?? [])\n .map((tag) => tag.toLowerCase().trim())\n .filter(Boolean)\n const lead = new Set(stemList([...tags.slice(0, 3), slug].join(' ')))\n const all = new Set(stemList([...tags, photo.alt ?? '', slug].join(' ')))\n const phrases = [\n ...tags.map((tag) => stemList(tag).join(' ')),\n stemList(photo.alt ?? '').join(' '),\n stemList(slug).join(' '),\n ]\n const leadPhrases = [\n ...tags.slice(0, 3).map((tag) => stemList(tag).join(' ')),\n stemList(slug).join(' '),\n ]\n const rejected = tags.some(\n (tag) =>\n REJECTED_TAGS.has(tag) ||\n tag.split(/\\s+/).some((word) => REJECTED_TAGS.has(word)),\n )\n // Whole words only, a hyphenated one kept whole: \"cross-stitch\" is not a cross.\n const words = new Set(\n [...tags, (photo.alt ?? '').toLowerCase()]\n .join(' ')\n .split(/[^a-zÀ-ɏ-]+/)\n .filter((word) => word.length > 1)\n .map(aiStockStem),\n )\n return { lead, all, phrases, leadPhrases, rejected, words }\n}\n\n/** Whether a phrase is among a hit's phrases, word for word. */\nconst among = (phrases: readonly string[], phrase: string) =>\n phrases.some((found) => ` ${found} `.includes(` ${phrase} `))\n\n/**\n * Whether a hit carries a topic its site never named ({@link SENSITIVE_TOPICS}):\n * a cross, a grave, a flag, a gun, a glass of wine, a child, a mannequin.\n */\nexport function aiStockSensitive(\n photo: Pick<StockPhoto, 'tags' | 'alt' | 'pageUrl'>,\n named: readonly string[] = [],\n): boolean {\n const { words: found } = hitWords(photo)\n const site = new Set(named)\n return SENSITIVE_STEMS.some(\n (topic) =>\n [...topic.words].some((word) => found.has(word)) &&\n ![...topic.allow].some((word) => site.has(word)),\n )\n}\n\n/**\n * How well a hit shows what a slot needs (AGL-3660), or 0 when it must not\n * fill it: a mockup or a blank never; a topic the site never named never\n * ({@link aiStockSensitive}); a picture of a thing only when the hit\n * names its object AND either the site's world or the subject's own\n * two-word phrase (\"bud vase\", \"wick trimmer\"); anything else only when it\n * names the site's world (when the site names one). Among the accepted, the\n * object among the hit's first tags counts 4 (a mention among its last, 2),\n * the subject's two-word phrase 3, each of its other words 1, and the\n * site's world 1, plus a quarter when it is among the first tags.\n */\nexport function aiStockRelevance(\n photo: Pick<StockPhoto, 'tags' | 'alt' | 'pageUrl'>,\n judgement: AiStockJudgement,\n): number {\n const hit = hitWords(photo)\n if (hit.rejected) return 0\n if (aiStockSensitive(photo, judgement.named)) return 0\n // Glass art is not pottery: a rival craft's photo names this craft's own words.\n for (const rival of judgement.rivals ?? [])\n if (\n rival.words.some((word) => hit.all.has(word)) &&\n !rival.core.some((word) => hit.all.has(word))\n )\n return 0\n const kin = judgement.kin ?? []\n const kinNamed = kin.some((phrase) => among(hit.phrases, phrase))\n const kinLead = kin.some((phrase) => among(hit.leadPhrases, phrase))\n const required = stemList(judgement.requires ?? '')\n // An object's kin stands for the category: a wax melt is the candle shop's.\n if (!kinNamed && required.some((stem) => !hit.all.has(stem))) return 0\n const subject = stemList(judgement.subject ?? '')\n const object =\n judgement.object ??\n (judgement.subject ? aiStockObjectWord(judgement.subject) : '')\n const domain = (judgement.domain ?? []).filter((stem) => stem !== object)\n // A hit naming the word it must name is of the site's world: a product's\n // category is that world even where it is the product's own object.\n const world =\n required.length > 0 || kinNamed || domain.some((stem) => hit.all.has(stem))\n const worldLead =\n (required.length > 0 && required.every((stem) => hit.lead.has(stem))) ||\n kinLead ||\n domain.some((stem) => hit.lead.has(stem))\n const pair = subject.length >= 2 ? subject.slice(-2).join(' ') : ''\n const phrased = Boolean(pair) && among(hit.phrases, pair)\n let score = 0\n if (judgement.strict && object) {\n // An object with kin is named only by them: \"melt\" alone is resin art.\n const named = kin.length ? kinNamed : hit.all.has(object)\n if (!named) return 0\n const leading = kin.length ? kinLead : hit.lead.has(object)\n // A lifestyle scene whose object is a last tag is not a photo OF it.\n if (judgement.lead && !leading) return 0\n if (domain.length && !world && !phrased) return 0\n score += leading ? 4 : 2\n if (kinNamed) score += 3\n } else if (domain.length) {\n if (!world) return 0\n }\n if (phrased) score += 3\n for (const word of new Set(subject))\n if (word !== object && hit.all.has(word)) score += 1\n if (world) score += worldLead ? 1.25 : 1\n return Math.max(score, 0.5)\n}\n\n/**\n * A search's acceptable hits in the order they are tried (AGL-3660): those\n * not placed yet, among the first few the library returned, those it must\n * not take dropped ({@link aiStockRelevance}), best first; the seed turns\n * the hits that score alike, so two jobs differ and one job repeats itself.\n */\nexport function aiStockRank(\n photos: readonly StockPhoto[],\n used: ReadonlySet<string>,\n seed: string,\n judgement: AiStockJudgement = {},\n): StockPhoto[] {\n const scored = photos\n .filter((photo) => !used.has(stockPhotoSourceKey(photo)))\n .slice(0, AI_LAYOUT_STOCK_PICK_WINDOW)\n .map((photo) => ({ photo, score: aiStockRelevance(photo, judgement) }))\n .filter((entry) => entry.score > 0)\n if (!scored.length) return []\n const turn = aiLayoutSeedNumber(seed)\n const ranked: StockPhoto[] = []\n for (const score of [...new Set(scored.map((entry) => entry.score))].sort(\n (a, b) => b - a,\n )) {\n const tier = scored.filter((entry) => entry.score === score)\n const start = turn % tier.length\n for (const entry of [...tier.slice(start), ...tier.slice(0, start)])\n ranked.push(entry.photo)\n }\n return ranked\n}\n\n/** The pick among a search's hits: the first {@link aiStockRank} would try, or `null`. */\nexport function aiStockPick(\n photos: readonly StockPhoto[],\n used: ReadonlySet<string>,\n seed: string,\n judgement: AiStockJudgement = {},\n): StockPhoto | null {\n return aiStockRank(photos, used, seed, judgement)[0] ?? null\n}\n\n/**\n * The library photos a stored page already shows: every `image` whose\n * source is the site's media reference. Starter photos are the platform's,\n * not the job's, and are not listed.\n */\nexport function aiStockPlacedSrcs(\n nodes: NodesMap | null | undefined,\n): string[] {\n const map = (nodes ?? {}) as unknown as Record<\n string,\n { componentId?: string; props?: Record<string, unknown> }\n >\n const out: string[] = []\n for (const node of Object.values(map)) {\n const src = node?.componentId === 'image' ? node.props?.['src'] : null\n if (typeof src === 'string' && src.startsWith('media:')) out.push(src)\n }\n return out\n}\n\n/**\n * What this process has placed for each job, by source key and asset src,\n * with the scope (the page's seed) that placed it (AGL-3660). It spans the\n * passes of one job that run here, so a page, the products or a post built\n * later does not place what an earlier one did, while a pass repeated over\n * its own page keeps its own photos. Bounded; the oldest job goes first.\n */\nconst JOB_PHOTOS = new Map<string, Map<string, string>>()\nconst JOB_PHOTOS_MAX = 200\n\nfunction jobPhotos(key: string | null): Map<string, string> | null {\n if (!key) return null\n let placed = JOB_PHOTOS.get(key)\n if (!placed) {\n placed = new Map()\n JOB_PHOTOS.set(key, placed)\n while (JOB_PHOTOS.size > JOB_PHOTOS_MAX) {\n const oldest = JOB_PHOTOS.keys().next().value\n if (oldest === undefined) break\n JOB_PHOTOS.delete(oldest)\n }\n }\n return placed\n}\n\n/** Forgets every job's placed photos; for specs. */\nexport function aiStockForgetJobPhotos(): void {\n JOB_PHOTOS.clear()\n}\n\nconst EXTENSIONS: Record<string, string> = {\n 'image/jpeg': 'jpg',\n 'image/png': 'png',\n 'image/webp': 'webp',\n}\n\nexport interface AiLayoutStockPhotoSourceInput {\n hostId: string\n /** The member the photos are stored as: the job's creator. */\n uid: string\n /** The job's seed, as the starter photos turn by; also the scope a photo is placed under. */\n seed: string\n /** The site's business type, else the job's brief. */\n business: string\n /** The page's section names, by plan index. */\n sectionNames: readonly string[]\n /**\n * The job the member started (`aiOriginJobId`), so a photo is placed once\n * across its pages. Absent, only the page itself is kept free of repeats.\n */\n jobId?: string\n /** Asset srcs or source keys the job's other pages already show. */\n avoid?: readonly string[]\n /** The most searches this source sends; a page's own bound where absent. A store's products search more (AGL-3676). */\n searches?: number\n signal?: AbortSignal\n}\n\nexport interface AiLayoutStockPhotoDeps {\n provider?: () => StockPhotoProvider | null\n ingest?: () => PluginMediaIngest | null\n budgetMs?: number\n}\n\n/** A slot's choice: the accepted hits it may be filled from, best first, and the query that found them. */\ninterface AiStockChoice {\n candidates: StockPhoto[]\n query: string\n}\n\n/**\n * The source for one page, or `null` when this deployment has no stock\n * photo library or no media door, which leaves every slot to the starters.\n */\nexport function aiLayoutStockPhotoSource(\n input: AiLayoutStockPhotoSourceInput,\n deps: AiLayoutStockPhotoDeps = {},\n): AiLayoutPictureSource | null {\n const provider = (deps.provider ?? stockPhotoProvider)()\n const ingest = (deps.ingest ?? pluginMediaIngest)()\n if (!provider || !ingest || !input.hostId || !input.uid) return null\n const terms = aiStockSiteTerms(input.business)\n return async (slots) => {\n const timeout = AbortSignal.timeout(\n deps.budgetMs ?? AI_LAYOUT_STOCK_PHOTOS_BUDGET_MS,\n )\n const signal = input.signal\n ? AbortSignal.any([input.signal, timeout])\n : timeout\n const scope = input.seed\n const job = jobPhotos(input.jobId ? `${input.hostId}:${input.jobId}` : null)\n const avoid = new Set(input.avoid ?? [])\n /** Placed elsewhere in the job: by another page in this process, or one the caller read. */\n const elsewhere = (key: string) =>\n avoid.has(key) || (job?.has(key) === true && job.get(key) !== scope)\n /** Source keys this page has claimed for a slot, or turned down. */\n const claimed = new Set<string>()\n /** Asset srcs this page has placed. */\n const placedSrcs = new Set<string>()\n const answers = new Map<string, Promise<StockPhoto[] | null>>()\n let searches = 0\n let storing = true\n const remember = (sourceKey: string, src: string) => {\n placedSrcs.add(src)\n job?.set(sourceKey, scope)\n job?.set(src, scope)\n }\n /** A search's hits; the same words twice are answered from memory, and `null` once the page's searches are spent. */\n const search = (wanted: AiStockSearch): Promise<StockPhoto[] | null> => {\n const request: StockPhotoSearchRequest = { ...wanted }\n delete (request as AiStockSearch).broad\n delete (request as AiStockSearch).subject\n delete (request as AiStockSearch).requires\n delete (request as AiStockSearch).object\n delete (request as AiStockSearch).kin\n delete (request as AiStockSearch).lead\n const key = JSON.stringify([\n request.query,\n request.orientation,\n request.people === true,\n ])\n const known = answers.get(key)\n if (known) return known\n if (searches >= (input.searches ?? AI_LAYOUT_STOCK_SEARCHES_PER_PAGE))\n return Promise.resolve(null)\n searches += 1\n const asked = provider\n .search(request, { signal })\n .then((found) => found?.photos ?? [])\n .catch(() => [] as StockPhoto[])\n answers.set(key, asked)\n return asked\n }\n /** The photo placed in the site's library, a copy it holds or a new one; `null` when it cannot be placed. */\n const keep = async (\n photo: StockPhoto,\n slot: AiLayoutPictureSlot,\n query: string,\n ): Promise<AiLayoutPicturePhoto | null> => {\n const sourceKey = stockPhotoSourceKey(photo)\n try {\n const kept = await ingest.findStockPhoto({\n hostId: input.hostId,\n sourceKey,\n })\n if (kept) {\n // Held because a slot of this page or another page of the job shows it.\n if (placedSrcs.has(kept.src) || elsewhere(kept.src)) return null\n remember(sourceKey, kept.src)\n return {\n src: kept.src,\n width: kept.width ?? photo.width,\n height: kept.height ?? photo.height,\n }\n }\n // A refusal to store (the storage band, a lockdown) holds for the\n // whole page, so the rest of it reuses what it can and copies nothing.\n if (!storing || signal.aborted) return null\n const bytes = await provider.download(photo, {\n maxBytes: AI_LAYOUT_STOCK_MAX_BYTES,\n signal,\n })\n if (!bytes || signal.aborted || !storing) return null\n const credit = provider.credit(photo)\n const stored = await ingest.ingest({\n hostId: input.hostId,\n uid: input.uid,\n fileName: `${photo.provider}-${photo.id}.${EXTENSIONS[bytes.contentType] ?? 'jpg'}`,\n contentType: bytes.contentType,\n bytes: bytes.bytes,\n alt: slot.alt,\n description: credit.text,\n stockPhoto: {\n key: sourceKey,\n provider: photo.provider,\n providerLabel: credit.providerLabel,\n id: photo.id,\n pageUrl: photo.pageUrl,\n photographer: photo.photographer,\n ...(photo.photographerUrl\n ? { photographerUrl: photo.photographerUrl }\n : {}),\n license: credit.license,\n licenseUrl: credit.licenseUrl,\n attributionRequired: credit.attributionRequired,\n query,\n },\n })\n if (stored.ok) {\n remember(sourceKey, stored.src)\n return {\n src: stored.src,\n width: stored.width ?? photo.width,\n height: stored.height ?? photo.height,\n }\n }\n storing = false\n const refusal = stored as Extract<\n PluginMediaIngestResult,\n { ok: false }\n >\n console.warn(\n 'ai layout pictures: the media library refused a stock photo',\n {\n status: refusal.status,\n reason: refusal.reason,\n },\n )\n } catch (error) {\n console.warn('ai layout pictures: a stock photo could not be kept', {\n error: String(error),\n })\n }\n return null\n }\n\n // 1. Choose, slot by slot, so the page's choices never collide: each\n // slot claims its best accepted hit before the next slot looks.\n const choices: Array<AiStockChoice | null> = []\n for (const [index, slot] of slots.entries()) {\n if (signal.aborted) {\n choices.push(null)\n continue\n }\n const sectionName = input.sectionNames[slot.sectionIndex] ?? ''\n const subject = aiStockSubjectWords(slot.alt, sectionName)\n let choice: AiStockChoice | null = null\n const requests = slot.product\n ? aiStockProductSearches(slot, terms.craft, terms.domain)\n : aiStockSearchesFor(\n slot,\n terms,\n subject,\n aiStockSlotPart(slot, sectionName),\n )\n for (const request of requests) {\n if (choice || signal.aborted) break\n const found = await search(request)\n if (found === null) break\n const blocked = new Set(claimed)\n for (const hit of found) {\n const key = stockPhotoSourceKey(hit)\n if (elsewhere(key)) blocked.add(key)\n }\n const ranked = aiStockRank(\n found,\n blocked,\n `${input.seed}:${index}:${request.query}`,\n {\n subject: request.subject ?? (request.broad ? '' : subject),\n domain: terms.domain,\n strict:\n (slot.role === 'gallery' && !request.broad) || !!request.requires,\n named: terms.named,\n rivals: terms.rivals,\n ...(request.requires ? { requires: request.requires } : {}),\n ...(request.object ? { object: request.object } : {}),\n ...(request.kin ? { kin: request.kin } : {}),\n ...(request.lead ? { lead: true } : {}),\n },\n )\n if (!ranked.length) continue\n const first = ranked[0] as StockPhoto\n claimed.add(stockPhotoSourceKey(first))\n choice = {\n candidates: ranked.slice(0, AI_LAYOUT_STOCK_TRIES_PER_SLOT),\n query: request.query,\n }\n }\n choices.push(choice)\n }\n\n // 2. Keep, a few at a time; a copy that fails tries the slot's next\n // accepted hit no other slot has claimed.\n const photos: Array<AiLayoutPicturePhoto | null> = slots.map(() => null)\n const fill = async (index: number) => {\n const choice = choices[index]\n const slot = slots[index]\n if (!choice || !slot) return\n for (const [rank, candidate] of choice.candidates.entries()) {\n if (signal.aborted) return\n const key = stockPhotoSourceKey(candidate)\n if (rank > 0) {\n if (claimed.has(key)) continue\n claimed.add(key)\n }\n const placed = await keep(candidate, slot, choice.query)\n if (placed) {\n photos[index] = placed\n return\n }\n }\n }\n let next = 0\n const worker = async () => {\n while (next < slots.length) {\n const index = next\n next += 1\n await fill(index)\n }\n }\n await Promise.all(\n Array.from(\n { length: Math.min(AI_LAYOUT_STOCK_COPIES_AT_ONCE, slots.length) },\n worker,\n ),\n )\n return photos\n }\n}\n"],"names":["pluginMediaIngest","STOCK_PHOTO_QUERY_MAX_CHARS","stockPhotoProvider","stockPhotoSourceKey","aiLayoutSeedNumber","aiStorefrontSays","AI_LAYOUT_STOCK_PHOTOS_BUDGET_MS","AI_LAYOUT_STOCK_SEARCHES_PER_PAGE","AI_LAYOUT_STOCK_PICK_WINDOW","AI_LAYOUT_STOCK_TRIES_PER_SLOT","AI_LAYOUT_STOCK_COPIES_AT_ONCE","AI_LAYOUT_STOCK_MAX_BYTES","AI_STOCK_PHOTO_PAGES_INPUT","AI_STOCK_PHOTO_AVOID_INPUT","words","list","Set","trim","split","STOPWORDS","FILLER","VENUES","ROLES","SITE_KINDS","DOING","BUSINESS_BREAK","PLACE_OR_AUDIENCE","WRAPPERS","REJECTED_TAGS","SENSITIVE_TOPICS","allow","always","SENSITIVE_STEMS","map","topic","aiStockStem","HAND_IDIOMS","withoutHands","text","reduce","out","idiom","replace","toLowerCase","saysMadeByHand","lower","some","lastIndex","found","test","OBJECT_KIN","objects","within","qualifier","kin","queries","objectKin","object","world","find","entry","word","includes","CRAFT_KIN","match","broad","rivals","core","making","contentWords","filter","length","has","endsWith","slice","stemList","CLAUSE_BREAK","businessClauses","businessType","clauses","breaker","RegExp","source","at","opener","matchAll","push","index","businessOf","clause","aiStockBusinessWords","named","join","aiStockCraftWords","business","craft","aiStockSiteTerms","domain","made","kept","stem","add","size","makingSearches","group","query","Boolean","subjectOf","aiStockSubjectWords","alt","sectionName","aiStockObjectWord","subject","reverse","clip","aiStockOrientation","aspect","aiStockSlotPart","slot","role","undefined","kinStems","phrase","aiStockProductSearches","subjects","orientation","minHeight","minWidth","craftStems","product","raw","kinPhrases","stems","every","own","seen","searches","lead","requires","aiStockSearchesFor","terms","part","subjectStems","namesWorld","qualify","collection","category","thing","held","people","key","pageSlug","pageUrl","last","decodeURIComponent","URL","pathname","pop","hitWords","photo","slug","tags","tag","all","phrases","leadPhrases","rejected","among","aiStockSensitive","site","aiStockRelevance","judgement","hit","rival","kinNamed","kinLead","required","worldLead","pair","phrased","score","strict","leading","Math","max","aiStockRank","photos","used","seed","scored","turn","ranked","sort","a","b","tier","start","aiStockPick","aiStockPlacedSrcs","nodes","node","Object","values","src","componentId","props","startsWith","JOB_PHOTOS","Map","JOB_PHOTOS_MAX","jobPhotos","placed","get","set","oldest","keys","next","value","delete","aiStockForgetJobPhotos","clear","EXTENSIONS","aiLayoutStockPhotoSource","input","deps","provider","ingest","hostId","uid","slots","timeout","AbortSignal","budgetMs","signal","any","scope","job","jobId","avoid","elsewhere","claimed","placedSrcs","answers","storing","remember","sourceKey","search","wanted","request","JSON","stringify","known","Promise","resolve","asked","then","catch","keep","findStockPhoto","width","height","aborted","bytes","download","maxBytes","credit","stored","fileName","id","contentType","description","stockPhoto","providerLabel","photographer","photographerUrl","license","licenseUrl","attributionRequired","ok","refusal","console","warn","status","reason","error","String","choices","entries","sectionNames","sectionIndex","choice","requests","blocked","first","candidates","fill","rank","candidate","worker","Array","from","min"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,iBAAiB,QAGZ,kDAAiD;AACxD,SACEC,2BAA2B,EAC3BC,kBAAkB,EAClBC,mBAAmB,QAKd,mDAAkD;AAEzD,SACEC,kBAAkB,QAIb,2CAAuC;AAC9C,SAASC,gBAAgB,QAAQ,6CAAyC;AAE1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkFC,GAED,+EAA+E,GAC/E,OAAO,MAAMC,mCAAmC,MAAM;AAEtD,yGAAyG,GACzG,OAAO,MAAMC,oCAAoC,GAAE;AAEnD,mCAAmC,GACnC,OAAO,MAAMC,8BAA8B,GAAE;AAE7C,kEAAkE,GAClE,MAAMC,iCAAiC;AAEvC,8BAA8B,GAC9B,MAAMC,iCAAiC;AAEvC,wDAAwD,GACxD,OAAO,MAAMC,4BAA4B,IAAI,OAAO,KAAI;AAExD;;;CAGC,GACD,OAAO,MAAMC,6BAA6B,kBAAiB;AAE3D;;;;CAIC,GACD,OAAO,MAAMC,6BAA6B,kBAAiB;AAE3D,MAAMC,QAAQ,CAACC,OAAiB,IAAIC,IAAID,KAAKE,IAAI,GAAGC,KAAK,CAAC;AAE1D,iCAAiC,GACjC,MAAMC,YAAYL,MAChB,6GACE,4GACA,iGACA,mFACA;AAGJ;;;CAGC,GACD,MAAMM,SAASN,MACb,kHACE,sGACA,wGACA,yGACA,iHACA,0GACA,qGACA,qGACA,4GACA,sDACA,yGACA;AAGJ,2FAA2F,GAC3F,MAAMO,SAASP,MACb,+GACE,qGACA;AAGJ,gFAAgF,GAChF,MAAMQ,QAAQR,MACZ;AAGF,kHAAkH,GAClH,MAAMS,aAAaT,MACjB,yGACE;AAGJ,yGAAyG,GACzG,MAAMU,QACJ;AAEF,6CAA6C,GAC7C,MAAMC,iBACJ;AAEF,qGAAqG,GACrG,MAAMC,oBACJ;AAEF;;;CAGC,GACD,MAAMC,WAAWb,MACf,kHACE;AAGJ;;;;CAIC,GACD,MAAMc,gBAAgBd,MACpB,iGACE;AAGJ;;;;;;;;CAQC,GACD,MAAMe,mBAKD;IACH;QACE,YAAY;QACZf,OAAO;YACL;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDgB,OAAO;YAAC;YAAY;YAAU;YAAgB;SAAU;IAC1D;IACA;QACE,sBAAsB;QACtBhB,OAAO;YACL;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDgB,OAAO;YAAC;YAAW;YAAY;YAAa;YAAY;SAAY;IACtE;IACA;QACE,YAAY;QACZhB,OAAO;YACL;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDgB,OAAO;YAAC;YAAY;YAAY;YAAa;YAAW;SAAY;IACtE;IACA;QACE,mBAAmB;QACnBhB,OAAO;YACL;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDgB,OAAO;YAAC;YAAW;YAAW;YAAW;YAAW;SAAU;IAChE;IACA;QACE,kDAAkD;QAClDhB,OAAO;YACL;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDgB,OAAO;YACL;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;IACH;IACA;QACE,qBAAqB;QACrBhB,OAAO;YAAC;YAAa;YAAW;YAAW;YAAY;YAAa;YAAQ;SAAQ;QACpFgB,OAAO;YAAC;YAAc;YAAQ;SAAQ;IACxC;IACA;QACE,mDAAmD;QACnDhB,OAAO;YAAC;YAAS;YAAY;YAAO;YAAQ;YAAQ;YAAU;YAAW;SAAS;QAClFgB,OAAO;YACL;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;IACH;IACA;QACE,oEAAoE;QACpE,yEAAyE;QACzE,mEAAmE;QACnEhB,OAAO;YACL;YACA;YA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"}
@@ -19,6 +19,20 @@ import type { NodesMap } from '@aglyn/aglyn/types/nodes';
19
19
  import { sendGa4SitePublished } from '@aglyn/tenant-data-admin/server/ga4-measurement-protocol';
20
20
  import { type Firestore } from 'firebase-admin/firestore';
21
21
  import type { AiJob, AiJobOutput, AiJobSitePublish } from '../model/ai-jobs.types';
22
+ import { type AiStoreLayoutLinks } from '../model/ai-layout-store-links';
23
+ /**
24
+ * A store's links a build drafted into the site's own layout (AGL-3676): the
25
+ * draft version, the links in it, and the site's page addresses they were
26
+ * checked against. Its publish makes that version the live one, or — where
27
+ * the layout changed since the draft was made — adds the same links to the
28
+ * version that is live now.
29
+ */
30
+ export interface AiSitePublishStoreLinks {
31
+ layoutId: string;
32
+ versionId: string;
33
+ links: AiStoreLayoutLinks;
34
+ screenPaths?: Readonly<Record<string, string>>;
35
+ }
22
36
  /**
23
37
  * A guided site start publishes what it built (AGL-3596).
24
38
  *
@@ -112,5 +126,9 @@ export declare function aiPublishGuidedSite(firestore: Firestore, input: {
112
126
  now: Date;
113
127
  /** The start owed its blog's posts and wrote none (AGL-3660): its links to the blog come out. */
114
128
  blogUnwritten?: boolean;
129
+ /** Paths the layout links that the start did not write, such as a store page (AGL-3676): their links come out. */
130
+ unwrittenHrefs?: readonly string[];
131
+ /** A store's links a build drafted into the site's own layout (AGL-3676), which go live with its pages. */
132
+ storeLinks?: AiSitePublishStoreLinks | null;
115
133
  }, deps?: AiSitePublishDeps): Promise<AiJobSitePublish>;
116
134
  export {};
@@ -23,6 +23,8 @@ import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cac
23
23
  import { sendGa4SitePublished } from "@aglyn/tenant-data-admin/server/ga4-measurement-protocol";
24
24
  import { FieldValue } from "firebase-admin/firestore";
25
25
  import { AI_SITE_BLOG_SLUGS } from "../model/ai-site-job.js";
26
+ import { aiLayoutWithStoreLinks } from "../model/ai-layout-store-links.js";
27
+ import { AI_STORE_LINKS_SOURCE_FIELD as STORE_LINKS_SOURCE_FIELD } from "./ai-job-store-links-layout.js";
26
28
  /**
27
29
  * Whether a build puts its pages live (AGL-3616): only where its request
28
30
  * asked to (`publish`, set by Assist's proposal) AND the member ticked the
@@ -176,7 +178,7 @@ function navEntriesOf(outputs) {
176
178
  * Firestore failure of the publish write itself reports every page as a
177
179
  * draft, for the same reason, and is logged.
178
180
  */ export async function aiPublishGuidedSite(firestore, input, deps = {}) {
179
- var _deps_sendSitePublished, _deps_dropCache, _job_hostId, _host_get, _accepted_find, _ref;
181
+ var _deps_sendSitePublished, _deps_dropCache, _job_hostId, _host_get, _accepted_find, _input_unwrittenHrefs, _ref, _input_storeLinks, _input_storeLinks1;
180
182
  const sendSitePublished = (_deps_sendSitePublished = deps.sendSitePublished) != null ? _deps_sendSitePublished : sendGa4SitePublished;
181
183
  const dropCache = (_deps_dropCache = deps.dropCache) != null ? _deps_dropCache : dropPluginSiteCache;
182
184
  const { job, now } = input;
@@ -252,11 +254,15 @@ function navEntriesOf(outputs) {
252
254
  hostId,
253
255
  pageIds: accepted.map((page)=>page.id),
254
256
  entries: navEntriesOf(pages).filter((entry)=>entries[entry.screenId]),
255
- droppedHrefs: aiSiteUnwrittenBlogHrefs(input.blogUnwritten),
257
+ droppedHrefs: [
258
+ ...aiSiteUnwrittenBlogHrefs(input.blogUnwritten),
259
+ ...(_input_unwrittenHrefs = input.unwrittenHrefs) != null ? _input_unwrittenHrefs : []
260
+ ],
256
261
  retiredHomeId: releases,
257
262
  homeId: (_ref = home == null ? void 0 : home.id) != null ? _ref : null,
258
263
  jobId: job.$id,
259
- now
264
+ now,
265
+ storeLinks: (_input_storeLinks = input.storeLinks) != null ? _input_storeLinks : null
260
266
  }).catch((error)=>{
261
267
  console.error('ai site publish: navigation not applied', {
262
268
  hostId,
@@ -265,6 +271,27 @@ function navEntriesOf(outputs) {
265
271
  });
266
272
  return null;
267
273
  });
274
+ // A store's links drafted into a layout the pages' navigation did not touch go live on their own.
275
+ const storeLinks = (_input_storeLinks1 = input.storeLinks) != null ? _input_storeLinks1 : null;
276
+ const storeLinksWrite = storeLinks && (layoutWrite == null ? void 0 : layoutWrite.layoutRef.id) !== storeLinks.layoutId ? await aiLayoutVersionWrite(firestore, {
277
+ hostId,
278
+ layoutId: storeLinks.layoutId,
279
+ transform: ()=>null,
280
+ storeLinks,
281
+ jobId: job.$id,
282
+ now
283
+ }).catch((error)=>{
284
+ console.error('ai site publish: store links not applied', {
285
+ hostId,
286
+ jobId: job.$id,
287
+ error
288
+ });
289
+ return null;
290
+ }) : null;
291
+ const layoutWrites = [
292
+ layoutWrite,
293
+ storeLinksWrite
294
+ ].filter((write)=>Boolean(write));
268
295
  const paths = [
269
296
  ...new Set([
270
297
  ...Object.values(entries),
@@ -300,10 +327,10 @@ function navEntriesOf(outputs) {
300
327
  merge: true
301
328
  });
302
329
  }
303
- if (layoutWrite) {
304
- batch.create(layoutWrite.versionRef, layoutWrite.version);
305
- batch.set(layoutWrite.layoutRef, {
306
- versionId: layoutWrite.versionRef.id,
330
+ for (const write of layoutWrites){
331
+ if (write.version) batch.create(write.versionRef, write.version);
332
+ batch.set(write.layoutRef, {
333
+ versionId: write.versionRef.id,
307
334
  updatedAt: now
308
335
  }, {
309
336
  merge: true
@@ -319,7 +346,7 @@ function navEntriesOf(outputs) {
319
346
  ],
320
347
  createdAt: FieldValue.serverTimestamp(),
321
348
  attempts: 0
322
- }, layoutWrite ? {
349
+ }, layoutWrites.length ? {
323
350
  entireHost: true
324
351
  } : {}));
325
352
  try {
@@ -349,7 +376,7 @@ function navEntriesOf(outputs) {
349
376
  hostId
350
377
  ],
351
378
  reason: 'guided AI site start published'
352
- }, layoutWrite ? {} : {
379
+ }, layoutWrites.length ? {} : {
353
380
  paths: {
354
381
  [hostId]: paths
355
382
  }
@@ -367,7 +394,8 @@ function navEntriesOf(outputs) {
367
394
  * their navigation entries and the home link moved off a retired starter;
368
395
  * `null` when there is nothing to change or no one layout to change.
369
396
  */ async function aiNavigationLayoutWrite(firestore, input) {
370
- if (!input.entries.length && !input.retiredHomeId && !input.droppedHrefs.length) return null;
397
+ var _input_storeLinks;
398
+ if (!input.entries.length && !input.retiredHomeId && !input.droppedHrefs.length && !input.storeLinks) return null;
371
399
  const hostRef = firestore.collection('hosts').doc(input.hostId);
372
400
  const layoutIds = new Set();
373
401
  for (const id of input.pageIds){
@@ -382,16 +410,49 @@ function navEntriesOf(outputs) {
382
410
  const [layoutId] = [
383
411
  ...layoutIds
384
412
  ];
385
- const layoutRef = hostRef.collection('layouts').doc(layoutId);
413
+ return aiLayoutVersionWrite(firestore, {
414
+ hostId: input.hostId,
415
+ layoutId,
416
+ transform: (nodes)=>aiLayoutWithNavigation(nodes, input),
417
+ storeLinks: (_input_storeLinks = input.storeLinks) != null ? _input_storeLinks : null,
418
+ jobId: input.jobId,
419
+ now: input.now
420
+ });
421
+ }
422
+ /**
423
+ * The new live version of one layout: `transform` applied to the version
424
+ * live now. A store's links drafted into this layout (AGL-3676) start it from
425
+ * that draft while the draft was made from the live version, and are added
426
+ * to the live version otherwise; a draft with nothing more to add becomes
427
+ * the live version as it is. `null` when nothing changes.
428
+ */ async function aiLayoutVersionWrite(firestore, input) {
429
+ var _input_storeLinks;
430
+ const { layoutId } = input;
431
+ const layoutRef = firestore.collection('hosts').doc(input.hostId).collection('layouts').doc(layoutId);
386
432
  const layout = await layoutRef.get();
387
433
  const currentVersionId = layout.get('versionId');
388
434
  if (!layout.exists || layout.get('deletedAt') != null || typeof currentVersionId !== 'string') return null;
389
435
  const current = await layoutRef.collection('versions').doc(currentVersionId).get();
390
- const nodes = current.exists ? decodeStoredNodes(current.get('nodes')) : null;
436
+ const links = ((_input_storeLinks = input.storeLinks) == null ? void 0 : _input_storeLinks.layoutId) === layoutId ? input.storeLinks : null;
437
+ const draftRef = links ? layoutRef.collection('versions').doc(links.versionId) : null;
438
+ const draft = draftRef ? await draftRef.get() : null;
439
+ const fromDraft = Boolean((draft == null ? void 0 : draft.exists) && draft.get(STORE_LINKS_SOURCE_FIELD) === currentVersionId);
440
+ const base = fromDraft && draft ? draft : current;
441
+ const nodes = base.exists ? decodeStoredNodes(base.get('nodes')) : null;
391
442
  if (!nodes) return null;
392
- const next = aiLayoutWithNavigation(nodes, input);
393
- const packed = next ? encodeStoredNodes(next) : null;
394
- if (!next || !packed) return null;
443
+ let next = input.transform(nodes);
444
+ if (links && !fromDraft) {
445
+ var _added_nodes;
446
+ const added = aiLayoutWithStoreLinks(next != null ? next : nodes, links.links, links.screenPaths);
447
+ next = (_added_nodes = added.nodes) != null ? _added_nodes : next;
448
+ }
449
+ if (!next) return fromDraft && draftRef ? {
450
+ layoutRef,
451
+ versionRef: draftRef,
452
+ version: null
453
+ } : null;
454
+ const packed = encodeStoredNodes(next);
455
+ if (!packed) return null;
395
456
  const versionRef = layoutRef.collection('versions').doc(createResourceUid());
396
457
  return {
397
458
  layoutRef,