@aglyn/plugins-ai 1.0.0-beta.233 → 1.0.0-beta.235
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.d.ts +25 -0
- package/src/lib/activity/ai-activity-actions.js +85 -4
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-brief-dialog.component.js +46 -2
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-campaign-create.component.js +1 -1
- package/src/lib/components/ai-campaign-create.component.js.map +1 -1
- package/src/lib/components/ai-credits-card.component.js +3 -3
- package/src/lib/components/ai-credits-card.component.js.map +1 -1
- package/src/lib/components/ai-crm-email-draft.component.js +1 -1
- package/src/lib/components/ai-crm-email-draft.component.js.map +1 -1
- package/src/lib/components/ai-crm-import-mapping.component.js +1 -1
- package/src/lib/components/ai-crm-import-mapping.component.js.map +1 -1
- package/src/lib/components/ai-crm-record-card.component.js +1 -1
- package/src/lib/components/ai-crm-record-card.component.js.map +1 -1
- package/src/lib/components/ai-describe-automation.component.js +4 -2
- package/src/lib/components/ai-describe-automation.component.js.map +1 -1
- package/src/lib/components/ai-describe-org-automation.component.js +1 -1
- package/src/lib/components/ai-describe-org-automation.component.js.map +1 -1
- package/src/lib/components/ai-experiment-cards.component.js +2 -2
- package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
- package/src/lib/components/ai-explain-automation.component.js +1 -1
- package/src/lib/components/ai-explain-automation.component.js.map +1 -1
- package/src/lib/components/ai-insight-card.component.js +3 -1
- package/src/lib/components/ai-insight-card.component.js.map +1 -1
- package/src/lib/components/ai-job-links.d.ts +6 -0
- package/src/lib/components/ai-job-links.js +9 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-jobs-list-page.component.d.ts +6 -1
- package/src/lib/components/ai-jobs-list-page.component.js +15 -2
- package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
- package/src/lib/components/ai-logic.component.js +5 -3
- package/src/lib/components/ai-logic.component.js.map +1 -1
- package/src/lib/components/ai-media-create.component.js +88 -16
- package/src/lib/components/ai-media-create.component.js.map +1 -1
- package/src/lib/components/ai-model-selector.component.js +8 -0
- package/src/lib/components/ai-model-selector.component.js.map +1 -1
- package/src/lib/components/ai-overlay-cards.component.js +2 -2
- package/src/lib/components/ai-overlay-cards.component.js.map +1 -1
- package/src/lib/components/ai-product-copy-card.component.js +2 -1
- package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
- package/src/lib/components/ai-products-hub-card.component.js +2 -1
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-revise-automation.component.js +1 -1
- package/src/lib/components/ai-revise-automation.component.js.map +1 -1
- package/src/lib/components/ai-seo-fields-card.component.js +7 -3
- package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
- package/src/lib/components/ai-site-batch-card.component.js +7 -4
- package/src/lib/components/ai-site-batch-card.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +48 -6
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-seo-start-card.component.js +7 -4
- package/src/lib/components/ai-site-seo-start-card.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.js +152 -36
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-theme-proposal-card.component.js +1 -0
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/components/ai-upsell-dialog.component.js +17 -3
- package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
- package/src/lib/components/ai-usage-strip.component.js +13 -2
- package/src/lib/components/ai-usage-strip.component.js.map +1 -1
- package/src/lib/components/assist-build-card.component.js +20 -3
- package/src/lib/components/assist-build-card.component.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +7 -1
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/billing-ai-allotments.component.js +2 -2
- package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
- package/src/lib/components/billing-ai-top-users.component.js +1 -0
- package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
- package/src/lib/components/billing-assist-overage-card.component.js +2 -2
- package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
- package/src/lib/components/host-ai-allotments.component.js +2 -2
- package/src/lib/components/host-ai-allotments.component.js.map +1 -1
- package/src/lib/components/member-ai-allotment-card.component.js +2 -2
- package/src/lib/components/member-ai-allotment-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-card.component.js +1 -0
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-conversations.component.d.ts +9 -3
- package/src/lib/components/staff-org-ai-conversations.component.js +174 -83
- package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -1
- package/src/lib/components/staff-user-ai-usage-card.component.js +2 -1
- package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
- package/src/lib/jobs/ai-job-admission.d.ts +2 -1
- package/src/lib/jobs/ai-job-admission.js.map +1 -1
- package/src/lib/jobs/ai-job-email-step.d.ts +39 -0
- package/src/lib/jobs/ai-job-email-step.js +116 -5
- package/src/lib/jobs/ai-job-email-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
- package/src/lib/jobs/ai-job-layout-language.js +6 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-site-pages.d.ts +14 -0
- package/src/lib/jobs/ai-job-layout-site-pages.js +43 -11
- package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +3 -2
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +26 -1
- package/src/lib/jobs/ai-job-page-language.js +75 -13
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +3 -0
- package/src/lib/jobs/ai-job-page-step.js +32 -7
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +34 -0
- package/src/lib/jobs/ai-job-plan-step.js +106 -14
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +81 -13
- package/src/lib/jobs/ai-job-site-content.js +210 -43
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +19 -0
- package/src/lib/jobs/ai-job-site-step.js +152 -51
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +8 -4
- package/src/lib/jobs/ai-jobs.js +19 -2
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +98 -0
- package/src/lib/jobs/ai-layout-stock-photos.js +248 -0
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -0
- package/src/lib/jobs/ai-site-publish.d.ts +11 -0
- package/src/lib/jobs/ai-site-publish.js +31 -1
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.d.ts +151 -0
- package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js +183 -0
- package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.d.ts +27 -0
- package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js +192 -0
- package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.d.ts +27 -0
- package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js +222 -0
- package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +37 -0
- package/src/lib/layout-language/ai-layout-compiler.js +1003 -19
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +114 -0
- package/src/lib/layout-language/ai-layout-design.js +209 -0
- package/src/lib/layout-language/ai-layout-design.js.map +1 -0
- package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
- package/src/lib/layout-language/ai-layout-frame.js +33 -8
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-gaps.js +18 -2
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.d.ts +2 -0
- package/src/lib/layout-language/ai-layout-language.js +15 -1
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/layout-language/ai-layout-links.d.ts +36 -0
- package/src/lib/layout-language/ai-layout-links.js +26 -2
- package/src/lib/layout-language/ai-layout-links.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +125 -0
- package/src/lib/layout-language/ai-layout-listings.js +187 -0
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-pictures.d.ts +11 -5
- package/src/lib/layout-language/ai-layout-pictures.js +80 -17
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-job-notice.d.ts +9 -1
- package/src/lib/model/ai-job-notice.js +23 -0
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
- package/src/lib/model/ai-plan-capabilities.js.map +1 -1
- package/src/lib/model/ai-site-build-progress.d.ts +12 -2
- package/src/lib/model/ai-site-build-progress.js +104 -15
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +236 -24
- package/src/lib/model/ai-site-job.js +269 -22
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.d.ts +30 -8
- package/src/lib/model/ai-site-kinds.js +735 -116
- package/src/lib/model/ai-site-kinds.js.map +1 -1
- package/src/lib/model/ai-site-look.d.ts +21 -0
- package/src/lib/model/ai-site-look.js +63 -14
- package/src/lib/model/ai-site-look.js.map +1 -1
- package/src/lib/model/ai-site-start-seo.d.ts +30 -0
- package/src/lib/model/ai-site-start-seo.js +132 -11
- package/src/lib/model/ai-site-start-seo.js.map +1 -1
- package/src/lib/model/ai-site-start.d.ts +2 -2
- package/src/lib/model/ai-site-start.js +3 -3
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/plugin.js +15 -3
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -1
- package/src/lib/runtime/ai-doctrine-validators.js +227 -4
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +5 -3
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.d.ts +2 -0
- package/src/lib/runtime/ai-palette.generated.js +31 -5
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/server/ai-admin-conversations.d.ts +9 -0
- package/src/lib/server/ai-admin-conversations.js +151 -9
- package/src/lib/server/ai-admin-conversations.js.map +1 -1
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/server/ai-jobs-route.js +15 -3
- package/src/lib/server/ai-jobs-route.js.map +1 -1
- package/src/lib/server/assist-edit.js +5 -2
- package/src/lib/server/assist-edit.js.map +1 -1
- package/src/lib/usage/free-ai-credits-left.d.ts +54 -0
- package/src/lib/usage/free-ai-credits-left.js +62 -0
- package/src/lib/usage/free-ai-credits-left.js.map +1 -0
- package/src/lib/usage/staff-org-ai-conversations.d.ts +23 -0
- package/src/lib/usage/staff-org-ai-conversations.js +45 -0
- package/src/lib/usage/staff-org-ai-conversations.js.map +1 -1
|
@@ -44,6 +44,8 @@ export interface AiLayoutSitePage {
|
|
|
44
44
|
id: string;
|
|
45
45
|
label: string;
|
|
46
46
|
slug: string;
|
|
47
|
+
/** The path of a destination that is no page, such as the blog a start writes (AGL-3660). */
|
|
48
|
+
href?: string;
|
|
47
49
|
}
|
|
48
50
|
/** Whether a slug is the site's root, the home page. */
|
|
49
51
|
export declare function aiLayoutIsHomeSlug(slug: string): boolean;
|
|
@@ -60,6 +62,18 @@ export declare function aiLayoutSitePagesOfPlan(screens: readonly AiBuildPlanScr
|
|
|
60
62
|
}): AiLayoutSitePage[];
|
|
61
63
|
/** The planned pages a layout unit's inputs carry; none for any other layout. */
|
|
62
64
|
export declare function aiLayoutSitePages(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutSitePage[];
|
|
65
|
+
/**
|
|
66
|
+
* The unit input naming the planned pages a start merged into its blog
|
|
67
|
+
* (AGL-3676): a page that stood in for it, whose links go to the blog.
|
|
68
|
+
*/
|
|
69
|
+
export declare const AI_LAYOUT_SITE_ALIASES_INPUT = "siteAliases";
|
|
70
|
+
/** A planned page merged into the blog: its id, linked as the blog's path, and the title it had. */
|
|
71
|
+
export interface AiLayoutSiteAlias extends AiLayoutSitePage {
|
|
72
|
+
href: string;
|
|
73
|
+
standsInFor: string;
|
|
74
|
+
}
|
|
75
|
+
/** The merged pages a unit's inputs carry; none for any other unit. */
|
|
76
|
+
export declare function aiLayoutSiteAliases(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutSiteAlias[];
|
|
63
77
|
/**
|
|
64
78
|
* What the layout's user turn says about those pages: that the platform
|
|
65
79
|
* writes the navigation, so the model builds none and leaves no empty element
|
|
@@ -65,17 +65,39 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
65
65
|
const pages = [];
|
|
66
66
|
for (const entry of raw){
|
|
67
67
|
if (!entry || typeof entry !== 'object') continue;
|
|
68
|
-
const { id, label, slug } = entry;
|
|
68
|
+
const { id, label, slug, href } = entry;
|
|
69
69
|
if (typeof id !== 'string' || !id || typeof label !== 'string' || !label.trim()) continue;
|
|
70
70
|
if (pages.some((page)=>page.id === id)) continue;
|
|
71
|
-
pages.push({
|
|
71
|
+
pages.push(_extends({
|
|
72
72
|
id,
|
|
73
73
|
label: label.trim(),
|
|
74
74
|
slug: typeof slug === 'string' ? slug : ''
|
|
75
|
-
})
|
|
75
|
+
}, typeof href === 'string' && /^\/[a-z0-9-]+(?:\/[a-z0-9-]+)*$/.test(href) ? {
|
|
76
|
+
href
|
|
77
|
+
} : {}));
|
|
76
78
|
}
|
|
77
79
|
return pages.slice(0, AI_LAYOUT_SITE_PAGES_MAX);
|
|
78
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* The unit input naming the planned pages a start merged into its blog
|
|
83
|
+
* (AGL-3676): a page that stood in for it, whose links go to the blog.
|
|
84
|
+
*/ export const AI_LAYOUT_SITE_ALIASES_INPUT = 'siteAliases';
|
|
85
|
+
/** The merged pages a unit's inputs carry; none for any other unit. */ export function aiLayoutSiteAliases(inputs) {
|
|
86
|
+
const raw = inputs == null ? void 0 : inputs[AI_LAYOUT_SITE_ALIASES_INPUT];
|
|
87
|
+
if (!Array.isArray(raw)) return [];
|
|
88
|
+
return raw.flatMap((entry)=>{
|
|
89
|
+
const { id, label, slug, href, standsInFor } = entry != null ? entry : {};
|
|
90
|
+
return typeof id === 'string' && id && typeof label === 'string' && typeof href === 'string' && /^\/[a-z0-9-]+$/.test(href) && typeof standsInFor === 'string' ? [
|
|
91
|
+
{
|
|
92
|
+
id,
|
|
93
|
+
label,
|
|
94
|
+
slug: typeof slug === 'string' ? slug : href,
|
|
95
|
+
href,
|
|
96
|
+
standsInFor
|
|
97
|
+
}
|
|
98
|
+
] : [];
|
|
99
|
+
});
|
|
100
|
+
}
|
|
79
101
|
/**
|
|
80
102
|
* What the layout's user turn says about those pages: that the platform
|
|
81
103
|
* writes the navigation, so the model builds none and leaves no empty element
|
|
@@ -170,20 +192,30 @@ const LINK_COMPONENTS = new Set([
|
|
|
170
192
|
const id = freshId(`aiNavLink_${page.id.replace(/[^A-Za-z0-9_]/g, '')}`);
|
|
171
193
|
nodes[id] = {
|
|
172
194
|
componentId: 'muiScreenLink',
|
|
173
|
-
props: {
|
|
174
|
-
children: page.label.slice(0, 40)
|
|
175
|
-
|
|
195
|
+
props: _extends({
|
|
196
|
+
children: page.label.slice(0, 40)
|
|
197
|
+
}, page.href ? {
|
|
198
|
+
href: page.href
|
|
199
|
+
} : {
|
|
200
|
+
screenId: page.id
|
|
201
|
+
}, {
|
|
176
202
|
renderAs: 'link',
|
|
177
203
|
color: 'inherit'
|
|
178
|
-
}
|
|
204
|
+
})
|
|
179
205
|
};
|
|
180
206
|
return id;
|
|
181
207
|
});
|
|
182
|
-
const linkedInHeader = new Set(header.map((id)=>nodes[id]).filter((node)=>LINK_COMPONENTS.has(String(node.componentId))).
|
|
183
|
-
var _node_props;
|
|
184
|
-
return
|
|
208
|
+
const linkedInHeader = new Set(header.map((id)=>nodes[id]).filter((node)=>LINK_COMPONENTS.has(String(node.componentId))).flatMap((node)=>{
|
|
209
|
+
var _node_props, _node_props1;
|
|
210
|
+
return [
|
|
211
|
+
(_node_props = node.props) == null ? void 0 : _node_props['screenId'],
|
|
212
|
+
(_node_props1 = node.props) == null ? void 0 : _node_props1['href']
|
|
213
|
+
];
|
|
185
214
|
}).filter((value)=>typeof value === 'string'));
|
|
186
|
-
const missing = pages.filter((page)
|
|
215
|
+
const missing = pages.filter((page)=>{
|
|
216
|
+
var _page_href;
|
|
217
|
+
return !linkedInHeader.has((_page_href = page.href) != null ? _page_href : page.id);
|
|
218
|
+
});
|
|
187
219
|
const navs = order.filter((id)=>elementOf(nodes[id]) === 'nav');
|
|
188
220
|
const headerNav = (_navs_find = navs.find((id)=>header.includes(id))) != null ? _navs_find : null;
|
|
189
221
|
if (missing.length) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-site-pages.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 { normalizeScreenSlug, SCREEN_ROOT_PATH } from '@aglyn/aglyn/app-utils/screen-route'\nimport type { AiBuildPlanScreen } from '../model/ai-build-plan'\n\n/**\n * The navigation of a layout a site scaffold builds before its pages (AGL-3596).\n *\n * A scaffold builds its layout FIRST, because every page renders inside it,\n * so when the layout is generated none of the site's pages exists yet: the\n * model cannot link them, and the layout instructions forbid inventing a page\n * id. A plan that gave the layout a nav region then left the model one way to\n * keep the promise — a nav element with nothing in it — which rule 16 refuses\n * as an empty container, twice, and the guided start stopped there.\n *\n * The scaffold does know the pages: their ids are minted when the plan is\n * kept (`AiBuildPlanScreen.id`), and each page unit writes its draft under\n * that id. So the layout unit is handed them (`sitePages`), and the header's\n * navigation is written by the platform rather than asked of the model: one\n * Page Link per planned page, inside the nav element the model left (filled,\n * never left empty) or a nav row the platform adds to the Toolbar Content.\n * The completed tree is what the doctrine checks and what is written, so rule\n * 16 holds unchanged — nothing it sees is empty — and the publish step, which\n * adds only the entries a header does not already carry, adds nothing twice.\n */\n\n/** The unit input a site layout's planned pages travel under. */\nexport const AI_LAYOUT_SITE_PAGES_INPUT = 'sitePages'\n\n/** The most pages the header links, as the layout instructions cap it. */\nexport const AI_LAYOUT_SITE_PAGES_MAX = 7\n\n/** A page the scaffold builds after the layout, as the layout links it. */\nexport interface AiLayoutSitePage {\n id: string\n label: string\n slug: string\n}\n\n/** Whether a slug is the site's root, the home page. */\nexport function aiLayoutIsHomeSlug(slug: string): boolean {\n return slug.replace(/^\\/+|\\/+$/g, '') === ''\n}\n\n/** A page that confirms what a visitor just did, which no navigation links. */\nconst CONFIRMATION = /thank|confirm|success|submitted/i\n\n/**\n * The pages a plan puts in the navigation, record templates aside, the home\n * page first whatever its `nav` flag says (AGL-3660): a guided start's plan\n * marked its Home `nav: false`, and the header linked only Contact. Every\n * other page keeps its `nav` flag, except in a guided start (`guided`), whose\n * pages are the ones the person asked for: each is linked, but a\n * confirmation page — a thank-you, a \"submitted\" — stays out.\n */\nexport function aiLayoutSitePagesOfPlan(\n screens: readonly AiBuildPlanScreen[],\n options: { guided?: boolean } = {},\n): AiLayoutSitePage[] {\n const linked = (screen: AiBuildPlanScreen) =>\n aiLayoutIsHomeSlug(screen.slug) ||\n (options.guided ? !CONFIRMATION.test(`${screen.slug} ${screen.title}`) : !!screen.nav)\n const pages = screens\n .filter((screen) => !screen.record && typeof screen.id === 'string' && screen.id && linked(screen))\n .map((screen) => ({ id: screen.id as string, label: screen.title.trim(), slug: screen.slug }))\n .filter((page) => page.label)\n return [...pages.filter((page) => aiLayoutIsHomeSlug(page.slug)), ...pages.filter((page) => !aiLayoutIsHomeSlug(page.slug))].slice(\n 0,\n AI_LAYOUT_SITE_PAGES_MAX,\n )\n}\n\n/** The planned pages a layout unit's inputs carry; none for any other layout. */\nexport function aiLayoutSitePages(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutSitePage[] {\n const raw = inputs?.[AI_LAYOUT_SITE_PAGES_INPUT]\n if (!Array.isArray(raw)) return []\n const pages: AiLayoutSitePage[] = []\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue\n const { id, label, slug } = entry as Record<string, unknown>\n if (typeof id !== 'string' || !id || typeof label !== 'string' || !label.trim()) continue\n if (pages.some((page) => page.id === id)) continue\n pages.push({ id, label: label.trim(), slug: typeof slug === 'string' ? slug : '' })\n }\n return pages.slice(0, AI_LAYOUT_SITE_PAGES_MAX)\n}\n\n/**\n * What the layout's user turn says about those pages: that the platform\n * writes the navigation, so the model builds none and leaves no empty element\n * for it. The same promise the completion below keeps.\n */\nexport function aiLayoutSitePagesLines(pages: readonly AiLayoutSitePage[]): string[] {\n if (!pages.length) return []\n return [\n `This site’s pages are built after this layout: ${pages\n .map((page) => `${page.label} (${screenPath(page.slug)})`)\n .join(', ')}.`,\n 'The platform writes the header’s navigation to those pages itself, as a nav row of Page Links inside the Toolbar Content. Build no navigation links to them and no nav element for them: leave no empty element where the links will go.',\n ]\n}\n\nfunction screenPath(slug: string): string {\n const normalized = normalizeScreenSlug(slug)\n if (!normalized || normalized === SCREEN_ROOT_PATH) return '/'\n return `/${normalized.replace(/^\\/+/, '')}`\n}\n\ntype RawNode = { componentId?: unknown; props?: Record<string, unknown>; nodes?: unknown; [key: string]: unknown }\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n Boolean(value) && typeof value === 'object' && !Array.isArray(value)\n\nconst childrenOf = (node: RawNode | undefined): string[] =>\n Array.isArray(node?.nodes) ? (node.nodes as unknown[]).filter((id): id is string => typeof id === 'string') : []\n\nconst elementOf = (node: RawNode | undefined): string => {\n const value = node?.props?.['component'] ?? node?.props?.['element']\n return typeof value === 'string' ? value : ''\n}\n\nconst LINK_COMPONENTS = new Set(['muiScreenLink', 'muiButton'])\n\n/**\n * The model's layout with the header's navigation written in: a Page Link to\n * every planned page the header does not already link, placed in the first\n * nav element above the Layout Slot, else in a nav row the platform appends\n * to the Toolbar Content; and every OTHER nav element the model left empty\n * given the same links, so no nav is ever empty. A link to the site's current\n * home page is pointed at the planned home, which replaces it when the site\n * is published. Anything that is not a node map, or has no planned pages, is\n * returned as it came, for the doctrine to judge.\n */\nexport function aiLayoutWithSitePages(\n tree: unknown,\n pages: readonly AiLayoutSitePage[],\n options: { homeScreenIds?: readonly string[] } = {},\n): unknown {\n if (!pages.length || !isRecord(tree) || !isRecord(tree['nodes'])) return tree\n const rootId = typeof tree['rootId'] === 'string' ? tree['rootId'] : null\n const source = tree['nodes'] as Record<string, unknown>\n if (!rootId || !isRecord(source[rootId])) return tree\n const nodes: Record<string, RawNode> = {}\n for (const [id, node] of Object.entries(source)) {\n if (!isRecord(node)) continue\n nodes[id] = {\n ...(node as RawNode),\n ...(isRecord(node['props']) ? { props: { ...(node['props'] as Record<string, unknown>) } } : {}),\n ...(Array.isArray(node['nodes']) ? { nodes: [...(node['nodes'] as unknown[])] } : {}),\n }\n }\n\n // Document order, and where the header ends: the Layout Slot.\n const order: string[] = []\n const seen = new Set<string>()\n const visit = (id: string) => {\n if (seen.has(id) || !nodes[id]) return\n seen.add(id)\n order.push(id)\n for (const child of childrenOf(nodes[id])) visit(child)\n }\n visit(rootId)\n const slotAt = order.findIndex((id) => nodes[id].componentId === 'layoutSlot')\n const header = slotAt === -1 ? order : order.slice(0, slotAt)\n\n // The site's current home is replaced by the planned one at publish.\n const plannedHome = pages.find((page) => screenPath(page.slug) === '/')\n const oldHomes = new Set(options.homeScreenIds ?? [])\n let changed = false\n if (plannedHome) {\n for (const id of order) {\n const node = nodes[id]\n const screenId = node.props?.['screenId']\n if (LINK_COMPONENTS.has(String(node.componentId)) && typeof screenId === 'string' && oldHomes.has(screenId)) {\n node.props = { ...node.props, screenId: plannedHome.id }\n changed = true\n }\n }\n }\n\n const used = new Set(Object.keys(nodes))\n const freshId = (base: string): string => {\n let id = base\n for (let n = 2; used.has(id); n += 1) id = `${base}${n}`\n used.add(id)\n return id\n }\n const linkNodes = (entries: readonly AiLayoutSitePage[]): string[] =>\n entries.map((page) => {\n const id = freshId(`aiNavLink_${page.id.replace(/[^A-Za-z0-9_]/g, '')}`)\n nodes[id] = {\n componentId: 'muiScreenLink',\n props: { children: page.label.slice(0, 40), screenId: page.id, renderAs: 'link', color: 'inherit' },\n }\n return id\n })\n\n const linkedInHeader = new Set(\n header\n .map((id) => nodes[id])\n .filter((node) => LINK_COMPONENTS.has(String(node.componentId)))\n .map((node) => node.props?.['screenId'])\n .filter((value): value is string => typeof value === 'string'),\n )\n const missing = pages.filter((page) => !linkedInHeader.has(page.id))\n const navs = order.filter((id) => elementOf(nodes[id]) === 'nav')\n const headerNav = navs.find((id) => header.includes(id)) ?? null\n\n if (missing.length) {\n if (headerNav) {\n nodes[headerNav].nodes = [...childrenOf(nodes[headerNav]), ...linkNodes(missing)]\n changed = true\n } else {\n const toolbar = header.find((id) => nodes[id].componentId === 'muiToolbar')\n if (toolbar) {\n const navId = freshId('aiSiteNav')\n nodes[navId] = {\n componentId: 'muiStack',\n props: { component: 'nav', direction: 'row', alignItems: 'center' },\n sx: { gap: 2 },\n nodes: linkNodes(missing),\n }\n nodes[toolbar].nodes = [...childrenOf(nodes[toolbar]), navId]\n changed = true\n }\n }\n }\n // A nav element the model left empty anywhere else — a footer's, say — links the same pages.\n for (const id of navs) {\n if (id === headerNav && missing.length) continue\n if (childrenOf(nodes[id]).some((child) => nodes[child])) continue\n nodes[id].nodes = linkNodes(pages)\n changed = true\n }\n return changed ? { ...tree, nodes } : tree\n}\n"],"names":["normalizeScreenSlug","SCREEN_ROOT_PATH","AI_LAYOUT_SITE_PAGES_INPUT","AI_LAYOUT_SITE_PAGES_MAX","aiLayoutIsHomeSlug","slug","replace","CONFIRMATION","aiLayoutSitePagesOfPlan","screens","options","linked","screen","guided","test","title","nav","pages","filter","record","id","map","label","trim","page","slice","aiLayoutSitePages","inputs","raw","Array","isArray","entry","some","push","aiLayoutSitePagesLines","length","screenPath","join","normalized","isRecord","value","Boolean","childrenOf","node","nodes","elementOf","props","LINK_COMPONENTS","Set","aiLayoutWithSitePages","tree","navs","rootId","source","Object","entries","order","seen","visit","has","add","child","slotAt","findIndex","componentId","header","plannedHome","find","oldHomes","homeScreenIds","changed","screenId","String","used","keys","freshId","base","n","linkNodes","children","renderAs","color","linkedInHeader","missing","headerNav","includes","toolbar","navId","component","direction","alignItems","sx","gap"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-site-pages.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 { normalizeScreenSlug, SCREEN_ROOT_PATH } from '@aglyn/aglyn/app-utils/screen-route'\nimport type { AiBuildPlanScreen } from '../model/ai-build-plan'\n\n/**\n * The navigation of a layout a site scaffold builds before its pages (AGL-3596).\n *\n * A scaffold builds its layout FIRST, because every page renders inside it,\n * so when the layout is generated none of the site's pages exists yet: the\n * model cannot link them, and the layout instructions forbid inventing a page\n * id. A plan that gave the layout a nav region then left the model one way to\n * keep the promise — a nav element with nothing in it — which rule 16 refuses\n * as an empty container, twice, and the guided start stopped there.\n *\n * The scaffold does know the pages: their ids are minted when the plan is\n * kept (`AiBuildPlanScreen.id`), and each page unit writes its draft under\n * that id. So the layout unit is handed them (`sitePages`), and the header's\n * navigation is written by the platform rather than asked of the model: one\n * Page Link per planned page, inside the nav element the model left (filled,\n * never left empty) or a nav row the platform adds to the Toolbar Content.\n * The completed tree is what the doctrine checks and what is written, so rule\n * 16 holds unchanged — nothing it sees is empty — and the publish step, which\n * adds only the entries a header does not already carry, adds nothing twice.\n */\n\n/** The unit input a site layout's planned pages travel under. */\nexport const AI_LAYOUT_SITE_PAGES_INPUT = 'sitePages'\n\n/** The most pages the header links, as the layout instructions cap it. */\nexport const AI_LAYOUT_SITE_PAGES_MAX = 7\n\n/** A page the scaffold builds after the layout, as the layout links it. */\nexport interface AiLayoutSitePage {\n id: string\n label: string\n slug: string\n /** The path of a destination that is no page, such as the blog a start writes (AGL-3660). */\n href?: string\n}\n\n/** Whether a slug is the site's root, the home page. */\nexport function aiLayoutIsHomeSlug(slug: string): boolean {\n return slug.replace(/^\\/+|\\/+$/g, '') === ''\n}\n\n/** A page that confirms what a visitor just did, which no navigation links. */\nconst CONFIRMATION = /thank|confirm|success|submitted/i\n\n/**\n * The pages a plan puts in the navigation, record templates aside, the home\n * page first whatever its `nav` flag says (AGL-3660): a guided start's plan\n * marked its Home `nav: false`, and the header linked only Contact. Every\n * other page keeps its `nav` flag, except in a guided start (`guided`), whose\n * pages are the ones the person asked for: each is linked, but a\n * confirmation page — a thank-you, a \"submitted\" — stays out.\n */\nexport function aiLayoutSitePagesOfPlan(\n screens: readonly AiBuildPlanScreen[],\n options: { guided?: boolean } = {},\n): AiLayoutSitePage[] {\n const linked = (screen: AiBuildPlanScreen) =>\n aiLayoutIsHomeSlug(screen.slug) ||\n (options.guided ? !CONFIRMATION.test(`${screen.slug} ${screen.title}`) : !!screen.nav)\n const pages = screens\n .filter((screen) => !screen.record && typeof screen.id === 'string' && screen.id && linked(screen))\n .map((screen) => ({ id: screen.id as string, label: screen.title.trim(), slug: screen.slug }))\n .filter((page) => page.label)\n return [...pages.filter((page) => aiLayoutIsHomeSlug(page.slug)), ...pages.filter((page) => !aiLayoutIsHomeSlug(page.slug))].slice(\n 0,\n AI_LAYOUT_SITE_PAGES_MAX,\n )\n}\n\n/** The planned pages a layout unit's inputs carry; none for any other layout. */\nexport function aiLayoutSitePages(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutSitePage[] {\n const raw = inputs?.[AI_LAYOUT_SITE_PAGES_INPUT]\n if (!Array.isArray(raw)) return []\n const pages: AiLayoutSitePage[] = []\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue\n const { id, label, slug, href } = entry as Record<string, unknown>\n if (typeof id !== 'string' || !id || typeof label !== 'string' || !label.trim()) continue\n if (pages.some((page) => page.id === id)) continue\n pages.push({\n id,\n label: label.trim(),\n slug: typeof slug === 'string' ? slug : '',\n // Only a path on this site, never an address elsewhere.\n ...(typeof href === 'string' && /^\\/[a-z0-9-]+(?:\\/[a-z0-9-]+)*$/.test(href) ? { href } : {}),\n })\n }\n return pages.slice(0, AI_LAYOUT_SITE_PAGES_MAX)\n}\n\n/**\n * The unit input naming the planned pages a start merged into its blog\n * (AGL-3676): a page that stood in for it, whose links go to the blog.\n */\nexport const AI_LAYOUT_SITE_ALIASES_INPUT = 'siteAliases'\n\n/** A planned page merged into the blog: its id, linked as the blog's path, and the title it had. */\nexport interface AiLayoutSiteAlias extends AiLayoutSitePage {\n href: string\n standsInFor: string\n}\n\n/** The merged pages a unit's inputs carry; none for any other unit. */\nexport function aiLayoutSiteAliases(inputs: Readonly<Record<string, unknown>> | null | undefined): AiLayoutSiteAlias[] {\n const raw = inputs?.[AI_LAYOUT_SITE_ALIASES_INPUT]\n if (!Array.isArray(raw)) return []\n return raw.flatMap((entry) => {\n const { id, label, slug, href, standsInFor } = (entry ?? {}) as Record<string, unknown>\n return typeof id === 'string' &&\n id &&\n typeof label === 'string' &&\n typeof href === 'string' &&\n /^\\/[a-z0-9-]+$/.test(href) &&\n typeof standsInFor === 'string'\n ? [{ id, label, slug: typeof slug === 'string' ? slug : href, href, standsInFor }]\n : []\n })\n}\n\n/**\n * What the layout's user turn says about those pages: that the platform\n * writes the navigation, so the model builds none and leaves no empty element\n * for it. The same promise the completion below keeps.\n */\nexport function aiLayoutSitePagesLines(pages: readonly AiLayoutSitePage[]): string[] {\n if (!pages.length) return []\n return [\n `This site’s pages are built after this layout: ${pages\n .map((page) => `${page.label} (${screenPath(page.slug)})`)\n .join(', ')}.`,\n 'The platform writes the header’s navigation to those pages itself, as a nav row of Page Links inside the Toolbar Content. Build no navigation links to them and no nav element for them: leave no empty element where the links will go.',\n ]\n}\n\nfunction screenPath(slug: string): string {\n const normalized = normalizeScreenSlug(slug)\n if (!normalized || normalized === SCREEN_ROOT_PATH) return '/'\n return `/${normalized.replace(/^\\/+/, '')}`\n}\n\ntype RawNode = { componentId?: unknown; props?: Record<string, unknown>; nodes?: unknown; [key: string]: unknown }\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n Boolean(value) && typeof value === 'object' && !Array.isArray(value)\n\nconst childrenOf = (node: RawNode | undefined): string[] =>\n Array.isArray(node?.nodes) ? (node.nodes as unknown[]).filter((id): id is string => typeof id === 'string') : []\n\nconst elementOf = (node: RawNode | undefined): string => {\n const value = node?.props?.['component'] ?? node?.props?.['element']\n return typeof value === 'string' ? value : ''\n}\n\nconst LINK_COMPONENTS = new Set(['muiScreenLink', 'muiButton'])\n\n/**\n * The model's layout with the header's navigation written in: a Page Link to\n * every planned page the header does not already link, placed in the first\n * nav element above the Layout Slot, else in a nav row the platform appends\n * to the Toolbar Content; and every OTHER nav element the model left empty\n * given the same links, so no nav is ever empty. A link to the site's current\n * home page is pointed at the planned home, which replaces it when the site\n * is published. Anything that is not a node map, or has no planned pages, is\n * returned as it came, for the doctrine to judge.\n */\nexport function aiLayoutWithSitePages(\n tree: unknown,\n pages: readonly AiLayoutSitePage[],\n options: { homeScreenIds?: readonly string[] } = {},\n): unknown {\n if (!pages.length || !isRecord(tree) || !isRecord(tree['nodes'])) return tree\n const rootId = typeof tree['rootId'] === 'string' ? tree['rootId'] : null\n const source = tree['nodes'] as Record<string, unknown>\n if (!rootId || !isRecord(source[rootId])) return tree\n const nodes: Record<string, RawNode> = {}\n for (const [id, node] of Object.entries(source)) {\n if (!isRecord(node)) continue\n nodes[id] = {\n ...(node as RawNode),\n ...(isRecord(node['props']) ? { props: { ...(node['props'] as Record<string, unknown>) } } : {}),\n ...(Array.isArray(node['nodes']) ? { nodes: [...(node['nodes'] as unknown[])] } : {}),\n }\n }\n\n // Document order, and where the header ends: the Layout Slot.\n const order: string[] = []\n const seen = new Set<string>()\n const visit = (id: string) => {\n if (seen.has(id) || !nodes[id]) return\n seen.add(id)\n order.push(id)\n for (const child of childrenOf(nodes[id])) visit(child)\n }\n visit(rootId)\n const slotAt = order.findIndex((id) => nodes[id].componentId === 'layoutSlot')\n const header = slotAt === -1 ? order : order.slice(0, slotAt)\n\n // The site's current home is replaced by the planned one at publish.\n const plannedHome = pages.find((page) => screenPath(page.slug) === '/')\n const oldHomes = new Set(options.homeScreenIds ?? [])\n let changed = false\n if (plannedHome) {\n for (const id of order) {\n const node = nodes[id]\n const screenId = node.props?.['screenId']\n if (LINK_COMPONENTS.has(String(node.componentId)) && typeof screenId === 'string' && oldHomes.has(screenId)) {\n node.props = { ...node.props, screenId: plannedHome.id }\n changed = true\n }\n }\n }\n\n const used = new Set(Object.keys(nodes))\n const freshId = (base: string): string => {\n let id = base\n for (let n = 2; used.has(id); n += 1) id = `${base}${n}`\n used.add(id)\n return id\n }\n const linkNodes = (entries: readonly AiLayoutSitePage[]): string[] =>\n entries.map((page) => {\n const id = freshId(`aiNavLink_${page.id.replace(/[^A-Za-z0-9_]/g, '')}`)\n nodes[id] = {\n componentId: 'muiScreenLink',\n props: {\n children: page.label.slice(0, 40),\n ...(page.href ? { href: page.href } : { screenId: page.id }),\n renderAs: 'link',\n color: 'inherit',\n },\n }\n return id\n })\n\n const linkedInHeader = new Set(\n header\n .map((id) => nodes[id])\n .filter((node) => LINK_COMPONENTS.has(String(node.componentId)))\n .flatMap((node) => [node.props?.['screenId'], node.props?.['href']])\n .filter((value): value is string => typeof value === 'string'),\n )\n const missing = pages.filter((page) => !linkedInHeader.has(page.href ?? page.id))\n const navs = order.filter((id) => elementOf(nodes[id]) === 'nav')\n const headerNav = navs.find((id) => header.includes(id)) ?? null\n\n if (missing.length) {\n if (headerNav) {\n nodes[headerNav].nodes = [...childrenOf(nodes[headerNav]), ...linkNodes(missing)]\n changed = true\n } else {\n const toolbar = header.find((id) => nodes[id].componentId === 'muiToolbar')\n if (toolbar) {\n const navId = freshId('aiSiteNav')\n nodes[navId] = {\n componentId: 'muiStack',\n props: { component: 'nav', direction: 'row', alignItems: 'center' },\n sx: { gap: 2 },\n nodes: linkNodes(missing),\n }\n nodes[toolbar].nodes = [...childrenOf(nodes[toolbar]), navId]\n changed = true\n }\n }\n }\n // A nav element the model left empty anywhere else — a footer's, say — links the same pages.\n for (const id of navs) {\n if (id === headerNav && missing.length) continue\n if (childrenOf(nodes[id]).some((child) => nodes[child])) continue\n nodes[id].nodes = linkNodes(pages)\n changed = true\n }\n return changed ? { ...tree, nodes } : tree\n}\n"],"names":["normalizeScreenSlug","SCREEN_ROOT_PATH","AI_LAYOUT_SITE_PAGES_INPUT","AI_LAYOUT_SITE_PAGES_MAX","aiLayoutIsHomeSlug","slug","replace","CONFIRMATION","aiLayoutSitePagesOfPlan","screens","options","linked","screen","guided","test","title","nav","pages","filter","record","id","map","label","trim","page","slice","aiLayoutSitePages","inputs","raw","Array","isArray","entry","href","some","push","AI_LAYOUT_SITE_ALIASES_INPUT","aiLayoutSiteAliases","flatMap","standsInFor","aiLayoutSitePagesLines","length","screenPath","join","normalized","isRecord","value","Boolean","childrenOf","node","nodes","elementOf","props","LINK_COMPONENTS","Set","aiLayoutWithSitePages","tree","navs","rootId","source","Object","entries","order","seen","visit","has","add","child","slotAt","findIndex","componentId","header","plannedHome","find","oldHomes","homeScreenIds","changed","screenId","String","used","keys","freshId","base","n","linkNodes","children","renderAs","color","linkedInHeader","missing","headerNav","includes","toolbar","navId","component","direction","alignItems","sx","gap"],"mappings":"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"}
|
|
@@ -34,7 +34,7 @@ import { aiLayoutInventedContactViolations, aiLayoutWithSiteName } from "./ai-la
|
|
|
34
34
|
import { aiLayoutWithFullHeight } from "./ai-layout-full-height.js";
|
|
35
35
|
import { AI_LAYOUT_FRAME_TOOL } from "../layout-language/ai-layout-language.js";
|
|
36
36
|
import { AI_JOB_LAYOUT_LANGUAGE_BUDGET, AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS, AI_LAYOUT_FRAME_KIND, aiLayoutFrameCheck, aiLayoutFramePrompt, aiLayoutFrameTargets, aiLayoutHomeId, aiLayoutNavPages, aiLayoutSiteName } from "./ai-job-layout-language.js";
|
|
37
|
-
import { AI_LAYOUT_LANGUAGE_THINKING, aiJobUsesLayoutLanguage } from "./ai-job-page-language.js";
|
|
37
|
+
import { AI_LAYOUT_LANGUAGE_THINKING, aiJobUsesLayoutLanguage, aiLayoutDesignOf } from "./ai-job-page-language.js";
|
|
38
38
|
import { aiJobStepBudget } from "./ai-job-budget.js";
|
|
39
39
|
import { registerAiJobStep } from "./ai-jobs.js";
|
|
40
40
|
/**
|
|
@@ -363,7 +363,8 @@ export function createAiJobLayoutStep(deps = {}) {
|
|
|
363
363
|
homeId: aiLayoutHomeId(pages, inventory),
|
|
364
364
|
pages,
|
|
365
365
|
targets,
|
|
366
|
-
extend: aiLayoutChecks(inventory, plan, job.brief)
|
|
366
|
+
extend: aiLayoutChecks(inventory, plan, job.brief),
|
|
367
|
+
design: aiLayoutDesignOf(job, '/')
|
|
367
368
|
}))
|
|
368
369
|
}, signal ? {
|
|
369
370
|
signal
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-step.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 { aiSiteStyleTokens } from '../model/ai-site-kinds'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { duplicateResource } from '@aglyn/tenant-data-admin/server/duplicate-resource'\nimport type { AiJob, AiJobOutput, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport { aiHomeScreenIds, type AiSiteInventory } from '../model/ai-site-inventory'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n aiDoctrineTreeTool,\n runValidatedGeneration,\n type AiValidatedTree,\n} from '../runtime/ai-doctrine'\nimport {\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineTree,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport {\n aiBuildingPlanCreations,\n aiJobPlanCreationsRefusal,\n aiPlanCreationsRunMinimumMs,\n} from './ai-job-plan-creations'\nimport { aiJobDraftId, aiOriginJobId } from './ai-job-draft-ids'\nimport {\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n readAiDraftNodes,\n writeAiDraft,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiBracketedFactsNote,\n aiCopySourceLines,\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiJobBriefLine,\n aiLimitReview,\n aiModelNodeIds,\n aiPlacedComponentIds,\n aiPlanCreation,\n aiPlanReferenceLines,\n aiPlanReuseViolations,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport { aiPlanRegionViolations, aiPlannedLayoutRegions } from './ai-job-plan-conformance'\nimport { aiLayoutSitePages, aiLayoutSitePagesLines, aiLayoutWithSitePages } from './ai-job-layout-site-pages'\nimport { aiLayoutInventedContactViolations, aiLayoutWithSiteName } from './ai-layout-site-facts'\nimport { aiLayoutWithFullHeight } from './ai-layout-full-height'\nimport type { AiJobStepRunner } from './ai-job-text-step'\nimport { AI_LAYOUT_FRAME_TOOL } from '../layout-language/ai-layout-language'\nimport {\n AI_JOB_LAYOUT_LANGUAGE_BUDGET,\n AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS,\n AI_LAYOUT_FRAME_KIND,\n aiLayoutFrameCheck,\n aiLayoutFramePrompt,\n aiLayoutFrameTargets,\n aiLayoutHomeId,\n aiLayoutNavPages,\n aiLayoutSiteName,\n type AiLayoutFrameBuilt,\n} from './ai-job-layout-language'\nimport { AI_LAYOUT_LANGUAGE_THINKING, aiJobUsesLayoutLanguage } from './ai-job-page-language'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The layout step (AGL-2909): the generation step of a `layout` job, run once\n * a member confirmed the job's plan.\n *\n * It builds ONE shared layout — a header with the site's navigation, the\n * Layout Slot each page renders in, a footer — through\n * `runValidatedGeneration('layout', …)`, so every building rule holds on the\n * tree before it is kept, and adds three checks of its own through `extend`: a\n * component the confirmed plan reuses is placed (rule 7), every region the\n * plan gives the layout is built (AGL-3024), and a navigation or menu\n * component the site already has is placed rather than drawn again (rule 1).\n *\n * The layout lands as a new draft (`ai-job-drafts.ts`): no screen uses it and\n * no layout nests it, so nothing on the live site changes until a member\n * assigns it. A plan that starts from a copy of a layout the site has gets\n * that copy, through the platform's duplicate module, and nothing generated —\n * so the copy is read back and held to the plan's regions the same way\n * (`aiCopiedLayoutReview`), because a branch that generates nothing is the one\n * branch where a promise can go unmet with no model to answer for it. A\n * source that already lacks a region the plan gives the layout is not copied\n * at all: the layout is built from it (AGL-3143 §14).\n *\n * A generation step runs on the job beat, never at an inline door: it\n * registers the least time its lookup rounds, its answer and its re-ask need\n * (AGL-3035), runs without extended thinking — the plan was reasoned out and\n * confirmed before it — and asks for no more of a tighter ceiling than the\n * doctrine's default for a layout than fits that time on the model it runs.\n */\n\n/** The longest answer a layout may run to; the tree is held to the layout budget either way. */\nexport const AI_JOB_LAYOUT_MAX_TOKENS = AI_ROUTING_TABLE['job.layout'].maxTokens\n\n/**\n * The layout step's time (AGL-3035, AGL-3036): its two inventory-lookup\n * rounds, its answer and its re-ask at the routing table's ceiling on the tier\n * `job.layout` is served from, fitted to what a beat can give a step.\n */\nexport const AI_JOB_LAYOUT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.layout'],\n maxTokens: AI_JOB_LAYOUT_MAX_TOKENS,\n})\n\n/** The least time one layout step needs before it starts. */\nexport const AI_JOB_LAYOUT_STEP_MINIMUM_MS = AI_JOB_LAYOUT_STEP_BUDGET.minimumMs\n\n/** A layout's name when the plan names none. */\nexport const AI_JOB_LAYOUT_DEFAULT_NAME = 'Site layout'\n\n/** The layout step's own instructions, cached after the doctrine. */\nexport const AI_JOB_LAYOUT_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n 'You build one shared layout for a website: the frame every page of the site renders inside. Answer with submit_layout: the whole layout as one flat node map.',\n 'A layout is a header, one Layout Slot and a footer, top to bottom. The header is an App Bar (muiAppBar) holding a Toolbar Content (muiToolbar) with the site’s name as text and its navigation. The footer is a Section (section) whose element is footer.',\n 'Navigation links the site’s own pages by id: a Page Link (muiScreenLink) whose screenId is a page id from the site inventory, in the order the brief and the confirmed plan give, seven at most in the header. Never invent a page id, and never link a page by its address.',\n 'When the site inventory lists a component whose name says it is navigation, a menu or a mega menu, place it in the header as an instance instead of building the navigation again. Place every component the confirmed plan reuses.',\n 'The Layout Slot (layoutSlot) is where each page renders and is the page’s main landmark: leave its component unset or main, and put nothing inside it. A layout carries no h1, because every page brings its own.',\n ].join('\\n'),\n },\n]\n\n/**\n * The layout job as the generation's user turn: the name, the brief, the\n * confirmed plan, and the layout a short copy would have started from\n * (`aiCopySourceLines`, AGL-3143 §14).\n */\nexport function aiJobLayoutPrompt(\n job: Pick<AiJob, 'brief'> & Partial<Pick<AiJob, 'inputs'>>,\n plan: AiJobPlan | null,\n name: string,\n source: readonly string[] = [],\n): string {\n return [\n `Layout name: ${name}`,\n aiJobBriefLine(job),\n ...aiPlanReferenceLines(plan),\n // A scaffold's layout is built before its pages, whose navigation the platform writes (AGL-3596).\n ...aiLayoutSitePagesLines(aiLayoutSitePages(job.inputs)),\n ...source,\n ].join('\\n')\n}\n\n/** A component name that says it is the site's navigation; a footer's links are not. */\nconst NAVIGATION_NAME = /\\b(?:nav|navigation|navbar|menu|mega ?menu)\\b/i\nconst FOOTER_NAME = /\\bfooter\\b/i\n\n/** A node that links somewhere: a Screen Link, or a Button pointed at a screen or an address. */\nfunction isLink(node: AiDoctrineNode): boolean {\n if (node.componentId === 'muiScreenLink') return true\n return (\n node.componentId === 'muiButton' &&\n (typeof node.props?.['screenId'] === 'string' || typeof node.props?.['href'] === 'string')\n )\n}\n\n/**\n * The layout door's checks on a tree the doctrine admitted: every component\n * the confirmed plan reuses is placed, every region the confirmed plan gives\n * the layout is built (AGL-3024), and navigation the site already keeps as a\n * component is placed rather than drawn again in the header.\n */\nexport function aiLayoutReuseCheck(\n inventory: AiSiteInventory | null,\n plan: AiJobPlan | null,\n): (tree: AiValidatedTree) => AiDoctrineViolation[] {\n const components = inventory?.components ?? []\n const navigation = components.filter(\n (component) => NAVIGATION_NAME.test(component.name) && !FOOTER_NAME.test(component.name),\n )\n return (tree) => {\n const placed = aiPlacedComponentIds(tree)\n const violations: AiDoctrineViolation[] = [\n ...aiPlanReuseViolations(inventory, plan, placed, 'layout'),\n ...aiPlanRegionViolations(plan, {\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n }),\n ]\n if (navigation.length && !navigation.some((component) => placed.has(component.id))) {\n const visits = walkTree({\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n })\n const slot = visits.findIndex(({ node }) => node.componentId === 'layoutSlot')\n const header = slot === -1 ? visits : visits.slice(0, slot)\n const links = header.filter(({ node }) => isLink(node)).map(({ id }) => id)\n if (links.length >= 2) {\n violations.push({\n rule: 1,\n code: 'navigation-rebuilt',\n message: `The site already has the \"${navigation[0].name}\" component. Place it in the header instead of building the navigation again.`,\n nodeIds: aiModelNodeIds(links, tree.sourceIds),\n })\n }\n }\n return violations\n }\n}\n\n/**\n * Every check the layout door adds to the doctrine's: the reuse and region\n * checks above, and no contact detail the brief does not give (AGL-3596).\n */\nexport function aiLayoutChecks(\n inventory: AiSiteInventory | null,\n plan: AiJobPlan | null,\n brief: string,\n): (tree: AiValidatedTree) => AiDoctrineViolation[] {\n const reuse = aiLayoutReuseCheck(inventory, plan)\n return (tree) => [\n ...reuse(tree),\n ...aiLayoutInventedContactViolations(\n { rootId: tree.rootId, nodes: tree.nodes as unknown as Record<string, AiDoctrineNode> },\n brief,\n ).map((violation) => ({ ...violation, nodeIds: aiModelNodeIds(violation.nodeIds ?? [], tree.sourceIds) })),\n ]\n}\n\n/**\n * The regions the confirmed plan gives the layout, against the layout a COPY\n * actually produced (AGL-3024); `null` when the copy keeps the plan, or when\n * the plan promised no region to keep.\n *\n * The duplicate branch generates nothing, so it is the one place a plan's\n * promise can go unmet without a model ever answering for it. A copy that\n * cannot be read — deleted between the write and this read, or stored under a\n * root this check does not know — settles nothing, and a promise this step\n * cannot settle is reviewed by a person rather than reported as kept.\n */\nexport async function aiCopiedLayoutReview(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; id: string; name: string; plan: AiJobPlan | null },\n): Promise<AiJobReview | null> {\n const planned = aiPlannedLayoutRegions(input.plan)\n if (!planned.regions.length && !planned.unreadable.length) return null\n const stored = await readAiDraftNodes(firestore, {\n kind: 'layout',\n hostId: input.hostId,\n id: input.id,\n })\n const nodes = (stored?.nodes ?? {}) as unknown as Record<string, AiDoctrineNode>\n if (!nodes[CANVAS_ROOT_ELEMENT_ID]) {\n return {\n reason: 'doctrine',\n message: `\"${input.name}\" was copied from the layout the plan names, and this job could not read the copy back to check it has the regions the plan gives it. Open the layout and check it before you put a page in it.`,\n findings: [],\n }\n }\n const violations = aiPlanRegionViolations(input.plan, { rootId: CANVAS_ROOT_ELEMENT_ID, nodes })\n if (!violations.length) return null\n return aiDoctrineReview({\n message: `\"${input.name}\" is a copy of the layout the plan names, and a copy is all it is: ${violations[0].message}`,\n violations,\n })\n}\n\n/**\n * A layout job is admitted for a site of the job's own org with a shared\n * layout to spare, and confirmed only for a plan whose other creations it\n * builds (AGL-3143 §15).\n */\nexport const aiLayoutJobAdmission: AiJobAdmission = (context) =>\n aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'layout',\n org: context.org,\n ownCheck: (hostId) => aiJobPlanCreationsRefusal('layout', context, hostId),\n })\n\nexport interface AiJobLayoutStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The platform's duplicate module, for a plan that starts from a copy. */\n duplicate?: typeof duplicateResource\n}\n\nexport function createAiJobLayoutStep(deps: AiJobLayoutStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n return async ({ job, now, signal, firestore, modelFor }) => {\n const hostId = job.hostId\n if (!hostId) throw new Error('a layout job names no site, and its admission refuses one')\n const output = (draft: AiDraftRecord, load?: AiLoadEstimate | null, note?: string | null): AiJobOutput => ({\n resource: 'layout',\n id: draft.id,\n versionId: draft.versionId,\n hostId,\n hostSubdomain: draft.hostSubdomain,\n label: draft.name,\n ...(load ? { load } : {}),\n ...(note ? { note } : {}),\n })\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.layout') ?? aiModelForStep('job.layout')\n\n // A run cut off after its draft was written reports that draft, found by the id the job recorded.\n const draftId = aiJobDraftId(job, 'layout')\n const written = await readAiDraft(firestore, { kind: 'layout', hostId, id: draftId })\n if (written) return aiUnspentOutcome(model, { outputs: [output(written)] })\n\n const [inventory, orgSnapshot] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n ])\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n const plan = aiConfirmedPlan(job)\n const creation = aiPlanCreation(plan, 'layout')\n const name = creation?.name || AI_JOB_LAYOUT_DEFAULT_NAME\n\n // The plan starts from a copy of a layout the site has (rule 15): the copy\n // is the draft, and nothing is generated. A source gone since the plan\n // was made is built from the brief instead. A source that cannot keep the\n // plan's regions is built FROM instead (AGL-3143 §14), as the page step\n // builds from a short source: copying it could only end in the review\n // below, and Try again would copy the same thing. Only a source read and\n // found short diverts; one this step cannot read still copies, and the\n // review after the copy answers for it.\n const source = creation?.duplicateOf\n ? inventory.layouts.find((layout) => layout.id === creation.duplicateOf)\n : undefined\n const sourceReview = source\n ? await aiCopiedLayoutReview(firestore, { hostId, id: source.id, plan, name: source.name })\n : null\n const sourceShort = Boolean(sourceReview?.findings.length)\n const sourceTree =\n source && sourceShort ? await readAiDraftNodes(firestore, { kind: 'layout', hostId, id: source.id }) : null\n const sourceLines =\n source && sourceShort ? aiCopySourceLines('layout', source.name, sourceTree?.nodes ?? null) : []\n if (creation?.duplicateOf && source && !sourceShort) {\n const copy = await duplicate('layout', {\n orgId: job.orgId,\n hostId,\n sourceId: creation.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n // The same claim the page step makes (AGL-3024): a copy core mints is\n // written under core's own id, never `draftId`, so a step that runs\n // again — a pass resumed, a run cut off after the copy — would mint a\n // second. The job's recorded id makes core replay the one it made.\n attemptKey: draftId,\n })\n if (copy.ok) {\n // A copy is not a build (AGL-3024). This branch generates nothing, so\n // whatever the plan promised BEYOND the source is not in the draft\n // unless the source already had it: a plan reading \"from a copy of the\n // base layout, adding a sidebar\" gets the base layout, the sidebar\n // nowhere in it, and until now reported Done. The copy's own regions\n // answer the plan's, and a copy that cannot be read answers nothing —\n // which is a stop for a person, never a Done.\n const review = await aiCopiedLayoutReview(firestore, { hostId, id: copy.id, plan, name })\n if (review) return aiUnspentOutcome(model, { review })\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n return aiUnspentOutcome(model, {\n outputs: [output({ id: copy.id, versionId: copy.versionId, name: copy.name, hostSubdomain })],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'layout', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n\n if (aiJobUsesLayoutLanguage(job)) {\n // ── The header and footer designed in the layout language (AGL-3660) ──\n const siteName = aiLayoutSiteName(job)\n const pages = aiLayoutNavPages(job, inventory)\n const targets = aiLayoutFrameTargets(job, inventory)\n const result = await runValidatedGeneration<AiLayoutFrameBuilt>(AI_LAYOUT_FRAME_KIND, {\n step: 'job.layout',\n model,\n instructions: AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS,\n inventory,\n messages: [{ role: 'user', content: aiLayoutFramePrompt({ job, siteName, pages, targets }) }],\n tool: AI_LAYOUT_FRAME_TOOL,\n maxTokens: AI_JOB_LAYOUT_LANGUAGE_BUDGET.maxTokens(model),\n ...AI_LAYOUT_LANGUAGE_THINKING,\n check: aiLayoutFrameCheck({\n ...aiSiteStyleTokens(job.inputs),\n siteName,\n homeId: aiLayoutHomeId(pages, inventory),\n pages,\n targets,\n extend: aiLayoutChecks(inventory, plan, job.brief),\n }),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n const draft = await writeAiDraft(firestore, {\n kind: 'layout',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: result.value.nodes,\n aiJobId: aiOriginJobId(job),\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its layout was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n const note = aiBracketedFactsNote({\n tree: { rootId: result.value.rootId, nodes: result.value.nodes as unknown as AiDoctrineTree['nodes'] },\n inventory,\n })\n return { ...spent, outputs: [output(draft, result.value.load, note)] }\n }\n\n // The pages a site scaffold builds after this layout (AGL-3596): their\n // ids are the plan's, so the header links them by id, written by the\n // platform before the doctrine checks the tree.\n const sitePages = aiLayoutSitePages(job.inputs)\n const result = await runValidatedGeneration('layout', {\n step: 'job.layout',\n model,\n instructions: AI_JOB_LAYOUT_INSTRUCTIONS,\n inventory,\n messages: [{ role: 'user', content: aiJobLayoutPrompt(job, plan, name, sourceLines) }],\n tool: aiDoctrineTreeTool('layout'),\n maxTokens: AI_JOB_LAYOUT_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.layout'].thinking ? { thinking: AI_ROUTING_TABLE['job.layout'].thinking } : {}),\n ...(AI_ROUTING_TABLE['job.layout'].effort ? { effort: AI_ROUTING_TABLE['job.layout'].effort } : {}),\n extend: aiLayoutChecks(inventory, plan, job.brief),\n // The site's name is written as the token that reads it, and the\n // layout fills the window so a short page keeps its footer at the\n // bottom (AGL-3596).\n complete: (tree: unknown) =>\n aiLayoutWithFullHeight(\n aiLayoutWithSiteName(\n sitePages.length ? aiLayoutWithSitePages(tree, sitePages, { homeScreenIds: aiHomeScreenIds(inventory) }) : tree,\n typeof job.inputs?.['businessName'] === 'string' ? job.inputs['businessName'] : null,\n ),\n ),\n ...(sitePages.length\n ? { context: { screenIds: [...inventory.screens.map((screen) => screen.id), ...sitePages.map((page) => page.id)] } }\n : {}),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n\n const draft = await writeAiDraft(firestore, {\n kind: 'layout',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: result.value.nodes,\n aiJobId: aiOriginJobId(job),\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its layout was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n // The facts the brief did not give, which the member fills before publishing (AGL-3056).\n const note = aiBracketedFactsNote({\n tree: { rootId: result.value.rootId, nodes: result.value.nodes as unknown as AiDoctrineTree['nodes'] },\n inventory,\n })\n return { ...spent, outputs: [output(draft, result.value.load, note)] }\n }\n}\n\n/** The layout step, building the components and forms its plan creates before the layout (AGL-3143 §15). */\nexport const runAiJobLayoutStep = aiBuildingPlanCreations('layout', 'job.layout', createAiJobLayoutStep())\n\n/** Registers the layout step and the check a layout job passes before it is created or resumed. */\nexport function registerAiLayoutJob(): void {\n registerAiJobStep('layout', runAiJobLayoutStep, {\n minimumMs: AI_JOB_LAYOUT_STEP_MINIMUM_MS,\n minimumMsFor: aiPlanCreationsRunMinimumMs('layout'),\n })\n registerAiJobAdmission('layout', aiLayoutJobAdmission)\n}\n"],"names":["aiSiteStyleTokens","CANVAS_ROOT_ELEMENT_ID","duplicateResource","aiHomeScreenIds","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDoctrineTreeTool","runValidatedGeneration","walkTree","readSiteInventory","registerAiJobAdmission","aiBuildingPlanCreations","aiJobPlanCreationsRefusal","aiPlanCreationsRunMinimumMs","aiJobDraftId","aiOriginJobId","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","readAiDraftNodes","writeAiDraft","aiBracketedFactsNote","aiCopySourceLines","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiJobBriefLine","aiLimitReview","aiModelNodeIds","aiPlacedComponentIds","aiPlanCreation","aiPlanReferenceLines","aiPlanReuseViolations","aiUnspentOutcome","aiPlanRegionViolations","aiPlannedLayoutRegions","aiLayoutSitePages","aiLayoutSitePagesLines","aiLayoutWithSitePages","aiLayoutInventedContactViolations","aiLayoutWithSiteName","aiLayoutWithFullHeight","AI_LAYOUT_FRAME_TOOL","AI_JOB_LAYOUT_LANGUAGE_BUDGET","AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS","AI_LAYOUT_FRAME_KIND","aiLayoutFrameCheck","aiLayoutFramePrompt","aiLayoutFrameTargets","aiLayoutHomeId","aiLayoutNavPages","aiLayoutSiteName","AI_LAYOUT_LANGUAGE_THINKING","aiJobUsesLayoutLanguage","aiJobStepBudget","registerAiJobStep","AI_JOB_LAYOUT_MAX_TOKENS","maxTokens","AI_JOB_LAYOUT_STEP_BUDGET","tier","AI_JOB_LAYOUT_STEP_MINIMUM_MS","minimumMs","AI_JOB_LAYOUT_DEFAULT_NAME","AI_JOB_LAYOUT_INSTRUCTIONS","text","join","aiJobLayoutPrompt","job","plan","name","source","inputs","NAVIGATION_NAME","FOOTER_NAME","isLink","node","componentId","props","aiLayoutReuseCheck","inventory","components","navigation","filter","component","test","tree","placed","violations","rootId","nodes","length","some","has","id","visits","slot","findIndex","header","slice","links","map","push","rule","code","message","nodeIds","sourceIds","aiLayoutChecks","brief","reuse","violation","aiCopiedLayoutReview","firestore","input","planned","regions","unreadable","stored","kind","hostId","reason","findings","aiLayoutJobAdmission","context","orgId","org","ownCheck","createAiJobLayoutStep","deps","readInventory","duplicate","now","signal","modelFor","orgSnapshot","Error","output","draft","load","note","resource","versionId","hostSubdomain","label","model","draftId","written","outputs","Promise","all","collection","doc","get","data","creation","duplicateOf","layouts","find","layout","undefined","sourceReview","sourceShort","Boolean","sourceTree","sourceLines","copy","sourceId","uid","createdBy","attemptKey","ok","review","status","error","allowance","siteName","pages","targets","result","step","instructions","messages","role","content","tool","check","homeId","extend","spent","refused","value","aiJobId","sitePages","thinking","effort","complete","homeScreenIds","screenIds","screens","screen","page","runAiJobLayoutStep","registerAiLayoutJob","minimumMsFor"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-layout-step.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 { aiSiteStyleTokens } from '../model/ai-site-kinds'\nimport { CANVAS_ROOT_ELEMENT_ID } from '@aglyn/aglyn/foundation/constants/canvas'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { duplicateResource } from '@aglyn/tenant-data-admin/server/duplicate-resource'\nimport type { AiJob, AiJobOutput, AiJobPlan, AiJobReview } from '../model/ai-jobs.types'\nimport { aiHomeScreenIds, type AiSiteInventory } from '../model/ai-site-inventory'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { AI_ROUTING_TABLE, aiModelForStep } from '../providers/routing'\nimport {\n aiDoctrineTreeTool,\n runValidatedGeneration,\n type AiValidatedTree,\n} from '../runtime/ai-doctrine'\nimport {\n walkTree,\n type AiDoctrineNode,\n type AiDoctrineTree,\n type AiDoctrineViolation,\n} from '../runtime/ai-doctrine-validators'\nimport type { AiLoadEstimate } from '../runtime/ai-palette'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { readSiteInventory } from '../runtime/site-inventory'\nimport { registerAiJobAdmission, type AiJobAdmission } from './ai-job-admission'\nimport {\n aiBuildingPlanCreations,\n aiJobPlanCreationsRefusal,\n aiPlanCreationsRunMinimumMs,\n} from './ai-job-plan-creations'\nimport { aiJobDraftId, aiOriginJobId } from './ai-job-draft-ids'\nimport {\n aiDraftAdmissionRefusal,\n aiDraftAllowanceRefusal,\n aiSiteSubdomain,\n readAiDraft,\n readAiDraftNodes,\n writeAiDraft,\n type AiDraftRecord,\n} from './ai-job-drafts'\nimport {\n aiBracketedFactsNote,\n aiCopySourceLines,\n aiConfirmedPlan,\n aiDoctrineReview,\n aiGenerationSpent,\n aiJobBriefLine,\n aiLimitReview,\n aiModelNodeIds,\n aiPlacedComponentIds,\n aiPlanCreation,\n aiPlanReferenceLines,\n aiPlanReuseViolations,\n aiUnspentOutcome,\n} from './ai-job-generation'\nimport { aiPlanRegionViolations, aiPlannedLayoutRegions } from './ai-job-plan-conformance'\nimport { aiLayoutSitePages, aiLayoutSitePagesLines, aiLayoutWithSitePages } from './ai-job-layout-site-pages'\nimport { aiLayoutInventedContactViolations, aiLayoutWithSiteName } from './ai-layout-site-facts'\nimport { aiLayoutWithFullHeight } from './ai-layout-full-height'\nimport type { AiJobStepRunner } from './ai-job-text-step'\nimport { AI_LAYOUT_FRAME_TOOL } from '../layout-language/ai-layout-language'\nimport {\n AI_JOB_LAYOUT_LANGUAGE_BUDGET,\n AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS,\n AI_LAYOUT_FRAME_KIND,\n aiLayoutFrameCheck,\n aiLayoutFramePrompt,\n aiLayoutFrameTargets,\n aiLayoutHomeId,\n aiLayoutNavPages,\n aiLayoutSiteName,\n type AiLayoutFrameBuilt,\n} from './ai-job-layout-language'\nimport { AI_LAYOUT_LANGUAGE_THINKING, aiJobUsesLayoutLanguage, aiLayoutDesignOf } from './ai-job-page-language'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The layout step (AGL-2909): the generation step of a `layout` job, run once\n * a member confirmed the job's plan.\n *\n * It builds ONE shared layout — a header with the site's navigation, the\n * Layout Slot each page renders in, a footer — through\n * `runValidatedGeneration('layout', …)`, so every building rule holds on the\n * tree before it is kept, and adds three checks of its own through `extend`: a\n * component the confirmed plan reuses is placed (rule 7), every region the\n * plan gives the layout is built (AGL-3024), and a navigation or menu\n * component the site already has is placed rather than drawn again (rule 1).\n *\n * The layout lands as a new draft (`ai-job-drafts.ts`): no screen uses it and\n * no layout nests it, so nothing on the live site changes until a member\n * assigns it. A plan that starts from a copy of a layout the site has gets\n * that copy, through the platform's duplicate module, and nothing generated —\n * so the copy is read back and held to the plan's regions the same way\n * (`aiCopiedLayoutReview`), because a branch that generates nothing is the one\n * branch where a promise can go unmet with no model to answer for it. A\n * source that already lacks a region the plan gives the layout is not copied\n * at all: the layout is built from it (AGL-3143 §14).\n *\n * A generation step runs on the job beat, never at an inline door: it\n * registers the least time its lookup rounds, its answer and its re-ask need\n * (AGL-3035), runs without extended thinking — the plan was reasoned out and\n * confirmed before it — and asks for no more of a tighter ceiling than the\n * doctrine's default for a layout than fits that time on the model it runs.\n */\n\n/** The longest answer a layout may run to; the tree is held to the layout budget either way. */\nexport const AI_JOB_LAYOUT_MAX_TOKENS = AI_ROUTING_TABLE['job.layout'].maxTokens\n\n/**\n * The layout step's time (AGL-3035, AGL-3036): its two inventory-lookup\n * rounds, its answer and its re-ask at the routing table's ceiling on the tier\n * `job.layout` is served from, fitted to what a beat can give a step.\n */\nexport const AI_JOB_LAYOUT_STEP_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS['job.layout'],\n maxTokens: AI_JOB_LAYOUT_MAX_TOKENS,\n})\n\n/** The least time one layout step needs before it starts. */\nexport const AI_JOB_LAYOUT_STEP_MINIMUM_MS = AI_JOB_LAYOUT_STEP_BUDGET.minimumMs\n\n/** A layout's name when the plan names none. */\nexport const AI_JOB_LAYOUT_DEFAULT_NAME = 'Site layout'\n\n/** The layout step's own instructions, cached after the doctrine. */\nexport const AI_JOB_LAYOUT_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n 'You build one shared layout for a website: the frame every page of the site renders inside. Answer with submit_layout: the whole layout as one flat node map.',\n 'A layout is a header, one Layout Slot and a footer, top to bottom. The header is an App Bar (muiAppBar) holding a Toolbar Content (muiToolbar) with the site’s name as text and its navigation. The footer is a Section (section) whose element is footer.',\n 'Navigation links the site’s own pages by id: a Page Link (muiScreenLink) whose screenId is a page id from the site inventory, in the order the brief and the confirmed plan give, seven at most in the header. Never invent a page id, and never link a page by its address.',\n 'When the site inventory lists a component whose name says it is navigation, a menu or a mega menu, place it in the header as an instance instead of building the navigation again. Place every component the confirmed plan reuses.',\n 'The Layout Slot (layoutSlot) is where each page renders and is the page’s main landmark: leave its component unset or main, and put nothing inside it. A layout carries no h1, because every page brings its own.',\n ].join('\\n'),\n },\n]\n\n/**\n * The layout job as the generation's user turn: the name, the brief, the\n * confirmed plan, and the layout a short copy would have started from\n * (`aiCopySourceLines`, AGL-3143 §14).\n */\nexport function aiJobLayoutPrompt(\n job: Pick<AiJob, 'brief'> & Partial<Pick<AiJob, 'inputs'>>,\n plan: AiJobPlan | null,\n name: string,\n source: readonly string[] = [],\n): string {\n return [\n `Layout name: ${name}`,\n aiJobBriefLine(job),\n ...aiPlanReferenceLines(plan),\n // A scaffold's layout is built before its pages, whose navigation the platform writes (AGL-3596).\n ...aiLayoutSitePagesLines(aiLayoutSitePages(job.inputs)),\n ...source,\n ].join('\\n')\n}\n\n/** A component name that says it is the site's navigation; a footer's links are not. */\nconst NAVIGATION_NAME = /\\b(?:nav|navigation|navbar|menu|mega ?menu)\\b/i\nconst FOOTER_NAME = /\\bfooter\\b/i\n\n/** A node that links somewhere: a Screen Link, or a Button pointed at a screen or an address. */\nfunction isLink(node: AiDoctrineNode): boolean {\n if (node.componentId === 'muiScreenLink') return true\n return (\n node.componentId === 'muiButton' &&\n (typeof node.props?.['screenId'] === 'string' || typeof node.props?.['href'] === 'string')\n )\n}\n\n/**\n * The layout door's checks on a tree the doctrine admitted: every component\n * the confirmed plan reuses is placed, every region the confirmed plan gives\n * the layout is built (AGL-3024), and navigation the site already keeps as a\n * component is placed rather than drawn again in the header.\n */\nexport function aiLayoutReuseCheck(\n inventory: AiSiteInventory | null,\n plan: AiJobPlan | null,\n): (tree: AiValidatedTree) => AiDoctrineViolation[] {\n const components = inventory?.components ?? []\n const navigation = components.filter(\n (component) => NAVIGATION_NAME.test(component.name) && !FOOTER_NAME.test(component.name),\n )\n return (tree) => {\n const placed = aiPlacedComponentIds(tree)\n const violations: AiDoctrineViolation[] = [\n ...aiPlanReuseViolations(inventory, plan, placed, 'layout'),\n ...aiPlanRegionViolations(plan, {\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n }),\n ]\n if (navigation.length && !navigation.some((component) => placed.has(component.id))) {\n const visits = walkTree({\n rootId: tree.rootId,\n nodes: tree.nodes as unknown as Record<string, AiDoctrineNode>,\n })\n const slot = visits.findIndex(({ node }) => node.componentId === 'layoutSlot')\n const header = slot === -1 ? visits : visits.slice(0, slot)\n const links = header.filter(({ node }) => isLink(node)).map(({ id }) => id)\n if (links.length >= 2) {\n violations.push({\n rule: 1,\n code: 'navigation-rebuilt',\n message: `The site already has the \"${navigation[0].name}\" component. Place it in the header instead of building the navigation again.`,\n nodeIds: aiModelNodeIds(links, tree.sourceIds),\n })\n }\n }\n return violations\n }\n}\n\n/**\n * Every check the layout door adds to the doctrine's: the reuse and region\n * checks above, and no contact detail the brief does not give (AGL-3596).\n */\nexport function aiLayoutChecks(\n inventory: AiSiteInventory | null,\n plan: AiJobPlan | null,\n brief: string,\n): (tree: AiValidatedTree) => AiDoctrineViolation[] {\n const reuse = aiLayoutReuseCheck(inventory, plan)\n return (tree) => [\n ...reuse(tree),\n ...aiLayoutInventedContactViolations(\n { rootId: tree.rootId, nodes: tree.nodes as unknown as Record<string, AiDoctrineNode> },\n brief,\n ).map((violation) => ({ ...violation, nodeIds: aiModelNodeIds(violation.nodeIds ?? [], tree.sourceIds) })),\n ]\n}\n\n/**\n * The regions the confirmed plan gives the layout, against the layout a COPY\n * actually produced (AGL-3024); `null` when the copy keeps the plan, or when\n * the plan promised no region to keep.\n *\n * The duplicate branch generates nothing, so it is the one place a plan's\n * promise can go unmet without a model ever answering for it. A copy that\n * cannot be read — deleted between the write and this read, or stored under a\n * root this check does not know — settles nothing, and a promise this step\n * cannot settle is reviewed by a person rather than reported as kept.\n */\nexport async function aiCopiedLayoutReview(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; id: string; name: string; plan: AiJobPlan | null },\n): Promise<AiJobReview | null> {\n const planned = aiPlannedLayoutRegions(input.plan)\n if (!planned.regions.length && !planned.unreadable.length) return null\n const stored = await readAiDraftNodes(firestore, {\n kind: 'layout',\n hostId: input.hostId,\n id: input.id,\n })\n const nodes = (stored?.nodes ?? {}) as unknown as Record<string, AiDoctrineNode>\n if (!nodes[CANVAS_ROOT_ELEMENT_ID]) {\n return {\n reason: 'doctrine',\n message: `\"${input.name}\" was copied from the layout the plan names, and this job could not read the copy back to check it has the regions the plan gives it. Open the layout and check it before you put a page in it.`,\n findings: [],\n }\n }\n const violations = aiPlanRegionViolations(input.plan, { rootId: CANVAS_ROOT_ELEMENT_ID, nodes })\n if (!violations.length) return null\n return aiDoctrineReview({\n message: `\"${input.name}\" is a copy of the layout the plan names, and a copy is all it is: ${violations[0].message}`,\n violations,\n })\n}\n\n/**\n * A layout job is admitted for a site of the job's own org with a shared\n * layout to spare, and confirmed only for a plan whose other creations it\n * builds (AGL-3143 §15).\n */\nexport const aiLayoutJobAdmission: AiJobAdmission = (context) =>\n aiDraftAdmissionRefusal(context.firestore, {\n orgId: context.orgId,\n hostId: context.hostId,\n kind: 'layout',\n org: context.org,\n ownCheck: (hostId) => aiJobPlanCreationsRefusal('layout', context, hostId),\n })\n\nexport interface AiJobLayoutStepDeps {\n /** The inventory reader; specs hand in a fake. */\n readInventory?: typeof readSiteInventory\n /** The platform's duplicate module, for a plan that starts from a copy. */\n duplicate?: typeof duplicateResource\n}\n\nexport function createAiJobLayoutStep(deps: AiJobLayoutStepDeps = {}): AiJobStepRunner {\n const readInventory = deps.readInventory ?? readSiteInventory\n const duplicate = deps.duplicate ?? duplicateResource\n return async ({ job, now, signal, firestore, modelFor }) => {\n const hostId = job.hostId\n if (!hostId) throw new Error('a layout job names no site, and its admission refuses one')\n const output = (draft: AiDraftRecord, load?: AiLoadEstimate | null, note?: string | null): AiJobOutput => ({\n resource: 'layout',\n id: draft.id,\n versionId: draft.versionId,\n hostId,\n hostSubdomain: draft.hostSubdomain,\n label: draft.name,\n ...(load ? { load } : {}),\n ...(note ? { note } : {}),\n })\n // The switch's answer for this job, else the routing table's (AGL-2942).\n const model = modelFor?.('job.layout') ?? aiModelForStep('job.layout')\n\n // A run cut off after its draft was written reports that draft, found by the id the job recorded.\n const draftId = aiJobDraftId(job, 'layout')\n const written = await readAiDraft(firestore, { kind: 'layout', hostId, id: draftId })\n if (written) return aiUnspentOutcome(model, { outputs: [output(written)] })\n\n const [inventory, orgSnapshot] = await Promise.all([\n readInventory(job.orgId, hostId, { firestore }),\n firestore.collection('orgs').doc(job.orgId).get(),\n ])\n const org = (orgSnapshot.data() ?? null) as Partial<AglynOrgBilling> | null\n const plan = aiConfirmedPlan(job)\n const creation = aiPlanCreation(plan, 'layout')\n const name = creation?.name || AI_JOB_LAYOUT_DEFAULT_NAME\n\n // The plan starts from a copy of a layout the site has (rule 15): the copy\n // is the draft, and nothing is generated. A source gone since the plan\n // was made is built from the brief instead. A source that cannot keep the\n // plan's regions is built FROM instead (AGL-3143 §14), as the page step\n // builds from a short source: copying it could only end in the review\n // below, and Try again would copy the same thing. Only a source read and\n // found short diverts; one this step cannot read still copies, and the\n // review after the copy answers for it.\n const source = creation?.duplicateOf\n ? inventory.layouts.find((layout) => layout.id === creation.duplicateOf)\n : undefined\n const sourceReview = source\n ? await aiCopiedLayoutReview(firestore, { hostId, id: source.id, plan, name: source.name })\n : null\n const sourceShort = Boolean(sourceReview?.findings.length)\n const sourceTree =\n source && sourceShort ? await readAiDraftNodes(firestore, { kind: 'layout', hostId, id: source.id }) : null\n const sourceLines =\n source && sourceShort ? aiCopySourceLines('layout', source.name, sourceTree?.nodes ?? null) : []\n if (creation?.duplicateOf && source && !sourceShort) {\n const copy = await duplicate('layout', {\n orgId: job.orgId,\n hostId,\n sourceId: creation.duplicateOf,\n name,\n uid: job.createdBy,\n org: org as Record<string, unknown> | null,\n // The same claim the page step makes (AGL-3024): a copy core mints is\n // written under core's own id, never `draftId`, so a step that runs\n // again — a pass resumed, a run cut off after the copy — would mint a\n // second. The job's recorded id makes core replay the one it made.\n attemptKey: draftId,\n })\n if (copy.ok) {\n // A copy is not a build (AGL-3024). This branch generates nothing, so\n // whatever the plan promised BEYOND the source is not in the draft\n // unless the source already had it: a plan reading \"from a copy of the\n // base layout, adding a sidebar\" gets the base layout, the sidebar\n // nowhere in it, and until now reported Done. The copy's own regions\n // answer the plan's, and a copy that cannot be read answers nothing —\n // which is a stop for a person, never a Done.\n const review = await aiCopiedLayoutReview(firestore, { hostId, id: copy.id, plan, name })\n if (review) return aiUnspentOutcome(model, { review })\n const hostSubdomain = await aiSiteSubdomain(firestore, hostId)\n return aiUnspentOutcome(model, {\n outputs: [output({ id: copy.id, versionId: copy.versionId, name: copy.name, hostSubdomain })],\n })\n }\n if (copy.ok === false && copy.status === 403) {\n return aiUnspentOutcome(model, { review: aiLimitReview(copy.error) })\n }\n }\n\n const allowance = await aiDraftAllowanceRefusal(firestore, { kind: 'layout', hostId, org })\n if (allowance) return aiUnspentOutcome(model, { review: aiLimitReview(allowance) })\n\n if (aiJobUsesLayoutLanguage(job)) {\n // ── The header and footer designed in the layout language (AGL-3660) ──\n const siteName = aiLayoutSiteName(job)\n const pages = aiLayoutNavPages(job, inventory)\n const targets = aiLayoutFrameTargets(job, inventory)\n const result = await runValidatedGeneration<AiLayoutFrameBuilt>(AI_LAYOUT_FRAME_KIND, {\n step: 'job.layout',\n model,\n instructions: AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS,\n inventory,\n messages: [{ role: 'user', content: aiLayoutFramePrompt({ job, siteName, pages, targets }) }],\n tool: AI_LAYOUT_FRAME_TOOL,\n maxTokens: AI_JOB_LAYOUT_LANGUAGE_BUDGET.maxTokens(model),\n ...AI_LAYOUT_LANGUAGE_THINKING,\n check: aiLayoutFrameCheck({\n ...aiSiteStyleTokens(job.inputs),\n siteName,\n homeId: aiLayoutHomeId(pages, inventory),\n pages,\n targets,\n extend: aiLayoutChecks(inventory, plan, job.brief),\n design: aiLayoutDesignOf(job, '/'),\n }),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n const draft = await writeAiDraft(firestore, {\n kind: 'layout',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: result.value.nodes,\n aiJobId: aiOriginJobId(job),\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its layout was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n const note = aiBracketedFactsNote({\n tree: { rootId: result.value.rootId, nodes: result.value.nodes as unknown as AiDoctrineTree['nodes'] },\n inventory,\n })\n return { ...spent, outputs: [output(draft, result.value.load, note)] }\n }\n\n // The pages a site scaffold builds after this layout (AGL-3596): their\n // ids are the plan's, so the header links them by id, written by the\n // platform before the doctrine checks the tree.\n const sitePages = aiLayoutSitePages(job.inputs)\n const result = await runValidatedGeneration('layout', {\n step: 'job.layout',\n model,\n instructions: AI_JOB_LAYOUT_INSTRUCTIONS,\n inventory,\n messages: [{ role: 'user', content: aiJobLayoutPrompt(job, plan, name, sourceLines) }],\n tool: aiDoctrineTreeTool('layout'),\n maxTokens: AI_JOB_LAYOUT_STEP_BUDGET.maxTokens(model),\n ...(AI_ROUTING_TABLE['job.layout'].thinking ? { thinking: AI_ROUTING_TABLE['job.layout'].thinking } : {}),\n ...(AI_ROUTING_TABLE['job.layout'].effort ? { effort: AI_ROUTING_TABLE['job.layout'].effort } : {}),\n extend: aiLayoutChecks(inventory, plan, job.brief),\n // The site's name is written as the token that reads it, and the\n // layout fills the window so a short page keeps its footer at the\n // bottom (AGL-3596).\n complete: (tree: unknown) =>\n aiLayoutWithFullHeight(\n aiLayoutWithSiteName(\n sitePages.length ? aiLayoutWithSitePages(tree, sitePages, { homeScreenIds: aiHomeScreenIds(inventory) }) : tree,\n typeof job.inputs?.['businessName'] === 'string' ? job.inputs['businessName'] : null,\n ),\n ),\n ...(sitePages.length\n ? { context: { screenIds: [...inventory.screens.map((screen) => screen.id), ...sitePages.map((page) => page.id)] } }\n : {}),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n if (result.status === 'needs_input') return { ...spent, review: aiDoctrineReview(result) }\n\n const draft = await writeAiDraft(firestore, {\n kind: 'layout',\n hostId,\n id: draftId,\n uid: job.createdBy,\n org,\n name,\n nodes: result.value.nodes,\n aiJobId: aiOriginJobId(job),\n now,\n })\n if (draft.ok === false) {\n if (draft.status === 404) throw new Error(`site ${hostId} vanished while its layout was generated`)\n return { ...spent, review: aiLimitReview(draft.error) }\n }\n // The facts the brief did not give, which the member fills before publishing (AGL-3056).\n const note = aiBracketedFactsNote({\n tree: { rootId: result.value.rootId, nodes: result.value.nodes as unknown as AiDoctrineTree['nodes'] },\n inventory,\n })\n return { ...spent, outputs: [output(draft, result.value.load, note)] }\n }\n}\n\n/** The layout step, building the components and forms its plan creates before the layout (AGL-3143 §15). */\nexport const runAiJobLayoutStep = aiBuildingPlanCreations('layout', 'job.layout', createAiJobLayoutStep())\n\n/** Registers the layout step and the check a layout job passes before it is created or resumed. */\nexport function registerAiLayoutJob(): void {\n registerAiJobStep('layout', runAiJobLayoutStep, {\n minimumMs: AI_JOB_LAYOUT_STEP_MINIMUM_MS,\n minimumMsFor: aiPlanCreationsRunMinimumMs('layout'),\n })\n registerAiJobAdmission('layout', aiLayoutJobAdmission)\n}\n"],"names":["aiSiteStyleTokens","CANVAS_ROOT_ELEMENT_ID","duplicateResource","aiHomeScreenIds","AI_STEP_TIERS","AI_ROUTING_TABLE","aiModelForStep","aiDoctrineTreeTool","runValidatedGeneration","walkTree","readSiteInventory","registerAiJobAdmission","aiBuildingPlanCreations","aiJobPlanCreationsRefusal","aiPlanCreationsRunMinimumMs","aiJobDraftId","aiOriginJobId","aiDraftAdmissionRefusal","aiDraftAllowanceRefusal","aiSiteSubdomain","readAiDraft","readAiDraftNodes","writeAiDraft","aiBracketedFactsNote","aiCopySourceLines","aiConfirmedPlan","aiDoctrineReview","aiGenerationSpent","aiJobBriefLine","aiLimitReview","aiModelNodeIds","aiPlacedComponentIds","aiPlanCreation","aiPlanReferenceLines","aiPlanReuseViolations","aiUnspentOutcome","aiPlanRegionViolations","aiPlannedLayoutRegions","aiLayoutSitePages","aiLayoutSitePagesLines","aiLayoutWithSitePages","aiLayoutInventedContactViolations","aiLayoutWithSiteName","aiLayoutWithFullHeight","AI_LAYOUT_FRAME_TOOL","AI_JOB_LAYOUT_LANGUAGE_BUDGET","AI_JOB_LAYOUT_LANGUAGE_INSTRUCTIONS","AI_LAYOUT_FRAME_KIND","aiLayoutFrameCheck","aiLayoutFramePrompt","aiLayoutFrameTargets","aiLayoutHomeId","aiLayoutNavPages","aiLayoutSiteName","AI_LAYOUT_LANGUAGE_THINKING","aiJobUsesLayoutLanguage","aiLayoutDesignOf","aiJobStepBudget","registerAiJobStep","AI_JOB_LAYOUT_MAX_TOKENS","maxTokens","AI_JOB_LAYOUT_STEP_BUDGET","tier","AI_JOB_LAYOUT_STEP_MINIMUM_MS","minimumMs","AI_JOB_LAYOUT_DEFAULT_NAME","AI_JOB_LAYOUT_INSTRUCTIONS","text","join","aiJobLayoutPrompt","job","plan","name","source","inputs","NAVIGATION_NAME","FOOTER_NAME","isLink","node","componentId","props","aiLayoutReuseCheck","inventory","components","navigation","filter","component","test","tree","placed","violations","rootId","nodes","length","some","has","id","visits","slot","findIndex","header","slice","links","map","push","rule","code","message","nodeIds","sourceIds","aiLayoutChecks","brief","reuse","violation","aiCopiedLayoutReview","firestore","input","planned","regions","unreadable","stored","kind","hostId","reason","findings","aiLayoutJobAdmission","context","orgId","org","ownCheck","createAiJobLayoutStep","deps","readInventory","duplicate","now","signal","modelFor","orgSnapshot","Error","output","draft","load","note","resource","versionId","hostSubdomain","label","model","draftId","written","outputs","Promise","all","collection","doc","get","data","creation","duplicateOf","layouts","find","layout","undefined","sourceReview","sourceShort","Boolean","sourceTree","sourceLines","copy","sourceId","uid","createdBy","attemptKey","ok","review","status","error","allowance","siteName","pages","targets","result","step","instructions","messages","role","content","tool","check","homeId","extend","design","spent","refused","value","aiJobId","sitePages","thinking","effort","complete","homeScreenIds","screenIds","screens","screen","page","runAiJobLayoutStep","registerAiLayoutJob","minimumMsFor"],"mappings":"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"}
|
|
@@ -23,6 +23,7 @@ import type { AiLayoutTargets } from '../layout-language/ai-layout-links';
|
|
|
23
23
|
import { type AiGenerationCheck, type AiValidatedGeneration } from '../runtime/ai-doctrine';
|
|
24
24
|
import { type AiDoctrineTreeContext, type AiDoctrineViolation } from '../runtime/ai-doctrine-validators';
|
|
25
25
|
import type { AiLoadEstimate } from '../runtime/ai-palette';
|
|
26
|
+
import type { AiLayoutDesign } from '../layout-language/ai-layout-design';
|
|
26
27
|
import type { AiSystemBlock } from '../runtime/ai-runtime';
|
|
27
28
|
/**
|
|
28
29
|
* A page built in the compact layout language (AGL-3660): the page step's
|
|
@@ -79,6 +80,12 @@ export declare const AI_LAYOUT_FRAME_THINKING_TOKENS = 4000;
|
|
|
79
80
|
* keeps a page small.
|
|
80
81
|
*/
|
|
81
82
|
export declare const AI_JOB_PAGE_LANGUAGE_TOKENS: number;
|
|
83
|
+
/**
|
|
84
|
+
* The most one section of a page is written in, its copy included: the top of
|
|
85
|
+
* the 120 to 250 tokens a section runs to. The Free site wall prices each
|
|
86
|
+
* planned section at it (AGL-3660).
|
|
87
|
+
*/
|
|
88
|
+
export declare const AI_LAYOUT_SECTION_MOST_TOKENS = 250;
|
|
82
89
|
/**
|
|
83
90
|
* The time a language page pass needs: its lookup rounds, its answer and its
|
|
84
91
|
* re-ask at `AI_JOB_PAGE_LANGUAGE_TOKENS` on the tier the page step is served
|
|
@@ -101,6 +108,13 @@ export declare function aiLayoutPageTargets(input: {
|
|
|
101
108
|
inventory: AiSiteInventory | null;
|
|
102
109
|
own: ReadonlyArray<string | null | undefined>;
|
|
103
110
|
}): AiLayoutTargets;
|
|
111
|
+
/**
|
|
112
|
+
* The page check's context with what a section listing the blog's posts
|
|
113
|
+
* binds (AGL-3676): each post's card links its post and shows its cover by
|
|
114
|
+
* the tokens the page fills per post, which a page's store admits whole only
|
|
115
|
+
* where a listing places them.
|
|
116
|
+
*/
|
|
117
|
+
export declare function aiLayoutListingContext(context: AiDoctrineTreeContext, targets: AiLayoutTargets): AiDoctrineTreeContext;
|
|
104
118
|
/** The page's user turn: the page, the brief, the plan, its sections and where its links may go. */
|
|
105
119
|
export declare function aiLayoutPagePrompt(input: {
|
|
106
120
|
job: Pick<AiJob, 'brief' | 'inputs' | '$id'>;
|
|
@@ -111,6 +125,13 @@ export declare function aiLayoutPagePrompt(input: {
|
|
|
111
125
|
/** The sections a re-ask still needs, by plan index; absent on the first ask. */
|
|
112
126
|
missing?: readonly number[];
|
|
113
127
|
}): string;
|
|
128
|
+
/**
|
|
129
|
+
* The site a page is designed for (AGL-3660): the kind its job names, the
|
|
130
|
+
* seed its look was drawn with — the same for every page of the site, so the
|
|
131
|
+
* site reads as one design — and whether the page is its home. `null` for a
|
|
132
|
+
* job that names no kind of site, which compiles the compiler's first way.
|
|
133
|
+
*/
|
|
134
|
+
export declare function aiLayoutDesignOf(job: Pick<AiJob, '$id' | 'inputs'>, slug: string): AiLayoutDesign | null;
|
|
114
135
|
/** A validated language page, stored as the page keeps it. */
|
|
115
136
|
export interface AiLayoutPageBuilt {
|
|
116
137
|
nodes: NodesMap;
|
|
@@ -134,6 +155,8 @@ export interface AiLayoutPageCheckInput {
|
|
|
134
155
|
kept?: Array<AiLayoutSection | null>;
|
|
135
156
|
/** The plan indices this answer's sections fill, in order; absent, every section. */
|
|
136
157
|
only?: readonly number[];
|
|
158
|
+
/** The site the page is drawn for (`aiLayoutDesignOf`); absent, the compiler's first design. */
|
|
159
|
+
design?: AiLayoutDesign | null;
|
|
137
160
|
}
|
|
138
161
|
/**
|
|
139
162
|
* The follow-up turn for the sections a page's answer left out (AGL-3660):
|
|
@@ -163,7 +186,9 @@ export declare function aiLayoutShownItems(itemIds: readonly (readonly string[])
|
|
|
163
186
|
* and an intro over nothing is a section a visitor reads as broken, so it is
|
|
164
187
|
* asked for again.
|
|
165
188
|
*/
|
|
166
|
-
export declare function aiLayoutEmptyItemSections(screen: Pick<AiBuildPlanScreen, 'sections'>, items: readonly number[]
|
|
189
|
+
export declare function aiLayoutEmptyItemSections(screen: Pick<AiBuildPlanScreen, 'sections'>, items: readonly number[],
|
|
190
|
+
/** Sections whose only items were customer quotes, which no answer may give (AGL-3676). */
|
|
191
|
+
quotesOnly?: readonly number[]): AiDoctrineViolation[];
|
|
167
192
|
/**
|
|
168
193
|
* The check a language page answer is held to: each planned section read,
|
|
169
194
|
* the page compiled, stored and checked whole. Sections that read are kept
|