@aglyn/plugins-ai 1.0.0-beta.253 → 1.0.0-beta.254
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-site-start-card.component.js +16 -8
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.js +13 -7
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +32 -16
- package/src/lib/jobs/ai-job-site-content.js +43 -20
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +3 -0
- package/src/lib/jobs/ai-job-site-step.js +23 -11
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/model/ai-plan-capabilities.d.ts +5 -0
- package/src/lib/model/ai-plan-capabilities.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +32 -12
- package/src/lib/model/ai-site-job.js +35 -20
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.d.ts +5 -5
- package/src/lib/model/ai-site-kinds.js +5 -5
- package/src/lib/model/ai-site-kinds.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.js +3 -1
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
|
@@ -85,6 +85,11 @@ export interface AiPlanCapabilities {
|
|
|
85
85
|
* SITE plan's worst case leaves room for. Absent for every other job.
|
|
86
86
|
*/
|
|
87
87
|
freeSitePages?: number;
|
|
88
|
+
/**
|
|
89
|
+
* The first posts a Free blog's site scaffold writes beside its pages
|
|
90
|
+
* (AGL-3798), which the Free wall counts before its sections. Absent is none.
|
|
91
|
+
*/
|
|
92
|
+
freeSitePosts?: number;
|
|
88
93
|
/**
|
|
89
94
|
* Whether this job's pages are written in the compact layout language
|
|
90
95
|
* (AGL-3660), whose compiler draws a section's repeated items itself: as
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-plan-capabilities.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AI_BUILD_PLAN_CREATE_KINDS,\n AI_BUILD_PLAN_CREATION_NOUNS,\n type AiBuildPlan,\n type AiBuildPlanCreateKind,\n} from './ai-build-plan'\n\n/**\n * What a plan may create on one site (AGL-3030): the workspace's plan and the\n * site's counts against its limits, narrowed to what the job itself builds.\n *\n * A plan that names a creation nothing can make is a dead end the member pays\n * for twice: once for the plan, and again for the plan they describe after\n * being told to make the creation by hand — which, on a workspace whose plan\n * does not include it, they cannot. So the plan step is TOLD what may be\n * created before it answers, and the plan rules refuse a creation outside it\n * with the one re-ask every other rule gets.\n *\n * ── Where the doctrine builds inline ─────────────────────────────────────\n *\n * The building doctrine makes a repeat a reusable component and a form a\n * saved form placed by id. A workspace whose plan keeps no reusable\n * components draws a list's repeated items in one section instead\n * (AGL-3071); `reusableComponents` is the one flag every validator reads for\n * that.\n *\n * A form is never drawn inline (AGL-3596): it is a saved form, counted\n * against the site's form allowance like one a member makes. Where the site\n * has none to place and may create none, the page places no form at all.\n *\n * ── Kept out of the cached prefix ────────────────────────────────────────\n *\n * The capabilities are per workspace and per site, so they ride the plan's\n * USER turn, never a system block: the doctrine's cached prefix stays one\n * entry for the platform. The doctrine states the rule once — create only\n * what the request says may be created, and build inline where it says the\n * workspace keeps no reusable components — and the request states the facts.\n *\n * This module imports nothing at runtime beyond the plan's own model, so the\n * validators, the plan step, the doors and their specs read one vocabulary.\n * What a workspace's plan and counts come to is read by\n * `readAiPlanCapabilities` in `jobs/ai-job-drafts.ts`, beside the band\n * arithmetic the create route and the draft writer share.\n */\n\n/** One creation kind, as a job on this site may make it or not. */\nexport interface AiPlanCreation {\n allowed: boolean\n /** How many more the site may hold; `null` when the plan counts none. */\n left: number | null\n /**\n * Why not, as a clause a sentence completes: \"this workspace's plan does\n * not include reusable components\". `null` when allowed.\n */\n reason: string | null\n}\n\nexport interface AiPlanCapabilities {\n /**\n * Whether the workspace keeps reusable components. Where it does not, a\n * list's repeated items are drawn in one section. Forms are not read off\n * it: whether one may be created is `create.form` (AGL-3596).\n */\n reusableComponents: boolean\n /** Every creation kind a plan may name, and whether this job may make one here. */\n create: Readonly<Record<AiBuildPlanCreateKind, AiPlanCreation>>\n /**\n * Whether the workspace spends the Free taste, whose monthly credits are a\n * wall (AGL-2925): a plan is then held to the sections the wall's worst\n * case pays for (AGL-3070). Absent is `false`.\n */\n freeTaste?: boolean\n /**\n * A Free workspace's site scaffold (AGL-3594): the most pages its plan may\n * build, which the plan step sets for a `site` job on the Free taste. The\n * Free wall then holds the plan to that many pages and to the sections a\n * SITE plan's worst case leaves room for. Absent for every other job.\n */\n freeSitePages?: number\n /**\n * Whether this job's pages are written in the compact layout language\n * (AGL-3660), whose compiler draws a section's repeated items itself: as\n * instances of a component the plan places there, or, with none, in their\n * compact form, a title over its text, which is smaller than a block rule 1\n * calls a repeat. A plan is then not asked for a component for a section's\n * repeated items (AGL-3616); a section two pages share still is one. The\n * plan step sets it for a `build` job. Absent is `false`.\n *\n * It is the plan half of one rule. The page half is the tree check's\n * `AiDoctrineTreeContext.repeatsCompiled`, which every page the layout\n * language compiles is checked with: where the site's design draws a\n * section's items as picture cards — larger than the compact form, and so\n * a block rule 1 would count — those cards are the compiler's, not a block\n * typed out twice, and rule 1 asks no component of them (AGL-3660).\n */\n repeatsCompiled?: boolean\n /**\n * The most datasets this job's plan keeps (AGL-3616): a site start builds\n * at most a few. A plan naming more is settled in code before the rules\n * read it (`aiSettlePlanDatasets`), never re-asked. Absent is no cap beyond\n * `create.dataset.left`.\n */\n datasetsMax?: number\n /**\n * The fewest pieces a section that shows the work is planned with\n * (AGL-3754): a site start's `AI_SITE_GALLERY_MIN_ITEMS`. A plan naming\n * fewer is raised to it in code before the rules read it\n * (`aiSettlePlanGalleries`), never re-asked. Absent leaves every count as\n * planned.\n */\n galleryMinItems?: number\n}\n\n/** What a job of one kind builds from its own plan, where it builds only some creations. */\nexport interface AiPlanJobScope {\n /** The job as a refusal names it: \"a page job\". */\n noun: string\n /** The creation kinds the job builds itself; any other is refused before the plan is kept. */\n creates: readonly AiBuildPlanCreateKind[]\n}\n\nconst ALLOWED: AiPlanCreation = { allowed: true, left: null, reason: null }\n\n/**\n * Capabilities that restrict nothing: what a plan is held to when no\n * workspace was read — an org-level job, an eval, a spec about something\n * else. The doctrine applies whole.\n */\nexport function aiUnrestrictedPlanCapabilities(): AiPlanCapabilities {\n return {\n reusableComponents: true,\n create: Object.fromEntries(\n AI_BUILD_PLAN_CREATE_KINDS.map((kind) => [kind, ALLOWED]),\n ) as Record<AiBuildPlanCreateKind, AiPlanCreation>,\n }\n}\n\n/**\n * The workspace's capabilities narrowed to what a job of one kind builds.\n * A creation the workspace itself cannot make keeps the workspace's reason,\n * which is the one a member can act on; one the job does not build says so.\n */\nexport function aiPlanCapabilitiesForJob(\n capabilities: AiPlanCapabilities,\n scope: AiPlanJobScope | null | undefined,\n): AiPlanCapabilities {\n if (!scope) return capabilities\n const create = { ...capabilities.create }\n for (const kind of AI_BUILD_PLAN_CREATE_KINDS) {\n if (!create[kind].allowed || scope.creates.includes(kind)) continue\n create[kind] = { allowed: false, left: null, reason: `${scope.noun} does not build one` }\n }\n return { ...capabilities, create }\n}\n\n/** \"a layout\", \"an email design\": a creation kind with its article. */\nexport function aiCreationNoun(kind: AiBuildPlanCreateKind): string {\n const noun = AI_BUILD_PLAN_CREATION_NOUNS[kind].noun\n return `${/^[aeiou]/.test(noun) ? 'an' : 'a'} ${noun}`\n}\n\n/**\n * The sentence a request states when the workspace keeps no reusable\n * components. It says a list's repeated items go in one section (AGL-3071):\n * \"a repeated item in its own section\" reads just as well as a section for\n * each item, which is how a live About page's four practice areas came to be\n * planned as four sections of one item.\n */\nexport const AI_PLAN_INLINE_SENTENCE =\n \"This workspace keeps no reusable components: draw a list's repeated items in one section.\"\n\n/**\n * The sentence a build's request states (AGL-3616): its pages are written in\n * the layout language, which draws a section's repeated items itself, so the\n * plan counts them in the section's `items` and needs no component for them.\n */\nexport const AI_PLAN_COMPILED_REPEATS_SENTENCE =\n \"A section's repeated items need no component here: count them in its items and the page draws them. A section two pages share is still one component.\"\n\n/**\n * The sentence a request states when the site has no saved form and this job\n * may create none (AGL-3596): the plan places no form, rather than one drawn\n * on the page with no saved form behind it.\n */\nexport const AI_PLAN_NO_FORM_SENTENCE =\n 'This site has no saved form and may create none: plan no form, and no section that collects answers.'\n\n/**\n * What the plan step's user turn says the job may create here: one line a\n * kind, and the inline sentence where the workspace keeps no reusable\n * components. Per workspace and per site, so it is never a system block.\n */\nexport function aiPlanCapabilityLines(capabilities: AiPlanCapabilities): string[] {\n const lines = ['What this job may create on this site:']\n for (const kind of AI_BUILD_PLAN_CREATE_KINDS) {\n const entry = capabilities.create[kind]\n const noun = AI_BUILD_PLAN_CREATION_NOUNS[kind].noun\n if (entry.allowed) {\n lines.push(`- ${noun}: yes${entry.left === null ? '' : `, ${entry.left} more`}`)\n } else {\n lines.push(`- ${noun}: no, because ${entry.reason ?? 'it cannot be made here'}`)\n }\n }\n if (!capabilities.reusableComponents) lines.push(AI_PLAN_INLINE_SENTENCE)\n return lines\n}\n\n/**\n * Whether a plan on this site can place a saved form at all (AGL-3596): the\n * site has one, or the job may create one. Absent capabilities restrict\n * nothing, as everywhere else.\n */\nexport function aiPlanCanPlaceForm(\n inventory: { forms: readonly unknown[] } | null,\n capabilities: Pick<AiPlanCapabilities, 'create'> | null,\n): boolean {\n if (inventory?.forms.length) return true\n return capabilities?.create.form.allowed !== false\n}\n\n/** What to do instead of a creation the job may not make, for the re-ask and the review. */\nfunction insteadOf(kind: AiBuildPlanCreateKind, capabilities: AiPlanCapabilities): string {\n switch (kind) {\n case 'component':\n return capabilities.reusableComponents\n ? 'Place a component the site already has.'\n : \"Draw a list's repeated items in one section instead.\"\n case 'form':\n return 'Place a form the site already has, or leave the form off the page.'\n case 'layout':\n return 'Put the page in a layout the site already has.'\n case 'template':\n return 'Build the page without a template.'\n case 'theme-change':\n return \"Use the colors the theme already has.\"\n case 'dataset':\n return 'Bind a dataset or collection the site already has, or keep the list short enough to write out.'\n case 'email':\n return 'Leave the email out of this job.'\n }\n}\n\n/** One creation a plan names that this job may not make here. */\nexport interface AiPlanUncreatable {\n kind: AiBuildPlanCreateKind\n name: string\n /** The plan path of the creation: `create[2]`. */\n path: string\n /** Why not, as a clause: \"this site has room for 1 more\". */\n reason: string\n /** One customer-safe sentence: what was refused, why, and what to do instead. */\n message: string\n}\n\n/**\n * Why the nth creation of a kind may not be made here, as a clause; `null`\n * when it may. A kind the site may make only so many more of refuses the ones\n * past that count, not the first of them.\n */\nfunction refusedReason(capability: AiPlanCreation, nth: number): string | null {\n const overCount = capability.allowed && capability.left !== null && nth > capability.left\n if (capability.allowed && !overCount) return null\n return overCount\n ? `this site has room for ${capability.left} more`\n : (capability.reason ?? 'it cannot be made here')\n}\n\n/** The creations a plan names that the capabilities refuse, in plan order. */\nexport function aiPlanUncreatable(\n plan: Pick<AiBuildPlan, 'create'>,\n capabilities: AiPlanCapabilities,\n): AiPlanUncreatable[] {\n const refused: AiPlanUncreatable[] = []\n const counted = new Map<AiBuildPlanCreateKind, number>()\n plan.create.forEach((entry, index) => {\n const nth = (counted.get(entry.kind) ?? 0) + 1\n counted.set(entry.kind, nth)\n const reason = refusedReason(capabilities.create[entry.kind], nth)\n if (reason === null) return\n refused.push({\n kind: entry.kind,\n name: entry.name,\n path: `create[${index}]`,\n reason,\n message: `The plan creates ${aiCreationNoun(entry.kind)} named \"${entry.name}\", and ${reason}. ${insteadOf(entry.kind, capabilities)}`,\n })\n })\n return refused\n}\n\n/**\n * Why the plan could not add one more creation of a kind here, and what to\n * do instead; `null` when it could (AGL-3040). Counted past the creations of\n * that kind the plan already names, as `aiPlanUncreatable` counts them, so a\n * plan told to declare a creation is never told to declare one the next\n * answer is refused for.\n */\nexport function aiPlanUncreatableKind(\n plan: Pick<AiBuildPlan, 'create'>,\n kind: AiBuildPlanCreateKind,\n capabilities: AiPlanCapabilities,\n): { reason: string; instead: string } | null {\n const planned = plan.create.filter((entry) => entry.kind === kind).length\n const reason = refusedReason(capabilities.create[kind], planned + 1)\n return reason === null ? null : { reason, instead: insteadOf(kind, capabilities) }\n}\n"],"names":["AI_BUILD_PLAN_CREATE_KINDS","AI_BUILD_PLAN_CREATION_NOUNS","ALLOWED","allowed","left","reason","aiUnrestrictedPlanCapabilities","reusableComponents","create","Object","fromEntries","map","kind","aiPlanCapabilitiesForJob","capabilities","scope","creates","includes","noun","aiCreationNoun","test","AI_PLAN_INLINE_SENTENCE","AI_PLAN_COMPILED_REPEATS_SENTENCE","AI_PLAN_NO_FORM_SENTENCE","aiPlanCapabilityLines","lines","entry","push","aiPlanCanPlaceForm","inventory","forms","length","form","insteadOf","refusedReason","capability","nth","overCount","aiPlanUncreatable","plan","refused","counted","Map","forEach","index","get","set","name","path","message","aiPlanUncreatableKind","planned","filter","instead"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/model/ai-plan-capabilities.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n AI_BUILD_PLAN_CREATE_KINDS,\n AI_BUILD_PLAN_CREATION_NOUNS,\n type AiBuildPlan,\n type AiBuildPlanCreateKind,\n} from './ai-build-plan'\n\n/**\n * What a plan may create on one site (AGL-3030): the workspace's plan and the\n * site's counts against its limits, narrowed to what the job itself builds.\n *\n * A plan that names a creation nothing can make is a dead end the member pays\n * for twice: once for the plan, and again for the plan they describe after\n * being told to make the creation by hand — which, on a workspace whose plan\n * does not include it, they cannot. So the plan step is TOLD what may be\n * created before it answers, and the plan rules refuse a creation outside it\n * with the one re-ask every other rule gets.\n *\n * ── Where the doctrine builds inline ─────────────────────────────────────\n *\n * The building doctrine makes a repeat a reusable component and a form a\n * saved form placed by id. A workspace whose plan keeps no reusable\n * components draws a list's repeated items in one section instead\n * (AGL-3071); `reusableComponents` is the one flag every validator reads for\n * that.\n *\n * A form is never drawn inline (AGL-3596): it is a saved form, counted\n * against the site's form allowance like one a member makes. Where the site\n * has none to place and may create none, the page places no form at all.\n *\n * ── Kept out of the cached prefix ────────────────────────────────────────\n *\n * The capabilities are per workspace and per site, so they ride the plan's\n * USER turn, never a system block: the doctrine's cached prefix stays one\n * entry for the platform. The doctrine states the rule once — create only\n * what the request says may be created, and build inline where it says the\n * workspace keeps no reusable components — and the request states the facts.\n *\n * This module imports nothing at runtime beyond the plan's own model, so the\n * validators, the plan step, the doors and their specs read one vocabulary.\n * What a workspace's plan and counts come to is read by\n * `readAiPlanCapabilities` in `jobs/ai-job-drafts.ts`, beside the band\n * arithmetic the create route and the draft writer share.\n */\n\n/** One creation kind, as a job on this site may make it or not. */\nexport interface AiPlanCreation {\n allowed: boolean\n /** How many more the site may hold; `null` when the plan counts none. */\n left: number | null\n /**\n * Why not, as a clause a sentence completes: \"this workspace's plan does\n * not include reusable components\". `null` when allowed.\n */\n reason: string | null\n}\n\nexport interface AiPlanCapabilities {\n /**\n * Whether the workspace keeps reusable components. Where it does not, a\n * list's repeated items are drawn in one section. Forms are not read off\n * it: whether one may be created is `create.form` (AGL-3596).\n */\n reusableComponents: boolean\n /** Every creation kind a plan may name, and whether this job may make one here. */\n create: Readonly<Record<AiBuildPlanCreateKind, AiPlanCreation>>\n /**\n * Whether the workspace spends the Free taste, whose monthly credits are a\n * wall (AGL-2925): a plan is then held to the sections the wall's worst\n * case pays for (AGL-3070). Absent is `false`.\n */\n freeTaste?: boolean\n /**\n * A Free workspace's site scaffold (AGL-3594): the most pages its plan may\n * build, which the plan step sets for a `site` job on the Free taste. The\n * Free wall then holds the plan to that many pages and to the sections a\n * SITE plan's worst case leaves room for. Absent for every other job.\n */\n freeSitePages?: number\n /**\n * The first posts a Free blog's site scaffold writes beside its pages\n * (AGL-3798), which the Free wall counts before its sections. Absent is none.\n */\n freeSitePosts?: number\n /**\n * Whether this job's pages are written in the compact layout language\n * (AGL-3660), whose compiler draws a section's repeated items itself: as\n * instances of a component the plan places there, or, with none, in their\n * compact form, a title over its text, which is smaller than a block rule 1\n * calls a repeat. A plan is then not asked for a component for a section's\n * repeated items (AGL-3616); a section two pages share still is one. The\n * plan step sets it for a `build` job. Absent is `false`.\n *\n * It is the plan half of one rule. The page half is the tree check's\n * `AiDoctrineTreeContext.repeatsCompiled`, which every page the layout\n * language compiles is checked with: where the site's design draws a\n * section's items as picture cards — larger than the compact form, and so\n * a block rule 1 would count — those cards are the compiler's, not a block\n * typed out twice, and rule 1 asks no component of them (AGL-3660).\n */\n repeatsCompiled?: boolean\n /**\n * The most datasets this job's plan keeps (AGL-3616): a site start builds\n * at most a few. A plan naming more is settled in code before the rules\n * read it (`aiSettlePlanDatasets`), never re-asked. Absent is no cap beyond\n * `create.dataset.left`.\n */\n datasetsMax?: number\n /**\n * The fewest pieces a section that shows the work is planned with\n * (AGL-3754): a site start's `AI_SITE_GALLERY_MIN_ITEMS`. A plan naming\n * fewer is raised to it in code before the rules read it\n * (`aiSettlePlanGalleries`), never re-asked. Absent leaves every count as\n * planned.\n */\n galleryMinItems?: number\n}\n\n/** What a job of one kind builds from its own plan, where it builds only some creations. */\nexport interface AiPlanJobScope {\n /** The job as a refusal names it: \"a page job\". */\n noun: string\n /** The creation kinds the job builds itself; any other is refused before the plan is kept. */\n creates: readonly AiBuildPlanCreateKind[]\n}\n\nconst ALLOWED: AiPlanCreation = { allowed: true, left: null, reason: null }\n\n/**\n * Capabilities that restrict nothing: what a plan is held to when no\n * workspace was read — an org-level job, an eval, a spec about something\n * else. The doctrine applies whole.\n */\nexport function aiUnrestrictedPlanCapabilities(): AiPlanCapabilities {\n return {\n reusableComponents: true,\n create: Object.fromEntries(\n AI_BUILD_PLAN_CREATE_KINDS.map((kind) => [kind, ALLOWED]),\n ) as Record<AiBuildPlanCreateKind, AiPlanCreation>,\n }\n}\n\n/**\n * The workspace's capabilities narrowed to what a job of one kind builds.\n * A creation the workspace itself cannot make keeps the workspace's reason,\n * which is the one a member can act on; one the job does not build says so.\n */\nexport function aiPlanCapabilitiesForJob(\n capabilities: AiPlanCapabilities,\n scope: AiPlanJobScope | null | undefined,\n): AiPlanCapabilities {\n if (!scope) return capabilities\n const create = { ...capabilities.create }\n for (const kind of AI_BUILD_PLAN_CREATE_KINDS) {\n if (!create[kind].allowed || scope.creates.includes(kind)) continue\n create[kind] = { allowed: false, left: null, reason: `${scope.noun} does not build one` }\n }\n return { ...capabilities, create }\n}\n\n/** \"a layout\", \"an email design\": a creation kind with its article. */\nexport function aiCreationNoun(kind: AiBuildPlanCreateKind): string {\n const noun = AI_BUILD_PLAN_CREATION_NOUNS[kind].noun\n return `${/^[aeiou]/.test(noun) ? 'an' : 'a'} ${noun}`\n}\n\n/**\n * The sentence a request states when the workspace keeps no reusable\n * components. It says a list's repeated items go in one section (AGL-3071):\n * \"a repeated item in its own section\" reads just as well as a section for\n * each item, which is how a live About page's four practice areas came to be\n * planned as four sections of one item.\n */\nexport const AI_PLAN_INLINE_SENTENCE =\n \"This workspace keeps no reusable components: draw a list's repeated items in one section.\"\n\n/**\n * The sentence a build's request states (AGL-3616): its pages are written in\n * the layout language, which draws a section's repeated items itself, so the\n * plan counts them in the section's `items` and needs no component for them.\n */\nexport const AI_PLAN_COMPILED_REPEATS_SENTENCE =\n \"A section's repeated items need no component here: count them in its items and the page draws them. A section two pages share is still one component.\"\n\n/**\n * The sentence a request states when the site has no saved form and this job\n * may create none (AGL-3596): the plan places no form, rather than one drawn\n * on the page with no saved form behind it.\n */\nexport const AI_PLAN_NO_FORM_SENTENCE =\n 'This site has no saved form and may create none: plan no form, and no section that collects answers.'\n\n/**\n * What the plan step's user turn says the job may create here: one line a\n * kind, and the inline sentence where the workspace keeps no reusable\n * components. Per workspace and per site, so it is never a system block.\n */\nexport function aiPlanCapabilityLines(capabilities: AiPlanCapabilities): string[] {\n const lines = ['What this job may create on this site:']\n for (const kind of AI_BUILD_PLAN_CREATE_KINDS) {\n const entry = capabilities.create[kind]\n const noun = AI_BUILD_PLAN_CREATION_NOUNS[kind].noun\n if (entry.allowed) {\n lines.push(`- ${noun}: yes${entry.left === null ? '' : `, ${entry.left} more`}`)\n } else {\n lines.push(`- ${noun}: no, because ${entry.reason ?? 'it cannot be made here'}`)\n }\n }\n if (!capabilities.reusableComponents) lines.push(AI_PLAN_INLINE_SENTENCE)\n return lines\n}\n\n/**\n * Whether a plan on this site can place a saved form at all (AGL-3596): the\n * site has one, or the job may create one. Absent capabilities restrict\n * nothing, as everywhere else.\n */\nexport function aiPlanCanPlaceForm(\n inventory: { forms: readonly unknown[] } | null,\n capabilities: Pick<AiPlanCapabilities, 'create'> | null,\n): boolean {\n if (inventory?.forms.length) return true\n return capabilities?.create.form.allowed !== false\n}\n\n/** What to do instead of a creation the job may not make, for the re-ask and the review. */\nfunction insteadOf(kind: AiBuildPlanCreateKind, capabilities: AiPlanCapabilities): string {\n switch (kind) {\n case 'component':\n return capabilities.reusableComponents\n ? 'Place a component the site already has.'\n : \"Draw a list's repeated items in one section instead.\"\n case 'form':\n return 'Place a form the site already has, or leave the form off the page.'\n case 'layout':\n return 'Put the page in a layout the site already has.'\n case 'template':\n return 'Build the page without a template.'\n case 'theme-change':\n return \"Use the colors the theme already has.\"\n case 'dataset':\n return 'Bind a dataset or collection the site already has, or keep the list short enough to write out.'\n case 'email':\n return 'Leave the email out of this job.'\n }\n}\n\n/** One creation a plan names that this job may not make here. */\nexport interface AiPlanUncreatable {\n kind: AiBuildPlanCreateKind\n name: string\n /** The plan path of the creation: `create[2]`. */\n path: string\n /** Why not, as a clause: \"this site has room for 1 more\". */\n reason: string\n /** One customer-safe sentence: what was refused, why, and what to do instead. */\n message: string\n}\n\n/**\n * Why the nth creation of a kind may not be made here, as a clause; `null`\n * when it may. A kind the site may make only so many more of refuses the ones\n * past that count, not the first of them.\n */\nfunction refusedReason(capability: AiPlanCreation, nth: number): string | null {\n const overCount = capability.allowed && capability.left !== null && nth > capability.left\n if (capability.allowed && !overCount) return null\n return overCount\n ? `this site has room for ${capability.left} more`\n : (capability.reason ?? 'it cannot be made here')\n}\n\n/** The creations a plan names that the capabilities refuse, in plan order. */\nexport function aiPlanUncreatable(\n plan: Pick<AiBuildPlan, 'create'>,\n capabilities: AiPlanCapabilities,\n): AiPlanUncreatable[] {\n const refused: AiPlanUncreatable[] = []\n const counted = new Map<AiBuildPlanCreateKind, number>()\n plan.create.forEach((entry, index) => {\n const nth = (counted.get(entry.kind) ?? 0) + 1\n counted.set(entry.kind, nth)\n const reason = refusedReason(capabilities.create[entry.kind], nth)\n if (reason === null) return\n refused.push({\n kind: entry.kind,\n name: entry.name,\n path: `create[${index}]`,\n reason,\n message: `The plan creates ${aiCreationNoun(entry.kind)} named \"${entry.name}\", and ${reason}. ${insteadOf(entry.kind, capabilities)}`,\n })\n })\n return refused\n}\n\n/**\n * Why the plan could not add one more creation of a kind here, and what to\n * do instead; `null` when it could (AGL-3040). Counted past the creations of\n * that kind the plan already names, as `aiPlanUncreatable` counts them, so a\n * plan told to declare a creation is never told to declare one the next\n * answer is refused for.\n */\nexport function aiPlanUncreatableKind(\n plan: Pick<AiBuildPlan, 'create'>,\n kind: AiBuildPlanCreateKind,\n capabilities: AiPlanCapabilities,\n): { reason: string; instead: string } | null {\n const planned = plan.create.filter((entry) => entry.kind === kind).length\n const reason = refusedReason(capabilities.create[kind], planned + 1)\n return reason === null ? null : { reason, instead: insteadOf(kind, capabilities) }\n}\n"],"names":["AI_BUILD_PLAN_CREATE_KINDS","AI_BUILD_PLAN_CREATION_NOUNS","ALLOWED","allowed","left","reason","aiUnrestrictedPlanCapabilities","reusableComponents","create","Object","fromEntries","map","kind","aiPlanCapabilitiesForJob","capabilities","scope","creates","includes","noun","aiCreationNoun","test","AI_PLAN_INLINE_SENTENCE","AI_PLAN_COMPILED_REPEATS_SENTENCE","AI_PLAN_NO_FORM_SENTENCE","aiPlanCapabilityLines","lines","entry","push","aiPlanCanPlaceForm","inventory","forms","length","form","insteadOf","refusedReason","capability","nth","overCount","aiPlanUncreatable","plan","refused","counted","Map","forEach","index","get","set","name","path","message","aiPlanUncreatableKind","planned","filter","instead"],"mappings":"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"}
|
|
@@ -111,6 +111,12 @@ export interface AiFreeSiteWorstCase {
|
|
|
111
111
|
retry: number;
|
|
112
112
|
/** A page's listing. */
|
|
113
113
|
listing: number;
|
|
114
|
+
/**
|
|
115
|
+
* A Free blog's one first post (`AI_SITE_FREE_POSTS`): its answer at the
|
|
116
|
+
* `copy.blog` ceiling writing its cached prefix, and its one re-ask reading
|
|
117
|
+
* it, on the fast tier a Free site's post is written on.
|
|
118
|
+
*/
|
|
119
|
+
post: number;
|
|
114
120
|
}
|
|
115
121
|
/**
|
|
116
122
|
* The Free site start's wall at its worst (AGL-3594, re-derived for the
|
|
@@ -134,7 +140,13 @@ export interface AiFreeSiteCreations {
|
|
|
134
140
|
pages: number;
|
|
135
141
|
/** The saved forms it makes; a guided start makes one where it places a form. */
|
|
136
142
|
forms: number;
|
|
143
|
+
/** The first posts it writes: a Free blog's one (`AI_SITE_FREE_POSTS`), else none. */
|
|
144
|
+
posts?: number;
|
|
137
145
|
}
|
|
146
|
+
/** How many first posts a Free blog start writes, on its provider's fast tier. */
|
|
147
|
+
export declare const AI_SITE_FREE_POSTS = 1;
|
|
148
|
+
/** The first posts a Free start of this kind writes: a blog's one, and none for any other kind. */
|
|
149
|
+
export declare function aiFreeSitePosts(siteKind: string | null | undefined): number;
|
|
138
150
|
/** A Free site start's credits at its worst for this many sections, its room for one retried page included. */
|
|
139
151
|
export declare function aiFreeSiteWorstCaseCredits(creations: AiFreeSiteCreations, sections: number, credits?: Readonly<AiFreeSiteWorstCase>): number;
|
|
140
152
|
/**
|
|
@@ -149,10 +161,10 @@ export declare function aiFreeSiteSectionsWithin(creations: AiFreeSiteCreations,
|
|
|
149
161
|
/**
|
|
150
162
|
* What a Free site start of this many pages can cost at its worst, in
|
|
151
163
|
* credits (AGL-3660): `aiFreeSiteWorstCaseCredits` for an empty site — the
|
|
152
|
-
* plan, its look, the layout and form a guided start builds,
|
|
153
|
-
* answer and listing, the sections its plan is held
|
|
154
|
-
* (`aiFreeSiteSectionsWithin` at the month's whole taste, which is what
|
|
155
|
-
* plan step allows) and the room for one retried page.
|
|
164
|
+
* plan, its look, the layout and form a guided start builds, a blog's one
|
|
165
|
+
* first post, each page's answer and listing, the sections its plan is held
|
|
166
|
+
* to (`aiFreeSiteSectionsWithin` at the month's whole taste, which is what
|
|
167
|
+
* the plan step allows) and the room for one retried page.
|
|
156
168
|
*
|
|
157
169
|
* The CEILING a start is quoted with ("up to 283"), no longer the figure it
|
|
158
170
|
* is admitted on (AGL-3722): that is the measured p90
|
|
@@ -160,7 +172,9 @@ export declare function aiFreeSiteSectionsWithin(creations: AiFreeSiteCreations,
|
|
|
160
172
|
* confirmed and pauses with Resume when its credits run out, instead of
|
|
161
173
|
* being held back from starting at all.
|
|
162
174
|
*/
|
|
163
|
-
export declare function aiFreeSiteCreditEstimate(pages: number,
|
|
175
|
+
export declare function aiFreeSiteCreditEstimate(pages: number, options?: {
|
|
176
|
+
siteKind?: string | null;
|
|
177
|
+
}, credits?: Readonly<AiFreeSiteWorstCase>): number;
|
|
164
178
|
/**
|
|
165
179
|
* What a Free workspace has left of its Free AI credits this month: the less
|
|
166
180
|
* of its own band's balance and its owner's, since the owner's allowance is
|
|
@@ -196,10 +210,13 @@ export declare function aiFreeCreditsResetLabel(resetsOn: string): string;
|
|
|
196
210
|
* look, the layout and form a guided start builds, and each page at the
|
|
197
211
|
* nominal section count, at what each MEASURED in production
|
|
198
212
|
* (`AI_MEASURED_UNIT_CREDITS`); the ceiling is the whole worst case the wall
|
|
199
|
-
* is proven with (`aiFreeSiteCreditEstimate`). Two pages: about
|
|
200
|
-
* (p90
|
|
213
|
+
* is proven with (`aiFreeSiteCreditEstimate`). Two pages: about 113
|
|
214
|
+
* (p90 203, up to 243). A blog's one first post has no measurement of its
|
|
215
|
+
* own yet, so it is counted at its worst case in all three.
|
|
201
216
|
*/
|
|
202
|
-
export declare function aiFreeSiteCreditRange(pages: number
|
|
217
|
+
export declare function aiFreeSiteCreditRange(pages: number, options?: {
|
|
218
|
+
siteKind?: string | null;
|
|
219
|
+
}): AiCreditRange;
|
|
203
220
|
/** The smaller first build a site start offers: its home page alone. */
|
|
204
221
|
export declare const AI_SITE_HOME_FIRST_LABEL = "Build the home page first";
|
|
205
222
|
/**
|
|
@@ -209,7 +226,9 @@ export declare const AI_SITE_HOME_FIRST_LABEL = "Build the home page first";
|
|
|
209
226
|
* refusal: the person may build what fits (the job pauses with Resume when
|
|
210
227
|
* the credits run out), build the home page first, or upgrade.
|
|
211
228
|
*/
|
|
212
|
-
export declare function aiFreeSitePrompt(credits: Pick<AiFreeCreditsLeft, 'left' | 'resetsOn'> | null | undefined, pages: number
|
|
229
|
+
export declare function aiFreeSitePrompt(credits: Pick<AiFreeCreditsLeft, 'left' | 'resetsOn'> | null | undefined, pages: number, options?: {
|
|
230
|
+
siteKind?: string | null;
|
|
231
|
+
}): AiCreditsPrompt | null;
|
|
213
232
|
/**
|
|
214
233
|
* Why a Free start is refused outright (AGL-3722): nothing at all is left,
|
|
215
234
|
* so there is nothing for "build what fits" to build.
|
|
@@ -232,7 +251,7 @@ export declare const AI_SITE_MAX_SECTIONS = 8;
|
|
|
232
251
|
*/
|
|
233
252
|
export declare const AI_SITE_HOME_MIN_SECTIONS = 5;
|
|
234
253
|
/**
|
|
235
|
-
* The blog a
|
|
254
|
+
* The blog a guided start writes its first posts into (AGL-3676), and
|
|
236
255
|
* the addresses it may answer at, in order of preference: the first one no
|
|
237
256
|
* planned page takes.
|
|
238
257
|
*/
|
|
@@ -599,12 +618,13 @@ export declare function aiSiteCreditEstimate(pages: number, options?: {
|
|
|
599
618
|
welcomeEmail?: boolean;
|
|
600
619
|
siteKind?: string | null;
|
|
601
620
|
}): number;
|
|
602
|
-
/** How many first posts a paid blog start writes, one pass each (`AI_SITE_POSTS`, held equal by `ai-site-job.spec.ts`)
|
|
621
|
+
/** How many first posts a paid blog start writes, one pass each (`AI_SITE_POSTS`, held equal by `ai-site-job.spec.ts`); a Free one writes `AI_SITE_FREE_POSTS`. */
|
|
603
622
|
export declare const AI_SITE_START_POSTS = 3;
|
|
604
623
|
/**
|
|
605
624
|
* The first content a paid start of this kind writes (AGL-3676): a store's
|
|
606
625
|
* first products, a blog's first posts, or nothing. `aiSiteContentPart`'s
|
|
607
|
-
* answer for a paid start, by the kind's id
|
|
626
|
+
* answer for a paid start, by the kind's id; a Free start writes a blog its
|
|
627
|
+
* one first post (`aiFreeSitePosts`) and a store nothing.
|
|
608
628
|
*/
|
|
609
629
|
export declare function aiSiteStartContent(siteKind: string | null | undefined): 'posts' | 'products' | null;
|
|
610
630
|
/**
|
|
@@ -95,7 +95,10 @@ import { AI_CREDIT_RANGE_ZERO, AI_MEASURED_PAID_SITE_CREDITS, AI_MEASURED_PASS_C
|
|
|
95
95
|
page: 43,
|
|
96
96
|
section: 4,
|
|
97
97
|
retry: 64,
|
|
98
|
-
listing: 1
|
|
98
|
+
listing: 1,
|
|
99
|
+
// On Claude Haiku 5.5 at the platform's markup; the same post on the
|
|
100
|
+
// balanced tier the `copy.blog` row routes to would be 68.
|
|
101
|
+
post: 4
|
|
99
102
|
};
|
|
100
103
|
/**
|
|
101
104
|
* The most sections a Free site plans across its pages (AGL-3660), whatever
|
|
@@ -103,9 +106,14 @@ import { AI_CREDIT_RANGE_ZERO, AI_MEASURED_PAID_SITE_CREDITS, AI_MEASURED_PASS_C
|
|
|
103
106
|
* figure, so the spend a site EXPECTS stays near what it measured; the wall
|
|
104
107
|
* proves it fits.
|
|
105
108
|
*/ export const AI_FREE_SITE_MAX_SECTIONS = 8;
|
|
109
|
+
/** How many first posts a Free blog start writes, on its provider's fast tier. */ export const AI_SITE_FREE_POSTS = 1;
|
|
110
|
+
/** The first posts a Free start of this kind writes: a blog's one, and none for any other kind. */ export function aiFreeSitePosts(siteKind) {
|
|
111
|
+
return aiSiteStartContent(siteKind) === 'posts' ? AI_SITE_FREE_POSTS : 0;
|
|
112
|
+
}
|
|
106
113
|
/** A Free site start's credits at its worst for this many sections, its room for one retried page included. */ export function aiFreeSiteWorstCaseCredits(creations, sections, credits = AI_FREE_SITE_WORST_CASE_CREDITS) {
|
|
114
|
+
var _creations_posts;
|
|
107
115
|
const pages = Math.max(1, Math.floor(creations.pages));
|
|
108
|
-
return credits.plan + credits.look + creations.layouts * credits.layout + creations.forms * credits.form + pages * (credits.page + credits.listing) + sections * credits.section + credits.retry;
|
|
116
|
+
return credits.plan + credits.look + creations.layouts * credits.layout + creations.forms * credits.form + pages * (credits.page + credits.listing) + sections * credits.section + credits.retry + Math.max(0, Math.floor((_creations_posts = creations.posts) != null ? _creations_posts : 0)) * credits.post;
|
|
109
117
|
}
|
|
110
118
|
/**
|
|
111
119
|
* The most sections a Free site's plan fits in the Free taste at its worst,
|
|
@@ -124,21 +132,22 @@ import { AI_CREDIT_RANGE_ZERO, AI_MEASURED_PAID_SITE_CREDITS, AI_MEASURED_PASS_C
|
|
|
124
132
|
/**
|
|
125
133
|
* What a Free site start of this many pages can cost at its worst, in
|
|
126
134
|
* credits (AGL-3660): `aiFreeSiteWorstCaseCredits` for an empty site — the
|
|
127
|
-
* plan, its look, the layout and form a guided start builds,
|
|
128
|
-
* answer and listing, the sections its plan is held
|
|
129
|
-
* (`aiFreeSiteSectionsWithin` at the month's whole taste, which is what
|
|
130
|
-
* plan step allows) and the room for one retried page.
|
|
135
|
+
* plan, its look, the layout and form a guided start builds, a blog's one
|
|
136
|
+
* first post, each page's answer and listing, the sections its plan is held
|
|
137
|
+
* to (`aiFreeSiteSectionsWithin` at the month's whole taste, which is what
|
|
138
|
+
* the plan step allows) and the room for one retried page.
|
|
131
139
|
*
|
|
132
140
|
* The CEILING a start is quoted with ("up to 283"), no longer the figure it
|
|
133
141
|
* is admitted on (AGL-3722): that is the measured p90
|
|
134
142
|
* (`aiFreeSiteCreditRange`), and a start past what is left is asked to be
|
|
135
143
|
* confirmed and pauses with Resume when its credits run out, instead of
|
|
136
144
|
* being held back from starting at all.
|
|
137
|
-
*/ export function aiFreeSiteCreditEstimate(pages, credits = AI_FREE_SITE_WORST_CASE_CREDITS) {
|
|
145
|
+
*/ export function aiFreeSiteCreditEstimate(pages, options = {}, credits = AI_FREE_SITE_WORST_CASE_CREDITS) {
|
|
138
146
|
const creations = {
|
|
139
147
|
layouts: 1,
|
|
140
148
|
forms: 1,
|
|
141
|
-
pages: Math.max(1, Math.floor(pages))
|
|
149
|
+
pages: Math.max(1, Math.floor(pages)),
|
|
150
|
+
posts: aiFreeSitePosts(options.siteKind)
|
|
142
151
|
};
|
|
143
152
|
const sections = aiFreeSiteSectionsWithin(creations, FREE_AI_TASTE_CREDITS_PER_MONTH, credits);
|
|
144
153
|
return aiFreeSiteWorstCaseCredits(creations, sections, credits);
|
|
@@ -161,13 +170,18 @@ import { AI_CREDIT_RANGE_ZERO, AI_MEASURED_PAID_SITE_CREDITS, AI_MEASURED_PASS_C
|
|
|
161
170
|
* look, the layout and form a guided start builds, and each page at the
|
|
162
171
|
* nominal section count, at what each MEASURED in production
|
|
163
172
|
* (`AI_MEASURED_UNIT_CREDITS`); the ceiling is the whole worst case the wall
|
|
164
|
-
* is proven with (`aiFreeSiteCreditEstimate`). Two pages: about
|
|
165
|
-
* (p90
|
|
166
|
-
|
|
173
|
+
* is proven with (`aiFreeSiteCreditEstimate`). Two pages: about 113
|
|
174
|
+
* (p90 203, up to 243). A blog's one first post has no measurement of its
|
|
175
|
+
* own yet, so it is counted at its worst case in all three.
|
|
176
|
+
*/ export function aiFreeSiteCreditRange(pages, options = {}) {
|
|
167
177
|
const count = Math.max(1, Math.floor(pages));
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
178
|
+
const { likely, p90 } = aiSiteStartMeasured(count);
|
|
179
|
+
const posts = aiFreeSitePosts(options.siteKind) * AI_FREE_SITE_WORST_CASE_CREDITS.post;
|
|
180
|
+
return aiCreditRangeOrdered({
|
|
181
|
+
likely: likely + posts,
|
|
182
|
+
p90: p90 + posts,
|
|
183
|
+
ceiling: aiFreeSiteCreditEstimate(count, options)
|
|
184
|
+
});
|
|
171
185
|
}
|
|
172
186
|
/** A site start's measured likely and p90 before its plan: plan, look, layout, form and its pages at the nominal sections. */ function aiSiteStartMeasured(pages) {
|
|
173
187
|
const { plan, theme, layout, form } = AI_MEASURED_UNIT_CREDITS;
|
|
@@ -184,11 +198,11 @@ import { AI_CREDIT_RANGE_ZERO, AI_MEASURED_PAID_SITE_CREDITS, AI_MEASURED_PASS_C
|
|
|
184
198
|
* about what is left, since the reservation still decides each pass. Not a
|
|
185
199
|
* refusal: the person may build what fits (the job pauses with Resume when
|
|
186
200
|
* the credits run out), build the home page first, or upgrade.
|
|
187
|
-
*/ export function aiFreeSitePrompt(credits, pages) {
|
|
201
|
+
*/ export function aiFreeSitePrompt(credits, pages, options = {}) {
|
|
188
202
|
if (!credits || !Number.isFinite(credits.left)) return null;
|
|
189
203
|
const count = Math.max(1, Math.floor(pages));
|
|
190
|
-
const home = aiFreeSiteCreditRange(1);
|
|
191
|
-
return aiCreditsPromptFor(aiFreeSiteCreditRange(count), credits, count > 1 ? _extends({
|
|
204
|
+
const home = aiFreeSiteCreditRange(1, options);
|
|
205
|
+
return aiCreditsPromptFor(aiFreeSiteCreditRange(count, options), credits, count > 1 ? _extends({
|
|
192
206
|
label: AI_SITE_HOME_FIRST_LABEL
|
|
193
207
|
}, home) : null);
|
|
194
208
|
}
|
|
@@ -213,7 +227,7 @@ import { AI_CREDIT_RANGE_ZERO, AI_MEASURED_PAID_SITE_CREDITS, AI_MEASURED_PASS_C
|
|
|
213
227
|
* first answer whose home is under it (`aiSiteThinHomeViolations`).
|
|
214
228
|
*/ export const AI_SITE_HOME_MIN_SECTIONS = 5;
|
|
215
229
|
/**
|
|
216
|
-
* The blog a
|
|
230
|
+
* The blog a guided start writes its first posts into (AGL-3676), and
|
|
217
231
|
* the addresses it may answer at, in order of preference: the first one no
|
|
218
232
|
* planned page takes.
|
|
219
233
|
*/ export const AI_SITE_BLOG_NAME = 'Blog';
|
|
@@ -706,11 +720,12 @@ const ID_CHARS = /^[A-Za-z0-9_-]{1,64}$/;
|
|
|
706
720
|
const passes = 1 + screens * (AI_SITE_NOMINAL_SECTIONS + 1) + 2 + (options.welcomeEmail ? 1 : 0) + (content === 'products' ? 1 : content === 'posts' ? AI_SITE_START_POSTS : 0);
|
|
707
721
|
return passes * AI_SITE_PASS_CREDITS;
|
|
708
722
|
}
|
|
709
|
-
/** How many first posts a paid blog start writes, one pass each (`AI_SITE_POSTS`, held equal by `ai-site-job.spec.ts`)
|
|
723
|
+
/** How many first posts a paid blog start writes, one pass each (`AI_SITE_POSTS`, held equal by `ai-site-job.spec.ts`); a Free one writes `AI_SITE_FREE_POSTS`. */ export const AI_SITE_START_POSTS = 3;
|
|
710
724
|
/**
|
|
711
725
|
* The first content a paid start of this kind writes (AGL-3676): a store's
|
|
712
726
|
* first products, a blog's first posts, or nothing. `aiSiteContentPart`'s
|
|
713
|
-
* answer for a paid start, by the kind's id
|
|
727
|
+
* answer for a paid start, by the kind's id; a Free start writes a blog its
|
|
728
|
+
* one first post (`aiFreeSitePosts`) and a store nothing.
|
|
714
729
|
*/ export function aiSiteStartContent(siteKind) {
|
|
715
730
|
if (siteKind === 'store') return 'products';
|
|
716
731
|
if (siteKind === 'blog') return 'posts';
|