@aglyn/plugins-ai 1.0.0-beta.240 → 1.0.0-beta.242
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-job-links.d.ts +17 -0
- package/src/lib/components/ai-job-links.js +63 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.js +8 -1
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +7 -1
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-store-finish-card.component.d.ts +28 -0
- package/src/lib/components/ai-store-finish-card.component.js +116 -0
- package/src/lib/components/ai-store-finish-card.component.js.map +1 -0
- package/src/lib/components/assist-build-card.component.js +12 -1
- package/src/lib/components/assist-build-card.component.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.d.ts +45 -1
- package/src/lib/jobs/ai-job-build-step.js +283 -7
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +9 -1
- package/src/lib/jobs/ai-job-drafts.js +4 -1
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
- package/src/lib/jobs/ai-job-layout-language.js +4 -2
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +3 -1
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +2 -1
- package/src/lib/jobs/ai-job-page-language.js +19 -7
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.d.ts +11 -0
- package/src/lib/jobs/ai-job-page-sections.js +40 -3
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +2 -1
- package/src/lib/jobs/ai-job-page-step.js +19 -8
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +12 -0
- package/src/lib/jobs/ai-job-plan-step.js +41 -7
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.js +3 -2
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +76 -2
- package/src/lib/jobs/ai-job-site-datasets.js +206 -13
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +18 -2
- package/src/lib/jobs/ai-job-site-step.js +109 -22
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-store-pages.d.ts +75 -0
- package/src/lib/jobs/ai-job-site-store-pages.js +195 -0
- package/src/lib/jobs/ai-job-site-store-pages.js.map +1 -0
- package/src/lib/jobs/ai-job-store-links-layout.d.ts +71 -0
- package/src/lib/jobs/ai-job-store-links-layout.js +95 -0
- package/src/lib/jobs/ai-job-store-links-layout.js.map +1 -0
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +129 -8
- package/src/lib/jobs/ai-layout-stock-photos.js +919 -75
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-site-publish.d.ts +18 -0
- package/src/lib/jobs/ai-site-publish.js +76 -15
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.d.ts +43 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.js +61 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-ember-oak-hits.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-saltmarsh-recordings.d.ts +39 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-saltmarsh-recordings.js +508 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-saltmarsh-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.js +25 -2
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.d.ts +10 -0
- package/src/lib/layout-language/ai-layout-frame.js +58 -4
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +6 -0
- package/src/lib/layout-language/ai-layout-listings.js +3 -0
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +2 -0
- package/src/lib/model/ai-build-job.js +23 -3
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +7 -0
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-build-progress.js +3 -1
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-build-store-pages.d.ts +133 -0
- package/src/lib/model/ai-build-store-pages.js +209 -0
- package/src/lib/model/ai-build-store-pages.js.map +1 -0
- package/src/lib/model/ai-layout-store-links.d.ts +64 -0
- package/src/lib/model/ai-layout-store-links.js +165 -0
- package/src/lib/model/ai-layout-store-links.js.map +1 -0
- package/src/lib/model/ai-site-inventory.d.ts +5 -0
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-store-pages.d.ts +192 -0
- package/src/lib/model/ai-site-store-pages.js +383 -0
- package/src/lib/model/ai-site-store-pages.js.map +1 -0
- package/src/lib/runtime/site-inventory.js +8 -3
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/testing/stand-in-dataset-index.js +8 -4
- package/src/lib/testing/stand-in-dataset-index.js.map +1 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { checkEntitlement } from "@aglyn/aglyn/app-utils/plan-entitlements";
|
|
18
|
+
import { hostRoleCanWrite } from "@aglyn/aglyn/app-utils/organizations";
|
|
19
|
+
import { ACCOUNTS_PLUGIN_ID } from "@aglyn/aglyn/plugin-manager/enabled-plugins";
|
|
20
|
+
import { HostScreenVisibility } from "@aglyn/aglyn/foundation/definitions/platform.types";
|
|
21
|
+
import { FieldValue } from "firebase-admin/firestore";
|
|
22
|
+
import { aiStorePageNodes, aiStorePagesToWrite, AI_STORE_PAGES } from "../model/ai-site-store-pages.js";
|
|
23
|
+
import { aiModelForStep } from "../providers/routing.js";
|
|
24
|
+
import { aiOriginJobId } from "./ai-job-draft-ids.js";
|
|
25
|
+
import { writeAiDraft, writeAiDraftScreenSeo } from "./ai-job-drafts.js";
|
|
26
|
+
import { aiUnspentOutcome } from "./ai-job-generation.js";
|
|
27
|
+
import { aiPluginDraftAdmissionRefusal } from "./ai-job-plugin-drafts.js";
|
|
28
|
+
/**
|
|
29
|
+
* The store pages' unit of a site start (AGL-3676): the account, cart and
|
|
30
|
+
* policy pages a paid store gets beside its planned pages
|
|
31
|
+
* (`model/ai-site-store-pages.ts`), written by code in one pass that asks no
|
|
32
|
+
* model and costs no credits.
|
|
33
|
+
*
|
|
34
|
+
* Each page is a draft written through the same writer, under the same pages
|
|
35
|
+
* allowance, as every page the start builds (`writeAiDraft`), under an id
|
|
36
|
+
* derived from the unit's job, so a pass run again finds its own drafts. The
|
|
37
|
+
* guided start's publish then puts them live with the rest.
|
|
38
|
+
*
|
|
39
|
+
* The site's user accounts are turned on here too: the Customer account's
|
|
40
|
+
* sign-in, and the platform's /signin and /signup, answer only where the site
|
|
41
|
+
* serves accounts (`accounts` is off by default on a site that predates
|
|
42
|
+
* 2026-08; every new site is born with it). A site whose owner switched them
|
|
43
|
+
* off keeps them off: an explicit deny is theirs.
|
|
44
|
+
*/ /** The unit input a store pages unit carries what it writes in. */ export const AI_SITE_STORE_PAGES_INPUT = 'storePages';
|
|
45
|
+
/** The commerce plugin's name, in the refusal that asks for it. */ const STORE_LABEL = 'Commerce';
|
|
46
|
+
/** What a store pages row says where nothing could be written: our failure. */ export const AI_SITE_STORE_PAGES_NOT_WRITTEN_COPY = 'Your account, cart and policy pages could not be added this time.';
|
|
47
|
+
/** The id of the store page a unit writes for `key`. */ export function aiSiteStorePageId(unitJobId, key) {
|
|
48
|
+
return `${unitJobId}-${key}`;
|
|
49
|
+
}
|
|
50
|
+
const PATH = /^\/[a-z0-9-]*$/;
|
|
51
|
+
function inputOf(job) {
|
|
52
|
+
var _job_inputs;
|
|
53
|
+
const raw = (_job_inputs = job.inputs) == null ? void 0 : _job_inputs[AI_SITE_STORE_PAGES_INPUT];
|
|
54
|
+
if (!raw || !Array.isArray(raw.pages) || !raw.facts) return null;
|
|
55
|
+
const keys = new Set(AI_STORE_PAGES.map((page)=>page.key));
|
|
56
|
+
const pages = raw.pages.filter((page)=>page && keys.has(page.key) && typeof page.slug === 'string');
|
|
57
|
+
const path = (value, fallback)=>typeof value === 'string' && PATH.test(value) ? value : fallback;
|
|
58
|
+
return {
|
|
59
|
+
pages,
|
|
60
|
+
facts: {
|
|
61
|
+
contactPath: raw.facts.contactPath ? path(raw.facts.contactPath, '') || null : null,
|
|
62
|
+
shopPath: path(raw.facts.shopPath, '/shop'),
|
|
63
|
+
shippingPath: path(raw.facts.shippingPath, '/shipping-returns')
|
|
64
|
+
},
|
|
65
|
+
layoutId: typeof raw.layoutId === 'string' && raw.layoutId ? raw.layoutId : null
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Whether the store pages may be added for this member on this site, asked
|
|
70
|
+
* before their pass so a refusal spends nothing; `null` admits. The plan must
|
|
71
|
+
* include a store (Starter and up), the commerce plugin must run here, and the
|
|
72
|
+
* member must be able to edit the site's pages.
|
|
73
|
+
*/ export async function aiSiteStorePagesRefusal(context, deps = {}) {
|
|
74
|
+
var _context_org, _deps_admission, _context_org1, _host_get;
|
|
75
|
+
const { job, firestore } = context;
|
|
76
|
+
if (!job.hostId) return 'Open the site first.';
|
|
77
|
+
if (!checkEntitlement((_context_org = context.org) != null ? _context_org : null, 'commerce')) return 'Your plan does not include a store.';
|
|
78
|
+
const refused = await ((_deps_admission = deps.admission) != null ? _deps_admission : aiPluginDraftAdmissionRefusal)({
|
|
79
|
+
firestore,
|
|
80
|
+
orgId: job.orgId,
|
|
81
|
+
hostId: job.hostId,
|
|
82
|
+
inputs: {},
|
|
83
|
+
org: (_context_org1 = context.org) != null ? _context_org1 : null
|
|
84
|
+
}, {
|
|
85
|
+
kind: 'site',
|
|
86
|
+
drafts: [
|
|
87
|
+
{
|
|
88
|
+
resource: 'product',
|
|
89
|
+
label: STORE_LABEL
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
now: context.now
|
|
93
|
+
});
|
|
94
|
+
if (refused) return refused.error;
|
|
95
|
+
const host = await firestore.collection('hosts').doc(job.hostId).get();
|
|
96
|
+
return hostRoleCanWrite(((_host_get = host.get('memberRoles')) != null ? _host_get : {})[job.createdBy]) ? null : 'Editing requires the editor role';
|
|
97
|
+
}
|
|
98
|
+
/** The store pages' pass: every page written, user accounts on, one output a page. */ export function createAiSiteStorePagesRunner(deps = {}) {
|
|
99
|
+
var _deps_write, _deps_writeSeo;
|
|
100
|
+
const write = (_deps_write = deps.write) != null ? _deps_write : writeAiDraft;
|
|
101
|
+
const writeSeo = (_deps_writeSeo = deps.writeSeo) != null ? _deps_writeSeo : writeAiDraftScreenSeo;
|
|
102
|
+
return async (context)=>{
|
|
103
|
+
var _ref;
|
|
104
|
+
const { job, firestore, now } = context;
|
|
105
|
+
const model = (_ref = context.modelFor == null ? void 0 : context.modelFor.call(context, 'job.plan')) != null ? _ref : aiModelForStep('job.plan');
|
|
106
|
+
const input = inputOf(job);
|
|
107
|
+
const hostId = job.hostId;
|
|
108
|
+
if (!input || !hostId) return _extends({}, aiUnspentOutcome(model), {
|
|
109
|
+
failure: AI_SITE_STORE_PAGES_NOT_WRITTEN_COPY
|
|
110
|
+
});
|
|
111
|
+
const outputs = [];
|
|
112
|
+
let refusal = null;
|
|
113
|
+
for (const page of aiStorePagesToWrite(input.pages)){
|
|
114
|
+
var _context_org;
|
|
115
|
+
const definition = AI_STORE_PAGES.find((one)=>one.key === page.key);
|
|
116
|
+
if (!definition) continue;
|
|
117
|
+
const id = aiSiteStorePageId(job.$id, page.key);
|
|
118
|
+
const draft = await write(firestore, _extends({
|
|
119
|
+
kind: 'screen',
|
|
120
|
+
hostId,
|
|
121
|
+
id,
|
|
122
|
+
uid: job.createdBy,
|
|
123
|
+
org: (_context_org = context.org) != null ? _context_org : null,
|
|
124
|
+
name: definition.title,
|
|
125
|
+
slug: page.slug,
|
|
126
|
+
nodes: aiStorePageNodes(page.key, input.facts),
|
|
127
|
+
layoutId: input.layoutId
|
|
128
|
+
}, definition.noindex ? {
|
|
129
|
+
visibility: HostScreenVisibility.UNLISTED
|
|
130
|
+
} : {}, {
|
|
131
|
+
aiJobId: aiOriginJobId(job),
|
|
132
|
+
now
|
|
133
|
+
}));
|
|
134
|
+
if (draft.ok === false) {
|
|
135
|
+
// The pages allowance is full, or the site is gone: what was written stays.
|
|
136
|
+
refusal = draft.error;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
await writeSeo(firestore, {
|
|
140
|
+
hostId,
|
|
141
|
+
id,
|
|
142
|
+
seo: definition.seo,
|
|
143
|
+
now
|
|
144
|
+
});
|
|
145
|
+
outputs.push({
|
|
146
|
+
resource: 'screen',
|
|
147
|
+
id,
|
|
148
|
+
versionId: draft.versionId,
|
|
149
|
+
hostId,
|
|
150
|
+
hostSubdomain: draft.hostSubdomain,
|
|
151
|
+
label: draft.name,
|
|
152
|
+
proposal: {
|
|
153
|
+
storePage: page.key,
|
|
154
|
+
path: page.href
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
if (outputs.length) await turnOnSiteAccounts(firestore, hostId, now);
|
|
159
|
+
if (!outputs.length) {
|
|
160
|
+
return refusal ? aiUnspentOutcome(model, {
|
|
161
|
+
review: {
|
|
162
|
+
reason: 'limit',
|
|
163
|
+
message: refusal,
|
|
164
|
+
findings: []
|
|
165
|
+
}
|
|
166
|
+
}) : _extends({}, aiUnspentOutcome(model), {
|
|
167
|
+
failure: AI_SITE_STORE_PAGES_NOT_WRITTEN_COPY
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
return aiUnspentOutcome(model, {
|
|
171
|
+
outputs
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
export const runAiSiteStorePagesUnit = createAiSiteStorePagesRunner();
|
|
176
|
+
/**
|
|
177
|
+
* Turns on the site's user accounts (`accounts`, default-off per site), so
|
|
178
|
+
* the account page's sign-in and the platform's /signin and /signup answer.
|
|
179
|
+
* An owner's explicit switch-off is left as it is.
|
|
180
|
+
*/ export async function turnOnSiteAccounts(firestore, hostId, now) {
|
|
181
|
+
var _host_get, _host_get1;
|
|
182
|
+
const hostRef = firestore.collection('hosts').doc(hostId);
|
|
183
|
+
const host = await hostRef.get();
|
|
184
|
+
if (!host.exists) return false;
|
|
185
|
+
const disabled = (_host_get = host.get('disabledPlugins')) != null ? _host_get : [];
|
|
186
|
+
const enabled = (_host_get1 = host.get('enabledPlugins')) != null ? _host_get1 : [];
|
|
187
|
+
if (disabled.includes(ACCOUNTS_PLUGIN_ID) || enabled.includes(ACCOUNTS_PLUGIN_ID)) return false;
|
|
188
|
+
await hostRef.update({
|
|
189
|
+
enabledPlugins: FieldValue.arrayUnion(ACCOUNTS_PLUGIN_ID),
|
|
190
|
+
updatedAt: now
|
|
191
|
+
});
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
//# sourceMappingURL=ai-job-site-store-pages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-site-store-pages.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { ACCOUNTS_PLUGIN_ID } from '@aglyn/aglyn/plugin-manager/enabled-plugins'\nimport { HostScreenVisibility } from '@aglyn/aglyn/foundation/definitions/platform.types'\nimport type { NodesMap } from '@aglyn/aglyn/types/nodes'\nimport { FieldValue } from 'firebase-admin/firestore'\nimport type { AiJob, AiJobOutput } from '../model/ai-jobs.types'\nimport {\n aiStorePageNodes,\n aiStorePagesToWrite,\n AI_STORE_PAGES,\n type AiStorePageFacts,\n type AiStorePagePlan,\n} from '../model/ai-site-store-pages'\nimport { aiModelForStep } from '../providers/routing'\nimport { aiOriginJobId } from './ai-job-draft-ids'\nimport { writeAiDraft, writeAiDraftScreenSeo } from './ai-job-drafts'\nimport { aiUnspentOutcome } from './ai-job-generation'\nimport { aiPluginDraftAdmissionRefusal } from './ai-job-plugin-drafts'\nimport type { AiJobStepContext, AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\n\n/**\n * The store pages' unit of a site start (AGL-3676): the account, cart and\n * policy pages a paid store gets beside its planned pages\n * (`model/ai-site-store-pages.ts`), written by code in one pass that asks no\n * model and costs no credits.\n *\n * Each page is a draft written through the same writer, under the same pages\n * allowance, as every page the start builds (`writeAiDraft`), under an id\n * derived from the unit's job, so a pass run again finds its own drafts. The\n * guided start's publish then puts them live with the rest.\n *\n * The site's user accounts are turned on here too: the Customer account's\n * sign-in, and the platform's /signin and /signup, answer only where the site\n * serves accounts (`accounts` is off by default on a site that predates\n * 2026-08; every new site is born with it). A site whose owner switched them\n * off keeps them off: an explicit deny is theirs.\n */\n\n/** The unit input a store pages unit carries what it writes in. */\nexport const AI_SITE_STORE_PAGES_INPUT = 'storePages'\n\n/** What a store pages unit is told: the store pages the plan implies, and what their words link. */\nexport interface AiSiteStorePagesInput {\n pages: AiStorePagePlan[]\n facts: AiStorePageFacts\n /** The layout the start built, which the pages render inside; the site's otherwise. */\n layoutId: string | null\n}\n\n/** The commerce plugin's name, in the refusal that asks for it. */\nconst STORE_LABEL = 'Commerce'\n\n/** What a store pages row says where nothing could be written: our failure. */\nexport const AI_SITE_STORE_PAGES_NOT_WRITTEN_COPY = 'Your account, cart and policy pages could not be added this time.'\n\n/** The id of the store page a unit writes for `key`. */\nexport function aiSiteStorePageId(unitJobId: string, key: string): string {\n return `${unitJobId}-${key}`\n}\n\nconst PATH = /^\\/[a-z0-9-]*$/\n\nfunction inputOf(job: Pick<AiJob, 'inputs'>): AiSiteStorePagesInput | null {\n const raw = job.inputs?.[AI_SITE_STORE_PAGES_INPUT] as Partial<AiSiteStorePagesInput> | undefined\n if (!raw || !Array.isArray(raw.pages) || !raw.facts) return null\n const keys = new Set(AI_STORE_PAGES.map((page) => page.key))\n const pages = raw.pages.filter((page) => page && keys.has(page.key) && typeof page.slug === 'string')\n const path = (value: unknown, fallback: string) => (typeof value === 'string' && PATH.test(value) ? value : fallback)\n return {\n pages,\n facts: {\n contactPath: raw.facts.contactPath ? path(raw.facts.contactPath, '') || null : null,\n shopPath: path(raw.facts.shopPath, '/shop'),\n shippingPath: path(raw.facts.shippingPath, '/shipping-returns'),\n },\n layoutId: typeof raw.layoutId === 'string' && raw.layoutId ? raw.layoutId : null,\n }\n}\n\n/**\n * Whether the store pages may be added for this member on this site, asked\n * before their pass so a refusal spends nothing; `null` admits. The plan must\n * include a store (Starter and up), the commerce plugin must run here, and the\n * member must be able to edit the site's pages.\n */\nexport async function aiSiteStorePagesRefusal(\n context: Pick<AiJobStepContext, 'firestore' | 'org' | 'now'> & { job: Pick<AiJob, 'orgId' | 'hostId' | 'createdBy'> },\n deps: { admission?: typeof aiPluginDraftAdmissionRefusal } = {},\n): Promise<string | null> {\n const { job, firestore } = context\n if (!job.hostId) return 'Open the site first.'\n if (!checkEntitlement((context.org ?? null) as never, 'commerce')) return 'Your plan does not include a store.'\n const refused = await (deps.admission ?? aiPluginDraftAdmissionRefusal)(\n { firestore, orgId: job.orgId, hostId: job.hostId, inputs: {}, org: context.org ?? null },\n { kind: 'site', drafts: [{ resource: 'product', label: STORE_LABEL }], now: context.now },\n )\n if (refused) return refused.error\n const host = await firestore.collection('hosts').doc(job.hostId).get()\n return hostRoleCanWrite((host.get('memberRoles') ?? {})[job.createdBy]) ? null : 'Editing requires the editor role'\n}\n\nexport interface AiSiteStorePagesRunnerDeps {\n write?: typeof writeAiDraft\n writeSeo?: typeof writeAiDraftScreenSeo\n}\n\n/** The store pages' pass: every page written, user accounts on, one output a page. */\nexport function createAiSiteStorePagesRunner(deps: AiSiteStorePagesRunnerDeps = {}): AiJobStepRunner {\n const write = deps.write ?? writeAiDraft\n const writeSeo = deps.writeSeo ?? writeAiDraftScreenSeo\n return async (context): Promise<AiJobStepOutcome> => {\n const { job, firestore, now } = context\n const model = context.modelFor?.('job.plan') ?? aiModelForStep('job.plan')\n const input = inputOf(job)\n const hostId = job.hostId\n if (!input || !hostId) return { ...aiUnspentOutcome(model), failure: AI_SITE_STORE_PAGES_NOT_WRITTEN_COPY }\n const outputs: AiJobOutput[] = []\n let refusal: string | null = null\n for (const page of aiStorePagesToWrite(input.pages)) {\n const definition = AI_STORE_PAGES.find((one) => one.key === page.key)\n if (!definition) continue\n const id = aiSiteStorePageId(job.$id, page.key)\n const draft = await write(firestore, {\n kind: 'screen',\n hostId,\n id,\n uid: job.createdBy,\n org: context.org ?? null,\n name: definition.title,\n slug: page.slug,\n nodes: aiStorePageNodes(page.key, input.facts) as unknown as NodesMap,\n layoutId: input.layoutId,\n // The account and the cart are a shopper's own pages: unlisted, noindex (AGL-3676).\n ...(definition.noindex ? { visibility: HostScreenVisibility.UNLISTED } : {}),\n aiJobId: aiOriginJobId(job),\n now,\n })\n if (draft.ok === false) {\n // The pages allowance is full, or the site is gone: what was written stays.\n refusal = draft.error\n break\n }\n await writeSeo(firestore, { hostId, id, seo: definition.seo, now })\n outputs.push({\n resource: 'screen',\n id,\n versionId: draft.versionId,\n hostId,\n hostSubdomain: draft.hostSubdomain,\n label: draft.name,\n proposal: { storePage: page.key, path: page.href },\n })\n }\n if (outputs.length) await turnOnSiteAccounts(firestore, hostId, now)\n if (!outputs.length) {\n return refusal\n ? aiUnspentOutcome(model, { review: { reason: 'limit', message: refusal, findings: [] } })\n : { ...aiUnspentOutcome(model), failure: AI_SITE_STORE_PAGES_NOT_WRITTEN_COPY }\n }\n return aiUnspentOutcome(model, { outputs })\n }\n}\n\nexport const runAiSiteStorePagesUnit = createAiSiteStorePagesRunner()\n\n/**\n * Turns on the site's user accounts (`accounts`, default-off per site), so\n * the account page's sign-in and the platform's /signin and /signup answer.\n * An owner's explicit switch-off is left as it is.\n */\nexport async function turnOnSiteAccounts(firestore: FirebaseFirestore.Firestore, hostId: string, now: Date): Promise<boolean> {\n const hostRef = firestore.collection('hosts').doc(hostId)\n const host = await hostRef.get()\n if (!host.exists) return false\n const disabled = (host.get('disabledPlugins') ?? []) as unknown[]\n const enabled = (host.get('enabledPlugins') ?? []) as unknown[]\n if (disabled.includes(ACCOUNTS_PLUGIN_ID) || enabled.includes(ACCOUNTS_PLUGIN_ID)) return false\n await hostRef.update({ enabledPlugins: FieldValue.arrayUnion(ACCOUNTS_PLUGIN_ID), updatedAt: now })\n return true\n}\n"],"names":["checkEntitlement","hostRoleCanWrite","ACCOUNTS_PLUGIN_ID","HostScreenVisibility","FieldValue","aiStorePageNodes","aiStorePagesToWrite","AI_STORE_PAGES","aiModelForStep","aiOriginJobId","writeAiDraft","writeAiDraftScreenSeo","aiUnspentOutcome","aiPluginDraftAdmissionRefusal","AI_SITE_STORE_PAGES_INPUT","STORE_LABEL","AI_SITE_STORE_PAGES_NOT_WRITTEN_COPY","aiSiteStorePageId","unitJobId","key","PATH","inputOf","job","raw","inputs","Array","isArray","pages","facts","keys","Set","map","page","filter","has","slug","path","value","fallback","test","contactPath","shopPath","shippingPath","layoutId","aiSiteStorePagesRefusal","context","deps","host","firestore","hostId","org","refused","admission","orgId","kind","drafts","resource","label","now","error","collection","doc","get","createdBy","createAiSiteStorePagesRunner","write","writeSeo","model","modelFor","input","failure","outputs","refusal","definition","find","one","id","$id","draft","uid","name","title","nodes","noindex","visibility","UNLISTED","aiJobId","ok","seo","push","versionId","hostSubdomain","proposal","storePage","href","length","turnOnSiteAccounts","review","reason","message","findings","runAiSiteStorePagesUnit","hostRef","exists","disabled","enabled","includes","update","enabledPlugins","arrayUnion","updatedAt"],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
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 { Firestore } from 'firebase-admin/firestore';
|
|
18
|
+
import { type AiStoreLayoutLinks } from '../model/ai-layout-store-links';
|
|
19
|
+
/**
|
|
20
|
+
* A store's links in the site's own layout, as a DRAFT (AGL-3676): a build
|
|
21
|
+
* that makes the site a store and builds no layout writes a new version of
|
|
22
|
+
* the layout its store pages render inside, with Account (and Cart, where
|
|
23
|
+
* the header has none) in the header and the account and policies in the
|
|
24
|
+
* footer (`aiLayoutWithStoreLinks`). The layout's current version — the one
|
|
25
|
+
* visitors see — is never touched: the new version waits beside it, and goes
|
|
26
|
+
* live only when the build publishes (`aiPublishGuidedSite`'s `storeLinks`),
|
|
27
|
+
* as every draft the build makes does.
|
|
28
|
+
*/
|
|
29
|
+
/** The field a draft version keeps the version it was made from in, so a publish knows whether it is still current. */
|
|
30
|
+
export declare const AI_STORE_LINKS_SOURCE_FIELD = "aiStoreLinksSourceVersionId";
|
|
31
|
+
/** What a store links draft version is called in the layout's versions. */
|
|
32
|
+
export declare const AI_STORE_LINKS_VERSION_NAME = "Account and policy links";
|
|
33
|
+
/** What writing a store's links into the site's layout came to. */
|
|
34
|
+
export type AiStoreLinksLayoutDraft = {
|
|
35
|
+
status: 'written';
|
|
36
|
+
layoutId: string;
|
|
37
|
+
versionId: string;
|
|
38
|
+
name: string;
|
|
39
|
+
}
|
|
40
|
+
/** Every link was already there. */
|
|
41
|
+
| {
|
|
42
|
+
status: 'present';
|
|
43
|
+
layoutId: string;
|
|
44
|
+
}
|
|
45
|
+
/** The layout has no header list or no footer list to add to: what it had room for, if anything, was written. */
|
|
46
|
+
| {
|
|
47
|
+
status: 'unrecognized';
|
|
48
|
+
layoutId: string;
|
|
49
|
+
versionId: string | null;
|
|
50
|
+
name: string;
|
|
51
|
+
}
|
|
52
|
+
/** The layout is gone, or has no current version to start from. */
|
|
53
|
+
| {
|
|
54
|
+
status: 'gone';
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Writes the draft version (idempotent: a version with this id already
|
|
58
|
+
* written is that draft). Reads the layout's current version and writes the
|
|
59
|
+
* new one beside it, a copy of it — its fields, everything the editor stored
|
|
60
|
+
* — with the links added.
|
|
61
|
+
*/
|
|
62
|
+
export declare function writeAiStoreLinksLayoutDraft(firestore: Firestore, input: {
|
|
63
|
+
hostId: string;
|
|
64
|
+
layoutId: string;
|
|
65
|
+
versionId: string;
|
|
66
|
+
links: AiStoreLayoutLinks;
|
|
67
|
+
screenPaths: Readonly<Record<string, string>>;
|
|
68
|
+
uid: string;
|
|
69
|
+
aiJobId: string;
|
|
70
|
+
now: Date;
|
|
71
|
+
}): Promise<AiStoreLinksLayoutDraft>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { decodeStoredNodes, encodeStoredNodes } from "@aglyn/aglyn/app-utils/stored-nodes";
|
|
18
|
+
import { aiLayoutWithStoreLinks } from "../model/ai-layout-store-links.js";
|
|
19
|
+
/**
|
|
20
|
+
* A store's links in the site's own layout, as a DRAFT (AGL-3676): a build
|
|
21
|
+
* that makes the site a store and builds no layout writes a new version of
|
|
22
|
+
* the layout its store pages render inside, with Account (and Cart, where
|
|
23
|
+
* the header has none) in the header and the account and policies in the
|
|
24
|
+
* footer (`aiLayoutWithStoreLinks`). The layout's current version — the one
|
|
25
|
+
* visitors see — is never touched: the new version waits beside it, and goes
|
|
26
|
+
* live only when the build publishes (`aiPublishGuidedSite`'s `storeLinks`),
|
|
27
|
+
* as every draft the build makes does.
|
|
28
|
+
*/ /** The field a draft version keeps the version it was made from in, so a publish knows whether it is still current. */ export const AI_STORE_LINKS_SOURCE_FIELD = 'aiStoreLinksSourceVersionId';
|
|
29
|
+
/** What a store links draft version is called in the layout's versions. */ export const AI_STORE_LINKS_VERSION_NAME = 'Account and policy links';
|
|
30
|
+
/**
|
|
31
|
+
* Writes the draft version (idempotent: a version with this id already
|
|
32
|
+
* written is that draft). Reads the layout's current version and writes the
|
|
33
|
+
* new one beside it, a copy of it — its fields, everything the editor stored
|
|
34
|
+
* — with the links added.
|
|
35
|
+
*/ export async function writeAiStoreLinksLayoutDraft(firestore, input) {
|
|
36
|
+
var _layout_get;
|
|
37
|
+
const layoutRef = firestore.collection('hosts').doc(input.hostId).collection('layouts').doc(input.layoutId);
|
|
38
|
+
const layout = await layoutRef.get();
|
|
39
|
+
const currentVersionId = layout.get('versionId');
|
|
40
|
+
if (!layout.exists || layout.get('deletedAt') != null || typeof currentVersionId !== 'string' || !currentVersionId) {
|
|
41
|
+
return {
|
|
42
|
+
status: 'gone'
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const name = String((_layout_get = layout.get('displayName')) != null ? _layout_get : '') || 'Layout';
|
|
46
|
+
const draftRef = layoutRef.collection('versions').doc(input.versionId);
|
|
47
|
+
const already = await draftRef.get();
|
|
48
|
+
if (already.exists) return {
|
|
49
|
+
status: 'written',
|
|
50
|
+
layoutId: input.layoutId,
|
|
51
|
+
versionId: input.versionId,
|
|
52
|
+
name
|
|
53
|
+
};
|
|
54
|
+
const current = await layoutRef.collection('versions').doc(currentVersionId).get();
|
|
55
|
+
const nodes = current.exists ? decodeStoredNodes(current.get('nodes')) : null;
|
|
56
|
+
if (!nodes) return {
|
|
57
|
+
status: 'gone'
|
|
58
|
+
};
|
|
59
|
+
const result = aiLayoutWithStoreLinks(nodes, input.links, input.screenPaths);
|
|
60
|
+
const recognized = result.header && result.footer;
|
|
61
|
+
const packed = result.nodes ? encodeStoredNodes(result.nodes) : null;
|
|
62
|
+
if (!packed) return recognized ? {
|
|
63
|
+
status: 'present',
|
|
64
|
+
layoutId: input.layoutId
|
|
65
|
+
} : {
|
|
66
|
+
status: 'unrecognized',
|
|
67
|
+
layoutId: input.layoutId,
|
|
68
|
+
versionId: null,
|
|
69
|
+
name
|
|
70
|
+
};
|
|
71
|
+
const copy = _extends({}, current.data());
|
|
72
|
+
delete copy['aiEditJobId'];
|
|
73
|
+
await draftRef.create(_extends({}, copy, {
|
|
74
|
+
nodes: Buffer.from(packed),
|
|
75
|
+
displayName: AI_STORE_LINKS_VERSION_NAME,
|
|
76
|
+
createdAt: input.now,
|
|
77
|
+
updatedAt: input.now,
|
|
78
|
+
createdBy: input.uid,
|
|
79
|
+
aiJobId: input.aiJobId,
|
|
80
|
+
[AI_STORE_LINKS_SOURCE_FIELD]: currentVersionId
|
|
81
|
+
}));
|
|
82
|
+
return recognized ? {
|
|
83
|
+
status: 'written',
|
|
84
|
+
layoutId: input.layoutId,
|
|
85
|
+
versionId: input.versionId,
|
|
86
|
+
name
|
|
87
|
+
} : {
|
|
88
|
+
status: 'unrecognized',
|
|
89
|
+
layoutId: input.layoutId,
|
|
90
|
+
versionId: input.versionId,
|
|
91
|
+
name
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
//# sourceMappingURL=ai-job-store-links-layout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-store-links-layout.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 { decodeStoredNodes, encodeStoredNodes } from '@aglyn/aglyn/app-utils/stored-nodes'\nimport type { Firestore } from 'firebase-admin/firestore'\nimport { aiLayoutWithStoreLinks, type AiStoreLayoutLinks } from '../model/ai-layout-store-links'\n\n/**\n * A store's links in the site's own layout, as a DRAFT (AGL-3676): a build\n * that makes the site a store and builds no layout writes a new version of\n * the layout its store pages render inside, with Account (and Cart, where\n * the header has none) in the header and the account and policies in the\n * footer (`aiLayoutWithStoreLinks`). The layout's current version — the one\n * visitors see — is never touched: the new version waits beside it, and goes\n * live only when the build publishes (`aiPublishGuidedSite`'s `storeLinks`),\n * as every draft the build makes does.\n */\n\n/** The field a draft version keeps the version it was made from in, so a publish knows whether it is still current. */\nexport const AI_STORE_LINKS_SOURCE_FIELD = 'aiStoreLinksSourceVersionId'\n\n/** What a store links draft version is called in the layout's versions. */\nexport const AI_STORE_LINKS_VERSION_NAME = 'Account and policy links'\n\n/** What writing a store's links into the site's layout came to. */\nexport type AiStoreLinksLayoutDraft =\n | { status: 'written'; layoutId: string; versionId: string; name: string }\n /** Every link was already there. */\n | { status: 'present'; layoutId: string }\n /** The layout has no header list or no footer list to add to: what it had room for, if anything, was written. */\n | { status: 'unrecognized'; layoutId: string; versionId: string | null; name: string }\n /** The layout is gone, or has no current version to start from. */\n | { status: 'gone' }\n\n/**\n * Writes the draft version (idempotent: a version with this id already\n * written is that draft). Reads the layout's current version and writes the\n * new one beside it, a copy of it — its fields, everything the editor stored\n * — with the links added.\n */\nexport async function writeAiStoreLinksLayoutDraft(\n firestore: Firestore,\n input: {\n hostId: string\n layoutId: string\n versionId: string\n links: AiStoreLayoutLinks\n screenPaths: Readonly<Record<string, string>>\n uid: string\n aiJobId: string\n now: Date\n },\n): Promise<AiStoreLinksLayoutDraft> {\n const layoutRef = firestore.collection('hosts').doc(input.hostId).collection('layouts').doc(input.layoutId)\n const layout = await layoutRef.get()\n const currentVersionId = layout.get('versionId')\n if (!layout.exists || layout.get('deletedAt') != null || typeof currentVersionId !== 'string' || !currentVersionId) {\n return { status: 'gone' }\n }\n const name = String(layout.get('displayName') ?? '') || 'Layout'\n const draftRef = layoutRef.collection('versions').doc(input.versionId)\n const already = await draftRef.get()\n if (already.exists) return { status: 'written', layoutId: input.layoutId, versionId: input.versionId, name }\n const current = await layoutRef.collection('versions').doc(currentVersionId).get()\n const nodes = current.exists ? decodeStoredNodes<Record<string, unknown>>(current.get('nodes')) : null\n if (!nodes) return { status: 'gone' }\n const result = aiLayoutWithStoreLinks(nodes, input.links, input.screenPaths)\n const recognized = result.header && result.footer\n const packed = result.nodes ? encodeStoredNodes(result.nodes as never) : null\n if (!packed) return recognized ? { status: 'present', layoutId: input.layoutId } : { status: 'unrecognized', layoutId: input.layoutId, versionId: null, name }\n const copy = { ...(current.data() as Record<string, unknown>) }\n delete copy['aiEditJobId']\n await draftRef.create({\n ...copy,\n nodes: Buffer.from(packed),\n displayName: AI_STORE_LINKS_VERSION_NAME,\n createdAt: input.now,\n updatedAt: input.now,\n createdBy: input.uid,\n aiJobId: input.aiJobId,\n [AI_STORE_LINKS_SOURCE_FIELD]: currentVersionId,\n })\n return recognized\n ? { status: 'written', layoutId: input.layoutId, versionId: input.versionId, name }\n : { status: 'unrecognized', layoutId: input.layoutId, versionId: input.versionId, name }\n}\n"],"names":["decodeStoredNodes","encodeStoredNodes","aiLayoutWithStoreLinks","AI_STORE_LINKS_SOURCE_FIELD","AI_STORE_LINKS_VERSION_NAME","writeAiStoreLinksLayoutDraft","firestore","input","layout","layoutRef","collection","doc","hostId","layoutId","get","currentVersionId","exists","status","name","String","draftRef","versionId","already","current","nodes","result","links","screenPaths","recognized","header","footer","packed","copy","data","create","Buffer","from","displayName","createdAt","now","updatedAt","createdBy","uid","aiJobId"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,iBAAiB,EAAEC,iBAAiB,QAAQ,sCAAqC;AAE1F,SAASC,sBAAsB,QAAiC,oCAAgC;AAEhG;;;;;;;;;CASC,GAED,qHAAqH,GACrH,OAAO,MAAMC,8BAA8B,8BAA6B;AAExE,yEAAyE,GACzE,OAAO,MAAMC,8BAA8B,2BAA0B;AAYrE;;;;;CAKC,GACD,OAAO,eAAeC,6BACpBC,SAAoB,EACpBC,KASC;QAQmBC;IANpB,MAAMC,YAAYH,UAAUI,UAAU,CAAC,SAASC,GAAG,CAACJ,MAAMK,MAAM,EAAEF,UAAU,CAAC,WAAWC,GAAG,CAACJ,MAAMM,QAAQ;IAC1G,MAAML,SAAS,MAAMC,UAAUK,GAAG;IAClC,MAAMC,mBAAmBP,OAAOM,GAAG,CAAC;IACpC,IAAI,CAACN,OAAOQ,MAAM,IAAIR,OAAOM,GAAG,CAAC,gBAAgB,QAAQ,OAAOC,qBAAqB,YAAY,CAACA,kBAAkB;QAClH,OAAO;YAAEE,QAAQ;QAAO;IAC1B;IACA,MAAMC,OAAOC,QAAOX,cAAAA,OAAOM,GAAG,CAAC,0BAAXN,cAA6B,OAAO;IACxD,MAAMY,WAAWX,UAAUC,UAAU,CAAC,YAAYC,GAAG,CAACJ,MAAMc,SAAS;IACrE,MAAMC,UAAU,MAAMF,SAASN,GAAG;IAClC,IAAIQ,QAAQN,MAAM,EAAE,OAAO;QAAEC,QAAQ;QAAWJ,UAAUN,MAAMM,QAAQ;QAAEQ,WAAWd,MAAMc,SAAS;QAAEH;IAAK;IAC3G,MAAMK,UAAU,MAAMd,UAAUC,UAAU,CAAC,YAAYC,GAAG,CAACI,kBAAkBD,GAAG;IAChF,MAAMU,QAAQD,QAAQP,MAAM,GAAGhB,kBAA2CuB,QAAQT,GAAG,CAAC,YAAY;IAClG,IAAI,CAACU,OAAO,OAAO;QAAEP,QAAQ;IAAO;IACpC,MAAMQ,SAASvB,uBAAuBsB,OAAOjB,MAAMmB,KAAK,EAAEnB,MAAMoB,WAAW;IAC3E,MAAMC,aAAaH,OAAOI,MAAM,IAAIJ,OAAOK,MAAM;IACjD,MAAMC,SAASN,OAAOD,KAAK,GAAGvB,kBAAkBwB,OAAOD,KAAK,IAAa;IACzE,IAAI,CAACO,QAAQ,OAAOH,aAAa;QAAEX,QAAQ;QAAWJ,UAAUN,MAAMM,QAAQ;IAAC,IAAI;QAAEI,QAAQ;QAAgBJ,UAAUN,MAAMM,QAAQ;QAAEQ,WAAW;QAAMH;IAAK;IAC7J,MAAMc,OAAO,aAAMT,QAAQU,IAAI;IAC/B,OAAOD,IAAI,CAAC,cAAc;IAC1B,MAAMZ,SAASc,MAAM,CAAC,aACjBF;QACHR,OAAOW,OAAOC,IAAI,CAACL;QACnBM,aAAajC;QACbkC,WAAW/B,MAAMgC,GAAG;QACpBC,WAAWjC,MAAMgC,GAAG;QACpBE,WAAWlC,MAAMmC,GAAG;QACpBC,SAASpC,MAAMoC,OAAO;QACtB,CAACxC,4BAA4B,EAAEY;;IAEjC,OAAOa,aACH;QAAEX,QAAQ;QAAWJ,UAAUN,MAAMM,QAAQ;QAAEQ,WAAWd,MAAMc,SAAS;QAAEH;IAAK,IAChF;QAAED,QAAQ;QAAgBJ,UAAUN,MAAMM,QAAQ;QAAEQ,WAAWd,MAAMc,SAAS;QAAEH;IAAK;AAC3F"}
|
|
@@ -71,6 +71,17 @@ import { type AiLayoutPictureSlot, type AiLayoutPictureSource } from '../layout-
|
|
|
71
71
|
* ranked: the object in the hit's first tags (what the photo is OF) over
|
|
72
72
|
* a mention in its last, the subject's other words, the domain. Among
|
|
73
73
|
* equals the job's seed picks, so two sites differ and one job repeats.
|
|
74
|
+
* What the beta.239 Ember & Oak start added: "hand-poured", "handmade"
|
|
75
|
+
* and "by hand" never make "hands" a subject, and a story of the craft
|
|
76
|
+
* asks for the maker at work ("candle making"); a hit carrying a topic
|
|
77
|
+
* the brief never named (a cross, a grave, a flag, a gun, wine, a child,
|
|
78
|
+
* a mannequin) never fills anything; a product, and a collection tile,
|
|
79
|
+
* take only a hit naming its head noun among the first tags, held to the
|
|
80
|
+
* shop's category; and an object whose word is not enough ("melt") is
|
|
81
|
+
* named and searched by its kin ("wax melt", "melt warmer"), never by the
|
|
82
|
+
* category alone. And from the Kiln & Clover portfolio: nudity never fills
|
|
83
|
+
* anything, for any business; a hit naming a rival craft (glass, for a
|
|
84
|
+
* potter) counts only when it names the craft's own words too.
|
|
74
85
|
* 4. **Never twice.** A photo is placed once per job: not twice on a page,
|
|
75
86
|
* and not on a page when another page (or another pass, in this process)
|
|
76
87
|
* of the same job already placed it. The caller hands in what the job's
|
|
@@ -84,6 +95,18 @@ import { type AiLayoutPictureSlot, type AiLayoutPictureSource } from '../layout-
|
|
|
84
95
|
* to its photographer. A copy that fails tries the slot's next accepted
|
|
85
96
|
* hit.
|
|
86
97
|
*
|
|
98
|
+
* 6. **Fall back to the craft, never off it** (the beta.241 Saltmarsh Soap
|
|
99
|
+
* Co start, a store "for handmade cold-process soap": its craft read as
|
|
100
|
+
* nothing, its hero searched "shop" and took a florist's bouquet, and four
|
|
101
|
+
* of five products took the starters' laptop desk and living room). A
|
|
102
|
+
* business that is only a place takes its craft from what it is for
|
|
103
|
+
* ("soap shop"); a hero names the craft itself ({@link AiStockSiteTerms.core});
|
|
104
|
+
* a product, after its own searches, tries its head noun anywhere among
|
|
105
|
+
* the tags, its gift wrapper with the craft and the craft's broad
|
|
106
|
+
* searches, and then — a product or a hero only — reuses an on-topic photo
|
|
107
|
+
* the job already shows rather than take a starter. Every slot of a
|
|
108
|
+
* craft's site tries the craft's broad searches last.
|
|
109
|
+
*
|
|
87
110
|
* A slot nothing acceptable answered — the library down, refusing, or out
|
|
88
111
|
* of time — is left `null` for the starter photos. A named object is never
|
|
89
112
|
* given one while the library answers anything of the site's world: the
|
|
@@ -103,6 +126,12 @@ export declare const AI_LAYOUT_STOCK_MAX_BYTES: number;
|
|
|
103
126
|
* written to, whose photos a page does not place again (AGL-3660).
|
|
104
127
|
*/
|
|
105
128
|
export declare const AI_STOCK_PHOTO_PAGES_INPUT = "stockPhotoPages";
|
|
129
|
+
/**
|
|
130
|
+
* The unit job input naming library photos (`media:` references) another
|
|
131
|
+
* part of the job already shows outside its pages — a dataset's records
|
|
132
|
+
* (AGL-3616) — which a page does not place again.
|
|
133
|
+
*/
|
|
134
|
+
export declare const AI_STOCK_PHOTO_AVOID_INPUT = "stockPhotoAvoid";
|
|
106
135
|
/**
|
|
107
136
|
* A word's singular, for matching only: "bowls" and "bowl", "vases" and
|
|
108
137
|
* "vase", "berries" and "berry", "dishes" and "dish" meet. Both sides of a
|
|
@@ -140,6 +169,22 @@ export interface AiStockSiteTerms {
|
|
|
140
169
|
domain: string[];
|
|
141
170
|
/** The searches for the site's world itself, broadest last. */
|
|
142
171
|
broad: string[];
|
|
172
|
+
/** The searches for its maker at work, for a story of the craft (AGL-3660); empty for a craft not listed. */
|
|
173
|
+
making: string[];
|
|
174
|
+
/** Every stem of the business type or brief: what lets a sensitive topic in ({@link SENSITIVE_TOPICS}). */
|
|
175
|
+
named: string[];
|
|
176
|
+
/** Rival crafts' words, each with the words that must then be named too ({@link CRAFT_KIN}). */
|
|
177
|
+
rivals: Array<{
|
|
178
|
+
words: string[];
|
|
179
|
+
core: string[];
|
|
180
|
+
}>;
|
|
181
|
+
/**
|
|
182
|
+
* The stems a photo of the craft ITSELF names, one of which a hero and a
|
|
183
|
+
* product's fallbacks must name (AGL-3660): the craft and, for a listed
|
|
184
|
+
* craft, its own names ("soap", "soapmaker", "skincare"). Empty where the
|
|
185
|
+
* site names no craft.
|
|
186
|
+
*/
|
|
187
|
+
core: string[];
|
|
143
188
|
}
|
|
144
189
|
/** The site's terms, read from its business type. */
|
|
145
190
|
export declare function aiStockSiteTerms(businessType: string): AiStockSiteTerms;
|
|
@@ -170,19 +215,72 @@ export interface AiStockSearch extends StockPhotoSearchRequest {
|
|
|
170
215
|
subject?: string;
|
|
171
216
|
/** A word every hit must name: a product's photo names the shop's category (AGL-3676). */
|
|
172
217
|
requires?: string;
|
|
218
|
+
/**
|
|
219
|
+
* Words of which a hit must name ONE, `requires` among them (AGL-3660): a
|
|
220
|
+
* shop's category or the slot's own word of its world, so a soap shop's
|
|
221
|
+
* body lotion is held to "lotion", never to "soap".
|
|
222
|
+
*/
|
|
223
|
+
category?: readonly string[];
|
|
224
|
+
/** The thing every hit must name, where it is not the subject's own: a product's head noun (AGL-3660). */
|
|
225
|
+
object?: string;
|
|
226
|
+
/** Phrases that name the object where its own word is not enough: "wax melt" ({@link OBJECT_KIN}). */
|
|
227
|
+
kin?: readonly string[];
|
|
228
|
+
/** Whether the object must be among the hit's first tags, what the photo is OF (AGL-3660). */
|
|
229
|
+
lead?: boolean;
|
|
230
|
+
/** Whether the hit's first tags must name the site's world, where the object need not lead (AGL-3660). */
|
|
231
|
+
leadWorld?: boolean;
|
|
232
|
+
}
|
|
233
|
+
/** A slot's part of a page, where it asks for more than its role: a collection tile or a story of the craft (AGL-3660). */
|
|
234
|
+
export type AiStockSlotPart = 'collection' | 'story';
|
|
235
|
+
/**
|
|
236
|
+
* A slot's part (AGL-3660): a tile of a collections band is product-like,
|
|
237
|
+
* held to the shop's category and its own object; a picture of a story of
|
|
238
|
+
* the craft, or of a thing made by hand, shows the maker at work.
|
|
239
|
+
*/
|
|
240
|
+
export declare function aiStockSlotPart(slot: Pick<AiLayoutPictureSlot, 'role' | 'alt'>, sectionName: string): AiStockSlotPart | undefined;
|
|
241
|
+
/** What a product falls back to once its own searches find nothing ({@link aiStockProductSearches}). */
|
|
242
|
+
export interface AiStockProductFallback {
|
|
243
|
+
/** The site's broad searches ({@link AiStockSiteTerms.broad}): "handmade soap". */
|
|
244
|
+
broad?: readonly string[];
|
|
245
|
+
/** The words a photo of the craft itself names ({@link AiStockSiteTerms.core}). */
|
|
246
|
+
core?: readonly string[];
|
|
173
247
|
}
|
|
174
248
|
/**
|
|
175
249
|
* A product's searches (AGL-3676), most specific first: each of its subjects
|
|
176
250
|
* — its name's noun phrase, then its photo's — with the shop's category
|
|
177
|
-
* where the phrase does not already
|
|
178
|
-
* category alone, a plain photo of what the shop sells. Every
|
|
179
|
-
* hit naming the category
|
|
180
|
-
*
|
|
181
|
-
*
|
|
251
|
+
* where the phrase does not already name the shop's world ("candle gift
|
|
252
|
+
* box"), then the category alone, a plain photo of what the shop sells. Every
|
|
253
|
+
* one must find a hit naming the category — or the product's own word of the
|
|
254
|
+
* shop's world, "lotion" in a soap shop — so no product is ever filled with a
|
|
255
|
+
* lifestyle shot of something else: the beta.237 Willow Wick start put a
|
|
256
|
+
* laptop and roses under "Candle Wick Trimmer" and an antique tea set under
|
|
257
|
+
* "Candle Gift Set".
|
|
258
|
+
*
|
|
259
|
+
* And each must find a hit naming the product's own head noun among its
|
|
260
|
+
* first tags (AGL-3660): the beta.239 Ember & Oak start put a bed with a book,
|
|
261
|
+
* a mug and a camera (its candle a last tag) under "Hand-Poured Soy Candle".
|
|
262
|
+
* An object whose own word is not enough ({@link OBJECT_KIN}) is searched by
|
|
263
|
+
* its kin instead of the category alone, and named by them: a single taper
|
|
264
|
+
* candle filled "Wax Melt Gift Set".
|
|
265
|
+
*
|
|
266
|
+
* Then, before a product is ever given a photo of something else, it falls
|
|
267
|
+
* back to its own craft (AGL-3660). The Saltmarsh Soap Co start (2026-10-10)
|
|
268
|
+
* found no hit naming "bar" or "box" among the first tags, and four of its
|
|
269
|
+
* five products took the starter photos — a laptop and roses, a living room
|
|
270
|
+
* of plants. So, in order:
|
|
271
|
+
*
|
|
272
|
+
* 1. The same searches again, the head noun named anywhere among the tags
|
|
273
|
+
* (no extra search: the page remembers its answers), and a gift's own
|
|
274
|
+
* wrapper with the craft ("soap gift box").
|
|
275
|
+
* 2. The site's broad searches ("handmade soap", "natural skincare"), any hit
|
|
276
|
+
* naming the craft itself ({@link AiStockSiteTerms.core}).
|
|
277
|
+
*
|
|
278
|
+
* The source then reuses an on-topic photo another slot of the job already
|
|
279
|
+
* shows before it gives up ({@link aiLayoutStockPhotoSource}).
|
|
182
280
|
*/
|
|
183
|
-
export declare function aiStockProductSearches(slot: Pick<AiLayoutPictureSlot, 'aspect' | 'product'>, craft: string): AiStockSearch[];
|
|
281
|
+
export declare function aiStockProductSearches(slot: Pick<AiLayoutPictureSlot, 'aspect' | 'product'>, craft: string, domain?: readonly string[], fallback?: AiStockProductFallback): AiStockSearch[];
|
|
184
282
|
/** The searches a slot tries, in order, most specific first, each distinct. */
|
|
185
|
-
export declare function aiStockSearchesFor(slot: Pick<AiLayoutPictureSlot, 'role' | 'alt' | 'aspect'>, terms: Pick<AiStockSiteTerms, 'business' | 'craft' | 'domain' | 'broad'
|
|
283
|
+
export declare function aiStockSearchesFor(slot: Pick<AiLayoutPictureSlot, 'role' | 'alt' | 'aspect'>, terms: Pick<AiStockSiteTerms, 'business' | 'craft' | 'domain' | 'broad'> & Partial<Pick<AiStockSiteTerms, 'making' | 'core'>>, subject: string, part?: AiStockSlotPart): AiStockSearch[];
|
|
186
284
|
/** What a hit is judged by. */
|
|
187
285
|
export interface AiStockJudgement {
|
|
188
286
|
/** The words the picture is of; empty for a picture of the site's world. */
|
|
@@ -196,10 +294,33 @@ export interface AiStockJudgement {
|
|
|
196
294
|
strict?: boolean;
|
|
197
295
|
/** A word every hit must name: a product's photo names the shop's category (AGL-3676). */
|
|
198
296
|
requires?: string;
|
|
297
|
+
/** Words of which a hit must name one, `requires` among them ({@link AiStockSearch.category}). */
|
|
298
|
+
category?: readonly string[];
|
|
299
|
+
/** The thing a hit must name, where it is not the subject's own (AGL-3660). */
|
|
300
|
+
object?: string;
|
|
301
|
+
/** Phrases naming the object where its word is not enough; one of them is then required, and stands for the category. */
|
|
302
|
+
kin?: readonly string[];
|
|
303
|
+
/** Whether the object must be among the hit's first tags (AGL-3660). */
|
|
304
|
+
lead?: boolean;
|
|
305
|
+
/** Whether the hit's first tags must name the site's world ({@link AiStockSearch.leadWorld}). */
|
|
306
|
+
leadWorld?: boolean;
|
|
307
|
+
/** The stems of the site's business type or brief, which let a sensitive topic in ({@link AiStockSiteTerms.named}). */
|
|
308
|
+
named?: readonly string[];
|
|
309
|
+
/** Rival crafts: a hit naming one is rejected unless it names the site's core words ({@link AiStockSiteTerms.rivals}). */
|
|
310
|
+
rivals?: ReadonlyArray<{
|
|
311
|
+
words: readonly string[];
|
|
312
|
+
core: readonly string[];
|
|
313
|
+
}>;
|
|
199
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Whether a hit carries a topic its site never named ({@link SENSITIVE_TOPICS}):
|
|
317
|
+
* a cross, a grave, a flag, a gun, a glass of wine, a child, a mannequin.
|
|
318
|
+
*/
|
|
319
|
+
export declare function aiStockSensitive(photo: Pick<StockPhoto, 'tags' | 'alt' | 'pageUrl'>, named?: readonly string[]): boolean;
|
|
200
320
|
/**
|
|
201
321
|
* How well a hit shows what a slot needs (AGL-3660), or 0 when it must not
|
|
202
|
-
* fill it: a mockup or a blank never; a
|
|
322
|
+
* fill it: a mockup or a blank never; a topic the site never named never
|
|
323
|
+
* ({@link aiStockSensitive}); a picture of a thing only when the hit
|
|
203
324
|
* names its object AND either the site's world or the subject's own
|
|
204
325
|
* two-word phrase ("bud vase", "wick trimmer"); anything else only when it
|
|
205
326
|
* names the site's world (when the site names one). Among the accepted, the
|