@aglyn/plugins-ai 1.0.0-beta.246 → 1.0.0-beta.247
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.js +5 -1
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-job-links.js +14 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
- package/src/lib/components/ai-site-start-card.component.js +92 -19
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/declarations.server.js +12 -0
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.js +56 -2
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.d.ts +3 -8
- package/src/lib/jobs/ai-job-build-step.js +49 -12
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-form-dataset.d.ts +8 -5
- package/src/lib/jobs/ai-job-form-dataset.js +28 -10
- package/src/lib/jobs/ai-job-form-dataset.js.map +1 -1
- package/src/lib/jobs/ai-job-form-step.d.ts +20 -4
- package/src/lib/jobs/ai-job-form-step.js +98 -28
- package/src/lib/jobs/ai-job-form-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +6 -2
- package/src/lib/jobs/ai-job-layout-language.js +16 -5
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +4 -1
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-media-step.d.ts +239 -0
- package/src/lib/jobs/ai-job-media-step.js +610 -0
- package/src/lib/jobs/ai-job-media-step.js.map +1 -0
- package/src/lib/jobs/ai-job-page-language.d.ts +2 -0
- package/src/lib/jobs/ai-job-page-language.js +9 -0
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.js +4 -3
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +3 -2
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +10 -1
- package/src/lib/jobs/ai-job-plan-step.js +73 -15
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-settings-step.d.ts +0 -4
- package/src/lib/jobs/ai-job-settings-step.js +2 -7
- package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +2 -2
- package/src/lib/jobs/ai-job-site-content.js +5 -3
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +1 -1
- package/src/lib/jobs/ai-job-site-datasets.js +3 -2
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-events.d.ts +148 -0
- package/src/lib/jobs/ai-job-site-events.js +313 -0
- package/src/lib/jobs/ai-job-site-events.js.map +1 -0
- package/src/lib/jobs/ai-job-site-step.d.ts +9 -3
- package/src/lib/jobs/ai-job-site-step.js +63 -17
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-tracking-step.d.ts +184 -0
- package/src/lib/jobs/ai-job-tracking-step.js +501 -0
- package/src/lib/jobs/ai-job-tracking-step.js.map +1 -0
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +21 -0
- package/src/lib/jobs/ai-layout-stock-photos.js +41 -4
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-site-publish.js +2 -3
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/ai-stock-photo-picker.d.ts +24 -0
- package/src/lib/jobs/ai-stock-photo-picker.js +108 -0
- package/src/lib/jobs/ai-stock-photo-picker.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.d.ts +37 -0
- package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.js +47 -0
- package/src/lib/jobs/fixtures/ai-jazz-events-build-plan-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.d.ts +21 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.js +128 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3760-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +10 -4
- package/src/lib/layout-language/ai-layout-compiler.js +38 -19
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.d.ts +7 -5
- package/src/lib/layout-language/ai-layout-frame.js +12 -9
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-gaps.js +3 -1
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
- package/src/lib/layout-language/ai-layout-labels.d.ts +70 -0
- package/src/lib/layout-language/ai-layout-labels.js +162 -0
- package/src/lib/layout-language/ai-layout-labels.js.map +1 -0
- package/src/lib/layout-language/ai-layout-links.js +5 -2
- package/src/lib/layout-language/ai-layout-links.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +14 -1
- package/src/lib/layout-language/ai-layout-listings.js +32 -5
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/model/ai-build-event-page.d.ts +70 -0
- package/src/lib/model/ai-build-event-page.js +145 -0
- package/src/lib/model/ai-build-event-page.js.map +1 -0
- package/src/lib/model/ai-build-job.d.ts +21 -0
- package/src/lib/model/ai-build-job.js +39 -0
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +25 -14
- package/src/lib/model/ai-build-plan.js +33 -23
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-job-activity.js +3 -1
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +10 -2
- package/src/lib/model/ai-jobs.types.js +3 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-plan-capabilities.d.ts +8 -0
- package/src/lib/model/ai-plan-capabilities.js.map +1 -1
- package/src/lib/model/ai-site-inventory.d.ts +2 -0
- package/src/lib/model/ai-site-inventory.js +5 -0
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +2 -2
- package/src/lib/model/ai-site-job.js +3 -3
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +25 -3
- package/src/lib/runtime/ai-doctrine-validators.js +53 -6
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.d.ts +6 -1
- package/src/lib/runtime/ai-doctrine.js +13 -9
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.d.ts +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-palette.d.ts +3 -1
- package/src/lib/runtime/ai-palette.generated.js +498 -10
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-palette.js +5 -3
- package/src/lib/runtime/ai-palette.js.map +1 -1
- package/src/lib/runtime/ai-template-examples.js +8 -2
- package/src/lib/runtime/ai-template-examples.js.map +1 -1
- package/src/lib/server.js +8 -0
- package/src/lib/server.js.map +1 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { AiDoctrineViolation } from '../runtime/ai-doctrine-validators';
|
|
18
|
+
import type { AiBuildOps } from './ai-build-job';
|
|
19
|
+
import { type AiBuildItem, type AiBuildPlan } from './ai-build-plan';
|
|
20
|
+
/**
|
|
21
|
+
* THE PAGE A BUILD'S EVENTS ARE LISTED ON (AGL-3755).
|
|
22
|
+
*
|
|
23
|
+
* A build's `event` items are written as events, and an event is seen on the
|
|
24
|
+
* site only through an Event List a page section places — the section whose
|
|
25
|
+
* uses name the event. The live eval of 2026-10-13 asked "add our next three
|
|
26
|
+
* Friday night jazz shows at 8pm to an events page" of a site with only a
|
|
27
|
+
* Home page, and the plan made the three events and no page: they landed
|
|
28
|
+
* nowhere a visitor could see. So, settled in code where there is one right
|
|
29
|
+
* answer ({@link aiSettleBuildEventPage}), and re-asked where there is not
|
|
30
|
+
* ({@link aiBuildUnlistedEventsCheck}):
|
|
31
|
+
*
|
|
32
|
+
* 1. Events no section names, and a page the plan makes about them (its
|
|
33
|
+
* address or title says events): the events are named in that page's
|
|
34
|
+
* section about them, else its second section, else its first.
|
|
35
|
+
* 2. None such, the site has no page at `/events`, and the plan has room
|
|
36
|
+
* for one more page: the plan gains an Events page at `/events`, linked
|
|
37
|
+
* in the navigation and in the layout the other pages render in, whose
|
|
38
|
+
* one section lists them.
|
|
39
|
+
* 3. Anything else — a site that already has an events page, a plan at its
|
|
40
|
+
* page cap — is left to the plan's one re-ask, which names the events.
|
|
41
|
+
*
|
|
42
|
+
* Only the `event` operation: other placed operations keep their own rules.
|
|
43
|
+
*/
|
|
44
|
+
/** The operation whose items this settles; the events-calendar plugin's, named, never imported. */
|
|
45
|
+
export declare const AI_BUILD_EVENT_OP = "event";
|
|
46
|
+
/**
|
|
47
|
+
* The page a build adds for its events. Its address is the SITE's page,
|
|
48
|
+
* spelled from its title so it is never read as the console's Events page.
|
|
49
|
+
*/
|
|
50
|
+
export declare const AI_BUILD_EVENT_PAGE: {
|
|
51
|
+
readonly title: "Events";
|
|
52
|
+
readonly slug: `/${string}`;
|
|
53
|
+
readonly section: "Upcoming events";
|
|
54
|
+
};
|
|
55
|
+
/** The plan's event items that no page section names. */
|
|
56
|
+
export declare function aiBuildUnlistedEvents(plan: Pick<AiBuildPlan, 'items' | 'screens'>): AiBuildItem[];
|
|
57
|
+
export interface AiBuildEventPageContext {
|
|
58
|
+
/** The operations this site has; the settling runs only where it has `event`. */
|
|
59
|
+
ops: AiBuildOps | null;
|
|
60
|
+
/** The site's pages, by address. */
|
|
61
|
+
siteSlugs: readonly string[];
|
|
62
|
+
/** How many pages the build may plan. */
|
|
63
|
+
pageLimit: number;
|
|
64
|
+
/** The layout the site's pages render in (its home page's, else its one layout), or null. */
|
|
65
|
+
siteLayout: string | null;
|
|
66
|
+
}
|
|
67
|
+
/** A build plan with its unlisted events placed where there is one right answer; otherwise as it came. */
|
|
68
|
+
export declare function aiSettleBuildEventPage<T extends AiBuildPlan>(plan: T, context: AiBuildEventPageContext): T;
|
|
69
|
+
/** The re-ask for events the settling could not place: each named, and where to put it. */
|
|
70
|
+
export declare function aiBuildUnlistedEventsCheck(ops: AiBuildOps | null): (plan: AiBuildPlan) => AiDoctrineViolation[];
|
|
@@ -0,0 +1,145 @@
|
|
|
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_BUILD_PLAN_LIMITS, aiPlanSlugKey } from "./ai-build-plan.js";
|
|
18
|
+
/**
|
|
19
|
+
* THE PAGE A BUILD'S EVENTS ARE LISTED ON (AGL-3755).
|
|
20
|
+
*
|
|
21
|
+
* A build's `event` items are written as events, and an event is seen on the
|
|
22
|
+
* site only through an Event List a page section places — the section whose
|
|
23
|
+
* uses name the event. The live eval of 2026-10-13 asked "add our next three
|
|
24
|
+
* Friday night jazz shows at 8pm to an events page" of a site with only a
|
|
25
|
+
* Home page, and the plan made the three events and no page: they landed
|
|
26
|
+
* nowhere a visitor could see. So, settled in code where there is one right
|
|
27
|
+
* answer ({@link aiSettleBuildEventPage}), and re-asked where there is not
|
|
28
|
+
* ({@link aiBuildUnlistedEventsCheck}):
|
|
29
|
+
*
|
|
30
|
+
* 1. Events no section names, and a page the plan makes about them (its
|
|
31
|
+
* address or title says events): the events are named in that page's
|
|
32
|
+
* section about them, else its second section, else its first.
|
|
33
|
+
* 2. None such, the site has no page at `/events`, and the plan has room
|
|
34
|
+
* for one more page: the plan gains an Events page at `/events`, linked
|
|
35
|
+
* in the navigation and in the layout the other pages render in, whose
|
|
36
|
+
* one section lists them.
|
|
37
|
+
* 3. Anything else — a site that already has an events page, a plan at its
|
|
38
|
+
* page cap — is left to the plan's one re-ask, which names the events.
|
|
39
|
+
*
|
|
40
|
+
* Only the `event` operation: other placed operations keep their own rules.
|
|
41
|
+
*/ /** The operation whose items this settles; the events-calendar plugin's, named, never imported. */ export const AI_BUILD_EVENT_OP = 'event';
|
|
42
|
+
const EVENT_PAGE_TITLE = 'Events';
|
|
43
|
+
/**
|
|
44
|
+
* The page a build adds for its events. Its address is the SITE's page,
|
|
45
|
+
* spelled from its title so it is never read as the console's Events page.
|
|
46
|
+
*/ export const AI_BUILD_EVENT_PAGE = {
|
|
47
|
+
title: EVENT_PAGE_TITLE,
|
|
48
|
+
slug: `/${EVENT_PAGE_TITLE.toLowerCase()}`,
|
|
49
|
+
section: 'Upcoming events'
|
|
50
|
+
};
|
|
51
|
+
/** A page or section that is about events. */ const ABOUT_EVENTS = /\b(events?|calendar|upcoming|what'?s on|shows?|gigs?|schedule|classes|workshops?)\b/i;
|
|
52
|
+
const refOf = (item)=>`new:${item.name}`;
|
|
53
|
+
/** The plan's event items that no page section names. */ export function aiBuildUnlistedEvents(plan) {
|
|
54
|
+
var _plan_items;
|
|
55
|
+
const uses = new Set(plan.screens.flatMap((screen)=>screen.sections.flatMap((section)=>section.uses.map((ref)=>ref.trim().toLowerCase()))));
|
|
56
|
+
return ((_plan_items = plan.items) != null ? _plan_items : []).filter((item)=>item.op === AI_BUILD_EVENT_OP && !uses.has(refOf(item).trim().toLowerCase()));
|
|
57
|
+
}
|
|
58
|
+
/** Whether a planned page is about events: its address or its title says so. */ function aboutEvents(screen) {
|
|
59
|
+
var _screen_slug_trim_replace_split_;
|
|
60
|
+
const first = (_screen_slug_trim_replace_split_ = screen.slug.trim().replace(/^\/+/, '').split('/')[0]) != null ? _screen_slug_trim_replace_split_ : '';
|
|
61
|
+
return first !== '' && ABOUT_EVENTS.test(`${first.replace(/-/g, ' ')} ${screen.title}`);
|
|
62
|
+
}
|
|
63
|
+
/** The section of an events page that lists them: the one named for them, else its second, else its first. */ function listingSection(screen) {
|
|
64
|
+
const named = screen.sections.findIndex((section)=>ABOUT_EVENTS.test(section.name));
|
|
65
|
+
if (named !== -1) return named;
|
|
66
|
+
return screen.sections.length > 1 ? 1 : 0;
|
|
67
|
+
}
|
|
68
|
+
/** A build plan with its unlisted events placed where there is one right answer; otherwise as it came. */ export function aiSettleBuildEventPage(plan, context) {
|
|
69
|
+
var _ref;
|
|
70
|
+
var _context_ops, _plan_screens_find;
|
|
71
|
+
if (!((_context_ops = context.ops) == null ? void 0 : _context_ops.has(AI_BUILD_EVENT_OP))) return plan;
|
|
72
|
+
const unlisted = aiBuildUnlistedEvents(plan);
|
|
73
|
+
if (!unlisted.length) return plan;
|
|
74
|
+
const refs = unlisted.map(refOf);
|
|
75
|
+
// 1. A page the plan makes about them lists them.
|
|
76
|
+
const page = plan.screens.findIndex((screen)=>!screen.record && screen.sections.length && aboutEvents(screen));
|
|
77
|
+
if (page !== -1) {
|
|
78
|
+
const screen = plan.screens[page];
|
|
79
|
+
const at = listingSection(screen);
|
|
80
|
+
const section = screen.sections[at];
|
|
81
|
+
if (!section || section.uses.length + refs.length > AI_BUILD_PLAN_LIMITS.uses) return plan;
|
|
82
|
+
const sections = screen.sections.map((one, index)=>index === at ? _extends({}, one, {
|
|
83
|
+
uses: [
|
|
84
|
+
...one.uses,
|
|
85
|
+
...refs
|
|
86
|
+
]
|
|
87
|
+
}) : one);
|
|
88
|
+
return _extends({}, plan, {
|
|
89
|
+
screens: plan.screens.map((one, index)=>index === page ? _extends({}, screen, {
|
|
90
|
+
sections
|
|
91
|
+
}) : one)
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
// 2. An Events page of its own, where the address is free and the plan has room.
|
|
95
|
+
const taken = new Set([
|
|
96
|
+
...context.siteSlugs,
|
|
97
|
+
...plan.screens.map((screen)=>screen.slug)
|
|
98
|
+
].map(aiPlanSlugKey));
|
|
99
|
+
if (taken.has(aiPlanSlugKey(AI_BUILD_EVENT_PAGE.slug)) || plan.screens.length >= context.pageLimit) return plan;
|
|
100
|
+
if (refs.length > AI_BUILD_PLAN_LIMITS.uses) return plan;
|
|
101
|
+
const names = unlisted.map((item)=>item.name);
|
|
102
|
+
const listed = names.length === 1 ? names[0] : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`;
|
|
103
|
+
const events = {
|
|
104
|
+
title: AI_BUILD_EVENT_PAGE.title,
|
|
105
|
+
slug: AI_BUILD_EVENT_PAGE.slug,
|
|
106
|
+
// The layout the plan's other pages render in, else the site's.
|
|
107
|
+
layout: (_ref = (_plan_screens_find = plan.screens.find((screen)=>screen.layout)) == null ? void 0 : _plan_screens_find.layout) != null ? _ref : context.siteLayout,
|
|
108
|
+
template: null,
|
|
109
|
+
duplicateOf: null,
|
|
110
|
+
nav: true,
|
|
111
|
+
seoTitle: AI_BUILD_EVENT_PAGE.title,
|
|
112
|
+
seoDescription: `Upcoming events: ${listed}.`.slice(0, AI_BUILD_PLAN_LIMITS.seoDescription),
|
|
113
|
+
sections: [
|
|
114
|
+
{
|
|
115
|
+
name: AI_BUILD_EVENT_PAGE.section,
|
|
116
|
+
uses: refs,
|
|
117
|
+
items: 0
|
|
118
|
+
}
|
|
119
|
+
],
|
|
120
|
+
record: null
|
|
121
|
+
};
|
|
122
|
+
return _extends({}, plan, {
|
|
123
|
+
screens: [
|
|
124
|
+
...plan.screens,
|
|
125
|
+
events
|
|
126
|
+
]
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
/** The re-ask for events the settling could not place: each named, and where to put it. */ export function aiBuildUnlistedEventsCheck(ops) {
|
|
130
|
+
return (plan)=>{
|
|
131
|
+
if (!(ops == null ? void 0 : ops.has(AI_BUILD_EVENT_OP))) return [];
|
|
132
|
+
const unlisted = aiBuildUnlistedEvents(plan);
|
|
133
|
+
if (!unlisted.length) return [];
|
|
134
|
+
const named = unlisted.map((item)=>`new:${item.name}`).join(', ');
|
|
135
|
+
return [
|
|
136
|
+
{
|
|
137
|
+
rule: null,
|
|
138
|
+
code: 'plan-job-shape',
|
|
139
|
+
message: `The events ${named} are listed on no page, so no visitor would see them. Put each in the uses of the section that lists the events, on the page the request names for them or a page about events this plan makes.`
|
|
140
|
+
}
|
|
141
|
+
];
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
//# sourceMappingURL=ai-build-event-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-build-event-page.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 { AiDoctrineViolation } from '../runtime/ai-doctrine-validators'\nimport type { AiBuildOps } from './ai-build-job'\nimport {\n AI_BUILD_PLAN_LIMITS,\n aiPlanSlugKey,\n type AiBuildItem,\n type AiBuildPlan,\n type AiBuildPlanScreen,\n} from './ai-build-plan'\n\n/**\n * THE PAGE A BUILD'S EVENTS ARE LISTED ON (AGL-3755).\n *\n * A build's `event` items are written as events, and an event is seen on the\n * site only through an Event List a page section places — the section whose\n * uses name the event. The live eval of 2026-10-13 asked \"add our next three\n * Friday night jazz shows at 8pm to an events page\" of a site with only a\n * Home page, and the plan made the three events and no page: they landed\n * nowhere a visitor could see. So, settled in code where there is one right\n * answer ({@link aiSettleBuildEventPage}), and re-asked where there is not\n * ({@link aiBuildUnlistedEventsCheck}):\n *\n * 1. Events no section names, and a page the plan makes about them (its\n * address or title says events): the events are named in that page's\n * section about them, else its second section, else its first.\n * 2. None such, the site has no page at `/events`, and the plan has room\n * for one more page: the plan gains an Events page at `/events`, linked\n * in the navigation and in the layout the other pages render in, whose\n * one section lists them.\n * 3. Anything else — a site that already has an events page, a plan at its\n * page cap — is left to the plan's one re-ask, which names the events.\n *\n * Only the `event` operation: other placed operations keep their own rules.\n */\n\n/** The operation whose items this settles; the events-calendar plugin's, named, never imported. */\nexport const AI_BUILD_EVENT_OP = 'event'\n\nconst EVENT_PAGE_TITLE = 'Events'\n\n/**\n * The page a build adds for its events. Its address is the SITE's page,\n * spelled from its title so it is never read as the console's Events page.\n */\nexport const AI_BUILD_EVENT_PAGE = {\n title: EVENT_PAGE_TITLE,\n slug: `/${EVENT_PAGE_TITLE.toLowerCase()}`,\n section: 'Upcoming events',\n} as const\n\n/** A page or section that is about events. */\nconst ABOUT_EVENTS = /\\b(events?|calendar|upcoming|what'?s on|shows?|gigs?|schedule|classes|workshops?)\\b/i\n\nconst refOf = (item: Pick<AiBuildItem, 'name'>) => `new:${item.name}`\n\n/** The plan's event items that no page section names. */\nexport function aiBuildUnlistedEvents(plan: Pick<AiBuildPlan, 'items' | 'screens'>): AiBuildItem[] {\n const uses = new Set(\n plan.screens.flatMap((screen) => screen.sections.flatMap((section) => section.uses.map((ref) => ref.trim().toLowerCase()))),\n )\n return (plan.items ?? []).filter((item) => item.op === AI_BUILD_EVENT_OP && !uses.has(refOf(item).trim().toLowerCase()))\n}\n\n/** Whether a planned page is about events: its address or its title says so. */\nfunction aboutEvents(screen: Pick<AiBuildPlanScreen, 'slug' | 'title'>): boolean {\n const first = screen.slug.trim().replace(/^\\/+/, '').split('/')[0] ?? ''\n return first !== '' && ABOUT_EVENTS.test(`${first.replace(/-/g, ' ')} ${screen.title}`)\n}\n\n/** The section of an events page that lists them: the one named for them, else its second, else its first. */\nfunction listingSection(screen: AiBuildPlanScreen): number {\n const named = screen.sections.findIndex((section) => ABOUT_EVENTS.test(section.name))\n if (named !== -1) return named\n return screen.sections.length > 1 ? 1 : 0\n}\n\nexport interface AiBuildEventPageContext {\n /** The operations this site has; the settling runs only where it has `event`. */\n ops: AiBuildOps | null\n /** The site's pages, by address. */\n siteSlugs: readonly string[]\n /** How many pages the build may plan. */\n pageLimit: number\n /** The layout the site's pages render in (its home page's, else its one layout), or null. */\n siteLayout: string | null\n}\n\n/** A build plan with its unlisted events placed where there is one right answer; otherwise as it came. */\nexport function aiSettleBuildEventPage<T extends AiBuildPlan>(plan: T, context: AiBuildEventPageContext): T {\n if (!context.ops?.has(AI_BUILD_EVENT_OP)) return plan\n const unlisted = aiBuildUnlistedEvents(plan)\n if (!unlisted.length) return plan\n const refs = unlisted.map(refOf)\n // 1. A page the plan makes about them lists them.\n const page = plan.screens.findIndex((screen) => !screen.record && screen.sections.length && aboutEvents(screen))\n if (page !== -1) {\n const screen = plan.screens[page] as AiBuildPlanScreen\n const at = listingSection(screen)\n const section = screen.sections[at]\n if (!section || section.uses.length + refs.length > AI_BUILD_PLAN_LIMITS.uses) return plan\n const sections = screen.sections.map((one, index) => (index === at ? { ...one, uses: [...one.uses, ...refs] } : one))\n return { ...plan, screens: plan.screens.map((one, index) => (index === page ? { ...screen, sections } : one)) }\n }\n // 2. An Events page of its own, where the address is free and the plan has room.\n const taken = new Set([...context.siteSlugs, ...plan.screens.map((screen) => screen.slug)].map(aiPlanSlugKey))\n if (taken.has(aiPlanSlugKey(AI_BUILD_EVENT_PAGE.slug)) || plan.screens.length >= context.pageLimit) return plan\n if (refs.length > AI_BUILD_PLAN_LIMITS.uses) return plan\n const names = unlisted.map((item) => item.name)\n const listed = names.length === 1 ? names[0] : `${names.slice(0, -1).join(', ')} and ${names[names.length - 1]}`\n const events: AiBuildPlanScreen = {\n title: AI_BUILD_EVENT_PAGE.title,\n slug: AI_BUILD_EVENT_PAGE.slug,\n // The layout the plan's other pages render in, else the site's.\n layout: plan.screens.find((screen) => screen.layout)?.layout ?? context.siteLayout,\n template: null,\n duplicateOf: null,\n nav: true,\n seoTitle: AI_BUILD_EVENT_PAGE.title,\n seoDescription: `Upcoming events: ${listed}.`.slice(0, AI_BUILD_PLAN_LIMITS.seoDescription),\n sections: [{ name: AI_BUILD_EVENT_PAGE.section, uses: refs, items: 0 }],\n record: null,\n }\n return { ...plan, screens: [...plan.screens, events] }\n}\n\n/** The re-ask for events the settling could not place: each named, and where to put it. */\nexport function aiBuildUnlistedEventsCheck(ops: AiBuildOps | null): (plan: AiBuildPlan) => AiDoctrineViolation[] {\n return (plan) => {\n if (!ops?.has(AI_BUILD_EVENT_OP)) return []\n const unlisted = aiBuildUnlistedEvents(plan)\n if (!unlisted.length) return []\n const named = unlisted.map((item) => `new:${item.name}`).join(', ')\n return [\n {\n rule: null,\n code: 'plan-job-shape',\n message: `The events ${named} are listed on no page, so no visitor would see them. Put each in the uses of the section that lists the events, on the page the request names for them or a page about events this plan makes.`,\n },\n ]\n }\n}\n"],"names":["AI_BUILD_PLAN_LIMITS","aiPlanSlugKey","AI_BUILD_EVENT_OP","EVENT_PAGE_TITLE","AI_BUILD_EVENT_PAGE","title","slug","toLowerCase","section","ABOUT_EVENTS","refOf","item","name","aiBuildUnlistedEvents","plan","uses","Set","screens","flatMap","screen","sections","map","ref","trim","items","filter","op","has","aboutEvents","first","replace","split","test","listingSection","named","findIndex","length","aiSettleBuildEventPage","context","ops","unlisted","refs","page","record","at","one","index","taken","siteSlugs","pageLimit","names","listed","slice","join","events","layout","find","siteLayout","template","duplicateOf","nav","seoTitle","seoDescription","aiBuildUnlistedEventsCheck","rule","code","message"],"mappings":"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"}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
17
|
import { type PluginAiCapability } from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities';
|
|
18
|
+
import type { PluginDraftRecord } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts';
|
|
18
19
|
import { type AiBuildItem, type AiBuildPlan, type AiBuildPlanCreate, type AiBuildPlanCreateKind, type AiBuildPlanScreen } from './ai-build-plan';
|
|
19
20
|
import type { AiJobItemLedger } from './ai-jobs.types';
|
|
20
21
|
import { type AiCreditRange, type AiCreditsSmaller } from './ai-credit-estimate';
|
|
@@ -135,6 +136,16 @@ export declare function aiBuildPlanShapeRefusal(plan: AiBuildPlan, options?: {
|
|
|
135
136
|
freeTaste?: boolean;
|
|
136
137
|
ops?: AiBuildOps;
|
|
137
138
|
}): string | null;
|
|
139
|
+
/**
|
|
140
|
+
* What the person must still say before this plan can be built, as the
|
|
141
|
+
* questions its operations ask (`missingInput`), or `null` when nothing is
|
|
142
|
+
* missing: an item the planner kept with nothing it may build from — a
|
|
143
|
+
* tracking change naming no id, AGL-3751 — is asked about, never built
|
|
144
|
+
* empty or from a value the planner made up. Not a plan rule: a re-ask
|
|
145
|
+
* cannot supply what only the person knows, so the plan is not kept and
|
|
146
|
+
* the build answers with the question.
|
|
147
|
+
*/
|
|
148
|
+
export declare function aiBuildPlanMissingInput(plan: AiBuildPlan, ops: AiBuildOps | null | undefined): string | null;
|
|
138
149
|
/** "a booking service", "an email design". */
|
|
139
150
|
export declare function aiArticle(noun: string): string;
|
|
140
151
|
/**
|
|
@@ -240,6 +251,8 @@ export declare function aiBuildPlacedItemLines(unit: AiBuildUnit, context: {
|
|
|
240
251
|
units: readonly AiBuildUnit[];
|
|
241
252
|
ledger: readonly AiJobItemLedger[];
|
|
242
253
|
ops?: AiBuildOps;
|
|
254
|
+
/** Operations whose items the platform places itself (a listing), so the page is told nothing of them. */
|
|
255
|
+
placedByCode?: readonly string[];
|
|
243
256
|
}): string[];
|
|
244
257
|
/** What a build came to: whether anything was delivered, and whether anything is left. */
|
|
245
258
|
export declare function aiBuildSettlement(ledger: readonly AiJobItemLedger[]): {
|
|
@@ -265,3 +278,11 @@ export declare function aiBuildRetryLedger(ledger: readonly AiJobItemLedger[], u
|
|
|
265
278
|
* page built after the layouts and forms the ledger holds.
|
|
266
279
|
*/
|
|
267
280
|
export declare function aiLedgerUnits(ledger: readonly AiJobItemLedger[]): AiBuildUnit[];
|
|
281
|
+
/**
|
|
282
|
+
* What the job drawer says under an item another plugin's writer made: a
|
|
283
|
+
* draft, or, for setup its writer saves live (`live`, a CRM pipeline or
|
|
284
|
+
* field, AGL-3746), that it was saved. A writer that went live only when the
|
|
285
|
+
* member may (an event the publishing role publishes, AGL-3755) reports
|
|
286
|
+
* `live: false` for one it kept as a draft, with its `notice` saying why.
|
|
287
|
+
*/
|
|
288
|
+
export declare function aiBuildItemOutputNote(capability: Pick<PluginAiCapability, 'where' | 'live'>, written?: Pick<PluginDraftRecord, 'live' | 'notice'>): string;
|
|
@@ -300,6 +300,29 @@ import { AI_CREDIT_RANGE_ZERO, AI_MEASURED_PASS_CREDITS, aiCreditRangeAdd, aiCre
|
|
|
300
300
|
}
|
|
301
301
|
return null;
|
|
302
302
|
}
|
|
303
|
+
/**
|
|
304
|
+
* What the person must still say before this plan can be built, as the
|
|
305
|
+
* questions its operations ask (`missingInput`), or `null` when nothing is
|
|
306
|
+
* missing: an item the planner kept with nothing it may build from — a
|
|
307
|
+
* tracking change naming no id, AGL-3751 — is asked about, never built
|
|
308
|
+
* empty or from a value the planner made up. Not a plan rule: a re-ask
|
|
309
|
+
* cannot supply what only the person knows, so the plan is not kept and
|
|
310
|
+
* the build answers with the question.
|
|
311
|
+
*/ export function aiBuildPlanMissingInput(plan, ops) {
|
|
312
|
+
var _plan_items;
|
|
313
|
+
if (!ops) return null;
|
|
314
|
+
const questions = [];
|
|
315
|
+
for (const item of (_plan_items = plan.items) != null ? _plan_items : []){
|
|
316
|
+
var _ops_get_missingInput, _ops_get;
|
|
317
|
+
const question = (_ops_get = ops.get(item.op)) == null ? void 0 : (_ops_get_missingInput = _ops_get.missingInput) == null ? void 0 : _ops_get_missingInput.call(_ops_get, {
|
|
318
|
+
name: item.name,
|
|
319
|
+
why: item.why,
|
|
320
|
+
args: item.args
|
|
321
|
+
});
|
|
322
|
+
if (question && !questions.includes(question)) questions.push(question);
|
|
323
|
+
}
|
|
324
|
+
return questions.length ? questions.join(' ') : null;
|
|
325
|
+
}
|
|
303
326
|
/** "a booking service", "an email design". */ export function aiArticle(noun) {
|
|
304
327
|
return `${/^[aeiou]/i.test(noun) ? 'an' : 'a'} ${noun}`;
|
|
305
328
|
}
|
|
@@ -565,8 +588,10 @@ const OPEN = [
|
|
|
565
588
|
const placed = new Map();
|
|
566
589
|
for (const { dep, capability } of aiBuildSectionItems(section, context.units, context.ops)){
|
|
567
590
|
var _ref, _placed_get, _ref1;
|
|
591
|
+
var _context_placedByCode;
|
|
568
592
|
const row = rows.get(dep.slot);
|
|
569
593
|
if (!row || !aiBuildItemDelivered(row)) continue;
|
|
594
|
+
if ((_context_placedByCode = context.placedByCode) == null ? void 0 : _context_placedByCode.includes(dep.op)) continue;
|
|
570
595
|
const blocks = (_ref = capability == null ? void 0 : capability.pageBlocks) != null ? _ref : [];
|
|
571
596
|
if (!blocks.length) continue;
|
|
572
597
|
const key = aiBuildBlocksKey(blocks);
|
|
@@ -691,5 +716,19 @@ function aiBuildBlocksKey(blocks) {
|
|
|
691
716
|
deps: row.op === 'page' ? creations : []
|
|
692
717
|
}));
|
|
693
718
|
}
|
|
719
|
+
/**
|
|
720
|
+
* What the job drawer says under an item another plugin's writer made: a
|
|
721
|
+
* draft, or, for setup its writer saves live (`live`, a CRM pipeline or
|
|
722
|
+
* field, AGL-3746), that it was saved. A writer that went live only when the
|
|
723
|
+
* member may (an event the publishing role publishes, AGL-3755) reports
|
|
724
|
+
* `live: false` for one it kept as a draft, with its `notice` saying why.
|
|
725
|
+
*/ export function aiBuildItemOutputNote(capability, written) {
|
|
726
|
+
var _ref;
|
|
727
|
+
var _written_notice;
|
|
728
|
+
const live = (_ref = written == null ? void 0 : written.live) != null ? _ref : capability.live;
|
|
729
|
+
const said = live ? `Live now, in ${capability.where}.` : `A draft, in ${capability.where}.`;
|
|
730
|
+
const notice = written == null ? void 0 : (_written_notice = written.notice) == null ? void 0 : _written_notice.trim();
|
|
731
|
+
return notice ? `${said} ${notice}` : said;
|
|
732
|
+
}
|
|
694
733
|
|
|
695
734
|
//# sourceMappingURL=ai-build-job.js.map
|