@aglyn/plugins-ai 1.0.0-beta.239 → 1.0.0-beta.241
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-job-requests.d.ts +5 -0
- package/src/lib/components/ai-job-requests.js +4 -2
- package/src/lib/components/ai-job-requests.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/components/assist-panel.component.js +14 -2
- package/src/lib/components/assist-panel.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 +11 -4
- 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 +26 -9
- 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 +53 -5
- 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 +51 -10
- 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-datasets.d.ts +89 -2
- package/src/lib/jobs/ai-job-site-datasets.js +239 -15
- 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 +69 -3
- package/src/lib/jobs/ai-layout-stock-photos.js +603 -42
- 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/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/layout-language/ai-layout-pictures.js +12 -4
- package/src/lib/layout-language/ai-layout-pictures.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/ai-doctrine-validators.d.ts +44 -0
- package/src/lib/runtime/ai-doctrine-validators.js +177 -0
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +3 -2
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- 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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-site-datasets.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 { checkDatasetQuota } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { pluginResourceDraftWriter } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport { isAiPlanNewRef, type AiBuildPlanCreate, type AiBuildPlanScreen } from '../model/ai-build-plan'\nimport type { AiJob, AiJobOutput, AiJobPlan } from '../model/ai-jobs.types'\nimport type { AiPlanCapabilities } from '../model/ai-plan-capabilities'\nimport { AI_SITE_DATASETS_MAX } from '../model/ai-site-job'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { aiModelForStep } from '../providers/routing'\nimport {\n AI_DATASET_MAX_TOKENS,\n AI_DATASET_STEP,\n aiDatasetDraftContent,\n generateAiDataset,\n} from '../runtime/ai-dataset-generation'\nimport {\n aiLayoutListingId,\n aiLayoutRecordsPlacements,\n type AiLayoutListing,\n type AiLayoutListingField,\n type AiLayoutListingScreen,\n} from '../layout-language/ai-layout-listings'\nimport type { AiJobAdmissionContext } from './ai-job-admission'\nimport { AI_DATASET_DRAFT_RESOURCE } from './ai-build-unit-outcome'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiGenerationSpent, 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 * A SITE START'S DATASETS (AGL-3616): \"Aglyn AI can also use/create datasets\n * too for whatever it needs\" (Zach, 2026-10-10).\n *\n * A site whose content is a list of like things — a restaurant's menu, a\n * studio's team, a contractor's services, a photographer's portfolio, an\n * events list, its questions and answers — keeps it as a DATASET, the way the\n * docs teach a person to: the owner edits one record and every page listing\n * it follows. On a workspace that may create datasets, the site plan creates\n * one for such content (`create`, kind `dataset`) and names it in the `uses`\n * of each section that lists it. The scaffold then builds each dataset as a\n * unit of its own, after the layout and before the form (which may write\n * its submissions to one) and the pages:\n *\n * - DESIGNED by the `submit_dataset` generation (`ai-dataset-generation.ts`):\n * the planned fields typed, at most a few more, and 3 to 12 records seeded\n * from the brief. Nothing anybody did not say: no review, testimonial,\n * rating, quote or person's name, no price the brief does not state.\n * - WRITTEN by the data plugin's `dataset` writer — this plugin never names\n * the datasets collection — with every rule the console's datasets route\n * has: the member's role and `data.manage`, `release_data_store`,\n * `dataStore` (Starter and up), `datasetsPerOrg` counted inside the create,\n * `recordsPerDataset` and the storage band.\n * - LISTED by the pages built after it: each section naming it repeats over\n * the dataset (`aiSiteDatasetListings`, a `records` listing the layout\n * compiler draws), and a page the plan made its record template binds its\n * fields by id and keeps the address field the writer added.\n *\n * Where datasets cannot be created — the Free taste, Starter's two used up,\n * the data store not released — the plan is told so and creates none, so its\n * sections write the items out. A dataset unit that fails or is refused\n * leaves the pages that list it built without it (they say so), and its\n * record page is not built.\n */\n\n/** The most datasets one site start creates. */\nexport { AI_SITE_DATASETS_MAX } from '../model/ai-site-job'\n\n/** The resource the data plugin's writer is registered under. */\nexport const AI_SITE_DATASET_RESOURCE = AI_DATASET_DRAFT_RESOURCE\n\n/** What the site's plugin list calls the plugin, for \"Turn on Data for this site\". */\nconst DATA_PLUGIN_LABEL = 'Data'\n\n/** The input a dataset unit's own job carries what it needs in. */\nexport const AI_SITE_DATASET_INPUT = 'siteDataset'\n\n/** What a dataset unit's job is told beside its creation. */\nexport interface AiSiteDatasetInput {\n /** The sections that list it, by page: \"Menu › Our dishes (12 items)\". */\n shownIn: string[]\n /** Whether a page of the plan is its record template. */\n recordPages: boolean\n /**\n * The form of the plan that writes its submissions to it (AGL-3616), by\n * name, with that form's planned fields; absent where no form does.\n */\n forForm?: { name: string; fields: string[] }\n}\n\n/** What a dataset row says when its design could not be written: our failure, refunded. */\nexport const AI_SITE_DATASET_NOT_WRITTEN_COPY = 'This dataset could not be added this time.'\n\n/** The note a written dataset carries on the job's page. */\nexport function aiSiteDatasetNote(records: number): string {\n return `${records} ${records === 1 ? 'record' : 'records'} to start, in Data. Edit them there and every page that lists them follows.`\n}\n\n/** The note a record template's row adds: the binding is the member's to save (rule 13). */\nexport const AI_SITE_RECORD_PAGE_NOTE =\n 'A page per record is drafted, not live: open Page Properties → Record pages to save it.'\n\n/** ASSUMED: what one dataset pass reads and writes beside its generation: the site, the writer's reads and its transaction. */\nexport const AI_SITE_DATASET_WRITES_MS = 2_000\n\n/** One dataset's pass: its generation and re-ask at its ceiling, and its writes. */\nexport const AI_SITE_DATASET_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS[AI_DATASET_STEP],\n maxTokens: AI_DATASET_MAX_TOKENS,\n lookups: 0,\n ownReadsMs: AI_SITE_DATASET_WRITES_MS,\n})\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** The plan's reference to a dataset as a section names it: `new:<name>`, compared lowercased. */\nfunction namesDataset(ref: string, name: string): boolean {\n return isAiPlanNewRef(ref) && ref.slice(ref.indexOf(':') + 1).trim().toLowerCase() === name.trim().toLowerCase()\n}\n\n/** The plan's datasets, in plan order, at most the most a start creates. */\nexport function aiSitePlanDatasets(plan: Pick<AiJobPlan, 'create'>): AiBuildPlanCreate[] {\n return plan.create.filter((entry) => entry.kind === 'dataset').slice(0, AI_SITE_DATASETS_MAX)\n}\n\n/** Where the plan lists a dataset, in words its design reads, whether a page is its record template, and the form that writes to it. */\nexport function aiSiteDatasetInputOf(\n dataset: Pick<AiBuildPlanCreate, 'name'>,\n screens: readonly AiBuildPlanScreen[],\n create: readonly AiBuildPlanCreate[] = [],\n): AiSiteDatasetInput {\n const shownIn: string[] = []\n for (const screen of screens) {\n for (const section of screen.sections) {\n if (!section.uses.some((ref) => namesDataset(ref, dataset.name))) continue\n shownIn.push(`${screen.title} › ${section.name}${section.items ? ` (${section.items} items)` : ''}`)\n }\n }\n const recordPages = screens.some((screen) => !!screen.record && namesDataset(screen.record.dataset, dataset.name))\n const form = create.find((entry) => entry.kind === 'form' && !!entry.writesTo && namesDataset(entry.writesTo, dataset.name))\n return {\n shownIn: shownIn.slice(0, 8),\n recordPages,\n ...(form ? { forForm: { name: form.name, fields: form.fields.slice(0, AI_SITE_FORM_DATASET_FIELDS_MAX) } } : {}),\n }\n}\n\n/** Whether a plan screen is the record template of a dataset the plan creates, and which. */\nexport function aiSiteRecordTemplateOf(screen: Pick<AiBuildPlanScreen, 'record'>): string | null {\n const ref = screen.record?.dataset\n return ref && isAiPlanNewRef(ref) ? ref.slice(ref.indexOf(':') + 1).trim() : null\n}\n\nfunction datasetInputOf(job: Pick<AiJob, 'inputs'>): AiSiteDatasetInput {\n const raw = (job.inputs?.[AI_SITE_DATASET_INPUT] ?? {}) as Partial<AiSiteDatasetInput>\n const form = raw.forForm && typeof raw.forForm === 'object' ? raw.forForm : null\n return {\n shownIn: Array.isArray(raw.shownIn) ? raw.shownIn.map(str).filter(Boolean).slice(0, 8) : [],\n recordPages: raw.recordPages === true,\n ...(form && str(form.name)\n ? {\n forForm: {\n name: str(form.name),\n fields: Array.isArray(form.fields) ? form.fields.map(str).filter(Boolean).slice(0, AI_SITE_FORM_DATASET_FIELDS_MAX) : [],\n },\n }\n : {}),\n }\n}\n\n/** The most fields a form's dataset is designed with: the data plugin writer's own ceiling is higher. */\nexport const AI_SITE_FORM_DATASET_FIELDS_MAX = 12\n\n/** The field types a planned `Name:type` may name, as the data plugin's writer takes them. */\nconst FORM_DATASET_FIELD_TYPES = ['text', 'number', 'integer', 'boolean', 'list'] as const\n\n/** A planned field as a person reads it: `fullName` and `full_name` are \"Full name\". */\nexport function aiSiteDatasetFieldName(planned: string): string {\n const words = planned\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_-]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n return words ? words.charAt(0).toUpperCase() + words.slice(1).toLowerCase() : ''\n}\n\n/**\n * A dataset a form writes to, as the data plugin's writer takes it\n * (AGL-3616): designed with no model, since its records are the\n * submissions. Its fields are its planned ones — or, where the plan gave it\n * none, the form's — each a person's words for it, text unless the plan\n * typed it as `Name:type`, never required, so a submission that leaves one\n * blank is still a record. It starts with no records. Pure.\n */\nexport function aiSiteFormDatasetContent(\n name: string,\n planned: readonly string[],\n form: { fields: readonly string[] },\n): Record<string, unknown> {\n const specs = (planned.length ? planned : form.fields).slice(0, AI_SITE_FORM_DATASET_FIELDS_MAX)\n const seen = new Set<string>()\n const fields: Array<{ name: string; type: string }> = []\n for (const spec of specs) {\n const at = spec.lastIndexOf(':')\n const typed = at > 0 ? spec.slice(at + 1).trim().toLowerCase() : ''\n const type = (FORM_DATASET_FIELD_TYPES as readonly string[]).includes(typed) ? typed : 'text'\n const fieldName = aiSiteDatasetFieldName(type === typed ? spec.slice(0, at) : spec).slice(0, 60)\n if (!fieldName || seen.has(fieldName.toLowerCase())) continue\n seen.add(fieldName.toLowerCase())\n fields.push({ name: fieldName, type })\n }\n return { name, fields, records: [] }\n}\n\n/** The note a form's dataset carries on the job's page. */\nexport function aiSiteFormDatasetNote(form: string): string {\n return `Empty to start: each submission of the form “${form}” is added here, in Data.`\n}\n\nconst limitReview = (message: string) => ({ reason: 'limit' as const, message, findings: [] })\n\nexport interface AiSiteDatasetRunnerDeps {\n generate?: typeof generateAiDataset\n writerFor?: typeof pluginResourceDraftWriter\n}\n\n/** The fields and record names a written dataset reports, as its output's proposal keeps them. */\ninterface AiSiteDatasetProposal {\n fields: AiLayoutListingField[]\n /** Each record's name: its first field's value. */\n recordNames: string[]\n addressField: string | null\n}\n\nfunction proposalOf(output: Pick<AiJobOutput, 'proposal'>): AiSiteDatasetProposal {\n const raw = (output.proposal ?? {}) as Partial<AiSiteDatasetProposal>\n return {\n fields: Array.isArray(raw.fields)\n ? raw.fields.filter((field) => field && typeof field.id === 'string' && field.id).map((field) => ({ id: field.id, name: str(field.name) || field.id, type: str(field.type) || 'text' }))\n : [],\n recordNames: Array.isArray(raw.recordNames) ? raw.recordNames.map(str).filter(Boolean).slice(0, 12) : [],\n addressField: str(raw.addressField) || null,\n }\n}\n\n/**\n * The dataset unit's runner, on the job the scaffold derived for it: the one\n * creation its plan carries, designed by the model and written by the data\n * plugin, under the creation's own id. A pass repeated after its write finds\n * the dataset the writer wrote and reports it again, spending nothing.\n */\nexport function createAiSiteDatasetRunner(deps: AiSiteDatasetRunnerDeps = {}): AiJobStepRunner {\n const generate = deps.generate ?? generateAiDataset\n const writerFor = deps.writerFor ?? pluginResourceDraftWriter\n return async (context): Promise<AiJobStepOutcome> => {\n const { job, firestore, signal } = context\n const model = context.modelFor?.(AI_DATASET_STEP) ?? aiModelForStep(AI_DATASET_STEP)\n const creation = (job.plan as AiJobPlan | null)?.create?.find((entry) => entry.kind === 'dataset')\n const keeper = writerFor(AI_SITE_DATASET_RESOURCE)\n if (!job.hostId || !creation) return { ...aiUnspentOutcome(model), failure: 'Open the site before adding its data.' }\n if (!keeper) return { ...aiUnspentOutcome(model), failure: 'Datasets are not available on this site.' }\n const host = await firestore.collection('hosts').doc(job.hostId).get()\n const hostSubdomain = str(host.get('subdomain')) || null\n const output = (id: string, name: string, proposal: AiSiteDatasetProposal, records: number): AiJobOutput => ({\n resource: 'draft',\n draftResource: AI_SITE_DATASET_RESOURCE,\n id,\n hostId: job.hostId ?? null,\n hostSubdomain,\n label: name,\n note: aiSiteDatasetNote(records),\n proposal: proposal as unknown as Record<string, unknown>,\n })\n const input = datasetInputOf(job)\n // Asked again under the same id: the dataset already written.\n const written = await keeper.writer.read({ hostId: job.hostId, id: job.$id })\n if (written) {\n const facts = written.facts as { fields?: AiLayoutListingField[]; addressField?: string | null }\n const again = output(written.id, written.name || creation.name, { fields: facts.fields ?? [], recordNames: [], addressField: facts.addressField ?? null }, 0)\n return aiUnspentOutcome(model, {\n outputs: [input.forForm ? { ...again, note: aiSiteFormDatasetNote(input.forForm.name) } : again],\n })\n }\n // A form's dataset (AGL-3616): its records are the submissions, so it is\n // designed from the plan with no model, and starts empty.\n if (input.forForm) {\n const content = aiSiteFormDatasetContent(creation.name, creation.fields, input.forForm)\n const checked = keeper.writer.check(content, { hostId: job.hostId })\n if (checked.ok === false) {\n console.error('ai site dataset: the dataset writer refused a form dataset we made', { orgId: job.orgId, jobId: job.$id, problems: checked.problems })\n return { ...aiUnspentOutcome(model), failure: AI_SITE_DATASET_NOT_WRITTEN_COPY }\n }\n const result = await keeper.writer.write({\n orgId: job.orgId,\n hostId: job.hostId,\n uid: job.createdBy,\n org: (context.org ?? null) as Readonly<Record<string, unknown>> | null,\n now: context.now,\n id: job.$id,\n name: creation.name,\n content,\n })\n if (result.ok === false) {\n if (result.status === 400) return { ...aiUnspentOutcome(model), failure: AI_SITE_DATASET_NOT_WRITTEN_COPY }\n return { ...aiUnspentOutcome(model), review: limitReview(result.error) }\n }\n const facts = result.facts as { fields?: AiLayoutListingField[]; addressField?: string | null }\n return aiUnspentOutcome(model, {\n outputs: [\n {\n ...output(result.id, result.name, { fields: facts.fields ?? [], recordNames: [], addressField: null }, 0),\n note: aiSiteFormDatasetNote(input.forForm.name),\n },\n ],\n })\n }\n const generation = await generate({\n brief: job.brief,\n merchantWords: job.brief,\n name: creation.name,\n why: creation.why,\n fields: creation.fields,\n shownIn: input.shownIn,\n recordPages: input.recordPages,\n model,\n maxTokens: AI_SITE_DATASET_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(generation)\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: generation.message }\n const content = aiDatasetDraftContent(creation.name, generation.value, { recordPages: input.recordPages })\n // The data plugin's own rules, before anything is written: content we\n // made that its model refuses is our failure, refunded.\n const checked = keeper.writer.check(content, { hostId: job.hostId })\n if (checked.ok === false) {\n console.error('ai site dataset: the dataset writer refused a dataset we made', { orgId: job.orgId, jobId: job.$id, problems: checked.problems })\n return { ...spent, failure: AI_SITE_DATASET_NOT_WRITTEN_COPY }\n }\n const result = await keeper.writer.write({\n orgId: job.orgId,\n hostId: job.hostId,\n uid: job.createdBy,\n org: (context.org ?? null) as Readonly<Record<string, unknown>> | null,\n now: context.now,\n id: job.$id,\n name: creation.name,\n content,\n })\n if (result.ok === false) {\n if (result.status === 400) return { ...spent, failure: AI_SITE_DATASET_NOT_WRITTEN_COPY }\n // The allowance, the role, the plan or the flag: the workspace's to change.\n return { ...spent, review: limitReview(result.error) }\n }\n const facts = result.facts as { fields?: AiLayoutListingField[]; records?: number; addressField?: string | null }\n const proposal: AiSiteDatasetProposal = {\n fields: facts.fields ?? [],\n recordNames: generation.value.records.map((values) => values[0] ?? '').filter(Boolean).slice(0, 12),\n addressField: facts.addressField ?? null,\n }\n return { ...spent, outputs: [output(result.id, result.name, proposal, facts.records ?? generation.value.records.length)] }\n }\n}\n\nexport const runAiSiteDatasetUnit = createAiSiteDatasetRunner()\n\n/**\n * Whether a dataset may be created for this member on this site, asked before\n * its first pass so a refusal spends nothing; `null` admits. The data plugin\n * must run here (on for the workspace and the site, past its release flag),\n * and its writer says whether this member may make one now: the role, the\n * plan, the flag, the allowance.\n */\nexport async function aiSiteDatasetRefusal(\n context: Pick<AiJobStepContext, 'firestore' | 'org' | 'now'> & { job: Pick<AiJob, 'orgId' | 'hostId' | 'createdBy'> },\n deps: { admission?: typeof aiPluginDraftAdmissionRefusal; writerFor?: (resource: string) => ReturnType<typeof pluginResourceDraftWriter> } = {},\n): Promise<string | null> {\n const { job } = context\n if (!job.hostId) return 'Open the site first.'\n const writerFor = deps.writerFor ?? pluginResourceDraftWriter\n const admission: AiJobAdmissionContext = {\n firestore: context.firestore,\n orgId: job.orgId,\n hostId: job.hostId,\n inputs: {},\n org: context.org ?? null,\n uid: job.createdBy,\n }\n const refused = await (deps.admission ?? aiPluginDraftAdmissionRefusal)(admission, {\n kind: 'site',\n drafts: [{ resource: AI_SITE_DATASET_RESOURCE, label: DATA_PLUGIN_LABEL }],\n writerFor: (resource) => writerFor(resource)?.writer ?? null,\n ownerFor: (resource) => writerFor(resource)?.pluginId ?? null,\n now: context.now,\n })\n return refused?.error ?? null\n}\n\n/**\n * A site plan's dataset capability on this workspace (AGL-3616): where the\n * workspace's plan includes datasets, whether the data plugin runs here and\n * this member may create one now, and how many a start may make. A clause the\n * plan's turn completes is the reason where it may not.\n */\nexport async function aiSitePlanDatasetCapability(\n capabilities: AiPlanCapabilities,\n context: Parameters<typeof aiSiteDatasetRefusal>[0],\n deps: Parameters<typeof aiSiteDatasetRefusal>[1] = {},\n): Promise<AiPlanCapabilities> {\n if (!capabilities.create.dataset.allowed) return capabilities\n const refusal = await aiSiteDatasetRefusal(context, deps).catch((error: unknown) => {\n console.error('ai site plan: the dataset admission failed', { orgId: context.job.orgId, error })\n return 'datasets could not be checked just now'\n })\n const left = Math.min(AI_SITE_DATASETS_MAX, checkDatasetQuota(context.org as never, 0).limit)\n const dataset = refusal\n ? { allowed: false, left: 0, reason: `datasets cannot be created here: ${refusal.replace(/\\.$/, '')}` }\n : { allowed: true, left, reason: null }\n return { ...capabilities, create: { ...capabilities.create, dataset } }\n}\n\n/**\n * A `records` listing for each dataset that was made and that a section of the\n * plan names (AGL-3616). Its fields, where none are given, are read off the\n * site's inventory when the page is designed (`aiLayoutListingsWithDatasets`).\n */\nexport function aiDatasetListingsOf(\n datasets: ReadonlyArray<{ id: string; name: string; records?: string[]; fields?: AiLayoutListingField[] }>,\n screens: readonly AiLayoutListingScreen[],\n): AiLayoutListing[] {\n return datasets.flatMap((dataset) => {\n const placements = aiLayoutRecordsPlacements(dataset, screens)\n if (!placements.length) return []\n return [\n {\n id: aiLayoutListingId('records', dataset.id),\n kind: 'records' as const,\n name: dataset.name,\n records: dataset.records ?? [],\n datasetId: dataset.id,\n ...(dataset.fields?.length ? { fields: dataset.fields } : {}),\n placements,\n },\n ]\n })\n}\n\n/**\n * The site's datasets a layout or a page unit is handed (AGL-3616), each a\n * `records` listing with the sections of the plan's pages that name it, once\n * its unit wrote it. A dataset not yet written, or one that failed, lists\n * nowhere: the pages built without it write the items out.\n */\nexport function aiSiteDatasetListings(input: {\n outputs: readonly AiJobOutput[]\n datasets: ReadonlyArray<Pick<AiBuildPlanCreate, 'name'>>\n screens: readonly AiLayoutListingScreen[]\n /** The ids each dataset's unit delivered, by plan name lowercased. */\n delivered: ReadonlyMap<string, string>\n}): AiLayoutListing[] {\n const made = input.datasets.flatMap((dataset) => {\n const id = input.delivered.get(dataset.name.trim().toLowerCase())\n const output = id\n ? input.outputs.find((entry) => entry.resource === 'draft' && entry.draftResource === AI_SITE_DATASET_RESOURCE && entry.id === id)\n : undefined\n if (!id || !output) return []\n const proposal = proposalOf(output)\n return [\n {\n id,\n // Placed by the name the plan's sections gave it.\n name: dataset.name,\n records: proposal.recordNames,\n // The address field is the record page's, never a card's words.\n fields: proposal.fields.filter((field) => field.id !== proposal.addressField),\n },\n ]\n })\n return aiDatasetListingsOf(made, input.screens)\n}\n\n/** What a page is told about the datasets built before it. */\nexport function aiSiteDatasetBriefLines(outputs: readonly AiJobOutput[]): string[] {\n const datasets = outputs.filter((output) => output.resource === 'draft' && output.draftResource === AI_SITE_DATASET_RESOURCE)\n return datasets.flatMap((output) => {\n const names = proposalOf(output).recordNames\n return names.length\n ? [`This site's dataset “${output.label}” holds: ${names.map((name) => `“${name}”`).join(', ')}. Where a page names one, name it as written; never name one that is not in this list.`]\n : []\n })\n}\n"],"names":["checkDatasetQuota","pluginResourceDraftWriter","isAiPlanNewRef","AI_SITE_DATASETS_MAX","AI_STEP_TIERS","aiModelForStep","AI_DATASET_MAX_TOKENS","AI_DATASET_STEP","aiDatasetDraftContent","generateAiDataset","aiLayoutListingId","aiLayoutRecordsPlacements","AI_DATASET_DRAFT_RESOURCE","aiJobStepBudget","aiGenerationSpent","aiUnspentOutcome","aiPluginDraftAdmissionRefusal","AI_SITE_DATASET_RESOURCE","DATA_PLUGIN_LABEL","AI_SITE_DATASET_INPUT","AI_SITE_DATASET_NOT_WRITTEN_COPY","aiSiteDatasetNote","records","AI_SITE_RECORD_PAGE_NOTE","AI_SITE_DATASET_WRITES_MS","AI_SITE_DATASET_BUDGET","tier","maxTokens","lookups","ownReadsMs","str","value","trim","namesDataset","ref","name","slice","indexOf","toLowerCase","aiSitePlanDatasets","plan","create","filter","entry","kind","aiSiteDatasetInputOf","dataset","screens","shownIn","screen","section","sections","uses","some","push","title","items","recordPages","record","form","find","writesTo","forForm","fields","AI_SITE_FORM_DATASET_FIELDS_MAX","aiSiteRecordTemplateOf","datasetInputOf","job","raw","inputs","Array","isArray","map","Boolean","FORM_DATASET_FIELD_TYPES","aiSiteDatasetFieldName","planned","words","replace","charAt","toUpperCase","aiSiteFormDatasetContent","specs","length","seen","Set","spec","at","lastIndexOf","typed","type","includes","fieldName","has","add","aiSiteFormDatasetNote","limitReview","message","reason","findings","proposalOf","output","proposal","field","id","recordNames","addressField","createAiSiteDatasetRunner","deps","generate","writerFor","context","facts","firestore","signal","model","modelFor","creation","keeper","hostId","failure","host","collection","doc","get","hostSubdomain","resource","draftResource","label","note","input","written","writer","read","$id","again","outputs","content","checked","check","ok","console","error","orgId","jobId","problems","result","write","uid","createdBy","org","now","status","review","generation","brief","merchantWords","why","spent","refused","values","runAiSiteDatasetUnit","aiSiteDatasetRefusal","admission","drafts","ownerFor","pluginId","aiSitePlanDatasetCapability","capabilities","allowed","refusal","catch","left","Math","min","limit","aiDatasetListingsOf","datasets","flatMap","placements","datasetId","aiSiteDatasetListings","made","delivered","undefined","aiSiteDatasetBriefLines","names","join"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-site-datasets.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 { checkDatasetQuota } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { pluginResourceDraftWriter } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport { isAiPlanNewRef, type AiBuildPlanCreate, type AiBuildPlanScreen } from '../model/ai-build-plan'\nimport type { AiJob, AiJobOutput, AiJobPlan } from '../model/ai-jobs.types'\nimport type { AiPlanCapabilities } from '../model/ai-plan-capabilities'\nimport { AI_SITE_DATASETS_MAX } from '../model/ai-site-job'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { aiModelForStep } from '../providers/routing'\nimport {\n AI_DATASET_MAX_TOKENS,\n AI_DATASET_STEP,\n aiDatasetDraftContent,\n generateAiDataset,\n} from '../runtime/ai-dataset-generation'\nimport {\n aiLayoutListingId,\n aiLayoutRecordsPlacements,\n type AiLayoutListing,\n type AiLayoutListingField,\n type AiLayoutListingScreen,\n} from '../layout-language/ai-layout-listings'\nimport {\n aiLayoutStarterPhotos,\n type AiLayoutPicturePhoto,\n type AiLayoutPictureSlot,\n} from '../layout-language/ai-layout-pictures'\nimport { aiSiteWords } from '../model/ai-site-job'\nimport type { AiJobAdmissionContext } from './ai-job-admission'\nimport { aiOriginJobId } from './ai-job-draft-ids'\nimport { aiSiteProductPhotoSrc } from './ai-job-site-content'\nimport { aiLayoutStockPhotoSource } from './ai-layout-stock-photos'\nimport { AI_DATASET_DRAFT_RESOURCE } from './ai-build-unit-outcome'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiGenerationSpent, 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 * A SITE START'S DATASETS (AGL-3616): \"Aglyn AI can also use/create datasets\n * too for whatever it needs\" (Zach, 2026-10-10).\n *\n * A site whose content is a list of like things — a restaurant's menu, a\n * studio's team, a contractor's services, a photographer's portfolio, an\n * events list, its questions and answers — keeps it as a DATASET, the way the\n * docs teach a person to: the owner edits one record and every page listing\n * it follows. On a workspace that may create datasets, the site plan creates\n * one for such content (`create`, kind `dataset`) and names it in the `uses`\n * of each section that lists it. The scaffold then builds each dataset as a\n * unit of its own, after the layout and before the form (which may write\n * its submissions to one) and the pages:\n *\n * - DESIGNED by the `submit_dataset` generation (`ai-dataset-generation.ts`):\n * the planned fields typed, at most a few more, and 3 to 12 records seeded\n * from the brief. Nothing anybody did not say: no review, testimonial,\n * rating, quote or person's name, no price the brief does not state.\n * - WRITTEN by the data plugin's `dataset` writer — this plugin never names\n * the datasets collection — with every rule the console's datasets route\n * has: the member's role and `data.manage`, `release_data_store`,\n * `dataStore` (Starter and up), `datasetsPerOrg` counted inside the create,\n * `recordsPerDataset` and the storage band.\n * - PICTURED where the plan lists it the way a gallery is listed (Zach,\n * 2026-10-10: a ceramic artist's \"Selected work\" came back as text-only\n * cards, where it had been an image-led gallery): a portfolio's pieces, a\n * team, a menu, classes and events, and services or offerings whose\n * section says it shows them in pictures (`aiSiteDatasetPicturesOf`). Such\n * a dataset gets an Image field, and each record a stock photo of its own\n * name in the site's craft, copied into the site's library — never one the\n * job already placed, else a starter photo — searched in code, so it\n * costs no credits (`aiSiteDatasetRecordPhotos`).\n * - LISTED by the pages built after it: each section naming it repeats over\n * the dataset (`aiSiteDatasetListings`, a `records` listing the layout\n * compiler draws, its card led by the record's photo where it has one),\n * and a page the plan made its record template binds its fields by id,\n * its photo the record's, and keeps the address field the writer added.\n *\n * Where datasets cannot be created — the Free taste, Starter's two used up,\n * the data store not released — the plan is told so and creates none, so its\n * sections write the items out. A dataset unit that fails or is refused\n * leaves the pages that list it built without it (they say so), and its\n * record page is not built.\n */\n\n/** The most datasets one site start creates. */\nexport { AI_SITE_DATASETS_MAX } from '../model/ai-site-job'\n\n/** The resource the data plugin's writer is registered under. */\nexport const AI_SITE_DATASET_RESOURCE = AI_DATASET_DRAFT_RESOURCE\n\n/** What the site's plugin list calls the plugin, for \"Turn on Data for this site\". */\nconst DATA_PLUGIN_LABEL = 'Data'\n\n/** The input a dataset unit's own job carries what it needs in. */\nexport const AI_SITE_DATASET_INPUT = 'siteDataset'\n\n/** What a dataset unit's job is told beside its creation. */\nexport interface AiSiteDatasetInput {\n /** The sections that list it, by page: \"Menu › Our dishes (12 items)\". */\n shownIn: string[]\n /** Whether a page of the plan is its record template. */\n recordPages: boolean\n /**\n * The form of the plan that writes its submissions to it (AGL-3616), by\n * name, with that form's planned fields; absent where no form does.\n */\n forForm?: { name: string; fields: string[] }\n /**\n * Whether its records carry a photo, and of what (AGL-3616): `things` for a\n * portfolio's pieces, a menu, classes; `people` for a team. Absent for a\n * list a visitor reads rather than looks at: questions, steps, hours.\n */\n pictures?: AiSiteDatasetPictures\n}\n\n/** What a dataset's records are pictured as: things or people. */\nexport type AiSiteDatasetPictures = 'things' | 'people'\n\n/** A list of questions, voices or plain facts: read, never pictured. */\nconst READ_ONLY_LIST =\n /\\b(faqs?|questions?|q ?& ?a|answers?|testimonials?|reviews?|quotes?|voices|hours|opening|steps?|process|policies|policy|terms|stats?|numbers|figures|milestones|timeline|values|benefits|reasons|features|requirements|checklist|ingredients|donations?|needs|partners|sponsors|supporters|roles|positions|jobs|openings)\\b/i\n/** A list of people: its photos are of the business's people. */\nconst PEOPLE_LIST =\n /\\b(team|staff|people|crew|instructors?|teachers?|tutors?|stylists?|barbers?|therapists?|coaches?|trainers?|chefs?|doctors?|dentists?|hygienists?|practitioners?|artists?|musicians?|performers?|speakers?|founders?|members?)\\b/i\n/** A list a visitor looks at before reading: pictured wherever it is listed. */\nconst PICTURED_LIST =\n /\\b(portfolio|pieces?|works?|projects?|gallery|galleries|collections?|series|menus?|dishes|plates|drinks|cocktails|wines?|beers?|coffees?|pastries|cakes|products?|items|events?|classes|class|workshops?|courses?|retreats?|tours?|trips?|adventures?|experiences?|rooms?|suites?|cabins?|properties|listings|homes|venues?|spaces?|recipes?|artworks?|paintings?|prints?|photos?|photographs?|designs?|commissions?|builds?|vehicles?|animals?|pets?|plants?|flowers?|bouquets?|arrangements?|case studies)\\b/i\n/** A section that says it shows its items in pictures: a gallery, a grid of photos, tiles. */\nconst PICTURED_SECTION =\n /\\b(gallery|galleries|grid|images?|photos?|photographs?|pictures?|lightbox|portfolio|showcase|tiles?|visuals?|lookbook|carousel|slideshow|mosaic|thumbnails?)\\b/i\n\n/**\n * Whether a dataset's records carry a photo, read off the plan (AGL-3616):\n * its name and the names of the sections that list it. Questions, voices and\n * plain facts never; people as people; a list a visitor looks at — pieces,\n * dishes, classes, events, rooms — always; any other list (services,\n * offerings, programs) only where a section that lists it says it shows them\n * in pictures, or names one of those. A dataset no page lists is not\n * pictured. Pure.\n */\nexport function aiSiteDatasetPicturesOf(\n dataset: Pick<AiBuildPlanCreate, 'name'>,\n sections: readonly string[],\n): AiSiteDatasetPictures | null {\n if (!sections.length) return null\n const own = dataset.name\n if (READ_ONLY_LIST.test(own)) return null\n if (PEOPLE_LIST.test(own)) return 'people'\n if (PICTURED_LIST.test(own)) return 'things'\n if (sections.some((section) => READ_ONLY_LIST.test(section) && !PICTURED_SECTION.test(section))) return null\n if (sections.some((section) => PEOPLE_LIST.test(section))) return 'people'\n if (sections.some((section) => PICTURED_LIST.test(section) || PICTURED_SECTION.test(section))) return 'things'\n return null\n}\n\n/** What a dataset row says when its design could not be written: our failure, refunded. */\nexport const AI_SITE_DATASET_NOT_WRITTEN_COPY = 'This dataset could not be added this time.'\n\n/** The note a written dataset carries on the job's page. */\nexport function aiSiteDatasetNote(records: number): string {\n return `${records} ${records === 1 ? 'record' : 'records'} to start, in Data. Edit them there and every page that lists them follows.`\n}\n\n/** The note a record template's row adds: the binding is the member's to save (rule 13). */\nexport const AI_SITE_RECORD_PAGE_NOTE =\n 'A page per record is drafted, not live: open Page Properties → Record pages to save it.'\n\n/** ASSUMED: what one dataset pass reads and writes beside its generation: the site, the writer's reads and its transaction. */\nexport const AI_SITE_DATASET_WRITES_MS = 2_000\n\n/** One dataset's pass: its generation and re-ask at its ceiling, and its writes. */\nexport const AI_SITE_DATASET_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS[AI_DATASET_STEP],\n maxTokens: AI_DATASET_MAX_TOKENS,\n lookups: 0,\n ownReadsMs: AI_SITE_DATASET_WRITES_MS,\n})\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** The plan's reference to a dataset as a section names it: `new:<name>`, compared lowercased. */\nfunction namesDataset(ref: string, name: string): boolean {\n return isAiPlanNewRef(ref) && ref.slice(ref.indexOf(':') + 1).trim().toLowerCase() === name.trim().toLowerCase()\n}\n\n/** The plan's datasets, in plan order, at most the most a start creates. */\nexport function aiSitePlanDatasets(plan: Pick<AiJobPlan, 'create'>): AiBuildPlanCreate[] {\n return plan.create.filter((entry) => entry.kind === 'dataset').slice(0, AI_SITE_DATASETS_MAX)\n}\n\n/** Where the plan lists a dataset, in words its design reads, whether a page is its record template, and the form that writes to it. */\nexport function aiSiteDatasetInputOf(\n dataset: Pick<AiBuildPlanCreate, 'name'>,\n screens: readonly AiBuildPlanScreen[],\n create: readonly AiBuildPlanCreate[] = [],\n): AiSiteDatasetInput {\n const shownIn: string[] = []\n const sectionNames: string[] = []\n for (const screen of screens) {\n for (const section of screen.sections) {\n if (!section.uses.some((ref) => namesDataset(ref, dataset.name))) continue\n shownIn.push(`${screen.title} › ${section.name}${section.items ? ` (${section.items} items)` : ''}`)\n sectionNames.push(section.name)\n }\n }\n const recordPages = screens.some((screen) => !!screen.record && namesDataset(screen.record.dataset, dataset.name))\n const form = create.find((entry) => entry.kind === 'form' && !!entry.writesTo && namesDataset(entry.writesTo, dataset.name))\n // A form's dataset holds its submissions: nobody's photo.\n const pictures = form ? null : aiSiteDatasetPicturesOf(dataset, sectionNames)\n return {\n shownIn: shownIn.slice(0, 8),\n recordPages,\n ...(form ? { forForm: { name: form.name, fields: form.fields.slice(0, AI_SITE_FORM_DATASET_FIELDS_MAX) } } : {}),\n ...(pictures ? { pictures } : {}),\n }\n}\n\n/** Whether a plan screen is the record template of a dataset the plan creates, and which. */\nexport function aiSiteRecordTemplateOf(screen: Pick<AiBuildPlanScreen, 'record'>): string | null {\n const ref = screen.record?.dataset\n return ref && isAiPlanNewRef(ref) ? ref.slice(ref.indexOf(':') + 1).trim() : null\n}\n\nfunction datasetInputOf(job: Pick<AiJob, 'inputs'>): AiSiteDatasetInput {\n const raw = (job.inputs?.[AI_SITE_DATASET_INPUT] ?? {}) as Partial<AiSiteDatasetInput>\n const form = raw.forForm && typeof raw.forForm === 'object' ? raw.forForm : null\n return {\n shownIn: Array.isArray(raw.shownIn) ? raw.shownIn.map(str).filter(Boolean).slice(0, 8) : [],\n recordPages: raw.recordPages === true,\n ...(form && str(form.name)\n ? {\n forForm: {\n name: str(form.name),\n fields: Array.isArray(form.fields) ? form.fields.map(str).filter(Boolean).slice(0, AI_SITE_FORM_DATASET_FIELDS_MAX) : [],\n },\n }\n : {}),\n ...(raw.pictures === 'things' || raw.pictures === 'people' ? { pictures: raw.pictures } : {}),\n }\n}\n\n/** The most fields a form's dataset is designed with: the data plugin writer's own ceiling is higher. */\nexport const AI_SITE_FORM_DATASET_FIELDS_MAX = 12\n\n/** The field types a planned `Name:type` may name, as the data plugin's writer takes them. */\nconst FORM_DATASET_FIELD_TYPES = ['text', 'number', 'integer', 'boolean', 'list'] as const\n\n/** A planned field as a person reads it: `fullName` and `full_name` are \"Full name\". */\nexport function aiSiteDatasetFieldName(planned: string): string {\n const words = planned\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[_-]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n return words ? words.charAt(0).toUpperCase() + words.slice(1).toLowerCase() : ''\n}\n\n/**\n * A dataset a form writes to, as the data plugin's writer takes it\n * (AGL-3616): designed with no model, since its records are the\n * submissions. Its fields are its planned ones — or, where the plan gave it\n * none, the form's — each a person's words for it, text unless the plan\n * typed it as `Name:type`, never required, so a submission that leaves one\n * blank is still a record. It starts with no records. Pure.\n */\nexport function aiSiteFormDatasetContent(\n name: string,\n planned: readonly string[],\n form: { fields: readonly string[] },\n): Record<string, unknown> {\n const specs = (planned.length ? planned : form.fields).slice(0, AI_SITE_FORM_DATASET_FIELDS_MAX)\n const seen = new Set<string>()\n const fields: Array<{ name: string; type: string }> = []\n for (const spec of specs) {\n const at = spec.lastIndexOf(':')\n const typed = at > 0 ? spec.slice(at + 1).trim().toLowerCase() : ''\n const type = (FORM_DATASET_FIELD_TYPES as readonly string[]).includes(typed) ? typed : 'text'\n const fieldName = aiSiteDatasetFieldName(type === typed ? spec.slice(0, at) : spec).slice(0, 60)\n if (!fieldName || seen.has(fieldName.toLowerCase())) continue\n seen.add(fieldName.toLowerCase())\n fields.push({ name: fieldName, type })\n }\n return { name, fields, records: [] }\n}\n\n/** The note a form's dataset carries on the job's page. */\nexport function aiSiteFormDatasetNote(form: string): string {\n return `Empty to start: each submission of the form “${form}” is added here, in Data.`\n}\n\nconst limitReview = (message: string) => ({ reason: 'limit' as const, message, findings: [] })\n\n/** The wall clock a dataset's photos may take inside its pass: the 20 seconds a page's pictures had (AGL-3660). */\nexport const AI_SITE_DATASET_PHOTOS_BUDGET_MS = 20_000\n\n/** The field a pictured dataset's photo is kept in, where the plan gave it none. */\nexport const AI_SITE_DATASET_IMAGE_FIELD = 'Image'\n\n/** A planned field that already names a record's photo. */\nconst IMAGE_FIELD_NAME =\n /^(?:main |cover |hero |featured? )?(?:image|photo|picture|photograph|thumbnail|headshot|portrait|cover)s?$/i\n\n/** One record's photo: the address its Image shows, and the library asset placed, which no page shows again. */\nexport interface AiSiteDatasetRecordPhoto {\n /** The media library's CDN path, or a starter's site path; `null` for none. */\n src: string | null\n /** The `media:` reference of the stock photo placed, or `null` for a starter. */\n placed: string | null\n}\n\n/**\n * A photo for each record of a pictured dataset (AGL-3616), in order: a stock\n * photo of the record's own name, in the site's craft (\"ceramic speckled\n * serving bowl\"), copied into the site's library, where the deployment has a\n * stock library — never twice in the dataset nor one another part of the job\n * placed, inside {@link AI_SITE_DATASET_PHOTOS_BUDGET_MS} — else a starter\n * photo, as a page's empty picture takes one. A team is searched as the\n * business's people. The photo is kept as the asset's CDN path, the form a\n * product keeps (`aiSiteProductPhotoSrc`), so a replace in the library still\n * reaches it. No AI model is asked, so no credits; never throws.\n */\nexport async function aiSiteDatasetRecordPhotos(input: {\n job: Pick<AiJob, '$id' | 'hostId' | 'createdBy' | 'inputs' | 'brief'>\n /** The dataset, as the plan named it. */\n name: string\n /** Each record's name, by position. */\n names: readonly string[]\n pictures: AiSiteDatasetPictures\n signal?: AbortSignal\n stockPhotos?: typeof aiLayoutStockPhotoSource\n}): Promise<AiSiteDatasetRecordPhoto[]> {\n const { job } = input\n if (!input.names.length || !job.hostId) return input.names.map(() => ({ src: null, placed: null }))\n const people = input.pictures === 'people'\n const slots: AiLayoutPictureSlot[] = input.names.map((name, index) => ({\n imageId: `record${index}`,\n frameId: null,\n iconId: null,\n alt: name.trim() || input.name,\n // The record card's own shape: a team's portraits stand, pieces sit wide.\n aspect: people ? 4 / 5 : 4 / 3,\n sectionIndex: 0,\n role: people ? ('about' as const) : ('gallery' as const),\n }))\n const seed = `${job.$id}:records`\n let found: ReadonlyArray<AiLayoutPicturePhoto | null> = []\n try {\n const source = (input.stockPhotos ?? aiLayoutStockPhotoSource)(\n {\n hostId: job.hostId,\n uid: job.createdBy,\n seed,\n business: aiSiteWords(job.inputs).about || job.brief,\n sectionNames: [input.name],\n jobId: aiOriginJobId(job),\n // Each record may try its name, its object and the site's world.\n searches: slots.length * 3,\n ...(input.signal ? { signal: input.signal } : {}),\n },\n { budgetMs: AI_SITE_DATASET_PHOTOS_BUDGET_MS },\n )\n if (source) found = await source(slots)\n } catch (error) {\n console.warn('ai site dataset: the stock photos failed; starter photos fill them', { error: String(error) })\n }\n const starters = aiLayoutStarterPhotos(slots, seed)\n const hostId = job.hostId\n return slots.map((_slot, index) => {\n const stock = aiSiteProductPhotoSrc(found[index]?.src, hostId)\n if (stock) return { src: stock, placed: found[index]?.src ?? null }\n return { src: aiSiteProductPhotoSrc(starters[index]?.src, hostId), placed: null }\n })\n}\n\n/**\n * A designed dataset's content with its records' photos (AGL-3616): the\n * planned field that names a photo (\"Image\", \"Photo\") becomes the data\n * plugin's `image` field, else one called \"Image\" is added after the others,\n * and each record holds its photo there; a record with none holds nothing.\n * Pure.\n */\nexport function aiSiteDatasetContentWithPhotos(\n content: Record<string, unknown>,\n photos: ReadonlyArray<string | null>,\n): Record<string, unknown> {\n const fields = (Array.isArray(content['fields']) ? content['fields'] : []) as Array<{ name: string; type?: string }>\n const named = fields.find((field) => IMAGE_FIELD_NAME.test(str(field.name)))\n const fieldName = named ? named.name : AI_SITE_DATASET_IMAGE_FIELD\n const nextFields = named\n ? fields.map((field) => (field === named ? { ...field, type: 'image' } : field))\n : [...fields, { name: AI_SITE_DATASET_IMAGE_FIELD, type: 'image' }]\n const records = (Array.isArray(content['records']) ? content['records'] : []) as Array<Record<string, unknown>>\n return {\n ...content,\n fields: nextFields,\n records: records.map((record, index) => {\n const next = { ...record }\n delete next[fieldName]\n const photo = photos[index]\n return photo ? { ...next, [fieldName]: photo } : next\n }),\n }\n}\n\nexport interface AiSiteDatasetRunnerDeps {\n generate?: typeof generateAiDataset\n writerFor?: typeof pluginResourceDraftWriter\n /** Where a pictured dataset's photos come from; the stock library, else the starters, otherwise. */\n photos?: typeof aiSiteDatasetRecordPhotos\n}\n\n/** The fields and record names a written dataset reports, as its output's proposal keeps them. */\ninterface AiSiteDatasetProposal {\n fields: AiLayoutListingField[]\n /** Each record's name: its first field's value. */\n recordNames: string[]\n addressField: string | null\n /** The field each record's photo is in, where it has one (AGL-3616). */\n imageField?: string | null\n /** The library photos its records show (`media:` references), which no page places again. */\n photos?: string[]\n /**\n * The fields most of its first records leave empty, by id (AGL-3616): a\n * tour's venues not yet known. A card never binds one — it would print\n * nothing on most records — so its listing leaves them out.\n */\n sparseFields?: string[]\n}\n\n/**\n * The fields of a designed dataset that its records leave empty on most of\n * them — every record, or more than half — by the id the data plugin gave\n * each (AGL-3616). Its first field is the record's name and is never one. The\n * live low-tide-hollow start (beta.239) seeded its tour dates with no venue,\n * rightly inventing none, and its cards bound the venue as their eyebrow.\n */\nexport function aiDatasetSparseFields(\n dataset: { fields: ReadonlyArray<{ name: string }>; records: ReadonlyArray<ReadonlyArray<string | undefined>> },\n written: readonly AiLayoutListingField[],\n): string[] {\n if (!dataset.records.length) return []\n const idOf = (name: string, column: number) =>\n written.find((field) => field.name.trim().toLowerCase() === name.trim().toLowerCase())?.id ?? written[column]?.id ?? null\n return dataset.fields.flatMap((field, column) => {\n if (column === 0) return []\n const filled = dataset.records.filter((values) => (values[column] ?? '').trim()).length\n const id = idOf(field.name, column)\n return id && filled * 2 <= dataset.records.length ? [id] : []\n })\n}\n\nfunction proposalOf(output: Pick<AiJobOutput, 'proposal'>): AiSiteDatasetProposal {\n const raw = (output.proposal ?? {}) as Partial<AiSiteDatasetProposal>\n return {\n fields: Array.isArray(raw.fields)\n ? raw.fields.filter((field) => field && typeof field.id === 'string' && field.id).map((field) => ({ id: field.id, name: str(field.name) || field.id, type: str(field.type) || 'text' }))\n : [],\n recordNames: Array.isArray(raw.recordNames) ? raw.recordNames.map(str).filter(Boolean).slice(0, 12) : [],\n addressField: str(raw.addressField) || null,\n imageField: str(raw.imageField) || null,\n photos: Array.isArray(raw.photos) ? raw.photos.map(str).filter((src) => src.startsWith('media:')).slice(0, 24) : [],\n sparseFields: Array.isArray(raw.sparseFields) ? raw.sparseFields.map(str).filter(Boolean) : [],\n }\n}\n\n/**\n * The dataset unit's runner, on the job the scaffold derived for it: the one\n * creation its plan carries, designed by the model and written by the data\n * plugin, under the creation's own id. A pass repeated after its write finds\n * the dataset the writer wrote and reports it again, spending nothing.\n */\nexport function createAiSiteDatasetRunner(deps: AiSiteDatasetRunnerDeps = {}): AiJobStepRunner {\n const generate = deps.generate ?? generateAiDataset\n const writerFor = deps.writerFor ?? pluginResourceDraftWriter\n const photosFor = deps.photos ?? aiSiteDatasetRecordPhotos\n return async (context): Promise<AiJobStepOutcome> => {\n const { job, firestore, signal } = context\n const model = context.modelFor?.(AI_DATASET_STEP) ?? aiModelForStep(AI_DATASET_STEP)\n const creation = (job.plan as AiJobPlan | null)?.create?.find((entry) => entry.kind === 'dataset')\n const keeper = writerFor(AI_SITE_DATASET_RESOURCE)\n if (!job.hostId || !creation) return { ...aiUnspentOutcome(model), failure: 'Open the site before adding its data.' }\n if (!keeper) return { ...aiUnspentOutcome(model), failure: 'Datasets are not available on this site.' }\n const host = await firestore.collection('hosts').doc(job.hostId).get()\n const hostSubdomain = str(host.get('subdomain')) || null\n const output = (id: string, name: string, proposal: AiSiteDatasetProposal, records: number): AiJobOutput => ({\n resource: 'draft',\n draftResource: AI_SITE_DATASET_RESOURCE,\n id,\n hostId: job.hostId ?? null,\n hostSubdomain,\n label: name,\n note: aiSiteDatasetNote(records),\n proposal: proposal as unknown as Record<string, unknown>,\n })\n const input = datasetInputOf(job)\n // Asked again under the same id: the dataset already written.\n const written = await keeper.writer.read({ hostId: job.hostId, id: job.$id })\n if (written) {\n const facts = written.facts as { fields?: AiLayoutListingField[]; addressField?: string | null; imageField?: string | null }\n const again = output(\n written.id,\n written.name || creation.name,\n { fields: facts.fields ?? [], recordNames: [], addressField: facts.addressField ?? null, imageField: facts.imageField ?? null },\n 0,\n )\n return aiUnspentOutcome(model, {\n outputs: [input.forForm ? { ...again, note: aiSiteFormDatasetNote(input.forForm.name) } : again],\n })\n }\n // A form's dataset (AGL-3616): its records are the submissions, so it is\n // designed from the plan with no model, and starts empty.\n if (input.forForm) {\n const content = aiSiteFormDatasetContent(creation.name, creation.fields, input.forForm)\n const checked = keeper.writer.check(content, { hostId: job.hostId })\n if (checked.ok === false) {\n console.error('ai site dataset: the dataset writer refused a form dataset we made', { orgId: job.orgId, jobId: job.$id, problems: checked.problems })\n return { ...aiUnspentOutcome(model), failure: AI_SITE_DATASET_NOT_WRITTEN_COPY }\n }\n const result = await keeper.writer.write({\n orgId: job.orgId,\n hostId: job.hostId,\n uid: job.createdBy,\n org: (context.org ?? null) as Readonly<Record<string, unknown>> | null,\n now: context.now,\n id: job.$id,\n name: creation.name,\n content,\n })\n if (result.ok === false) {\n if (result.status === 400) return { ...aiUnspentOutcome(model), failure: AI_SITE_DATASET_NOT_WRITTEN_COPY }\n return { ...aiUnspentOutcome(model), review: limitReview(result.error) }\n }\n const facts = result.facts as { fields?: AiLayoutListingField[]; addressField?: string | null }\n return aiUnspentOutcome(model, {\n outputs: [\n {\n ...output(result.id, result.name, { fields: facts.fields ?? [], recordNames: [], addressField: null }, 0),\n note: aiSiteFormDatasetNote(input.forForm.name),\n },\n ],\n })\n }\n const generation = await generate({\n brief: job.brief,\n merchantWords: job.brief,\n name: creation.name,\n why: creation.why,\n fields: creation.fields,\n shownIn: input.shownIn,\n recordPages: input.recordPages,\n model,\n maxTokens: AI_SITE_DATASET_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(generation)\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: generation.message }\n const designed = aiDatasetDraftContent(creation.name, generation.value, { recordPages: input.recordPages })\n // A list a visitor looks at keeps its records' photos (AGL-3616): searched\n // in code, never asked of the model, so they cost no credits.\n const photos = input.pictures\n ? await photosFor({\n job,\n name: creation.name,\n names: generation.value.records.map((values) => values[0] ?? ''),\n pictures: input.pictures,\n ...(signal ? { signal } : {}),\n }).catch(() => [] as AiSiteDatasetRecordPhoto[])\n : []\n const content = input.pictures\n ? aiSiteDatasetContentWithPhotos(designed, generation.value.records.map((_values, index) => photos[index]?.src ?? null))\n : designed\n // The data plugin's own rules, before anything is written: content we\n // made that its model refuses is our failure, refunded.\n const checked = keeper.writer.check(content, { hostId: job.hostId })\n if (checked.ok === false) {\n console.error('ai site dataset: the dataset writer refused a dataset we made', { orgId: job.orgId, jobId: job.$id, problems: checked.problems })\n return { ...spent, failure: AI_SITE_DATASET_NOT_WRITTEN_COPY }\n }\n const result = await keeper.writer.write({\n orgId: job.orgId,\n hostId: job.hostId,\n uid: job.createdBy,\n org: (context.org ?? null) as Readonly<Record<string, unknown>> | null,\n now: context.now,\n id: job.$id,\n name: creation.name,\n content,\n })\n if (result.ok === false) {\n if (result.status === 400) return { ...spent, failure: AI_SITE_DATASET_NOT_WRITTEN_COPY }\n // The allowance, the role, the plan or the flag: the workspace's to change.\n return { ...spent, review: limitReview(result.error) }\n }\n const facts = result.facts as {\n fields?: AiLayoutListingField[]\n records?: number\n addressField?: string | null\n imageField?: string | null\n }\n const placed = photos.flatMap((photo) => (photo.placed ? [photo.placed] : []))\n const proposal: AiSiteDatasetProposal = {\n fields: facts.fields ?? [],\n recordNames: generation.value.records.map((values) => values[0] ?? '').filter(Boolean).slice(0, 12),\n addressField: facts.addressField ?? null,\n ...(facts.imageField ? { imageField: facts.imageField } : {}),\n ...(placed.length ? { photos: placed } : {}),\n sparseFields: aiDatasetSparseFields(generation.value, facts.fields ?? []),\n }\n return { ...spent, outputs: [output(result.id, result.name, proposal, facts.records ?? generation.value.records.length)] }\n }\n}\n\nexport const runAiSiteDatasetUnit = createAiSiteDatasetRunner()\n\n/**\n * Whether a dataset may be created for this member on this site, asked before\n * its first pass so a refusal spends nothing; `null` admits. The data plugin\n * must run here (on for the workspace and the site, past its release flag),\n * and its writer says whether this member may make one now: the role, the\n * plan, the flag, the allowance.\n */\nexport async function aiSiteDatasetRefusal(\n context: Pick<AiJobStepContext, 'firestore' | 'org' | 'now'> & { job: Pick<AiJob, 'orgId' | 'hostId' | 'createdBy'> },\n deps: { admission?: typeof aiPluginDraftAdmissionRefusal; writerFor?: (resource: string) => ReturnType<typeof pluginResourceDraftWriter> } = {},\n): Promise<string | null> {\n const { job } = context\n if (!job.hostId) return 'Open the site first.'\n const writerFor = deps.writerFor ?? pluginResourceDraftWriter\n const admission: AiJobAdmissionContext = {\n firestore: context.firestore,\n orgId: job.orgId,\n hostId: job.hostId,\n inputs: {},\n org: context.org ?? null,\n uid: job.createdBy,\n }\n const refused = await (deps.admission ?? aiPluginDraftAdmissionRefusal)(admission, {\n kind: 'site',\n drafts: [{ resource: AI_SITE_DATASET_RESOURCE, label: DATA_PLUGIN_LABEL }],\n writerFor: (resource) => writerFor(resource)?.writer ?? null,\n ownerFor: (resource) => writerFor(resource)?.pluginId ?? null,\n now: context.now,\n })\n return refused?.error ?? null\n}\n\n/**\n * A site plan's dataset capability on this workspace (AGL-3616): where the\n * workspace's plan includes datasets, whether the data plugin runs here and\n * this member may create one now, and how many a start may make. A clause the\n * plan's turn completes is the reason where it may not.\n */\nexport async function aiSitePlanDatasetCapability(\n capabilities: AiPlanCapabilities,\n context: Parameters<typeof aiSiteDatasetRefusal>[0],\n deps: Parameters<typeof aiSiteDatasetRefusal>[1] = {},\n): Promise<AiPlanCapabilities> {\n if (!capabilities.create.dataset.allowed) return capabilities\n const refusal = await aiSiteDatasetRefusal(context, deps).catch((error: unknown) => {\n console.error('ai site plan: the dataset admission failed', { orgId: context.job.orgId, error })\n return 'datasets could not be checked just now'\n })\n const left = Math.min(AI_SITE_DATASETS_MAX, checkDatasetQuota(context.org as never, 0).limit)\n const dataset = refusal\n ? { allowed: false, left: 0, reason: `datasets cannot be created here: ${refusal.replace(/\\.$/, '')}` }\n : { allowed: true, left, reason: null }\n return { ...capabilities, create: { ...capabilities.create, dataset } }\n}\n\n/**\n * A `records` listing for each dataset that was made and that a section of the\n * plan names (AGL-3616). Its fields, where none are given, are read off the\n * site's inventory when the page is designed (`aiLayoutListingsWithDatasets`).\n */\nexport function aiDatasetListingsOf(\n datasets: ReadonlyArray<{ id: string; name: string; records?: string[]; fields?: AiLayoutListingField[]; imageField?: string | null }>,\n screens: readonly AiLayoutListingScreen[],\n): AiLayoutListing[] {\n return datasets.flatMap((dataset) => {\n const placements = aiLayoutRecordsPlacements(dataset, screens)\n if (!placements.length) return []\n return [\n {\n id: aiLayoutListingId('records', dataset.id),\n kind: 'records' as const,\n name: dataset.name,\n records: dataset.records ?? [],\n datasetId: dataset.id,\n ...(dataset.fields?.length ? { fields: dataset.fields } : {}),\n // Its card leads with the record's photo (AGL-3616).\n ...(dataset.imageField ? { imageField: dataset.imageField } : {}),\n placements,\n },\n ]\n })\n}\n\n/**\n * The site's datasets a layout or a page unit is handed (AGL-3616), each a\n * `records` listing with the sections of the plan's pages that name it, once\n * its unit wrote it. A dataset not yet written, or one that failed, lists\n * nowhere: the pages built without it write the items out.\n */\nexport function aiSiteDatasetListings(input: {\n outputs: readonly AiJobOutput[]\n datasets: ReadonlyArray<Pick<AiBuildPlanCreate, 'name'>>\n screens: readonly AiLayoutListingScreen[]\n /** The ids each dataset's unit delivered, by plan name lowercased. */\n delivered: ReadonlyMap<string, string>\n}): AiLayoutListing[] {\n const made = input.datasets.flatMap((dataset) => {\n const id = input.delivered.get(dataset.name.trim().toLowerCase())\n const output = id\n ? input.outputs.find((entry) => entry.resource === 'draft' && entry.draftResource === AI_SITE_DATASET_RESOURCE && entry.id === id)\n : undefined\n if (!id || !output) return []\n const proposal = proposalOf(output)\n return [\n {\n id,\n // Placed by the name the plan's sections gave it.\n name: dataset.name,\n records: proposal.recordNames,\n // The address field is the record page's, never a card's words; the\n // photo is the card's picture, never its words; a field most records\n // leave empty is no card's either (AGL-3616).\n fields: proposal.fields.filter(\n (field, index) =>\n field.id !== proposal.addressField &&\n field.id !== proposal.imageField &&\n (index === 0 || !proposal.sparseFields?.includes(field.id)),\n ),\n imageField: proposal.imageField ?? null,\n },\n ]\n })\n return aiDatasetListingsOf(made, input.screens)\n}\n\n/** The library photos the site's datasets' records show (AGL-3616), which a page does not place again. */\nexport function aiSiteDatasetPlacedPhotos(outputs: readonly AiJobOutput[]): string[] {\n return [\n ...new Set(\n outputs\n .filter((output) => output.resource === 'draft' && output.draftResource === AI_SITE_DATASET_RESOURCE)\n .flatMap((output) => proposalOf(output).photos ?? []),\n ),\n ]\n}\n\n/** What a page is told about the datasets built before it. */\nexport function aiSiteDatasetBriefLines(outputs: readonly AiJobOutput[]): string[] {\n const datasets = outputs.filter((output) => output.resource === 'draft' && output.draftResource === AI_SITE_DATASET_RESOURCE)\n return datasets.flatMap((output) => {\n const names = proposalOf(output).recordNames\n return names.length\n ? [`This site's dataset “${output.label}” holds: ${names.map((name) => `“${name}”`).join(', ')}. Where a page names one, name it as written; never name one that is not in this list.`]\n : []\n })\n}\n"],"names":["checkDatasetQuota","pluginResourceDraftWriter","isAiPlanNewRef","AI_SITE_DATASETS_MAX","AI_STEP_TIERS","aiModelForStep","AI_DATASET_MAX_TOKENS","AI_DATASET_STEP","aiDatasetDraftContent","generateAiDataset","aiLayoutListingId","aiLayoutRecordsPlacements","aiLayoutStarterPhotos","aiSiteWords","aiOriginJobId","aiSiteProductPhotoSrc","aiLayoutStockPhotoSource","AI_DATASET_DRAFT_RESOURCE","aiJobStepBudget","aiGenerationSpent","aiUnspentOutcome","aiPluginDraftAdmissionRefusal","AI_SITE_DATASET_RESOURCE","DATA_PLUGIN_LABEL","AI_SITE_DATASET_INPUT","READ_ONLY_LIST","PEOPLE_LIST","PICTURED_LIST","PICTURED_SECTION","aiSiteDatasetPicturesOf","dataset","sections","length","own","name","test","some","section","AI_SITE_DATASET_NOT_WRITTEN_COPY","aiSiteDatasetNote","records","AI_SITE_RECORD_PAGE_NOTE","AI_SITE_DATASET_WRITES_MS","AI_SITE_DATASET_BUDGET","tier","maxTokens","lookups","ownReadsMs","str","value","trim","namesDataset","ref","slice","indexOf","toLowerCase","aiSitePlanDatasets","plan","create","filter","entry","kind","aiSiteDatasetInputOf","screens","shownIn","sectionNames","screen","uses","push","title","items","recordPages","record","form","find","writesTo","pictures","forForm","fields","AI_SITE_FORM_DATASET_FIELDS_MAX","aiSiteRecordTemplateOf","datasetInputOf","job","raw","inputs","Array","isArray","map","Boolean","FORM_DATASET_FIELD_TYPES","aiSiteDatasetFieldName","planned","words","replace","charAt","toUpperCase","aiSiteFormDatasetContent","specs","seen","Set","spec","at","lastIndexOf","typed","type","includes","fieldName","has","add","aiSiteFormDatasetNote","limitReview","message","reason","findings","AI_SITE_DATASET_PHOTOS_BUDGET_MS","AI_SITE_DATASET_IMAGE_FIELD","IMAGE_FIELD_NAME","aiSiteDatasetRecordPhotos","input","names","hostId","src","placed","people","slots","index","imageId","frameId","iconId","alt","aspect","sectionIndex","role","seed","$id","found","source","stockPhotos","uid","createdBy","business","about","brief","jobId","searches","signal","budgetMs","error","console","warn","String","starters","_slot","stock","aiSiteDatasetContentWithPhotos","content","photos","named","field","nextFields","next","photo","aiDatasetSparseFields","written","idOf","column","id","flatMap","filled","values","proposalOf","output","proposal","recordNames","addressField","imageField","startsWith","sparseFields","createAiSiteDatasetRunner","deps","generate","writerFor","photosFor","context","facts","firestore","model","modelFor","creation","keeper","failure","host","collection","doc","get","hostSubdomain","resource","draftResource","label","note","writer","read","again","outputs","checked","check","ok","orgId","problems","result","write","org","now","status","review","generation","merchantWords","why","spent","refused","designed","catch","_values","runAiSiteDatasetUnit","aiSiteDatasetRefusal","admission","drafts","ownerFor","pluginId","aiSitePlanDatasetCapability","capabilities","allowed","refusal","left","Math","min","limit","aiDatasetListingsOf","datasets","placements","datasetId","aiSiteDatasetListings","made","delivered","undefined","aiSiteDatasetPlacedPhotos","aiSiteDatasetBriefLines","join"],"mappings":"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"}
|
|
@@ -25,6 +25,7 @@ import { aiPublishGuidedSite } from './ai-site-publish';
|
|
|
25
25
|
import { aiPublishSitePosts, aiSiteContentRefusal } from './ai-job-site-content';
|
|
26
26
|
import { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache';
|
|
27
27
|
import { aiSiteDatasetRefusal } from './ai-job-site-datasets';
|
|
28
|
+
import { aiSiteStorePagesRefusal, type AiSiteStorePagesInput } from './ai-job-site-store-pages';
|
|
28
29
|
import { type AiJobStepContext, type AiJobStepOutcome, type AiJobStepRunner } from './ai-job-text-step';
|
|
29
30
|
import { aiJobStepRunnerFor } from './ai-jobs';
|
|
30
31
|
/**
|
|
@@ -85,9 +86,11 @@ import { aiJobStepRunnerFor } from './ai-jobs';
|
|
|
85
86
|
* page job's. The site's datasets (AGL-3616) are built after the layout and
|
|
86
87
|
* before the form, which may write its submissions to one; a blog's first
|
|
87
88
|
* posts and a store's first products (AGL-3676) after the form; all of them
|
|
88
|
-
* before the pages, which are told what they are.
|
|
89
|
+
* before the pages, which are told what they are. A paid store's account,
|
|
90
|
+
* cart and policy pages (`store`, AGL-3676) come after its planned pages,
|
|
91
|
+
* which keep the first claim on the site's pages allowance.
|
|
89
92
|
*/
|
|
90
|
-
export type AiSiteUnitKind = 'theme' | 'layout' | 'form' | 'component' | 'dataset' | 'posts' | 'products' | 'page' | 'email';
|
|
93
|
+
export type AiSiteUnitKind = 'theme' | 'layout' | 'form' | 'component' | 'dataset' | 'posts' | 'products' | 'page' | 'store' | 'email';
|
|
91
94
|
export interface AiSiteUnit {
|
|
92
95
|
kind: AiSiteUnitKind;
|
|
93
96
|
/** The job kind whose registered runner builds it. */
|
|
@@ -224,6 +227,14 @@ export declare function aiSiteUnitJobId(job: Pick<AiJob, '$id' | 'steps'>, unit:
|
|
|
224
227
|
* for leaving it out.
|
|
225
228
|
*/
|
|
226
229
|
export declare function aiSiteUnitJob(job: AiJob, unit: AiSiteUnit, built: BuiltRefs): AiJob;
|
|
230
|
+
/** What a store pages unit writes (AGL-3676): the plan's store pages, their words' links, and the layout. */
|
|
231
|
+
export declare function aiSiteStorePagesInputOf(plan: Pick<AiJobPlan, 'screens'>, built: BuiltRefs): AiSiteStorePagesInput;
|
|
232
|
+
/**
|
|
233
|
+
* Whether this site start adds a store's own pages (AGL-3676): its ledger
|
|
234
|
+
* owes them and has not given up on them. The ledger is written before the
|
|
235
|
+
* layout is built, so the header links the account before the page exists.
|
|
236
|
+
*/
|
|
237
|
+
export declare function aiSiteAddsStorePages(job: Pick<AiJob, 'items'>): boolean;
|
|
227
238
|
/**
|
|
228
239
|
* Whether this site start writes its first posts (AGL-3676): its ledger owes
|
|
229
240
|
* the posts part and has not given up on it. The ledger is written before the
|
|
@@ -291,6 +302,9 @@ export interface AiJobSiteStepDeps {
|
|
|
291
302
|
products?: AiJobStepRunner;
|
|
292
303
|
/** Whether a part may be built for this member, before its first pass. */
|
|
293
304
|
contentRefusal?: typeof aiSiteContentRefusal;
|
|
305
|
+
/** The store's own pages' pass and whether they may be added here (AGL-3676); specs hand in fakes. */
|
|
306
|
+
storePages?: AiJobStepRunner;
|
|
307
|
+
storePagesRefusal?: typeof aiSiteStorePagesRefusal;
|
|
294
308
|
/** A dataset's pass and whether one may be made here (AGL-3616); specs hand in fakes. */
|
|
295
309
|
dataset?: AiJobStepRunner;
|
|
296
310
|
datasetRefusal?: typeof aiSiteDatasetRefusal;
|
|
@@ -328,6 +342,8 @@ export declare function aiSiteLedgerUnits(units: readonly AiSiteUnit[]): AiBuild
|
|
|
328
342
|
export declare function aiSiteInitialLedger(units: readonly AiSiteUnit[], outputs: readonly AiJobOutput[]): AiJobItemLedger[];
|
|
329
343
|
export declare function createAiJobSiteStep(deps?: AiJobSiteStepDeps): AiJobStepRunner;
|
|
330
344
|
export declare const runAiJobSiteStep: AiJobStepRunner;
|
|
345
|
+
/** ASSUMED: what the store pages' pass needs — five draft transactions and their search listings, no model. */
|
|
346
|
+
export declare const AI_SITE_STORE_PAGES_MINIMUM_MS = 15000;
|
|
331
347
|
/**
|
|
332
348
|
* The least time a scaffold's next pass needs (AGL-3035). The scaffold asks no
|
|
333
349
|
* model itself: each pass hands one unit to the step registered for its kind,
|
|
@@ -28,17 +28,19 @@ import { aiModelForStep } from "../providers/routing.js";
|
|
|
28
28
|
import { registerAiJobAdmission } from "./ai-job-admission.js";
|
|
29
29
|
import { aiOriginJobId, aiRecordedJobDraftId } from "./ai-job-draft-ids.js";
|
|
30
30
|
import { readAiDraftNodes } from "./ai-job-drafts.js";
|
|
31
|
-
import { AI_STOCK_PHOTO_PAGES_INPUT } from "./ai-layout-stock-photos.js";
|
|
31
|
+
import { AI_STOCK_PHOTO_AVOID_INPUT, AI_STOCK_PHOTO_PAGES_INPUT } from "./ai-layout-stock-photos.js";
|
|
32
32
|
import { AI_LAYOUT_SITE_ALIASES_INPUT, AI_LAYOUT_SITE_PAGES_INPUT, AI_LAYOUT_SITE_PAGES_MAX, aiLayoutIsHomeSlug, aiLayoutSitePagesOfPlan } from "./ai-job-layout-site-pages.js";
|
|
33
33
|
import { aiPageSectionNodeId } from "./ai-job-page-sections.js";
|
|
34
|
-
import { AI_LAYOUT_LISTINGS_INPUT } from "../layout-language/ai-layout-listings.js";
|
|
34
|
+
import { AI_LAYOUT_LISTINGS_INPUT, aiLayoutIsShopPage } from "../layout-language/ai-layout-listings.js";
|
|
35
35
|
import { aiSiteKindOfInputs } from "../model/ai-site-kinds.js";
|
|
36
36
|
import { AI_LAYOUT_FORM_PAGE_INPUT, AI_LAYOUT_LANGUAGE_INPUT, aiLayoutFormPageOfPlan } from "./ai-job-page-language.js";
|
|
37
37
|
import { aiJobPublishesSite, aiPublishGuidedSite } from "./ai-site-publish.js";
|
|
38
38
|
import { AI_SITE_CONTENT_INPUT, AI_SITE_POST_BUDGET, AI_SITE_POSTS, AI_SITE_POSTS_LABEL, AI_SITE_PRODUCTS_LABEL, AI_SITE_PRODUCTS_PRICE_NOTE, aiPublishSitePosts, aiSiteContentBriefLines, aiSiteContentPart, aiSiteContentRefusal, aiSiteListings, aiSiteProductsBriefLine, createAiSiteProductsRunner, runAiSitePostsUnit } from "./ai-job-site-content.js";
|
|
39
39
|
import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cache";
|
|
40
|
-
import { AI_SITE_DATASET_BUDGET, AI_SITE_DATASET_INPUT, AI_SITE_DATASETS_MAX, aiSiteDatasetBriefLines, aiSiteDatasetInputOf, aiSiteDatasetListings, aiSiteDatasetRefusal, aiSitePlanDatasets, aiSiteRecordTemplateOf, runAiSiteDatasetUnit } from "./ai-job-site-datasets.js";
|
|
40
|
+
import { AI_SITE_DATASET_BUDGET, AI_SITE_DATASET_INPUT, AI_SITE_DATASETS_MAX, aiSiteDatasetBriefLines, aiSiteDatasetInputOf, aiSiteDatasetListings, aiSiteDatasetPlacedPhotos, aiSiteDatasetRefusal, aiSitePlanDatasets, aiSiteRecordTemplateOf, runAiSiteDatasetUnit } from "./ai-job-site-datasets.js";
|
|
41
41
|
import { AI_SITE_LOOK_BUDGET, aiRunSiteLook } from "./ai-job-site-look.js";
|
|
42
|
+
import { AI_SITE_STORE_PAGES_INPUT, aiSiteStorePagesRefusal, runAiSiteStorePagesUnit } from "./ai-job-site-store-pages.js";
|
|
43
|
+
import { AI_SITE_STORE_LINKS_INPUT, AI_SITE_STORE_PAGES_LABEL, AI_SITE_STORE_PAGES_NOTE, aiStoreFrameLinks, aiStorePagesOfPlan } from "../model/ai-site-store-pages.js";
|
|
42
44
|
import { AI_FORM_DATASET_MADE_INPUT } from "./ai-job-form-dataset.js";
|
|
43
45
|
import { aiConfirmedPlan, aiUnspentOutcome } from "./ai-job-generation.js";
|
|
44
46
|
import { AI_JOB_BRIEF_MAX_CHARS } from "./ai-job-text-step.js";
|
|
@@ -85,6 +87,11 @@ import { aiJobStepRunMinimumMs, aiJobStepRunnerFor, registerAiJobStep, registerA
|
|
|
85
87
|
dataset: {
|
|
86
88
|
jobKind: 'text',
|
|
87
89
|
resource: 'draft'
|
|
90
|
+
},
|
|
91
|
+
// The scaffold's own runner writes them, by code (`ai-job-site-store-pages.ts`).
|
|
92
|
+
store: {
|
|
93
|
+
jobKind: 'text',
|
|
94
|
+
resource: 'screen'
|
|
88
95
|
}
|
|
89
96
|
};
|
|
90
97
|
/** The unit kind a creation is built as, where a unit builds it. */ const CREATION_UNIT_KINDS = {
|
|
@@ -131,7 +138,7 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
|
|
|
131
138
|
* (one a pass, the largest part) and a welcome email — with nothing to
|
|
132
139
|
* spare, so a runner that never finishes is still bounded while a real site
|
|
133
140
|
* is not.
|
|
134
|
-
*/ export const AI_SITE_MAX_PASSES = AI_SITE_PAGES.max * (AI_SITE_MAX_SECTIONS + 1) + CREATION_UNITS.length + AI_SITE_DATASETS_MAX + AI_SITE_POSTS +
|
|
141
|
+
*/ export const AI_SITE_MAX_PASSES = AI_SITE_PAGES.max * (AI_SITE_MAX_SECTIONS + 1) + CREATION_UNITS.length + AI_SITE_DATASETS_MAX + AI_SITE_POSTS + 2;
|
|
135
142
|
/**
|
|
136
143
|
* The units a plan implies, in build order: the palette first, because a
|
|
137
144
|
* member reads it while the pages are still building; then the layout every
|
|
@@ -202,6 +209,15 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
|
|
|
202
209
|
label: screen.title
|
|
203
210
|
}));
|
|
204
211
|
});
|
|
212
|
+
// A paid store's own pages (AGL-3676): outside the plan's pages, after them.
|
|
213
|
+
if (options.content === 'products') {
|
|
214
|
+
units.push(_extends({
|
|
215
|
+
kind: 'store'
|
|
216
|
+
}, UNIT_KINDS.store, {
|
|
217
|
+
slot: 'store',
|
|
218
|
+
label: AI_SITE_STORE_PAGES_LABEL
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
205
221
|
if (options.welcomeEmail) {
|
|
206
222
|
units.push(_extends({
|
|
207
223
|
kind: 'email'
|
|
@@ -464,15 +480,18 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
|
|
|
464
480
|
// A page is told the pages built before it, whose photos it does not place
|
|
465
481
|
// again (AGL-3660): the maker's portrait in About is never a vase in Work.
|
|
466
482
|
if (unit.screen) {
|
|
467
|
-
var _job_outputs2;
|
|
483
|
+
var _job_outputs2, _job_outputs3;
|
|
468
484
|
const pages = ((_job_outputs2 = job.outputs) != null ? _job_outputs2 : []).filter((output)=>output.resource === 'screen' && output.hostId === job.hostId && output.id).map((output)=>output.id);
|
|
469
485
|
if (pages.length) unitInputs[AI_STOCK_PHOTO_PAGES_INPUT] = pages;
|
|
486
|
+
// Nor a photo the site's datasets' records already show (AGL-3616).
|
|
487
|
+
const records = aiSiteDatasetPlacedPhotos((_job_outputs3 = job.outputs) != null ? _job_outputs3 : []);
|
|
488
|
+
if (records.length) unitInputs[AI_STOCK_PHOTO_AVOID_INPUT] = records;
|
|
470
489
|
}
|
|
471
490
|
// The layout is built before the pages, so it is told them (AGL-3596): their
|
|
472
491
|
// ids are minted on the plan, and the platform writes the header's links.
|
|
473
492
|
// A page is told them too, so its buttons may go to a page built after it.
|
|
474
493
|
if (job.kind === 'site' && (unit.kind === 'layout' || unit.kind === 'page')) {
|
|
475
|
-
var
|
|
494
|
+
var _job_outputs4, _job_outputs5;
|
|
476
495
|
var _aiSiteKindOfInputs, _aiSiteKindOfInputs1;
|
|
477
496
|
// A guided start links every page the person asked for (AGL-3660), and
|
|
478
497
|
// the blog its first posts are written into, by its path, second after
|
|
@@ -511,7 +530,7 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
|
|
|
511
530
|
// The store's catalog and the blog, listed by the sections that show them,
|
|
512
531
|
// and the cart in a selling site's header (AGL-3676).
|
|
513
532
|
const listings = aiSiteListings({
|
|
514
|
-
outputs: (
|
|
533
|
+
outputs: (_job_outputs4 = job.outputs) != null ? _job_outputs4 : [],
|
|
515
534
|
screens: merged.screens,
|
|
516
535
|
sells: aiSiteSellsProducts(job),
|
|
517
536
|
// A store lists its catalog on its Shop page even when its first
|
|
@@ -528,20 +547,28 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
|
|
|
528
547
|
entry.id
|
|
529
548
|
]));
|
|
530
549
|
listings.push(...aiSiteDatasetListings({
|
|
531
|
-
outputs: (
|
|
550
|
+
outputs: (_job_outputs5 = job.outputs) != null ? _job_outputs5 : [],
|
|
532
551
|
datasets: aiSitePlanDatasets(plan),
|
|
533
552
|
screens: merged.screens,
|
|
534
553
|
delivered
|
|
535
554
|
}));
|
|
536
555
|
if (listings.length) unitInputs[AI_LAYOUT_LISTINGS_INPUT] = listings;
|
|
556
|
+
// A paid store's header links its account beside the cart, and its footer
|
|
557
|
+
// its account and policies (AGL-3676), by path: they are written after.
|
|
558
|
+
if (unit.kind === 'layout' && aiSiteAddsStorePages(job)) {
|
|
559
|
+
unitInputs[AI_SITE_STORE_LINKS_INPUT] = aiStoreFrameLinks(aiStorePagesOfPlan(merged.screens));
|
|
560
|
+
}
|
|
537
561
|
}
|
|
562
|
+
// The store's own pages (AGL-3676): which to write, what their words link,
|
|
563
|
+
// and the layout they render inside.
|
|
564
|
+
if (unit.kind === 'store') unitInputs[AI_SITE_STORE_PAGES_INPUT] = aiSiteStorePagesInputOf(plan, built);
|
|
538
565
|
// A site's pages and its layout are designed in the layout language and
|
|
539
566
|
// compiled (AGL-3660), and a page is told which page places the site's form.
|
|
540
567
|
if (unit.kind === 'layout' || unit.kind === 'page') {
|
|
541
|
-
var
|
|
568
|
+
var _job_outputs6;
|
|
542
569
|
var _look_proposal;
|
|
543
570
|
// The look designed first (AGL-3660): its header arrangement and band rhythm.
|
|
544
|
-
const look = ((
|
|
571
|
+
const look = ((_job_outputs6 = job.outputs) != null ? _job_outputs6 : []).find((output)=>output.resource === 'theme' && output.id === 'look');
|
|
545
572
|
const style = look == null ? void 0 : (_look_proposal = look.proposal) == null ? void 0 : _look_proposal['style'];
|
|
546
573
|
if (style) unitInputs['siteStyle'] = {
|
|
547
574
|
headerAlign: style['headerAlign'],
|
|
@@ -553,11 +580,11 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
|
|
|
553
580
|
if (formPage) unitInputs[AI_LAYOUT_FORM_PAGE_INPUT] = formPage;
|
|
554
581
|
}
|
|
555
582
|
if (unit.kind === 'posts') {
|
|
556
|
-
var
|
|
583
|
+
var _job_outputs7, _inputs_businessName;
|
|
557
584
|
// The posts already written (one a pass), the addresses the site's own
|
|
558
585
|
// pages answer at, and the byline the person gave (AGL-3676).
|
|
559
586
|
const posts = {
|
|
560
|
-
written: ((
|
|
587
|
+
written: ((_job_outputs7 = job.outputs) != null ? _job_outputs7 : []).filter((output)=>output.resource === 'entry').map((output)=>({
|
|
561
588
|
id: output.id,
|
|
562
589
|
title: output.label
|
|
563
590
|
})),
|
|
@@ -594,6 +621,30 @@ export const AI_SITE_NO_PLAN_COPY = 'This site job has no confirmed plan to buil
|
|
|
594
621
|
brief: brief.join('\n').slice(0, AI_JOB_BRIEF_MAX_CHARS)
|
|
595
622
|
});
|
|
596
623
|
}
|
|
624
|
+
/** What a store pages unit writes (AGL-3676): the plan's store pages, their words' links, and the layout. */ export function aiSiteStorePagesInputOf(plan, built) {
|
|
625
|
+
var _path, _ref;
|
|
626
|
+
var _pages_find;
|
|
627
|
+
const pages = aiStorePagesOfPlan(plan.screens);
|
|
628
|
+
const path = (screen)=>screen ? `/${screen.slug.trim().replace(/^\/+/, '').split('/')[0].toLowerCase()}` : null;
|
|
629
|
+
const others = plan.screens.filter((screen)=>!screen.record && !aiLayoutIsHomeSlug(screen.slug));
|
|
630
|
+
return {
|
|
631
|
+
pages,
|
|
632
|
+
facts: {
|
|
633
|
+
contactPath: path(others.find((screen)=>/\b(contact|get in touch|visit us)\b/i.test(`${screen.slug.replace(/-/g, ' ')} ${screen.title}`))),
|
|
634
|
+
shopPath: (_path = path(others.find((screen)=>aiLayoutIsShopPage(screen)))) != null ? _path : '/shop',
|
|
635
|
+
shippingPath: (_ref = (_pages_find = pages.find((page)=>page.key === 'shipping')) == null ? void 0 : _pages_find.href) != null ? _ref : '/shipping-returns'
|
|
636
|
+
},
|
|
637
|
+
layoutId: aiSiteBuiltLayoutId(built)
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* Whether this site start adds a store's own pages (AGL-3676): its ledger
|
|
642
|
+
* owes them and has not given up on them. The ledger is written before the
|
|
643
|
+
* layout is built, so the header links the account before the page exists.
|
|
644
|
+
*/ export function aiSiteAddsStorePages(job) {
|
|
645
|
+
var _job_items;
|
|
646
|
+
return ((_job_items = job.items) != null ? _job_items : []).some((row)=>row.slot === 'store' && row.status !== 'skipped' && row.status !== 'failed');
|
|
647
|
+
}
|
|
597
648
|
/**
|
|
598
649
|
* Whether this site start writes its first posts (AGL-3676): its ledger owes
|
|
599
650
|
* the posts part and has not given up on it. The ledger is written before the
|
|
@@ -794,7 +845,7 @@ export const aiSiteJobAdmission = createAiSiteJobAdmission();
|
|
|
794
845
|
}).map((unit)=>unit.slot);
|
|
795
846
|
return units.map((unit)=>_extends({
|
|
796
847
|
slot: unit.slot,
|
|
797
|
-
op: unit.kind === 'theme' || unit.kind === 'posts' || unit.kind === 'products' || unit.kind === 'dataset' ? unit.kind : unit.jobKind,
|
|
848
|
+
op: unit.kind === 'theme' || unit.kind === 'posts' || unit.kind === 'products' || unit.kind === 'dataset' || unit.kind === 'store' ? unit.kind : unit.jobKind,
|
|
798
849
|
label: unit.label
|
|
799
850
|
}, unit.creation ? {
|
|
800
851
|
creation: unit.creation
|
|
@@ -833,24 +884,29 @@ export const aiSiteJobAdmission = createAiSiteJobAdmission();
|
|
|
833
884
|
});
|
|
834
885
|
}
|
|
835
886
|
/** The units a scaffold owes on this job: what its plan implies that this deployment can build. */ function aiSiteOwedUnits(job, plan, inputs, freeTaste, runnerFor) {
|
|
887
|
+
var _job_items;
|
|
888
|
+
// A start whose ledger was written before the store pages existed (AGL-3676)
|
|
889
|
+
// is not owed them: its ledger has no row for them to settle.
|
|
890
|
+
const ledgered = !((_job_items = job.items) == null ? void 0 : _job_items.length) || job.items.some((row)=>row.slot === 'store');
|
|
836
891
|
return aiSiteJobUnits(plan, {
|
|
837
892
|
welcomeEmail: aiSiteWelcomeEmail(inputs, freeTaste),
|
|
838
893
|
content: aiSiteContentPart(job.inputs, freeTaste)
|
|
839
|
-
}).filter((unit)=>unit.kind === 'theme' || unit.kind === 'posts' || unit.kind === 'dataset' || runnerFor(unit.jobKind));
|
|
894
|
+
}).filter((unit)=>unit.kind === 'store' && ledgered || unit.kind === 'theme' || unit.kind === 'posts' || unit.kind === 'dataset' || unit.kind !== 'store' && runnerFor(unit.jobKind));
|
|
840
895
|
}
|
|
841
896
|
export function createAiJobSiteStep(deps = {}) {
|
|
842
|
-
var _deps_runnerFor, _deps_readNodes, _deps_publish, _deps_look, _deps_contentRefusal, _deps_datasetRefusal, _deps_publishPosts, _deps_dropCache;
|
|
897
|
+
var _deps_runnerFor, _deps_readNodes, _deps_publish, _deps_look, _deps_contentRefusal, _deps_datasetRefusal, _deps_storePagesRefusal, _deps_publishPosts, _deps_dropCache;
|
|
843
898
|
const runnerFor = (_deps_runnerFor = deps.runnerFor) != null ? _deps_runnerFor : aiJobStepRunnerFor;
|
|
844
899
|
const readNodes = (_deps_readNodes = deps.readNodes) != null ? _deps_readNodes : readAiDraftNodes;
|
|
845
900
|
const publish = (_deps_publish = deps.publish) != null ? _deps_publish : aiPublishGuidedSite;
|
|
846
901
|
const look = (_deps_look = deps.look) != null ? _deps_look : aiRunSiteLook;
|
|
847
902
|
const contentRefusal = (_deps_contentRefusal = deps.contentRefusal) != null ? _deps_contentRefusal : aiSiteContentRefusal;
|
|
848
903
|
const datasetRefusal = (_deps_datasetRefusal = deps.datasetRefusal) != null ? _deps_datasetRefusal : aiSiteDatasetRefusal;
|
|
904
|
+
const storePagesRefusal = (_deps_storePagesRefusal = deps.storePagesRefusal) != null ? _deps_storePagesRefusal : aiSiteStorePagesRefusal;
|
|
849
905
|
const publishPosts = (_deps_publishPosts = deps.publishPosts) != null ? _deps_publishPosts : aiPublishSitePosts;
|
|
850
906
|
const dropCache = (_deps_dropCache = deps.dropCache) != null ? _deps_dropCache : dropPluginSiteCache;
|
|
851
907
|
return async (context)=>{
|
|
852
|
-
var _ref, _job_outputs, _deps_posts, _deps_products, _deps_dataset, _job_outputs1;
|
|
853
|
-
var _job_items, _rows_get, _rows_get1;
|
|
908
|
+
var _ref, _job_outputs, _deps_posts, _deps_products, _deps_dataset, _deps_storePages, _job_outputs1;
|
|
909
|
+
var _job_items, _rows_get, _rows_get1, _rows_get2;
|
|
854
910
|
const { job } = context;
|
|
855
911
|
// The scaffold asks no model of its own. What it names where it spends
|
|
856
912
|
// nothing is the model its plan ran on, which is the one exchange this
|
|
@@ -903,6 +959,8 @@ export function createAiJobSiteStep(deps = {}) {
|
|
|
903
959
|
now: context.now
|
|
904
960
|
}, postsRow ? {
|
|
905
961
|
blogUnwritten: !aiBuildItemDelivered(postsRow) || !((_postsRow_outputs = postsRow.outputs) == null ? void 0 : _postsRow_outputs.length)
|
|
962
|
+
} : {}, rows.get('store') ? {
|
|
963
|
+
unwrittenHrefs: unwrittenStoreHrefs(pages)
|
|
906
964
|
} : {})).catch((error)=>{
|
|
907
965
|
// The site is built either way; the pages stay drafts and say so.
|
|
908
966
|
console.error('ai site publish threw', {
|
|
@@ -951,11 +1009,18 @@ export function createAiJobSiteStep(deps = {}) {
|
|
|
951
1009
|
var _unit_screen;
|
|
952
1010
|
return unit.kind === 'page' && ((_unit_screen = unit.screen) == null ? void 0 : _unit_screen.record);
|
|
953
1011
|
}).map((unit)=>unit.slot));
|
|
1012
|
+
/** The paths of the store's pages the frame links that are not among the pages going live (AGL-3676). */ const unwrittenStoreHrefs = (pages)=>{
|
|
1013
|
+
const live = new Set(pages.map((page)=>{
|
|
1014
|
+
var _page_proposal;
|
|
1015
|
+
return (_page_proposal = page.proposal) == null ? void 0 : _page_proposal['storePage'];
|
|
1016
|
+
}).filter(Boolean));
|
|
1017
|
+
return aiStorePagesOfPlan(plan.screens).filter((page)=>!page.planned && !live.has(page.key)).map((page)=>page.href);
|
|
1018
|
+
};
|
|
954
1019
|
const builtPages = (extra = [])=>{
|
|
955
1020
|
var _job_outputs;
|
|
956
1021
|
const ids = new Set(units.filter((unit)=>{
|
|
957
1022
|
var _rows_get;
|
|
958
|
-
return unit.kind === 'page' && !templates.has(unit.slot) && aiBuildItemDelivered((_rows_get = rows.get(unit.slot)) != null ? _rows_get : {
|
|
1023
|
+
return (unit.kind === 'page' || unit.kind === 'store') && !templates.has(unit.slot) && aiBuildItemDelivered((_rows_get = rows.get(unit.slot)) != null ? _rows_get : {
|
|
959
1024
|
status: 'pending'
|
|
960
1025
|
});
|
|
961
1026
|
}).flatMap((unit)=>{
|
|
@@ -974,7 +1039,7 @@ export function createAiJobSiteStep(deps = {}) {
|
|
|
974
1039
|
const catalog = unit.kind === 'products' ? runnerFor('products') : undefined;
|
|
975
1040
|
const runner = unit.kind === 'theme' ? (lookContext)=>look(lookContext, lookContext.job) : unit.kind === 'posts' ? (_deps_posts = deps.posts) != null ? _deps_posts : runAiSitePostsUnit : unit.kind === 'products' ? (_deps_products = deps.products) != null ? _deps_products : catalog ? createAiSiteProductsRunner({
|
|
976
1041
|
catalog
|
|
977
|
-
}) : undefined : unit.kind === 'dataset' ? (_deps_dataset = deps.dataset) != null ? _deps_dataset : runAiSiteDatasetUnit : runnerFor(unit.jobKind);
|
|
1042
|
+
}) : undefined : unit.kind === 'dataset' ? (_deps_dataset = deps.dataset) != null ? _deps_dataset : runAiSiteDatasetUnit : unit.kind === 'store' ? (_deps_storePages = deps.storePages) != null ? _deps_storePages : runAiSiteStorePagesUnit : runnerFor(unit.jobKind);
|
|
978
1043
|
if (!runner) return _extends({}, aiUnspentOutcome(model), init);
|
|
979
1044
|
const othersOpen = ledgerUnits.some((one)=>{
|
|
980
1045
|
var _rows_get;
|
|
@@ -1053,6 +1118,25 @@ export function createAiJobSiteStep(deps = {}) {
|
|
|
1053
1118
|
note: `Not built: ${refusal}`
|
|
1054
1119
|
}));
|
|
1055
1120
|
}
|
|
1121
|
+
// The store's own pages ask the same (AGL-3676): a plan with a store, the
|
|
1122
|
+
// commerce plugin here, and a member who may edit the site's pages.
|
|
1123
|
+
if (unit.kind === 'store' && ((_rows_get2 = rows.get(unit.slot)) == null ? void 0 : _rows_get2.status) !== 'running') {
|
|
1124
|
+
const refusal = await storePagesRefusal(_extends({}, context, {
|
|
1125
|
+
job
|
|
1126
|
+
})).catch((error)=>{
|
|
1127
|
+
console.error('ai site store pages admission failed', {
|
|
1128
|
+
orgId: job.orgId,
|
|
1129
|
+
jobId: job.$id,
|
|
1130
|
+
error
|
|
1131
|
+
});
|
|
1132
|
+
return AI_SITE_UNIT_EMPTY_COPY;
|
|
1133
|
+
});
|
|
1134
|
+
if (refusal) return closing(settle({
|
|
1135
|
+
slot: unit.slot,
|
|
1136
|
+
status: 'skipped',
|
|
1137
|
+
note: `Not built: ${refusal}`
|
|
1138
|
+
}));
|
|
1139
|
+
}
|
|
1056
1140
|
// A record template whose dataset was not made has nothing to show
|
|
1057
1141
|
// (AGL-3616): it is not built, and the pages that list the dataset are
|
|
1058
1142
|
// built without it.
|
|
@@ -1152,7 +1236,7 @@ export function createAiJobSiteStep(deps = {}) {
|
|
|
1152
1236
|
outputs: outcome.outputs.map((output)=>output.id),
|
|
1153
1237
|
// A store's products are listed before they are priced (AGL-3676):
|
|
1154
1238
|
// the row says what is left before the store sells.
|
|
1155
|
-
note: unit.kind === 'products' && outcome.outputs.length ? AI_SITE_PRODUCTS_PRICE_NOTE : note
|
|
1239
|
+
note: unit.kind === 'products' && outcome.outputs.length ? AI_SITE_PRODUCTS_PRICE_NOTE : unit.kind === 'store' && outcome.outputs.length ? AI_SITE_STORE_PAGES_NOTE : note
|
|
1156
1240
|
}, degraded ? {
|
|
1157
1241
|
degradedBy: degradation.degradedBy
|
|
1158
1242
|
} : {}), spent);
|
|
@@ -1160,10 +1244,11 @@ export function createAiJobSiteStep(deps = {}) {
|
|
|
1160
1244
|
rows.set(unit.slot, _extends({}, rows.get(unit.slot), {
|
|
1161
1245
|
status: degraded ? 'degraded' : 'succeeded'
|
|
1162
1246
|
}));
|
|
1163
|
-
return finish(done, builtPages(unit.kind === 'page' && !templates.has(unit.slot) ? outcome.outputs.filter((output)=>output.resource === 'screen') : []));
|
|
1247
|
+
return finish(done, builtPages(unit.kind === 'page' && !templates.has(unit.slot) || unit.kind === 'store' ? outcome.outputs.filter((output)=>output.resource === 'screen') : []));
|
|
1164
1248
|
};
|
|
1165
1249
|
}
|
|
1166
1250
|
export const runAiJobSiteStep = createAiJobSiteStep();
|
|
1251
|
+
/** ASSUMED: what the store pages' pass needs — five draft transactions and their search listings, no model. */ export const AI_SITE_STORE_PAGES_MINIMUM_MS = 15000;
|
|
1167
1252
|
/**
|
|
1168
1253
|
* The least time a scaffold's next pass needs (AGL-3035). The scaffold asks no
|
|
1169
1254
|
* model itself: each pass hands one unit to the step registered for its kind,
|
|
@@ -1183,7 +1268,7 @@ export const runAiJobSiteStep = createAiJobSiteStep();
|
|
|
1183
1268
|
const units = aiSiteJobUnits(plan, {
|
|
1184
1269
|
welcomeEmail: inputs.welcomeEmail,
|
|
1185
1270
|
content: aiSiteContentPart(job.inputs, false)
|
|
1186
|
-
}).filter((unit)=>(unit.kind === 'theme' || unit.kind === 'posts' || unit.kind === 'dataset' || aiJobStepRunnerFor(unit.jobKind)) && (!owed.size || owed.has(unit.slot)));
|
|
1271
|
+
}).filter((unit)=>(unit.kind === 'theme' || unit.kind === 'posts' || unit.kind === 'dataset' || unit.kind === 'store' || aiJobStepRunnerFor(unit.jobKind)) && (!owed.size || owed.has(unit.slot)));
|
|
1187
1272
|
const outputs = (_job_outputs = job.outputs) != null ? _job_outputs : [];
|
|
1188
1273
|
const ledger = ((_job_items1 = job.items) == null ? void 0 : _job_items1.length) ? job.items : aiSiteInitialLedger(units, outputs);
|
|
1189
1274
|
const next = aiBuildNextUnit(aiSiteLedgerUnits(units), ledger);
|
|
@@ -1192,6 +1277,8 @@ export const runAiJobSiteStep = createAiJobSiteStep();
|
|
|
1192
1277
|
if (unit.kind === 'theme') return AI_SITE_LOOK_BUDGET.minimumMs;
|
|
1193
1278
|
if (unit.kind === 'posts') return AI_SITE_POST_BUDGET.minimumMs;
|
|
1194
1279
|
if (unit.kind === 'dataset') return AI_SITE_DATASET_BUDGET.minimumMs;
|
|
1280
|
+
// The store's pages ask no model: five draft writes (AGL-3676).
|
|
1281
|
+
if (unit.kind === 'store') return AI_SITE_STORE_PAGES_MINIMUM_MS;
|
|
1195
1282
|
return aiJobStepRunMinimumMs(aiSiteUnitJob(job, unit, aiBuildBuiltRefs(aiSiteLedgerUnits(units), ledger, outputs)));
|
|
1196
1283
|
}
|
|
1197
1284
|
/**
|