@aglyn/plugins-ai 1.0.0-beta.232 → 1.0.0-beta.234
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/components/ai-job-links.d.ts +6 -0
- package/src/lib/components/ai-job-links.js +9 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +41 -3
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.js +91 -8
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/declarations.server.js +7 -4
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/jobs/ai-build-capabilities.js +8 -2
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-job-admission.d.ts +2 -1
- package/src/lib/jobs/ai-job-admission.js.map +1 -1
- package/src/lib/jobs/ai-job-email-step.d.ts +39 -0
- package/src/lib/jobs/ai-job-email-step.js +116 -5
- package/src/lib/jobs/ai-job-email-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
- package/src/lib/jobs/ai-job-layout-language.js +3 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-site-pages.d.ts +2 -0
- package/src/lib/jobs/ai-job-layout-site-pages.js +23 -11
- package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +3 -2
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +16 -0
- package/src/lib/jobs/ai-job-page-language.js +35 -6
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.d.ts +3 -0
- package/src/lib/jobs/ai-job-page-step.js +18 -4
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +40 -1
- package/src/lib/jobs/ai-job-plan-step.js +121 -16
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +6 -6
- package/src/lib/jobs/ai-job-site-content.js +6 -12
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +11 -0
- package/src/lib/jobs/ai-job-site-step.js +96 -44
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +14 -4
- package/src/lib/jobs/ai-jobs-notify.js +70 -4
- package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
- package/src/lib/jobs/ai-jobs.d.ts +22 -4
- package/src/lib/jobs/ai-jobs.js +47 -12
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +98 -0
- package/src/lib/jobs/ai-layout-stock-photos.js +248 -0
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -0
- package/src/lib/jobs/ai-site-publish.d.ts +11 -0
- package/src/lib/jobs/ai-site-publish.js +31 -1
- package/src/lib/jobs/ai-site-publish.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.d.ts +151 -0
- package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js +183 -0
- package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.d.ts +27 -0
- package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js +192 -0
- package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.d.ts +27 -0
- package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js +222 -0
- package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.d.ts +29 -0
- package/src/lib/layout-language/ai-layout-compiler.js +698 -12
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +107 -0
- package/src/lib/layout-language/ai-layout-design.js +197 -0
- package/src/lib/layout-language/ai-layout-design.js.map +1 -0
- package/src/lib/layout-language/ai-layout-frame.d.ts +6 -0
- package/src/lib/layout-language/ai-layout-frame.js +22 -6
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.d.ts +2 -0
- package/src/lib/layout-language/ai-layout-language.js +15 -1
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/layout-language/ai-layout-links.d.ts +11 -0
- package/src/lib/layout-language/ai-layout-links.js +4 -0
- package/src/lib/layout-language/ai-layout-links.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +11 -5
- package/src/lib/layout-language/ai-layout-pictures.js +80 -17
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-job-notice.d.ts +9 -1
- package/src/lib/model/ai-job-notice.js +23 -0
- package/src/lib/model/ai-job-notice.js.map +1 -1
- package/src/lib/model/ai-plan-capabilities.d.ts +23 -0
- package/src/lib/model/ai-plan-capabilities.js +5 -0
- package/src/lib/model/ai-plan-capabilities.js.map +1 -1
- package/src/lib/model/ai-site-build-progress.d.ts +12 -2
- package/src/lib/model/ai-site-build-progress.js +78 -4
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +209 -22
- package/src/lib/model/ai-site-job.js +247 -20
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.js +6 -1
- package/src/lib/model/ai-site-kinds.js.map +1 -1
- package/src/lib/model/ai-site-look.d.ts +21 -0
- package/src/lib/model/ai-site-look.js +63 -14
- package/src/lib/model/ai-site-look.js.map +1 -1
- package/src/lib/operator-alerts.d.ts +18 -0
- package/src/lib/operator-alerts.js +42 -0
- package/src/lib/operator-alerts.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +39 -1
- package/src/lib/runtime/ai-doctrine-validators.js +236 -6
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +5 -3
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.d.ts +2 -0
- package/src/lib/runtime/ai-palette.generated.js +18 -2
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/server/ai-jobs-resume.js.map +1 -1
- package/src/lib/server/ai-jobs-route.js +15 -3
- package/src/lib/server/ai-jobs-route.js.map +1 -1
- package/src/lib/server/assist-edit.js +5 -2
- package/src/lib/server/assist-edit.js.map +1 -1
- package/src/lib/usage/free-ai-credits-left.d.ts +54 -0
- package/src/lib/usage/free-ai-credits-left.js +62 -0
- package/src/lib/usage/free-ai-credits-left.js.map +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-site-start-card.component.tsx"],"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\n'use client'\n\nimport { lockdownRefusalText, parseLockdownRefusal, PLATFORM_BRAND_NAME } from '@aglyn/aglyn'\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport type { ConsoleHostFirstRunZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { ICON_VARIANT_CLOSE } from '@aglyn/shared-data-enums'\nimport {\n mdiAccount,\n mdiBookOpenPageVariant,\n mdiBriefcaseOutline,\n mdiCalendarHeart,\n mdiCamera,\n mdiClockOutline,\n mdiContentCut,\n mdiCreation,\n mdiDumbbell,\n mdiHandHeart,\n mdiHeartPulse,\n mdiHomeCity,\n mdiMusic,\n mdiPaletteOutline,\n mdiRocketLaunch,\n mdiScaleBalance,\n mdiSchool,\n mdiShopping,\n mdiSilverwareForkKnife,\n mdiViewGridOutline,\n mdiWrench,\n mdiYoga,\n mdiInformationOutline,\n mdiPageLayoutHeaderFooter,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { OptionCardGrid } from '@aglyn/shared-ui-jsx/components/option-card-grid.component'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n AppBar,\n Box,\n Button,\n Card,\n CardActionArea,\n Chip,\n CircularProgress,\n Container,\n Dialog,\n DialogActions,\n DialogContent,\n IconButton,\n MenuItem,\n Stack,\n Step,\n StepLabel,\n Stepper,\n TextField,\n Toolbar,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { alpha, type Theme } from '@mui/material/styles'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useId, useRef, useState, type ReactNode } from 'react'\nimport {\n AI_JOB_AUTO_CONFIRM_INPUT,\n AI_SITE_FREE_PAGES,\n AI_SITE_FREE_PAGES_NOTE,\n AI_SITE_SUBMISSION_CHOICES,\n aiFreeSiteCreditEstimate,\n aiSiteCreditEstimate,\n aiSitePagesBand,\n type AiSiteSubmissions,\n} from '../model/ai-site-job'\nimport { FREE_AI_TASTE_CREDITS_PER_MONTH } from '../plan-entitlements'\nimport {\n AI_SITE_START_ANSWERS,\n AI_SITE_START_TYPES,\n aiSiteStartBrief,\n aiSiteStartKind,\n aiSiteStartInputs,\n aiSiteStartRefusal,\n type AiSiteStartAnswers,\n} from '../model/ai-site-start'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { AI_SITE_KINDS } from '../model/ai-site-kinds'\n\n/** The icon each kind of site's card shows. */\nconst AI_SITE_KIND_ICONS: Record<string, string> = {\n business: mdiBriefcaseOutline.path,\n trades: mdiWrench.path,\n professional: mdiScaleBalance.path,\n wellness: mdiHeartPulse.path,\n restaurant: mdiSilverwareForkKnife.path,\n store: mdiShopping.path,\n portfolio: mdiViewGridOutline.path,\n studio: mdiPaletteOutline.path,\n photography: mdiCamera.path,\n blog: mdiBookOpenPageVariant.path,\n events: mdiCalendarHeart.path,\n fitness: mdiDumbbell.path,\n yoga: mdiYoga.path,\n beauty: mdiContentCut.path,\n realestate: mdiHomeCity.path,\n education: mdiSchool.path,\n nonprofit: mdiHandHeart.path,\n music: mdiMusic.path,\n personal: mdiAccount.path,\n landing: mdiRocketLaunch.path,\n 'coming-soon': mdiClockOutline.path,\n}\nimport { AiJobFollow } from './ai-job-follow.component'\nimport { AiModelSelector } from './ai-model-selector.component'\nimport { useAiModelChoice } from './use-ai-model-choice'\nimport { aiSiteBuildHref } from './ai-job-links'\nimport { publishAiJob } from './ai-jobs-store'\n\n/**\n * The guided start (AGL-2918), on the `hostFirstRun` zone of the page a newly\n * created site lands on: a choice, a few questions, and the site scaffold they\n * become.\n *\n * ── Two steps (AGL-3594) ─────────────────────────────────────────────────\n *\n * Step 1 asks how to start, as two equal cards: the starter site — a\n * ready-made home page with a header and footer — or AI. The starter card is\n * the zone's `startBlank`, which gives a site born for the guided start the\n * starter every other new site is born with. The AI card moves to step 2, the\n * questions, which have a Back to step 1.\n *\n * ── A full screen dialog, and therefore three ways out ───────────────────\n *\n * The questions take the whole screen, which is the only presentation that\n * gets them read — but a surface that takes the screen and cannot be left is\n * a funnel, so leaving is drawn first and works three ways on both steps:\n *\n * - the close control at the start of the header,\n * - \"Skip\" at the end of it, named \"Skip and start blank\",\n * - Escape, which `Dialog` reports through `onClose`.\n *\n * Before a job is started all three are the zone's own `startBlank`: the\n * starter site, and no job, no draft, no record of a site half begun. After,\n * they close through `leave`, and the job builds the site.\n *\n * The header is the dialog's own chrome rather than part of its body, so the\n * way out cannot be scrolled off, and it is never disabled — least of all\n * while a request is in flight, which is the moment a person most wants it.\n * Nothing is focused ahead of the dialog's own frame, so the first thing a\n * keyboard reaches is the close control.\n *\n * ── It is absent until the route has answered ────────────────────────────\n *\n * The dialog covers the page, so being drawn on a guess is worse here than it\n * is for a card: it would be a takeover every flag-off workspace watches\n * disappear. Nothing renders at all — no `Dialog`, no portal — until the jobs\n * route has admitted this workspace, and a 404, a 403 or an unreachable route\n * is this widget staying absent and the ordinary blank page being all there\n * is. That the widget is mounted is itself the last of a stack of gates: the\n * shell held the plan's `aiGenerative` entitlement, the site's AI switch and\n * the reader's `ai.generate` first. A lockdown is the start door's to say,\n * here, in its own words.\n *\n * ── It asks, it does not build ───────────────────────────────────────────\n *\n * \"Plan my site\" starts a `site` job, which PLANS first: the pages, their\n * addresses, the navigation, the layout, the contact form and a palette, with\n * an estimated cost beside the button that confirms it. Nothing is built\n * until the person confirms that plan, and everything it then builds is an\n * unpublished draft.\n *\n * ── It stays with the job (AGL-3593) ─────────────────────────────────────\n *\n * Once the job exists the dialog follows it live — planning, the plan with\n * its Confirm, building, then the draft pages to open — so a person never has\n * to leave it to get their site built. \"Open AI jobs\" follows the job in the\n * Assist panel instead; it leaves the guided start the way every other exit\n * does, because the dialog covers the panel it opens.\n */\n\ntype Verdict = 'checking' | 'ready' | 'hidden'\n\ntype Step = 'choose' | 'describe'\n\n/** Labels the dialog for a reader, from its own heading. */\nconst TITLE_ID = 'ai-site-start-title'\n\n/**\n * The takeover's surface, for its paper, its bar and its footer alike: the\n * console page's own background (AGL-3596). The paper sits at elevation 0,\n * which is what keeps MUI's dark-mode elevation overlay off it.\n */\nexport const AI_SITE_START_SURFACE_SX = { bgcolor: 'background.default' } as const\n\n/** The two steps, as the indicator under the header names them. */\nconst STEP_LABELS = ['Choose', 'Describe'] as const\n\n/** The soft round badge an icon sits in, in its accent's tint. */\nfunction IconBadge({ accent, children }: { accent: 'primary' | 'secondary'; children: ReactNode }) {\n return (\n <Box\n aria-hidden\n sx={(theme: Theme) => ({\n width: theme.spacing(9),\n height: theme.spacing(9),\n borderRadius: '50%',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n bgcolor: alpha(theme.palette[accent].main, theme.palette.mode === 'dark' ? 0.24 : 0.12),\n color: `${accent}.main`,\n fontSize: theme.spacing(5.5),\n })}\n >\n {children}\n </Box>\n )\n}\n\n/**\n * One way to start, as a card the whole of which is the button. Its accessible\n * name is its title; its description describes it.\n */\nfunction ChoiceCard({\n accent,\n icon,\n iconId,\n title,\n description,\n busy = false,\n onChoose,\n children,\n}: {\n accent: 'primary' | 'secondary'\n icon: string\n /** Names the icon for a reader of the markup; the icon itself is decorative. */\n iconId: string\n title: string\n description: string\n busy?: boolean\n onChoose: () => void\n children?: ReactNode\n}) {\n const id = useId()\n return (\n <Card\n variant=\"outlined\"\n sx={(theme: Theme) => ({\n borderRadius: 2,\n height: '100%',\n transition: theme.transitions.create(['border-color', 'box-shadow', 'transform'], {\n duration: theme.transitions.duration.shorter,\n }),\n '&:hover, &:focus-within': {\n borderColor: `${accent}.main`,\n boxShadow: theme.shadows[4],\n transform: `translateY(-${theme.spacing(0.25)})`,\n },\n '@media (prefers-reduced-motion: reduce)': {\n transition: 'none',\n '&:hover, &:focus-within': { transform: 'none' },\n },\n })}\n >\n <CardActionArea\n onClick={onChoose}\n disabled={busy}\n aria-labelledby={`${id}-title`}\n aria-describedby={`${id}-description`}\n aria-busy={busy || undefined}\n sx={{ height: '100%', p: { xs: 3, sm: 4 } }}\n >\n <Stack spacing={2} sx={{ alignItems: 'center', textAlign: 'center' }}>\n <IconBadge accent={accent}>\n {busy ? (\n <CircularProgress size=\"1em\" color={accent} aria-label={`${title}…`} />\n ) : (\n <MdiIcon path={icon} data-icon={iconId} aria-hidden />\n )}\n </IconBadge>\n <Typography id={`${id}-title`} variant=\"h6\" component=\"h2\">\n {title}\n </Typography>\n <Typography id={`${id}-description`} variant=\"body2\" color=\"text.secondary\">\n {description}\n </Typography>\n {children}\n </Stack>\n </CardActionArea>\n </Card>\n )\n}\n\n/** A small label above a group of questions. */\nfunction SectionLabel({ children }: { children: ReactNode }) {\n return (\n <Typography variant=\"overline\" color=\"text.secondary\" component=\"h2\" sx={{ display: 'block' }}>\n {children}\n </Typography>\n )\n}\n\n/**\n * The guided start, as the `hostFirstRun` zone draws it, or reopened on a\n * failed start's own answers (AGL-3596): `initialAnswers` opens it on the\n * questions, filled in, for the person to adjust and start a fresh job.\n */\nexport type AiSiteStartCardProps = ConsoleHostFirstRunZoneProps & {\n initialAnswers?: AiSiteStartAnswers | null\n}\n\nexport function AiSiteStartCard({\n hostId,\n orgId,\n orgSlug,\n host,\n startBlank,\n leave,\n initialAnswers = null,\n}: AiSiteStartCardProps) {\n const { data: user } = useUser()\n const router = useRouter()\n // Held in a ref so a request reads WHO is signed in, never the identity of\n // the object that says so.\n const userRef = useRef(user)\n userRef.current = user\n const uid = user?.uid ?? null\n const [verdict, setVerdict] = useState<Verdict>('checking')\n // Read once: a start reopened on its answers stays one while it is open.\n const reopened = useRef(initialAnswers !== null)\n const [step, setStep] = useState<Step>(initialAnswers ? 'describe' : 'choose')\n const [answers, setAnswers] = useState<AiSiteStartAnswers>(initialAnswers ?? AI_SITE_START_ANSWERS)\n const [busy, setBusy] = useState(false)\n // The starter card's own busy state while `startBlank` runs (AGL-3594).\n const [startingStarter, setStartingStarter] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n /** The site job \"Plan my site\" started, as the create door answered with it. */\n const [started, setStarted] = useState<AiJobSummary | null>(null)\n // The Free taste's page band (AGL-3594), read off the verdict request.\n const [freeTaste, setFreeTaste] = useState(false)\n\n useEffect(() => {\n if (!orgId || !uid) return\n let active = true\n void (async () => {\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=1`,\n )\n const payload = response.ok ? await response.json().catch(() => null) : null\n if (active) {\n if (payload?.freeTaste === true) {\n setFreeTaste(true)\n // A reopened start keeps the pages it asked for, within the band.\n setAnswers((current) => ({\n ...current,\n pages: reopened.current ? Math.min(current.pages, AI_SITE_FREE_PAGES.max) : AI_SITE_FREE_PAGES.max,\n welcomeEmail: false,\n }))\n }\n setVerdict(response.status === 404 || response.status === 403 ? 'hidden' : 'ready')\n }\n } catch {\n if (active) setVerdict('hidden')\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, uid])\n\n const answer = useCallback(\n (patch: Partial<AiSiteStartAnswers>) =>\n setAnswers((current) => ({ ...current, ...patch })),\n [],\n )\n\n const refusal = aiSiteStartRefusal(answers, { freeTaste })\n const band = aiSitePagesBand(freeTaste)\n // On a paid plan the person picks the model that builds the site (AGL-3660),\n // Auto by default, from the models their plan allows; every step of the job\n // runs on it. A Free workspace builds on the default and is offered none.\n const modelChoice = useAiModelChoice({ orgId, hostId, surface: 'jobs', kind: 'job.page' })\n // A pick remembered from before needs its cost to estimate by; Auto does not,\n // and the list is otherwise read when the picker opens.\n const { load: loadModels, model: rememberedModel } = modelChoice\n useEffect(() => {\n if (!freeTaste && step === 'describe' && rememberedModel) loadModels()\n }, [freeTaste, step, rememberedModel, loadModels])\n const pickedModel = freeTaste ? null : modelChoice.model\n\n const plan = useCallback(async () => {\n if (!orgId || aiSiteStartRefusal(answers, { freeTaste })) return\n setBusy(true)\n setNotice(null)\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n kind: 'site',\n brief: aiSiteStartBrief(answers),\n // The guided start confirms its own plan (AGL-3594): the build\n // follows the plan with no approval to make.\n inputs: { ...aiSiteStartInputs(answers), [AI_JOB_AUTO_CONFIRM_INPUT]: true },\n ...(pickedModel ? { model: pickedModel } : {}),\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'The site could not be started. Try again.'),\n )\n return\n }\n const created = (payload?.job as AiJobSummary | undefined) ?? null\n if (!created) {\n setNotice('The site could not be started. Try again.')\n return\n }\n // The indicator and the launcher count it from the moment it exists.\n publishAiJob(created)\n // Where the next step is (AGL-3594): the site's \"Building your site\"\n // page for this job. The zone closes without the starter first, so the\n // setup page does not ask again behind it.\n const href = aiSiteBuildHref(orgSlug, host, created.id)\n if (href) {\n ;(leave ?? (() => undefined))()\n router.push(href)\n return\n }\n setStarted(created)\n } catch {\n setNotice('The site could not be started. Try again.')\n } finally {\n setBusy(false)\n }\n }, [orgId, hostId, answers, freeTaste, orgSlug, host, leave, router, pickedModel])\n\n const chooseStarter = useCallback(() => {\n setStartingStarter(true)\n trackEvent('site_start_choice', { choice: 'starter' })\n startBlank()\n }, [startBlank])\n\n const chooseAi = useCallback(() => {\n trackEvent('site_start_choice', { choice: 'ai' })\n setStep('describe')\n }, [])\n\n if (verdict !== 'ready') return null\n\n // Before a job is started every way out is the blank site, which writes the\n // starter (AGL-3594); after, it only closes — the job builds the site.\n const exit = started ? (leave ?? startBlank) : startBlank\n\n // The chosen model's cost against Auto's, as the model list states it.\n const modelMultiplier =\n (pickedModel && modelChoice.options?.options.find((option) => option.id === pickedModel)?.multiplier) || 1\n const estimate = freeTaste\n ? aiFreeSiteCreditEstimate(answers.pages)\n : Math.round(\n aiSiteCreditEstimate(answers.pages, {\n welcomeEmail: answers.welcomeEmail,\n }) * modelMultiplier,\n )\n const estimateText = freeTaste\n ? `Up to about ${estimate.toLocaleString('en-US')} of the ${FREE_AI_TASTE_CREDITS_PER_MONTH} AI credits your Free workspace has each month`\n : `About ${estimate.toLocaleString('en-US')} credits, estimated`\n\n const choosing = step === 'choose' && !started\n\n return (\n <Dialog\n open\n fullScreen\n onClose={exit}\n aria-labelledby={TITLE_ID}\n // The console's own page surface, in both modes. A Dialog's paper sits\n // at elevation 24 by default, and in dark mode MUI lightens an elevated\n // paper with a white overlay, which turned the whole takeover a\n // washed-out gray; the overlay is for floating surfaces, and this one\n // fills the screen, so its paper is flat.\n slotProps={{ paper: { elevation: 0, sx: AI_SITE_START_SURFACE_SX } }}\n >\n {/*\n The way out, before anything it is a way out of: a slim header on the\n paper surface. On a narrow screen it is the TITLE that gives way, so\n the exits keep their words at every width.\n */}\n <AppBar\n position=\"relative\"\n color=\"inherit\"\n elevation={0}\n enableColorOnDark\n sx={{ ...AI_SITE_START_SURFACE_SX, borderBottom: 1, borderColor: 'divider' }}\n >\n <Toolbar variant=\"dense\" sx={{ gap: 1 }}>\n <IconButton\n edge=\"start\"\n color=\"inherit\"\n onClick={exit}\n aria-label=\"Close the guided start\"\n >\n <MdiIcon path={ICON_VARIANT_CLOSE.path} />\n </IconButton>\n <Typography\n id={TITLE_ID}\n variant=\"subtitle1\"\n component=\"div\"\n noWrap\n sx={{ textOverflow: 'ellipsis', flex: 1 }}\n >\n {'Start your site'}\n </Typography>\n <Button color=\"inherit\" onClick={exit} aria-label={started ? 'Close' : 'Skip and start blank'}>\n {started ? 'Close' : 'Skip'}\n </Button>\n </Toolbar>\n </AppBar>\n <DialogContent sx={{ px: { xs: 2, sm: 3 } }}>\n <Container maxWidth={choosing ? 'md' : 'sm'} disableGutters sx={{ py: { xs: 3, sm: 5 } }}>\n <Stack spacing={4}>\n <Stepper\n activeStep={choosing ? 0 : 1}\n aria-label={choosing ? 'Step 1 of 2' : 'Step 2 of 2'}\n sx={{ maxWidth: (theme: Theme) => theme.spacing(40), width: '100%', mx: 'auto' }}\n >\n {STEP_LABELS.map((label) => (\n <Step key={label}>\n <StepLabel>{label}</StepLabel>\n </Step>\n ))}\n </Stepper>\n <Stack spacing={1} sx={{ textAlign: choosing ? 'center' : 'left' }}>\n <Typography variant=\"h4\" component=\"h1\">\n {choosing ? 'How do you want to start?' : 'Tell us about your site'}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n {choosing\n ? 'You can change everything later.'\n : `A few answers and ${PLATFORM_BRAND_NAME} AI plans and writes your pages, then publishes your site.`}\n </Typography>\n </Stack>\n {notice && <Alert severity=\"info\">{notice}</Alert>}\n {choosing ? (\n <Box\n sx={{\n display: 'grid',\n gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' },\n gap: 3,\n }}\n >\n <ChoiceCard\n accent=\"secondary\"\n icon={mdiPageLayoutHeaderFooter.path}\n iconId=\"page-layout-header-footer\"\n title=\"Start from the starter site\"\n description=\"A ready-made home page with a header, footer and contact form you can edit.\"\n busy={startingStarter}\n onChoose={chooseStarter}\n />\n <ChoiceCard\n accent=\"primary\"\n icon={mdiCreation.path}\n iconId=\"creation\"\n title=\"Start with AI\"\n description={`Answer a few questions and ${PLATFORM_BRAND_NAME} AI plans your pages.`}\n onChoose={chooseAi}\n >\n <Chip size=\"small\" color=\"primary\" label=\"Recommended\" />\n {freeTaste && (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Up to ${AI_SITE_FREE_PAGES.max} pages on the Free plan`}\n </Typography>\n )}\n </ChoiceCard>\n </Box>\n ) : started && orgId ? (\n <Card variant=\"outlined\" sx={{ borderRadius: 2, p: { xs: 2, sm: 3 } }}>\n <AiJobFollow\n job={started}\n orgId={orgId}\n orgSlug={orgSlug}\n user={user}\n intro={\n 'Your site is being planned. Review the plan here or in AI jobs, and confirm ' +\n 'it to build — nothing is built, and nothing is published, until you do.'\n }\n // Full screen: AI jobs opens in the panel this dialog covers.\n onOpenJobs={exit}\n />\n </Card>\n ) : (\n <>\n <Stack spacing={2} component=\"section\" aria-label=\"Your business\">\n <SectionLabel>{'Your business'}</SectionLabel>\n <TextField\n fullWidth\n required\n label=\"What kind of site are you creating?\"\n placeholder=\"a neighborhood dog groomer that takes bookings\"\n value={answers.siteType}\n onChange={(event) => answer({ siteType: event.target.value })}\n helperText=\"A few words is enough, or pick one below.\"\n />\n <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>\n {AI_SITE_START_TYPES.map((type) => (\n <Chip\n key={type}\n label={type}\n size=\"small\"\n color={answers.siteType === type ? 'primary' : 'default'}\n variant={answers.siteType === type ? 'filled' : 'outlined'}\n onClick={() => answer({ siteType: type })}\n />\n ))}\n </Box>\n <TextField\n fullWidth\n label=\"Who is it for?\"\n placeholder=\"local dog owners who want a regular groom booked online\"\n value={answers.audience}\n onChange={(event) => answer({ audience: event.target.value })}\n helperText=\"Optional. It narrows who the pages are written for.\"\n />\n </Stack>\n <Stack spacing={2} component=\"section\" aria-label=\"Style\">\n <SectionLabel>{'Style'}</SectionLabel>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {answers.kind\n ? 'This decides how your site looks and which pages it usually has.'\n : 'Picked from your answer above. Choose another to change how your site looks and which pages it usually has.'}\n </Typography>\n <OptionCardGrid\n label=\"Style of site\"\n options={AI_SITE_KINDS.map((kind) => ({\n id: kind.id,\n title: kind.label,\n description: kind.blurb,\n icon: AI_SITE_KIND_ICONS[kind.id],\n }))}\n value={aiSiteStartKind(answers).id}\n onChange={(kind) => answer({ kind })}\n />\n </Stack>\n <Stack spacing={2} component=\"section\" aria-label=\"Details\">\n <SectionLabel>{'Details'}</SectionLabel>\n {/*\n The one setting a new site owner has to make, asked where\n they are already answering: a contact form nobody routed\n files its messages somewhere the person has not looked.\n */}\n <TextField\n select\n fullWidth\n label=\"Where do form submissions go?\"\n value={answers.submissions}\n onChange={(event) =>\n answer({ submissions: event.target.value as AiSiteSubmissions })\n }\n helperText=\"You can change this on the form itself afterwards.\"\n >\n {AI_SITE_SUBMISSION_CHOICES.map((option) => (\n <MenuItem key={option.id} value={option.id}>\n {`${option.label} — ${option.blurb}`}\n </MenuItem>\n ))}\n </TextField>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>\n <TextField\n select\n fullWidth\n label=\"Pages\"\n value={answers.pages}\n onChange={(event) => answer({ pages: Number(event.target.value) })}\n helperText={freeTaste ? AI_SITE_FREE_PAGES_NOTE : undefined}\n >\n {Array.from(\n { length: band.max - band.min + 1 },\n (_, index) => band.min + index,\n ).map((count) => (\n <MenuItem key={count} value={count}>\n {count}\n </MenuItem>\n ))}\n </TextField>\n {!freeTaste && (\n <TextField\n select\n fullWidth\n label=\"Welcome email\"\n value={answers.welcomeEmail ? 'yes' : 'no'}\n onChange={(event) => answer({ welcomeEmail: event.target.value === 'yes' })}\n >\n <MenuItem value=\"yes\">{'Draft one'}</MenuItem>\n <MenuItem value=\"no\">{'No'}</MenuItem>\n </TextField>\n )}\n </Stack>\n </Stack>\n </>\n )}\n </Stack>\n </Container>\n </DialogContent>\n {step === 'describe' && !started && (\n /* The footer stays put while the questions scroll. It wraps, because\n the estimate, the reason and the buttons are wider than a phone. */\n <DialogActions\n sx={{\n px: { xs: 2, sm: 3 },\n py: 2,\n flexWrap: 'wrap',\n rowGap: 1,\n columnGap: 2,\n borderTop: 1,\n borderColor: 'divider',\n ...AI_SITE_START_SURFACE_SX,\n }}\n >\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center', mr: 'auto', minWidth: 0 }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${estimateText}.`}\n </Typography>\n <Tooltip title=\"What it really costs is what each step spends, and you can watch that add up while it runs.\">\n <Box\n component=\"span\"\n tabIndex={0}\n aria-label=\"How the estimate works\"\n sx={{ display: 'inline-flex', color: 'text.secondary' }}\n >\n <MdiIcon path={mdiInformationOutline.path} fontSize=\"small\" />\n </Box>\n </Tooltip>\n </Stack>\n {refusal && (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {refusal}\n </Typography>\n )}\n {!freeTaste && <AiModelSelector choice={modelChoice} disabled={busy} />}\n <Button onClick={() => setStep('choose')}>{'Back'}</Button>\n <Button\n variant=\"contained\"\n size=\"large\"\n disabled={busy || Boolean(refusal)}\n onClick={plan}\n startIcon={<MdiIcon path={mdiCreation.path} />}\n >\n {busy ? 'Starting…' : 'Plan my site'}\n </Button>\n </DialogActions>\n )}\n </Dialog>\n )\n}\n\nexport default AiSiteStartCard\n"],"names":["lockdownRefusalText","parseLockdownRefusal","PLATFORM_BRAND_NAME","trackEvent","ICON_VARIANT_CLOSE","mdiAccount","mdiBookOpenPageVariant","mdiBriefcaseOutline","mdiCalendarHeart","mdiCamera","mdiClockOutline","mdiContentCut","mdiCreation","mdiDumbbell","mdiHandHeart","mdiHeartPulse","mdiHomeCity","mdiMusic","mdiPaletteOutline","mdiRocketLaunch","mdiScaleBalance","mdiSchool","mdiShopping","mdiSilverwareForkKnife","mdiViewGridOutline","mdiWrench","mdiYoga","mdiInformationOutline","mdiPageLayoutHeaderFooter","MdiIcon","OptionCardGrid","authorizedFetch","useUser","Alert","AppBar","Box","Button","Card","CardActionArea","Chip","CircularProgress","Container","Dialog","DialogActions","DialogContent","IconButton","MenuItem","Stack","Step","StepLabel","Stepper","TextField","Toolbar","Tooltip","Typography","alpha","useRouter","useCallback","useEffect","useId","useRef","useState","AI_JOB_AUTO_CONFIRM_INPUT","AI_SITE_FREE_PAGES","AI_SITE_FREE_PAGES_NOTE","AI_SITE_SUBMISSION_CHOICES","aiFreeSiteCreditEstimate","aiSiteCreditEstimate","aiSitePagesBand","FREE_AI_TASTE_CREDITS_PER_MONTH","AI_SITE_START_ANSWERS","AI_SITE_START_TYPES","aiSiteStartBrief","aiSiteStartKind","aiSiteStartInputs","aiSiteStartRefusal","AI_SITE_KINDS","AI_SITE_KIND_ICONS","business","path","trades","professional","wellness","restaurant","store","portfolio","studio","photography","blog","events","fitness","yoga","beauty","realestate","education","nonprofit","music","personal","landing","AiJobFollow","AiModelSelector","useAiModelChoice","aiSiteBuildHref","publishAiJob","TITLE_ID","AI_SITE_START_SURFACE_SX","bgcolor","STEP_LABELS","IconBadge","accent","children","aria-hidden","sx","theme","width","spacing","height","borderRadius","display","alignItems","justifyContent","palette","main","mode","color","fontSize","ChoiceCard","icon","iconId","title","description","busy","onChoose","id","variant","transition","transitions","create","duration","shorter","borderColor","boxShadow","shadows","transform","onClick","disabled","aria-labelledby","aria-describedby","aria-busy","undefined","p","xs","sm","textAlign","size","aria-label","data-icon","component","SectionLabel","AiSiteStartCard","hostId","orgId","orgSlug","host","startBlank","leave","initialAnswers","modelChoice","data","user","router","userRef","current","uid","verdict","setVerdict","reopened","step","setStep","answers","setAnswers","setBusy","startingStarter","setStartingStarter","notice","setNotice","started","setStarted","freeTaste","setFreeTaste","active","response","encodeURIComponent","payload","ok","json","catch","pages","Math","min","max","welcomeEmail","status","answer","patch","refusal","band","surface","kind","load","loadModels","model","rememberedModel","pickedModel","plan","method","headers","body","JSON","stringify","brief","inputs","locked","String","error","created","job","href","push","chooseStarter","choice","chooseAi","exit","modelMultiplier","options","find","option","multiplier","estimate","round","estimateText","toLocaleString","choosing","open","fullScreen","onClose","slotProps","paper","elevation","position","enableColorOnDark","borderBottom","gap","edge","noWrap","textOverflow","flex","px","maxWidth","disableGutters","py","activeStep","mx","map","label","severity","gridTemplateColumns","intro","onOpenJobs","fullWidth","required","placeholder","value","siteType","onChange","event","target","helperText","flexWrap","type","audience","blurb","select","submissions","direction","Number","Array","from","length","_","index","count","rowGap","columnGap","borderTop","mr","minWidth","tabIndex","Boolean","startIcon"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;AAEA,SAASA,mBAAmB,EAAEC,oBAAoB,EAAEC,mBAAmB,QAAQ,eAAc;AAC7F,SAASC,UAAU,QAAQ,0CAAyC;AAEpE,SAASC,kBAAkB,QAAQ,2BAA0B;AAC7D,SACEC,UAAU,EACVC,sBAAsB,EACtBC,mBAAmB,EACnBC,gBAAgB,EAChBC,SAAS,EACTC,eAAe,EACfC,aAAa,EACbC,WAAW,EACXC,WAAW,EACXC,YAAY,EACZC,aAAa,EACbC,WAAW,EACXC,QAAQ,EACRC,iBAAiB,EACjBC,eAAe,EACfC,eAAe,EACfC,SAAS,EACTC,WAAW,EACXC,sBAAsB,EACtBC,kBAAkB,EAClBC,SAAS,EACTC,OAAO,EACPC,qBAAqB,EACrBC,yBAAyB,QACpB,yBAAwB;AAC/B,SAASC,OAAO,QAAQ,uBAAsB;AAC9C,SAASC,cAAc,QAAQ,6DAA4D;AAC3F,SAASC,eAAe,QAAQ,2CAA0C;AAC1E,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SACEC,KAAK,EACLC,MAAM,EACNC,GAAG,EACHC,MAAM,EACNC,IAAI,EACJC,cAAc,EACdC,IAAI,EACJC,gBAAgB,EAChBC,SAAS,EACTC,MAAM,EACNC,aAAa,EACbC,aAAa,EACbC,UAAU,EACVC,QAAQ,EACRC,KAAK,EACLC,IAAI,EACJC,SAAS,EACTC,OAAO,EACPC,SAAS,EACTC,OAAO,EACPC,OAAO,EACPC,UAAU,QACL,gBAAe;AACtB,SAASC,KAAK,QAAoB,uBAAsB;AACxD,SAASC,SAAS,QAAQ,kBAAiB;AAC3C,SAASC,WAAW,EAAEC,SAAS,EAAEC,KAAK,EAAEC,MAAM,EAAEC,QAAQ,QAAwB,QAAO;AACvF,SACEC,yBAAyB,EACzBC,kBAAkB,EAClBC,uBAAuB,EACvBC,0BAA0B,EAC1BC,wBAAwB,EACxBC,oBAAoB,EACpBC,eAAe,QAEV,0BAAsB;AAC7B,SAASC,+BAA+B,QAAQ,0BAAsB;AACtE,SACEC,qBAAqB,EACrBC,mBAAmB,EACnBC,gBAAgB,EAChBC,eAAe,EACfC,iBAAiB,EACjBC,kBAAkB,QAEb,4BAAwB;AAE/B,SAASC,aAAa,QAAQ,4BAAwB;AAEtD,6CAA6C,GAC7C,MAAMC,qBAA6C;IACjDC,UAAUvE,oBAAoBwE,IAAI;IAClCC,QAAQvD,UAAUsD,IAAI;IACtBE,cAAc7D,gBAAgB2D,IAAI;IAClCG,UAAUnE,cAAcgE,IAAI;IAC5BI,YAAY5D,uBAAuBwD,IAAI;IACvCK,OAAO9D,YAAYyD,IAAI;IACvBM,WAAW7D,mBAAmBuD,IAAI;IAClCO,QAAQpE,kBAAkB6D,IAAI;IAC9BQ,aAAa9E,UAAUsE,IAAI;IAC3BS,MAAMlF,uBAAuByE,IAAI;IACjCU,QAAQjF,iBAAiBuE,IAAI;IAC7BW,SAAS7E,YAAYkE,IAAI;IACzBY,MAAMjE,QAAQqD,IAAI;IAClBa,QAAQjF,cAAcoE,IAAI;IAC1Bc,YAAY7E,YAAY+D,IAAI;IAC5Be,WAAWzE,UAAU0D,IAAI;IACzBgB,WAAWjF,aAAaiE,IAAI;IAC5BiB,OAAO/E,SAAS8D,IAAI;IACpBkB,UAAU5F,WAAW0E,IAAI;IACzBmB,SAAS/E,gBAAgB4D,IAAI;IAC7B,eAAerE,gBAAgBqE,IAAI;AACrC;AACA,SAASoB,WAAW,QAAQ,+BAA2B;AACvD,SAASC,eAAe,QAAQ,mCAA+B;AAC/D,SAASC,gBAAgB,QAAQ,2BAAuB;AACxD,SAASC,eAAe,QAAQ,oBAAgB;AAChD,SAASC,YAAY,QAAQ,qBAAiB;AAoE9C,0DAA0D,GAC1D,MAAMC,WAAW;AAEjB;;;;CAIC,GACD,OAAO,MAAMC,2BAA2B;IAAEC,SAAS;AAAqB,EAAU;AAElF,iEAAiE,GACjE,MAAMC,cAAc;IAAC;IAAU;CAAW;AAE1C,gEAAgE,GAChE,SAASC,UAAU,EAAEC,MAAM,EAAEC,QAAQ,EAA4D;IAC/F,qBACE,KAAC3E;QACC4E,aAAW;QACXC,IAAI,CAACC,QAAkB,CAAA;gBACrBC,OAAOD,MAAME,OAAO,CAAC;gBACrBC,QAAQH,MAAME,OAAO,CAAC;gBACtBE,cAAc;gBACdC,SAAS;gBACTC,YAAY;gBACZC,gBAAgB;gBAChBd,SAASnD,MAAM0D,MAAMQ,OAAO,CAACZ,OAAO,CAACa,IAAI,EAAET,MAAMQ,OAAO,CAACE,IAAI,KAAK,SAAS,OAAO;gBAClFC,OAAO,GAAGf,OAAO,KAAK,CAAC;gBACvBgB,UAAUZ,MAAME,OAAO,CAAC;YAC1B,CAAA;kBAECL;;AAGP;AAEA;;;CAGC,GACD,SAASgB,WAAW,EAClBjB,MAAM,EACNkB,IAAI,EACJC,MAAM,EACNC,KAAK,EACLC,WAAW,EACXC,OAAO,KAAK,EACZC,QAAQ,EACRtB,QAAQ,EAWT;IACC,MAAMuB,KAAK1E;IACX,qBACE,KAACtB;QACCiG,SAAQ;QACRtB,IAAI,CAACC,QAAkB,CAAA;gBACrBI,cAAc;gBACdD,QAAQ;gBACRmB,YAAYtB,MAAMuB,WAAW,CAACC,MAAM,CAAC;oBAAC;oBAAgB;oBAAc;iBAAY,EAAE;oBAChFC,UAAUzB,MAAMuB,WAAW,CAACE,QAAQ,CAACC,OAAO;gBAC9C;gBACA,2BAA2B;oBACzBC,aAAa,GAAG/B,OAAO,KAAK,CAAC;oBAC7BgC,WAAW5B,MAAM6B,OAAO,CAAC,EAAE;oBAC3BC,WAAW,CAAC,YAAY,EAAE9B,MAAME,OAAO,CAAC,MAAM,CAAC,CAAC;gBAClD;gBACA,2CAA2C;oBACzCoB,YAAY;oBACZ,2BAA2B;wBAAEQ,WAAW;oBAAO;gBACjD;YACF,CAAA;kBAEA,cAAA,KAACzG;YACC0G,SAASZ;YACTa,UAAUd;YACVe,mBAAiB,GAAGb,GAAG,MAAM,CAAC;YAC9Bc,oBAAkB,GAAGd,GAAG,YAAY,CAAC;YACrCe,aAAWjB,QAAQkB;YACnBrC,IAAI;gBAAEI,QAAQ;gBAAQkC,GAAG;oBAAEC,IAAI;oBAAGC,IAAI;gBAAE;YAAE;sBAE1C,cAAA,MAACzG;gBAAMoE,SAAS;gBAAGH,IAAI;oBAAEO,YAAY;oBAAUkC,WAAW;gBAAS;;kCACjE,KAAC7C;wBAAUC,QAAQA;kCAChBsB,qBACC,KAAC3F;4BAAiBkH,MAAK;4BAAM9B,OAAOf;4BAAQ8C,cAAY,GAAG1B,MAAM,CAAC,CAAC;2CAEnE,KAACpG;4BAAQkD,MAAMgD;4BAAM6B,aAAW5B;4BAAQjB,aAAW;;;kCAGvD,KAACzD;wBAAW+E,IAAI,GAAGA,GAAG,MAAM,CAAC;wBAAEC,SAAQ;wBAAKuB,WAAU;kCACnD5B;;kCAEH,KAAC3E;wBAAW+E,IAAI,GAAGA,GAAG,YAAY,CAAC;wBAAEC,SAAQ;wBAAQV,OAAM;kCACxDM;;oBAEFpB;;;;;AAKX;AAEA,8CAA8C,GAC9C,SAASgD,aAAa,EAAEhD,QAAQ,EAA2B;IACzD,qBACE,KAACxD;QAAWgF,SAAQ;QAAWV,OAAM;QAAiBiC,WAAU;QAAK7C,IAAI;YAAEM,SAAS;QAAQ;kBACzFR;;AAGP;AAWA,OAAO,SAASiD,gBAAgB,EAC9BC,MAAM,EACNC,KAAK,EACLC,OAAO,EACPC,IAAI,EACJC,UAAU,EACVC,KAAK,EACLC,iBAAiB,IAAI,EACA;;QAiJHC,mCAAAA;IAhJlB,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAGzI;IACvB,MAAM0I,SAASlH;IACf,2EAA2E;IAC3E,2BAA2B;IAC3B,MAAMmH,UAAU/G,OAAO6G;IACvBE,QAAQC,OAAO,GAAGH;IAClB,MAAMI,cAAMJ,wBAAAA,KAAMI,GAAG,mBAAI;IACzB,MAAM,CAACC,SAASC,WAAW,GAAGlH,SAAkB;IAChD,yEAAyE;IACzE,MAAMmH,WAAWpH,OAAO0G,mBAAmB;IAC3C,MAAM,CAACW,MAAMC,QAAQ,GAAGrH,SAAeyG,iBAAiB,aAAa;IACrE,MAAM,CAACa,SAASC,WAAW,GAAGvH,SAA6ByG,yBAAAA,iBAAkBhG;IAC7E,MAAM,CAAC6D,MAAMkD,QAAQ,GAAGxH,SAAS;IACjC,wEAAwE;IACxE,MAAM,CAACyH,iBAAiBC,mBAAmB,GAAG1H,SAAS;IACvD,MAAM,CAAC2H,QAAQC,UAAU,GAAG5H,SAAwB;IACpD,8EAA8E,GAC9E,MAAM,CAAC6H,SAASC,WAAW,GAAG9H,SAA8B;IAC5D,uEAAuE;IACvE,MAAM,CAAC+H,WAAWC,aAAa,GAAGhI,SAAS;IAE3CH,UAAU;QACR,IAAI,CAACuG,SAAS,CAACY,KAAK;QACpB,IAAIiB,SAAS;QACb,KAAK,AAAC,CAAA;YACJ,IAAI;gBACF,MAAMC,WAAW,MAAMhK,gBACrB4I,QAAQC,OAAO,EACf,CAAC,mBAAmB,EAAEoB,mBAAmB/B,OAAO,QAAQ,CAAC;gBAE3D,MAAMgC,UAAUF,SAASG,EAAE,GAAG,MAAMH,SAASI,IAAI,GAAGC,KAAK,CAAC,IAAM,QAAQ;gBACxE,IAAIN,QAAQ;oBACV,IAAIG,CAAAA,2BAAAA,QAASL,SAAS,MAAK,MAAM;wBAC/BC,aAAa;wBACb,kEAAkE;wBAClET,WAAW,CAACR,UAAa,aACpBA;gCACHyB,OAAOrB,SAASJ,OAAO,GAAG0B,KAAKC,GAAG,CAAC3B,QAAQyB,KAAK,EAAEtI,mBAAmByI,GAAG,IAAIzI,mBAAmByI,GAAG;gCAClGC,cAAc;;oBAElB;oBACA1B,WAAWgB,SAASW,MAAM,KAAK,OAAOX,SAASW,MAAM,KAAK,MAAM,WAAW;gBAC7E;YACF,EAAE,eAAM;gBACN,IAAIZ,QAAQf,WAAW;YACzB;QACF,CAAA;QACA,OAAO;YACLe,SAAS;QACX;IACF,GAAG;QAAC7B;QAAOY;KAAI;IAEf,MAAM8B,SAASlJ,YACb,CAACmJ,QACCxB,WAAW,CAACR,UAAa,aAAKA,SAAYgC,SAC5C,EAAE;IAGJ,MAAMC,UAAUlI,mBAAmBwG,SAAS;QAAES;IAAU;IACxD,MAAMkB,OAAO1I,gBAAgBwH;IAC7B,6EAA6E;IAC7E,4EAA4E;IAC5E,0EAA0E;IAC1E,MAAMrB,cAAclE,iBAAiB;QAAE4D;QAAOD;QAAQ+C,SAAS;QAAQC,MAAM;IAAW;IACxF,8EAA8E;IAC9E,wDAAwD;IACxD,MAAM,EAAEC,MAAMC,UAAU,EAAEC,OAAOC,eAAe,EAAE,GAAG7C;IACrD7G,UAAU;QACR,IAAI,CAACkI,aAAaX,SAAS,cAAcmC,iBAAiBF;IAC5D,GAAG;QAACtB;QAAWX;QAAMmC;QAAiBF;KAAW;IACjD,MAAMG,cAAczB,YAAY,OAAOrB,YAAY4C,KAAK;IAExD,MAAMG,OAAO7J,YAAY;QACvB,IAAI,CAACwG,SAAStF,mBAAmBwG,SAAS;YAAES;QAAU,IAAI;QAC1DP,QAAQ;QACRI,UAAU;QACV,IAAI;;YACF,MAAMM,WAAW,MAAMhK,gBAAgB4I,QAAQC,OAAO,EAAE,gBAAgB;gBACtE2C,QAAQ;gBACRC,SAAS;oBAAE,gBAAgB;gBAAmB;gBAC9CC,MAAMC,KAAKC,SAAS,CAAC;oBACnB1D;oBACAD;oBACAgD,MAAM;oBACNY,OAAOpJ,iBAAiB2G;oBACxB,+DAA+D;oBAC/D,6CAA6C;oBAC7C0C,QAAQ,aAAKnJ,kBAAkByG;wBAAU,CAACrH,0BAA0B,EAAE;;mBAClEuJ,cAAc;oBAAEF,OAAOE;gBAAY,IAAI,CAAC;YAEhD;YACA,MAAMpB,UAAU,MAAMF,SAASI,IAAI,GAAGC,KAAK,CAAC,IAAM;YAClD,IAAI,CAACL,SAASG,EAAE,EAAE;;gBAChB,MAAM4B,SAAS7N,qBAAqB8L,SAASW,MAAM,EAAET;gBACrDR,UACEqC,SACI9N,oBAAoB8N,UACpBC,gBAAO9B,2BAAAA,QAAS+B,KAAK,oBAAI;gBAE/B;YACF;YACA,MAAMC,kBAAWhC,2BAAAA,QAASiC,GAAG,mBAAiC;YAC9D,IAAI,CAACD,SAAS;gBACZxC,UAAU;gBACV;YACF;YACA,qEAAqE;YACrElF,aAAa0H;YACb,qEAAqE;YACrE,uEAAuE;YACvE,2CAA2C;YAC3C,MAAME,OAAO7H,gBAAgB4D,SAASC,MAAM8D,QAAQ5F,EAAE;YACtD,IAAI8F,MAAM;;gBACP,CAAC9D,gBAAAA,QAAU,IAAMhB;gBAClBqB,OAAO0D,IAAI,CAACD;gBACZ;YACF;YACAxC,WAAWsC;QACb,EAAE,eAAM;YACNxC,UAAU;QACZ,SAAU;YACRJ,QAAQ;QACV;IACF,GAAG;QAACpB;QAAOD;QAAQmB;QAASS;QAAW1B;QAASC;QAAME;QAAOK;QAAQ2C;KAAY;IAEjF,MAAMgB,gBAAgB5K,YAAY;QAChC8H,mBAAmB;QACnBpL,WAAW,qBAAqB;YAAEmO,QAAQ;QAAU;QACpDlE;IACF,GAAG;QAACA;KAAW;IAEf,MAAMmE,WAAW9K,YAAY;QAC3BtD,WAAW,qBAAqB;YAAEmO,QAAQ;QAAK;QAC/CpD,QAAQ;IACV,GAAG,EAAE;IAEL,IAAIJ,YAAY,SAAS,OAAO;IAEhC,4EAA4E;IAC5E,uEAAuE;IACvE,MAAM0D,OAAO9C,UAAWrB,gBAAAA,QAASD,aAAcA;IAE/C,uEAAuE;IACvE,MAAMqE,kBACJ,AAACpB,iBAAe9C,uBAAAA,YAAYmE,OAAO,sBAAnBnE,oCAAAA,qBAAqBmE,OAAO,CAACC,IAAI,CAAC,CAACC,SAAWA,OAAOvG,EAAE,KAAKgF,iCAA5D9C,kCAA0EsE,UAAU,KAAK;IAC3G,MAAMC,WAAWlD,YACb1H,yBAAyBiH,QAAQkB,KAAK,IACtCC,KAAKyC,KAAK,CACR5K,qBAAqBgH,QAAQkB,KAAK,EAAE;QAClCI,cAActB,QAAQsB,YAAY;IACpC,KAAKgC;IAEX,MAAMO,eAAepD,YACjB,CAAC,YAAY,EAAEkD,SAASG,cAAc,CAAC,SAAS,QAAQ,EAAE5K,gCAAgC,8CAA8C,CAAC,GACzI,CAAC,MAAM,EAAEyK,SAASG,cAAc,CAAC,SAAS,mBAAmB,CAAC;IAElE,MAAMC,WAAWjE,SAAS,YAAY,CAACS;IAEvC,qBACE,MAAChJ;QACCyM,IAAI;QACJC,UAAU;QACVC,SAASb;QACTtF,mBAAiB1C;QACjB,uEAAuE;QACvE,wEAAwE;QACxE,gEAAgE;QAChE,sEAAsE;QACtE,0CAA0C;QAC1C8I,WAAW;YAAEC,OAAO;gBAAEC,WAAW;gBAAGxI,IAAIP;YAAyB;QAAE;;0BAOnE,KAACvE;gBACCuN,UAAS;gBACT7H,OAAM;gBACN4H,WAAW;gBACXE,iBAAiB;gBACjB1I,IAAI,aAAKP;oBAA0BkJ,cAAc;oBAAG/G,aAAa;;0BAEjE,cAAA,MAACxF;oBAAQkF,SAAQ;oBAAQtB,IAAI;wBAAE4I,KAAK;oBAAE;;sCACpC,KAAC/M;4BACCgN,MAAK;4BACLjI,OAAM;4BACNoB,SAASwF;4BACT7E,cAAW;sCAEX,cAAA,KAAC9H;gCAAQkD,MAAM3E,mBAAmB2E,IAAI;;;sCAExC,KAACzB;4BACC+E,IAAI7B;4BACJ8B,SAAQ;4BACRuB,WAAU;4BACViG,MAAM;4BACN9I,IAAI;gCAAE+I,cAAc;gCAAYC,MAAM;4BAAE;sCAEvC;;sCAEH,KAAC5N;4BAAOwF,OAAM;4BAAUoB,SAASwF;4BAAM7E,cAAY+B,UAAU,UAAU;sCACpEA,UAAU,UAAU;;;;;0BAI3B,KAAC9I;gBAAcoE,IAAI;oBAAEiJ,IAAI;wBAAE1G,IAAI;wBAAGC,IAAI;oBAAE;gBAAE;0BACxC,cAAA,KAAC/G;oBAAUyN,UAAUhB,WAAW,OAAO;oBAAMiB,cAAc;oBAACnJ,IAAI;wBAAEoJ,IAAI;4BAAE7G,IAAI;4BAAGC,IAAI;wBAAE;oBAAE;8BACrF,cAAA,MAACzG;wBAAMoE,SAAS;;0CACd,KAACjE;gCACCmN,YAAYnB,WAAW,IAAI;gCAC3BvF,cAAYuF,WAAW,gBAAgB;gCACvClI,IAAI;oCAAEkJ,UAAU,CAACjJ,QAAiBA,MAAME,OAAO,CAAC;oCAAKD,OAAO;oCAAQoJ,IAAI;gCAAO;0CAE9E3J,YAAY4J,GAAG,CAAC,CAACC,sBAChB,KAACxN;kDACC,cAAA,KAACC;sDAAWuN;;uCADHA;;0CAKf,MAACzN;gCAAMoE,SAAS;gCAAGH,IAAI;oCAAEyC,WAAWyF,WAAW,WAAW;gCAAO;;kDAC/D,KAAC5L;wCAAWgF,SAAQ;wCAAKuB,WAAU;kDAChCqF,WAAW,8BAA8B;;kDAE5C,KAAC5L;wCAAWgF,SAAQ;wCAAQV,OAAM;kDAC/BsH,WACG,qCACA,CAAC,kBAAkB,EAAEhP,oBAAoB,0DAA0D,CAAC;;;;4BAG3GsL,wBAAU,KAACvJ;gCAAMwO,UAAS;0CAAQjF;;4BAClC0D,yBACC,MAAC/M;gCACC6E,IAAI;oCACFM,SAAS;oCACToJ,qBAAqB;wCAAEnH,IAAI;wCAAOC,IAAI;oCAAU;oCAChDoG,KAAK;gCACP;;kDAEA,KAAC9H;wCACCjB,QAAO;wCACPkB,MAAMnG,0BAA0BmD,IAAI;wCACpCiD,QAAO;wCACPC,OAAM;wCACNC,aAAY;wCACZC,MAAMmD;wCACNlD,UAAUiG;;kDAEZ,MAACvG;wCACCjB,QAAO;wCACPkB,MAAMnH,YAAYmE,IAAI;wCACtBiD,QAAO;wCACPC,OAAM;wCACNC,aAAa,CAAC,2BAA2B,EAAEhI,oBAAoB,qBAAqB,CAAC;wCACrFkI,UAAUmG;;0DAEV,KAAChM;gDAAKmH,MAAK;gDAAQ9B,OAAM;gDAAU4I,OAAM;;4CACxC5E,2BACC,KAACtI;gDAAWgF,SAAQ;gDAAUV,OAAM;0DACjC,CAAC,MAAM,EAAE7D,mBAAmByI,GAAG,CAAC,uBAAuB,CAAC;;;;;iCAK/Dd,WAAWzB,sBACb,KAAC5H;gCAAKiG,SAAQ;gCAAWtB,IAAI;oCAAEK,cAAc;oCAAGiC,GAAG;wCAAEC,IAAI;wCAAGC,IAAI;oCAAE;gCAAE;0CAClE,cAAA,KAACrD;oCACC+H,KAAKxC;oCACLzB,OAAOA;oCACPC,SAASA;oCACTO,MAAMA;oCACNkG,OACE,iFACA;oCAEF,8DAA8D;oCAC9DC,YAAYpC;;+CAIhB;;kDACE,MAACzL;wCAAMoE,SAAS;wCAAG0C,WAAU;wCAAUF,cAAW;;0DAChD,KAACG;0DAAc;;0DACf,KAAC3G;gDACC0N,SAAS;gDACTC,QAAQ;gDACRN,OAAM;gDACNO,aAAY;gDACZC,OAAO7F,QAAQ8F,QAAQ;gDACvBC,UAAU,CAACC,QAAUxE,OAAO;wDAAEsE,UAAUE,MAAMC,MAAM,CAACJ,KAAK;oDAAC;gDAC3DK,YAAW;;0DAEb,KAAClP;gDAAI6E,IAAI;oDAAEM,SAAS;oDAAQgK,UAAU;oDAAQ1B,KAAK;gDAAE;0DAClDrL,oBAAoBgM,GAAG,CAAC,CAACgB,qBACxB,KAAChP;wDAECiO,OAAOe;wDACP7H,MAAK;wDACL9B,OAAOuD,QAAQ8F,QAAQ,KAAKM,OAAO,YAAY;wDAC/CjJ,SAAS6C,QAAQ8F,QAAQ,KAAKM,OAAO,WAAW;wDAChDvI,SAAS,IAAM2D,OAAO;gEAAEsE,UAAUM;4DAAK;uDALlCA;;0DASX,KAACpO;gDACC0N,SAAS;gDACTL,OAAM;gDACNO,aAAY;gDACZC,OAAO7F,QAAQqG,QAAQ;gDACvBN,UAAU,CAACC,QAAUxE,OAAO;wDAAE6E,UAAUL,MAAMC,MAAM,CAACJ,KAAK;oDAAC;gDAC3DK,YAAW;;;;kDAGf,MAACtO;wCAAMoE,SAAS;wCAAG0C,WAAU;wCAAUF,cAAW;;0DAChD,KAACG;0DAAc;;0DACf,KAACxG;gDAAWgF,SAAQ;gDAAQV,OAAM;0DAC/BuD,QAAQ6B,IAAI,GACT,qEACA;;0DAEN,KAAClL;gDACC0O,OAAM;gDACN9B,SAAS9J,cAAc2L,GAAG,CAAC,CAACvD,OAAU,CAAA;wDACpC3E,IAAI2E,KAAK3E,EAAE;wDACXJ,OAAO+E,KAAKwD,KAAK;wDACjBtI,aAAa8E,KAAKyE,KAAK;wDACvB1J,MAAMlD,kBAAkB,CAACmI,KAAK3E,EAAE,CAAC;oDACnC,CAAA;gDACA2I,OAAOvM,gBAAgB0G,SAAS9C,EAAE;gDAClC6I,UAAU,CAAClE,OAASL,OAAO;wDAAEK;oDAAK;;;;kDAGtC,MAACjK;wCAAMoE,SAAS;wCAAG0C,WAAU;wCAAUF,cAAW;;0DAChD,KAACG;0DAAc;;0DAMf,KAAC3G;gDACCuO,MAAM;gDACNb,SAAS;gDACTL,OAAM;gDACNQ,OAAO7F,QAAQwG,WAAW;gDAC1BT,UAAU,CAACC,QACTxE,OAAO;wDAAEgF,aAAaR,MAAMC,MAAM,CAACJ,KAAK;oDAAsB;gDAEhEK,YAAW;0DAEVpN,2BAA2BsM,GAAG,CAAC,CAAC3B,uBAC/B,KAAC9L;wDAAyBkO,OAAOpC,OAAOvG,EAAE;kEACvC,GAAGuG,OAAO4B,KAAK,CAAC,GAAG,EAAE5B,OAAO6C,KAAK,EAAE;uDADvB7C,OAAOvG,EAAE;;0DAK5B,MAACtF;gDAAM6O,WAAW;oDAAErI,IAAI;oDAAUC,IAAI;gDAAM;gDAAGrC,SAAS;;kEACtD,KAAChE;wDACCuO,MAAM;wDACNb,SAAS;wDACTL,OAAM;wDACNQ,OAAO7F,QAAQkB,KAAK;wDACpB6E,UAAU,CAACC,QAAUxE,OAAO;gEAAEN,OAAOwF,OAAOV,MAAMC,MAAM,CAACJ,KAAK;4DAAE;wDAChEK,YAAYzF,YAAY5H,0BAA0BqF;kEAEjDyI,MAAMC,IAAI,CACT;4DAAEC,QAAQlF,KAAKN,GAAG,GAAGM,KAAKP,GAAG,GAAG;wDAAE,GAClC,CAAC0F,GAAGC,QAAUpF,KAAKP,GAAG,GAAG2F,OACzB3B,GAAG,CAAC,CAAC4B,sBACL,KAACrP;gEAAqBkO,OAAOmB;0EAC1BA;+DADYA;;oDAKlB,CAACvG,2BACA,MAACzI;wDACCuO,MAAM;wDACNb,SAAS;wDACTL,OAAM;wDACNQ,OAAO7F,QAAQsB,YAAY,GAAG,QAAQ;wDACtCyE,UAAU,CAACC,QAAUxE,OAAO;gEAAEF,cAAc0E,MAAMC,MAAM,CAACJ,KAAK,KAAK;4DAAM;;0EAEzE,KAAClO;gEAASkO,OAAM;0EAAO;;0EACvB,KAAClO;gEAASkO,OAAM;0EAAM;;;;;;;;;;;;;;YAUvC/F,SAAS,cAAc,CAACS,WACvB;4EACoE,iBACpE,MAAC/I;gBACCqE,IAAI;oBACFiJ,IAAI;wBAAE1G,IAAI;wBAAGC,IAAI;oBAAE;oBACnB4G,IAAI;oBACJkB,UAAU;oBACVc,QAAQ;oBACRC,WAAW;oBACXC,WAAW;oBACX1J,aAAa;mBACVnC;;kCAGL,MAAC1D;wBAAM6O,WAAU;wBAAMzK,SAAS;wBAAKH,IAAI;4BAAEO,YAAY;4BAAUgL,IAAI;4BAAQC,UAAU;wBAAE;;0CACvF,KAAClP;gCAAWgF,SAAQ;gCAAQV,OAAM;0CAC/B,GAAGoH,aAAa,CAAC,CAAC;;0CAErB,KAAC3L;gCAAQ4E,OAAM;0CACb,cAAA,KAAC9F;oCACC0H,WAAU;oCACV4I,UAAU;oCACV9I,cAAW;oCACX3C,IAAI;wCAAEM,SAAS;wCAAeM,OAAO;oCAAiB;8CAEtD,cAAA,KAAC/F;wCAAQkD,MAAMpD,sBAAsBoD,IAAI;wCAAE8C,UAAS;;;;;;oBAIzDgF,yBACC,KAACvJ;wBAAWgF,SAAQ;wBAAQV,OAAM;kCAC/BiF;;oBAGJ,CAACjB,2BAAa,KAACxF;wBAAgBkI,QAAQ/D;wBAAatB,UAAUd;;kCAC/D,KAAC/F;wBAAO4G,SAAS,IAAMkC,QAAQ;kCAAY;;kCAC3C,KAAC9I;wBACCkG,SAAQ;wBACRoB,MAAK;wBACLT,UAAUd,QAAQuK,QAAQ7F;wBAC1B7D,SAASsE;wBACTqF,yBAAW,KAAC9Q;4BAAQkD,MAAMnE,YAAYmE,IAAI;;kCAEzCoD,OAAO,cAAc;;;;;;AAMlC;AAEA,eAAe4B,gBAAe"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-site-start-card.component.tsx"],"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\n'use client'\n\nimport { lockdownRefusalText, parseLockdownRefusal, PLATFORM_BRAND_NAME } from '@aglyn/aglyn'\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport type { ConsoleHostFirstRunZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { ICON_VARIANT_CLOSE } from '@aglyn/shared-data-enums'\nimport {\n mdiAccount,\n mdiBookOpenPageVariant,\n mdiBriefcaseOutline,\n mdiCalendarHeart,\n mdiCamera,\n mdiClockOutline,\n mdiContentCut,\n mdiCreation,\n mdiDumbbell,\n mdiHandHeart,\n mdiHeartPulse,\n mdiHomeCity,\n mdiMusic,\n mdiPaletteOutline,\n mdiRocketLaunch,\n mdiScaleBalance,\n mdiSchool,\n mdiShopping,\n mdiSilverwareForkKnife,\n mdiViewGridOutline,\n mdiWrench,\n mdiYoga,\n mdiInformationOutline,\n mdiPageLayoutHeaderFooter,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { OptionCardGrid } from '@aglyn/shared-ui-jsx/components/option-card-grid.component'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n AppBar,\n Box,\n Button,\n Card,\n CardActionArea,\n Chip,\n CircularProgress,\n Container,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n IconButton,\n MenuItem,\n Stack,\n Step,\n StepLabel,\n Stepper,\n TextField,\n Toolbar,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { alpha, type Theme } from '@mui/material/styles'\nimport { useRouter } from 'next/navigation'\nimport {\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type ReactNode,\n type SyntheticEvent,\n} from 'react'\nimport {\n AI_JOB_AUTO_CONFIRM_INPUT,\n AI_SITE_FREE_PAGES,\n AI_SITE_FREE_PAGES_NOTE,\n AI_SITE_SUBMISSION_CHOICES,\n aiFreeCreditsResetLabel,\n aiFreeSiteCreditEstimate,\n aiFreeSiteShortfall,\n aiFreeSiteShortfallText,\n aiSiteCreditEstimate,\n type AiFreeCreditsLeft,\n aiSitePagesBand,\n type AiSiteSubmissions,\n} from '../model/ai-site-job'\nimport { FREE_AI_TASTE_CREDITS_PER_MONTH } from '../plan-entitlements'\nimport {\n AI_SITE_START_ANSWERS,\n AI_SITE_START_TYPES,\n aiSiteStartBrief,\n aiSiteStartKind,\n aiSiteStartInputs,\n aiSiteStartRefusal,\n type AiSiteStartAnswers,\n} from '../model/ai-site-start'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { AI_SITE_KINDS } from '../model/ai-site-kinds'\n\n/** The icon each kind of site's card shows. */\nconst AI_SITE_KIND_ICONS: Record<string, string> = {\n business: mdiBriefcaseOutline.path,\n trades: mdiWrench.path,\n professional: mdiScaleBalance.path,\n wellness: mdiHeartPulse.path,\n restaurant: mdiSilverwareForkKnife.path,\n store: mdiShopping.path,\n portfolio: mdiViewGridOutline.path,\n studio: mdiPaletteOutline.path,\n photography: mdiCamera.path,\n blog: mdiBookOpenPageVariant.path,\n events: mdiCalendarHeart.path,\n fitness: mdiDumbbell.path,\n yoga: mdiYoga.path,\n beauty: mdiContentCut.path,\n realestate: mdiHomeCity.path,\n education: mdiSchool.path,\n nonprofit: mdiHandHeart.path,\n music: mdiMusic.path,\n personal: mdiAccount.path,\n landing: mdiRocketLaunch.path,\n 'coming-soon': mdiClockOutline.path,\n}\nimport { AiJobFollow } from './ai-job-follow.component'\nimport { AiModelSelector } from './ai-model-selector.component'\nimport { useAiModelChoice } from './use-ai-model-choice'\nimport { aiCreditsBillingHref, aiSiteBuildHref } from './ai-job-links'\nimport { publishAiJob } from './ai-jobs-store'\n\n/**\n * The guided start (AGL-2918), on the `hostFirstRun` zone of the page a newly\n * created site lands on: a choice, a few questions, and the site scaffold they\n * become.\n *\n * ── Two steps (AGL-3594) ─────────────────────────────────────────────────\n *\n * Step 1 asks how to start, as two equal cards: the starter site — a\n * ready-made home page with a header and footer — or AI. The starter card is\n * the zone's `startBlank`, which gives a site born for the guided start the\n * starter every other new site is born with. The AI card moves to step 2, the\n * questions, which have a Back to step 1.\n *\n * ── A full screen dialog, and therefore three ways out ───────────────────\n *\n * The questions take the whole screen, which is the only presentation that\n * gets them read — but a surface that takes the screen and cannot be left is\n * a funnel, so leaving is drawn first and works three ways on both steps:\n *\n * - the close control at the start of the header,\n * - \"Skip\" at the end of it, named \"Skip and start blank\",\n * - Escape, which `Dialog` reports through `onClose`.\n *\n * Before a job is started all three are the zone's own `startBlank`: the\n * starter site, and no job, no draft, no record of a site half begun. After,\n * they close through `leave`, and the job builds the site.\n *\n * Escape and the close control are DISMISSALS, though, and Skip is a choice\n * (AGL-3660, 2026-10-08). Once anything is typed or picked, a dismissal asks\n * \"Leave without your answers?\" first, focused on Keep editing, so a stray\n * Escape — the second press after closing the model menu was the one in\n * production — can never trade a brief for the starter. Escape that reached\n * the dialog from a portal it does not contain (a menu, a select's list) is\n * that popup's, and the dialog ignores it.\n *\n * The header is the dialog's own chrome rather than part of its body, so the\n * way out cannot be scrolled off, and it is never disabled — least of all\n * while a request is in flight, which is the moment a person most wants it.\n * Nothing is focused ahead of the dialog's own frame, so the first thing a\n * keyboard reaches is the close control.\n *\n * ── It is absent until the route has answered ────────────────────────────\n *\n * The dialog covers the page, so being drawn on a guess is worse here than it\n * is for a card: it would be a takeover every flag-off workspace watches\n * disappear. Nothing renders at all — no `Dialog`, no portal — until the jobs\n * route has admitted this workspace, and a 404, a 403 or an unreachable route\n * is this widget staying absent and the ordinary blank page being all there\n * is. That the widget is mounted is itself the last of a stack of gates: the\n * shell held the plan's `aiGenerative` entitlement, the site's AI switch and\n * the reader's `ai.generate` first. A lockdown is the start door's to say,\n * here, in its own words.\n *\n * ── It asks, it does not build ───────────────────────────────────────────\n *\n * \"Plan my site\" starts a `site` job, which PLANS first: the pages, their\n * addresses, the navigation, the layout, the contact form and a palette, with\n * an estimated cost beside the button that confirms it. Nothing is built\n * until the person confirms that plan, and everything it then builds is an\n * unpublished draft.\n *\n * ── It stays with the job (AGL-3593) ─────────────────────────────────────\n *\n * Once the job exists the dialog follows it live — planning, the plan with\n * its Confirm, building, then the draft pages to open — so a person never has\n * to leave it to get their site built. \"Open AI jobs\" follows the job in the\n * Assist panel instead; it leaves the guided start the way every other exit\n * does, because the dialog covers the panel it opens.\n */\n\ntype Verdict = 'checking' | 'ready' | 'hidden'\n\ntype Step = 'choose' | 'describe'\n\n/** Labels the dialog for a reader, from its own heading. */\nconst TITLE_ID = 'ai-site-start-title'\n\n/**\n * The takeover's surface, for its paper, its bar and its footer alike: the\n * console page's own background (AGL-3596). The paper sits at elevation 0,\n * which is what keeps MUI's dark-mode elevation overlay off it.\n */\nexport const AI_SITE_START_SURFACE_SX = { bgcolor: 'background.default' } as const\n\n/** The two steps, as the indicator under the header names them. */\nconst STEP_LABELS = ['Choose', 'Describe'] as const\n\n/** The soft round badge an icon sits in, in its accent's tint. */\nfunction IconBadge({ accent, children }: { accent: 'primary' | 'secondary'; children: ReactNode }) {\n return (\n <Box\n aria-hidden\n sx={(theme: Theme) => ({\n width: theme.spacing(9),\n height: theme.spacing(9),\n borderRadius: '50%',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n bgcolor: alpha(theme.palette[accent].main, theme.palette.mode === 'dark' ? 0.24 : 0.12),\n color: `${accent}.main`,\n fontSize: theme.spacing(5.5),\n })}\n >\n {children}\n </Box>\n )\n}\n\n/**\n * One way to start, as a card the whole of which is the button. Its accessible\n * name is its title; its description describes it.\n */\nfunction ChoiceCard({\n accent,\n icon,\n iconId,\n title,\n description,\n busy = false,\n onChoose,\n children,\n}: {\n accent: 'primary' | 'secondary'\n icon: string\n /** Names the icon for a reader of the markup; the icon itself is decorative. */\n iconId: string\n title: string\n description: string\n busy?: boolean\n onChoose: () => void\n children?: ReactNode\n}) {\n const id = useId()\n return (\n <Card\n variant=\"outlined\"\n sx={(theme: Theme) => ({\n borderRadius: 2,\n height: '100%',\n transition: theme.transitions.create(['border-color', 'box-shadow', 'transform'], {\n duration: theme.transitions.duration.shorter,\n }),\n '&:hover, &:focus-within': {\n borderColor: `${accent}.main`,\n boxShadow: theme.shadows[4],\n transform: `translateY(-${theme.spacing(0.25)})`,\n },\n '@media (prefers-reduced-motion: reduce)': {\n transition: 'none',\n '&:hover, &:focus-within': { transform: 'none' },\n },\n })}\n >\n <CardActionArea\n onClick={onChoose}\n disabled={busy}\n aria-labelledby={`${id}-title`}\n aria-describedby={`${id}-description`}\n aria-busy={busy || undefined}\n sx={{ height: '100%', p: { xs: 3, sm: 4 } }}\n >\n <Stack spacing={2} sx={{ alignItems: 'center', textAlign: 'center' }}>\n <IconBadge accent={accent}>\n {busy ? (\n <CircularProgress size=\"1em\" color={accent} aria-label={`${title}…`} />\n ) : (\n <MdiIcon path={icon} data-icon={iconId} aria-hidden />\n )}\n </IconBadge>\n <Typography id={`${id}-title`} variant=\"h6\" component=\"h2\">\n {title}\n </Typography>\n <Typography id={`${id}-description`} variant=\"body2\" color=\"text.secondary\">\n {description}\n </Typography>\n {children}\n </Stack>\n </CardActionArea>\n </Card>\n )\n}\n\n/** A small label above a group of questions. */\nfunction SectionLabel({ children }: { children: ReactNode }) {\n return (\n <Typography variant=\"overline\" color=\"text.secondary\" component=\"h2\" sx={{ display: 'block' }}>\n {children}\n </Typography>\n )\n}\n\n/**\n * The guided start, as the `hostFirstRun` zone draws it, or reopened on a\n * failed start's own answers (AGL-3596): `initialAnswers` opens it on the\n * questions, filled in, for the person to adjust and start a fresh job.\n */\nexport type AiSiteStartCardProps = ConsoleHostFirstRunZoneProps & {\n initialAnswers?: AiSiteStartAnswers | null\n}\n\nexport function AiSiteStartCard({\n hostId,\n orgId,\n orgSlug,\n host,\n startBlank,\n leave,\n initialAnswers = null,\n}: AiSiteStartCardProps) {\n const { data: user } = useUser()\n const router = useRouter()\n // Held in a ref so a request reads WHO is signed in, never the identity of\n // the object that says so.\n const userRef = useRef(user)\n userRef.current = user\n const uid = user?.uid ?? null\n const [verdict, setVerdict] = useState<Verdict>('checking')\n // Read once: a start reopened on its answers stays one while it is open.\n const reopened = useRef(initialAnswers !== null)\n const [step, setStep] = useState<Step>(initialAnswers ? 'describe' : 'choose')\n const [answers, setAnswers] = useState<AiSiteStartAnswers>(initialAnswers ?? AI_SITE_START_ANSWERS)\n const [busy, setBusy] = useState(false)\n // The starter card's own busy state while `startBlank` runs (AGL-3594).\n const [startingStarter, setStartingStarter] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n /** The site job \"Plan my site\" started, as the create door answered with it. */\n const [started, setStarted] = useState<AiJobSummary | null>(null)\n // The Free taste's page band (AGL-3594), read off the verdict request.\n const [freeTaste, setFreeTaste] = useState(false)\n // What the Free workspace has left this month (AGL-3660), as the same read\n // the create door refuses on: the less of its own band and its owner's\n // allowance across their Free workspaces. `null` when the route said none.\n const [freeCredits, setFreeCredits] = useState<AiFreeCreditsLeft | null>(null)\n // \"Leave without your answers?\" (AGL-3660): a dismissal with a brief typed.\n const [confirmingLeave, setConfirmingLeave] = useState(false)\n // The dialog's own root, to tell its Escape from a nested popup's.\n const dialogRef = useRef<HTMLDivElement | null>(null)\n\n useEffect(() => {\n if (!orgId || !uid) return\n let active = true\n void (async () => {\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=1`,\n )\n const payload = response.ok ? await response.json().catch(() => null) : null\n if (active) {\n if (payload?.freeTaste === true) {\n setFreeTaste(true)\n const credits = payload?.freeCredits as AiFreeCreditsLeft | undefined\n if (credits && Number.isFinite(credits.left)) setFreeCredits(credits)\n // A reopened start keeps the pages it asked for, within the band.\n setAnswers((current) => ({\n ...current,\n pages: reopened.current ? Math.min(current.pages, AI_SITE_FREE_PAGES.max) : AI_SITE_FREE_PAGES.max,\n welcomeEmail: false,\n }))\n }\n setVerdict(response.status === 404 || response.status === 403 ? 'hidden' : 'ready')\n }\n } catch {\n if (active) setVerdict('hidden')\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, uid])\n\n const answer = useCallback(\n (patch: Partial<AiSiteStartAnswers>) =>\n setAnswers((current) => ({ ...current, ...patch })),\n [],\n )\n\n // A Free start what is left cannot pay for is not started (AGL-3660): the\n // dialog says so before anything spends, and the create door refuses it too.\n const shortfall = freeTaste ? aiFreeSiteShortfall(freeCredits, answers.pages) : null\n const refusal = aiSiteStartRefusal(answers, { freeTaste })\n const band = aiSitePagesBand(freeTaste)\n // On a paid plan the person picks the model that builds the site (AGL-3660),\n // Auto by default, from the models their plan allows; every step of the job\n // runs on it. A Free workspace builds on the default and is offered none.\n const modelChoice = useAiModelChoice({ orgId, hostId, surface: 'jobs', kind: 'job.page' })\n // A pick remembered from before needs its cost to estimate by; Auto does not,\n // and the list is otherwise read when the picker opens.\n const { load: loadModels, model: rememberedModel } = modelChoice\n useEffect(() => {\n if (!freeTaste && step === 'describe' && rememberedModel) loadModels()\n }, [freeTaste, step, rememberedModel, loadModels])\n const pickedModel = freeTaste ? null : modelChoice.model\n\n const plan = useCallback(async () => {\n if (!orgId || aiSiteStartRefusal(answers, { freeTaste }) || shortfall) return\n setBusy(true)\n setNotice(null)\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n kind: 'site',\n brief: aiSiteStartBrief(answers),\n // The guided start confirms its own plan (AGL-3594): the build\n // follows the plan with no approval to make.\n inputs: { ...aiSiteStartInputs(answers), [AI_JOB_AUTO_CONFIRM_INPUT]: true },\n ...(pickedModel ? { model: pickedModel } : {}),\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'The site could not be started. Try again.'),\n )\n return\n }\n const created = (payload?.job as AiJobSummary | undefined) ?? null\n if (!created) {\n setNotice('The site could not be started. Try again.')\n return\n }\n // The indicator and the launcher count it from the moment it exists.\n publishAiJob(created)\n // Where the next step is (AGL-3594): the site's \"Building your site\"\n // page for this job. The zone closes without the starter first, so the\n // setup page does not ask again behind it.\n const href = aiSiteBuildHref(orgSlug, host, created.id)\n if (href) {\n ;(leave ?? (() => undefined))()\n router.push(href)\n return\n }\n setStarted(created)\n } catch {\n setNotice('The site could not be started. Try again.')\n } finally {\n setBusy(false)\n }\n }, [orgId, hostId, answers, freeTaste, shortfall, orgSlug, host, leave, router, pickedModel])\n\n const chooseStarter = useCallback(() => {\n setStartingStarter(true)\n trackEvent('site_start_choice', { choice: 'starter' })\n startBlank()\n }, [startBlank])\n\n const chooseAi = useCallback(() => {\n trackEvent('site_start_choice', { choice: 'ai' })\n setStep('describe')\n }, [])\n\n if (verdict !== 'ready') return null\n\n // Before a job is started every way out is the blank site, which writes the\n // starter (AGL-3594); after, it only closes — the job builds the site.\n const exit = started ? (leave ?? startBlank) : startBlank\n // Anything the person typed or picked, which a dismissal must not discard\n // silently (AGL-3660). After a job starts there is nothing left to lose.\n const drafted =\n !started &&\n Boolean(answers.siteType.trim() || answers.audience.trim() || answers.kind !== null)\n // Escape and the close control: a dismissal, never a choice. Escape that\n // bubbled here through React from a portal outside this dialog's own DOM is\n // a nested menu's, already handled there, and is not this dialog's to act on.\n const dismiss = (event?: SyntheticEvent | object, reason?: string) => {\n if (reason === 'escapeKeyDown') {\n const target = (event as SyntheticEvent | undefined)?.target\n const root = dialogRef.current\n if (root && target instanceof Node && !root.contains(target)) return\n }\n if (drafted) setConfirmingLeave(true)\n else exit()\n }\n const leaveDraft = () => {\n setConfirmingLeave(false)\n exit()\n }\n\n // The chosen model's cost against Auto's, as the model list states it.\n const modelMultiplier =\n (pickedModel && modelChoice.options?.options.find((option) => option.id === pickedModel)?.multiplier) || 1\n const estimate = freeTaste\n ? aiFreeSiteCreditEstimate(answers.pages)\n : Math.round(\n aiSiteCreditEstimate(answers.pages, {\n welcomeEmail: answers.welcomeEmail,\n }) * modelMultiplier,\n )\n // A Free start quotes what is LEFT (AGL-3660), shared across the owner's\n // Free workspaces — never the month's whole allowance as if none were spent.\n const estimateText = freeTaste\n ? freeCredits\n ? `Up to about ${estimate.toLocaleString('en-US')} AI credits. You have ${freeCredits.left.toLocaleString('en-US')} of your ${freeCredits.total.toLocaleString('en-US')} free AI credits left this month, until ${aiFreeCreditsResetLabel(freeCredits.resetsOn)}`\n : `Up to about ${estimate.toLocaleString('en-US')} of the ${FREE_AI_TASTE_CREDITS_PER_MONTH} AI credits you get free each month`\n : `About ${estimate.toLocaleString('en-US')} credits, estimated`\n\n const choosing = step === 'choose' && !started\n\n return (\n <Dialog\n open\n fullScreen\n ref={dialogRef}\n onClose={dismiss}\n aria-labelledby={TITLE_ID}\n // The console's own page surface, in both modes. A Dialog's paper sits\n // at elevation 24 by default, and in dark mode MUI lightens an elevated\n // paper with a white overlay, which turned the whole takeover a\n // washed-out gray; the overlay is for floating surfaces, and this one\n // fills the screen, so its paper is flat.\n slotProps={{ paper: { elevation: 0, sx: AI_SITE_START_SURFACE_SX } }}\n >\n {/*\n The way out, before anything it is a way out of: a slim header on the\n paper surface. On a narrow screen it is the TITLE that gives way, so\n the exits keep their words at every width.\n */}\n <AppBar\n position=\"relative\"\n color=\"inherit\"\n elevation={0}\n enableColorOnDark\n sx={{ ...AI_SITE_START_SURFACE_SX, borderBottom: 1, borderColor: 'divider' }}\n >\n <Toolbar variant=\"dense\" sx={{ gap: 1 }}>\n <IconButton\n edge=\"start\"\n color=\"inherit\"\n onClick={() => dismiss()}\n aria-label=\"Close the guided start\"\n >\n <MdiIcon path={ICON_VARIANT_CLOSE.path} />\n </IconButton>\n <Typography\n id={TITLE_ID}\n variant=\"subtitle1\"\n component=\"div\"\n noWrap\n sx={{ textOverflow: 'ellipsis', flex: 1 }}\n >\n {'Start your site'}\n </Typography>\n <Button color=\"inherit\" onClick={exit} aria-label={started ? 'Close' : 'Skip and start blank'}>\n {started ? 'Close' : 'Skip'}\n </Button>\n </Toolbar>\n </AppBar>\n <DialogContent sx={{ px: { xs: 2, sm: 3 } }}>\n <Container maxWidth={choosing ? 'md' : 'sm'} disableGutters sx={{ py: { xs: 3, sm: 5 } }}>\n <Stack spacing={4}>\n <Stepper\n activeStep={choosing ? 0 : 1}\n aria-label={choosing ? 'Step 1 of 2' : 'Step 2 of 2'}\n sx={{ maxWidth: (theme: Theme) => theme.spacing(40), width: '100%', mx: 'auto' }}\n >\n {STEP_LABELS.map((label) => (\n <Step key={label}>\n <StepLabel>{label}</StepLabel>\n </Step>\n ))}\n </Stepper>\n <Stack spacing={1} sx={{ textAlign: choosing ? 'center' : 'left' }}>\n <Typography variant=\"h4\" component=\"h1\">\n {choosing ? 'How do you want to start?' : 'Tell us about your site'}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n {choosing\n ? 'You can change everything later.'\n : `A few answers and ${PLATFORM_BRAND_NAME} AI plans and writes your pages, then publishes your site.`}\n </Typography>\n </Stack>\n {notice && <Alert severity=\"info\">{notice}</Alert>}\n {step === 'describe' && !started && shortfall && freeCredits && (\n <Alert\n severity=\"warning\"\n action={\n orgSlug ? (\n <Button color=\"inherit\" size=\"small\" href={aiCreditsBillingHref(orgSlug)}>\n {'Upgrade'}\n </Button>\n ) : undefined\n }\n >\n {aiFreeSiteShortfallText(shortfall, freeCredits.resetsOn)}\n {answers.pages > 1 && !aiFreeSiteShortfall(freeCredits, 1)\n ? ' Or choose 1 page, which what you have left covers.'\n : null}\n </Alert>\n )}\n {choosing ? (\n <Box\n sx={{\n display: 'grid',\n gridTemplateColumns: { xs: '1fr', sm: '1fr 1fr' },\n gap: 3,\n }}\n >\n <ChoiceCard\n accent=\"secondary\"\n icon={mdiPageLayoutHeaderFooter.path}\n iconId=\"page-layout-header-footer\"\n title=\"Start from the starter site\"\n description=\"A ready-made home page with a header, footer and contact form you can edit.\"\n busy={startingStarter}\n onChoose={chooseStarter}\n />\n <ChoiceCard\n accent=\"primary\"\n icon={mdiCreation.path}\n iconId=\"creation\"\n title=\"Start with AI\"\n description={`Answer a few questions and ${PLATFORM_BRAND_NAME} AI plans your pages.`}\n onChoose={chooseAi}\n >\n <Chip size=\"small\" color=\"primary\" label=\"Recommended\" />\n {freeTaste && (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Up to ${AI_SITE_FREE_PAGES.max} pages on the Free plan`}\n </Typography>\n )}\n </ChoiceCard>\n </Box>\n ) : started && orgId ? (\n <Card variant=\"outlined\" sx={{ borderRadius: 2, p: { xs: 2, sm: 3 } }}>\n <AiJobFollow\n job={started}\n orgId={orgId}\n orgSlug={orgSlug}\n user={user}\n intro={\n 'Your site is being planned. Review the plan here or in AI jobs, and confirm ' +\n 'it to build — nothing is built, and nothing is published, until you do.'\n }\n // Full screen: AI jobs opens in the panel this dialog covers.\n onOpenJobs={exit}\n />\n </Card>\n ) : (\n <>\n <Stack spacing={2} component=\"section\" aria-label=\"Your business\">\n <SectionLabel>{'Your business'}</SectionLabel>\n <TextField\n fullWidth\n required\n label=\"What kind of site are you creating?\"\n placeholder=\"a neighborhood dog groomer that takes bookings\"\n value={answers.siteType}\n onChange={(event) => answer({ siteType: event.target.value })}\n helperText=\"A few words is enough, or pick one below.\"\n />\n <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>\n {AI_SITE_START_TYPES.map((type) => (\n <Chip\n key={type}\n label={type}\n size=\"small\"\n color={answers.siteType === type ? 'primary' : 'default'}\n variant={answers.siteType === type ? 'filled' : 'outlined'}\n onClick={() => answer({ siteType: type })}\n />\n ))}\n </Box>\n <TextField\n fullWidth\n label=\"Who is it for?\"\n placeholder=\"local dog owners who want a regular groom booked online\"\n value={answers.audience}\n onChange={(event) => answer({ audience: event.target.value })}\n helperText=\"Optional. It narrows who the pages are written for.\"\n />\n </Stack>\n <Stack spacing={2} component=\"section\" aria-label=\"Style\">\n <SectionLabel>{'Style'}</SectionLabel>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {answers.kind\n ? 'This decides how your site looks and which pages it usually has.'\n : 'Picked from your answer above. Choose another to change how your site looks and which pages it usually has.'}\n </Typography>\n <OptionCardGrid\n label=\"Style of site\"\n options={AI_SITE_KINDS.map((kind) => ({\n id: kind.id,\n title: kind.label,\n description: kind.blurb,\n icon: AI_SITE_KIND_ICONS[kind.id],\n }))}\n value={aiSiteStartKind(answers).id}\n onChange={(kind) => answer({ kind })}\n />\n </Stack>\n <Stack spacing={2} component=\"section\" aria-label=\"Details\">\n <SectionLabel>{'Details'}</SectionLabel>\n {/*\n The one setting a new site owner has to make, asked where\n they are already answering: a contact form nobody routed\n files its messages somewhere the person has not looked.\n */}\n <TextField\n select\n fullWidth\n label=\"Where do form submissions go?\"\n value={answers.submissions}\n onChange={(event) =>\n answer({ submissions: event.target.value as AiSiteSubmissions })\n }\n helperText=\"You can change this on the form itself afterwards.\"\n >\n {AI_SITE_SUBMISSION_CHOICES.map((option) => (\n <MenuItem key={option.id} value={option.id}>\n {`${option.label} — ${option.blurb}`}\n </MenuItem>\n ))}\n </TextField>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>\n <TextField\n select\n fullWidth\n label=\"Pages\"\n value={answers.pages}\n onChange={(event) => answer({ pages: Number(event.target.value) })}\n helperText={freeTaste ? AI_SITE_FREE_PAGES_NOTE : undefined}\n >\n {Array.from(\n { length: band.max - band.min + 1 },\n (_, index) => band.min + index,\n ).map((count) => (\n <MenuItem key={count} value={count}>\n {count}\n </MenuItem>\n ))}\n </TextField>\n {!freeTaste && (\n <TextField\n select\n fullWidth\n label=\"Welcome email\"\n value={answers.welcomeEmail ? 'yes' : 'no'}\n onChange={(event) => answer({ welcomeEmail: event.target.value === 'yes' })}\n >\n <MenuItem value=\"yes\">{'Draft one'}</MenuItem>\n <MenuItem value=\"no\">{'No'}</MenuItem>\n </TextField>\n )}\n </Stack>\n </Stack>\n </>\n )}\n </Stack>\n </Container>\n </DialogContent>\n {step === 'describe' && !started && (\n /* The footer stays put while the questions scroll. It wraps, because\n the estimate, the reason and the buttons are wider than a phone. */\n <DialogActions\n sx={{\n px: { xs: 2, sm: 3 },\n py: 2,\n flexWrap: 'wrap',\n rowGap: 1,\n columnGap: 2,\n borderTop: 1,\n borderColor: 'divider',\n ...AI_SITE_START_SURFACE_SX,\n }}\n >\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center', mr: 'auto', minWidth: 0 }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${estimateText}.`}\n </Typography>\n <Tooltip title=\"What it really costs is what each step spends, and you can watch that add up while it runs.\">\n <Box\n component=\"span\"\n tabIndex={0}\n aria-label=\"How the estimate works\"\n sx={{ display: 'inline-flex', color: 'text.secondary' }}\n >\n <MdiIcon path={mdiInformationOutline.path} fontSize=\"small\" />\n </Box>\n </Tooltip>\n </Stack>\n {refusal && (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {refusal}\n </Typography>\n )}\n {!freeTaste && <AiModelSelector choice={modelChoice} disabled={busy} />}\n <Button onClick={() => setStep('choose')}>{'Back'}</Button>\n <Button\n variant=\"contained\"\n size=\"large\"\n disabled={busy || Boolean(refusal) || Boolean(shortfall)}\n onClick={plan}\n startIcon={<MdiIcon path={mdiCreation.path} />}\n >\n {busy ? 'Starting…' : 'Plan my site'}\n </Button>\n </DialogActions>\n )}\n <Dialog\n open={confirmingLeave}\n onClose={() => setConfirmingLeave(false)}\n aria-labelledby={`${TITLE_ID}-leave`}\n aria-describedby={`${TITLE_ID}-leave-text`}\n maxWidth=\"xs\"\n >\n <DialogTitle id={`${TITLE_ID}-leave`}>{'Leave without your answers?'}</DialogTitle>\n <DialogContent>\n <DialogContentText id={`${TITLE_ID}-leave-text`}>\n {'What you typed is not saved. Leaving starts your site from the starter site instead.'}\n </DialogContentText>\n </DialogContent>\n <DialogActions>\n <Button color=\"inherit\" onClick={leaveDraft}>\n {'Leave and start blank'}\n </Button>\n <Button variant=\"contained\" autoFocus onClick={() => setConfirmingLeave(false)}>\n {'Keep editing'}\n </Button>\n </DialogActions>\n </Dialog>\n </Dialog>\n )\n}\n\nexport default AiSiteStartCard\n"],"names":["lockdownRefusalText","parseLockdownRefusal","PLATFORM_BRAND_NAME","trackEvent","ICON_VARIANT_CLOSE","mdiAccount","mdiBookOpenPageVariant","mdiBriefcaseOutline","mdiCalendarHeart","mdiCamera","mdiClockOutline","mdiContentCut","mdiCreation","mdiDumbbell","mdiHandHeart","mdiHeartPulse","mdiHomeCity","mdiMusic","mdiPaletteOutline","mdiRocketLaunch","mdiScaleBalance","mdiSchool","mdiShopping","mdiSilverwareForkKnife","mdiViewGridOutline","mdiWrench","mdiYoga","mdiInformationOutline","mdiPageLayoutHeaderFooter","MdiIcon","OptionCardGrid","authorizedFetch","useUser","Alert","AppBar","Box","Button","Card","CardActionArea","Chip","CircularProgress","Container","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","IconButton","MenuItem","Stack","Step","StepLabel","Stepper","TextField","Toolbar","Tooltip","Typography","alpha","useRouter","useCallback","useEffect","useId","useRef","useState","AI_JOB_AUTO_CONFIRM_INPUT","AI_SITE_FREE_PAGES","AI_SITE_FREE_PAGES_NOTE","AI_SITE_SUBMISSION_CHOICES","aiFreeCreditsResetLabel","aiFreeSiteCreditEstimate","aiFreeSiteShortfall","aiFreeSiteShortfallText","aiSiteCreditEstimate","aiSitePagesBand","FREE_AI_TASTE_CREDITS_PER_MONTH","AI_SITE_START_ANSWERS","AI_SITE_START_TYPES","aiSiteStartBrief","aiSiteStartKind","aiSiteStartInputs","aiSiteStartRefusal","AI_SITE_KINDS","AI_SITE_KIND_ICONS","business","path","trades","professional","wellness","restaurant","store","portfolio","studio","photography","blog","events","fitness","yoga","beauty","realestate","education","nonprofit","music","personal","landing","AiJobFollow","AiModelSelector","useAiModelChoice","aiCreditsBillingHref","aiSiteBuildHref","publishAiJob","TITLE_ID","AI_SITE_START_SURFACE_SX","bgcolor","STEP_LABELS","IconBadge","accent","children","aria-hidden","sx","theme","width","spacing","height","borderRadius","display","alignItems","justifyContent","palette","main","mode","color","fontSize","ChoiceCard","icon","iconId","title","description","busy","onChoose","id","variant","transition","transitions","create","duration","shorter","borderColor","boxShadow","shadows","transform","onClick","disabled","aria-labelledby","aria-describedby","aria-busy","undefined","p","xs","sm","textAlign","size","aria-label","data-icon","component","SectionLabel","AiSiteStartCard","hostId","orgId","orgSlug","host","startBlank","leave","initialAnswers","modelChoice","data","user","router","userRef","current","uid","verdict","setVerdict","reopened","step","setStep","answers","setAnswers","setBusy","startingStarter","setStartingStarter","notice","setNotice","started","setStarted","freeTaste","setFreeTaste","freeCredits","setFreeCredits","confirmingLeave","setConfirmingLeave","dialogRef","active","response","encodeURIComponent","payload","ok","json","catch","credits","Number","isFinite","left","pages","Math","min","max","welcomeEmail","status","answer","patch","shortfall","refusal","band","surface","kind","load","loadModels","model","rememberedModel","pickedModel","plan","method","headers","body","JSON","stringify","brief","inputs","locked","String","error","created","job","href","push","chooseStarter","choice","chooseAi","exit","drafted","Boolean","siteType","trim","audience","dismiss","event","reason","target","root","Node","contains","leaveDraft","modelMultiplier","options","find","option","multiplier","estimate","round","estimateText","toLocaleString","total","resetsOn","choosing","open","fullScreen","ref","onClose","slotProps","paper","elevation","position","enableColorOnDark","borderBottom","gap","edge","noWrap","textOverflow","flex","px","maxWidth","disableGutters","py","activeStep","mx","map","label","severity","action","gridTemplateColumns","intro","onOpenJobs","fullWidth","required","placeholder","value","onChange","helperText","flexWrap","type","blurb","select","submissions","direction","Array","from","length","_","index","count","rowGap","columnGap","borderTop","mr","minWidth","tabIndex","startIcon","autoFocus"],"mappings":"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"}
|
|
@@ -23,7 +23,7 @@ import { listUsageAlertContributors, registerUsageAlertContributor } from "@agly
|
|
|
23
23
|
import { registerPluginUsageMeter } from "@aglyn/aglyn/plugin-manager/plugin-usage-meters";
|
|
24
24
|
import { registerOperatorAlerts } from "@aglyn/aglyn/plugin-manager/operator-alerts";
|
|
25
25
|
import { AI_PLUGIN_ID, AI_USAGE_METER_ID } from "./constants.js";
|
|
26
|
-
import { AI_PROVIDER_UNAVAILABLE } from "./operator-alerts.js";
|
|
26
|
+
import { AI_BUILD_PARTLY_FAILED, AI_JOB_FAILED, AI_PROVIDER_UNAVAILABLE } from "./operator-alerts.js";
|
|
27
27
|
import { registerAiDeclarations } from "./declarations.js";
|
|
28
28
|
/** The provider-spend staff alerts' contributor id under the plugin. */ const AI_USAGE_ALERTS_ID = 'provider-spend';
|
|
29
29
|
/**
|
|
@@ -134,10 +134,13 @@ import { registerAiDeclarations } from "./declarations.js";
|
|
|
134
134
|
pluginId: AI_PLUGIN_ID
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
|
-
// The platform provider account alert (AGL-3377),
|
|
138
|
-
//
|
|
137
|
+
// The platform provider account alert (AGL-3377), and a job failing or a
|
|
138
|
+
// build finishing with parts failed on our side (AGL-3683), listed on
|
|
139
|
+
// Staff → Operator alerts before the first one is raised.
|
|
139
140
|
registerOperatorAlerts([
|
|
140
|
-
AI_PROVIDER_UNAVAILABLE
|
|
141
|
+
AI_PROVIDER_UNAVAILABLE,
|
|
142
|
+
AI_JOB_FAILED,
|
|
143
|
+
AI_BUILD_PARTLY_FAILED
|
|
141
144
|
], {
|
|
142
145
|
pluginId: AI_PLUGIN_ID
|
|
143
146
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/declarations.server.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\n// Each registry from its own module, not the `@aglyn/aglyn/server` barrel:\n// boot needs three registries rather than the whole server surface, and a\n// suite that stubs that barrel wholesale still loads these declarations\n// against the real registries.\nimport {\n listPluginEventHandlers,\n registerPluginEventHandler,\n} from '@aglyn/aglyn/plugin-manager/plugin-events'\nimport {\n listPluginUserErasers,\n registerPluginUserEraser,\n} from '@aglyn/aglyn/plugin-manager/plugin-user-erasure'\nimport {\n listUsageAlertContributors,\n registerUsageAlertContributor,\n} from '@aglyn/aglyn/plugin-manager/usage-alert-contributors'\nimport { registerPluginUsageMeter } from '@aglyn/aglyn/plugin-manager/plugin-usage-meters'\nimport { registerOperatorAlerts } from '@aglyn/aglyn/plugin-manager/operator-alerts'\nimport { AI_PLUGIN_ID, AI_USAGE_METER_ID } from './constants'\nimport { AI_PROVIDER_UNAVAILABLE } from './operator-alerts'\nimport { registerAiDeclarations } from './declarations'\n\n/** The provider-spend staff alerts' contributor id under the plugin. */\nconst AI_USAGE_ALERTS_ID = 'provider-spend'\n\n/**\n * The plugin's SERVER declarations (AGL-2939): what the server must know\n * at boot, before any door of the plugin has been called. The two platform\n * events the plugin writes activity for — the AI add-on bought or dropped,\n * an `ai.*` permission moved — are raised by core billing and membership\n * routes that never load the plugin's API surface, so the subscriptions\n * have to be in place from the first request. So does the eraser: an\n * account erasure is a core route too, and a person's AI usage months sit\n * under each org, keyed by the uid, where no core delete reaches them. So\n * does the usage alert contributor (AGL-2984): the usage-alerts sweep is a\n * core cron, and it evaluates the plugin's staff alerts on provider spend for\n * every org it reads.\n *\n * Light at boot by construction: the writers behind the handlers are\n * imported when the first event arrives, and the alert rules when the sweep\n * first evaluates an org, not when the process starts, so the boot cost is\n * the registration and nothing that touches Firestore.\n */\nexport function registerAiServerDeclarations(): void {\n registerAiDeclarations()\n // Idempotent against each REGISTRY rather than a module flag, so a\n // registry reset (a spec) or a second module instance registers again.\n if (!listPluginEventHandlers('org.seatAddons.changed').includes(AI_PLUGIN_ID)) {\n registerPluginEventHandler(\n 'org.seatAddons.changed',\n async ({ orgId, actor, before, after }) => {\n const { logAiAddonChanged } = await import('./activity/ai-activity')\n await logAiAddonChanged(orgId, actor, { before, after })\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'org.permissions.changed',\n async ({ orgId, actor, subject, permission, granted }) => {\n if (!permission.startsWith('ai.')) return\n const { logAiPermissionChanged } = await import('./activity/ai-activity')\n await logAiPermissionChanged(orgId, actor, { subject, permission, granted })\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // THE PLATFORM'S BILLING EVENTS (AGL-3011). Subscribed at boot for the\n // same reason as the two above: they are raised by the billing webhook,\n // which never loads an AI door, and a workspace's overage standing moving\n // late is a workspace charged or refused against a fact we already had.\n //\n // The handler modules are imported when the first event arrives, so a\n // process that never receives one pays only for the registration.\n if (!listPluginEventHandlers('billing.invoice.paid').includes(AI_PLUGIN_ID)) {\n registerPluginEventHandler(\n 'billing.invoice.paid',\n async (payload) => {\n const { onAiBillingInvoicePaid } = await import('./billing/ai-overage-events')\n await onAiBillingInvoicePaid(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.invoice.failed',\n async (payload) => {\n const { onAiBillingInvoiceFailed } = await import('./billing/ai-overage-events')\n await onAiBillingInvoiceFailed(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.invoice.closed',\n async (payload) => {\n const { onAiBillingInvoiceClosed } = await import('./billing/ai-overage-events')\n await onAiBillingInvoiceClosed(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.dispute.opened',\n async (payload) => {\n const { onAiBillingDisputeOpened } = await import('./billing/ai-overage-events')\n await onAiBillingDisputeOpened(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.paymentMethod.changed',\n async (payload) => {\n const { onAiBillingPaymentMethodChanged } = await import(\n './billing/ai-overage-events'\n )\n await onAiBillingPaymentMethodChanged(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // The plugin's meter in the monthly usage sweep: the month's provider spend\n // onto the rollup, the credits past the band onto the invoice, and — from\n // the cutover month (AGL-3011) — the close-out of what the plugin invoices\n // itself. Registered at boot because the sweep is a core cron, and declared\n // in `usageAxes` so the sweep refuses to bill a month without it. The same\n // registration again replaces itself.\n registerPluginUsageMeter({\n pluginId: AI_PLUGIN_ID,\n id: AI_USAGE_METER_ID,\n measure: async (context) => {\n const { measureAiMonth } = await import('./billing/ai-month-meter')\n return measureAiMonth(context)\n },\n closeMonth: async (context) => {\n const { closeAiMonth } = await import('./billing/ai-month-meter')\n await closeAiMonth(context)\n },\n })\n if (!listPluginUserErasers().includes(AI_PLUGIN_ID)) {\n registerPluginUserEraser(\n async (request) => {\n const { eraseAiUsageForUser } = await import('./usage/ai-usage-eraser')\n return eraseAiUsageForUser(request)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // The platform provider account alert (AGL-3377), listed on Staff →\n // Operator alerts before the first one is ever raised.\n registerOperatorAlerts([AI_PROVIDER_UNAVAILABLE], { pluginId: AI_PLUGIN_ID })\n if (\n !listUsageAlertContributors().some(\n (contributor) =>\n contributor.pluginId === AI_PLUGIN_ID &&\n contributor.id === AI_USAGE_ALERTS_ID,\n )\n ) {\n registerUsageAlertContributor({\n pluginId: AI_PLUGIN_ID,\n id: AI_USAGE_ALERTS_ID,\n evaluate: async (context) => {\n const { evaluateAiUsageAlerts } = await import('./usage/ai-usage-alerts')\n await evaluateAiUsageAlerts(context)\n },\n quotaChecks: async (context) => {\n const { aiQuotaChecks } = await import('./usage/ai-quota-checks')\n return aiQuotaChecks(context)\n },\n })\n }\n}\n\nregisterAiServerDeclarations()\n"],"names":["listPluginEventHandlers","registerPluginEventHandler","listPluginUserErasers","registerPluginUserEraser","listUsageAlertContributors","registerUsageAlertContributor","registerPluginUsageMeter","registerOperatorAlerts","AI_PLUGIN_ID","AI_USAGE_METER_ID","AI_PROVIDER_UNAVAILABLE","registerAiDeclarations","AI_USAGE_ALERTS_ID","registerAiServerDeclarations","includes","orgId","actor","before","after","logAiAddonChanged","pluginId","subject","permission","granted","startsWith","logAiPermissionChanged","payload","onAiBillingInvoicePaid","onAiBillingInvoiceFailed","onAiBillingInvoiceClosed","onAiBillingDisputeOpened","onAiBillingPaymentMethodChanged","id","measure","context","measureAiMonth","closeMonth","closeAiMonth","request","eraseAiUsageForUser","some","contributor","evaluate","evaluateAiUsageAlerts","quotaChecks","aiQuotaChecks"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/declarations.server.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\n// Each registry from its own module, not the `@aglyn/aglyn/server` barrel:\n// boot needs three registries rather than the whole server surface, and a\n// suite that stubs that barrel wholesale still loads these declarations\n// against the real registries.\nimport {\n listPluginEventHandlers,\n registerPluginEventHandler,\n} from '@aglyn/aglyn/plugin-manager/plugin-events'\nimport {\n listPluginUserErasers,\n registerPluginUserEraser,\n} from '@aglyn/aglyn/plugin-manager/plugin-user-erasure'\nimport {\n listUsageAlertContributors,\n registerUsageAlertContributor,\n} from '@aglyn/aglyn/plugin-manager/usage-alert-contributors'\nimport { registerPluginUsageMeter } from '@aglyn/aglyn/plugin-manager/plugin-usage-meters'\nimport { registerOperatorAlerts } from '@aglyn/aglyn/plugin-manager/operator-alerts'\nimport { AI_PLUGIN_ID, AI_USAGE_METER_ID } from './constants'\nimport { AI_BUILD_PARTLY_FAILED, AI_JOB_FAILED, AI_PROVIDER_UNAVAILABLE } from './operator-alerts'\nimport { registerAiDeclarations } from './declarations'\n\n/** The provider-spend staff alerts' contributor id under the plugin. */\nconst AI_USAGE_ALERTS_ID = 'provider-spend'\n\n/**\n * The plugin's SERVER declarations (AGL-2939): what the server must know\n * at boot, before any door of the plugin has been called. The two platform\n * events the plugin writes activity for — the AI add-on bought or dropped,\n * an `ai.*` permission moved — are raised by core billing and membership\n * routes that never load the plugin's API surface, so the subscriptions\n * have to be in place from the first request. So does the eraser: an\n * account erasure is a core route too, and a person's AI usage months sit\n * under each org, keyed by the uid, where no core delete reaches them. So\n * does the usage alert contributor (AGL-2984): the usage-alerts sweep is a\n * core cron, and it evaluates the plugin's staff alerts on provider spend for\n * every org it reads.\n *\n * Light at boot by construction: the writers behind the handlers are\n * imported when the first event arrives, and the alert rules when the sweep\n * first evaluates an org, not when the process starts, so the boot cost is\n * the registration and nothing that touches Firestore.\n */\nexport function registerAiServerDeclarations(): void {\n registerAiDeclarations()\n // Idempotent against each REGISTRY rather than a module flag, so a\n // registry reset (a spec) or a second module instance registers again.\n if (!listPluginEventHandlers('org.seatAddons.changed').includes(AI_PLUGIN_ID)) {\n registerPluginEventHandler(\n 'org.seatAddons.changed',\n async ({ orgId, actor, before, after }) => {\n const { logAiAddonChanged } = await import('./activity/ai-activity')\n await logAiAddonChanged(orgId, actor, { before, after })\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'org.permissions.changed',\n async ({ orgId, actor, subject, permission, granted }) => {\n if (!permission.startsWith('ai.')) return\n const { logAiPermissionChanged } = await import('./activity/ai-activity')\n await logAiPermissionChanged(orgId, actor, { subject, permission, granted })\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // THE PLATFORM'S BILLING EVENTS (AGL-3011). Subscribed at boot for the\n // same reason as the two above: they are raised by the billing webhook,\n // which never loads an AI door, and a workspace's overage standing moving\n // late is a workspace charged or refused against a fact we already had.\n //\n // The handler modules are imported when the first event arrives, so a\n // process that never receives one pays only for the registration.\n if (!listPluginEventHandlers('billing.invoice.paid').includes(AI_PLUGIN_ID)) {\n registerPluginEventHandler(\n 'billing.invoice.paid',\n async (payload) => {\n const { onAiBillingInvoicePaid } = await import('./billing/ai-overage-events')\n await onAiBillingInvoicePaid(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.invoice.failed',\n async (payload) => {\n const { onAiBillingInvoiceFailed } = await import('./billing/ai-overage-events')\n await onAiBillingInvoiceFailed(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.invoice.closed',\n async (payload) => {\n const { onAiBillingInvoiceClosed } = await import('./billing/ai-overage-events')\n await onAiBillingInvoiceClosed(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.dispute.opened',\n async (payload) => {\n const { onAiBillingDisputeOpened } = await import('./billing/ai-overage-events')\n await onAiBillingDisputeOpened(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.paymentMethod.changed',\n async (payload) => {\n const { onAiBillingPaymentMethodChanged } = await import(\n './billing/ai-overage-events'\n )\n await onAiBillingPaymentMethodChanged(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // The plugin's meter in the monthly usage sweep: the month's provider spend\n // onto the rollup, the credits past the band onto the invoice, and — from\n // the cutover month (AGL-3011) — the close-out of what the plugin invoices\n // itself. Registered at boot because the sweep is a core cron, and declared\n // in `usageAxes` so the sweep refuses to bill a month without it. The same\n // registration again replaces itself.\n registerPluginUsageMeter({\n pluginId: AI_PLUGIN_ID,\n id: AI_USAGE_METER_ID,\n measure: async (context) => {\n const { measureAiMonth } = await import('./billing/ai-month-meter')\n return measureAiMonth(context)\n },\n closeMonth: async (context) => {\n const { closeAiMonth } = await import('./billing/ai-month-meter')\n await closeAiMonth(context)\n },\n })\n if (!listPluginUserErasers().includes(AI_PLUGIN_ID)) {\n registerPluginUserEraser(\n async (request) => {\n const { eraseAiUsageForUser } = await import('./usage/ai-usage-eraser')\n return eraseAiUsageForUser(request)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // The platform provider account alert (AGL-3377), and a job failing or a\n // build finishing with parts failed on our side (AGL-3683), listed on\n // Staff → Operator alerts before the first one is raised.\n registerOperatorAlerts([AI_PROVIDER_UNAVAILABLE, AI_JOB_FAILED, AI_BUILD_PARTLY_FAILED], {\n pluginId: AI_PLUGIN_ID,\n })\n if (\n !listUsageAlertContributors().some(\n (contributor) =>\n contributor.pluginId === AI_PLUGIN_ID &&\n contributor.id === AI_USAGE_ALERTS_ID,\n )\n ) {\n registerUsageAlertContributor({\n pluginId: AI_PLUGIN_ID,\n id: AI_USAGE_ALERTS_ID,\n evaluate: async (context) => {\n const { evaluateAiUsageAlerts } = await import('./usage/ai-usage-alerts')\n await evaluateAiUsageAlerts(context)\n },\n quotaChecks: async (context) => {\n const { aiQuotaChecks } = await import('./usage/ai-quota-checks')\n return aiQuotaChecks(context)\n },\n })\n }\n}\n\nregisterAiServerDeclarations()\n"],"names":["listPluginEventHandlers","registerPluginEventHandler","listPluginUserErasers","registerPluginUserEraser","listUsageAlertContributors","registerUsageAlertContributor","registerPluginUsageMeter","registerOperatorAlerts","AI_PLUGIN_ID","AI_USAGE_METER_ID","AI_BUILD_PARTLY_FAILED","AI_JOB_FAILED","AI_PROVIDER_UNAVAILABLE","registerAiDeclarations","AI_USAGE_ALERTS_ID","registerAiServerDeclarations","includes","orgId","actor","before","after","logAiAddonChanged","pluginId","subject","permission","granted","startsWith","logAiPermissionChanged","payload","onAiBillingInvoicePaid","onAiBillingInvoiceFailed","onAiBillingInvoiceClosed","onAiBillingDisputeOpened","onAiBillingPaymentMethodChanged","id","measure","context","measureAiMonth","closeMonth","closeAiMonth","request","eraseAiUsageForUser","some","contributor","evaluate","evaluateAiUsageAlerts","quotaChecks","aiQuotaChecks"],"mappings":"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"}
|
|
@@ -329,13 +329,19 @@ const passes = (count)=>()=>count * AI_SITE_PASS_CREDITS;
|
|
|
329
329
|
for (const one of items){
|
|
330
330
|
var _one_pageBlocks;
|
|
331
331
|
const args = Object.entries(one.argsSchema.properties).map(([name, property])=>{
|
|
332
|
-
var _one_argsSchema_required;
|
|
332
|
+
var _one_argsSchema_required, _property_items;
|
|
333
333
|
const required = ((_one_argsSchema_required = one.argsSchema.required) == null ? void 0 : _one_argsSchema_required.includes(name)) ? '' : ' (optional)';
|
|
334
|
-
|
|
334
|
+
// An array's values are named too (AGL-3616): told only "days: array",
|
|
335
|
+
// the live canonical plan wrote "monday" for the enum's "mon".
|
|
336
|
+
const choices = property.enum ? ` one of ${property.enum.join('|')}` : ((_property_items = property.items) == null ? void 0 : _property_items.enum) ? ` of ${property.items.enum.join('|')}` : '';
|
|
335
337
|
return `${name}: ${property.type}${choices}${required} — ${property.description}`;
|
|
336
338
|
});
|
|
337
339
|
lines.push(`- ${one.op}: ${aiNounWithArticle(one.noun)}, at most ${one.maxPerPlan}; for ${one.intents.join('; ')}.${((_one_pageBlocks = one.pageBlocks) == null ? void 0 : _one_pageBlocks.length) ? ` A page section that uses it places the ${one.pageBlocks.join(' or ')} block: put new:<name> in that section's uses.` : ''} Args: ${args.length ? `{ ${args.join('; ')} }` : '{}'}.`);
|
|
338
340
|
}
|
|
341
|
+
// Measured live on the canonical request (AGL-3616): asked for "forms for
|
|
342
|
+
// bookings", the planner made a plain form rather than the operation that
|
|
343
|
+
// takes bookings. An operation does the job; a form that imitates it does not.
|
|
344
|
+
lines.push('When the request asks for what one of these operations makes, plan that operation as an item, and place it on the page that offers it. Never stand in for it with a form, page or component that only imitates it: a form that collects what the operation would handle does not do its job. A form is for what no operation here makes, such as general inquiries.');
|
|
339
345
|
return lines;
|
|
340
346
|
}
|
|
341
347
|
function aiNounWithArticle(noun) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-build-capabilities.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { isHostPluginEnabled } from '@aglyn/aglyn/plugin-manager/enabled-plugins'\nimport {\n pluginAiCapabilities,\n registerPluginAiCapability,\n type PluginAiCapability,\n type ResolvedPluginAiCapability,\n} from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { filterEnabledPluginsByReleaseFlags } from '@aglyn/tenant-data-admin/server/release-flags'\nimport { AI_TEMPLATE_SUBJECTS } from '../model/ai-template-subjects'\nimport { AI_SITE_PASS_CREDITS } from '../model/ai-site-job'\nimport type { AiBuildOps } from '../model/ai-build-job'\nimport { AI_LOGIC_FUNCTION_RESOURCE } from '../model/ai-logic-job'\nimport { AI_AUTOMATION_RESOURCE } from '../model/ai-workflow-job'\nimport { AI_EDIT_TARGET_KINDS } from '../model/ai-edit-job'\nimport { AI_CAMPAIGN_RESOURCE } from './ai-job-campaign-step'\nimport { aiPluginDraftOwner, type AiPluginDraftOwnerLookup } from './ai-job-plugin-drafts'\nimport { aiJobStepRunnerFor } from './ai-jobs'\n\n/**\n * What THIS plugin lets a build make (AGL-3616), registered on the core's\n * capability contract exactly as any other plugin registers its own, so the\n * planner, the gates and the estimate read one list and name no plugin.\n *\n * Every one is built by a runner this plugin already has (`runnerKind`):\n * pages, layouts, forms, components and email designs are the plan's\n * creations and screens; templates, campaigns and automations are items with\n * arguments. A campaign and an automation are still WRITTEN by the marketing\n * and workflows plugins, through the writers their runners already call, and\n * a site function by the logic plugin's `function` writer, which the `logic`\n * runner calls when its job is a build's unit. A change to a page or layout\n * the site already has is the `edit` runner's.\n */\n\n/** This plugin's id, as its registrations name their owner. */\nconst AI_PLUGIN_ID = 'ai'\n\nconst NO_ARGS = { type: 'object', properties: {}, additionalProperties: false } as const\n\nconst passes = (count: number) => () => count * AI_SITE_PASS_CREDITS\n\n/** The operations this plugin owns, as it registers them. */\nexport const AI_OWNED_CAPABILITIES: readonly PluginAiCapability[] = [\n {\n op: 'page',\n noun: 'page',\n where: 'Pages',\n intents: ['new pages: home, about, services, contact, pricing, a landing page'],\n argsSchema: NO_ARGS,\n maxPerPlan: 8,\n freeAllowed: true,\n runnerKind: 'page',\n estimateCredits: passes(6),\n degrade: 'omit',\n },\n {\n op: 'layout',\n noun: 'layout',\n where: 'Layouts',\n intents: ['a header and footer shared by pages'],\n argsSchema: NO_ARGS,\n maxPerPlan: 1,\n freeAllowed: true,\n runnerKind: 'layout',\n estimateCredits: passes(1),\n degrade: 'fallback',\n },\n {\n op: 'form',\n noun: 'form',\n where: 'Forms',\n intents: ['a contact form, a quote request, a sign-up form'],\n argsSchema: NO_ARGS,\n maxPerPlan: 3,\n freeAllowed: true,\n runnerKind: 'form',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'component',\n noun: 'component',\n where: 'Components',\n intents: ['a reusable card or block placed on several pages'],\n argsSchema: NO_ARGS,\n maxPerPlan: 4,\n freeAllowed: false,\n runnerKind: 'component',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'email',\n noun: 'email design',\n where: 'Emails → Templates',\n intents: ['an email design: a welcome, a reply, an announcement'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'email',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'template',\n noun: 'page template',\n where: 'Templates',\n intents: ['a page template for blog posts, products or authors'],\n argsSchema: {\n type: 'object',\n properties: {\n subject: {\n type: 'string',\n description: 'What each page shows: entry (a collection entry), product or author.',\n enum: AI_TEMPLATE_SUBJECTS,\n },\n collectionId: {\n type: 'string',\n description: 'For entry: the collection id from the site inventory.',\n maxLength: 100,\n },\n },\n required: ['subject'],\n additionalProperties: false,\n },\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'template',\n estimateCredits: passes(6),\n degrade: 'omit',\n },\n {\n op: 'campaign',\n noun: 'email campaign',\n where: 'Marketing → Campaigns',\n intents: ['an email campaign drafted and left unsent'],\n argsSchema: {\n type: 'object',\n properties: {\n name: { type: 'string', description: 'What the campaign is called.', maxLength: 120 },\n },\n additionalProperties: false,\n },\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'campaign',\n estimateCredits: passes(2),\n degrade: 'omit',\n },\n {\n op: 'workflow',\n noun: 'automation',\n where: 'Automations',\n intents: ['an automation: when a form is sent, do something'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'workflow',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n // A function needs a model, so it is this plugin's operation; the logic\n // plugin owns the record and writes it (AGL-3616). New functions only: a\n // build never changes one the site already has.\n op: 'function',\n noun: 'site function',\n where: 'Logic → Functions',\n intents: ['a site function that works a value out from what it is given: a price, a total, a quote'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n // Free includes one function (`functionsPerHost: 1`), and Logic no feature.\n freeAllowed: true,\n quota: 'functionsPerHost',\n runnerKind: 'logic',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n // A change to something the site already has (AGL-3616), made by the\n // `edit` runner: the Assist edit rung's protocol against the stored\n // document, saved as a version visitors do not see. Not on the Free\n // taste: without version history only an unpublished page can take a\n // change, which is rarely the page a request means, and its admission\n // would refuse the item after the plan was confirmed.\n op: 'edit',\n noun: 'page change',\n where: 'the page’s or layout’s versions',\n intents: [\n 'a change to a page or layout the site already has — shorter copy, a new section, different wording — saved as a version visitors do not see until it is published',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n target: {\n type: 'string',\n description: 'The id of an existing page or layout from the site inventory.',\n maxLength: 64,\n },\n targetKind: {\n type: 'string',\n description: 'screen when target is a page, layout when it is a layout.',\n enum: AI_EDIT_TARGET_KINDS,\n },\n },\n required: ['target'],\n additionalProperties: false,\n },\n maxPerPlan: 3,\n freeAllowed: false,\n runnerKind: 'edit',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n]\n\n/**\n * The resource another plugin writes for each operation of this plugin's that\n * cannot be built without it. The operation runs where that writer's owner\n * runs — registered, switched on for the workspace and the site, and past its\n * release flag — exactly as an operation that owner registered would.\n */\nexport const AI_OWNED_OP_WRITERS: Readonly<Partial<Record<string, string>>> = {\n function: AI_LOGIC_FUNCTION_RESOURCE,\n // A campaign is written by the marketing plugin's campaign writer, an\n // automation by the workflows plugin's: neither is offered where its\n // writer's plugin is off, rather than planned and then skipped.\n campaign: AI_CAMPAIGN_RESOURCE,\n workflow: AI_AUTOMATION_RESOURCE,\n}\n\n/** Registers this plugin's build operations; the console surface calls it. */\nexport function registerAiBuildCapabilities(): void {\n for (const capability of AI_OWNED_CAPABILITIES) {\n registerPluginAiCapability(capability, { pluginId: AI_PLUGIN_ID })\n }\n}\n\n/** What decides which operations a build may use here. */\nexport interface AiBuildOpsContext {\n orgId: string\n hostId: string | null\n org: Partial<AglynOrgBilling> | null\n /** The site document, for the plugins switched on and off on it. */\n host: Readonly<Record<string, unknown>> | null\n freeTaste: boolean\n}\n\nexport interface AiBuildOpsDeps {\n /** The registered capabilities; the core registry's otherwise. */\n capabilities?: () => ResolvedPluginAiCapability[]\n /** The release-flag read; the platform's otherwise. */\n releasedPlugins?: (pluginIds: string[], orgId: string) => Promise<string[]>\n /** Whether a runner is registered for a kind; the machine's otherwise. */\n hasRunner?: (kind: string) => boolean\n /** Which plugin writes a resource; the core's registry otherwise. */\n writerOwner?: AiPluginDraftOwnerLookup\n}\n\n/**\n * The operations a build may plan on this site, by op (AGL-3616): every\n * registered capability whose owner runs here — switched on for the\n * workspace and the site, past its release flag; for one of this plugin's\n * operations another plugin writes (`AI_OWNED_OP_WRITERS`), that writer's\n * owner, and a writer registered at all — whose entitlement the plan\n * holds, which the Free taste may use where the workspace is on it, and,\n * for an AI-run operation, whose runner this process loaded. One release-flag\n * read for every owner at once. What a member may do and what an allowance\n * has left are the owner's to say when the item runs (`refusal`).\n */\nexport async function aiBuildOps(context: AiBuildOpsContext, deps: AiBuildOpsDeps = {}): Promise<AiBuildOps> {\n const all = (deps.capabilities ?? pluginAiCapabilities)()\n const hasRunner = deps.hasRunner ?? ((kind: string) => aiJobStepRunnerFor(kind as never) !== null)\n const writerOwner = deps.writerOwner ?? aiPluginDraftOwner\n // The plugin each of this plugin's operations depends on, by op; `null` where its writer is not registered.\n const writtenBy = new Map<string, string | null>()\n for (const { pluginId, capability } of all) {\n const resource = pluginId === AI_PLUGIN_ID ? AI_OWNED_OP_WRITERS[capability.op] : undefined\n if (resource) writtenBy.set(capability.op, writerOwner(resource))\n }\n const owners = [\n ...new Set(\n [...all.map((one) => one.pluginId), ...writtenBy.values()].filter(\n (id): id is string => !!id && id !== AI_PLUGIN_ID,\n ),\n ),\n ]\n const released = new Set(\n owners.length\n ? await (deps.releasedPlugins ??\n ((ids, orgId) => filterEnabledPluginsByReleaseFlags(ids, { orgId, authorization: null })))(owners, context.orgId)\n : [],\n )\n const ops = new Map<string, PluginAiCapability>()\n for (const { pluginId, capability } of all) {\n // The plugin that must run here: the owner, or for one of this plugin's\n // operations, the plugin that writes what it makes.\n const runsOn = pluginId === AI_PLUGIN_ID ? writtenBy.get(capability.op) : pluginId\n if (runsOn === null) continue\n if (runsOn !== undefined && runsOn !== AI_PLUGIN_ID) {\n if (!released.has(runsOn)) continue\n if (!isHostPluginEnabled(context.org as { enabledPlugins?: string[] } | null, context.host, runsOn)) continue\n }\n if (capability.feature && !checkEntitlement(context.org, capability.feature)) continue\n if (context.freeTaste && !capability.freeAllowed) continue\n if (capability.runnerKind && !hasRunner(capability.runnerKind)) continue\n ops.set(capability.op, capability)\n }\n return ops\n}\n\n/**\n * The lines the plan step's request lists the item operations in: each op\n * with what it makes, its arguments and their meaning. Per site, so they ride\n * the job's own turn and never a cached system block.\n */\nexport function aiBuildOpLines(ops: AiBuildOps, structural: readonly string[]): string[] {\n const items = [...ops.values()].filter((one) => !structural.includes(one.op))\n if (!items.length) return ['Operations for items: none on this site. Leave items empty.']\n const lines = ['Operations for items (anything else goes in create or screens):']\n for (const one of items) {\n const args = Object.entries(one.argsSchema.properties).map(([name, property]) => {\n const required = one.argsSchema.required?.includes(name) ? '' : ' (optional)'\n const choices = property.enum ? ` one of ${property.enum.join('|')}` : ''\n return `${name}: ${property.type}${choices}${required} — ${property.description}`\n })\n lines.push(\n `- ${one.op}: ${aiNounWithArticle(one.noun)}, at most ${one.maxPerPlan}; for ${one.intents.join('; ')}.${\n one.pageBlocks?.length ? ` A page section that uses it places the ${one.pageBlocks.join(' or ')} block: put new:<name> in that section's uses.` : ''\n } Args: ${args.length ? `{ ${args.join('; ')} }` : '{}'}.`,\n )\n }\n return lines\n}\n\nfunction aiNounWithArticle(noun: string): string {\n return `${/^[aeiou]/i.test(noun) ? 'an' : 'a'} ${noun}`\n}\n\n/** What a build can make here, in the owner's words, for the chat's instructions. */\nexport function aiBuildIntents(ops: AiBuildOps): string[] {\n return [...ops.values()].flatMap((one) => one.intents)\n}\n"],"names":["checkEntitlement","isHostPluginEnabled","pluginAiCapabilities","registerPluginAiCapability","filterEnabledPluginsByReleaseFlags","AI_TEMPLATE_SUBJECTS","AI_SITE_PASS_CREDITS","AI_LOGIC_FUNCTION_RESOURCE","AI_AUTOMATION_RESOURCE","AI_EDIT_TARGET_KINDS","AI_CAMPAIGN_RESOURCE","aiPluginDraftOwner","aiJobStepRunnerFor","AI_PLUGIN_ID","NO_ARGS","type","properties","additionalProperties","passes","count","AI_OWNED_CAPABILITIES","op","noun","where","intents","argsSchema","maxPerPlan","freeAllowed","runnerKind","estimateCredits","degrade","subject","description","enum","collectionId","maxLength","required","name","quota","target","targetKind","AI_OWNED_OP_WRITERS","function","campaign","workflow","registerAiBuildCapabilities","capability","pluginId","aiBuildOps","context","deps","all","capabilities","hasRunner","kind","writerOwner","writtenBy","Map","resource","undefined","set","owners","Set","map","one","values","filter","id","released","length","releasedPlugins","ids","orgId","authorization","ops","runsOn","get","has","org","host","feature","freeTaste","aiBuildOpLines","structural","items","includes","lines","args","Object","entries","property","choices","join","push","aiNounWithArticle","pageBlocks","test","aiBuildIntents","flatMap"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-build-capabilities.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { checkEntitlement } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { isHostPluginEnabled } from '@aglyn/aglyn/plugin-manager/enabled-plugins'\nimport {\n pluginAiCapabilities,\n registerPluginAiCapability,\n type PluginAiCapability,\n type ResolvedPluginAiCapability,\n} from '@aglyn/aglyn/plugin-manager/plugin-ai-capabilities'\nimport { filterEnabledPluginsByReleaseFlags } from '@aglyn/tenant-data-admin/server/release-flags'\nimport { AI_TEMPLATE_SUBJECTS } from '../model/ai-template-subjects'\nimport { AI_SITE_PASS_CREDITS } from '../model/ai-site-job'\nimport type { AiBuildOps } from '../model/ai-build-job'\nimport { AI_LOGIC_FUNCTION_RESOURCE } from '../model/ai-logic-job'\nimport { AI_AUTOMATION_RESOURCE } from '../model/ai-workflow-job'\nimport { AI_EDIT_TARGET_KINDS } from '../model/ai-edit-job'\nimport { AI_CAMPAIGN_RESOURCE } from './ai-job-campaign-step'\nimport { aiPluginDraftOwner, type AiPluginDraftOwnerLookup } from './ai-job-plugin-drafts'\nimport { aiJobStepRunnerFor } from './ai-jobs'\n\n/**\n * What THIS plugin lets a build make (AGL-3616), registered on the core's\n * capability contract exactly as any other plugin registers its own, so the\n * planner, the gates and the estimate read one list and name no plugin.\n *\n * Every one is built by a runner this plugin already has (`runnerKind`):\n * pages, layouts, forms, components and email designs are the plan's\n * creations and screens; templates, campaigns and automations are items with\n * arguments. A campaign and an automation are still WRITTEN by the marketing\n * and workflows plugins, through the writers their runners already call, and\n * a site function by the logic plugin's `function` writer, which the `logic`\n * runner calls when its job is a build's unit. A change to a page or layout\n * the site already has is the `edit` runner's.\n */\n\n/** This plugin's id, as its registrations name their owner. */\nconst AI_PLUGIN_ID = 'ai'\n\nconst NO_ARGS = { type: 'object', properties: {}, additionalProperties: false } as const\n\nconst passes = (count: number) => () => count * AI_SITE_PASS_CREDITS\n\n/** The operations this plugin owns, as it registers them. */\nexport const AI_OWNED_CAPABILITIES: readonly PluginAiCapability[] = [\n {\n op: 'page',\n noun: 'page',\n where: 'Pages',\n intents: ['new pages: home, about, services, contact, pricing, a landing page'],\n argsSchema: NO_ARGS,\n maxPerPlan: 8,\n freeAllowed: true,\n runnerKind: 'page',\n estimateCredits: passes(6),\n degrade: 'omit',\n },\n {\n op: 'layout',\n noun: 'layout',\n where: 'Layouts',\n intents: ['a header and footer shared by pages'],\n argsSchema: NO_ARGS,\n maxPerPlan: 1,\n freeAllowed: true,\n runnerKind: 'layout',\n estimateCredits: passes(1),\n degrade: 'fallback',\n },\n {\n op: 'form',\n noun: 'form',\n where: 'Forms',\n intents: ['a contact form, a quote request, a sign-up form'],\n argsSchema: NO_ARGS,\n maxPerPlan: 3,\n freeAllowed: true,\n runnerKind: 'form',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'component',\n noun: 'component',\n where: 'Components',\n intents: ['a reusable card or block placed on several pages'],\n argsSchema: NO_ARGS,\n maxPerPlan: 4,\n freeAllowed: false,\n runnerKind: 'component',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'email',\n noun: 'email design',\n where: 'Emails → Templates',\n intents: ['an email design: a welcome, a reply, an announcement'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'email',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n op: 'template',\n noun: 'page template',\n where: 'Templates',\n intents: ['a page template for blog posts, products or authors'],\n argsSchema: {\n type: 'object',\n properties: {\n subject: {\n type: 'string',\n description: 'What each page shows: entry (a collection entry), product or author.',\n enum: AI_TEMPLATE_SUBJECTS,\n },\n collectionId: {\n type: 'string',\n description: 'For entry: the collection id from the site inventory.',\n maxLength: 100,\n },\n },\n required: ['subject'],\n additionalProperties: false,\n },\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'template',\n estimateCredits: passes(6),\n degrade: 'omit',\n },\n {\n op: 'campaign',\n noun: 'email campaign',\n where: 'Marketing → Campaigns',\n intents: ['an email campaign drafted and left unsent'],\n argsSchema: {\n type: 'object',\n properties: {\n name: { type: 'string', description: 'What the campaign is called.', maxLength: 120 },\n },\n additionalProperties: false,\n },\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'campaign',\n estimateCredits: passes(2),\n degrade: 'omit',\n },\n {\n op: 'workflow',\n noun: 'automation',\n where: 'Automations',\n intents: ['an automation: when a form is sent, do something'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n freeAllowed: false,\n runnerKind: 'workflow',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n // A function needs a model, so it is this plugin's operation; the logic\n // plugin owns the record and writes it (AGL-3616). New functions only: a\n // build never changes one the site already has.\n op: 'function',\n noun: 'site function',\n where: 'Logic → Functions',\n intents: ['a site function that works a value out from what it is given: a price, a total, a quote'],\n argsSchema: NO_ARGS,\n maxPerPlan: 2,\n // Free includes one function (`functionsPerHost: 1`), and Logic no feature.\n freeAllowed: true,\n quota: 'functionsPerHost',\n runnerKind: 'logic',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n {\n // A change to something the site already has (AGL-3616), made by the\n // `edit` runner: the Assist edit rung's protocol against the stored\n // document, saved as a version visitors do not see. Not on the Free\n // taste: without version history only an unpublished page can take a\n // change, which is rarely the page a request means, and its admission\n // would refuse the item after the plan was confirmed.\n op: 'edit',\n noun: 'page change',\n where: 'the page’s or layout’s versions',\n intents: [\n 'a change to a page or layout the site already has — shorter copy, a new section, different wording — saved as a version visitors do not see until it is published',\n ],\n argsSchema: {\n type: 'object',\n properties: {\n target: {\n type: 'string',\n description: 'The id of an existing page or layout from the site inventory.',\n maxLength: 64,\n },\n targetKind: {\n type: 'string',\n description: 'screen when target is a page, layout when it is a layout.',\n enum: AI_EDIT_TARGET_KINDS,\n },\n },\n required: ['target'],\n additionalProperties: false,\n },\n maxPerPlan: 3,\n freeAllowed: false,\n runnerKind: 'edit',\n estimateCredits: passes(1),\n degrade: 'omit',\n },\n]\n\n/**\n * The resource another plugin writes for each operation of this plugin's that\n * cannot be built without it. The operation runs where that writer's owner\n * runs — registered, switched on for the workspace and the site, and past its\n * release flag — exactly as an operation that owner registered would.\n */\nexport const AI_OWNED_OP_WRITERS: Readonly<Partial<Record<string, string>>> = {\n function: AI_LOGIC_FUNCTION_RESOURCE,\n // A campaign is written by the marketing plugin's campaign writer, an\n // automation by the workflows plugin's: neither is offered where its\n // writer's plugin is off, rather than planned and then skipped.\n campaign: AI_CAMPAIGN_RESOURCE,\n workflow: AI_AUTOMATION_RESOURCE,\n}\n\n/** Registers this plugin's build operations; the console surface calls it. */\nexport function registerAiBuildCapabilities(): void {\n for (const capability of AI_OWNED_CAPABILITIES) {\n registerPluginAiCapability(capability, { pluginId: AI_PLUGIN_ID })\n }\n}\n\n/** What decides which operations a build may use here. */\nexport interface AiBuildOpsContext {\n orgId: string\n hostId: string | null\n org: Partial<AglynOrgBilling> | null\n /** The site document, for the plugins switched on and off on it. */\n host: Readonly<Record<string, unknown>> | null\n freeTaste: boolean\n}\n\nexport interface AiBuildOpsDeps {\n /** The registered capabilities; the core registry's otherwise. */\n capabilities?: () => ResolvedPluginAiCapability[]\n /** The release-flag read; the platform's otherwise. */\n releasedPlugins?: (pluginIds: string[], orgId: string) => Promise<string[]>\n /** Whether a runner is registered for a kind; the machine's otherwise. */\n hasRunner?: (kind: string) => boolean\n /** Which plugin writes a resource; the core's registry otherwise. */\n writerOwner?: AiPluginDraftOwnerLookup\n}\n\n/**\n * The operations a build may plan on this site, by op (AGL-3616): every\n * registered capability whose owner runs here — switched on for the\n * workspace and the site, past its release flag; for one of this plugin's\n * operations another plugin writes (`AI_OWNED_OP_WRITERS`), that writer's\n * owner, and a writer registered at all — whose entitlement the plan\n * holds, which the Free taste may use where the workspace is on it, and,\n * for an AI-run operation, whose runner this process loaded. One release-flag\n * read for every owner at once. What a member may do and what an allowance\n * has left are the owner's to say when the item runs (`refusal`).\n */\nexport async function aiBuildOps(context: AiBuildOpsContext, deps: AiBuildOpsDeps = {}): Promise<AiBuildOps> {\n const all = (deps.capabilities ?? pluginAiCapabilities)()\n const hasRunner = deps.hasRunner ?? ((kind: string) => aiJobStepRunnerFor(kind as never) !== null)\n const writerOwner = deps.writerOwner ?? aiPluginDraftOwner\n // The plugin each of this plugin's operations depends on, by op; `null` where its writer is not registered.\n const writtenBy = new Map<string, string | null>()\n for (const { pluginId, capability } of all) {\n const resource = pluginId === AI_PLUGIN_ID ? AI_OWNED_OP_WRITERS[capability.op] : undefined\n if (resource) writtenBy.set(capability.op, writerOwner(resource))\n }\n const owners = [\n ...new Set(\n [...all.map((one) => one.pluginId), ...writtenBy.values()].filter(\n (id): id is string => !!id && id !== AI_PLUGIN_ID,\n ),\n ),\n ]\n const released = new Set(\n owners.length\n ? await (deps.releasedPlugins ??\n ((ids, orgId) => filterEnabledPluginsByReleaseFlags(ids, { orgId, authorization: null })))(owners, context.orgId)\n : [],\n )\n const ops = new Map<string, PluginAiCapability>()\n for (const { pluginId, capability } of all) {\n // The plugin that must run here: the owner, or for one of this plugin's\n // operations, the plugin that writes what it makes.\n const runsOn = pluginId === AI_PLUGIN_ID ? writtenBy.get(capability.op) : pluginId\n if (runsOn === null) continue\n if (runsOn !== undefined && runsOn !== AI_PLUGIN_ID) {\n if (!released.has(runsOn)) continue\n if (!isHostPluginEnabled(context.org as { enabledPlugins?: string[] } | null, context.host, runsOn)) continue\n }\n if (capability.feature && !checkEntitlement(context.org, capability.feature)) continue\n if (context.freeTaste && !capability.freeAllowed) continue\n if (capability.runnerKind && !hasRunner(capability.runnerKind)) continue\n ops.set(capability.op, capability)\n }\n return ops\n}\n\n/**\n * The lines the plan step's request lists the item operations in: each op\n * with what it makes, its arguments and their meaning. Per site, so they ride\n * the job's own turn and never a cached system block.\n */\nexport function aiBuildOpLines(ops: AiBuildOps, structural: readonly string[]): string[] {\n const items = [...ops.values()].filter((one) => !structural.includes(one.op))\n if (!items.length) return ['Operations for items: none on this site. Leave items empty.']\n const lines = ['Operations for items (anything else goes in create or screens):']\n for (const one of items) {\n const args = Object.entries(one.argsSchema.properties).map(([name, property]) => {\n const required = one.argsSchema.required?.includes(name) ? '' : ' (optional)'\n // An array's values are named too (AGL-3616): told only \"days: array\",\n // the live canonical plan wrote \"monday\" for the enum's \"mon\".\n const choices = property.enum\n ? ` one of ${property.enum.join('|')}`\n : property.items?.enum\n ? ` of ${property.items.enum.join('|')}`\n : ''\n return `${name}: ${property.type}${choices}${required} — ${property.description}`\n })\n lines.push(\n `- ${one.op}: ${aiNounWithArticle(one.noun)}, at most ${one.maxPerPlan}; for ${one.intents.join('; ')}.${\n one.pageBlocks?.length ? ` A page section that uses it places the ${one.pageBlocks.join(' or ')} block: put new:<name> in that section's uses.` : ''\n } Args: ${args.length ? `{ ${args.join('; ')} }` : '{}'}.`,\n )\n }\n // Measured live on the canonical request (AGL-3616): asked for \"forms for\n // bookings\", the planner made a plain form rather than the operation that\n // takes bookings. An operation does the job; a form that imitates it does not.\n lines.push(\n 'When the request asks for what one of these operations makes, plan that operation as an item, and place it on the page that offers it. Never stand in for it with a form, page or component that only imitates it: a form that collects what the operation would handle does not do its job. A form is for what no operation here makes, such as general inquiries.',\n )\n return lines\n}\n\nfunction aiNounWithArticle(noun: string): string {\n return `${/^[aeiou]/i.test(noun) ? 'an' : 'a'} ${noun}`\n}\n\n/** What a build can make here, in the owner's words, for the chat's instructions. */\nexport function aiBuildIntents(ops: AiBuildOps): string[] {\n return [...ops.values()].flatMap((one) => one.intents)\n}\n"],"names":["checkEntitlement","isHostPluginEnabled","pluginAiCapabilities","registerPluginAiCapability","filterEnabledPluginsByReleaseFlags","AI_TEMPLATE_SUBJECTS","AI_SITE_PASS_CREDITS","AI_LOGIC_FUNCTION_RESOURCE","AI_AUTOMATION_RESOURCE","AI_EDIT_TARGET_KINDS","AI_CAMPAIGN_RESOURCE","aiPluginDraftOwner","aiJobStepRunnerFor","AI_PLUGIN_ID","NO_ARGS","type","properties","additionalProperties","passes","count","AI_OWNED_CAPABILITIES","op","noun","where","intents","argsSchema","maxPerPlan","freeAllowed","runnerKind","estimateCredits","degrade","subject","description","enum","collectionId","maxLength","required","name","quota","target","targetKind","AI_OWNED_OP_WRITERS","function","campaign","workflow","registerAiBuildCapabilities","capability","pluginId","aiBuildOps","context","deps","all","capabilities","hasRunner","kind","writerOwner","writtenBy","Map","resource","undefined","set","owners","Set","map","one","values","filter","id","released","length","releasedPlugins","ids","orgId","authorization","ops","runsOn","get","has","org","host","feature","freeTaste","aiBuildOpLines","structural","items","includes","lines","args","Object","entries","property","choices","join","push","aiNounWithArticle","pageBlocks","test","aiBuildIntents","flatMap"],"mappings":"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"}
|
|
@@ -59,7 +59,8 @@ export interface AiJobAdmissionContext {
|
|
|
59
59
|
uid?: string | null;
|
|
60
60
|
}
|
|
61
61
|
export interface AiJobAdmissionRefusal {
|
|
62
|
-
|
|
62
|
+
/** 429: what the month's AI credits have left cannot pay for it (AGL-3660). */
|
|
63
|
+
status: 400 | 403 | 404 | 429;
|
|
63
64
|
/** Customer-safe: the door answers with it as it stands. */
|
|
64
65
|
error: string;
|
|
65
66
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-admission.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 type { AiJobKind, AiJobPlan } from '../model/ai-jobs.types'\nimport {\n AI_OFF_FOR_SITE_COPY,\n AI_OFF_FOR_SITE_STATUS,\n isAiOffForSite,\n} from '../model/ai-site-switch'\n\n/**\n * What a job kind checks before a job of it runs (AGL-2909).\n *\n * The create door parses the body every kind shares — the org, the site, the\n * brief, scalar inputs — and nothing the machine knows says whether THIS kind\n * can finish: a layout needs a site to write to, a page template names the\n * collection it renders, and both write a document the site's plan counts.\n * A job that cannot finish should never start, because a planned kind spends\n * on its plan before it writes anything. So a kind registers one check, the\n * create door asks it after the gate ladder admitted the request and before\n * the job exists, and the resume door asks it again before a confirmed plan\n * runs, since an allowance free at creation may be used by now. Either way\n * a refusal hands the reservation back and spends nothing.\n *\n * The resume door also hands the check the plan being confirmed (AGL-2907),\n * so a kind that can build only some plans — a page job builds one screen\n * from what the site has — refuses the others when a member confirms them,\n * not a beat later from inside the step.\n *\n * The registry lives apart from the machine so the doors read it without the\n * generators, and a generator registers here beside its runner.\n */\n\nexport interface AiJobAdmissionContext {\n /** The Admin SDK handle the door runs on. */\n firestore: FirebaseFirestore.Firestore\n orgId: string\n hostId: string | null\n /** The job's kind-specific inputs, as the door parsed or the job stored them. */\n inputs: Readonly<Record<string, unknown>>\n /** The org document the gate ladder read; a check narrows it to the fields it reads. */\n org: object | null\n /**\n * The job's plan when the door is resuming a job that has one; absent at\n * creation, when no plan exists yet.\n */\n plan?: AiJobPlan | null\n /**\n * The member whose request spends: the creator at the create door, the\n * member confirming at the resume door. A kind whose draft another plugin\n * writes asks that plugin whether this member may create one.\n */\n uid?: string | null\n}\n\nexport interface AiJobAdmissionRefusal {\n status: 400 | 403 | 404\n /** Customer-safe: the door answers with it as it stands. */\n error: string\n}\n\nexport type AiJobAdmission = (\n context: AiJobAdmissionContext,\n) => Promise<AiJobAdmissionRefusal | null>\n\nconst admissions = new Map<AiJobKind, AiJobAdmission>()\n\n/** Idempotent per kind; the last registration wins, and `null` unregisters. */\nexport function registerAiJobAdmission(kind: AiJobKind, admission: AiJobAdmission | null): void {\n if (admission) admissions.set(kind, admission)\n else admissions.delete(kind)\n}\n\n/** The kind's registered admission, or `null` when it has none. */\nexport function aiJobAdmissionFor(kind: AiJobKind): AiJobAdmission | null {\n return admissions.get(kind) ?? null\n}\n\n/** The kind's refusal, or `null` when it admits the job or has nothing to check. */\nexport async function aiJobAdmissionRefusal(\n kind: AiJobKind,\n context: AiJobAdmissionContext,\n): Promise<AiJobAdmissionRefusal | null> {\n const admission = admissions.get(kind)\n return admission ? admission(context) : null\n}\n\n/**\n * The refusal for a job on a site that switched AI off (AGL-3028), or `null`.\n *\n * Every kind meets it, and every door asks it BEFORE the kind's own check —\n * the create door, each site of an agency batch, and a resume — so no job\n * comes to exist, or runs another step, for a site that said no, and a\n * switched-off site never pays for a kind's reads. A job with no site is\n * workspace-level and always passes.\n */\nexport async function aiJobSiteRefusal(\n context: Pick<AiJobAdmissionContext, 'firestore' | 'org' | 'hostId'>,\n): Promise<AiJobAdmissionRefusal | null> {\n return (await isAiOffForSite(context.firestore, context.org, context.hostId))\n ? { status: AI_OFF_FOR_SITE_STATUS, error: AI_OFF_FOR_SITE_COPY }\n : null\n}\n"],"names":["AI_OFF_FOR_SITE_COPY","AI_OFF_FOR_SITE_STATUS","isAiOffForSite","admissions","Map","registerAiJobAdmission","kind","admission","set","delete","aiJobAdmissionFor","get","aiJobAdmissionRefusal","context","aiJobSiteRefusal","firestore","org","hostId","status","error"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SACEA,oBAAoB,EACpBC,sBAAsB,EACtBC,cAAc,QACT,6BAAyB;
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/jobs/ai-job-admission.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 type { AiJobKind, AiJobPlan } from '../model/ai-jobs.types'\nimport {\n AI_OFF_FOR_SITE_COPY,\n AI_OFF_FOR_SITE_STATUS,\n isAiOffForSite,\n} from '../model/ai-site-switch'\n\n/**\n * What a job kind checks before a job of it runs (AGL-2909).\n *\n * The create door parses the body every kind shares — the org, the site, the\n * brief, scalar inputs — and nothing the machine knows says whether THIS kind\n * can finish: a layout needs a site to write to, a page template names the\n * collection it renders, and both write a document the site's plan counts.\n * A job that cannot finish should never start, because a planned kind spends\n * on its plan before it writes anything. So a kind registers one check, the\n * create door asks it after the gate ladder admitted the request and before\n * the job exists, and the resume door asks it again before a confirmed plan\n * runs, since an allowance free at creation may be used by now. Either way\n * a refusal hands the reservation back and spends nothing.\n *\n * The resume door also hands the check the plan being confirmed (AGL-2907),\n * so a kind that can build only some plans — a page job builds one screen\n * from what the site has — refuses the others when a member confirms them,\n * not a beat later from inside the step.\n *\n * The registry lives apart from the machine so the doors read it without the\n * generators, and a generator registers here beside its runner.\n */\n\nexport interface AiJobAdmissionContext {\n /** The Admin SDK handle the door runs on. */\n firestore: FirebaseFirestore.Firestore\n orgId: string\n hostId: string | null\n /** The job's kind-specific inputs, as the door parsed or the job stored them. */\n inputs: Readonly<Record<string, unknown>>\n /** The org document the gate ladder read; a check narrows it to the fields it reads. */\n org: object | null\n /**\n * The job's plan when the door is resuming a job that has one; absent at\n * creation, when no plan exists yet.\n */\n plan?: AiJobPlan | null\n /**\n * The member whose request spends: the creator at the create door, the\n * member confirming at the resume door. A kind whose draft another plugin\n * writes asks that plugin whether this member may create one.\n */\n uid?: string | null\n}\n\nexport interface AiJobAdmissionRefusal {\n /** 429: what the month's AI credits have left cannot pay for it (AGL-3660). */\n status: 400 | 403 | 404 | 429\n /** Customer-safe: the door answers with it as it stands. */\n error: string\n}\n\nexport type AiJobAdmission = (\n context: AiJobAdmissionContext,\n) => Promise<AiJobAdmissionRefusal | null>\n\nconst admissions = new Map<AiJobKind, AiJobAdmission>()\n\n/** Idempotent per kind; the last registration wins, and `null` unregisters. */\nexport function registerAiJobAdmission(kind: AiJobKind, admission: AiJobAdmission | null): void {\n if (admission) admissions.set(kind, admission)\n else admissions.delete(kind)\n}\n\n/** The kind's registered admission, or `null` when it has none. */\nexport function aiJobAdmissionFor(kind: AiJobKind): AiJobAdmission | null {\n return admissions.get(kind) ?? null\n}\n\n/** The kind's refusal, or `null` when it admits the job or has nothing to check. */\nexport async function aiJobAdmissionRefusal(\n kind: AiJobKind,\n context: AiJobAdmissionContext,\n): Promise<AiJobAdmissionRefusal | null> {\n const admission = admissions.get(kind)\n return admission ? admission(context) : null\n}\n\n/**\n * The refusal for a job on a site that switched AI off (AGL-3028), or `null`.\n *\n * Every kind meets it, and every door asks it BEFORE the kind's own check —\n * the create door, each site of an agency batch, and a resume — so no job\n * comes to exist, or runs another step, for a site that said no, and a\n * switched-off site never pays for a kind's reads. A job with no site is\n * workspace-level and always passes.\n */\nexport async function aiJobSiteRefusal(\n context: Pick<AiJobAdmissionContext, 'firestore' | 'org' | 'hostId'>,\n): Promise<AiJobAdmissionRefusal | null> {\n return (await isAiOffForSite(context.firestore, context.org, context.hostId))\n ? { status: AI_OFF_FOR_SITE_STATUS, error: AI_OFF_FOR_SITE_COPY }\n : null\n}\n"],"names":["AI_OFF_FOR_SITE_COPY","AI_OFF_FOR_SITE_STATUS","isAiOffForSite","admissions","Map","registerAiJobAdmission","kind","admission","set","delete","aiJobAdmissionFor","get","aiJobAdmissionRefusal","context","aiJobSiteRefusal","firestore","org","hostId","status","error"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SACEA,oBAAoB,EACpBC,sBAAsB,EACtBC,cAAc,QACT,6BAAyB;AA0DhC,MAAMC,aAAa,IAAIC;AAEvB,6EAA6E,GAC7E,OAAO,SAASC,uBAAuBC,IAAe,EAAEC,SAAgC;IACtF,IAAIA,WAAWJ,WAAWK,GAAG,CAACF,MAAMC;SAC/BJ,WAAWM,MAAM,CAACH;AACzB;AAEA,iEAAiE,GACjE,OAAO,SAASI,kBAAkBJ,IAAe;QACxCH;IAAP,QAAOA,kBAAAA,WAAWQ,GAAG,CAACL,iBAAfH,kBAAwB;AACjC;AAEA,kFAAkF,GAClF,OAAO,eAAeS,sBACpBN,IAAe,EACfO,OAA8B;IAE9B,MAAMN,YAAYJ,WAAWQ,GAAG,CAACL;IACjC,OAAOC,YAAYA,UAAUM,WAAW;AAC1C;AAEA;;;;;;;;CAQC,GACD,OAAO,eAAeC,iBACpBD,OAAoE;IAEpE,OAAO,AAAC,MAAMX,eAAeW,QAAQE,SAAS,EAAEF,QAAQG,GAAG,EAAEH,QAAQI,MAAM,IACvE;QAAEC,QAAQjB;QAAwBkB,OAAOnB;IAAqB,IAC9D;AACN"}
|