@aglyn/plugins-ai 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
- package/src/lib/components/ai-site-start-card.component.js +40 -13
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-site-start-steps.component.d.ts +17 -1
- package/src/lib/components/ai-site-start-steps.component.js +30 -7
- package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +8 -12
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +3 -0
- package/src/lib/jobs/ai-job-drafts.js +5 -0
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +10 -0
- package/src/lib/jobs/ai-job-layout-language.js +38 -2
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +3 -3
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-site-import-compile.js +15 -7
- package/src/lib/jobs/ai-site-import-compile.js.map +1 -1
- package/src/lib/layout-language/ai-layout-compiler.js +39 -1
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.d.ts +6 -0
- package/src/lib/layout-language/ai-layout-frame.js +85 -30
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.d.ts +5 -0
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/model/ai-site-import-fidelity.d.ts +175 -0
- package/src/lib/model/ai-site-import-fidelity.js +941 -0
- package/src/lib/model/ai-site-import-fidelity.js.map +1 -0
- package/src/lib/model/ai-site-start.d.ts +27 -0
- package/src/lib/model/ai-site-start.js +41 -0
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/model/assist-page-change.d.ts +26 -6
- package/src/lib/model/assist-page-change.js +32 -10
- package/src/lib/model/assist-page-change.js.map +1 -1
- package/src/lib/runtime/ai-node-tree.js +18 -2
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/server/assist-chat.d.ts +5 -0
- package/src/lib/server/assist-chat.js +11 -2
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-page-change.d.ts +12 -0
- package/src/lib/server/assist-page-change.js +23 -1
- package/src/lib/server/assist-page-change.js.map +1 -1
- package/src/lib/site-import/fixtures/site-import-fidelity-replay.d.ts +62 -0
- package/src/lib/site-import/fixtures/site-import-fidelity-replay.js +374 -0
- package/src/lib/site-import/fixtures/site-import-fidelity-replay.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-import-fidelity.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, SCORED AGAINST ITS SOURCE (AGL-4109).\n *\n * On 10/29 an import of aglyn.com shipped with every section flattened to one\n * column of lines and a header of page titles, and nothing measured it: the\n * specs checked that words came through, never how the page was laid out.\n * This scores a built page against what the reader read of its source\n * (`SiteImportLayout`, AGL-4106), with no model and no browser, over the\n * page's nodes as the compiler draws them or as Firestore stores them:\n *\n * - **The words**: every line of the source exactly once, as written, and\n * none of the page's own. A score of words is 1 or the page is wrong.\n * - **The structure**: its sections and their bands; its rows of columns\n * side by side; its groups by kind, each item its own element with its\n * parts (an FAQ's question on an accordion, a card's link on the card);\n * its tables, tab strips, pictures, icons, buttons as the source draws\n * them, headings and forms.\n * - **The frame** (`siteImportFrameFidelity`): the header's top-level items\n * in order, each menu's entries with their lines and icons, the buttons,\n * the phone menu holding all of it, labels on one line; the footer's\n * columns, links, profiles and facts.\n *\n * Every measure is anchored to the source's own words, never to how the\n * compiler names its elements: each line of the source is found on the\n * built page, and the structure is asked of the elements holding them (are\n * a group's thirteen items thirteen elements, or one stack of lines?). So\n * the same score reads a page compiled in a spec and a page stored on a live\n * site. The score is the lowest ratio of the structure; words are held apart\n * at exactly 1.\n *=========================================*/\n\nimport type { SiteImportBlock } from '../site-import/site-import-html'\nimport type {\n SiteImportBand,\n SiteImportButtonStyle,\n SiteImportFooter,\n SiteImportGroupKind,\n SiteImportLayout,\n SiteImportLayoutItem,\n SiteImportLayoutNode,\n SiteImportLayoutSection,\n SiteImportLineRef,\n SiteImportNavTree,\n} from './ai-site-import'\n\n/** The lowest score of structure a page brought in ships at (AGL-4109); words are held at exactly 1. */\nexport const SITE_IMPORT_FIDELITY_THRESHOLD = 0.9\n\n/** A node of a page or a layout, as the compiler draws it or Firestore stores it, decoded. */\nexport interface SiteImportFidelityNode {\n componentId?: string\n props?: Record<string, unknown> | null\n sx?: Record<string, unknown> | null\n nodes?: readonly string[] | null\n parentId?: string | null\n interactions?: readonly unknown[] | null\n}\n\n/** A built page or layout: its nodes and the one they hang from. */\nexport interface SiteImportFidelityTree {\n rootId: string\n nodes: Readonly<Record<string, SiteImportFidelityNode>>\n}\n\n/** What the source had, what the built page kept of it, and the share kept. */\nexport interface SiteImportFidelityCount {\n source: number\n built: number\n ratio: number\n}\n\n/** The words of a page: every one of the source's, as written, exactly once, and none of its own. */\nexport interface SiteImportFidelityWords {\n /** Words the source's lines hold. */\n source: number\n /** Words the built page shows. */\n built: number\n /** The source's words the page does not show (counted as often as they are missing). */\n missing: number\n /** Words the page shows that are not the source's, or are shown more often than the source says them. */\n extra: number\n /** Words shown once each, over the larger of the two. */\n ratio: number\n /** A few of the missing and extra words, for a report. */\n examples: { missing: string[]; extra: string[] }\n}\n\nexport interface SiteImportFidelityGroup {\n /** Groups of the kind kept whole: every item of them its own element, with its parts. */\n groups: SiteImportFidelityCount\n /** Items of the kind kept, each its own element with its parts. */\n items: SiteImportFidelityCount\n}\n\n/** A page brought in, scored against its source. */\nexport interface SiteImportPageFidelity {\n /** The lowest ratio of the structure: what the page ships on, at `SITE_IMPORT_FIDELITY_THRESHOLD` or above. */\n score: number\n /** The measure that set the score; `null` when every measure is whole. */\n lowest: string | null\n words: SiteImportFidelityWords\n sections: SiteImportFidelityCount\n /** Sections drawn in the source's band, in order. */\n bands: SiteImportFidelityCount & { read: SiteImportBand[]; drawn: SiteImportBand[] }\n /** The source's rows of two or more columns, set side by side on the page. */\n rows: SiteImportFidelityCount\n /** The source's groups by kind. A group of lines that only share a shape (`text`) is drawn as its lines, and is not scored. */\n groups: Partial<Record<SiteImportGroupKind, SiteImportFidelityGroup>>\n tables: SiteImportFidelityCount\n tabs: SiteImportFidelityCount\n /** The source's pictures placed on the page. */\n pictures: SiteImportFidelityCount\n /**\n * The source's icons drawn: each group item's own, each bullet's mark. An\n * icon standing alone in the source (a video's play mark) is no word and no\n * picture of the page, and the build leaves it out (AGL-4107): it is\n * counted in `lone` and not scored.\n */\n icons: SiteImportFidelityCount & { lone: number }\n /**\n * The lines the source draws as buttons that go somewhere (a page, an\n * address, an email, a phone), drawn as buttons of the same style with\n * their destinations. A button that goes nowhere a page can send a visitor\n * (a film's play button its script runs) is kept as its words: counted in\n * `unlinked`, not scored.\n */\n buttons: SiteImportFidelityCount & { unlinked: number }\n /** The source's headings, drawn as headings. */\n headings: SiteImportFidelityCount\n /** The source's forms, where the site has a form a page places. */\n forms: SiteImportFidelityCount\n}\n\n/** A page's source as the import read it: its layout over its main-region blocks, and its photos as the library holds them. */\nexport interface SiteImportFidelitySource {\n layout: SiteImportLayout\n /** The page's main-region blocks, which the layout's lines are indexes of. */\n blocks: readonly SiteImportBlock[]\n /** The source's pictures as the site's library holds them (`from` → `src`); without them a picture is counted, not placed. */\n photos?: ReadonlyArray<{ from?: string; src: string }>\n}\n\n/*------------------------------------------\n * Words and lines\n *-----------------------------------------*/\n\nconst clean = (text: unknown): string => (typeof text === 'string' ? text.replace(/\\s+/g, ' ').trim() : '')\n\nconst WORD = /[\\p{L}\\p{N}]+/gu\n\n/** A line's words, in order. */\nconst wordsOf = (text: string): string[] => text.match(WORD) ?? []\n\n/**\n * A line's words as one string, for comparing two lines whatever their\n * spacing and marks; a line of marks alone (a table's \"✓\", a \"—\") as it is.\n */\nconst wordLine = (text: string): string => wordsOf(text).join(' ') || clean(text)\n\n/** Whether a block is a line of the page: a link inside a sentence is in its sentence. */\nfunction isLine(block: SiteImportBlock | undefined): boolean {\n if (!block) return false\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/** A cell of the Table element's grid, its pipes unescaped. */\nfunction tableCells(rows: string): string[] {\n return rows\n .split('\\n')\n .flatMap((line) =>\n line\n .replace(/^\\s*\\|/, '')\n .replace(/\\|\\s*$/, '')\n .split(/(?<!\\\\)\\|/)\n .map((cell) => clean(cell.replace(/\\\\\\|/g, '|'))),\n )\n .filter(Boolean)\n}\n\n/** The words a node shows a visitor: its text, its list lines, its tab labels, its table's cells. */\nfunction shownTexts(node: SiteImportFidelityNode | undefined): string[] {\n if (!node) return []\n const props = node.props ?? {}\n const out: string[] = []\n for (const key of ['children', 'primary', 'secondary']) {\n const value = clean(props[key])\n if (value) out.push(value)\n }\n if (node.componentId === 'muiTabs' && typeof props['labels'] === 'string') out.push(...props['labels'].split('\\n').map(clean).filter(Boolean))\n if (node.componentId === 'dataTable' && typeof props['rows'] === 'string') out.push(...tableCells(props['rows']))\n return out\n}\n\n/** Where a node sends a visitor: a page, an address, a section of the page. */\nfunction hasDestination(node: SiteImportFidelityNode | undefined): boolean {\n if (!node) return false\n const props = node.props ?? {}\n return !!clean(props['screenId']) || !!clean(props['href']) || !!clean(props['to']) || (node.interactions?.length ?? 0) > 0\n}\n\nconst BUTTON_VARIANT: Readonly<Record<SiteImportButtonStyle, string>> = { primary: 'contained', secondary: 'outlined', quiet: 'text' }\n\n/** A section's band as the compiler draws it: a dark scheme, the paper ground, the brand fill, else plain. */\nexport function siteImportFidelityBandOf(node: SiteImportFidelityNode | undefined): SiteImportBand {\n if (node?.props?.['colorScheme'] === 'dark') return 'dark'\n const ground = node?.sx?.['bgcolor']\n if (ground === 'background.paper') return 'soft'\n if (ground === 'primary.main') return 'brand'\n return 'plain'\n}\n\n/** The node a stored page or layout hangs from: the canvas root, else the one with no parent. */\nexport function siteImportFidelityTreeOf(nodes: Readonly<Record<string, SiteImportFidelityNode>>): SiteImportFidelityTree {\n const ids = Object.keys(nodes)\n const rootId = nodes['_@_'] ? '_@_' : (ids.find((id) => !nodes[id]?.parentId) ?? ids[0] ?? '_@_')\n return { rootId, nodes }\n}\n\n/*------------------------------------------\n * The built page, walked once\n *-----------------------------------------*/\n\ninterface Piece {\n node: string\n text: string\n /** Its words, joined: what lines are compared by. */\n key: string\n at: number\n}\n\n/** A built tree in document order: each node's span, its parent, and the words each shows. */\nclass Built {\n readonly order: string[] = []\n private readonly enter = new Map<string, number>()\n private readonly exit = new Map<string, number>()\n readonly parent = new Map<string, string>()\n readonly pieces: Piece[] = []\n\n constructor(readonly tree: SiteImportFidelityTree) {\n const seen = new Set<string>()\n const visit = (id: string, parent: string | null) => {\n if (seen.has(id) || !tree.nodes[id]) return\n seen.add(id)\n if (parent) this.parent.set(id, parent)\n const at = this.order.length\n this.enter.set(id, at)\n this.order.push(id)\n for (const text of shownTexts(tree.nodes[id])) this.pieces.push({ node: id, text, key: wordLine(text), at })\n for (const child of tree.nodes[id]?.nodes ?? []) visit(child, id)\n this.exit.set(id, this.order.length - 1)\n }\n visit(tree.rootId, null)\n }\n\n node(id: string): SiteImportFidelityNode | undefined {\n return this.tree.nodes[id]\n }\n\n /** Whether `a` is `b` or holds it. */\n holds(a: string, b: string): boolean {\n const start = this.enter.get(a)\n const end = this.exit.get(a)\n const point = this.enter.get(b)\n return start !== undefined && end !== undefined && point !== undefined && point >= start && point <= end\n }\n\n /** The nodes under one, itself included, in document order. */\n under(id: string): string[] {\n const start = this.enter.get(id)\n const end = this.exit.get(id)\n return start === undefined || end === undefined ? [] : this.order.slice(start, end + 1)\n }\n\n children(id: string): string[] {\n return (this.tree.nodes[id]?.nodes ?? []).filter((child) => this.enter.has(child))\n }\n\n /** The deepest node holding every one of `ids`. */\n common(ids: readonly string[]): string | null {\n if (!ids.length) return null\n for (let at: string | undefined = ids[0]; at; at = this.parent.get(at)) {\n if (ids.every((id) => this.holds(at as string, id))) return at\n }\n return null\n }\n\n /** Whether any node under `id` (itself included) is one `test` takes. */\n has(id: string, test: (node: SiteImportFidelityNode) => boolean): boolean {\n return this.under(id).some((at) => {\n const node = this.tree.nodes[at]\n return !!node && test(node)\n })\n }\n}\n\nconst ratioKept = (source: number, built: number): SiteImportFidelityCount => ({ source, built, ratio: source > 0 ? Math.min(1, built / source) : 1 })\n\nconst ratioBoth = (source: number, built: number): SiteImportFidelityCount => ({\n source,\n built,\n ratio: Math.max(source, built) > 0 ? Math.min(source, built) / Math.max(source, built) : 1,\n})\n\n/*------------------------------------------\n * The page\n *-----------------------------------------*/\n\n/**\n * Each line of the source found on the built page (AGL-4109): the element\n * showing its words, or the picture the library copy of it is. A line is\n * looked for in its own section, nearest after the line before it; an\n * element holds one line, or the run of lines a build joins into one (a\n * list item's words, a quote's).\n */\nclass Placement {\n /** Line → the node showing it. */\n readonly of = new Map<SiteImportLineRef, string>()\n private readonly claimedBy = new Map<Piece, SiteImportLineRef>()\n private readonly imageClaimed = new Set<string>()\n\n constructor(\n private readonly built: Built,\n private readonly source: SiteImportFidelitySource,\n sectionRoots: ReadonlyArray<string | null>,\n ) {\n const photoOf = new Map<string, string>()\n for (const photo of source.photos ?? []) if (photo.from) photoOf.set(photo.from, photo.src)\n source.layout.sections.forEach((section, index) => {\n const root = sectionRoots[index] ?? built.tree.rootId\n const scope = new Set(built.under(root))\n const pieces = built.pieces.filter((piece) => scope.has(piece.node))\n const images = built.under(root).filter((id) => built.node(id)?.componentId === 'image')\n let cursor = -1\n let previous: SiteImportLineRef | null = null\n for (const ref of section.lines) {\n const block = source.blocks[ref]\n if (!isLine(block)) continue\n if (block.kind === 'image') {\n const src = photoOf.get(block.url)\n const found = src ? images.find((id) => !this.imageClaimed.has(id) && built.node(id)?.props?.['src'] === src) : undefined\n if (found) {\n this.imageClaimed.add(found)\n this.of.set(ref, found)\n }\n previous = ref\n continue\n }\n const piece = this.find(pieces, clean(block.text), cursor, previous)\n if (piece) {\n this.claimedBy.set(piece, ref)\n this.of.set(ref, piece.node)\n cursor = piece.at\n }\n previous = ref\n }\n })\n }\n\n /** The piece showing a line: its words exactly, else a part of them (a long paragraph split), else the run it was joined into. */\n private find(pieces: readonly Piece[], text: string, cursor: number, previous: SiteImportLineRef | null): Piece | null {\n const key = wordLine(text)\n if (!key) return null\n const free = (piece: Piece) => {\n const owner = this.claimedBy.get(piece)\n // A piece holds one line, or the lines a build joined: the one before this one.\n return owner === undefined || owner === previous\n }\n // The nearest to the line placed before it: a build keeps a section's lines in their order, near enough.\n const pick = (list: Piece[]) =>\n list.reduce<Piece | null>((best, piece) => {\n if (!best) return piece\n const gap = Math.abs(piece.at - cursor)\n const bestGap = Math.abs(best.at - cursor)\n return gap < bestGap || (gap === bestGap && piece.at >= cursor && best.at < cursor) ? piece : best\n }, null)\n const exact = pieces.filter((piece) => piece.key === key && this.claimedBy.get(piece) === undefined)\n if (exact.length) return pick(exact)\n const padded = ` ${key} `\n const part = pieces.filter((piece) => free(piece) && piece.key && (` ${piece.key} `.includes(padded) || padded.includes(` ${piece.key} `)))\n return pick(part)\n }\n\n /** The nodes a list of lines is shown by, and whether every one of them was found. */\n nodesOf(refs: readonly SiteImportLineRef[]): { nodes: string[]; whole: boolean; measured: boolean } {\n const nodes: string[] = []\n let whole = true\n let measured = false\n const pictures = !!this.source.photos?.length\n for (const ref of refs) {\n const block = this.source.blocks[ref]\n if (!isLine(block)) continue\n if (block.kind === 'image' && !pictures) continue\n measured = true\n const node = this.of.get(ref)\n if (node) nodes.push(node)\n else whole = false\n }\n return { nodes, whole, measured }\n }\n}\n\n/** One part of the source's structure (an item, a cell), found on the built page. */\ninterface Unit {\n nodes: string[]\n whole: boolean\n measured: boolean\n /** The element holding all of it and nothing of the parts beside it. */\n element: string | null\n}\n\n/**\n * Units found on the page, each as the element of its own (AGL-4109): the\n * highest element holding its lines and none of the other units' lines. A\n * unit whose lines are not held apart from another's (thirteen cards' titles\n * in one stack under their thirteen lines) has no element of its own.\n */\nfunction unitsOn(built: Built, placement: Placement, parts: ReadonlyArray<readonly SiteImportLineRef[]>, stop: string | null): Unit[] {\n const found = parts.map((refs) => placement.nodesOf(refs))\n return found.map((part, index) => {\n const others = found.flatMap((other, at) => (at === index ? [] : other.nodes))\n const anchor = built.common(part.nodes)\n if (!anchor || others.some((node) => built.holds(anchor, node))) return { ...part, element: null }\n let element = anchor\n for (let up = built.parent.get(element); up && up !== stop && !others.some((node) => built.holds(up as string, node)); up = built.parent.get(up)) {\n element = up\n if (built.node(up)?.componentId === 'section') break\n }\n return { ...part, element }\n })\n}\n\n/** Whether a node lays its children side by side: a Grid container, or a Stack set in a row. */\nfunction setsSideBySide(node: SiteImportFidelityNode | undefined): boolean {\n if (!node) return false\n const props = node.props ?? {}\n if (node.componentId === 'muiGrid') return props['container'] === true || props['container'] === 'true'\n if (node.componentId === 'muiStack') return props['direction'] === 'row' || (typeof props['direction'] === 'string' && /\\b(?:sm|md|lg):row\\b/.test(props['direction']))\n return false\n}\n\n/**\n * Whether a row of the source is a byline: one cell a lone picture, the other\n * a few words (a writer's portrait beside their name and date), which the\n * build draws as the portrait, small, over its words (AGL-4107), not as two\n * columns across the page.\n */\nfunction isByline(source: SiteImportFidelitySource, cells: ReadonlyArray<{ nodes: SiteImportLayoutNode[] }>): boolean {\n if (cells.length !== 2) return false\n const at = cells.findIndex((cell) => cell.nodes.length === 1 && cell.nodes[0]?.kind === 'picture')\n if (at === -1) return false\n const words = cells[1 - at]?.nodes ?? []\n if (!words.every((node) => node.kind === 'line')) return false\n const count = words.reduce((total, node) => {\n const block = node.kind === 'line' ? source.blocks[node.line] : undefined\n return total + (block && block.kind !== 'image' ? wordsOf(clean(block.text)).length : 0)\n }, 0)\n return count > 0 && count <= 12\n}\n\n/** Every node of a section's layout, rows, cells and tab panels opened, in order. */\nfunction layoutNodes(nodes: readonly SiteImportLayoutNode[]): SiteImportLayoutNode[] {\n const out: SiteImportLayoutNode[] = []\n const visit = (list: readonly SiteImportLayoutNode[]) => {\n for (const node of list) {\n out.push(node)\n if (node.kind === 'row') for (const cell of node.cells) visit(cell.nodes)\n if (node.kind === 'tabs') for (const tab of node.tabs) visit(tab.panel)\n }\n }\n visit(nodes)\n return out\n}\n\n/** Every line a list of layout nodes places, rows, groups, tables and tabs opened. */\nfunction linesOf(nodes: readonly SiteImportLayoutNode[]): SiteImportLineRef[] {\n const out: SiteImportLineRef[] = []\n for (const node of layoutNodes(nodes)) {\n if (node.kind === 'line' || node.kind === 'picture') out.push(node.line)\n else if (node.kind === 'group') for (const item of node.items) out.push(...item.lines)\n else if (node.kind === 'table') for (const row of node.rows) for (const cell of row.cells) out.push(...cell.lines)\n else if (node.kind === 'tabs') for (const tab of node.tabs) if (tab.label !== null) out.push(tab.label)\n }\n return out\n}\n\n/**\n * A built page scored against its source (AGL-4109): the words, each once;\n * the sections and their bands; the rows of columns; the groups by kind, each\n * item an element of its own with its parts; the tables, tabs, pictures,\n * icons, buttons, headings and forms. The score is the lowest ratio of the\n * structure.\n */\nexport function siteImportFidelity(source: SiteImportFidelitySource, tree: SiteImportFidelityTree): SiteImportPageFidelity {\n const built = new Built(tree)\n const sections = source.layout.sections\n const drawnSections = built.children(tree.rootId)\n // The build draws each of the source's sections as one, in order; a page whose count differs is searched whole.\n const aligned = drawnSections.length === sections.length\n const roots = sections.map((_, index) => (aligned ? (drawnSections[index] ?? null) : null))\n const placement = new Placement(built, source, roots)\n\n const words = wordsScore(source, built)\n const read = sections.map((section) => section.band)\n const drawn = drawnSections.map((id) => siteImportFidelityBandOf(built.node(id)))\n const bandsKept = read.filter((band, index) => drawn[index] === band).length\n\n const rows = { source: 0, built: 0 }\n const groups: Partial<Record<SiteImportGroupKind, { groups: number; groupsKept: number; items: number; itemsKept: number }>> = {}\n const tables = { source: 0, built: 0 }\n const tabs = { source: 0, built: 0 }\n const buttons = { source: 0, built: 0, unlinked: 0 }\n const icons = { source: 0, built: 0, lone: 0 }\n\n sections.forEach((section, index) => {\n const root = roots[index] ?? null\n for (const node of layoutNodes(section.nodes)) {\n switch (node.kind) {\n case 'icon':\n icons.lone += 1\n break\n case 'line': {\n if (!node.button) break\n const block = source.blocks[node.line]\n // A button that goes nowhere a page can send a visitor (a film's play button its script runs) is kept as its words.\n if (block?.kind !== 'link' || !/^(?:https?:|mailto:|tel:)/i.test(block.href)) {\n buttons.unlinked += 1\n break\n }\n buttons.source += 1\n const id = placement.of.get(node.line)\n const drawnAs = id ? built.node(id) : undefined\n if (drawnAs?.componentId === 'muiButton' && drawnAs.props?.['variant'] === BUTTON_VARIANT[node.button] && hasDestination(drawnAs)) buttons.built += 1\n break\n }\n case 'row': {\n const cells = node.cells.filter((cell) => linesOf(cell.nodes).length)\n if (cells.length < 2 || isByline(source, cells)) break\n rows.source += 1\n const units = unitsOn(built, placement, cells.map((cell) => linesOf(cell.nodes)), root)\n const parents = new Set(units.map((unit) => (unit.element ? built.parent.get(unit.element) : null)))\n const parent = parents.size === 1 ? [...parents][0] : null\n if (units.every((unit) => unit.whole && unit.element) && parent && setsSideBySide(built.node(parent))) rows.built += 1\n break\n }\n case 'group': {\n const units = unitsOn(built, placement, node.items.map((item) => item.lines), root)\n // Each item's own icon and its bullets' marks, in the element that is the item.\n node.items.forEach((item, at) => {\n const wanted = (item.icon !== undefined ? 1 : 0) + (item.listIcon !== undefined ? (item.list?.length ?? 0) : 0)\n if (!wanted) return\n icons.source += wanted\n const element = units[at]?.element\n if (element) icons.built += Math.min(wanted, built.under(element).filter((id) => built.node(id)?.componentId === 'icon').length)\n })\n // Lines that only share a shape are drawn as their lines: no group to keep.\n if (node.group === 'text') break\n const entry = (groups[node.group] ??= { groups: 0, groupsKept: 0, items: 0, itemsKept: 0 })\n let kept = 0\n let measured = 0\n node.items.forEach((item, at) => {\n const unit = units[at] as Unit\n if (!unit.measured) return\n measured += 1\n if (itemKept(built, node.group, item, unit)) kept += 1\n })\n entry.groups += 1\n entry.items += measured\n entry.itemsKept += kept\n if (measured > 0 && kept === measured) entry.groupsKept += 1\n break\n }\n case 'table': {\n tables.source += 1\n const cells = node.rows.flatMap((row) => row.cells.map((cell) => wordLine(cell.lines.map((ref) => clean((source.blocks[ref] as { text?: string })?.text)).join(' '))).filter(Boolean))\n // The theme's Table element, every cell of the source's in it.\n const kept = built.under(root ?? tree.rootId).some((id) => {\n const drawnAs = built.node(id)\n if (drawnAs?.componentId !== 'dataTable' || typeof drawnAs.props?.['rows'] !== 'string') return false\n const shown = new Set(tableCells(drawnAs.props['rows']).map(wordLine))\n return cells.every((cell) => shown.has(cell))\n })\n if (kept) tables.built += 1\n break\n }\n case 'tabs': {\n tabs.source += 1\n const labels = node.tabs.flatMap((tab) => (tab.label !== null ? [tab.label] : []))\n const found = placement.nodesOf(labels)\n if (!found.whole || !found.nodes.length) break\n // The Tabs element names every tab itself; a strip draws each label as an element of its own, side by side.\n const one = new Set(found.nodes).size === 1\n const strip = built.common(found.nodes)\n if (one ? built.node(found.nodes[0] as string)?.componentId === 'muiTabs' : !!strip && setsSideBySide(built.node(strip))) tabs.built += 1\n break\n }\n default:\n break\n }\n }\n })\n\n const pictures = picturesScore(source, built, placement)\n const headings = headingsScore(source, built, placement)\n const forms = ratioBoth(\n sections.reduce((total, section) => total + layoutNodes(section.nodes).filter((node) => node.kind === 'form').length, 0),\n built.order.filter((id) => built.node(id)?.componentId === 'form').length,\n )\n\n const report: Omit<SiteImportPageFidelity, 'score' | 'lowest'> = {\n words,\n sections: ratioBoth(sections.length, drawnSections.length),\n bands: { ...ratioKept(read.length, bandsKept), read, drawn },\n rows: ratioKept(rows.source, rows.built),\n groups: Object.fromEntries(\n Object.entries(groups).map(([kind, entry]) => [kind, { groups: ratioKept(entry.groups, entry.groupsKept), items: ratioKept(entry.items, entry.itemsKept) }]),\n ),\n tables: ratioKept(tables.source, tables.built),\n tabs: ratioKept(tabs.source, tabs.built),\n pictures,\n icons: { ...ratioKept(icons.source, icons.built), lone: icons.lone },\n buttons: { ...ratioKept(buttons.source, buttons.built), unlinked: buttons.unlinked },\n headings,\n forms,\n }\n return { ...lowestOf(pageMeasures(report)), ...report }\n}\n\n/** Whether an item of a group is kept: its own element, every line of it found, and the parts its kind and source say it has. */\nfunction itemKept(built: Built, kind: SiteImportGroupKind, item: SiteImportLayoutItem, unit: Unit): boolean {\n if (!unit.whole || !unit.element) return false\n const element = unit.element\n // A question opens its answer: the theme's accordion.\n if (kind === 'faq' && !built.has(element, (node) => node.componentId === 'muiAccordion')) return false\n // An item the source made a link as a whole still goes there.\n if (item.to && !built.has(element, hasDestination)) return false\n // A picture of the item's own, where the library's copy is not known to the score.\n if (item.image !== undefined && !built.has(element, (node) => node.componentId === 'image')) return false\n return true\n}\n\n/** The words: the source's lines against what the page shows, each word as often as it is said. */\nfunction wordsScore(source: SiteImportFidelitySource, built: Built): SiteImportFidelityWords {\n const tally = (lines: readonly string[]) => {\n const counts = new Map<string, number>()\n for (const word of lines.flatMap(wordsOf)) counts.set(word, (counts.get(word) ?? 0) + 1)\n return counts\n }\n const said = tally(source.blocks.flatMap((block) => (isLine(block) && block.kind !== 'image' ? [clean(block.text)] : [])))\n const shown = tally(built.pieces.map((piece) => piece.text))\n let sourceWords = 0\n let builtWords = 0\n let both = 0\n const missing: string[] = []\n const extra: string[] = []\n for (const [word, count] of said) {\n sourceWords += count\n const on = shown.get(word) ?? 0\n both += Math.min(count, on)\n if (on < count) missing.push(word)\n }\n for (const [word, count] of shown) {\n builtWords += count\n if (count > (said.get(word) ?? 0)) extra.push(word)\n }\n const most = Math.max(sourceWords, builtWords)\n return {\n source: sourceWords,\n built: builtWords,\n missing: sourceWords - both,\n extra: builtWords - both,\n ratio: most > 0 ? both / most : 1,\n examples: { missing: missing.slice(0, 8), extra: extra.slice(0, 8) },\n }\n}\n\n/** The pictures: placed where the library's copies are known, else counted. */\nfunction picturesScore(source: SiteImportFidelitySource, built: Built, placement: Placement): SiteImportFidelityCount {\n const refs = source.layout.sections.flatMap((section) => section.lines).filter((ref) => source.blocks[ref]?.kind === 'image' && isLine(source.blocks[ref]))\n const drawn = built.order.filter((id) => built.node(id)?.componentId === 'image').length\n if (!source.photos?.length) return ratioBoth(refs.length, drawn)\n return ratioKept(refs.length, refs.filter((ref) => placement.of.has(ref)).length)\n}\n\n/** The headings: each of the source's drawn as a heading (an `h1`–`h6`, or the question of an accordion). */\nfunction headingsScore(source: SiteImportFidelitySource, built: Built, placement: Placement): SiteImportFidelityCount {\n const refs = source.layout.sections.flatMap((section: SiteImportLayoutSection) => section.lines).filter((ref) => source.blocks[ref]?.kind === 'heading')\n const kept = refs.filter((ref) => {\n const id = placement.of.get(ref)\n const node = id ? built.node(id) : undefined\n return !!node && (/^h[1-6]$/.test(String(node.props?.['component'] ?? '')) || node.componentId === 'muiAccordionSummary')\n })\n return ratioKept(refs.length, kept.length)\n}\n\n/** Every structural measure of a page by name, groups by kind. */\nfunction pageMeasures(report: Omit<SiteImportPageFidelity, 'score' | 'lowest'>): Array<[string, SiteImportFidelityCount]> {\n return [\n ['sections', report.sections],\n ['bands', report.bands],\n ['rows', report.rows],\n ...Object.entries(report.groups).map(([kind, entry]): [string, SiteImportFidelityCount] => [`${kind} items`, entry.items]),\n ['tables', report.tables],\n ['tabs', report.tabs],\n ['pictures', report.pictures],\n ['icons', report.icons],\n ['buttons', report.buttons],\n ['headings', report.headings],\n ['forms', report.forms],\n ]\n}\n\n/** The score: the lowest ratio, and the measure it came from. */\nfunction lowestOf(measures: ReadonlyArray<[string, SiteImportFidelityCount]>): { score: number; lowest: string | null } {\n let score = 1\n let lowest: string | null = null\n for (const [name, count] of measures) {\n if (count.ratio < score) {\n score = count.ratio\n lowest = name\n }\n }\n return { score, lowest }\n}\n\n/*------------------------------------------\n * The frame\n *-----------------------------------------*/\n\n/** A built header and footer, scored against the source's. */\nexport interface SiteImportFrameFidelity {\n /** The lowest ratio of the header's and footer's measures. */\n score: number\n lowest: string | null\n /** The source's top-level items, in the header's row, over the larger of the two (a row of page titles scores low). */\n items: SiteImportFidelityCount\n /** Whether the row's items are in the source's order. */\n inOrder: boolean\n /** The source's menus drawn as menus under their names. */\n menus: SiteImportFidelityCount\n /** The source's menu entries, each a link in its menu. */\n entries: SiteImportFidelityCount\n /** The lines under entries and their icons, in their menus. */\n details: SiteImportFidelityCount\n /** The header's buttons, drawn as the source draws them, each with its destination. */\n ctas: SiteImportFidelityCount\n /** Everything of the row in the phone menu: each menu's name and entries, the links, the buttons. */\n drawer: SiteImportFidelityCount\n /** A menu button shown where the row is not, opening the phone menu. */\n phone: SiteImportFidelityCount\n /** The row's labels held to one line. */\n nowrap: SiteImportFidelityCount\n /** The footer's columns under their headings. */\n columns: SiteImportFidelityCount\n /** The footer's links, its columns' and its own. */\n links: SiteImportFidelityCount\n /** Its profiles on other sites. */\n social: SiteImportFidelityCount\n /** Its words, address, phone, email and © line, word for word. */\n facts: SiteImportFidelityCount\n}\n\n/** A node's label as a menu, a link or a button carries it. */\nfunction labelOf(node: SiteImportFidelityNode | undefined): string {\n if (!node) return ''\n return clean(node.props?.['label']) || clean(node.props?.['children']) || clean(node.props?.['ariaLabel'])\n}\n\nconst LINK_COMPONENTS = new Set(['muiScreenLink', 'muiButton', 'muiLinkBox', 'muiLink'])\n\n/**\n * A built header and footer scored against the source's (AGL-4109): the\n * header's top-level items in order, each menu's entries with the lines and\n * icons under them, its buttons, the phone menu holding all of it, the row's\n * labels on one line; the footer's columns, links, profiles and facts. The\n * score is the lowest ratio.\n */\nexport function siteImportFrameFidelity(source: { nav: SiteImportNavTree | null | undefined; footer: SiteImportFooter | null | undefined }, tree: SiteImportFidelityTree): SiteImportFrameFidelity {\n const built = new Built(tree)\n const all = built.order\n const is = (componentId: string) => (id: string) => built.node(id)?.componentId === componentId\n const drawer = all.find(is('muiDrawer')) ?? null\n const inDrawer = (id: string) => !!drawer && built.holds(drawer, id)\n const header = all.find((id) => built.node(id)?.componentId === 'muiAppBar' || built.node(id)?.props?.['component'] === 'header') ?? null\n const footer = all.find((id) => built.node(id)?.props?.['element'] === 'footer' || built.node(id)?.props?.['component'] === 'footer') ?? null\n const inHeader = header ? built.under(header).filter((id) => !inDrawer(id)) : []\n const row = inHeader.find((id) => built.node(id)?.props?.['component'] === 'nav') ?? null\n const nav = source.nav ?? null\n const groups = nav?.items ?? []\n\n // The row: its items by name, in order.\n const rowItems = row ? built.children(row).map((id) => labelOf(built.node(id))) : []\n const wanted = groups.map((item) => clean(item.label))\n const left = [...rowItems]\n let matched = 0\n for (const label of wanted) {\n const at = left.indexOf(label)\n if (at !== -1) {\n matched += 1\n left.splice(at, 1)\n }\n }\n const inOrder = rowItems.filter((label) => wanted.includes(label)).join('\\n') === wanted.filter((label) => rowItems.includes(label)).join('\\n')\n\n // Each menu: drawn as one, under its name, with its entries, their lines and icons.\n const menus = { source: 0, built: 0 }\n const entries = { source: 0, built: 0 }\n const details = { source: 0, built: 0 }\n for (const group of groups) {\n if (!group.items.length) continue\n menus.source += 1\n entries.source += group.items.length\n const menu = row\n ? built.under(row).find((id) => (built.node(id)?.componentId === 'muiMegaMenu' || built.node(id)?.componentId === 'muiNavMenu') && clean(built.node(id)?.props?.['label']) === clean(group.label))\n : undefined\n for (const entry of group.items) {\n if (entry.text) details.source += 1\n if (entry.iconPath) details.source += 1\n }\n if (!menu) continue\n menus.built += 1\n const inside = built.under(menu)\n const links = inside.filter((id) => LINK_COMPONENTS.has(built.node(id)?.componentId ?? ''))\n const texts = new Set(inside.flatMap((id) => shownTexts(built.node(id))))\n for (const entry of group.items) {\n const link = links.find((id) => labelOf(built.node(id)) === clean(entry.label) || shownTexts(built.node(id)).includes(clean(entry.label)) || built.under(id).some((at) => shownTexts(built.node(at)).includes(clean(entry.label))))\n if (!link) continue\n entries.built += 1\n if (entry.text && texts.has(clean(entry.text))) details.built += 1\n if (entry.iconPath && built.has(link, (node) => node.componentId === 'icon')) details.built += 1\n }\n }\n\n // The buttons, as the source draws them.\n const ctas = { source: 0, built: 0 }\n for (const cta of nav?.ctas ?? []) {\n ctas.source += 1\n const button = inHeader.find((id) => built.node(id)?.componentId === 'muiButton' && clean(built.node(id)?.props?.['children']) === clean(cta.label))\n const node = button ? built.node(button) : undefined\n if (node && node.props?.['variant'] === BUTTON_VARIANT[cta.style] && (!cta.href || hasDestination(node))) ctas.built += 1\n }\n\n // The phone menu: every name, entry, link and button of the row.\n const drawerTexts = new Set(drawer ? built.under(drawer).flatMap((id) => shownTexts(built.node(id))) : [])\n const drawerWanted = [\n ...groups.flatMap((group) => [clean(group.label), ...group.items.map((entry) => clean(entry.label))]),\n ...(nav?.ctas ?? []).map((cta) => clean(cta.label)),\n ].filter(Boolean)\n const toggle = inHeader.some(is('muiDrawerToggle'))\n const hasRow = groups.length > 0 || (nav?.ctas.length ?? 0) > 0\n\n // The footer.\n const foot = source.footer ?? null\n const footIds = footer ? built.under(footer) : []\n const footTexts = new Set(footIds.flatMap((id) => shownTexts(built.node(id))))\n const footLinks = footIds.filter((id) => LINK_COMPONENTS.has(built.node(id)?.componentId ?? ''))\n const footLinkLabels = footLinks.map((id) => labelOf(built.node(id)))\n const columnLinks = (foot?.columns ?? []).flatMap((column) => column.links)\n const linksWanted = [...columnLinks, ...(foot?.links ?? [])]\n const linkLeft = [...footLinkLabels]\n let linksKept = 0\n for (const link of linksWanted) {\n const at = linkLeft.indexOf(clean(link.label))\n if (at !== -1) {\n linksKept += 1\n linkLeft.splice(at, 1)\n }\n }\n const profiles = footIds.filter(is('socialLinks')).flatMap((id) => Object.entries(built.node(id)?.props ?? {}).filter(([, value]) => typeof value === 'string' && /^https?:/.test(value as string)))\n const hrefs = new Set(footLinks.map((id) => clean(built.node(id)?.props?.['href'])))\n const socialKept = (foot?.social ?? []).filter((profile) => profiles.some(([network, href]) => network === profile.network || href === profile.href) || hrefs.has(clean(profile.href))).length\n const facts = [...(foot?.text ?? []), foot?.address, foot?.phone, foot?.email, foot?.copyright].map(clean).filter(Boolean)\n\n const report: Omit<SiteImportFrameFidelity, 'score' | 'lowest'> = {\n // Over the larger of the two: a row of the site's page titles in place of the source's menus scores low.\n items: { source: wanted.length, built: matched, ratio: Math.max(wanted.length, rowItems.length) > 0 ? matched / Math.max(wanted.length, rowItems.length) : 1 },\n inOrder,\n menus: ratioKept(menus.source, menus.built),\n entries: ratioKept(entries.source, entries.built),\n details: ratioKept(details.source, details.built),\n ctas: ratioKept(ctas.source, ctas.built),\n drawer: ratioKept(drawerWanted.length, drawerWanted.filter((label) => drawerTexts.has(label)).length),\n phone: ratioKept(hasRow ? 1 : 0, hasRow && toggle && !!drawer ? 1 : 0),\n nowrap: ratioKept(row ? 1 : 0, row && built.node(row)?.sx?.['whiteSpace'] === 'nowrap' ? 1 : 0),\n columns: ratioKept(foot?.columns.length ?? 0, (foot?.columns ?? []).filter((column) => footTexts.has(clean(column.heading))).length),\n links: ratioKept(linksWanted.length, linksKept),\n social: ratioKept(foot?.social.length ?? 0, socialKept),\n facts: ratioKept(facts.length, facts.filter((fact) => footTexts.has(fact)).length),\n }\n const measures: Array<[string, SiteImportFidelityCount]> = [\n ['items', report.items],\n ['menus', report.menus],\n ['entries', report.entries],\n ['details', report.details],\n ['ctas', report.ctas],\n ['drawer', report.drawer],\n ['phone', report.phone],\n ['nowrap', report.nowrap],\n ['columns', report.columns],\n ['links', report.links],\n ['social', report.social],\n ['facts', report.facts],\n ]\n return { ...lowestOf(measures), ...report }\n}\n"],"names":["SITE_IMPORT_FIDELITY_THRESHOLD","clean","text","replace","trim","WORD","wordsOf","match","wordLine","join","isLine","block","kind","url","inline","tableCells","rows","split","flatMap","line","map","cell","filter","Boolean","shownTexts","node","props","out","key","value","push","componentId","hasDestination","interactions","length","BUTTON_VARIANT","primary","secondary","quiet","siteImportFidelityBandOf","ground","sx","siteImportFidelityTreeOf","nodes","ids","Object","keys","rootId","find","id","parentId","Built","tree","holds","a","b","start","enter","get","end","exit","point","undefined","under","order","slice","children","child","has","common","at","parent","every","test","some","Map","pieces","seen","Set","visit","add","set","ratioKept","source","built","ratio","Math","min","ratioBoth","max","Placement","cursor","previous","free","piece","owner","claimedBy","pick","list","reduce","best","gap","abs","bestGap","exact","padded","part","includes","nodesOf","refs","whole","measured","pictures","photos","ref","blocks","of","sectionRoots","imageClaimed","photoOf","photo","from","src","layout","sections","forEach","section","index","root","scope","images","lines","found","unitsOn","placement","parts","stop","others","other","anchor","element","up","setsSideBySide","isByline","cells","findIndex","words","count","total","layoutNodes","tab","tabs","panel","linesOf","item","items","row","label","siteImportFidelity","drawnSections","aligned","roots","_","wordsScore","read","band","drawn","bandsKept","groups","tables","buttons","unlinked","icons","lone","drawnAs","button","href","units","parents","unit","size","wanted","icon","listIcon","group","entry","groupsKept","itemsKept","kept","itemKept","shown","labels","one","strip","picturesScore","headings","headingsScore","forms","report","bands","fromEntries","entries","lowestOf","pageMeasures","to","image","tally","counts","word","said","sourceWords","builtWords","both","missing","extra","on","most","examples","String","measures","score","lowest","name","labelOf","LINK_COMPONENTS","siteImportFrameFidelity","all","inHeader","is","drawer","inDrawer","header","footer","nav","rowItems","left","matched","indexOf","splice","inOrder","menus","details","menu","iconPath","inside","links","texts","link","ctas","cta","style","drawerTexts","drawerWanted","toggle","hasRow","foot","footIds","footTexts","footLinks","footLinkLabels","columnLinks","columns","column","linksWanted","linkLeft","linksKept","profiles","hrefs","socialKept","social","profile","network","facts","address","phone","email","copyright","nowrap","heading","fact"],"mappings":"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"}
|
|
@@ -205,6 +205,33 @@ export declare function aiSiteStartInputsOf(inputs: Readonly<Record<string, unkn
|
|
|
205
205
|
export declare function aiSiteStartAnswersFromInputs(inputs: Readonly<Record<string, unknown>> | null | undefined, options?: {
|
|
206
206
|
freeTaste?: boolean;
|
|
207
207
|
}): AiSiteStartAnswers | null;
|
|
208
|
+
/**
|
|
209
|
+
* The questions' answers an AI starter fills in (AGL-4049), from the answers
|
|
210
|
+
* its listing carries: what "Choose your starter" hands the start when a
|
|
211
|
+
* person picks one. Every answer stays the person's to change.
|
|
212
|
+
*
|
|
213
|
+
* Read from the keys a starter writes (`first-party-starters.mjs`, and a
|
|
214
|
+
* publisher's AI starter in the same shape):
|
|
215
|
+
*
|
|
216
|
+
* - `kind` → the style chosen, and the kind it names. The person chose it by
|
|
217
|
+
* picking the starter, so it arrives chosen; a kind this deployment does
|
|
218
|
+
* not know chooses nothing, and the Style step asks as ever.
|
|
219
|
+
* - `brandColors.primary` → the brand color, when it is a color.
|
|
220
|
+
* - `pages`, when it is a whole number → how many pages to plan, held to the
|
|
221
|
+
* workspace's band. A description of the pages is the kind's own guidance,
|
|
222
|
+
* which the kind already carries to the plan.
|
|
223
|
+
* - `brief` → what the site is for, as typed on the first question.
|
|
224
|
+
*
|
|
225
|
+
* Anything else a starter carries is left out — `style`'s finer tokens and
|
|
226
|
+
* `fonts` included: the questions ask neither, and the kind's look and the
|
|
227
|
+
* brand color are what carry a starter's look into the job. Every other
|
|
228
|
+
* answer starts where the questions start, so a starter never answers for
|
|
229
|
+
* the person what it does not say. Anything that is not a set of answers
|
|
230
|
+
* reads as none.
|
|
231
|
+
*/
|
|
232
|
+
export declare function aiSiteStartAnswersFromStarter(answers: Readonly<Record<string, unknown>> | null | undefined, options?: {
|
|
233
|
+
freeTaste?: boolean;
|
|
234
|
+
}): AiSiteStartAnswers;
|
|
208
235
|
/**
|
|
209
236
|
* The questions' answers for a site staff are building for a client: what
|
|
210
237
|
* the business does is the kind of site, and the details the intake holds
|
|
@@ -318,6 +318,47 @@ import { AI_SITE_INPUT_MAX_CHARS, AI_SITE_SUBMISSIONS, aiSitePagesBand } from ".
|
|
|
318
318
|
inspiration: text(AI_SITE_INSPIRATION_INPUT).split(/\s+/).filter(Boolean).slice(0, aiSiteInspirationLimit(options.freeTaste))
|
|
319
319
|
} : {});
|
|
320
320
|
}
|
|
321
|
+
// ── An AI starter's answers (AGL-4049) ──────────────────────────────────────
|
|
322
|
+
/** A brand color as the questions hold one: six hex digits. */ const STARTER_BRAND_HEX = /^#[0-9a-f]{6}$/i;
|
|
323
|
+
const isAnswersRecord = (value)=>!!value && typeof value === 'object' && !Array.isArray(value);
|
|
324
|
+
/**
|
|
325
|
+
* The questions' answers an AI starter fills in (AGL-4049), from the answers
|
|
326
|
+
* its listing carries: what "Choose your starter" hands the start when a
|
|
327
|
+
* person picks one. Every answer stays the person's to change.
|
|
328
|
+
*
|
|
329
|
+
* Read from the keys a starter writes (`first-party-starters.mjs`, and a
|
|
330
|
+
* publisher's AI starter in the same shape):
|
|
331
|
+
*
|
|
332
|
+
* - `kind` → the style chosen, and the kind it names. The person chose it by
|
|
333
|
+
* picking the starter, so it arrives chosen; a kind this deployment does
|
|
334
|
+
* not know chooses nothing, and the Style step asks as ever.
|
|
335
|
+
* - `brandColors.primary` → the brand color, when it is a color.
|
|
336
|
+
* - `pages`, when it is a whole number → how many pages to plan, held to the
|
|
337
|
+
* workspace's band. A description of the pages is the kind's own guidance,
|
|
338
|
+
* which the kind already carries to the plan.
|
|
339
|
+
* - `brief` → what the site is for, as typed on the first question.
|
|
340
|
+
*
|
|
341
|
+
* Anything else a starter carries is left out — `style`'s finer tokens and
|
|
342
|
+
* `fonts` included: the questions ask neither, and the kind's look and the
|
|
343
|
+
* brand color are what carry a starter's look into the job. Every other
|
|
344
|
+
* answer starts where the questions start, so a starter never answers for
|
|
345
|
+
* the person what it does not say. Anything that is not a set of answers
|
|
346
|
+
* reads as none.
|
|
347
|
+
*/ export function aiSiteStartAnswersFromStarter(answers, options = {}) {
|
|
348
|
+
if (!isAnswersRecord(answers)) return _extends({}, AI_SITE_START_ANSWERS);
|
|
349
|
+
const band = aiSitePagesBand(options.freeTaste);
|
|
350
|
+
const kind = aiSiteKind(typeof answers['kind'] === 'string' ? answers['kind'] : null);
|
|
351
|
+
const colors = isAnswersRecord(answers['brandColors']) ? answers['brandColors'] : {};
|
|
352
|
+
const primary = typeof colors['primary'] === 'string' ? colors['primary'].trim() : '';
|
|
353
|
+
const pages = answers['pages'];
|
|
354
|
+
const brief = typeof answers['brief'] === 'string' ? answers['brief'] : '';
|
|
355
|
+
return _extends({}, AI_SITE_START_ANSWERS, {
|
|
356
|
+
siteType: bounded(brief, AI_SITE_INPUT_MAX_CHARS)
|
|
357
|
+
}, kind ? aiSiteStartStyleAnswer(kind.id) : {}, {
|
|
358
|
+
pages: typeof pages === 'number' && Number.isInteger(pages) ? Math.min(band.max, Math.max(band.min, pages)) : Math.min(band.max, Math.max(band.min, AI_SITE_START_ANSWERS.pages)),
|
|
359
|
+
brand: STARTER_BRAND_HEX.test(primary) ? primary.toLowerCase() : ''
|
|
360
|
+
});
|
|
361
|
+
}
|
|
321
362
|
// ── A build staff run for a client (AGL-3958) ───────────────────────────────
|
|
322
363
|
/** Shortens an answer to what a scalar input admits, on a word where it can. */ function bounded(value, max) {
|
|
323
364
|
const trimmed = value.trim().replace(/\s+/g, ' ');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-start.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ClientBuildIntake } from '@aglyn/aglyn/app-utils/client-build'\nimport { SITE_STYLE_AUTO } from '@aglyn/aglyn/app-utils/site-styles'\nimport { weeklyHoursFromText, weeklyHoursToText } from '@aglyn/shared-ui-jsx/utils/weekly-hours'\nimport {\n AI_SITE_DETAIL_KEYS,\n AI_SITE_NO_DETAILS,\n aiSiteDetailProblem,\n aiSiteDetailsOf,\n type AiSiteDetailKey,\n type AiSiteDetails,\n} from './ai-site-details'\nimport { aiSiteKind, aiSiteKindFor, type AiSiteKind } from './ai-site-kinds'\nimport {\n AI_SITE_INSPIRATION_INPUT,\n aiSiteInspirationInput,\n aiSiteInspirationLimit,\n aiSiteInspirationRefusal,\n aiSiteInspirationUrl,\n} from './ai-site-inspiration'\nimport {\n AI_SITE_INPUT_MAX_CHARS,\n AI_SITE_SUBMISSIONS,\n aiSitePagesBand,\n type AiSiteJobInputs,\n type AiSiteSubmissions,\n} from './ai-site-job'\n\n/**\n * The guided start (AGL-2918): the questions a person answers on a site they\n * have just made, and the site job those answers become.\n *\n * A scaffold already exists — the `site` job plans four to eight pages, a\n * layout, a contact form, a palette and a welcome email, and builds every one\n * of them as a draft. What it had no way in through was a brief: somebody had\n * to know what to write, on a site where nothing has happened yet. So this\n * module is the QUESTIONS, and nothing else: what is asked, what an answer may\n * be, and how a set of answers becomes the brief and the scalar inputs the\n * `site` door already takes.\n *\n * ── Answering is never the way out ───────────────────────────────────────\n *\n * There is no answer here that means \"not this\". The way past the questions\n * is the `hostFirstRun` zone's own `startBlank`, which is drawn beside them\n * from the first one and creates nothing at all — see the zone in\n * `CONSOLE_WIDGET_SLOTS`. Putting the exit inside the question set would make\n * leaving a thing you answer your way to, and it is not.\n *\n * ── Every answer but the first is optional ───────────────────────────────\n *\n * Only what a site is FOR has no default worth guessing. The audience narrows\n * the copy, the example steers the shape, the page count and the welcome\n * email are defaults a person may change — so a person who types six words\n * and confirms gets a planned site, and the rest of the questions are there\n * for the person who wants to answer them.\n *\n * Pure: shapes, vocabularies and two string builders. No React, no network,\n * and no import of the starter catalog — {@link AI_SITE_START_EXAMPLES} names\n * the starters rather than reading them, so the console does not carry a\n * catalog of node maps to draw five cards. `ai-site-start.spec.ts` holds the\n * two lists to each other.\n */\n\n/** A worked example a person picks to steer what the site should look like. */\nexport interface AiSiteStartExample {\n /** A `STARTER_TEMPLATES` id, which is a persisted identifier. */\n id: string\n /** The starter as its card names it. */\n label: string\n /** The starter's category, which is how the examples group. */\n category: string\n /** What the example is, in one line a person reads before picking it. */\n blurb: string\n}\n\n/**\n * The examples the start offers, in the order they are shown. The platform's\n * own starter sites: a person who has no words for what they want has seen\n * these five shapes before, and picking one says more than a sentence would.\n */\nexport const AI_SITE_START_EXAMPLES: readonly AiSiteStartExample[] = [\n {\n id: 'business',\n label: 'Business',\n category: 'Business',\n blurb: 'A home page led by what you do, an about page and a contact page',\n },\n {\n id: 'landing',\n label: 'Landing Page',\n category: 'Marketing',\n blurb: 'One page that leads to one action, with the reasons to take it',\n },\n {\n id: 'portfolio',\n label: 'Portfolio',\n category: 'Personal',\n blurb: 'An introduction, a grid of work and a way to get in touch',\n },\n {\n id: 'physical-shop',\n label: 'Shop (physical products)',\n category: 'Commerce',\n blurb: 'Featured products, a shop to browse, and a cart',\n },\n {\n id: 'digital-shop',\n label: 'Shop (digital products)',\n category: 'Commerce',\n blurb: 'Downloads up front, a shop to browse, and a cart',\n },\n]\n\n/**\n * Kinds of site offered beside the first question's box, so answering it can\n * be a tap. They fill the box rather than replacing it: what somebody is\n * building is theirs to say, and a list of eight is a list somebody is not on.\n */\nexport const AI_SITE_START_TYPES: readonly string[] = [\n 'a local service business',\n 'a restaurant or cafe',\n 'a professional practice',\n 'a trades business',\n 'a shop',\n 'a nonprofit',\n 'a personal portfolio',\n 'an event',\n]\n\n/**\n * What a person answered; the shape the card holds and the brief is built from.\n *\n * Beside the answers each step asks, the business details its \"More details\"\n * offers (AGL-3979, `AiSiteDetails`): every one optional and `''` until given,\n * the phone number and email on the Details step itself.\n */\nexport interface AiSiteStartAnswers extends AiSiteDetails {\n /** What kind of site this is. The one answer with no default. */\n siteType: string\n /** Who the site is for; empty when they did not say. */\n audience: string\n /** The example they liked, by {@link AiSiteStartExample.id}; `null` for none. */\n example: string | null\n /**\n * The style card they clicked (AGL-4049): a kind's id, or\n * `SITE_STYLE_AUTO` for \"Pick for me\"; `null` while they have clicked none.\n * Nothing is chosen for a person, so the Style step does not go forward on\n * `null` ({@link aiSiteStartStyleAnswer} sets this and {@link kind} together).\n */\n style: string | null\n /**\n * The kind of site they picked (AGL-3660), by its `AI_SITE_KINDS` id;\n * `null` for \"Pick for me\", or before a style is chosen, when the kind\n * their first answer suggests is the one used ({@link aiSiteStartKind}).\n */\n kind: string | null\n /** How many pages to plan. */\n pages: number\n /** Where the contact form's submissions go. */\n submissions: AiSiteSubmissions\n /** Whether the scaffold also drafts a welcome email. */\n welcomeEmail: boolean\n /** The city or town the business is in (AGL-3979); empty when they did not say. */\n city: string\n /** The brand color, as a six-digit hex (AGL-3979); empty for the style's own. */\n brand: string\n /**\n * The sites they like (AGL-3975), as typed, one a field; absent or empty\n * for none. Asked only while `release_ai_site_inspiration` is on.\n */\n inspiration?: string[]\n}\n\n/**\n * What the questions start on. Five pages because it is the middle of the\n * band the scaffold builds, a welcome email because the scaffold drafts one\n * unless it is told not to, and the Inbox because it is what every submission\n * does anyway — filing a lead as well is the addition, and an addition is the\n * thing a person should have to choose.\n */\nexport const AI_SITE_START_ANSWERS: AiSiteStartAnswers = {\n siteType: '',\n audience: '',\n example: null,\n style: null,\n kind: null,\n pages: 5,\n submissions: 'inbox',\n welcomeEmail: true,\n city: '',\n brand: '',\n ...AI_SITE_NO_DETAILS,\n}\n\n/**\n * Why the Style step does not go forward before a card is clicked (AGL-4049),\n * and why the review does not plan: nothing is chosen for a person, and\n * \"Pick for me\" is a card too. The door itself admits a start without one\n * ({@link aiSiteStartRefusal} is what it would refuse), so this is the\n * questions' rule, held by `aiSiteStartStepErrors` and the card.\n */\nexport const AI_SITE_START_STYLE_REQUIRED = 'Choose a style, or Pick for me.'\n\n/** The answers a person may give beyond the ones each step asks, by key (AGL-3979). */\nexport type AiSiteStartExtraKey = AiSiteDetailKey | 'city' | 'brand'\n\n/** Every extra, in the order the review lists them. */\nexport const AI_SITE_START_EXTRA_KEYS: readonly AiSiteStartExtraKey[] = [\n 'phone',\n 'email',\n 'tagline',\n 'founded',\n 'serviceArea',\n 'priceRange',\n 'languages',\n 'brand',\n 'street',\n 'city',\n 'region',\n 'postalCode',\n 'country',\n 'hours',\n 'social',\n]\n\n/** What is wrong with one extra, worded for beside its field; `null` when it may be used. */\nexport function aiSiteStartExtraProblem(\n key: AiSiteStartExtraKey,\n value: string,\n): { code: 'too_long' | 'invalid'; message: string } | null {\n if (key === 'city' || key === 'brand') {\n return value.trim().length > AI_SITE_INPUT_MAX_CHARS\n ? { code: 'too_long', message: `Keep this under ${AI_SITE_INPUT_MAX_CHARS} characters.` }\n : null\n }\n return aiSiteDetailProblem(key, value)\n}\n\n/** The extras a person gave, in the review's order: what \"More details\" was used for. */\nexport function aiSiteStartExtrasGiven(answers: AiSiteStartAnswers): AiSiteStartExtraKey[] {\n return AI_SITE_START_EXTRA_KEYS.filter((key) => (answers[key] ?? '').trim() !== '')\n}\n\n/**\n * A style card clicked (AGL-4049), as the answers hold it: the card's id as\n * the style, and the kind it names — none for \"Pick for me\", which leaves the\n * kind to what the site is and who it is for.\n */\nexport function aiSiteStartStyleAnswer(id: string): Pick<AiSiteStartAnswers, 'style' | 'kind'> {\n return { style: id, kind: id === SITE_STYLE_AUTO ? null : (aiSiteKind(id)?.id ?? null) }\n}\n\n/** The kind of site the answers pick, else the one what the site is and who it is for suggest. */\nexport function aiSiteStartKind(\n answers: Pick<AiSiteStartAnswers, 'kind' | 'siteType'> & Partial<Pick<AiSiteStartAnswers, 'audience'>>,\n): AiSiteKind {\n return aiSiteKind(answers.kind) ?? aiSiteKindFor(answers.siteType, answers.audience ?? '')\n}\n\n/** The example an answer names, or `null` when it names none this list knows. */\nexport function aiSiteStartExample(\n answers: AiSiteStartAnswers,\n): AiSiteStartExample | null {\n if (!answers.example) return null\n return AI_SITE_START_EXAMPLES.find((entry) => entry.id === answers.example) ?? null\n}\n\n/**\n * Why these answers cannot start a site, in a sentence a person reads; `null`\n * when they can. The door's own parser is what actually admits the job — this\n * is what the card says before it asks for one.\n */\nexport function aiSiteStartRefusal(\n answers: AiSiteStartAnswers,\n options: { freeTaste?: boolean } = {},\n): string | null {\n // The workspace's own band (AGL-3594): one or two pages on the Free taste.\n const band = aiSitePagesBand(options.freeTaste)\n const siteType = answers.siteType.trim()\n if (!siteType) return 'Say what kind of site this is.'\n if (siteType.length > AI_SITE_INPUT_MAX_CHARS) {\n return `Keep what kind of site this is under ${AI_SITE_INPUT_MAX_CHARS} characters.`\n }\n if (answers.audience.trim().length > AI_SITE_INPUT_MAX_CHARS) {\n return `Keep who the site is for under ${AI_SITE_INPUT_MAX_CHARS} characters.`\n }\n if (!Number.isInteger(answers.pages) || answers.pages < band.min || answers.pages > band.max) {\n return `A site is planned with ${band.min} to ${band.max} pages.`\n }\n if (answers.example !== null && !aiSiteStartExample(answers)) {\n return 'Pick one of the examples, or none of them.'\n }\n if (!(AI_SITE_SUBMISSIONS as readonly string[]).includes(answers.submissions)) {\n return 'Say where the contact form’s submissions go.'\n }\n for (const key of AI_SITE_START_EXTRA_KEYS) {\n const problem = aiSiteStartExtraProblem(key, answers[key] ?? '')\n if (problem) return problem.message\n }\n return aiSiteStartInspirationRefusal(answers, options)\n}\n\n/** The sites they typed, without the empty fields. */\nexport function aiSiteStartInspirationTyped(answers: Pick<AiSiteStartAnswers, 'inspiration'>): string[] {\n return (answers.inspiration ?? []).map((entry) => entry.trim()).filter(Boolean)\n}\n\n/** Why the sites they like cannot go forward, as the door would refuse them; `null` when they can. */\nexport function aiSiteStartInspirationRefusal(\n answers: Pick<AiSiteStartAnswers, 'inspiration'>,\n options: { freeTaste?: boolean } = {},\n): string | null {\n const typed = aiSiteStartInspirationTyped(answers)\n if (typed.some((entry) => !aiSiteInspirationUrl(entry))) {\n return 'Give each site you like as its https address, like https://example.com.'\n }\n return aiSiteInspirationRefusal(typed, options.freeTaste)\n}\n\n/**\n * The answers as the brief the `site` job is created with: the plan step reads\n * it as the person's own words, so it says what they said and asks for nothing\n * they did not.\n */\nexport function aiSiteStartBrief(answers: AiSiteStartAnswers): string {\n const siteType = answers.siteType.trim()\n const audience = answers.audience.trim()\n const example = aiSiteStartExample(answers)\n const sentences = [\n `A ${answers.pages}-page website for ${siteType}.`,\n ]\n if (audience) sentences.push(`It is for ${audience}.`)\n if (example) {\n sentences.push(\n `Follow the shape of the ${example.label} starter: ${example.blurb.toLowerCase()}.`,\n )\n }\n // The kind they picked, or the one their answer suggests (AGL-3660).\n sentences.push(`Make it a ${aiSiteStartKind(answers).label.toLowerCase()} site.`)\n // The form on the page a visitor opens to get in touch, never every page's\n // last band (AGL-3899): \"on the page that asks to be contacted\" read as\n // the home's closing band on 16 of the 22 newest production starts.\n sentences.push(\n answers.pages === 1\n ? 'Plan the page around what a visitor comes to do, with a contact form on it.'\n : 'Plan a page for each thing a visitor comes to do, with the contact form on the page a visitor opens to get in touch.',\n )\n if (audience) {\n // Said of the FORM as well as of the copy: who fills a form in is what\n // decides which fields it asks for, and a brief that named the audience\n // once, in a sentence about the pages, left the form to guess.\n sentences.push(`Ask the contact form for what ${audience} would be asked for.`)\n }\n return sentences.join(' ')\n}\n\n/**\n * The answers as the `site` job's scalar inputs. Read by the door's\n * {@link parseAiSiteJobInputs}, and put in front of the model one per line by\n * the plan step, which is how the audience and the example reach the plan\n * without a prompt of their own.\n *\n * The per-site variables an agency batch fills — the business name, the city,\n * the brand — are left empty here: the questions never ask them. The name is\n * the site's own, and the create door fills it from the site\n * (`aiJobAdmittedInputs`, AGL-3596) when the job is started — a job that\n * reached the model with no name had the model invent one for its header and\n * footer.\n */\nexport function aiSiteStartInputs(\n answers: AiSiteStartAnswers,\n): Omit<AiSiteJobInputs, 'batchId' | 'inspiration' | AiSiteDetailKey> & Partial<AiSiteDetails> & { inspiration?: string } {\n const example = aiSiteStartExample(answers)\n const kind = aiSiteStartKind(answers)\n // The details are sent only where given (AGL-3979): the plan step lists\n // every scalar input on a line of its own, and a start without them is\n // asked exactly what it was asked before.\n const details: Partial<AiSiteDetails> = {}\n for (const key of AI_SITE_DETAIL_KEYS) {\n const value = (answers[key] ?? '').trim()\n if (value) details[key] = value\n }\n // The sites they like travel only when there are some (AGL-3975), so a\n // start without them asks for exactly what it always did.\n const inspiration = aiSiteInspirationInput(aiSiteStartInspirationTyped(answers))\n return {\n businessType: answers.siteType.trim(),\n audience: answers.audience.trim(),\n starter: example?.id ?? kind.starter,\n siteKind: kind.id,\n pages: answers.pages,\n businessName: '',\n city: (answers.city ?? '').trim(),\n brand: (answers.brand ?? '').trim(),\n submissions: answers.submissions,\n welcomeEmail: answers.welcomeEmail,\n ...details,\n ...(inspiration ? { [AI_SITE_INSPIRATION_INPUT]: inspiration } : {}),\n }\n}\n\n/** The site inputs a guided start's answers become, by name: what a summary carries back (AGL-3596). */\nexport const AI_SITE_START_INPUT_KEYS = [\n 'businessType',\n 'audience',\n 'starter',\n 'siteKind',\n 'pages',\n 'businessName',\n 'city',\n 'brand',\n 'submissions',\n 'welcomeEmail',\n ...AI_SITE_DETAIL_KEYS,\n AI_SITE_INSPIRATION_INPUT,\n] as const\n\n/** A site job's scalar inputs, and nothing else its inputs carry; `null` when it has none. */\nexport function aiSiteStartInputsOf(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): Record<string, unknown> | null {\n if (!inputs) return null\n const kept: Record<string, unknown> = {}\n for (const key of AI_SITE_START_INPUT_KEYS) {\n const value = inputs[key]\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') kept[key] = value\n }\n return Object.keys(kept).length ? kept : null\n}\n\n/**\n * A failed start's answers, back as the questions hold them (AGL-3596), so\n * Try again reopens the guided start filled in for the person to adjust:\n * the inverse of {@link aiSiteStartInputs}. Read defensively — an answer the\n * questions no longer offer goes back to its default — and held to the\n * workspace's page band. `null` when the inputs say no kind of site at all.\n */\nexport function aiSiteStartAnswersFromInputs(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n options: { freeTaste?: boolean } = {},\n): AiSiteStartAnswers | null {\n const text = (key: string) => (typeof inputs?.[key] === 'string' ? (inputs[key] as string).trim() : '')\n const siteType = text('businessType')\n if (!siteType) return null\n const band = aiSitePagesBand(options.freeTaste)\n const pages = typeof inputs?.['pages'] === 'number' ? Math.round(inputs['pages'] as number) : AI_SITE_START_ANSWERS.pages\n const starter = text('starter')\n const submissions = text('submissions')\n return {\n siteType,\n audience: text('audience'),\n example: AI_SITE_START_EXAMPLES.some((entry) => entry.id === starter) ? starter : null,\n // A start that ran had its style chosen: its kind, or \"Pick for me\".\n ...aiSiteStartStyleAnswer(aiSiteKind(text('siteKind'))?.id ?? SITE_STYLE_AUTO),\n pages: Math.min(band.max, Math.max(band.min, pages)),\n submissions: (AI_SITE_SUBMISSIONS as readonly string[]).includes(submissions)\n ? (submissions as AiSiteSubmissions)\n : AI_SITE_START_ANSWERS.submissions,\n welcomeEmail: options.freeTaste ? false : inputs?.['welcomeEmail'] !== false,\n city: text('city'),\n brand: text('brand'),\n ...(Object.fromEntries(AI_SITE_DETAIL_KEYS.map((key) => [key, text(key)])) as unknown as AiSiteDetails),\n // The sites they liked come back as typed, within the workspace's limit (AGL-3975).\n ...(text(AI_SITE_INSPIRATION_INPUT)\n ? { inspiration: text(AI_SITE_INSPIRATION_INPUT).split(/\\s+/).filter(Boolean).slice(0, aiSiteInspirationLimit(options.freeTaste)) }\n : {}),\n }\n}\n\n// ── A build staff run for a client (AGL-3958) ───────────────────────────────\n\n/** Shortens an answer to what a scalar input admits, on a word where it can. */\nfunction bounded(value: string, max: number): string {\n const trimmed = value.trim().replace(/\\s+/g, ' ')\n if (trimmed.length <= max) return trimmed\n const cut = trimmed.slice(0, max)\n const space = cut.lastIndexOf(' ')\n return (space > max / 2 ? cut.slice(0, space) : cut).trim()\n}\n\n/** A time with am or pm: \"9am\", \"5:30 pm\", \"12 a.m.\". */\nconst TWELVE_HOUR_TIME = /\\b(\\d{1,2})(?::(\\d{2}))?\\s*([ap])\\.?m\\.?(?![a-z])/gi\n\n/**\n * The hours an owner gave, in the shape the Details step's hours picker\n * writes (`Tu-Sa 09:00-17:00`, one set of days a line), so the plan reads\n * them as it reads hours picked there. A time with am or pm is the time it\n * names, \"to\" joins a range as a dash does, and days with their hours may be\n * set apart by a comma, a semicolon or a line. Whatever still does not read\n * as hours — \"8–5\", \"Sat by appointment\" — is left out rather than guessed at.\n */\nfunction clientBuildHours(hours: string): string {\n const lines = hours\n .replace(TWELVE_HOUR_TIME, (time, hour: string, minutes: string | undefined, half: string) => {\n const h = Number(hour)\n if (h < 1 || h > 12) return time\n const clock = (h % 12) + (half.toLowerCase() === 'p' ? 12 : 0)\n return `${String(clock).padStart(2, '0')}:${minutes ?? '00'}`\n })\n .replace(/\\s+to\\s+/gi, '-')\n .replace(/(\\d{1,2}:\\d{2})\\s*,\\s*/g, '$1\\n')\n .replace(/;/g, '\\n')\n const week = weeklyHoursFromText(lines).hours\n return week.some((day) => day.length) ? weeklyHoursToText(week) : ''\n}\n\n/**\n * The towns an intake serves, one a line, as the Details step's \"Areas you\n * serve\" holds them: \"Austin, Round Rock and Pflugerville\" is three.\n */\nfunction clientBuildServiceArea(towns: string): string {\n return towns\n .split(/\\r?\\n|[,;]|\\s+(?:and|&)\\s+/i)\n .map((town) => town.trim())\n .filter(Boolean)\n .join('\\n')\n}\n\n/**\n * The questions' answers for a site staff are building for a client: what\n * the business does is the kind of site, and the details the intake holds\n * fill the Details step's own fields (AGL-3979, AGL-3995): the phone, the\n * email, the towns as the areas it serves and the hours, each held to that\n * detail's own rule, so one the Details step would refuse is left empty\n * there. Every other answer keeps its default, which the review shows to\n * change. The intake, a refused detail included, also reaches the job\n * through {@link aiSiteStartClientInputs} and {@link aiSiteStartClientBrief}.\n */\nexport function aiSiteStartAnswersFromClientBuild(intake: ClientBuildIntake): AiSiteStartAnswers {\n const details = aiSiteDetailsOf({\n phone: intake.phone,\n email: intake.email,\n serviceArea: clientBuildServiceArea(intake.towns),\n hours: clientBuildHours(intake.hours),\n })\n return {\n ...AI_SITE_START_ANSWERS,\n siteType: bounded(intake.whatTheyDo, AI_SITE_INPUT_MAX_CHARS),\n // The site exists already and the intake answered for it: what the\n // business does picks its style, as \"Pick for me\" does, and staff change\n // it on the review like any answer.\n ...aiSiteStartStyleAnswer(SITE_STYLE_AUTO),\n phone: details.phone,\n email: details.email,\n serviceArea: details.serviceArea,\n hours: details.hours,\n }\n}\n\n/**\n * The per-site inputs the intake fills, which the guided start's own\n * questions leave empty: the business's name for the header and footer, and\n * the towns it serves as its place.\n *\n * The towns are the place only while no address is given. The Entity reads\n * `city` as the address's own locality wherever there is a street or a\n * postal code (`aiSiteEntity`), so once the answers carry either, `city` is\n * the city the Details step was given with that address, never the towns\n * (AGL-3995). The intake asks no address, so today that is the Details step's\n * own, typed on the review; the towns still reach the site as the areas it\n * serves.\n */\nexport function aiSiteStartClientInputs(\n intake: ClientBuildIntake,\n answers?: Pick<AiSiteStartAnswers, 'street' | 'postalCode' | 'city'> | null,\n): Pick<AiSiteJobInputs, 'businessName' | 'city'> {\n const addressed = Boolean(answers?.street.trim() || answers?.postalCode.trim())\n return {\n businessName: bounded(intake.businessName, AI_SITE_INPUT_MAX_CHARS),\n city: bounded(addressed ? (answers?.city ?? '') : intake.towns, AI_SITE_INPUT_MAX_CHARS),\n }\n}\n\n/**\n * The intake as sentences added to the brief: how to reach the business and\n * when it is open, which the site should say where a visitor looks for them,\n * and the owner's own words, quoted, which the copy should sound like. Empty\n * answers say nothing.\n */\nexport function aiSiteStartClientBrief(intake: ClientBuildIntake): string {\n const name = intake.businessName.trim()\n const towns = intake.towns.trim()\n const phone = intake.phone.trim()\n const email = intake.email.trim()\n const hours = intake.hours.trim()\n const words = intake.ownerWords.trim().replace(/\\s+/g, ' ')\n const sentences: string[] = []\n if (name) sentences.push(`The business is called ${name}.`)\n if (towns) sentences.push(`It serves ${towns}.`)\n const contact = [phone ? `call ${phone}` : '', email ? `email ${email}` : ''].filter(Boolean)\n if (contact.length) {\n sentences.push(`Visitors reach it to ${contact.join(' or ')}; say so wherever the site tells them how to get in touch.`)\n }\n if (hours) sentences.push(`Its hours are ${hours}.`)\n if (words) sentences.push(`In the owner's own words: \"${words}\" Write the copy so it sounds like them.`)\n return sentences.join(' ')\n}\n\n/**\n * What the review says the client build adds beyond its own rows, so staff\n * see the intake is used without it being asked again: the business's name\n * and the owner's own words, which no row shows. The phone, email, towns and\n * hours are the review's Details rows already (AGL-3995), so the note does\n * not say them twice: \"Also from the client's details: Harbor Plumbing and\n * the owner's own words.\"\n */\nexport function aiSiteStartClientNote(intake: ClientBuildIntake): string {\n const parts = [intake.businessName.trim(), intake.ownerWords.trim() ? \"the owner's own words\" : ''].filter(Boolean)\n if (!parts.length) return 'Built from the client’s details.'\n return `Also from the client’s details: ${parts.join(' and ')}.`\n}\n"],"names":["SITE_STYLE_AUTO","weeklyHoursFromText","weeklyHoursToText","AI_SITE_DETAIL_KEYS","AI_SITE_NO_DETAILS","aiSiteDetailProblem","aiSiteDetailsOf","aiSiteKind","aiSiteKindFor","AI_SITE_INSPIRATION_INPUT","aiSiteInspirationInput","aiSiteInspirationLimit","aiSiteInspirationRefusal","aiSiteInspirationUrl","AI_SITE_INPUT_MAX_CHARS","AI_SITE_SUBMISSIONS","aiSitePagesBand","AI_SITE_START_EXAMPLES","id","label","category","blurb","AI_SITE_START_TYPES","AI_SITE_START_ANSWERS","siteType","audience","example","style","kind","pages","submissions","welcomeEmail","city","brand","AI_SITE_START_STYLE_REQUIRED","AI_SITE_START_EXTRA_KEYS","aiSiteStartExtraProblem","key","value","trim","length","code","message","aiSiteStartExtrasGiven","answers","filter","aiSiteStartStyleAnswer","aiSiteStartKind","aiSiteStartExample","find","entry","aiSiteStartRefusal","options","band","freeTaste","Number","isInteger","min","max","includes","problem","aiSiteStartInspirationRefusal","aiSiteStartInspirationTyped","inspiration","map","Boolean","typed","some","aiSiteStartBrief","sentences","push","toLowerCase","join","aiSiteStartInputs","details","businessType","starter","siteKind","businessName","AI_SITE_START_INPUT_KEYS","aiSiteStartInputsOf","inputs","kept","Object","keys","aiSiteStartAnswersFromInputs","text","Math","round","fromEntries","split","slice","bounded","trimmed","replace","cut","space","lastIndexOf","TWELVE_HOUR_TIME","clientBuildHours","hours","lines","time","hour","minutes","half","h","clock","String","padStart","week","day","clientBuildServiceArea","towns","town","aiSiteStartAnswersFromClientBuild","intake","phone","email","serviceArea","whatTheyDo","aiSiteStartClientInputs","addressed","street","postalCode","aiSiteStartClientBrief","name","words","ownerWords","contact","aiSiteStartClientNote","parts"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-site-start.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ClientBuildIntake } from '@aglyn/aglyn/app-utils/client-build'\nimport { SITE_STYLE_AUTO } from '@aglyn/aglyn/app-utils/site-styles'\nimport { weeklyHoursFromText, weeklyHoursToText } from '@aglyn/shared-ui-jsx/utils/weekly-hours'\nimport {\n AI_SITE_DETAIL_KEYS,\n AI_SITE_NO_DETAILS,\n aiSiteDetailProblem,\n aiSiteDetailsOf,\n type AiSiteDetailKey,\n type AiSiteDetails,\n} from './ai-site-details'\nimport { aiSiteKind, aiSiteKindFor, type AiSiteKind } from './ai-site-kinds'\nimport {\n AI_SITE_INSPIRATION_INPUT,\n aiSiteInspirationInput,\n aiSiteInspirationLimit,\n aiSiteInspirationRefusal,\n aiSiteInspirationUrl,\n} from './ai-site-inspiration'\nimport {\n AI_SITE_INPUT_MAX_CHARS,\n AI_SITE_SUBMISSIONS,\n aiSitePagesBand,\n type AiSiteJobInputs,\n type AiSiteSubmissions,\n} from './ai-site-job'\n\n/**\n * The guided start (AGL-2918): the questions a person answers on a site they\n * have just made, and the site job those answers become.\n *\n * A scaffold already exists — the `site` job plans four to eight pages, a\n * layout, a contact form, a palette and a welcome email, and builds every one\n * of them as a draft. What it had no way in through was a brief: somebody had\n * to know what to write, on a site where nothing has happened yet. So this\n * module is the QUESTIONS, and nothing else: what is asked, what an answer may\n * be, and how a set of answers becomes the brief and the scalar inputs the\n * `site` door already takes.\n *\n * ── Answering is never the way out ───────────────────────────────────────\n *\n * There is no answer here that means \"not this\". The way past the questions\n * is the `hostFirstRun` zone's own `startBlank`, which is drawn beside them\n * from the first one and creates nothing at all — see the zone in\n * `CONSOLE_WIDGET_SLOTS`. Putting the exit inside the question set would make\n * leaving a thing you answer your way to, and it is not.\n *\n * ── Every answer but the first is optional ───────────────────────────────\n *\n * Only what a site is FOR has no default worth guessing. The audience narrows\n * the copy, the example steers the shape, the page count and the welcome\n * email are defaults a person may change — so a person who types six words\n * and confirms gets a planned site, and the rest of the questions are there\n * for the person who wants to answer them.\n *\n * Pure: shapes, vocabularies and two string builders. No React, no network,\n * and no import of the starter catalog — {@link AI_SITE_START_EXAMPLES} names\n * the starters rather than reading them, so the console does not carry a\n * catalog of node maps to draw five cards. `ai-site-start.spec.ts` holds the\n * two lists to each other.\n */\n\n/** A worked example a person picks to steer what the site should look like. */\nexport interface AiSiteStartExample {\n /** A `STARTER_TEMPLATES` id, which is a persisted identifier. */\n id: string\n /** The starter as its card names it. */\n label: string\n /** The starter's category, which is how the examples group. */\n category: string\n /** What the example is, in one line a person reads before picking it. */\n blurb: string\n}\n\n/**\n * The examples the start offers, in the order they are shown. The platform's\n * own starter sites: a person who has no words for what they want has seen\n * these five shapes before, and picking one says more than a sentence would.\n */\nexport const AI_SITE_START_EXAMPLES: readonly AiSiteStartExample[] = [\n {\n id: 'business',\n label: 'Business',\n category: 'Business',\n blurb: 'A home page led by what you do, an about page and a contact page',\n },\n {\n id: 'landing',\n label: 'Landing Page',\n category: 'Marketing',\n blurb: 'One page that leads to one action, with the reasons to take it',\n },\n {\n id: 'portfolio',\n label: 'Portfolio',\n category: 'Personal',\n blurb: 'An introduction, a grid of work and a way to get in touch',\n },\n {\n id: 'physical-shop',\n label: 'Shop (physical products)',\n category: 'Commerce',\n blurb: 'Featured products, a shop to browse, and a cart',\n },\n {\n id: 'digital-shop',\n label: 'Shop (digital products)',\n category: 'Commerce',\n blurb: 'Downloads up front, a shop to browse, and a cart',\n },\n]\n\n/**\n * Kinds of site offered beside the first question's box, so answering it can\n * be a tap. They fill the box rather than replacing it: what somebody is\n * building is theirs to say, and a list of eight is a list somebody is not on.\n */\nexport const AI_SITE_START_TYPES: readonly string[] = [\n 'a local service business',\n 'a restaurant or cafe',\n 'a professional practice',\n 'a trades business',\n 'a shop',\n 'a nonprofit',\n 'a personal portfolio',\n 'an event',\n]\n\n/**\n * What a person answered; the shape the card holds and the brief is built from.\n *\n * Beside the answers each step asks, the business details its \"More details\"\n * offers (AGL-3979, `AiSiteDetails`): every one optional and `''` until given,\n * the phone number and email on the Details step itself.\n */\nexport interface AiSiteStartAnswers extends AiSiteDetails {\n /** What kind of site this is. The one answer with no default. */\n siteType: string\n /** Who the site is for; empty when they did not say. */\n audience: string\n /** The example they liked, by {@link AiSiteStartExample.id}; `null` for none. */\n example: string | null\n /**\n * The style card they clicked (AGL-4049): a kind's id, or\n * `SITE_STYLE_AUTO` for \"Pick for me\"; `null` while they have clicked none.\n * Nothing is chosen for a person, so the Style step does not go forward on\n * `null` ({@link aiSiteStartStyleAnswer} sets this and {@link kind} together).\n */\n style: string | null\n /**\n * The kind of site they picked (AGL-3660), by its `AI_SITE_KINDS` id;\n * `null` for \"Pick for me\", or before a style is chosen, when the kind\n * their first answer suggests is the one used ({@link aiSiteStartKind}).\n */\n kind: string | null\n /** How many pages to plan. */\n pages: number\n /** Where the contact form's submissions go. */\n submissions: AiSiteSubmissions\n /** Whether the scaffold also drafts a welcome email. */\n welcomeEmail: boolean\n /** The city or town the business is in (AGL-3979); empty when they did not say. */\n city: string\n /** The brand color, as a six-digit hex (AGL-3979); empty for the style's own. */\n brand: string\n /**\n * The sites they like (AGL-3975), as typed, one a field; absent or empty\n * for none. Asked only while `release_ai_site_inspiration` is on.\n */\n inspiration?: string[]\n}\n\n/**\n * What the questions start on. Five pages because it is the middle of the\n * band the scaffold builds, a welcome email because the scaffold drafts one\n * unless it is told not to, and the Inbox because it is what every submission\n * does anyway — filing a lead as well is the addition, and an addition is the\n * thing a person should have to choose.\n */\nexport const AI_SITE_START_ANSWERS: AiSiteStartAnswers = {\n siteType: '',\n audience: '',\n example: null,\n style: null,\n kind: null,\n pages: 5,\n submissions: 'inbox',\n welcomeEmail: true,\n city: '',\n brand: '',\n ...AI_SITE_NO_DETAILS,\n}\n\n/**\n * Why the Style step does not go forward before a card is clicked (AGL-4049),\n * and why the review does not plan: nothing is chosen for a person, and\n * \"Pick for me\" is a card too. The door itself admits a start without one\n * ({@link aiSiteStartRefusal} is what it would refuse), so this is the\n * questions' rule, held by `aiSiteStartStepErrors` and the card.\n */\nexport const AI_SITE_START_STYLE_REQUIRED = 'Choose a style, or Pick for me.'\n\n/** The answers a person may give beyond the ones each step asks, by key (AGL-3979). */\nexport type AiSiteStartExtraKey = AiSiteDetailKey | 'city' | 'brand'\n\n/** Every extra, in the order the review lists them. */\nexport const AI_SITE_START_EXTRA_KEYS: readonly AiSiteStartExtraKey[] = [\n 'phone',\n 'email',\n 'tagline',\n 'founded',\n 'serviceArea',\n 'priceRange',\n 'languages',\n 'brand',\n 'street',\n 'city',\n 'region',\n 'postalCode',\n 'country',\n 'hours',\n 'social',\n]\n\n/** What is wrong with one extra, worded for beside its field; `null` when it may be used. */\nexport function aiSiteStartExtraProblem(\n key: AiSiteStartExtraKey,\n value: string,\n): { code: 'too_long' | 'invalid'; message: string } | null {\n if (key === 'city' || key === 'brand') {\n return value.trim().length > AI_SITE_INPUT_MAX_CHARS\n ? { code: 'too_long', message: `Keep this under ${AI_SITE_INPUT_MAX_CHARS} characters.` }\n : null\n }\n return aiSiteDetailProblem(key, value)\n}\n\n/** The extras a person gave, in the review's order: what \"More details\" was used for. */\nexport function aiSiteStartExtrasGiven(answers: AiSiteStartAnswers): AiSiteStartExtraKey[] {\n return AI_SITE_START_EXTRA_KEYS.filter((key) => (answers[key] ?? '').trim() !== '')\n}\n\n/**\n * A style card clicked (AGL-4049), as the answers hold it: the card's id as\n * the style, and the kind it names — none for \"Pick for me\", which leaves the\n * kind to what the site is and who it is for.\n */\nexport function aiSiteStartStyleAnswer(id: string): Pick<AiSiteStartAnswers, 'style' | 'kind'> {\n return { style: id, kind: id === SITE_STYLE_AUTO ? null : (aiSiteKind(id)?.id ?? null) }\n}\n\n/** The kind of site the answers pick, else the one what the site is and who it is for suggest. */\nexport function aiSiteStartKind(\n answers: Pick<AiSiteStartAnswers, 'kind' | 'siteType'> & Partial<Pick<AiSiteStartAnswers, 'audience'>>,\n): AiSiteKind {\n return aiSiteKind(answers.kind) ?? aiSiteKindFor(answers.siteType, answers.audience ?? '')\n}\n\n/** The example an answer names, or `null` when it names none this list knows. */\nexport function aiSiteStartExample(\n answers: AiSiteStartAnswers,\n): AiSiteStartExample | null {\n if (!answers.example) return null\n return AI_SITE_START_EXAMPLES.find((entry) => entry.id === answers.example) ?? null\n}\n\n/**\n * Why these answers cannot start a site, in a sentence a person reads; `null`\n * when they can. The door's own parser is what actually admits the job — this\n * is what the card says before it asks for one.\n */\nexport function aiSiteStartRefusal(\n answers: AiSiteStartAnswers,\n options: { freeTaste?: boolean } = {},\n): string | null {\n // The workspace's own band (AGL-3594): one or two pages on the Free taste.\n const band = aiSitePagesBand(options.freeTaste)\n const siteType = answers.siteType.trim()\n if (!siteType) return 'Say what kind of site this is.'\n if (siteType.length > AI_SITE_INPUT_MAX_CHARS) {\n return `Keep what kind of site this is under ${AI_SITE_INPUT_MAX_CHARS} characters.`\n }\n if (answers.audience.trim().length > AI_SITE_INPUT_MAX_CHARS) {\n return `Keep who the site is for under ${AI_SITE_INPUT_MAX_CHARS} characters.`\n }\n if (!Number.isInteger(answers.pages) || answers.pages < band.min || answers.pages > band.max) {\n return `A site is planned with ${band.min} to ${band.max} pages.`\n }\n if (answers.example !== null && !aiSiteStartExample(answers)) {\n return 'Pick one of the examples, or none of them.'\n }\n if (!(AI_SITE_SUBMISSIONS as readonly string[]).includes(answers.submissions)) {\n return 'Say where the contact form’s submissions go.'\n }\n for (const key of AI_SITE_START_EXTRA_KEYS) {\n const problem = aiSiteStartExtraProblem(key, answers[key] ?? '')\n if (problem) return problem.message\n }\n return aiSiteStartInspirationRefusal(answers, options)\n}\n\n/** The sites they typed, without the empty fields. */\nexport function aiSiteStartInspirationTyped(answers: Pick<AiSiteStartAnswers, 'inspiration'>): string[] {\n return (answers.inspiration ?? []).map((entry) => entry.trim()).filter(Boolean)\n}\n\n/** Why the sites they like cannot go forward, as the door would refuse them; `null` when they can. */\nexport function aiSiteStartInspirationRefusal(\n answers: Pick<AiSiteStartAnswers, 'inspiration'>,\n options: { freeTaste?: boolean } = {},\n): string | null {\n const typed = aiSiteStartInspirationTyped(answers)\n if (typed.some((entry) => !aiSiteInspirationUrl(entry))) {\n return 'Give each site you like as its https address, like https://example.com.'\n }\n return aiSiteInspirationRefusal(typed, options.freeTaste)\n}\n\n/**\n * The answers as the brief the `site` job is created with: the plan step reads\n * it as the person's own words, so it says what they said and asks for nothing\n * they did not.\n */\nexport function aiSiteStartBrief(answers: AiSiteStartAnswers): string {\n const siteType = answers.siteType.trim()\n const audience = answers.audience.trim()\n const example = aiSiteStartExample(answers)\n const sentences = [\n `A ${answers.pages}-page website for ${siteType}.`,\n ]\n if (audience) sentences.push(`It is for ${audience}.`)\n if (example) {\n sentences.push(\n `Follow the shape of the ${example.label} starter: ${example.blurb.toLowerCase()}.`,\n )\n }\n // The kind they picked, or the one their answer suggests (AGL-3660).\n sentences.push(`Make it a ${aiSiteStartKind(answers).label.toLowerCase()} site.`)\n // The form on the page a visitor opens to get in touch, never every page's\n // last band (AGL-3899): \"on the page that asks to be contacted\" read as\n // the home's closing band on 16 of the 22 newest production starts.\n sentences.push(\n answers.pages === 1\n ? 'Plan the page around what a visitor comes to do, with a contact form on it.'\n : 'Plan a page for each thing a visitor comes to do, with the contact form on the page a visitor opens to get in touch.',\n )\n if (audience) {\n // Said of the FORM as well as of the copy: who fills a form in is what\n // decides which fields it asks for, and a brief that named the audience\n // once, in a sentence about the pages, left the form to guess.\n sentences.push(`Ask the contact form for what ${audience} would be asked for.`)\n }\n return sentences.join(' ')\n}\n\n/**\n * The answers as the `site` job's scalar inputs. Read by the door's\n * {@link parseAiSiteJobInputs}, and put in front of the model one per line by\n * the plan step, which is how the audience and the example reach the plan\n * without a prompt of their own.\n *\n * The per-site variables an agency batch fills — the business name, the city,\n * the brand — are left empty here: the questions never ask them. The name is\n * the site's own, and the create door fills it from the site\n * (`aiJobAdmittedInputs`, AGL-3596) when the job is started — a job that\n * reached the model with no name had the model invent one for its header and\n * footer.\n */\nexport function aiSiteStartInputs(\n answers: AiSiteStartAnswers,\n): Omit<AiSiteJobInputs, 'batchId' | 'inspiration' | AiSiteDetailKey> & Partial<AiSiteDetails> & { inspiration?: string } {\n const example = aiSiteStartExample(answers)\n const kind = aiSiteStartKind(answers)\n // The details are sent only where given (AGL-3979): the plan step lists\n // every scalar input on a line of its own, and a start without them is\n // asked exactly what it was asked before.\n const details: Partial<AiSiteDetails> = {}\n for (const key of AI_SITE_DETAIL_KEYS) {\n const value = (answers[key] ?? '').trim()\n if (value) details[key] = value\n }\n // The sites they like travel only when there are some (AGL-3975), so a\n // start without them asks for exactly what it always did.\n const inspiration = aiSiteInspirationInput(aiSiteStartInspirationTyped(answers))\n return {\n businessType: answers.siteType.trim(),\n audience: answers.audience.trim(),\n starter: example?.id ?? kind.starter,\n siteKind: kind.id,\n pages: answers.pages,\n businessName: '',\n city: (answers.city ?? '').trim(),\n brand: (answers.brand ?? '').trim(),\n submissions: answers.submissions,\n welcomeEmail: answers.welcomeEmail,\n ...details,\n ...(inspiration ? { [AI_SITE_INSPIRATION_INPUT]: inspiration } : {}),\n }\n}\n\n/** The site inputs a guided start's answers become, by name: what a summary carries back (AGL-3596). */\nexport const AI_SITE_START_INPUT_KEYS = [\n 'businessType',\n 'audience',\n 'starter',\n 'siteKind',\n 'pages',\n 'businessName',\n 'city',\n 'brand',\n 'submissions',\n 'welcomeEmail',\n ...AI_SITE_DETAIL_KEYS,\n AI_SITE_INSPIRATION_INPUT,\n] as const\n\n/** A site job's scalar inputs, and nothing else its inputs carry; `null` when it has none. */\nexport function aiSiteStartInputsOf(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n): Record<string, unknown> | null {\n if (!inputs) return null\n const kept: Record<string, unknown> = {}\n for (const key of AI_SITE_START_INPUT_KEYS) {\n const value = inputs[key]\n if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') kept[key] = value\n }\n return Object.keys(kept).length ? kept : null\n}\n\n/**\n * A failed start's answers, back as the questions hold them (AGL-3596), so\n * Try again reopens the guided start filled in for the person to adjust:\n * the inverse of {@link aiSiteStartInputs}. Read defensively — an answer the\n * questions no longer offer goes back to its default — and held to the\n * workspace's page band. `null` when the inputs say no kind of site at all.\n */\nexport function aiSiteStartAnswersFromInputs(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n options: { freeTaste?: boolean } = {},\n): AiSiteStartAnswers | null {\n const text = (key: string) => (typeof inputs?.[key] === 'string' ? (inputs[key] as string).trim() : '')\n const siteType = text('businessType')\n if (!siteType) return null\n const band = aiSitePagesBand(options.freeTaste)\n const pages = typeof inputs?.['pages'] === 'number' ? Math.round(inputs['pages'] as number) : AI_SITE_START_ANSWERS.pages\n const starter = text('starter')\n const submissions = text('submissions')\n return {\n siteType,\n audience: text('audience'),\n example: AI_SITE_START_EXAMPLES.some((entry) => entry.id === starter) ? starter : null,\n // A start that ran had its style chosen: its kind, or \"Pick for me\".\n ...aiSiteStartStyleAnswer(aiSiteKind(text('siteKind'))?.id ?? SITE_STYLE_AUTO),\n pages: Math.min(band.max, Math.max(band.min, pages)),\n submissions: (AI_SITE_SUBMISSIONS as readonly string[]).includes(submissions)\n ? (submissions as AiSiteSubmissions)\n : AI_SITE_START_ANSWERS.submissions,\n welcomeEmail: options.freeTaste ? false : inputs?.['welcomeEmail'] !== false,\n city: text('city'),\n brand: text('brand'),\n ...(Object.fromEntries(AI_SITE_DETAIL_KEYS.map((key) => [key, text(key)])) as unknown as AiSiteDetails),\n // The sites they liked come back as typed, within the workspace's limit (AGL-3975).\n ...(text(AI_SITE_INSPIRATION_INPUT)\n ? { inspiration: text(AI_SITE_INSPIRATION_INPUT).split(/\\s+/).filter(Boolean).slice(0, aiSiteInspirationLimit(options.freeTaste)) }\n : {}),\n }\n}\n\n// ── An AI starter's answers (AGL-4049) ──────────────────────────────────────\n\n/** A brand color as the questions hold one: six hex digits. */\nconst STARTER_BRAND_HEX = /^#[0-9a-f]{6}$/i\n\nconst isAnswersRecord = (value: unknown): value is Readonly<Record<string, unknown>> =>\n !!value && typeof value === 'object' && !Array.isArray(value)\n\n/**\n * The questions' answers an AI starter fills in (AGL-4049), from the answers\n * its listing carries: what \"Choose your starter\" hands the start when a\n * person picks one. Every answer stays the person's to change.\n *\n * Read from the keys a starter writes (`first-party-starters.mjs`, and a\n * publisher's AI starter in the same shape):\n *\n * - `kind` → the style chosen, and the kind it names. The person chose it by\n * picking the starter, so it arrives chosen; a kind this deployment does\n * not know chooses nothing, and the Style step asks as ever.\n * - `brandColors.primary` → the brand color, when it is a color.\n * - `pages`, when it is a whole number → how many pages to plan, held to the\n * workspace's band. A description of the pages is the kind's own guidance,\n * which the kind already carries to the plan.\n * - `brief` → what the site is for, as typed on the first question.\n *\n * Anything else a starter carries is left out — `style`'s finer tokens and\n * `fonts` included: the questions ask neither, and the kind's look and the\n * brand color are what carry a starter's look into the job. Every other\n * answer starts where the questions start, so a starter never answers for\n * the person what it does not say. Anything that is not a set of answers\n * reads as none.\n */\nexport function aiSiteStartAnswersFromStarter(\n answers: Readonly<Record<string, unknown>> | null | undefined,\n options: { freeTaste?: boolean } = {},\n): AiSiteStartAnswers {\n if (!isAnswersRecord(answers)) return { ...AI_SITE_START_ANSWERS }\n const band = aiSitePagesBand(options.freeTaste)\n const kind = aiSiteKind(typeof answers['kind'] === 'string' ? answers['kind'] : null)\n const colors = isAnswersRecord(answers['brandColors']) ? answers['brandColors'] : {}\n const primary = typeof colors['primary'] === 'string' ? colors['primary'].trim() : ''\n const pages = answers['pages']\n const brief = typeof answers['brief'] === 'string' ? answers['brief'] : ''\n return {\n ...AI_SITE_START_ANSWERS,\n siteType: bounded(brief, AI_SITE_INPUT_MAX_CHARS),\n ...(kind ? aiSiteStartStyleAnswer(kind.id) : {}),\n pages:\n typeof pages === 'number' && Number.isInteger(pages)\n ? Math.min(band.max, Math.max(band.min, pages))\n : Math.min(band.max, Math.max(band.min, AI_SITE_START_ANSWERS.pages)),\n brand: STARTER_BRAND_HEX.test(primary) ? primary.toLowerCase() : '',\n }\n}\n\n// ── A build staff run for a client (AGL-3958) ───────────────────────────────\n\n/** Shortens an answer to what a scalar input admits, on a word where it can. */\nfunction bounded(value: string, max: number): string {\n const trimmed = value.trim().replace(/\\s+/g, ' ')\n if (trimmed.length <= max) return trimmed\n const cut = trimmed.slice(0, max)\n const space = cut.lastIndexOf(' ')\n return (space > max / 2 ? cut.slice(0, space) : cut).trim()\n}\n\n/** A time with am or pm: \"9am\", \"5:30 pm\", \"12 a.m.\". */\nconst TWELVE_HOUR_TIME = /\\b(\\d{1,2})(?::(\\d{2}))?\\s*([ap])\\.?m\\.?(?![a-z])/gi\n\n/**\n * The hours an owner gave, in the shape the Details step's hours picker\n * writes (`Tu-Sa 09:00-17:00`, one set of days a line), so the plan reads\n * them as it reads hours picked there. A time with am or pm is the time it\n * names, \"to\" joins a range as a dash does, and days with their hours may be\n * set apart by a comma, a semicolon or a line. Whatever still does not read\n * as hours — \"8–5\", \"Sat by appointment\" — is left out rather than guessed at.\n */\nfunction clientBuildHours(hours: string): string {\n const lines = hours\n .replace(TWELVE_HOUR_TIME, (time, hour: string, minutes: string | undefined, half: string) => {\n const h = Number(hour)\n if (h < 1 || h > 12) return time\n const clock = (h % 12) + (half.toLowerCase() === 'p' ? 12 : 0)\n return `${String(clock).padStart(2, '0')}:${minutes ?? '00'}`\n })\n .replace(/\\s+to\\s+/gi, '-')\n .replace(/(\\d{1,2}:\\d{2})\\s*,\\s*/g, '$1\\n')\n .replace(/;/g, '\\n')\n const week = weeklyHoursFromText(lines).hours\n return week.some((day) => day.length) ? weeklyHoursToText(week) : ''\n}\n\n/**\n * The towns an intake serves, one a line, as the Details step's \"Areas you\n * serve\" holds them: \"Austin, Round Rock and Pflugerville\" is three.\n */\nfunction clientBuildServiceArea(towns: string): string {\n return towns\n .split(/\\r?\\n|[,;]|\\s+(?:and|&)\\s+/i)\n .map((town) => town.trim())\n .filter(Boolean)\n .join('\\n')\n}\n\n/**\n * The questions' answers for a site staff are building for a client: what\n * the business does is the kind of site, and the details the intake holds\n * fill the Details step's own fields (AGL-3979, AGL-3995): the phone, the\n * email, the towns as the areas it serves and the hours, each held to that\n * detail's own rule, so one the Details step would refuse is left empty\n * there. Every other answer keeps its default, which the review shows to\n * change. The intake, a refused detail included, also reaches the job\n * through {@link aiSiteStartClientInputs} and {@link aiSiteStartClientBrief}.\n */\nexport function aiSiteStartAnswersFromClientBuild(intake: ClientBuildIntake): AiSiteStartAnswers {\n const details = aiSiteDetailsOf({\n phone: intake.phone,\n email: intake.email,\n serviceArea: clientBuildServiceArea(intake.towns),\n hours: clientBuildHours(intake.hours),\n })\n return {\n ...AI_SITE_START_ANSWERS,\n siteType: bounded(intake.whatTheyDo, AI_SITE_INPUT_MAX_CHARS),\n // The site exists already and the intake answered for it: what the\n // business does picks its style, as \"Pick for me\" does, and staff change\n // it on the review like any answer.\n ...aiSiteStartStyleAnswer(SITE_STYLE_AUTO),\n phone: details.phone,\n email: details.email,\n serviceArea: details.serviceArea,\n hours: details.hours,\n }\n}\n\n/**\n * The per-site inputs the intake fills, which the guided start's own\n * questions leave empty: the business's name for the header and footer, and\n * the towns it serves as its place.\n *\n * The towns are the place only while no address is given. The Entity reads\n * `city` as the address's own locality wherever there is a street or a\n * postal code (`aiSiteEntity`), so once the answers carry either, `city` is\n * the city the Details step was given with that address, never the towns\n * (AGL-3995). The intake asks no address, so today that is the Details step's\n * own, typed on the review; the towns still reach the site as the areas it\n * serves.\n */\nexport function aiSiteStartClientInputs(\n intake: ClientBuildIntake,\n answers?: Pick<AiSiteStartAnswers, 'street' | 'postalCode' | 'city'> | null,\n): Pick<AiSiteJobInputs, 'businessName' | 'city'> {\n const addressed = Boolean(answers?.street.trim() || answers?.postalCode.trim())\n return {\n businessName: bounded(intake.businessName, AI_SITE_INPUT_MAX_CHARS),\n city: bounded(addressed ? (answers?.city ?? '') : intake.towns, AI_SITE_INPUT_MAX_CHARS),\n }\n}\n\n/**\n * The intake as sentences added to the brief: how to reach the business and\n * when it is open, which the site should say where a visitor looks for them,\n * and the owner's own words, quoted, which the copy should sound like. Empty\n * answers say nothing.\n */\nexport function aiSiteStartClientBrief(intake: ClientBuildIntake): string {\n const name = intake.businessName.trim()\n const towns = intake.towns.trim()\n const phone = intake.phone.trim()\n const email = intake.email.trim()\n const hours = intake.hours.trim()\n const words = intake.ownerWords.trim().replace(/\\s+/g, ' ')\n const sentences: string[] = []\n if (name) sentences.push(`The business is called ${name}.`)\n if (towns) sentences.push(`It serves ${towns}.`)\n const contact = [phone ? `call ${phone}` : '', email ? `email ${email}` : ''].filter(Boolean)\n if (contact.length) {\n sentences.push(`Visitors reach it to ${contact.join(' or ')}; say so wherever the site tells them how to get in touch.`)\n }\n if (hours) sentences.push(`Its hours are ${hours}.`)\n if (words) sentences.push(`In the owner's own words: \"${words}\" Write the copy so it sounds like them.`)\n return sentences.join(' ')\n}\n\n/**\n * What the review says the client build adds beyond its own rows, so staff\n * see the intake is used without it being asked again: the business's name\n * and the owner's own words, which no row shows. The phone, email, towns and\n * hours are the review's Details rows already (AGL-3995), so the note does\n * not say them twice: \"Also from the client's details: Harbor Plumbing and\n * the owner's own words.\"\n */\nexport function aiSiteStartClientNote(intake: ClientBuildIntake): string {\n const parts = [intake.businessName.trim(), intake.ownerWords.trim() ? \"the owner's own words\" : ''].filter(Boolean)\n if (!parts.length) return 'Built from the client’s details.'\n return `Also from the client’s details: ${parts.join(' and ')}.`\n}\n"],"names":["SITE_STYLE_AUTO","weeklyHoursFromText","weeklyHoursToText","AI_SITE_DETAIL_KEYS","AI_SITE_NO_DETAILS","aiSiteDetailProblem","aiSiteDetailsOf","aiSiteKind","aiSiteKindFor","AI_SITE_INSPIRATION_INPUT","aiSiteInspirationInput","aiSiteInspirationLimit","aiSiteInspirationRefusal","aiSiteInspirationUrl","AI_SITE_INPUT_MAX_CHARS","AI_SITE_SUBMISSIONS","aiSitePagesBand","AI_SITE_START_EXAMPLES","id","label","category","blurb","AI_SITE_START_TYPES","AI_SITE_START_ANSWERS","siteType","audience","example","style","kind","pages","submissions","welcomeEmail","city","brand","AI_SITE_START_STYLE_REQUIRED","AI_SITE_START_EXTRA_KEYS","aiSiteStartExtraProblem","key","value","trim","length","code","message","aiSiteStartExtrasGiven","answers","filter","aiSiteStartStyleAnswer","aiSiteStartKind","aiSiteStartExample","find","entry","aiSiteStartRefusal","options","band","freeTaste","Number","isInteger","min","max","includes","problem","aiSiteStartInspirationRefusal","aiSiteStartInspirationTyped","inspiration","map","Boolean","typed","some","aiSiteStartBrief","sentences","push","toLowerCase","join","aiSiteStartInputs","details","businessType","starter","siteKind","businessName","AI_SITE_START_INPUT_KEYS","aiSiteStartInputsOf","inputs","kept","Object","keys","aiSiteStartAnswersFromInputs","text","Math","round","fromEntries","split","slice","STARTER_BRAND_HEX","isAnswersRecord","Array","isArray","aiSiteStartAnswersFromStarter","colors","primary","brief","bounded","test","trimmed","replace","cut","space","lastIndexOf","TWELVE_HOUR_TIME","clientBuildHours","hours","lines","time","hour","minutes","half","h","clock","String","padStart","week","day","clientBuildServiceArea","towns","town","aiSiteStartAnswersFromClientBuild","intake","phone","email","serviceArea","whatTheyDo","aiSiteStartClientInputs","addressed","street","postalCode","aiSiteStartClientBrief","name","words","ownerWords","contact","aiSiteStartClientNote","parts"],"mappings":"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"}
|
|
@@ -90,11 +90,31 @@ export interface AssistPageChangeTurn {
|
|
|
90
90
|
pageChangeJob?: AiJobSummary | null;
|
|
91
91
|
pageChangeNotice?: string | null;
|
|
92
92
|
}
|
|
93
|
+
/** A turn of a thread, as far as what it changed goes. */
|
|
94
|
+
export interface AssistOutcomeTurn extends AssistPageChangeTurn {
|
|
95
|
+
edit?: {
|
|
96
|
+
summary: string;
|
|
97
|
+
} | null;
|
|
98
|
+
editOutcome?: {
|
|
99
|
+
kind: string;
|
|
100
|
+
message?: string;
|
|
101
|
+
} | null;
|
|
102
|
+
}
|
|
103
|
+
/** How many of a thread's outcomes ride a question, the newest kept. */
|
|
104
|
+
export declare const ASSIST_OUTCOMES_SENT = 12;
|
|
105
|
+
/**
|
|
106
|
+
* What became of a turn's page change, or null when the turn asked for none
|
|
107
|
+
* (AGL-4116).
|
|
108
|
+
*/
|
|
109
|
+
export declare function assistPageChangeOutcome(turn: AssistPageChangeTurn): string | null;
|
|
93
110
|
/**
|
|
94
|
-
* What
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
111
|
+
* What a thread's changes came to, oldest first, for the next question to
|
|
112
|
+
* carry apart from the thread's words (AGL-4116).
|
|
113
|
+
*
|
|
114
|
+
* Apart, because a note added to the assistant's own turn reads as the model
|
|
115
|
+
* saying so: it is told never to say a change is made, so it took its record
|
|
116
|
+
* of a saved draft for its own mistake and took the change back, telling the
|
|
117
|
+
* person nothing had changed. The chat sees a thread's text and nothing else,
|
|
118
|
+
* so without these it cannot know what happened at all.
|
|
99
119
|
*/
|
|
100
|
-
export declare function
|
|
120
|
+
export declare function assistThreadOutcomes(turns: readonly AssistOutcomeTurn[]): string[];
|
|
@@ -82,26 +82,48 @@ function isRecord(value) {
|
|
|
82
82
|
}
|
|
83
83
|
return null;
|
|
84
84
|
}
|
|
85
|
+
/** How many of a thread's outcomes ride a question, the newest kept. */ export const ASSIST_OUTCOMES_SENT = 12;
|
|
85
86
|
/**
|
|
86
|
-
* What became of a turn's page change,
|
|
87
|
-
*
|
|
88
|
-
|
|
89
|
-
* "I'll make this change" with no outcome and told the person nothing in the
|
|
90
|
-
* chat had changed their page.
|
|
91
|
-
*/ export function assistPageChangeHistoryNote(turn) {
|
|
87
|
+
* What became of a turn's page change, or null when the turn asked for none
|
|
88
|
+
* (AGL-4116).
|
|
89
|
+
*/ export function assistPageChangeOutcome(turn) {
|
|
92
90
|
var _turn_pageChangeJob, _output_note;
|
|
93
91
|
const proposal = turn.pageChange;
|
|
94
92
|
if (!proposal) return null;
|
|
95
93
|
const change = `Page change on ${proposal.pageName}`;
|
|
96
|
-
if (turn.pageChangeNotice) return
|
|
94
|
+
if (turn.pageChangeNotice) return `${change} not started: ${turn.pageChangeNotice}`;
|
|
97
95
|
const job = (_turn_pageChangeJob = turn.pageChangeJob) != null ? _turn_pageChangeJob : null;
|
|
98
96
|
const output = assistPageChangeOutput(job);
|
|
99
|
-
if (output) return
|
|
97
|
+
if (output) return `${change}: ${(_output_note = output.note) != null ? _output_note : 'saved, not published.'}`;
|
|
100
98
|
if ((job == null ? void 0 : job.status) === 'failed' || (job == null ? void 0 : job.status) === 'canceled') {
|
|
101
99
|
var _job_error;
|
|
102
|
-
return
|
|
100
|
+
return `${change} not made: ${(_job_error = job.error) != null ? _job_error : job.status}`;
|
|
103
101
|
}
|
|
104
|
-
return
|
|
102
|
+
return `${change} being made.`;
|
|
103
|
+
}
|
|
104
|
+
/** What became of a turn's edit card, or null while it waits for an answer. */ function assistEditOutcome(turn) {
|
|
105
|
+
var _turn_editOutcome, _turn_editOutcome1, _turn_editOutcome2;
|
|
106
|
+
if (!turn.edit) return null;
|
|
107
|
+
const summary = turn.edit.summary;
|
|
108
|
+
if (((_turn_editOutcome = turn.editOutcome) == null ? void 0 : _turn_editOutcome.kind) === 'applied') return `Applied to the open canvas as an unsaved change: ${summary}`;
|
|
109
|
+
if (((_turn_editOutcome1 = turn.editOutcome) == null ? void 0 : _turn_editOutcome1.kind) === 'dismissed') return `Not applied, they turned it down: ${summary}`;
|
|
110
|
+
if (((_turn_editOutcome2 = turn.editOutcome) == null ? void 0 : _turn_editOutcome2.kind) === 'failed') return `Could not be applied: ${summary}`;
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* What a thread's changes came to, oldest first, for the next question to
|
|
115
|
+
* carry apart from the thread's words (AGL-4116).
|
|
116
|
+
*
|
|
117
|
+
* Apart, because a note added to the assistant's own turn reads as the model
|
|
118
|
+
* saying so: it is told never to say a change is made, so it took its record
|
|
119
|
+
* of a saved draft for its own mistake and took the change back, telling the
|
|
120
|
+
* person nothing had changed. The chat sees a thread's text and nothing else,
|
|
121
|
+
* so without these it cannot know what happened at all.
|
|
122
|
+
*/ export function assistThreadOutcomes(turns) {
|
|
123
|
+
return turns.flatMap((turn)=>[
|
|
124
|
+
assistPageChangeOutcome(turn),
|
|
125
|
+
assistEditOutcome(turn)
|
|
126
|
+
]).filter((outcome)=>outcome !== null).slice(-ASSIST_OUTCOMES_SENT);
|
|
105
127
|
}
|
|
106
128
|
|
|
107
129
|
//# sourceMappingURL=assist-page-change.js.map
|