@aglyn/plugins-ai 1.0.0-beta.256 → 1.0.0-beta.258
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/components/assist-panel.component.js +21 -8
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/insights/ai-figure-readers.js +15 -8
- package/src/lib/insights/ai-figure-readers.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.d.ts +9 -0
- package/src/lib/jobs/ai-job-build-step.js +19 -3
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-form-step.js +3 -3
- package/src/lib/jobs/ai-job-form-step.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.js +1 -1
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +19 -0
- package/src/lib/jobs/ai-job-page-language.js +24 -4
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.js +12 -1
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +14 -0
- package/src/lib/jobs/ai-job-page-step.js +69 -20
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-seo-step.js +3 -2
- package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +10 -5
- package/src/lib/jobs/ai-layout-stock-photos.js +89 -7
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-stock-photo-copper-kettle-recordings.d.ts +21 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-copper-kettle-recordings.js +126 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-copper-kettle-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.js +52 -52
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-copy.d.ts +9 -5
- package/src/lib/layout-language/ai-layout-copy.js +22 -14
- package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.js +4 -4
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +44 -15
- package/src/lib/layout-language/ai-layout-pictures.js +29 -8
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-build-job.js +14 -0
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-host-publish-context.d.ts +6 -0
- package/src/lib/model/ai-host-publish-context.js +15 -2
- package/src/lib/model/ai-host-publish-context.js.map +1 -1
- package/src/lib/model/assist-follow-up.d.ts +35 -0
- package/src/lib/model/assist-follow-up.js +44 -4
- package/src/lib/model/assist-follow-up.js.map +1 -1
- package/src/lib/providers/anthropic.js +17 -4
- package/src/lib/providers/anthropic.js.map +1 -1
- package/src/lib/providers/cache-write-hour.d.ts +37 -0
- package/src/lib/providers/cache-write-hour.js +46 -0
- package/src/lib/providers/cache-write-hour.js.map +1 -0
- package/src/lib/providers/contract.d.ts +16 -3
- package/src/lib/providers/contract.js.map +1 -1
- package/src/lib/providers/routing.d.ts +13 -0
- package/src/lib/providers/routing.js +27 -11
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/runtime/ai-dev-env.d.ts +13 -3
- package/src/lib/runtime/ai-dev-env.js +17 -3
- package/src/lib/runtime/ai-dev-env.js.map +1 -1
- package/src/lib/runtime/ai-dev-replay.js +4 -2
- package/src/lib/runtime/ai-dev-replay.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +41 -1
- package/src/lib/runtime/ai-doctrine-validators.js +263 -2
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +7 -1
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +1 -1
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +1 -1
- package/src/lib/runtime/ai-eval.js +18 -1
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +162 -2
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-placeholder-tokens.d.ts +70 -0
- package/src/lib/runtime/ai-placeholder-tokens.js +169 -0
- package/src/lib/runtime/ai-placeholder-tokens.js.map +1 -0
- package/src/lib/runtime/ai-repeated-items.js +5 -2
- package/src/lib/runtime/ai-repeated-items.js.map +1 -1
- package/src/lib/runtime/ai-runtime.js +2 -3
- package/src/lib/runtime/ai-runtime.js.map +1 -1
- package/src/lib/server/assist-chat.js +8 -3
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-view-context.d.ts +5 -1
- package/src/lib/server/assist-view-context.js +8 -3
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/usage/assist-usage.d.ts +2 -0
- package/src/lib/usage/assist-usage.js +8 -3
- package/src/lib/usage/assist-usage.js.map +1 -1
|
@@ -57,17 +57,21 @@ export declare function aiLayoutCleanText(value: unknown): string;
|
|
|
57
57
|
* word the job was given — the brief, the site's answers and its profile.
|
|
58
58
|
*/
|
|
59
59
|
export declare function aiLayoutGuardContacts(line: string, facts: string): string;
|
|
60
|
-
/**
|
|
61
|
-
|
|
60
|
+
/**
|
|
61
|
+
* A line fitted to its place with invented contact details written as gaps;
|
|
62
|
+
* `language` is the site's (see `aiLayoutFitText`).
|
|
63
|
+
*/
|
|
64
|
+
export declare function aiLayoutWords(value: unknown, place: AiLayoutTextPlace, facts: string, language?: string): string;
|
|
62
65
|
/**
|
|
63
66
|
* A line held to the characters its place holds (see `fitText`), and none at
|
|
64
|
-
* all where what is left is filler.
|
|
67
|
+
* all where what is left is filler. `language` is the site's language code,
|
|
68
|
+
* which says what a dangling word is (AGL-3828); absent, English.
|
|
65
69
|
*/
|
|
66
|
-
export declare function aiLayoutFitText(value: unknown, place: AiLayoutTextPlace): string;
|
|
70
|
+
export declare function aiLayoutFitText(value: unknown, place: AiLayoutTextPlace, language?: string): string;
|
|
67
71
|
/**
|
|
68
72
|
* Whether a line is filler rule 14 refuses ("lorem ipsum", "your text here"):
|
|
69
73
|
* such a line is no copy, and the compiler leaves it out rather than ship it.
|
|
70
74
|
*/
|
|
71
75
|
export declare function aiLayoutIsFiller(text: string): boolean;
|
|
72
76
|
/** A quote that cannot be a real customer's, written as the gap the owner fills with one (rule 14). */
|
|
73
|
-
export declare function aiLayoutQuoteGap(value: unknown): string;
|
|
77
|
+
export declare function aiLayoutQuoteGap(value: unknown, language?: string): string;
|
|
@@ -49,10 +49,14 @@
|
|
|
49
49
|
return value.replace(MARKUP, '')// A JSON escape a model wrote out as text ("\\u2014") is the character it names.
|
|
50
50
|
.replace(/\\u([0-9a-fA-F]{4})/g, (_, hex)=>String.fromCharCode(parseInt(hex, 16))).replace(/\s+/g, ' ').trim();
|
|
51
51
|
}
|
|
52
|
-
/**
|
|
52
|
+
/**
|
|
53
|
+
* The line with every trailing dangling word taken off, as rule 14 reads one,
|
|
54
|
+
* in the site's language (AGL-3828): a Spanish line cut to "…para la" loses
|
|
55
|
+
* both words, as an English one cut to "…for the" does.
|
|
56
|
+
*/ function withoutDanglingTail(line, language) {
|
|
53
57
|
let text = line.replace(TRAILING_JOINERS, '').trim();
|
|
54
58
|
for(let guard = 0; guard < 6; guard += 1){
|
|
55
|
-
const word = aiDanglingWord(text);
|
|
59
|
+
const word = aiDanglingWord(text, language);
|
|
56
60
|
if (!word) break;
|
|
57
61
|
text = text.slice(0, text.length - word.length).replace(TRAILING_JOINERS, '').trim();
|
|
58
62
|
}
|
|
@@ -63,10 +67,10 @@
|
|
|
63
67
|
* a line to end: at the last sentence's end that leaves at least half the
|
|
64
68
|
* room, else the last clause boundary that does, else the last whole word. A
|
|
65
69
|
* line that fits is returned clean, with any dangling tail taken off.
|
|
66
|
-
*/ function fitText(value, place) {
|
|
70
|
+
*/ function fitText(value, place, language) {
|
|
67
71
|
const text = aiLayoutCleanText(value);
|
|
68
72
|
const limit = AI_LAYOUT_TEXT_LIMITS[place];
|
|
69
|
-
if (text.length <= limit) return withoutDanglingTail(text);
|
|
73
|
+
if (text.length <= limit) return withoutDanglingTail(text, language);
|
|
70
74
|
const room = text.slice(0, limit + 1);
|
|
71
75
|
const half = Math.floor(limit * 0.4);
|
|
72
76
|
const lastMatch = (pattern)=>{
|
|
@@ -79,11 +83,11 @@
|
|
|
79
83
|
return at;
|
|
80
84
|
};
|
|
81
85
|
const sentence = lastMatch(SENTENCE_END);
|
|
82
|
-
if (sentence > 0) return withoutDanglingTail(room.slice(0, sentence));
|
|
86
|
+
if (sentence > 0) return withoutDanglingTail(room.slice(0, sentence), language);
|
|
83
87
|
const clause = lastMatch(CLAUSE_END);
|
|
84
|
-
if (clause > 0) return withoutDanglingTail(room.slice(0, clause));
|
|
88
|
+
if (clause > 0) return withoutDanglingTail(room.slice(0, clause), language);
|
|
85
89
|
const space = room.lastIndexOf(' ', limit);
|
|
86
|
-
return withoutDanglingTail(space > half ? room.slice(0, space) : room.slice(0, limit));
|
|
90
|
+
return withoutDanglingTail(space > half ? room.slice(0, space) : room.slice(0, limit), language);
|
|
87
91
|
}
|
|
88
92
|
/** A phone number as people write one: seven or more digits with the usual separators. */ const PHONE = /\(?\+?\d[\d\s().-]{6,}\d/g;
|
|
89
93
|
const EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
@@ -106,14 +110,18 @@ const EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
|
106
110
|
return knownDigits.has(digits) ? phone : '[phone number]';
|
|
107
111
|
});
|
|
108
112
|
}
|
|
109
|
-
/**
|
|
110
|
-
|
|
113
|
+
/**
|
|
114
|
+
* A line fitted to its place with invented contact details written as gaps;
|
|
115
|
+
* `language` is the site's (see `aiLayoutFitText`).
|
|
116
|
+
*/ export function aiLayoutWords(value, place, facts, language) {
|
|
117
|
+
return aiLayoutFitText(aiLayoutGuardContacts(aiLayoutCleanText(value), facts), place, language);
|
|
111
118
|
}
|
|
112
119
|
/**
|
|
113
120
|
* A line held to the characters its place holds (see `fitText`), and none at
|
|
114
|
-
* all where what is left is filler.
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
* all where what is left is filler. `language` is the site's language code,
|
|
122
|
+
* which says what a dangling word is (AGL-3828); absent, English.
|
|
123
|
+
*/ export function aiLayoutFitText(value, place, language) {
|
|
124
|
+
const text = fitText(value, place, language);
|
|
117
125
|
return aiLayoutIsFiller(text) ? '' : text;
|
|
118
126
|
}
|
|
119
127
|
/**
|
|
@@ -127,8 +135,8 @@ const EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
|
127
135
|
}
|
|
128
136
|
]).length > 0;
|
|
129
137
|
}
|
|
130
|
-
/** A quote that cannot be a real customer's, written as the gap the owner fills with one (rule 14). */ export function aiLayoutQuoteGap(value) {
|
|
131
|
-
const text = aiLayoutFitText(value, 'quote').replace(/^["“'‘]+|["”'’]+$/g, '').trim();
|
|
138
|
+
/** A quote that cannot be a real customer's, written as the gap the owner fills with one (rule 14). */ export function aiLayoutQuoteGap(value, language) {
|
|
139
|
+
const text = aiLayoutFitText(value, 'quote', language).replace(/^["“'‘]+|["”'’]+$/g, '').trim();
|
|
132
140
|
if (!text) return '';
|
|
133
141
|
if (/^\[.*\]$/.test(text)) return text;
|
|
134
142
|
return `[${text.replace(/^\[|\]$/g, '')}]`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-copy.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n aiDanglingWord,\n detectOffVoiceCopy,\n} from '../runtime/ai-doctrine-validators'\n\n/**\n * The words of a layout language document, made to fit where they are shown\n * (AGL-3660). Every word is the model's; the compiler only cleans and fits\n * them: markup a person would see is taken out, a line too long for its place\n * ends at a clean boundary instead of mid-word, a line that would end on a\n * dangling word (\"…for you and\") loses it, and a phone number or an email\n * address the brief never gave is written as the bracketed gap the doctrine\n * asks for (rule 14) rather than shown as a fact.\n */\n\n/** Characters each place holds before its line is ended. */\nexport const AI_LAYOUT_TEXT_LIMITS = {\n /** A page's h1. */\n title: 70,\n /** A section's heading. */\n heading: 80,\n /** A card's, a step's or a sub-heading's title. */\n itemTitle: 60,\n eyebrow: 40,\n /** A button's label; the palette holds a label to 40. */\n label: 28,\n lede: 240,\n text: 420,\n itemText: 240,\n listItem: 140,\n stat: 14,\n statLabel: 60,\n question: 120,\n answer: 420,\n quote: 240,\n note: 200,\n /** An image's description, which is its alt text. */\n alt: 160,\n} as const\n\nexport type AiLayoutTextPlace = keyof typeof AI_LAYOUT_TEXT_LIMITS\n\n/** Inline markup a model writes out of habit, which a page would print literally. */\nconst MARKUP = /(\\*\\*|__|`|~~|^#{1,6}\\s+|^\\s*[-*•]\\s+)/gm\n\n/** A sentence's end inside a line. */\nconst SENTENCE_END = /[.!?…](?=[\"'”’)\\]]?\\s)/g\n\n/** A clause boundary inside a line: a comma, a semicolon, a colon or a dash, with what follows. */\nconst CLAUSE_END = /\\s*(?:[,;:]|\\s[—–-])\\s/g\n\n/** Punctuation that may not end a line it was cut back to. */\nconst TRAILING_JOINERS = /[\\s,;:—–-]+$/\n\n/** Collapses whitespace and takes out markup a page would print. */\nexport function aiLayoutCleanText(value: unknown): string {\n if (typeof value !== 'string') return ''\n return value\n .replace(MARKUP, '')\n // A JSON escape a model wrote out as text (\"\\\\u2014\") is the character it names.\n .replace(/\\\\u([0-9a-fA-F]{4})/g, (_, hex: string) => String.fromCharCode(parseInt(hex, 16)))\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\n/** The line with every trailing dangling word taken off, as rule 14 reads one. */\nfunction withoutDanglingTail(line: string): string {\n let text = line.replace(TRAILING_JOINERS, '').trim()\n for (let guard = 0; guard < 6; guard += 1) {\n const word = aiDanglingWord(text)\n if (!word) break\n text = text\n .slice(0, text.length - word.length)\n .replace(TRAILING_JOINERS, '')\n .trim()\n }\n return text\n}\n\n/**\n * A line held to the characters its place holds, ended where a reader expects\n * a line to end: at the last sentence's end that leaves at least half the\n * room, else the last clause boundary that does, else the last whole word. A\n * line that fits is returned clean, with any dangling tail taken off.\n */\nfunction fitText(value: unknown, place: AiLayoutTextPlace): string {\n const text = aiLayoutCleanText(value)\n const limit = AI_LAYOUT_TEXT_LIMITS[place]\n if (text.length <= limit) return withoutDanglingTail(text)\n const room = text.slice(0, limit + 1)\n const half = Math.floor(limit * 0.4)\n const lastMatch = (pattern: RegExp): number => {\n let at = -1\n for (const match of room.matchAll(pattern)) {\n const end = (match.index ?? 0) + (pattern === SENTENCE_END ? 1 : 0)\n if (end <= limit && end >= half) at = end\n }\n return at\n }\n const sentence = lastMatch(SENTENCE_END)\n if (sentence > 0) return withoutDanglingTail(room.slice(0, sentence))\n const clause = lastMatch(CLAUSE_END)\n if (clause > 0) return withoutDanglingTail(room.slice(0, clause))\n const space = room.lastIndexOf(' ', limit)\n return withoutDanglingTail(\n space > half ? room.slice(0, space) : room.slice(0, limit),\n )\n}\n\n/** A phone number as people write one: seven or more digits with the usual separators. */\nconst PHONE = /\\(?\\+?\\d[\\d\\s().-]{6,}\\d/g\nconst EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/g\n\n/** The digits of a phone number, which is what two spellings of one number share. */\nfunction digitsOf(value: string): string {\n return value.replace(/\\D/g, '')\n}\n\n/**\n * The line with every phone number and email address the brief did not give\n * written as the gap (rule 14): a site's contact details are facts, and a\n * model that writes one the brief lacks is inventing it. `facts` is every\n * word the job was given — the brief, the site's answers and its profile.\n */\nexport function aiLayoutGuardContacts(line: string, facts: string): string {\n const known = facts.toLowerCase()\n const knownDigits = new Set((facts.match(PHONE) ?? []).map(digitsOf))\n return line\n .replace(EMAIL, (email) =>\n known.includes(email.toLowerCase()) ? email : '[email address]',\n )\n .replace(PHONE, (phone) => {\n const digits = digitsOf(phone)\n // A year, a price or a count is not a phone number.\n if (digits.length < 7) return phone\n return knownDigits.has(digits) ? phone : '[phone number]'\n })\n}\n\n/** A line fitted to its place with invented contact details written as gaps. */\nexport function aiLayoutWords(\n value: unknown,\n place: AiLayoutTextPlace,\n facts: string,\n): string {\n return aiLayoutFitText(\n aiLayoutGuardContacts(aiLayoutCleanText(value), facts),\n place,\n )\n}\n\n/**\n * A line held to the characters its place holds (see `fitText`), and none at\n * all where what is left is filler.\n */\nexport function aiLayoutFitText(\n value: unknown,\n place: AiLayoutTextPlace,\n): string {\n const text = fitText(value, place)\n return aiLayoutIsFiller(text) ? '' : text\n}\n\n/**\n * Whether a line is filler rule 14 refuses (\"lorem ipsum\", \"your text here\"):\n * such a line is no copy, and the compiler leaves it out rather than ship it.\n */\nexport function aiLayoutIsFiller(text: string): boolean {\n return detectOffVoiceCopy([{ at: 'line', text }]).length > 0\n}\n\n/** A quote that cannot be a real customer's, written as the gap the owner fills with one (rule 14). */\nexport function aiLayoutQuoteGap(value: unknown): string {\n const text = aiLayoutFitText(value, 'quote')\n .replace(/^[\"“'‘]+|[\"”'’]+$/g, '')\n .trim()\n if (!text) return ''\n if (/^\\[.*\\]$/.test(text)) return text\n return `[${text.replace(/^\\[|\\]$/g, '')}]`\n}\n"],"names":["aiDanglingWord","detectOffVoiceCopy","AI_LAYOUT_TEXT_LIMITS","title","heading","itemTitle","eyebrow","label","lede","text","itemText","listItem","stat","statLabel","question","answer","quote","note","alt","MARKUP","SENTENCE_END","CLAUSE_END","TRAILING_JOINERS","aiLayoutCleanText","value","replace","_","hex","String","fromCharCode","parseInt","trim","withoutDanglingTail","line","guard","word","slice","length","fitText","place","limit","room","half","Math","floor","lastMatch","pattern","at","match","matchAll","end","index","sentence","clause","space","lastIndexOf","PHONE","EMAIL","digitsOf","aiLayoutGuardContacts","facts","known","toLowerCase","knownDigits","Set","map","email","includes","phone","digits","has","aiLayoutWords","aiLayoutFitText","aiLayoutIsFiller","aiLayoutQuoteGap","test"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-copy.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n aiDanglingWord,\n detectOffVoiceCopy,\n} from '../runtime/ai-doctrine-validators'\n\n/**\n * The words of a layout language document, made to fit where they are shown\n * (AGL-3660). Every word is the model's; the compiler only cleans and fits\n * them: markup a person would see is taken out, a line too long for its place\n * ends at a clean boundary instead of mid-word, a line that would end on a\n * dangling word (\"…for you and\") loses it, and a phone number or an email\n * address the brief never gave is written as the bracketed gap the doctrine\n * asks for (rule 14) rather than shown as a fact.\n */\n\n/** Characters each place holds before its line is ended. */\nexport const AI_LAYOUT_TEXT_LIMITS = {\n /** A page's h1. */\n title: 70,\n /** A section's heading. */\n heading: 80,\n /** A card's, a step's or a sub-heading's title. */\n itemTitle: 60,\n eyebrow: 40,\n /** A button's label; the palette holds a label to 40. */\n label: 28,\n lede: 240,\n text: 420,\n itemText: 240,\n listItem: 140,\n stat: 14,\n statLabel: 60,\n question: 120,\n answer: 420,\n quote: 240,\n note: 200,\n /** An image's description, which is its alt text. */\n alt: 160,\n} as const\n\nexport type AiLayoutTextPlace = keyof typeof AI_LAYOUT_TEXT_LIMITS\n\n/** Inline markup a model writes out of habit, which a page would print literally. */\nconst MARKUP = /(\\*\\*|__|`|~~|^#{1,6}\\s+|^\\s*[-*•]\\s+)/gm\n\n/** A sentence's end inside a line. */\nconst SENTENCE_END = /[.!?…](?=[\"'”’)\\]]?\\s)/g\n\n/** A clause boundary inside a line: a comma, a semicolon, a colon or a dash, with what follows. */\nconst CLAUSE_END = /\\s*(?:[,;:]|\\s[—–-])\\s/g\n\n/** Punctuation that may not end a line it was cut back to. */\nconst TRAILING_JOINERS = /[\\s,;:—–-]+$/\n\n/** Collapses whitespace and takes out markup a page would print. */\nexport function aiLayoutCleanText(value: unknown): string {\n if (typeof value !== 'string') return ''\n return value\n .replace(MARKUP, '')\n // A JSON escape a model wrote out as text (\"\\\\u2014\") is the character it names.\n .replace(/\\\\u([0-9a-fA-F]{4})/g, (_, hex: string) => String.fromCharCode(parseInt(hex, 16)))\n .replace(/\\s+/g, ' ')\n .trim()\n}\n\n/**\n * The line with every trailing dangling word taken off, as rule 14 reads one,\n * in the site's language (AGL-3828): a Spanish line cut to \"…para la\" loses\n * both words, as an English one cut to \"…for the\" does.\n */\nfunction withoutDanglingTail(line: string, language?: string): string {\n let text = line.replace(TRAILING_JOINERS, '').trim()\n for (let guard = 0; guard < 6; guard += 1) {\n const word = aiDanglingWord(text, language)\n if (!word) break\n text = text\n .slice(0, text.length - word.length)\n .replace(TRAILING_JOINERS, '')\n .trim()\n }\n return text\n}\n\n/**\n * A line held to the characters its place holds, ended where a reader expects\n * a line to end: at the last sentence's end that leaves at least half the\n * room, else the last clause boundary that does, else the last whole word. A\n * line that fits is returned clean, with any dangling tail taken off.\n */\nfunction fitText(value: unknown, place: AiLayoutTextPlace, language?: string): string {\n const text = aiLayoutCleanText(value)\n const limit = AI_LAYOUT_TEXT_LIMITS[place]\n if (text.length <= limit) return withoutDanglingTail(text, language)\n const room = text.slice(0, limit + 1)\n const half = Math.floor(limit * 0.4)\n const lastMatch = (pattern: RegExp): number => {\n let at = -1\n for (const match of room.matchAll(pattern)) {\n const end = (match.index ?? 0) + (pattern === SENTENCE_END ? 1 : 0)\n if (end <= limit && end >= half) at = end\n }\n return at\n }\n const sentence = lastMatch(SENTENCE_END)\n if (sentence > 0) return withoutDanglingTail(room.slice(0, sentence), language)\n const clause = lastMatch(CLAUSE_END)\n if (clause > 0) return withoutDanglingTail(room.slice(0, clause), language)\n const space = room.lastIndexOf(' ', limit)\n return withoutDanglingTail(\n space > half ? room.slice(0, space) : room.slice(0, limit),\n language,\n )\n}\n\n/** A phone number as people write one: seven or more digits with the usual separators. */\nconst PHONE = /\\(?\\+?\\d[\\d\\s().-]{6,}\\d/g\nconst EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}/g\n\n/** The digits of a phone number, which is what two spellings of one number share. */\nfunction digitsOf(value: string): string {\n return value.replace(/\\D/g, '')\n}\n\n/**\n * The line with every phone number and email address the brief did not give\n * written as the gap (rule 14): a site's contact details are facts, and a\n * model that writes one the brief lacks is inventing it. `facts` is every\n * word the job was given — the brief, the site's answers and its profile.\n */\nexport function aiLayoutGuardContacts(line: string, facts: string): string {\n const known = facts.toLowerCase()\n const knownDigits = new Set((facts.match(PHONE) ?? []).map(digitsOf))\n return line\n .replace(EMAIL, (email) =>\n known.includes(email.toLowerCase()) ? email : '[email address]',\n )\n .replace(PHONE, (phone) => {\n const digits = digitsOf(phone)\n // A year, a price or a count is not a phone number.\n if (digits.length < 7) return phone\n return knownDigits.has(digits) ? phone : '[phone number]'\n })\n}\n\n/**\n * A line fitted to its place with invented contact details written as gaps;\n * `language` is the site's (see `aiLayoutFitText`).\n */\nexport function aiLayoutWords(\n value: unknown,\n place: AiLayoutTextPlace,\n facts: string,\n language?: string,\n): string {\n return aiLayoutFitText(\n aiLayoutGuardContacts(aiLayoutCleanText(value), facts),\n place,\n language,\n )\n}\n\n/**\n * A line held to the characters its place holds (see `fitText`), and none at\n * all where what is left is filler. `language` is the site's language code,\n * which says what a dangling word is (AGL-3828); absent, English.\n */\nexport function aiLayoutFitText(\n value: unknown,\n place: AiLayoutTextPlace,\n language?: string,\n): string {\n const text = fitText(value, place, language)\n return aiLayoutIsFiller(text) ? '' : text\n}\n\n/**\n * Whether a line is filler rule 14 refuses (\"lorem ipsum\", \"your text here\"):\n * such a line is no copy, and the compiler leaves it out rather than ship it.\n */\nexport function aiLayoutIsFiller(text: string): boolean {\n return detectOffVoiceCopy([{ at: 'line', text }]).length > 0\n}\n\n/** A quote that cannot be a real customer's, written as the gap the owner fills with one (rule 14). */\nexport function aiLayoutQuoteGap(value: unknown, language?: string): string {\n const text = aiLayoutFitText(value, 'quote', language)\n .replace(/^[\"“'‘]+|[\"”'’]+$/g, '')\n .trim()\n if (!text) return ''\n if (/^\\[.*\\]$/.test(text)) return text\n return `[${text.replace(/^\\[|\\]$/g, '')}]`\n}\n"],"names":["aiDanglingWord","detectOffVoiceCopy","AI_LAYOUT_TEXT_LIMITS","title","heading","itemTitle","eyebrow","label","lede","text","itemText","listItem","stat","statLabel","question","answer","quote","note","alt","MARKUP","SENTENCE_END","CLAUSE_END","TRAILING_JOINERS","aiLayoutCleanText","value","replace","_","hex","String","fromCharCode","parseInt","trim","withoutDanglingTail","line","language","guard","word","slice","length","fitText","place","limit","room","half","Math","floor","lastMatch","pattern","at","match","matchAll","end","index","sentence","clause","space","lastIndexOf","PHONE","EMAIL","digitsOf","aiLayoutGuardContacts","facts","known","toLowerCase","knownDigits","Set","map","email","includes","phone","digits","has","aiLayoutWords","aiLayoutFitText","aiLayoutIsFiller","aiLayoutQuoteGap","test"],"mappings":"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"}
|
|
@@ -139,7 +139,7 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
139
139
|
splitAt: -2,
|
|
140
140
|
quotesOnly: []
|
|
141
141
|
};
|
|
142
|
-
const name = aiLayoutFitText(plan.siteName, 'heading') || 'Home';
|
|
142
|
+
const name = aiLayoutFitText(plan.siteName, 'heading', plan.language) || 'Home';
|
|
143
143
|
const header = (_frame_header = frame.header) != null ? _frame_header : {
|
|
144
144
|
blocks: []
|
|
145
145
|
};
|
|
@@ -148,7 +148,7 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
148
148
|
const scope = frameScope(page, 'header', band === 'brand' || band === 'dark' ? 'brand' : 'plain', false);
|
|
149
149
|
const navLink = (entry, role, interactions)=>{
|
|
150
150
|
const id = tree.add('muiScreenLink', _extends({
|
|
151
|
-
children: aiLayoutFitText(entry.label, 'label') || entry.label.slice(0, 28)
|
|
151
|
+
children: aiLayoutFitText(entry.label, 'label', plan.language) || entry.label.slice(0, 28)
|
|
152
152
|
}, entry.href ? {
|
|
153
153
|
href: entry.href
|
|
154
154
|
} : {
|
|
@@ -210,7 +210,7 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
210
210
|
what: `a ${block.kind} in the header; left out`
|
|
211
211
|
});
|
|
212
212
|
}
|
|
213
|
-
const ctaLabel = ctaBlock ? aiLayoutFitText(ctaBlock.text, 'label') : '';
|
|
213
|
+
const ctaLabel = ctaBlock ? aiLayoutFitText(ctaBlock.text, 'label', plan.language) : '';
|
|
214
214
|
const ctaResolved = ctaBlock && ctaLabel ? aiLayoutResolveLink(ctaBlock.to, ctaLabel, scope.link, page.targets) : null;
|
|
215
215
|
if (ctaBlock && !ctaResolved) page.settled.push({
|
|
216
216
|
at: 'header',
|
|
@@ -349,7 +349,7 @@ import { AiLayoutTreeBuilder } from "./ai-layout-tree.js";
|
|
|
349
349
|
], 'header');
|
|
350
350
|
// A selling store's announcement bar, above the header, linking its shop (AGL-3676).
|
|
351
351
|
const announcement = store && plan.cart && plan.shopId ? (()=>{
|
|
352
|
-
const said = aiLayoutFitText(noteBlock == null ? void 0 : noteBlock.text, 'note');
|
|
352
|
+
const said = aiLayoutFitText(noteBlock == null ? void 0 : noteBlock.text, 'note', plan.language);
|
|
353
353
|
if (noteBlock && !said) page.settled.push({
|
|
354
354
|
at: 'header',
|
|
355
355
|
what: 'an announcement with no words left; the shop’s own line instead'
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-frame.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiStoreFrameLinks } from '../model/ai-site-store-pages'\nimport { AI_ICON_LIBRARY } from '../runtime/ai-icon-library'\nimport {\n AI_LAYOUT_MAX_IMAGES,\n bandSx,\n compileFlow,\n compileRow,\n destinationProps,\n type PageScope,\n type SectionScope,\n} from './ai-layout-compiler'\nimport { aiLayoutFitText } from './ai-layout-copy'\nimport { aiLayoutRepairFrameHollows } from './ai-layout-frame-hollows'\nimport { AI_LAYOUT_CART_ELEMENT } from './ai-layout-listings'\nimport {\n AI_LAYOUT_MAX_ITEMS,\n type AiLayoutBand,\n type AiLayoutBlock,\n type AiLayoutSection,\n type AiLayoutSettlement,\n} from './ai-layout-language'\nimport { AI_LAYOUT_LABELS_EN, aiLayoutLabel } from './ai-layout-labels'\nimport {\n aiLayoutRenamedLabel,\n aiLayoutResolveLink,\n type AiLayoutPage,\n type AiLayoutTargets,\n} from './ai-layout-links'\nimport { AiLayoutTreeBuilder, type AiLayoutRawTree } from './ai-layout-tree'\n\n/**\n * A site's frame — the header and the footer every page renders between —\n * compiled from the layout language (AGL-3660), the same language a page is\n * designed in.\n *\n * The design is the model's: the header's band and alignment and its one\n * call to action, and the footer's columns and every word in them. What a\n * frame owes the site is the platform's, written here, so it is never wrong\n * and never invented: the site's name as the brand, linking home; its pages\n * as the navigation, a row on a wide screen and the platform's Drawer behind\n * its Menu Button on a phone; the Layout Slot, the page's one main landmark,\n * held tall enough that the footer sits at the bottom of a short page; and\n * the site's pages and name in the footer, where the design gave neither.\n */\n\nexport interface AiLayoutFramePlan {\n /** The site's name, which the header and the footer carry as its brand. */\n siteName: string\n /** The site's home page, which the brand links; none where the site has none yet. */\n homeId: string | null\n /** The pages the navigation links, in order. */\n navPages: readonly AiLayoutPage[]\n /**\n * Whether the site's pages close on a dark photo band (AGL-3660,\n * `AiLayoutDesignChoices.coverClose`): a brand or dark footer under it\n * would read as a second loud band, so the footer takes the quiet one.\n */\n closesDark?: boolean\n /**\n * Whether the site sells (AGL-3676): its header carries the store's cart\n * button — the commerce plugin's Cart, which opens the cart in a drawer\n * from every page — beside the navigation.\n */\n cart?: boolean\n /**\n * A selling store's Shop page (AGL-3676), by its id. Its header is a\n * storefront's: the cart beside the navigation and no call-to-action button\n * repeating a link the navigation already has, and a thin announcement bar\n * above it — the line the design gave its header as a note, else one that\n * opens the shop — linking the Shop page.\n */\n shopId?: string | null\n /**\n * A store's own pages (AGL-3676), by path: the header links the account\n * beside the cart, and the footer the account and the store's policies\n * under the site's pages. They are written after the layout, so they are\n * linked by their addresses, as the blog is.\n */\n storeLinks?: AiStoreFrameLinks | null\n /** The site's language (AGL-3760): the frame's own words are in it where `ai-layout-labels.ts` lists it. */\n language?: string\n}\n\n/** What the header's link to a store's account page says (AGL-3676), in English. */\nexport const AI_LAYOUT_ACCOUNT_LINK = AI_LAYOUT_LABELS_EN.account\n\n/** What a store's announcement bar says where its design gave it no line (AGL-3676): true of every store. */\nexport const AI_LAYOUT_STORE_ANNOUNCEMENT = AI_LAYOUT_LABELS_EN.storeAnnouncement\n\n/** The way into the shop beside the design's own announcement (AGL-3676). */\nexport const AI_LAYOUT_STORE_ANNOUNCEMENT_LINK = AI_LAYOUT_LABELS_EN.shopNow\n\nexport interface AiLayoutCompiledFrame {\n tree: AiLayoutRawTree\n settled: AiLayoutSettlement[]\n}\n\n/** The header's colors by band, as palette roles. */\nfunction headerLook(band: AiLayoutBand): {\n props: Record<string, unknown>\n sx: Record<string, unknown>\n} {\n if (band === 'brand')\n return { props: { color: 'primary' }, sx: { boxShadow: 'none' } }\n if (band === 'dark') {\n return {\n props: { color: 'primary' },\n sx: {\n boxShadow: 'none',\n bgcolor: 'primary.dark',\n color: 'primary.contrastText',\n },\n }\n }\n // A rule, a shadow or nothing under a plain header is the site theme's\n // MuiAppBar style (AGL-3660); the band's color stays here.\n return {\n props: { color: 'inherit' },\n sx: { bgcolor: 'background.paper' },\n }\n}\n\n/** A line that reads as a copyright notice: \"© 2026 …\", \"Copyright …\", \"All rights reserved.\" */\nexport function aiLayoutIsCopyright(text: unknown): boolean {\n return typeof text === 'string' && /^\\s*(?:©|\\(c\\)|copyright\\b)|all rights reserved/i.test(text)\n}\n\n/** What a footer leaves out: a picture slot, and the page sections a footer is no place for. */\nconst FOOTER_LEAVES_OUT: ReadonlySet<AiLayoutBlock['kind']> = new Set([\n 'image',\n 'faq',\n 'quotes',\n 'cards',\n 'steps',\n 'stats',\n 'form',\n 'component',\n])\n\n/** A frame scope: no headings of its own (a layout carries no outline), no pictures. */\nfunction frameScope(\n page: PageScope,\n at: string,\n band: AiLayoutBand,\n centered: boolean,\n): SectionScope {\n return {\n page,\n index: at === 'header' ? 0 : 1,\n at,\n band,\n centered,\n level: 2,\n headed: true,\n itemsLeft: AI_LAYOUT_MAX_ITEMS,\n frame: true,\n link: { sections: [], from: -1, formSection: null },\n }\n}\n\n/** Compiles a site's header and footer, designed in the layout language, into its layout. */\nexport function aiCompileLayoutFrame(\n frame: { header: AiLayoutSection | null; footer: AiLayoutSection | null },\n plan: AiLayoutFramePlan,\n targets: AiLayoutTargets,\n): AiLayoutCompiledFrame {\n const tree = new AiLayoutTreeBuilder('lf')\n // A store's own pages are destinations the footer's list links by path (AGL-3676).\n const storePages: AiLayoutPage[] = (plan.storeLinks?.footer ?? []).map((link) => ({\n id: `store:${link.href}`,\n label: link.label,\n slug: link.href,\n href: link.href,\n }))\n const page: PageScope = {\n tree,\n plan: { title: plan.siteName, sections: [] },\n targets: { ...targets, pages: [...targets.pages, ...storePages], pageId: null },\n // A footer is drawn compact: its lines are lines, never blocks rule 1 would ask to share.\n options: { reusableComponents: true, ...(plan.language ? { language: plan.language } : {}) },\n settled: [],\n scrollTo: {},\n // A frame's lines are its own, never a page section's items.\n itemIds: [],\n // A frame shows no picture slot: a header and a footer are the same on every page.\n images: AI_LAYOUT_MAX_IMAGES,\n strong: { brand: 0, dark: 0 },\n formSection: null,\n pageIcon: AI_ICON_LIBRARY.sparkle,\n formsPlaced: new Set(),\n // A frame is drawn the platform's one way on every site.\n design: null,\n pictures: 0,\n features: 0,\n splitAt: -2,\n quotesOnly: [],\n }\n const name = aiLayoutFitText(plan.siteName, 'heading') || 'Home'\n const header = frame.header ?? { blocks: [] }\n const band = header.band ?? 'plain'\n const look = headerLook(band)\n const scope = frameScope(\n page,\n 'header',\n band === 'brand' || band === 'dark' ? 'brand' : 'plain',\n false,\n )\n const navLink = (\n entry: AiLayoutPage,\n role: string,\n interactions?: unknown[],\n ) => {\n const id = tree.add(\n 'muiScreenLink',\n {\n children:\n aiLayoutFitText(entry.label, 'label') || entry.label.slice(0, 28),\n // The blog is a path, not a page (AGL-3660).\n ...(entry.href ? { href: entry.href } : { screenId: entry.id }),\n renderAs: 'link',\n color: 'inherit',\n },\n { textDecoration: 'none' },\n null,\n role,\n )\n if (interactions) tree.nodes[id].interactions = interactions\n return id\n }\n const pages = plan.navPages.filter((entry) => entry.label.trim())\n const centeredNav = header.align === 'center' && pages.length > 0\n // The brand takes the room the navigation does not, and gives way before\n // it does: a long name ends in an ellipsis rather than pushing into the links.\n const brandSx = {\n typography: 'h6',\n fontWeight: 800,\n textDecoration: 'none',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n minWidth: 0,\n ...(centeredNav ? {} : { flexGrow: 1 }),\n }\n const brand = plan.homeId\n ? tree.add(\n 'muiScreenLink',\n {\n children: name.slice(0, 40),\n screenId: plan.homeId,\n renderAs: 'link',\n color: 'inherit',\n },\n brandSx,\n null,\n 'brand',\n )\n : tree.add(\n 'muiTypography',\n { children: name, variant: 'h6', component: 'p' },\n brandSx,\n null,\n 'brand',\n )\n const nav = pages.length\n ? tree.add(\n 'muiStack',\n {\n component: 'nav',\n ariaLabel: 'Main',\n direction: 'row',\n spacing: '3',\n alignItems: 'center',\n },\n { display: { xs: 'none', md: 'flex' } },\n pages.map((entry) => navLink(entry, 'navLink')),\n 'nav',\n )\n : null\n // The design's call to action: its first button, where it goes somewhere.\n const ctaBlock = header.blocks.find((block) => block.kind === 'button')\n // A store's announcement: the first short line the design gave its header (AGL-3676).\n const store = !!plan.shopId\n const noteBlock = store ? header.blocks.find((block) => block.kind === 'note' || block.kind === 'text' || block.kind === 'lede') : undefined\n for (const block of header.blocks) {\n if (block !== ctaBlock && block !== noteBlock)\n page.settled.push({\n at: 'header',\n what: `a ${block.kind} in the header; left out`,\n })\n }\n const ctaLabel = ctaBlock ? aiLayoutFitText(ctaBlock.text, 'label') : ''\n const ctaResolved =\n ctaBlock && ctaLabel\n ? aiLayoutResolveLink(ctaBlock.to, ctaLabel, scope.link, page.targets)\n : null\n if (ctaBlock && !ctaResolved)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" has nowhere to go; left out`,\n })\n // A storefront's header is its navigation and its cart (AGL-3676): the\n // live Willow Wick header's \"Shop candles\" button repeated its nav's Shop.\n // A store's button that only repeats a page the navigation links is left\n // out, and a store that sells carries its cart in that place instead.\n const repeatsNav =\n ctaResolved?.kind === 'page' && pages.some((entry) => !entry.href && entry.id === ctaResolved.screenId)\n const ctaTo = ctaResolved && store && (plan.cart || repeatsNav) ? null : ctaResolved\n if (ctaResolved && !ctaTo)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" left out: a store's header carries its ${plan.cart ? 'cart' : 'navigation'}, not a button repeating it`,\n })\n const ctaProps = ctaTo\n ? {\n children: aiLayoutRenamedLabel(ctaLabel, ctaTo),\n variant: 'contained',\n color: scope.band === 'brand' ? 'secondary' : 'primary',\n ...destinationProps(ctaTo),\n }\n : null\n // On the smallest phones the call to action moves into the menu.\n const cta = ctaProps\n ? tree.add(\n 'muiButton',\n ctaProps,\n {\n display: { xs: 'none', sm: 'inline-flex' },\n whiteSpace: 'nowrap',\n flexShrink: 0,\n },\n null,\n 'cta',\n )\n : null\n // A store's account, beside its cart and in the phone's menu (AGL-3676).\n const accountPath = plan.storeLinks?.account ?? null\n const accountLink = (role: string, interactions?: unknown[], sx?: Record<string, unknown>) => {\n const id = tree.add(\n 'muiScreenLink',\n { children: aiLayoutLabel('account', plan.language), href: accountPath, renderAs: 'link', color: 'inherit' },\n { textDecoration: 'none', whiteSpace: 'nowrap', ...sx },\n null,\n role,\n )\n if (interactions) tree.nodes[id].interactions = interactions\n return id\n }\n // A phone's menu: the platform's Drawer, opened by its Menu Button; a link\n // followed from it closes it.\n const close = [AI_LAYOUT_CLOSE_DRAWER]\n const drawer = pages.length\n ? tree.add(\n 'muiDrawer',\n { anchor: 'right' },\n null,\n [\n tree.add(\n 'muiStack',\n { component: 'nav', ariaLabel: 'Menu' },\n {\n display: 'flex',\n flexDirection: 'column',\n gap: 2,\n p: 3,\n typography: 'h6',\n },\n [\n ...pages.map((entry) => navLink(entry, 'menuLink', close)),\n accountPath ? accountLink('menuAccount', close) : null,\n ctaProps\n ? tree.add(\n 'muiButton',\n { ...ctaProps, fullWidth: true },\n { mt: 2 },\n null,\n 'menuCta',\n )\n : null,\n ],\n 'menu',\n ),\n ],\n 'drawer',\n )\n : null\n const toggle = drawer\n ? tree.add(\n 'muiDrawerToggle',\n { ariaLabel: 'Open the menu' },\n { display: { xs: 'inline-flex', md: 'none' }, flexShrink: 0 },\n null,\n 'menuButton',\n )\n : null\n const middle =\n nav && centeredNav\n ? tree.add(\n 'muiBox',\n null,\n { flexGrow: 1, display: 'flex', justifyContent: 'center' },\n [nav],\n 'navRoom',\n )\n : nav\n // A store's cart, on every page, where a shopper looks for it (AGL-3676).\n const cart = plan.cart\n ? tree.add(AI_LAYOUT_CART_ELEMENT, { variant: 'button' }, { flexShrink: 0 }, null, 'cart')\n : null\n // On a phone the account is in the menu, and the cart stays in the bar.\n const account = accountPath ? accountLink('account', undefined, { display: { xs: 'none', md: 'inline-flex' }, flexShrink: 0 }) : null\n // The row lives in a Container inside the Toolbar Content, which may only sit in an App Bar.\n const row = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { display: 'flex', alignItems: 'center', columnGap: 3 },\n [brand, middle, cta, account, cart, toggle, drawer],\n 'headerRow',\n )\n const toolbar = tree.add(\n 'muiToolbar',\n { disableGutters: true },\n { minHeight: { xs: 64, md: 76 } },\n [row],\n 'toolbar',\n )\n const bar = tree.add(\n 'muiAppBar',\n {\n ...look.props,\n position: 'sticky',\n // Over a page that opens with a photo cover, the bar sits on the photo (AGL-3660).\n overHero: true,\n component: 'header',\n ariaLabel: 'Site header',\n },\n look.sx,\n [toolbar],\n 'header',\n )\n // A selling store's announcement bar, above the header, linking its shop (AGL-3676).\n const announcement =\n store && plan.cart && plan.shopId\n ? (() => {\n const said = aiLayoutFitText(noteBlock?.text, 'note')\n if (noteBlock && !said) page.settled.push({ at: 'header', what: 'an announcement with no words left; the shop’s own line instead' })\n // The design's line, then the way into the shop; else the way in alone.\n const link = tree.add(\n 'muiScreenLink',\n {\n children: aiLayoutLabel(said ? 'shopNow' : 'storeAnnouncement', plan.language),\n screenId: plan.shopId,\n renderAs: 'link',\n color: 'inherit',\n },\n { fontWeight: 700 },\n null,\n 'announcementLink',\n )\n const words = said\n ? tree.add('muiTypography', { children: said, variant: 'body2', component: 'p' }, null, null, 'announcementText')\n : null\n return tree.add(\n 'muiStack',\n { direction: 'row', spacing: '1.5', justifyContent: 'center', alignItems: 'baseline', useFlexGap: true, flexWrap: 'wrap' },\n { bgcolor: 'primary.main', color: 'primary.contrastText', typography: 'body2', py: 1, px: 2 },\n [words, link],\n 'announcement',\n )\n })()\n : null\n // The page grows into the room the window leaves, so a short page keeps\n // its footer at the bottom.\n const slot = tree.add(\n 'layoutSlot',\n { component: 'main' },\n { flexGrow: 1 },\n null,\n 'slot',\n )\n const footer = compileFooter(page, frame.footer, plan, name)\n // No heading with nothing under it, no column of only a heading, columns\n // top-aligned (AGL-3735): a list under a heading can compile to nothing.\n if (aiLayoutRepairFrameHollows(tree.nodes as unknown as Record<string, unknown>)) {\n page.settled.push({ at: 'footer', what: 'a heading with nothing under it taken out, and its column with it' })\n }\n const column = tree.add(\n 'muiStack',\n null,\n { minHeight: '100vh' },\n [announcement, bar, slot, footer],\n 'frame',\n )\n tree.prune(column, new Set([slot]))\n const rootId = tree.add('div', null, null, [column], 'layout')\n return { tree: { rootId, nodes: tree.nodes }, settled: page.settled }\n}\n\n/** What a store's own column of account and policy links is headed where the footer design gave no store heading (AGL-3735). */\nexport const AI_LAYOUT_STORE_FOOTER_HEADING = AI_LAYOUT_LABELS_EN.customerCare\n\n/** A footer heading a store's account and policies belong under (AGL-3735). */\nconst STORE_HEADING = /\\b(?:shop|store|shopping|help|customer|care|support|service|orders?|account|policies|policy|legal|info|information|shipping|returns)\\b/i\n\n/**\n * The footer heading a store's account and policy links go under\n * (AGL-3735): a heading of the design's that names the store or its help\n * (\"Our shop\", \"Help\", \"Customer care\"), the first with nothing under it\n * before one with words or links of its own. Never the heading over the\n * site's pages, whose list is the site's navigation. `end` is where the\n * links go: the end of what that heading heads.\n */\nexport function aiLayoutFooterStoreHeading(\n columns: readonly AiLayoutBlock[][],\n navItems: readonly { to: string }[],\n): { column: AiLayoutBlock[]; end: number; heading: string } | null {\n const navTo = new Set(navItems.map((item) => item.to))\n const candidates: Array<{ column: AiLayoutBlock[]; end: number; heading: string; empty: boolean }> = []\n for (const column of columns) {\n column.forEach((block, at) => {\n if (block.kind !== 'heading' || !STORE_HEADING.test(block.text ?? '')) return\n let end = at + 1\n while (end < column.length && column[end].kind !== 'heading') end += 1\n const under = column.slice(at + 1, end)\n const site = under.some((one) => one.kind === 'list' && (one.items ?? []).some((item) => navTo.has(item.to ?? '')))\n if (site) return\n candidates.push({ column: column as AiLayoutBlock[], end, heading: String(block.text), empty: under.length === 0 })\n })\n }\n const pick = candidates.find((one) => one.empty) ?? candidates[0]\n return pick ? { column: pick.column, end: pick.end, heading: pick.heading } : null\n}\n\n/** A link followed from the phone menu closes it. */\nconst AI_LAYOUT_CLOSE_DRAWER = {\n id: 'ai-close-menu',\n name: 'Close the menu',\n enabled: true,\n trigger: { event: 'elementClick', everyTime: true },\n steps: [{ type: 'closeDrawer' }],\n}\n\n/**\n * The footer the design describes, with what every footer owes a visitor\n * written by code: the site's pages, where the design links none of them,\n * and the site's name under a rule.\n */\nfunction compileFooter(\n page: PageScope,\n footer: AiLayoutSection | null,\n plan: AiLayoutFramePlan,\n name: string,\n): string {\n const tree = page.tree\n const asked = footer?.band ?? 'soft'\n const band = plan.closesDark && (asked === 'brand' || asked === 'dark') ? 'soft' : asked\n if (band !== asked) page.settled.push({ at: 'footer', what: `a ${asked} footer under a dark closing band drawn soft` })\n const scope = frameScope(page, 'footer', band, footer?.align === 'center')\n // The footer's page links are the site's pages, every one, as the header's\n // are (AGL-3660): a list the model wrote of page links is drawn with them\n // in its place, and a footer with none gets one. The model places the list;\n // it never chooses which pages it holds.\n const navItems = plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n }))\n let linksPages = false\n const blocks = (footer?.blocks ?? []).flatMap((given): AiLayoutBlock[] => {\n // The bottom bar carries the frame's one copyright line; a line of the\n // answer's own that reads as one would print it twice.\n if (aiLayoutIsCopyright(given.text)) {\n page.settled.push({ at: 'footer', what: 'a copyright line; the footer writes its own' })\n return []\n }\n const notice = (item: { title: string; text: string }) => aiLayoutIsCopyright(item.title) || aiLayoutIsCopyright(item.text)\n const block = given.items?.some(notice) ? { ...given, items: given.items.filter((item) => !notice(item)) } : given\n if (block !== given) page.settled.push({ at: 'footer', what: 'a copyright line in a list; the footer writes its own' })\n if (FOOTER_LEAVES_OUT.has(block.kind)) {\n page.settled.push({\n at: 'footer',\n what: `a ${block.kind} in the footer; left out`,\n })\n return []\n }\n const pageLinks =\n block.kind === 'list' &&\n (block.items ?? []).some((item) => /^page:/.test(item.to ?? ''))\n if (!pageLinks) return [block]\n if (linksPages || !navItems.length) {\n page.settled.push({ at: 'footer', what: 'a second list of page links; left out' })\n return []\n }\n linksPages = true\n return [{ ...block, items: navItems }]\n })\n const cols = footer?.cols && footer.cols.length >= 2 ? footer.cols : null\n const columns: AiLayoutBlock[][] = cols ? cols.map(() => []) : [[]]\n for (const block of blocks)\n columns[\n cols && block.col !== undefined ? Math.min(block.col, cols.length - 1) : 0\n ].push(block)\n const weights = cols ? [...cols] : [2]\n if (!linksPages && plan.navPages.length) {\n columns.push([\n {\n kind: 'list',\n items: plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n })),\n },\n ])\n weights.push(1)\n }\n // A store's account and policies (AGL-3676): under the store heading the\n // design already wrote (\"Our shop\", \"Help\"…), never as a column with no\n // heading beside a heading with nothing under it (AGL-3735); where the\n // design wrote none, a column of their own, headed.\n const storeLinks = plan.storeLinks?.footer ?? []\n if (storeLinks.length) {\n const list: AiLayoutBlock = { kind: 'list', items: storeLinks.map((link) => ({ title: link.label, text: '', to: `page:store:${link.href}` })) }\n const home = aiLayoutFooterStoreHeading(columns, navItems)\n if (home) {\n home.column.splice(home.end, 0, list)\n page.settled.push({ at: 'footer', what: `the store's account and policy links under \"${home.heading}\"` })\n } else {\n columns.push([{ kind: 'heading', text: aiLayoutLabel('customerCare', plan.language) }, list])\n weights.push(1)\n }\n }\n const filled = columns\n .map((column, position) => ({ column, weight: weights[position] ?? 1 }))\n .filter((entry) => entry.column.length)\n // A footer's columns are top-aligned: each starts at its heading (AGL-3735).\n const top =\n filled.length > 1\n ? compileRow(scope, filled, 'flex-start')\n : filled.length === 1\n ? compileFlow(scope, filled[0].column, 'full')\n : null\n const copyright = tree.add(\n 'muiTypography',\n { children: `© ${name}`, variant: 'body2', component: 'p' },\n scope.band === 'brand' ? null : { color: 'text.secondary' },\n null,\n 'copyright',\n )\n const bottom = tree.add(\n 'muiBox',\n null,\n { pt: 3, borderTop: 1, borderColor: 'divider' },\n [copyright],\n 'footerBottom',\n )\n const stack = tree.add(\n 'muiStack',\n { spacing: '6' },\n null,\n [top, bottom],\n 'footerStack',\n )\n const container = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { py: { xs: 6, md: 8 } },\n [stack],\n 'footerInner',\n )\n return tree.add(\n 'section',\n {\n element: 'footer',\n ariaLabel: 'Site footer',\n ...(scope.band === 'dark' ? { colorScheme: 'dark' } : {}),\n },\n scope.band === 'soft'\n ? { bgcolor: 'background.paper', borderTop: 1, borderColor: 'divider' }\n : bandSx(scope.band),\n [container],\n 'footer',\n )\n}\n"],"names":["AI_ICON_LIBRARY","AI_LAYOUT_MAX_IMAGES","bandSx","compileFlow","compileRow","destinationProps","aiLayoutFitText","aiLayoutRepairFrameHollows","AI_LAYOUT_CART_ELEMENT","AI_LAYOUT_MAX_ITEMS","AI_LAYOUT_LABELS_EN","aiLayoutLabel","aiLayoutRenamedLabel","aiLayoutResolveLink","AiLayoutTreeBuilder","AI_LAYOUT_ACCOUNT_LINK","account","AI_LAYOUT_STORE_ANNOUNCEMENT","storeAnnouncement","AI_LAYOUT_STORE_ANNOUNCEMENT_LINK","shopNow","headerLook","band","props","color","sx","boxShadow","bgcolor","aiLayoutIsCopyright","text","test","FOOTER_LEAVES_OUT","Set","frameScope","page","at","centered","index","level","headed","itemsLeft","frame","link","sections","from","formSection","aiCompileLayoutFrame","plan","targets","header","tree","storePages","storeLinks","footer","map","id","href","label","slug","title","siteName","pages","pageId","options","reusableComponents","language","settled","scrollTo","itemIds","images","strong","brand","dark","pageIcon","sparkle","formsPlaced","design","pictures","features","splitAt","quotesOnly","name","blocks","look","scope","navLink","entry","role","interactions","add","children","slice","screenId","renderAs","textDecoration","nodes","navPages","filter","trim","centeredNav","align","length","brandSx","typography","fontWeight","whiteSpace","overflow","textOverflow","minWidth","flexGrow","homeId","variant","component","nav","ariaLabel","direction","spacing","alignItems","display","xs","md","ctaBlock","find","block","kind","store","shopId","noteBlock","undefined","push","what","ctaLabel","ctaResolved","to","repeatsNav","some","ctaTo","cart","ctaProps","cta","sm","flexShrink","accountPath","accountLink","close","AI_LAYOUT_CLOSE_DRAWER","drawer","anchor","flexDirection","gap","p","fullWidth","mt","toggle","middle","justifyContent","row","maxWidth","columnGap","toolbar","disableGutters","minHeight","bar","position","overHero","announcement","said","words","useFlexGap","flexWrap","py","px","slot","compileFooter","column","prune","rootId","AI_LAYOUT_STORE_FOOTER_HEADING","customerCare","STORE_HEADING","aiLayoutFooterStoreHeading","columns","navItems","candidates","navTo","item","forEach","end","under","site","one","items","has","heading","String","empty","pick","enabled","trigger","event","everyTime","steps","type","asked","closesDark","linksPages","flatMap","given","notice","pageLinks","cols","col","Math","min","weights","list","home","splice","filled","weight","top","copyright","bottom","pt","borderTop","borderColor","stack","container","element","colorScheme"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-frame.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { AiStoreFrameLinks } from '../model/ai-site-store-pages'\nimport { AI_ICON_LIBRARY } from '../runtime/ai-icon-library'\nimport {\n AI_LAYOUT_MAX_IMAGES,\n bandSx,\n compileFlow,\n compileRow,\n destinationProps,\n type PageScope,\n type SectionScope,\n} from './ai-layout-compiler'\nimport { aiLayoutFitText } from './ai-layout-copy'\nimport { aiLayoutRepairFrameHollows } from './ai-layout-frame-hollows'\nimport { AI_LAYOUT_CART_ELEMENT } from './ai-layout-listings'\nimport {\n AI_LAYOUT_MAX_ITEMS,\n type AiLayoutBand,\n type AiLayoutBlock,\n type AiLayoutSection,\n type AiLayoutSettlement,\n} from './ai-layout-language'\nimport { AI_LAYOUT_LABELS_EN, aiLayoutLabel } from './ai-layout-labels'\nimport {\n aiLayoutRenamedLabel,\n aiLayoutResolveLink,\n type AiLayoutPage,\n type AiLayoutTargets,\n} from './ai-layout-links'\nimport { AiLayoutTreeBuilder, type AiLayoutRawTree } from './ai-layout-tree'\n\n/**\n * A site's frame — the header and the footer every page renders between —\n * compiled from the layout language (AGL-3660), the same language a page is\n * designed in.\n *\n * The design is the model's: the header's band and alignment and its one\n * call to action, and the footer's columns and every word in them. What a\n * frame owes the site is the platform's, written here, so it is never wrong\n * and never invented: the site's name as the brand, linking home; its pages\n * as the navigation, a row on a wide screen and the platform's Drawer behind\n * its Menu Button on a phone; the Layout Slot, the page's one main landmark,\n * held tall enough that the footer sits at the bottom of a short page; and\n * the site's pages and name in the footer, where the design gave neither.\n */\n\nexport interface AiLayoutFramePlan {\n /** The site's name, which the header and the footer carry as its brand. */\n siteName: string\n /** The site's home page, which the brand links; none where the site has none yet. */\n homeId: string | null\n /** The pages the navigation links, in order. */\n navPages: readonly AiLayoutPage[]\n /**\n * Whether the site's pages close on a dark photo band (AGL-3660,\n * `AiLayoutDesignChoices.coverClose`): a brand or dark footer under it\n * would read as a second loud band, so the footer takes the quiet one.\n */\n closesDark?: boolean\n /**\n * Whether the site sells (AGL-3676): its header carries the store's cart\n * button — the commerce plugin's Cart, which opens the cart in a drawer\n * from every page — beside the navigation.\n */\n cart?: boolean\n /**\n * A selling store's Shop page (AGL-3676), by its id. Its header is a\n * storefront's: the cart beside the navigation and no call-to-action button\n * repeating a link the navigation already has, and a thin announcement bar\n * above it — the line the design gave its header as a note, else one that\n * opens the shop — linking the Shop page.\n */\n shopId?: string | null\n /**\n * A store's own pages (AGL-3676), by path: the header links the account\n * beside the cart, and the footer the account and the store's policies\n * under the site's pages. They are written after the layout, so they are\n * linked by their addresses, as the blog is.\n */\n storeLinks?: AiStoreFrameLinks | null\n /** The site's language (AGL-3760): the frame's own words are in it where `ai-layout-labels.ts` lists it. */\n language?: string\n}\n\n/** What the header's link to a store's account page says (AGL-3676), in English. */\nexport const AI_LAYOUT_ACCOUNT_LINK = AI_LAYOUT_LABELS_EN.account\n\n/** What a store's announcement bar says where its design gave it no line (AGL-3676): true of every store. */\nexport const AI_LAYOUT_STORE_ANNOUNCEMENT = AI_LAYOUT_LABELS_EN.storeAnnouncement\n\n/** The way into the shop beside the design's own announcement (AGL-3676). */\nexport const AI_LAYOUT_STORE_ANNOUNCEMENT_LINK = AI_LAYOUT_LABELS_EN.shopNow\n\nexport interface AiLayoutCompiledFrame {\n tree: AiLayoutRawTree\n settled: AiLayoutSettlement[]\n}\n\n/** The header's colors by band, as palette roles. */\nfunction headerLook(band: AiLayoutBand): {\n props: Record<string, unknown>\n sx: Record<string, unknown>\n} {\n if (band === 'brand')\n return { props: { color: 'primary' }, sx: { boxShadow: 'none' } }\n if (band === 'dark') {\n return {\n props: { color: 'primary' },\n sx: {\n boxShadow: 'none',\n bgcolor: 'primary.dark',\n color: 'primary.contrastText',\n },\n }\n }\n // A rule, a shadow or nothing under a plain header is the site theme's\n // MuiAppBar style (AGL-3660); the band's color stays here.\n return {\n props: { color: 'inherit' },\n sx: { bgcolor: 'background.paper' },\n }\n}\n\n/** A line that reads as a copyright notice: \"© 2026 …\", \"Copyright …\", \"All rights reserved.\" */\nexport function aiLayoutIsCopyright(text: unknown): boolean {\n return typeof text === 'string' && /^\\s*(?:©|\\(c\\)|copyright\\b)|all rights reserved/i.test(text)\n}\n\n/** What a footer leaves out: a picture slot, and the page sections a footer is no place for. */\nconst FOOTER_LEAVES_OUT: ReadonlySet<AiLayoutBlock['kind']> = new Set([\n 'image',\n 'faq',\n 'quotes',\n 'cards',\n 'steps',\n 'stats',\n 'form',\n 'component',\n])\n\n/** A frame scope: no headings of its own (a layout carries no outline), no pictures. */\nfunction frameScope(\n page: PageScope,\n at: string,\n band: AiLayoutBand,\n centered: boolean,\n): SectionScope {\n return {\n page,\n index: at === 'header' ? 0 : 1,\n at,\n band,\n centered,\n level: 2,\n headed: true,\n itemsLeft: AI_LAYOUT_MAX_ITEMS,\n frame: true,\n link: { sections: [], from: -1, formSection: null },\n }\n}\n\n/** Compiles a site's header and footer, designed in the layout language, into its layout. */\nexport function aiCompileLayoutFrame(\n frame: { header: AiLayoutSection | null; footer: AiLayoutSection | null },\n plan: AiLayoutFramePlan,\n targets: AiLayoutTargets,\n): AiLayoutCompiledFrame {\n const tree = new AiLayoutTreeBuilder('lf')\n // A store's own pages are destinations the footer's list links by path (AGL-3676).\n const storePages: AiLayoutPage[] = (plan.storeLinks?.footer ?? []).map((link) => ({\n id: `store:${link.href}`,\n label: link.label,\n slug: link.href,\n href: link.href,\n }))\n const page: PageScope = {\n tree,\n plan: { title: plan.siteName, sections: [] },\n targets: { ...targets, pages: [...targets.pages, ...storePages], pageId: null },\n // A footer is drawn compact: its lines are lines, never blocks rule 1 would ask to share.\n options: { reusableComponents: true, ...(plan.language ? { language: plan.language } : {}) },\n settled: [],\n scrollTo: {},\n // A frame's lines are its own, never a page section's items.\n itemIds: [],\n // A frame shows no picture slot: a header and a footer are the same on every page.\n images: AI_LAYOUT_MAX_IMAGES,\n strong: { brand: 0, dark: 0 },\n formSection: null,\n pageIcon: AI_ICON_LIBRARY.sparkle,\n formsPlaced: new Set(),\n // A frame is drawn the platform's one way on every site.\n design: null,\n pictures: 0,\n features: 0,\n splitAt: -2,\n quotesOnly: [],\n }\n const name = aiLayoutFitText(plan.siteName, 'heading', plan.language) || 'Home'\n const header = frame.header ?? { blocks: [] }\n const band = header.band ?? 'plain'\n const look = headerLook(band)\n const scope = frameScope(\n page,\n 'header',\n band === 'brand' || band === 'dark' ? 'brand' : 'plain',\n false,\n )\n const navLink = (\n entry: AiLayoutPage,\n role: string,\n interactions?: unknown[],\n ) => {\n const id = tree.add(\n 'muiScreenLink',\n {\n children:\n aiLayoutFitText(entry.label, 'label', plan.language) || entry.label.slice(0, 28),\n // The blog is a path, not a page (AGL-3660).\n ...(entry.href ? { href: entry.href } : { screenId: entry.id }),\n renderAs: 'link',\n color: 'inherit',\n },\n { textDecoration: 'none' },\n null,\n role,\n )\n if (interactions) tree.nodes[id].interactions = interactions\n return id\n }\n const pages = plan.navPages.filter((entry) => entry.label.trim())\n const centeredNav = header.align === 'center' && pages.length > 0\n // The brand takes the room the navigation does not, and gives way before\n // it does: a long name ends in an ellipsis rather than pushing into the links.\n const brandSx = {\n typography: 'h6',\n fontWeight: 800,\n textDecoration: 'none',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n minWidth: 0,\n ...(centeredNav ? {} : { flexGrow: 1 }),\n }\n const brand = plan.homeId\n ? tree.add(\n 'muiScreenLink',\n {\n children: name.slice(0, 40),\n screenId: plan.homeId,\n renderAs: 'link',\n color: 'inherit',\n },\n brandSx,\n null,\n 'brand',\n )\n : tree.add(\n 'muiTypography',\n { children: name, variant: 'h6', component: 'p' },\n brandSx,\n null,\n 'brand',\n )\n const nav = pages.length\n ? tree.add(\n 'muiStack',\n {\n component: 'nav',\n ariaLabel: 'Main',\n direction: 'row',\n spacing: '3',\n alignItems: 'center',\n },\n { display: { xs: 'none', md: 'flex' } },\n pages.map((entry) => navLink(entry, 'navLink')),\n 'nav',\n )\n : null\n // The design's call to action: its first button, where it goes somewhere.\n const ctaBlock = header.blocks.find((block) => block.kind === 'button')\n // A store's announcement: the first short line the design gave its header (AGL-3676).\n const store = !!plan.shopId\n const noteBlock = store ? header.blocks.find((block) => block.kind === 'note' || block.kind === 'text' || block.kind === 'lede') : undefined\n for (const block of header.blocks) {\n if (block !== ctaBlock && block !== noteBlock)\n page.settled.push({\n at: 'header',\n what: `a ${block.kind} in the header; left out`,\n })\n }\n const ctaLabel = ctaBlock ? aiLayoutFitText(ctaBlock.text, 'label', plan.language) : ''\n const ctaResolved =\n ctaBlock && ctaLabel\n ? aiLayoutResolveLink(ctaBlock.to, ctaLabel, scope.link, page.targets)\n : null\n if (ctaBlock && !ctaResolved)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" has nowhere to go; left out`,\n })\n // A storefront's header is its navigation and its cart (AGL-3676): the\n // live Willow Wick header's \"Shop candles\" button repeated its nav's Shop.\n // A store's button that only repeats a page the navigation links is left\n // out, and a store that sells carries its cart in that place instead.\n const repeatsNav =\n ctaResolved?.kind === 'page' && pages.some((entry) => !entry.href && entry.id === ctaResolved.screenId)\n const ctaTo = ctaResolved && store && (plan.cart || repeatsNav) ? null : ctaResolved\n if (ctaResolved && !ctaTo)\n page.settled.push({\n at: 'header',\n what: `the button \"${ctaLabel}\" left out: a store's header carries its ${plan.cart ? 'cart' : 'navigation'}, not a button repeating it`,\n })\n const ctaProps = ctaTo\n ? {\n children: aiLayoutRenamedLabel(ctaLabel, ctaTo),\n variant: 'contained',\n color: scope.band === 'brand' ? 'secondary' : 'primary',\n ...destinationProps(ctaTo),\n }\n : null\n // On the smallest phones the call to action moves into the menu.\n const cta = ctaProps\n ? tree.add(\n 'muiButton',\n ctaProps,\n {\n display: { xs: 'none', sm: 'inline-flex' },\n whiteSpace: 'nowrap',\n flexShrink: 0,\n },\n null,\n 'cta',\n )\n : null\n // A store's account, beside its cart and in the phone's menu (AGL-3676).\n const accountPath = plan.storeLinks?.account ?? null\n const accountLink = (role: string, interactions?: unknown[], sx?: Record<string, unknown>) => {\n const id = tree.add(\n 'muiScreenLink',\n { children: aiLayoutLabel('account', plan.language), href: accountPath, renderAs: 'link', color: 'inherit' },\n { textDecoration: 'none', whiteSpace: 'nowrap', ...sx },\n null,\n role,\n )\n if (interactions) tree.nodes[id].interactions = interactions\n return id\n }\n // A phone's menu: the platform's Drawer, opened by its Menu Button; a link\n // followed from it closes it.\n const close = [AI_LAYOUT_CLOSE_DRAWER]\n const drawer = pages.length\n ? tree.add(\n 'muiDrawer',\n { anchor: 'right' },\n null,\n [\n tree.add(\n 'muiStack',\n { component: 'nav', ariaLabel: 'Menu' },\n {\n display: 'flex',\n flexDirection: 'column',\n gap: 2,\n p: 3,\n typography: 'h6',\n },\n [\n ...pages.map((entry) => navLink(entry, 'menuLink', close)),\n accountPath ? accountLink('menuAccount', close) : null,\n ctaProps\n ? tree.add(\n 'muiButton',\n { ...ctaProps, fullWidth: true },\n { mt: 2 },\n null,\n 'menuCta',\n )\n : null,\n ],\n 'menu',\n ),\n ],\n 'drawer',\n )\n : null\n const toggle = drawer\n ? tree.add(\n 'muiDrawerToggle',\n { ariaLabel: 'Open the menu' },\n { display: { xs: 'inline-flex', md: 'none' }, flexShrink: 0 },\n null,\n 'menuButton',\n )\n : null\n const middle =\n nav && centeredNav\n ? tree.add(\n 'muiBox',\n null,\n { flexGrow: 1, display: 'flex', justifyContent: 'center' },\n [nav],\n 'navRoom',\n )\n : nav\n // A store's cart, on every page, where a shopper looks for it (AGL-3676).\n const cart = plan.cart\n ? tree.add(AI_LAYOUT_CART_ELEMENT, { variant: 'button' }, { flexShrink: 0 }, null, 'cart')\n : null\n // On a phone the account is in the menu, and the cart stays in the bar.\n const account = accountPath ? accountLink('account', undefined, { display: { xs: 'none', md: 'inline-flex' }, flexShrink: 0 }) : null\n // The row lives in a Container inside the Toolbar Content, which may only sit in an App Bar.\n const row = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { display: 'flex', alignItems: 'center', columnGap: 3 },\n [brand, middle, cta, account, cart, toggle, drawer],\n 'headerRow',\n )\n const toolbar = tree.add(\n 'muiToolbar',\n { disableGutters: true },\n { minHeight: { xs: 64, md: 76 } },\n [row],\n 'toolbar',\n )\n const bar = tree.add(\n 'muiAppBar',\n {\n ...look.props,\n position: 'sticky',\n // Over a page that opens with a photo cover, the bar sits on the photo (AGL-3660).\n overHero: true,\n component: 'header',\n ariaLabel: 'Site header',\n },\n look.sx,\n [toolbar],\n 'header',\n )\n // A selling store's announcement bar, above the header, linking its shop (AGL-3676).\n const announcement =\n store && plan.cart && plan.shopId\n ? (() => {\n const said = aiLayoutFitText(noteBlock?.text, 'note', plan.language)\n if (noteBlock && !said) page.settled.push({ at: 'header', what: 'an announcement with no words left; the shop’s own line instead' })\n // The design's line, then the way into the shop; else the way in alone.\n const link = tree.add(\n 'muiScreenLink',\n {\n children: aiLayoutLabel(said ? 'shopNow' : 'storeAnnouncement', plan.language),\n screenId: plan.shopId,\n renderAs: 'link',\n color: 'inherit',\n },\n { fontWeight: 700 },\n null,\n 'announcementLink',\n )\n const words = said\n ? tree.add('muiTypography', { children: said, variant: 'body2', component: 'p' }, null, null, 'announcementText')\n : null\n return tree.add(\n 'muiStack',\n { direction: 'row', spacing: '1.5', justifyContent: 'center', alignItems: 'baseline', useFlexGap: true, flexWrap: 'wrap' },\n { bgcolor: 'primary.main', color: 'primary.contrastText', typography: 'body2', py: 1, px: 2 },\n [words, link],\n 'announcement',\n )\n })()\n : null\n // The page grows into the room the window leaves, so a short page keeps\n // its footer at the bottom.\n const slot = tree.add(\n 'layoutSlot',\n { component: 'main' },\n { flexGrow: 1 },\n null,\n 'slot',\n )\n const footer = compileFooter(page, frame.footer, plan, name)\n // No heading with nothing under it, no column of only a heading, columns\n // top-aligned (AGL-3735): a list under a heading can compile to nothing.\n if (aiLayoutRepairFrameHollows(tree.nodes as unknown as Record<string, unknown>)) {\n page.settled.push({ at: 'footer', what: 'a heading with nothing under it taken out, and its column with it' })\n }\n const column = tree.add(\n 'muiStack',\n null,\n { minHeight: '100vh' },\n [announcement, bar, slot, footer],\n 'frame',\n )\n tree.prune(column, new Set([slot]))\n const rootId = tree.add('div', null, null, [column], 'layout')\n return { tree: { rootId, nodes: tree.nodes }, settled: page.settled }\n}\n\n/** What a store's own column of account and policy links is headed where the footer design gave no store heading (AGL-3735). */\nexport const AI_LAYOUT_STORE_FOOTER_HEADING = AI_LAYOUT_LABELS_EN.customerCare\n\n/** A footer heading a store's account and policies belong under (AGL-3735). */\nconst STORE_HEADING = /\\b(?:shop|store|shopping|help|customer|care|support|service|orders?|account|policies|policy|legal|info|information|shipping|returns)\\b/i\n\n/**\n * The footer heading a store's account and policy links go under\n * (AGL-3735): a heading of the design's that names the store or its help\n * (\"Our shop\", \"Help\", \"Customer care\"), the first with nothing under it\n * before one with words or links of its own. Never the heading over the\n * site's pages, whose list is the site's navigation. `end` is where the\n * links go: the end of what that heading heads.\n */\nexport function aiLayoutFooterStoreHeading(\n columns: readonly AiLayoutBlock[][],\n navItems: readonly { to: string }[],\n): { column: AiLayoutBlock[]; end: number; heading: string } | null {\n const navTo = new Set(navItems.map((item) => item.to))\n const candidates: Array<{ column: AiLayoutBlock[]; end: number; heading: string; empty: boolean }> = []\n for (const column of columns) {\n column.forEach((block, at) => {\n if (block.kind !== 'heading' || !STORE_HEADING.test(block.text ?? '')) return\n let end = at + 1\n while (end < column.length && column[end].kind !== 'heading') end += 1\n const under = column.slice(at + 1, end)\n const site = under.some((one) => one.kind === 'list' && (one.items ?? []).some((item) => navTo.has(item.to ?? '')))\n if (site) return\n candidates.push({ column: column as AiLayoutBlock[], end, heading: String(block.text), empty: under.length === 0 })\n })\n }\n const pick = candidates.find((one) => one.empty) ?? candidates[0]\n return pick ? { column: pick.column, end: pick.end, heading: pick.heading } : null\n}\n\n/** A link followed from the phone menu closes it. */\nconst AI_LAYOUT_CLOSE_DRAWER = {\n id: 'ai-close-menu',\n name: 'Close the menu',\n enabled: true,\n trigger: { event: 'elementClick', everyTime: true },\n steps: [{ type: 'closeDrawer' }],\n}\n\n/**\n * The footer the design describes, with what every footer owes a visitor\n * written by code: the site's pages, where the design links none of them,\n * and the site's name under a rule.\n */\nfunction compileFooter(\n page: PageScope,\n footer: AiLayoutSection | null,\n plan: AiLayoutFramePlan,\n name: string,\n): string {\n const tree = page.tree\n const asked = footer?.band ?? 'soft'\n const band = plan.closesDark && (asked === 'brand' || asked === 'dark') ? 'soft' : asked\n if (band !== asked) page.settled.push({ at: 'footer', what: `a ${asked} footer under a dark closing band drawn soft` })\n const scope = frameScope(page, 'footer', band, footer?.align === 'center')\n // The footer's page links are the site's pages, every one, as the header's\n // are (AGL-3660): a list the model wrote of page links is drawn with them\n // in its place, and a footer with none gets one. The model places the list;\n // it never chooses which pages it holds.\n const navItems = plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n }))\n let linksPages = false\n const blocks = (footer?.blocks ?? []).flatMap((given): AiLayoutBlock[] => {\n // The bottom bar carries the frame's one copyright line; a line of the\n // answer's own that reads as one would print it twice.\n if (aiLayoutIsCopyright(given.text)) {\n page.settled.push({ at: 'footer', what: 'a copyright line; the footer writes its own' })\n return []\n }\n const notice = (item: { title: string; text: string }) => aiLayoutIsCopyright(item.title) || aiLayoutIsCopyright(item.text)\n const block = given.items?.some(notice) ? { ...given, items: given.items.filter((item) => !notice(item)) } : given\n if (block !== given) page.settled.push({ at: 'footer', what: 'a copyright line in a list; the footer writes its own' })\n if (FOOTER_LEAVES_OUT.has(block.kind)) {\n page.settled.push({\n at: 'footer',\n what: `a ${block.kind} in the footer; left out`,\n })\n return []\n }\n const pageLinks =\n block.kind === 'list' &&\n (block.items ?? []).some((item) => /^page:/.test(item.to ?? ''))\n if (!pageLinks) return [block]\n if (linksPages || !navItems.length) {\n page.settled.push({ at: 'footer', what: 'a second list of page links; left out' })\n return []\n }\n linksPages = true\n return [{ ...block, items: navItems }]\n })\n const cols = footer?.cols && footer.cols.length >= 2 ? footer.cols : null\n const columns: AiLayoutBlock[][] = cols ? cols.map(() => []) : [[]]\n for (const block of blocks)\n columns[\n cols && block.col !== undefined ? Math.min(block.col, cols.length - 1) : 0\n ].push(block)\n const weights = cols ? [...cols] : [2]\n if (!linksPages && plan.navPages.length) {\n columns.push([\n {\n kind: 'list',\n items: plan.navPages.map((entry) => ({\n title: entry.label,\n text: '',\n to: `page:${entry.id}`,\n })),\n },\n ])\n weights.push(1)\n }\n // A store's account and policies (AGL-3676): under the store heading the\n // design already wrote (\"Our shop\", \"Help\"…), never as a column with no\n // heading beside a heading with nothing under it (AGL-3735); where the\n // design wrote none, a column of their own, headed.\n const storeLinks = plan.storeLinks?.footer ?? []\n if (storeLinks.length) {\n const list: AiLayoutBlock = { kind: 'list', items: storeLinks.map((link) => ({ title: link.label, text: '', to: `page:store:${link.href}` })) }\n const home = aiLayoutFooterStoreHeading(columns, navItems)\n if (home) {\n home.column.splice(home.end, 0, list)\n page.settled.push({ at: 'footer', what: `the store's account and policy links under \"${home.heading}\"` })\n } else {\n columns.push([{ kind: 'heading', text: aiLayoutLabel('customerCare', plan.language) }, list])\n weights.push(1)\n }\n }\n const filled = columns\n .map((column, position) => ({ column, weight: weights[position] ?? 1 }))\n .filter((entry) => entry.column.length)\n // A footer's columns are top-aligned: each starts at its heading (AGL-3735).\n const top =\n filled.length > 1\n ? compileRow(scope, filled, 'flex-start')\n : filled.length === 1\n ? compileFlow(scope, filled[0].column, 'full')\n : null\n const copyright = tree.add(\n 'muiTypography',\n { children: `© ${name}`, variant: 'body2', component: 'p' },\n scope.band === 'brand' ? null : { color: 'text.secondary' },\n null,\n 'copyright',\n )\n const bottom = tree.add(\n 'muiBox',\n null,\n { pt: 3, borderTop: 1, borderColor: 'divider' },\n [copyright],\n 'footerBottom',\n )\n const stack = tree.add(\n 'muiStack',\n { spacing: '6' },\n null,\n [top, bottom],\n 'footerStack',\n )\n const container = tree.add(\n 'muiContainer',\n { maxWidth: 'lg' },\n { py: { xs: 6, md: 8 } },\n [stack],\n 'footerInner',\n )\n return tree.add(\n 'section',\n {\n element: 'footer',\n ariaLabel: 'Site footer',\n ...(scope.band === 'dark' ? { colorScheme: 'dark' } : {}),\n },\n scope.band === 'soft'\n ? { bgcolor: 'background.paper', borderTop: 1, borderColor: 'divider' }\n : bandSx(scope.band),\n [container],\n 'footer',\n )\n}\n"],"names":["AI_ICON_LIBRARY","AI_LAYOUT_MAX_IMAGES","bandSx","compileFlow","compileRow","destinationProps","aiLayoutFitText","aiLayoutRepairFrameHollows","AI_LAYOUT_CART_ELEMENT","AI_LAYOUT_MAX_ITEMS","AI_LAYOUT_LABELS_EN","aiLayoutLabel","aiLayoutRenamedLabel","aiLayoutResolveLink","AiLayoutTreeBuilder","AI_LAYOUT_ACCOUNT_LINK","account","AI_LAYOUT_STORE_ANNOUNCEMENT","storeAnnouncement","AI_LAYOUT_STORE_ANNOUNCEMENT_LINK","shopNow","headerLook","band","props","color","sx","boxShadow","bgcolor","aiLayoutIsCopyright","text","test","FOOTER_LEAVES_OUT","Set","frameScope","page","at","centered","index","level","headed","itemsLeft","frame","link","sections","from","formSection","aiCompileLayoutFrame","plan","targets","header","tree","storePages","storeLinks","footer","map","id","href","label","slug","title","siteName","pages","pageId","options","reusableComponents","language","settled","scrollTo","itemIds","images","strong","brand","dark","pageIcon","sparkle","formsPlaced","design","pictures","features","splitAt","quotesOnly","name","blocks","look","scope","navLink","entry","role","interactions","add","children","slice","screenId","renderAs","textDecoration","nodes","navPages","filter","trim","centeredNav","align","length","brandSx","typography","fontWeight","whiteSpace","overflow","textOverflow","minWidth","flexGrow","homeId","variant","component","nav","ariaLabel","direction","spacing","alignItems","display","xs","md","ctaBlock","find","block","kind","store","shopId","noteBlock","undefined","push","what","ctaLabel","ctaResolved","to","repeatsNav","some","ctaTo","cart","ctaProps","cta","sm","flexShrink","accountPath","accountLink","close","AI_LAYOUT_CLOSE_DRAWER","drawer","anchor","flexDirection","gap","p","fullWidth","mt","toggle","middle","justifyContent","row","maxWidth","columnGap","toolbar","disableGutters","minHeight","bar","position","overHero","announcement","said","words","useFlexGap","flexWrap","py","px","slot","compileFooter","column","prune","rootId","AI_LAYOUT_STORE_FOOTER_HEADING","customerCare","STORE_HEADING","aiLayoutFooterStoreHeading","columns","navItems","candidates","navTo","item","forEach","end","under","site","one","items","has","heading","String","empty","pick","enabled","trigger","event","everyTime","steps","type","asked","closesDark","linksPages","flatMap","given","notice","pageLinks","cols","col","Math","min","weights","list","home","splice","filled","weight","top","copyright","bottom","pt","borderTop","borderColor","stack","container","element","colorScheme"],"mappings":"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"}
|
|
@@ -29,16 +29,26 @@ import type { NodesMap } from '@aglyn/aglyn/types/nodes';
|
|
|
29
29
|
* 1. A stock photo searched for by the slot's own description and copied into
|
|
30
30
|
* the site's media library (`jobs/ai-layout-stock-photos.ts`), when the
|
|
31
31
|
* deployment has a stock-photo key.
|
|
32
|
-
* 2.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
32
|
+
* 2. Where the library cannot be asked at all — no stock-photo key, the
|
|
33
|
+
* library down, refusing or out of time before it answered — one of the
|
|
34
|
+
* STARTER PHOTOS every new site is born with (`DEFAULT_SITE_IMAGES`,
|
|
35
|
+
* served at `/_static/starter/` by the tenant and the console alike,
|
|
36
|
+
* credited in the CREDITS file beside them). They are the platform's own
|
|
37
|
+
* files, so a visitor's browser asks no other host for them, and a
|
|
38
|
+
* starter reads better than no picture when there is no library.
|
|
37
39
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* Where the library answered and nothing in it fits a slot
|
|
41
|
+
* ({@link AI_LAYOUT_NO_PHOTO}), the slot keeps the compiler's no-photo
|
|
42
|
+
* treatment — its frame with its icon, or its section's own band behind a
|
|
43
|
+
* cover — and never a starter: the beta.257 Copper Kettle Catering start
|
|
44
|
+
* (2026-10-15) opened a barbecue caterer's home page on the starters'
|
|
45
|
+
* pottery workbench (AGL-3831). An empty picture draws nothing on the
|
|
46
|
+
* published page, and the Besigner shows the owner where to choose one.
|
|
47
|
+
*
|
|
48
|
+
* A slot keeps the alt text the model wrote only when it describes the photo
|
|
49
|
+
* placed: a stock photo that is not what it says is described by what it is
|
|
50
|
+
* (`aiStockPhotoAlt`), and a starter photo always by its own words, since
|
|
51
|
+
* the model never wrote for it (AGL-3831).
|
|
42
52
|
*/
|
|
43
53
|
/** What a picture slot is for, read from where it sits and what it says. */
|
|
44
54
|
export type AiLayoutPictureRole = 'hero' | 'about' | 'gallery';
|
|
@@ -93,34 +103,47 @@ export declare function aiLayoutPictureSlots(nodes: NodesMap, rootId: string, se
|
|
|
93
103
|
ids: readonly string[];
|
|
94
104
|
names: readonly string[];
|
|
95
105
|
}): AiLayoutPictureSlot[];
|
|
96
|
-
/** The starter photos, by name. */
|
|
106
|
+
/** The starter photos, by name, each with the words that describe it. */
|
|
97
107
|
export declare const AI_LAYOUT_STARTER_PHOTOS: {
|
|
98
108
|
readonly hero: {
|
|
99
109
|
readonly src: "/_static/starter/hero-team.jpg";
|
|
100
110
|
readonly width: 1600;
|
|
101
111
|
readonly height: 1067;
|
|
112
|
+
readonly alt: "A small team working together around a table in a bright office";
|
|
102
113
|
};
|
|
103
114
|
readonly about: {
|
|
104
115
|
readonly src: "/_static/starter/about-owner.jpg";
|
|
105
116
|
readonly width: 1600;
|
|
106
117
|
readonly height: 900;
|
|
118
|
+
readonly alt: "A smiling business owner standing behind the counter of her coffee shop";
|
|
107
119
|
};
|
|
108
120
|
readonly desk: {
|
|
109
121
|
readonly src: "/_static/starter/gallery-desk.jpg";
|
|
110
122
|
readonly width: 1600;
|
|
111
123
|
readonly height: 1068;
|
|
124
|
+
readonly alt: "A tidy desk with a laptop, a lamp and fresh flowers";
|
|
112
125
|
};
|
|
113
126
|
readonly plants: {
|
|
114
127
|
readonly src: "/_static/starter/gallery-plants.jpg";
|
|
115
128
|
readonly width: 1200;
|
|
116
129
|
readonly height: 900;
|
|
130
|
+
readonly alt: "Houseplants on a sunny windowsill";
|
|
117
131
|
};
|
|
118
132
|
readonly craft: {
|
|
119
133
|
readonly src: "/_static/starter/gallery-craft.jpg";
|
|
120
134
|
readonly width: 1600;
|
|
121
135
|
readonly height: 1065;
|
|
136
|
+
readonly alt: "Hands working on a craft project at a workbench";
|
|
122
137
|
};
|
|
123
138
|
};
|
|
139
|
+
/**
|
|
140
|
+
* A source's answer for a slot the library answered and nothing in it fits
|
|
141
|
+
* (AGL-3831): the slot keeps its no-photo treatment rather than take a
|
|
142
|
+
* starter. Its source is empty, so a caller reading `src` to fill something
|
|
143
|
+
* that must hold a photo (a product's, a record's) falls back as it would on
|
|
144
|
+
* `null`.
|
|
145
|
+
*/
|
|
146
|
+
export declare const AI_LAYOUT_NO_PHOTO: Readonly<AiLayoutPicturePhoto>;
|
|
124
147
|
/** A small stable number from a seed, so the same job always picks the same photos. */
|
|
125
148
|
export declare function aiLayoutSeedNumber(seed: string): number;
|
|
126
149
|
/**
|
|
@@ -148,15 +171,19 @@ export declare function aiLayoutStarterPhotos(slots: readonly AiLayoutPictureSlo
|
|
|
148
171
|
export declare function aiLayoutPlacePictures(nodes: NodesMap, slots: readonly AiLayoutPictureSlot[], photos: ReadonlyArray<AiLayoutPicturePhoto | null>): NodesMap;
|
|
149
172
|
/**
|
|
150
173
|
* A source of photos found for the slots themselves — a stock search copied
|
|
151
|
-
* into the site's library — answering
|
|
152
|
-
*
|
|
174
|
+
* into the site's library — answering per slot, in order: a photo; or
|
|
175
|
+
* {@link AI_LAYOUT_NO_PHOTO} where the library answered and nothing fits,
|
|
176
|
+
* which leaves the slot without a photo; or `null` where the library could
|
|
177
|
+
* not be asked, which takes a starter photo. It may throw, and then every
|
|
178
|
+
* slot takes a starter.
|
|
153
179
|
*/
|
|
154
180
|
export type AiLayoutPictureSource = (slots: readonly AiLayoutPictureSlot[]) => Promise<ReadonlyArray<AiLayoutPicturePhoto | null>>;
|
|
155
181
|
/**
|
|
156
182
|
* Fills a stored page's empty picture slots (AGL-3660): from `source` where
|
|
157
|
-
* one is given and it answers
|
|
158
|
-
*
|
|
159
|
-
*
|
|
183
|
+
* one is given and it answers; a slot it answers with nothing fitting stays
|
|
184
|
+
* without a photo; every other slot takes a starter photo, described by its
|
|
185
|
+
* own words (AGL-3831). Never throws, and never leaves a page worse than it
|
|
186
|
+
* came: a picture is decoration, and a job never fails over one.
|
|
160
187
|
*/
|
|
161
188
|
export declare function aiResolveLayoutPictures(nodes: NodesMap, input: {
|
|
162
189
|
rootId: string;
|
|
@@ -165,4 +192,6 @@ export declare function aiResolveLayoutPictures(nodes: NodesMap, input: {
|
|
|
165
192
|
/** A per-job word, so two sites do not open with the same photo. */
|
|
166
193
|
seed: string;
|
|
167
194
|
source?: AiLayoutPictureSource | null;
|
|
195
|
+
/** The site's language (AGL-3750); a starter photo's English words are not its own. */
|
|
196
|
+
language?: string;
|
|
168
197
|
}): Promise<NodesMap>;
|