@aglyn/plugins-ai 1.0.0-beta.230 → 1.0.0-beta.232
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.js +8 -4
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-assist-provider-on-host.component.d.ts +66 -0
- package/src/lib/components/ai-assist-provider-on-host.component.js +78 -0
- package/src/lib/components/ai-assist-provider-on-host.component.js.map +1 -0
- package/src/lib/components/ai-assist-provider.component.js +9 -10
- package/src/lib/components/ai-assist-provider.component.js.map +1 -1
- package/src/lib/components/ai-job-follow.component.js +1 -0
- package/src/lib/components/ai-job-follow.component.js.map +1 -1
- package/src/lib/components/ai-job-links.js +13 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.js +3 -1
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-jobs-list-page.component.js +1 -0
- package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
- package/src/lib/components/ai-media-create.component.d.ts +20 -7
- package/src/lib/components/ai-media-create.component.js +107 -55
- package/src/lib/components/ai-media-create.component.js.map +1 -1
- package/src/lib/components/ai-permissions-on-host.component.d.ts +3 -46
- package/src/lib/components/ai-permissions-on-host.component.js +5 -53
- package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
- package/src/lib/components/ai-seo-fields-card.component.js +1 -0
- package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +20 -8
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
- package/src/lib/components/ai-site-memory-card.component.js +187 -0
- package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
- package/src/lib/components/ai-site-start-card.component.js +80 -77
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/components/ai-usage-strip.component.js +1 -0
- package/src/lib/components/ai-usage-strip.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.d.ts +10 -0
- package/src/lib/components/assist-panel.component.js +119 -9
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/assist-signals-page.component.js +2 -0
- package/src/lib/components/assist-signals-page.component.js.map +1 -1
- package/src/lib/components/billing-ai-allotments.component.js +3 -0
- package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
- package/src/lib/components/billing-ai-top-users.component.js +1 -0
- package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
- package/src/lib/components/billing-assist-overage-card.component.js +2 -0
- package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
- package/src/lib/components/member-ai-usage-card.component.js +10 -0
- package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-card.component.js +1 -0
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
- package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
- package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
- package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
- package/src/lib/jobs/ai-build-capabilities.js +101 -7
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-build-unit-outcome.js +4 -2
- package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -1
- package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
- package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
- package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
- package/src/lib/jobs/ai-job-auto-confirm.d.ts +8 -0
- package/src/lib/jobs/ai-job-auto-confirm.js +30 -6
- package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -1
- package/src/lib/jobs/ai-job-build-step.js +14 -1
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
- package/src/lib/jobs/ai-job-edit-step.js +631 -0
- package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
- package/src/lib/jobs/ai-job-generation.d.ts +8 -0
- package/src/lib/jobs/ai-job-generation.js +13 -2
- package/src/lib/jobs/ai-job-generation.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.js +29 -5
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +78 -0
- package/src/lib/jobs/ai-job-layout-language.js +200 -0
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -0
- package/src/lib/jobs/ai-job-layout-site-pages.d.ts +13 -2
- package/src/lib/jobs/ai-job-layout-site-pages.js +18 -2
- package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.d.ts +5 -0
- package/src/lib/jobs/ai-job-layout-step.js +107 -6
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
- package/src/lib/jobs/ai-job-logic-step.js +87 -6
- package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +194 -0
- package/src/lib/jobs/ai-job-page-language.js +418 -0
- package/src/lib/jobs/ai-job-page-language.js.map +1 -0
- package/src/lib/jobs/ai-job-page-sections.d.ts +37 -1
- package/src/lib/jobs/ai-job-page-sections.js +93 -11
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +4 -4
- package/src/lib/jobs/ai-job-page-step.js +115 -14
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +20 -6
- package/src/lib/jobs/ai-job-plan-step.js +62 -22
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
- package/src/lib/jobs/ai-job-site-content.js +443 -0
- package/src/lib/jobs/ai-job-site-content.js.map +1 -0
- package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
- package/src/lib/jobs/ai-job-site-look.js +322 -0
- package/src/lib/jobs/ai-job-site-look.js.map +1 -0
- package/src/lib/jobs/ai-job-site-step.d.ts +30 -5
- package/src/lib/jobs/ai-job-site-step.js +183 -27
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.js +2 -2
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +6 -1
- package/src/lib/jobs/ai-jobs-notify.js +15 -3
- package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +1 -1
- package/src/lib/jobs/ai-jobs.js +17 -8
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-full-height.d.ts +40 -0
- package/src/lib/jobs/ai-layout-full-height.js +82 -0
- package/src/lib/jobs/ai-layout-full-height.js.map +1 -0
- package/src/lib/jobs/ai-layout-site-facts.d.ts +54 -0
- package/src/lib/jobs/ai-layout-site-facts.js +153 -0
- package/src/lib/jobs/ai-layout-site-facts.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +154 -0
- package/src/lib/layout-language/ai-layout-compiler.js +1235 -0
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -0
- package/src/lib/layout-language/ai-layout-copy.d.ts +73 -0
- package/src/lib/layout-language/ai-layout-copy.js +137 -0
- package/src/lib/layout-language/ai-layout-copy.js.map +1 -0
- package/src/lib/layout-language/ai-layout-frame.d.ts +52 -0
- package/src/lib/layout-language/ai-layout-frame.js +458 -0
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -0
- package/src/lib/layout-language/ai-layout-gaps.d.ts +63 -0
- package/src/lib/layout-language/ai-layout-gaps.js +345 -0
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -0
- package/src/lib/layout-language/ai-layout-language.d.ts +168 -0
- package/src/lib/layout-language/ai-layout-language.js +529 -0
- package/src/lib/layout-language/ai-layout-language.js.map +1 -0
- package/src/lib/layout-language/ai-layout-links.d.ts +80 -0
- package/src/lib/layout-language/ai-layout-links.js +109 -0
- package/src/lib/layout-language/ai-layout-links.js.map +1 -0
- package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
- package/src/lib/layout-language/ai-layout-pictures.js +214 -0
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
- package/src/lib/layout-language/ai-layout-store.d.ts +41 -0
- package/src/lib/layout-language/ai-layout-store.js +75 -0
- package/src/lib/layout-language/ai-layout-store.js.map +1 -0
- package/src/lib/layout-language/ai-layout-tree.d.ts +59 -0
- package/src/lib/layout-language/ai-layout-tree.js +84 -0
- package/src/lib/layout-language/ai-layout-tree.js.map +1 -0
- package/src/lib/lazy-widget.d.ts +45 -0
- package/src/lib/lazy-widget.js +53 -0
- package/src/lib/lazy-widget.js.map +1 -0
- package/src/lib/model/ai-build-job.js +2 -1
- package/src/lib/model/ai-build-job.js.map +1 -1
- package/src/lib/model/ai-build-progress.d.ts +20 -3
- package/src/lib/model/ai-build-progress.js +52 -7
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-edit-job.d.ts +109 -0
- package/src/lib/model/ai-edit-job.js +157 -0
- package/src/lib/model/ai-edit-job.js.map +1 -0
- package/src/lib/model/ai-host-publish-context.d.ts +51 -0
- package/src/lib/model/ai-host-publish-context.js +104 -0
- package/src/lib/model/ai-host-publish-context.js.map +1 -0
- package/src/lib/model/ai-job-activity.d.ts +22 -0
- package/src/lib/model/ai-job-activity.js +23 -1
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-job-failure-copy.d.ts +38 -1
- package/src/lib/model/ai-job-failure-copy.js +56 -1
- package/src/lib/model/ai-job-failure-copy.js.map +1 -1
- package/src/lib/model/ai-job-notice.d.ts +3 -1
- package/src/lib/model/ai-job-notice.js +31 -9
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +22 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-logic-job.d.ts +18 -1
- package/src/lib/model/ai-logic-job.js +24 -3
- package/src/lib/model/ai-logic-job.js.map +1 -1
- package/src/lib/model/ai-media-credits.d.ts +8 -4
- package/src/lib/model/ai-media-credits.js +11 -4
- package/src/lib/model/ai-media-credits.js.map +1 -1
- package/src/lib/model/ai-media-kinds.d.ts +68 -0
- package/src/lib/model/ai-media-kinds.js +98 -0
- package/src/lib/model/ai-media-kinds.js.map +1 -0
- package/src/lib/model/ai-site-build-progress.d.ts +9 -1
- package/src/lib/model/ai-site-build-progress.js +54 -14
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-context.d.ts +111 -0
- package/src/lib/model/ai-site-context.js +222 -0
- package/src/lib/model/ai-site-context.js.map +1 -0
- package/src/lib/model/ai-site-job.d.ts +18 -2
- package/src/lib/model/ai-site-job.js +21 -5
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.d.ts +164 -0
- package/src/lib/model/ai-site-kinds.js +1942 -0
- package/src/lib/model/ai-site-kinds.js.map +1 -0
- package/src/lib/model/ai-site-look.d.ts +150 -0
- package/src/lib/model/ai-site-look.js +1236 -0
- package/src/lib/model/ai-site-look.js.map +1 -0
- package/src/lib/model/ai-site-memory.d.ts +80 -0
- package/src/lib/model/ai-site-memory.js +171 -0
- package/src/lib/model/ai-site-memory.js.map +1 -0
- package/src/lib/model/ai-site-start-seo.d.ts +1 -1
- package/src/lib/model/ai-site-start.d.ts +15 -3
- package/src/lib/model/ai-site-start.js +21 -4
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/model/assist-follow-up.d.ts +92 -0
- package/src/lib/model/assist-follow-up.js +128 -0
- package/src/lib/model/assist-follow-up.js.map +1 -0
- package/src/lib/plugin-config.js +2 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +99 -48
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/anthropic.d.ts +9 -1
- package/src/lib/providers/anthropic.js +143 -9
- package/src/lib/providers/anthropic.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +38 -8
- package/src/lib/providers/catalog.js +106 -14
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/contract.d.ts +29 -0
- package/src/lib/providers/contract.js.map +1 -1
- package/src/lib/providers/image-contract.d.ts +22 -1
- package/src/lib/providers/image-contract.js +40 -1
- package/src/lib/providers/image-contract.js.map +1 -1
- package/src/lib/providers/image-endpoints.d.ts +24 -0
- package/src/lib/providers/image-endpoints.js +28 -0
- package/src/lib/providers/image-endpoints.js.map +1 -0
- package/src/lib/providers/model-choice.js +23 -2
- package/src/lib/providers/model-choice.js.map +1 -1
- package/src/lib/providers/routing.js +18 -0
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
- package/src/lib/providers/vertex-image-endpoint.js +80 -0
- package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
- package/src/lib/providers/vertex-image.d.ts +13 -27
- package/src/lib/providers/vertex-image.js +15 -42
- package/src/lib/providers/vertex-image.js.map +1 -1
- package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
- package/src/lib/runtime/ai-blog-post-generation.js +89 -0
- package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
- package/src/lib/runtime/ai-dev-env.d.ts +53 -0
- package/src/lib/runtime/ai-dev-env.js +72 -0
- package/src/lib/runtime/ai-dev-env.js.map +1 -0
- package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
- package/src/lib/runtime/ai-dev-replay.js +313 -0
- package/src/lib/runtime/ai-dev-replay.js.map +1 -0
- package/src/lib/runtime/ai-doctrine-validators.d.ts +82 -0
- package/src/lib/runtime/ai-doctrine-validators.js +436 -4
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +16 -3
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +4 -0
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +13 -1
- package/src/lib/runtime/ai-eval.js +84 -1
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-icon-library.d.ts +257 -0
- package/src/lib/runtime/ai-icon-library.js +254 -0
- package/src/lib/runtime/ai-icon-library.js.map +1 -0
- package/src/lib/runtime/ai-live-batch.d.ts +45 -0
- package/src/lib/runtime/ai-live-batch.js +89 -0
- package/src/lib/runtime/ai-live-batch.js.map +1 -0
- package/src/lib/runtime/ai-node-tree.d.ts +9 -0
- package/src/lib/runtime/ai-node-tree.js +30 -4
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.d.ts +6 -0
- package/src/lib/runtime/ai-palette.generated.js +70 -0
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-palette.js.map +1 -1
- package/src/lib/runtime/ai-runtime.js +46 -2
- package/src/lib/runtime/ai-runtime.js.map +1 -1
- package/src/lib/runtime/site-context.d.ts +58 -0
- package/src/lib/runtime/site-context.js +127 -0
- package/src/lib/runtime/site-context.js.map +1 -0
- package/src/lib/server/ai-admin-conversations.d.ts +22 -0
- package/src/lib/server/ai-admin-conversations.js +197 -0
- package/src/lib/server/ai-admin-conversations.js.map +1 -0
- package/src/lib/server/ai-media-image.d.ts +5 -5
- package/src/lib/server/ai-media-image.js +25 -11
- package/src/lib/server/ai-media-image.js.map +1 -1
- package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
- package/src/lib/server/ai-media-raster-prompt.js +53 -0
- package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
- package/src/lib/server/assist-chat.d.ts +11 -0
- package/src/lib/server/assist-chat.js +57 -18
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit-applied.js +16 -3
- package/src/lib/server/assist-edit-applied.js.map +1 -1
- package/src/lib/server/assist-view-context.d.ts +19 -1
- package/src/lib/server/assist-view-context.js +38 -1
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +10 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/subprocessors.js +17 -1
- package/src/lib/subprocessors.js.map +1 -1
- package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
- package/src/lib/tools/ai-blog-post-tool.js +175 -0
- package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
- package/src/lib/tools/ai-workflow-tool.d.ts +6 -2
- package/src/lib/tools/ai-workflow-tool.js +16 -14
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage/assist-usage.d.ts +3 -0
- package/src/lib/usage/assist-usage.js +4 -2
- package/src/lib/usage/assist-usage.js.map +1 -1
- package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
- package/src/lib/usage/staff-org-ai-conversations.js +152 -0
- package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-site-content.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 { hostRoleCanWrite } from '@aglyn/aglyn/app-utils/organizations'\nimport { pluginResourceDraftWriter } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts'\nimport {\n publishContentEntries,\n writeContentCollection,\n writeContentEntryDraft,\n} from '@aglyn/tenant-data-admin/server/content-entry-drafts'\nimport { aiProductsProposalOf } from '../model/ai-products'\nimport type { AiJob, AiJobOutput } from '../model/ai-jobs.types'\nimport { aiSiteKindOfInputs } from '../model/ai-site-kinds'\nimport { AI_STEP_TIERS } from '../providers/catalog'\nimport { aiModelForStep } from '../providers/routing'\nimport {\n AI_BLOG_POST_MAX_TOKENS,\n AI_BLOG_POST_STEP,\n generateAiBlogPost,\n} from '../runtime/ai-blog-post-generation'\nimport { aiLayoutStarterPhotos } from '../layout-language/ai-layout-pictures'\nimport { aiJobAdmissionFor, type AiJobAdmissionRefusal } from './ai-job-admission'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiGenerationSpent, aiUnspentOutcome } from './ai-job-generation'\nimport type { AiJobStepContext, AiJobStepOutcome, AiJobStepRunner } from './ai-job-text-step'\n\n/**\n * A GUIDED START'S FIRST POSTS AND FIRST PRODUCTS (AGL-3676).\n *\n * A site of the kind \"Blog & writing\" or \"Online store\" got the right look\n * and pages, and nothing real in them: the writing page's cards and the\n * shop's grid named posts and products nobody had written. On a paid plan,\n * the scaffold now builds one more part for those two kinds, before the\n * pages, each its own row with its own timer and credits:\n *\n * - WRITING YOUR FIRST POSTS: a content collection \"Blog\" — the platform's\n * own blog, served at `/{slug}` with an entry page per post — and\n * `AI_SITE_POSTS` posts in it, one generation a pass. Each is written as a\n * draft through the same rules the console's routes hold\n * (`content-entry-drafts.ts`), bylined with the business's name the person\n * gave, and given a starter photo as its cover. They go live with the\n * site: the guided start's publish publishes them with its pages\n * (`aiPublishSitePosts`), as a person's Publish would.\n * - ADDING YOUR FIRST PRODUCTS: the `products` step's catalog, asked for\n * `AI_SITE_PRODUCTS` products, each written through the commerce plugin's\n * `product` draft writer — a DRAFT with no price, off the storefront until\n * the owner prices it, since no price is ever invented. No photo either:\n * the writer takes none, and a stock picture of something else would show\n * a shopper a product that is not the one for sale.\n *\n * The pages built after them are told their titles or names, so the writing\n * page and the shop feature what exists rather than inventing their own.\n *\n * ── Not on the Free taste ────────────────────────────────────────────────\n *\n * A Free site's sections cap is DERIVED to spend the whole taste less one\n * retried section (`aiFreeSiteSectionsWithin`), so there is no slack a post\n * could use without taking a section away; and a Free plan includes neither\n * the `commerce` feature nor a product (`productsPerHost` is 0).\n * `ai-job-free-site.spec.ts` shows the arithmetic. Free sites keep the pages\n * their kind already describes; the owner adds posts and products after.\n */\n\n/** How many first posts a paid blog gets. */\nexport const AI_SITE_POSTS = 3\n\n/** How many first products a paid store gets: asked for, and the most written. */\nexport const AI_SITE_PRODUCTS = { min: 3, max: 6 } as const\n\n/** What each part's row reads. */\nexport const AI_SITE_POSTS_LABEL = 'Writing your first posts'\nexport const AI_SITE_PRODUCTS_LABEL = 'Adding your first products'\n\n/** The blog the posts go in, and the addresses it may answer at, in order of preference. */\nexport const AI_SITE_BLOG_NAME = 'Blog'\nexport const AI_SITE_BLOG_SLUGS = ['blog', 'posts', 'journal', 'articles', 'writing', 'stories'] as const\n\n/** The input a site unit's own job carries what the part needs in. */\nexport const AI_SITE_CONTENT_INPUT = 'siteContent'\n\n/** Which part a site of these answers gets; `null` for the Free taste and every other kind. */\nexport function aiSiteContentPart(\n inputs: Readonly<Record<string, unknown>> | null | undefined,\n freeTaste: boolean,\n): 'posts' | 'products' | null {\n if (freeTaste) return null\n const kind = aiSiteKindOfInputs(inputs)?.id\n if (kind === 'blog') return 'posts'\n if (kind === 'store') return 'products'\n return null\n}\n\n/**\n * ASSUMED: what one post pass reads and writes beside its generation: the\n * host, the collection's slug claim and the entry's transaction.\n */\nexport const AI_SITE_POST_WRITES_MS = 1_500\n\n/** One post's pass: its generation and re-ask at the `copy.blog` ceiling, and its writes. */\nexport const AI_SITE_POST_BUDGET = aiJobStepBudget({\n tier: AI_STEP_TIERS[AI_BLOG_POST_STEP],\n maxTokens: AI_BLOG_POST_MAX_TOKENS,\n lookups: 0,\n ownReadsMs: AI_SITE_POST_WRITES_MS,\n})\n\n/** What a posts unit's job is told: the posts already written and the addresses the blog must not take. */\nexport interface AiSitePostsInput {\n written: Array<{ id: string; title: string }>\n total: number\n /** Addresses the site's own pages answer at, which the blog must not shadow. */\n avoidSlugs: string[]\n /** Who the posts are by: the business's name as given, else empty. */\n byline: string\n}\n\n/** What a products unit's job is told. */\nexport interface AiSiteProductsInput {\n min: number\n max: number\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\nfunction postsInputOf(job: Pick<AiJob, 'inputs'>): AiSitePostsInput {\n const raw = (job.inputs?.[AI_SITE_CONTENT_INPUT] ?? {}) as Partial<AiSitePostsInput>\n return {\n written: Array.isArray(raw.written) ? raw.written.filter((one) => one && typeof one.id === 'string') : [],\n total: typeof raw.total === 'number' && raw.total > 0 ? Math.floor(raw.total) : AI_SITE_POSTS,\n avoidSlugs: Array.isArray(raw.avoidSlugs) ? raw.avoidSlugs.map(str).filter(Boolean) : [],\n byline: str(raw.byline),\n }\n}\n\n/** The id of the `index`th post, of the blog its unit writes under `unitJobId`. */\nexport function aiSitePostId(unitJobId: string, index: number): string {\n return `${unitJobId}-${index}`\n}\n\n/** The id of the `index`th product a products unit writes. */\nexport function aiSiteProductId(unitJobId: string, index: number): string {\n return `${unitJobId}-${index}`\n}\n\n/** A site's subdomain and name, as its outputs and its posts' byline read them. */\nasync function readSite(\n firestore: FirebaseFirestore.Firestore,\n hostId: string,\n): Promise<{ subdomain: string | null; name: string } | null> {\n const host = await firestore.collection('hosts').doc(hostId).get()\n if (!host.exists) return null\n const seo = (host.get('seo') ?? {}) as Record<string, unknown>\n const subdomain = str(host.get('subdomain')) || null\n return { subdomain, name: str(host.get('displayName')) || str(seo['title']) }\n}\n\nconst limitReview = (message: string) => ({ reason: 'limit' as const, message, findings: [] })\n\nexport interface AiSitePostsRunnerDeps {\n generate?: typeof generateAiBlogPost\n writeCollection?: typeof writeContentCollection\n writeEntry?: typeof writeContentEntryDraft\n}\n\n/**\n * The posts unit's runner, on the job the scaffold derived for it: one post a\n * pass, continuing while posts remain. Its first pass makes the blog; every\n * pass finds it again under the unit's own id, and a post under its own id,\n * so a pass repeated after its write reports the post it already wrote.\n */\nexport function createAiSitePostsRunner(deps: AiSitePostsRunnerDeps = {}): AiJobStepRunner {\n const generate = deps.generate ?? generateAiBlogPost\n const writeCollection = deps.writeCollection ?? writeContentCollection\n const writeEntry = deps.writeEntry ?? writeContentEntryDraft\n return async (context): Promise<AiJobStepOutcome> => {\n const { job, firestore, signal } = context\n const model = context.modelFor?.(AI_BLOG_POST_STEP) ?? aiModelForStep(AI_BLOG_POST_STEP)\n if (!job.hostId) return { ...aiUnspentOutcome(model), failure: 'Open the site before writing its posts.' }\n const input = postsInputOf(job)\n const index = input.written.length\n if (index >= input.total) return aiUnspentOutcome(model)\n const site = await readSite(firestore, job.hostId)\n if (!site) return { ...aiUnspentOutcome(model), failure: 'This site no longer exists.' }\n const avoid = new Set(input.avoidSlugs.map((slug) => slug.toLowerCase()))\n const blog = await writeCollection(firestore, {\n hostId: job.hostId,\n uid: job.createdBy,\n id: job.$id,\n displayName: AI_SITE_BLOG_NAME,\n slugs: AI_SITE_BLOG_SLUGS.filter((slug) => !avoid.has(slug)),\n now: context.now,\n })\n if (blog.ok === false) return aiUnspentOutcome(model, { review: limitReview(blog.error) })\n\n const generation = await generate({\n brief: job.brief,\n merchantWords: job.brief,\n earlierTitles: input.written.map((post) => post.title),\n index: index + 1,\n total: input.total,\n model,\n maxTokens: AI_SITE_POST_BUDGET.maxTokens(model),\n ...(signal ? { signal } : {}),\n })\n const spent = aiGenerationSpent(generation)\n if (generation.status === 'refused') return { ...spent, refused: true }\n if (generation.status === 'needs_input') return { ...spent, failure: generation.message }\n const post = generation.value\n // A starter photo as its cover, turned by the job so blogs differ.\n const covers = aiLayoutStarterPhotos(\n Array.from({ length: input.total }, (_, slot) => ({\n imageId: `cover${slot}`,\n frameId: null,\n iconId: null,\n alt: '',\n aspect: 16 / 9,\n sectionIndex: slot + 1,\n role: 'gallery' as const,\n })),\n job.$id,\n )\n const byline = input.byline || site.name\n const written = await writeEntry(firestore, {\n hostId: job.hostId,\n collectionId: blog.id,\n uid: job.createdBy,\n id: aiSitePostId(job.$id, index),\n content: {\n title: post.title,\n excerpt: post.excerpt,\n body: post.body,\n seoDescription: post.seoDescription,\n coverImage: covers[index]?.src,\n ...(byline ? { authorName: byline } : {}),\n },\n now: context.now,\n })\n if (written.ok === false) return { ...spent, review: limitReview(written.error) }\n const output: AiJobOutput = {\n resource: 'entry',\n id: written.id,\n hostId: job.hostId,\n hostSubdomain: site.subdomain,\n label: written.title,\n note: `A post in ${blog.displayName}, at /${blog.slug}/${written.slug}. It goes live with the site.`,\n proposal: { collectionId: blog.id, collectionSlug: blog.slug, slug: written.slug },\n }\n return { ...spent, outputs: [output], ...(index + 1 < input.total ? { continue: true } : {}) }\n }\n}\n\nexport const runAiSitePostsUnit = createAiSitePostsRunner()\n\nexport interface AiSiteProductsRunnerDeps {\n /** The catalog's runner: the `products` step's. */\n catalog: AiJobStepRunner\n writerFor?: typeof pluginResourceDraftWriter\n}\n\n/** The sentence the products unit's brief ends with, which the catalog's rules read as how many. */\nexport function aiSiteProductsBriefLine(input: AiSiteProductsInput = AI_SITE_PRODUCTS): string {\n return `Propose between ${input.min} and ${input.max} products: the store's first ones.`\n}\n\n/**\n * The products unit's runner: the `products` step's catalog for the store's\n * brief, then each proposed product, up to the most a site starts with,\n * written as an unpriced draft by the plugin that keeps products. A product\n * past the plan's allowance stops the writing there, and the row says how\n * many were added; none at all is the allowance's review.\n */\nexport function createAiSiteProductsRunner(deps: AiSiteProductsRunnerDeps): AiJobStepRunner {\n const writerFor = deps.writerFor ?? pluginResourceDraftWriter\n return async (context): Promise<AiJobStepOutcome> => {\n const { job } = context\n const keeper = writerFor('product')\n const model = context.modelFor?.('job.products') ?? aiModelForStep('job.products')\n if (!keeper || !job.hostId) return { ...aiUnspentOutcome(model), failure: 'Products are not available on this site.' }\n const outcome = await deps.catalog({\n ...context,\n job: { ...job, kind: 'products', inputs: { ...job.inputs, target: 'catalog' } },\n })\n if (outcome.refused || outcome.failure || outcome.review) return outcome\n const catalog = outcome.outputs.map((output) => ({ output, proposal: aiProductsProposalOf(output) })).find(\n (entry) => entry.proposal?.kind === 'catalog',\n )\n if (!catalog || catalog.proposal?.kind !== 'catalog') return { ...outcome, outputs: [] }\n const proposed = catalog.proposal.products.slice(0, AI_SITE_PRODUCTS.max)\n const outputs: AiJobOutput[] = []\n let stopped: string | null = null\n for (const [index, product] of proposed.entries()) {\n const written = await keeper.writer.write({\n orgId: job.orgId,\n hostId: job.hostId,\n uid: job.createdBy,\n org: (context.org ?? null) as Readonly<Record<string, unknown>> | null,\n now: context.now,\n id: aiSiteProductId(job.$id, index),\n name: product.name,\n // No price: the owner sets it. No photo: the writer takes none.\n content: {\n name: product.name,\n type: product.type,\n description: product.description,\n tags: product.tags,\n options: product.options,\n seoTitle: product.seoTitle,\n seoDescription: product.seoDescription,\n },\n })\n if (written.ok === false) {\n stopped = written.error\n break\n }\n outputs.push({\n resource: 'product',\n id: written.id,\n hostId: job.hostId,\n hostSubdomain: catalog.output.hostSubdomain ?? null,\n label: written.name,\n note: 'A draft with no price. Set its price and photo in Products to put it on sale.',\n })\n }\n if (!outputs.length) {\n return { ...outcome, outputs: [], review: limitReview(stopped ?? 'No product could be added.') }\n }\n // The allowance that stopped the rest is said where the last one added is.\n if (stopped) {\n const last = outputs[outputs.length - 1]\n last.note = `${last.note} ${outputs.length} of ${proposed.length} added: ${stopped}.`\n }\n return { ...outcome, outputs }\n }\n}\n\n/**\n * Whether a part may be built for this member on this site, asked before its\n * first pass so a refusal spends nothing; `null` admits. Products ask what a\n * `products` job asks (the plan's commerce feature, Commerce on and released\n * for the site) and the writer's own refusal (role, allowance); posts ask\n * that the member may write the site's content.\n */\nexport async function aiSiteContentRefusal(\n part: 'posts' | 'products',\n context: Pick<AiJobStepContext, 'firestore' | 'org' | 'now'> & { job: Pick<AiJob, 'orgId' | 'hostId' | 'createdBy' | 'inputs'> },\n deps: { writerFor?: typeof pluginResourceDraftWriter; admissionFor?: typeof aiJobAdmissionFor } = {},\n): Promise<string | null> {\n const { job, firestore } = context\n if (!job.hostId) return 'Open the site first.'\n if (part === 'posts') {\n const host = await firestore.collection('hosts').doc(job.hostId).get()\n return hostRoleCanWrite((host.get('memberRoles') ?? {})[job.createdBy]) ? null : 'Editing requires the editor role'\n }\n const admission = (deps.admissionFor ?? aiJobAdmissionFor)('products')\n const refused: AiJobAdmissionRefusal | null = admission\n ? await admission({\n firestore,\n orgId: job.orgId,\n hostId: job.hostId,\n inputs: { target: 'catalog' },\n org: context.org ?? null,\n uid: job.createdBy,\n })\n : { status: 403, error: 'Products are not available on this site.' }\n if (refused) return refused.error\n const keeper = (deps.writerFor ?? pluginResourceDraftWriter)('product')\n if (!keeper) return 'Products are not available on this site.'\n const room = await keeper.writer.refusal({\n orgId: job.orgId,\n hostId: job.hostId,\n uid: job.createdBy,\n org: (context.org ?? null) as Readonly<Record<string, unknown>> | null,\n now: context.now,\n })\n return room?.error ?? null\n}\n\n/** The guided start's posts, published with its pages (AGL-3676). */\nexport interface AiSitePostsPublish {\n published: number\n kept: number\n /** The blog's own addresses, for the publish's cache drop. */\n paths: string[]\n}\n\n/**\n * Publishes the posts a guided start wrote, as the entry editor's Publish\n * does, once its pages are live. Never throws: a post that stays a draft is\n * the owner's to publish from Content.\n */\nexport async function aiPublishSitePosts(\n firestore: FirebaseFirestore.Firestore,\n input: { job: Pick<AiJob, '$id' | 'orgId' | 'hostId' | 'createdBy'>; outputs: readonly AiJobOutput[]; now: Date },\n publish: typeof publishContentEntries = publishContentEntries,\n): Promise<AiSitePostsPublish | null> {\n const posts = input.outputs.filter((output) => output.resource === 'entry')\n const collectionId = str(posts[0]?.proposal?.['collectionId'])\n const collectionSlug = str(posts[0]?.proposal?.['collectionSlug'])\n if (!input.job.hostId || !posts.length || !collectionId) return null\n try {\n const result = await publish(firestore, {\n hostId: input.job.hostId,\n collectionId,\n uid: input.job.createdBy,\n ids: posts.map((post) => post.id),\n now: input.now,\n })\n if ('ok' in result) {\n console.warn('ai site posts not published', { jobId: input.job.$id, error: result.error })\n return { published: 0, kept: posts.length, paths: [] }\n }\n const slugs = posts\n .filter((post) => result.published.includes(post.id))\n .map((post) => str(post.proposal?.['slug']))\n .filter(Boolean)\n return {\n published: result.published.length,\n kept: result.kept.length,\n paths: collectionSlug ? [`/${collectionSlug}`, ...slugs.map((slug) => `/${collectionSlug}/${slug}`)] : [],\n }\n } catch (error) {\n console.error('ai site posts publish threw', { orgId: input.job.orgId, jobId: input.job.$id, error })\n return null\n }\n}\n\n/** What a page of a blog or a store is told about the posts or products built before it. */\nexport function aiSiteContentBriefLines(outputs: readonly AiJobOutput[]): string[] {\n const posts = outputs.filter((output) => output.resource === 'entry')\n const products = outputs.filter((output) => output.resource === 'product' && !output.proposal)\n const lines: string[] = []\n if (posts.length) {\n const slug = str(posts[0].proposal?.['collectionSlug'])\n lines.push(\n `This site's blog${slug ? ` at /${slug}` : ''} has these posts: ${posts.map((post) => `“${post.label}”`).join(', ')}.`,\n 'Where a page features writing, feature these posts by their titles; never name a post that is not in this list.',\n )\n }\n if (products.length) {\n lines.push(\n `This store's products are: ${products.map((product) => `“${product.label}”`).join(', ')}.`,\n 'Where a page features products, feature these by their names; never name another product, and never state a price.',\n )\n }\n return lines\n}\n"],"names":["hostRoleCanWrite","pluginResourceDraftWriter","publishContentEntries","writeContentCollection","writeContentEntryDraft","aiProductsProposalOf","aiSiteKindOfInputs","AI_STEP_TIERS","aiModelForStep","AI_BLOG_POST_MAX_TOKENS","AI_BLOG_POST_STEP","generateAiBlogPost","aiLayoutStarterPhotos","aiJobAdmissionFor","aiJobStepBudget","aiGenerationSpent","aiUnspentOutcome","AI_SITE_POSTS","AI_SITE_PRODUCTS","min","max","AI_SITE_POSTS_LABEL","AI_SITE_PRODUCTS_LABEL","AI_SITE_BLOG_NAME","AI_SITE_BLOG_SLUGS","AI_SITE_CONTENT_INPUT","aiSiteContentPart","inputs","freeTaste","kind","id","AI_SITE_POST_WRITES_MS","AI_SITE_POST_BUDGET","tier","maxTokens","lookups","ownReadsMs","str","value","trim","postsInputOf","job","raw","written","Array","isArray","filter","one","total","Math","floor","avoidSlugs","map","Boolean","byline","aiSitePostId","unitJobId","index","aiSiteProductId","readSite","firestore","hostId","host","collection","doc","get","exists","seo","subdomain","name","limitReview","message","reason","findings","createAiSitePostsRunner","deps","generate","writeCollection","writeEntry","context","covers","signal","model","modelFor","failure","input","length","site","avoid","Set","slug","toLowerCase","blog","uid","createdBy","$id","displayName","slugs","has","now","ok","review","error","generation","brief","merchantWords","earlierTitles","post","title","spent","status","refused","from","_","slot","imageId","frameId","iconId","alt","aspect","sectionIndex","role","collectionId","content","excerpt","body","seoDescription","coverImage","src","authorName","output","resource","hostSubdomain","label","note","proposal","collectionSlug","outputs","continue","runAiSitePostsUnit","aiSiteProductsBriefLine","createAiSiteProductsRunner","writerFor","catalog","keeper","outcome","target","find","entry","proposed","products","slice","stopped","product","entries","writer","write","orgId","org","type","description","tags","options","seoTitle","push","last","aiSiteContentRefusal","part","admission","admissionFor","room","refusal","aiPublishSitePosts","publish","posts","result","ids","console","warn","jobId","published","kept","paths","includes","aiSiteContentBriefLines","lines","join"],"mappings":"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"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type ThemeHostDocument } from '@aglyn/aglyn/app-utils/site-theme';
|
|
18
|
+
import type { AiJob } from '../model/ai-jobs.types';
|
|
19
|
+
import { type AiSiteBase, type AiSiteKind } from '../model/ai-site-kinds';
|
|
20
|
+
import { type AiSiteLookAnswer, type AiSiteStyle } from '../model/ai-site-look';
|
|
21
|
+
import { runValidatedGeneration, type AiGenerationCheck } from '../runtime/ai-doctrine';
|
|
22
|
+
import type { AiSystemBlock } from '../runtime/ai-runtime';
|
|
23
|
+
import { type AiJobStepContext, type AiJobStepOutcome } from './ai-job-text-step';
|
|
24
|
+
/**
|
|
25
|
+
* A site's look, designed first (AGL-3660): the scaffold's theme unit.
|
|
26
|
+
*
|
|
27
|
+
* One short answer from the fast tier — the base theme, two hues, a ground,
|
|
28
|
+
* a font pairing and the building blocks' styles, chosen for this business
|
|
29
|
+
* within its kind — then code builds the theme (`ai-site-look.ts`) and saves
|
|
30
|
+
* it the way Setup → Theme saves a pick and its edits: the base theme
|
|
31
|
+
* selected, the site's own look as the override over it, and the style
|
|
32
|
+
* tokens on the site for later edits to keep. The header, the footer and
|
|
33
|
+
* every page are built after it, so they render in it from their first draft.
|
|
34
|
+
*
|
|
35
|
+
* The look never fails a site. An answer that could not be read leaves the
|
|
36
|
+
* kind and the seed to choose; a site whose owner already changed its theme
|
|
37
|
+
* keeps it.
|
|
38
|
+
*/
|
|
39
|
+
/** The answer is a dozen short values, about 150 tokens; the ceiling leaves room to spare. */
|
|
40
|
+
export declare const AI_SITE_LOOK_MAX_TOKENS = 600;
|
|
41
|
+
/** A look pass's time, on the fast tier the look is asked on. */
|
|
42
|
+
export declare const AI_SITE_LOOK_BUDGET: import("./ai-job-budget").AiJobStepBudget;
|
|
43
|
+
/**
|
|
44
|
+
* The generation kind a look is asked under: values code builds a theme
|
|
45
|
+
* from, composing nothing, so the doctrine's short fields block leads it.
|
|
46
|
+
*/
|
|
47
|
+
export declare const AI_SITE_LOOK_KIND = "site-look";
|
|
48
|
+
/** The look door's cached instructions. */
|
|
49
|
+
export declare const AI_SITE_LOOK_INSTRUCTIONS: readonly AiSystemBlock[];
|
|
50
|
+
/** The look's user turn: the site, its kind and what suits it, and the seed. */
|
|
51
|
+
export declare function aiSiteLookPrompt(input: {
|
|
52
|
+
job: Pick<AiJob, 'brief' | '$id' | 'inputs'>;
|
|
53
|
+
kind: AiSiteKind;
|
|
54
|
+
bases: ReadonlyArray<{
|
|
55
|
+
base: AiSiteBase;
|
|
56
|
+
words: string;
|
|
57
|
+
}>;
|
|
58
|
+
}): string;
|
|
59
|
+
/** The answer, read leniently; a call with nothing usable is still an answer, and the seed fills it. */
|
|
60
|
+
export declare const aiSiteLookCheck: AiGenerationCheck<AiSiteLookAnswer>;
|
|
61
|
+
/** The model a look is asked on: the fast tier of the job's provider, unless the creator picked a model. */
|
|
62
|
+
export declare function aiSiteLookModel(job: Pick<AiJob, 'model'>, resolved: string | undefined): string;
|
|
63
|
+
/** The preset id a base names. */
|
|
64
|
+
export declare const aiSiteBasePresetId: (base: AiSiteBase) => string;
|
|
65
|
+
/** The bases a kind suits that this deployment has, best first, with what each looks like. */
|
|
66
|
+
export declare function aiSiteLookBases(kind: AiSiteKind): Array<{
|
|
67
|
+
base: AiSiteBase;
|
|
68
|
+
words: string;
|
|
69
|
+
}>;
|
|
70
|
+
/**
|
|
71
|
+
* Whether a site still has the theme it was born with (AGL-3497), or none:
|
|
72
|
+
* the only theme a look may replace. A site whose owner picked a theme, edited
|
|
73
|
+
* one, or installed one keeps it.
|
|
74
|
+
*/
|
|
75
|
+
export declare function aiSiteThemeUntouched(host: ThemeHostDocument & Record<string, unknown>): boolean;
|
|
76
|
+
export type AiSiteThemeWrite = 'applied' | 'kept' | 'missing';
|
|
77
|
+
/**
|
|
78
|
+
* Saves a look on its site in one transaction: the base theme picked through
|
|
79
|
+
* the theme library (so the site's starter theme is filed in its library, as
|
|
80
|
+
* a person's switch files it), the look as the override over the base, and
|
|
81
|
+
* the style tokens. Again for the same job is a no-op.
|
|
82
|
+
*/
|
|
83
|
+
export declare function aiSaveSiteTheme(firestore: FirebaseFirestore.Firestore, input: {
|
|
84
|
+
hostId: string;
|
|
85
|
+
jobId: string;
|
|
86
|
+
style: AiSiteStyle;
|
|
87
|
+
}): Promise<{
|
|
88
|
+
write: AiSiteThemeWrite;
|
|
89
|
+
baseName: string;
|
|
90
|
+
}>;
|
|
91
|
+
export interface AiSiteLookDeps {
|
|
92
|
+
save?: typeof aiSaveSiteTheme;
|
|
93
|
+
generate?: typeof runValidatedGeneration;
|
|
94
|
+
others?: typeof aiWorkspaceSiteStyles;
|
|
95
|
+
}
|
|
96
|
+
/** The most of a workspace's sites a look is drawn apart from. */
|
|
97
|
+
export declare const AI_SITE_LOOK_SIBLINGS = 50;
|
|
98
|
+
/**
|
|
99
|
+
* The looks of a workspace's other sites (AGL-3660), so a new one is drawn
|
|
100
|
+
* apart from each: none shares their base, hue family, heading font and
|
|
101
|
+
* buttons. A read that fails draws apart from nothing.
|
|
102
|
+
*/
|
|
103
|
+
export declare function aiWorkspaceSiteStyles(firestore: FirebaseFirestore.Firestore, input: {
|
|
104
|
+
orgId: string;
|
|
105
|
+
hostId: string;
|
|
106
|
+
}): Promise<AiSiteStyle[]>;
|
|
107
|
+
/**
|
|
108
|
+
* The theme unit's pass: ask for the look, build it, save it. `job` is the
|
|
109
|
+
* unit's derived job, carrying the scaffold's inputs and its origin job id,
|
|
110
|
+
* which is what the seed is drawn from — so a resumed pass draws the same.
|
|
111
|
+
*/
|
|
112
|
+
export declare function aiRunSiteLook(context: AiJobStepContext, job: AiJob, deps?: AiSiteLookDeps): Promise<AiJobStepOutcome>;
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { DEFAULT_SITE_THEME } from "@aglyn/aglyn/app-utils/default-site";
|
|
18
|
+
import { diffOverride, overrideWriteValue } from "@aglyn/aglyn/app-utils/artifact-overrides";
|
|
19
|
+
import { themeArtifactContent } from "@aglyn/aglyn/app-utils/site-theme";
|
|
20
|
+
import { readThemeSelection, SITE_THEME_ENTRY_ID } from "@aglyn/aglyn/app-utils/theme-library";
|
|
21
|
+
import { dropPluginSiteCache } from "@aglyn/aglyn/plugin-manager/plugin-site-cache";
|
|
22
|
+
import { listServerThemePresets } from "@aglyn/aglyn/plugin-manager/plugin-theme-presets";
|
|
23
|
+
import { runThemeLibraryAction } from "@aglyn/tenant-data-admin/server/theme-library-write";
|
|
24
|
+
import { AI_SITE_FONT_PAIRINGS, aiSiteKindFor, aiSiteKindOfInputs } from "../model/ai-site-kinds.js";
|
|
25
|
+
import { AI_SITE_LOOK_TOOL, AI_SITE_LOOK_TOOL_NAME, aiReadSiteLook, aiSiteSeed, aiSiteStyleFor, aiSiteTheme } from "../model/ai-site-look.js";
|
|
26
|
+
import { aiCatalogEntry, aiDefaultModelFor } from "../providers/catalog.js";
|
|
27
|
+
import { aiModelForStep } from "../providers/routing.js";
|
|
28
|
+
import { runValidatedGeneration } from "../runtime/ai-doctrine.js";
|
|
29
|
+
import { aiJobStepBudget } from "./ai-job-budget.js";
|
|
30
|
+
import { aiGenerationSpent, aiUnspentOutcome } from "./ai-job-generation.js";
|
|
31
|
+
import { AI_JOB_BRIEF_MAX_CHARS } from "./ai-job-text-step.js";
|
|
32
|
+
/**
|
|
33
|
+
* A site's look, designed first (AGL-3660): the scaffold's theme unit.
|
|
34
|
+
*
|
|
35
|
+
* One short answer from the fast tier — the base theme, two hues, a ground,
|
|
36
|
+
* a font pairing and the building blocks' styles, chosen for this business
|
|
37
|
+
* within its kind — then code builds the theme (`ai-site-look.ts`) and saves
|
|
38
|
+
* it the way Setup → Theme saves a pick and its edits: the base theme
|
|
39
|
+
* selected, the site's own look as the override over it, and the style
|
|
40
|
+
* tokens on the site for later edits to keep. The header, the footer and
|
|
41
|
+
* every page are built after it, so they render in it from their first draft.
|
|
42
|
+
*
|
|
43
|
+
* The look never fails a site. An answer that could not be read leaves the
|
|
44
|
+
* kind and the seed to choose; a site whose owner already changed its theme
|
|
45
|
+
* keeps it.
|
|
46
|
+
*/ /** The answer is a dozen short values, about 150 tokens; the ceiling leaves room to spare. */ export const AI_SITE_LOOK_MAX_TOKENS = 600;
|
|
47
|
+
/** A look pass's time, on the fast tier the look is asked on. */ export const AI_SITE_LOOK_BUDGET = aiJobStepBudget({
|
|
48
|
+
tier: 'fast',
|
|
49
|
+
maxTokens: AI_SITE_LOOK_MAX_TOKENS
|
|
50
|
+
});
|
|
51
|
+
/**
|
|
52
|
+
* The generation kind a look is asked under: values code builds a theme
|
|
53
|
+
* from, composing nothing, so the doctrine's short fields block leads it.
|
|
54
|
+
*/ export const AI_SITE_LOOK_KIND = 'site-look';
|
|
55
|
+
/** The base themes as the model reads them; `starter` is the one a new site is born with. */ const STARTER_WORDS = 'the starter theme a new site is born with: a soft neutral ground, rounder corners and calm buttons';
|
|
56
|
+
/** The look door's cached instructions. */ export const AI_SITE_LOOK_INSTRUCTIONS = [
|
|
57
|
+
{
|
|
58
|
+
text: [
|
|
59
|
+
"You choose the look of a new small-business website: the platform's base theme it starts from, and the choices that make it this business's own.",
|
|
60
|
+
`Answer by calling ${AI_SITE_LOOK_TOOL_NAME} exactly once, at once, and write nothing else: no reasoning, no prose.`,
|
|
61
|
+
'- Choose for this business, its audience and its kind of site, never a default: a roofer and a florist should not share a color, a law firm and a yoga studio should not share a typeface.',
|
|
62
|
+
'- Prefer a base theme, font pairing and corners from the lists the request gives for this kind of site; the lists are in no order, so weigh each for this business.',
|
|
63
|
+
'- hue is the brand color and accent sits beside it; pick colors people would expect of this business, and let the design seed tip a close call.',
|
|
64
|
+
'- A hex color written in the brief is the brand color: put it in brand. Otherwise brand is null.',
|
|
65
|
+
'- Choose the building blocks to suit the kind: quiet, flat cards and small eyebrows for minimal sites; raised cards and capitals for bold ones; soft tinted cards for calm ones.'
|
|
66
|
+
].join('\n'),
|
|
67
|
+
cacheBreakpoint: true
|
|
68
|
+
}
|
|
69
|
+
];
|
|
70
|
+
/** A list in a seed's order (Fisher–Yates over a small LCG). */ function shuffled(list, seed) {
|
|
71
|
+
const out = [
|
|
72
|
+
...list
|
|
73
|
+
];
|
|
74
|
+
let state = seed >>> 0 || 1;
|
|
75
|
+
for(let index = out.length - 1; index > 0; index -= 1){
|
|
76
|
+
state = Math.imul(state, 1664525) + 1013904223 >>> 0;
|
|
77
|
+
const other = state % (index + 1);
|
|
78
|
+
[out[index], out[other]] = [
|
|
79
|
+
out[other],
|
|
80
|
+
out[index]
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
/** The look's user turn: the site, its kind and what suits it, and the seed. */ export function aiSiteLookPrompt(input) {
|
|
86
|
+
const { job, kind } = input;
|
|
87
|
+
const text = (key)=>{
|
|
88
|
+
var _job_inputs;
|
|
89
|
+
return typeof ((_job_inputs = job.inputs) == null ? void 0 : _job_inputs[key]) === 'string' ? job.inputs[key].trim() : '';
|
|
90
|
+
};
|
|
91
|
+
const pairings = AI_SITE_FONT_PAIRINGS.filter((pairing)=>kind.look.fonts.includes(pairing.id));
|
|
92
|
+
// The lists in the job's own order: a model reads the first of a list as
|
|
93
|
+
// the answer, and every site of a kind would get the same base and type.
|
|
94
|
+
const seed = aiSiteSeed(job.$id);
|
|
95
|
+
return [
|
|
96
|
+
`Kind of site: ${kind.label}. ${kind.design}`,
|
|
97
|
+
text('businessName') ? `Business name: ${text('businessName')}` : '',
|
|
98
|
+
text('businessType') ? `Business: ${text('businessType')}` : '',
|
|
99
|
+
text('audience') ? `For: ${text('audience')}` : '',
|
|
100
|
+
text('brand') ? `Brand: ${text('brand')}` : '',
|
|
101
|
+
`Brief: ${job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`,
|
|
102
|
+
`Base themes that suit it: ${shuffled(input.bases, seed).map((entry)=>`${entry.base} (${entry.words})`).join('; ')}.`,
|
|
103
|
+
`Font pairings that suit it: ${shuffled(pairings, seed + 1).map((pairing)=>`${pairing.id} (${pairing.feel})`).join('; ')}.`,
|
|
104
|
+
`Corners that suit it: ${kind.look.corners.join(', ')}. Its palette runs ${kind.look.chroma === 'neutral' ? 'neutral, ink and paper with one accent color' : kind.look.chroma}.`,
|
|
105
|
+
`Design seed: ${job.$id.slice(-6)}.`
|
|
106
|
+
].filter(Boolean).join('\n');
|
|
107
|
+
}
|
|
108
|
+
/** The answer, read leniently; a call with nothing usable is still an answer, and the seed fills it. */ export const aiSiteLookCheck = (answer)=>({
|
|
109
|
+
value: aiReadSiteLook(answer),
|
|
110
|
+
violations: []
|
|
111
|
+
});
|
|
112
|
+
/** The model a look is asked on: the fast tier of the job's provider, unless the creator picked a model. */ export function aiSiteLookModel(job, resolved) {
|
|
113
|
+
var _ref;
|
|
114
|
+
const fallback = resolved != null ? resolved : aiModelForStep('job.theme');
|
|
115
|
+
const picked = typeof job.model === 'string' ? job.model.trim() : '';
|
|
116
|
+
if (picked && picked !== 'auto') return fallback;
|
|
117
|
+
const entry = aiCatalogEntry(fallback);
|
|
118
|
+
return (_ref = entry ? aiDefaultModelFor(entry.provider, 'fast') : undefined) != null ? _ref : fallback;
|
|
119
|
+
}
|
|
120
|
+
/** The preset id a base names. */ export const aiSiteBasePresetId = (base)=>`theme-presets.${base}`;
|
|
121
|
+
/** The bases a kind suits that this deployment has, best first, with what each looks like. */ export function aiSiteLookBases(kind) {
|
|
122
|
+
const presets = listServerThemePresets();
|
|
123
|
+
return kind.look.bases.flatMap((base)=>{
|
|
124
|
+
var _preset_description;
|
|
125
|
+
if (base === 'starter') return [
|
|
126
|
+
{
|
|
127
|
+
base,
|
|
128
|
+
words: STARTER_WORDS
|
|
129
|
+
}
|
|
130
|
+
];
|
|
131
|
+
const preset = presets.find((entry)=>entry.id === aiSiteBasePresetId(base));
|
|
132
|
+
return preset ? [
|
|
133
|
+
{
|
|
134
|
+
base,
|
|
135
|
+
words: (_preset_description = preset.description) != null ? _preset_description : preset.name
|
|
136
|
+
}
|
|
137
|
+
] : [];
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/** Plain JSON, deep-sorted, for comparing two themes as stored. */ function stable(value) {
|
|
141
|
+
return JSON.stringify(value, (_key, inner)=>inner && typeof inner === 'object' && !Array.isArray(inner) ? Object.fromEntries(Object.entries(inner).sort(([a], [b])=>a.localeCompare(b))) : inner);
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Whether a site still has the theme it was born with (AGL-3497), or none:
|
|
145
|
+
* the only theme a look may replace. A site whose owner picked a theme, edited
|
|
146
|
+
* one, or installed one keeps it.
|
|
147
|
+
*/ export function aiSiteThemeUntouched(host) {
|
|
148
|
+
if (host.themeOverride) return false;
|
|
149
|
+
const selection = readThemeSelection(host);
|
|
150
|
+
if (selection.kind === 'default') return true;
|
|
151
|
+
if (selection.kind !== 'custom' || selection.id !== SITE_THEME_ENTRY_ID) return false;
|
|
152
|
+
return stable(themeArtifactContent(host.theme)) === stable(themeArtifactContent(DEFAULT_SITE_THEME));
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Saves a look on its site in one transaction: the base theme picked through
|
|
156
|
+
* the theme library (so the site's starter theme is filed in its library, as
|
|
157
|
+
* a person's switch files it), the look as the override over the base, and
|
|
158
|
+
* the style tokens. Again for the same job is a no-op.
|
|
159
|
+
*/ export async function aiSaveSiteTheme(firestore, input) {
|
|
160
|
+
var _listServerThemePresets_find, _ref;
|
|
161
|
+
const hostRef = firestore.collection('hosts').doc(input.hostId);
|
|
162
|
+
const preset = input.style.base === 'starter' ? null : (_listServerThemePresets_find = listServerThemePresets().find((entry)=>entry.id === aiSiteBasePresetId(input.style.base))) != null ? _listServerThemePresets_find : null;
|
|
163
|
+
const base = preset ? themeArtifactContent(preset.theme) : DEFAULT_SITE_THEME;
|
|
164
|
+
const baseName = (_ref = preset == null ? void 0 : preset.name) != null ? _ref : 'Site theme';
|
|
165
|
+
const edited = aiSiteTheme(_extends({}, input.style, {
|
|
166
|
+
base: preset ? input.style.base : 'starter'
|
|
167
|
+
}), base);
|
|
168
|
+
const override = overrideWriteValue(diffOverride(base, edited), null);
|
|
169
|
+
const siteStyle = _extends({}, input.style, {
|
|
170
|
+
base: preset ? input.style.base : 'starter',
|
|
171
|
+
jobId: input.jobId
|
|
172
|
+
});
|
|
173
|
+
const write = await firestore.runTransaction(async (tx)=>{
|
|
174
|
+
const snapshot = await tx.get(hostRef);
|
|
175
|
+
if (!snapshot.exists) return 'missing';
|
|
176
|
+
const host = snapshot.data();
|
|
177
|
+
const stored = host['siteStyle'];
|
|
178
|
+
if ((stored == null ? void 0 : stored.jobId) === input.jobId) return 'applied';
|
|
179
|
+
if (!aiSiteThemeUntouched(host)) return 'kept';
|
|
180
|
+
const extra = {
|
|
181
|
+
themeOverride: override,
|
|
182
|
+
siteStyle
|
|
183
|
+
};
|
|
184
|
+
if (preset) {
|
|
185
|
+
const plan = await runThemeLibraryAction(tx, hostRef, {
|
|
186
|
+
action: 'select',
|
|
187
|
+
target: {
|
|
188
|
+
kind: 'preset',
|
|
189
|
+
id: preset.id,
|
|
190
|
+
name: preset.name,
|
|
191
|
+
theme: base
|
|
192
|
+
}
|
|
193
|
+
}, extra);
|
|
194
|
+
if (plan.ok === false) throw new Error(`the site's base theme could not be picked: ${plan.error}`);
|
|
195
|
+
} else {
|
|
196
|
+
tx.update(hostRef, _extends({
|
|
197
|
+
theme: DEFAULT_SITE_THEME
|
|
198
|
+
}, extra));
|
|
199
|
+
}
|
|
200
|
+
return 'applied';
|
|
201
|
+
});
|
|
202
|
+
if (write === 'applied') {
|
|
203
|
+
// The theme is live the moment it is written; cached pages go now.
|
|
204
|
+
await dropPluginSiteCache({
|
|
205
|
+
hostIds: [
|
|
206
|
+
input.hostId
|
|
207
|
+
],
|
|
208
|
+
reason: 'ai site look'
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
write,
|
|
213
|
+
baseName
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/** The sentence a look's row and output carry. */ function lookLabel(style, baseName) {
|
|
217
|
+
const pairing = AI_SITE_FONT_PAIRINGS.find((entry)=>entry.id === style.fonts);
|
|
218
|
+
return `Your look: ${baseName} base, ${pairing ? `${pairing.heading.family} type` : 'its own type'}, ${style.cards} cards`;
|
|
219
|
+
}
|
|
220
|
+
/** The most of a workspace's sites a look is drawn apart from. */ export const AI_SITE_LOOK_SIBLINGS = 50;
|
|
221
|
+
/**
|
|
222
|
+
* The looks of a workspace's other sites (AGL-3660), so a new one is drawn
|
|
223
|
+
* apart from each: none shares their base, hue family, heading font and
|
|
224
|
+
* buttons. A read that fails draws apart from nothing.
|
|
225
|
+
*/ export async function aiWorkspaceSiteStyles(firestore, input) {
|
|
226
|
+
try {
|
|
227
|
+
const snapshot = await firestore.collection('hosts').where('orgId', '==', input.orgId).limit(AI_SITE_LOOK_SIBLINGS).get();
|
|
228
|
+
return snapshot.docs.flatMap((doc)=>{
|
|
229
|
+
const style = doc.get('siteStyle');
|
|
230
|
+
return doc.id !== input.hostId && style && typeof style.base === 'string' ? [
|
|
231
|
+
style
|
|
232
|
+
] : [];
|
|
233
|
+
});
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.warn('ai site look: the workspace sites could not be read; drawing apart from none', {
|
|
236
|
+
orgId: input.orgId,
|
|
237
|
+
error
|
|
238
|
+
});
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* The theme unit's pass: ask for the look, build it, save it. `job` is the
|
|
244
|
+
* unit's derived job, carrying the scaffold's inputs and its origin job id,
|
|
245
|
+
* which is what the seed is drawn from — so a resumed pass draws the same.
|
|
246
|
+
*/ export async function aiRunSiteLook(context, job, deps = {}) {
|
|
247
|
+
var _deps_save, _deps_generate, _aiSiteKindOfInputs, _deps_others;
|
|
248
|
+
var _job_inputs, _job_inputs1;
|
|
249
|
+
const save = (_deps_save = deps.save) != null ? _deps_save : aiSaveSiteTheme;
|
|
250
|
+
const generate = (_deps_generate = deps.generate) != null ? _deps_generate : runValidatedGeneration;
|
|
251
|
+
const model = aiSiteLookModel(job, context.modelFor == null ? void 0 : context.modelFor.call(context, 'job.theme'));
|
|
252
|
+
if (!job.hostId) return _extends({}, aiUnspentOutcome(model), {
|
|
253
|
+
failure: 'This site job names no site.'
|
|
254
|
+
});
|
|
255
|
+
const kind = (_aiSiteKindOfInputs = aiSiteKindOfInputs(job.inputs)) != null ? _aiSiteKindOfInputs : aiSiteKindFor(job.brief);
|
|
256
|
+
const origin = typeof ((_job_inputs = job.inputs) == null ? void 0 : _job_inputs['originJobId']) === 'string' ? job.inputs['originJobId'] : job.$id;
|
|
257
|
+
const result = await generate(AI_SITE_LOOK_KIND, _extends({
|
|
258
|
+
step: 'job.theme',
|
|
259
|
+
model,
|
|
260
|
+
instructions: AI_SITE_LOOK_INSTRUCTIONS,
|
|
261
|
+
messages: [
|
|
262
|
+
{
|
|
263
|
+
role: 'user',
|
|
264
|
+
content: aiSiteLookPrompt({
|
|
265
|
+
job: _extends({}, job, {
|
|
266
|
+
$id: origin
|
|
267
|
+
}),
|
|
268
|
+
kind,
|
|
269
|
+
bases: aiSiteLookBases(kind)
|
|
270
|
+
})
|
|
271
|
+
}
|
|
272
|
+
],
|
|
273
|
+
tool: AI_SITE_LOOK_TOOL,
|
|
274
|
+
maxTokens: AI_SITE_LOOK_BUDGET.maxTokens(model),
|
|
275
|
+
thinking: 'off',
|
|
276
|
+
check: aiSiteLookCheck
|
|
277
|
+
}, context.signal ? {
|
|
278
|
+
signal: context.signal
|
|
279
|
+
} : {}));
|
|
280
|
+
const spent = aiGenerationSpent(result);
|
|
281
|
+
if (result.status === 'refused') return _extends({}, spent, {
|
|
282
|
+
refused: true
|
|
283
|
+
});
|
|
284
|
+
// An answer that could not be used leaves the kind and the seed to choose.
|
|
285
|
+
const answer = result.status === 'ok' ? result.value : {};
|
|
286
|
+
const brand = typeof ((_job_inputs1 = job.inputs) == null ? void 0 : _job_inputs1['brand']) === 'string' ? job.inputs['brand'] : null;
|
|
287
|
+
const others = (_deps_others = deps.others) != null ? _deps_others : aiWorkspaceSiteStyles;
|
|
288
|
+
const avoid = await others(context.firestore, {
|
|
289
|
+
orgId: job.orgId,
|
|
290
|
+
hostId: job.hostId
|
|
291
|
+
});
|
|
292
|
+
const style = aiSiteStyleFor({
|
|
293
|
+
kind,
|
|
294
|
+
answer,
|
|
295
|
+
seed: aiSiteSeed(origin),
|
|
296
|
+
brand,
|
|
297
|
+
avoid
|
|
298
|
+
});
|
|
299
|
+
const saved = await save(context.firestore, {
|
|
300
|
+
hostId: job.hostId,
|
|
301
|
+
jobId: origin,
|
|
302
|
+
style
|
|
303
|
+
});
|
|
304
|
+
if (saved.write === 'missing') throw new Error(`site ${job.hostId} vanished while its look was chosen`);
|
|
305
|
+
const output = {
|
|
306
|
+
resource: 'theme',
|
|
307
|
+
id: 'look',
|
|
308
|
+
hostId: job.hostId,
|
|
309
|
+
label: saved.write === 'kept' ? 'Kept the theme you chose for this site' : lookLabel(style, saved.baseName),
|
|
310
|
+
proposal: JSON.parse(JSON.stringify({
|
|
311
|
+
style,
|
|
312
|
+
write: saved.write
|
|
313
|
+
}))
|
|
314
|
+
};
|
|
315
|
+
return _extends({}, spent, {
|
|
316
|
+
outputs: [
|
|
317
|
+
output
|
|
318
|
+
]
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
//# sourceMappingURL=ai-job-site-look.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-site-look.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 { DEFAULT_SITE_THEME } from '@aglyn/aglyn/app-utils/default-site'\nimport { diffOverride, overrideWriteValue } from '@aglyn/aglyn/app-utils/artifact-overrides'\nimport { themeArtifactContent, type ThemeHostDocument } from '@aglyn/aglyn/app-utils/site-theme'\nimport { readThemeSelection, SITE_THEME_ENTRY_ID } from '@aglyn/aglyn/app-utils/theme-library'\nimport { dropPluginSiteCache } from '@aglyn/aglyn/plugin-manager/plugin-site-cache'\nimport { listServerThemePresets } from '@aglyn/aglyn/plugin-manager/plugin-theme-presets'\nimport type { HostTheme } from '@aglyn/shared-data-types'\nimport { runThemeLibraryAction } from '@aglyn/tenant-data-admin/server/theme-library-write'\nimport type { AiJob, AiJobOutput } from '../model/ai-jobs.types'\nimport { AI_SITE_FONT_PAIRINGS, aiSiteKindFor, aiSiteKindOfInputs, type AiSiteBase, type AiSiteKind } from '../model/ai-site-kinds'\nimport {\n AI_SITE_LOOK_TOOL,\n AI_SITE_LOOK_TOOL_NAME,\n aiReadSiteLook,\n aiSiteSeed,\n aiSiteStyleFor,\n aiSiteTheme,\n type AiSiteLookAnswer,\n type AiSiteStyle,\n} from '../model/ai-site-look'\nimport { aiCatalogEntry, aiDefaultModelFor } from '../providers/catalog'\nimport { aiModelForStep } from '../providers/routing'\nimport { runValidatedGeneration, type AiGenerationCheck } from '../runtime/ai-doctrine'\nimport type { AiSystemBlock } from '../runtime/ai-runtime'\nimport { aiJobStepBudget } from './ai-job-budget'\nimport { aiGenerationSpent, aiUnspentOutcome } from './ai-job-generation'\nimport { AI_JOB_BRIEF_MAX_CHARS, type AiJobStepContext, type AiJobStepOutcome } from './ai-job-text-step'\n\n/**\n * A site's look, designed first (AGL-3660): the scaffold's theme unit.\n *\n * One short answer from the fast tier — the base theme, two hues, a ground,\n * a font pairing and the building blocks' styles, chosen for this business\n * within its kind — then code builds the theme (`ai-site-look.ts`) and saves\n * it the way Setup → Theme saves a pick and its edits: the base theme\n * selected, the site's own look as the override over it, and the style\n * tokens on the site for later edits to keep. The header, the footer and\n * every page are built after it, so they render in it from their first draft.\n *\n * The look never fails a site. An answer that could not be read leaves the\n * kind and the seed to choose; a site whose owner already changed its theme\n * keeps it.\n */\n\n/** The answer is a dozen short values, about 150 tokens; the ceiling leaves room to spare. */\nexport const AI_SITE_LOOK_MAX_TOKENS = 600\n\n/** A look pass's time, on the fast tier the look is asked on. */\nexport const AI_SITE_LOOK_BUDGET = aiJobStepBudget({ tier: 'fast', maxTokens: AI_SITE_LOOK_MAX_TOKENS })\n\n/**\n * The generation kind a look is asked under: values code builds a theme\n * from, composing nothing, so the doctrine's short fields block leads it.\n */\nexport const AI_SITE_LOOK_KIND = 'site-look'\n\n/** The base themes as the model reads them; `starter` is the one a new site is born with. */\nconst STARTER_WORDS = 'the starter theme a new site is born with: a soft neutral ground, rounder corners and calm buttons'\n\n/** The look door's cached instructions. */\nexport const AI_SITE_LOOK_INSTRUCTIONS: readonly AiSystemBlock[] = [\n {\n text: [\n \"You choose the look of a new small-business website: the platform's base theme it starts from, and the choices that make it this business's own.\",\n `Answer by calling ${AI_SITE_LOOK_TOOL_NAME} exactly once, at once, and write nothing else: no reasoning, no prose.`,\n '- Choose for this business, its audience and its kind of site, never a default: a roofer and a florist should not share a color, a law firm and a yoga studio should not share a typeface.',\n '- Prefer a base theme, font pairing and corners from the lists the request gives for this kind of site; the lists are in no order, so weigh each for this business.',\n '- hue is the brand color and accent sits beside it; pick colors people would expect of this business, and let the design seed tip a close call.',\n '- A hex color written in the brief is the brand color: put it in brand. Otherwise brand is null.',\n '- Choose the building blocks to suit the kind: quiet, flat cards and small eyebrows for minimal sites; raised cards and capitals for bold ones; soft tinted cards for calm ones.',\n ].join('\\n'),\n cacheBreakpoint: true,\n },\n]\n\n/** A list in a seed's order (Fisher–Yates over a small LCG). */\nfunction shuffled<T>(list: readonly T[], seed: number): T[] {\n const out = [...list]\n let state = seed >>> 0 || 1\n for (let index = out.length - 1; index > 0; index -= 1) {\n state = (Math.imul(state, 1664525) + 1013904223) >>> 0\n const other = state % (index + 1)\n ;[out[index], out[other]] = [out[other] as T, out[index] as T]\n }\n return out\n}\n\n/** The look's user turn: the site, its kind and what suits it, and the seed. */\nexport function aiSiteLookPrompt(input: {\n job: Pick<AiJob, 'brief' | '$id' | 'inputs'>\n kind: AiSiteKind\n bases: ReadonlyArray<{ base: AiSiteBase; words: string }>\n}): string {\n const { job, kind } = input\n const text = (key: string) => (typeof job.inputs?.[key] === 'string' ? (job.inputs[key] as string).trim() : '')\n const pairings = AI_SITE_FONT_PAIRINGS.filter((pairing) => kind.look.fonts.includes(pairing.id))\n // The lists in the job's own order: a model reads the first of a list as\n // the answer, and every site of a kind would get the same base and type.\n const seed = aiSiteSeed(job.$id)\n return [\n `Kind of site: ${kind.label}. ${kind.design}`,\n text('businessName') ? `Business name: ${text('businessName')}` : '',\n text('businessType') ? `Business: ${text('businessType')}` : '',\n text('audience') ? `For: ${text('audience')}` : '',\n text('brand') ? `Brand: ${text('brand')}` : '',\n `Brief: ${job.brief.slice(0, AI_JOB_BRIEF_MAX_CHARS)}`,\n `Base themes that suit it: ${shuffled(input.bases, seed).map((entry) => `${entry.base} (${entry.words})`).join('; ')}.`,\n `Font pairings that suit it: ${shuffled(pairings, seed + 1).map((pairing) => `${pairing.id} (${pairing.feel})`).join('; ')}.`,\n `Corners that suit it: ${kind.look.corners.join(', ')}. Its palette runs ${kind.look.chroma === 'neutral' ? 'neutral, ink and paper with one accent color' : kind.look.chroma}.`,\n `Design seed: ${job.$id.slice(-6)}.`,\n ]\n .filter(Boolean)\n .join('\\n')\n}\n\n/** The answer, read leniently; a call with nothing usable is still an answer, and the seed fills it. */\nexport const aiSiteLookCheck: AiGenerationCheck<AiSiteLookAnswer> = (answer) => ({\n value: aiReadSiteLook(answer),\n violations: [],\n})\n\n/** The model a look is asked on: the fast tier of the job's provider, unless the creator picked a model. */\nexport function aiSiteLookModel(job: Pick<AiJob, 'model'>, resolved: string | undefined): string {\n const fallback = resolved ?? aiModelForStep('job.theme')\n const picked = typeof job.model === 'string' ? job.model.trim() : ''\n if (picked && picked !== 'auto') return fallback\n const entry = aiCatalogEntry(fallback)\n return (entry ? aiDefaultModelFor(entry.provider, 'fast') : undefined) ?? fallback\n}\n\n/** The preset id a base names. */\nexport const aiSiteBasePresetId = (base: AiSiteBase) => `theme-presets.${base}`\n\n/** The bases a kind suits that this deployment has, best first, with what each looks like. */\nexport function aiSiteLookBases(kind: AiSiteKind): Array<{ base: AiSiteBase; words: string }> {\n const presets = listServerThemePresets()\n return kind.look.bases.flatMap((base): Array<{ base: AiSiteBase; words: string }> => {\n if (base === 'starter') return [{ base, words: STARTER_WORDS }]\n const preset = presets.find((entry) => entry.id === aiSiteBasePresetId(base))\n return preset ? [{ base, words: preset.description ?? preset.name }] : []\n })\n}\n\n/** Plain JSON, deep-sorted, for comparing two themes as stored. */\nfunction stable(value: unknown): string {\n return JSON.stringify(value, (_key, inner) =>\n inner && typeof inner === 'object' && !Array.isArray(inner)\n ? Object.fromEntries(Object.entries(inner as Record<string, unknown>).sort(([a], [b]) => a.localeCompare(b)))\n : inner,\n )\n}\n\n/**\n * Whether a site still has the theme it was born with (AGL-3497), or none:\n * the only theme a look may replace. A site whose owner picked a theme, edited\n * one, or installed one keeps it.\n */\nexport function aiSiteThemeUntouched(host: ThemeHostDocument & Record<string, unknown>): boolean {\n if (host.themeOverride) return false\n const selection = readThemeSelection(host)\n if (selection.kind === 'default') return true\n if (selection.kind !== 'custom' || selection.id !== SITE_THEME_ENTRY_ID) return false\n return stable(themeArtifactContent(host.theme)) === stable(themeArtifactContent(DEFAULT_SITE_THEME))\n}\n\nexport type AiSiteThemeWrite = 'applied' | 'kept' | 'missing'\n\n/**\n * Saves a look on its site in one transaction: the base theme picked through\n * the theme library (so the site's starter theme is filed in its library, as\n * a person's switch files it), the look as the override over the base, and\n * the style tokens. Again for the same job is a no-op.\n */\nexport async function aiSaveSiteTheme(\n firestore: FirebaseFirestore.Firestore,\n input: { hostId: string; jobId: string; style: AiSiteStyle },\n): Promise<{ write: AiSiteThemeWrite; baseName: string }> {\n const hostRef = firestore.collection('hosts').doc(input.hostId)\n const preset =\n input.style.base === 'starter'\n ? null\n : (listServerThemePresets().find((entry) => entry.id === aiSiteBasePresetId(input.style.base)) ?? null)\n const base: HostTheme = preset ? themeArtifactContent(preset.theme) : DEFAULT_SITE_THEME\n const baseName = preset?.name ?? 'Site theme'\n const edited = aiSiteTheme({ ...input.style, base: preset ? input.style.base : 'starter' }, base)\n const override = overrideWriteValue(diffOverride(base, edited), null)\n const siteStyle = { ...input.style, base: preset ? input.style.base : 'starter', jobId: input.jobId }\n const write = await firestore.runTransaction(async (tx): Promise<AiSiteThemeWrite> => {\n const snapshot = await tx.get(hostRef)\n if (!snapshot.exists) return 'missing'\n const host = snapshot.data() as ThemeHostDocument & Record<string, unknown>\n const stored = host['siteStyle'] as { jobId?: unknown } | undefined\n if (stored?.jobId === input.jobId) return 'applied'\n if (!aiSiteThemeUntouched(host)) return 'kept'\n const extra = { themeOverride: override, siteStyle }\n if (preset) {\n const plan = await runThemeLibraryAction(\n tx,\n hostRef,\n { action: 'select', target: { kind: 'preset', id: preset.id, name: preset.name, theme: base } },\n extra,\n )\n if (plan.ok === false) throw new Error(`the site's base theme could not be picked: ${plan.error}`)\n } else {\n tx.update(hostRef, { theme: DEFAULT_SITE_THEME, ...extra })\n }\n return 'applied'\n })\n if (write === 'applied') {\n // The theme is live the moment it is written; cached pages go now.\n await dropPluginSiteCache({ hostIds: [input.hostId], reason: 'ai site look' })\n }\n return { write, baseName }\n}\n\n/** The sentence a look's row and output carry. */\nfunction lookLabel(style: AiSiteStyle, baseName: string): string {\n const pairing = AI_SITE_FONT_PAIRINGS.find((entry) => entry.id === style.fonts)\n return `Your look: ${baseName} base, ${pairing ? `${pairing.heading.family} type` : 'its own type'}, ${style.cards} cards`\n}\n\nexport interface AiSiteLookDeps {\n save?: typeof aiSaveSiteTheme\n generate?: typeof runValidatedGeneration\n others?: typeof aiWorkspaceSiteStyles\n}\n\n/** The most of a workspace's sites a look is drawn apart from. */\nexport const AI_SITE_LOOK_SIBLINGS = 50\n\n/**\n * The looks of a workspace's other sites (AGL-3660), so a new one is drawn\n * apart from each: none shares their base, hue family, heading font and\n * buttons. A read that fails draws apart from nothing.\n */\nexport async function aiWorkspaceSiteStyles(\n firestore: FirebaseFirestore.Firestore,\n input: { orgId: string; hostId: string },\n): Promise<AiSiteStyle[]> {\n try {\n const snapshot = await firestore\n .collection('hosts')\n .where('orgId', '==', input.orgId)\n .limit(AI_SITE_LOOK_SIBLINGS)\n .get()\n return snapshot.docs.flatMap((doc) => {\n const style = doc.get('siteStyle') as AiSiteStyle | undefined\n return doc.id !== input.hostId && style && typeof style.base === 'string' ? [style] : []\n })\n } catch (error) {\n console.warn('ai site look: the workspace sites could not be read; drawing apart from none', { orgId: input.orgId, error })\n return []\n }\n}\n\n/**\n * The theme unit's pass: ask for the look, build it, save it. `job` is the\n * unit's derived job, carrying the scaffold's inputs and its origin job id,\n * which is what the seed is drawn from — so a resumed pass draws the same.\n */\nexport async function aiRunSiteLook(\n context: AiJobStepContext,\n job: AiJob,\n deps: AiSiteLookDeps = {},\n): Promise<AiJobStepOutcome> {\n const save = deps.save ?? aiSaveSiteTheme\n const generate = deps.generate ?? runValidatedGeneration\n const model = aiSiteLookModel(job, context.modelFor?.('job.theme'))\n if (!job.hostId) return { ...aiUnspentOutcome(model), failure: 'This site job names no site.' }\n const kind = aiSiteKindOfInputs(job.inputs) ?? aiSiteKindFor(job.brief)\n const origin = typeof job.inputs?.['originJobId'] === 'string' ? (job.inputs['originJobId'] as string) : job.$id\n const result = await generate<AiSiteLookAnswer>(AI_SITE_LOOK_KIND, {\n step: 'job.theme',\n model,\n instructions: AI_SITE_LOOK_INSTRUCTIONS as AiSystemBlock[],\n messages: [{ role: 'user', content: aiSiteLookPrompt({ job: { ...job, $id: origin }, kind, bases: aiSiteLookBases(kind) }) }],\n tool: AI_SITE_LOOK_TOOL,\n maxTokens: AI_SITE_LOOK_BUDGET.maxTokens(model),\n thinking: 'off',\n check: aiSiteLookCheck,\n ...(context.signal ? { signal: context.signal } : {}),\n })\n const spent = aiGenerationSpent(result)\n if (result.status === 'refused') return { ...spent, refused: true }\n // An answer that could not be used leaves the kind and the seed to choose.\n const answer = result.status === 'ok' ? result.value : {}\n const brand = typeof job.inputs?.['brand'] === 'string' ? (job.inputs['brand'] as string) : null\n const others = deps.others ?? aiWorkspaceSiteStyles\n const avoid = await others(context.firestore, { orgId: job.orgId, hostId: job.hostId })\n const style = aiSiteStyleFor({ kind, answer, seed: aiSiteSeed(origin), brand, avoid })\n const saved = await save(context.firestore, { hostId: job.hostId, jobId: origin, style })\n if (saved.write === 'missing') throw new Error(`site ${job.hostId} vanished while its look was chosen`)\n const output: AiJobOutput = {\n resource: 'theme',\n id: 'look',\n hostId: job.hostId,\n label:\n saved.write === 'kept'\n ? 'Kept the theme you chose for this site'\n : lookLabel(style, saved.baseName),\n proposal: JSON.parse(JSON.stringify({ style, write: saved.write })) as Record<string, unknown>,\n }\n return { ...spent, outputs: [output] }\n}\n"],"names":["DEFAULT_SITE_THEME","diffOverride","overrideWriteValue","themeArtifactContent","readThemeSelection","SITE_THEME_ENTRY_ID","dropPluginSiteCache","listServerThemePresets","runThemeLibraryAction","AI_SITE_FONT_PAIRINGS","aiSiteKindFor","aiSiteKindOfInputs","AI_SITE_LOOK_TOOL","AI_SITE_LOOK_TOOL_NAME","aiReadSiteLook","aiSiteSeed","aiSiteStyleFor","aiSiteTheme","aiCatalogEntry","aiDefaultModelFor","aiModelForStep","runValidatedGeneration","aiJobStepBudget","aiGenerationSpent","aiUnspentOutcome","AI_JOB_BRIEF_MAX_CHARS","AI_SITE_LOOK_MAX_TOKENS","AI_SITE_LOOK_BUDGET","tier","maxTokens","AI_SITE_LOOK_KIND","STARTER_WORDS","AI_SITE_LOOK_INSTRUCTIONS","text","join","cacheBreakpoint","shuffled","list","seed","out","state","index","length","Math","imul","other","aiSiteLookPrompt","input","job","kind","key","inputs","trim","pairings","filter","pairing","look","fonts","includes","id","$id","label","design","brief","slice","bases","map","entry","base","words","feel","corners","chroma","Boolean","aiSiteLookCheck","answer","value","violations","aiSiteLookModel","resolved","fallback","picked","model","provider","undefined","aiSiteBasePresetId","aiSiteLookBases","presets","flatMap","preset","find","description","name","stable","JSON","stringify","_key","inner","Array","isArray","Object","fromEntries","entries","sort","a","b","localeCompare","aiSiteThemeUntouched","host","themeOverride","selection","theme","aiSaveSiteTheme","firestore","hostRef","collection","doc","hostId","style","baseName","edited","override","siteStyle","jobId","write","runTransaction","tx","snapshot","get","exists","data","stored","extra","plan","action","target","ok","Error","error","update","hostIds","reason","lookLabel","heading","family","cards","AI_SITE_LOOK_SIBLINGS","aiWorkspaceSiteStyles","where","orgId","limit","docs","console","warn","aiRunSiteLook","context","deps","save","generate","modelFor","failure","origin","result","step","instructions","messages","role","content","tool","thinking","check","signal","spent","status","refused","brand","others","avoid","saved","output","resource","proposal","parse","outputs"],"mappings":"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"}
|