@aglyn/plugins-ai 1.0.0-beta.264 → 1.0.0-beta.268
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 +18 -18
- package/src/lib/assets/site-styles/beauty.webp +0 -0
- package/src/lib/assets/site-styles/blog.webp +0 -0
- package/src/lib/assets/site-styles/business.webp +0 -0
- package/src/lib/assets/site-styles/coming-soon.webp +0 -0
- package/src/lib/assets/site-styles/education.webp +0 -0
- package/src/lib/assets/site-styles/events.webp +0 -0
- package/src/lib/assets/site-styles/fitness.webp +0 -0
- package/src/lib/assets/site-styles/landing.webp +0 -0
- package/src/lib/assets/site-styles/music.webp +0 -0
- package/src/lib/assets/site-styles/nonprofit.webp +0 -0
- package/src/lib/assets/site-styles/personal.webp +0 -0
- package/src/lib/assets/site-styles/photography.webp +0 -0
- package/src/lib/assets/site-styles/portfolio.webp +0 -0
- package/src/lib/assets/site-styles/professional.webp +0 -0
- package/src/lib/assets/site-styles/realestate.webp +0 -0
- package/src/lib/assets/site-styles/restaurant.webp +0 -0
- package/src/lib/assets/site-styles/store.webp +0 -0
- package/src/lib/assets/site-styles/studio.webp +0 -0
- package/src/lib/assets/site-styles/trades.webp +0 -0
- package/src/lib/assets/site-styles/wellness.webp +0 -0
- package/src/lib/assets/site-styles/yoga.webp +0 -0
- package/src/lib/components/ai-job-site.d.ts +8 -1
- package/src/lib/components/ai-job-site.js +7 -0
- package/src/lib/components/ai-job-site.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +37 -0
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.d.ts +12 -0
- package/src/lib/components/ai-site-start-card.component.js +279 -240
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-site-start-steps.component.d.ts +114 -0
- package/src/lib/components/ai-site-start-steps.component.js +480 -0
- package/src/lib/components/ai-site-start-steps.component.js.map +1 -0
- package/src/lib/components/ai-site-start-tracking.d.ts +122 -0
- package/src/lib/components/ai-site-start-tracking.js +296 -0
- package/src/lib/components/ai-site-start-tracking.js.map +1 -0
- package/src/lib/components/ai-site-style-previews.d.ts +26 -0
- package/src/lib/components/ai-site-style-previews.js +115 -0
- package/src/lib/components/ai-site-style-previews.js.map +1 -0
- package/src/lib/components/assist-jobs-drawer.component.js +24 -2
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/insights/ai-insight-digest.d.ts +9 -0
- package/src/lib/insights/ai-insight-digest.js +35 -11
- package/src/lib/insights/ai-insight-digest.js.map +1 -1
- package/src/lib/jobs/ai-empty-site-sweep.d.ts +154 -0
- package/src/lib/jobs/ai-empty-site-sweep.js +346 -0
- package/src/lib/jobs/ai-empty-site-sweep.js.map +1 -0
- package/src/lib/jobs/ai-job-page-language.d.ts +9 -0
- package/src/lib/jobs/ai-job-page-language.js +20 -2
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +43 -8
- package/src/lib/jobs/ai-job-plan-step.js +99 -18
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +82 -1
- package/src/lib/jobs/ai-job-site-datasets.js +276 -14
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-jobs-beat.d.ts +11 -3
- package/src/lib/jobs/ai-jobs-beat.js +23 -6
- package/src/lib/jobs/ai-jobs-beat.js.map +1 -1
- package/src/lib/jobs/ai-jobs.js +46 -7
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +143 -10
- package/src/lib/jobs/ai-layout-stock-photos.js +1046 -102
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.d.ts +58 -0
- package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.js +678 -0
- package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.d.ts +33 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.js +270 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.d.ts +33 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.js +277 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.d.ts +35 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.js +379 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.d.ts +31 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.js +208 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.js +49 -12
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
- package/src/lib/layout-language/ai-layout-design.js +19 -13
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +48 -0
- package/src/lib/layout-language/ai-layout-listings.js +91 -5
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +15 -0
- package/src/lib/layout-language/ai-layout-pictures.js +6 -2
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +20 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-site-home-arcs.d.ts +75 -0
- package/src/lib/model/ai-site-home-arcs.js +741 -0
- package/src/lib/model/ai-site-home-arcs.js.map +1 -0
- package/src/lib/model/ai-site-inventory.d.ts +18 -0
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +9 -6
- package/src/lib/model/ai-site-job.js +8 -5
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.js +24 -24
- package/src/lib/model/ai-site-kinds.js.map +1 -1
- package/src/lib/model/ai-site-start-handoff.d.ts +49 -0
- package/src/lib/model/ai-site-start-handoff.js +88 -0
- package/src/lib/model/ai-site-start-handoff.js.map +1 -0
- package/src/lib/model/ai-site-start-steps.d.ts +67 -0
- package/src/lib/model/ai-site-start-steps.js +127 -0
- package/src/lib/model/ai-site-start-steps.js.map +1 -0
- package/src/lib/model/ai-site-start.js +4 -1
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -0
- package/src/lib/runtime/ai-doctrine-validators.js +119 -2
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.d.ts +6 -1
- package/src/lib/runtime/ai-doctrine.js +9 -5
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +5 -2
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/site-inventory.js +6 -0
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server/ai-insight-digest-route.js +2 -2
- package/src/lib/server/ai-insight-digest-route.js.map +1 -1
- package/src/lib/usage/assist-free-taste.js +3 -0
- package/src/lib/usage/assist-free-taste.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/assist-jobs-drawer.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\nimport { checkEntitlement } from '@aglyn/aglyn'\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport { formatBytes } from '@aglyn/aglyn/app-utils/measure-node-map'\nimport {\n AI_JOB_TERMINAL_STATUSES,\n type AiJobOutput,\n type AiJobSummary,\n} from '../model/ai-jobs.types'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { mdiChevronDown, mdiChevronUp } from '@aglyn/shared-data-mdi'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { AppLink, HelpTip, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Box,\n Button,\n Chip,\n CircularProgress,\n Collapse,\n IconButton,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n AI_JOB_PHASE_LABELS,\n aiJobActivityCounts,\n aiJobPhase,\n aiJobsHeaderChips,\n type AiJobActivityState,\n type AiJobPhase,\n} from '../model/ai-job-activity'\nimport type { AiInsightSurface } from '../model/ai-insight'\nimport { resumeAiJobRequest, type AiJobResumeOptions } from './ai-job-requests'\nimport { useAiJobCancel } from './ai-job-cancel.component'\nimport { aiBuildCanRetry, aiBuildItemRows, aiBuildRetryCreditRange, aiSitePartialCopy } from '../model/ai-build-progress'\nimport { aiCreditRangeText, type AiCreditsPrompt } from '../model/ai-credit-estimate'\nimport { AiCreditsPromptNotice } from './ai-credits-prompt.component'\nimport { publishAiJob, type AiJobsOpenRequest } from './ai-jobs-store'\nimport { AiPageBriefDialog } from './ai-page-brief-dialog.component'\nimport { AiInsightDialog } from './ai-insight-dialog.component'\nimport { AiJobPlan } from './ai-job-plan.component'\nimport { aiCreditsBillingHref, aiJobOutputHref, aiJobPrimaryLink, aiSiteBuildHref } from './ai-job-links'\nimport { aiJobRefundCopy } from '../model/ai-job-failure-copy'\nimport { readEventFrames } from './ai-job-events'\n\n/** Try again, with what it is likely to cost (AGL-3722). */\nfunction aiBuildRetryLabel(job: AiJobSummary): string {\n const range = aiBuildRetryCreditRange(job)\n return range ? `Try again what failed · ${aiCreditRangeText(range)}` : 'Try again what failed'\n}\n\n// Where an output opens, and the stream reader, live beside the dialogs that\n// share them (AGL-3593); this module has always exported them.\nexport { aiJobOutputHref, aiJobPrimaryLink, readEventFrames }\n\n/**\n * The AI jobs drawer inside the Assist panel (AGL-2904): the workspace's\n * running and recent generation jobs, each step's progress, an \"open\n * draft\" link per output, and cancel.\n *\n * Reads through the routes, never Firestore directly, so the same\n * membership, flag and entitlement rungs answer every read; progress comes\n * from the events route's server-sent stream, re-opened when the server\n * says `reconnect`. Everything loads on EXPAND rather than on mount: the\n * panel opens on every console page, and a list nobody asked for is a read\n * on every page view.\n *\n * It opens EXPANDED while any of the workspace's jobs is moving or waiting\n * for the person (AGL-3593) — the shared list the top-bar indicator reads\n * says so — because a plan waiting behind a collapsed row is a site that\n * never gets built. An \"Open AI jobs\" anywhere in the console expands it,\n * scrolls to the job it names and highlights it for a moment.\n *\n * Hidden — not disabled — unless the flag shows it AND the plan carries\n * `aiGenerative`: a released-off feature does not exist, and a plan without\n * it is sold the add-on on the Billing page, not here.\n */\n\n/** Jobs the drawer lists; the route caps higher, the drawer is a glance. */\nconst LIST_LIMIT = 10\n\n/** How much of a brief a row shows. */\nconst BRIEF_PREVIEW_CHARS = 90\n\n/**\n * A row's chip says where the job stands in the words every AI surface uses\n * (AGL-3593): a plan waiting to be confirmed is \"Plan ready\", never \"Running\"\n * and never the machine's `needs_review`.\n */\nconst PHASE_COLOR: Record<AiJobPhase, 'default' | 'primary' | 'success' | 'error' | 'warning'> = {\n queued: 'default',\n planning: 'primary',\n 'plan-ready': 'warning',\n building: 'primary',\n attention: 'warning',\n done: 'success',\n failed: 'error',\n canceled: 'default',\n}\n\n/** The header's count chips, colored as the rows they count. */\nconst STATE_COLOR: Record<AiJobActivityState, 'primary' | 'warning'> = {\n 'needs-you': 'warning',\n attention: 'warning',\n running: 'primary',\n}\n\n/** What each item state reads as in a row (AGL-3616). */\nconst ITEM_STATE_LABELS = {\n done: 'built',\n active: 'building',\n waiting: 'waiting',\n failed: 'failed',\n skipped: 'not built',\n} as const\n\n/** How long a job AI jobs was opened on stays highlighted. */\nconst HIGHLIGHT_MS = 2_500\n\n/**\n * Where a guided start that ended unbuilt is tried again (AGL-3596): its\n * \"Building your site\" page, opened on its own answers (`?retry=1`). Only for\n * a job of the site the console is on, whose subdomain the address names;\n * `null` otherwise.\n */\nexport function aiJobRestartHref(\n job: Pick<AiJobSummary, 'id' | 'kind' | 'status' | 'hostId' | 'siteInputs'>,\n input: { orgSlug: string; hostId?: string | null; pathname: string },\n): string | null {\n if (job.kind !== 'site' || (job.status !== 'failed' && job.status !== 'canceled')) return null\n if (!job.siteInputs || !job.hostId || job.hostId !== input.hostId) return null\n const subdomain = /^\\/[^/]+\\/hosts\\/([^/?#]+)/.exec(input.pathname)?.[1] ?? null\n const href = aiSiteBuildHref(input.orgSlug, subdomain, job.id)\n return href ? `${href}?retry=1` : null\n}\n\n/** What a job cost the person: what it spent, less what we refunded. */\nfunction netCredits(job: Pick<AiJobSummary, 'creditsSpent' | 'refundedCredits'>): number {\n return Math.max(0, (job.creditsSpent ?? 0) - (job.refundedCredits ?? 0))\n}\n\n/**\n * The navigation entry a page job proposes for the page it built (AGL-2907),\n * or `null`. The job writes no menu: the member adds the entry once the page\n * is live.\n */\nexport function aiJobNavigationProposal(output: AiJobOutput): string | null {\n if (output.resource !== 'screen') return null\n const navigation = output.proposal?.['navigation']\n if (!navigation || typeof navigation !== 'object') return null\n const label = (navigation as Record<string, unknown>)['label']\n return typeof label === 'string' && label.trim() ? label.trim() : null\n}\n\n\nexport interface AssistJobsDrawerProps {\n /** The org the panel is scoped to — `undefined` where the page named none. */\n orgId: string | undefined\n org: Partial<AglynOrgBilling> | undefined\n orgReady: boolean\n orgSlug: string\n user: Parameters<typeof authorizedFetch>[0]\n /** The `release_ai_generative` verdict, staff bypass applied (the shell's). */\n visible: boolean\n /**\n * The viewer is staff (AGL-3078): a job that stopped for a broken rule shows\n * them where the refused answer broke it. Who may read a job is unchanged.\n */\n isStaff?: boolean\n /** The site the console page is on, when it is on one: a page is described for a site. */\n hostId?: string | null\n /**\n * The page's insight surface (AGL-2915) — a site's Analytics, Data or CRM\n * Reports page, or the workspace's Data page — with the site's subdomain;\n * `null` elsewhere, where no question about the figures is offered.\n */\n insight?: { surface: Exclude<AiInsightSurface, 'digest'>; host: string | null } | null\n /**\n * The workspace's jobs that are not yet settled, from the shared store the\n * top-bar indicator reads (AGL-3593). The header counts them, and the\n * drawer opens expanded while there is any. Absent, the drawer counts what\n * it loaded itself.\n */\n inFlight?: readonly AiJobSummary[]\n /**\n * The latest request to open AI jobs (AGL-3593): a new `seq` expands the\n * drawer, and a named job is scrolled to and highlighted.\n */\n focus?: AiJobsOpenRequest | null\n}\n\nexport function AssistJobsDrawer({\n orgId,\n org,\n orgReady,\n orgSlug,\n user,\n visible,\n isStaff = false,\n hostId,\n insight,\n inFlight,\n focus,\n}: AssistJobsDrawerProps): JSX.Element | null {\n const entitled = orgReady && checkEntitlement(org as never, 'aiGenerative')\n const [expanded, setExpanded] = useState(false)\n /**\n * The person opened or closed the list themselves: from then on the\n * expanded-by-default rule leaves their choice alone until the workspace\n * changes.\n */\n const toggledRef = useRef(false)\n /** The job AI jobs was opened on, highlighted for a moment once it is listed. */\n const [highlight, setHighlight] = useState<string | null>(null)\n const listRef = useRef<HTMLDivElement | null>(null)\n const [jobs, setJobs] = useState<AiJobSummary[]>([])\n const [loading, setLoading] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n /** Whether the page brief dialog is open (AGL-2907). */\n const [describing, setDescribing] = useState(false)\n /**\n * The insight dialog (AGL-2915): `'ask'` for a new question, a job id for an\n * answer a job already wrote, `null` when closed.\n */\n const [insightOpen, setInsightOpen] = useState<string | null>(null)\n /** Job ids whose terminal event has been tracked, so a re-read does not count twice. */\n const trackedRef = useRef(new Set<string>())\n /** The job whose stream is open, so a re-render does not open a second. */\n const watchingRef = useRef<string | null>(null)\n\n const patchJob = useCallback((next: AiJobSummary) => {\n // Every counting surface moves with the row (AGL-3593).\n publishAiJob(next)\n setJobs((prior) => {\n const index = prior.findIndex((job) => job.id === next.id)\n if (index === -1) return [next, ...prior].slice(0, LIST_LIMIT)\n const copy = [...prior]\n copy[index] = next\n return copy\n })\n if (\n AI_JOB_TERMINAL_STATUSES.includes(next.status) &&\n !trackedRef.current.has(next.id)\n ) {\n trackedRef.current.add(next.id)\n if (next.status === 'done') {\n trackEvent('ai_job_completed', { kind: next.kind, credits: next.creditsSpent })\n } else if (next.status === 'failed') {\n trackEvent('ai_job_failed', { kind: next.kind })\n }\n }\n }, [])\n\n const load = useCallback(async () => {\n if (!orgId) return\n setLoading(true)\n setNotice(null)\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=${LIST_LIMIT}`,\n )\n if (!response.ok) {\n const payload = await response.json().catch(() => null)\n setNotice(String(payload?.error ?? 'The AI jobs could not be loaded — try again.'))\n return\n }\n const payload = (await response.json()) as { jobs: AiJobSummary[] }\n // Every job that arrives already terminal is one the drawer did not\n // watch finish, so its outcome is not this session's to count.\n for (const job of payload.jobs) {\n if (AI_JOB_TERMINAL_STATUSES.includes(job.status)) trackedRef.current.add(job.id)\n // A job the indicator still counts that has settled since leaves it.\n publishAiJob(job)\n }\n setJobs(payload.jobs)\n } catch {\n setNotice('The AI jobs could not be loaded — try again.')\n } finally {\n setLoading(false)\n }\n }, [orgId, user])\n\n useEffect(() => {\n if (expanded) void load()\n }, [expanded, load])\n\n // Reset when the panel's org changes: a list from one workspace must not\n // show under another's key while the new one loads.\n useEffect(() => {\n setJobs([])\n setExpanded(false)\n toggledRef.current = false\n watchingRef.current = null\n }, [orgId])\n\n // Expanded by default while anything is moving or waiting (AGL-3593): a\n // plan waiting to be confirmed behind a collapsed row is a site that never\n // gets built. Otherwise collapsed, which reads nothing until asked.\n const anyInFlight = (inFlight?.length ?? 0) > 0\n useEffect(() => {\n if (anyInFlight && !toggledRef.current) setExpanded(true)\n }, [anyInFlight])\n\n // A request to open AI jobs (AGL-3593): expand, read the list again so a\n // job created a moment ago is in it, and highlight the job named.\n const focusSeq = focus?.seq ?? 0\n const focusJobId = focus?.jobId ?? null\n const expandedRef = useRef(expanded)\n expandedRef.current = expanded\n useEffect(() => {\n if (!focusSeq) return\n toggledRef.current = true\n setHighlight(focusJobId)\n // An open list does not re-run the expand effect's read, so it reads here.\n if (expandedRef.current) void load()\n else setExpanded(true)\n // Only a new request runs this; `load` and the job are read as they stand.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [focusSeq])\n\n // Scroll to the highlighted job once it is listed, then let it fade.\n const highlightListed = highlight !== null && jobs.some((job) => job.id === highlight)\n useEffect(() => {\n if (!highlight || !highlightListed) return undefined\n const row = Array.from(\n listRef.current?.querySelectorAll<HTMLElement>('[data-ai-job-id]') ?? [],\n ).find((element) => element.dataset['aiJobId'] === highlight)\n row?.scrollIntoView?.({ block: 'nearest', behavior: 'smooth' })\n const timer = setTimeout(() => setHighlight(null), HIGHLIGHT_MS)\n return () => clearTimeout(timer)\n }, [highlight, highlightListed])\n\n // Watch the most recent job that is still moving. One stream at a time:\n // the route re-reads a document every two seconds per open stream, and a\n // drawer with three running jobs does not need three of them.\n const watched = jobs.find((job) => !AI_JOB_TERMINAL_STATUSES.includes(job.status))\n const watchedId = watched?.id ?? null\n useEffect(() => {\n if (!expanded || !orgId || !watchedId || watchingRef.current === watchedId) return\n watchingRef.current = watchedId\n const controller = new AbortController()\n const watch = async () => {\n // The server closes a stream at its deadline with `reconnect`; the\n // loop here re-opens it until the job settles or the drawer unmounts.\n let again = true\n while (again && !controller.signal.aborted) {\n again = false\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs/${encodeURIComponent(watchedId)}/events?orgId=${encodeURIComponent(orgId)}`,\n { signal: controller.signal },\n )\n if (!response.ok || !response.body) return\n await readEventFrames(response.body, (event) => {\n if (event['type'] === 'state') patchJob(event['job'] as AiJobSummary)\n else if (event['type'] === 'reconnect') again = true\n else if (event['type'] === 'gone') {\n setJobs((prior) => prior.filter((job) => job.id !== watchedId))\n }\n })\n } catch {\n // The stream ended without a terminal state — a network blip or\n // the drawer closing. The next expand reloads the list.\n return\n }\n }\n }\n void watch().finally(() => {\n if (watchingRef.current === watchedId) watchingRef.current = null\n })\n return () => {\n controller.abort()\n if (watchingRef.current === watchedId) watchingRef.current = null\n }\n }, [expanded, orgId, watchedId, user, patchJob])\n\n // The same door the dialog that started the job cancels through (AGL-3593),\n // behind the one confirm every Cancel shows (AGL-3616).\n const canceling = useAiJobCancel({ user, orgId, onJob: patchJob })\n\n /** The job whose resume is in flight, so its button cannot send twice. */\n const [resuming, setResuming] = useState<string | null>(null)\n // A Free Try again past what is left (AGL-3722): its prompt, by job, until chosen.\n const [retryPrompt, setRetryPrompt] = useState<{ jobId: string; prompt: AiCreditsPrompt } | null>(null)\n\n // Confirms a plan, or tries again a step whose answer broke a building\n // rule (AGL-2935). The door runs the next step inline and answers with the\n // job, so the row moves on without waiting for the stream.\n const resume = useCallback(\n async (job: AiJobSummary, options?: AiJobResumeOptions) => {\n if (!orgId) return\n setResuming(job.id)\n setNotice(null)\n setRetryPrompt(null)\n // The same door the dialog that started the job confirms through (AGL-3593).\n const { job: next, error, credits } = await resumeAiJobRequest(user, orgId, job, options)\n if (next) patchJob(next)\n // A Free job past what is left asks with its choices (AGL-3722): a\n // retry here, a plan on its own card from the job the door answered with.\n if (credits && options?.retry) setRetryPrompt({ jobId: job.id, prompt: credits })\n else if (error && !(credits && job.status === 'needs_review')) setNotice(error)\n setResuming(null)\n },\n [orgId, user, patchJob],\n )\n\n if (!visible || !orgId || !entitled) return null\n\n // Counted by state, never as one \"running\" figure (AGL-3593): a plan waiting\n // to be confirmed needs the person, and a stopped job is not moving.\n const chips = aiJobsHeaderChips(aiJobActivityCounts(inFlight ?? jobs))\n\n return (\n <Box sx={{ mb: 2, borderBottom: 1, borderColor: 'divider', pb: 1 }}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n AI jobs\n {chips.map((chip) => (\n <Chip\n key={chip.state}\n size=\"small\"\n color={STATE_COLOR[chip.state]}\n label={chip.label}\n sx={{ ml: 1 }}\n />\n ))}\n </Typography>\n <HelpTip\n {...pluginDocsHelp('aiJobs', {\n anchor: '#ai-jobs-in-assist',\n excerpt:\n 'Every AI job in this workspace, newest first: where it stands, the credits it used, and its plan to confirm, cancel or try again.',\n })}\n />\n {/* A question about the page's figures (AGL-2915). */}\n {insight && (\n <Button size=\"small\" onClick={() => setInsightOpen('ask')}>\n Ask about your numbers\n </Button>\n )}\n {/* A page from a brief (AGL-2907), on a site's own routes. */}\n {hostId && (\n <Button size=\"small\" onClick={() => setDescribing(true)}>\n Describe a page\n </Button>\n )}\n <IconButton\n size=\"small\"\n aria-label={expanded ? 'Hide AI jobs' : 'Show AI jobs'}\n aria-expanded={expanded}\n onClick={() => {\n toggledRef.current = true\n setExpanded((prior) => !prior)\n }}\n >\n <MdiIcon\n path={expanded ? mdiChevronUp.path : mdiChevronDown.path}\n sx={{ fontSize: 18 }}\n />\n </IconButton>\n </Stack>\n <Collapse in={expanded} unmountOnExit>\n {notice && (\n <Typography variant=\"caption\" color=\"error\" component=\"div\" sx={{ mt: 0.5 }}>\n {notice}\n </Typography>\n )}\n {loading && !jobs.length && (\n <CircularProgress size={16} sx={{ mt: 1 }} aria-label=\"Loading AI jobs\" />\n )}\n {!loading && !jobs.length && !notice && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\" sx={{ mt: 0.5 }}>\n No AI jobs yet.\n </Typography>\n )}\n {canceling.dialog}\n <Stack ref={listRef} spacing={1} sx={{ mt: 1 }} role=\"list\" aria-label=\"AI jobs\">\n {jobs.map((job) => {\n const restartHref = (row: AiJobSummary) =>\n aiJobRestartHref(row, {\n orgSlug,\n hostId,\n pathname: typeof window === 'undefined' ? '' : window.location.pathname,\n })\n const stepsDone = job.steps.filter((step) => step.status === 'done').length\n const current = job.steps.find((step) => step.status === 'running')\n const terminal = AI_JOB_TERMINAL_STATUSES.includes(job.status)\n const phase = aiJobPhase(job)\n const primary = aiJobPrimaryLink(job, orgSlug)\n const highlighted = highlight === job.id\n return (\n <Box\n key={job.id}\n role=\"listitem\"\n data-ai-job-id={job.id}\n aria-current={highlighted ? 'true' : undefined}\n sx={{\n borderRadius: 1,\n bgcolor: highlighted ? 'action.selected' : 'action.hover',\n outline: 2,\n outlineColor: highlighted ? 'primary.main' : 'transparent',\n transition: (theme) =>\n theme.transitions.create(['background-color', 'outline-color'], {\n duration: theme.transitions.duration.standard,\n }),\n px: 1.5,\n py: 1,\n }}\n >\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Chip size=\"small\" variant=\"outlined\" label={job.kind} />\n <Chip size=\"small\" color={PHASE_COLOR[phase]} label={AI_JOB_PHASE_LABELS[phase]} />\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ flexGrow: 1 }}>\n {stepsDone}/{job.steps.length} steps\n {current ? ` — ${current.name}` : ''}\n {/* Net of what a failure on our side gave back (AGL-3594). */}\n {netCredits(job) > 0 ? ` · ${netCredits(job)} credits` : ''}\n </Typography>\n {!terminal && (\n <Button\n size=\"small\"\n disabled={Boolean(job.cancelRequested) || canceling.busyJobId === job.id}\n onClick={() => canceling.ask(job)}\n >\n {job.cancelRequested ? 'Stopping…' : 'Cancel'}\n </Button>\n )}\n </Stack>\n <Typography variant=\"body2\" sx={{ mt: 0.5, wordBreak: 'break-word' }}>\n {job.brief.length > BRIEF_PREVIEW_CHARS\n ? `${job.brief.slice(0, BRIEF_PREVIEW_CHARS)}…`\n : job.brief}\n </Typography>\n {job.error && (\n <Typography variant=\"caption\" color={job.status === 'failed' ? 'error' : 'warning.main'} component=\"div\">\n {job.error}\n </Typography>\n )}\n {/* What became of its credits, from the job's recorded give-back (AGL-3596). */}\n {aiJobRefundCopy(job) && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {aiJobRefundCopy(job)}\n </Typography>\n )}\n {/* A site that built part of itself says what, and that the rest was on us (AGL-3616). */}\n {aiSitePartialCopy(job) && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {aiSitePartialCopy(job)}\n </Typography>\n )}\n {/* A build, item by item: where each stands, and what became of its credits (AGL-3616). */}\n {job.items?.length ? (\n <Box component=\"ul\" aria-label=\"Items\" sx={{ m: 0, mt: 0.5, pl: 2 }}>\n {aiBuildItemRows(job).map((row) => (\n <Typography key={row.slot} component=\"li\" variant=\"caption\" data-item-state={row.state}>\n {`${row.label} — ${ITEM_STATE_LABELS[row.state]}`}\n {row.detail ? `. ${row.detail}` : ''}\n </Typography>\n ))}\n </Box>\n ) : null}\n {aiBuildCanRetry(job) ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={resuming === job.id}\n onClick={() => void resume(job, { retry: 'failed-items' })}\n sx={{ mt: 0.5 }}\n >\n {aiBuildRetryLabel(job)}\n </Button>\n ) : null}\n {retryPrompt?.jobId === job.id ? (\n <AiCreditsPromptNotice\n prompt={retryPrompt.prompt}\n noun=\"build\"\n orgSlug={orgSlug}\n busy={resuming === job.id}\n onBuildWhatFits={() => void resume(job, { retry: 'failed-items', creditsConfirmed: true })}\n />\n ) : null}\n {/* Paused by the meter (AGL-3722): Resume carries on from where it stopped. */}\n {job.status === 'needs_input' ? (\n <Stack direction=\"row\" spacing={1} sx={{ mt: 0.5 }}>\n <Button size=\"small\" variant=\"contained\" disabled={resuming === job.id} onClick={() => void resume(job)}>\n {'Resume'}\n </Button>\n <Button size=\"small\" component={AppLink} href={aiCreditsBillingHref(orgSlug)}>\n {'Upgrade'}\n </Button>\n </Stack>\n ) : null}\n {restartHref(job) ? (\n <Button size=\"small\" variant=\"contained\" component={AppLink} href={restartHref(job) as string} sx={{ mt: 0.5 }}>\n {'Try again'}\n </Button>\n ) : null}\n {/* What a finished job built, first (AGL-3593). */}\n {primary ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n component={AppLink}\n href={primary.href}\n sx={{ mt: 0.5 }}\n >\n {primary.label}\n </Button>\n ) : null}\n {job.outputs.map((output, index) => {\n const href = aiJobOutputHref(output, orgSlug)\n const navigation = aiJobNavigationProposal(output)\n return (\n <Box key={`${output.resource}:${output.id}:${index}`} sx={{ mt: 0.5 }}>\n {output.resource === 'insight' ? (\n <Button size=\"small\" onClick={() => setInsightOpen(output.id)}>\n View answer\n </Button>\n ) : href ? (\n <AppLink componentVariant=\"naked\" href={href}>\n Open draft — {output.label}\n </AppLink>\n ) : output.text ? (\n <Typography\n variant=\"body2\"\n component=\"pre\"\n sx={{ whiteSpace: 'pre-wrap', fontFamily: 'inherit', m: 0 }}\n >\n {output.text}\n </Typography>\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {output.label}\n </Typography>\n )}\n {output.load ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n About {formatBytes(output.load.totalBytes)} on a first visit\n </Typography>\n ) : null}\n {output.note ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {output.note}\n </Typography>\n ) : null}\n {navigation ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n Add “{navigation}” to your navigation once the page is live.\n </Typography>\n ) : null}\n </Box>\n )\n })}\n <AiJobPlan\n job={job}\n onResume={(target, options) => void resume(target, options)}\n busy={resuming === job.id}\n staff={isStaff}\n user={user}\n orgSlug={orgSlug}\n />\n </Box>\n )\n })}\n </Stack>\n </Collapse>\n {/*\n The brief dialog (AGL-2907). It starts a job and closes; the list\n below it is the only place the job is watched, so an open list reads\n the new job back as soon as the dialog hands it over.\n */}\n {insightOpen !== null && (\n <AiInsightDialog\n open\n onClose={() => {\n setInsightOpen(null)\n if (expanded) void load()\n }}\n orgId={orgId}\n orgSlug={orgSlug}\n hostId={insight?.host ? (hostId ?? null) : null}\n host={insight?.host ?? null}\n surface={insight?.surface ?? 'analytics'}\n user={user}\n uid={(user as { uid?: string } | null | undefined)?.uid ?? null}\n jobId={insightOpen === 'ask' ? null : insightOpen}\n />\n )}\n {hostId && (\n <AiPageBriefDialog\n open={describing}\n onClose={() => {\n setDescribing(false)\n if (expanded) void load()\n }}\n orgId={orgId}\n hostId={hostId}\n user={user}\n orgSlug={orgSlug}\n isStaff={isStaff}\n />\n )}\n </Box>\n )\n}\n\nexport default AssistJobsDrawer\n"],"names":["checkEntitlement","trackEvent","formatBytes","AI_JOB_TERMINAL_STATUSES","mdiChevronDown","mdiChevronUp","pluginDocsHelp","AppLink","HelpTip","MdiIcon","authorizedFetch","Box","Button","Chip","CircularProgress","Collapse","IconButton","Stack","Typography","useCallback","useEffect","useRef","useState","AI_JOB_PHASE_LABELS","aiJobActivityCounts","aiJobPhase","aiJobsHeaderChips","resumeAiJobRequest","useAiJobCancel","aiBuildCanRetry","aiBuildItemRows","aiBuildRetryCreditRange","aiSitePartialCopy","aiCreditRangeText","AiCreditsPromptNotice","publishAiJob","AiPageBriefDialog","AiInsightDialog","AiJobPlan","aiCreditsBillingHref","aiJobOutputHref","aiJobPrimaryLink","aiSiteBuildHref","aiJobRefundCopy","readEventFrames","aiBuildRetryLabel","job","range","LIST_LIMIT","BRIEF_PREVIEW_CHARS","PHASE_COLOR","queued","planning","building","attention","done","failed","canceled","STATE_COLOR","running","ITEM_STATE_LABELS","active","waiting","skipped","HIGHLIGHT_MS","aiJobRestartHref","input","kind","status","siteInputs","hostId","subdomain","exec","pathname","href","orgSlug","id","netCredits","Math","max","creditsSpent","refundedCredits","aiJobNavigationProposal","output","resource","navigation","proposal","label","trim","AssistJobsDrawer","orgId","org","orgReady","user","visible","isStaff","insight","inFlight","focus","entitled","expanded","setExpanded","toggledRef","highlight","setHighlight","listRef","jobs","setJobs","loading","setLoading","notice","setNotice","describing","setDescribing","insightOpen","setInsightOpen","trackedRef","Set","watchingRef","patchJob","next","prior","index","findIndex","slice","copy","includes","current","has","add","credits","load","response","encodeURIComponent","ok","payload","json","catch","String","error","anyInFlight","length","focusSeq","seq","focusJobId","jobId","expandedRef","highlightListed","some","row","undefined","Array","from","querySelectorAll","find","element","dataset","scrollIntoView","block","behavior","timer","setTimeout","clearTimeout","watched","watchedId","controller","AbortController","watch","again","signal","aborted","body","event","filter","finally","abort","canceling","onJob","resuming","setResuming","retryPrompt","setRetryPrompt","resume","options","retry","prompt","chips","sx","mb","borderBottom","borderColor","pb","direction","spacing","alignItems","variant","flexGrow","map","chip","size","color","state","ml","anchor","excerpt","onClick","aria-label","aria-expanded","path","fontSize","in","unmountOnExit","component","mt","dialog","ref","role","restartHref","window","location","stepsDone","steps","step","terminal","phase","primary","highlighted","data-ai-job-id","aria-current","borderRadius","bgcolor","outline","outlineColor","transition","theme","transitions","create","duration","standard","px","py","name","disabled","Boolean","cancelRequested","busyJobId","ask","wordBreak","brief","items","m","pl","data-item-state","detail","slot","noun","busy","onBuildWhatFits","creditsConfirmed","outputs","componentVariant","text","whiteSpace","fontFamily","totalBytes","note","onResume","target","staff","open","onClose","host","surface","uid"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/assist-jobs-drawer.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\nimport { checkEntitlement } from '@aglyn/aglyn'\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport { formatBytes } from '@aglyn/aglyn/app-utils/measure-node-map'\nimport {\n AI_JOB_TERMINAL_STATUSES,\n type AiJobOutput,\n type AiJobSummary,\n} from '../model/ai-jobs.types'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { mdiChevronDown, mdiChevronUp } from '@aglyn/shared-data-mdi'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { AppLink, HelpTip, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Box,\n Button,\n Chip,\n CircularProgress,\n Collapse,\n IconButton,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n AI_JOB_PHASE_LABELS,\n aiJobActivityCounts,\n aiJobPhase,\n aiJobsHeaderChips,\n type AiJobActivityState,\n type AiJobPhase,\n} from '../model/ai-job-activity'\nimport type { AiInsightSurface } from '../model/ai-insight'\nimport { resumeAiJobRequest, type AiJobResumeOptions } from './ai-job-requests'\nimport { useAiJobCancel } from './ai-job-cancel.component'\nimport { aiBuildCanRetry, aiBuildItemRows, aiBuildRetryCreditRange, aiSitePartialCopy } from '../model/ai-build-progress'\nimport { aiCreditRangeText, type AiCreditsPrompt } from '../model/ai-credit-estimate'\nimport { AiCreditsPromptNotice } from './ai-credits-prompt.component'\nimport { publishAiJob, type AiJobsOpenRequest } from './ai-jobs-store'\nimport { AiPageBriefDialog } from './ai-page-brief-dialog.component'\nimport { AiInsightDialog } from './ai-insight-dialog.component'\nimport { AiJobPlan } from './ai-job-plan.component'\nimport { aiCreditsBillingHref, aiJobOutputHref, aiJobPrimaryLink, aiSiteBuildHref } from './ai-job-links'\nimport { aiJobRefundCopy } from '../model/ai-job-failure-copy'\nimport {\n AI_SITE_REMOVED_ROW_COPY,\n AI_SITE_START_AGAIN_LABEL,\n aiCreateSiteHref,\n stashAiSiteStartAnswers,\n} from '../model/ai-site-start-handoff'\nimport { readEventFrames } from './ai-job-events'\n\n/** Try again, with what it is likely to cost (AGL-3722). */\nfunction aiBuildRetryLabel(job: AiJobSummary): string {\n const range = aiBuildRetryCreditRange(job)\n return range ? `Try again what failed · ${aiCreditRangeText(range)}` : 'Try again what failed'\n}\n\n// Where an output opens, and the stream reader, live beside the dialogs that\n// share them (AGL-3593); this module has always exported them.\nexport { aiJobOutputHref, aiJobPrimaryLink, readEventFrames }\n\n/**\n * The AI jobs drawer inside the Assist panel (AGL-2904): the workspace's\n * running and recent generation jobs, each step's progress, an \"open\n * draft\" link per output, and cancel.\n *\n * Reads through the routes, never Firestore directly, so the same\n * membership, flag and entitlement rungs answer every read; progress comes\n * from the events route's server-sent stream, re-opened when the server\n * says `reconnect`. Everything loads on EXPAND rather than on mount: the\n * panel opens on every console page, and a list nobody asked for is a read\n * on every page view.\n *\n * It opens EXPANDED while any of the workspace's jobs is moving or waiting\n * for the person (AGL-3593) — the shared list the top-bar indicator reads\n * says so — because a plan waiting behind a collapsed row is a site that\n * never gets built. An \"Open AI jobs\" anywhere in the console expands it,\n * scrolls to the job it names and highlights it for a moment.\n *\n * Hidden — not disabled — unless the flag shows it AND the plan carries\n * `aiGenerative`: a released-off feature does not exist, and a plan without\n * it is sold the add-on on the Billing page, not here.\n */\n\n/** Jobs the drawer lists; the route caps higher, the drawer is a glance. */\nconst LIST_LIMIT = 10\n\n/** How much of a brief a row shows. */\nconst BRIEF_PREVIEW_CHARS = 90\n\n/**\n * A row's chip says where the job stands in the words every AI surface uses\n * (AGL-3593): a plan waiting to be confirmed is \"Plan ready\", never \"Running\"\n * and never the machine's `needs_review`.\n */\nconst PHASE_COLOR: Record<AiJobPhase, 'default' | 'primary' | 'success' | 'error' | 'warning'> = {\n queued: 'default',\n planning: 'primary',\n 'plan-ready': 'warning',\n building: 'primary',\n attention: 'warning',\n done: 'success',\n failed: 'error',\n canceled: 'default',\n}\n\n/** The header's count chips, colored as the rows they count. */\nconst STATE_COLOR: Record<AiJobActivityState, 'primary' | 'warning'> = {\n 'needs-you': 'warning',\n attention: 'warning',\n running: 'primary',\n}\n\n/** What each item state reads as in a row (AGL-3616). */\nconst ITEM_STATE_LABELS = {\n done: 'built',\n active: 'building',\n waiting: 'waiting',\n failed: 'failed',\n skipped: 'not built',\n} as const\n\n/** How long a job AI jobs was opened on stays highlighted. */\nconst HIGHLIGHT_MS = 2_500\n\n/**\n * Where a guided start that ended unbuilt is tried again (AGL-3596): its\n * \"Building your site\" page, opened on its own answers (`?retry=1`). Only for\n * a job of the site the console is on, whose subdomain the address names;\n * `null` otherwise.\n */\nexport function aiJobRestartHref(\n job: Pick<AiJobSummary, 'id' | 'kind' | 'status' | 'hostId' | 'siteInputs'>,\n input: { orgSlug: string; hostId?: string | null; pathname: string },\n): string | null {\n if (job.kind !== 'site' || (job.status !== 'failed' && job.status !== 'canceled')) return null\n if (!job.siteInputs || !job.hostId || job.hostId !== input.hostId) return null\n const subdomain = /^\\/[^/]+\\/hosts\\/([^/?#]+)/.exec(input.pathname)?.[1] ?? null\n const href = aiSiteBuildHref(input.orgSlug, subdomain, job.id)\n return href ? `${href}?retry=1` : null\n}\n\n/** What a job cost the person: what it spent, less what we refunded. */\nfunction netCredits(job: Pick<AiJobSummary, 'creditsSpent' | 'refundedCredits'>): number {\n return Math.max(0, (job.creditsSpent ?? 0) - (job.refundedCredits ?? 0))\n}\n\n/**\n * The navigation entry a page job proposes for the page it built (AGL-2907),\n * or `null`. The job writes no menu: the member adds the entry once the page\n * is live.\n */\nexport function aiJobNavigationProposal(output: AiJobOutput): string | null {\n if (output.resource !== 'screen') return null\n const navigation = output.proposal?.['navigation']\n if (!navigation || typeof navigation !== 'object') return null\n const label = (navigation as Record<string, unknown>)['label']\n return typeof label === 'string' && label.trim() ? label.trim() : null\n}\n\n\nexport interface AssistJobsDrawerProps {\n /** The org the panel is scoped to — `undefined` where the page named none. */\n orgId: string | undefined\n org: Partial<AglynOrgBilling> | undefined\n orgReady: boolean\n orgSlug: string\n user: Parameters<typeof authorizedFetch>[0]\n /** The `release_ai_generative` verdict, staff bypass applied (the shell's). */\n visible: boolean\n /**\n * The viewer is staff (AGL-3078): a job that stopped for a broken rule shows\n * them where the refused answer broke it. Who may read a job is unchanged.\n */\n isStaff?: boolean\n /** The site the console page is on, when it is on one: a page is described for a site. */\n hostId?: string | null\n /**\n * The page's insight surface (AGL-2915) — a site's Analytics, Data or CRM\n * Reports page, or the workspace's Data page — with the site's subdomain;\n * `null` elsewhere, where no question about the figures is offered.\n */\n insight?: { surface: Exclude<AiInsightSurface, 'digest'>; host: string | null } | null\n /**\n * The workspace's jobs that are not yet settled, from the shared store the\n * top-bar indicator reads (AGL-3593). The header counts them, and the\n * drawer opens expanded while there is any. Absent, the drawer counts what\n * it loaded itself.\n */\n inFlight?: readonly AiJobSummary[]\n /**\n * The latest request to open AI jobs (AGL-3593): a new `seq` expands the\n * drawer, and a named job is scrolled to and highlighted.\n */\n focus?: AiJobsOpenRequest | null\n}\n\nexport function AssistJobsDrawer({\n orgId,\n org,\n orgReady,\n orgSlug,\n user,\n visible,\n isStaff = false,\n hostId,\n insight,\n inFlight,\n focus,\n}: AssistJobsDrawerProps): JSX.Element | null {\n const entitled = orgReady && checkEntitlement(org as never, 'aiGenerative')\n const [expanded, setExpanded] = useState(false)\n /**\n * The person opened or closed the list themselves: from then on the\n * expanded-by-default rule leaves their choice alone until the workspace\n * changes.\n */\n const toggledRef = useRef(false)\n /** The job AI jobs was opened on, highlighted for a moment once it is listed. */\n const [highlight, setHighlight] = useState<string | null>(null)\n const listRef = useRef<HTMLDivElement | null>(null)\n const [jobs, setJobs] = useState<AiJobSummary[]>([])\n const [loading, setLoading] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n /** Whether the page brief dialog is open (AGL-2907). */\n const [describing, setDescribing] = useState(false)\n /**\n * The insight dialog (AGL-2915): `'ask'` for a new question, a job id for an\n * answer a job already wrote, `null` when closed.\n */\n const [insightOpen, setInsightOpen] = useState<string | null>(null)\n /** Job ids whose terminal event has been tracked, so a re-read does not count twice. */\n const trackedRef = useRef(new Set<string>())\n /** The job whose stream is open, so a re-render does not open a second. */\n const watchingRef = useRef<string | null>(null)\n\n const patchJob = useCallback((next: AiJobSummary) => {\n // Every counting surface moves with the row (AGL-3593).\n publishAiJob(next)\n setJobs((prior) => {\n const index = prior.findIndex((job) => job.id === next.id)\n if (index === -1) return [next, ...prior].slice(0, LIST_LIMIT)\n const copy = [...prior]\n copy[index] = next\n return copy\n })\n if (\n AI_JOB_TERMINAL_STATUSES.includes(next.status) &&\n !trackedRef.current.has(next.id)\n ) {\n trackedRef.current.add(next.id)\n if (next.status === 'done') {\n trackEvent('ai_job_completed', { kind: next.kind, credits: next.creditsSpent })\n } else if (next.status === 'failed') {\n trackEvent('ai_job_failed', { kind: next.kind })\n }\n }\n }, [])\n\n const load = useCallback(async () => {\n if (!orgId) return\n setLoading(true)\n setNotice(null)\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=${LIST_LIMIT}`,\n )\n if (!response.ok) {\n const payload = await response.json().catch(() => null)\n setNotice(String(payload?.error ?? 'The AI jobs could not be loaded — try again.'))\n return\n }\n const payload = (await response.json()) as { jobs: AiJobSummary[] }\n // Every job that arrives already terminal is one the drawer did not\n // watch finish, so its outcome is not this session's to count.\n for (const job of payload.jobs) {\n if (AI_JOB_TERMINAL_STATUSES.includes(job.status)) trackedRef.current.add(job.id)\n // A job the indicator still counts that has settled since leaves it.\n publishAiJob(job)\n }\n setJobs(payload.jobs)\n } catch {\n setNotice('The AI jobs could not be loaded — try again.')\n } finally {\n setLoading(false)\n }\n }, [orgId, user])\n\n useEffect(() => {\n if (expanded) void load()\n }, [expanded, load])\n\n // Reset when the panel's org changes: a list from one workspace must not\n // show under another's key while the new one loads.\n useEffect(() => {\n setJobs([])\n setExpanded(false)\n toggledRef.current = false\n watchingRef.current = null\n }, [orgId])\n\n // Expanded by default while anything is moving or waiting (AGL-3593): a\n // plan waiting to be confirmed behind a collapsed row is a site that never\n // gets built. Otherwise collapsed, which reads nothing until asked.\n const anyInFlight = (inFlight?.length ?? 0) > 0\n useEffect(() => {\n if (anyInFlight && !toggledRef.current) setExpanded(true)\n }, [anyInFlight])\n\n // A request to open AI jobs (AGL-3593): expand, read the list again so a\n // job created a moment ago is in it, and highlight the job named.\n const focusSeq = focus?.seq ?? 0\n const focusJobId = focus?.jobId ?? null\n const expandedRef = useRef(expanded)\n expandedRef.current = expanded\n useEffect(() => {\n if (!focusSeq) return\n toggledRef.current = true\n setHighlight(focusJobId)\n // An open list does not re-run the expand effect's read, so it reads here.\n if (expandedRef.current) void load()\n else setExpanded(true)\n // Only a new request runs this; `load` and the job are read as they stand.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [focusSeq])\n\n // Scroll to the highlighted job once it is listed, then let it fade.\n const highlightListed = highlight !== null && jobs.some((job) => job.id === highlight)\n useEffect(() => {\n if (!highlight || !highlightListed) return undefined\n const row = Array.from(\n listRef.current?.querySelectorAll<HTMLElement>('[data-ai-job-id]') ?? [],\n ).find((element) => element.dataset['aiJobId'] === highlight)\n row?.scrollIntoView?.({ block: 'nearest', behavior: 'smooth' })\n const timer = setTimeout(() => setHighlight(null), HIGHLIGHT_MS)\n return () => clearTimeout(timer)\n }, [highlight, highlightListed])\n\n // Watch the most recent job that is still moving. One stream at a time:\n // the route re-reads a document every two seconds per open stream, and a\n // drawer with three running jobs does not need three of them.\n const watched = jobs.find((job) => !AI_JOB_TERMINAL_STATUSES.includes(job.status))\n const watchedId = watched?.id ?? null\n useEffect(() => {\n if (!expanded || !orgId || !watchedId || watchingRef.current === watchedId) return\n watchingRef.current = watchedId\n const controller = new AbortController()\n const watch = async () => {\n // The server closes a stream at its deadline with `reconnect`; the\n // loop here re-opens it until the job settles or the drawer unmounts.\n let again = true\n while (again && !controller.signal.aborted) {\n again = false\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs/${encodeURIComponent(watchedId)}/events?orgId=${encodeURIComponent(orgId)}`,\n { signal: controller.signal },\n )\n if (!response.ok || !response.body) return\n await readEventFrames(response.body, (event) => {\n if (event['type'] === 'state') patchJob(event['job'] as AiJobSummary)\n else if (event['type'] === 'reconnect') again = true\n else if (event['type'] === 'gone') {\n setJobs((prior) => prior.filter((job) => job.id !== watchedId))\n }\n })\n } catch {\n // The stream ended without a terminal state — a network blip or\n // the drawer closing. The next expand reloads the list.\n return\n }\n }\n }\n void watch().finally(() => {\n if (watchingRef.current === watchedId) watchingRef.current = null\n })\n return () => {\n controller.abort()\n if (watchingRef.current === watchedId) watchingRef.current = null\n }\n }, [expanded, orgId, watchedId, user, patchJob])\n\n // The same door the dialog that started the job cancels through (AGL-3593),\n // behind the one confirm every Cancel shows (AGL-3616).\n const canceling = useAiJobCancel({ user, orgId, onJob: patchJob })\n\n /** The job whose resume is in flight, so its button cannot send twice. */\n const [resuming, setResuming] = useState<string | null>(null)\n // A Free Try again past what is left (AGL-3722): its prompt, by job, until chosen.\n const [retryPrompt, setRetryPrompt] = useState<{ jobId: string; prompt: AiCreditsPrompt } | null>(null)\n\n // Confirms a plan, or tries again a step whose answer broke a building\n // rule (AGL-2935). The door runs the next step inline and answers with the\n // job, so the row moves on without waiting for the stream.\n const resume = useCallback(\n async (job: AiJobSummary, options?: AiJobResumeOptions) => {\n if (!orgId) return\n setResuming(job.id)\n setNotice(null)\n setRetryPrompt(null)\n // The same door the dialog that started the job confirms through (AGL-3593).\n const { job: next, error, credits } = await resumeAiJobRequest(user, orgId, job, options)\n if (next) patchJob(next)\n // A Free job past what is left asks with its choices (AGL-3722): a\n // retry here, a plan on its own card from the job the door answered with.\n if (credits && options?.retry) setRetryPrompt({ jobId: job.id, prompt: credits })\n else if (error && !(credits && job.status === 'needs_review')) setNotice(error)\n setResuming(null)\n },\n [orgId, user, patchJob],\n )\n\n if (!visible || !orgId || !entitled) return null\n\n // Counted by state, never as one \"running\" figure (AGL-3593): a plan waiting\n // to be confirmed needs the person, and a stopped job is not moving.\n const chips = aiJobsHeaderChips(aiJobActivityCounts(inFlight ?? jobs))\n\n return (\n <Box sx={{ mb: 2, borderBottom: 1, borderColor: 'divider', pb: 1 }}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n AI jobs\n {chips.map((chip) => (\n <Chip\n key={chip.state}\n size=\"small\"\n color={STATE_COLOR[chip.state]}\n label={chip.label}\n sx={{ ml: 1 }}\n />\n ))}\n </Typography>\n <HelpTip\n {...pluginDocsHelp('aiJobs', {\n anchor: '#ai-jobs-in-assist',\n excerpt:\n 'Every AI job in this workspace, newest first: where it stands, the credits it used, and its plan to confirm, cancel or try again.',\n })}\n />\n {/* A question about the page's figures (AGL-2915). */}\n {insight && (\n <Button size=\"small\" onClick={() => setInsightOpen('ask')}>\n Ask about your numbers\n </Button>\n )}\n {/* A page from a brief (AGL-2907), on a site's own routes. */}\n {hostId && (\n <Button size=\"small\" onClick={() => setDescribing(true)}>\n Describe a page\n </Button>\n )}\n <IconButton\n size=\"small\"\n aria-label={expanded ? 'Hide AI jobs' : 'Show AI jobs'}\n aria-expanded={expanded}\n onClick={() => {\n toggledRef.current = true\n setExpanded((prior) => !prior)\n }}\n >\n <MdiIcon\n path={expanded ? mdiChevronUp.path : mdiChevronDown.path}\n sx={{ fontSize: 18 }}\n />\n </IconButton>\n </Stack>\n <Collapse in={expanded} unmountOnExit>\n {notice && (\n <Typography variant=\"caption\" color=\"error\" component=\"div\" sx={{ mt: 0.5 }}>\n {notice}\n </Typography>\n )}\n {loading && !jobs.length && (\n <CircularProgress size={16} sx={{ mt: 1 }} aria-label=\"Loading AI jobs\" />\n )}\n {!loading && !jobs.length && !notice && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\" sx={{ mt: 0.5 }}>\n No AI jobs yet.\n </Typography>\n )}\n {canceling.dialog}\n <Stack ref={listRef} spacing={1} sx={{ mt: 1 }} role=\"list\" aria-label=\"AI jobs\">\n {jobs.map((job) => {\n const restartHref = (row: AiJobSummary) =>\n aiJobRestartHref(row, {\n orgSlug,\n hostId,\n pathname: typeof window === 'undefined' ? '' : window.location.pathname,\n })\n const stepsDone = job.steps.filter((step) => step.status === 'done').length\n const current = job.steps.find((step) => step.status === 'running')\n const terminal = AI_JOB_TERMINAL_STATUSES.includes(job.status)\n const phase = aiJobPhase(job)\n const primary = aiJobPrimaryLink(job, orgSlug)\n const highlighted = highlight === job.id\n return (\n <Box\n key={job.id}\n role=\"listitem\"\n data-ai-job-id={job.id}\n aria-current={highlighted ? 'true' : undefined}\n sx={{\n borderRadius: 1,\n bgcolor: highlighted ? 'action.selected' : 'action.hover',\n outline: 2,\n outlineColor: highlighted ? 'primary.main' : 'transparent',\n transition: (theme) =>\n theme.transitions.create(['background-color', 'outline-color'], {\n duration: theme.transitions.duration.standard,\n }),\n px: 1.5,\n py: 1,\n }}\n >\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Chip size=\"small\" variant=\"outlined\" label={job.kind} />\n <Chip size=\"small\" color={PHASE_COLOR[phase]} label={AI_JOB_PHASE_LABELS[phase]} />\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ flexGrow: 1 }}>\n {stepsDone}/{job.steps.length} steps\n {current ? ` — ${current.name}` : ''}\n {/* Net of what a failure on our side gave back (AGL-3594). */}\n {netCredits(job) > 0 ? ` · ${netCredits(job)} credits` : ''}\n </Typography>\n {!terminal && (\n <Button\n size=\"small\"\n disabled={Boolean(job.cancelRequested) || canceling.busyJobId === job.id}\n onClick={() => canceling.ask(job)}\n >\n {job.cancelRequested ? 'Stopping…' : 'Cancel'}\n </Button>\n )}\n </Stack>\n <Typography variant=\"body2\" sx={{ mt: 0.5, wordBreak: 'break-word' }}>\n {job.brief.length > BRIEF_PREVIEW_CHARS\n ? `${job.brief.slice(0, BRIEF_PREVIEW_CHARS)}…`\n : job.brief}\n </Typography>\n {job.error && (\n <Typography variant=\"caption\" color={job.status === 'failed' ? 'error' : 'warning.main'} component=\"div\">\n {job.error}\n </Typography>\n )}\n {/* What became of its credits, from the job's recorded give-back (AGL-3596). */}\n {aiJobRefundCopy(job) && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {aiJobRefundCopy(job)}\n </Typography>\n )}\n {/* A site that built part of itself says what, and that the rest was on us (AGL-3616). */}\n {aiSitePartialCopy(job) && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {aiSitePartialCopy(job)}\n </Typography>\n )}\n {/* A build, item by item: where each stands, and what became of its credits (AGL-3616). */}\n {job.items?.length ? (\n <Box component=\"ul\" aria-label=\"Items\" sx={{ m: 0, mt: 0.5, pl: 2 }}>\n {aiBuildItemRows(job).map((row) => (\n <Typography key={row.slot} component=\"li\" variant=\"caption\" data-item-state={row.state}>\n {`${row.label} — ${ITEM_STATE_LABELS[row.state]}`}\n {row.detail ? `. ${row.detail}` : ''}\n </Typography>\n ))}\n </Box>\n ) : null}\n {aiBuildCanRetry(job) ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={resuming === job.id}\n onClick={() => void resume(job, { retry: 'failed-items' })}\n sx={{ mt: 0.5 }}\n >\n {aiBuildRetryLabel(job)}\n </Button>\n ) : null}\n {retryPrompt?.jobId === job.id ? (\n <AiCreditsPromptNotice\n prompt={retryPrompt.prompt}\n noun=\"build\"\n orgSlug={orgSlug}\n busy={resuming === job.id}\n onBuildWhatFits={() => void resume(job, { retry: 'failed-items', creditsConfirmed: true })}\n />\n ) : null}\n {/* Paused by the meter (AGL-3722): Resume carries on from where it stopped. */}\n {job.status === 'needs_input' ? (\n <Stack direction=\"row\" spacing={1} sx={{ mt: 0.5 }}>\n <Button size=\"small\" variant=\"contained\" disabled={resuming === job.id} onClick={() => void resume(job)}>\n {'Resume'}\n </Button>\n <Button size=\"small\" component={AppLink} href={aiCreditsBillingHref(orgSlug)}>\n {'Upgrade'}\n </Button>\n </Stack>\n ) : null}\n {job.siteRemoved ? (\n // Its empty site was removed (AGL-3936): Create site, with the same answers.\n <>\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {AI_SITE_REMOVED_ROW_COPY}\n </Typography>\n {aiCreateSiteHref(orgSlug) && job.siteInputs ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n component={AppLink}\n href={aiCreateSiteHref(orgSlug) as string}\n onClick={() => stashAiSiteStartAnswers(job.siteInputs)}\n sx={{ mt: 0.5 }}\n >\n {AI_SITE_START_AGAIN_LABEL}\n </Button>\n ) : null}\n </>\n ) : restartHref(job) ? (\n <Button size=\"small\" variant=\"contained\" component={AppLink} href={restartHref(job) as string} sx={{ mt: 0.5 }}>\n {'Try again'}\n </Button>\n ) : null}\n {/* What a finished job built, first (AGL-3593). */}\n {primary ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n component={AppLink}\n href={primary.href}\n sx={{ mt: 0.5 }}\n >\n {primary.label}\n </Button>\n ) : null}\n {job.outputs.map((output, index) => {\n const href = aiJobOutputHref(output, orgSlug)\n const navigation = aiJobNavigationProposal(output)\n return (\n <Box key={`${output.resource}:${output.id}:${index}`} sx={{ mt: 0.5 }}>\n {output.resource === 'insight' ? (\n <Button size=\"small\" onClick={() => setInsightOpen(output.id)}>\n View answer\n </Button>\n ) : href ? (\n <AppLink componentVariant=\"naked\" href={href}>\n Open draft — {output.label}\n </AppLink>\n ) : output.text ? (\n <Typography\n variant=\"body2\"\n component=\"pre\"\n sx={{ whiteSpace: 'pre-wrap', fontFamily: 'inherit', m: 0 }}\n >\n {output.text}\n </Typography>\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {output.label}\n </Typography>\n )}\n {output.load ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n About {formatBytes(output.load.totalBytes)} on a first visit\n </Typography>\n ) : null}\n {output.note ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {output.note}\n </Typography>\n ) : null}\n {navigation ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n Add “{navigation}” to your navigation once the page is live.\n </Typography>\n ) : null}\n </Box>\n )\n })}\n <AiJobPlan\n job={job}\n onResume={(target, options) => void resume(target, options)}\n busy={resuming === job.id}\n staff={isStaff}\n user={user}\n orgSlug={orgSlug}\n />\n </Box>\n )\n })}\n </Stack>\n </Collapse>\n {/*\n The brief dialog (AGL-2907). It starts a job and closes; the list\n below it is the only place the job is watched, so an open list reads\n the new job back as soon as the dialog hands it over.\n */}\n {insightOpen !== null && (\n <AiInsightDialog\n open\n onClose={() => {\n setInsightOpen(null)\n if (expanded) void load()\n }}\n orgId={orgId}\n orgSlug={orgSlug}\n hostId={insight?.host ? (hostId ?? null) : null}\n host={insight?.host ?? null}\n surface={insight?.surface ?? 'analytics'}\n user={user}\n uid={(user as { uid?: string } | null | undefined)?.uid ?? null}\n jobId={insightOpen === 'ask' ? null : insightOpen}\n />\n )}\n {hostId && (\n <AiPageBriefDialog\n open={describing}\n onClose={() => {\n setDescribing(false)\n if (expanded) void load()\n }}\n orgId={orgId}\n hostId={hostId}\n user={user}\n orgSlug={orgSlug}\n isStaff={isStaff}\n />\n )}\n </Box>\n )\n}\n\nexport default AssistJobsDrawer\n"],"names":["checkEntitlement","trackEvent","formatBytes","AI_JOB_TERMINAL_STATUSES","mdiChevronDown","mdiChevronUp","pluginDocsHelp","AppLink","HelpTip","MdiIcon","authorizedFetch","Box","Button","Chip","CircularProgress","Collapse","IconButton","Stack","Typography","useCallback","useEffect","useRef","useState","AI_JOB_PHASE_LABELS","aiJobActivityCounts","aiJobPhase","aiJobsHeaderChips","resumeAiJobRequest","useAiJobCancel","aiBuildCanRetry","aiBuildItemRows","aiBuildRetryCreditRange","aiSitePartialCopy","aiCreditRangeText","AiCreditsPromptNotice","publishAiJob","AiPageBriefDialog","AiInsightDialog","AiJobPlan","aiCreditsBillingHref","aiJobOutputHref","aiJobPrimaryLink","aiSiteBuildHref","aiJobRefundCopy","AI_SITE_REMOVED_ROW_COPY","AI_SITE_START_AGAIN_LABEL","aiCreateSiteHref","stashAiSiteStartAnswers","readEventFrames","aiBuildRetryLabel","job","range","LIST_LIMIT","BRIEF_PREVIEW_CHARS","PHASE_COLOR","queued","planning","building","attention","done","failed","canceled","STATE_COLOR","running","ITEM_STATE_LABELS","active","waiting","skipped","HIGHLIGHT_MS","aiJobRestartHref","input","kind","status","siteInputs","hostId","subdomain","exec","pathname","href","orgSlug","id","netCredits","Math","max","creditsSpent","refundedCredits","aiJobNavigationProposal","output","resource","navigation","proposal","label","trim","AssistJobsDrawer","orgId","org","orgReady","user","visible","isStaff","insight","inFlight","focus","entitled","expanded","setExpanded","toggledRef","highlight","setHighlight","listRef","jobs","setJobs","loading","setLoading","notice","setNotice","describing","setDescribing","insightOpen","setInsightOpen","trackedRef","Set","watchingRef","patchJob","next","prior","index","findIndex","slice","copy","includes","current","has","add","credits","load","response","encodeURIComponent","ok","payload","json","catch","String","error","anyInFlight","length","focusSeq","seq","focusJobId","jobId","expandedRef","highlightListed","some","row","undefined","Array","from","querySelectorAll","find","element","dataset","scrollIntoView","block","behavior","timer","setTimeout","clearTimeout","watched","watchedId","controller","AbortController","watch","again","signal","aborted","body","event","filter","finally","abort","canceling","onJob","resuming","setResuming","retryPrompt","setRetryPrompt","resume","options","retry","prompt","chips","sx","mb","borderBottom","borderColor","pb","direction","spacing","alignItems","variant","flexGrow","map","chip","size","color","state","ml","anchor","excerpt","onClick","aria-label","aria-expanded","path","fontSize","in","unmountOnExit","component","mt","dialog","ref","role","restartHref","window","location","stepsDone","steps","step","terminal","phase","primary","highlighted","data-ai-job-id","aria-current","borderRadius","bgcolor","outline","outlineColor","transition","theme","transitions","create","duration","standard","px","py","name","disabled","Boolean","cancelRequested","busyJobId","ask","wordBreak","brief","items","m","pl","data-item-state","detail","slot","noun","busy","onBuildWhatFits","creditsConfirmed","siteRemoved","outputs","componentVariant","text","whiteSpace","fontFamily","totalBytes","note","onResume","target","staff","open","onClose","host","surface","uid"],"mappings":"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"}
|
|
@@ -126,6 +126,8 @@ export interface AiInsightDigestDeps {
|
|
|
126
126
|
/** The site the digest is about, and the org whose brand it wears (AGL-3367). */
|
|
127
127
|
siteName: string;
|
|
128
128
|
org: Record<string, unknown>;
|
|
129
|
+
/** The designed email's merge values, from {@link aiInsightDigestEmailParts}. */
|
|
130
|
+
merge?: Record<string, string>;
|
|
129
131
|
}) => Promise<AiInsightDigestSendResult>;
|
|
130
132
|
/** The console's absolute origin for an email link, or `''` when none is configured. */
|
|
131
133
|
consoleOrigin: string;
|
|
@@ -167,6 +169,13 @@ export declare function aiInsightDigestEmailText(input: {
|
|
|
167
169
|
/** The week the figures cover, "Oct 5 – Oct 11"; absent says "this week". */
|
|
168
170
|
periodLabel?: string;
|
|
169
171
|
}): string;
|
|
172
|
+
/**
|
|
173
|
+
* The same digest as {@link aiInsightDigestEmailText}, cut into the parts the
|
|
174
|
+
* `ai-insights-digest` system email draws: the workspace and week above the
|
|
175
|
+
* heading, the insights as a numbered list, a button to the site's
|
|
176
|
+
* Analytics page, and why it came.
|
|
177
|
+
*/
|
|
178
|
+
export declare function aiInsightDigestEmailParts(input: Parameters<typeof aiInsightDigestEmailText>[0]): Record<'digest.eyebrow' | 'digest.intro' | 'digest.insights' | 'digest.analyticsUrl' | 'digest.why', string>;
|
|
170
179
|
/**
|
|
171
180
|
* One call of the sweep: delivery for this week and the last on the first
|
|
172
181
|
* call, then — on a Monday — one chunk of workspaces' creation, resumed by
|
|
@@ -66,6 +66,28 @@ const str = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
|
66
66
|
input.settingsUrl ? `You asked for weekly insights for ${forWhom}. Turn them off in Notifications: ${input.settingsUrl}` : `You asked for weekly insights for ${forWhom}. Turn them off in your Notifications settings.`
|
|
67
67
|
].join('\n');
|
|
68
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* The same digest as {@link aiInsightDigestEmailText}, cut into the parts the
|
|
71
|
+
* `ai-insights-digest` system email draws: the workspace and week above the
|
|
72
|
+
* heading, the insights as a numbered list, a button to the site's
|
|
73
|
+
* Analytics page, and why it came.
|
|
74
|
+
*/ export function aiInsightDigestEmailParts(input) {
|
|
75
|
+
var _input_analyticsUrl;
|
|
76
|
+
var _input_workspaceName, _input_periodLabel;
|
|
77
|
+
const workspace = ((_input_workspaceName = input.workspaceName) == null ? void 0 : _input_workspaceName.trim()) || '';
|
|
78
|
+
const credits = Math.max(0, Math.round(Number(input.creditsSpent) || 0));
|
|
79
|
+
const forWhom = workspace || 'this workspace';
|
|
80
|
+
return {
|
|
81
|
+
'digest.eyebrow': [
|
|
82
|
+
workspace,
|
|
83
|
+
(_input_periodLabel = input.periodLabel) == null ? void 0 : _input_periodLabel.trim()
|
|
84
|
+
].filter(Boolean).join(' · '),
|
|
85
|
+
'digest.intro': `What ${input.siteName}'s figures showed ${input.periodLabel ? 'last week' : 'this week'}. ` + 'Each one is traced to the figures it cites, so you can check it on the Analytics page.',
|
|
86
|
+
'digest.insights': input.insights.map((insight, index)=>`${index + 1}. ${insight.text}`).join('\n\n'),
|
|
87
|
+
'digest.analyticsUrl': (_input_analyticsUrl = input.analyticsUrl) != null ? _input_analyticsUrl : '',
|
|
88
|
+
'digest.why': `You asked for weekly insights for ${forWhom}.` + (credits ? ` Writing these used ${credits.toLocaleString('en-US')} of ${workspace ? `${workspace}’s` : 'the workspace’s'} AI credits.` : '') + (input.settingsUrl ? ` Turn them off in Notifications: ${input.settingsUrl}` : ' Turn them off in your Notifications settings.')
|
|
89
|
+
};
|
|
90
|
+
}
|
|
69
91
|
async function readUsers(firestore, uids) {
|
|
70
92
|
if (!uids.length) return new Map();
|
|
71
93
|
const docs = await firestore.getAll(...uids.map((uid)=>firestore.collection('users').doc(uid)));
|
|
@@ -232,22 +254,24 @@ async function readUsers(firestore, uids) {
|
|
|
232
254
|
}
|
|
233
255
|
const address = str((_member_email = member.email) != null ? _member_email : user == null ? void 0 : user.get('email')).toLowerCase();
|
|
234
256
|
if (address.includes('@')) {
|
|
257
|
+
const emailInput = {
|
|
258
|
+
siteName: site.name,
|
|
259
|
+
productName: branding.productName,
|
|
260
|
+
insights: record.insights,
|
|
261
|
+
analyticsUrl,
|
|
262
|
+
settingsUrl: origin ? `${origin}/manage/notifications` : null,
|
|
263
|
+
workspaceName: str(org['name']),
|
|
264
|
+
creditsSpent: job.creditsSpent,
|
|
265
|
+
periodLabel: aiInsightDigestPeriod(job.inputs, new Date(Number(marker.createdAtMs) || now.getTime())).label
|
|
266
|
+
};
|
|
235
267
|
const sent = await deps.send({
|
|
236
268
|
to: address,
|
|
237
269
|
subject: `Your weekly insights for ${site.name}`,
|
|
238
|
-
text: aiInsightDigestEmailText(
|
|
239
|
-
siteName: site.name,
|
|
240
|
-
productName: branding.productName,
|
|
241
|
-
insights: record.insights,
|
|
242
|
-
analyticsUrl,
|
|
243
|
-
settingsUrl: origin ? `${origin}/manage/notifications` : null,
|
|
244
|
-
workspaceName: str(org['name']),
|
|
245
|
-
creditsSpent: job.creditsSpent,
|
|
246
|
-
periodLabel: aiInsightDigestPeriod(job.inputs, new Date(Number(marker.createdAtMs) || now.getTime())).label
|
|
247
|
-
}),
|
|
270
|
+
text: aiInsightDigestEmailText(emailInput),
|
|
248
271
|
fromName: branding.fromName,
|
|
249
272
|
siteName: site.name,
|
|
250
|
-
org
|
|
273
|
+
org,
|
|
274
|
+
merge: aiInsightDigestEmailParts(emailInput)
|
|
251
275
|
});
|
|
252
276
|
if (sent.rateLimited) {
|
|
253
277
|
// Nothing is marked, so the next run delivers this site whole.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/insights/ai-insight-digest.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 { FieldPath } from 'firebase-admin/firestore'\nimport { INSIGHT_DIGESTS_FIELD, insightDigestSubscribed, notificationMuted } from '@aglyn/aglyn/app-utils/notifications'\nimport { hostRoleFor } from '@aglyn/aglyn/app-utils/organizations'\nimport { checkEntitlement, resolveBrandingProfile, resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { isReleaseFlagOnForOrg, parseOrgReleaseFlagOverrides, type ReleaseFlagValue } from '@aglyn/aglyn/app-utils/release-flags'\nimport type { AglynOrgMember } from '@aglyn/aglyn/foundation/definitions/organization.types'\nimport { weeklySummaryPeriod } from '@aglyn/aglyn/app-utils/weekly-summary'\nimport {\n aiInsightDigestPeriod,\n AI_INSIGHTS_COLLECTION,\n aiInsightIsoWeek,\n type AiInsightRecord,\n} from '../model/ai-insight'\nimport { isAiOnForSite } from '../model/ai-site-switch'\nimport { cancelAiJob, createAiJob, getAiJob } from '../jobs/ai-jobs'\n\n/**\n * THE WEEKLY INSIGHTS (AGL-2915): `POST /api/admin/ai-insights-digest`, twice\n * a day on the console's scheduler.\n *\n * ## Who asked for it\n *\n * A person turns weekly insights on for a workspace beside the answers\n * themselves (`users/{uid}.insightDigests.{orgId}`), and off there or in\n * Notifications. It is opt-in, because each digest is a generation the\n * workspace pays for. At the sweep, every rung is asked again rather than\n * trusted from the switch: the workspace's plan includes `aiGenerative`,\n * `release_ai_generative` is on for it, its AI is not paused, the site has AI\n * on, and the person still holds `ai.generate` on the site.\n *\n * ## Monday: one job per site\n *\n * On a Monday run, for each workspace with someone who asked, the sites those\n * people reach — the busiest `AI_INSIGHT_DIGEST_MAX_SITES` by yesterday's page\n * views — each get one `insight` job with the `digest` surface, created by the\n * first of them who may generate there. The job runs on the AI jobs beat like\n * any other: it reserves, meters and records its credits, and a workspace at\n * its band — a Free one that has used its credits — has its job parked by the\n * reservation like any other. `orgs/{orgId}/aiInsightDigests/{week}` records\n * the jobs; `aiInsightDigestQueue/{week}` lists the workspaces still owed a\n * delivery, so a run that delivers reads one document rather than every\n * workspace.\n *\n * ## Every run: deliver what is written\n *\n * A digest whose job is done and kept at least one insight is delivered once:\n * a `content.insightsDigest` notification (unless the person muted the\n * category) and an email through the platform's own sending path, to each\n * person who asked for the workspace's insights and reaches the site. A job\n * that failed, kept nothing, or parked for credits is SKIPPED SILENTLY: a\n * parked job is canceled rather than left to spend later, and nobody is told,\n * because nobody asked about that week in particular. A job still running\n * past `AI_INSIGHT_DIGEST_DEADLINE_MS` is canceled and skipped the same way.\n *\n * The email says only what the answer says: the insights, each traced to the\n * figures it cites, and a link to the site's Analytics page. It is sent by the\n * platform to a person who asked for it, never to a contact.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport const AI_INSIGHTS_DIGEST_CRON_ID = 'ai-insights-digest'\nexport const AI_INSIGHTS_DIGEST_PATH = 'admin/ai-insights-digest'\n\n/** Orgs one call reads; a chunk that times out never advances the cursor, so the safe size is one that finishes. */\nexport const AI_INSIGHT_DIGEST_ORG_CHUNK = 25\n\n/** Sites one workspace's digest covers a week. */\nexport const AI_INSIGHT_DIGEST_MAX_SITES = 5\n\n/** Sites a workspace's sweep reads before it ranks them. */\nexport const AI_INSIGHT_DIGEST_HOST_READ_LIMIT = 50\n\n/** Workspaces one run delivers to, per week. */\nexport const AI_INSIGHT_DIGEST_DELIVER_LIMIT = 100\n\n/** How long a digest's job may take before its week is given up. */\nexport const AI_INSIGHT_DIGEST_DEADLINE_MS = 48 * 60 * 60_000\n\n/**\n * The time one call works before it stops and leaves the rest to the next:\n * inside the plugin dispatcher's 60 s function ceiling, with room for the\n * reads before the first check and the reply after the last. A call killed\n * mid-send could send one person a site's digest twice, because a send is\n * stamped after it goes.\n */\nexport const AI_INSIGHT_DIGEST_RUN_BUDGET_MS = 45_000\n\nexport const AI_INSIGHT_DIGEST_MARKERS = 'aiInsightDigests'\nexport const AI_INSIGHT_DIGEST_QUEUE = 'aiInsightDigestQueue'\n\nexport type AiInsightDigestSiteState = 'pending' | 'delivered' | 'skipped'\n\nexport interface AiInsightDigestSite {\n jobId: string\n name: string\n state: AiInsightDigestSiteState\n /**\n * The people already sent this site's digest, so a run the send rate\n * stopped never sends one twice. Dropped when the site settles, so a\n * settled marker names no person.\n */\n sent?: Record<string, boolean>\n}\n\nexport interface AiInsightDigestMarker {\n week: string\n createdAtMs: number\n sites: Record<string, AiInsightDigestSite>\n}\n\n/** What a send answered: whether it went, and whether the platform's send rate refused it for now. */\nexport interface AiInsightDigestSendResult {\n sent: boolean\n rateLimited: boolean\n}\n\n/** Everything the sweep reaches beyond Firestore, so a spec can hold each one. */\nexport interface AiInsightDigestDeps {\n firestore: Firestore\n now: Date\n flagValue: ReleaseFlagValue\n listMembers: (orgId: string) => Promise<AglynOrgMember[]>\n mayGenerate: (orgId: string, hostId: string, member: AglynOrgMember) => Promise<boolean>\n paused: (orgId: string) => Promise<boolean>\n notify: (uid: string, payload: { title: string; body: string; link: string; orgId: string; hostId: string }) => Promise<void>\n send: (email: {\n to: string\n subject: string\n text: string\n fromName: string\n /** The site the digest is about, and the org whose brand it wears (AGL-3367). */\n siteName: string\n org: Record<string, unknown>\n }) => Promise<AiInsightDigestSendResult>\n /** The console's absolute origin for an email link, or `''` when none is configured. */\n consoleOrigin: string\n /** The call's working time; `AI_INSIGHT_DIGEST_RUN_BUDGET_MS` unless a spec holds it. */\n budgetMs?: number\n}\n\nexport interface AiInsightDigestReport {\n week: string\n swept: number\n created: number\n delivered: number\n skipped: number\n /** The send rate refused or the call's time ran out; the run stops, and the next one delivers the rest. */\n deferred: boolean\n nextCursor: string | null\n done: boolean\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** The week a Monday digest covers: the week the run falls in. */\nexport function aiInsightDigestWeek(now: Date): string {\n return aiInsightIsoWeek(now)\n}\n\n/** The week before, whose late digests a run still delivers. */\nfunction previousWeek(now: Date): string {\n return aiInsightIsoWeek(new Date(now.getTime() - 7 * 86_400_000))\n}\n\n/**\n * A digest email's words: the insights, the site's Analytics page, and where\n * to turn it off. The digest is an AI job metered against the workspace, so\n * it says what it drew, and it names the workspace a member of several asked\n * for it in.\n */\nexport function aiInsightDigestEmailText(input: {\n siteName: string\n productName: string\n insights: ReadonlyArray<{ text: string }>\n /** Absolute links, or `null` where no console origin is configured to build one from. */\n analyticsUrl: string | null\n settingsUrl: string | null\n /** The workspace the digest was asked for in; `''` falls back to \"this workspace\". */\n workspaceName?: string\n /** The AI credits the digest's job drew; 0 or absent says nothing. */\n creditsSpent?: number\n /** The week the figures cover, \"Oct 5 – Oct 11\"; absent says \"this week\". */\n periodLabel?: string\n}): string {\n const workspace = input.workspaceName?.trim() || ''\n const credits = Math.max(0, Math.round(Number(input.creditsSpent) || 0))\n const drew = credits\n ? ` Writing it used ${credits.toLocaleString('en-US')} of ` +\n `${workspace ? `${workspace}’s` : 'the workspace’s'} AI credits.`\n : ''\n const forWhom = workspace || 'this workspace'\n return [\n input.periodLabel\n ? `Here is what ${input.siteName}'s figures showed last week, ${input.periodLabel}.${drew}`\n : `Here is what ${input.siteName}'s figures showed this week.${drew}`,\n '',\n ...input.insights.map((insight, index) => `${index + 1}. ${insight.text}`),\n '',\n input.analyticsUrl\n ? `Each of these is traced to the figures it cites. See them in ${input.productName}: ${input.analyticsUrl}`\n : `Each of these is traced to the figures it cites. See them on the site's Analytics page in ${input.productName}.`,\n '',\n input.settingsUrl\n ? `You asked for weekly insights for ${forWhom}. Turn them off in Notifications: ${input.settingsUrl}`\n : `You asked for weekly insights for ${forWhom}. Turn them off in your Notifications settings.`,\n ].join('\\n')\n}\n\nasync function readUsers(firestore: Firestore, uids: readonly string[]) {\n if (!uids.length) return new Map<string, FirebaseFirestore.DocumentSnapshot>()\n const docs = await firestore.getAll(...uids.map((uid) => firestore.collection('users').doc(uid)))\n return new Map(docs.map((doc) => [doc.id, doc]))\n}\n\n/** Creates one workspace's digests for the week; answers how many jobs it created. */\nasync function createOrgDigest(\n deps: AiInsightDigestDeps,\n orgDoc: FirebaseFirestore.QueryDocumentSnapshot,\n week: string,\n): Promise<number> {\n const { firestore, now } = deps\n const orgId = orgDoc.id\n const org = (orgDoc.data() ?? {}) as Record<string, unknown>\n if (!checkEntitlement(org as never, 'aiGenerative')) return 0\n const flagOn = isReleaseFlagOnForOrg(\n 'release_ai_generative',\n deps.flagValue,\n orgId,\n parseOrgReleaseFlagOverrides(org['releaseFlags']),\n resolveEffectivePlan(org as never),\n )\n if (!flagOn || (await deps.paused(orgId))) return 0\n const markerRef = orgDoc.ref.collection(AI_INSIGHT_DIGEST_MARKERS).doc(week)\n if ((await markerRef.get()).exists) return 0\n\n const members = (await deps.listMembers(orgId)).filter((member) => member.orgSuspended !== true)\n const users = await readUsers(firestore, members.map((member) => member.$id).sort())\n const subscribers = members\n .filter((member) => insightDigestSubscribed(users.get(member.$id)?.get(INSIGHT_DIGESTS_FIELD), orgId))\n .sort((a, b) => a.$id.localeCompare(b.$id))\n if (!subscribers.length) return 0\n\n const hosts = await firestore\n .collection('hosts')\n .where('orgId', '==', orgId)\n .limit(AI_INSIGHT_DIGEST_HOST_READ_LIMIT)\n .get()\n const yesterday = new Date(now.getTime() - 86_400_000).toISOString().slice(0, 10)\n const candidates: Array<{ hostId: string; name: string; creator: string; views: number }> = []\n for (const host of hosts.docs) {\n const data = host.data() ?? {}\n if (!isAiOnForSite(org, data)) continue\n let creator: string | null = null\n for (const member of subscribers) {\n if (hostRoleFor(member, host.id) !== null && (await deps.mayGenerate(orgId, host.id, member))) {\n creator = member.$id\n break\n }\n }\n if (!creator) continue\n const day = await host.ref.collection('analytics').doc(yesterday).get()\n candidates.push({\n hostId: host.id,\n name: str(data['displayName']) || str(data['name']) || str(data['subdomain']) || host.id,\n creator,\n views: Number(day.get('total') ?? 0) || 0,\n })\n }\n const chosen = candidates\n .sort((a, b) => b.views - a.views || a.hostId.localeCompare(b.hostId))\n .slice(0, AI_INSIGHT_DIGEST_MAX_SITES)\n if (!chosen.length) return 0\n\n const sites: Record<string, AiInsightDigestSite> = {}\n for (const site of chosen) {\n const job = await createAiJob(\n firestore,\n {\n orgId,\n hostId: site.hostId,\n kind: 'insight',\n brief: `Weekly insights for ${site.name}`,\n inputs: { surface: 'digest', week, through: weeklySummaryPeriod(now).to },\n createdBy: site.creator,\n },\n now,\n )\n sites[site.hostId] = { jobId: job.$id, name: site.name, state: 'pending' }\n }\n const marker: AiInsightDigestMarker = { week, createdAtMs: now.getTime(), sites }\n await markerRef.set(marker)\n await firestore\n .collection(AI_INSIGHT_DIGEST_QUEUE)\n .doc(week)\n .set({ week, pending: { [orgId]: true } }, { merge: true })\n return chosen.length\n}\n\n/** Delivers or settles one workspace's digests for a week; `deferred` when the send rate refused. */\nasync function deliverOrgDigest(\n deps: AiInsightDigestDeps,\n orgId: string,\n week: string,\n overBudget: () => boolean,\n): Promise<{ delivered: number; skipped: number; settled: boolean; deferred: boolean }> {\n const { firestore, now } = deps\n const orgRef = firestore.collection('orgs').doc(orgId)\n const markerRef = orgRef.collection(AI_INSIGHT_DIGEST_MARKERS).doc(week)\n const markerSnapshot = await markerRef.get()\n const result = { delivered: 0, skipped: 0, settled: true, deferred: false }\n if (!markerSnapshot.exists) return result\n const marker = markerSnapshot.data() as AiInsightDigestMarker\n const org = ((await orgRef.get()).data() ?? {}) as Record<string, unknown>\n let members: AglynOrgMember[] | null = null\n const branding = resolveBrandingProfile(org as never)\n\n for (const [hostId, site] of Object.entries(marker.sites ?? {})) {\n if (site.state !== 'pending') continue\n const settle = async (state: AiInsightDigestSiteState) => {\n // Replaced whole rather than merged, which drops `sent`.\n const settled: AiInsightDigestSite = { jobId: site.jobId, name: site.name, state }\n await markerRef.update({ [`sites.${hostId}`]: settled })\n if (state === 'delivered') result.delivered += 1\n else result.skipped += 1\n }\n const job = await getAiJob(firestore, orgId, site.jobId)\n if (!job || job.status === 'failed' || job.status === 'canceled') {\n await settle('skipped')\n continue\n }\n if (job.status !== 'done') {\n const late = now.getTime() - Number(marker.createdAtMs ?? 0) > AI_INSIGHT_DIGEST_DEADLINE_MS\n // A job parked for credits — a Free workspace at its band — is given up\n // rather than left to spend on a week that is already over.\n if (job.status === 'needs_input' || late) {\n await cancelAiJob(firestore, orgId, site.jobId, now)\n await settle('skipped')\n } else {\n result.settled = false\n }\n continue\n }\n const answer = await orgRef.collection(AI_INSIGHTS_COLLECTION).doc(site.jobId).get()\n const record = answer.exists ? (answer.data() as AiInsightRecord) : null\n if (!record?.insights?.length) {\n await settle('skipped')\n continue\n }\n\n members ??= (await deps.listMembers(orgId)).filter((member) => member.orgSuspended !== true)\n const reaching = members.filter((member) => hostRoleFor(member, hostId) !== null)\n const users = await readUsers(firestore, reaching.map((member) => member.$id).sort())\n const host = (await firestore.collection('hosts').doc(hostId).get()).data() ?? {}\n const orgSlug = str(org['slug'])\n const subdomain = str(host['subdomain'])\n const origin = deps.consoleOrigin\n const analyticsUrl = origin && orgSlug && subdomain ? `${origin}/${orgSlug}/hosts/${subdomain}/analytics` : null\n for (const member of reaching.sort((a, b) => a.$id.localeCompare(b.$id))) {\n const user = users.get(member.$id)\n if (!insightDigestSubscribed(user?.get(INSIGHT_DIGESTS_FIELD), orgId)) continue\n if (site.sent?.[member.$id]) continue\n if (overBudget()) {\n // Out of time: what was sent is stamped, and the next run sends the rest.\n result.deferred = true\n result.settled = false\n return result\n }\n const address = str(member.email ?? user?.get('email')).toLowerCase()\n if (address.includes('@')) {\n const sent = await deps.send({\n to: address,\n subject: `Your weekly insights for ${site.name}`,\n text: aiInsightDigestEmailText({\n siteName: site.name,\n productName: branding.productName,\n insights: record.insights,\n analyticsUrl,\n settingsUrl: origin ? `${origin}/manage/notifications` : null,\n workspaceName: str(org['name']),\n creditsSpent: job.creditsSpent,\n periodLabel: aiInsightDigestPeriod(job.inputs, new Date(Number(marker.createdAtMs) || now.getTime())).label,\n }),\n fromName: branding.fromName,\n siteName: site.name,\n org,\n })\n if (sent.rateLimited) {\n // Nothing is marked, so the next run delivers this site whole.\n result.deferred = true\n result.settled = false\n return result\n }\n }\n if (!notificationMuted(user?.get('notificationPrefs'), 'content.insightsDigest')) {\n await deps.notify(member.$id, {\n title: `Weekly insights · ${site.name}`,\n body: record.insights[0].text,\n link: `/${hostId}/analytics`,\n orgId,\n hostId,\n })\n }\n await markerRef.set({ sites: { [hostId]: { sent: { [member.$id]: true } } } }, { merge: true })\n }\n await settle('delivered')\n }\n return result\n}\n\n/**\n * One call of the sweep: delivery for this week and the last on the first\n * call, then — on a Monday — one chunk of workspaces' creation, resumed by\n * `cursor`. The response shape is the console crons' (`nextCursor`, `done`),\n * so the scheduler's loop carries a platform of any size across calls.\n */\nexport async function runAiInsightDigestSweep(\n deps: AiInsightDigestDeps,\n options: { cursor: string | null },\n): Promise<AiInsightDigestReport> {\n const { firestore, now } = deps\n const startedAtMs = Date.now()\n const budgetMs = deps.budgetMs ?? AI_INSIGHT_DIGEST_RUN_BUDGET_MS\n const overBudget = () => Date.now() - startedAtMs > budgetMs\n const week = aiInsightDigestWeek(now)\n const report: AiInsightDigestReport = {\n week,\n swept: 0,\n created: 0,\n delivered: 0,\n skipped: 0,\n deferred: false,\n nextCursor: null,\n done: true,\n }\n\n if (!options.cursor) {\n for (const owed of [previousWeek(now), week]) {\n const queueRef = firestore.collection(AI_INSIGHT_DIGEST_QUEUE).doc(owed)\n const pending = Object.entries(((await queueRef.get()).get('pending') ?? {}) as Record<string, unknown>)\n .filter(([, owed]) => owed === true)\n .map(([orgId]) => orgId)\n .sort()\n .slice(0, AI_INSIGHT_DIGEST_DELIVER_LIMIT)\n for (const orgId of pending) {\n if (overBudget()) {\n report.deferred = true\n return report\n }\n try {\n const delivered = await deliverOrgDigest(deps, orgId, owed, overBudget)\n report.delivered += delivered.delivered\n report.skipped += delivered.skipped\n if (delivered.settled) {\n await queueRef.set({ pending: { [orgId]: false } }, { merge: true })\n }\n if (delivered.deferred) {\n report.deferred = true\n return report\n }\n } catch (error) {\n console.error('ai insight digest delivery failed', { orgId, week: owed, error })\n }\n }\n }\n }\n\n // Monday is the week's first day by ISO 8601, and the day a digest is made.\n if (now.getUTCDay() !== 1) return report\n\n let query = firestore.collection('orgs').orderBy(FieldPath.documentId()).limit(AI_INSIGHT_DIGEST_ORG_CHUNK + 1)\n if (options.cursor) query = query.startAfter(firestore.collection('orgs').doc(options.cursor))\n const page = await query.get()\n const orgs = page.docs.slice(0, AI_INSIGHT_DIGEST_ORG_CHUNK)\n for (const [index, orgDoc] of orgs.entries()) {\n if (overBudget()) {\n // Out of time: the next call resumes after the last workspace read. A\n // call that read none has no cursor to hand on, and the next scheduled\n // run starts the week's workspaces again, which the markers make safe.\n report.nextCursor = index ? orgs[index - 1].id : (options.cursor ?? null)\n report.done = report.nextCursor === null\n return report\n }\n report.swept += 1\n try {\n report.created += await createOrgDigest(deps, orgDoc, week)\n } catch (error) {\n console.error('ai insight digest creation failed', { orgId: orgDoc.id, week, error })\n }\n }\n const more = page.docs.length > AI_INSIGHT_DIGEST_ORG_CHUNK\n report.nextCursor = more ? (orgs[orgs.length - 1]?.id ?? null) : null\n report.done = !more\n return report\n}\n"],"names":["FieldPath","INSIGHT_DIGESTS_FIELD","insightDigestSubscribed","notificationMuted","hostRoleFor","checkEntitlement","resolveBrandingProfile","resolveEffectivePlan","isReleaseFlagOnForOrg","parseOrgReleaseFlagOverrides","weeklySummaryPeriod","aiInsightDigestPeriod","AI_INSIGHTS_COLLECTION","aiInsightIsoWeek","isAiOnForSite","cancelAiJob","createAiJob","getAiJob","AI_INSIGHTS_DIGEST_CRON_ID","AI_INSIGHTS_DIGEST_PATH","AI_INSIGHT_DIGEST_ORG_CHUNK","AI_INSIGHT_DIGEST_MAX_SITES","AI_INSIGHT_DIGEST_HOST_READ_LIMIT","AI_INSIGHT_DIGEST_DELIVER_LIMIT","AI_INSIGHT_DIGEST_DEADLINE_MS","AI_INSIGHT_DIGEST_RUN_BUDGET_MS","AI_INSIGHT_DIGEST_MARKERS","AI_INSIGHT_DIGEST_QUEUE","str","value","trim","aiInsightDigestWeek","now","previousWeek","Date","getTime","aiInsightDigestEmailText","input","workspace","workspaceName","credits","Math","max","round","Number","creditsSpent","drew","toLocaleString","forWhom","periodLabel","siteName","insights","map","insight","index","text","analyticsUrl","productName","settingsUrl","join","readUsers","firestore","uids","length","Map","docs","getAll","uid","collection","doc","id","createOrgDigest","deps","orgDoc","week","orgId","org","data","flagOn","flagValue","paused","markerRef","ref","get","exists","members","listMembers","filter","member","orgSuspended","users","$id","sort","subscribers","a","b","localeCompare","hosts","where","limit","yesterday","toISOString","slice","candidates","host","day","creator","mayGenerate","push","hostId","name","views","chosen","sites","site","job","kind","brief","inputs","surface","through","to","createdBy","jobId","state","marker","createdAtMs","set","pending","merge","deliverOrgDigest","overBudget","orgRef","markerSnapshot","result","delivered","skipped","settled","deferred","branding","Object","entries","record","settle","update","status","late","answer","reaching","orgSlug","subdomain","origin","consoleOrigin","user","sent","address","email","toLowerCase","includes","send","subject","label","fromName","rateLimited","notify","title","body","link","runAiInsightDigestSweep","options","orgs","startedAtMs","budgetMs","report","swept","created","nextCursor","done","cursor","owed","queueRef","error","console","getUTCDay","query","orderBy","documentId","startAfter","page","more"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/insights/ai-insight-digest.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 { FieldPath } from 'firebase-admin/firestore'\nimport { INSIGHT_DIGESTS_FIELD, insightDigestSubscribed, notificationMuted } from '@aglyn/aglyn/app-utils/notifications'\nimport { hostRoleFor } from '@aglyn/aglyn/app-utils/organizations'\nimport { checkEntitlement, resolveBrandingProfile, resolveEffectivePlan } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { isReleaseFlagOnForOrg, parseOrgReleaseFlagOverrides, type ReleaseFlagValue } from '@aglyn/aglyn/app-utils/release-flags'\nimport type { AglynOrgMember } from '@aglyn/aglyn/foundation/definitions/organization.types'\nimport { weeklySummaryPeriod } from '@aglyn/aglyn/app-utils/weekly-summary'\nimport {\n aiInsightDigestPeriod,\n AI_INSIGHTS_COLLECTION,\n aiInsightIsoWeek,\n type AiInsightRecord,\n} from '../model/ai-insight'\nimport { isAiOnForSite } from '../model/ai-site-switch'\nimport { cancelAiJob, createAiJob, getAiJob } from '../jobs/ai-jobs'\n\n/**\n * THE WEEKLY INSIGHTS (AGL-2915): `POST /api/admin/ai-insights-digest`, twice\n * a day on the console's scheduler.\n *\n * ## Who asked for it\n *\n * A person turns weekly insights on for a workspace beside the answers\n * themselves (`users/{uid}.insightDigests.{orgId}`), and off there or in\n * Notifications. It is opt-in, because each digest is a generation the\n * workspace pays for. At the sweep, every rung is asked again rather than\n * trusted from the switch: the workspace's plan includes `aiGenerative`,\n * `release_ai_generative` is on for it, its AI is not paused, the site has AI\n * on, and the person still holds `ai.generate` on the site.\n *\n * ## Monday: one job per site\n *\n * On a Monday run, for each workspace with someone who asked, the sites those\n * people reach — the busiest `AI_INSIGHT_DIGEST_MAX_SITES` by yesterday's page\n * views — each get one `insight` job with the `digest` surface, created by the\n * first of them who may generate there. The job runs on the AI jobs beat like\n * any other: it reserves, meters and records its credits, and a workspace at\n * its band — a Free one that has used its credits — has its job parked by the\n * reservation like any other. `orgs/{orgId}/aiInsightDigests/{week}` records\n * the jobs; `aiInsightDigestQueue/{week}` lists the workspaces still owed a\n * delivery, so a run that delivers reads one document rather than every\n * workspace.\n *\n * ## Every run: deliver what is written\n *\n * A digest whose job is done and kept at least one insight is delivered once:\n * a `content.insightsDigest` notification (unless the person muted the\n * category) and an email through the platform's own sending path, to each\n * person who asked for the workspace's insights and reaches the site. A job\n * that failed, kept nothing, or parked for credits is SKIPPED SILENTLY: a\n * parked job is canceled rather than left to spend later, and nobody is told,\n * because nobody asked about that week in particular. A job still running\n * past `AI_INSIGHT_DIGEST_DEADLINE_MS` is canceled and skipped the same way.\n *\n * The email says only what the answer says: the insights, each traced to the\n * figures it cites, and a link to the site's Analytics page. It is sent by the\n * platform to a person who asked for it, never to a contact.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\nexport const AI_INSIGHTS_DIGEST_CRON_ID = 'ai-insights-digest'\nexport const AI_INSIGHTS_DIGEST_PATH = 'admin/ai-insights-digest'\n\n/** Orgs one call reads; a chunk that times out never advances the cursor, so the safe size is one that finishes. */\nexport const AI_INSIGHT_DIGEST_ORG_CHUNK = 25\n\n/** Sites one workspace's digest covers a week. */\nexport const AI_INSIGHT_DIGEST_MAX_SITES = 5\n\n/** Sites a workspace's sweep reads before it ranks them. */\nexport const AI_INSIGHT_DIGEST_HOST_READ_LIMIT = 50\n\n/** Workspaces one run delivers to, per week. */\nexport const AI_INSIGHT_DIGEST_DELIVER_LIMIT = 100\n\n/** How long a digest's job may take before its week is given up. */\nexport const AI_INSIGHT_DIGEST_DEADLINE_MS = 48 * 60 * 60_000\n\n/**\n * The time one call works before it stops and leaves the rest to the next:\n * inside the plugin dispatcher's 60 s function ceiling, with room for the\n * reads before the first check and the reply after the last. A call killed\n * mid-send could send one person a site's digest twice, because a send is\n * stamped after it goes.\n */\nexport const AI_INSIGHT_DIGEST_RUN_BUDGET_MS = 45_000\n\nexport const AI_INSIGHT_DIGEST_MARKERS = 'aiInsightDigests'\nexport const AI_INSIGHT_DIGEST_QUEUE = 'aiInsightDigestQueue'\n\nexport type AiInsightDigestSiteState = 'pending' | 'delivered' | 'skipped'\n\nexport interface AiInsightDigestSite {\n jobId: string\n name: string\n state: AiInsightDigestSiteState\n /**\n * The people already sent this site's digest, so a run the send rate\n * stopped never sends one twice. Dropped when the site settles, so a\n * settled marker names no person.\n */\n sent?: Record<string, boolean>\n}\n\nexport interface AiInsightDigestMarker {\n week: string\n createdAtMs: number\n sites: Record<string, AiInsightDigestSite>\n}\n\n/** What a send answered: whether it went, and whether the platform's send rate refused it for now. */\nexport interface AiInsightDigestSendResult {\n sent: boolean\n rateLimited: boolean\n}\n\n/** Everything the sweep reaches beyond Firestore, so a spec can hold each one. */\nexport interface AiInsightDigestDeps {\n firestore: Firestore\n now: Date\n flagValue: ReleaseFlagValue\n listMembers: (orgId: string) => Promise<AglynOrgMember[]>\n mayGenerate: (orgId: string, hostId: string, member: AglynOrgMember) => Promise<boolean>\n paused: (orgId: string) => Promise<boolean>\n notify: (uid: string, payload: { title: string; body: string; link: string; orgId: string; hostId: string }) => Promise<void>\n send: (email: {\n to: string\n subject: string\n text: string\n fromName: string\n /** The site the digest is about, and the org whose brand it wears (AGL-3367). */\n siteName: string\n org: Record<string, unknown>\n /** The designed email's merge values, from {@link aiInsightDigestEmailParts}. */\n merge?: Record<string, string>\n }) => Promise<AiInsightDigestSendResult>\n /** The console's absolute origin for an email link, or `''` when none is configured. */\n consoleOrigin: string\n /** The call's working time; `AI_INSIGHT_DIGEST_RUN_BUDGET_MS` unless a spec holds it. */\n budgetMs?: number\n}\n\nexport interface AiInsightDigestReport {\n week: string\n swept: number\n created: number\n delivered: number\n skipped: number\n /** The send rate refused or the call's time ran out; the run stops, and the next one delivers the rest. */\n deferred: boolean\n nextCursor: string | null\n done: boolean\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** The week a Monday digest covers: the week the run falls in. */\nexport function aiInsightDigestWeek(now: Date): string {\n return aiInsightIsoWeek(now)\n}\n\n/** The week before, whose late digests a run still delivers. */\nfunction previousWeek(now: Date): string {\n return aiInsightIsoWeek(new Date(now.getTime() - 7 * 86_400_000))\n}\n\n/**\n * A digest email's words: the insights, the site's Analytics page, and where\n * to turn it off. The digest is an AI job metered against the workspace, so\n * it says what it drew, and it names the workspace a member of several asked\n * for it in.\n */\nexport function aiInsightDigestEmailText(input: {\n siteName: string\n productName: string\n insights: ReadonlyArray<{ text: string }>\n /** Absolute links, or `null` where no console origin is configured to build one from. */\n analyticsUrl: string | null\n settingsUrl: string | null\n /** The workspace the digest was asked for in; `''` falls back to \"this workspace\". */\n workspaceName?: string\n /** The AI credits the digest's job drew; 0 or absent says nothing. */\n creditsSpent?: number\n /** The week the figures cover, \"Oct 5 – Oct 11\"; absent says \"this week\". */\n periodLabel?: string\n}): string {\n const workspace = input.workspaceName?.trim() || ''\n const credits = Math.max(0, Math.round(Number(input.creditsSpent) || 0))\n const drew = credits\n ? ` Writing it used ${credits.toLocaleString('en-US')} of ` +\n `${workspace ? `${workspace}’s` : 'the workspace’s'} AI credits.`\n : ''\n const forWhom = workspace || 'this workspace'\n return [\n input.periodLabel\n ? `Here is what ${input.siteName}'s figures showed last week, ${input.periodLabel}.${drew}`\n : `Here is what ${input.siteName}'s figures showed this week.${drew}`,\n '',\n ...input.insights.map((insight, index) => `${index + 1}. ${insight.text}`),\n '',\n input.analyticsUrl\n ? `Each of these is traced to the figures it cites. See them in ${input.productName}: ${input.analyticsUrl}`\n : `Each of these is traced to the figures it cites. See them on the site's Analytics page in ${input.productName}.`,\n '',\n input.settingsUrl\n ? `You asked for weekly insights for ${forWhom}. Turn them off in Notifications: ${input.settingsUrl}`\n : `You asked for weekly insights for ${forWhom}. Turn them off in your Notifications settings.`,\n ].join('\\n')\n}\n\n/**\n * The same digest as {@link aiInsightDigestEmailText}, cut into the parts the\n * `ai-insights-digest` system email draws: the workspace and week above the\n * heading, the insights as a numbered list, a button to the site's\n * Analytics page, and why it came.\n */\nexport function aiInsightDigestEmailParts(\n input: Parameters<typeof aiInsightDigestEmailText>[0],\n): Record<\n 'digest.eyebrow' | 'digest.intro' | 'digest.insights' | 'digest.analyticsUrl' | 'digest.why',\n string\n> {\n const workspace = input.workspaceName?.trim() || ''\n const credits = Math.max(0, Math.round(Number(input.creditsSpent) || 0))\n const forWhom = workspace || 'this workspace'\n return {\n 'digest.eyebrow': [workspace, input.periodLabel?.trim()].filter(Boolean).join(' · '),\n 'digest.intro':\n `What ${input.siteName}'s figures showed ${input.periodLabel ? 'last week' : 'this week'}. ` +\n 'Each one is traced to the figures it cites, so you can check it on the Analytics page.',\n 'digest.insights': input.insights.map((insight, index) => `${index + 1}. ${insight.text}`).join('\\n\\n'),\n 'digest.analyticsUrl': input.analyticsUrl ?? '',\n 'digest.why':\n `You asked for weekly insights for ${forWhom}.` +\n (credits\n ? ` Writing these used ${credits.toLocaleString('en-US')} of ${workspace ? `${workspace}’s` : 'the workspace’s'} AI credits.`\n : '') +\n (input.settingsUrl\n ? ` Turn them off in Notifications: ${input.settingsUrl}`\n : ' Turn them off in your Notifications settings.'),\n }\n}\n\nasync function readUsers(firestore: Firestore, uids: readonly string[]) {\n if (!uids.length) return new Map<string, FirebaseFirestore.DocumentSnapshot>()\n const docs = await firestore.getAll(...uids.map((uid) => firestore.collection('users').doc(uid)))\n return new Map(docs.map((doc) => [doc.id, doc]))\n}\n\n/** Creates one workspace's digests for the week; answers how many jobs it created. */\nasync function createOrgDigest(\n deps: AiInsightDigestDeps,\n orgDoc: FirebaseFirestore.QueryDocumentSnapshot,\n week: string,\n): Promise<number> {\n const { firestore, now } = deps\n const orgId = orgDoc.id\n const org = (orgDoc.data() ?? {}) as Record<string, unknown>\n if (!checkEntitlement(org as never, 'aiGenerative')) return 0\n const flagOn = isReleaseFlagOnForOrg(\n 'release_ai_generative',\n deps.flagValue,\n orgId,\n parseOrgReleaseFlagOverrides(org['releaseFlags']),\n resolveEffectivePlan(org as never),\n )\n if (!flagOn || (await deps.paused(orgId))) return 0\n const markerRef = orgDoc.ref.collection(AI_INSIGHT_DIGEST_MARKERS).doc(week)\n if ((await markerRef.get()).exists) return 0\n\n const members = (await deps.listMembers(orgId)).filter((member) => member.orgSuspended !== true)\n const users = await readUsers(firestore, members.map((member) => member.$id).sort())\n const subscribers = members\n .filter((member) => insightDigestSubscribed(users.get(member.$id)?.get(INSIGHT_DIGESTS_FIELD), orgId))\n .sort((a, b) => a.$id.localeCompare(b.$id))\n if (!subscribers.length) return 0\n\n const hosts = await firestore\n .collection('hosts')\n .where('orgId', '==', orgId)\n .limit(AI_INSIGHT_DIGEST_HOST_READ_LIMIT)\n .get()\n const yesterday = new Date(now.getTime() - 86_400_000).toISOString().slice(0, 10)\n const candidates: Array<{ hostId: string; name: string; creator: string; views: number }> = []\n for (const host of hosts.docs) {\n const data = host.data() ?? {}\n if (!isAiOnForSite(org, data)) continue\n let creator: string | null = null\n for (const member of subscribers) {\n if (hostRoleFor(member, host.id) !== null && (await deps.mayGenerate(orgId, host.id, member))) {\n creator = member.$id\n break\n }\n }\n if (!creator) continue\n const day = await host.ref.collection('analytics').doc(yesterday).get()\n candidates.push({\n hostId: host.id,\n name: str(data['displayName']) || str(data['name']) || str(data['subdomain']) || host.id,\n creator,\n views: Number(day.get('total') ?? 0) || 0,\n })\n }\n const chosen = candidates\n .sort((a, b) => b.views - a.views || a.hostId.localeCompare(b.hostId))\n .slice(0, AI_INSIGHT_DIGEST_MAX_SITES)\n if (!chosen.length) return 0\n\n const sites: Record<string, AiInsightDigestSite> = {}\n for (const site of chosen) {\n const job = await createAiJob(\n firestore,\n {\n orgId,\n hostId: site.hostId,\n kind: 'insight',\n brief: `Weekly insights for ${site.name}`,\n inputs: { surface: 'digest', week, through: weeklySummaryPeriod(now).to },\n createdBy: site.creator,\n },\n now,\n )\n sites[site.hostId] = { jobId: job.$id, name: site.name, state: 'pending' }\n }\n const marker: AiInsightDigestMarker = { week, createdAtMs: now.getTime(), sites }\n await markerRef.set(marker)\n await firestore\n .collection(AI_INSIGHT_DIGEST_QUEUE)\n .doc(week)\n .set({ week, pending: { [orgId]: true } }, { merge: true })\n return chosen.length\n}\n\n/** Delivers or settles one workspace's digests for a week; `deferred` when the send rate refused. */\nasync function deliverOrgDigest(\n deps: AiInsightDigestDeps,\n orgId: string,\n week: string,\n overBudget: () => boolean,\n): Promise<{ delivered: number; skipped: number; settled: boolean; deferred: boolean }> {\n const { firestore, now } = deps\n const orgRef = firestore.collection('orgs').doc(orgId)\n const markerRef = orgRef.collection(AI_INSIGHT_DIGEST_MARKERS).doc(week)\n const markerSnapshot = await markerRef.get()\n const result = { delivered: 0, skipped: 0, settled: true, deferred: false }\n if (!markerSnapshot.exists) return result\n const marker = markerSnapshot.data() as AiInsightDigestMarker\n const org = ((await orgRef.get()).data() ?? {}) as Record<string, unknown>\n let members: AglynOrgMember[] | null = null\n const branding = resolveBrandingProfile(org as never)\n\n for (const [hostId, site] of Object.entries(marker.sites ?? {})) {\n if (site.state !== 'pending') continue\n const settle = async (state: AiInsightDigestSiteState) => {\n // Replaced whole rather than merged, which drops `sent`.\n const settled: AiInsightDigestSite = { jobId: site.jobId, name: site.name, state }\n await markerRef.update({ [`sites.${hostId}`]: settled })\n if (state === 'delivered') result.delivered += 1\n else result.skipped += 1\n }\n const job = await getAiJob(firestore, orgId, site.jobId)\n if (!job || job.status === 'failed' || job.status === 'canceled') {\n await settle('skipped')\n continue\n }\n if (job.status !== 'done') {\n const late = now.getTime() - Number(marker.createdAtMs ?? 0) > AI_INSIGHT_DIGEST_DEADLINE_MS\n // A job parked for credits — a Free workspace at its band — is given up\n // rather than left to spend on a week that is already over.\n if (job.status === 'needs_input' || late) {\n await cancelAiJob(firestore, orgId, site.jobId, now)\n await settle('skipped')\n } else {\n result.settled = false\n }\n continue\n }\n const answer = await orgRef.collection(AI_INSIGHTS_COLLECTION).doc(site.jobId).get()\n const record = answer.exists ? (answer.data() as AiInsightRecord) : null\n if (!record?.insights?.length) {\n await settle('skipped')\n continue\n }\n\n members ??= (await deps.listMembers(orgId)).filter((member) => member.orgSuspended !== true)\n const reaching = members.filter((member) => hostRoleFor(member, hostId) !== null)\n const users = await readUsers(firestore, reaching.map((member) => member.$id).sort())\n const host = (await firestore.collection('hosts').doc(hostId).get()).data() ?? {}\n const orgSlug = str(org['slug'])\n const subdomain = str(host['subdomain'])\n const origin = deps.consoleOrigin\n const analyticsUrl = origin && orgSlug && subdomain ? `${origin}/${orgSlug}/hosts/${subdomain}/analytics` : null\n for (const member of reaching.sort((a, b) => a.$id.localeCompare(b.$id))) {\n const user = users.get(member.$id)\n if (!insightDigestSubscribed(user?.get(INSIGHT_DIGESTS_FIELD), orgId)) continue\n if (site.sent?.[member.$id]) continue\n if (overBudget()) {\n // Out of time: what was sent is stamped, and the next run sends the rest.\n result.deferred = true\n result.settled = false\n return result\n }\n const address = str(member.email ?? user?.get('email')).toLowerCase()\n if (address.includes('@')) {\n const emailInput = {\n siteName: site.name,\n productName: branding.productName,\n insights: record.insights,\n analyticsUrl,\n settingsUrl: origin ? `${origin}/manage/notifications` : null,\n workspaceName: str(org['name']),\n creditsSpent: job.creditsSpent,\n periodLabel: aiInsightDigestPeriod(job.inputs, new Date(Number(marker.createdAtMs) || now.getTime())).label,\n }\n const sent = await deps.send({\n to: address,\n subject: `Your weekly insights for ${site.name}`,\n text: aiInsightDigestEmailText(emailInput),\n fromName: branding.fromName,\n siteName: site.name,\n org,\n merge: aiInsightDigestEmailParts(emailInput),\n })\n if (sent.rateLimited) {\n // Nothing is marked, so the next run delivers this site whole.\n result.deferred = true\n result.settled = false\n return result\n }\n }\n if (!notificationMuted(user?.get('notificationPrefs'), 'content.insightsDigest')) {\n await deps.notify(member.$id, {\n title: `Weekly insights · ${site.name}`,\n body: record.insights[0].text,\n link: `/${hostId}/analytics`,\n orgId,\n hostId,\n })\n }\n await markerRef.set({ sites: { [hostId]: { sent: { [member.$id]: true } } } }, { merge: true })\n }\n await settle('delivered')\n }\n return result\n}\n\n/**\n * One call of the sweep: delivery for this week and the last on the first\n * call, then — on a Monday — one chunk of workspaces' creation, resumed by\n * `cursor`. The response shape is the console crons' (`nextCursor`, `done`),\n * so the scheduler's loop carries a platform of any size across calls.\n */\nexport async function runAiInsightDigestSweep(\n deps: AiInsightDigestDeps,\n options: { cursor: string | null },\n): Promise<AiInsightDigestReport> {\n const { firestore, now } = deps\n const startedAtMs = Date.now()\n const budgetMs = deps.budgetMs ?? AI_INSIGHT_DIGEST_RUN_BUDGET_MS\n const overBudget = () => Date.now() - startedAtMs > budgetMs\n const week = aiInsightDigestWeek(now)\n const report: AiInsightDigestReport = {\n week,\n swept: 0,\n created: 0,\n delivered: 0,\n skipped: 0,\n deferred: false,\n nextCursor: null,\n done: true,\n }\n\n if (!options.cursor) {\n for (const owed of [previousWeek(now), week]) {\n const queueRef = firestore.collection(AI_INSIGHT_DIGEST_QUEUE).doc(owed)\n const pending = Object.entries(((await queueRef.get()).get('pending') ?? {}) as Record<string, unknown>)\n .filter(([, owed]) => owed === true)\n .map(([orgId]) => orgId)\n .sort()\n .slice(0, AI_INSIGHT_DIGEST_DELIVER_LIMIT)\n for (const orgId of pending) {\n if (overBudget()) {\n report.deferred = true\n return report\n }\n try {\n const delivered = await deliverOrgDigest(deps, orgId, owed, overBudget)\n report.delivered += delivered.delivered\n report.skipped += delivered.skipped\n if (delivered.settled) {\n await queueRef.set({ pending: { [orgId]: false } }, { merge: true })\n }\n if (delivered.deferred) {\n report.deferred = true\n return report\n }\n } catch (error) {\n console.error('ai insight digest delivery failed', { orgId, week: owed, error })\n }\n }\n }\n }\n\n // Monday is the week's first day by ISO 8601, and the day a digest is made.\n if (now.getUTCDay() !== 1) return report\n\n let query = firestore.collection('orgs').orderBy(FieldPath.documentId()).limit(AI_INSIGHT_DIGEST_ORG_CHUNK + 1)\n if (options.cursor) query = query.startAfter(firestore.collection('orgs').doc(options.cursor))\n const page = await query.get()\n const orgs = page.docs.slice(0, AI_INSIGHT_DIGEST_ORG_CHUNK)\n for (const [index, orgDoc] of orgs.entries()) {\n if (overBudget()) {\n // Out of time: the next call resumes after the last workspace read. A\n // call that read none has no cursor to hand on, and the next scheduled\n // run starts the week's workspaces again, which the markers make safe.\n report.nextCursor = index ? orgs[index - 1].id : (options.cursor ?? null)\n report.done = report.nextCursor === null\n return report\n }\n report.swept += 1\n try {\n report.created += await createOrgDigest(deps, orgDoc, week)\n } catch (error) {\n console.error('ai insight digest creation failed', { orgId: orgDoc.id, week, error })\n }\n }\n const more = page.docs.length > AI_INSIGHT_DIGEST_ORG_CHUNK\n report.nextCursor = more ? (orgs[orgs.length - 1]?.id ?? null) : null\n report.done = !more\n return report\n}\n"],"names":["FieldPath","INSIGHT_DIGESTS_FIELD","insightDigestSubscribed","notificationMuted","hostRoleFor","checkEntitlement","resolveBrandingProfile","resolveEffectivePlan","isReleaseFlagOnForOrg","parseOrgReleaseFlagOverrides","weeklySummaryPeriod","aiInsightDigestPeriod","AI_INSIGHTS_COLLECTION","aiInsightIsoWeek","isAiOnForSite","cancelAiJob","createAiJob","getAiJob","AI_INSIGHTS_DIGEST_CRON_ID","AI_INSIGHTS_DIGEST_PATH","AI_INSIGHT_DIGEST_ORG_CHUNK","AI_INSIGHT_DIGEST_MAX_SITES","AI_INSIGHT_DIGEST_HOST_READ_LIMIT","AI_INSIGHT_DIGEST_DELIVER_LIMIT","AI_INSIGHT_DIGEST_DEADLINE_MS","AI_INSIGHT_DIGEST_RUN_BUDGET_MS","AI_INSIGHT_DIGEST_MARKERS","AI_INSIGHT_DIGEST_QUEUE","str","value","trim","aiInsightDigestWeek","now","previousWeek","Date","getTime","aiInsightDigestEmailText","input","workspace","workspaceName","credits","Math","max","round","Number","creditsSpent","drew","toLocaleString","forWhom","periodLabel","siteName","insights","map","insight","index","text","analyticsUrl","productName","settingsUrl","join","aiInsightDigestEmailParts","filter","Boolean","readUsers","firestore","uids","length","Map","docs","getAll","uid","collection","doc","id","createOrgDigest","deps","orgDoc","week","orgId","org","data","flagOn","flagValue","paused","markerRef","ref","get","exists","members","listMembers","member","orgSuspended","users","$id","sort","subscribers","a","b","localeCompare","hosts","where","limit","yesterday","toISOString","slice","candidates","host","day","creator","mayGenerate","push","hostId","name","views","chosen","sites","site","job","kind","brief","inputs","surface","through","to","createdBy","jobId","state","marker","createdAtMs","set","pending","merge","deliverOrgDigest","overBudget","orgRef","markerSnapshot","result","delivered","skipped","settled","deferred","branding","Object","entries","record","settle","update","status","late","answer","reaching","orgSlug","subdomain","origin","consoleOrigin","user","sent","address","email","toLowerCase","includes","emailInput","label","send","subject","fromName","rateLimited","notify","title","body","link","runAiInsightDigestSweep","options","orgs","startedAtMs","budgetMs","report","swept","created","nextCursor","done","cursor","owed","queueRef","error","console","getUTCDay","query","orderBy","documentId","startAfter","page","more"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,SAAS,QAAQ,2BAA0B;AACpD,SAASC,qBAAqB,EAAEC,uBAAuB,EAAEC,iBAAiB,QAAQ,uCAAsC;AACxH,SAASC,WAAW,QAAQ,uCAAsC;AAClE,SAASC,gBAAgB,EAAEC,sBAAsB,EAAEC,oBAAoB,QAAQ,2CAA0C;AACzH,SAASC,qBAAqB,EAAEC,4BAA4B,QAA+B,uCAAsC;AAEjI,SAASC,mBAAmB,QAAQ,wCAAuC;AAC3E,SACEC,qBAAqB,EACrBC,sBAAsB,EACtBC,gBAAgB,QAEX,yBAAqB;AAC5B,SAASC,aAAa,QAAQ,6BAAyB;AACvD,SAASC,WAAW,EAAEC,WAAW,EAAEC,QAAQ,QAAQ,qBAAiB;AA+CpE,OAAO,MAAMC,6BAA6B,qBAAoB;AAC9D,OAAO,MAAMC,0BAA0B,2BAA0B;AAEjE,kHAAkH,GAClH,OAAO,MAAMC,8BAA8B,GAAE;AAE7C,gDAAgD,GAChD,OAAO,MAAMC,8BAA8B,EAAC;AAE5C,0DAA0D,GAC1D,OAAO,MAAMC,oCAAoC,GAAE;AAEnD,8CAA8C,GAC9C,OAAO,MAAMC,kCAAkC,IAAG;AAElD,kEAAkE,GAClE,OAAO,MAAMC,gCAAgC,KAAK,KAAK,MAAM;AAE7D;;;;;;CAMC,GACD,OAAO,MAAMC,kCAAkC,MAAM;AAErD,OAAO,MAAMC,4BAA4B,mBAAkB;AAC3D,OAAO,MAAMC,0BAA0B,uBAAsB;AAkE7D,MAAMC,MAAM,CAACC,QAA4B,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;AAEpF,gEAAgE,GAChE,OAAO,SAASC,oBAAoBC,GAAS;IAC3C,OAAOnB,iBAAiBmB;AAC1B;AAEA,8DAA8D,GAC9D,SAASC,aAAaD,GAAS;IAC7B,OAAOnB,iBAAiB,IAAIqB,KAAKF,IAAIG,OAAO,KAAK,IAAI;AACvD;AAEA;;;;;CAKC,GACD,OAAO,SAASC,yBAAyBC,KAaxC;QACmBA;IAAlB,MAAMC,YAAYD,EAAAA,uBAAAA,MAAME,aAAa,qBAAnBF,qBAAqBP,IAAI,OAAM;IACjD,MAAMU,UAAUC,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACC,OAAOP,MAAMQ,YAAY,KAAK;IACrE,MAAMC,OAAON,UACT,CAAC,iBAAiB,EAAEA,QAAQO,cAAc,CAAC,SAAS,IAAI,CAAC,GACzD,GAAGT,YAAY,GAAGA,UAAU,EAAE,CAAC,GAAG,kBAAkB,YAAY,CAAC,GACjE;IACJ,MAAMU,UAAUV,aAAa;IAC7B,OAAO;QACLD,MAAMY,WAAW,GACb,CAAC,aAAa,EAAEZ,MAAMa,QAAQ,CAAC,6BAA6B,EAAEb,MAAMY,WAAW,CAAC,CAAC,EAAEH,MAAM,GACzF,CAAC,aAAa,EAAET,MAAMa,QAAQ,CAAC,4BAA4B,EAAEJ,MAAM;QACvE;WACGT,MAAMc,QAAQ,CAACC,GAAG,CAAC,CAACC,SAASC,QAAU,GAAGA,QAAQ,EAAE,EAAE,EAAED,QAAQE,IAAI,EAAE;QACzE;QACAlB,MAAMmB,YAAY,GACd,CAAC,6DAA6D,EAAEnB,MAAMoB,WAAW,CAAC,EAAE,EAAEpB,MAAMmB,YAAY,EAAE,GAC1G,CAAC,0FAA0F,EAAEnB,MAAMoB,WAAW,CAAC,CAAC,CAAC;QACrH;QACApB,MAAMqB,WAAW,GACb,CAAC,kCAAkC,EAAEV,QAAQ,kCAAkC,EAAEX,MAAMqB,WAAW,EAAE,GACpG,CAAC,kCAAkC,EAAEV,QAAQ,+CAA+C,CAAC;KAClG,CAACW,IAAI,CAAC;AACT;AAEA;;;;;CAKC,GACD,OAAO,SAASC,0BACdvB,KAAqD;QAc5BA;QATPA,sBAIcA;IAJhC,MAAMC,YAAYD,EAAAA,uBAAAA,MAAME,aAAa,qBAAnBF,qBAAqBP,IAAI,OAAM;IACjD,MAAMU,UAAUC,KAAKC,GAAG,CAAC,GAAGD,KAAKE,KAAK,CAACC,OAAOP,MAAMQ,YAAY,KAAK;IACrE,MAAMG,UAAUV,aAAa;IAC7B,OAAO;QACL,kBAAkB;YAACA;aAAWD,qBAAAA,MAAMY,WAAW,qBAAjBZ,mBAAmBP,IAAI;SAAG,CAAC+B,MAAM,CAACC,SAASH,IAAI,CAAC;QAC9E,gBACE,CAAC,KAAK,EAAEtB,MAAMa,QAAQ,CAAC,kBAAkB,EAAEb,MAAMY,WAAW,GAAG,cAAc,YAAY,EAAE,CAAC,GAC5F;QACF,mBAAmBZ,MAAMc,QAAQ,CAACC,GAAG,CAAC,CAACC,SAASC,QAAU,GAAGA,QAAQ,EAAE,EAAE,EAAED,QAAQE,IAAI,EAAE,EAAEI,IAAI,CAAC;QAChG,qBAAqB,GAAEtB,sBAAAA,MAAMmB,YAAY,YAAlBnB,sBAAsB;QAC7C,cACE,CAAC,kCAAkC,EAAEW,QAAQ,CAAC,CAAC,GAC9CR,CAAAA,UACG,CAAC,oBAAoB,EAAEA,QAAQO,cAAc,CAAC,SAAS,IAAI,EAAET,YAAY,GAAGA,UAAU,EAAE,CAAC,GAAG,kBAAkB,YAAY,CAAC,GAC3H,EAAC,IACJD,CAAAA,MAAMqB,WAAW,GACd,CAAC,iCAAiC,EAAErB,MAAMqB,WAAW,EAAE,GACvD,gDAA+C;IACvD;AACF;AAEA,eAAeK,UAAUC,SAAoB,EAAEC,IAAuB;IACpE,IAAI,CAACA,KAAKC,MAAM,EAAE,OAAO,IAAIC;IAC7B,MAAMC,OAAO,MAAMJ,UAAUK,MAAM,IAAIJ,KAAKb,GAAG,CAAC,CAACkB,MAAQN,UAAUO,UAAU,CAAC,SAASC,GAAG,CAACF;IAC3F,OAAO,IAAIH,IAAIC,KAAKhB,GAAG,CAAC,CAACoB,MAAQ;YAACA,IAAIC,EAAE;YAAED;SAAI;AAChD;AAEA,oFAAoF,GACpF,eAAeE,gBACbC,IAAyB,EACzBC,MAA+C,EAC/CC,IAAY;QAICD;IAFb,MAAM,EAAEZ,SAAS,EAAEhC,GAAG,EAAE,GAAG2C;IAC3B,MAAMG,QAAQF,OAAOH,EAAE;IACvB,MAAMM,OAAOH,eAAAA,OAAOI,IAAI,cAAXJ,eAAiB,CAAC;IAC/B,IAAI,CAACvE,iBAAiB0E,KAAc,iBAAiB,OAAO;IAC5D,MAAME,SAASzE,sBACb,yBACAmE,KAAKO,SAAS,EACdJ,OACArE,6BAA6BsE,GAAG,CAAC,eAAe,GAChDxE,qBAAqBwE;IAEvB,IAAI,CAACE,UAAW,MAAMN,KAAKQ,MAAM,CAACL,QAAS,OAAO;IAClD,MAAMM,YAAYR,OAAOS,GAAG,CAACd,UAAU,CAAC7C,2BAA2B8C,GAAG,CAACK;IACvE,IAAI,AAAC,CAAA,MAAMO,UAAUE,GAAG,EAAC,EAAGC,MAAM,EAAE,OAAO;IAE3C,MAAMC,UAAU,AAAC,CAAA,MAAMb,KAAKc,WAAW,CAACX,MAAK,EAAGjB,MAAM,CAAC,CAAC6B,SAAWA,OAAOC,YAAY,KAAK;IAC3F,MAAMC,QAAQ,MAAM7B,UAAUC,WAAWwB,QAAQpC,GAAG,CAAC,CAACsC,SAAWA,OAAOG,GAAG,EAAEC,IAAI;IACjF,MAAMC,cAAcP,QACjB3B,MAAM,CAAC,CAAC6B;YAAmCE;eAAxB1F,yBAAwB0F,aAAAA,MAAMN,GAAG,CAACI,OAAOG,GAAG,sBAApBD,WAAuBN,GAAG,CAACrF,wBAAwB6E;OAC9FgB,IAAI,CAAC,CAACE,GAAGC,IAAMD,EAAEH,GAAG,CAACK,aAAa,CAACD,EAAEJ,GAAG;IAC3C,IAAI,CAACE,YAAY7B,MAAM,EAAE,OAAO;IAEhC,MAAMiC,QAAQ,MAAMnC,UACjBO,UAAU,CAAC,SACX6B,KAAK,CAAC,SAAS,MAAMtB,OACrBuB,KAAK,CAAC/E,mCACNgE,GAAG;IACN,MAAMgB,YAAY,IAAIpE,KAAKF,IAAIG,OAAO,KAAK,UAAYoE,WAAW,GAAGC,KAAK,CAAC,GAAG;IAC9E,MAAMC,aAAsF,EAAE;IAC9F,KAAK,MAAMC,QAAQP,MAAM/B,IAAI,CAAE;YAChBsC,YAeGC;QAfhB,MAAM3B,QAAO0B,aAAAA,KAAK1B,IAAI,cAAT0B,aAAe,CAAC;QAC7B,IAAI,CAAC5F,cAAciE,KAAKC,OAAO;QAC/B,IAAI4B,UAAyB;QAC7B,KAAK,MAAMlB,UAAUK,YAAa;YAChC,IAAI3F,YAAYsF,QAAQgB,KAAKjC,EAAE,MAAM,QAAS,MAAME,KAAKkC,WAAW,CAAC/B,OAAO4B,KAAKjC,EAAE,EAAEiB,SAAU;gBAC7FkB,UAAUlB,OAAOG,GAAG;gBACpB;YACF;QACF;QACA,IAAI,CAACe,SAAS;QACd,MAAMD,MAAM,MAAMD,KAAKrB,GAAG,CAACd,UAAU,CAAC,aAAaC,GAAG,CAAC8B,WAAWhB,GAAG;QACrEmB,WAAWK,IAAI,CAAC;YACdC,QAAQL,KAAKjC,EAAE;YACfuC,MAAMpF,IAAIoD,IAAI,CAAC,cAAc,KAAKpD,IAAIoD,IAAI,CAAC,OAAO,KAAKpD,IAAIoD,IAAI,CAAC,YAAY,KAAK0B,KAAKjC,EAAE;YACxFmC;YACAK,OAAOrE,QAAO+D,WAAAA,IAAIrB,GAAG,CAAC,oBAARqB,WAAoB,MAAM;QAC1C;IACF;IACA,MAAMO,SAAST,WACZX,IAAI,CAAC,CAACE,GAAGC,IAAMA,EAAEgB,KAAK,GAAGjB,EAAEiB,KAAK,IAAIjB,EAAEe,MAAM,CAACb,aAAa,CAACD,EAAEc,MAAM,GACnEP,KAAK,CAAC,GAAGnF;IACZ,IAAI,CAAC6F,OAAOhD,MAAM,EAAE,OAAO;IAE3B,MAAMiD,QAA6C,CAAC;IACpD,KAAK,MAAMC,QAAQF,OAAQ;QACzB,MAAMG,MAAM,MAAMrG,YAChBgD,WACA;YACEc;YACAiC,QAAQK,KAAKL,MAAM;YACnBO,MAAM;YACNC,OAAO,CAAC,oBAAoB,EAAEH,KAAKJ,IAAI,EAAE;YACzCQ,QAAQ;gBAAEC,SAAS;gBAAU5C;gBAAM6C,SAAShH,oBAAoBsB,KAAK2F,EAAE;YAAC;YACxEC,WAAWR,KAAKR,OAAO;QACzB,GACA5E;QAEFmF,KAAK,CAACC,KAAKL,MAAM,CAAC,GAAG;YAAEc,OAAOR,IAAIxB,GAAG;YAAEmB,MAAMI,KAAKJ,IAAI;YAAEc,OAAO;QAAU;IAC3E;IACA,MAAMC,SAAgC;QAAElD;QAAMmD,aAAahG,IAAIG,OAAO;QAAIgF;IAAM;IAChF,MAAM/B,UAAU6C,GAAG,CAACF;IACpB,MAAM/D,UACHO,UAAU,CAAC5C,yBACX6C,GAAG,CAACK,MACJoD,GAAG,CAAC;QAAEpD;QAAMqD,SAAS;YAAE,CAACpD,MAAM,EAAE;QAAK;IAAE,GAAG;QAAEqD,OAAO;IAAK;IAC3D,OAAOjB,OAAOhD,MAAM;AACtB;AAEA,mGAAmG,GACnG,eAAekE,iBACbzD,IAAyB,EACzBG,KAAa,EACbD,IAAY,EACZwD,UAAyB;QASZ,OAI+BN;IAX5C,MAAM,EAAE/D,SAAS,EAAEhC,GAAG,EAAE,GAAG2C;IAC3B,MAAM2D,SAAStE,UAAUO,UAAU,CAAC,QAAQC,GAAG,CAACM;IAChD,MAAMM,YAAYkD,OAAO/D,UAAU,CAAC7C,2BAA2B8C,GAAG,CAACK;IACnE,MAAM0D,iBAAiB,MAAMnD,UAAUE,GAAG;IAC1C,MAAMkD,SAAS;QAAEC,WAAW;QAAGC,SAAS;QAAGC,SAAS;QAAMC,UAAU;IAAM;IAC1E,IAAI,CAACL,eAAehD,MAAM,EAAE,OAAOiD;IACnC,MAAMT,SAASQ,eAAevD,IAAI;IAClC,MAAMD,OAAO,QAAA,AAAC,CAAA,MAAMuD,OAAOhD,GAAG,EAAC,EAAGN,IAAI,cAAzB,QAA+B,CAAC;IAC7C,IAAIQ,UAAmC;IACvC,MAAMqD,WAAWvI,uBAAuByE;IAExC,KAAK,MAAM,CAACgC,QAAQK,KAAK,IAAI0B,OAAOC,OAAO,EAAChB,gBAAAA,OAAOZ,KAAK,YAAZY,gBAAgB,CAAC,GAAI;YAoClD;YARRiB;QA3BL,IAAI5B,KAAKU,KAAK,KAAK,WAAW;QAC9B,MAAMmB,SAAS,OAAOnB;YACpB,yDAAyD;YACzD,MAAMa,UAA+B;gBAAEd,OAAOT,KAAKS,KAAK;gBAAEb,MAAMI,KAAKJ,IAAI;gBAAEc;YAAM;YACjF,MAAM1C,UAAU8D,MAAM,CAAC;gBAAE,CAAC,CAAC,MAAM,EAAEnC,QAAQ,CAAC,EAAE4B;YAAQ;YACtD,IAAIb,UAAU,aAAaU,OAAOC,SAAS,IAAI;iBAC1CD,OAAOE,OAAO,IAAI;QACzB;QACA,MAAMrB,MAAM,MAAMpG,SAAS+C,WAAWc,OAAOsC,KAAKS,KAAK;QACvD,IAAI,CAACR,OAAOA,IAAI8B,MAAM,KAAK,YAAY9B,IAAI8B,MAAM,KAAK,YAAY;YAChE,MAAMF,OAAO;YACb;QACF;QACA,IAAI5B,IAAI8B,MAAM,KAAK,QAAQ;gBACWpB;YAApC,MAAMqB,OAAOpH,IAAIG,OAAO,KAAKS,QAAOmF,sBAAAA,OAAOC,WAAW,YAAlBD,sBAAsB,KAAKvG;YAC/D,wEAAwE;YACxE,4DAA4D;YAC5D,IAAI6F,IAAI8B,MAAM,KAAK,iBAAiBC,MAAM;gBACxC,MAAMrI,YAAYiD,WAAWc,OAAOsC,KAAKS,KAAK,EAAE7F;gBAChD,MAAMiH,OAAO;YACf,OAAO;gBACLT,OAAOG,OAAO,GAAG;YACnB;YACA;QACF;QACA,MAAMU,SAAS,MAAMf,OAAO/D,UAAU,CAAC3D,wBAAwB4D,GAAG,CAAC4C,KAAKS,KAAK,EAAEvC,GAAG;QAClF,MAAM0D,SAASK,OAAO9D,MAAM,GAAI8D,OAAOrE,IAAI,KAAyB;QACpE,IAAI,EAACgE,2BAAAA,mBAAAA,OAAQ7F,QAAQ,qBAAhB6F,iBAAkB9E,MAAM,GAAE;YAC7B,MAAM+E,OAAO;YACb;QACF;QAEAzD,kBAAAA,UAAAA,UAAY,AAAC,CAAA,MAAMb,KAAKc,WAAW,CAACX,MAAK,EAAGjB,MAAM,CAAC,CAAC6B,SAAWA,OAAOC,YAAY,KAAK;QACvF,MAAM2D,WAAW9D,QAAQ3B,MAAM,CAAC,CAAC6B,SAAWtF,YAAYsF,QAAQqB,YAAY;QAC5E,MAAMnB,QAAQ,MAAM7B,UAAUC,WAAWsF,SAASlG,GAAG,CAAC,CAACsC,SAAWA,OAAOG,GAAG,EAAEC,IAAI;QAClF,MAAMY,QAAO,SAAA,AAAC,CAAA,MAAM1C,UAAUO,UAAU,CAAC,SAASC,GAAG,CAACuC,QAAQzB,GAAG,EAAC,EAAGN,IAAI,cAA5D,SAAkE,CAAC;QAChF,MAAMuE,UAAU3H,IAAImD,GAAG,CAAC,OAAO;QAC/B,MAAMyE,YAAY5H,IAAI8E,IAAI,CAAC,YAAY;QACvC,MAAM+C,SAAS9E,KAAK+E,aAAa;QACjC,MAAMlG,eAAeiG,UAAUF,WAAWC,YAAY,GAAGC,OAAO,CAAC,EAAEF,QAAQ,OAAO,EAAEC,UAAU,UAAU,CAAC,GAAG;QAC5G,KAAK,MAAM9D,UAAU4D,SAASxD,IAAI,CAAC,CAACE,GAAGC,IAAMD,EAAEH,GAAG,CAACK,aAAa,CAACD,EAAEJ,GAAG,GAAI;gBAUpDH;gBAPhB0B;YAFJ,MAAMuC,OAAO/D,MAAMN,GAAG,CAACI,OAAOG,GAAG;YACjC,IAAI,CAAC3F,wBAAwByJ,wBAAAA,KAAMrE,GAAG,CAACrF,wBAAwB6E,QAAQ;YACvE,KAAIsC,aAAAA,KAAKwC,IAAI,qBAATxC,UAAW,CAAC1B,OAAOG,GAAG,CAAC,EAAE;YAC7B,IAAIwC,cAAc;gBAChB,0EAA0E;gBAC1EG,OAAOI,QAAQ,GAAG;gBAClBJ,OAAOG,OAAO,GAAG;gBACjB,OAAOH;YACT;YACA,MAAMqB,UAAUjI,KAAI8D,gBAAAA,OAAOoE,KAAK,YAAZpE,gBAAgBiE,wBAAAA,KAAMrE,GAAG,CAAC,UAAUyE,WAAW;YACnE,IAAIF,QAAQG,QAAQ,CAAC,MAAM;gBACzB,MAAMC,aAAa;oBACjB/G,UAAUkE,KAAKJ,IAAI;oBACnBvD,aAAaoF,SAASpF,WAAW;oBACjCN,UAAU6F,OAAO7F,QAAQ;oBACzBK;oBACAE,aAAa+F,SAAS,GAAGA,OAAO,qBAAqB,CAAC,GAAG;oBACzDlH,eAAeX,IAAImD,GAAG,CAAC,OAAO;oBAC9BlC,cAAcwE,IAAIxE,YAAY;oBAC9BI,aAAatC,sBAAsB0G,IAAIG,MAAM,EAAE,IAAItF,KAAKU,OAAOmF,OAAOC,WAAW,KAAKhG,IAAIG,OAAO,KAAK+H,KAAK;gBAC7G;gBACA,MAAMN,OAAO,MAAMjF,KAAKwF,IAAI,CAAC;oBAC3BxC,IAAIkC;oBACJO,SAAS,CAAC,yBAAyB,EAAEhD,KAAKJ,IAAI,EAAE;oBAChDzD,MAAMnB,yBAAyB6H;oBAC/BI,UAAUxB,SAASwB,QAAQ;oBAC3BnH,UAAUkE,KAAKJ,IAAI;oBACnBjC;oBACAoD,OAAOvE,0BAA0BqG;gBACnC;gBACA,IAAIL,KAAKU,WAAW,EAAE;oBACpB,+DAA+D;oBAC/D9B,OAAOI,QAAQ,GAAG;oBAClBJ,OAAOG,OAAO,GAAG;oBACjB,OAAOH;gBACT;YACF;YACA,IAAI,CAACrI,kBAAkBwJ,wBAAAA,KAAMrE,GAAG,CAAC,sBAAsB,2BAA2B;gBAChF,MAAMX,KAAK4F,MAAM,CAAC7E,OAAOG,GAAG,EAAE;oBAC5B2E,OAAO,CAAC,kBAAkB,EAAEpD,KAAKJ,IAAI,EAAE;oBACvCyD,MAAMzB,OAAO7F,QAAQ,CAAC,EAAE,CAACI,IAAI;oBAC7BmH,MAAM,CAAC,CAAC,EAAE3D,OAAO,UAAU,CAAC;oBAC5BjC;oBACAiC;gBACF;YACF;YACA,MAAM3B,UAAU6C,GAAG,CAAC;gBAAEd,OAAO;oBAAE,CAACJ,OAAO,EAAE;wBAAE6C,MAAM;4BAAE,CAAClE,OAAOG,GAAG,CAAC,EAAE;wBAAK;oBAAE;gBAAE;YAAE,GAAG;gBAAEsC,OAAO;YAAK;QAC/F;QACA,MAAMc,OAAO;IACf;IACA,OAAOT;AACT;AAEA;;;;;CAKC,GACD,OAAO,eAAemC,wBACpBhG,IAAyB,EACzBiG,OAAkC;QAIjBjG;QAqEWkG;IAvE5B,MAAM,EAAE7G,SAAS,EAAEhC,GAAG,EAAE,GAAG2C;IAC3B,MAAMmG,cAAc5I,KAAKF,GAAG;IAC5B,MAAM+I,YAAWpG,iBAAAA,KAAKoG,QAAQ,YAAbpG,iBAAiBlD;IAClC,MAAM4G,aAAa,IAAMnG,KAAKF,GAAG,KAAK8I,cAAcC;IACpD,MAAMlG,OAAO9C,oBAAoBC;IACjC,MAAMgJ,SAAgC;QACpCnG;QACAoG,OAAO;QACPC,SAAS;QACTzC,WAAW;QACXC,SAAS;QACTE,UAAU;QACVuC,YAAY;QACZC,MAAM;IACR;IAEA,IAAI,CAACR,QAAQS,MAAM,EAAE;QACnB,KAAK,MAAMC,QAAQ;YAACrJ,aAAaD;YAAM6C;SAAK,CAAE;gBAEZ;YADhC,MAAM0G,WAAWvH,UAAUO,UAAU,CAAC5C,yBAAyB6C,GAAG,CAAC8G;YACnE,MAAMpD,UAAUY,OAAOC,OAAO,EAAE,OAAA,AAAC,CAAA,MAAMwC,SAASjG,GAAG,EAAC,EAAGA,GAAG,CAAC,sBAA3B,OAAyC,CAAC,GACvEzB,MAAM,CAAC,CAAC,GAAGyH,KAAK,GAAKA,SAAS,MAC9BlI,GAAG,CAAC,CAAC,CAAC0B,MAAM,GAAKA,OACjBgB,IAAI,GACJU,KAAK,CAAC,GAAGjF;YACZ,KAAK,MAAMuD,SAASoD,QAAS;gBAC3B,IAAIG,cAAc;oBAChB2C,OAAOpC,QAAQ,GAAG;oBAClB,OAAOoC;gBACT;gBACA,IAAI;oBACF,MAAMvC,YAAY,MAAML,iBAAiBzD,MAAMG,OAAOwG,MAAMjD;oBAC5D2C,OAAOvC,SAAS,IAAIA,UAAUA,SAAS;oBACvCuC,OAAOtC,OAAO,IAAID,UAAUC,OAAO;oBACnC,IAAID,UAAUE,OAAO,EAAE;wBACrB,MAAM4C,SAAStD,GAAG,CAAC;4BAAEC,SAAS;gCAAE,CAACpD,MAAM,EAAE;4BAAM;wBAAE,GAAG;4BAAEqD,OAAO;wBAAK;oBACpE;oBACA,IAAIM,UAAUG,QAAQ,EAAE;wBACtBoC,OAAOpC,QAAQ,GAAG;wBAClB,OAAOoC;oBACT;gBACF,EAAE,OAAOQ,OAAO;oBACdC,QAAQD,KAAK,CAAC,qCAAqC;wBAAE1G;wBAAOD,MAAMyG;wBAAME;oBAAM;gBAChF;YACF;QACF;IACF;IAEA,4EAA4E;IAC5E,IAAIxJ,IAAI0J,SAAS,OAAO,GAAG,OAAOV;IAElC,IAAIW,QAAQ3H,UAAUO,UAAU,CAAC,QAAQqH,OAAO,CAAC5L,UAAU6L,UAAU,IAAIxF,KAAK,CAACjF,8BAA8B;IAC7G,IAAIwJ,QAAQS,MAAM,EAAEM,QAAQA,MAAMG,UAAU,CAAC9H,UAAUO,UAAU,CAAC,QAAQC,GAAG,CAACoG,QAAQS,MAAM;IAC5F,MAAMU,OAAO,MAAMJ,MAAMrG,GAAG;IAC5B,MAAMuF,OAAOkB,KAAK3H,IAAI,CAACoC,KAAK,CAAC,GAAGpF;IAChC,KAAK,MAAM,CAACkC,OAAOsB,OAAO,IAAIiG,KAAK9B,OAAO,GAAI;QAC5C,IAAIV,cAAc;gBAIkCuC;YAHlD,sEAAsE;YACtE,uEAAuE;YACvE,uEAAuE;YACvEI,OAAOG,UAAU,GAAG7H,QAAQuH,IAAI,CAACvH,QAAQ,EAAE,CAACmB,EAAE,IAAImG,kBAAAA,QAAQS,MAAM,YAAdT,kBAAkB;YACpEI,OAAOI,IAAI,GAAGJ,OAAOG,UAAU,KAAK;YACpC,OAAOH;QACT;QACAA,OAAOC,KAAK,IAAI;QAChB,IAAI;YACFD,OAAOE,OAAO,IAAI,MAAMxG,gBAAgBC,MAAMC,QAAQC;QACxD,EAAE,OAAO2G,OAAO;YACdC,QAAQD,KAAK,CAAC,qCAAqC;gBAAE1G,OAAOF,OAAOH,EAAE;gBAAEI;gBAAM2G;YAAM;QACrF;IACF;IACA,MAAMQ,OAAOD,KAAK3H,IAAI,CAACF,MAAM,GAAG9C;IAChC4J,OAAOG,UAAU,GAAGa,gBAAQnB,SAAAA,IAAI,CAACA,KAAK3G,MAAM,GAAG,EAAE,qBAArB2G,OAAuBpG,EAAE,mBAAI,OAAQ;IACjEuG,OAAOI,IAAI,GAAG,CAACY;IACf,OAAOhB;AACT"}
|