@aglyn/plugins-ai 1.0.0-beta.288 → 1.0.0-beta.290
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/activity/ai-activity-actions.d.ts +3 -2
- package/src/lib/activity/ai-activity-actions.js +3 -0
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +2 -0
- package/src/lib/components/ai-job-requests.js +2 -0
- package/src/lib/components/ai-job-requests.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +4 -0
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-import-job.component.d.ts +6 -1
- package/src/lib/components/ai-site-import-job.component.js +124 -17
- package/src/lib/components/ai-site-import-job.component.js.map +1 -1
- package/src/lib/components/ai-site-import-start.component.d.ts +6 -0
- package/src/lib/components/ai-site-import-start.component.js +18 -4
- package/src/lib/components/ai-site-import-start.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.js +2 -1
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/jobs/ai-image-reads.d.ts +20 -2
- package/src/lib/jobs/ai-image-reads.js +47 -0
- package/src/lib/jobs/ai-image-reads.js.map +1 -1
- package/src/lib/jobs/ai-job-import-step.d.ts +53 -3
- package/src/lib/jobs/ai-job-import-step.js +386 -64
- package/src/lib/jobs/ai-job-import-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +33 -0
- package/src/lib/jobs/ai-job-layout-language.js +75 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +8 -3
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +29 -0
- package/src/lib/jobs/ai-job-page-language.js +178 -2
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +3 -0
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-site-import-document.d.ts +117 -0
- package/src/lib/jobs/ai-site-import-document.js +710 -0
- package/src/lib/jobs/ai-site-import-document.js.map +1 -0
- package/src/lib/jobs/ai-site-import-look.d.ts +112 -0
- package/src/lib/jobs/ai-site-import-look.js +402 -0
- package/src/lib/jobs/ai-site-import-look.js.map +1 -0
- package/src/lib/jobs/ai-site-import-publish.d.ts +41 -0
- package/src/lib/jobs/ai-site-import-publish.js +92 -0
- package/src/lib/jobs/ai-site-import-publish.js.map +1 -0
- package/src/lib/jobs/ai-theme-brand-inputs.d.ts +27 -2
- package/src/lib/jobs/ai-theme-brand-inputs.js +37 -8
- package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.d.ts +17 -0
- package/src/lib/layout-language/ai-layout-frame.js +73 -2
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.d.ts +43 -0
- package/src/lib/layout-language/ai-layout-language.js +199 -0
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +15 -0
- package/src/lib/model/ai-credit-estimate.js +14 -1
- package/src/lib/model/ai-credit-estimate.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +13 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-import-view.d.ts +47 -3
- package/src/lib/model/ai-site-import-view.js +122 -20
- package/src/lib/model/ai-site-import-view.js.map +1 -1
- package/src/lib/model/ai-site-import.d.ts +232 -7
- package/src/lib/model/ai-site-import.js +365 -35
- package/src/lib/model/ai-site-import.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +18 -0
- package/src/lib/runtime/ai-doctrine-validators.js +16 -5
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +31 -0
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/server/ai-image-reads-route.js +24 -1
- package/src/lib/server/ai-image-reads-route.js.map +1 -1
- package/src/lib/server/ai-jobs-resume.js +7 -1
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/site-import/fixtures/aglyn-com-recordings.d.ts +57 -0
- package/src/lib/site-import/fixtures/aglyn-com-recordings.js +172 -0
- package/src/lib/site-import/fixtures/aglyn-com-recordings.js.map +1 -0
- package/src/lib/site-import/site-import-crawl.d.ts +32 -7
- package/src/lib/site-import/site-import-crawl.js +133 -44
- package/src/lib/site-import/site-import-crawl.js.map +1 -1
- package/src/lib/site-import/site-import-edge-server.d.ts +31 -0
- package/src/lib/site-import/site-import-edge-server.js +31 -0
- package/src/lib/site-import/site-import-edge-server.js.map +1 -0
- package/src/lib/site-import/site-import-edge.d.ts +44 -0
- package/src/lib/site-import/site-import-edge.js +108 -0
- package/src/lib/site-import/site-import-edge.js.map +1 -0
- package/src/lib/site-import/site-import-html.d.ts +73 -3
- package/src/lib/site-import/site-import-html.js +332 -45
- package/src/lib/site-import/site-import-html.js.map +1 -1
- package/src/lib/site-import/site-import-images.d.ts +22 -0
- package/src/lib/site-import/site-import-images.js +78 -2
- package/src/lib/site-import/site-import-images.js.map +1 -1
- package/src/lib/site-import/site-import-page.d.ts +44 -2
- package/src/lib/site-import/site-import-page.js +116 -16
- package/src/lib/site-import/site-import-page.js.map +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-site-import-document.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 * A PAGE BROUGHT IN WORD FOR WORD (AGL-4084).\n *\n * An imported page is never rewritten. Its words are the source's, as the\n * crawler read them (`AiSiteImportPageRead.blocks`), and this module is the\n * only thing that puts them on the new page:\n *\n * - **Lines.** The page's main region, numbered: every heading, paragraph,\n * list item, quote, link and picture in source order. A link inside a\n * sentence is in its sentence already and is no line of its own.\n * - **Sections.** One for the page's opening (its title and what comes\n * before the first second-level heading), then one for each second-level\n * heading, as the source divides itself. The page's plan has exactly\n * these sections, by these names.\n * - **Copied** (a collection's page, a long page): laid out by code, every\n * line in order, the bands alternating plain and soft, with no model at\n * all.\n * - **Arranged** (a main page): the model's answer says each section's band\n * and columns and which numbered lines go in which kind of block\n * (`AI_LAYOUT_ARRANGE_TOOL`, which has no field for a word of copy). Code\n * substitutes the lines exactly as written. A block naming a line already\n * placed is dropped, a line of another section is not taken, and every\n * line the answer never placed is added at the end of its own section, so\n * each line is on the page exactly once.\n *\n * Both write the palette's own elements with the theme's tokens, the way the\n * layout language compiler does (`ai-layout-compiler.ts`), so the page\n * renders in the site's theme and every word stays editable in the\n * Besigner's fields. Nothing is fitted, cut at a clean boundary or reworded:\n * a paragraph longer than a text element holds is split between sentences,\n * and a page longer than one page may weigh ends at a line, marked `cut`, so\n * the review says how many of its words came through.\n *=========================================*/\n\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport { bandSx } from '../layout-language/ai-layout-compiler'\nimport {\n AI_LAYOUT_ARRANGE_ITEM_KINDS,\n type AiLayoutArrangeBand,\n type AiLayoutArrangedBlock,\n type AiLayoutArrangedSection,\n type AiLayoutArrangement,\n} from '../layout-language/ai-layout-language'\nimport type { AiLayoutPage } from '../layout-language/ai-layout-links'\nimport { AiLayoutTreeBuilder, aiLayoutSpans, type AiLayoutRawTree } from '../layout-language/ai-layout-tree'\nimport {\n AI_SITE_IMPORT_LABEL_MAX_CHARS,\n AI_SITE_IMPORT_MAX_SECTIONS,\n type AiSiteImportBuild,\n type AiSiteImportPhoto,\n} from '../model/ai-site-import'\nimport type { SiteImportBlock } from '../site-import/site-import-html'\nimport { siteImportLabel } from '../site-import/site-import-page'\nimport { AI_TEXT_LIMITS } from '../runtime/ai-palette'\n\n/** The unit input carrying a page brought in word for word: its lines, its photos and where its links go. */\nexport const AI_LAYOUT_IMPORTED_SECTIONS_INPUT = 'importedSections'\n\n/** A page brought in, as the import step hands it to the page step. Untrusted data from the owner's site. */\nexport interface AiSiteImportDocument {\n build: AiSiteImportBuild\n /** The page's name on the new site: its h1 where the source shows no heading. */\n title: string\n /** Where the page is on the owner's website. */\n url: string\n /** The page's main region, in order. */\n blocks: SiteImportBlock[]\n /** Its photos as the library holds them, each by the source address it was read from. */\n photos: AiSiteImportPhoto[]\n /** The site's own pages a link may go to: the source path each was read from, and the page it is now. */\n links: Array<{ path: string; id: string }>\n /** The origins the old site answers on: a link to one of them is to a page of its own. */\n origins: string[]\n}\n\n/** The most characters a text element holds (the palette's ceiling for body copy). */\nexport const AI_SITE_IMPORT_TEXT_MAX_CHARS = AI_TEXT_LIMITS.body\n\n/** The most characters a heading style holds; a longer heading is drawn in a subtitle style, still a heading. */\nexport const AI_SITE_IMPORT_HEADLINE_MAX_CHARS = AI_TEXT_LIMITS.headline\n\n/** The most characters a button's label holds; a longer link keeps its words as a line of text. */\nexport const AI_SITE_IMPORT_LABEL_CHARS = 40\n\n/**\n * What one page may weigh, inside the budget of a page of its owner's own\n * words (`AI_OWN_WORDS_PAGE_BUDGET`): a page that would run past it ends at\n * the line before, marked `cut`.\n */\nexport const AI_SITE_IMPORT_DOC_MAX_NODES = 1_400\nexport const AI_SITE_IMPORT_DOC_MAX_CHARS = 200_000\n\nconst SECTION_PADDING = { xs: 8, md: 12 } as const\n\n/** One numbered line of a page. */\nexport interface AiSiteImportLine {\n /** 1-based, in source order. */\n n: number\n block: SiteImportBlock\n /** The section it belongs to, by plan index. */\n section: number\n}\n\nexport interface AiSiteImportDocSection {\n name: string\n /** Its lines' numbers, in order. */\n lines: number[]\n}\n\nexport interface AiSiteImportDocLines {\n lines: AiSiteImportLine[]\n sections: AiSiteImportDocSection[]\n}\n\nconst clean = (text: string) => String(text ?? '').replace(/\\s+/g, ' ').trim()\n\n/** Whether a block is a line of its own: a link inside a sentence is in its sentence already. */\nfunction isLine(block: SiteImportBlock): boolean {\n if (block.kind === 'image') return !!block.url\n if (block.kind === 'link') return !block.inline && !!clean(block.text)\n return !!clean(block.text)\n}\n\n/**\n * The page's lines and sections (AGL-4084): the opening, then one section\n * at each second-level heading, at most `AI_SITE_IMPORT_MAX_SECTIONS` (the\n * last holds whatever follows). A page that opens on a second-level heading\n * opens with it. Section names are the headings' words, neutralized and cut\n * to a label, since they travel outside a fence.\n */\nexport function aiSiteImportDocLines(blocks: readonly SiteImportBlock[], title: string): AiSiteImportDocLines {\n const lines: AiSiteImportLine[] = []\n const sections: AiSiteImportDocSection[] = []\n let current: AiSiteImportDocSection | null = null\n for (const block of blocks) {\n if (!isLine(block)) continue\n const starts = block.kind === 'heading' && block.level === 2 && sections.length < AI_SITE_IMPORT_MAX_SECTIONS\n if (!current || (starts && current.lines.length > 0)) {\n current = { name: '', lines: [] }\n sections.push(current)\n }\n const n = lines.length + 1\n lines.push({ n, block, section: sections.length - 1 })\n current.lines.push(n)\n }\n const named = new Set<string>()\n sections.forEach((section, index) => {\n const heading = section.lines.map((n) => lines[n - 1].block).find((block) => block.kind === 'heading')\n let name = siteImportLabel(heading && heading.kind === 'heading' ? heading.text : index === 0 ? title : '') || (index === 0 ? siteImportLabel(title) : '')\n if (!name) name = `Section ${index + 1}`\n // Two sections of one name are told apart, so a link to either finds it.\n let unique = name\n for (let attempt = 2; named.has(unique.toLowerCase()); attempt += 1) {\n unique = `${name.slice(0, AI_SITE_IMPORT_LABEL_MAX_CHARS - 4)} (${attempt})`\n }\n named.add(unique.toLowerCase())\n section.name = unique\n })\n if (!sections.length) sections.push({ name: siteImportLabel(title) || 'Introduction', lines: [] })\n return { lines, sections }\n}\n\n/** The words a line shows: an image's are its description. */\nexport function aiSiteImportLineText(line: Pick<AiSiteImportLine, 'block'>): string {\n return line.block.kind === 'image' ? clean(line.block.alt) : clean(line.block.text)\n}\n\nconst wordsIn = (text: string) => (text.match(/[\\p{L}\\p{N}]+/gu) ?? []).length\n\n/** The input a page job carries, read defensively; `null` on every other job. */\nexport function aiSiteImportDocumentOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiSiteImportDocument | null {\n const raw = inputs?.[AI_LAYOUT_IMPORTED_SECTIONS_INPUT]\n if (!raw || typeof raw !== 'object') return null\n const value = raw as Record<string, unknown>\n const build = value['build'] === 'copied' || value['build'] === 'arranged' ? value['build'] : null\n if (!build || !Array.isArray(value['blocks'])) return null\n const blocks = (value['blocks'] as unknown[]).flatMap((entry): SiteImportBlock[] => {\n if (!entry || typeof entry !== 'object') return []\n const block = entry as Record<string, unknown>\n const text = typeof block['text'] === 'string' ? block['text'] : ''\n const region = 'main' as const\n switch (block['kind']) {\n case 'heading': {\n const level = Number(block['level'])\n return [{ kind: 'heading', level: (Number.isInteger(level) && level >= 1 && level <= 6 ? level : 2) as 1 | 2 | 3 | 4 | 5 | 6, text, region }]\n }\n case 'paragraph':\n case 'list-item':\n case 'quote':\n return [{ kind: block['kind'], text, region }]\n case 'image':\n return typeof block['url'] === 'string' ? [{ kind: 'image', url: block['url'], alt: typeof block['alt'] === 'string' ? block['alt'] : '', region }] : []\n case 'link':\n return typeof block['href'] === 'string'\n ? [{ kind: 'link', text, href: block['href'], region, ...(block['inline'] === true ? { inline: true as const } : {}) }]\n : []\n default:\n return []\n }\n })\n const strings = (list: unknown) => (Array.isArray(list) ? list.filter((entry): entry is string => typeof entry === 'string') : [])\n return {\n build,\n title: typeof value['title'] === 'string' ? value['title'] : '',\n url: typeof value['url'] === 'string' ? value['url'].slice(0, 2048) : '',\n blocks,\n photos: (Array.isArray(value['photos']) ? value['photos'] : []).flatMap((entry): AiSiteImportPhoto[] => {\n const photo = entry as Record<string, unknown> | null\n return photo && typeof photo['mediaId'] === 'string' && typeof photo['src'] === 'string'\n ? [\n {\n mediaId: photo['mediaId'],\n src: photo['src'],\n ...(typeof photo['alt'] === 'string' ? { alt: photo['alt'] } : {}),\n ...(typeof photo['from'] === 'string' ? { from: photo['from'] } : {}),\n },\n ]\n : []\n }),\n links: (Array.isArray(value['links']) ? value['links'] : []).flatMap((entry) => {\n const link = entry as Record<string, unknown> | null\n return link && typeof link['path'] === 'string' && typeof link['id'] === 'string' ? [{ path: link['path'], id: link['id'] }] : []\n }),\n origins: strings(value['origins']),\n }\n}\n\n/*------------------------------------------\n * Drawing the lines\n *-----------------------------------------*/\n\n/** A paragraph cut between sentences into parts a text element holds, every word kept in order. */\nexport function aiSiteImportTextParts(text: string, max = AI_SITE_IMPORT_TEXT_MAX_CHARS): string[] {\n const words = clean(text)\n if (words.length <= max) return words ? [words] : []\n const sentences = words.match(/[^.!?…]+(?:[.!?…]+[\"'”’)\\]]*\\s*|$)/g) ?? [words]\n const parts: string[] = []\n let part = ''\n const push = () => {\n if (part.trim()) parts.push(part.trim())\n part = ''\n }\n for (const sentence of sentences) {\n if ((part + sentence).trim().length <= max) {\n part += sentence\n continue\n }\n push()\n // One sentence longer than an element holds is cut between words.\n let rest = sentence.trim()\n while (rest.length > max) {\n const at = rest.lastIndexOf(' ', max)\n const cut = at > 0 ? at : max\n parts.push(rest.slice(0, cut).trim())\n rest = rest.slice(cut).trim()\n }\n part = rest ? `${rest} ` : ''\n }\n push()\n return parts\n}\n\ntype Style = 'heading' | 'eyebrow' | 'lede' | 'text' | 'note'\n\ninterface DocScope {\n tree: AiLayoutTreeBuilder\n doc: AiSiteImportDocument\n /** The pages a link may go to by id. */\n pageIds: ReadonlySet<string>\n /** Source path → page id, for the ones a link may go to. */\n pathTo: ReadonlyMap<string, string>\n photos: ReadonlyMap<string, AiSiteImportPhoto>\n /** Whether the page's h1 is written. */\n titled: boolean\n /** The level of the last heading drawn, which the next may go at most one under. */\n lastLevel: number\n /** Pictures drawn so far: the first loads at once, the rest when reached. */\n pictures: number\n /** Lines on the page, in the order drawn. */\n placed: number[]\n words: number\n chars: number\n cut: boolean\n}\n\n/** Whether the page has room for one more line of this many characters. */\nfunction room(scope: DocScope, chars: number): boolean {\n if (scope.cut) return false\n if (Object.keys(scope.tree.nodes).length >= AI_SITE_IMPORT_DOC_MAX_NODES || scope.chars + chars > AI_SITE_IMPORT_DOC_MAX_CHARS) {\n scope.cut = true\n return false\n }\n return true\n}\n\nfunction took(scope: DocScope, line: AiSiteImportLine): void {\n scope.placed.push(line.n)\n const text = aiSiteImportLineText(line)\n scope.chars += text.length\n if (line.block.kind !== 'image') scope.words += wordsIn(text)\n}\n\n/** A heading at its place in the outline: the page's one h1 first, then the section's h2, then h3 under it. */\nfunction headingNode(scope: DocScope, text: string, level: number, centered: boolean): string {\n // The outline never skips a level: an h3 straight under the h1 is drawn an h2.\n const tag = !scope.titled ? 1 : Math.max(2, Math.min(3, level, scope.lastLevel + 1))\n scope.titled = true\n scope.lastLevel = tag\n const variant = text.length > AI_SITE_IMPORT_HEADLINE_MAX_CHARS ? 'subtitle1' : tag === 1 ? 'h1' : tag === 2 ? 'h2' : 'h4'\n return scope.tree.add(\n 'muiTypography',\n { children: text, variant, component: `h${tag}`, ...(centered ? { align: 'center' } : {}) },\n null,\n null,\n 'heading',\n )\n}\n\nconst VARIANTS: Record<Exclude<Style, 'heading'>, string> = { eyebrow: 'overline', lede: 'lede', text: 'body1', note: 'body2' }\n\n/** Words in a style, split where a text element would cut them. */\nfunction textNodes(scope: DocScope, text: string, style: Exclude<Style, 'heading'>, centered: boolean): string[] {\n return aiSiteImportTextParts(text).map((part) =>\n scope.tree.add(\n 'muiTypography',\n { children: part, variant: VARIANTS[style], component: 'p', ...(centered ? { align: 'center' } : {}) },\n style === 'note' ? { color: 'text.secondary' } : null,\n null,\n style,\n ),\n )\n}\n\n/** Where a link goes on the new site, or `null` where it goes nowhere a page of it may send a visitor. */\nfunction linkProps(scope: DocScope, href: string): Record<string, unknown> | null {\n if (/^mailto:[^\\s@]+@[^\\s@]+$/i.test(href) || /^tel:\\+?[\\d\\s().-]{3,}$/i.test(href)) return { href }\n let url: URL\n try {\n url = new URL(href)\n } catch {\n return null\n }\n if (url.protocol !== 'https:' && url.protocol !== 'http:') return null\n if (scope.doc.origins.includes(url.origin)) {\n // The old site's own page: the page it became, where that page may be linked.\n const path = url.pathname.replace(/\\/+$/, '') || '/'\n const id = scope.pathTo.get(path)\n return id && scope.pageIds.has(id) ? { screenId: id } : null\n }\n return url.protocol === 'https:' ? { href: url.toString(), target: '_blank' } : null\n}\n\n/** A link line: a button that goes where the source's did, or its words as a line where it cannot. */\nfunction linkNodes(scope: DocScope, line: AiSiteImportLine, style: 'primary' | 'secondary' | 'quiet', centered: boolean): string[] {\n const block = line.block\n if (block.kind !== 'link') return []\n const label = clean(block.text)\n const goes = label.length <= AI_SITE_IMPORT_LABEL_CHARS ? linkProps(scope, block.href) : null\n if (!goes) return textNodes(scope, label, 'text', centered)\n const look =\n style === 'primary'\n ? { variant: 'contained', color: 'primary' }\n : style === 'secondary'\n ? { variant: 'outlined', color: 'primary' }\n : { variant: 'text', color: 'primary' }\n return [\n scope.tree.add(\n 'muiButton',\n { children: label, ...look, size: 'medium', ...goes },\n { alignSelf: centered ? 'center' : 'flex-start' },\n null,\n 'button',\n ),\n ]\n}\n\n/** A picture line: the photo the library holds for it, or nothing where it could not be brought in. */\nfunction imageNodes(scope: DocScope, line: AiSiteImportLine): string[] {\n const block = line.block\n if (block.kind !== 'image') return []\n const photo = scope.photos.get(block.url)\n if (!photo) return []\n const alt = clean(block.alt || photo.alt || '').slice(0, 200)\n scope.pictures += 1\n return [\n scope.tree.add(\n 'image',\n {\n src: photo.src,\n ...(alt ? { alt } : { decorative: true }),\n objectFit: 'cover',\n ...(scope.pictures > 1 ? { loading: 'lazy' } : {}),\n },\n null,\n null,\n 'image',\n ),\n ]\n}\n\n/** One line in a style a block asked for; a picture is a picture and a link a link, wherever they are placed. */\nfunction lineNodes(scope: DocScope, line: AiSiteImportLine, style: Style, centered: boolean, button: 'primary' | 'secondary' | 'quiet' = 'quiet'): string[] {\n if (!room(scope, aiSiteImportLineText(line).length)) return []\n const block = line.block\n let ids: string[]\n if (block.kind === 'image') ids = imageNodes(scope, line)\n else if (block.kind === 'link') ids = linkNodes(scope, line, button, centered)\n else if (style === 'heading' || (block.kind === 'heading' && style === 'text')) ids = [headingNode(scope, clean(block.text), block.kind === 'heading' ? block.level : 2, centered)]\n else if (block.kind === 'quote' && style === 'text') {\n ids = [scope.tree.add('muiBox', { component: 'blockquote' }, null, textNodes(scope, block.text, 'text', centered), 'quote')]\n } else ids = textNodes(scope, block.text, style, centered)\n // A picture the library could not bring in is not on the page.\n if (ids.length) took(scope, line)\n return ids\n}\n\n/** A run of lines as a list, one item each. */\nfunction listNode(scope: DocScope, lines: readonly AiSiteImportLine[]): string[] {\n const rows: string[] = []\n const rest: string[] = []\n for (const line of lines) {\n if (line.block.kind === 'image' || line.block.kind === 'link') {\n rest.push(...lineNodes(scope, line, 'text', false))\n continue\n }\n const text = aiSiteImportLineText(line)\n if (!room(scope, text.length)) break\n for (const part of aiSiteImportTextParts(text)) {\n rows.push(\n scope.tree.add('muiListItem', { disableGutters: true }, null, [scope.tree.add('muiListItemText', { primary: part }, null, null, 'line')], 'row'),\n )\n }\n took(scope, line)\n }\n return [...(rows.length ? [scope.tree.add('muiList', { disablePadding: true }, null, rows, 'list')] : []), ...rest]\n}\n\n/** A group's items, each a title line over its text lines, laid out side by side. */\nfunction groupNode(scope: DocScope, kind: AiLayoutArrangedBlock['kind'], items: readonly AiSiteImportLine[][]): string[] {\n const pairs: string[] = []\n const rest: string[] = []\n for (const item of items) {\n const words = item.filter((line) => line.block.kind !== 'image' && line.block.kind !== 'link')\n for (const line of item) if (line.block.kind === 'image' || line.block.kind === 'link') rest.push(...lineNodes(scope, line, 'text', false))\n if (!words.length) continue\n const [title, ...body] = words\n const text = body.map(aiSiteImportLineText).join(' ')\n if (!room(scope, aiSiteImportLineText(title).length + text.length)) break\n const primary = aiSiteImportLineText(title)\n if (primary.length > AI_SITE_IMPORT_TEXT_MAX_CHARS || text.length > AI_SITE_IMPORT_TEXT_MAX_CHARS) {\n // An item too long for its element is its lines, one under another.\n for (const line of words) rest.push(...lineNodes(scope, line, 'text', false))\n continue\n }\n pairs.push(scope.tree.add('muiListItemText', { primary, ...(text ? { secondary: text } : {}) }, null, null, 'pair'))\n for (const line of words) took(scope, line)\n }\n if (!pairs.length) return rest\n const perRow = kind === 'faq' || kind === 'steps' || pairs.length === 1 ? 1 : Math.min(3, pairs.length)\n if (perRow === 1) return [scope.tree.add('muiStack', { spacing: '3' }, null, pairs, kind), ...rest]\n const size = perRow === 2 ? 'xs:12 sm:6' : 'xs:12 md:4'\n const cells = pairs.map((id) => scope.tree.add('muiGrid', { size }, null, [id], 'cell'))\n return [scope.tree.add('muiGrid', { container: true, spacing: '3' }, null, cells, kind), ...rest]\n}\n\n/** A section root: its band, its container and its content, under the id its plan stores it by. */\nfunction sectionNode(scope: DocScope, index: number, name: string, band: AiLayoutArrangeBand, parts: readonly string[], id: string, wide: boolean): string {\n const tree = scope.tree\n const content = parts.length === 1 ? parts[0] : tree.add('muiStack', { spacing: '3', useFlexGap: true }, null, parts, 'content')\n const container = tree.add('muiContainer', { maxWidth: wide ? 'lg' : 'md' }, { py: { ...SECTION_PADDING } }, [content], 'container')\n return tree.add(\n 'section',\n { element: 'section', ariaLabel: name || `Section ${index + 1}`, ...(band === 'dark' ? { colorScheme: 'dark' } : {}) },\n bandSx(band),\n [container],\n 'section',\n id,\n )\n}\n\n/** The lines of a section, in source order, as the source draws them: lists as lists, the rest one under another. */\nfunction copiedParts(scope: DocScope, lines: readonly AiSiteImportLine[]): string[] {\n const parts: string[] = []\n for (let at = 0; at < lines.length; at += 1) {\n const line = lines[at]\n if (line.block.kind === 'list-item') {\n const run: AiSiteImportLine[] = []\n while (at < lines.length && lines[at].block.kind === 'list-item') run.push(lines[at++])\n at -= 1\n parts.push(...listNode(scope, run))\n continue\n }\n parts.push(...lineNodes(scope, line, 'text', false))\n }\n return parts\n}\n\n/** One arranged block's lines, still unplaced and of its own section; `null` where it named a line already placed. */\nfunction ownLines(lineOf: (n: number) => AiSiteImportLine | undefined, numbers: readonly number[], section: number, used: Set<number>): AiSiteImportLine[] | null {\n const lines: AiSiteImportLine[] = []\n for (const n of numbers) {\n const line = lineOf(n)\n if (!line || line.section !== section) continue\n if (used.has(n)) return null\n lines.push(line)\n }\n return lines\n}\n\n/** An arranged block drawn: its lines in the kind it asked for. */\nfunction arrangedBlock(scope: DocScope, block: AiLayoutArrangedBlock, lines: readonly AiSiteImportLine[], items: readonly AiSiteImportLine[][], centered: boolean): string[] {\n switch (block.kind) {\n case 'list':\n return listNode(scope, lines)\n case 'cards':\n case 'steps':\n case 'stats':\n case 'faq':\n return groupNode(scope, block.kind, items.length ? items : lines.map((line) => [line]))\n case 'button': {\n const style = block.style === 'primary' || block.style === 'secondary' ? block.style : 'quiet'\n return lines.flatMap((line) => lineNodes(scope, line, 'text', centered, style))\n }\n case 'image':\n return lines.flatMap((line) => lineNodes(scope, line, 'text', centered))\n case 'heading':\n case 'eyebrow':\n case 'lede':\n case 'note': {\n const style: Style = block.kind\n return lines.flatMap((line, position) =>\n // A heading block's first line is the heading; any more are its words.\n lineNodes(scope, line, style === 'heading' && position > 0 ? 'text' : style, centered),\n )\n }\n default:\n return lines.flatMap((line) => lineNodes(scope, line, line.block.kind === 'heading' ? 'heading' : 'text', centered))\n }\n}\n\n/** One section as the arrangement lays it out, every line of it that the arrangement left out added at its end. */\nfunction arrangedParts(\n scope: DocScope,\n arranged: AiLayoutArrangedSection,\n index: number,\n lines: readonly AiSiteImportLine[],\n lineOf: (n: number) => AiSiteImportLine | undefined,\n used: Set<number>,\n): { parts: string[]; wide: boolean } {\n const centered = arranged.align === 'center'\n const cols = arranged.cols && arranged.cols.length >= 2 ? arranged.cols : null\n const head: string[] = []\n const tail: string[] = []\n const columns: string[][] = cols ? cols.map(() => []) : []\n let placedInRow = false\n for (const block of arranged.blocks) {\n const groups = AI_LAYOUT_ARRANGE_ITEM_KINDS.has(block.kind)\n const named = groups && block.items?.length ? block.items.flat() : block.from\n const taken = ownLines(lineOf, named, index, used)\n // A block that names a line twice is dropped; its other lines are added at the end.\n if (!taken?.length) continue\n for (const line of taken) used.add(line.n)\n const items = groups && block.items?.length ? block.items.map((item) => taken.filter((line) => item.includes(line.n))).filter((item) => item.length) : []\n const ids = arrangedBlock(scope, block, taken, items, centered && block.col === undefined)\n if (cols && block.col !== undefined) {\n columns[Math.min(block.col, cols.length - 1)].push(...ids)\n placedInRow = true\n } else (placedInRow ? tail : head).push(...ids)\n }\n // Every line the arrangement did not place, in source order, at the section's end.\n const left = lines.filter((line) => !used.has(line.n))\n for (const line of left) used.add(line.n)\n tail.push(...copiedParts(scope, left))\n const filled = columns.map((ids, position) => ({ ids, weight: cols?.[position] ?? 1 })).filter((entry) => entry.ids.length)\n const parts = [...head]\n if (filled.length >= 2) {\n const spans = aiLayoutSpans(filled.map((entry) => entry.weight))\n const cells = filled.map((entry, position) =>\n scope.tree.add(\n 'muiGrid',\n { size: `xs:12 md:${spans[position]}` },\n null,\n [entry.ids.length === 1 ? entry.ids[0] : scope.tree.add('muiStack', { spacing: '3' }, null, entry.ids, 'column')],\n 'cell',\n ),\n )\n parts.push(scope.tree.add('muiGrid', { container: true, spacing: '6' }, null, cells, 'row'))\n } else if (filled.length === 1) parts.push(...filled[0].ids)\n parts.push(...tail)\n return { parts, wide: filled.length >= 2 }\n}\n\nexport interface AiSiteImportCompileInput {\n doc: AiSiteImportDocument\n /** The ids the page stores its sections under, in plan order: one for each of the document's sections. */\n sectionIds: readonly string[]\n /** The site's pages a link may go to (the page step's targets). */\n pages: readonly Pick<AiLayoutPage, 'id'>[]\n /** The model's arrangement, for an arranged page; absent, the page is laid out as the source has it. */\n arrangement?: AiLayoutArrangement | null\n}\n\nexport interface AiSiteImportCompiled {\n tree: AiLayoutRawTree\n /** The line numbers on the page, in the order drawn. */\n placed: number[]\n /** Every line the page has. */\n lines: number\n /** The words on the page, and the words its lines hold. */\n words: number\n sourceWords: number\n /** Set when the page ran past what one page may weigh, and ends at the line before. */\n cut: boolean\n}\n\n/**\n * The page brought in, compiled into its tree (AGL-4084): copied as the\n * source has it, or as the arrangement lays it out, with every line on it\n * exactly once in either case and never a word that is not the source's.\n */\nexport function aiCompileImportedDocument(input: AiSiteImportCompileInput): AiSiteImportCompiled {\n const { doc } = input\n const { lines, sections } = aiSiteImportDocLines(doc.blocks, doc.title)\n const tree = new AiLayoutTreeBuilder('li')\n const pageIds = new Set(input.pages.map((page) => page.id))\n const scope: DocScope = {\n tree,\n doc,\n pageIds,\n pathTo: new Map(doc.links.map((link) => [link.path.replace(/\\/+$/, '') || '/', link.id])),\n photos: new Map(doc.photos.flatMap((photo) => (photo.from ? [[photo.from, photo] as const] : []))),\n titled: false,\n lastLevel: 1,\n pictures: 0,\n placed: [],\n words: 0,\n chars: 0,\n cut: false,\n }\n const lineOf = (n: number) => lines[n - 1]\n const used = new Set<number>()\n const arranged = doc.build === 'arranged' ? input.arrangement : null\n // An arranged opening with no heading block takes its first heading line as one, so the page keeps its h1.\n const opening = arranged?.sections[0]\n if (opening && !opening.blocks.some((block) => block.kind === 'heading')) {\n const first = opening.blocks.find((block) => block.from.some((n) => lineOf(n)?.block.kind === 'heading'))\n if (first && !AI_LAYOUT_ARRANGE_ITEM_KINDS.has(first.kind) && first.kind !== 'list') first.kind = 'heading'\n }\n const roots = sections.map((section, index) => {\n const own = section.lines.map(lineOf)\n // The page's h1: its title, where its opening shows no heading of its own.\n const titleParts: string[] = []\n if (index === 0 && !own.some((line) => line.block.kind === 'heading') && !(arranged?.sections[0]?.blocks.some((block) => block.kind === 'heading'))) {\n titleParts.push(headingNode(scope, clean(doc.title) || section.name, 1, false))\n }\n const plan = arranged?.sections[index] ?? null\n const drawn = plan ? arrangedParts(scope, plan, index, own, lineOf, used) : { parts: copiedParts(scope, own.filter((line) => !used.has(line.n))), wide: false }\n for (const line of own) used.add(line.n)\n const band: AiLayoutArrangeBand = plan?.band ?? (index % 2 === 1 ? 'soft' : 'plain')\n const parts = [...titleParts, ...drawn.parts]\n if (!parts.length) {\n // A section whose lines could not come through (its pictures, the page's weight) keeps its place by its name.\n parts.push(headingNode(scope, section.name, 2, false))\n }\n return sectionNode(scope, index, section.name, band, parts, input.sectionIds[index] ?? `section-${index + 1}`, drawn.wide)\n })\n const rootId = tree.add('div', null, null, roots, 'page', CANVAS_ROOT_ELEMENT_ID)\n for (const root of roots) tree.prune(root)\n const sourceWords = lines.reduce((total, line) => total + (line.block.kind === 'image' ? 0 : wordsIn(aiSiteImportLineText(line))), 0)\n return { tree: { rootId, nodes: tree.nodes }, placed: scope.placed, lines: lines.length, words: scope.words, sourceWords, cut: scope.cut }\n}\n\n/*------------------------------------------\n * What the arrangement is shown\n *-----------------------------------------*/\n\n/** The most characters of a page's numbered lines an arrangement prompt shows. */\nexport const AI_SITE_IMPORT_ARRANGE_MAX_CHARS = 60_000\n\nconst LINE_KIND: Record<SiteImportBlock['kind'], string> = {\n heading: 'heading',\n paragraph: 'text',\n 'list-item': 'list item',\n quote: 'quote',\n link: 'link',\n image: 'picture',\n}\n\n/** The page's lines as the arrangement reads them: each section, its numbered lines and their kinds. */\nexport function aiSiteImportArrangeText(doc: Pick<AiSiteImportDocument, 'blocks' | 'title'>): string {\n const { lines, sections } = aiSiteImportDocLines(doc.blocks, doc.title)\n return sections\n .map((section, index) =>\n [\n `Section ${index + 1}: ${section.name}`,\n ...section.lines.map((n) => {\n const line = lines[n - 1]\n const kind = line.block.kind === 'heading' ? `heading ${line.block.level}` : LINE_KIND[line.block.kind]\n return `[${n}] (${kind}) ${aiSiteImportLineText(line) || '(no description)'}`\n }),\n ].join('\\n'),\n )\n .join('\\n\\n')\n}\n"],"names":["CANVAS_ROOT_ELEMENT_ID","bandSx","AI_LAYOUT_ARRANGE_ITEM_KINDS","AiLayoutTreeBuilder","aiLayoutSpans","AI_SITE_IMPORT_LABEL_MAX_CHARS","AI_SITE_IMPORT_MAX_SECTIONS","siteImportLabel","AI_TEXT_LIMITS","AI_LAYOUT_IMPORTED_SECTIONS_INPUT","AI_SITE_IMPORT_TEXT_MAX_CHARS","body","AI_SITE_IMPORT_HEADLINE_MAX_CHARS","headline","AI_SITE_IMPORT_LABEL_CHARS","AI_SITE_IMPORT_DOC_MAX_NODES","AI_SITE_IMPORT_DOC_MAX_CHARS","SECTION_PADDING","xs","md","clean","text","String","replace","trim","isLine","block","kind","url","inline","aiSiteImportDocLines","blocks","title","lines","sections","current","starts","level","length","name","push","n","section","named","Set","forEach","index","heading","map","find","unique","attempt","has","toLowerCase","slice","add","aiSiteImportLineText","line","alt","wordsIn","match","aiSiteImportDocumentOf","inputs","raw","value","build","Array","isArray","flatMap","entry","region","Number","isInteger","href","strings","list","filter","photos","photo","mediaId","src","from","links","link","path","id","origins","aiSiteImportTextParts","max","words","sentences","parts","part","sentence","rest","at","lastIndexOf","cut","room","scope","chars","Object","keys","tree","nodes","took","placed","headingNode","centered","tag","titled","Math","min","lastLevel","variant","children","component","align","VARIANTS","eyebrow","lede","note","textNodes","style","color","linkProps","test","URL","protocol","doc","includes","origin","pathname","pathTo","get","pageIds","screenId","toString","target","linkNodes","label","goes","look","size","alignSelf","imageNodes","pictures","decorative","objectFit","loading","lineNodes","button","ids","listNode","rows","disableGutters","primary","disablePadding","groupNode","items","pairs","item","join","secondary","perRow","spacing","cells","container","sectionNode","band","wide","content","useFlexGap","maxWidth","py","element","ariaLabel","colorScheme","copiedParts","run","ownLines","lineOf","numbers","used","arrangedBlock","position","arrangedParts","arranged","cols","head","tail","columns","placedInRow","groups","flat","taken","col","undefined","left","filled","weight","spans","aiCompileImportedDocument","input","pages","page","Map","arrangement","opening","some","first","roots","own","titleParts","plan","drawn","sectionIds","rootId","root","prune","sourceWords","reduce","total","AI_SITE_IMPORT_ARRANGE_MAX_CHARS","LINE_KIND","paragraph","quote","image","aiSiteImportArrangeText"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2CAgC2C,GAE3C,SAASA,sBAAsB,QAAQ,2CAA0C;AACjF,SAASC,MAAM,QAAQ,2CAAuC;AAC9D,SACEC,4BAA4B,QAKvB,2CAAuC;AAE9C,SAASC,mBAAmB,EAAEC,aAAa,QAA8B,uCAAmC;AAC5G,SACEC,8BAA8B,EAC9BC,2BAA2B,QAGtB,6BAAyB;AAEhC,SAASC,eAAe,QAAQ,qCAAiC;AACjE,SAASC,cAAc,QAAQ,2BAAuB;AAEtD,2GAA2G,GAC3G,OAAO,MAAMC,oCAAoC,mBAAkB;AAmBnE,oFAAoF,GACpF,OAAO,MAAMC,gCAAgCF,eAAeG,IAAI,CAAA;AAEhE,+GAA+G,GAC/G,OAAO,MAAMC,oCAAoCJ,eAAeK,QAAQ,CAAA;AAExE,iGAAiG,GACjG,OAAO,MAAMC,6BAA6B,GAAE;AAE5C;;;;CAIC,GACD,OAAO,MAAMC,+BAA+B,KAAK;AACjD,OAAO,MAAMC,+BAA+B,OAAO;AAEnD,MAAMC,kBAAkB;IAAEC,IAAI;IAAGC,IAAI;AAAG;AAsBxC,MAAMC,QAAQ,CAACC,OAAiBC,OAAOD,eAAAA,OAAQ,IAAIE,OAAO,CAAC,QAAQ,KAAKC,IAAI;AAE5E,+FAA+F,GAC/F,SAASC,OAAOC,KAAsB;IACpC,IAAIA,MAAMC,IAAI,KAAK,SAAS,OAAO,CAAC,CAACD,MAAME,GAAG;IAC9C,IAAIF,MAAMC,IAAI,KAAK,QAAQ,OAAO,CAACD,MAAMG,MAAM,IAAI,CAAC,CAACT,MAAMM,MAAML,IAAI;IACrE,OAAO,CAAC,CAACD,MAAMM,MAAML,IAAI;AAC3B;AAEA;;;;;;CAMC,GACD,OAAO,SAASS,qBAAqBC,MAAkC,EAAEC,KAAa;IACpF,MAAMC,QAA4B,EAAE;IACpC,MAAMC,WAAqC,EAAE;IAC7C,IAAIC,UAAyC;IAC7C,KAAK,MAAMT,SAASK,OAAQ;QAC1B,IAAI,CAACN,OAAOC,QAAQ;QACpB,MAAMU,SAASV,MAAMC,IAAI,KAAK,aAAaD,MAAMW,KAAK,KAAK,KAAKH,SAASI,MAAM,GAAGhC;QAClF,IAAI,CAAC6B,WAAYC,UAAUD,QAAQF,KAAK,CAACK,MAAM,GAAG,GAAI;YACpDH,UAAU;gBAAEI,MAAM;gBAAIN,OAAO,EAAE;YAAC;YAChCC,SAASM,IAAI,CAACL;QAChB;QACA,MAAMM,IAAIR,MAAMK,MAAM,GAAG;QACzBL,MAAMO,IAAI,CAAC;YAAEC;YAAGf;YAAOgB,SAASR,SAASI,MAAM,GAAG;QAAE;QACpDH,QAAQF,KAAK,CAACO,IAAI,CAACC;IACrB;IACA,MAAME,QAAQ,IAAIC;IAClBV,SAASW,OAAO,CAAC,CAACH,SAASI;QACzB,MAAMC,UAAUL,QAAQT,KAAK,CAACe,GAAG,CAAC,CAACP,IAAMR,KAAK,CAACQ,IAAI,EAAE,CAACf,KAAK,EAAEuB,IAAI,CAAC,CAACvB,QAAUA,MAAMC,IAAI,KAAK;QAC5F,IAAIY,OAAOhC,gBAAgBwC,WAAWA,QAAQpB,IAAI,KAAK,YAAYoB,QAAQ1B,IAAI,GAAGyB,UAAU,IAAId,QAAQ,OAAQc,CAAAA,UAAU,IAAIvC,gBAAgByB,SAAS,EAAC;QACxJ,IAAI,CAACO,MAAMA,OAAO,CAAC,QAAQ,EAAEO,QAAQ,GAAG;QACxC,yEAAyE;QACzE,IAAII,SAASX;QACb,IAAK,IAAIY,UAAU,GAAGR,MAAMS,GAAG,CAACF,OAAOG,WAAW,KAAKF,WAAW,EAAG;YACnED,SAAS,GAAGX,KAAKe,KAAK,CAAC,GAAGjD,iCAAiC,GAAG,EAAE,EAAE8C,QAAQ,CAAC,CAAC;QAC9E;QACAR,MAAMY,GAAG,CAACL,OAAOG,WAAW;QAC5BX,QAAQH,IAAI,GAAGW;IACjB;IACA,IAAI,CAAChB,SAASI,MAAM,EAAEJ,SAASM,IAAI,CAAC;QAAED,MAAMhC,gBAAgByB,UAAU;QAAgBC,OAAO,EAAE;IAAC;IAChG,OAAO;QAAEA;QAAOC;IAAS;AAC3B;AAEA,4DAA4D,GAC5D,OAAO,SAASsB,qBAAqBC,IAAqC;IACxE,OAAOA,KAAK/B,KAAK,CAACC,IAAI,KAAK,UAAUP,MAAMqC,KAAK/B,KAAK,CAACgC,GAAG,IAAItC,MAAMqC,KAAK/B,KAAK,CAACL,IAAI;AACpF;AAEA,MAAMsC,UAAU,CAACtC;QAAkBA;WAAD,EAACA,cAAAA,KAAKuC,KAAK,CAAC,ynaAAXvC,cAAiC,EAAE,EAAEiB,MAAM;;AAE9E,+EAA+E,GAC/E,OAAO,SAASuB,uBAAuBC,MAA4D;IACjG,MAAMC,MAAMD,0BAAAA,MAAQ,CAACrD,kCAAkC;IACvD,IAAI,CAACsD,OAAO,OAAOA,QAAQ,UAAU,OAAO;IAC5C,MAAMC,QAAQD;IACd,MAAME,QAAQD,KAAK,CAAC,QAAQ,KAAK,YAAYA,KAAK,CAAC,QAAQ,KAAK,aAAaA,KAAK,CAAC,QAAQ,GAAG;IAC9F,IAAI,CAACC,SAAS,CAACC,MAAMC,OAAO,CAACH,KAAK,CAAC,SAAS,GAAG,OAAO;IACtD,MAAMjC,SAAS,AAACiC,KAAK,CAAC,SAAS,CAAeI,OAAO,CAAC,CAACC;QACrD,IAAI,CAACA,SAAS,OAAOA,UAAU,UAAU,OAAO,EAAE;QAClD,MAAM3C,QAAQ2C;QACd,MAAMhD,OAAO,OAAOK,KAAK,CAAC,OAAO,KAAK,WAAWA,KAAK,CAAC,OAAO,GAAG;QACjE,MAAM4C,SAAS;QACf,OAAQ5C,KAAK,CAAC,OAAO;YACnB,KAAK;gBAAW;oBACd,MAAMW,QAAQkC,OAAO7C,KAAK,CAAC,QAAQ;oBACnC,OAAO;wBAAC;4BAAEC,MAAM;4BAAWU,OAAQkC,OAAOC,SAAS,CAACnC,UAAUA,SAAS,KAAKA,SAAS,IAAIA,QAAQ;4BAA6BhB;4BAAMiD;wBAAO;qBAAE;gBAC/I;YACA,KAAK;YACL,KAAK;YACL,KAAK;gBACH,OAAO;oBAAC;wBAAE3C,MAAMD,KAAK,CAAC,OAAO;wBAAEL;wBAAMiD;oBAAO;iBAAE;YAChD,KAAK;gBACH,OAAO,OAAO5C,KAAK,CAAC,MAAM,KAAK,WAAW;oBAAC;wBAAEC,MAAM;wBAASC,KAAKF,KAAK,CAAC,MAAM;wBAAEgC,KAAK,OAAOhC,KAAK,CAAC,MAAM,KAAK,WAAWA,KAAK,CAAC,MAAM,GAAG;wBAAI4C;oBAAO;iBAAE,GAAG,EAAE;YAC1J,KAAK;gBACH,OAAO,OAAO5C,KAAK,CAAC,OAAO,KAAK,WAC5B;oBAAC;wBAAEC,MAAM;wBAAQN;wBAAMoD,MAAM/C,KAAK,CAAC,OAAO;wBAAE4C;uBAAY5C,KAAK,CAAC,SAAS,KAAK,OAAO;wBAAEG,QAAQ;oBAAc,IAAI,CAAC;iBAAK,GACrH,EAAE;YACR;gBACE,OAAO,EAAE;QACb;IACF;IACA,MAAM6C,UAAU,CAACC,OAAmBT,MAAMC,OAAO,CAACQ,QAAQA,KAAKC,MAAM,CAAC,CAACP,QAA2B,OAAOA,UAAU,YAAY,EAAE;IACjI,OAAO;QACLJ;QACAjC,OAAO,OAAOgC,KAAK,CAAC,QAAQ,KAAK,WAAWA,KAAK,CAAC,QAAQ,GAAG;QAC7DpC,KAAK,OAAOoC,KAAK,CAAC,MAAM,KAAK,WAAWA,KAAK,CAAC,MAAM,CAACV,KAAK,CAAC,GAAG,QAAQ;QACtEvB;QACA8C,QAAQ,AAACX,CAAAA,MAAMC,OAAO,CAACH,KAAK,CAAC,SAAS,IAAIA,KAAK,CAAC,SAAS,GAAG,EAAE,AAAD,EAAGI,OAAO,CAAC,CAACC;YACvE,MAAMS,QAAQT;YACd,OAAOS,SAAS,OAAOA,KAAK,CAAC,UAAU,KAAK,YAAY,OAAOA,KAAK,CAAC,MAAM,KAAK,WAC5E;gBACE;oBACEC,SAASD,KAAK,CAAC,UAAU;oBACzBE,KAAKF,KAAK,CAAC,MAAM;mBACb,OAAOA,KAAK,CAAC,MAAM,KAAK,WAAW;oBAAEpB,KAAKoB,KAAK,CAAC,MAAM;gBAAC,IAAI,CAAC,GAC5D,OAAOA,KAAK,CAAC,OAAO,KAAK,WAAW;oBAAEG,MAAMH,KAAK,CAAC,OAAO;gBAAC,IAAI,CAAC;aAEtE,GACD,EAAE;QACR;QACAI,OAAO,AAAChB,CAAAA,MAAMC,OAAO,CAACH,KAAK,CAAC,QAAQ,IAAIA,KAAK,CAAC,QAAQ,GAAG,EAAE,AAAD,EAAGI,OAAO,CAAC,CAACC;YACpE,MAAMc,OAAOd;YACb,OAAOc,QAAQ,OAAOA,IAAI,CAAC,OAAO,KAAK,YAAY,OAAOA,IAAI,CAAC,KAAK,KAAK,WAAW;gBAAC;oBAAEC,MAAMD,IAAI,CAAC,OAAO;oBAAEE,IAAIF,IAAI,CAAC,KAAK;gBAAC;aAAE,GAAG,EAAE;QACnI;QACAG,SAASZ,QAAQV,KAAK,CAAC,UAAU;IACnC;AACF;AAEA;;2CAE2C,GAE3C,iGAAiG,GACjG,OAAO,SAASuB,sBAAsBlE,IAAY,EAAEmE,MAAM9E,6BAA6B;QAGnE+E;IAFlB,MAAMA,QAAQrE,MAAMC;IACpB,IAAIoE,MAAMnD,MAAM,IAAIkD,KAAK,OAAOC,QAAQ;QAACA;KAAM,GAAG,EAAE;IACpD,MAAMC,aAAYD,eAAAA,MAAM7B,KAAK,CAAC,kDAAZ6B,eAAsD;QAACA;KAAM;IAC/E,MAAME,QAAkB,EAAE;IAC1B,IAAIC,OAAO;IACX,MAAMpD,OAAO;QACX,IAAIoD,KAAKpE,IAAI,IAAImE,MAAMnD,IAAI,CAACoD,KAAKpE,IAAI;QACrCoE,OAAO;IACT;IACA,KAAK,MAAMC,YAAYH,UAAW;QAChC,IAAI,AAACE,CAAAA,OAAOC,QAAO,EAAGrE,IAAI,GAAGc,MAAM,IAAIkD,KAAK;YAC1CI,QAAQC;YACR;QACF;QACArD;QACA,kEAAkE;QAClE,IAAIsD,OAAOD,SAASrE,IAAI;QACxB,MAAOsE,KAAKxD,MAAM,GAAGkD,IAAK;YACxB,MAAMO,KAAKD,KAAKE,WAAW,CAAC,KAAKR;YACjC,MAAMS,MAAMF,KAAK,IAAIA,KAAKP;YAC1BG,MAAMnD,IAAI,CAACsD,KAAKxC,KAAK,CAAC,GAAG2C,KAAKzE,IAAI;YAClCsE,OAAOA,KAAKxC,KAAK,CAAC2C,KAAKzE,IAAI;QAC7B;QACAoE,OAAOE,OAAO,GAAGA,KAAK,CAAC,CAAC,GAAG;IAC7B;IACAtD;IACA,OAAOmD;AACT;AAyBA,yEAAyE,GACzE,SAASO,KAAKC,KAAe,EAAEC,KAAa;IAC1C,IAAID,MAAMF,GAAG,EAAE,OAAO;IACtB,IAAII,OAAOC,IAAI,CAACH,MAAMI,IAAI,CAACC,KAAK,EAAElE,MAAM,IAAIvB,gCAAgCoF,MAAMC,KAAK,GAAGA,QAAQpF,8BAA8B;QAC9HmF,MAAMF,GAAG,GAAG;QACZ,OAAO;IACT;IACA,OAAO;AACT;AAEA,SAASQ,KAAKN,KAAe,EAAE1C,IAAsB;IACnD0C,MAAMO,MAAM,CAAClE,IAAI,CAACiB,KAAKhB,CAAC;IACxB,MAAMpB,OAAOmC,qBAAqBC;IAClC0C,MAAMC,KAAK,IAAI/E,KAAKiB,MAAM;IAC1B,IAAImB,KAAK/B,KAAK,CAACC,IAAI,KAAK,SAASwE,MAAMV,KAAK,IAAI9B,QAAQtC;AAC1D;AAEA,6GAA6G,GAC7G,SAASsF,YAAYR,KAAe,EAAE9E,IAAY,EAAEgB,KAAa,EAAEuE,QAAiB;IAClF,+EAA+E;IAC/E,MAAMC,MAAM,CAACV,MAAMW,MAAM,GAAG,IAAIC,KAAKvB,GAAG,CAAC,GAAGuB,KAAKC,GAAG,CAAC,GAAG3E,OAAO8D,MAAMc,SAAS,GAAG;IACjFd,MAAMW,MAAM,GAAG;IACfX,MAAMc,SAAS,GAAGJ;IAClB,MAAMK,UAAU7F,KAAKiB,MAAM,GAAG1B,oCAAoC,cAAciG,QAAQ,IAAI,OAAOA,QAAQ,IAAI,OAAO;IACtH,OAAOV,MAAMI,IAAI,CAAChD,GAAG,CACnB,iBACA;QAAE4D,UAAU9F;QAAM6F;QAASE,WAAW,CAAC,CAAC,EAAEP,KAAK;OAAMD,WAAW;QAAES,OAAO;IAAS,IAAI,CAAC,IACvF,MACA,MACA;AAEJ;AAEA,MAAMC,WAAsD;IAAEC,SAAS;IAAYC,MAAM;IAAQnG,MAAM;IAASoG,MAAM;AAAQ;AAE9H,iEAAiE,GACjE,SAASC,UAAUvB,KAAe,EAAE9E,IAAY,EAAEsG,KAAgC,EAAEf,QAAiB;IACnG,OAAOrB,sBAAsBlE,MAAM2B,GAAG,CAAC,CAAC4C,OACtCO,MAAMI,IAAI,CAAChD,GAAG,CACZ,iBACA;YAAE4D,UAAUvB;YAAMsB,SAASI,QAAQ,CAACK,MAAM;YAAEP,WAAW;WAASR,WAAW;YAAES,OAAO;QAAS,IAAI,CAAC,IAClGM,UAAU,SAAS;YAAEC,OAAO;QAAiB,IAAI,MACjD,MACAD;AAGN;AAEA,wGAAwG,GACxG,SAASE,UAAU1B,KAAe,EAAE1B,IAAY;IAC9C,IAAI,4BAA4BqD,IAAI,CAACrD,SAAS,2BAA2BqD,IAAI,CAACrD,OAAO,OAAO;QAAEA;IAAK;IACnG,IAAI7C;IACJ,IAAI;QACFA,MAAM,IAAImG,IAAItD;IAChB,EAAE,eAAM;QACN,OAAO;IACT;IACA,IAAI7C,IAAIoG,QAAQ,KAAK,YAAYpG,IAAIoG,QAAQ,KAAK,SAAS,OAAO;IAClE,IAAI7B,MAAM8B,GAAG,CAAC3C,OAAO,CAAC4C,QAAQ,CAACtG,IAAIuG,MAAM,GAAG;QAC1C,8EAA8E;QAC9E,MAAM/C,OAAOxD,IAAIwG,QAAQ,CAAC7G,OAAO,CAAC,QAAQ,OAAO;QACjD,MAAM8D,KAAKc,MAAMkC,MAAM,CAACC,GAAG,CAAClD;QAC5B,OAAOC,MAAMc,MAAMoC,OAAO,CAACnF,GAAG,CAACiC,MAAM;YAAEmD,UAAUnD;QAAG,IAAI;IAC1D;IACA,OAAOzD,IAAIoG,QAAQ,KAAK,WAAW;QAAEvD,MAAM7C,IAAI6G,QAAQ;QAAIC,QAAQ;IAAS,IAAI;AAClF;AAEA,oGAAoG,GACpG,SAASC,UAAUxC,KAAe,EAAE1C,IAAsB,EAAEkE,KAAwC,EAAEf,QAAiB;IACrH,MAAMlF,QAAQ+B,KAAK/B,KAAK;IACxB,IAAIA,MAAMC,IAAI,KAAK,QAAQ,OAAO,EAAE;IACpC,MAAMiH,QAAQxH,MAAMM,MAAML,IAAI;IAC9B,MAAMwH,OAAOD,MAAMtG,MAAM,IAAIxB,6BAA6B+G,UAAU1B,OAAOzE,MAAM+C,IAAI,IAAI;IACzF,IAAI,CAACoE,MAAM,OAAOnB,UAAUvB,OAAOyC,OAAO,QAAQhC;IAClD,MAAMkC,OACJnB,UAAU,YACN;QAAET,SAAS;QAAaU,OAAO;IAAU,IACzCD,UAAU,cACR;QAAET,SAAS;QAAYU,OAAO;IAAU,IACxC;QAAEV,SAAS;QAAQU,OAAO;IAAU;IAC5C,OAAO;QACLzB,MAAMI,IAAI,CAAChD,GAAG,CACZ,aACA;YAAE4D,UAAUyB;WAAUE;YAAMC,MAAM;WAAaF,OAC/C;YAAEG,WAAWpC,WAAW,WAAW;QAAa,GAChD,MACA;KAEH;AACH;AAEA,qGAAqG,GACrG,SAASqC,WAAW9C,KAAe,EAAE1C,IAAsB;IACzD,MAAM/B,QAAQ+B,KAAK/B,KAAK;IACxB,IAAIA,MAAMC,IAAI,KAAK,SAAS,OAAO,EAAE;IACrC,MAAMmD,QAAQqB,MAAMtB,MAAM,CAACyD,GAAG,CAAC5G,MAAME,GAAG;IACxC,IAAI,CAACkD,OAAO,OAAO,EAAE;IACrB,MAAMpB,MAAMtC,MAAMM,MAAMgC,GAAG,IAAIoB,MAAMpB,GAAG,IAAI,IAAIJ,KAAK,CAAC,GAAG;IACzD6C,MAAM+C,QAAQ,IAAI;IAClB,OAAO;QACL/C,MAAMI,IAAI,CAAChD,GAAG,CACZ,SACA;YACEyB,KAAKF,MAAME,GAAG;WACVtB,MAAM;YAAEA;QAAI,IAAI;YAAEyF,YAAY;QAAK;YACvCC,WAAW;WACPjD,MAAM+C,QAAQ,GAAG,IAAI;YAAEG,SAAS;QAAO,IAAI,CAAC,IAElD,MACA,MACA;KAEH;AACH;AAEA,+GAA+G,GAC/G,SAASC,UAAUnD,KAAe,EAAE1C,IAAsB,EAAEkE,KAAY,EAAEf,QAAiB,EAAE2C,SAA4C,OAAO;IAC9I,IAAI,CAACrD,KAAKC,OAAO3C,qBAAqBC,MAAMnB,MAAM,GAAG,OAAO,EAAE;IAC9D,MAAMZ,QAAQ+B,KAAK/B,KAAK;IACxB,IAAI8H;IACJ,IAAI9H,MAAMC,IAAI,KAAK,SAAS6H,MAAMP,WAAW9C,OAAO1C;SAC/C,IAAI/B,MAAMC,IAAI,KAAK,QAAQ6H,MAAMb,UAAUxC,OAAO1C,MAAM8F,QAAQ3C;SAChE,IAAIe,UAAU,aAAcjG,MAAMC,IAAI,KAAK,aAAagG,UAAU,QAAS6B,MAAM;QAAC7C,YAAYR,OAAO/E,MAAMM,MAAML,IAAI,GAAGK,MAAMC,IAAI,KAAK,YAAYD,MAAMW,KAAK,GAAG,GAAGuE;KAAU;SAC9K,IAAIlF,MAAMC,IAAI,KAAK,WAAWgG,UAAU,QAAQ;QACnD6B,MAAM;YAACrD,MAAMI,IAAI,CAAChD,GAAG,CAAC,UAAU;gBAAE6D,WAAW;YAAa,GAAG,MAAMM,UAAUvB,OAAOzE,MAAML,IAAI,EAAE,QAAQuF,WAAW;SAAS;IAC9H,OAAO4C,MAAM9B,UAAUvB,OAAOzE,MAAML,IAAI,EAAEsG,OAAOf;IACjD,+DAA+D;IAC/D,IAAI4C,IAAIlH,MAAM,EAAEmE,KAAKN,OAAO1C;IAC5B,OAAO+F;AACT;AAEA,6CAA6C,GAC7C,SAASC,SAAStD,KAAe,EAAElE,KAAkC;IACnE,MAAMyH,OAAiB,EAAE;IACzB,MAAM5D,OAAiB,EAAE;IACzB,KAAK,MAAMrC,QAAQxB,MAAO;QACxB,IAAIwB,KAAK/B,KAAK,CAACC,IAAI,KAAK,WAAW8B,KAAK/B,KAAK,CAACC,IAAI,KAAK,QAAQ;YAC7DmE,KAAKtD,IAAI,IAAI8G,UAAUnD,OAAO1C,MAAM,QAAQ;YAC5C;QACF;QACA,MAAMpC,OAAOmC,qBAAqBC;QAClC,IAAI,CAACyC,KAAKC,OAAO9E,KAAKiB,MAAM,GAAG;QAC/B,KAAK,MAAMsD,QAAQL,sBAAsBlE,MAAO;YAC9CqI,KAAKlH,IAAI,CACP2D,MAAMI,IAAI,CAAChD,GAAG,CAAC,eAAe;gBAAEoG,gBAAgB;YAAK,GAAG,MAAM;gBAACxD,MAAMI,IAAI,CAAChD,GAAG,CAAC,mBAAmB;oBAAEqG,SAAShE;gBAAK,GAAG,MAAM,MAAM;aAAQ,EAAE;QAE9I;QACAa,KAAKN,OAAO1C;IACd;IACA,OAAO;WAAKiG,KAAKpH,MAAM,GAAG;YAAC6D,MAAMI,IAAI,CAAChD,GAAG,CAAC,WAAW;gBAAEsG,gBAAgB;YAAK,GAAG,MAAMH,MAAM;SAAQ,GAAG,EAAE;WAAM5D;KAAK;AACrH;AAEA,mFAAmF,GACnF,SAASgE,UAAU3D,KAAe,EAAExE,IAAmC,EAAEoI,KAAoC;IAC3G,MAAMC,QAAkB,EAAE;IAC1B,MAAMlE,OAAiB,EAAE;IACzB,KAAK,MAAMmE,QAAQF,MAAO;QACxB,MAAMtE,QAAQwE,KAAKrF,MAAM,CAAC,CAACnB,OAASA,KAAK/B,KAAK,CAACC,IAAI,KAAK,WAAW8B,KAAK/B,KAAK,CAACC,IAAI,KAAK;QACvF,KAAK,MAAM8B,QAAQwG,KAAM,IAAIxG,KAAK/B,KAAK,CAACC,IAAI,KAAK,WAAW8B,KAAK/B,KAAK,CAACC,IAAI,KAAK,QAAQmE,KAAKtD,IAAI,IAAI8G,UAAUnD,OAAO1C,MAAM,QAAQ;QACpI,IAAI,CAACgC,MAAMnD,MAAM,EAAE;QACnB,MAAM,CAACN,OAAO,GAAGrB,KAAK,GAAG8E;QACzB,MAAMpE,OAAOV,KAAKqC,GAAG,CAACQ,sBAAsB0G,IAAI,CAAC;QACjD,IAAI,CAAChE,KAAKC,OAAO3C,qBAAqBxB,OAAOM,MAAM,GAAGjB,KAAKiB,MAAM,GAAG;QACpE,MAAMsH,UAAUpG,qBAAqBxB;QACrC,IAAI4H,QAAQtH,MAAM,GAAG5B,iCAAiCW,KAAKiB,MAAM,GAAG5B,+BAA+B;YACjG,oEAAoE;YACpE,KAAK,MAAM+C,QAAQgC,MAAOK,KAAKtD,IAAI,IAAI8G,UAAUnD,OAAO1C,MAAM,QAAQ;YACtE;QACF;QACAuG,MAAMxH,IAAI,CAAC2D,MAAMI,IAAI,CAAChD,GAAG,CAAC,mBAAmB;YAAEqG;WAAavI,OAAO;YAAE8I,WAAW9I;QAAK,IAAI,CAAC,IAAM,MAAM,MAAM;QAC5G,KAAK,MAAMoC,QAAQgC,MAAOgB,KAAKN,OAAO1C;IACxC;IACA,IAAI,CAACuG,MAAM1H,MAAM,EAAE,OAAOwD;IAC1B,MAAMsE,SAASzI,SAAS,SAASA,SAAS,WAAWqI,MAAM1H,MAAM,KAAK,IAAI,IAAIyE,KAAKC,GAAG,CAAC,GAAGgD,MAAM1H,MAAM;IACtG,IAAI8H,WAAW,GAAG,OAAO;QAACjE,MAAMI,IAAI,CAAChD,GAAG,CAAC,YAAY;YAAE8G,SAAS;QAAI,GAAG,MAAML,OAAOrI;WAAUmE;KAAK;IACnG,MAAMiD,OAAOqB,WAAW,IAAI,eAAe;IAC3C,MAAME,QAAQN,MAAMhH,GAAG,CAAC,CAACqC,KAAOc,MAAMI,IAAI,CAAChD,GAAG,CAAC,WAAW;YAAEwF;QAAK,GAAG,MAAM;YAAC1D;SAAG,EAAE;IAChF,OAAO;QAACc,MAAMI,IAAI,CAAChD,GAAG,CAAC,WAAW;YAAEgH,WAAW;YAAMF,SAAS;QAAI,GAAG,MAAMC,OAAO3I;WAAUmE;KAAK;AACnG;AAEA,iGAAiG,GACjG,SAAS0E,YAAYrE,KAAe,EAAErD,KAAa,EAAEP,IAAY,EAAEkI,IAAyB,EAAE9E,KAAwB,EAAEN,EAAU,EAAEqF,IAAa;IAC/I,MAAMnE,OAAOJ,MAAMI,IAAI;IACvB,MAAMoE,UAAUhF,MAAMrD,MAAM,KAAK,IAAIqD,KAAK,CAAC,EAAE,GAAGY,KAAKhD,GAAG,CAAC,YAAY;QAAE8G,SAAS;QAAKO,YAAY;IAAK,GAAG,MAAMjF,OAAO;IACtH,MAAM4E,YAAYhE,KAAKhD,GAAG,CAAC,gBAAgB;QAAEsH,UAAUH,OAAO,OAAO;IAAK,GAAG;QAAEI,IAAI,aAAK7J;IAAkB,GAAG;QAAC0J;KAAQ,EAAE;IACxH,OAAOpE,KAAKhD,GAAG,CACb,WACA;QAAEwH,SAAS;QAAWC,WAAWzI,QAAQ,CAAC,QAAQ,EAAEO,QAAQ,GAAG;OAAM2H,SAAS,SAAS;QAAEQ,aAAa;IAAO,IAAI,CAAC,IAClHhL,OAAOwK,OACP;QAACF;KAAU,EACX,WACAlF;AAEJ;AAEA,mHAAmH,GACnH,SAAS6F,YAAY/E,KAAe,EAAElE,KAAkC;IACtE,MAAM0D,QAAkB,EAAE;IAC1B,IAAK,IAAII,KAAK,GAAGA,KAAK9D,MAAMK,MAAM,EAAEyD,MAAM,EAAG;QAC3C,MAAMtC,OAAOxB,KAAK,CAAC8D,GAAG;QACtB,IAAItC,KAAK/B,KAAK,CAACC,IAAI,KAAK,aAAa;YACnC,MAAMwJ,MAA0B,EAAE;YAClC,MAAOpF,KAAK9D,MAAMK,MAAM,IAAIL,KAAK,CAAC8D,GAAG,CAACrE,KAAK,CAACC,IAAI,KAAK,YAAawJ,IAAI3I,IAAI,CAACP,KAAK,CAAC8D,KAAK;YACtFA,MAAM;YACNJ,MAAMnD,IAAI,IAAIiH,SAAStD,OAAOgF;YAC9B;QACF;QACAxF,MAAMnD,IAAI,IAAI8G,UAAUnD,OAAO1C,MAAM,QAAQ;IAC/C;IACA,OAAOkC;AACT;AAEA,oHAAoH,GACpH,SAASyF,SAASC,MAAmD,EAAEC,OAA0B,EAAE5I,OAAe,EAAE6I,IAAiB;IACnI,MAAMtJ,QAA4B,EAAE;IACpC,KAAK,MAAMQ,KAAK6I,QAAS;QACvB,MAAM7H,OAAO4H,OAAO5I;QACpB,IAAI,CAACgB,QAAQA,KAAKf,OAAO,KAAKA,SAAS;QACvC,IAAI6I,KAAKnI,GAAG,CAACX,IAAI,OAAO;QACxBR,MAAMO,IAAI,CAACiB;IACb;IACA,OAAOxB;AACT;AAEA,iEAAiE,GACjE,SAASuJ,cAAcrF,KAAe,EAAEzE,KAA4B,EAAEO,KAAkC,EAAE8H,KAAoC,EAAEnD,QAAiB;IAC/J,OAAQlF,MAAMC,IAAI;QAChB,KAAK;YACH,OAAO8H,SAAStD,OAAOlE;QACzB,KAAK;QACL,KAAK;QACL,KAAK;QACL,KAAK;YACH,OAAO6H,UAAU3D,OAAOzE,MAAMC,IAAI,EAAEoI,MAAMzH,MAAM,GAAGyH,QAAQ9H,MAAMe,GAAG,CAAC,CAACS,OAAS;oBAACA;iBAAK;QACvF,KAAK;YAAU;gBACb,MAAMkE,QAAQjG,MAAMiG,KAAK,KAAK,aAAajG,MAAMiG,KAAK,KAAK,cAAcjG,MAAMiG,KAAK,GAAG;gBACvF,OAAO1F,MAAMmC,OAAO,CAAC,CAACX,OAAS6F,UAAUnD,OAAO1C,MAAM,QAAQmD,UAAUe;YAC1E;QACA,KAAK;YACH,OAAO1F,MAAMmC,OAAO,CAAC,CAACX,OAAS6F,UAAUnD,OAAO1C,MAAM,QAAQmD;QAChE,KAAK;QACL,KAAK;QACL,KAAK;QACL,KAAK;YAAQ;gBACX,MAAMe,QAAejG,MAAMC,IAAI;gBAC/B,OAAOM,MAAMmC,OAAO,CAAC,CAACX,MAAMgI,WAC1B,uEAAuE;oBACvEnC,UAAUnD,OAAO1C,MAAMkE,UAAU,aAAa8D,WAAW,IAAI,SAAS9D,OAAOf;YAEjF;QACA;YACE,OAAO3E,MAAMmC,OAAO,CAAC,CAACX,OAAS6F,UAAUnD,OAAO1C,MAAMA,KAAK/B,KAAK,CAACC,IAAI,KAAK,YAAY,YAAY,QAAQiF;IAC9G;AACF;AAEA,iHAAiH,GACjH,SAAS8E,cACPvF,KAAe,EACfwF,QAAiC,EACjC7I,KAAa,EACbb,KAAkC,EAClCoJ,MAAmD,EACnDE,IAAiB;IAEjB,MAAM3E,WAAW+E,SAAStE,KAAK,KAAK;IACpC,MAAMuE,OAAOD,SAASC,IAAI,IAAID,SAASC,IAAI,CAACtJ,MAAM,IAAI,IAAIqJ,SAASC,IAAI,GAAG;IAC1E,MAAMC,OAAiB,EAAE;IACzB,MAAMC,OAAiB,EAAE;IACzB,MAAMC,UAAsBH,OAAOA,KAAK5I,GAAG,CAAC,IAAM,EAAE,IAAI,EAAE;IAC1D,IAAIgJ,cAAc;IAClB,KAAK,MAAMtK,SAASiK,SAAS5J,MAAM,CAAE;YAEXL,cAKAA;QANxB,MAAMuK,SAAS/L,6BAA6BkD,GAAG,CAAC1B,MAAMC,IAAI;QAC1D,MAAMgB,QAAQsJ,YAAUvK,eAAAA,MAAMqI,KAAK,qBAAXrI,aAAaY,MAAM,IAAGZ,MAAMqI,KAAK,CAACmC,IAAI,KAAKxK,MAAMuD,IAAI;QAC7E,MAAMkH,QAAQf,SAASC,QAAQ1I,OAAOG,OAAOyI;QAC7C,oFAAoF;QACpF,IAAI,EAACY,yBAAAA,MAAO7J,MAAM,GAAE;QACpB,KAAK,MAAMmB,QAAQ0I,MAAOZ,KAAKhI,GAAG,CAACE,KAAKhB,CAAC;QACzC,MAAMsH,QAAQkC,YAAUvK,gBAAAA,MAAMqI,KAAK,qBAAXrI,cAAaY,MAAM,IAAGZ,MAAMqI,KAAK,CAAC/G,GAAG,CAAC,CAACiH,OAASkC,MAAMvH,MAAM,CAAC,CAACnB,OAASwG,KAAK/B,QAAQ,CAACzE,KAAKhB,CAAC,IAAImC,MAAM,CAAC,CAACqF,OAASA,KAAK3H,MAAM,IAAI,EAAE;QACzJ,MAAMkH,MAAMgC,cAAcrF,OAAOzE,OAAOyK,OAAOpC,OAAOnD,YAAYlF,MAAM0K,GAAG,KAAKC;QAChF,IAAIT,QAAQlK,MAAM0K,GAAG,KAAKC,WAAW;YACnCN,OAAO,CAAChF,KAAKC,GAAG,CAACtF,MAAM0K,GAAG,EAAER,KAAKtJ,MAAM,GAAG,GAAG,CAACE,IAAI,IAAIgH;YACtDwC,cAAc;QAChB,OAAO,AAACA,CAAAA,cAAcF,OAAOD,IAAG,EAAGrJ,IAAI,IAAIgH;IAC7C;IACA,mFAAmF;IACnF,MAAM8C,OAAOrK,MAAM2C,MAAM,CAAC,CAACnB,OAAS,CAAC8H,KAAKnI,GAAG,CAACK,KAAKhB,CAAC;IACpD,KAAK,MAAMgB,QAAQ6I,KAAMf,KAAKhI,GAAG,CAACE,KAAKhB,CAAC;IACxCqJ,KAAKtJ,IAAI,IAAI0I,YAAY/E,OAAOmG;IAChC,MAAMC,SAASR,QAAQ/I,GAAG,CAAC,CAACwG,KAAKiC;;eAAc;YAAEjC;YAAKgD,MAAM,UAAEZ,wBAAAA,IAAM,CAACH,SAAS,mBAAI;QAAE;OAAI7G,MAAM,CAAC,CAACP,QAAUA,MAAMmF,GAAG,CAAClH,MAAM;IAC1H,MAAMqD,QAAQ;WAAIkG;KAAK;IACvB,IAAIU,OAAOjK,MAAM,IAAI,GAAG;QACtB,MAAMmK,QAAQrM,cAAcmM,OAAOvJ,GAAG,CAAC,CAACqB,QAAUA,MAAMmI,MAAM;QAC9D,MAAMlC,QAAQiC,OAAOvJ,GAAG,CAAC,CAACqB,OAAOoH,WAC/BtF,MAAMI,IAAI,CAAChD,GAAG,CACZ,WACA;gBAAEwF,MAAM,CAAC,SAAS,EAAE0D,KAAK,CAAChB,SAAS,EAAE;YAAC,GACtC,MACA;gBAACpH,MAAMmF,GAAG,CAAClH,MAAM,KAAK,IAAI+B,MAAMmF,GAAG,CAAC,EAAE,GAAGrD,MAAMI,IAAI,CAAChD,GAAG,CAAC,YAAY;oBAAE8G,SAAS;gBAAI,GAAG,MAAMhG,MAAMmF,GAAG,EAAE;aAAU,EACjH;QAGJ7D,MAAMnD,IAAI,CAAC2D,MAAMI,IAAI,CAAChD,GAAG,CAAC,WAAW;YAAEgH,WAAW;YAAMF,SAAS;QAAI,GAAG,MAAMC,OAAO;IACvF,OAAO,IAAIiC,OAAOjK,MAAM,KAAK,GAAGqD,MAAMnD,IAAI,IAAI+J,MAAM,CAAC,EAAE,CAAC/C,GAAG;IAC3D7D,MAAMnD,IAAI,IAAIsJ;IACd,OAAO;QAAEnG;QAAO+E,MAAM6B,OAAOjK,MAAM,IAAI;IAAE;AAC3C;AAyBA;;;;CAIC,GACD,OAAO,SAASoK,0BAA0BC,KAA+B;IACvE,MAAM,EAAE1E,GAAG,EAAE,GAAG0E;IAChB,MAAM,EAAE1K,KAAK,EAAEC,QAAQ,EAAE,GAAGJ,qBAAqBmG,IAAIlG,MAAM,EAAEkG,IAAIjG,KAAK;IACtE,MAAMuE,OAAO,IAAIpG,oBAAoB;IACrC,MAAMoI,UAAU,IAAI3F,IAAI+J,MAAMC,KAAK,CAAC5J,GAAG,CAAC,CAAC6J,OAASA,KAAKxH,EAAE;IACzD,MAAMc,QAAkB;QACtBI;QACA0B;QACAM;QACAF,QAAQ,IAAIyE,IAAI7E,IAAI/C,KAAK,CAAClC,GAAG,CAAC,CAACmC,OAAS;gBAACA,KAAKC,IAAI,CAAC7D,OAAO,CAAC,QAAQ,OAAO;gBAAK4D,KAAKE,EAAE;aAAC;QACvFR,QAAQ,IAAIiI,IAAI7E,IAAIpD,MAAM,CAACT,OAAO,CAAC,CAACU,QAAWA,MAAMG,IAAI,GAAG;gBAAC;oBAACH,MAAMG,IAAI;oBAAEH;iBAAM;aAAU,GAAG,EAAE;QAC/FgC,QAAQ;QACRG,WAAW;QACXiC,UAAU;QACVxC,QAAQ,EAAE;QACVjB,OAAO;QACPW,OAAO;QACPH,KAAK;IACP;IACA,MAAMoF,SAAS,CAAC5I,IAAcR,KAAK,CAACQ,IAAI,EAAE;IAC1C,MAAM8I,OAAO,IAAI3I;IACjB,MAAM+I,WAAW1D,IAAIhE,KAAK,KAAK,aAAa0I,MAAMI,WAAW,GAAG;IAChE,2GAA2G;IAC3G,MAAMC,UAAUrB,4BAAAA,SAAUzJ,QAAQ,CAAC,EAAE;IACrC,IAAI8K,WAAW,CAACA,QAAQjL,MAAM,CAACkL,IAAI,CAAC,CAACvL,QAAUA,MAAMC,IAAI,KAAK,YAAY;QACxE,MAAMuL,QAAQF,QAAQjL,MAAM,CAACkB,IAAI,CAAC,CAACvB,QAAUA,MAAMuD,IAAI,CAACgI,IAAI,CAAC,CAACxK;oBAAM4I;uBAAAA,EAAAA,UAAAA,OAAO5I,uBAAP4I,QAAW3J,KAAK,CAACC,IAAI,MAAK;;QAC9F,IAAIuL,SAAS,CAAChN,6BAA6BkD,GAAG,CAAC8J,MAAMvL,IAAI,KAAKuL,MAAMvL,IAAI,KAAK,QAAQuL,MAAMvL,IAAI,GAAG;IACpG;IACA,MAAMwL,QAAQjL,SAASc,GAAG,CAAC,CAACN,SAASI;yBAgByB6J;YAZehB;QAH3E,MAAMyB,MAAM1K,QAAQT,KAAK,CAACe,GAAG,CAACqI;QAC9B,2EAA2E;QAC3E,MAAMgC,aAAuB,EAAE;QAC/B,IAAIvK,UAAU,KAAK,CAACsK,IAAIH,IAAI,CAAC,CAACxJ,OAASA,KAAK/B,KAAK,CAACC,IAAI,KAAK,cAAc,EAAEgK,6BAAAA,sBAAAA,SAAUzJ,QAAQ,CAAC,EAAE,qBAArByJ,oBAAuB5J,MAAM,CAACkL,IAAI,CAAC,CAACvL,QAAUA,MAAMC,IAAI,KAAK,aAAa;YACnJ0L,WAAW7K,IAAI,CAACmE,YAAYR,OAAO/E,MAAM6G,IAAIjG,KAAK,KAAKU,QAAQH,IAAI,EAAE,GAAG;QAC1E;QACA,MAAM+K,eAAO3B,4BAAAA,SAAUzJ,QAAQ,CAACY,MAAM,mBAAI;QAC1C,MAAMyK,QAAQD,OAAO5B,cAAcvF,OAAOmH,MAAMxK,OAAOsK,KAAK/B,QAAQE,QAAQ;YAAE5F,OAAOuF,YAAY/E,OAAOiH,IAAIxI,MAAM,CAAC,CAACnB,OAAS,CAAC8H,KAAKnI,GAAG,CAACK,KAAKhB,CAAC;YAAKiI,MAAM;QAAM;QAC9J,KAAK,MAAMjH,QAAQ2J,IAAK7B,KAAKhI,GAAG,CAACE,KAAKhB,CAAC;QACvC,MAAMgI,gBAA4B6C,wBAAAA,KAAM7C,IAAI,oBAAK3H,QAAQ,MAAM,IAAI,SAAS;QAC5E,MAAM6C,QAAQ;eAAI0H;eAAeE,MAAM5H,KAAK;SAAC;QAC7C,IAAI,CAACA,MAAMrD,MAAM,EAAE;YACjB,8GAA8G;YAC9GqD,MAAMnD,IAAI,CAACmE,YAAYR,OAAOzD,QAAQH,IAAI,EAAE,GAAG;QACjD;QACA,OAAOiI,YAAYrE,OAAOrD,OAAOJ,QAAQH,IAAI,EAAEkI,MAAM9E,QAAOgH,0BAAAA,MAAMa,UAAU,CAAC1K,MAAM,YAAvB6J,0BAA2B,CAAC,QAAQ,EAAE7J,QAAQ,GAAG,EAAEyK,MAAM7C,IAAI;IAC3H;IACA,MAAM+C,SAASlH,KAAKhD,GAAG,CAAC,OAAO,MAAM,MAAM4J,OAAO,QAAQnN;IAC1D,KAAK,MAAM0N,QAAQP,MAAO5G,KAAKoH,KAAK,CAACD;IACrC,MAAME,cAAc3L,MAAM4L,MAAM,CAAC,CAACC,OAAOrK,OAASqK,QAASrK,CAAAA,KAAK/B,KAAK,CAACC,IAAI,KAAK,UAAU,IAAIgC,QAAQH,qBAAqBC,MAAK,GAAI;IACnI,OAAO;QAAE8C,MAAM;YAAEkH;YAAQjH,OAAOD,KAAKC,KAAK;QAAC;QAAGE,QAAQP,MAAMO,MAAM;QAAEzE,OAAOA,MAAMK,MAAM;QAAEmD,OAAOU,MAAMV,KAAK;QAAEmI;QAAa3H,KAAKE,MAAMF,GAAG;IAAC;AAC3I;AAEA;;2CAE2C,GAE3C,gFAAgF,GAChF,OAAO,MAAM8H,mCAAmC,MAAM;AAEtD,MAAMC,YAAqD;IACzDjL,SAAS;IACTkL,WAAW;IACX,aAAa;IACbC,OAAO;IACP/I,MAAM;IACNgJ,OAAO;AACT;AAEA,sGAAsG,GACtG,OAAO,SAASC,wBAAwBnG,GAAmD;IACzF,MAAM,EAAEhG,KAAK,EAAEC,QAAQ,EAAE,GAAGJ,qBAAqBmG,IAAIlG,MAAM,EAAEkG,IAAIjG,KAAK;IACtE,OAAOE,SACJc,GAAG,CAAC,CAACN,SAASI,QACb;YACE,CAAC,QAAQ,EAAEA,QAAQ,EAAE,EAAE,EAAEJ,QAAQH,IAAI,EAAE;eACpCG,QAAQT,KAAK,CAACe,GAAG,CAAC,CAACP;gBACpB,MAAMgB,OAAOxB,KAAK,CAACQ,IAAI,EAAE;gBACzB,MAAMd,OAAO8B,KAAK/B,KAAK,CAACC,IAAI,KAAK,YAAY,CAAC,QAAQ,EAAE8B,KAAK/B,KAAK,CAACW,KAAK,EAAE,GAAG2L,SAAS,CAACvK,KAAK/B,KAAK,CAACC,IAAI,CAAC;gBACvG,OAAO,CAAC,CAAC,EAAEc,EAAE,GAAG,EAAEd,KAAK,EAAE,EAAE6B,qBAAqBC,SAAS,oBAAoB;YAC/E;SACD,CAACyG,IAAI,CAAC,OAERA,IAAI,CAAC;AACV"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { PluginMediaIngest } from '@aglyn/aglyn/plugin-manager/plugin-media-ingest';
|
|
18
|
+
import type { AiSiteImportHome, AiSiteImportLook, AiSiteImportPhoto } from '../model/ai-site-import';
|
|
19
|
+
import { type AiSiteStyle } from '../model/ai-site-look';
|
|
20
|
+
import { type SiteImportCrawlSeams } from '../site-import/site-import-crawl';
|
|
21
|
+
import { aiSaveSiteTheme, aiWorkspaceSiteStyles } from './ai-job-site-look';
|
|
22
|
+
import { type AiSiteInspirationSite } from './ai-site-inspiration-signals';
|
|
23
|
+
import { type AiReferencePage, type PublicFetchOptions, type PublicFetchResult } from './ai-theme-brand-inputs';
|
|
24
|
+
/** The height an SVG logo is drawn at as a picture: crisp at twice the header's 40. */
|
|
25
|
+
export declare const AI_SITE_IMPORT_LOGO_HEIGHT = 192;
|
|
26
|
+
/** The largest logo file read. */
|
|
27
|
+
export declare const AI_SITE_IMPORT_LOGO_MAX_BYTES: number;
|
|
28
|
+
type Fetch = (url: string, options: PublicFetchOptions) => Promise<PublicFetchResult>;
|
|
29
|
+
/**
|
|
30
|
+
* The home page and its first stylesheets, read as AglynBot (AGL-4084), the
|
|
31
|
+
* edge headers going with the page and with a stylesheet only on the page's
|
|
32
|
+
* own host. `null` when the page cannot be read.
|
|
33
|
+
*/
|
|
34
|
+
export declare function readSiteImportLookPage(url: string, input: {
|
|
35
|
+
fetch?: Fetch;
|
|
36
|
+
headers?: Readonly<Record<string, string>> | null;
|
|
37
|
+
signal?: AbortSignal;
|
|
38
|
+
}): Promise<AiReferencePage | null>;
|
|
39
|
+
/**
|
|
40
|
+
* A source's brand colors as it DRAWS them (AGL-4084): the colors its markup
|
|
41
|
+
* and stylesheets use solid, most used first, the colorful before the greys.
|
|
42
|
+
* A translucent color is a tint of one (a hover, a focus ring), never a use
|
|
43
|
+
* of it, so it is not counted: aglyn.com's focus rings, a light blue at a
|
|
44
|
+
* quarter strength, outnumbered the deep blue its buttons and links are
|
|
45
|
+
* drawn in, which the theme reader would otherwise take as its brand.
|
|
46
|
+
*/
|
|
47
|
+
export declare function aiSiteImportBrandColors(page: Pick<AiReferencePage, 'html' | 'stylesheets'>): string[];
|
|
48
|
+
/**
|
|
49
|
+
* The look a source's signals pin (AGL-4084), drawn the way a guided start's
|
|
50
|
+
* is: the kind of site its own description says, the import's seed, its
|
|
51
|
+
* strongest color as the brand. `null` where the source showed no color to
|
|
52
|
+
* carry a brand, so the site keeps the theme it has.
|
|
53
|
+
*/
|
|
54
|
+
export declare function aiSiteImportStyle(site: AiSiteInspirationSite | null, input: {
|
|
55
|
+
seed: number;
|
|
56
|
+
description: string;
|
|
57
|
+
avoid?: readonly AiSiteStyle[];
|
|
58
|
+
}): AiSiteStyle | null;
|
|
59
|
+
/**
|
|
60
|
+
* The site's listing in the home page's own words (AGL-4084), as field
|
|
61
|
+
* paths: its title and description each only where the site still has the
|
|
62
|
+
* one it was created with, so an owner's own listing is never written over.
|
|
63
|
+
* `{}` with nothing to write.
|
|
64
|
+
*/
|
|
65
|
+
export declare function aiSiteImportListingUpdates(seo: unknown, displayName: unknown, home: Pick<AiSiteImportHome, 'title' | 'description'>): Record<string, string>;
|
|
66
|
+
/** Turns an SVG drawing into a PNG of a height; `null` where it cannot. */
|
|
67
|
+
export type AiSiteImportRasterize = (svg: Uint8Array, height: number) => Promise<Uint8Array | null>;
|
|
68
|
+
export interface AiSiteImportLookDeps {
|
|
69
|
+
fetch?: Fetch;
|
|
70
|
+
headersFor?: SiteImportCrawlSeams['headersFor'];
|
|
71
|
+
save?: typeof aiSaveSiteTheme;
|
|
72
|
+
others?: typeof aiWorkspaceSiteStyles;
|
|
73
|
+
ingest?: PluginMediaIngest | null;
|
|
74
|
+
rasterize?: AiSiteImportRasterize;
|
|
75
|
+
/** The site's document, as the listing reads it. */
|
|
76
|
+
readHost?: (firestore: FirebaseFirestore.Firestore, hostId: string) => Promise<Record<string, unknown> | null>;
|
|
77
|
+
writeHost?: (firestore: FirebaseFirestore.Firestore, hostId: string, updates: Record<string, unknown>) => Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
export interface AiSiteImportLookInput {
|
|
80
|
+
firestore: FirebaseFirestore.Firestore;
|
|
81
|
+
hostId: string;
|
|
82
|
+
orgId: string;
|
|
83
|
+
/** The member the logo and the share picture are stored as. */
|
|
84
|
+
uid: string;
|
|
85
|
+
/** The import job's id, which the look's seed is drawn from. */
|
|
86
|
+
jobId: string;
|
|
87
|
+
domain: string;
|
|
88
|
+
home: AiSiteImportHome;
|
|
89
|
+
at: string;
|
|
90
|
+
signal?: AbortSignal;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The source header's logo in the site's library (AGL-4084), once: the copy
|
|
94
|
+
* a page already brought in is reused by its key. An SVG becomes a PNG.
|
|
95
|
+
* `null` where it cannot be brought in, and the header then prints the name.
|
|
96
|
+
*/
|
|
97
|
+
export declare function aiSiteImportLogo(input: {
|
|
98
|
+
hostId: string;
|
|
99
|
+
uid: string;
|
|
100
|
+
domain: string;
|
|
101
|
+
logo: NonNullable<AiSiteImportHome['brand']['logo']>;
|
|
102
|
+
alt: string;
|
|
103
|
+
headers?: Readonly<Record<string, string>> | null;
|
|
104
|
+
signal?: AbortSignal;
|
|
105
|
+
}, deps: {
|
|
106
|
+
fetch?: Fetch;
|
|
107
|
+
ingest: PluginMediaIngest | null;
|
|
108
|
+
rasterize?: AiSiteImportRasterize;
|
|
109
|
+
}): Promise<AiSiteImportPhoto | null>;
|
|
110
|
+
/** The look pass (AGL-4084): the theme, the logo and the listing, from the owner's own home page. No model. */
|
|
111
|
+
export declare function aiRunSiteImportLook(input: AiSiteImportLookInput, deps?: AiSiteImportLookDeps): Promise<AiSiteImportLook>;
|
|
112
|
+
export {};
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /*==========================================
|
|
17
|
+
* THE SITE BROUGHT IN KEEPS ITS OWN LOOK (AGL-4084).
|
|
18
|
+
*
|
|
19
|
+
* The first wave's look pass, before the header and footer, with no model:
|
|
20
|
+
*
|
|
21
|
+
* - **The theme.** The owner's own home page is read once more as AglynBot
|
|
22
|
+
* (with the edge headers a site this platform serves gets), and the
|
|
23
|
+
* signals a guided start reads off the sites a person likes (AGL-3975:
|
|
24
|
+
* colors, ground, type mood, corners, buttons, cards, header, density,
|
|
25
|
+
* motion) pin the look: its strongest colorful color is the brand, its
|
|
26
|
+
* second the accent. The look is drawn and saved exactly as a guided
|
|
27
|
+
* start's (`aiSiteStyleFor`, `aiSaveSiteTheme`): a built-in base plus the
|
|
28
|
+
* brand colors and a pairing of the source's type mood, never its font.
|
|
29
|
+
* A site whose owner already chose a theme keeps it.
|
|
30
|
+
* - **The logo.** The source header's logo, as the source draws it, into the
|
|
31
|
+
* site's media library once: an SVG is turned into a PNG 192 pixels tall
|
|
32
|
+
* (the library takes pictures, not drawings), a picture is kept as it is.
|
|
33
|
+
* - **The listing.** The home page's own title, description and share
|
|
34
|
+
* picture become the site's listing, only while the site still has the
|
|
35
|
+
* listing it was created with: an owner's own words are never written
|
|
36
|
+
* over.
|
|
37
|
+
*
|
|
38
|
+
* Nothing here fails a wave: a source whose look cannot be read keeps the
|
|
39
|
+
* site's theme, and a logo that cannot be brought in leaves the header its
|
|
40
|
+
* name.
|
|
41
|
+
*=========================================*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
42
|
+
import { aiSiteListingSeeded } from "../model/ai-site-entity.js";
|
|
43
|
+
import { aiSiteKindFor } from "../model/ai-site-kinds.js";
|
|
44
|
+
import { aiSiteSeed, aiSiteStyleFor } from "../model/ai-site-look.js";
|
|
45
|
+
import { aiSiteIsStarterSrc } from "../model/ai-site-photos.js";
|
|
46
|
+
import { AGLYN_BOT_USER_AGENT } from "../site-import/site-import-crawl.js";
|
|
47
|
+
import { bringInSiteImportPhotos, siteImportImageKey, siteImportLogoUrl } from "../site-import/site-import-images.js";
|
|
48
|
+
import { aiSaveSiteTheme, aiWorkspaceSiteStyles } from "./ai-job-site-look.js";
|
|
49
|
+
import { aiSiteInspirationLookPins } from "./ai-site-inspiration.js";
|
|
50
|
+
import { aiSiteInspirationSignals } from "./ai-site-inspiration-signals.js";
|
|
51
|
+
import { AI_THEME_BRAND_COLORS_PER_SOURCE, AI_THEME_REFERENCE_MAX_BYTES, AI_THEME_REFERENCE_STYLESHEETS, AI_THEME_STYLESHEET_MAX_BYTES, AI_THEME_THEME_COLOR_WEIGHT, fetchPublicTextDetailed, rankBrandColors, stylesheetUrlsOf } from "./ai-theme-brand-inputs.js";
|
|
52
|
+
/** The height an SVG logo is drawn at as a picture: crisp at twice the header's 40. */ export const AI_SITE_IMPORT_LOGO_HEIGHT = 192;
|
|
53
|
+
/** The largest logo file read. */ export const AI_SITE_IMPORT_LOGO_MAX_BYTES = 1024 * 1024;
|
|
54
|
+
/** One read's own clock in the look pass. */ const LOOK_READ_MS = 8000;
|
|
55
|
+
const sameHost = (a, b)=>{
|
|
56
|
+
try {
|
|
57
|
+
return new URL(a).host === new URL(b).host;
|
|
58
|
+
} catch (unused) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* The home page and its first stylesheets, read as AglynBot (AGL-4084), the
|
|
64
|
+
* edge headers going with the page and with a stylesheet only on the page's
|
|
65
|
+
* own host. `null` when the page cannot be read.
|
|
66
|
+
*/ export async function readSiteImportLookPage(url, input) {
|
|
67
|
+
var _input_fetch;
|
|
68
|
+
const fetchText = (_input_fetch = input.fetch) != null ? _input_fetch : fetchPublicTextDetailed;
|
|
69
|
+
const headers = input.headers && Object.keys(input.headers).length ? input.headers : null;
|
|
70
|
+
const read = (address, maxBytes, accept, own)=>fetchText(address, _extends({
|
|
71
|
+
maxBytes,
|
|
72
|
+
accept,
|
|
73
|
+
userAgent: AGLYN_BOT_USER_AGENT,
|
|
74
|
+
timeoutMs: LOOK_READ_MS
|
|
75
|
+
}, input.signal ? {
|
|
76
|
+
signal: input.signal
|
|
77
|
+
} : {}, headers && own ? {
|
|
78
|
+
headers
|
|
79
|
+
} : {})).catch(()=>null);
|
|
80
|
+
const page = await read(url, AI_THEME_REFERENCE_MAX_BYTES, /text\/html|application\/xhtml\+xml/i, true);
|
|
81
|
+
if (!(page == null ? void 0 : page.ok)) return null;
|
|
82
|
+
const stylesheets = [];
|
|
83
|
+
for (const sheet of stylesheetUrlsOf(page.text, page.url).slice(0, AI_THEME_REFERENCE_STYLESHEETS)){
|
|
84
|
+
const css = await read(sheet, AI_THEME_STYLESHEET_MAX_BYTES, /text\/css/i, sameHost(sheet, page.url));
|
|
85
|
+
if (css == null ? void 0 : css.ok) stylesheets.push(css.text);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
url: page.url,
|
|
89
|
+
html: page.text,
|
|
90
|
+
stylesheets
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
const SOLID_HEX = /(?<!&)#(?:[0-9a-f]{6}|[0-9a-f]{3})(?![0-9a-z])/gi;
|
|
94
|
+
const SOLID_RGB = /rgba?\(\s*(\d{1,3})\s*[,\s]\s*(\d{1,3})\s*[,\s]\s*(\d{1,3})\s*(?:[,/]\s*([\d.]+%?)\s*)?\)/gi;
|
|
95
|
+
const THEME_COLOR = /<meta\b[^>]*\bname\s*=\s*["']?theme-color\b[^>]*\bcontent\s*=\s*["']?\s*(#[0-9a-f]{3,6})/gi;
|
|
96
|
+
const hexOf = (value)=>{
|
|
97
|
+
const raw = value.trim().toLowerCase();
|
|
98
|
+
if (/^#[0-9a-f]{6}$/.test(raw)) return raw;
|
|
99
|
+
if (/^#[0-9a-f]{3}$/.test(raw)) return `#${raw[1]}${raw[1]}${raw[2]}${raw[2]}${raw[3]}${raw[3]}`;
|
|
100
|
+
return null;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* A source's brand colors as it DRAWS them (AGL-4084): the colors its markup
|
|
104
|
+
* and stylesheets use solid, most used first, the colorful before the greys.
|
|
105
|
+
* A translucent color is a tint of one (a hover, a focus ring), never a use
|
|
106
|
+
* of it, so it is not counted: aglyn.com's focus rings, a light blue at a
|
|
107
|
+
* quarter strength, outnumbered the deep blue its buttons and links are
|
|
108
|
+
* drawn in, which the theme reader would otherwise take as its brand.
|
|
109
|
+
*/ export function aiSiteImportBrandColors(page) {
|
|
110
|
+
var _match_;
|
|
111
|
+
const weights = new Map();
|
|
112
|
+
const add = (hex, weight = 1)=>{
|
|
113
|
+
var _weights_get;
|
|
114
|
+
if (hex) weights.set(hex, ((_weights_get = weights.get(hex)) != null ? _weights_get : 0) + weight);
|
|
115
|
+
};
|
|
116
|
+
for (const match of page.html.matchAll(THEME_COLOR))add(hexOf((_match_ = match[1]) != null ? _match_ : ''), AI_THEME_THEME_COLOR_WEIGHT);
|
|
117
|
+
for (const text of [
|
|
118
|
+
page.html,
|
|
119
|
+
...page.stylesheets
|
|
120
|
+
]){
|
|
121
|
+
for (const match of text.matchAll(SOLID_HEX))add(hexOf(match[0]));
|
|
122
|
+
for (const match of text.matchAll(SOLID_RGB)){
|
|
123
|
+
const alpha = match[4];
|
|
124
|
+
if (alpha !== undefined && (alpha.endsWith('%') ? Number(alpha.slice(0, -1)) < 100 : Number(alpha) < 1)) continue;
|
|
125
|
+
const channels = [
|
|
126
|
+
match[1],
|
|
127
|
+
match[2],
|
|
128
|
+
match[3]
|
|
129
|
+
].map((value)=>Math.min(255, Number(value)));
|
|
130
|
+
add(`#${channels.map((value)=>value.toString(16).padStart(2, '0')).join('')}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return rankBrandColors(weights, AI_THEME_BRAND_COLORS_PER_SOURCE);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The look a source's signals pin (AGL-4084), drawn the way a guided start's
|
|
137
|
+
* is: the kind of site its own description says, the import's seed, its
|
|
138
|
+
* strongest color as the brand. `null` where the source showed no color to
|
|
139
|
+
* carry a brand, so the site keeps the theme it has.
|
|
140
|
+
*/ export function aiSiteImportStyle(site, input) {
|
|
141
|
+
var _input_avoid;
|
|
142
|
+
if (!site) return null;
|
|
143
|
+
const kind = aiSiteKindFor(input.description);
|
|
144
|
+
const pins = aiSiteInspirationLookPins({
|
|
145
|
+
v: 1,
|
|
146
|
+
sites: [
|
|
147
|
+
site
|
|
148
|
+
],
|
|
149
|
+
unread: 0
|
|
150
|
+
}, {
|
|
151
|
+
kind,
|
|
152
|
+
seed: input.seed
|
|
153
|
+
});
|
|
154
|
+
if (!pins.brand) return null;
|
|
155
|
+
return aiSiteStyleFor({
|
|
156
|
+
kind,
|
|
157
|
+
answer: {},
|
|
158
|
+
seed: input.seed,
|
|
159
|
+
brand: pins.brand,
|
|
160
|
+
avoid: (_input_avoid = input.avoid) != null ? _input_avoid : [],
|
|
161
|
+
pins
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* The site's listing in the home page's own words (AGL-4084), as field
|
|
166
|
+
* paths: its title and description each only where the site still has the
|
|
167
|
+
* one it was created with, so an owner's own listing is never written over.
|
|
168
|
+
* `{}` with nothing to write.
|
|
169
|
+
*/ export function aiSiteImportListingUpdates(seo, displayName, home) {
|
|
170
|
+
const seeded = aiSiteListingSeeded(seo, displayName);
|
|
171
|
+
const current = seo && typeof seo === 'object' ? seo : {};
|
|
172
|
+
const updates = {};
|
|
173
|
+
const title = home.title.replace(/\s+/g, ' ').trim();
|
|
174
|
+
const description = home.description.replace(/\s+/g, ' ').trim();
|
|
175
|
+
if (title && seeded.title && title !== current['title']) updates['seo.title'] = title;
|
|
176
|
+
if (description && seeded.description && description !== current['description']) updates['seo.description'] = description;
|
|
177
|
+
return updates;
|
|
178
|
+
}
|
|
179
|
+
/** Whether the site's share picture is still open: none, or the starter's (AGL-3988). */ function shareOpen(seo) {
|
|
180
|
+
const image = seo == null ? void 0 : seo.image;
|
|
181
|
+
if (image === undefined || image === null) return true;
|
|
182
|
+
return typeof image === 'string' && (!image.trim() || aiSiteIsStarterSrc(image));
|
|
183
|
+
}
|
|
184
|
+
async function readHostDoc(firestore, hostId) {
|
|
185
|
+
var _snapshot_data;
|
|
186
|
+
const snapshot = await firestore.collection('hosts').doc(hostId).get();
|
|
187
|
+
return snapshot.exists ? (_snapshot_data = snapshot.data()) != null ? _snapshot_data : {} : null;
|
|
188
|
+
}
|
|
189
|
+
async function writeHostDoc(firestore, hostId, updates) {
|
|
190
|
+
await firestore.collection('hosts').doc(hostId).update(updates);
|
|
191
|
+
}
|
|
192
|
+
/** Turns an SVG into a PNG in this process, where `sharp` is shipped; `null` where it is not. */ async function rasterizeHere(svg, height) {
|
|
193
|
+
const images = await import("./ai-image-reads.js");
|
|
194
|
+
const origin = images.aiImageReadsOrigin();
|
|
195
|
+
if (origin) return images.requestAiLogoRaster(origin, svg, height);
|
|
196
|
+
return images.rasterizeAiLogoSvg(svg, height);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* The source header's logo in the site's library (AGL-4084), once: the copy
|
|
200
|
+
* a page already brought in is reused by its key. An SVG becomes a PNG.
|
|
201
|
+
* `null` where it cannot be brought in, and the header then prints the name.
|
|
202
|
+
*/ export async function aiSiteImportLogo(input, deps) {
|
|
203
|
+
const ingest = deps.ingest;
|
|
204
|
+
const url = siteImportLogoUrl(input.logo.url);
|
|
205
|
+
if (!(ingest == null ? void 0 : ingest.findImportedPhoto) || !url) return null;
|
|
206
|
+
const sourceKey = siteImportImageKey(url);
|
|
207
|
+
try {
|
|
208
|
+
var _deps_fetch;
|
|
209
|
+
var _fetched_data;
|
|
210
|
+
const held = await ingest.findImportedPhoto({
|
|
211
|
+
hostId: input.hostId,
|
|
212
|
+
sourceKey
|
|
213
|
+
});
|
|
214
|
+
if (held) return _extends({
|
|
215
|
+
mediaId: held.mediaId,
|
|
216
|
+
src: held.src,
|
|
217
|
+
alt: input.alt,
|
|
218
|
+
from: url
|
|
219
|
+
}, held.width ? {
|
|
220
|
+
width: held.width
|
|
221
|
+
} : {}, held.height ? {
|
|
222
|
+
height: held.height
|
|
223
|
+
} : {});
|
|
224
|
+
const fetched = await ((_deps_fetch = deps.fetch) != null ? _deps_fetch : fetchPublicTextDetailed)(url, _extends({
|
|
225
|
+
maxBytes: AI_SITE_IMPORT_LOGO_MAX_BYTES,
|
|
226
|
+
accept: /^image\//i,
|
|
227
|
+
acceptHeader: 'image/svg+xml,image/png,image/webp,image/jpeg;q=0.9',
|
|
228
|
+
userAgent: AGLYN_BOT_USER_AGENT,
|
|
229
|
+
timeoutMs: LOOK_READ_MS,
|
|
230
|
+
refuseOver: true,
|
|
231
|
+
binary: true
|
|
232
|
+
}, input.signal ? {
|
|
233
|
+
signal: input.signal
|
|
234
|
+
} : {}, input.headers && sameHost(url, `https://${input.domain}/`) ? {
|
|
235
|
+
headers: input.headers
|
|
236
|
+
} : {}));
|
|
237
|
+
if (!fetched.ok || !((_fetched_data = fetched.data) == null ? void 0 : _fetched_data.byteLength)) return null;
|
|
238
|
+
const type = fetched.contentType.split(';')[0].trim().toLowerCase();
|
|
239
|
+
let bytes = fetched.data;
|
|
240
|
+
let contentType = type;
|
|
241
|
+
if (type === 'image/svg+xml' || input.logo.type === 'svg') {
|
|
242
|
+
var _deps_rasterize;
|
|
243
|
+
bytes = await ((_deps_rasterize = deps.rasterize) != null ? _deps_rasterize : rasterizeHere)(fetched.data, AI_SITE_IMPORT_LOGO_HEIGHT).catch(()=>null);
|
|
244
|
+
contentType = 'image/png';
|
|
245
|
+
} else if (!/^image\/(?:png|jpeg|webp|gif)$/.test(type)) {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
if (!(bytes == null ? void 0 : bytes.byteLength)) return null;
|
|
249
|
+
const stored = await ingest.ingest(_extends({
|
|
250
|
+
hostId: input.hostId,
|
|
251
|
+
uid: input.uid,
|
|
252
|
+
fileName: `logo.${contentType === 'image/jpeg' ? 'jpg' : contentType.split('/')[1]}`,
|
|
253
|
+
contentType,
|
|
254
|
+
bytes
|
|
255
|
+
}, input.alt ? {
|
|
256
|
+
alt: input.alt.slice(0, 250)
|
|
257
|
+
} : {}, {
|
|
258
|
+
importedFrom: {
|
|
259
|
+
key: sourceKey,
|
|
260
|
+
url,
|
|
261
|
+
domain: input.domain
|
|
262
|
+
}
|
|
263
|
+
}));
|
|
264
|
+
if (!stored.ok) return null;
|
|
265
|
+
return _extends({
|
|
266
|
+
mediaId: stored.mediaId,
|
|
267
|
+
src: stored.src,
|
|
268
|
+
alt: input.alt,
|
|
269
|
+
from: url
|
|
270
|
+
}, stored.width ? {
|
|
271
|
+
width: stored.width
|
|
272
|
+
} : {}, stored.height ? {
|
|
273
|
+
height: stored.height
|
|
274
|
+
} : {});
|
|
275
|
+
} catch (error) {
|
|
276
|
+
console.warn('[site-import] the logo could not be brought in', {
|
|
277
|
+
hostId: input.hostId,
|
|
278
|
+
error
|
|
279
|
+
});
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/** The look pass (AGL-4084): the theme, the logo and the listing, from the owner's own home page. No model. */ export async function aiRunSiteImportLook(input, deps = {}) {
|
|
284
|
+
var _deps_others, _deps_ingest, _deps_readHost, _ref;
|
|
285
|
+
const { firestore, hostId, home } = input;
|
|
286
|
+
let headers = null;
|
|
287
|
+
if (deps.headersFor) {
|
|
288
|
+
try {
|
|
289
|
+
headers = await deps.headersFor(input.domain);
|
|
290
|
+
} catch (error) {
|
|
291
|
+
console.warn('[site-import] the edge headers could not be read; reading the look without them', {
|
|
292
|
+
domain: input.domain,
|
|
293
|
+
error
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const seed = aiSiteSeed(input.jobId);
|
|
298
|
+
const page = await readSiteImportLookPage(home.url, _extends({}, deps.fetch ? {
|
|
299
|
+
fetch: deps.fetch
|
|
300
|
+
} : {}, {
|
|
301
|
+
headers
|
|
302
|
+
}, input.signal ? {
|
|
303
|
+
signal: input.signal
|
|
304
|
+
} : {}));
|
|
305
|
+
// The source's own signals, its brand colors as it draws them solid.
|
|
306
|
+
const signals = page ? aiSiteInspirationSignals(page) : null;
|
|
307
|
+
const site = signals && page ? _extends({}, signals, {
|
|
308
|
+
colors: aiSiteImportBrandColors(page)
|
|
309
|
+
}) : signals;
|
|
310
|
+
const avoid = await ((_deps_others = deps.others) != null ? _deps_others : aiWorkspaceSiteStyles)(firestore, {
|
|
311
|
+
orgId: input.orgId,
|
|
312
|
+
hostId
|
|
313
|
+
});
|
|
314
|
+
const style = aiSiteImportStyle(site, {
|
|
315
|
+
seed,
|
|
316
|
+
description: `${home.siteName} ${home.description}`,
|
|
317
|
+
avoid
|
|
318
|
+
});
|
|
319
|
+
let theme = 'unread';
|
|
320
|
+
if (style) {
|
|
321
|
+
var _deps_save;
|
|
322
|
+
const saved = await ((_deps_save = deps.save) != null ? _deps_save : aiSaveSiteTheme)(firestore, {
|
|
323
|
+
hostId,
|
|
324
|
+
jobId: input.jobId,
|
|
325
|
+
style
|
|
326
|
+
});
|
|
327
|
+
if (saved.write === 'missing') throw new Error(`site ${hostId} vanished while its look was brought in`);
|
|
328
|
+
theme = saved.write === 'kept' ? 'kept' : 'applied';
|
|
329
|
+
}
|
|
330
|
+
const ingest = (_deps_ingest = deps.ingest) != null ? _deps_ingest : null;
|
|
331
|
+
const logo = home.brand.logo ? await aiSiteImportLogo(_extends({
|
|
332
|
+
hostId,
|
|
333
|
+
uid: input.uid,
|
|
334
|
+
domain: input.domain,
|
|
335
|
+
logo: home.brand.logo,
|
|
336
|
+
alt: home.siteName || home.brand.text,
|
|
337
|
+
headers
|
|
338
|
+
}, input.signal ? {
|
|
339
|
+
signal: input.signal
|
|
340
|
+
} : {}), _extends({
|
|
341
|
+
ingest
|
|
342
|
+
}, deps.fetch ? {
|
|
343
|
+
fetch: deps.fetch
|
|
344
|
+
} : {}, deps.rasterize ? {
|
|
345
|
+
rasterize: deps.rasterize
|
|
346
|
+
} : {})) : null;
|
|
347
|
+
// The site's listing in its own words, only over the one it was created with.
|
|
348
|
+
let seo = false;
|
|
349
|
+
const host = await ((_deps_readHost = deps.readHost) != null ? _deps_readHost : readHostDoc)(firestore, hostId);
|
|
350
|
+
if (host) {
|
|
351
|
+
const updates = aiSiteImportListingUpdates(host['seo'], host['displayName'], home);
|
|
352
|
+
if (home.ogImage && shareOpen(host['seo'])) {
|
|
353
|
+
const share = await bringInSiteImportPhotos(_extends({
|
|
354
|
+
hostId,
|
|
355
|
+
uid: input.uid,
|
|
356
|
+
domain: input.domain,
|
|
357
|
+
images: [
|
|
358
|
+
{
|
|
359
|
+
url: home.ogImage,
|
|
360
|
+
alt: ''
|
|
361
|
+
}
|
|
362
|
+
],
|
|
363
|
+
deadlineAt: Date.now() + LOOK_READ_MS
|
|
364
|
+
}, input.signal ? {
|
|
365
|
+
signal: input.signal
|
|
366
|
+
} : {}), _extends({
|
|
367
|
+
ingest
|
|
368
|
+
}, deps.fetch ? {
|
|
369
|
+
fetch: deps.fetch
|
|
370
|
+
} : {}));
|
|
371
|
+
const photo = share.assets[0];
|
|
372
|
+
if (photo) {
|
|
373
|
+
updates['seo.image'] = photo.src;
|
|
374
|
+
if (photo.width) updates['seo.imageWidth'] = photo.width;
|
|
375
|
+
if (photo.height) updates['seo.imageHeight'] = photo.height;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (Object.keys(updates).length) {
|
|
379
|
+
var _deps_writeHost;
|
|
380
|
+
await ((_deps_writeHost = deps.writeHost) != null ? _deps_writeHost : writeHostDoc)(firestore, hostId, updates);
|
|
381
|
+
seo = true;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const drawn = style ? _extends({}, typeof style.headerAlign === 'string' ? {
|
|
385
|
+
headerAlign: style.headerAlign
|
|
386
|
+
} : {}, typeof style.rhythm === 'string' ? {
|
|
387
|
+
rhythm: style.rhythm
|
|
388
|
+
} : {}, typeof style.motion === 'string' ? {
|
|
389
|
+
motion: style.motion
|
|
390
|
+
} : {}) : null;
|
|
391
|
+
return {
|
|
392
|
+
at: input.at,
|
|
393
|
+
theme,
|
|
394
|
+
colors: (_ref = site == null ? void 0 : site.colors) != null ? _ref : [],
|
|
395
|
+
logo,
|
|
396
|
+
seo,
|
|
397
|
+
seed: style ? style.seed : seed,
|
|
398
|
+
style: drawn
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
//# sourceMappingURL=ai-site-import-look.js.map
|