@aglyn/plugins-ai 1.0.0-beta.230 → 1.0.0-beta.231
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/components/ai-assist-provider-on-host.component.d.ts +66 -0
- package/src/lib/components/ai-assist-provider-on-host.component.js +78 -0
- package/src/lib/components/ai-assist-provider-on-host.component.js.map +1 -0
- package/src/lib/components/ai-assist-provider.component.js +9 -10
- package/src/lib/components/ai-assist-provider.component.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.js +3 -1
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-permissions-on-host.component.d.ts +3 -46
- package/src/lib/components/ai-permissions-on-host.component.js +5 -53
- package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +20 -8
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/jobs/ai-build-unit-outcome.js +4 -2
- package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -1
- package/src/lib/jobs/ai-job-auto-confirm.d.ts +8 -0
- package/src/lib/jobs/ai-job-auto-confirm.js +30 -6
- package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.js +11 -0
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-generation.d.ts +8 -0
- package/src/lib/jobs/ai-job-generation.js +13 -2
- package/src/lib/jobs/ai-job-generation.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.js +8 -1
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +73 -0
- package/src/lib/jobs/ai-job-layout-language.js +194 -0
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -0
- package/src/lib/jobs/ai-job-layout-site-pages.d.ts +13 -2
- package/src/lib/jobs/ai-job-layout-site-pages.js +18 -2
- package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.d.ts +5 -0
- package/src/lib/jobs/ai-job-layout-step.js +105 -5
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +149 -0
- package/src/lib/jobs/ai-job-page-language.js +355 -0
- package/src/lib/jobs/ai-job-page-language.js.map +1 -0
- package/src/lib/jobs/ai-job-page-sections.d.ts +37 -1
- package/src/lib/jobs/ai-job-page-sections.js +93 -11
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +4 -4
- package/src/lib/jobs/ai-job-page-step.js +105 -14
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +1 -1
- package/src/lib/jobs/ai-job-plan-step.js +8 -2
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +4 -0
- package/src/lib/jobs/ai-job-site-step.js +20 -3
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.js +2 -2
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +1 -1
- package/src/lib/jobs/ai-jobs.js +17 -8
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-full-height.d.ts +40 -0
- package/src/lib/jobs/ai-layout-full-height.js +82 -0
- package/src/lib/jobs/ai-layout-full-height.js.map +1 -0
- package/src/lib/jobs/ai-layout-site-facts.d.ts +54 -0
- package/src/lib/jobs/ai-layout-site-facts.js +153 -0
- package/src/lib/jobs/ai-layout-site-facts.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +145 -0
- package/src/lib/layout-language/ai-layout-compiler.js +1148 -0
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -0
- package/src/lib/layout-language/ai-layout-copy.d.ts +73 -0
- package/src/lib/layout-language/ai-layout-copy.js +137 -0
- package/src/lib/layout-language/ai-layout-copy.js.map +1 -0
- package/src/lib/layout-language/ai-layout-frame.d.ts +52 -0
- package/src/lib/layout-language/ai-layout-frame.js +457 -0
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -0
- package/src/lib/layout-language/ai-layout-gaps.d.ts +63 -0
- package/src/lib/layout-language/ai-layout-gaps.js +345 -0
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -0
- package/src/lib/layout-language/ai-layout-language.d.ts +161 -0
- package/src/lib/layout-language/ai-layout-language.js +446 -0
- package/src/lib/layout-language/ai-layout-language.js.map +1 -0
- package/src/lib/layout-language/ai-layout-links.d.ts +80 -0
- package/src/lib/layout-language/ai-layout-links.js +109 -0
- package/src/lib/layout-language/ai-layout-links.js.map +1 -0
- package/src/lib/layout-language/ai-layout-store.d.ts +39 -0
- package/src/lib/layout-language/ai-layout-store.js +69 -0
- package/src/lib/layout-language/ai-layout-store.js.map +1 -0
- package/src/lib/layout-language/ai-layout-tree.d.ts +59 -0
- package/src/lib/layout-language/ai-layout-tree.js +84 -0
- package/src/lib/layout-language/ai-layout-tree.js.map +1 -0
- package/src/lib/lazy-widget.d.ts +45 -0
- package/src/lib/lazy-widget.js +53 -0
- package/src/lib/lazy-widget.js.map +1 -0
- package/src/lib/model/ai-build-job.js +2 -1
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +20 -3
- package/src/lib/model/ai-build-progress.js +48 -6
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-host-publish-context.d.ts +51 -0
- package/src/lib/model/ai-host-publish-context.js +104 -0
- package/src/lib/model/ai-host-publish-context.js.map +1 -0
- package/src/lib/model/ai-job-activity.d.ts +22 -0
- package/src/lib/model/ai-job-activity.js +22 -1
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-job-failure-copy.d.ts +38 -1
- package/src/lib/model/ai-job-failure-copy.js +56 -1
- package/src/lib/model/ai-job-failure-copy.js.map +1 -1
- package/src/lib/model/ai-job-notice.d.ts +3 -1
- package/src/lib/model/ai-job-notice.js +31 -9
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +19 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-build-progress.d.ts +5 -1
- package/src/lib/model/ai-site-build-progress.js +27 -4
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +8 -0
- package/src/lib/model/ai-site-job.js +9 -0
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-start-seo.d.ts +1 -1
- package/src/lib/model/ai-site-start.d.ts +5 -2
- package/src/lib/model/ai-site-start.js +5 -2
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/plugin.js +79 -47
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +1 -1
- package/src/lib/providers/catalog.js +3 -3
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +82 -0
- package/src/lib/runtime/ai-doctrine-validators.js +436 -4
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +12 -3
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-icon-library.d.ts +257 -0
- package/src/lib/runtime/ai-icon-library.js +254 -0
- package/src/lib/runtime/ai-icon-library.js.map +1 -0
- package/src/lib/runtime/ai-node-tree.d.ts +9 -0
- package/src/lib/runtime/ai-node-tree.js +30 -4
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.d.ts +6 -0
- package/src/lib/runtime/ai-palette.generated.js +70 -0
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-palette.js.map +1 -1
- package/src/lib/tools/ai-workflow-tool.d.ts +6 -2
- package/src/lib/tools/ai-workflow-tool.js +16 -14
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
17
|
+
import { AI_ICON_WORDS } from "../runtime/ai-icon-library.js";
|
|
18
|
+
/**
|
|
19
|
+
* The compact layout language (AGL-3660): what a model designs a page, and a
|
|
20
|
+
* site's header and footer, in — and the one place its shape is declared.
|
|
21
|
+
*
|
|
22
|
+
* ── Why a language, not a tree ───────────────────────────────────────────
|
|
23
|
+
*
|
|
24
|
+
* A page used to be asked for as a raw element tree, packed as escaped JSON
|
|
25
|
+
* text inside a tool call and held to the doctrine's building rules: Grid
|
|
26
|
+
* items sized as strings, every child attached, one h1, a destination on
|
|
27
|
+
* every button, headings under their ceiling. A real model kept about half
|
|
28
|
+
* of a page's sections on the first try, and a Free section was cut off at
|
|
29
|
+
* its token ceiling. The founder chose a language over a library of section
|
|
30
|
+
* templates (10/7): every page is designed for its own request, so no two
|
|
31
|
+
* sites come out alike, and the rules hold because CODE writes the tree.
|
|
32
|
+
*
|
|
33
|
+
* So the model says WHAT each section is and how it is arranged, in a few
|
|
34
|
+
* words of structure, and writes every word a visitor reads; the compiler
|
|
35
|
+
* (`ai-layout-compiler.ts`) turns that into the element tree with the
|
|
36
|
+
* palette's elements and the theme's tokens, by construction.
|
|
37
|
+
*
|
|
38
|
+
* ── The shape ────────────────────────────────────────────────────────────
|
|
39
|
+
*
|
|
40
|
+
* A page is its plan's sections, in order. A SECTION is
|
|
41
|
+
* - `band` — its background: `plain` (the page), `soft` (a paper band),
|
|
42
|
+
* `brand` (the brand color) or `dark` (always dark);
|
|
43
|
+
* - `cols` — a row of columns by relative width, as `[7, 5]` or `[1, 1, 1]`,
|
|
44
|
+
* up to four; absent, one column;
|
|
45
|
+
* - `align` — `start` or `center`;
|
|
46
|
+
* - `blocks` — what it shows, top to bottom.
|
|
47
|
+
*
|
|
48
|
+
* A BLOCK is one thing a visitor reads or uses. Its `kind` is one of
|
|
49
|
+
* `AI_LAYOUT_BLOCK_KINDS`; `col` places it in a column of the row (0 first),
|
|
50
|
+
* and a block with no `col` spans the section — above the row when it comes
|
|
51
|
+
* before the first placed block, below it when it comes after. `text` is its
|
|
52
|
+
* words, `to` where it goes, `style` its emphasis and `icon` a word from the
|
|
53
|
+
* icon library. A group — `list`, `cards`, `steps`, `stats`, `quotes`,
|
|
54
|
+
* `faq` — carries its `items`, each a `title`, a `text`, and optionally its
|
|
55
|
+
* own `to` and `icon`.
|
|
56
|
+
*
|
|
57
|
+
* Where a block goes (`to`): `page:<id>` for a page of this site (its label
|
|
58
|
+
* works too), `#<n>` for section n of this page, `form` for the site's form,
|
|
59
|
+
* a form's or a component's id for a `form` or `component` block, or an
|
|
60
|
+
* `https:` address the brief gave.
|
|
61
|
+
*
|
|
62
|
+
* A site's header and footer are two sections in the same language
|
|
63
|
+
* (`AI_LAYOUT_FRAME_TOOL`): the header's brand and navigation are the
|
|
64
|
+
* site's own and written by the compiler, so the model gives the header its
|
|
65
|
+
* band, its alignment and an optional call-to-action button; the footer is
|
|
66
|
+
* a section like any other.
|
|
67
|
+
*
|
|
68
|
+
* ── Cheap by shape ───────────────────────────────────────────────────────
|
|
69
|
+
*
|
|
70
|
+
* The schema is a structured object in the tool input (never JSON text in a
|
|
71
|
+
* string), small enough for a strict tool (`tool-schema-limits.spec.ts`), and
|
|
72
|
+
* the language's instructions ride in the cached prefix. A section is a few
|
|
73
|
+
* dozen words of structure beside its copy, so a whole page is one answer.
|
|
74
|
+
*/ /** Every kind of block, in the order the schema lists them. */ export const AI_LAYOUT_BLOCK_KINDS = [
|
|
75
|
+
'eyebrow',
|
|
76
|
+
'heading',
|
|
77
|
+
'lede',
|
|
78
|
+
'text',
|
|
79
|
+
'note',
|
|
80
|
+
'list',
|
|
81
|
+
'button',
|
|
82
|
+
'image',
|
|
83
|
+
'cards',
|
|
84
|
+
'steps',
|
|
85
|
+
'stats',
|
|
86
|
+
'quotes',
|
|
87
|
+
'faq',
|
|
88
|
+
'form',
|
|
89
|
+
'component'
|
|
90
|
+
];
|
|
91
|
+
/** The kinds that carry `items`. */ export const AI_LAYOUT_GROUP_KINDS = new Set([
|
|
92
|
+
'list',
|
|
93
|
+
'cards',
|
|
94
|
+
'steps',
|
|
95
|
+
'stats',
|
|
96
|
+
'quotes',
|
|
97
|
+
'faq'
|
|
98
|
+
]);
|
|
99
|
+
export const AI_LAYOUT_BANDS = [
|
|
100
|
+
'plain',
|
|
101
|
+
'soft',
|
|
102
|
+
'brand',
|
|
103
|
+
'dark'
|
|
104
|
+
];
|
|
105
|
+
export const AI_LAYOUT_ALIGNS = [
|
|
106
|
+
'start',
|
|
107
|
+
'center'
|
|
108
|
+
];
|
|
109
|
+
export const AI_LAYOUT_STYLES = [
|
|
110
|
+
'primary',
|
|
111
|
+
'secondary',
|
|
112
|
+
'quiet',
|
|
113
|
+
'large'
|
|
114
|
+
];
|
|
115
|
+
/** The most columns a row holds; a fifth is folded into the fourth. */ export const AI_LAYOUT_MAX_COLS = 4;
|
|
116
|
+
/**
|
|
117
|
+
* The most items a group shows: one under the eight copies rule 8 reads as a
|
|
118
|
+
* list typed out by hand, which belongs in a dataset.
|
|
119
|
+
*/ export const AI_LAYOUT_MAX_ITEMS = 7;
|
|
120
|
+
/** The most quotes a group shows: each is a gap the owner fills with a real one. */ export const AI_LAYOUT_MAX_QUOTES = 3;
|
|
121
|
+
const ITEM_SCHEMA = {
|
|
122
|
+
type: 'object',
|
|
123
|
+
additionalProperties: false,
|
|
124
|
+
required: [
|
|
125
|
+
'title',
|
|
126
|
+
'text',
|
|
127
|
+
'to',
|
|
128
|
+
'icon'
|
|
129
|
+
],
|
|
130
|
+
properties: {
|
|
131
|
+
title: {
|
|
132
|
+
type: 'string'
|
|
133
|
+
},
|
|
134
|
+
text: {
|
|
135
|
+
type: 'string'
|
|
136
|
+
},
|
|
137
|
+
to: {
|
|
138
|
+
type: 'string'
|
|
139
|
+
},
|
|
140
|
+
icon: {
|
|
141
|
+
type: 'string'
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const BLOCK_SCHEMA = {
|
|
146
|
+
type: 'object',
|
|
147
|
+
additionalProperties: false,
|
|
148
|
+
required: [
|
|
149
|
+
'kind',
|
|
150
|
+
'col',
|
|
151
|
+
'text',
|
|
152
|
+
'to',
|
|
153
|
+
'style',
|
|
154
|
+
'icon',
|
|
155
|
+
'items'
|
|
156
|
+
],
|
|
157
|
+
properties: {
|
|
158
|
+
kind: {
|
|
159
|
+
type: 'string',
|
|
160
|
+
enum: [
|
|
161
|
+
...AI_LAYOUT_BLOCK_KINDS
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
col: {
|
|
165
|
+
type: 'integer'
|
|
166
|
+
},
|
|
167
|
+
text: {
|
|
168
|
+
type: 'string'
|
|
169
|
+
},
|
|
170
|
+
to: {
|
|
171
|
+
type: 'string'
|
|
172
|
+
},
|
|
173
|
+
style: {
|
|
174
|
+
type: 'string',
|
|
175
|
+
enum: [
|
|
176
|
+
...AI_LAYOUT_STYLES,
|
|
177
|
+
'none'
|
|
178
|
+
]
|
|
179
|
+
},
|
|
180
|
+
icon: {
|
|
181
|
+
type: 'string'
|
|
182
|
+
},
|
|
183
|
+
items: {
|
|
184
|
+
type: 'array',
|
|
185
|
+
items: ITEM_SCHEMA
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const SECTION_SCHEMA = {
|
|
190
|
+
type: 'object',
|
|
191
|
+
additionalProperties: false,
|
|
192
|
+
required: [
|
|
193
|
+
'band',
|
|
194
|
+
'cols',
|
|
195
|
+
'align',
|
|
196
|
+
'blocks'
|
|
197
|
+
],
|
|
198
|
+
properties: {
|
|
199
|
+
band: {
|
|
200
|
+
type: 'string',
|
|
201
|
+
enum: [
|
|
202
|
+
...AI_LAYOUT_BANDS
|
|
203
|
+
]
|
|
204
|
+
},
|
|
205
|
+
cols: {
|
|
206
|
+
type: 'array',
|
|
207
|
+
items: {
|
|
208
|
+
type: 'integer'
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
align: {
|
|
212
|
+
type: 'string',
|
|
213
|
+
enum: [
|
|
214
|
+
...AI_LAYOUT_ALIGNS
|
|
215
|
+
]
|
|
216
|
+
},
|
|
217
|
+
blocks: {
|
|
218
|
+
type: 'array',
|
|
219
|
+
items: BLOCK_SCHEMA
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
/** The page tool: every section of one page, in plan order, in one answer. */ export const AI_LAYOUT_PAGE_TOOL = {
|
|
224
|
+
name: 'submit_page',
|
|
225
|
+
description: 'Submit the page: one entry in sections for each planned section, in order, in the layout language.',
|
|
226
|
+
strict: true,
|
|
227
|
+
inputSchema: {
|
|
228
|
+
type: 'object',
|
|
229
|
+
additionalProperties: false,
|
|
230
|
+
required: [
|
|
231
|
+
'sections'
|
|
232
|
+
],
|
|
233
|
+
properties: {
|
|
234
|
+
sections: {
|
|
235
|
+
type: 'array',
|
|
236
|
+
items: SECTION_SCHEMA
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
/** The frame tool: the site's header and footer, in the same language. */ export const AI_LAYOUT_FRAME_TOOL = {
|
|
242
|
+
name: 'submit_frame',
|
|
243
|
+
description: "Submit the site's header and footer, each as a section in the layout language.",
|
|
244
|
+
strict: true,
|
|
245
|
+
inputSchema: {
|
|
246
|
+
type: 'object',
|
|
247
|
+
additionalProperties: false,
|
|
248
|
+
required: [
|
|
249
|
+
'header',
|
|
250
|
+
'footer'
|
|
251
|
+
],
|
|
252
|
+
properties: {
|
|
253
|
+
header: SECTION_SCHEMA,
|
|
254
|
+
footer: SECTION_SCHEMA
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
/**
|
|
259
|
+
* The language as a model is taught it: the cached instructions both doors
|
|
260
|
+
* share. Every word of copy is the model's; this says only how a design is
|
|
261
|
+
* written down and what each word becomes.
|
|
262
|
+
*/ export const AI_LAYOUT_LANGUAGE_TEXT = [
|
|
263
|
+
"You design web pages in a compact layout language. You decide the structure and write every word; the platform turns your design into the page, with the site's theme, spacing and type, so never describe styling beyond the words below.",
|
|
264
|
+
'',
|
|
265
|
+
'A page is a list of sections, one for each section the plan names, in that order. A section has:',
|
|
266
|
+
'- band: plain (the page background), soft (a light band), brand (the brand color) or dark (always dark). Vary the bands down a page so neighboring sections read apart; keep brand and dark to one or two sections a page.',
|
|
267
|
+
'- cols: relative column widths for a row, such as [1, 1] for halves, [7, 5] for a wide and a narrow column, or [1, 1, 1]; at most 4. [] for one column.',
|
|
268
|
+
'- align: start or center.',
|
|
269
|
+
'- blocks: what the section shows, top to bottom. A block with col 0 or more goes in that column of the row (0 is the first); a block with col -1 spans the whole section, above the row when it comes first and below it when it comes last.',
|
|
270
|
+
'Every field is always written: a block that does not use one writes "" (text, to, icon), "none" (style), -1 (col) or [] (items); an item that does not use one writes "".',
|
|
271
|
+
'',
|
|
272
|
+
'Block kinds:',
|
|
273
|
+
"- eyebrow: a short label above a heading. heading: a heading; the first heading of the page's first section is the page's title, and the first heading of every other section is that section's heading. lede: the larger sentence under a heading. text: a paragraph. note: small print.",
|
|
274
|
+
'- button: text is the label and to says where it goes; style primary, secondary or quiet. Two buttons in a row read as a pair.',
|
|
275
|
+
'- image: text describes the picture to place, which becomes its alt text; the owner adds the picture. icon names a drawing to show until then.',
|
|
276
|
+
'- list: items are the lines (title only; text may be empty).',
|
|
277
|
+
"- cards, steps, stats, faq: items, each with a title and a text. cards are features or services; steps are numbered; stats are a figure (title) with its label (text), only for figures the brief gives; faq items are a question (title) and its answer (text). Use no quotes block: the site has no customer's words to show.",
|
|
278
|
+
"- form: places a saved form by its id in to. component: places a reusable component by its id in to; its items fill the component's props, title the prop name and text the value.",
|
|
279
|
+
'- style large on a heading makes the page title display-sized. icon (on a block or an item) is one of: ' + AI_ICON_WORDS.join(', ') + '.',
|
|
280
|
+
'',
|
|
281
|
+
"Where a link goes (to): page:<page id> for a page of this site; #<n> for section n of this page (counted from 1); form for the site's form; or an https: address the brief gives. A button the page has nowhere to send is left out. A to value is never written into words: in text, name a page by its title.",
|
|
282
|
+
'',
|
|
283
|
+
"Writing: plain, specific copy in the site's voice, from the brief, the business and its audience, never filler. Keep a page title under 70 characters and a section heading under 80. The site is published exactly as you write it, so where the brief leaves out a fact such as a price, a figure, a name, a phone number, an email, an address or opening hours, leave it out: write the sentence without it, and make no list line, card or contact row for it. Never write a placeholder or a gap in square brackets, and never invent the fact. Never invent a customer, a review, an award or a statistic.",
|
|
284
|
+
'',
|
|
285
|
+
'Design each page for this business and this request: choose the bands, the rows and the blocks that tell its story best, so no two sites read alike.'
|
|
286
|
+
].join('\n');
|
|
287
|
+
// ── Reading an answer ────────────────────────────────────────────────────
|
|
288
|
+
function isRecord(value) {
|
|
289
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
290
|
+
}
|
|
291
|
+
function text(value) {
|
|
292
|
+
return typeof value === 'string' && value.trim() ? value : undefined;
|
|
293
|
+
}
|
|
294
|
+
function oneOf(value, values) {
|
|
295
|
+
return typeof value === 'string' && values.includes(value) ? value : undefined;
|
|
296
|
+
}
|
|
297
|
+
function readItem(raw) {
|
|
298
|
+
if (!isRecord(raw)) return null;
|
|
299
|
+
const item = {
|
|
300
|
+
title: typeof raw['title'] === 'string' ? raw['title'] : '',
|
|
301
|
+
text: typeof raw['text'] === 'string' ? raw['text'] : ''
|
|
302
|
+
};
|
|
303
|
+
if (!item.title.trim() && !item.text.trim()) return null;
|
|
304
|
+
const to = text(raw['to']);
|
|
305
|
+
const icon = text(raw['icon']);
|
|
306
|
+
return _extends({}, item, to ? {
|
|
307
|
+
to
|
|
308
|
+
} : {}, icon ? {
|
|
309
|
+
icon
|
|
310
|
+
} : {});
|
|
311
|
+
}
|
|
312
|
+
function readBlock(raw, at, settled) {
|
|
313
|
+
var _block_items;
|
|
314
|
+
if (!isRecord(raw)) {
|
|
315
|
+
settled.push({
|
|
316
|
+
at,
|
|
317
|
+
what: 'not a block; left out'
|
|
318
|
+
});
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
const kind = oneOf(raw['kind'], AI_LAYOUT_BLOCK_KINDS);
|
|
322
|
+
if (!kind) {
|
|
323
|
+
var _raw_kind;
|
|
324
|
+
settled.push({
|
|
325
|
+
at,
|
|
326
|
+
what: `unknown kind ${JSON.stringify((_raw_kind = raw['kind']) != null ? _raw_kind : null)}; left out`
|
|
327
|
+
});
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
const block = {
|
|
331
|
+
kind
|
|
332
|
+
};
|
|
333
|
+
if (typeof raw['col'] === 'number' && Number.isInteger(raw['col']) && raw['col'] >= 0) block.col = raw['col'];
|
|
334
|
+
const words = text(raw['text']);
|
|
335
|
+
if (words) block.text = words;
|
|
336
|
+
const to = text(raw['to']);
|
|
337
|
+
if (to) block.to = to.trim();
|
|
338
|
+
const style = oneOf(raw['style'], AI_LAYOUT_STYLES);
|
|
339
|
+
if (style) block.style = style;
|
|
340
|
+
const icon = text(raw['icon']);
|
|
341
|
+
if (icon) block.icon = icon.trim().toLowerCase();
|
|
342
|
+
if (AI_LAYOUT_GROUP_KINDS.has(kind) || kind === 'component') {
|
|
343
|
+
const items = (Array.isArray(raw['items']) ? raw['items'] : []).map(readItem).filter((item)=>!!item);
|
|
344
|
+
const most = kind === 'quotes' ? AI_LAYOUT_MAX_QUOTES : kind === 'component' ? Infinity : AI_LAYOUT_MAX_ITEMS;
|
|
345
|
+
if (items.length > most) settled.push({
|
|
346
|
+
at,
|
|
347
|
+
what: `${items.length} items; the first ${most} kept`
|
|
348
|
+
});
|
|
349
|
+
if (items.length) block.items = items.slice(0, most);
|
|
350
|
+
}
|
|
351
|
+
// A block with nothing to show is no block: a group with no items, words
|
|
352
|
+
// with no words. A form or a component may name its record alone.
|
|
353
|
+
const empty = AI_LAYOUT_GROUP_KINDS.has(kind) ? !((_block_items = block.items) == null ? void 0 : _block_items.length) : kind === 'form' || kind === 'component' ? false : !block.text;
|
|
354
|
+
if (empty) {
|
|
355
|
+
settled.push({
|
|
356
|
+
at,
|
|
357
|
+
what: `an empty ${kind}; left out`
|
|
358
|
+
});
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
return block;
|
|
362
|
+
}
|
|
363
|
+
/** A section read from an answer, or `null` with why it could not be. */ export function aiReadLayoutSection(raw, at, settled) {
|
|
364
|
+
if (!isRecord(raw)) return null;
|
|
365
|
+
const section = {
|
|
366
|
+
blocks: []
|
|
367
|
+
};
|
|
368
|
+
const band = oneOf(raw['band'], AI_LAYOUT_BANDS);
|
|
369
|
+
if (band) section.band = band;
|
|
370
|
+
const align = oneOf(raw['align'], AI_LAYOUT_ALIGNS);
|
|
371
|
+
if (align) section.align = align;
|
|
372
|
+
if (Array.isArray(raw['cols'])) {
|
|
373
|
+
const cols = raw['cols'].filter((value)=>typeof value === 'number' && Number.isFinite(value) && value > 0);
|
|
374
|
+
if (cols.length > AI_LAYOUT_MAX_COLS) {
|
|
375
|
+
settled.push({
|
|
376
|
+
at: `${at}.cols`,
|
|
377
|
+
what: `${cols.length} columns; the first ${AI_LAYOUT_MAX_COLS} kept`
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
if (cols.length >= 2) section.cols = cols.slice(0, AI_LAYOUT_MAX_COLS).map((value)=>Math.min(12, Math.max(1, Math.round(value))));
|
|
381
|
+
}
|
|
382
|
+
const blocks = Array.isArray(raw['blocks']) ? raw['blocks'] : [];
|
|
383
|
+
blocks.forEach((entry, index)=>{
|
|
384
|
+
const block = readBlock(entry, `${at}.blocks[${index}]`, settled);
|
|
385
|
+
if (!block) return;
|
|
386
|
+
if (block.col !== undefined && section.cols && block.col >= section.cols.length) {
|
|
387
|
+
settled.push({
|
|
388
|
+
at: `${at}.blocks[${index}]`,
|
|
389
|
+
what: `column ${block.col} of ${section.cols.length}; placed in the last`
|
|
390
|
+
});
|
|
391
|
+
block.col = section.cols.length - 1;
|
|
392
|
+
}
|
|
393
|
+
if (block.col !== undefined && !section.cols) delete block.col;
|
|
394
|
+
section.blocks.push(block);
|
|
395
|
+
});
|
|
396
|
+
return section.blocks.length ? section : null;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* The page an answer describes, against the number of sections its plan
|
|
400
|
+
* names: each planned section takes the answer's section at its position,
|
|
401
|
+
* an answer's extra sections are left out, and a planned section the answer
|
|
402
|
+
* left empty or out is `null` — the one thing a re-ask asks for again.
|
|
403
|
+
*/ export function aiReadLayoutPage(raw, planned) {
|
|
404
|
+
const settled = [];
|
|
405
|
+
const given = isRecord(raw) && Array.isArray(raw['sections']) ? raw['sections'] : [];
|
|
406
|
+
if (given.length > planned) {
|
|
407
|
+
settled.push({
|
|
408
|
+
at: 'sections',
|
|
409
|
+
what: `${given.length} sections for a plan of ${planned}; the extra left out`
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
const sections = Array.from({
|
|
413
|
+
length: planned
|
|
414
|
+
}, (_, index)=>aiReadLayoutSection(given[index], `sections[${index}]`, settled));
|
|
415
|
+
return {
|
|
416
|
+
sections,
|
|
417
|
+
settled
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
/** A frame answer read: a header and a footer, either `null` where it was not usable. */ export function aiReadLayoutFrame(raw) {
|
|
421
|
+
var _aiReadLayoutSection;
|
|
422
|
+
const settled = [];
|
|
423
|
+
const record = isRecord(raw) ? raw : {};
|
|
424
|
+
// A header needs no block of its own: its brand and navigation are the
|
|
425
|
+
// site's, so an empty one is a plain bar.
|
|
426
|
+
const header = isRecord(record['header']) ? (_aiReadLayoutSection = aiReadLayoutSection(record['header'], 'header', settled)) != null ? _aiReadLayoutSection : readBare(record['header']) : null;
|
|
427
|
+
const footer = aiReadLayoutSection(record['footer'], 'footer', settled);
|
|
428
|
+
return {
|
|
429
|
+
header,
|
|
430
|
+
footer,
|
|
431
|
+
settled
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
/** A section with no usable block, keeping only its band and alignment. */ function readBare(raw) {
|
|
435
|
+
const band = oneOf(raw['band'], AI_LAYOUT_BANDS);
|
|
436
|
+
const align = oneOf(raw['align'], AI_LAYOUT_ALIGNS);
|
|
437
|
+
return _extends({}, band ? {
|
|
438
|
+
band
|
|
439
|
+
} : {}, align ? {
|
|
440
|
+
align
|
|
441
|
+
} : {}, {
|
|
442
|
+
blocks: []
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
//# sourceMappingURL=ai-layout-language.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/layout-language/ai-layout-language.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 { AiTool } from '../providers/contract'\nimport { AI_ICON_WORDS } from '../runtime/ai-icon-library'\n\n/**\n * The compact layout language (AGL-3660): what a model designs a page, and a\n * site's header and footer, in — and the one place its shape is declared.\n *\n * ── Why a language, not a tree ───────────────────────────────────────────\n *\n * A page used to be asked for as a raw element tree, packed as escaped JSON\n * text inside a tool call and held to the doctrine's building rules: Grid\n * items sized as strings, every child attached, one h1, a destination on\n * every button, headings under their ceiling. A real model kept about half\n * of a page's sections on the first try, and a Free section was cut off at\n * its token ceiling. The founder chose a language over a library of section\n * templates (10/7): every page is designed for its own request, so no two\n * sites come out alike, and the rules hold because CODE writes the tree.\n *\n * So the model says WHAT each section is and how it is arranged, in a few\n * words of structure, and writes every word a visitor reads; the compiler\n * (`ai-layout-compiler.ts`) turns that into the element tree with the\n * palette's elements and the theme's tokens, by construction.\n *\n * ── The shape ────────────────────────────────────────────────────────────\n *\n * A page is its plan's sections, in order. A SECTION is\n * - `band` — its background: `plain` (the page), `soft` (a paper band),\n * `brand` (the brand color) or `dark` (always dark);\n * - `cols` — a row of columns by relative width, as `[7, 5]` or `[1, 1, 1]`,\n * up to four; absent, one column;\n * - `align` — `start` or `center`;\n * - `blocks` — what it shows, top to bottom.\n *\n * A BLOCK is one thing a visitor reads or uses. Its `kind` is one of\n * `AI_LAYOUT_BLOCK_KINDS`; `col` places it in a column of the row (0 first),\n * and a block with no `col` spans the section — above the row when it comes\n * before the first placed block, below it when it comes after. `text` is its\n * words, `to` where it goes, `style` its emphasis and `icon` a word from the\n * icon library. A group — `list`, `cards`, `steps`, `stats`, `quotes`,\n * `faq` — carries its `items`, each a `title`, a `text`, and optionally its\n * own `to` and `icon`.\n *\n * Where a block goes (`to`): `page:<id>` for a page of this site (its label\n * works too), `#<n>` for section n of this page, `form` for the site's form,\n * a form's or a component's id for a `form` or `component` block, or an\n * `https:` address the brief gave.\n *\n * A site's header and footer are two sections in the same language\n * (`AI_LAYOUT_FRAME_TOOL`): the header's brand and navigation are the\n * site's own and written by the compiler, so the model gives the header its\n * band, its alignment and an optional call-to-action button; the footer is\n * a section like any other.\n *\n * ── Cheap by shape ───────────────────────────────────────────────────────\n *\n * The schema is a structured object in the tool input (never JSON text in a\n * string), small enough for a strict tool (`tool-schema-limits.spec.ts`), and\n * the language's instructions ride in the cached prefix. A section is a few\n * dozen words of structure beside its copy, so a whole page is one answer.\n */\n\n/** Every kind of block, in the order the schema lists them. */\nexport const AI_LAYOUT_BLOCK_KINDS = [\n 'eyebrow',\n 'heading',\n 'lede',\n 'text',\n 'note',\n 'list',\n 'button',\n 'image',\n 'cards',\n 'steps',\n 'stats',\n 'quotes',\n 'faq',\n 'form',\n 'component',\n] as const\n\nexport type AiLayoutBlockKind = (typeof AI_LAYOUT_BLOCK_KINDS)[number]\n\n/** The kinds that carry `items`. */\nexport const AI_LAYOUT_GROUP_KINDS: ReadonlySet<AiLayoutBlockKind> = new Set([\n 'list',\n 'cards',\n 'steps',\n 'stats',\n 'quotes',\n 'faq',\n])\n\nexport const AI_LAYOUT_BANDS = ['plain', 'soft', 'brand', 'dark'] as const\nexport type AiLayoutBand = (typeof AI_LAYOUT_BANDS)[number]\n\nexport const AI_LAYOUT_ALIGNS = ['start', 'center'] as const\nexport type AiLayoutAlign = (typeof AI_LAYOUT_ALIGNS)[number]\n\nexport const AI_LAYOUT_STYLES = [\n 'primary',\n 'secondary',\n 'quiet',\n 'large',\n] as const\nexport type AiLayoutStyle = (typeof AI_LAYOUT_STYLES)[number]\n\n/** The most columns a row holds; a fifth is folded into the fourth. */\nexport const AI_LAYOUT_MAX_COLS = 4\n\n/**\n * The most items a group shows: one under the eight copies rule 8 reads as a\n * list typed out by hand, which belongs in a dataset.\n */\nexport const AI_LAYOUT_MAX_ITEMS = 7\n\n/** The most quotes a group shows: each is a gap the owner fills with a real one. */\nexport const AI_LAYOUT_MAX_QUOTES = 3\n\nexport interface AiLayoutItem {\n title: string\n text: string\n to?: string\n icon?: string\n}\n\nexport interface AiLayoutBlock {\n kind: AiLayoutBlockKind\n col?: number\n text?: string\n to?: string\n style?: AiLayoutStyle\n icon?: string\n items?: AiLayoutItem[]\n}\n\nexport interface AiLayoutSection {\n band?: AiLayoutBand\n cols?: number[]\n align?: AiLayoutAlign\n blocks: AiLayoutBlock[]\n}\n\n/** A page: its plan's sections, in order. */\nexport interface AiLayoutPageDoc {\n sections: AiLayoutSection[]\n}\n\n/** A site's frame: the header and the footer every page renders between. */\nexport interface AiLayoutFrameDoc {\n header: AiLayoutSection\n footer: AiLayoutSection\n}\n\nconst ITEM_SCHEMA = {\n type: 'object',\n additionalProperties: false,\n required: ['title', 'text', 'to', 'icon'],\n properties: {\n title: { type: 'string' },\n text: { type: 'string' },\n to: { type: 'string' },\n icon: { type: 'string' },\n },\n} as const\n\nconst BLOCK_SCHEMA = {\n type: 'object',\n additionalProperties: false,\n required: ['kind', 'col', 'text', 'to', 'style', 'icon', 'items'],\n properties: {\n kind: { type: 'string', enum: [...AI_LAYOUT_BLOCK_KINDS] },\n col: { type: 'integer' },\n text: { type: 'string' },\n to: { type: 'string' },\n style: { type: 'string', enum: [...AI_LAYOUT_STYLES, 'none'] },\n icon: { type: 'string' },\n items: { type: 'array', items: ITEM_SCHEMA },\n },\n} as const\n\nconst SECTION_SCHEMA = {\n type: 'object',\n additionalProperties: false,\n required: ['band', 'cols', 'align', 'blocks'],\n properties: {\n band: { type: 'string', enum: [...AI_LAYOUT_BANDS] },\n cols: { type: 'array', items: { type: 'integer' } },\n align: { type: 'string', enum: [...AI_LAYOUT_ALIGNS] },\n blocks: { type: 'array', items: BLOCK_SCHEMA },\n },\n} as const\n\n/** The page tool: every section of one page, in plan order, in one answer. */\nexport const AI_LAYOUT_PAGE_TOOL: AiTool = {\n name: 'submit_page',\n description:\n 'Submit the page: one entry in sections for each planned section, in order, in the layout language.',\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['sections'],\n properties: { sections: { type: 'array', items: SECTION_SCHEMA } },\n },\n}\n\n/** The frame tool: the site's header and footer, in the same language. */\nexport const AI_LAYOUT_FRAME_TOOL: AiTool = {\n name: 'submit_frame',\n description:\n \"Submit the site's header and footer, each as a section in the layout language.\",\n strict: true,\n inputSchema: {\n type: 'object',\n additionalProperties: false,\n required: ['header', 'footer'],\n properties: { header: SECTION_SCHEMA, footer: SECTION_SCHEMA },\n },\n}\n\n/**\n * The language as a model is taught it: the cached instructions both doors\n * share. Every word of copy is the model's; this says only how a design is\n * written down and what each word becomes.\n */\nexport const AI_LAYOUT_LANGUAGE_TEXT = [\n \"You design web pages in a compact layout language. You decide the structure and write every word; the platform turns your design into the page, with the site's theme, spacing and type, so never describe styling beyond the words below.\",\n '',\n 'A page is a list of sections, one for each section the plan names, in that order. A section has:',\n '- band: plain (the page background), soft (a light band), brand (the brand color) or dark (always dark). Vary the bands down a page so neighboring sections read apart; keep brand and dark to one or two sections a page.',\n '- cols: relative column widths for a row, such as [1, 1] for halves, [7, 5] for a wide and a narrow column, or [1, 1, 1]; at most 4. [] for one column.',\n '- align: start or center.',\n '- blocks: what the section shows, top to bottom. A block with col 0 or more goes in that column of the row (0 is the first); a block with col -1 spans the whole section, above the row when it comes first and below it when it comes last.',\n 'Every field is always written: a block that does not use one writes \"\" (text, to, icon), \"none\" (style), -1 (col) or [] (items); an item that does not use one writes \"\".',\n '',\n 'Block kinds:',\n \"- eyebrow: a short label above a heading. heading: a heading; the first heading of the page's first section is the page's title, and the first heading of every other section is that section's heading. lede: the larger sentence under a heading. text: a paragraph. note: small print.\",\n '- button: text is the label and to says where it goes; style primary, secondary or quiet. Two buttons in a row read as a pair.',\n '- image: text describes the picture to place, which becomes its alt text; the owner adds the picture. icon names a drawing to show until then.',\n '- list: items are the lines (title only; text may be empty).',\n \"- cards, steps, stats, faq: items, each with a title and a text. cards are features or services; steps are numbered; stats are a figure (title) with its label (text), only for figures the brief gives; faq items are a question (title) and its answer (text). Use no quotes block: the site has no customer's words to show.\",\n \"- form: places a saved form by its id in to. component: places a reusable component by its id in to; its items fill the component's props, title the prop name and text the value.\",\n '- style large on a heading makes the page title display-sized. icon (on a block or an item) is one of: ' +\n AI_ICON_WORDS.join(', ') +\n '.',\n '',\n \"Where a link goes (to): page:<page id> for a page of this site; #<n> for section n of this page (counted from 1); form for the site's form; or an https: address the brief gives. A button the page has nowhere to send is left out. A to value is never written into words: in text, name a page by its title.\",\n '',\n \"Writing: plain, specific copy in the site's voice, from the brief, the business and its audience, never filler. Keep a page title under 70 characters and a section heading under 80. The site is published exactly as you write it, so where the brief leaves out a fact such as a price, a figure, a name, a phone number, an email, an address or opening hours, leave it out: write the sentence without it, and make no list line, card or contact row for it. Never write a placeholder or a gap in square brackets, and never invent the fact. Never invent a customer, a review, an award or a statistic.\",\n '',\n 'Design each page for this business and this request: choose the bands, the rows and the blocks that tell its story best, so no two sites read alike.',\n].join('\\n')\n\n// ── Reading an answer ────────────────────────────────────────────────────\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction text(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() ? value : undefined\n}\n\nfunction oneOf<T extends string>(\n value: unknown,\n values: readonly T[],\n): T | undefined {\n return typeof value === 'string' &&\n (values as readonly string[]).includes(value)\n ? (value as T)\n : undefined\n}\n\n/** What the reader settled on its own, for the job's record and the specs. */\nexport interface AiLayoutSettlement {\n /** Where: `sections[2].blocks[1]`, `header`. */\n at: string\n what: string\n}\n\nfunction readItem(raw: unknown): AiLayoutItem | null {\n if (!isRecord(raw)) return null\n const item: AiLayoutItem = {\n title: typeof raw['title'] === 'string' ? raw['title'] : '',\n text: typeof raw['text'] === 'string' ? raw['text'] : '',\n }\n if (!item.title.trim() && !item.text.trim()) return null\n const to = text(raw['to'])\n const icon = text(raw['icon'])\n return { ...item, ...(to ? { to } : {}), ...(icon ? { icon } : {}) }\n}\n\nfunction readBlock(\n raw: unknown,\n at: string,\n settled: AiLayoutSettlement[],\n): AiLayoutBlock | null {\n if (!isRecord(raw)) {\n settled.push({ at, what: 'not a block; left out' })\n return null\n }\n const kind = oneOf(raw['kind'], AI_LAYOUT_BLOCK_KINDS)\n if (!kind) {\n settled.push({\n at,\n what: `unknown kind ${JSON.stringify(raw['kind'] ?? null)}; left out`,\n })\n return null\n }\n const block: AiLayoutBlock = { kind }\n if (\n typeof raw['col'] === 'number' &&\n Number.isInteger(raw['col']) &&\n raw['col'] >= 0\n )\n block.col = raw['col']\n const words = text(raw['text'])\n if (words) block.text = words\n const to = text(raw['to'])\n if (to) block.to = to.trim()\n const style = oneOf(raw['style'], AI_LAYOUT_STYLES)\n if (style) block.style = style\n const icon = text(raw['icon'])\n if (icon) block.icon = icon.trim().toLowerCase()\n if (AI_LAYOUT_GROUP_KINDS.has(kind) || kind === 'component') {\n const items = (Array.isArray(raw['items']) ? raw['items'] : [])\n .map(readItem)\n .filter((item): item is AiLayoutItem => !!item)\n const most =\n kind === 'quotes'\n ? AI_LAYOUT_MAX_QUOTES\n : kind === 'component'\n ? Infinity\n : AI_LAYOUT_MAX_ITEMS\n if (items.length > most)\n settled.push({\n at,\n what: `${items.length} items; the first ${most} kept`,\n })\n if (items.length) block.items = items.slice(0, most)\n }\n // A block with nothing to show is no block: a group with no items, words\n // with no words. A form or a component may name its record alone.\n const empty = AI_LAYOUT_GROUP_KINDS.has(kind)\n ? !block.items?.length\n : kind === 'form' || kind === 'component'\n ? false\n : !block.text\n if (empty) {\n settled.push({ at, what: `an empty ${kind}; left out` })\n return null\n }\n return block\n}\n\n/** A section read from an answer, or `null` with why it could not be. */\nexport function aiReadLayoutSection(\n raw: unknown,\n at: string,\n settled: AiLayoutSettlement[],\n): AiLayoutSection | null {\n if (!isRecord(raw)) return null\n const section: AiLayoutSection = { blocks: [] }\n const band = oneOf(raw['band'], AI_LAYOUT_BANDS)\n if (band) section.band = band\n const align = oneOf(raw['align'], AI_LAYOUT_ALIGNS)\n if (align) section.align = align\n if (Array.isArray(raw['cols'])) {\n const cols = raw['cols'].filter(\n (value): value is number =>\n typeof value === 'number' && Number.isFinite(value) && value > 0,\n )\n if (cols.length > AI_LAYOUT_MAX_COLS) {\n settled.push({\n at: `${at}.cols`,\n what: `${cols.length} columns; the first ${AI_LAYOUT_MAX_COLS} kept`,\n })\n }\n if (cols.length >= 2)\n section.cols = cols\n .slice(0, AI_LAYOUT_MAX_COLS)\n .map((value) => Math.min(12, Math.max(1, Math.round(value))))\n }\n const blocks = Array.isArray(raw['blocks']) ? raw['blocks'] : []\n blocks.forEach((entry, index) => {\n const block = readBlock(entry, `${at}.blocks[${index}]`, settled)\n if (!block) return\n if (\n block.col !== undefined &&\n section.cols &&\n block.col >= section.cols.length\n ) {\n settled.push({\n at: `${at}.blocks[${index}]`,\n what: `column ${block.col} of ${section.cols.length}; placed in the last`,\n })\n block.col = section.cols.length - 1\n }\n if (block.col !== undefined && !section.cols) delete block.col\n section.blocks.push(block)\n })\n return section.blocks.length ? section : null\n}\n\n/** A page answer read: the sections it gave, by plan position, and the positions it could not. */\nexport interface AiLayoutPageReading {\n /** One entry per planned section; `null` where the answer gave nothing usable. */\n sections: Array<AiLayoutSection | null>\n settled: AiLayoutSettlement[]\n}\n\n/**\n * The page an answer describes, against the number of sections its plan\n * names: each planned section takes the answer's section at its position,\n * an answer's extra sections are left out, and a planned section the answer\n * left empty or out is `null` — the one thing a re-ask asks for again.\n */\nexport function aiReadLayoutPage(\n raw: unknown,\n planned: number,\n): AiLayoutPageReading {\n const settled: AiLayoutSettlement[] = []\n const given =\n isRecord(raw) && Array.isArray(raw['sections']) ? raw['sections'] : []\n if (given.length > planned) {\n settled.push({\n at: 'sections',\n what: `${given.length} sections for a plan of ${planned}; the extra left out`,\n })\n }\n const sections = Array.from({ length: planned }, (_, index) =>\n aiReadLayoutSection(given[index], `sections[${index}]`, settled),\n )\n return { sections, settled }\n}\n\n/** A frame answer read: a header and a footer, either `null` where it was not usable. */\nexport function aiReadLayoutFrame(raw: unknown): {\n header: AiLayoutSection | null\n footer: AiLayoutSection | null\n settled: AiLayoutSettlement[]\n} {\n const settled: AiLayoutSettlement[] = []\n const record = isRecord(raw) ? raw : {}\n // A header needs no block of its own: its brand and navigation are the\n // site's, so an empty one is a plain bar.\n const header = isRecord(record['header'])\n ? (aiReadLayoutSection(record['header'], 'header', settled) ??\n readBare(record['header']))\n : null\n const footer = aiReadLayoutSection(record['footer'], 'footer', settled)\n return { header, footer, settled }\n}\n\n/** A section with no usable block, keeping only its band and alignment. */\nfunction readBare(raw: Record<string, unknown>): AiLayoutSection {\n const band = oneOf(raw['band'], AI_LAYOUT_BANDS)\n const align = oneOf(raw['align'], AI_LAYOUT_ALIGNS)\n return { ...(band ? { band } : {}), ...(align ? { align } : {}), blocks: [] }\n}\n"],"names":["AI_ICON_WORDS","AI_LAYOUT_BLOCK_KINDS","AI_LAYOUT_GROUP_KINDS","Set","AI_LAYOUT_BANDS","AI_LAYOUT_ALIGNS","AI_LAYOUT_STYLES","AI_LAYOUT_MAX_COLS","AI_LAYOUT_MAX_ITEMS","AI_LAYOUT_MAX_QUOTES","ITEM_SCHEMA","type","additionalProperties","required","properties","title","text","to","icon","BLOCK_SCHEMA","kind","enum","col","style","items","SECTION_SCHEMA","band","cols","align","blocks","AI_LAYOUT_PAGE_TOOL","name","description","strict","inputSchema","sections","AI_LAYOUT_FRAME_TOOL","header","footer","AI_LAYOUT_LANGUAGE_TEXT","join","isRecord","value","Array","isArray","trim","undefined","oneOf","values","includes","readItem","raw","item","readBlock","at","settled","block","push","what","JSON","stringify","Number","isInteger","words","toLowerCase","has","map","filter","most","Infinity","length","slice","empty","aiReadLayoutSection","section","isFinite","Math","min","max","round","forEach","entry","index","aiReadLayoutPage","planned","given","from","_","aiReadLayoutFrame","record","readBare"],"mappings":"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"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Where a link the layout language names goes (AGL-3660), resolved by code to
|
|
19
|
+
* a destination rule 10 admits: a page of this site by its id, a section of
|
|
20
|
+
* this page by the platform's Scroll to element interaction, or an `https:`
|
|
21
|
+
* address the brief itself gave. A destination that resolves to nothing, or
|
|
22
|
+
* to the home page under words that do not say home, is no destination, and
|
|
23
|
+
* the compiler leaves the button out rather than shipping a dead control.
|
|
24
|
+
*/
|
|
25
|
+
/** A page of the site a link may go to: built, or minted on a guided start's plan. */
|
|
26
|
+
export interface AiLayoutPage {
|
|
27
|
+
id: string;
|
|
28
|
+
label: string;
|
|
29
|
+
slug: string;
|
|
30
|
+
}
|
|
31
|
+
/** Everything a page's links resolve against. */
|
|
32
|
+
export interface AiLayoutTargets {
|
|
33
|
+
/** The page being built, which no link of its own goes to. */
|
|
34
|
+
pageId?: string | null;
|
|
35
|
+
/** The site's pages a link may go to. */
|
|
36
|
+
pages: readonly AiLayoutPage[];
|
|
37
|
+
/** The site's home pages, which only words that say home link (rule 10). */
|
|
38
|
+
homeIds: readonly string[];
|
|
39
|
+
/** The saved forms a page may place. */
|
|
40
|
+
forms: readonly {
|
|
41
|
+
id: string;
|
|
42
|
+
name: string;
|
|
43
|
+
}[];
|
|
44
|
+
/** The page that places the site's form, where that is another page. */
|
|
45
|
+
formPageId?: string | null;
|
|
46
|
+
/** The reusable components a page may place, with the props each fills. */
|
|
47
|
+
components: readonly {
|
|
48
|
+
id: string;
|
|
49
|
+
name: string;
|
|
50
|
+
props: Record<string, string>;
|
|
51
|
+
}[];
|
|
52
|
+
/** Every word the job was given: the brief, the site's answers, its profile. */
|
|
53
|
+
facts: string;
|
|
54
|
+
}
|
|
55
|
+
/** Where a link resolved to. */
|
|
56
|
+
export type AiLayoutDestination = {
|
|
57
|
+
kind: 'page';
|
|
58
|
+
screenId: string;
|
|
59
|
+
} | {
|
|
60
|
+
kind: 'section';
|
|
61
|
+
index: number;
|
|
62
|
+
} | {
|
|
63
|
+
kind: 'href';
|
|
64
|
+
href: string;
|
|
65
|
+
};
|
|
66
|
+
export interface AiLayoutLinkScope {
|
|
67
|
+
/** The page's section names in plan order. */
|
|
68
|
+
sections: readonly string[];
|
|
69
|
+
/** The section the link sits in, which it does not scroll to. */
|
|
70
|
+
from: number;
|
|
71
|
+
/** The section of this page that places the site's form, if one does. */
|
|
72
|
+
formSection: number | null;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Where a link goes: the destination its `to` names, read the language's
|
|
76
|
+
* way; failing that, for words that ask a visitor to get in touch, the site's
|
|
77
|
+
* form; failing that, a page whose name the words say. `null` is a link with
|
|
78
|
+
* nowhere to go, which is left out.
|
|
79
|
+
*/
|
|
80
|
+
export declare function aiLayoutResolveLink(to: string | undefined, label: string, scope: AiLayoutLinkScope, targets: AiLayoutTargets): AiLayoutDestination | null;
|