@aglyn/plugins-ai 1.0.0 → 1.1.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
|
@@ -38,6 +38,18 @@ export interface AssistSitePage {
|
|
|
38
38
|
export declare function assistPageChangeTool(): AiTool;
|
|
39
39
|
/** The protocol: the same for every site, so it is cached. */
|
|
40
40
|
export declare function assistPageChangeBlock(): string;
|
|
41
|
+
/**
|
|
42
|
+
* The outcomes a question carries, held to what may enter a prompt. A client
|
|
43
|
+
* posts these as it posts its own history, so they are bounded the same way.
|
|
44
|
+
*/
|
|
45
|
+
export declare function parseAssistOutcomes(raw: unknown): string[];
|
|
46
|
+
/**
|
|
47
|
+
* What happened in this chat, as Aglyn recorded it (AGL-4116). Its own block,
|
|
48
|
+
* in Aglyn's voice, and never added to the assistant's turns: the rules about
|
|
49
|
+
* a change not being made yet are about what is proposed now, and a record in
|
|
50
|
+
* the assistant's own words read as breaking them.
|
|
51
|
+
*/
|
|
52
|
+
export declare function assistOutcomesBlock(outcomes: readonly string[]): string | null;
|
|
41
53
|
/**
|
|
42
54
|
* The site's pages, per site and per moment, so volatile — and, on a plan
|
|
43
55
|
* without version history, that a new version cannot be made, so a request
|
|
@@ -14,7 +14,8 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/ import {
|
|
17
|
+
*/ import { PLATFORM_BRAND_NAME } from "@aglyn/aglyn/app-utils/platform-brand";
|
|
18
|
+
import { ASSIST_OUTCOMES_SENT, ASSIST_PAGE_CHANGE_ACTION_ID, ASSIST_PAGE_CHANGE_SUMMARY_MAX_CHARS, ASSIST_PAGE_CHANGE_TOOL_NAME } from "../model/assist-page-change.js";
|
|
18
19
|
import { ASSIST_BUILD_TOOL_NAME } from "../model/assist-build.js";
|
|
19
20
|
import { AI_JOB_BRIEF_MAX_CHARS } from "../jobs/ai-job-text-step.js";
|
|
20
21
|
import { assistBuildBriefWithoutGaps } from "./assist-build.js";
|
|
@@ -65,6 +66,27 @@ export function assistPageChangeTool() {
|
|
|
65
66
|
`- One page per message. For changes to several pages, change the first and say to ask for the next. A request that also asks for new pages, forms or anything else to be made is a build: call ${ASSIST_BUILD_TOOL_NAME} with the whole request instead.`
|
|
66
67
|
].join('\n');
|
|
67
68
|
}
|
|
69
|
+
/** How long one outcome may be, so a client's list stays a list. */ const ASSIST_OUTCOME_MAX_CHARS = 300;
|
|
70
|
+
/**
|
|
71
|
+
* The outcomes a question carries, held to what may enter a prompt. A client
|
|
72
|
+
* posts these as it posts its own history, so they are bounded the same way.
|
|
73
|
+
*/ export function parseAssistOutcomes(raw) {
|
|
74
|
+
if (!Array.isArray(raw)) return [];
|
|
75
|
+
return raw.filter((outcome)=>typeof outcome === 'string').map((outcome)=>outcome.replace(/\s+/g, ' ').trim().slice(0, ASSIST_OUTCOME_MAX_CHARS)).filter(Boolean).slice(-ASSIST_OUTCOMES_SENT);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* What happened in this chat, as Aglyn recorded it (AGL-4116). Its own block,
|
|
79
|
+
* in Aglyn's voice, and never added to the assistant's turns: the rules about
|
|
80
|
+
* a change not being made yet are about what is proposed now, and a record in
|
|
81
|
+
* the assistant's own words read as breaking them.
|
|
82
|
+
*/ export function assistOutcomesBlock(outcomes) {
|
|
83
|
+
if (!outcomes.length) return null;
|
|
84
|
+
return [
|
|
85
|
+
`What has happened in this chat, as ${PLATFORM_BRAND_NAME} recorded it. You did not write this, and it is what actually happened:`,
|
|
86
|
+
...outcomes.map((outcome)=>`- ${outcome}`),
|
|
87
|
+
'Earlier messages only show what was proposed. When asked what has changed, answer from this list, and say the canvas shows those changes because they were made here. Never take back anything on this list. The rules about never saying a change is made are for what you propose now, not for this list.'
|
|
88
|
+
].join('\n');
|
|
89
|
+
}
|
|
68
90
|
/**
|
|
69
91
|
* The site's pages, per site and per moment, so volatile — and, on a plan
|
|
70
92
|
* without version history, that a new version cannot be made, so a request
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/assist-page-change.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ASSIST_PAGE_CHANGE_ACTION_ID,\n ASSIST_PAGE_CHANGE_SUMMARY_MAX_CHARS,\n ASSIST_PAGE_CHANGE_TOOL_NAME,\n type AssistPageChangeProposal,\n} from '../model/assist-page-change'\nimport { ASSIST_BUILD_TOOL_NAME } from '../model/assist-build'\nimport type { AiTool } from '../providers/contract'\nimport { AI_JOB_BRIEF_MAX_CHARS } from '../jobs/ai-job-text-step'\nimport { assistBuildBriefWithoutGaps } from './assist-build'\n\n/**\n * A change to a page the site already has, from the Assist chat (AGL-4116):\n * the tool the model asks through, the site's pages it picks from, and the\n * resolution the panel starts an `edit` job from. See\n * `model/assist-page-change.ts`.\n */\n\n/** The most pages the chat is shown; a site's pages past it are asked about by name. */\nexport const ASSIST_SITE_PAGES_MAX = 60\n\n/** One of the site's pages, as the chat is shown it. */\nexport interface AssistSitePage {\n id: string\n name: string\n /** The address it is served at, or would be once published. */\n slug: string\n /** Visitors reach it: the site routes its address to it. */\n live: boolean\n /** The site's home page. */\n home: boolean\n}\n\nconst STRING = { type: 'string' } as const\n\nexport function assistPageChangeTool(): AiTool {\n return {\n name: ASSIST_PAGE_CHANGE_TOOL_NAME,\n description:\n 'Change a page this site already has. The change is saved into that page’s draft, or as a new ' +\n 'version when the user asks for one, and the user is given a link to open it in the Besigner, ' +\n 'check it and publish it. Call at most once per message, for one page.',\n inputSchema: {\n type: 'object',\n properties: {\n summary: STRING,\n page: STRING,\n change: STRING,\n newVersion: { type: 'boolean' },\n },\n required: ['summary', 'page', 'change', 'newVersion'],\n additionalProperties: false,\n },\n strict: true,\n }\n}\n\n/** The protocol: the same for every site, so it is cached. */\nexport function assistPageChangeBlock(): string {\n return [\n 'Changing a page the site already has:',\n `- When the user asks to change something on one of the pages under \"The site's pages\" (its words, pictures, sections, layout or look), do not plan a build. Write one sentence saying you will make the change as a draft of that page, then call the ${ASSIST_PAGE_CHANGE_TOOL_NAME} tool once.`,\n '- page: the id of that page from the list. Where two pages share a name or an address, it is the live one unless the user names the other.',\n '- change: exactly what they asked to change on that page, in their words, with what this conversation settled about it. Add nothing they did not ask for.',\n '- summary: one short sentence, such as \"Make the top image fill the background on Home\".',\n '- The change is saved into that page’s draft, on any plan: the published page stays as it is until they publish. When it is ready, the chat shows Open in the Besigner, which opens that draft, and they publish it there. A further change to the same page is made on that draft.',\n '- newVersion: true only when they ask for the change as a new or separate version of the page; otherwise false. Never offer a new version they did not ask for.',\n '- Nothing is changed when you call the tool: never say it is done. Never say you cannot use a draft, cannot edit the page or cannot give a link.',\n `- One page per message. For changes to several pages, change the first and say to ask for the next. A request that also asks for new pages, forms or anything else to be made is a build: call ${ASSIST_BUILD_TOOL_NAME} with the whole request instead.`,\n ].join('\\n')\n}\n\n/**\n * The site's pages, per site and per moment, so volatile — and, on a plan\n * without version history, that a new version cannot be made, so a request\n * for one is answered with the draft rather than a promise.\n */\nexport function assistSitePagesBlock(\n pages: readonly AssistSitePage[],\n options: { versioning: boolean } = { versioning: true },\n): string | null {\n if (!pages.length) return null\n return [\n \"The site's pages (id · name · address · live or draft):\",\n ...pages.map(\n (page) =>\n `- ${page.id} · ${page.name} · ${page.slug || '/'} · ${page.live ? 'live' : 'draft, not live'}${page.home ? ', the home page' : ''}`,\n ),\n ...(options.versioning\n ? []\n : [\n 'This workspace’s plan keeps no version history (it comes with Pro), so newVersion is always false: if they ask for a new version, say the change goes into the page’s draft instead.',\n ]),\n ].join('\\n')\n}\n\nfunction text(value: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\n/**\n * The site's pages a change may name: not deleted, not a collection's entry\n * template, an email design or an error body. Live ones and the home page\n * first, so a cap keeps the pages a person means.\n */\nexport async function readAssistSitePages(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n): Promise<AssistSitePage[]> {\n const host = firestore.collection('hosts').doc(hostId)\n const [hostDoc, screens] = await Promise.all([\n host.get(),\n host\n .collection('screens')\n .select('displayName', 'name', 'slug', 'kind', 'deletedAt')\n .limit(ASSIST_SITE_PAGES_MAX * 3)\n .get(),\n ])\n const routing = (hostDoc.get('screens') ?? {}) as Record<string, unknown>\n const homeId = text(hostDoc.get('defaultHomeScreenId'))\n const pages: AssistSitePage[] = []\n for (const doc of screens.docs) {\n const data = doc.data()\n // A page has no kind; an entry template, an email design and an error\n // body each carry theirs, as the site inventory reads them.\n if (data['deletedAt'] || text(data['kind'])) continue\n const slug = text(data['slug'])\n pages.push({\n id: doc.id,\n name: text(data['displayName']) || text(data['name']) || slug || doc.id,\n slug: slug.startsWith('/') ? slug : `/${slug}`,\n live: typeof routing[doc.id] === 'string',\n home: doc.id === homeId,\n })\n }\n const rank = (page: AssistSitePage) => (page.home ? 0 : page.live ? 1 : 2)\n return pages.sort((a, b) => rank(a) - rank(b) || a.slug.localeCompare(b.slug)).slice(0, ASSIST_SITE_PAGES_MAX)\n}\n\n/**\n * The model's call, held to the site's own pages: a page it named that the\n * site does not have, or a change with nothing in it, proposes nothing.\n */\nexport function resolveAssistPageChange(\n input: Record<string, unknown> | null,\n scope: { hostId: string; pages: readonly AssistSitePage[] },\n): AssistPageChangeProposal | null {\n if (!input) return null\n const page = scope.pages.find((one) => one.id === text(input['page']))\n if (!page) return null\n const change = assistBuildBriefWithoutGaps(text(input['change']))\n if (!change) return null\n const summary = text(input['summary'])\n return {\n id: ASSIST_PAGE_CHANGE_ACTION_ID,\n hostId: scope.hostId,\n pageId: page.id,\n pageName: page.name,\n brief: change.slice(0, AI_JOB_BRIEF_MAX_CHARS),\n summary: (summary || `Change ${page.name}`).slice(0, ASSIST_PAGE_CHANGE_SUMMARY_MAX_CHARS),\n ...(input['newVersion'] === true ? { newVersion: true } : {}),\n }\n}\n"],"names":["ASSIST_PAGE_CHANGE_ACTION_ID","ASSIST_PAGE_CHANGE_SUMMARY_MAX_CHARS","ASSIST_PAGE_CHANGE_TOOL_NAME","ASSIST_BUILD_TOOL_NAME","AI_JOB_BRIEF_MAX_CHARS","assistBuildBriefWithoutGaps","ASSIST_SITE_PAGES_MAX","STRING","type","assistPageChangeTool","name","description","inputSchema","properties","summary","page","change","newVersion","required","additionalProperties","strict","assistPageChangeBlock","join","assistSitePagesBlock","pages","options","versioning","length","map","id","slug","live","home","text","value","trim","readAssistSitePages","firestore","hostId","hostDoc","host","collection","doc","screens","Promise","all","get","select","limit","routing","homeId","docs","data","push","startsWith","rank","sort","a","b","localeCompare","slice","resolveAssistPageChange","input","scope","find","one","pageId","pageName","brief"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/server/assist-page-change.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 { PLATFORM_BRAND_NAME } from '@aglyn/aglyn/app-utils/platform-brand'\nimport {\n ASSIST_OUTCOMES_SENT,\n ASSIST_PAGE_CHANGE_ACTION_ID,\n ASSIST_PAGE_CHANGE_SUMMARY_MAX_CHARS,\n ASSIST_PAGE_CHANGE_TOOL_NAME,\n type AssistPageChangeProposal,\n} from '../model/assist-page-change'\nimport { ASSIST_BUILD_TOOL_NAME } from '../model/assist-build'\nimport type { AiTool } from '../providers/contract'\nimport { AI_JOB_BRIEF_MAX_CHARS } from '../jobs/ai-job-text-step'\nimport { assistBuildBriefWithoutGaps } from './assist-build'\n\n/**\n * A change to a page the site already has, from the Assist chat (AGL-4116):\n * the tool the model asks through, the site's pages it picks from, and the\n * resolution the panel starts an `edit` job from. See\n * `model/assist-page-change.ts`.\n */\n\n/** The most pages the chat is shown; a site's pages past it are asked about by name. */\nexport const ASSIST_SITE_PAGES_MAX = 60\n\n/** One of the site's pages, as the chat is shown it. */\nexport interface AssistSitePage {\n id: string\n name: string\n /** The address it is served at, or would be once published. */\n slug: string\n /** Visitors reach it: the site routes its address to it. */\n live: boolean\n /** The site's home page. */\n home: boolean\n}\n\nconst STRING = { type: 'string' } as const\n\nexport function assistPageChangeTool(): AiTool {\n return {\n name: ASSIST_PAGE_CHANGE_TOOL_NAME,\n description:\n 'Change a page this site already has. The change is saved into that page’s draft, or as a new ' +\n 'version when the user asks for one, and the user is given a link to open it in the Besigner, ' +\n 'check it and publish it. Call at most once per message, for one page.',\n inputSchema: {\n type: 'object',\n properties: {\n summary: STRING,\n page: STRING,\n change: STRING,\n newVersion: { type: 'boolean' },\n },\n required: ['summary', 'page', 'change', 'newVersion'],\n additionalProperties: false,\n },\n strict: true,\n }\n}\n\n/** The protocol: the same for every site, so it is cached. */\nexport function assistPageChangeBlock(): string {\n return [\n 'Changing a page the site already has:',\n `- When the user asks to change something on one of the pages under \"The site's pages\" (its words, pictures, sections, layout or look), do not plan a build. Write one sentence saying you will make the change as a draft of that page, then call the ${ASSIST_PAGE_CHANGE_TOOL_NAME} tool once.`,\n '- page: the id of that page from the list. Where two pages share a name or an address, it is the live one unless the user names the other.',\n '- change: exactly what they asked to change on that page, in their words, with what this conversation settled about it. Add nothing they did not ask for.',\n '- summary: one short sentence, such as \"Make the top image fill the background on Home\".',\n '- The change is saved into that page’s draft, on any plan: the published page stays as it is until they publish. When it is ready, the chat shows Open in the Besigner, which opens that draft, and they publish it there. A further change to the same page is made on that draft.',\n '- newVersion: true only when they ask for the change as a new or separate version of the page; otherwise false. Never offer a new version they did not ask for.',\n '- Nothing is changed when you call the tool: never say it is done. Never say you cannot use a draft, cannot edit the page or cannot give a link.',\n `- One page per message. For changes to several pages, change the first and say to ask for the next. A request that also asks for new pages, forms or anything else to be made is a build: call ${ASSIST_BUILD_TOOL_NAME} with the whole request instead.`,\n ].join('\\n')\n}\n\n/** How long one outcome may be, so a client's list stays a list. */\nconst ASSIST_OUTCOME_MAX_CHARS = 300\n\n/**\n * The outcomes a question carries, held to what may enter a prompt. A client\n * posts these as it posts its own history, so they are bounded the same way.\n */\nexport function parseAssistOutcomes(raw: unknown): string[] {\n if (!Array.isArray(raw)) return []\n return raw\n .filter((outcome): outcome is string => typeof outcome === 'string')\n .map((outcome) => outcome.replace(/\\s+/g, ' ').trim().slice(0, ASSIST_OUTCOME_MAX_CHARS))\n .filter(Boolean)\n .slice(-ASSIST_OUTCOMES_SENT)\n}\n\n/**\n * What happened in this chat, as Aglyn recorded it (AGL-4116). Its own block,\n * in Aglyn's voice, and never added to the assistant's turns: the rules about\n * a change not being made yet are about what is proposed now, and a record in\n * the assistant's own words read as breaking them.\n */\nexport function assistOutcomesBlock(outcomes: readonly string[]): string | null {\n if (!outcomes.length) return null\n return [\n `What has happened in this chat, as ${PLATFORM_BRAND_NAME} recorded it. You did not write this, and it is what actually happened:`,\n ...outcomes.map((outcome) => `- ${outcome}`),\n 'Earlier messages only show what was proposed. When asked what has changed, answer from this list, and say the canvas shows those changes because they were made here. Never take back anything on this list. The rules about never saying a change is made are for what you propose now, not for this list.',\n ].join('\\n')\n}\n\n/**\n * The site's pages, per site and per moment, so volatile — and, on a plan\n * without version history, that a new version cannot be made, so a request\n * for one is answered with the draft rather than a promise.\n */\nexport function assistSitePagesBlock(\n pages: readonly AssistSitePage[],\n options: { versioning: boolean } = { versioning: true },\n): string | null {\n if (!pages.length) return null\n return [\n \"The site's pages (id · name · address · live or draft):\",\n ...pages.map(\n (page) =>\n `- ${page.id} · ${page.name} · ${page.slug || '/'} · ${page.live ? 'live' : 'draft, not live'}${page.home ? ', the home page' : ''}`,\n ),\n ...(options.versioning\n ? []\n : [\n 'This workspace’s plan keeps no version history (it comes with Pro), so newVersion is always false: if they ask for a new version, say the change goes into the page’s draft instead.',\n ]),\n ].join('\\n')\n}\n\nfunction text(value: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\n/**\n * The site's pages a change may name: not deleted, not a collection's entry\n * template, an email design or an error body. Live ones and the home page\n * first, so a cap keeps the pages a person means.\n */\nexport async function readAssistSitePages(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n): Promise<AssistSitePage[]> {\n const host = firestore.collection('hosts').doc(hostId)\n const [hostDoc, screens] = await Promise.all([\n host.get(),\n host\n .collection('screens')\n .select('displayName', 'name', 'slug', 'kind', 'deletedAt')\n .limit(ASSIST_SITE_PAGES_MAX * 3)\n .get(),\n ])\n const routing = (hostDoc.get('screens') ?? {}) as Record<string, unknown>\n const homeId = text(hostDoc.get('defaultHomeScreenId'))\n const pages: AssistSitePage[] = []\n for (const doc of screens.docs) {\n const data = doc.data()\n // A page has no kind; an entry template, an email design and an error\n // body each carry theirs, as the site inventory reads them.\n if (data['deletedAt'] || text(data['kind'])) continue\n const slug = text(data['slug'])\n pages.push({\n id: doc.id,\n name: text(data['displayName']) || text(data['name']) || slug || doc.id,\n slug: slug.startsWith('/') ? slug : `/${slug}`,\n live: typeof routing[doc.id] === 'string',\n home: doc.id === homeId,\n })\n }\n const rank = (page: AssistSitePage) => (page.home ? 0 : page.live ? 1 : 2)\n return pages.sort((a, b) => rank(a) - rank(b) || a.slug.localeCompare(b.slug)).slice(0, ASSIST_SITE_PAGES_MAX)\n}\n\n/**\n * The model's call, held to the site's own pages: a page it named that the\n * site does not have, or a change with nothing in it, proposes nothing.\n */\nexport function resolveAssistPageChange(\n input: Record<string, unknown> | null,\n scope: { hostId: string; pages: readonly AssistSitePage[] },\n): AssistPageChangeProposal | null {\n if (!input) return null\n const page = scope.pages.find((one) => one.id === text(input['page']))\n if (!page) return null\n const change = assistBuildBriefWithoutGaps(text(input['change']))\n if (!change) return null\n const summary = text(input['summary'])\n return {\n id: ASSIST_PAGE_CHANGE_ACTION_ID,\n hostId: scope.hostId,\n pageId: page.id,\n pageName: page.name,\n brief: change.slice(0, AI_JOB_BRIEF_MAX_CHARS),\n summary: (summary || `Change ${page.name}`).slice(0, ASSIST_PAGE_CHANGE_SUMMARY_MAX_CHARS),\n ...(input['newVersion'] === true ? { newVersion: true } : {}),\n }\n}\n"],"names":["PLATFORM_BRAND_NAME","ASSIST_OUTCOMES_SENT","ASSIST_PAGE_CHANGE_ACTION_ID","ASSIST_PAGE_CHANGE_SUMMARY_MAX_CHARS","ASSIST_PAGE_CHANGE_TOOL_NAME","ASSIST_BUILD_TOOL_NAME","AI_JOB_BRIEF_MAX_CHARS","assistBuildBriefWithoutGaps","ASSIST_SITE_PAGES_MAX","STRING","type","assistPageChangeTool","name","description","inputSchema","properties","summary","page","change","newVersion","required","additionalProperties","strict","assistPageChangeBlock","join","ASSIST_OUTCOME_MAX_CHARS","parseAssistOutcomes","raw","Array","isArray","filter","outcome","map","replace","trim","slice","Boolean","assistOutcomesBlock","outcomes","length","assistSitePagesBlock","pages","options","versioning","id","slug","live","home","text","value","readAssistSitePages","firestore","hostId","hostDoc","host","collection","doc","screens","Promise","all","get","select","limit","routing","homeId","docs","data","push","startsWith","rank","sort","a","b","localeCompare","resolveAssistPageChange","input","scope","find","one","pageId","pageName","brief"],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { AiSiteImportDocument } from '../../jobs/ai-site-import-document';
|
|
18
|
+
import { type SiteImportFooter, type SiteImportNavTree } from '../../model/ai-site-import';
|
|
19
|
+
import type { SiteImportFidelitySource, SiteImportFidelityTree } from '../../model/ai-site-import-fidelity';
|
|
20
|
+
import { type SiteLayoutPageName } from './site-layout-recordings';
|
|
21
|
+
/** A recorded page as a wave's read keeps it and the import step hands it on: its blocks, its layout, its photos in the library. */
|
|
22
|
+
export declare function siteImportReplayDocument(name: SiteLayoutPageName): AiSiteImportDocument;
|
|
23
|
+
/** A recorded page built as the page step builds it: its source, its stored nodes, and what the page check said. */
|
|
24
|
+
export interface SiteImportReplayPage {
|
|
25
|
+
doc: AiSiteImportDocument;
|
|
26
|
+
source: SiteImportFidelitySource;
|
|
27
|
+
tree: SiteImportFidelityTree;
|
|
28
|
+
violations: unknown[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A recorded page built and checked as the page step stores it (AGL-4109):
|
|
32
|
+
* with a recorded design `answer`, as a main page is arranged, or with none,
|
|
33
|
+
* as a page is drawn from its layout alone; `forms` are the site's saved
|
|
34
|
+
* forms. `layout: false` builds it as a read with no layout was built, one
|
|
35
|
+
* section at each second-level heading, as the 10/29 import did.
|
|
36
|
+
*/
|
|
37
|
+
export declare function siteImportReplayPage(name: SiteLayoutPageName, options?: {
|
|
38
|
+
answer?: Record<string, unknown> | null;
|
|
39
|
+
forms?: Array<{
|
|
40
|
+
id: string;
|
|
41
|
+
name: string;
|
|
42
|
+
}>;
|
|
43
|
+
layout?: boolean;
|
|
44
|
+
}): SiteImportReplayPage;
|
|
45
|
+
/**
|
|
46
|
+
* The design answer recorded for aglyn.com's Home, in the tool's shape: a
|
|
47
|
+
* split hero, ruled cards, a dark band asked where the source drew its own
|
|
48
|
+
* white, a style of pictures for a group that has none. The compile spec
|
|
49
|
+
* holds the same one.
|
|
50
|
+
*/
|
|
51
|
+
export declare const SITE_IMPORT_REPLAY_HOME_ANSWER: Readonly<Record<string, unknown>>;
|
|
52
|
+
/** The recorded home pages a header and footer is drawn from. */
|
|
53
|
+
export type SiteImportReplayHome = Extract<SiteLayoutPageName, 'home' | 'pottery'>;
|
|
54
|
+
/** A recorded home page's header and footer as the frame step draws them: the source's menu and footer, the stored nodes, the check's word. */
|
|
55
|
+
export interface SiteImportReplayFrame {
|
|
56
|
+
nav: SiteImportNavTree | null;
|
|
57
|
+
footer: SiteImportFooter | null;
|
|
58
|
+
tree: SiteImportFidelityTree;
|
|
59
|
+
violations: unknown[];
|
|
60
|
+
}
|
|
61
|
+
/** A recorded home page's header and footer, drawn with no model as the layout step stores them (AGL-4108). */
|
|
62
|
+
export declare function siteImportReplayFrame(name: SiteImportReplayHome): SiteImportReplayFrame;
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
3
|
+
/**
|
|
4
|
+
* @license
|
|
5
|
+
* Copyright 2026 Aglyn LLC
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
* you may not use this file except in compliance with the License.
|
|
9
|
+
* You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/ /**
|
|
19
|
+
* The recorded pages brought in again, offline (AGL-4109), for the fidelity
|
|
20
|
+
* spec and the fidelity report: each page of `site-layout/` read by the
|
|
21
|
+
* reader, built by the import's compile and held to its page check as the
|
|
22
|
+
* page step stores it, and each recorded home page's header and footer drawn
|
|
23
|
+
* as the frame step draws them, with no model. Every page the source links
|
|
24
|
+
* on its own site is a page the import plans, as on a whole-site import. No
|
|
25
|
+
* live AI: a design answer here is a recorded one, in the shape the tool
|
|
26
|
+
* answers.
|
|
27
|
+
*/ import { aiImportedPageCheck } from "../../jobs/ai-job-page-language.js";
|
|
28
|
+
import { aiSiteImportFrameJob, aiSiteImportHomeOf } from "../../jobs/ai-job-import-step.js";
|
|
29
|
+
import { aiLayoutSiteBrandOf, aiLayoutSiteFrameOf, aiLayoutSourceFrameBuild } from "../../jobs/ai-job-layout-language.js";
|
|
30
|
+
import { aiLayoutChecks } from "../../jobs/ai-job-layout-step.js";
|
|
31
|
+
import { aiSiteImportSectionNames } from "../../jobs/ai-site-import-compile.js";
|
|
32
|
+
import { aiSiteImportLedger } from "../../model/ai-site-import.js";
|
|
33
|
+
import { emptyAiSiteInventory } from "../../model/ai-site-inventory.js";
|
|
34
|
+
import { extractSiteImportPage } from "../site-import-html.js";
|
|
35
|
+
import { siteImportLayout, siteImportLookOf } from "../site-import-layout.js";
|
|
36
|
+
import { siteImportFooterColumns, siteImportNavTree } from "../site-import-nav.js";
|
|
37
|
+
import { siteImportPageRead } from "../site-import-page.js";
|
|
38
|
+
import { SITE_LAYOUT_PAGES, siteLayoutRecording, siteLayoutSameSite } from "./site-layout-recordings.js";
|
|
39
|
+
/** The page's same-site addresses, each a page the import plans: every page a link may go to. */ function plannedLinks(blocks, origin) {
|
|
40
|
+
const paths = new Set();
|
|
41
|
+
for (const block of blocks){
|
|
42
|
+
if (block.kind !== 'link') continue;
|
|
43
|
+
try {
|
|
44
|
+
const url = new URL(block.href);
|
|
45
|
+
const path = url.pathname.replace(/\/+$/, '') || '/';
|
|
46
|
+
if (url.origin === origin && path !== '/') paths.add(path);
|
|
47
|
+
} catch (unused) {
|
|
48
|
+
// Not an address.
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return [
|
|
52
|
+
...paths
|
|
53
|
+
].map((path)=>({
|
|
54
|
+
path,
|
|
55
|
+
id: `p${path.replace(/[^a-z0-9]+/gi, '-')}`
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
/** A recorded page as a wave's read keeps it and the import step hands it on: its blocks, its layout, its photos in the library. */ export function siteImportReplayDocument(name) {
|
|
59
|
+
var _read_blocks;
|
|
60
|
+
const { url } = SITE_LAYOUT_PAGES[name];
|
|
61
|
+
const page = extractSiteImportPage(siteLayoutRecording(name), url, siteLayoutSameSite(name), {
|
|
62
|
+
structure: true
|
|
63
|
+
});
|
|
64
|
+
const layout = siteImportLayout(page);
|
|
65
|
+
const path = new URL(url).pathname;
|
|
66
|
+
const read = siteImportPageRead(_extends({}, page, {
|
|
67
|
+
layout,
|
|
68
|
+
url,
|
|
69
|
+
path,
|
|
70
|
+
foundBy: path === '/' ? 'home' : 'sitemap',
|
|
71
|
+
bytes: 0
|
|
72
|
+
}), 'now');
|
|
73
|
+
const blocks = (_read_blocks = read.blocks) != null ? _read_blocks : [];
|
|
74
|
+
const origin = new URL(url).origin;
|
|
75
|
+
const pictures = [
|
|
76
|
+
...new Set(blocks.flatMap((block)=>block.kind === 'image' ? [
|
|
77
|
+
block.url
|
|
78
|
+
] : []))
|
|
79
|
+
];
|
|
80
|
+
return _extends({
|
|
81
|
+
build: 'arranged',
|
|
82
|
+
title: read.title,
|
|
83
|
+
url,
|
|
84
|
+
blocks,
|
|
85
|
+
photos: pictures.map((from, index)=>({
|
|
86
|
+
mediaId: `m${index}`,
|
|
87
|
+
src: `media:host-import/m${index}`,
|
|
88
|
+
from,
|
|
89
|
+
width: 1600,
|
|
90
|
+
height: 900
|
|
91
|
+
})),
|
|
92
|
+
links: plannedLinks(blocks, origin),
|
|
93
|
+
origins: [
|
|
94
|
+
origin,
|
|
95
|
+
origin.replace('://', '://www.')
|
|
96
|
+
]
|
|
97
|
+
}, read.layout ? {
|
|
98
|
+
layout: read.layout
|
|
99
|
+
} : {});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* A recorded page built and checked as the page step stores it (AGL-4109):
|
|
103
|
+
* with a recorded design `answer`, as a main page is arranged, or with none,
|
|
104
|
+
* as a page is drawn from its layout alone; `forms` are the site's saved
|
|
105
|
+
* forms. `layout: false` builds it as a read with no layout was built, one
|
|
106
|
+
* section at each second-level heading, as the 10/29 import did.
|
|
107
|
+
*/ export function siteImportReplayPage(name, options = {}) {
|
|
108
|
+
var _options_forms, _options_answer, _ref;
|
|
109
|
+
var _result_value;
|
|
110
|
+
const read = siteImportReplayDocument(name);
|
|
111
|
+
const { layout } = read, rest = _object_without_properties_loose(read, [
|
|
112
|
+
"layout"
|
|
113
|
+
]);
|
|
114
|
+
const doc = options.layout === false ? rest : read;
|
|
115
|
+
const names = aiSiteImportSectionNames(doc);
|
|
116
|
+
const sectionIds = names.map((_, index)=>`sec-${index}`);
|
|
117
|
+
const screen = {
|
|
118
|
+
title: doc.title,
|
|
119
|
+
slug: name === 'home' ? '/' : `/${name}`,
|
|
120
|
+
sections: names.map((section)=>({
|
|
121
|
+
name: section,
|
|
122
|
+
uses: [],
|
|
123
|
+
items: 0
|
|
124
|
+
}))
|
|
125
|
+
};
|
|
126
|
+
const forms = (_options_forms = options.forms) != null ? _options_forms : [];
|
|
127
|
+
const targets = {
|
|
128
|
+
pageId: 'p0',
|
|
129
|
+
pages: [],
|
|
130
|
+
homeIds: [],
|
|
131
|
+
forms,
|
|
132
|
+
formPageId: null,
|
|
133
|
+
components: [],
|
|
134
|
+
facts: ''
|
|
135
|
+
};
|
|
136
|
+
const check = aiImportedPageCheck({
|
|
137
|
+
screen: screen,
|
|
138
|
+
sectionIds,
|
|
139
|
+
targets: targets,
|
|
140
|
+
context: {
|
|
141
|
+
screenIds: doc.links.map((link)=>link.id),
|
|
142
|
+
formIds: forms.map((form)=>form.id),
|
|
143
|
+
componentIds: [],
|
|
144
|
+
codeBuilt: true
|
|
145
|
+
},
|
|
146
|
+
design: {
|
|
147
|
+
kind: 'business',
|
|
148
|
+
seed: 7,
|
|
149
|
+
home: name === 'home'
|
|
150
|
+
},
|
|
151
|
+
doc
|
|
152
|
+
});
|
|
153
|
+
const result = check((_options_answer = options.answer) != null ? _options_answer : null);
|
|
154
|
+
const nodes = (_ref = (_result_value = result.value) == null ? void 0 : _result_value.nodes) != null ? _ref : {};
|
|
155
|
+
return {
|
|
156
|
+
doc,
|
|
157
|
+
source: {
|
|
158
|
+
layout: layout,
|
|
159
|
+
blocks: read.blocks,
|
|
160
|
+
photos: read.photos
|
|
161
|
+
},
|
|
162
|
+
tree: {
|
|
163
|
+
rootId: '_@_',
|
|
164
|
+
nodes
|
|
165
|
+
},
|
|
166
|
+
violations: result.violations
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The design answer recorded for aglyn.com's Home, in the tool's shape: a
|
|
171
|
+
* split hero, ruled cards, a dark band asked where the source drew its own
|
|
172
|
+
* white, a style of pictures for a group that has none. The compile spec
|
|
173
|
+
* holds the same one.
|
|
174
|
+
*/ export const SITE_IMPORT_REPLAY_HOME_ANSWER = {
|
|
175
|
+
sections: [
|
|
176
|
+
{
|
|
177
|
+
hero: 'split',
|
|
178
|
+
band: 'source',
|
|
179
|
+
align: 'start',
|
|
180
|
+
groups: []
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
hero: 'none',
|
|
184
|
+
band: 'source',
|
|
185
|
+
align: 'source',
|
|
186
|
+
groups: [
|
|
187
|
+
{
|
|
188
|
+
kind: 'source',
|
|
189
|
+
style: 'ruled'
|
|
190
|
+
}
|
|
191
|
+
]
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
hero: 'none',
|
|
195
|
+
band: 'dark',
|
|
196
|
+
align: 'source',
|
|
197
|
+
groups: []
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
hero: 'none',
|
|
201
|
+
band: 'source',
|
|
202
|
+
align: 'source',
|
|
203
|
+
groups: [
|
|
204
|
+
{
|
|
205
|
+
kind: 'source',
|
|
206
|
+
style: 'pictures'
|
|
207
|
+
}
|
|
208
|
+
]
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
};
|
|
212
|
+
const NOW = new Date('2026-10-29T18:00:00.000Z');
|
|
213
|
+
/** A recorded home page as the crawl keeps it: its menu, footer and look read as the source builds them. */ function crawledHome(name) {
|
|
214
|
+
const { url } = SITE_LAYOUT_PAGES[name];
|
|
215
|
+
const extract = extractSiteImportPage(siteLayoutRecording(name), url, siteLayoutSameSite(name), {
|
|
216
|
+
structure: true
|
|
217
|
+
});
|
|
218
|
+
const source = extract;
|
|
219
|
+
const { structure: _structure, loginForm: _loginForm } = extract, rest = _object_without_properties_loose(extract, [
|
|
220
|
+
"structure",
|
|
221
|
+
"loginForm"
|
|
222
|
+
]);
|
|
223
|
+
return _extends({}, rest, {
|
|
224
|
+
url,
|
|
225
|
+
path: '/',
|
|
226
|
+
foundBy: 'home',
|
|
227
|
+
bytes: 0,
|
|
228
|
+
nav: siteImportNavTree(source),
|
|
229
|
+
footer: siteImportFooterColumns(source),
|
|
230
|
+
look: siteImportLookOf(source)
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/** A site planned for its first wave: Home, and two pages of its own. */ function replayState(name) {
|
|
234
|
+
const origin = new URL(SITE_LAYOUT_PAGES[name].url).origin;
|
|
235
|
+
const paths = name === 'home' ? [
|
|
236
|
+
'/',
|
|
237
|
+
'/product',
|
|
238
|
+
'/pricing'
|
|
239
|
+
] : [
|
|
240
|
+
'/',
|
|
241
|
+
'/classes',
|
|
242
|
+
'/gallery'
|
|
243
|
+
];
|
|
244
|
+
const pages = paths.map((path, index)=>{
|
|
245
|
+
const slug = path === '/' ? '/' : path.split('/').filter(Boolean).pop();
|
|
246
|
+
return {
|
|
247
|
+
slot: `p${index}`,
|
|
248
|
+
url: `${origin}${path}`,
|
|
249
|
+
path,
|
|
250
|
+
title: slug,
|
|
251
|
+
group: 'main',
|
|
252
|
+
wave: 1,
|
|
253
|
+
draftId: `draft${index}`,
|
|
254
|
+
slug,
|
|
255
|
+
words: 300,
|
|
256
|
+
images: 0
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
return {
|
|
260
|
+
domain: new URL(origin).hostname.replace(/^www\./, ''),
|
|
261
|
+
siteName: 'import-check',
|
|
262
|
+
ownership: {
|
|
263
|
+
method: 'dns',
|
|
264
|
+
provenAt: 1,
|
|
265
|
+
detail: null
|
|
266
|
+
},
|
|
267
|
+
readAt: NOW.toISOString(),
|
|
268
|
+
pageLimit: null,
|
|
269
|
+
existingPages: 0,
|
|
270
|
+
found: pages.length,
|
|
271
|
+
stoppedBy: null,
|
|
272
|
+
groups: [],
|
|
273
|
+
pages,
|
|
274
|
+
waves: [
|
|
275
|
+
{
|
|
276
|
+
index: 1,
|
|
277
|
+
label: 'Wave 1',
|
|
278
|
+
group: 'main',
|
|
279
|
+
slots: pages.map((page)=>page.slot),
|
|
280
|
+
frame: true,
|
|
281
|
+
status: 'running',
|
|
282
|
+
startedAt: NOW.toISOString(),
|
|
283
|
+
finishedAt: null
|
|
284
|
+
}
|
|
285
|
+
],
|
|
286
|
+
leftOut: [],
|
|
287
|
+
leftOutCount: {},
|
|
288
|
+
frame: {
|
|
289
|
+
draftId: 'frameDraft'
|
|
290
|
+
},
|
|
291
|
+
home: aiSiteImportHomeOf({
|
|
292
|
+
pages: [
|
|
293
|
+
crawledHome(name)
|
|
294
|
+
]
|
|
295
|
+
}),
|
|
296
|
+
look: {
|
|
297
|
+
at: NOW.toISOString(),
|
|
298
|
+
theme: 'applied',
|
|
299
|
+
colors: [],
|
|
300
|
+
logo: name === 'home' ? {
|
|
301
|
+
mediaId: 'm1',
|
|
302
|
+
src: 'media:host-import/m1',
|
|
303
|
+
width: 632,
|
|
304
|
+
height: 192
|
|
305
|
+
} : null,
|
|
306
|
+
seo: true,
|
|
307
|
+
seed: 7
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
/** A recorded home page's header and footer, drawn with no model as the layout step stores them (AGL-4108). */ export function siteImportReplayFrame(name) {
|
|
312
|
+
var _frameJob_plan, _ref, _ref1, _ref2, _ref3;
|
|
313
|
+
var _frameJob_inputs, _state_home, _built_value, _state_home1, _state_home2;
|
|
314
|
+
const state = replayState(name);
|
|
315
|
+
const job = {
|
|
316
|
+
$id: 'importJob',
|
|
317
|
+
orgId: 'org-replay',
|
|
318
|
+
hostId: 'host-replay',
|
|
319
|
+
kind: 'import',
|
|
320
|
+
status: 'running',
|
|
321
|
+
brief: `Bring in ${state.domain}`,
|
|
322
|
+
inputs: {
|
|
323
|
+
domain: state.domain
|
|
324
|
+
},
|
|
325
|
+
steps: [],
|
|
326
|
+
outputs: [],
|
|
327
|
+
createdBy: 'uid-replay',
|
|
328
|
+
createdAt: NOW,
|
|
329
|
+
updatedAt: NOW,
|
|
330
|
+
expiresAt: NOW
|
|
331
|
+
};
|
|
332
|
+
const frameJob = aiSiteImportFrameJob(job, state, state.waves[0], aiSiteImportLedger(state));
|
|
333
|
+
const home = state.pages[0];
|
|
334
|
+
const built = aiLayoutSourceFrameBuild({
|
|
335
|
+
siteName: String((_frameJob_inputs = frameJob.inputs) == null ? void 0 : _frameJob_inputs['businessName']),
|
|
336
|
+
homeId: home.draftId,
|
|
337
|
+
pages: [
|
|
338
|
+
{
|
|
339
|
+
id: home.draftId,
|
|
340
|
+
label: 'Home',
|
|
341
|
+
slug: '/'
|
|
342
|
+
}
|
|
343
|
+
],
|
|
344
|
+
targets: {
|
|
345
|
+
pageId: null,
|
|
346
|
+
pages: [],
|
|
347
|
+
homeIds: [
|
|
348
|
+
home.draftId
|
|
349
|
+
],
|
|
350
|
+
forms: [],
|
|
351
|
+
formPageId: null,
|
|
352
|
+
components: [],
|
|
353
|
+
facts: ''
|
|
354
|
+
},
|
|
355
|
+
extend: aiLayoutChecks(emptyAiSiteInventory('host-replay'), (_frameJob_plan = frameJob.plan) != null ? _frameJob_plan : null, [
|
|
356
|
+
frameJob.brief,
|
|
357
|
+
...(_ref = (_state_home = state.home) == null ? void 0 : _state_home.facts.lines) != null ? _ref : []
|
|
358
|
+
].join('\n')),
|
|
359
|
+
brand: aiLayoutSiteBrandOf(frameJob.inputs),
|
|
360
|
+
source: aiLayoutSiteFrameOf(frameJob.inputs)
|
|
361
|
+
});
|
|
362
|
+
const nodes = (_ref1 = (_built_value = built.value) == null ? void 0 : _built_value.nodes) != null ? _ref1 : {};
|
|
363
|
+
return {
|
|
364
|
+
nav: (_ref2 = (_state_home1 = state.home) == null ? void 0 : _state_home1.nav) != null ? _ref2 : null,
|
|
365
|
+
footer: (_ref3 = (_state_home2 = state.home) == null ? void 0 : _state_home2.footer) != null ? _ref3 : null,
|
|
366
|
+
tree: {
|
|
367
|
+
rootId: '_@_',
|
|
368
|
+
nodes
|
|
369
|
+
},
|
|
370
|
+
violations: built.violations
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
//# sourceMappingURL=site-import-fidelity-replay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../libs/plugins/ai/src/lib/site-import/fixtures/site-import-fidelity-replay.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 * The recorded pages brought in again, offline (AGL-4109), for the fidelity\n * spec and the fidelity report: each page of `site-layout/` read by the\n * reader, built by the import's compile and held to its page check as the\n * page step stores it, and each recorded home page's header and footer drawn\n * as the frame step draws them, with no model. Every page the source links\n * on its own site is a page the import plans, as on a whole-site import. No\n * live AI: a design answer here is a recorded one, in the shape the tool\n * answers.\n */\n\nimport { aiImportedPageCheck } from '../../jobs/ai-job-page-language'\nimport { aiSiteImportFrameJob, aiSiteImportHomeOf } from '../../jobs/ai-job-import-step'\nimport { aiLayoutSiteBrandOf, aiLayoutSiteFrameOf, aiLayoutSourceFrameBuild } from '../../jobs/ai-job-layout-language'\nimport { aiLayoutChecks } from '../../jobs/ai-job-layout-step'\nimport { aiSiteImportSectionNames } from '../../jobs/ai-site-import-compile'\nimport type { AiSiteImportDocument } from '../../jobs/ai-site-import-document'\nimport type { AiLayoutFrameSource } from '../../layout-language/ai-layout-frame-source'\nimport type { AiJob } from '../../model/ai-jobs.types'\nimport { aiSiteImportLedger, type AiSiteImportPage, type AiSiteImportState, type SiteImportFooter, type SiteImportNavTree } from '../../model/ai-site-import'\nimport type { SiteImportFidelitySource, SiteImportFidelityTree } from '../../model/ai-site-import-fidelity'\nimport { emptyAiSiteInventory } from '../../model/ai-site-inventory'\nimport type { SiteImportInventory } from '../site-import-crawl'\nimport { extractSiteImportPage, type SiteImportBlock } from '../site-import-html'\nimport { siteImportLayout, siteImportLookOf } from '../site-import-layout'\nimport { siteImportFooterColumns, siteImportNavTree, type SiteImportFrameSource } from '../site-import-nav'\nimport { siteImportPageRead } from '../site-import-page'\nimport { SITE_LAYOUT_PAGES, siteLayoutRecording, siteLayoutSameSite, type SiteLayoutPageName } from './site-layout-recordings'\n\n/** The page's same-site addresses, each a page the import plans: every page a link may go to. */\nfunction plannedLinks(blocks: readonly SiteImportBlock[], origin: string): Array<{ path: string; id: string }> {\n const paths = new Set<string>()\n for (const block of blocks) {\n if (block.kind !== 'link') continue\n try {\n const url = new URL(block.href)\n const path = url.pathname.replace(/\\/+$/, '') || '/'\n if (url.origin === origin && path !== '/') paths.add(path)\n } catch {\n // Not an address.\n }\n }\n return [...paths].map((path) => ({ path, id: `p${path.replace(/[^a-z0-9]+/gi, '-')}` }))\n}\n\n/** A recorded page as a wave's read keeps it and the import step hands it on: its blocks, its layout, its photos in the library. */\nexport function siteImportReplayDocument(name: SiteLayoutPageName): AiSiteImportDocument {\n const { url } = SITE_LAYOUT_PAGES[name]\n const page = extractSiteImportPage(siteLayoutRecording(name), url, siteLayoutSameSite(name), { structure: true })\n const layout = siteImportLayout(page as Parameters<typeof siteImportLayout>[0])\n const path = new URL(url).pathname\n const read = siteImportPageRead({ ...page, layout, url, path, foundBy: path === '/' ? 'home' : 'sitemap', bytes: 0 } as never, 'now')\n const blocks = read.blocks ?? []\n const origin = new URL(url).origin\n const pictures = [...new Set(blocks.flatMap((block) => (block.kind === 'image' ? [block.url] : [])))]\n return {\n build: 'arranged',\n title: read.title,\n url,\n blocks,\n photos: pictures.map((from, index) => ({ mediaId: `m${index}`, src: `media:host-import/m${index}`, from, width: 1600, height: 900 })),\n links: plannedLinks(blocks, origin),\n origins: [origin, origin.replace('://', '://www.')],\n ...(read.layout ? { layout: read.layout } : {}),\n }\n}\n\n/** A recorded page built as the page step builds it: its source, its stored nodes, and what the page check said. */\nexport interface SiteImportReplayPage {\n doc: AiSiteImportDocument\n source: SiteImportFidelitySource\n tree: SiteImportFidelityTree\n violations: unknown[]\n}\n\n/**\n * A recorded page built and checked as the page step stores it (AGL-4109):\n * with a recorded design `answer`, as a main page is arranged, or with none,\n * as a page is drawn from its layout alone; `forms` are the site's saved\n * forms. `layout: false` builds it as a read with no layout was built, one\n * section at each second-level heading, as the 10/29 import did.\n */\nexport function siteImportReplayPage(\n name: SiteLayoutPageName,\n options: { answer?: Record<string, unknown> | null; forms?: Array<{ id: string; name: string }>; layout?: boolean } = {},\n): SiteImportReplayPage {\n const read = siteImportReplayDocument(name)\n const { layout, ...rest } = read\n const doc: AiSiteImportDocument = options.layout === false ? rest : read\n const names = aiSiteImportSectionNames(doc)\n const sectionIds = names.map((_, index) => `sec-${index}`)\n const screen = { title: doc.title, slug: name === 'home' ? '/' : `/${name}`, sections: names.map((section) => ({ name: section, uses: [] as string[], items: 0 })) }\n const forms = options.forms ?? []\n const targets = { pageId: 'p0', pages: [], homeIds: [], forms, formPageId: null, components: [], facts: '' }\n const check = aiImportedPageCheck({\n screen: screen as never,\n sectionIds,\n targets: targets as never,\n context: { screenIds: doc.links.map((link) => link.id), formIds: forms.map((form) => form.id), componentIds: [], codeBuilt: true },\n design: { kind: 'business', seed: 7, home: name === 'home' } as never,\n doc,\n })\n const result = check(options.answer ?? null)\n const nodes = (result.value?.nodes ?? {}) as unknown as SiteImportFidelityTree['nodes']\n return {\n doc,\n source: { layout: layout as NonNullable<typeof layout>, blocks: read.blocks, photos: read.photos },\n tree: { rootId: '_@_', nodes },\n violations: result.violations,\n }\n}\n\n/**\n * The design answer recorded for aglyn.com's Home, in the tool's shape: a\n * split hero, ruled cards, a dark band asked where the source drew its own\n * white, a style of pictures for a group that has none. The compile spec\n * holds the same one.\n */\nexport const SITE_IMPORT_REPLAY_HOME_ANSWER: Readonly<Record<string, unknown>> = {\n sections: [\n { hero: 'split', band: 'source', align: 'start', groups: [] },\n { hero: 'none', band: 'source', align: 'source', groups: [{ kind: 'source', style: 'ruled' }] },\n { hero: 'none', band: 'dark', align: 'source', groups: [] },\n { hero: 'none', band: 'source', align: 'source', groups: [{ kind: 'source', style: 'pictures' }] },\n ],\n}\n\n/** The recorded home pages a header and footer is drawn from. */\nexport type SiteImportReplayHome = Extract<SiteLayoutPageName, 'home' | 'pottery'>\n\nconst NOW = new Date('2026-10-29T18:00:00.000Z')\n\n/** A recorded home page as the crawl keeps it: its menu, footer and look read as the source builds them. */\nfunction crawledHome(name: SiteImportReplayHome): SiteImportInventory['pages'][number] {\n const { url } = SITE_LAYOUT_PAGES[name]\n const extract = extractSiteImportPage(siteLayoutRecording(name), url, siteLayoutSameSite(name), { structure: true })\n const source = extract as SiteImportFrameSource\n const { structure: _structure, loginForm: _loginForm, ...rest } = extract\n return { ...rest, url, path: '/', foundBy: 'home', bytes: 0, nav: siteImportNavTree(source), footer: siteImportFooterColumns(source), look: siteImportLookOf(source) }\n}\n\n/** A site planned for its first wave: Home, and two pages of its own. */\nfunction replayState(name: SiteImportReplayHome): AiSiteImportState {\n const origin = new URL(SITE_LAYOUT_PAGES[name].url).origin\n const paths = name === 'home' ? ['/', '/product', '/pricing'] : ['/', '/classes', '/gallery']\n const pages: AiSiteImportPage[] = paths.map((path, index) => {\n const slug = path === '/' ? '/' : (path.split('/').filter(Boolean).pop() as string)\n return { slot: `p${index}`, url: `${origin}${path}`, path, title: slug, group: 'main', wave: 1, draftId: `draft${index}`, slug, words: 300, images: 0 }\n })\n return {\n domain: new URL(origin).hostname.replace(/^www\\./, ''),\n siteName: 'import-check',\n ownership: { method: 'dns', provenAt: 1, detail: null },\n readAt: NOW.toISOString(),\n pageLimit: null,\n existingPages: 0,\n found: pages.length,\n stoppedBy: null,\n groups: [],\n pages,\n waves: [{ index: 1, label: 'Wave 1', group: 'main', slots: pages.map((page) => page.slot), frame: true, status: 'running', startedAt: NOW.toISOString(), finishedAt: null }],\n leftOut: [],\n leftOutCount: {},\n frame: { draftId: 'frameDraft' },\n home: aiSiteImportHomeOf({ pages: [crawledHome(name)] }),\n look: {\n at: NOW.toISOString(),\n theme: 'applied',\n colors: [],\n logo: name === 'home' ? { mediaId: 'm1', src: 'media:host-import/m1', width: 632, height: 192 } : null,\n seo: true,\n seed: 7,\n },\n }\n}\n\n/** A recorded home page's header and footer as the frame step draws them: the source's menu and footer, the stored nodes, the check's word. */\nexport interface SiteImportReplayFrame {\n nav: SiteImportNavTree | null\n footer: SiteImportFooter | null\n tree: SiteImportFidelityTree\n violations: unknown[]\n}\n\n/** A recorded home page's header and footer, drawn with no model as the layout step stores them (AGL-4108). */\nexport function siteImportReplayFrame(name: SiteImportReplayHome): SiteImportReplayFrame {\n const state = replayState(name)\n const job = {\n $id: 'importJob',\n orgId: 'org-replay',\n hostId: 'host-replay',\n kind: 'import',\n status: 'running',\n brief: `Bring in ${state.domain}`,\n inputs: { domain: state.domain },\n steps: [],\n outputs: [],\n createdBy: 'uid-replay',\n createdAt: NOW,\n updatedAt: NOW,\n expiresAt: NOW,\n } as unknown as AiJob\n const frameJob = aiSiteImportFrameJob(job, state, state.waves[0] as AiSiteImportState['waves'][number], aiSiteImportLedger(state))\n const home = state.pages[0] as AiSiteImportPage\n const built = aiLayoutSourceFrameBuild({\n siteName: String(frameJob.inputs?.['businessName']),\n homeId: home.draftId,\n pages: [{ id: home.draftId, label: 'Home', slug: '/' }],\n targets: { pageId: null, pages: [], homeIds: [home.draftId], forms: [], formPageId: null, components: [], facts: '' } as never,\n extend: aiLayoutChecks(emptyAiSiteInventory('host-replay'), frameJob.plan ?? null, [frameJob.brief, ...(state.home?.facts.lines ?? [])].join('\\n')),\n brand: aiLayoutSiteBrandOf(frameJob.inputs),\n source: aiLayoutSiteFrameOf(frameJob.inputs) as AiLayoutFrameSource,\n })\n const nodes = (built.value?.nodes ?? {}) as unknown as SiteImportFidelityTree['nodes']\n return { nav: state.home?.nav ?? null, footer: state.home?.footer ?? null, tree: { rootId: '_@_', nodes }, violations: built.violations }\n}\n"],"names":["aiImportedPageCheck","aiSiteImportFrameJob","aiSiteImportHomeOf","aiLayoutSiteBrandOf","aiLayoutSiteFrameOf","aiLayoutSourceFrameBuild","aiLayoutChecks","aiSiteImportSectionNames","aiSiteImportLedger","emptyAiSiteInventory","extractSiteImportPage","siteImportLayout","siteImportLookOf","siteImportFooterColumns","siteImportNavTree","siteImportPageRead","SITE_LAYOUT_PAGES","siteLayoutRecording","siteLayoutSameSite","plannedLinks","blocks","origin","paths","Set","block","kind","url","URL","href","path","pathname","replace","add","map","id","siteImportReplayDocument","name","read","page","structure","layout","foundBy","bytes","pictures","flatMap","build","title","photos","from","index","mediaId","src","width","height","links","origins","siteImportReplayPage","options","result","rest","doc","names","sectionIds","_","screen","slug","sections","section","uses","items","forms","targets","pageId","pages","homeIds","formPageId","components","facts","check","context","screenIds","link","formIds","form","componentIds","codeBuilt","design","seed","home","answer","nodes","value","source","tree","rootId","violations","SITE_IMPORT_REPLAY_HOME_ANSWER","hero","band","align","groups","style","NOW","Date","crawledHome","extract","_structure","loginForm","_loginForm","nav","footer","look","replayState","split","filter","Boolean","pop","slot","group","wave","draftId","words","images","domain","hostname","siteName","ownership","method","provenAt","detail","readAt","toISOString","pageLimit","existingPages","found","length","stoppedBy","waves","label","slots","frame","status","startedAt","finishedAt","leftOut","leftOutCount","at","theme","colors","logo","seo","siteImportReplayFrame","frameJob","state","built","job","$id","orgId","hostId","brief","inputs","steps","outputs","createdBy","createdAt","updatedAt","expiresAt","String","homeId","extend","plan","lines","join","brand"],"mappings":"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"}
|