@aglyn/plugins-ai 1.0.0-beta.272 → 1.0.0-beta.273
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.
- package/package.json +17 -17
- package/src/lib/components/ai-site-start-card.component.js +237 -234
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/jobs/ai-empty-site-sweep.d.ts +12 -1
- package/src/lib/jobs/ai-empty-site-sweep.js +15 -3
- package/src/lib/jobs/ai-empty-site-sweep.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +21 -2
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-seo-step.js +8 -4
- package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +39 -1
- package/src/lib/jobs/ai-job-site-datasets.js +111 -15
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +12 -3
- package/src/lib/jobs/ai-job-site-step.js +85 -11
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.d.ts +8 -0
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +3 -0
- package/src/lib/jobs/ai-jobs.js +4 -0
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-owner-photos.js +4 -4
- package/src/lib/jobs/ai-layout-owner-photos.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +35 -0
- package/src/lib/jobs/ai-layout-stock-photos.js +114 -43
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-site-late-photos.d.ts +143 -0
- package/src/lib/jobs/ai-site-late-photos.js +486 -0
- package/src/lib/jobs/ai-site-late-photos.js.map +1 -0
- package/src/lib/jobs/ai-site-publish.d.ts +18 -0
- package/src/lib/jobs/ai-site-publish.js +123 -5
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-site-late-photos-laurel-fork.d.ts +83 -0
- package/src/lib/jobs/fixtures/ai-site-late-photos-laurel-fork.js +1553 -0
- package/src/lib/jobs/fixtures/ai-site-late-photos-laurel-fork.js.map +1 -0
- package/src/lib/layout-language/ai-layout-pictures.d.ts +50 -13
- package/src/lib/layout-language/ai-layout-pictures.js +81 -19
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +10 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-photos.d.ts +119 -0
- package/src/lib/model/ai-site-photos.js +128 -0
- package/src/lib/model/ai-site-photos.js.map +1 -0
- package/src/lib/runtime/ai-doctrine-validators.d.ts +3 -1
- package/src/lib/runtime/ai-doctrine-validators.js +3 -1
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/seo-fields.d.ts +29 -4
- package/src/lib/runtime/seo-fields.js +58 -11
- package/src/lib/runtime/seo-fields.js.map +1 -1
- package/src/lib/tools/ai-seo-tool.d.ts +25 -1
- package/src/lib/tools/ai-seo-tool.js +61 -0
- package/src/lib/tools/ai-seo-tool.js.map +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-site-late-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 { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport { pluginMediaIngest, type PluginMediaIngest } from '@aglyn/aglyn/plugin-manager/plugin-media-ingest'\nimport { pluginResourceDraftWriter } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport {\n stockPhotoProvider,\n stockPhotoSourceKey,\n type StockPhotoProvider,\n} from '@aglyn/aglyn/plugin-manager/stock-photo-provider'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport type { Firestore } from 'firebase-admin/firestore'\nimport {\n aiLayoutJobPhotoPicks,\n aiLayoutPlacePictures,\n type AiLayoutPicturePhoto,\n type AiLayoutPictureSlot,\n} from '../layout-language/ai-layout-pictures'\nimport type { AiJob } from '../model/ai-jobs.types'\nimport { aiSiteWords } from '../model/ai-site-job'\nimport {\n aiSiteIsStarterSrc,\n aiSitePhotosLastRefusalMs,\n type AiSitePhotos,\n type AiSiteStarterList,\n type AiStockRefusedPicks,\n} from '../model/ai-site-photos'\nimport { AI_DATASET_DRAFT_RESOURCE } from './ai-build-unit-outcome'\nimport { aiOriginJobId } from './ai-job-draft-ids'\nimport { readAiDraftNodes, updateAiDraftNodes } from './ai-job-drafts'\nimport { aiSiteProductPhotoSrc } from './ai-job-site-content'\nimport { aiSiteDatasetPlacedPhotoDetails, aiSiteDatasetRecordsFromOwn } from './ai-job-site-datasets'\nimport {\n AI_LAYOUT_STOCK_MAX_BYTES,\n AI_STOCK_PHOTO_OWN_REFUSALS,\n aiStockIngestRequest,\n aiStockPhotoAlt,\n aiStockPhotoWordsOf,\n aiStockPlacedPhotos,\n aiStockProperWords,\n} from './ai-layout-stock-photos'\n\n/**\n * THE LATE PHOTO PASS OF A SITE START (AGL-3988).\n *\n * Every page and every dataset of a guided start is built in a pass of its\n * own, and each copies its own stock photos as it goes. Pixabay's image hosts\n * (behind Cloudflare) answer a burst of downloads with 429 for minutes at a\n * time, whatever the client waits inside one pass. The v1.0.0-beta.271 Laurel\n * Fork Photography start (job yZUqIPmjIZ, 2026-10-19 23:05Z) had 22 cover\n * downloads refused in the beat that built its datasets, 11 on its home page\n * and 20 later. Photo 1846114, refused at 23:08, was copied at 23:10:42. The\n * \"Gallery collections\" covers were written before the job had copied any\n * photo, and the home page was built first, so each fell back to the office\n * team and the desk, which also became the site's share image.\n *\n * So once every page is built, and before the site is published, the site\n * step runs this pass over what the job holds in `sitePhotos`:\n *\n * 1. **The refused picks again, after the burst.** It waits until\n * {@link AI_SITE_LATE_PHOTOS_WAIT_MS} after the last refusal, then asks\n * each picture's own picks for again from the search that found them,\n * which the provider's 24-hour cache answers. No model, no credits. A\n * pick a later page copied is taken from the library; two downloads\n * refused in a row say the burst is not over, and nothing more is\n * downloaded.\n * 2. **Then the job's own photos, everywhere.** A picture still on a starter\n * takes (a) its own pick where the page does not show it yet, else (b)\n * one of the photos the job copied that the page does not show, by the\n * matchers its pages and covers already use: a page's picture by role,\n * shape and words, a hero a wide photo (`aiLayoutJobPhotoPicks`); and\n * only where none is left, (c) one the page shows elsewhere, and last\n * the photo beside it, since the other choice is a starter that shows\n * another business. A record goes by its category, then the people its\n * moment shows, its neighbor's photo only where nothing else is left,\n * from the end that repeats least (`aiSiteDatasetRecordsFromOwn`). A\n * team's records take only their own picks, never a photo of something\n * else.\n *\n * The pages' drafts are written in place, only where a picture still holds\n * a starter; the records through the data plugin's writer, only where a\n * record still holds the starter it was written with. A job that copied no\n * photo at all keeps its starters. Nothing here throws: a picture is\n * decoration, and a site is never held back by one.\n */\n\n/** How long after the image hosts refused a pick it is asked for again: their bursts clear within minutes. */\nexport const AI_SITE_LATE_PHOTOS_WAIT_MS = 60_000\n\n/** The wall clock the refused picks may take to copy, inside the pass's signal. */\nexport const AI_SITE_LATE_PHOTOS_COPY_MS = 30_000\n\n/**\n * Downloads refused in a row after which the pass downloads nothing more: the\n * burst is not over, and each refusal costs the image hosts' own backoff.\n */\nexport const AI_SITE_LATE_PHOTOS_REFUSALS_IN_A_ROW = 2\n\n/** ASSUMED: the pages' drafts, the records, and the publish that follows in the same pass. */\nexport const AI_SITE_LATE_PHOTOS_WRITES_MS = 20_000\n\n/** The least time the pass needs: the wait, the copies, and the writes after them. */\nexport const AI_SITE_LATE_PHOTOS_MINIMUM_MS =\n AI_SITE_LATE_PHOTOS_WAIT_MS + AI_SITE_LATE_PHOTOS_COPY_MS + AI_SITE_LATE_PHOTOS_WRITES_MS\n\nexport interface AiSiteLatePhotosInput {\n firestore: Firestore\n job: Pick<AiJob, '$id' | 'orgId' | 'hostId' | 'createdBy' | 'inputs' | 'plan' | 'outputs'>\n photos: AiSitePhotos\n /** The pages the start built: only their pictures are filled, and only their photos are the job's. */\n pageIds: readonly string[]\n org: Readonly<Record<string, unknown>> | null\n now: Date\n signal?: AbortSignal\n}\n\nexport interface AiSiteLatePhotosResult {\n /** What is left for another pass, where this one ran out of time; `null` once it is done. */\n photos: AiSitePhotos | null\n /** Pictures whose refused picks were asked for again, and of them copied. */\n retried: number\n copied: number\n /** Pictures given one of the job's own photos. */\n filled: number\n /** Pictures still on a starter. */\n left: number\n}\n\nexport interface AiSiteLatePhotosDeps {\n provider?: () => StockPhotoProvider | null\n ingest?: () => PluginMediaIngest | null\n readNodes?: typeof readAiDraftNodes\n updateNodes?: typeof updateAiDraftNodes\n writerFor?: typeof pluginResourceDraftWriter\n now?: () => number\n sleep?: (ms: number, signal?: AbortSignal) => Promise<void>\n}\n\n/** Waits `ms`, or less when `signal` aborts first; resolves either way. */\nfunction sleepFor(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal?.aborted || ms <= 0) return resolve()\n const timer = setTimeout(done, ms)\n function done() {\n clearTimeout(timer)\n signal?.removeEventListener('abort', done)\n resolve()\n }\n signal?.addEventListener('abort', done, { once: true })\n })\n}\n\n/** One image of a stored page, in document order. */\ninterface PageImage {\n imageId: string\n src: string\n width: number\n height: number\n alt: string\n}\n\n/**\n * A stored page's pictures in document order, as its picture slots were:\n * every `image` showing a photo, never one bound to a record's field.\n */\nexport function aiSitePageImages(nodes: NodesMap, rootId = CANVAS_ROOT_ELEMENT_ID): PageImage[] {\n const map = nodes as unknown as Record<string, { componentId?: string; props?: Record<string, unknown>; nodes?: string[] }>\n const images: PageImage[] = []\n const seen = new Set<string>()\n const visit = (id: string) => {\n if (seen.has(id)) return\n seen.add(id)\n const node = map[id]\n if (!node) return\n if (node.componentId === 'image') {\n const src = node.props?.['src']\n if (typeof src === 'string' && src.trim() && !src.includes('{{')) {\n images.push({\n imageId: id,\n src,\n width: Number(node.props?.['intrinsicWidth']) || 0,\n height: Number(node.props?.['intrinsicHeight']) || 0,\n alt: typeof node.props?.['alt'] === 'string' ? (node.props['alt'] as string) : '',\n })\n }\n }\n for (const child of node.nodes ?? []) visit(child)\n }\n visit(rootId)\n return images\n}\n\n/** A starter slot as the matchers read it: its words, role and shape, and nothing it is not. */\nfunction slotOf(\n imageId: string,\n entry: { words: string; role: AiLayoutPictureSlot['role']; aspect: number } | null,\n): AiLayoutPictureSlot {\n return {\n imageId,\n frameId: null,\n iconId: null,\n alt: entry?.words ?? '',\n aspect: entry && entry.aspect > 0 ? entry.aspect : 1,\n sectionIndex: entry?.role === 'hero' ? 0 : 1,\n role: entry?.role ?? 'gallery',\n }\n}\n\n/**\n * The page with each starter its plan names given its photo, re-read on the\n * stored page: an image that no longer holds a starter is left as it is.\n */\nexport function aiSitePageWithPhotos(\n nodes: NodesMap,\n plan: ReadonlyArray<{ slot: AiLayoutPictureSlot; photo: AiLayoutPicturePhoto }>,\n): NodesMap | null {\n const map = nodes as unknown as Record<string, { props?: Record<string, unknown> }>\n const live = plan.filter(({ slot }) => aiSiteIsStarterSrc(map[slot.imageId]?.props?.['src']))\n if (!live.length) return null\n return aiLayoutPlacePictures(\n nodes,\n live.map(({ slot }) => slot),\n // A photo that brings no words of its own takes the slot's, never the starter's (AGL-3831).\n live.map(({ slot, photo }) => (photo.alt?.trim() || !slot.alt ? photo : { ...photo, alt: slot.alt })),\n )\n}\n\n/** Runs the late photo pass (see the module note). Never throws. */\nexport async function aiSiteLatePhotos(\n input: AiSiteLatePhotosInput,\n deps: AiSiteLatePhotosDeps = {},\n): Promise<AiSiteLatePhotosResult> {\n const { job, signal } = input\n const now = deps.now ?? Date.now\n const sleep = deps.sleep ?? sleepFor\n const readNodes = deps.readNodes ?? readAiDraftNodes\n const updateNodes = deps.updateNodes ?? updateAiDraftNodes\n const writerFor = deps.writerFor ?? pluginResourceDraftWriter\n const hostId = job.hostId ?? ''\n const built = new Set(input.pageIds)\n const pages = input.photos.pages.filter((page) => built.has(page.screen) && page.slots.length)\n const lists = input.photos.lists\n const counts = { retried: 0, copied: 0, filled: 0, left: 0 }\n const done = (): AiSiteLatePhotosResult => ({ photos: null, ...counts })\n if (!hostId || (!pages.length && !lists.length)) return done()\n const provider = (deps.provider ?? stockPhotoProvider)()\n const ingest = (deps.ingest ?? pluginMediaIngest)()\n try {\n // ── 1. The refused picks, asked for again once the burst is over ──\n const retried = new Map<string, AiLayoutPicturePhoto>()\n const asked: Array<{ key: string; picks: AiStockRefusedPicks; slot: AiLayoutPictureSlot }> = [\n // The pages first, each one's hero first: what a visitor sees first.\n ...pages.flatMap((page) =>\n [...page.slots]\n .sort((a, b) => Number(b.role === 'hero') - Number(a.role === 'hero'))\n .flatMap((slot) => (slot.refused ? [{ key: `${page.screen}:${slot.image}`, picks: slot.refused, slot: slotOf(slot.image, slot) }] : [])),\n ),\n ...lists.flatMap((list) =>\n list.records.flatMap((record, index) =>\n record.refused && aiSiteIsStarterSrc(record.value)\n ? [\n {\n key: `${list.dataset}:${index}`,\n picks: record.refused,\n slot: {\n ...slotOf(`record${index}`, { words: record.name, role: list.people ? 'about' : 'gallery', aspect: list.people ? 4 / 5 : 4 / 3 }),\n // A record's name is a search's words, never what its photo shows (AGL-3856).\n standIn: true,\n },\n },\n ]\n : [],\n ),\n ),\n ]\n if (asked.length && provider && ingest) {\n const last = aiSitePhotosLastRefusalMs({ pages, lists })\n const wait = last === null ? 0 : Math.min(AI_SITE_LATE_PHOTOS_WAIT_MS, Math.max(0, last + AI_SITE_LATE_PHOTOS_WAIT_MS - now()))\n if (wait > 0) await sleep(wait, signal)\n // The pass's own time ran out while it waited: the next pass picks it up.\n if (signal?.aborted) return { photos: input.photos, ...counts }\n const timeout = AbortSignal.timeout(AI_SITE_LATE_PHOTOS_COPY_MS)\n const copying = signal ? AbortSignal.any([signal, timeout]) : timeout\n const words = aiStockPhotoWordsOf(job)\n const proper = aiStockProperWords(aiSiteWords(job.inputs).about)\n /**\n * Whether a pick not in the site's library yet is still downloaded:\n * not once the library refused to store one, nor once two downloads\n * in a row were refused, which says the burst is not over. The picks\n * a later page copied are still looked up in the library.\n */\n let downloading = true\n let refusedInARow = 0\n for (const entry of asked) {\n if (copying.aborted) break\n counts.retried += 1\n const found = await provider.search(entry.picks.request, { signal: copying }).catch(() => null)\n for (const sourceKey of entry.picks.keys) {\n if (copying.aborted) break\n const photo = found?.photos.find((hit) => stockPhotoSourceKey(hit) === sourceKey)\n if (!photo) continue\n const alt = aiStockPhotoAlt(photo, entry.slot, { ...(words.language ? { language: words.language } : {}), proper }) ?? entry.slot.alt\n const kept = await ingest.findStockPhoto({ hostId, sourceKey }).catch(() => null)\n if (kept) {\n retried.set(entry.key, { src: kept.src, width: kept.width ?? photo.width, height: kept.height ?? photo.height, alt })\n break\n }\n if (!downloading) continue\n const bytes = await provider.download(photo, { maxBytes: AI_LAYOUT_STOCK_MAX_BYTES, signal: copying }).catch(() => null)\n if (copying.aborted) break\n if (!bytes) {\n refusedInARow += 1\n if (refusedInARow >= AI_SITE_LATE_PHOTOS_REFUSALS_IN_A_ROW) downloading = false\n continue\n }\n refusedInARow = 0\n const stored = await ingest\n .ingest(aiStockIngestRequest(provider, { hostId, uid: job.createdBy, photo, bytes, alt, query: entry.picks.request.query }))\n .catch(() => null)\n if (stored?.ok) {\n retried.set(entry.key, { src: stored.src, width: stored.width ?? photo.width, height: stored.height ?? photo.height, alt })\n counts.copied += 1\n break\n }\n // A refusal of this photo's own bytes tries the next pick; any other holds for the pass (AGL-3923).\n if (!stored || !AI_STOCK_PHOTO_OWN_REFUSALS.has(stored.status)) downloading = false\n }\n }\n }\n\n // The pass's own time ran out while it copied: the next pass finds what it copied in the library.\n if (signal?.aborted) return { photos: input.photos, ...counts }\n\n // ── 2. The job's own photos, for every picture still on a starter ──\n const read = new Map<string, NodesMap | null>()\n for (const id of built) {\n const stored = await readNodes(input.firestore, { kind: 'screen', hostId, id }).catch(() => null)\n read.set(id, stored?.nodes ?? null)\n }\n const seen = new Set<string>()\n const pool = [\n ...[...read.values()].flatMap((nodes) => aiStockPlacedPhotos(nodes)),\n ...lists.flatMap((list) => list.records.flatMap((record) => (record.photo ? [record.photo] : []))),\n ...aiSiteDatasetPlacedPhotoDetails(job.outputs ?? []),\n ...retried.values(),\n ].filter((photo) => photo.src.startsWith('media:') && !seen.has(photo.src) && Boolean(seen.add(photo.src)))\n const origin = aiOriginJobId(job)\n\n for (const page of pages) {\n const nodes = read.get(page.screen)\n if (!nodes) continue\n const images = aiSitePageImages(nodes)\n const entries = new Map(page.slots.map((slot) => [slot.image, slot]))\n const slots = images.map((image) => slotOf(image.imageId, entries.get(image.imageId) ?? null))\n const placed: Array<AiLayoutPicturePhoto | null> = images.map((image) =>\n aiSiteIsStarterSrc(image.src) ? null : { src: image.src, width: image.width, height: image.height, alt: image.alt },\n )\n const need = images.map((image) => entries.has(image.imageId) && aiSiteIsStarterSrc(image.src))\n const photos: Array<AiLayoutPicturePhoto | null> = images.map(() => null)\n // (a) A picture's own pick first, where the page does not show it yet;\n // else it is one of the job's photos like any other.\n images.forEach((image, index) => {\n const own = need[index] ? retried.get(`${page.screen}:${image.imageId}`) : undefined\n if (!own || placed.some((photo) => photo?.src === own.src)) return\n photos[index] = own\n placed[index] = own\n need[index] = false\n })\n // (b) Then the job's photos, by the matcher the page's own pass used: one\n // the page does not show first; (c) a repeat only where nothing else is\n // left, since the other choice is a starter that shows another business.\n if (pool.length) {\n aiLayoutJobPhotoPicks(slots, need, pool, placed, `${origin}:${page.screen}:late`, { repeats: 'last' }).forEach((pick, index) => {\n if (pick && need[index]) photos[index] = pick\n })\n }\n const starters = images.filter((image) => entries.has(image.imageId) && aiSiteIsStarterSrc(image.src)).length\n const plan = images.flatMap((image, index) => {\n const photo = photos[index]\n return photo ? [{ slot: slots[index] as AiLayoutPictureSlot, photo }] : []\n })\n const update = plan.length\n ? await updateNodes(input.firestore, {\n kind: 'screen',\n hostId,\n id: page.screen,\n now: input.now,\n update: (current) => aiSitePageWithPhotos(current, plan),\n })\n : null\n const filled = update?.ok ? plan.length : 0\n counts.filled += filled\n counts.left += Math.max(0, starters - filled)\n }\n\n for (const list of lists) {\n const filled = aiSiteListPhotos(list, retried, pool, `${origin}:${list.dataset}:late`)\n const records = filled.flatMap((photo, index) => {\n const record = list.records[index]\n const value = photo ? aiSiteProductPhotoSrc(photo.src, hostId) : null\n if (!record || !photo || !value || !aiSiteIsStarterSrc(record.value)) return []\n return [\n {\n order: index,\n // The starter's words go with it: a photo that brings none leaves the description empty (AGL-3856).\n values: { [list.imageField]: value, ...(list.altField ? { [list.altField]: photo.alt?.trim() ?? '' } : {}) },\n where: { [list.imageField]: record.value },\n },\n ]\n })\n const starters = list.records.filter((record) => aiSiteIsStarterSrc(record.value)).length\n if (!records.length) {\n counts.left += starters\n continue\n }\n const keeper = writerFor(AI_DATASET_DRAFT_RESOURCE)\n if (!keeper?.writer.reviseRecords) {\n console.warn('ai site late photos: the dataset writer cannot change records here', { jobId: job.$id, dataset: list.dataset })\n counts.left += starters\n continue\n }\n const revised = await keeper.writer.reviseRecords({\n orgId: job.orgId,\n hostId,\n uid: job.createdBy,\n org: input.org,\n now: input.now,\n id: list.dataset,\n records,\n })\n const changed = revised.ok ? revised.changed : 0\n if (revised.ok === false) {\n console.warn('ai site late photos: the dataset writer refused the covers', { jobId: job.$id, dataset: list.dataset, status: revised.status })\n }\n counts.filled += changed\n counts.left += Math.max(0, starters - changed)\n }\n console.info('ai site late photos', { jobId: job.$id, hostId, ...counts })\n return done()\n } catch (error) {\n // The site is built either way; its pictures keep what they hold.\n if (signal?.aborted) return { photos: input.photos, ...counts }\n console.error('ai site late photos failed; the site keeps its pictures as they are', { jobId: job.$id, error: String(error) })\n return done()\n }\n}\n\n/**\n * A list's records' photos once the late pass is done (AGL-3988): each\n * record's own refused pick where it was copied, then, for a list of things,\n * one of the photos it or the job shows (`aiSiteDatasetRecordsFromOwn`). A\n * team's records take only their own picks: no photo of the job's is of\n * one of the business's people. Pure.\n */\nexport function aiSiteListPhotos(\n list: AiSiteStarterList,\n retried: ReadonlyMap<string, AiLayoutPicturePhoto>,\n pool: readonly AiLayoutPicturePhoto[],\n seed: string,\n): Array<AiLayoutPicturePhoto | null> {\n const current = list.records.map((record, index): AiLayoutPicturePhoto | null =>\n aiSiteIsStarterSrc(record.value) ? (retried.get(`${list.dataset}:${index}`) ?? null) : (record.photo ?? null),\n )\n if (list.people) return current\n return aiSiteDatasetRecordsFromOwn(\n list.records.map((record) => ({ name: record.name, category: record.category ?? null })),\n current,\n seed,\n pool,\n // A starter is the other choice: a record every photo sits beside takes one anyway.\n { besideLast: true },\n )\n}\n"],"names":["CANVAS_ROOT_ELEMENT_ID","pluginMediaIngest","pluginResourceDraftWriter","stockPhotoProvider","stockPhotoSourceKey","aiLayoutJobPhotoPicks","aiLayoutPlacePictures","aiSiteWords","aiSiteIsStarterSrc","aiSitePhotosLastRefusalMs","AI_DATASET_DRAFT_RESOURCE","aiOriginJobId","readAiDraftNodes","updateAiDraftNodes","aiSiteProductPhotoSrc","aiSiteDatasetPlacedPhotoDetails","aiSiteDatasetRecordsFromOwn","AI_LAYOUT_STOCK_MAX_BYTES","AI_STOCK_PHOTO_OWN_REFUSALS","aiStockIngestRequest","aiStockPhotoAlt","aiStockPhotoWordsOf","aiStockPlacedPhotos","aiStockProperWords","AI_SITE_LATE_PHOTOS_WAIT_MS","AI_SITE_LATE_PHOTOS_COPY_MS","AI_SITE_LATE_PHOTOS_REFUSALS_IN_A_ROW","AI_SITE_LATE_PHOTOS_WRITES_MS","AI_SITE_LATE_PHOTOS_MINIMUM_MS","sleepFor","ms","signal","Promise","resolve","aborted","timer","setTimeout","done","clearTimeout","removeEventListener","addEventListener","once","aiSitePageImages","nodes","rootId","map","images","seen","Set","visit","id","node","has","add","componentId","src","props","trim","includes","push","imageId","width","Number","height","alt","child","slotOf","entry","frameId","iconId","words","aspect","sectionIndex","role","aiSitePageWithPhotos","plan","live","filter","slot","length","photo","aiSiteLatePhotos","input","deps","job","now","Date","sleep","readNodes","updateNodes","writerFor","hostId","built","pageIds","pages","photos","page","screen","slots","lists","counts","retried","copied","filled","left","provider","ingest","Map","asked","flatMap","sort","a","b","refused","key","image","picks","list","records","record","index","value","dataset","name","people","standIn","last","wait","Math","min","max","timeout","AbortSignal","copying","any","proper","inputs","about","downloading","refusedInARow","found","search","request","catch","sourceKey","keys","find","hit","language","kept","findStockPhoto","set","bytes","download","maxBytes","stored","uid","createdBy","query","ok","status","read","firestore","kind","pool","values","outputs","startsWith","Boolean","origin","get","entries","placed","need","forEach","own","undefined","some","repeats","pick","starters","update","current","aiSiteListPhotos","order","imageField","altField","where","keeper","writer","reviseRecords","console","warn","jobId","$id","revised","orgId","org","changed","info","error","String","seed","category","besideLast"],"mappings":"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"}
|
|
@@ -20,6 +20,7 @@ import { sendGa4SitePublished } from '@aglyn/tenant-data-admin/server/ga4-measur
|
|
|
20
20
|
import { type Firestore } from 'firebase-admin/firestore';
|
|
21
21
|
import type { AiJob, AiJobOutput, AiJobSitePublish } from '../model/ai-jobs.types';
|
|
22
22
|
import { type AiStoreLayoutLinks } from '../model/ai-layout-store-links';
|
|
23
|
+
import type { AiLayoutPicturePhoto } from '../layout-language/ai-layout-pictures';
|
|
23
24
|
/**
|
|
24
25
|
* A store's links a build drafted into the site's own layout (AGL-3676): the
|
|
25
26
|
* draft version, the links in it, and the site's page addresses they were
|
|
@@ -180,6 +181,23 @@ export declare function aiRestoreGuidedBlogEntry(firestore: Firestore, input: {
|
|
|
180
181
|
blog: AiSiteBlogEntry;
|
|
181
182
|
now: Date;
|
|
182
183
|
}, deps?: Pick<AiSitePublishDeps, 'dropCache'>): Promise<boolean>;
|
|
184
|
+
/**
|
|
185
|
+
* Whether a site's share image is the publish's to set (AGL-3988): it has
|
|
186
|
+
* none, or still the starter photo every new site is created with. An image
|
|
187
|
+
* its owner picked is theirs, whatever it is.
|
|
188
|
+
*/
|
|
189
|
+
export declare function aiSiteShareImageOpen(seo: unknown): boolean;
|
|
190
|
+
/**
|
|
191
|
+
* The photo a published site's share image is set from (AGL-3988): its home
|
|
192
|
+
* page's hero — the first library photo of its first section — else the
|
|
193
|
+
* widest photo its pages show, a wide one first. `null` where they show no
|
|
194
|
+
* library photo. The beta.271 Laurel Fork start shared an office team on a
|
|
195
|
+
* wedding photographer's every link: nothing set the share image from what
|
|
196
|
+
* the start built, so it kept the starter the site was created with. Pure.
|
|
197
|
+
*/
|
|
198
|
+
export declare function aiSiteSharePhoto(home: NodesMap | null, pages: ReadonlyArray<NodesMap | null>): AiLayoutPicturePhoto | null;
|
|
199
|
+
/** The host fields the share image is written in, as the Social image card writes them (AGL-3988). */
|
|
200
|
+
export declare function aiSiteShareImageUpdates(photo: AiLayoutPicturePhoto): Record<string, unknown>;
|
|
183
201
|
/**
|
|
184
202
|
* A retry's publish merged into the start's first (AGL-3730): the pages it
|
|
185
203
|
* put live join the published list, a page it published leaves the drafts,
|
|
@@ -21,6 +21,7 @@ import { blockingRouteOwner, normalizeScreenSlug, SCREEN_ROOT_PATH, screenRouteP
|
|
|
21
21
|
import { resolvePageLocale, siteLanguageCode } from "@aglyn/aglyn/app-utils/seo-locale";
|
|
22
22
|
import { decodeStoredNodes, encodeStoredNodes } from "@aglyn/aglyn/app-utils/stored-nodes";
|
|
23
23
|
import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cache";
|
|
24
|
+
import { CANVAS_ROOT_ELEMENT_ID } from "@aglyn/aglyn/foundation/constants/canvas";
|
|
24
25
|
import { sendGa4SitePublished } from "@aglyn/tenant-data-admin/server/ga4-measurement-protocol";
|
|
25
26
|
import { FieldValue } from "firebase-admin/firestore";
|
|
26
27
|
import { aiLayoutFrameHollows, aiLayoutFrameHollowsSaid, aiLayoutFrameIsHollow, aiLayoutRepairFrameHollows, aiLayoutWithoutFrameHollows } from "../layout-language/ai-layout-frame-hollows.js";
|
|
@@ -30,7 +31,8 @@ import { aiSiteSeoProposalForInputs } from "../model/ai-site-start-seo.js";
|
|
|
30
31
|
import { aiLayoutWithStoreLinks } from "../model/ai-layout-store-links.js";
|
|
31
32
|
import { aiLayoutWithMenuChanges, aiNoMenuChanges } from "../model/ai-layout-menus.js";
|
|
32
33
|
import { AI_STORE_LINKS_SOURCE_FIELD as STORE_LINKS_SOURCE_FIELD } from "./ai-job-store-links-layout.js";
|
|
33
|
-
import { updateAiDraftNodes } from "./ai-job-drafts.js";
|
|
34
|
+
import { readAiDraftNodes, updateAiDraftNodes } from "./ai-job-drafts.js";
|
|
35
|
+
import { aiSiteIsStarterSrc } from "../model/ai-site-photos.js";
|
|
34
36
|
/**
|
|
35
37
|
* Whether a build puts its pages live (AGL-3616): only where its request
|
|
36
38
|
* asked to (`publish`, set by Assist's proposal) AND the member ticked the
|
|
@@ -386,6 +388,90 @@ function navEntriesOf(outputs) {
|
|
|
386
388
|
if (dropped.complete) await outboxRef.delete().catch(()=>undefined);
|
|
387
389
|
return true;
|
|
388
390
|
}
|
|
391
|
+
/**
|
|
392
|
+
* Whether a site's share image is the publish's to set (AGL-3988): it has
|
|
393
|
+
* none, or still the starter photo every new site is created with. An image
|
|
394
|
+
* its owner picked is theirs, whatever it is.
|
|
395
|
+
*/ export function aiSiteShareImageOpen(seo) {
|
|
396
|
+
const image = seo == null ? void 0 : seo.image;
|
|
397
|
+
if (image === undefined || image === null) return true;
|
|
398
|
+
return typeof image === 'string' && (!image.trim() || aiSiteIsStarterSrc(image));
|
|
399
|
+
}
|
|
400
|
+
/** The library photos a stored page shows, in document order, each once, with whether it sits in the page's first section. */ function libraryPhotosOf(nodes) {
|
|
401
|
+
var _ref;
|
|
402
|
+
var _map_CANVAS_ROOT_ELEMENT_ID_nodes, _map_CANVAS_ROOT_ELEMENT_ID;
|
|
403
|
+
if (!nodes) return [];
|
|
404
|
+
const map = nodes;
|
|
405
|
+
const firstSection = (_ref = (_map_CANVAS_ROOT_ELEMENT_ID = map[CANVAS_ROOT_ELEMENT_ID]) == null ? void 0 : (_map_CANVAS_ROOT_ELEMENT_ID_nodes = _map_CANVAS_ROOT_ELEMENT_ID.nodes) == null ? void 0 : _map_CANVAS_ROOT_ELEMENT_ID_nodes[0]) != null ? _ref : null;
|
|
406
|
+
const out = [];
|
|
407
|
+
const seen = new Set();
|
|
408
|
+
const visit = (id, lead)=>{
|
|
409
|
+
var _node_nodes;
|
|
410
|
+
if (seen.has(id)) return;
|
|
411
|
+
seen.add(id);
|
|
412
|
+
const node = map[id];
|
|
413
|
+
if (!node) return;
|
|
414
|
+
if (node.componentId === 'image') {
|
|
415
|
+
var _node_props, _node_props1, _node_props2;
|
|
416
|
+
const src = (_node_props = node.props) == null ? void 0 : _node_props['src'];
|
|
417
|
+
const width = Number((_node_props1 = node.props) == null ? void 0 : _node_props1['intrinsicWidth']);
|
|
418
|
+
const height = Number((_node_props2 = node.props) == null ? void 0 : _node_props2['intrinsicHeight']);
|
|
419
|
+
if (typeof src === 'string' && src.startsWith('media:') && width > 0 && height > 0) {
|
|
420
|
+
var _node_props3;
|
|
421
|
+
const alt = typeof ((_node_props3 = node.props) == null ? void 0 : _node_props3['alt']) === 'string' ? node.props['alt'].trim() : '';
|
|
422
|
+
out.push(_extends({
|
|
423
|
+
src,
|
|
424
|
+
width,
|
|
425
|
+
height
|
|
426
|
+
}, alt ? {
|
|
427
|
+
alt
|
|
428
|
+
} : {}, {
|
|
429
|
+
lead
|
|
430
|
+
}));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
for (const child of (_node_nodes = node.nodes) != null ? _node_nodes : [])visit(child, lead || child === firstSection);
|
|
434
|
+
};
|
|
435
|
+
visit(CANVAS_ROOT_ELEMENT_ID, false);
|
|
436
|
+
return out;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* The photo a published site's share image is set from (AGL-3988): its home
|
|
440
|
+
* page's hero — the first library photo of its first section — else the
|
|
441
|
+
* widest photo its pages show, a wide one first. `null` where they show no
|
|
442
|
+
* library photo. The beta.271 Laurel Fork start shared an office team on a
|
|
443
|
+
* wedding photographer's every link: nothing set the share image from what
|
|
444
|
+
* the start built, so it kept the starter the site was created with. Pure.
|
|
445
|
+
*/ export function aiSiteSharePhoto(home, pages) {
|
|
446
|
+
const hero = libraryPhotosOf(home).find((photo)=>photo.lead);
|
|
447
|
+
const pick = (photo)=>_extends({
|
|
448
|
+
src: photo.src,
|
|
449
|
+
width: photo.width,
|
|
450
|
+
height: photo.height
|
|
451
|
+
}, photo.alt ? {
|
|
452
|
+
alt: photo.alt
|
|
453
|
+
} : {});
|
|
454
|
+
if (hero) return pick(hero);
|
|
455
|
+
const all = [
|
|
456
|
+
home,
|
|
457
|
+
...pages
|
|
458
|
+
].flatMap(libraryPhotosOf);
|
|
459
|
+
const wide = (photo)=>photo.width / photo.height >= 1.3;
|
|
460
|
+
const [best] = [
|
|
461
|
+
...all
|
|
462
|
+
].sort((a, b)=>Number(wide(b)) - Number(wide(a)) || b.width - a.width);
|
|
463
|
+
return best ? pick(best) : null;
|
|
464
|
+
}
|
|
465
|
+
/** The host fields the share image is written in, as the Social image card writes them (AGL-3988). */ export function aiSiteShareImageUpdates(photo) {
|
|
466
|
+
var _ref;
|
|
467
|
+
var _photo_alt;
|
|
468
|
+
return {
|
|
469
|
+
'seo.image': photo.src,
|
|
470
|
+
'seo.imageWidth': photo.width,
|
|
471
|
+
'seo.imageHeight': photo.height,
|
|
472
|
+
'seo.imageAlt': (_ref = (_photo_alt = photo.alt) == null ? void 0 : _photo_alt.trim()) != null ? _ref : ''
|
|
473
|
+
};
|
|
474
|
+
}
|
|
389
475
|
/**
|
|
390
476
|
* A retry's publish merged into the start's first (AGL-3730): the pages it
|
|
391
477
|
* put live join the published list, a page it published leaves the drafts,
|
|
@@ -413,7 +499,8 @@ function navEntriesOf(outputs) {
|
|
|
413
499
|
* Firestore failure of the publish write itself reports every page as a
|
|
414
500
|
* draft, for the same reason, and is logged.
|
|
415
501
|
*/ export async function aiPublishGuidedSite(firestore, input, deps = {}) {
|
|
416
|
-
var _deps_sendSitePublished, _deps_dropCache, _job_hostId, _host_get, _input_siteOrder, _input_unbuiltScreenIds, _accepted_find, _input_unwrittenHrefs, _ref, _input_storeLinks, _input_storeLinks1, _input_language, _input_language1;
|
|
502
|
+
var _deps_sendSitePublished, _deps_dropCache, _job_hostId, _host_get, _input_siteOrder, _input_unbuiltScreenIds, _accepted_find, _input_unwrittenHrefs, _ref, _input_storeLinks, _input_storeLinks1, _input_language, _input_language1, _ref1, _ref2;
|
|
503
|
+
var _Object_entries_find;
|
|
417
504
|
const sendSitePublished = (_deps_sendSitePublished = deps.sendSitePublished) != null ? _deps_sendSitePublished : sendGa4SitePublished;
|
|
418
505
|
const dropCache = (_deps_dropCache = deps.dropCache) != null ? _deps_dropCache : dropPluginSiteCache;
|
|
419
506
|
const { job, now } = input;
|
|
@@ -442,9 +529,9 @@ function navEntriesOf(outputs) {
|
|
|
442
529
|
const accepted = [];
|
|
443
530
|
const siteOrder = (_input_siteOrder = input.siteOrder) != null ? _input_siteOrder : [];
|
|
444
531
|
for (const page of pages){
|
|
445
|
-
var
|
|
532
|
+
var _ref3, _snapshot_get, _snapshot_get1;
|
|
446
533
|
const snapshot = await hostRef.collection('screens').doc(page.id).get();
|
|
447
|
-
const label = String((
|
|
534
|
+
const label = String((_ref3 = (_snapshot_get = snapshot.get('displayName')) != null ? _snapshot_get : page.label) != null ? _ref3 : '') || page.label;
|
|
448
535
|
if (!snapshot.exists || snapshot.get('deletedAt') != null) {
|
|
449
536
|
result.drafts.push({
|
|
450
537
|
id: page.id,
|
|
@@ -621,7 +708,22 @@ function navEntriesOf(outputs) {
|
|
|
621
708
|
// `og:site_name` and /llms.txt read it, so it asks for the whole site too.
|
|
622
709
|
const listing = aiSiteListingUpdates(host.get('seo'), siteName, aiSiteSeoProposalForInputs(job.inputs, (_input_language1 = input.language) != null ? _input_language1 : null, siteName));
|
|
623
710
|
Object.assign(hostUpdates, listing);
|
|
624
|
-
|
|
711
|
+
// The site's share image (AGL-3988), where it has none or the starter it
|
|
712
|
+
// was created with: its home hero's photo, else its widest. Every page's
|
|
713
|
+
// `og:image` falls back to it, so it asks for the whole site too.
|
|
714
|
+
// A retry's home page is the one already live at the site's address.
|
|
715
|
+
const homeId = (_ref1 = (_ref2 = home == null ? void 0 : home.id) != null ? _ref2 : (_Object_entries_find = Object.entries(before).find(([id, path])=>path === SCREEN_ROOT_PATH && id !== starter)) == null ? void 0 : _Object_entries_find[0]) != null ? _ref1 : null;
|
|
716
|
+
const share = aiSiteShareImageOpen(host.get('seo')) ? await aiSiteSharePhotoOf(firestore, hostId, accepted, homeId).catch((error)=>{
|
|
717
|
+
console.error('ai site publish: the share image could not be read', {
|
|
718
|
+
hostId,
|
|
719
|
+
jobId: job.$id,
|
|
720
|
+
error
|
|
721
|
+
});
|
|
722
|
+
return null;
|
|
723
|
+
}) : null;
|
|
724
|
+
const shareImage = share ? aiSiteShareImageUpdates(share) : {};
|
|
725
|
+
Object.assign(hostUpdates, shareImage);
|
|
726
|
+
const entireHost = layoutWrites.length > 0 || language !== null || Object.keys(entity).length > 0 || Object.keys(listing).length > 0 || Object.keys(shareImage).length > 0;
|
|
625
727
|
batch.update(hostRef, hostUpdates);
|
|
626
728
|
for (const page of accepted){
|
|
627
729
|
batch.set(hostRef.collection('screens').doc(page.id), _extends({
|
|
@@ -696,6 +798,22 @@ function navEntriesOf(outputs) {
|
|
|
696
798
|
});
|
|
697
799
|
return result;
|
|
698
800
|
}
|
|
801
|
+
/** The share image's photo, read off the pages going live (AGL-3988). */ async function aiSiteSharePhotoOf(firestore, hostId, pages, homeId) {
|
|
802
|
+
const read = async (id)=>{
|
|
803
|
+
var _ref;
|
|
804
|
+
var _this;
|
|
805
|
+
return (_ref = (_this = await readAiDraftNodes(firestore, {
|
|
806
|
+
kind: 'screen',
|
|
807
|
+
hostId,
|
|
808
|
+
id
|
|
809
|
+
})) == null ? void 0 : _this.nodes) != null ? _ref : null;
|
|
810
|
+
};
|
|
811
|
+
const [home, ...others] = await Promise.all([
|
|
812
|
+
homeId ? read(homeId) : Promise.resolve(null),
|
|
813
|
+
...pages.filter((page)=>page.id !== homeId).map((page)=>read(page.id))
|
|
814
|
+
]);
|
|
815
|
+
return aiSiteSharePhoto(home != null ? home : null, others);
|
|
816
|
+
}
|
|
699
817
|
/**
|
|
700
818
|
* The new live version of the layout the published pages render inside, with
|
|
701
819
|
* their navigation entries and the home link moved off a retired starter;
|