@aglyn/plugins-ai 1.0.0-beta.270 → 1.0.0-beta.272
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.d.ts +4 -3
- package/src/lib/activity/ai-activity-actions.js +4 -0
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/assets/site-styles/beauty.webp +0 -0
- package/src/lib/assets/site-styles/blog.webp +0 -0
- package/src/lib/assets/site-styles/business.webp +0 -0
- package/src/lib/assets/site-styles/coming-soon.webp +0 -0
- package/src/lib/assets/site-styles/education.webp +0 -0
- package/src/lib/assets/site-styles/events.webp +0 -0
- package/src/lib/assets/site-styles/fitness.webp +0 -0
- package/src/lib/assets/site-styles/landing.webp +0 -0
- package/src/lib/assets/site-styles/music.webp +0 -0
- package/src/lib/assets/site-styles/nonprofit.webp +0 -0
- package/src/lib/assets/site-styles/personal.webp +0 -0
- package/src/lib/assets/site-styles/photography.webp +0 -0
- package/src/lib/assets/site-styles/portfolio.webp +0 -0
- package/src/lib/assets/site-styles/professional.webp +0 -0
- package/src/lib/assets/site-styles/realestate.webp +0 -0
- package/src/lib/assets/site-styles/restaurant.webp +0 -0
- package/src/lib/assets/site-styles/store.webp +0 -0
- package/src/lib/assets/site-styles/studio.webp +0 -0
- package/src/lib/assets/site-styles/trades.webp +0 -0
- package/src/lib/assets/site-styles/wellness.webp +0 -0
- package/src/lib/assets/site-styles/yoga.webp +0 -0
- package/src/lib/components/ai-credits-card.component.d.ts +4 -3
- package/src/lib/components/ai-credits-card.component.js +15 -15
- package/src/lib/components/ai-credits-card.component.js.map +1 -1
- package/src/lib/components/ai-job-input-form.component.d.ts +36 -0
- package/src/lib/components/ai-job-input-form.component.js +100 -0
- package/src/lib/components/ai-job-input-form.component.js.map +1 -0
- package/src/lib/components/ai-job-requests.d.ts +4 -0
- package/src/lib/components/ai-job-requests.js +4 -0
- package/src/lib/components/ai-job-requests.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +18 -0
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-import-job.component.d.ts +41 -0
- package/src/lib/components/ai-site-import-job.component.js +707 -0
- package/src/lib/components/ai-site-import-job.component.js.map +1 -0
- package/src/lib/components/ai-site-import-start.component.d.ts +64 -0
- package/src/lib/components/ai-site-import-start.component.js +335 -0
- package/src/lib/components/ai-site-import-start.component.js.map +1 -0
- package/src/lib/components/ai-site-ownership-proof.component.d.ts +42 -0
- package/src/lib/components/ai-site-ownership-proof.component.js +353 -0
- package/src/lib/components/ai-site-ownership-proof.component.js.map +1 -0
- package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
- package/src/lib/components/ai-site-start-card.component.js +261 -119
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-site-start-steps.component.d.ts +43 -2
- package/src/lib/components/ai-site-start-steps.component.js +651 -15
- package/src/lib/components/ai-site-start-steps.component.js.map +1 -1
- package/src/lib/components/ai-site-start-tracking.d.ts +10 -0
- package/src/lib/components/ai-site-start-tracking.js +14 -0
- package/src/lib/components/ai-site-start-tracking.js.map +1 -1
- package/src/lib/components/ai-site-style-previews.js +26 -22
- package/src/lib/components/ai-site-style-previews.js.map +1 -1
- package/src/lib/components/ai-upsell-dialog.component.d.ts +7 -5
- package/src/lib/components/ai-upsell-dialog.component.js +8 -6
- package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
- package/src/lib/components/assist-build-card.component.js +11 -1
- package/src/lib/components/assist-build-card.component.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +9 -1
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +8 -4
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-card.component.js +4 -7
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
- package/src/lib/declarations.d.ts +2 -2
- package/src/lib/declarations.js +9 -5
- package/src/lib/declarations.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.js +3 -1
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-business-profile-prefill.d.ts +1 -1
- package/src/lib/jobs/ai-business-profile-prefill.js +14 -3
- package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.js +5 -3
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +14 -0
- package/src/lib/jobs/ai-job-drafts.js +24 -0
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-form-step.d.ts +15 -1
- package/src/lib/jobs/ai-job-form-step.js +75 -8
- package/src/lib/jobs/ai-job-form-step.js.map +1 -1
- package/src/lib/jobs/ai-job-import-step.d.ts +136 -0
- package/src/lib/jobs/ai-job-import-step.js +809 -0
- package/src/lib/jobs/ai-job-import-step.js.map +1 -0
- package/src/lib/jobs/ai-job-page-language.d.ts +52 -1
- package/src/lib/jobs/ai-job-page-language.js +99 -5
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +19 -2
- package/src/lib/jobs/ai-job-page-step.js +42 -18
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +25 -2
- package/src/lib/jobs/ai-job-plan-step.js +103 -29
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-settings-step.js +4 -3
- package/src/lib/jobs/ai-job-settings-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +27 -0
- package/src/lib/jobs/ai-job-site-datasets.js +140 -14
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-job-site-look.js +18 -5
- package/src/lib/jobs/ai-job-site-look.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +16 -1
- package/src/lib/jobs/ai-job-site-step.js +95 -22
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.d.ts +15 -1
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-job-tracking-step.d.ts +14 -3
- package/src/lib/jobs/ai-job-tracking-step.js +28 -5
- package/src/lib/jobs/ai-job-tracking-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +13 -1
- package/src/lib/jobs/ai-jobs.js +77 -10
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-owner-photos.d.ts +16 -1
- package/src/lib/jobs/ai-layout-owner-photos.js +47 -0
- package/src/lib/jobs/ai-layout-owner-photos.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +27 -2
- package/src/lib/jobs/ai-layout-stock-photos.js +190 -19
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-site-inspiration-signals.d.ts +98 -0
- package/src/lib/jobs/ai-site-inspiration-signals.js +585 -0
- package/src/lib/jobs/ai-site-inspiration-signals.js.map +1 -0
- package/src/lib/jobs/ai-site-inspiration.d.ts +157 -0
- package/src/lib/jobs/ai-site-inspiration.js +346 -0
- package/src/lib/jobs/ai-site-inspiration.js.map +1 -0
- package/src/lib/jobs/ai-site-publish.d.ts +26 -1
- package/src/lib/jobs/ai-site-publish.js +162 -13
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/ai-theme-brand-inputs.d.ts +77 -3
- package/src/lib/jobs/ai-theme-brand-inputs.js +135 -31
- package/src/lib/jobs/ai-theme-brand-inputs.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.d.ts +97 -0
- package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js +137 -0
- package/src/lib/jobs/fixtures/ai-candle-shop-free-plan-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-site-inspiration-pages.d.ts +38 -0
- package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js +103 -0
- package/src/lib/jobs/fixtures/ai-site-inspiration-pages.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.d.ts +25 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js +174 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3988-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +1 -1
- package/src/lib/layout-language/ai-layout-compiler.js +438 -25
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-contact.d.ts +57 -0
- package/src/lib/layout-language/ai-layout-contact.js +137 -0
- package/src/lib/layout-language/ai-layout-contact.js.map +1 -0
- package/src/lib/layout-language/ai-layout-copy.d.ts +12 -0
- package/src/lib/layout-language/ai-layout-copy.js +34 -2
- package/src/lib/layout-language/ai-layout-copy.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +65 -8
- package/src/lib/layout-language/ai-layout-design.js +411 -97
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.js +23 -0
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-labels.d.ts +2 -0
- package/src/lib/layout-language/ai-layout-labels.js +12 -6
- package/src/lib/layout-language/ai-layout-labels.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.d.ts +3 -2
- package/src/lib/layout-language/ai-layout-language.js +4 -3
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/layout-language/ai-layout-links.d.ts +13 -0
- package/src/lib/layout-language/ai-layout-links.js +30 -0
- package/src/lib/layout-language/ai-layout-links.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +49 -6
- package/src/lib/layout-language/ai-layout-pictures.js +145 -22
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-build-job.d.ts +16 -1
- package/src/lib/model/ai-build-job.js +40 -0
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-credit-estimate.d.ts +20 -0
- package/src/lib/model/ai-credit-estimate.js +13 -0
- package/src/lib/model/ai-credit-estimate.js.map +1 -1
- package/src/lib/model/ai-job-activity.d.ts +2 -2
- package/src/lib/model/ai-job-activity.js +6 -4
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-job-input.d.ts +33 -0
- package/src/lib/model/ai-job-input.js +59 -0
- package/src/lib/model/ai-job-input.js.map +1 -0
- package/src/lib/model/ai-job-notice.d.ts +4 -0
- package/src/lib/model/ai-job-notice.js +28 -4
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +69 -3
- package/src/lib/model/ai-jobs.types.js +2 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-details.d.ts +122 -0
- package/src/lib/model/ai-site-details.js +300 -0
- package/src/lib/model/ai-site-details.js.map +1 -0
- package/src/lib/model/ai-site-entity.d.ts +181 -0
- package/src/lib/model/ai-site-entity.js +390 -0
- package/src/lib/model/ai-site-entity.js.map +1 -0
- package/src/lib/model/ai-site-import-view.d.ts +130 -0
- package/src/lib/model/ai-site-import-view.js +182 -0
- package/src/lib/model/ai-site-import-view.js.map +1 -0
- package/src/lib/model/ai-site-import.d.ts +302 -0
- package/src/lib/model/ai-site-import.js +526 -0
- package/src/lib/model/ai-site-import.js.map +1 -0
- package/src/lib/model/ai-site-inspiration.d.ts +72 -0
- package/src/lib/model/ai-site-inspiration.js +109 -0
- package/src/lib/model/ai-site-inspiration.js.map +1 -0
- package/src/lib/model/ai-site-job.d.ts +13 -1
- package/src/lib/model/ai-site-job.js +15 -2
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.js +109 -21
- package/src/lib/model/ai-site-kinds.js.map +1 -1
- package/src/lib/model/ai-site-look.d.ts +10 -0
- package/src/lib/model/ai-site-look.js +24 -0
- package/src/lib/model/ai-site-look.js.map +1 -1
- package/src/lib/model/ai-site-start-seo.d.ts +19 -5
- package/src/lib/model/ai-site-start-seo.js +25 -7
- package/src/lib/model/ai-site-start-seo.js.map +1 -1
- package/src/lib/model/ai-site-start-steps.d.ts +9 -3
- package/src/lib/model/ai-site-start-steps.js +67 -5
- package/src/lib/model/ai-site-start-steps.js.map +1 -1
- package/src/lib/model/ai-site-start.d.ts +81 -4
- package/src/lib/model/ai-site-start.js +207 -13
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/model/ai-upsell-copy.js +4 -1
- package/src/lib/model/ai-upsell-copy.js.map +1 -1
- package/src/lib/plan-entitlements.d.ts +4 -3
- package/src/lib/plan-entitlements.js +3 -8
- package/src/lib/plan-entitlements.js.map +1 -1
- package/src/lib/runtime/ai-dataset-generation.d.ts +8 -1
- package/src/lib/runtime/ai-dataset-generation.js +12 -2
- package/src/lib/runtime/ai-dataset-generation.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +26 -33
- package/src/lib/runtime/ai-doctrine-validators.js +54 -44
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +1 -1
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-node-tree.d.ts +7 -0
- package/src/lib/runtime/ai-node-tree.js +9 -1
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/server/ai-create-route.d.ts +1 -1
- package/src/lib/server/ai-create-route.js +3 -4
- package/src/lib/server/ai-create-route.js.map +1 -1
- package/src/lib/server/ai-jobs-resume.js +27 -3
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/server/ai-jobs-route.js +18 -0
- package/src/lib/server/ai-jobs-route.js.map +1 -1
- package/src/lib/server/ai-site-import-route.d.ts +89 -0
- package/src/lib/server/ai-site-import-route.js +356 -0
- package/src/lib/server/ai-site-import-route.js.map +1 -0
- package/src/lib/server.js +14 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/site-import/fixtures/site-import-crawl-recordings.d.ts +45 -0
- package/src/lib/site-import/fixtures/site-import-crawl-recordings.js +313 -0
- package/src/lib/site-import/fixtures/site-import-crawl-recordings.js.map +1 -0
- package/src/lib/site-import/fixtures/site-import-wave-recordings.d.ts +50 -0
- package/src/lib/site-import/fixtures/site-import-wave-recordings.js +217 -0
- package/src/lib/site-import/fixtures/site-import-wave-recordings.js.map +1 -0
- package/src/lib/site-import/site-import-crawl.d.ts +163 -0
- package/src/lib/site-import/site-import-crawl.js +467 -0
- package/src/lib/site-import/site-import-crawl.js.map +1 -0
- package/src/lib/site-import/site-import-flag.d.ts +23 -0
- package/src/lib/site-import/site-import-flag.js +23 -0
- package/src/lib/site-import/site-import-flag.js.map +1 -0
- package/src/lib/site-import/site-import-html.d.ts +94 -0
- package/src/lib/site-import/site-import-html.js +462 -0
- package/src/lib/site-import/site-import-html.js.map +1 -0
- package/src/lib/site-import/site-import-images.d.ts +54 -0
- package/src/lib/site-import/site-import-images.js +204 -0
- package/src/lib/site-import/site-import-images.js.map +1 -0
- package/src/lib/site-import/site-import-page.d.ts +48 -0
- package/src/lib/site-import/site-import-page.js +150 -0
- package/src/lib/site-import/site-import-page.js.map +1 -0
- package/src/lib/site-import/site-import-review.d.ts +40 -0
- package/src/lib/site-import/site-import-review.js +149 -0
- package/src/lib/site-import/site-import-review.js.map +1 -0
- package/src/lib/site-import/site-import-robots.d.ts +52 -0
- package/src/lib/site-import/site-import-robots.js +126 -0
- package/src/lib/site-import/site-import-robots.js.map +1 -0
- package/src/lib/usage/assist-credits.d.ts +4 -4
- package/src/lib/usage/assist-credits.js +4 -4
- package/src/lib/usage/assist-credits.js.map +1 -1
- package/src/lib/usage/staff-org-ai-view.js +6 -3
- package/src/lib/usage/staff-org-ai-view.js.map +1 -1
- package/src/lib/usage/staff-org-ai.d.ts +4 -2
- package/src/lib/usage/staff-org-ai.js +7 -7
- package/src/lib/usage/staff-org-ai.js.map +1 -1
- package/src/lib/usage/staff-org-usage-ai.js +2 -2
- package/src/lib/usage/staff-org-usage-ai.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-settings-step.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 { supportedTimeZones } from '@aglyn/aglyn/app-utils/collection-entry-date'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { inheritedMediaAlt } from '@aglyn/aglyn/app-utils/media-metadata'\nimport { mediaNodeSrc } from '@aglyn/aglyn/app-utils/media-ref'\nimport { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { visibleToHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { SCREEN_ROOT_PATH } from '@aglyn/aglyn/app-utils/screen-route'\nimport { siteLanguageCode } from '@aglyn/aglyn/app-utils/seo-locale'\nimport { isSeoTitleVariable } from '@aglyn/aglyn/app-utils/seo-title-variables'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport type { PluginAiCapabilityArgsSchema } from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { syncHostProjectionForMembers } from '@aglyn/tenant-data-admin/server/host-memberships'\nimport { getLockdownVerdict } from '@aglyn/tenant-data-admin/server/lockdown'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport { FieldValue, type Firestore } from 'firebase-admin/firestore'\nimport type { AiJobOutput } from '../model/ai-jobs.types'\nimport { registerAiJobAdmission, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiJobEditModel } from './ai-job-edit-step'\nimport { aiLimitReview, aiUnspentOutcome } from './ai-job-generation'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `settings` step (AGL-3748): a change to what a member can change in the\n * site's settings — its name, its search defaults, its favicon, logo and\n * social image from the media library, its language, its time zone, its\n * contact details and its consent banner's mode — as a first-class build\n * operation, so \"rename my site to Juniper Clay Studio\", \"make the site\n * Spanish\" or \"set the SEO description\" is one item on Assist's plan card.\n *\n * NO MODEL. The planner already wrote the values as the item's arguments;\n * this checks each against the rule the console's own control holds it to\n * and writes it, so it costs no credits.\n *\n * ── Only the console's own fields ────────────────────────────────────────\n *\n * Every field written is one a settings card or form writes, under the same\n * name and to the same limit: `displayName` (Admin → General, 30), `seo.title`\n * (60), `seo.description` (155), `seo.separator` (3), `seo.titlePattern`\n * (120, its own variables only), `seo.entity.name`, `seo.entity.description`\n * (300), `seo.entity.telephone`, `business.supportEmail` and\n * `business.address` (the Business details card), `locales`/`defaultLocale`\n * (the Languages card), `timeZone` (one of the zones the console offers),\n * `consent.mode` (the consent banner card), and `seo.favicon`, `logoUrl` and\n * `seo.image` (with its size and alt) written as the media pickers write\n * them, through `mediaNodeSrc`.\n *\n * NEVER: the address (subdomain, domains), plugins, members and roles,\n * billing, lockdown, deletion, search engine indexing, analytics and\n * advertising ids, verification tokens, or turning the consent banner off.\n * Those are not arguments, so the planner cannot ask for them.\n *\n * ── Live on confirm ──────────────────────────────────────────────────────\n *\n * A setting has no draft: the console saves it straight to the host\n * document, and the tenant renders it from there. So does this, once the\n * person confirms the plan (Zach, 2026-10-12: live on confirm). The write\n * drops the site's cached pages as the console's door does\n * (`host-document-writes.ts`), with a site-wide publish outbox entry in the\n * same transaction for the drain to fire if the drop does not land.\n *\n * ── Who may ──────────────────────────────────────────────────────────────\n *\n * The Firestore rules' own for these keys (`canWriteHostContent` and\n * `hostWritesFrozen`): a member with a write role on the site (admin, editor\n * or author), while no lockdown freezes the site, its org or the platform.\n * Asked at the door, again before the write, and the role once more inside\n * the transaction that writes.\n *\n * ── Idempotent ───────────────────────────────────────────────────────────\n *\n * The transaction compares each value with what is stored and writes only\n * what differs, so a pass asked again writes nothing and announces nothing.\n */\n\nexport const AI_SETTINGS_KIND = 'settings' as const\n\n/**\n * ASSUMED: the least time the step needs — the site, the org, up to three\n * media lookups in two libraries, and the one transaction that writes. No\n * model, so it fits an inline door.\n */\nexport const AI_JOB_SETTINGS_STEP_MINIMUM_MS = 5_000\n\nexport const AI_SETTINGS_NO_SITE_COPY = 'Open the site whose settings should change.'\nexport const AI_SETTINGS_ROLE_COPY = 'Changing the site’s settings needs the admin, editor or author role on this site.'\nexport const AI_SETTINGS_LOCKED_COPY = 'This site is not accepting changes right now.'\nexport const AI_SETTINGS_NOTHING_COPY = 'The request named no site setting this could change.'\n\n/** Where a person finds what the op changed, as the build's notes name it. */\nexport const AI_SETTINGS_WHERE = 'the site’s Settings'\n\n/** The output's fixed id: one site's settings, whatever changed. */\nexport const AI_SETTINGS_OUTPUT_ID = 'site'\n\n/** The console's own limits, each from the control that edits the field. */\nexport const AI_SETTINGS_LIMITS = {\n /** Admin → General, Display name. */\n name: 30,\n /** Setup → SEO, Title. */\n seoTitle: 60,\n /** Setup → SEO, Description. */\n seoDescription: 155,\n /** Setup → SEO, Separator. */\n separator: 3,\n /** Setup → SEO, Title pattern. */\n titlePattern: 120,\n /** Setup → SEO, the publisher's description. */\n businessDescription: 300,\n businessName: 100,\n telephone: 40,\n supportEmail: 254,\n address: 200,\n} as const\n\n/** The consent banner card's two modes; turning the banner off is not offered. */\nexport const AI_SETTINGS_CONSENT_MODES = ['geo', 'strict'] as const\n\n/** The media a setting may name, and the field each is written to. */\nexport const AI_SETTINGS_MEDIA_ARGS = ['favicon', 'logo', 'socialImage'] as const\nexport type AiSettingsMediaArg = (typeof AI_SETTINGS_MEDIA_ARGS)[number]\n\nconst MEDIA_ARG_NOUNS: Readonly<Record<AiSettingsMediaArg, string>> = {\n favicon: 'favicon',\n logo: 'logo',\n socialImage: 'social image',\n}\n\n/** The item's arguments, as the planner fills them in. Every one optional. */\nexport const AI_SETTINGS_ARGS_SCHEMA: PluginAiCapabilityArgsSchema = {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n description: 'The site’s name as the console lists it. Visitors see the search title, not this.',\n maxLength: AI_SETTINGS_LIMITS.name,\n },\n seoTitle: {\n type: 'string',\n description: 'The search title pages fall back to, usually the business’s name.',\n maxLength: AI_SETTINGS_LIMITS.seoTitle,\n },\n seoDescription: {\n type: 'string',\n description: 'The search description pages fall back to: one or two plain sentences.',\n maxLength: AI_SETTINGS_LIMITS.seoDescription,\n },\n separator: {\n type: 'string',\n description: 'The character between a page’s name and the site title, such as | or ·.',\n maxLength: AI_SETTINGS_LIMITS.separator,\n },\n titlePattern: {\n type: 'string',\n description: 'How an untitled page is titled, using only {{page.name}}, {{site.separator}} and {{site.name}}.',\n maxLength: AI_SETTINGS_LIMITS.titlePattern,\n },\n businessName: {\n type: 'string',\n description: 'The business’s name as search engines and AI assistants are told it.',\n maxLength: AI_SETTINGS_LIMITS.businessName,\n },\n businessDescription: {\n type: 'string',\n description: 'What the business is, in one sentence (its tagline), as search engines and AI assistants are told it.',\n maxLength: AI_SETTINGS_LIMITS.businessDescription,\n },\n telephone: {\n type: 'string',\n description: 'The business’s phone number, exactly as the person gave it.',\n maxLength: AI_SETTINGS_LIMITS.telephone,\n },\n supportEmail: {\n type: 'string',\n description: 'The contact email pages and emails show, exactly as the person gave it.',\n maxLength: AI_SETTINGS_LIMITS.supportEmail,\n },\n address: {\n type: 'string',\n description: 'The business’s address on one line, exactly as the person gave it.',\n maxLength: AI_SETTINGS_LIMITS.address,\n },\n language: {\n type: 'string',\n description: 'The language the site is in, as a code such as en, es, fr or pt-BR. It does not translate the pages.',\n maxLength: 10,\n },\n timeZone: {\n type: 'string',\n description: 'The site’s time zone as an IANA name, such as America/Chicago.',\n maxLength: 64,\n },\n consentMode: {\n type: 'string',\n description: 'The cookie banner’s mode: geo (asks only where the law requires) or strict (asks every visitor).',\n enum: AI_SETTINGS_CONSENT_MODES,\n },\n favicon: {\n type: 'string',\n description: 'An image already in the media library, by its id or exact file name, for the browser tab icon.',\n maxLength: 200,\n },\n logo: {\n type: 'string',\n description: 'An image already in the media library, by its id or exact file name, for the site’s logo.',\n maxLength: 200,\n },\n socialImage: {\n type: 'string',\n description: 'An image already in the media library, by its id or exact file name, for the card shown when a page is shared.',\n maxLength: 200,\n },\n },\n additionalProperties: false,\n}\n\n/** What an item asks, each value read and held to the console's rule. */\nexport interface AiSettingsRequest {\n name?: string\n seoTitle?: string\n seoDescription?: string\n separator?: string\n titlePattern?: string\n businessName?: string\n businessDescription?: string\n telephone?: string\n supportEmail?: string\n address?: string\n /** A language code the Languages card would accept. */\n language?: string\n timeZone?: string\n consentMode?: (typeof AI_SETTINGS_CONSENT_MODES)[number]\n favicon?: string\n logo?: string\n socialImage?: string\n}\n\nexport interface AiSettingsRead {\n request: AiSettingsRequest\n /** A sentence per value that could not be used, so the rest still are. */\n problems: string[]\n}\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim() : '')\nconst EMAIL = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\nconst TELEPHONE = /^[+()0-9][0-9 ()+.\\-/]{4,38}[0-9)]$/\nconst PATTERN_VARIABLE = /\\{\\{\\s*([^}]*?)\\s*\\}\\}/g\n\n/** Whether a title pattern names only the variables the renderer knows. */\nexport function aiSettingsTitlePatternOk(pattern: string): boolean {\n if (/[{}]/.test(pattern.replace(PATTERN_VARIABLE, ''))) return false\n return [...pattern.matchAll(PATTERN_VARIABLE)].every((match) => isSeoTitleVariable(match[1]))\n}\n\n/** The item's arguments read: what may be written, and a sentence for each that may not. */\nexport function aiSettingsRequestOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiSettingsRead {\n const request: AiSettingsRequest = {}\n const problems: string[] = []\n const bounded = (key: keyof typeof AI_SETTINGS_LIMITS, noun: string) => {\n const value = text(inputs?.[key])\n if (!value) return\n if (value.length > AI_SETTINGS_LIMITS[key]) {\n problems.push(`The ${noun} is longer than ${AI_SETTINGS_LIMITS[key]} characters.`)\n return\n }\n request[key] = value\n }\n bounded('name', 'site name')\n bounded('seoTitle', 'search title')\n bounded('seoDescription', 'search description')\n bounded('separator', 'title separator')\n bounded('businessName', 'business name')\n bounded('businessDescription', 'business description')\n bounded('address', 'address')\n bounded('titlePattern', 'title pattern')\n if (request.titlePattern && !aiSettingsTitlePatternOk(request.titlePattern)) {\n problems.push('The title pattern may use only {{page.name}}, {{site.separator}} and {{site.name}}.')\n delete request.titlePattern\n }\n bounded('telephone', 'phone number')\n if (request.telephone && !TELEPHONE.test(request.telephone)) {\n problems.push(`“${request.telephone}” is not a phone number.`)\n delete request.telephone\n }\n bounded('supportEmail', 'contact email')\n if (request.supportEmail && !EMAIL.test(request.supportEmail)) {\n problems.push(`“${request.supportEmail}” is not an email address.`)\n delete request.supportEmail\n }\n const language = text(inputs?.['language'])\n if (language) {\n const locale = siteLanguageCode(language)\n if (locale) request.language = locale\n else problems.push(`“${language}” is not a language code such as es or pt-BR.`)\n }\n const timeZone = text(inputs?.['timeZone'])\n if (timeZone) {\n if (supportedTimeZones().includes(timeZone)) request.timeZone = timeZone\n else problems.push(`“${timeZone}” is not a time zone the site settings offer.`)\n }\n const consentMode = text(inputs?.['consentMode'])\n if (consentMode) {\n if ((AI_SETTINGS_CONSENT_MODES as readonly string[]).includes(consentMode)) {\n request.consentMode = consentMode as AiSettingsRequest['consentMode']\n } else problems.push('The cookie banner’s mode is geo or strict.')\n }\n for (const key of AI_SETTINGS_MEDIA_ARGS) {\n const value = text(inputs?.[key])\n if (value) request[key] = value.slice(0, 200)\n }\n return { request, problems }\n}\n\n/** Whether a read request asks for anything at all. */\nexport function aiSettingsRequestEmpty(request: AiSettingsRequest): boolean {\n return Object.keys(request).length === 0\n}\n\n// ── Media ─────────────────────────────────────────────────────────────────\n\n/** A media library row, as a setting may name it. */\nexport interface AiSettingsMediaRow {\n id: string\n fileName: string\n contentType: string\n url: string | null\n cdnPath: string | null\n contentHash: string | null\n private: boolean\n deleted: boolean\n /** The org library's sites; `undefined` on the site's own library. */\n visibleTo?: readonly string[] | null\n width: number | null\n height: number | null\n alt: string\n}\n\n/** The image a setting names, as written to the host. */\nexport interface AiSettingsMedia {\n src: string\n width: number | null\n height: number | null\n alt: string\n fileName: string\n}\n\nfunction mediaRowOf(doc: FirebaseFirestore.QueryDocumentSnapshot | FirebaseFirestore.DocumentSnapshot, org: boolean): AiSettingsMediaRow {\n const data = (doc.data() ?? {}) as Record<string, unknown>\n const number = (value: unknown) => (typeof value === 'number' && value > 0 ? value : null)\n return {\n id: doc.id,\n fileName: String(data['fileName'] ?? ''),\n contentType: String(data['contentType'] ?? ''),\n url: typeof data['url'] === 'string' ? data['url'] : null,\n cdnPath: typeof data['cdnPath'] === 'string' ? data['cdnPath'] : null,\n contentHash: typeof data['contentHash'] === 'string' ? data['contentHash'] : null,\n private: data['private'] === true,\n deleted: data['deletedAt'] != null,\n ...(org ? { visibleTo: Array.isArray(data['visibleTo']) ? (data['visibleTo'] as string[]) : null } : {}),\n width: number(data['width']),\n height: number(data['height']),\n alt: typeof data['alt'] === 'string' ? data['alt'] : '',\n }\n}\n\n/**\n * The rows a media name could be, from the site's own library and then its\n * org's: the one whose id it is, else those whose file name it is.\n */\nexport async function readAiSettingsMediaRows(\n firestore: Firestore,\n input: { hostId: string; orgId: string; source: string },\n): Promise<AiSettingsMediaRow[]> {\n const libraries = [\n { ref: firestore.collection('hosts').doc(input.hostId).collection('media'), org: false },\n { ref: firestore.collection('orgs').doc(input.orgId).collection('media'), org: true },\n ]\n const rows: AiSettingsMediaRow[] = []\n for (const library of libraries) {\n if (/^[A-Za-z0-9_-]{1,128}$/.test(input.source)) {\n const byId = await library.ref.doc(input.source).get()\n if (byId.exists) rows.push(mediaRowOf(byId, library.org))\n }\n const byName = await library.ref.where('fileName', '==', input.source).limit(5).get()\n for (const doc of byName.docs) if (!rows.some((row) => row.id === doc.id)) rows.push(mediaRowOf(doc, library.org))\n }\n return rows\n}\n\n/**\n * The image a setting names, chosen from the rows read: a public image of\n * this site's library, or of its org's that this site may use, never deleted.\n * Its id wins over a file name; two images of one name are refused in words.\n */\nexport function aiSettingsMediaOf(\n arg: AiSettingsMediaArg,\n source: string,\n rows: readonly AiSettingsMediaRow[],\n hostId: string,\n): AiSettingsMedia | { error: string } {\n const noun = MEDIA_ARG_NOUNS[arg]\n const usable = rows.filter(\n (row) =>\n !row.deleted &&\n !row.private &&\n row.contentType.startsWith('image/') &&\n (row.visibleTo === undefined || visibleToHost(row.visibleTo, hostId)),\n )\n const byId = usable.find((row) => row.id === source)\n const named = usable.filter((row) => row.fileName === source)\n if (!byId && named.length > 1) return { error: `The media library has more than one image called “${source}”; name the one for the ${noun}.` }\n const row = byId ?? named[0]\n if (!row) return { error: `The media library has no image “${source}” this site can use for the ${noun}.` }\n const src = mediaNodeSrc({ url: row.url, cdnPath: row.cdnPath, contentHash: row.contentHash, private: row.private })\n if (!src) return { error: `The image “${source}” has no address the site can show.` }\n return { src, width: row.width, height: row.height, alt: row.alt, fileName: row.fileName || row.id }\n}\n\n// ── The write ─────────────────────────────────────────────────────────────\n\n/** One change to the host document, with the sentence that says it was made. */\ninterface AiSettingsChange {\n path: string\n value: unknown\n said: string\n}\n\nconst getPath = (data: Record<string, unknown>, path: string): unknown =>\n path.split('.').reduce<unknown>((at, key) => (at && typeof at === 'object' ? (at as Record<string, unknown>)[key] : undefined), data)\n\nconst sameValue = (a: unknown, b: unknown): boolean => JSON.stringify(a ?? null) === JSON.stringify(b ?? null)\n\n/**\n * The language change, as the Languages card would save it: a site with no\n * languages, or one, now publishes in this one; a multilingual site that\n * lists it makes it the default. A multilingual site that does not list it\n * is refused, because adding a language is the Languages card's decision.\n */\nexport function aiSettingsLanguageChanges(\n host: { locales?: unknown; defaultLocale?: unknown },\n language: string,\n): AiSettingsChange[] | { error: string } {\n const locales = Array.isArray(host.locales) ? host.locales.filter((one): one is string => typeof one === 'string') : []\n const said = `The site’s language is now ${language}.`\n if (locales.length > 1) {\n if (!locales.includes(language)) {\n return { error: `This site publishes in ${locales.join(', ')}; add ${language} in Settings → Details → Languages first.` }\n }\n return [{ path: 'defaultLocale', value: language, said }]\n }\n return [\n { path: 'locales', value: [language], said },\n { path: 'defaultLocale', value: language, said },\n ]\n}\n\n/** Every change the request asks of this host, before comparing with what is stored. */\nexport function aiSettingsChanges(\n host: Record<string, unknown>,\n request: AiSettingsRequest,\n media: Partial<Record<AiSettingsMediaArg, AiSettingsMedia>>,\n): { changes: AiSettingsChange[]; problems: string[] } {\n const changes: AiSettingsChange[] = []\n const problems: string[] = []\n const set = (path: string, value: unknown, said: string) => changes.push({ path, value, said })\n if (request.name) set('displayName', request.name, `The site is now called “${request.name}”.`)\n if (request.seoTitle) set('seo.title', request.seoTitle, `The search title is now “${request.seoTitle}”.`)\n if (request.seoDescription) set('seo.description', request.seoDescription, 'The search description is set.')\n if (request.separator) set('seo.separator', request.separator, `The title separator is now “${request.separator}”.`)\n if (request.titlePattern) set('seo.titlePattern', request.titlePattern, `Untitled pages are now titled “${request.titlePattern}”.`)\n if (request.businessName) set('seo.entity.name', request.businessName, `The business name is now “${request.businessName}”.`)\n if (request.businessDescription) set('seo.entity.description', request.businessDescription, 'The business description is set.')\n if (request.telephone) set('seo.entity.telephone', request.telephone, `The phone number is now ${request.telephone}.`)\n if (request.supportEmail) set('business.supportEmail', request.supportEmail, `The contact email is now ${request.supportEmail}.`)\n if (request.address) set('business.address', request.address, 'The address is set.')\n if (request.timeZone) set('timeZone', request.timeZone, `The site’s time zone is now ${request.timeZone}.`)\n if (request.consentMode) {\n set('consent.mode', request.consentMode, request.consentMode === 'strict' ? 'The cookie banner now asks every visitor.' : 'The cookie banner now asks where the law requires.')\n }\n if (request.language) {\n const language = aiSettingsLanguageChanges(host, request.language)\n if ('error' in language) problems.push(language.error)\n else changes.push(...language)\n }\n if (media.favicon) set('seo.favicon', media.favicon.src, `The favicon is now “${media.favicon.fileName}”.`)\n if (media.logo) set('logoUrl', media.logo.src, `The logo is now “${media.logo.fileName}”.`)\n if (media.socialImage) {\n const image = media.socialImage\n const said = `The social image is now “${image.fileName}”.`\n set('seo.image', image.src, said)\n // Its size and alt, as the Social image card copies them at pick time.\n set('seo.imageWidth', image.width ?? 0, said)\n set('seo.imageHeight', image.height ?? 0, said)\n const savedAlt = getPath(host, 'seo.imageAlt')\n set('seo.imageAlt', inheritedMediaAlt({ placementAlt: savedAlt, assetAlt: image.alt }) ?? (typeof savedAlt === 'string' ? savedAlt : ''), said)\n }\n return { changes, problems }\n}\n\nexport type AiSettingsWrite =\n | { ok: true; done: string[]; notDone: string[]; changed: string[]; outboxId: string | null }\n | { ok: false; error: string }\n\n/**\n * The settings written, in one transaction that reads the host as it is\n * stored NOW: the member still holds a write role, and only the values that\n * differ from what is stored are written, with a site-wide publish outbox\n * entry beside them so the live site's cached pages are dropped.\n */\nexport async function writeAiSiteSettings(\n firestore: Firestore,\n input: {\n hostId: string\n uid: string\n request: AiSettingsRequest\n media: Partial<Record<AiSettingsMediaArg, AiSettingsMedia>>\n now: Date\n },\n): Promise<AiSettingsWrite> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n return firestore.runTransaction(async (tx): Promise<AiSettingsWrite> => {\n const host = await tx.get(hostRef)\n if (!host.exists) return { ok: false, error: 'Unknown site' }\n const roles = (host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return { ok: false, error: AI_SETTINGS_ROLE_COPY }\n const data = (host.data() ?? {}) as Record<string, unknown>\n const { changes, problems } = aiSettingsChanges(data, input.request, input.media)\n const changed = changes.filter((change) => !sameValue(getPath(data, change.path), change.value))\n const done = [...new Set(changes.map((change) => change.said))]\n if (!changed.length) return { ok: true, done, notDone: problems, changed: [], outboxId: null }\n const updates: Record<string, unknown> = { updatedAt: input.now }\n for (const change of changed) updates[change.path] = change.value\n tx.update(hostRef, updates)\n // The announce, written down in the save's own transaction: a setting\n // has no address, so it asks for the whole site, as the console's does.\n const outboxRef = firestore.collection('publishOutbox').doc(createResourceUid())\n tx.set(outboxRef, {\n hostId: input.hostId,\n paths: [SCREEN_ROOT_PATH],\n entireHost: true,\n createdAt: FieldValue.serverTimestamp(),\n attempts: 0,\n })\n return { ok: true, done, notDone: problems, changed: changed.map((change) => change.path), outboxId: outboxRef.id }\n })\n}\n\n// ── The step ──────────────────────────────────────────────────────────────\n\n/** Why this member may not change this site's settings now, or `null`. */\nexport async function aiSettingsAccessRefusal(\n input: { host: FirebaseFirestore.DocumentSnapshot; uid: string; org: object | null },\n deps: { lockdown?: typeof getLockdownVerdict } = {},\n): Promise<string | null> {\n if (!input.host.exists) return 'Unknown site'\n const roles = (input.host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return AI_SETTINGS_ROLE_COPY\n const locked = await (deps.lockdown ?? getLockdownVerdict)({\n staff: false,\n uid: input.uid,\n org: input.org as never,\n host: input.host.data() as never,\n intent: 'write',\n })\n return locked ? AI_SETTINGS_LOCKED_COPY : null\n}\n\n/** The output's note: what changed, that it is live, and what was not done. */\nexport function aiSettingsOutputNote(done: readonly string[], notDone: readonly string[], language: boolean): string {\n // The build's own wording for an item with no draft state (AGL-3745).\n const did = done.length ? `${done.join(' ')} Live now, in ${AI_SETTINGS_WHERE}.` : 'Nothing needed changing.'\n const translate = language ? ' The pages keep the words they have: changing the language does not translate them.' : ''\n const not = notDone.length ? ` Not done: ${notDone.join(' ')}` : ''\n return `${did}${translate}${not}`.slice(0, 1_000)\n}\n\nexport interface AiJobSettingsStepDeps {\n readMedia?: typeof readAiSettingsMediaRows\n write?: typeof writeAiSiteSettings\n lockdown?: typeof getLockdownVerdict\n dropCache?: typeof dropPluginSiteCache\n syncMemberships?: typeof syncHostProjectionForMembers\n}\n\n/** Each media argument resolved to its image, or a sentence for each that cannot be. */\nasync function mediaOf(\n firestore: Firestore,\n input: { hostId: string; orgId: string; request: AiSettingsRequest },\n readMedia: typeof readAiSettingsMediaRows,\n): Promise<{ media: Partial<Record<AiSettingsMediaArg, AiSettingsMedia>>; problems: string[] }> {\n const media: Partial<Record<AiSettingsMediaArg, AiSettingsMedia>> = {}\n const problems: string[] = []\n for (const arg of AI_SETTINGS_MEDIA_ARGS) {\n const source = input.request[arg]\n if (!source) continue\n const rows = await readMedia(firestore, { hostId: input.hostId, orgId: input.orgId, source })\n const found = aiSettingsMediaOf(arg, source, rows, input.hostId)\n if ('error' in found) problems.push(found.error)\n else media[arg] = found\n }\n return { media, problems }\n}\n\nexport function createAiJobSettingsStep(deps: AiJobSettingsStepDeps = {}): AiJobStepRunner {\n const readMedia = deps.readMedia ?? readAiSettingsMediaRows\n const write = deps.write ?? writeAiSiteSettings\n const dropCache = deps.dropCache ?? dropPluginSiteCache\n const syncMemberships = deps.syncMemberships ?? syncHostProjectionForMembers\n return async ({ job, now, firestore, org, modelFor }): Promise<AiJobStepOutcome> => {\n // No model is asked: the outcome records the edit step's at no cost.\n const model = modelFor?.('job.edit') ?? aiJobEditModel()\n const unspent = (failure: string): AiJobStepOutcome => ({ ...aiUnspentOutcome(model), failure })\n const hostId = job.hostId\n if (!hostId) return unspent(AI_SETTINGS_NO_SITE_COPY)\n const read = aiSettingsRequestOf(job.inputs)\n if (aiSettingsRequestEmpty(read.request)) return unspent([AI_SETTINGS_NOTHING_COPY, ...read.problems].join(' '))\n const orgData = (org ?? ((await firestore.collection('orgs').doc(job.orgId).get()).data() ?? null)) as Record<string, unknown> | null\n const host = await firestore.collection('hosts').doc(hostId).get()\n const refused = await aiSettingsAccessRefusal({ host, uid: job.createdBy, org: orgData }, { lockdown: deps.lockdown })\n if (refused) return aiUnspentOutcome(model, { review: aiLimitReview(refused) })\n const found = await mediaOf(firestore, { hostId, orgId: job.orgId, request: read.request }, readMedia)\n const written = await write(firestore, { hostId, uid: job.createdBy, request: read.request, media: found.media, now })\n if (written.ok === false) return unspent(written.error)\n const notDone = [...read.problems, ...found.problems, ...written.notDone]\n if (!written.done.length) return unspent([AI_SETTINGS_NOTHING_COPY, ...notDone].join(' '))\n if (written.outboxId) {\n // The live site's cached pages go now; the outbox entry stays for the\n // drain unless the drop certainly happened.\n const dropped = await dropCache({ hostIds: [hostId], reason: 'AI build changed site settings' }).catch(() => ({ complete: false }))\n if (dropped.complete) await firestore.collection('publishOutbox').doc(written.outboxId).delete().catch(() => undefined)\n }\n if (written.changed.includes('displayName')) {\n // The site switcher lists sites by the name members' projections carry.\n await syncMemberships(job.orgId, hostId).catch((error: unknown) => {\n console.error('ai settings: membership projection not synced', { hostId, jobId: job.$id, error })\n })\n }\n const output: AiJobOutput = {\n resource: 'settings',\n id: AI_SETTINGS_OUTPUT_ID,\n hostId,\n hostSubdomain: (typeof host.get('subdomain') === 'string' && host.get('subdomain')) || null,\n label: 'Site settings',\n note: aiSettingsOutputNote(written.done, notDone, Boolean(read.request.language)),\n }\n return aiUnspentOutcome(model, { outputs: [output] })\n }\n}\n\nexport const runAiJobSettingsStep = createAiJobSettingsStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\nexport interface AiSettingsJobAdmissionDeps {\n ownerOf?: typeof resolveOrgIdForHost\n lockdown?: typeof getLockdownVerdict\n}\n\n/**\n * A settings change is admitted on a site of the job's own org, for a member\n * who may write its settings now, where the request names at least one\n * setting this can change. Refused in those words before anything is written.\n */\nexport function createAiSettingsJobAdmission(deps: AiSettingsJobAdmissionDeps = {}): AiJobAdmission {\n const ownerOf = deps.ownerOf ?? resolveOrgIdForHost\n return async (context): Promise<AiJobAdmissionRefusal | null> => {\n if (!context.hostId) return { status: 400, error: AI_SETTINGS_NO_SITE_COPY }\n const owner = await ownerOf(context.hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: 'Unknown site' }\n const host = await context.firestore.collection('hosts').doc(context.hostId).get()\n const refused = await aiSettingsAccessRefusal({ host, uid: context.uid ?? '', org: context.org }, { lockdown: deps.lockdown })\n if (refused) return { status: 403, error: refused }\n const read = aiSettingsRequestOf(context.inputs as Record<string, unknown>)\n return aiSettingsRequestEmpty(read.request) ? { status: 400, error: [AI_SETTINGS_NOTHING_COPY, ...read.problems].join(' ') } : null\n }\n}\n\nexport const aiSettingsJobAdmission = createAiSettingsJobAdmission()\n\n/** Registers the settings step and its door (AGL-3748). */\nexport function registerAiSettingsJob(): void {\n registerAiJobStep(AI_SETTINGS_KIND, runAiJobSettingsStep, { minimumMs: AI_JOB_SETTINGS_STEP_MINIMUM_MS })\n registerAiJobAdmission(AI_SETTINGS_KIND, aiSettingsJobAdmission)\n}\n\n"],"names":["supportedTimeZones","createResourceUid","inheritedMediaAlt","mediaNodeSrc","hostRoleCanWrite","visibleToHost","SCREEN_ROOT_PATH","siteLanguageCode","isSeoTitleVariable","dropPluginSiteCache","syncHostProjectionForMembers","getLockdownVerdict","resolveOrgIdForHost","FieldValue","registerAiJobAdmission","aiJobEditModel","aiLimitReview","aiUnspentOutcome","registerAiJobStep","AI_SETTINGS_KIND","AI_JOB_SETTINGS_STEP_MINIMUM_MS","AI_SETTINGS_NO_SITE_COPY","AI_SETTINGS_ROLE_COPY","AI_SETTINGS_LOCKED_COPY","AI_SETTINGS_NOTHING_COPY","AI_SETTINGS_WHERE","AI_SETTINGS_OUTPUT_ID","AI_SETTINGS_LIMITS","name","seoTitle","seoDescription","separator","titlePattern","businessDescription","businessName","telephone","supportEmail","address","AI_SETTINGS_CONSENT_MODES","AI_SETTINGS_MEDIA_ARGS","MEDIA_ARG_NOUNS","favicon","logo","socialImage","AI_SETTINGS_ARGS_SCHEMA","type","properties","description","maxLength","language","timeZone","consentMode","enum","additionalProperties","text","value","replace","trim","EMAIL","TELEPHONE","PATTERN_VARIABLE","aiSettingsTitlePatternOk","pattern","test","matchAll","every","match","aiSettingsRequestOf","inputs","request","problems","bounded","key","noun","length","push","locale","includes","slice","aiSettingsRequestEmpty","Object","keys","mediaRowOf","doc","org","data","number","id","fileName","String","contentType","url","cdnPath","contentHash","private","deleted","visibleTo","Array","isArray","width","height","alt","readAiSettingsMediaRows","firestore","input","libraries","ref","collection","hostId","orgId","rows","library","source","byId","get","exists","byName","where","limit","docs","some","row","aiSettingsMediaOf","arg","usable","filter","startsWith","undefined","find","named","error","src","getPath","path","split","reduce","at","sameValue","a","b","JSON","stringify","aiSettingsLanguageChanges","host","locales","one","said","join","aiSettingsChanges","media","changes","set","image","savedAlt","placementAlt","assetAlt","writeAiSiteSettings","hostRef","runTransaction","tx","ok","roles","uid","changed","change","done","Set","map","notDone","outboxId","updates","updatedAt","now","update","outboxRef","paths","entireHost","createdAt","serverTimestamp","attempts","aiSettingsAccessRefusal","deps","locked","lockdown","staff","intent","aiSettingsOutputNote","did","translate","not","mediaOf","readMedia","found","createAiJobSettingsStep","write","dropCache","syncMemberships","job","modelFor","model","unspent","failure","read","orgData","refused","createdBy","review","written","dropped","hostIds","reason","catch","complete","delete","console","jobId","$id","output","resource","hostSubdomain","label","note","Boolean","outputs","runAiJobSettingsStep","createAiSettingsJobAdmission","ownerOf","context","status","owner","aiSettingsJobAdmission","registerAiSettingsJob","minimumMs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-settings-step.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 { supportedTimeZones } from '@aglyn/aglyn/app-utils/collection-entry-date'\nimport { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport { inheritedMediaAlt } from '@aglyn/aglyn/app-utils/media-metadata'\nimport { mediaNodeSrc } from '@aglyn/aglyn/app-utils/media-ref'\nimport { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { visibleToHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { SCREEN_ROOT_PATH } from '@aglyn/aglyn/app-utils/screen-route'\nimport { siteLanguageCode } from '@aglyn/aglyn/app-utils/seo-locale'\nimport { isSeoTitleVariable } from '@aglyn/aglyn/app-utils/seo-title-variables'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport type { PluginAiCapabilityArgsSchema } from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { changesProjectedHostFields, syncHostProjectionForMembers } from '@aglyn/tenant-data-admin/server/host-memberships'\nimport { getLockdownVerdict } from '@aglyn/tenant-data-admin/server/lockdown'\nimport { resolveOrgIdForHost } from '@aglyn/tenant-data-admin/server/organizations'\nimport { FieldValue, type Firestore } from 'firebase-admin/firestore'\nimport type { AiJobOutput } from '../model/ai-jobs.types'\nimport { registerAiJobAdmission, type AiJobAdmission, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiJobEditModel } from './ai-job-edit-step'\nimport { aiLimitReview, aiUnspentOutcome } from './ai-job-generation'\nimport type { AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\nimport { registerAiJobStep } from './ai-jobs'\n\n/**\n * The `settings` step (AGL-3748): a change to what a member can change in the\n * site's settings — its name, its search defaults, its favicon, logo and\n * social image from the media library, its language, its time zone, its\n * contact details and its consent banner's mode — as a first-class build\n * operation, so \"rename my site to Juniper Clay Studio\", \"make the site\n * Spanish\" or \"set the SEO description\" is one item on Assist's plan card.\n *\n * NO MODEL. The planner already wrote the values as the item's arguments;\n * this checks each against the rule the console's own control holds it to\n * and writes it, so it costs no credits.\n *\n * ── Only the console's own fields ────────────────────────────────────────\n *\n * Every field written is one a settings card or form writes, under the same\n * name and to the same limit: `displayName` (Admin → General, 30), `seo.title`\n * (60), `seo.description` (155), `seo.separator` (3), `seo.titlePattern`\n * (120, its own variables only), `seo.entity.name`, `seo.entity.description`\n * (300), `seo.entity.telephone`, `business.supportEmail` and\n * `business.address` (the Business details card), `locales`/`defaultLocale`\n * (the Languages card), `timeZone` (one of the zones the console offers),\n * `consent.mode` (the consent banner card), and `seo.favicon`, `logoUrl` and\n * `seo.image` (with its size and alt) written as the media pickers write\n * them, through `mediaNodeSrc`.\n *\n * NEVER: the address (subdomain, domains), plugins, members and roles,\n * billing, lockdown, deletion, search engine indexing, analytics and\n * advertising ids, verification tokens, or turning the consent banner off.\n * Those are not arguments, so the planner cannot ask for them.\n *\n * ── Live on confirm ──────────────────────────────────────────────────────\n *\n * A setting has no draft: the console saves it straight to the host\n * document, and the tenant renders it from there. So does this, once the\n * person confirms the plan (Zach, 2026-10-12: live on confirm). The write\n * drops the site's cached pages as the console's door does\n * (`host-document-writes.ts`), with a site-wide publish outbox entry in the\n * same transaction for the drain to fire if the drop does not land.\n *\n * ── Who may ──────────────────────────────────────────────────────────────\n *\n * The Firestore rules' own for these keys (`canWriteHostContent` and\n * `hostWritesFrozen`): a member with a write role on the site (admin, editor\n * or author), while no lockdown freezes the site, its org or the platform.\n * Asked at the door, again before the write, and the role once more inside\n * the transaction that writes.\n *\n * ── Idempotent ───────────────────────────────────────────────────────────\n *\n * The transaction compares each value with what is stored and writes only\n * what differs, so a pass asked again writes nothing and announces nothing.\n */\n\nexport const AI_SETTINGS_KIND = 'settings' as const\n\n/**\n * ASSUMED: the least time the step needs — the site, the org, up to three\n * media lookups in two libraries, and the one transaction that writes. No\n * model, so it fits an inline door.\n */\nexport const AI_JOB_SETTINGS_STEP_MINIMUM_MS = 5_000\n\nexport const AI_SETTINGS_NO_SITE_COPY = 'Open the site whose settings should change.'\nexport const AI_SETTINGS_ROLE_COPY = 'Changing the site’s settings needs the admin, editor or author role on this site.'\nexport const AI_SETTINGS_LOCKED_COPY = 'This site is not accepting changes right now.'\nexport const AI_SETTINGS_NOTHING_COPY = 'The request named no site setting this could change.'\n\n/** Where a person finds what the op changed, as the build's notes name it. */\nexport const AI_SETTINGS_WHERE = 'the site’s Settings'\n\n/** The output's fixed id: one site's settings, whatever changed. */\nexport const AI_SETTINGS_OUTPUT_ID = 'site'\n\n/** The console's own limits, each from the control that edits the field. */\nexport const AI_SETTINGS_LIMITS = {\n /** Admin → General, Display name. */\n name: 30,\n /** Setup → SEO, Title. */\n seoTitle: 60,\n /** Setup → SEO, Description. */\n seoDescription: 155,\n /** Setup → SEO, Separator. */\n separator: 3,\n /** Setup → SEO, Title pattern. */\n titlePattern: 120,\n /** Setup → SEO, the publisher's description. */\n businessDescription: 300,\n businessName: 100,\n telephone: 40,\n supportEmail: 254,\n address: 200,\n} as const\n\n/** The consent banner card's two modes; turning the banner off is not offered. */\nexport const AI_SETTINGS_CONSENT_MODES = ['geo', 'strict'] as const\n\n/** The media a setting may name, and the field each is written to. */\nexport const AI_SETTINGS_MEDIA_ARGS = ['favicon', 'logo', 'socialImage'] as const\nexport type AiSettingsMediaArg = (typeof AI_SETTINGS_MEDIA_ARGS)[number]\n\nconst MEDIA_ARG_NOUNS: Readonly<Record<AiSettingsMediaArg, string>> = {\n favicon: 'favicon',\n logo: 'logo',\n socialImage: 'social image',\n}\n\n/** The item's arguments, as the planner fills them in. Every one optional. */\nexport const AI_SETTINGS_ARGS_SCHEMA: PluginAiCapabilityArgsSchema = {\n type: 'object',\n properties: {\n name: {\n type: 'string',\n description: 'The site’s name as the console lists it. Visitors see the search title, not this.',\n maxLength: AI_SETTINGS_LIMITS.name,\n },\n seoTitle: {\n type: 'string',\n description: 'The search title pages fall back to, usually the business’s name.',\n maxLength: AI_SETTINGS_LIMITS.seoTitle,\n },\n seoDescription: {\n type: 'string',\n description: 'The search description pages fall back to: one or two plain sentences.',\n maxLength: AI_SETTINGS_LIMITS.seoDescription,\n },\n separator: {\n type: 'string',\n description: 'The character between a page’s name and the site title, such as | or ·.',\n maxLength: AI_SETTINGS_LIMITS.separator,\n },\n titlePattern: {\n type: 'string',\n description: 'How an untitled page is titled, using only {{page.name}}, {{site.separator}} and {{site.name}}.',\n maxLength: AI_SETTINGS_LIMITS.titlePattern,\n },\n businessName: {\n type: 'string',\n description: 'The business’s name as search engines and AI assistants are told it.',\n maxLength: AI_SETTINGS_LIMITS.businessName,\n },\n businessDescription: {\n type: 'string',\n description: 'What the business is, in one sentence (its tagline), as search engines and AI assistants are told it.',\n maxLength: AI_SETTINGS_LIMITS.businessDescription,\n },\n telephone: {\n type: 'string',\n description: 'The business’s phone number, exactly as the person gave it.',\n maxLength: AI_SETTINGS_LIMITS.telephone,\n },\n supportEmail: {\n type: 'string',\n description: 'The contact email pages and emails show, exactly as the person gave it.',\n maxLength: AI_SETTINGS_LIMITS.supportEmail,\n },\n address: {\n type: 'string',\n description: 'The business’s address on one line, exactly as the person gave it.',\n maxLength: AI_SETTINGS_LIMITS.address,\n },\n language: {\n type: 'string',\n description: 'The language the site is in, as a code such as en, es, fr or pt-BR. It does not translate the pages.',\n maxLength: 10,\n },\n timeZone: {\n type: 'string',\n description: 'The site’s time zone as an IANA name, such as America/Chicago.',\n maxLength: 64,\n },\n consentMode: {\n type: 'string',\n description: 'The cookie banner’s mode: geo (asks only where the law requires) or strict (asks every visitor).',\n enum: AI_SETTINGS_CONSENT_MODES,\n },\n favicon: {\n type: 'string',\n description: 'An image already in the media library, by its id or exact file name, for the browser tab icon.',\n maxLength: 200,\n },\n logo: {\n type: 'string',\n description: 'An image already in the media library, by its id or exact file name, for the site’s logo.',\n maxLength: 200,\n },\n socialImage: {\n type: 'string',\n description: 'An image already in the media library, by its id or exact file name, for the card shown when a page is shared.',\n maxLength: 200,\n },\n },\n additionalProperties: false,\n}\n\n/** What an item asks, each value read and held to the console's rule. */\nexport interface AiSettingsRequest {\n name?: string\n seoTitle?: string\n seoDescription?: string\n separator?: string\n titlePattern?: string\n businessName?: string\n businessDescription?: string\n telephone?: string\n supportEmail?: string\n address?: string\n /** A language code the Languages card would accept. */\n language?: string\n timeZone?: string\n consentMode?: (typeof AI_SETTINGS_CONSENT_MODES)[number]\n favicon?: string\n logo?: string\n socialImage?: string\n}\n\nexport interface AiSettingsRead {\n request: AiSettingsRequest\n /** A sentence per value that could not be used, so the rest still are. */\n problems: string[]\n}\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.replace(/\\s+/g, ' ').trim() : '')\nconst EMAIL = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\nconst TELEPHONE = /^[+()0-9][0-9 ()+.\\-/]{4,38}[0-9)]$/\nconst PATTERN_VARIABLE = /\\{\\{\\s*([^}]*?)\\s*\\}\\}/g\n\n/** Whether a title pattern names only the variables the renderer knows. */\nexport function aiSettingsTitlePatternOk(pattern: string): boolean {\n if (/[{}]/.test(pattern.replace(PATTERN_VARIABLE, ''))) return false\n return [...pattern.matchAll(PATTERN_VARIABLE)].every((match) => isSeoTitleVariable(match[1]))\n}\n\n/** The item's arguments read: what may be written, and a sentence for each that may not. */\nexport function aiSettingsRequestOf(inputs: Readonly<Record<string, unknown>> | null | undefined): AiSettingsRead {\n const request: AiSettingsRequest = {}\n const problems: string[] = []\n const bounded = (key: keyof typeof AI_SETTINGS_LIMITS, noun: string) => {\n const value = text(inputs?.[key])\n if (!value) return\n if (value.length > AI_SETTINGS_LIMITS[key]) {\n problems.push(`The ${noun} is longer than ${AI_SETTINGS_LIMITS[key]} characters.`)\n return\n }\n request[key] = value\n }\n bounded('name', 'site name')\n bounded('seoTitle', 'search title')\n bounded('seoDescription', 'search description')\n bounded('separator', 'title separator')\n bounded('businessName', 'business name')\n bounded('businessDescription', 'business description')\n bounded('address', 'address')\n bounded('titlePattern', 'title pattern')\n if (request.titlePattern && !aiSettingsTitlePatternOk(request.titlePattern)) {\n problems.push('The title pattern may use only {{page.name}}, {{site.separator}} and {{site.name}}.')\n delete request.titlePattern\n }\n bounded('telephone', 'phone number')\n if (request.telephone && !TELEPHONE.test(request.telephone)) {\n problems.push(`“${request.telephone}” is not a phone number.`)\n delete request.telephone\n }\n bounded('supportEmail', 'contact email')\n if (request.supportEmail && !EMAIL.test(request.supportEmail)) {\n problems.push(`“${request.supportEmail}” is not an email address.`)\n delete request.supportEmail\n }\n const language = text(inputs?.['language'])\n if (language) {\n const locale = siteLanguageCode(language)\n if (locale) request.language = locale\n else problems.push(`“${language}” is not a language code such as es or pt-BR.`)\n }\n const timeZone = text(inputs?.['timeZone'])\n if (timeZone) {\n if (supportedTimeZones().includes(timeZone)) request.timeZone = timeZone\n else problems.push(`“${timeZone}” is not a time zone the site settings offer.`)\n }\n const consentMode = text(inputs?.['consentMode'])\n if (consentMode) {\n if ((AI_SETTINGS_CONSENT_MODES as readonly string[]).includes(consentMode)) {\n request.consentMode = consentMode as AiSettingsRequest['consentMode']\n } else problems.push('The cookie banner’s mode is geo or strict.')\n }\n for (const key of AI_SETTINGS_MEDIA_ARGS) {\n const value = text(inputs?.[key])\n if (value) request[key] = value.slice(0, 200)\n }\n return { request, problems }\n}\n\n/** Whether a read request asks for anything at all. */\nexport function aiSettingsRequestEmpty(request: AiSettingsRequest): boolean {\n return Object.keys(request).length === 0\n}\n\n// ── Media ─────────────────────────────────────────────────────────────────\n\n/** A media library row, as a setting may name it. */\nexport interface AiSettingsMediaRow {\n id: string\n fileName: string\n contentType: string\n url: string | null\n cdnPath: string | null\n contentHash: string | null\n private: boolean\n deleted: boolean\n /** The org library's sites; `undefined` on the site's own library. */\n visibleTo?: readonly string[] | null\n width: number | null\n height: number | null\n alt: string\n}\n\n/** The image a setting names, as written to the host. */\nexport interface AiSettingsMedia {\n src: string\n width: number | null\n height: number | null\n alt: string\n fileName: string\n}\n\nfunction mediaRowOf(doc: FirebaseFirestore.QueryDocumentSnapshot | FirebaseFirestore.DocumentSnapshot, org: boolean): AiSettingsMediaRow {\n const data = (doc.data() ?? {}) as Record<string, unknown>\n const number = (value: unknown) => (typeof value === 'number' && value > 0 ? value : null)\n return {\n id: doc.id,\n fileName: String(data['fileName'] ?? ''),\n contentType: String(data['contentType'] ?? ''),\n url: typeof data['url'] === 'string' ? data['url'] : null,\n cdnPath: typeof data['cdnPath'] === 'string' ? data['cdnPath'] : null,\n contentHash: typeof data['contentHash'] === 'string' ? data['contentHash'] : null,\n private: data['private'] === true,\n deleted: data['deletedAt'] != null,\n ...(org ? { visibleTo: Array.isArray(data['visibleTo']) ? (data['visibleTo'] as string[]) : null } : {}),\n width: number(data['width']),\n height: number(data['height']),\n alt: typeof data['alt'] === 'string' ? data['alt'] : '',\n }\n}\n\n/**\n * The rows a media name could be, from the site's own library and then its\n * org's: the one whose id it is, else those whose file name it is.\n */\nexport async function readAiSettingsMediaRows(\n firestore: Firestore,\n input: { hostId: string; orgId: string; source: string },\n): Promise<AiSettingsMediaRow[]> {\n const libraries = [\n { ref: firestore.collection('hosts').doc(input.hostId).collection('media'), org: false },\n { ref: firestore.collection('orgs').doc(input.orgId).collection('media'), org: true },\n ]\n const rows: AiSettingsMediaRow[] = []\n for (const library of libraries) {\n if (/^[A-Za-z0-9_-]{1,128}$/.test(input.source)) {\n const byId = await library.ref.doc(input.source).get()\n if (byId.exists) rows.push(mediaRowOf(byId, library.org))\n }\n const byName = await library.ref.where('fileName', '==', input.source).limit(5).get()\n for (const doc of byName.docs) if (!rows.some((row) => row.id === doc.id)) rows.push(mediaRowOf(doc, library.org))\n }\n return rows\n}\n\n/**\n * The image a setting names, chosen from the rows read: a public image of\n * this site's library, or of its org's that this site may use, never deleted.\n * Its id wins over a file name; two images of one name are refused in words.\n */\nexport function aiSettingsMediaOf(\n arg: AiSettingsMediaArg,\n source: string,\n rows: readonly AiSettingsMediaRow[],\n hostId: string,\n): AiSettingsMedia | { error: string } {\n const noun = MEDIA_ARG_NOUNS[arg]\n const usable = rows.filter(\n (row) =>\n !row.deleted &&\n !row.private &&\n row.contentType.startsWith('image/') &&\n (row.visibleTo === undefined || visibleToHost(row.visibleTo, hostId)),\n )\n const byId = usable.find((row) => row.id === source)\n const named = usable.filter((row) => row.fileName === source)\n if (!byId && named.length > 1) return { error: `The media library has more than one image called “${source}”; name the one for the ${noun}.` }\n const row = byId ?? named[0]\n if (!row) return { error: `The media library has no image “${source}” this site can use for the ${noun}.` }\n const src = mediaNodeSrc({ url: row.url, cdnPath: row.cdnPath, contentHash: row.contentHash, private: row.private })\n if (!src) return { error: `The image “${source}” has no address the site can show.` }\n return { src, width: row.width, height: row.height, alt: row.alt, fileName: row.fileName || row.id }\n}\n\n// ── The write ─────────────────────────────────────────────────────────────\n\n/** One change to the host document, with the sentence that says it was made. */\ninterface AiSettingsChange {\n path: string\n value: unknown\n said: string\n}\n\nconst getPath = (data: Record<string, unknown>, path: string): unknown =>\n path.split('.').reduce<unknown>((at, key) => (at && typeof at === 'object' ? (at as Record<string, unknown>)[key] : undefined), data)\n\nconst sameValue = (a: unknown, b: unknown): boolean => JSON.stringify(a ?? null) === JSON.stringify(b ?? null)\n\n/**\n * The language change, as the Languages card would save it: a site with no\n * languages, or one, now publishes in this one; a multilingual site that\n * lists it makes it the default. A multilingual site that does not list it\n * is refused, because adding a language is the Languages card's decision.\n */\nexport function aiSettingsLanguageChanges(\n host: { locales?: unknown; defaultLocale?: unknown },\n language: string,\n): AiSettingsChange[] | { error: string } {\n const locales = Array.isArray(host.locales) ? host.locales.filter((one): one is string => typeof one === 'string') : []\n const said = `The site’s language is now ${language}.`\n if (locales.length > 1) {\n if (!locales.includes(language)) {\n return { error: `This site publishes in ${locales.join(', ')}; add ${language} in Settings → Details → Languages first.` }\n }\n return [{ path: 'defaultLocale', value: language, said }]\n }\n return [\n { path: 'locales', value: [language], said },\n { path: 'defaultLocale', value: language, said },\n ]\n}\n\n/** Every change the request asks of this host, before comparing with what is stored. */\nexport function aiSettingsChanges(\n host: Record<string, unknown>,\n request: AiSettingsRequest,\n media: Partial<Record<AiSettingsMediaArg, AiSettingsMedia>>,\n): { changes: AiSettingsChange[]; problems: string[] } {\n const changes: AiSettingsChange[] = []\n const problems: string[] = []\n const set = (path: string, value: unknown, said: string) => changes.push({ path, value, said })\n if (request.name) set('displayName', request.name, `The site is now called “${request.name}”.`)\n if (request.seoTitle) set('seo.title', request.seoTitle, `The search title is now “${request.seoTitle}”.`)\n if (request.seoDescription) set('seo.description', request.seoDescription, 'The search description is set.')\n if (request.separator) set('seo.separator', request.separator, `The title separator is now “${request.separator}”.`)\n if (request.titlePattern) set('seo.titlePattern', request.titlePattern, `Untitled pages are now titled “${request.titlePattern}”.`)\n if (request.businessName) set('seo.entity.name', request.businessName, `The business name is now “${request.businessName}”.`)\n if (request.businessDescription) set('seo.entity.description', request.businessDescription, 'The business description is set.')\n if (request.telephone) set('seo.entity.telephone', request.telephone, `The phone number is now ${request.telephone}.`)\n if (request.supportEmail) set('business.supportEmail', request.supportEmail, `The contact email is now ${request.supportEmail}.`)\n if (request.address) set('business.address', request.address, 'The address is set.')\n if (request.timeZone) set('timeZone', request.timeZone, `The site’s time zone is now ${request.timeZone}.`)\n if (request.consentMode) {\n set('consent.mode', request.consentMode, request.consentMode === 'strict' ? 'The cookie banner now asks every visitor.' : 'The cookie banner now asks where the law requires.')\n }\n if (request.language) {\n const language = aiSettingsLanguageChanges(host, request.language)\n if ('error' in language) problems.push(language.error)\n else changes.push(...language)\n }\n if (media.favicon) set('seo.favicon', media.favicon.src, `The favicon is now “${media.favicon.fileName}”.`)\n if (media.logo) set('logoUrl', media.logo.src, `The logo is now “${media.logo.fileName}”.`)\n if (media.socialImage) {\n const image = media.socialImage\n const said = `The social image is now “${image.fileName}”.`\n set('seo.image', image.src, said)\n // Its size and alt, as the Social image card copies them at pick time.\n set('seo.imageWidth', image.width ?? 0, said)\n set('seo.imageHeight', image.height ?? 0, said)\n const savedAlt = getPath(host, 'seo.imageAlt')\n set('seo.imageAlt', inheritedMediaAlt({ placementAlt: savedAlt, assetAlt: image.alt }) ?? (typeof savedAlt === 'string' ? savedAlt : ''), said)\n }\n return { changes, problems }\n}\n\nexport type AiSettingsWrite =\n | { ok: true; done: string[]; notDone: string[]; changed: string[]; outboxId: string | null }\n | { ok: false; error: string }\n\n/**\n * The settings written, in one transaction that reads the host as it is\n * stored NOW: the member still holds a write role, and only the values that\n * differ from what is stored are written, with a site-wide publish outbox\n * entry beside them so the live site's cached pages are dropped.\n */\nexport async function writeAiSiteSettings(\n firestore: Firestore,\n input: {\n hostId: string\n uid: string\n request: AiSettingsRequest\n media: Partial<Record<AiSettingsMediaArg, AiSettingsMedia>>\n now: Date\n },\n): Promise<AiSettingsWrite> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n return firestore.runTransaction(async (tx): Promise<AiSettingsWrite> => {\n const host = await tx.get(hostRef)\n if (!host.exists) return { ok: false, error: 'Unknown site' }\n const roles = (host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return { ok: false, error: AI_SETTINGS_ROLE_COPY }\n const data = (host.data() ?? {}) as Record<string, unknown>\n const { changes, problems } = aiSettingsChanges(data, input.request, input.media)\n const changed = changes.filter((change) => !sameValue(getPath(data, change.path), change.value))\n const done = [...new Set(changes.map((change) => change.said))]\n if (!changed.length) return { ok: true, done, notDone: problems, changed: [], outboxId: null }\n const updates: Record<string, unknown> = { updatedAt: input.now }\n for (const change of changed) updates[change.path] = change.value\n tx.update(hostRef, updates)\n // The announce, written down in the save's own transaction: a setting\n // has no address, so it asks for the whole site, as the console's does.\n const outboxRef = firestore.collection('publishOutbox').doc(createResourceUid())\n tx.set(outboxRef, {\n hostId: input.hostId,\n paths: [SCREEN_ROOT_PATH],\n entireHost: true,\n createdAt: FieldValue.serverTimestamp(),\n attempts: 0,\n })\n return { ok: true, done, notDone: problems, changed: changed.map((change) => change.path), outboxId: outboxRef.id }\n })\n}\n\n// ── The step ──────────────────────────────────────────────────────────────\n\n/** Why this member may not change this site's settings now, or `null`. */\nexport async function aiSettingsAccessRefusal(\n input: { host: FirebaseFirestore.DocumentSnapshot; uid: string; org: object | null },\n deps: { lockdown?: typeof getLockdownVerdict } = {},\n): Promise<string | null> {\n if (!input.host.exists) return 'Unknown site'\n const roles = (input.host.get('memberRoles') ?? {}) as Record<string, unknown>\n if (!hostRoleCanWrite(roles[input.uid])) return AI_SETTINGS_ROLE_COPY\n const locked = await (deps.lockdown ?? getLockdownVerdict)({\n staff: false,\n uid: input.uid,\n org: input.org as never,\n host: input.host.data() as never,\n intent: 'write',\n })\n return locked ? AI_SETTINGS_LOCKED_COPY : null\n}\n\n/** The output's note: what changed, that it is live, and what was not done. */\nexport function aiSettingsOutputNote(done: readonly string[], notDone: readonly string[], language: boolean): string {\n // The build's own wording for an item with no draft state (AGL-3745).\n const did = done.length ? `${done.join(' ')} Live now, in ${AI_SETTINGS_WHERE}.` : 'Nothing needed changing.'\n const translate = language ? ' The pages keep the words they have: changing the language does not translate them.' : ''\n const not = notDone.length ? ` Not done: ${notDone.join(' ')}` : ''\n return `${did}${translate}${not}`.slice(0, 1_000)\n}\n\nexport interface AiJobSettingsStepDeps {\n readMedia?: typeof readAiSettingsMediaRows\n write?: typeof writeAiSiteSettings\n lockdown?: typeof getLockdownVerdict\n dropCache?: typeof dropPluginSiteCache\n syncMemberships?: typeof syncHostProjectionForMembers\n}\n\n/** Each media argument resolved to its image, or a sentence for each that cannot be. */\nasync function mediaOf(\n firestore: Firestore,\n input: { hostId: string; orgId: string; request: AiSettingsRequest },\n readMedia: typeof readAiSettingsMediaRows,\n): Promise<{ media: Partial<Record<AiSettingsMediaArg, AiSettingsMedia>>; problems: string[] }> {\n const media: Partial<Record<AiSettingsMediaArg, AiSettingsMedia>> = {}\n const problems: string[] = []\n for (const arg of AI_SETTINGS_MEDIA_ARGS) {\n const source = input.request[arg]\n if (!source) continue\n const rows = await readMedia(firestore, { hostId: input.hostId, orgId: input.orgId, source })\n const found = aiSettingsMediaOf(arg, source, rows, input.hostId)\n if ('error' in found) problems.push(found.error)\n else media[arg] = found\n }\n return { media, problems }\n}\n\nexport function createAiJobSettingsStep(deps: AiJobSettingsStepDeps = {}): AiJobStepRunner {\n const readMedia = deps.readMedia ?? readAiSettingsMediaRows\n const write = deps.write ?? writeAiSiteSettings\n const dropCache = deps.dropCache ?? dropPluginSiteCache\n const syncMemberships = deps.syncMemberships ?? syncHostProjectionForMembers\n return async ({ job, now, firestore, org, modelFor }): Promise<AiJobStepOutcome> => {\n // No model is asked: the outcome records the edit step's at no cost.\n const model = modelFor?.('job.edit') ?? aiJobEditModel()\n const unspent = (failure: string): AiJobStepOutcome => ({ ...aiUnspentOutcome(model), failure })\n const hostId = job.hostId\n if (!hostId) return unspent(AI_SETTINGS_NO_SITE_COPY)\n const read = aiSettingsRequestOf(job.inputs)\n if (aiSettingsRequestEmpty(read.request)) return unspent([AI_SETTINGS_NOTHING_COPY, ...read.problems].join(' '))\n const orgData = (org ?? ((await firestore.collection('orgs').doc(job.orgId).get()).data() ?? null)) as Record<string, unknown> | null\n const host = await firestore.collection('hosts').doc(hostId).get()\n const refused = await aiSettingsAccessRefusal({ host, uid: job.createdBy, org: orgData }, { lockdown: deps.lockdown })\n if (refused) return aiUnspentOutcome(model, { review: aiLimitReview(refused) })\n const found = await mediaOf(firestore, { hostId, orgId: job.orgId, request: read.request }, readMedia)\n const written = await write(firestore, { hostId, uid: job.createdBy, request: read.request, media: found.media, now })\n if (written.ok === false) return unspent(written.error)\n const notDone = [...read.problems, ...found.problems, ...written.notDone]\n if (!written.done.length) return unspent([AI_SETTINGS_NOTHING_COPY, ...notDone].join(' '))\n if (written.outboxId) {\n // The live site's cached pages go now; the outbox entry stays for the\n // drain unless the drop certainly happened.\n const dropped = await dropCache({ hostIds: [hostId], reason: 'AI build changed site settings' }).catch(() => ({ complete: false }))\n if (dropped.complete) await firestore.collection('publishOutbox').doc(written.outboxId).delete().catch(() => undefined)\n }\n if (changesProjectedHostFields(written.changed)) {\n // Every site list reads the name and favicon members' rows carry, so a\n // new favicon owes the re-fan as much as a new name does (AGL-3972).\n await syncMemberships(job.orgId, hostId).catch((error: unknown) => {\n console.error('ai settings: membership projection not synced', { hostId, jobId: job.$id, error })\n })\n }\n const output: AiJobOutput = {\n resource: 'settings',\n id: AI_SETTINGS_OUTPUT_ID,\n hostId,\n hostSubdomain: (typeof host.get('subdomain') === 'string' && host.get('subdomain')) || null,\n label: 'Site settings',\n note: aiSettingsOutputNote(written.done, notDone, Boolean(read.request.language)),\n }\n return aiUnspentOutcome(model, { outputs: [output] })\n }\n}\n\nexport const runAiJobSettingsStep = createAiJobSettingsStep()\n\n// ── Admission ─────────────────────────────────────────────────────────────\n\nexport interface AiSettingsJobAdmissionDeps {\n ownerOf?: typeof resolveOrgIdForHost\n lockdown?: typeof getLockdownVerdict\n}\n\n/**\n * A settings change is admitted on a site of the job's own org, for a member\n * who may write its settings now, where the request names at least one\n * setting this can change. Refused in those words before anything is written.\n */\nexport function createAiSettingsJobAdmission(deps: AiSettingsJobAdmissionDeps = {}): AiJobAdmission {\n const ownerOf = deps.ownerOf ?? resolveOrgIdForHost\n return async (context): Promise<AiJobAdmissionRefusal | null> => {\n if (!context.hostId) return { status: 400, error: AI_SETTINGS_NO_SITE_COPY }\n const owner = await ownerOf(context.hostId)\n if (!owner || owner !== context.orgId) return { status: 404, error: 'Unknown site' }\n const host = await context.firestore.collection('hosts').doc(context.hostId).get()\n const refused = await aiSettingsAccessRefusal({ host, uid: context.uid ?? '', org: context.org }, { lockdown: deps.lockdown })\n if (refused) return { status: 403, error: refused }\n const read = aiSettingsRequestOf(context.inputs as Record<string, unknown>)\n return aiSettingsRequestEmpty(read.request) ? { status: 400, error: [AI_SETTINGS_NOTHING_COPY, ...read.problems].join(' ') } : null\n }\n}\n\nexport const aiSettingsJobAdmission = createAiSettingsJobAdmission()\n\n/** Registers the settings step and its door (AGL-3748). */\nexport function registerAiSettingsJob(): void {\n registerAiJobStep(AI_SETTINGS_KIND, runAiJobSettingsStep, { minimumMs: AI_JOB_SETTINGS_STEP_MINIMUM_MS })\n registerAiJobAdmission(AI_SETTINGS_KIND, aiSettingsJobAdmission)\n}\n\n"],"names":["supportedTimeZones","createResourceUid","inheritedMediaAlt","mediaNodeSrc","hostRoleCanWrite","visibleToHost","SCREEN_ROOT_PATH","siteLanguageCode","isSeoTitleVariable","dropPluginSiteCache","changesProjectedHostFields","syncHostProjectionForMembers","getLockdownVerdict","resolveOrgIdForHost","FieldValue","registerAiJobAdmission","aiJobEditModel","aiLimitReview","aiUnspentOutcome","registerAiJobStep","AI_SETTINGS_KIND","AI_JOB_SETTINGS_STEP_MINIMUM_MS","AI_SETTINGS_NO_SITE_COPY","AI_SETTINGS_ROLE_COPY","AI_SETTINGS_LOCKED_COPY","AI_SETTINGS_NOTHING_COPY","AI_SETTINGS_WHERE","AI_SETTINGS_OUTPUT_ID","AI_SETTINGS_LIMITS","name","seoTitle","seoDescription","separator","titlePattern","businessDescription","businessName","telephone","supportEmail","address","AI_SETTINGS_CONSENT_MODES","AI_SETTINGS_MEDIA_ARGS","MEDIA_ARG_NOUNS","favicon","logo","socialImage","AI_SETTINGS_ARGS_SCHEMA","type","properties","description","maxLength","language","timeZone","consentMode","enum","additionalProperties","text","value","replace","trim","EMAIL","TELEPHONE","PATTERN_VARIABLE","aiSettingsTitlePatternOk","pattern","test","matchAll","every","match","aiSettingsRequestOf","inputs","request","problems","bounded","key","noun","length","push","locale","includes","slice","aiSettingsRequestEmpty","Object","keys","mediaRowOf","doc","org","data","number","id","fileName","String","contentType","url","cdnPath","contentHash","private","deleted","visibleTo","Array","isArray","width","height","alt","readAiSettingsMediaRows","firestore","input","libraries","ref","collection","hostId","orgId","rows","library","source","byId","get","exists","byName","where","limit","docs","some","row","aiSettingsMediaOf","arg","usable","filter","startsWith","undefined","find","named","error","src","getPath","path","split","reduce","at","sameValue","a","b","JSON","stringify","aiSettingsLanguageChanges","host","locales","one","said","join","aiSettingsChanges","media","changes","set","image","savedAlt","placementAlt","assetAlt","writeAiSiteSettings","hostRef","runTransaction","tx","ok","roles","uid","changed","change","done","Set","map","notDone","outboxId","updates","updatedAt","now","update","outboxRef","paths","entireHost","createdAt","serverTimestamp","attempts","aiSettingsAccessRefusal","deps","locked","lockdown","staff","intent","aiSettingsOutputNote","did","translate","not","mediaOf","readMedia","found","createAiJobSettingsStep","write","dropCache","syncMemberships","job","modelFor","model","unspent","failure","read","orgData","refused","createdBy","review","written","dropped","hostIds","reason","catch","complete","delete","console","jobId","$id","output","resource","hostSubdomain","label","note","Boolean","outputs","runAiJobSettingsStep","createAiSettingsJobAdmission","ownerOf","context","status","owner","aiSettingsJobAdmission","registerAiSettingsJob","minimumMs"],"mappings":"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"}
|
|
@@ -21,6 +21,7 @@ import type { AiJob, AiJobOutput, AiJobPlan } from '../model/ai-jobs.types';
|
|
|
21
21
|
import type { AiPlanCapabilities } from '../model/ai-plan-capabilities';
|
|
22
22
|
import { generateAiDataset } from '../runtime/ai-dataset-generation';
|
|
23
23
|
import { type AiLayoutListing, type AiLayoutListingField, type AiLayoutListingScreen } from '../layout-language/ai-layout-listings';
|
|
24
|
+
import { type AiLayoutPicturePhoto } from '../layout-language/ai-layout-pictures';
|
|
24
25
|
import { aiLayoutStockPhotoSource } from './ai-layout-stock-photos';
|
|
25
26
|
import { aiPluginDraftAdmissionRefusal } from './ai-job-plugin-drafts';
|
|
26
27
|
import type { AiJobStepContext, AiJobStepRunner } from './ai-job-text-step';
|
|
@@ -158,6 +159,9 @@ export interface AiSiteDatasetRecordPhoto {
|
|
|
158
159
|
placed: string | null;
|
|
159
160
|
/** What the photo shows, in its own words (AGL-3856); `null` for none. */
|
|
160
161
|
alt?: string | null;
|
|
162
|
+
/** The library photo's size, where it is one (AGL-3988): what a page the library fails reuses it at. */
|
|
163
|
+
width?: number;
|
|
164
|
+
height?: number;
|
|
161
165
|
}
|
|
162
166
|
/**
|
|
163
167
|
* A photo for each record of a pictured dataset (AGL-3616), in order: a stock
|
|
@@ -182,6 +186,27 @@ export declare function aiSiteDatasetRecordPhotos(input: {
|
|
|
182
186
|
signal?: AbortSignal;
|
|
183
187
|
stockPhotos?: typeof aiLayoutStockPhotoSource;
|
|
184
188
|
}): Promise<AiSiteDatasetRecordPhoto[]>;
|
|
189
|
+
/**
|
|
190
|
+
* The photos of a list's records, each record its library left without one
|
|
191
|
+
* given one of those the list already shows (AGL-3988). The beta.270 Sugar
|
|
192
|
+
* Hollow Photography covers, a wedding photographer's, filed five "Weddings
|
|
193
|
+
* and elopements" galleries under a windowsill, a workbench, a desk and an
|
|
194
|
+
* office team, every copy refused by Pixabay's rate limit, while three of
|
|
195
|
+
* its records had copied a snowy mountain wedding, a couple at sunset and a
|
|
196
|
+
* father and son in the mountains.
|
|
197
|
+
*
|
|
198
|
+
* A record takes, in order: a photo of a record filed under the same
|
|
199
|
+
* category; a photo whose words name the people its own moment shows
|
|
200
|
+
* (`aiStockRecordScene`: a couple for "Couple Portraits", never a family);
|
|
201
|
+
* either again where only the record beside it shows them; and last any
|
|
202
|
+
* photo the list shows. Never the photo of the record beside it while
|
|
203
|
+
* another fits, the least shown first, the seed picking among equals. A
|
|
204
|
+
* record no photo fits keeps none, and takes a starter. Pure.
|
|
205
|
+
*/
|
|
206
|
+
export declare function aiSiteDatasetRecordsFromOwn(records: ReadonlyArray<{
|
|
207
|
+
name: string;
|
|
208
|
+
category?: string | null;
|
|
209
|
+
}>, photos: ReadonlyArray<AiLayoutPicturePhoto | null>, seed: string): Array<AiLayoutPicturePhoto | null>;
|
|
185
210
|
/**
|
|
186
211
|
* A designed dataset's content with its records' photos (AGL-3616): the
|
|
187
212
|
* planned field that names a photo ("Image", "Photo") becomes the data
|
|
@@ -363,5 +388,7 @@ export declare function aiSiteDatasetListings(input: {
|
|
|
363
388
|
}): AiLayoutListing[];
|
|
364
389
|
/** The library photos the site's datasets' records show (AGL-3616), which a page does not place again. */
|
|
365
390
|
export declare function aiSiteDatasetPlacedPhotos(outputs: readonly AiJobOutput[]): string[];
|
|
391
|
+
/** The same photos with their size and words (AGL-3988), which a page's picture the library fails takes before any starter. */
|
|
392
|
+
export declare function aiSiteDatasetPlacedPhotoDetails(outputs: readonly AiJobOutput[]): AiLayoutPicturePhoto[];
|
|
366
393
|
/** What a page is told about the datasets built before it. */
|
|
367
394
|
export declare function aiSiteDatasetBriefLines(outputs: readonly AiJobOutput[]): string[];
|
|
@@ -24,11 +24,11 @@ import { AI_STEP_TIERS } from "../providers/catalog.js";
|
|
|
24
24
|
import { aiModelForStep } from "../providers/routing.js";
|
|
25
25
|
import { AI_DATASET_MAX_TOKENS, AI_DATASET_STEP, aiDatasetDraftContent, generateAiDataset } from "../runtime/ai-dataset-generation.js";
|
|
26
26
|
import { aiLayoutListingId, aiLayoutRecordBaseOf, aiLayoutRecordsPlacements } from "../layout-language/ai-layout-listings.js";
|
|
27
|
-
import { aiLayoutStarterPhotos } from "../layout-language/ai-layout-pictures.js";
|
|
27
|
+
import { aiLayoutNeutralStarterRun, aiLayoutSeedNumber, aiLayoutStarterPhotos } from "../layout-language/ai-layout-pictures.js";
|
|
28
28
|
import { aiSiteWords } from "../model/ai-site-job.js";
|
|
29
29
|
import { aiOriginJobId } from "./ai-job-draft-ids.js";
|
|
30
30
|
import { aiSiteProductPhotoSrc } from "./ai-job-site-content.js";
|
|
31
|
-
import { aiLayoutStockPhotoSource, aiStockPhotoWordsOf } from "./ai-layout-stock-photos.js";
|
|
31
|
+
import { aiLayoutStockPhotoSource, aiStockJobPhotosOf, aiStockPhotoWordsOf, aiStockRecordScene, aiStockStem } from "./ai-layout-stock-photos.js";
|
|
32
32
|
import { AI_DATASET_DRAFT_RESOURCE } from "./ai-build-unit-outcome.js";
|
|
33
33
|
import { aiJobStepBudget } from "./ai-job-budget.js";
|
|
34
34
|
import { aiGenerationSpent, aiUnspentOutcome } from "./ai-job-generation.js";
|
|
@@ -267,6 +267,7 @@ const limitReview = (message)=>({
|
|
|
267
267
|
});
|
|
268
268
|
const seed = `${job.$id}:records`;
|
|
269
269
|
let found = [];
|
|
270
|
+
/** Whether the site has a stock library: its records then never take a starter that shows another business (AGL-3988). */ let library = false;
|
|
270
271
|
try {
|
|
271
272
|
var _input_stockPhotos;
|
|
272
273
|
const source = ((_input_stockPhotos = input.stockPhotos) != null ? _input_stockPhotos : aiLayoutStockPhotoSource)(_extends({
|
|
@@ -286,23 +287,47 @@ const limitReview = (message)=>({
|
|
|
286
287
|
} : {}), {
|
|
287
288
|
budgetMs: AI_SITE_DATASET_PHOTOS_BUDGET_MS
|
|
288
289
|
});
|
|
290
|
+
library = Boolean(source);
|
|
289
291
|
if (source) found = await source(slots);
|
|
290
292
|
} catch (error) {
|
|
291
|
-
console.warn('ai site dataset: the stock photos failed;
|
|
293
|
+
console.warn('ai site dataset: the stock photos failed; the job’s own photos or starters fill them', {
|
|
292
294
|
error: String(error)
|
|
293
295
|
});
|
|
294
296
|
}
|
|
295
|
-
const starters = aiLayoutStarterPhotos(slots, seed);
|
|
296
297
|
const hostId = job.hostId;
|
|
297
|
-
|
|
298
|
+
const copied = slots.map((_slot, index)=>{
|
|
299
|
+
var _found_index;
|
|
300
|
+
return ((_found_index = found[index]) == null ? void 0 : _found_index.src) ? found[index] : null;
|
|
301
|
+
});
|
|
302
|
+
// A record left without a photo takes one this list already shows, on its
|
|
303
|
+
// own subject (AGL-3988), before any starter.
|
|
304
|
+
const photos = library && !people ? aiSiteDatasetRecordsFromOwn(input.names.map((name, index)=>{
|
|
298
305
|
var _ref;
|
|
299
|
-
var
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
placed: (_ref = (_found_index1 = found[index]) == null ? void 0 : _found_index1.src) != null ? _ref : null,
|
|
304
|
-
alt: ((_found_index2 = found[index]) == null ? void 0 : (_found_index_alt = _found_index2.alt) == null ? void 0 : _found_index_alt.trim()) || null
|
|
306
|
+
var _input_categories;
|
|
307
|
+
return {
|
|
308
|
+
name,
|
|
309
|
+
category: (_ref = (_input_categories = input.categories) == null ? void 0 : _input_categories[index]) != null ? _ref : null
|
|
305
310
|
};
|
|
311
|
+
}), copied, seed) : copied;
|
|
312
|
+
// Only a job with no photo of its own takes starters: with a library, the
|
|
313
|
+
// neutral ones in turn, never a workbench or houseplants (AGL-3923); a
|
|
314
|
+
// team's records, and a site with no library, the starters as before.
|
|
315
|
+
const starters = library && !people ? aiLayoutNeutralStarterRun(slots.length, seed) : aiLayoutStarterPhotos(slots, seed);
|
|
316
|
+
return slots.map((_slot, index)=>{
|
|
317
|
+
var _starters_index, _starters_index_alt, _starters_index1;
|
|
318
|
+
const photo = photos[index];
|
|
319
|
+
const stock = aiSiteProductPhotoSrc(photo == null ? void 0 : photo.src, hostId);
|
|
320
|
+
if (photo && stock) {
|
|
321
|
+
var _found_index, _photo_alt;
|
|
322
|
+
const copied = ((_found_index = found[index]) == null ? void 0 : _found_index.src) === photo.src;
|
|
323
|
+
return {
|
|
324
|
+
src: stock,
|
|
325
|
+
placed: copied ? photo.src : null,
|
|
326
|
+
alt: ((_photo_alt = photo.alt) == null ? void 0 : _photo_alt.trim()) || null,
|
|
327
|
+
width: photo.width,
|
|
328
|
+
height: photo.height
|
|
329
|
+
};
|
|
330
|
+
}
|
|
306
331
|
return {
|
|
307
332
|
src: aiSiteProductPhotoSrc((_starters_index = starters[index]) == null ? void 0 : _starters_index.src, hostId),
|
|
308
333
|
placed: null,
|
|
@@ -310,6 +335,87 @@ const limitReview = (message)=>({
|
|
|
310
335
|
};
|
|
311
336
|
});
|
|
312
337
|
}
|
|
338
|
+
/**
|
|
339
|
+
* The photos of a list's records, each record its library left without one
|
|
340
|
+
* given one of those the list already shows (AGL-3988). The beta.270 Sugar
|
|
341
|
+
* Hollow Photography covers, a wedding photographer's, filed five "Weddings
|
|
342
|
+
* and elopements" galleries under a windowsill, a workbench, a desk and an
|
|
343
|
+
* office team, every copy refused by Pixabay's rate limit, while three of
|
|
344
|
+
* its records had copied a snowy mountain wedding, a couple at sunset and a
|
|
345
|
+
* father and son in the mountains.
|
|
346
|
+
*
|
|
347
|
+
* A record takes, in order: a photo of a record filed under the same
|
|
348
|
+
* category; a photo whose words name the people its own moment shows
|
|
349
|
+
* (`aiStockRecordScene`: a couple for "Couple Portraits", never a family);
|
|
350
|
+
* either again where only the record beside it shows them; and last any
|
|
351
|
+
* photo the list shows. Never the photo of the record beside it while
|
|
352
|
+
* another fits, the least shown first, the seed picking among equals. A
|
|
353
|
+
* record no photo fits keeps none, and takes a starter. Pure.
|
|
354
|
+
*/ export function aiSiteDatasetRecordsFromOwn(records, photos, seed) {
|
|
355
|
+
const own = photos.flatMap((photo, index)=>(photo == null ? void 0 : photo.src) ? [
|
|
356
|
+
{
|
|
357
|
+
photo,
|
|
358
|
+
index
|
|
359
|
+
}
|
|
360
|
+
] : []);
|
|
361
|
+
if (!own.length) return [
|
|
362
|
+
...photos
|
|
363
|
+
];
|
|
364
|
+
const category = (index)=>{
|
|
365
|
+
var _ref;
|
|
366
|
+
var _records_index_category, _records_index;
|
|
367
|
+
return (_ref = (_records_index = records[index]) == null ? void 0 : (_records_index_category = _records_index.category) == null ? void 0 : _records_index_category.replace(/\s+/g, ' ').trim().toLowerCase()) != null ? _ref : '';
|
|
368
|
+
};
|
|
369
|
+
const turn = aiLayoutSeedNumber(seed);
|
|
370
|
+
/** Whether a photo's words name the people a record's moment shows, and none it never shows. */ const shows = (photo, record)=>{
|
|
371
|
+
var _record_category, _photo_alt;
|
|
372
|
+
const scene = aiStockRecordScene(record.name, (_record_category = record.category) != null ? _record_category : '');
|
|
373
|
+
if (!scene) return false;
|
|
374
|
+
const stems = new Set(((_photo_alt = photo.alt) != null ? _photo_alt : '').toLowerCase().split(/[^a-z\u00c0-\u024f]+/).filter(Boolean).map(aiStockStem));
|
|
375
|
+
return scene.people.some((stem)=>stems.has(stem)) && !scene.shun.some((stem)=>stems.has(stem));
|
|
376
|
+
};
|
|
377
|
+
/** The list filled in one order; the record beside one filled before it is known, the one after not yet. */ const fill = (order)=>{
|
|
378
|
+
const filled = [
|
|
379
|
+
...photos
|
|
380
|
+
];
|
|
381
|
+
for (const index of order){
|
|
382
|
+
var _filled_index, _filled_, _filled_1;
|
|
383
|
+
const record = records[index];
|
|
384
|
+
if (!record || ((_filled_index = filled[index]) == null ? void 0 : _filled_index.src)) continue;
|
|
385
|
+
const beside = new Set([
|
|
386
|
+
(_filled_ = filled[index - 1]) == null ? void 0 : _filled_.src,
|
|
387
|
+
(_filled_1 = filled[index + 1]) == null ? void 0 : _filled_1.src
|
|
388
|
+
].filter(Boolean));
|
|
389
|
+
const apart = (entry)=>!beside.has(entry.photo.src);
|
|
390
|
+
const same = own.filter((entry)=>category(index) && category(entry.index) === category(index));
|
|
391
|
+
const subject = own.filter((entry)=>shows(entry.photo, record));
|
|
392
|
+
const pool = [
|
|
393
|
+
same.filter(apart),
|
|
394
|
+
subject.filter(apart),
|
|
395
|
+
same,
|
|
396
|
+
subject,
|
|
397
|
+
own.filter(apart)
|
|
398
|
+
].find((list)=>list.length);
|
|
399
|
+
if (!pool) continue;
|
|
400
|
+
const uses = (src)=>filled.filter((photo)=>(photo == null ? void 0 : photo.src) === src).length;
|
|
401
|
+
const rank = (at)=>((at - turn) % records.length + records.length) % records.length;
|
|
402
|
+
const [pick] = [
|
|
403
|
+
...pool
|
|
404
|
+
].sort((a, b)=>uses(a.photo.src) - uses(b.photo.src) || rank(a.index) - rank(b.index));
|
|
405
|
+
if (pick) filled[index] = _extends({}, pick.photo);
|
|
406
|
+
}
|
|
407
|
+
return filled;
|
|
408
|
+
};
|
|
409
|
+
// Filled from either end, the one that shows a photo beside itself least:
|
|
410
|
+
// a record beside a copied one is the most held, and is served first.
|
|
411
|
+
const forward = fill(records.map((_record, index)=>index));
|
|
412
|
+
const backward = fill(records.map((_record, index)=>records.length - 1 - index));
|
|
413
|
+
const repeats = (list)=>list.filter((photo, index)=>{
|
|
414
|
+
var _list_;
|
|
415
|
+
return index > 0 && (photo == null ? void 0 : photo.src) && photo.src === ((_list_ = list[index - 1]) == null ? void 0 : _list_.src);
|
|
416
|
+
}).length;
|
|
417
|
+
return repeats(backward) < repeats(forward) ? backward : forward;
|
|
418
|
+
}
|
|
313
419
|
/**
|
|
314
420
|
* A designed dataset's content with its records' photos (AGL-3616): the
|
|
315
421
|
* planned field that names a photo ("Image", "Photo") becomes the data
|
|
@@ -457,7 +563,7 @@ function proposalOf(output) {
|
|
|
457
563
|
const photosFor = (_deps_photos = deps.photos) != null ? _deps_photos : aiSiteDatasetRecordPhotos;
|
|
458
564
|
return async (context)=>{
|
|
459
565
|
var _ref, _context_org, _facts_fields, _facts_addressField, _facts_fields1, _facts_fields2, _facts_records;
|
|
460
|
-
var _job_plan_create, _job_plan;
|
|
566
|
+
var _job_plan_create, _job_plan, _job_plan1;
|
|
461
567
|
const { job, firestore, signal } = context;
|
|
462
568
|
const model = (_ref = context.modelFor == null ? void 0 : context.modelFor.call(context, AI_DATASET_STEP)) != null ? _ref : aiModelForStep(AI_DATASET_STEP);
|
|
463
569
|
const creation = (_job_plan = job.plan) == null ? void 0 : (_job_plan_create = _job_plan.create) == null ? void 0 : _job_plan_create.find((entry)=>entry.kind === 'dataset');
|
|
@@ -575,9 +681,11 @@ function proposalOf(output) {
|
|
|
575
681
|
if (generation.status === 'needs_input') return _extends({}, spent, {
|
|
576
682
|
failure: generation.message
|
|
577
683
|
});
|
|
578
|
-
const designed = aiDatasetDraftContent(creation.name, generation.value, {
|
|
684
|
+
const designed = aiDatasetDraftContent(creation.name, generation.value, _extends({
|
|
579
685
|
recordPages: input.recordPages
|
|
580
|
-
})
|
|
686
|
+
}, ((_job_plan1 = job.plan) == null ? void 0 : _job_plan1.language) ? {
|
|
687
|
+
language: job.plan.language
|
|
688
|
+
} : {}));
|
|
581
689
|
// What each record is filed under, which its photo and the sections that list it by its value read (AGL-3856).
|
|
582
690
|
const category = aiSiteDatasetCategoryOf(generation.value);
|
|
583
691
|
// A list a visitor looks at keeps its records' photos (AGL-3616): searched
|
|
@@ -643,6 +751,15 @@ function proposalOf(output) {
|
|
|
643
751
|
const placed = photos.flatMap((photo)=>photo.placed ? [
|
|
644
752
|
photo.placed
|
|
645
753
|
] : []);
|
|
754
|
+
const placedPhotos = photos.flatMap((photo)=>photo.placed && photo.width && photo.height ? [
|
|
755
|
+
_extends({
|
|
756
|
+
src: photo.placed,
|
|
757
|
+
width: photo.width,
|
|
758
|
+
height: photo.height
|
|
759
|
+
}, photo.alt ? {
|
|
760
|
+
alt: photo.alt
|
|
761
|
+
} : {})
|
|
762
|
+
] : []);
|
|
646
763
|
const proposal = _extends({
|
|
647
764
|
fields: (_facts_fields = facts.fields) != null ? _facts_fields : [],
|
|
648
765
|
recordNames: generation.value.records.map((values)=>{
|
|
@@ -654,6 +771,8 @@ function proposalOf(output) {
|
|
|
654
771
|
imageField: facts.imageField
|
|
655
772
|
} : {}, placed.length ? {
|
|
656
773
|
photos: placed
|
|
774
|
+
} : {}, placedPhotos.length ? {
|
|
775
|
+
placedPhotos
|
|
657
776
|
} : {}, {
|
|
658
777
|
sparseFields: aiDatasetSparseFields(generation.value, (_facts_fields1 = facts.fields) != null ? _facts_fields1 : [])
|
|
659
778
|
}, aiSiteDatasetGroupsOf(category, (_facts_fields2 = facts.fields) != null ? _facts_fields2 : [], generation.value));
|
|
@@ -1029,6 +1148,13 @@ export const runAiSiteDatasetUnit = createAiSiteDatasetRunner();
|
|
|
1029
1148
|
}))
|
|
1030
1149
|
];
|
|
1031
1150
|
}
|
|
1151
|
+
/** The same photos with their size and words (AGL-3988), which a page's picture the library fails takes before any starter. */ export function aiSiteDatasetPlacedPhotoDetails(outputs) {
|
|
1152
|
+
const seen = new Set();
|
|
1153
|
+
return outputs.filter((output)=>output.resource === 'draft' && output.draftResource === AI_SITE_DATASET_RESOURCE).flatMap((output)=>{
|
|
1154
|
+
var _output_proposal;
|
|
1155
|
+
return aiStockJobPhotosOf((_output_proposal = output.proposal) == null ? void 0 : _output_proposal.placedPhotos);
|
|
1156
|
+
}).filter((photo)=>!seen.has(photo.src) && Boolean(seen.add(photo.src)));
|
|
1157
|
+
}
|
|
1032
1158
|
/** What a page is told about the datasets built before it. */ export function aiSiteDatasetBriefLines(outputs) {
|
|
1033
1159
|
const datasets = outputs.filter((output)=>output.resource === 'draft' && output.draftResource === AI_SITE_DATASET_RESOURCE);
|
|
1034
1160
|
return datasets.flatMap((output)=>{
|