@aglyn/plugins-ai 1.0.0-beta.277 → 1.0.0-beta.282

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 (76) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +2 -26
  3. package/src/lib/activity/ai-activity-actions.js +5 -42
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/activity/ai-activity-words.d.ts +42 -0
  6. package/src/lib/activity/ai-activity-words.js +63 -0
  7. package/src/lib/activity/ai-activity-words.js.map +1 -0
  8. package/src/lib/components/ai-insight-card.component.d.ts +2 -6
  9. package/src/lib/components/ai-insight-card.component.js +2 -6
  10. package/src/lib/components/ai-insight-card.component.js.map +1 -1
  11. package/src/lib/components/ai-insight-dialog.component.js +22 -32
  12. package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
  13. package/src/lib/components/ai-job-links.js +15 -0
  14. package/src/lib/components/ai-job-links.js.map +1 -1
  15. package/src/lib/components/assist-panel.component.js +3 -1
  16. package/src/lib/components/assist-panel.component.js.map +1 -1
  17. package/src/lib/jobs/ai-build-capabilities.js +23 -0
  18. package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
  19. package/src/lib/jobs/ai-job-build-step.js +2 -1
  20. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  21. package/src/lib/jobs/ai-job-import-step.d.ts +1 -0
  22. package/src/lib/jobs/ai-job-import-step.js +17 -11
  23. package/src/lib/jobs/ai-job-import-step.js.map +1 -1
  24. package/src/lib/jobs/ai-job-plan-step.js +3 -2
  25. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  26. package/src/lib/jobs/ai-job-privacy-step.d.ts +184 -0
  27. package/src/lib/jobs/ai-job-privacy-step.js +471 -0
  28. package/src/lib/jobs/ai-job-privacy-step.js.map +1 -0
  29. package/src/lib/jobs/ai-site-inspiration.d.ts +10 -0
  30. package/src/lib/jobs/ai-site-inspiration.js +15 -0
  31. package/src/lib/jobs/ai-site-inspiration.js.map +1 -1
  32. package/src/lib/model/ai-insight-ask.d.ts +57 -0
  33. package/src/lib/model/ai-insight-ask.js +64 -0
  34. package/src/lib/model/ai-insight-ask.js.map +1 -0
  35. package/src/lib/model/ai-insight.d.ts +1 -6
  36. package/src/lib/model/ai-insight.js +2 -8
  37. package/src/lib/model/ai-insight.js.map +1 -1
  38. package/src/lib/model/ai-job-activity.js +1 -0
  39. package/src/lib/model/ai-job-activity.js.map +1 -1
  40. package/src/lib/model/ai-jobs.types.d.ts +5 -1
  41. package/src/lib/model/ai-jobs.types.js +1 -0
  42. package/src/lib/model/ai-jobs.types.js.map +1 -1
  43. package/src/lib/model/ai-permissions.js +6 -2
  44. package/src/lib/model/ai-permissions.js.map +1 -1
  45. package/src/lib/model/ai-site-import.d.ts +2 -0
  46. package/src/lib/model/ai-site-import.js +2 -0
  47. package/src/lib/model/ai-site-import.js.map +1 -1
  48. package/src/lib/model/ai-site-kinds.d.ts +10 -28
  49. package/src/lib/model/ai-site-kinds.js +12 -496
  50. package/src/lib/model/ai-site-kinds.js.map +1 -1
  51. package/src/lib/providers/anthropic.js +23 -14
  52. package/src/lib/providers/anthropic.js.map +1 -1
  53. package/src/lib/providers/contract.d.ts +39 -4
  54. package/src/lib/providers/contract.js +28 -0
  55. package/src/lib/providers/contract.js.map +1 -1
  56. package/src/lib/providers/routing.d.ts +3 -0
  57. package/src/lib/providers/routing.js +4 -3
  58. package/src/lib/providers/routing.js.map +1 -1
  59. package/src/lib/runtime/ai-dev-env.d.ts +3 -2
  60. package/src/lib/runtime/ai-dev-env.js +3 -2
  61. package/src/lib/runtime/ai-dev-env.js.map +1 -1
  62. package/src/lib/runtime/ai-page-links.d.ts +5 -2
  63. package/src/lib/runtime/ai-page-links.js +11 -3
  64. package/src/lib/runtime/ai-page-links.js.map +1 -1
  65. package/src/lib/runtime/ai-runtime.js +5 -2
  66. package/src/lib/runtime/ai-runtime.js.map +1 -1
  67. package/src/lib/server/assist-chat.js +58 -30
  68. package/src/lib/server/assist-chat.js.map +1 -1
  69. package/src/lib/server.js +2 -0
  70. package/src/lib/server.js.map +1 -1
  71. package/src/lib/site-import/fixtures/site-import-crawl-recordings.d.ts +3 -1
  72. package/src/lib/site-import/fixtures/site-import-crawl-recordings.js +19 -1
  73. package/src/lib/site-import/fixtures/site-import-crawl-recordings.js.map +1 -1
  74. package/src/lib/site-import/site-import-page.d.ts +2 -2
  75. package/src/lib/site-import/site-import-page.js +11 -1
  76. package/src/lib/site-import/site-import-page.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-import.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * BRING MY SITE: THE WHOLE SITE, IN REVIEWED WAVES (AGL-3976, parent AGL-3973).\n *\n * The `import` job reads a website the workspace has PROVED it owns\n * (AGL-3974's claims) and brings it in as draft pages of a new site. This\n * module is the job's plan of record, pure, so the step, the doors, the\n * console and the specs read one set of rules:\n *\n * - **The inventory.** The crawl's pages, grouped: the MAIN pages (home, the\n * pages its menu links, every top-level page, and any page that belongs to\n * no collection), then each COLLECTION (an address prefix holding at least\n * `AI_SITE_IMPORT_COLLECTION_MIN` pages: `/blog/…`, `/news/…`, a dated\n * `/2024/05/…` archive).\n * - **Never past the plan.** Pages are kept in that order until the site's\n * room under its plan's page count is used; the rest are LEFT OUT, said so\n * with the reason, never dropped silently. So a plan's limit always leaves\n * out a collection's tail before it leaves out a main page.\n * - **Waves.** The kept pages are cut into waves of at most\n * `AI_SITE_IMPORT_WAVE_PAGES`. The first wave is the main pages, with the\n * site's header and footer built first; each later wave is one\n * collection's next slice, so a review is about one kind of page at a\n * time (\"Blog, 1 of 3\"). A wave runs, then waits for the person: nothing\n * starts the next one but them.\n * - **The price.** An imported page is an AI page (`AI_MEASURED_IMPORT_CREDITS`,\n * about 27 credits); a wave is quoted before it starts as its pages and,\n * for the first, its header and footer.\n *=========================================*/\n\nimport { aiCreditRangeOrdered, AI_MEASURED_IMPORT_CREDITS, type AiCreditRange } from './ai-credit-estimate'\nimport type { AiJobItemLedger } from './ai-jobs.types'\n\n/** The job kind. */\nexport const AI_SITE_IMPORT_KIND = 'import' as const\n\n/** The input naming the proved domain the job brings in. */\nexport const AI_SITE_IMPORT_DOMAIN_INPUT = 'domain'\n\n/** The most pages one wave brings in. */\nexport const AI_SITE_IMPORT_WAVE_PAGES = 10\n\n/**\n * The most pages one import reads, whatever the plan allows: an unlimited\n * plan is still one crawl's worth, and the crawl's own time budget may stop\n * sooner (the inventory then says so).\n */\nexport const AI_SITE_IMPORT_MAX_PAGES = 200\n\n/** The fewest pages under one address prefix that make it a collection. */\nexport const AI_SITE_IMPORT_COLLECTION_MIN = 3\n\n/** The most photos brought in from one page. */\nexport const AI_SITE_IMPORT_IMAGES_PER_PAGE = 8\n\n/** The most characters of one page's text a wave keeps for its model answer. */\nexport const AI_SITE_IMPORT_PAGE_TEXT_MAX_CHARS = 6_000\n\n/** The most sections one imported page is planned with. */\nexport const AI_SITE_IMPORT_MAX_SECTIONS = 6\n\n/** The most characters a section's name, a page's title or a label keeps. */\nexport const AI_SITE_IMPORT_LABEL_MAX_CHARS = 60\n\n/** A page with fewer words than this and no photo has nothing to bring in. */\nexport const AI_SITE_IMPORT_MIN_WORDS = 5\n\n/** The item ledger slot of the site's header and footer. */\nexport const AI_SITE_IMPORT_FRAME_SLOT = 'frame'\n\n/** The ceiling one imported page reserves: its answer pass and its listing pass, at 50 each. */\nexport const AI_SITE_IMPORT_PAGE_CEILING_CREDITS = 100\n\n/** The ceiling the header and footer reserve: one answer at 50. */\nexport const AI_SITE_IMPORT_FRAME_CEILING_CREDITS = 50\n\n/** The most left-out pages listed by address; the rest are counted. */\nexport const AI_SITE_IMPORT_LEFT_OUT_LISTED = 60\n\n/** A page the crawl read, as the inventory keeps it: no text, only what plans it. */\nexport interface AiSiteImportListedPage {\n url: string\n path: string\n title: string\n foundBy: 'home' | 'sitemap' | 'link'\n words: number\n images: number\n}\n\nexport type AiSiteImportGroupKind = 'main' | 'collection'\n\nexport interface AiSiteImportGroup {\n /** `main`, or `c:<prefix>` for a collection. */\n id: string\n label: string\n kind: AiSiteImportGroupKind\n /** Pages of the group the crawl read, kept or not. */\n pages: number\n}\n\n/** Why a page the site has is not brought in. */\nexport type AiSiteImportLeftOutReason =\n /** Past the site's room under its plan's page count. */\n | 'plan-limit'\n /** robots.txt keeps AglynBot off it. */\n | 'robots'\n /** A sign-in page: refused outright. */\n | 'login-form'\n /** No text and no photo: nothing to bring in. */\n | 'empty'\n /** It could not be read: an error, no answer, or not a web page. */\n | 'unreadable'\n /** Over the size one page may be. */\n | 'too-large'\n /** It sends visitors to another website. */\n | 'off-site'\n\nexport const AI_SITE_IMPORT_LEFT_OUT_REASONS: readonly AiSiteImportLeftOutReason[] = [\n 'plan-limit',\n 'robots',\n 'login-form',\n 'empty',\n 'unreadable',\n 'too-large',\n 'off-site',\n]\n\nexport interface AiSiteImportLeftOut {\n url: string\n path: string\n title: string\n reason: AiSiteImportLeftOutReason\n}\n\n/** A photo brought into the site's media library, as a page places it. */\nexport interface AiSiteImportPhoto {\n mediaId: string\n src: string\n width?: number\n height?: number\n alt?: string\n}\n\n/** What a wave's read found on one page, for its model answer. Never on the wire. */\nexport interface AiSiteImportPageRead {\n at: string\n title: string\n /** The page's text in order, cut to `AI_SITE_IMPORT_PAGE_TEXT_MAX_CHARS`; fenced only when a prompt reads it. */\n text: string\n /** The page's sections, from its headings. */\n sections: string[]\n /** Its photos' addresses, at most `AI_SITE_IMPORT_IMAGES_PER_PAGE`. */\n images: Array<{ url: string; alt: string }>\n}\n\nexport interface AiSiteImportPage {\n /** The item ledger slot: `p0`, `p1`, … */\n slot: string\n url: string\n path: string\n title: string\n group: string\n wave: number\n /** The id the page's draft is written under, minted with the inventory. */\n draftId: string\n /** The page's address on the new site. */\n slug: string\n words: number\n images: number\n /** Filled by the wave's read; the text is cleared once the page is in. */\n read?: AiSiteImportPageRead | null\n /** Set when the wave's read found the page cannot be brought in after all. */\n refused?: AiSiteImportLeftOutReason | null\n /** Filled once the page's photos are in the library. */\n photos?: {\n brought: number\n /** Already in the library from an earlier page or import: the same file, never a second copy. */\n reused: number\n skipped: number\n assets: AiSiteImportPhoto[]\n } | null\n}\n\nexport type AiSiteImportWaveStatus = 'waiting' | 'running' | 'review' | 'done'\n\nexport interface AiSiteImportWave {\n index: number\n label: string\n /** The group the wave brings in; the first wave's is `main`. */\n group: string\n slots: string[]\n /** Whether the wave builds the site's header and footer first. */\n frame: boolean\n status: AiSiteImportWaveStatus\n startedAt: string | null\n finishedAt: string | null\n}\n\nexport interface AiSiteImportState {\n domain: string\n /** The site's name, as Create site was given it: the header's name. */\n siteName: string\n ownership: { method: string | null; provenAt: number | null; detail: string | null }\n readAt: string\n /** The plan's page count; `null` when it has none. */\n pageLimit: number | null\n /** Pages the site already had when its import read the old site. */\n existingPages: number\n /** Pages the crawl read. */\n found: number\n /** Why the crawl stopped short of the whole site, or `null` when it did not. */\n stoppedBy: string | null\n groups: AiSiteImportGroup[]\n pages: AiSiteImportPage[]\n waves: AiSiteImportWave[]\n /** At most `AI_SITE_IMPORT_LEFT_OUT_LISTED`, by address. */\n leftOut: AiSiteImportLeftOut[]\n /** Every page left out, by reason, listed or not. */\n leftOutCount: Partial<Record<AiSiteImportLeftOutReason, number>>\n /** The header and footer's draft id, built in the first wave; `null` when there are no main pages. */\n frame: { draftId: string } | null\n /** The Trust & Safety row this import is logged under. */\n reviewId: string | null\n}\n\n/*------------------------------------------\n * Grouping and waves\n *-----------------------------------------*/\n\nconst clipLabel = (text: string) => {\n const line = String(text ?? '').replace(/\\s+/g, ' ').trim()\n return line.length > AI_SITE_IMPORT_LABEL_MAX_CHARS\n ? `${line.slice(0, AI_SITE_IMPORT_LABEL_MAX_CHARS - 1).trimEnd()}…`\n : line\n}\n\nconst segmentsOf = (path: string) => String(path ?? '').split('/').filter(Boolean)\n\n/** A collection's prefix for a page address: its first segment, a dated archive's `posts`; `null` for a top-level page. */\nexport function aiSiteImportPrefix(path: string): string | null {\n const segments = segmentsOf(path)\n if (segments.length < 2) return null\n const first = segments[0].toLowerCase()\n return /^\\d{4}$/.test(first) ? 'posts' : first\n}\n\n/** \"news-and-events\" → \"News and events\". */\nexport function aiSiteImportPrefixLabel(prefix: string): string {\n const words = decodeURIComponent(prefix).replace(/[-_+]+/g, ' ').trim()\n return words ? `${words.charAt(0).toUpperCase()}${words.slice(1)}` : 'Pages'\n}\n\n/**\n * The inventory's groups, in the order they are brought in: the main pages,\n * then each collection — one the home page's menu links first, in menu order,\n * then the larger before the smaller.\n */\nexport function aiSiteImportGroups(\n pages: readonly AiSiteImportListedPage[],\n menu: readonly string[] = [],\n): { groups: AiSiteImportGroup[]; groupOf: (page: AiSiteImportListedPage) => string } {\n const counts = new Map<string, number>()\n for (const page of pages) {\n const prefix = aiSiteImportPrefix(page.path)\n if (prefix) counts.set(prefix, (counts.get(prefix) ?? 0) + 1)\n }\n const menuPaths = new Set(menu.map((path) => path.replace(/\\/+$/, '') || '/'))\n const collections = new Set([...counts].filter(([, count]) => count >= AI_SITE_IMPORT_COLLECTION_MIN).map(([prefix]) => prefix))\n const groupOf = (page: AiSiteImportListedPage) => {\n const path = page.path.replace(/\\/+$/, '') || '/'\n if (page.foundBy === 'home' || menuPaths.has(path)) return 'main'\n const prefix = aiSiteImportPrefix(page.path)\n return prefix && collections.has(prefix) ? `c:${prefix}` : 'main'\n }\n const menuRank = (prefix: string) => {\n const index = menu.findIndex((path) => {\n const first = segmentsOf(path)[0]?.toLowerCase()\n return first === prefix\n })\n return index === -1 ? Number.POSITIVE_INFINITY : index\n }\n const sized = new Map<string, number>()\n for (const page of pages) sized.set(groupOf(page), (sized.get(groupOf(page)) ?? 0) + 1)\n const ordered = [...collections]\n .filter((prefix) => (sized.get(`c:${prefix}`) ?? 0) > 0)\n .sort((a, b) => menuRank(a) - menuRank(b) || (sized.get(`c:${b}`) ?? 0) - (sized.get(`c:${a}`) ?? 0) || a.localeCompare(b))\n const groups: AiSiteImportGroup[] = []\n if ((sized.get('main') ?? 0) > 0) groups.push({ id: 'main', label: 'Main pages', kind: 'main', pages: sized.get('main') ?? 0 })\n for (const prefix of ordered) {\n groups.push({ id: `c:${prefix}`, label: aiSiteImportPrefixLabel(prefix), kind: 'collection', pages: sized.get(`c:${prefix}`) ?? 0 })\n }\n return { groups, groupOf }\n}\n\n/**\n * The main pages in the order a visitor meets them: home, then the menu's\n * order, then the rest as the crawl read them. A collection keeps the crawl's\n * order, which is its sitemap's.\n */\nfunction orderedWithin(\n group: string,\n pages: readonly AiSiteImportListedPage[],\n menu: readonly string[],\n): AiSiteImportListedPage[] {\n if (group !== 'main') return [...pages]\n const rank = (page: AiSiteImportListedPage) => {\n if (page.foundBy === 'home' || segmentsOf(page.path).length === 0) return -1\n const path = page.path.replace(/\\/+$/, '') || '/'\n const index = menu.findIndex((entry) => (entry.replace(/\\/+$/, '') || '/') === path)\n return index === -1 ? Number.POSITIVE_INFINITY : index\n }\n return pages\n .map((page, order) => ({ page, order }))\n .sort((a, b) => rank(a.page) - rank(b.page) || a.order - b.order)\n .map(({ page }) => page)\n}\n\n/**\n * The address a page takes on the new site: one segment, as every Aglyn page\n * has, from the last segment of its old address; home is the root. Two pages\n * that would take the same address are told apart by their collection, then\n * by a number. The draft writer still makes it unique on the site itself.\n */\nexport function aiSiteImportSlug(path: string, taken: Set<string>): string {\n const segments = segmentsOf(path)\n if (!segments.length) {\n if (!taken.has('/')) {\n taken.add('/')\n return '/'\n }\n }\n const clean = (value: string) =>\n decodeURIComponent(value)\n .toLowerCase()\n .replace(/\\.(?:html?|php|aspx?)$/, '')\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n const last = clean(segments[segments.length - 1] ?? '') || 'page'\n const prefixed = segments.length > 1 ? clean(`${segments[0]}-${segments[segments.length - 1]}`) : last\n for (const candidate of [last, prefixed]) {\n if (candidate && !taken.has(candidate)) {\n taken.add(candidate)\n return candidate\n }\n }\n let attempt = 2\n while (taken.has(`${last}-${attempt}`)) attempt += 1\n const slug = `${last}-${attempt}`\n taken.add(slug)\n return slug\n}\n\nexport interface AiSiteImportInventoryInput {\n domain: string\n siteName: string\n ownership: AiSiteImportState['ownership']\n readAt: string\n pages: readonly AiSiteImportListedPage[]\n /** Addresses the home page's menu links, in its order. */\n menu: readonly string[]\n /** What the crawl could not read, by reason. */\n skipped: ReadonlyArray<{ url: string; reason: AiSiteImportLeftOutReason }>\n /** Skips the crawl only counted. */\n skippedOverflow?: number\n stoppedBy: string | null\n /** The plan's page count; `null` for none. */\n pageLimit: number | null\n existingPages: number\n /** Mints one draft id; the inventory names every page's draft before any is written. */\n mintId: () => string\n reviewId?: string | null\n wavePages?: number\n}\n\n/**\n * The site's inventory cut into waves, every page left out said with its\n * reason. Pure: the step reads the site, this decides what comes in.\n */\nexport function aiSiteImportInventory(input: AiSiteImportInventoryInput): AiSiteImportState {\n const wavePages = Math.max(1, Math.floor(input.wavePages ?? AI_SITE_IMPORT_WAVE_PAGES))\n const leftOut: AiSiteImportLeftOut[] = []\n const leftOutCount: Partial<Record<AiSiteImportLeftOutReason, number>> = {}\n const leave = (entry: AiSiteImportLeftOut) => {\n leftOutCount[entry.reason] = (leftOutCount[entry.reason] ?? 0) + 1\n if (leftOut.length < AI_SITE_IMPORT_LEFT_OUT_LISTED) leftOut.push(entry)\n }\n for (const skipped of input.skipped) {\n let path = '/'\n try {\n path = new URL(skipped.url).pathname\n } catch {\n // A malformed address keeps the root as its path.\n }\n leave({ url: skipped.url, path, title: '', reason: skipped.reason })\n }\n if (input.skippedOverflow) leftOutCount['unreadable'] = (leftOutCount['unreadable'] ?? 0) + input.skippedOverflow\n\n const readable = input.pages.filter((page) => {\n if (page.words >= AI_SITE_IMPORT_MIN_WORDS || page.images > 0) return true\n leave({ url: page.url, path: page.path, title: clipLabel(page.title), reason: 'empty' })\n return false\n })\n const { groups, groupOf } = aiSiteImportGroups(readable, input.menu)\n const limit = input.pageLimit === null || !Number.isFinite(input.pageLimit) ? null : Math.max(0, Math.floor(input.pageLimit))\n let room = limit === null ? Number.POSITIVE_INFINITY : Math.max(0, limit - Math.max(0, input.existingPages))\n const pages: AiSiteImportPage[] = []\n const waves: AiSiteImportWave[] = []\n const taken = new Set<string>()\n for (const group of groups) {\n const members = orderedWithin(group.id, readable.filter((page) => groupOf(page) === group.id), input.menu)\n const kept: AiSiteImportListedPage[] = []\n for (const page of members) {\n if (room > 0) {\n kept.push(page)\n room -= 1\n } else {\n leave({ url: page.url, path: page.path, title: clipLabel(page.title), reason: 'plan-limit' })\n }\n }\n const parts = Math.ceil(kept.length / wavePages)\n for (let part = 0; part < parts; part += 1) {\n const index = waves.length\n const slice = kept.slice(part * wavePages, (part + 1) * wavePages)\n const slots: string[] = []\n for (const page of slice) {\n const slot = `p${pages.length}`\n slots.push(slot)\n pages.push({\n slot,\n url: page.url,\n path: page.path,\n title: clipLabel(page.title) || aiSiteImportPrefixLabel(segmentsOf(page.path).pop() ?? 'Home'),\n group: group.id,\n wave: index,\n draftId: input.mintId(),\n slug: aiSiteImportSlug(page.path, taken),\n words: page.words,\n images: page.images,\n })\n }\n waves.push({\n index,\n label: parts > 1 ? `${group.label}, ${part + 1} of ${parts}` : group.label,\n group: group.id,\n slots,\n frame: index === 0 && group.kind === 'main',\n status: 'waiting',\n startedAt: null,\n finishedAt: null,\n })\n }\n }\n return {\n domain: input.domain,\n siteName: clipLabel(input.siteName) || input.domain,\n ownership: input.ownership,\n readAt: input.readAt,\n pageLimit: limit,\n existingPages: Math.max(0, input.existingPages),\n found: input.pages.length,\n stoppedBy: input.stoppedBy,\n groups,\n pages,\n waves,\n leftOut,\n leftOutCount,\n frame: waves[0]?.frame ? { draftId: input.mintId() } : null,\n reviewId: input.reviewId ?? null,\n }\n}\n\n/** The item ledger an inventory starts: the header and footer, then every page, all pending. */\nexport function aiSiteImportLedger(state: AiSiteImportState): AiJobItemLedger[] {\n const row = (slot: string, op: string, label: string): AiJobItemLedger => ({\n slot,\n op,\n label,\n status: 'pending',\n attempt: 1,\n creditsSpent: 0,\n creditsRefunded: 0,\n outputs: [],\n })\n return [\n ...(state.frame ? [row(AI_SITE_IMPORT_FRAME_SLOT, 'layout', 'Header and footer')] : []),\n ...state.pages.map((page) => row(page.slot, 'page', page.title)),\n ]\n}\n\n/*------------------------------------------\n * What runs next\n *-----------------------------------------*/\n\nexport type AiSiteImportWork =\n | { kind: 'inventory' }\n | { kind: 'start-wave'; wave: AiSiteImportWave }\n /** Pages of the running wave its read did not reach before its clock ran out. */\n | { kind: 'read'; wave: AiSiteImportWave; pages: AiSiteImportPage[] }\n | { kind: 'frame'; wave: AiSiteImportWave }\n | { kind: 'images'; wave: AiSiteImportWave; page: AiSiteImportPage }\n | { kind: 'page'; wave: AiSiteImportWave; page: AiSiteImportPage }\n | { kind: 'close-wave'; wave: AiSiteImportWave }\n | { kind: 'done' }\n\nconst OPEN = new Set(['pending', 'running'])\n\n/** Whether an item is still to be built. */\nexport function aiSiteImportItemOpen(items: readonly AiJobItemLedger[] | null | undefined, slot: string): boolean {\n const row = (items ?? []).find((entry) => entry.slot === slot)\n return Boolean(row && OPEN.has(row.status))\n}\n\n/** The wave running now, else `null`. */\nexport function aiSiteImportRunningWave(state: AiSiteImportState | null | undefined): AiSiteImportWave | null {\n return state?.waves.find((wave) => wave.status === 'running') ?? null\n}\n\n/** The next wave a person may start, else `null`. */\nexport function aiSiteImportNextWave(state: AiSiteImportState | null | undefined): AiSiteImportWave | null {\n if (!state || aiSiteImportRunningWave(state)) return null\n return state.waves.find((wave) => wave.status === 'waiting') ?? null\n}\n\n/** The wave a person is reviewing, else `null`. */\nexport function aiSiteImportReviewWave(state: AiSiteImportState | null | undefined): AiSiteImportWave | null {\n return state?.waves.find((wave) => wave.status === 'review') ?? null\n}\n\n/** What the next pass of the step does. */\nexport function aiSiteImportNextWork(\n state: AiSiteImportState | null | undefined,\n items: readonly AiJobItemLedger[] | null | undefined,\n): AiSiteImportWork {\n if (!state) return { kind: 'inventory' }\n const running = aiSiteImportRunningWave(state)\n if (!running) {\n const next = aiSiteImportNextWave(state)\n return next ? { kind: 'start-wave', wave: next } : { kind: 'done' }\n }\n // A page the wave's read has not reached yet is read before anything is built.\n const unread = running.slots\n .map((slot) => state.pages.find((page) => page.slot === slot))\n .filter((page): page is AiSiteImportPage => !!page && !page.read && !page.refused && aiSiteImportItemOpen(items, page.slot))\n if (unread.length) return { kind: 'read', wave: running, pages: unread }\n if (running.frame && state.frame && aiSiteImportItemOpen(items, AI_SITE_IMPORT_FRAME_SLOT)) {\n return { kind: 'frame', wave: running }\n }\n for (const slot of running.slots) {\n if (!aiSiteImportItemOpen(items, slot)) continue\n const page = state.pages.find((entry) => entry.slot === slot)\n if (!page) continue\n return page.photos ? { kind: 'page', wave: running, page } : { kind: 'images', wave: running, page }\n }\n return { kind: 'close-wave', wave: running }\n}\n\n/*------------------------------------------\n * The state, one pass at a time\n *-----------------------------------------*/\n\nconst withPage = (state: AiSiteImportState, slot: string, patch: Partial<AiSiteImportPage>): AiSiteImportState => ({\n ...state,\n pages: state.pages.map((page) => (page.slot === slot ? { ...page, ...patch } : page)),\n})\n\nconst withWave = (state: AiSiteImportState, index: number, patch: Partial<AiSiteImportWave>): AiSiteImportState => ({\n ...state,\n waves: state.waves.map((wave) => (wave.index === index ? { ...wave, ...patch } : wave)),\n})\n\n/** A wave started: running, with what its read found on each page. */\nexport function aiSiteImportWaveStarted(\n state: AiSiteImportState,\n index: number,\n at: string,\n reads: ReadonlyMap<string, AiSiteImportPageRead | AiSiteImportLeftOutReason>,\n): AiSiteImportState {\n // The wave reviewed before this one is done: starting the next is the person's say-so.\n const next = withWave(\n { ...state, waves: state.waves.map((wave) => (wave.status === 'review' ? { ...wave, status: 'done' as const } : wave)) },\n index,\n { status: 'running', startedAt: at, finishedAt: null },\n )\n return aiSiteImportPagesRead(next, reads)\n}\n\n/** More of a running wave's pages, read now. */\nexport function aiSiteImportPagesRead(\n state: AiSiteImportState,\n reads: ReadonlyMap<string, AiSiteImportPageRead | AiSiteImportLeftOutReason>,\n): AiSiteImportState {\n let next = state\n for (const [slot, read] of reads) {\n next =\n typeof read === 'string'\n ? withPage(next, slot, { refused: read, read: null })\n : withPage(next, slot, { read, refused: null, title: clipLabel(read.title) || next.pages.find((page) => page.slot === slot)?.title || '' })\n }\n return next\n}\n\n/** A page's photos are in the library. */\nexport function aiSiteImportPhotosIn(\n state: AiSiteImportState,\n slot: string,\n photos: NonNullable<AiSiteImportPage['photos']>,\n): AiSiteImportState {\n return withPage(state, slot, { photos })\n}\n\n/** A page is in: its text is no longer kept. */\nexport function aiSiteImportPageSettled(state: AiSiteImportState, slot: string): AiSiteImportState {\n const page = state.pages.find((entry) => entry.slot === slot)\n if (!page?.read) return state\n return withPage(state, slot, { read: { ...page.read, text: '' } })\n}\n\n/** A wave is in: waiting for its review, or done when no wave follows it. */\nexport function aiSiteImportWaveClosed(state: AiSiteImportState, index: number, at: string): AiSiteImportState {\n const later = state.waves.some((wave) => wave.index > index && wave.status === 'waiting')\n let next = withWave(state, index, { status: later ? 'review' : 'done', finishedAt: at })\n // What the wave read is no longer needed by any model answer.\n for (const slot of state.waves.find((wave) => wave.index === index)?.slots ?? []) next = aiSiteImportPageSettled(next, slot)\n return next\n}\n\n/** The state as the console reads it: every page's read text left off. */\nexport function aiSiteImportWire(state: AiSiteImportState | null | undefined): AiSiteImportState | null {\n if (!state) return null\n return {\n ...state,\n pages: state.pages.map((page) =>\n page.read ? { ...page, read: { ...page.read, text: '', images: [] } } : page,\n ),\n }\n}\n\n/*------------------------------------------\n * Credits\n *-----------------------------------------*/\n\n/**\n * What bringing in this many pages costs, quoted before it starts (AGL-3976):\n * each page an AI page as measured, its ceiling its two passes, and the\n * site's header and footer once when the wave builds them.\n */\nexport function aiSiteImportCreditRange(pages: number, options: { frame?: boolean } = {}): AiCreditRange {\n const count = Math.max(0, Math.floor(Number.isFinite(pages) ? pages : 0))\n const { page, frame } = AI_MEASURED_IMPORT_CREDITS\n return aiCreditRangeOrdered({\n likely: count * page.median + (options.frame ? frame.median : 0),\n p90: count * page.p90 + (options.frame ? frame.p90 : 0),\n ceiling: count * AI_SITE_IMPORT_PAGE_CEILING_CREDITS + (options.frame ? AI_SITE_IMPORT_FRAME_CEILING_CREDITS : 0),\n })\n}\n\n/** A wave's quote: the pages it has still to bring in, and its header and footer while they are owed. */\nexport function aiSiteImportWaveCreditRange(\n state: AiSiteImportState,\n wave: AiSiteImportWave,\n items?: readonly AiJobItemLedger[] | null,\n): AiCreditRange {\n const owed = (slot: string) => (items ? aiSiteImportItemOpen(items, slot) : true)\n const pages = wave.slots.filter((slot) => owed(slot) && !state.pages.find((page) => page.slot === slot)?.refused).length\n return aiSiteImportCreditRange(pages, { frame: wave.frame && !!state.frame && owed(AI_SITE_IMPORT_FRAME_SLOT) })\n}\n\n/*------------------------------------------\n * Words\n *-----------------------------------------*/\n\n/** Why a page is not brought in, as a person reads it. */\nexport const AI_SITE_IMPORT_LEFT_OUT_COPY: Readonly<Record<AiSiteImportLeftOutReason, string>> = {\n 'plan-limit': 'Past your plan’s page count. Upgrade for more pages, then bring the rest in.',\n robots: 'The site’s robots.txt asks AglynBot not to read it.',\n 'login-form': 'A sign-in page. Pages that ask for a password are never brought in.',\n empty: 'Nothing to bring in: the page has no text or photos.',\n unreadable: 'It could not be read just now.',\n 'too-large': 'Larger than a page can be to bring in.',\n 'off-site': 'It sends visitors to another website.',\n}\n\n/** \"3 pages\" / \"1 page\". */\nexport function aiSiteImportPagesText(count: number): string {\n return `${count.toLocaleString('en-US')} ${count === 1 ? 'page' : 'pages'}`\n}\n\n/** The review a finished wave parks on, or the first wave's go-ahead once the site is read. */\nexport function aiSiteImportWaveMessage(\n state: AiSiteImportState,\n items: readonly AiJobItemLedger[] | null | undefined,\n): string {\n const next = aiSiteImportNextWave(state)\n const reviewing = aiSiteImportReviewWave(state)\n if (!reviewing) {\n const kept = state.pages.length\n return next\n ? `Your site is read: ${aiSiteImportPagesText(kept)} to bring in, in ${state.waves.length === 1 ? 'one wave' : `${state.waves.length} waves`}. Start the first wave when you are ready.`\n : 'Your site is read, and nothing on it can be brought in.'\n }\n const rows = (items ?? []).filter((row) => reviewing.slots.includes(row.slot))\n const done = rows.filter((row) => row.status === 'succeeded' || row.status === 'degraded').length\n const lead = `${reviewing.label} is in: ${aiSiteImportPagesText(done)} ready to review as drafts.`\n return next ? `${lead} Start ${next.label} when you are ready.` : lead\n}\n"],"names":["aiCreditRangeOrdered","AI_MEASURED_IMPORT_CREDITS","AI_SITE_IMPORT_KIND","AI_SITE_IMPORT_DOMAIN_INPUT","AI_SITE_IMPORT_WAVE_PAGES","AI_SITE_IMPORT_MAX_PAGES","AI_SITE_IMPORT_COLLECTION_MIN","AI_SITE_IMPORT_IMAGES_PER_PAGE","AI_SITE_IMPORT_PAGE_TEXT_MAX_CHARS","AI_SITE_IMPORT_MAX_SECTIONS","AI_SITE_IMPORT_LABEL_MAX_CHARS","AI_SITE_IMPORT_MIN_WORDS","AI_SITE_IMPORT_FRAME_SLOT","AI_SITE_IMPORT_PAGE_CEILING_CREDITS","AI_SITE_IMPORT_FRAME_CEILING_CREDITS","AI_SITE_IMPORT_LEFT_OUT_LISTED","AI_SITE_IMPORT_LEFT_OUT_REASONS","clipLabel","text","line","String","replace","trim","length","slice","trimEnd","segmentsOf","path","split","filter","Boolean","aiSiteImportPrefix","segments","first","toLowerCase","test","aiSiteImportPrefixLabel","prefix","words","decodeURIComponent","charAt","toUpperCase","aiSiteImportGroups","pages","menu","sized","counts","Map","page","set","get","menuPaths","Set","map","collections","count","groupOf","foundBy","has","menuRank","index","findIndex","Number","POSITIVE_INFINITY","ordered","sort","a","b","localeCompare","groups","push","id","label","kind","orderedWithin","group","rank","entry","order","aiSiteImportSlug","taken","add","clean","value","last","prefixed","candidate","attempt","slug","aiSiteImportInventory","input","leftOutCount","waves","wavePages","Math","max","floor","leftOut","leave","reason","skipped","URL","url","pathname","title","skippedOverflow","readable","images","limit","pageLimit","isFinite","room","existingPages","members","kept","parts","ceil","part","slots","slot","pop","wave","draftId","mintId","frame","status","startedAt","finishedAt","domain","siteName","ownership","readAt","found","stoppedBy","reviewId","aiSiteImportLedger","state","row","op","creditsSpent","creditsRefunded","outputs","OPEN","aiSiteImportItemOpen","items","find","aiSiteImportRunningWave","aiSiteImportNextWave","aiSiteImportReviewWave","aiSiteImportNextWork","running","next","unread","read","refused","photos","withPage","patch","withWave","aiSiteImportWaveStarted","at","reads","aiSiteImportPagesRead","aiSiteImportPhotosIn","aiSiteImportPageSettled","aiSiteImportWaveClosed","later","some","aiSiteImportWire","aiSiteImportCreditRange","options","likely","median","p90","ceiling","aiSiteImportWaveCreditRange","owed","AI_SITE_IMPORT_LEFT_OUT_COPY","robots","empty","unreadable","aiSiteImportPagesText","toLocaleString","aiSiteImportWaveMessage","reviewing","rows","includes","done","lead"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-import.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * BRING MY SITE: THE WHOLE SITE, IN REVIEWED WAVES (AGL-3976, parent AGL-3973).\n *\n * The `import` job reads a website the workspace has PROVED it owns\n * (AGL-3974's claims) and brings it in as draft pages of a new site. This\n * module is the job's plan of record, pure, so the step, the doors, the\n * console and the specs read one set of rules:\n *\n * - **The inventory.** The crawl's pages, grouped: the MAIN pages (home, the\n * pages its menu links, every top-level page, and any page that belongs to\n * no collection), then each COLLECTION (an address prefix holding at least\n * `AI_SITE_IMPORT_COLLECTION_MIN` pages: `/blog/…`, `/news/…`, a dated\n * `/2024/05/…` archive).\n * - **Never past the plan.** Pages are kept in that order until the site's\n * room under its plan's page count is used; the rest are LEFT OUT, said so\n * with the reason, never dropped silently. So a plan's limit always leaves\n * out a collection's tail before it leaves out a main page.\n * - **Waves.** The kept pages are cut into waves of at most\n * `AI_SITE_IMPORT_WAVE_PAGES`. The first wave is the main pages, with the\n * site's header and footer built first; each later wave is one\n * collection's next slice, so a review is about one kind of page at a\n * time (\"Blog, 1 of 3\"). A wave runs, then waits for the person: nothing\n * starts the next one but them.\n * - **The price.** An imported page is an AI page (`AI_MEASURED_IMPORT_CREDITS`,\n * about 27 credits); a wave is quoted before it starts as its pages and,\n * for the first, its header and footer.\n *=========================================*/\n\nimport { aiCreditRangeOrdered, AI_MEASURED_IMPORT_CREDITS, type AiCreditRange } from './ai-credit-estimate'\nimport type { AiJobItemLedger } from './ai-jobs.types'\n\n/** The job kind. */\nexport const AI_SITE_IMPORT_KIND = 'import' as const\n\n/** The input naming the proved domain the job brings in. */\nexport const AI_SITE_IMPORT_DOMAIN_INPUT = 'domain'\n\n/** The most pages one wave brings in. */\nexport const AI_SITE_IMPORT_WAVE_PAGES = 10\n\n/**\n * The most pages one import reads, whatever the plan allows: an unlimited\n * plan is still one crawl's worth, and the crawl's own time budget may stop\n * sooner (the inventory then says so).\n */\nexport const AI_SITE_IMPORT_MAX_PAGES = 200\n\n/** The fewest pages under one address prefix that make it a collection. */\nexport const AI_SITE_IMPORT_COLLECTION_MIN = 3\n\n/** The most photos brought in from one page. */\nexport const AI_SITE_IMPORT_IMAGES_PER_PAGE = 8\n\n/** The most characters of one page's text a wave keeps for its model answer. */\nexport const AI_SITE_IMPORT_PAGE_TEXT_MAX_CHARS = 6_000\n\n/** The most sections one imported page is planned with. */\nexport const AI_SITE_IMPORT_MAX_SECTIONS = 6\n\n/** The most characters a section's name, a page's title or a label keeps. */\nexport const AI_SITE_IMPORT_LABEL_MAX_CHARS = 60\n\n/** A page with fewer words than this and no photo has nothing to bring in. */\nexport const AI_SITE_IMPORT_MIN_WORDS = 5\n\n/** The item ledger slot of the site's header and footer. */\nexport const AI_SITE_IMPORT_FRAME_SLOT = 'frame'\n\n/** The ceiling one imported page reserves: its answer pass and its listing pass, at 50 each. */\nexport const AI_SITE_IMPORT_PAGE_CEILING_CREDITS = 100\n\n/** The ceiling the header and footer reserve: one answer at 50. */\nexport const AI_SITE_IMPORT_FRAME_CEILING_CREDITS = 50\n\n/** The most left-out pages listed by address; the rest are counted. */\nexport const AI_SITE_IMPORT_LEFT_OUT_LISTED = 60\n\n/** A page the crawl read, as the inventory keeps it: no text, only what plans it. */\nexport interface AiSiteImportListedPage {\n url: string\n path: string\n title: string\n foundBy: 'home' | 'sitemap' | 'link'\n words: number\n images: number\n}\n\nexport type AiSiteImportGroupKind = 'main' | 'collection'\n\nexport interface AiSiteImportGroup {\n /** `main`, or `c:<prefix>` for a collection. */\n id: string\n label: string\n kind: AiSiteImportGroupKind\n /** Pages of the group the crawl read, kept or not. */\n pages: number\n}\n\n/** Why a page the site has is not brought in. */\nexport type AiSiteImportLeftOutReason =\n /** Past the site's room under its plan's page count. */\n | 'plan-limit'\n /** robots.txt keeps AglynBot off it. */\n | 'robots'\n /** A sign-in page: refused outright. */\n | 'login-form'\n /** No text and no photo: nothing to bring in. */\n | 'empty'\n /** It could not be read: an error, no answer, or not a web page. */\n | 'unreadable'\n /** The site's bot protection or firewall turned AglynBot away (401, 403 or 429). */\n | 'blocked'\n /** Over the size one page may be. */\n | 'too-large'\n /** It sends visitors to another website. */\n | 'off-site'\n\nexport const AI_SITE_IMPORT_LEFT_OUT_REASONS: readonly AiSiteImportLeftOutReason[] = [\n 'plan-limit',\n 'robots',\n 'login-form',\n 'empty',\n 'unreadable',\n 'blocked',\n 'too-large',\n 'off-site',\n]\n\nexport interface AiSiteImportLeftOut {\n url: string\n path: string\n title: string\n reason: AiSiteImportLeftOutReason\n}\n\n/** A photo brought into the site's media library, as a page places it. */\nexport interface AiSiteImportPhoto {\n mediaId: string\n src: string\n width?: number\n height?: number\n alt?: string\n}\n\n/** What a wave's read found on one page, for its model answer. Never on the wire. */\nexport interface AiSiteImportPageRead {\n at: string\n title: string\n /** The page's text in order, cut to `AI_SITE_IMPORT_PAGE_TEXT_MAX_CHARS`; fenced only when a prompt reads it. */\n text: string\n /** The page's sections, from its headings. */\n sections: string[]\n /** Its photos' addresses, at most `AI_SITE_IMPORT_IMAGES_PER_PAGE`. */\n images: Array<{ url: string; alt: string }>\n}\n\nexport interface AiSiteImportPage {\n /** The item ledger slot: `p0`, `p1`, … */\n slot: string\n url: string\n path: string\n title: string\n group: string\n wave: number\n /** The id the page's draft is written under, minted with the inventory. */\n draftId: string\n /** The page's address on the new site. */\n slug: string\n words: number\n images: number\n /** Filled by the wave's read; the text is cleared once the page is in. */\n read?: AiSiteImportPageRead | null\n /** Set when the wave's read found the page cannot be brought in after all. */\n refused?: AiSiteImportLeftOutReason | null\n /** Filled once the page's photos are in the library. */\n photos?: {\n brought: number\n /** Already in the library from an earlier page or import: the same file, never a second copy. */\n reused: number\n skipped: number\n assets: AiSiteImportPhoto[]\n } | null\n}\n\nexport type AiSiteImportWaveStatus = 'waiting' | 'running' | 'review' | 'done'\n\nexport interface AiSiteImportWave {\n index: number\n label: string\n /** The group the wave brings in; the first wave's is `main`. */\n group: string\n slots: string[]\n /** Whether the wave builds the site's header and footer first. */\n frame: boolean\n status: AiSiteImportWaveStatus\n startedAt: string | null\n finishedAt: string | null\n}\n\nexport interface AiSiteImportState {\n domain: string\n /** The site's name, as Create site was given it: the header's name. */\n siteName: string\n ownership: { method: string | null; provenAt: number | null; detail: string | null }\n readAt: string\n /** The plan's page count; `null` when it has none. */\n pageLimit: number | null\n /** Pages the site already had when its import read the old site. */\n existingPages: number\n /** Pages the crawl read. */\n found: number\n /** Why the crawl stopped short of the whole site, or `null` when it did not. */\n stoppedBy: string | null\n groups: AiSiteImportGroup[]\n pages: AiSiteImportPage[]\n waves: AiSiteImportWave[]\n /** At most `AI_SITE_IMPORT_LEFT_OUT_LISTED`, by address. */\n leftOut: AiSiteImportLeftOut[]\n /** Every page left out, by reason, listed or not. */\n leftOutCount: Partial<Record<AiSiteImportLeftOutReason, number>>\n /** The header and footer's draft id, built in the first wave; `null` when there are no main pages. */\n frame: { draftId: string } | null\n /** The Trust & Safety row this import is logged under. */\n reviewId: string | null\n}\n\n/*------------------------------------------\n * Grouping and waves\n *-----------------------------------------*/\n\nconst clipLabel = (text: string) => {\n const line = String(text ?? '').replace(/\\s+/g, ' ').trim()\n return line.length > AI_SITE_IMPORT_LABEL_MAX_CHARS\n ? `${line.slice(0, AI_SITE_IMPORT_LABEL_MAX_CHARS - 1).trimEnd()}…`\n : line\n}\n\nconst segmentsOf = (path: string) => String(path ?? '').split('/').filter(Boolean)\n\n/** A collection's prefix for a page address: its first segment, a dated archive's `posts`; `null` for a top-level page. */\nexport function aiSiteImportPrefix(path: string): string | null {\n const segments = segmentsOf(path)\n if (segments.length < 2) return null\n const first = segments[0].toLowerCase()\n return /^\\d{4}$/.test(first) ? 'posts' : first\n}\n\n/** \"news-and-events\" → \"News and events\". */\nexport function aiSiteImportPrefixLabel(prefix: string): string {\n const words = decodeURIComponent(prefix).replace(/[-_+]+/g, ' ').trim()\n return words ? `${words.charAt(0).toUpperCase()}${words.slice(1)}` : 'Pages'\n}\n\n/**\n * The inventory's groups, in the order they are brought in: the main pages,\n * then each collection — one the home page's menu links first, in menu order,\n * then the larger before the smaller.\n */\nexport function aiSiteImportGroups(\n pages: readonly AiSiteImportListedPage[],\n menu: readonly string[] = [],\n): { groups: AiSiteImportGroup[]; groupOf: (page: AiSiteImportListedPage) => string } {\n const counts = new Map<string, number>()\n for (const page of pages) {\n const prefix = aiSiteImportPrefix(page.path)\n if (prefix) counts.set(prefix, (counts.get(prefix) ?? 0) + 1)\n }\n const menuPaths = new Set(menu.map((path) => path.replace(/\\/+$/, '') || '/'))\n const collections = new Set([...counts].filter(([, count]) => count >= AI_SITE_IMPORT_COLLECTION_MIN).map(([prefix]) => prefix))\n const groupOf = (page: AiSiteImportListedPage) => {\n const path = page.path.replace(/\\/+$/, '') || '/'\n if (page.foundBy === 'home' || menuPaths.has(path)) return 'main'\n const prefix = aiSiteImportPrefix(page.path)\n return prefix && collections.has(prefix) ? `c:${prefix}` : 'main'\n }\n const menuRank = (prefix: string) => {\n const index = menu.findIndex((path) => {\n const first = segmentsOf(path)[0]?.toLowerCase()\n return first === prefix\n })\n return index === -1 ? Number.POSITIVE_INFINITY : index\n }\n const sized = new Map<string, number>()\n for (const page of pages) sized.set(groupOf(page), (sized.get(groupOf(page)) ?? 0) + 1)\n const ordered = [...collections]\n .filter((prefix) => (sized.get(`c:${prefix}`) ?? 0) > 0)\n .sort((a, b) => menuRank(a) - menuRank(b) || (sized.get(`c:${b}`) ?? 0) - (sized.get(`c:${a}`) ?? 0) || a.localeCompare(b))\n const groups: AiSiteImportGroup[] = []\n if ((sized.get('main') ?? 0) > 0) groups.push({ id: 'main', label: 'Main pages', kind: 'main', pages: sized.get('main') ?? 0 })\n for (const prefix of ordered) {\n groups.push({ id: `c:${prefix}`, label: aiSiteImportPrefixLabel(prefix), kind: 'collection', pages: sized.get(`c:${prefix}`) ?? 0 })\n }\n return { groups, groupOf }\n}\n\n/**\n * The main pages in the order a visitor meets them: home, then the menu's\n * order, then the rest as the crawl read them. A collection keeps the crawl's\n * order, which is its sitemap's.\n */\nfunction orderedWithin(\n group: string,\n pages: readonly AiSiteImportListedPage[],\n menu: readonly string[],\n): AiSiteImportListedPage[] {\n if (group !== 'main') return [...pages]\n const rank = (page: AiSiteImportListedPage) => {\n if (page.foundBy === 'home' || segmentsOf(page.path).length === 0) return -1\n const path = page.path.replace(/\\/+$/, '') || '/'\n const index = menu.findIndex((entry) => (entry.replace(/\\/+$/, '') || '/') === path)\n return index === -1 ? Number.POSITIVE_INFINITY : index\n }\n return pages\n .map((page, order) => ({ page, order }))\n .sort((a, b) => rank(a.page) - rank(b.page) || a.order - b.order)\n .map(({ page }) => page)\n}\n\n/**\n * The address a page takes on the new site: one segment, as every Aglyn page\n * has, from the last segment of its old address; home is the root. Two pages\n * that would take the same address are told apart by their collection, then\n * by a number. The draft writer still makes it unique on the site itself.\n */\nexport function aiSiteImportSlug(path: string, taken: Set<string>): string {\n const segments = segmentsOf(path)\n if (!segments.length) {\n if (!taken.has('/')) {\n taken.add('/')\n return '/'\n }\n }\n const clean = (value: string) =>\n decodeURIComponent(value)\n .toLowerCase()\n .replace(/\\.(?:html?|php|aspx?)$/, '')\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n const last = clean(segments[segments.length - 1] ?? '') || 'page'\n const prefixed = segments.length > 1 ? clean(`${segments[0]}-${segments[segments.length - 1]}`) : last\n for (const candidate of [last, prefixed]) {\n if (candidate && !taken.has(candidate)) {\n taken.add(candidate)\n return candidate\n }\n }\n let attempt = 2\n while (taken.has(`${last}-${attempt}`)) attempt += 1\n const slug = `${last}-${attempt}`\n taken.add(slug)\n return slug\n}\n\nexport interface AiSiteImportInventoryInput {\n domain: string\n siteName: string\n ownership: AiSiteImportState['ownership']\n readAt: string\n pages: readonly AiSiteImportListedPage[]\n /** Addresses the home page's menu links, in its order. */\n menu: readonly string[]\n /** What the crawl could not read, by reason. */\n skipped: ReadonlyArray<{ url: string; reason: AiSiteImportLeftOutReason }>\n /** Skips the crawl only counted. */\n skippedOverflow?: number\n stoppedBy: string | null\n /** The plan's page count; `null` for none. */\n pageLimit: number | null\n existingPages: number\n /** Mints one draft id; the inventory names every page's draft before any is written. */\n mintId: () => string\n reviewId?: string | null\n wavePages?: number\n}\n\n/**\n * The site's inventory cut into waves, every page left out said with its\n * reason. Pure: the step reads the site, this decides what comes in.\n */\nexport function aiSiteImportInventory(input: AiSiteImportInventoryInput): AiSiteImportState {\n const wavePages = Math.max(1, Math.floor(input.wavePages ?? AI_SITE_IMPORT_WAVE_PAGES))\n const leftOut: AiSiteImportLeftOut[] = []\n const leftOutCount: Partial<Record<AiSiteImportLeftOutReason, number>> = {}\n const leave = (entry: AiSiteImportLeftOut) => {\n leftOutCount[entry.reason] = (leftOutCount[entry.reason] ?? 0) + 1\n if (leftOut.length < AI_SITE_IMPORT_LEFT_OUT_LISTED) leftOut.push(entry)\n }\n for (const skipped of input.skipped) {\n let path = '/'\n try {\n path = new URL(skipped.url).pathname\n } catch {\n // A malformed address keeps the root as its path.\n }\n leave({ url: skipped.url, path, title: '', reason: skipped.reason })\n }\n if (input.skippedOverflow) leftOutCount['unreadable'] = (leftOutCount['unreadable'] ?? 0) + input.skippedOverflow\n\n const readable = input.pages.filter((page) => {\n if (page.words >= AI_SITE_IMPORT_MIN_WORDS || page.images > 0) return true\n leave({ url: page.url, path: page.path, title: clipLabel(page.title), reason: 'empty' })\n return false\n })\n const { groups, groupOf } = aiSiteImportGroups(readable, input.menu)\n const limit = input.pageLimit === null || !Number.isFinite(input.pageLimit) ? null : Math.max(0, Math.floor(input.pageLimit))\n let room = limit === null ? Number.POSITIVE_INFINITY : Math.max(0, limit - Math.max(0, input.existingPages))\n const pages: AiSiteImportPage[] = []\n const waves: AiSiteImportWave[] = []\n const taken = new Set<string>()\n for (const group of groups) {\n const members = orderedWithin(group.id, readable.filter((page) => groupOf(page) === group.id), input.menu)\n const kept: AiSiteImportListedPage[] = []\n for (const page of members) {\n if (room > 0) {\n kept.push(page)\n room -= 1\n } else {\n leave({ url: page.url, path: page.path, title: clipLabel(page.title), reason: 'plan-limit' })\n }\n }\n const parts = Math.ceil(kept.length / wavePages)\n for (let part = 0; part < parts; part += 1) {\n const index = waves.length\n const slice = kept.slice(part * wavePages, (part + 1) * wavePages)\n const slots: string[] = []\n for (const page of slice) {\n const slot = `p${pages.length}`\n slots.push(slot)\n pages.push({\n slot,\n url: page.url,\n path: page.path,\n title: clipLabel(page.title) || aiSiteImportPrefixLabel(segmentsOf(page.path).pop() ?? 'Home'),\n group: group.id,\n wave: index,\n draftId: input.mintId(),\n slug: aiSiteImportSlug(page.path, taken),\n words: page.words,\n images: page.images,\n })\n }\n waves.push({\n index,\n label: parts > 1 ? `${group.label}, ${part + 1} of ${parts}` : group.label,\n group: group.id,\n slots,\n frame: index === 0 && group.kind === 'main',\n status: 'waiting',\n startedAt: null,\n finishedAt: null,\n })\n }\n }\n return {\n domain: input.domain,\n siteName: clipLabel(input.siteName) || input.domain,\n ownership: input.ownership,\n readAt: input.readAt,\n pageLimit: limit,\n existingPages: Math.max(0, input.existingPages),\n found: input.pages.length,\n stoppedBy: input.stoppedBy,\n groups,\n pages,\n waves,\n leftOut,\n leftOutCount,\n frame: waves[0]?.frame ? { draftId: input.mintId() } : null,\n reviewId: input.reviewId ?? null,\n }\n}\n\n/** The item ledger an inventory starts: the header and footer, then every page, all pending. */\nexport function aiSiteImportLedger(state: AiSiteImportState): AiJobItemLedger[] {\n const row = (slot: string, op: string, label: string): AiJobItemLedger => ({\n slot,\n op,\n label,\n status: 'pending',\n attempt: 1,\n creditsSpent: 0,\n creditsRefunded: 0,\n outputs: [],\n })\n return [\n ...(state.frame ? [row(AI_SITE_IMPORT_FRAME_SLOT, 'layout', 'Header and footer')] : []),\n ...state.pages.map((page) => row(page.slot, 'page', page.title)),\n ]\n}\n\n/*------------------------------------------\n * What runs next\n *-----------------------------------------*/\n\nexport type AiSiteImportWork =\n | { kind: 'inventory' }\n | { kind: 'start-wave'; wave: AiSiteImportWave }\n /** Pages of the running wave its read did not reach before its clock ran out. */\n | { kind: 'read'; wave: AiSiteImportWave; pages: AiSiteImportPage[] }\n | { kind: 'frame'; wave: AiSiteImportWave }\n | { kind: 'images'; wave: AiSiteImportWave; page: AiSiteImportPage }\n | { kind: 'page'; wave: AiSiteImportWave; page: AiSiteImportPage }\n | { kind: 'close-wave'; wave: AiSiteImportWave }\n | { kind: 'done' }\n\nconst OPEN = new Set(['pending', 'running'])\n\n/** Whether an item is still to be built. */\nexport function aiSiteImportItemOpen(items: readonly AiJobItemLedger[] | null | undefined, slot: string): boolean {\n const row = (items ?? []).find((entry) => entry.slot === slot)\n return Boolean(row && OPEN.has(row.status))\n}\n\n/** The wave running now, else `null`. */\nexport function aiSiteImportRunningWave(state: AiSiteImportState | null | undefined): AiSiteImportWave | null {\n return state?.waves.find((wave) => wave.status === 'running') ?? null\n}\n\n/** The next wave a person may start, else `null`. */\nexport function aiSiteImportNextWave(state: AiSiteImportState | null | undefined): AiSiteImportWave | null {\n if (!state || aiSiteImportRunningWave(state)) return null\n return state.waves.find((wave) => wave.status === 'waiting') ?? null\n}\n\n/** The wave a person is reviewing, else `null`. */\nexport function aiSiteImportReviewWave(state: AiSiteImportState | null | undefined): AiSiteImportWave | null {\n return state?.waves.find((wave) => wave.status === 'review') ?? null\n}\n\n/** What the next pass of the step does. */\nexport function aiSiteImportNextWork(\n state: AiSiteImportState | null | undefined,\n items: readonly AiJobItemLedger[] | null | undefined,\n): AiSiteImportWork {\n if (!state) return { kind: 'inventory' }\n const running = aiSiteImportRunningWave(state)\n if (!running) {\n const next = aiSiteImportNextWave(state)\n return next ? { kind: 'start-wave', wave: next } : { kind: 'done' }\n }\n // A page the wave's read has not reached yet is read before anything is built.\n const unread = running.slots\n .map((slot) => state.pages.find((page) => page.slot === slot))\n .filter((page): page is AiSiteImportPage => !!page && !page.read && !page.refused && aiSiteImportItemOpen(items, page.slot))\n if (unread.length) return { kind: 'read', wave: running, pages: unread }\n if (running.frame && state.frame && aiSiteImportItemOpen(items, AI_SITE_IMPORT_FRAME_SLOT)) {\n return { kind: 'frame', wave: running }\n }\n for (const slot of running.slots) {\n if (!aiSiteImportItemOpen(items, slot)) continue\n const page = state.pages.find((entry) => entry.slot === slot)\n if (!page) continue\n return page.photos ? { kind: 'page', wave: running, page } : { kind: 'images', wave: running, page }\n }\n return { kind: 'close-wave', wave: running }\n}\n\n/*------------------------------------------\n * The state, one pass at a time\n *-----------------------------------------*/\n\nconst withPage = (state: AiSiteImportState, slot: string, patch: Partial<AiSiteImportPage>): AiSiteImportState => ({\n ...state,\n pages: state.pages.map((page) => (page.slot === slot ? { ...page, ...patch } : page)),\n})\n\nconst withWave = (state: AiSiteImportState, index: number, patch: Partial<AiSiteImportWave>): AiSiteImportState => ({\n ...state,\n waves: state.waves.map((wave) => (wave.index === index ? { ...wave, ...patch } : wave)),\n})\n\n/** A wave started: running, with what its read found on each page. */\nexport function aiSiteImportWaveStarted(\n state: AiSiteImportState,\n index: number,\n at: string,\n reads: ReadonlyMap<string, AiSiteImportPageRead | AiSiteImportLeftOutReason>,\n): AiSiteImportState {\n // The wave reviewed before this one is done: starting the next is the person's say-so.\n const next = withWave(\n { ...state, waves: state.waves.map((wave) => (wave.status === 'review' ? { ...wave, status: 'done' as const } : wave)) },\n index,\n { status: 'running', startedAt: at, finishedAt: null },\n )\n return aiSiteImportPagesRead(next, reads)\n}\n\n/** More of a running wave's pages, read now. */\nexport function aiSiteImportPagesRead(\n state: AiSiteImportState,\n reads: ReadonlyMap<string, AiSiteImportPageRead | AiSiteImportLeftOutReason>,\n): AiSiteImportState {\n let next = state\n for (const [slot, read] of reads) {\n next =\n typeof read === 'string'\n ? withPage(next, slot, { refused: read, read: null })\n : withPage(next, slot, { read, refused: null, title: clipLabel(read.title) || next.pages.find((page) => page.slot === slot)?.title || '' })\n }\n return next\n}\n\n/** A page's photos are in the library. */\nexport function aiSiteImportPhotosIn(\n state: AiSiteImportState,\n slot: string,\n photos: NonNullable<AiSiteImportPage['photos']>,\n): AiSiteImportState {\n return withPage(state, slot, { photos })\n}\n\n/** A page is in: its text is no longer kept. */\nexport function aiSiteImportPageSettled(state: AiSiteImportState, slot: string): AiSiteImportState {\n const page = state.pages.find((entry) => entry.slot === slot)\n if (!page?.read) return state\n return withPage(state, slot, { read: { ...page.read, text: '' } })\n}\n\n/** A wave is in: waiting for its review, or done when no wave follows it. */\nexport function aiSiteImportWaveClosed(state: AiSiteImportState, index: number, at: string): AiSiteImportState {\n const later = state.waves.some((wave) => wave.index > index && wave.status === 'waiting')\n let next = withWave(state, index, { status: later ? 'review' : 'done', finishedAt: at })\n // What the wave read is no longer needed by any model answer.\n for (const slot of state.waves.find((wave) => wave.index === index)?.slots ?? []) next = aiSiteImportPageSettled(next, slot)\n return next\n}\n\n/** The state as the console reads it: every page's read text left off. */\nexport function aiSiteImportWire(state: AiSiteImportState | null | undefined): AiSiteImportState | null {\n if (!state) return null\n return {\n ...state,\n pages: state.pages.map((page) =>\n page.read ? { ...page, read: { ...page.read, text: '', images: [] } } : page,\n ),\n }\n}\n\n/*------------------------------------------\n * Credits\n *-----------------------------------------*/\n\n/**\n * What bringing in this many pages costs, quoted before it starts (AGL-3976):\n * each page an AI page as measured, its ceiling its two passes, and the\n * site's header and footer once when the wave builds them.\n */\nexport function aiSiteImportCreditRange(pages: number, options: { frame?: boolean } = {}): AiCreditRange {\n const count = Math.max(0, Math.floor(Number.isFinite(pages) ? pages : 0))\n const { page, frame } = AI_MEASURED_IMPORT_CREDITS\n return aiCreditRangeOrdered({\n likely: count * page.median + (options.frame ? frame.median : 0),\n p90: count * page.p90 + (options.frame ? frame.p90 : 0),\n ceiling: count * AI_SITE_IMPORT_PAGE_CEILING_CREDITS + (options.frame ? AI_SITE_IMPORT_FRAME_CEILING_CREDITS : 0),\n })\n}\n\n/** A wave's quote: the pages it has still to bring in, and its header and footer while they are owed. */\nexport function aiSiteImportWaveCreditRange(\n state: AiSiteImportState,\n wave: AiSiteImportWave,\n items?: readonly AiJobItemLedger[] | null,\n): AiCreditRange {\n const owed = (slot: string) => (items ? aiSiteImportItemOpen(items, slot) : true)\n const pages = wave.slots.filter((slot) => owed(slot) && !state.pages.find((page) => page.slot === slot)?.refused).length\n return aiSiteImportCreditRange(pages, { frame: wave.frame && !!state.frame && owed(AI_SITE_IMPORT_FRAME_SLOT) })\n}\n\n/*------------------------------------------\n * Words\n *-----------------------------------------*/\n\n/** Why a page is not brought in, as a person reads it. */\nexport const AI_SITE_IMPORT_LEFT_OUT_COPY: Readonly<Record<AiSiteImportLeftOutReason, string>> = {\n 'plan-limit': 'Past your plan’s page count. Upgrade for more pages, then bring the rest in.',\n robots: 'The site’s robots.txt asks AglynBot not to read it.',\n 'login-form': 'A sign-in page. Pages that ask for a password are never brought in.',\n empty: 'Nothing to bring in: the page has no text or photos.',\n unreadable: 'It could not be read just now.',\n blocked:\n 'The site’s bot protection or firewall turned AglynBot away. Allow AglynBot in your site’s bot or firewall settings, then bring the site in again.',\n 'too-large': 'Larger than a page can be to bring in.',\n 'off-site': 'It sends visitors to another website.',\n}\n\n/** \"3 pages\" / \"1 page\". */\nexport function aiSiteImportPagesText(count: number): string {\n return `${count.toLocaleString('en-US')} ${count === 1 ? 'page' : 'pages'}`\n}\n\n/** The review a finished wave parks on, or the first wave's go-ahead once the site is read. */\nexport function aiSiteImportWaveMessage(\n state: AiSiteImportState,\n items: readonly AiJobItemLedger[] | null | undefined,\n): string {\n const next = aiSiteImportNextWave(state)\n const reviewing = aiSiteImportReviewWave(state)\n if (!reviewing) {\n const kept = state.pages.length\n return next\n ? `Your site is read: ${aiSiteImportPagesText(kept)} to bring in, in ${state.waves.length === 1 ? 'one wave' : `${state.waves.length} waves`}. Start the first wave when you are ready.`\n : 'Your site is read, and nothing on it can be brought in.'\n }\n const rows = (items ?? []).filter((row) => reviewing.slots.includes(row.slot))\n const done = rows.filter((row) => row.status === 'succeeded' || row.status === 'degraded').length\n const lead = `${reviewing.label} is in: ${aiSiteImportPagesText(done)} ready to review as drafts.`\n return next ? `${lead} Start ${next.label} when you are ready.` : lead\n}\n"],"names":["aiCreditRangeOrdered","AI_MEASURED_IMPORT_CREDITS","AI_SITE_IMPORT_KIND","AI_SITE_IMPORT_DOMAIN_INPUT","AI_SITE_IMPORT_WAVE_PAGES","AI_SITE_IMPORT_MAX_PAGES","AI_SITE_IMPORT_COLLECTION_MIN","AI_SITE_IMPORT_IMAGES_PER_PAGE","AI_SITE_IMPORT_PAGE_TEXT_MAX_CHARS","AI_SITE_IMPORT_MAX_SECTIONS","AI_SITE_IMPORT_LABEL_MAX_CHARS","AI_SITE_IMPORT_MIN_WORDS","AI_SITE_IMPORT_FRAME_SLOT","AI_SITE_IMPORT_PAGE_CEILING_CREDITS","AI_SITE_IMPORT_FRAME_CEILING_CREDITS","AI_SITE_IMPORT_LEFT_OUT_LISTED","AI_SITE_IMPORT_LEFT_OUT_REASONS","clipLabel","text","line","String","replace","trim","length","slice","trimEnd","segmentsOf","path","split","filter","Boolean","aiSiteImportPrefix","segments","first","toLowerCase","test","aiSiteImportPrefixLabel","prefix","words","decodeURIComponent","charAt","toUpperCase","aiSiteImportGroups","pages","menu","sized","counts","Map","page","set","get","menuPaths","Set","map","collections","count","groupOf","foundBy","has","menuRank","index","findIndex","Number","POSITIVE_INFINITY","ordered","sort","a","b","localeCompare","groups","push","id","label","kind","orderedWithin","group","rank","entry","order","aiSiteImportSlug","taken","add","clean","value","last","prefixed","candidate","attempt","slug","aiSiteImportInventory","input","leftOutCount","waves","wavePages","Math","max","floor","leftOut","leave","reason","skipped","URL","url","pathname","title","skippedOverflow","readable","images","limit","pageLimit","isFinite","room","existingPages","members","kept","parts","ceil","part","slots","slot","pop","wave","draftId","mintId","frame","status","startedAt","finishedAt","domain","siteName","ownership","readAt","found","stoppedBy","reviewId","aiSiteImportLedger","state","row","op","creditsSpent","creditsRefunded","outputs","OPEN","aiSiteImportItemOpen","items","find","aiSiteImportRunningWave","aiSiteImportNextWave","aiSiteImportReviewWave","aiSiteImportNextWork","running","next","unread","read","refused","photos","withPage","patch","withWave","aiSiteImportWaveStarted","at","reads","aiSiteImportPagesRead","aiSiteImportPhotosIn","aiSiteImportPageSettled","aiSiteImportWaveClosed","later","some","aiSiteImportWire","aiSiteImportCreditRange","options","likely","median","p90","ceiling","aiSiteImportWaveCreditRange","owed","AI_SITE_IMPORT_LEFT_OUT_COPY","robots","empty","unreadable","blocked","aiSiteImportPagesText","toLocaleString","aiSiteImportWaveMessage","reviewing","rows","includes","done","lead"],"mappings":"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"}
@@ -38,42 +38,24 @@
38
38
  * on every plan (the owner adds more from the Content, Commerce and Events
39
39
  * sections afterward).
40
40
  *
41
+ * The font pairings and the built-in bases are the platform's since AGL-4049
42
+ * (the core's `site-font-pairings.ts` and `SITE_THEME_BASES`), because a site
43
+ * starter names them too; this module re-exports both under its old names.
44
+ *
41
45
  * Pure data and two small readers, safe for the console to import: no
42
46
  * theme compiler, no network.
43
47
  */
44
- /** A font pairing the look may choose: a heading family over a body family. */
45
- export interface AiSiteFontPairing {
46
- id: string;
47
- /** What the pairing reads like, as the model is told. */
48
- feel: string;
49
- heading: {
50
- family: string;
51
- category: 'sans-serif' | 'serif';
52
- weight: number;
53
- sub: number;
54
- };
55
- body: {
56
- family: string;
57
- category: 'sans-serif' | 'serif';
58
- weights: number[];
59
- };
60
- /** Letter spacing the headings carry, in em. */
61
- tracking?: number;
62
- /** Headings set in capitals: a condensed display face. */
63
- caps?: boolean;
64
- }
65
- /**
66
- * The pairings, every family from the Google Fonts catalog the fonts plugin
67
- * serves (`google-fonts.catalog.json`) and every weight one that family
68
- * has — `ai-site-look.spec.ts` holds both. The tenant loads them the way it
69
- * loads the starter theme's family, so nothing here adds a font host.
70
- */
48
+ import { type SiteFontPairing } from '@aglyn/aglyn/app-utils/site-font-pairings';
49
+ /** A font pairing the look may choose: a heading family over a body family (`SiteFontPairing`). */
50
+ export type AiSiteFontPairing = SiteFontPairing;
51
+ /** The pairings: the platform's list (AGL-4049), under the name the AI start always used. */
71
52
  export declare const AI_SITE_FONT_PAIRINGS: readonly AiSiteFontPairing[];
72
53
  export type AiSiteFontPairingId = string;
73
54
  /**
74
55
  * The platform's built-in themes a site's look is layered on, by the short
75
56
  * name of the themes plugin's preset id (\`theme-presets.<name>\`), and
76
- * \`starter\`: the theme a new site is born with (AGL-3497).
57
+ * \`starter\`: the theme a new site is born with (AGL-3497). The platform's
58
+ * list since AGL-4049 (`SITE_THEME_BASES`), which a site starter names too.
77
59
  */
78
60
  export declare const AI_SITE_BASES: readonly ["starter", "material-ui", "bootstrap", "minimal", "material3", "ant-design", "fluent", "carbon", "cupertino"];
79
61
  export type AiSiteBase = (typeof AI_SITE_BASES)[number];