@aglyn/plugins-ai 1.0.0-beta.217 → 1.0.0-beta.219
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/README.md +2 -2
- package/package.json +17 -17
- package/src/index.d.ts +1 -0
- package/src/index.js.map +1 -1
- package/src/lib/activity/ai-activity-actions.js +10 -2
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-assist-provider.component.js +2 -2
- package/src/lib/components/ai-assist-provider.component.js.map +1 -1
- package/src/lib/components/ai-brief-dialog.component.js +3 -3
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-experiment-cards.component.js +1 -1
- package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.d.ts +8 -0
- package/src/lib/components/ai-job-plan.component.js +21 -4
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-permissions-on-host.component.d.ts +12 -2
- package/src/lib/components/ai-permissions-on-host.component.js +12 -3
- package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
- package/src/lib/components/ai-product-copy-card.component.d.ts +1 -1
- package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
- package/src/lib/components/ai-product-import-option.component.d.ts +1 -1
- package/src/lib/components/ai-product-import-option.component.js.map +1 -1
- package/src/lib/components/ai-product-zones.d.ts +124 -0
- package/src/lib/components/ai-product-zones.js +32 -0
- package/src/lib/components/ai-product-zones.js.map +1 -0
- package/src/lib/components/ai-products-hub-card.component.d.ts +1 -1
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-products-proposals.component.d.ts +1 -1
- package/src/lib/components/ai-products-proposals.component.js.map +1 -1
- package/src/lib/components/ai-seo-audit-card.component.js +35 -92
- package/src/lib/components/ai-seo-audit-card.component.js.map +1 -1
- package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +21 -15
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +2 -2
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/assist-signals-page.component.js +2 -2
- package/src/lib/components/assist-signals-page.component.js.map +1 -1
- package/src/lib/jobs/ai-email-bindings.d.ts +28 -21
- package/src/lib/jobs/ai-email-bindings.js +75 -42
- package/src/lib/jobs/ai-email-bindings.js.map +1 -1
- package/src/lib/jobs/ai-job-campaign-step.d.ts +0 -2
- package/src/lib/jobs/ai-job-campaign-step.js +5 -10
- package/src/lib/jobs/ai-job-campaign-step.js.map +1 -1
- package/src/lib/jobs/ai-job-component-step.js +9 -3
- package/src/lib/jobs/ai-job-component-step.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +9 -1
- package/src/lib/jobs/ai-job-drafts.js +12 -5
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-email-step.d.ts +0 -2
- package/src/lib/jobs/ai-job-email-step.js +1 -3
- package/src/lib/jobs/ai-job-email-step.js.map +1 -1
- package/src/lib/jobs/ai-job-experiment-step.d.ts +1 -1
- package/src/lib/jobs/ai-job-experiment-step.js +1 -1
- package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
- package/src/lib/jobs/ai-job-generation.d.ts +1 -1
- package/src/lib/jobs/ai-job-generation.js +10 -2
- package/src/lib/jobs/ai-job-generation.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +1 -1
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.d.ts +4 -2
- package/src/lib/jobs/ai-job-page-sections.js +7 -4
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +7 -2
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-conformance.js +33 -0
- package/src/lib/jobs/ai-job-plan-conformance.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +3 -0
- package/src/lib/jobs/ai-job-plan-step.js +29 -17
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plugin-drafts.d.ts +14 -6
- package/src/lib/jobs/ai-job-plugin-drafts.js +12 -4
- package/src/lib/jobs/ai-job-plugin-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-products-step.d.ts +7 -0
- package/src/lib/jobs/ai-job-products-step.js +54 -32
- package/src/lib/jobs/ai-job-products-step.js.map +1 -1
- package/src/lib/jobs/ai-job-seo-step.d.ts +3 -24
- package/src/lib/jobs/ai-job-seo-step.js +35 -108
- package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
- package/src/lib/jobs/ai-job-template-step.js +1 -0
- package/src/lib/jobs/ai-job-template-step.js.map +1 -1
- package/src/lib/jobs/ai-job-theme-step.d.ts +1 -1
- package/src/lib/jobs/ai-job-theme-step.js +1 -1
- package/src/lib/jobs/ai-job-theme-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.js +17 -9
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-workflow-records.d.ts +19 -11
- package/src/lib/jobs/ai-workflow-records.js +60 -27
- package/src/lib/jobs/ai-workflow-records.js.map +1 -1
- package/src/lib/model/ai-allotments.d.ts +11 -3
- package/src/lib/model/ai-allotments.js +21 -4
- package/src/lib/model/ai-allotments.js.map +1 -1
- package/src/lib/model/ai-automation-outline.d.ts +21 -2
- package/src/lib/model/ai-automation-outline.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +57 -0
- package/src/lib/model/ai-build-plan.js +116 -7
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-seo.d.ts +11 -51
- package/src/lib/model/ai-seo.js +0 -17
- package/src/lib/model/ai-seo.js.map +1 -1
- package/src/lib/model/ai-site-inventory.js +1 -1
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-template-subjects.js +2 -2
- package/src/lib/model/ai-template-subjects.js.map +1 -1
- package/src/lib/model/ai-theme-proposal.d.ts +1 -1
- package/src/lib/model/ai-theme-proposal.js +1 -1
- package/src/lib/model/ai-theme-proposal.js.map +1 -1
- package/src/lib/model/ai-workflow-job.d.ts +1 -3
- package/src/lib/model/ai-workflow-job.js +1 -2
- package/src/lib/model/ai-workflow-job.js.map +1 -1
- package/src/lib/plan-entitlements.d.ts +149 -0
- package/src/lib/plan-entitlements.js +168 -0
- package/src/lib/plan-entitlements.js.map +1 -0
- package/src/lib/plugin.js +1 -1
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +25 -4
- package/src/lib/runtime/ai-doctrine-validators.js +179 -23
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +5 -5
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +29 -14
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +8 -1
- package/src/lib/runtime/ai-eval.js +29 -2
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-node-tree.js +3 -3
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.d.ts +6 -0
- package/src/lib/runtime/ai-palette.generated.js +21 -255
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-palette.js +7 -1
- package/src/lib/runtime/ai-palette.js.map +1 -1
- package/src/lib/runtime/seo-audit.d.ts +13 -63
- package/src/lib/runtime/seo-audit.js +22 -207
- package/src/lib/runtime/seo-audit.js.map +1 -1
- package/src/lib/runtime/site-inventory.js +1 -1
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server/ai-admin-org.js +4 -25
- package/src/lib/server/ai-admin-org.js.map +1 -1
- package/src/lib/server/ai-admin-user.js +2 -19
- package/src/lib/server/ai-admin-user.js.map +1 -1
- package/src/lib/server/ai-assist-prompts.js +3 -3
- package/src/lib/server/ai-assist-prompts.js.map +1 -1
- package/src/lib/server/ai-insight-digest-route.js +8 -1
- package/src/lib/server/ai-insight-digest-route.js.map +1 -1
- package/src/lib/server/assist-chat.js +11 -10
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-view-context.js +23 -23
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.d.ts +1 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-automation-index.d.ts +20 -0
- package/src/lib/testing/stand-in-automation-index.js +102 -0
- package/src/lib/testing/stand-in-automation-index.js.map +1 -0
- package/src/lib/testing/stand-in-list-send-time.d.ts +4 -0
- package/src/lib/testing/stand-in-list-send-time.js +77 -0
- package/src/lib/testing/stand-in-list-send-time.js.map +1 -0
- package/src/lib/testing/stand-in-pipeline-index.d.ts +32 -0
- package/src/lib/testing/stand-in-pipeline-index.js +59 -0
- package/src/lib/testing/stand-in-pipeline-index.js.map +1 -0
- package/src/lib/testing/stand-in-product-index.d.ts +20 -0
- package/src/lib/testing/stand-in-product-index.js +105 -0
- package/src/lib/testing/stand-in-product-index.js.map +1 -0
- package/src/lib/tools/ai-component-tool.js +2 -2
- package/src/lib/tools/ai-component-tool.js.map +1 -1
- package/src/lib/tools/ai-duplicate-resource-tool.js +1 -1
- package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
- package/src/lib/tools/ai-seo-tool.d.ts +6 -18
- package/src/lib/tools/ai-seo-tool.js +7 -38
- package/src/lib/tools/ai-seo-tool.js.map +1 -1
- package/src/lib/tools/ai-workflow-tool.d.ts +1 -1
- package/src/lib/tools/ai-workflow-tool.js +1 -1
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage/ai-allotment-alerts.d.ts +3 -0
- package/src/lib/usage/ai-allotment-alerts.js +20 -8
- package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
- package/src/lib/usage/assist-free-taste.js +2 -1
- package/src/lib/usage/assist-free-taste.js.map +1 -1
- package/src/lib/usage/assist-signal-mining.js +1 -1
- package/src/lib/usage/assist-signal-mining.js.map +1 -1
- package/src/lib/usage-axes.d.ts +39 -0
- package/src/lib/usage-axes.js +70 -0
- package/src/lib/usage-axes.js.map +1 -0
- package/src/lib/runtime/seo-page-facts.d.ts +0 -61
- package/src/lib/runtime/seo-page-facts.js +0 -150
- package/src/lib/runtime/seo-page-facts.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-seo-fields-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport {\n SEO_LISTING_FIELDS,\n seoListingFieldCount,\n seoListingFieldTooLong,\n type SeoListingFieldKey,\n} from '@aglyn/aglyn/app-utils/seo-listing-fields'\nimport type {\n ConsoleSeoFieldsZoneProps,\n ConsoleSeoFieldValues,\n} from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useHostOrgId, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Link,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport {\n aiSeoAuditView,\n aiSeoFieldsProposalOf,\n type AiSeoFieldValues,\n type AiSeoKeywordCoverage,\n} from '../model/ai-seo'\nimport { readEventFrames } from './assist-jobs-drawer.component'\n\n/**\n * \"Write SEO\" (AGL-2910), inside a search listing editor through the\n * `seoFields` zone: the screen detail page's SEO card, and the product\n * editor's search engine listing. A person asks; an `seo` job writes the\n * listing from what the page or the product says; the proposal is shown with\n * each field's count, and \"Put in the fields\" stages it in the editor above\n * as unsaved changes. The editor's own Save is the write — this card never\n * writes a listing, and a page's listing is what its live head serves.\n *\n * On a page, the card also offers what a site audit proposed for it, once\n * that audit was applied: its listing values wait here rather than being\n * written by the audit.\n *\n * It renders nothing until the jobs route has answered for this workspace:\n * the shell has decided the plan and the permission, but the release flag is\n * the route's, and a 404 there is this card staying absent.\n */\n\n/** How many recent jobs the card reads to find this listing's proposals. */\nconst RECENT_JOBS_READ = 20\n\n/** How much of a product's description a job carries. */\nconst PRODUCT_TEXT_MAX_CHARS = 2_000\n\nconst KEYWORDS_MAX_CHARS = 300\n\ntype Verdict = 'checking' | 'ready' | 'hidden'\n\nconst isTerminal = (job: AiJobSummary) => AI_JOB_TERMINAL_STATUSES.includes(job.status)\n\n/** The proposed values this editor can take: its own fields, and an image description only beside an image. */\nexport function aiSeoValuesForEditor(\n values: AiSeoFieldValues,\n fields: readonly SeoListingFieldKey[],\n hasImage: boolean,\n): ConsoleSeoFieldValues {\n const out: ConsoleSeoFieldValues = {}\n for (const key of fields) {\n const value = values[key]\n if (typeof value !== 'string' || !value.trim()) continue\n if (key === 'imageAlt' && !hasImage) continue\n out[key] = value\n }\n return out\n}\n\nfunction Coverage({ keywords }: { keywords: readonly AiSeoKeywordCoverage[] }) {\n if (!keywords.length) return null\n return (\n <Stack direction=\"row\" spacing={0.5} sx={{ flexWrap: 'wrap', rowGap: 0.5 }} aria-label=\"Keyword coverage\">\n {keywords.map((entry) => {\n const covered = entry.inTitle || entry.inDescription\n return (\n <Chip\n key={entry.keyword}\n size=\"small\"\n variant=\"outlined\"\n color={covered ? 'success' : 'default'}\n label={covered ? entry.keyword : `${entry.keyword} — not used`}\n />\n )\n })}\n </Stack>\n )\n}\n\nfunction ProposedValues(props: {\n heading: string\n values: ConsoleSeoFieldValues\n fields: readonly SeoListingFieldKey[]\n}) {\n return (\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{props.heading}</Typography>\n {props.fields\n .filter((key) => props.values[key])\n .map((key) => (\n <Stack key={key} spacing={0.25}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ flexGrow: 1 }}>\n {SEO_LISTING_FIELDS[key].label}\n </Typography>\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={seoListingFieldTooLong(key, props.values[key]) ? 'error' : 'default'}\n label={seoListingFieldCount(key, props.values[key])}\n />\n </Stack>\n <Typography variant=\"body2\" sx={{ wordBreak: 'break-word' }}>\n {props.values[key]}\n </Typography>\n </Stack>\n ))}\n </Stack>\n )\n}\n\nexport function AiSeoFieldsCard(props: ConsoleSeoFieldsZoneProps) {\n const { hostId, subject, fields, values, hasImage, proposeValues } = props\n // A plugin-hosted editor may not know the org; the site does.\n const hostOrgId = useHostOrgId(props.orgId ? undefined : hostId)\n const orgId = props.orgId ?? hostOrgId ?? undefined\n const { data: user } = useUser()\n // Held in a ref so the reads below key on WHO is signed in, never on the\n // identity of the object that says so.\n const userRef = useRef(user)\n userRef.current = user\n const uid = user?.uid ?? null\n const subjectKind = subject.kind\n const subjectId = subject.id ?? null\n\n const [verdict, setVerdict] = useState<Verdict>('checking')\n const [notice, setNotice] = useState<string | null>(null)\n const [keywords, setKeywords] = useState('')\n const [busy, setBusy] = useState(false)\n const [job, setJob] = useState<AiJobSummary | null>(null)\n const [fromAudit, setFromAudit] = useState<{ jobId: string; values: AiSeoFieldValues } | null>(null)\n const [staged, setStaged] = useState<string | null>(null)\n const watchRef = useRef<AbortController | null>(null)\n\n useEffect(() => () => watchRef.current?.abort(), [])\n\n useEffect(() => {\n if (!orgId || !hostId || !uid) return\n let active = true\n setVerdict('checking')\n setJob(null)\n setFromAudit(null)\n void (async () => {\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=${RECENT_JOBS_READ}`,\n )\n if (!active) return\n // The flag off (404) and the plan without generation (403): the\n // feature is not this workspace's, so the card is not here at all.\n if (response.status === 404 || response.status === 403) {\n setVerdict('hidden')\n return\n }\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'Recent SEO proposals could not be loaded.'),\n )\n } else if (subjectId) {\n const jobs = ((payload?.jobs ?? []) as AiJobSummary[]).filter(\n (entry) => entry.kind === 'seo' && entry.hostId === hostId,\n )\n const latest = jobs.find((entry) => {\n const proposal = aiSeoFieldsProposalOf(entry)\n return proposal?.subject.kind === subjectKind && proposal.subject.id === subjectId\n })\n if (latest) setJob(latest)\n if (subjectKind === 'screen') {\n const audit = jobs.find((entry) => entry.applied?.staged.includes(subjectId))\n const fix = audit ? aiSeoAuditView(audit.outputs)?.fixes[subjectId] : undefined\n if (audit && fix && Object.keys(fix.values).length) {\n setFromAudit({ jobId: audit.id, values: fix.values })\n }\n }\n }\n setVerdict('ready')\n } catch {\n if (!active) return\n setNotice('Recent SEO proposals could not be loaded.')\n setVerdict('ready')\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, hostId, uid, subjectKind, subjectId])\n\n /** Follows a job through the events route until it settles. */\n const watch = useCallback(async (jobId: string, org: string) => {\n watchRef.current?.abort()\n const controller = new AbortController()\n watchRef.current = controller\n let again = true\n while (again && !controller.signal.aborted) {\n again = false\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs/${encodeURIComponent(jobId)}/events?orgId=${encodeURIComponent(org)}`,\n { signal: controller.signal },\n )\n if (!response.ok || !response.body) return\n await readEventFrames(response.body, (event) => {\n if (event['type'] === 'state') setJob(event['job'] as AiJobSummary)\n else if (event['type'] === 'reconnect') again = true\n })\n } catch {\n return\n }\n }\n }, [])\n\n const write = useCallback(async () => {\n if (!orgId) return\n setBusy(true)\n setNotice(null)\n setStaged(null)\n const common = { fields: fields.join(','), keywords: keywords.trim() }\n const inputs =\n subject.kind === 'screen'\n ? { target: 'screen', screenId: subject.id, versionId: subject.versionId ?? '', ...common }\n : {\n target: 'product',\n productId: subject.id ?? '',\n name: subject.name.trim(),\n text: subject.description.slice(0, PRODUCT_TEXT_MAX_CHARS),\n currentTitle: values.title ?? '',\n currentDescription: values.description ?? '',\n ...common,\n }\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n kind: 'seo',\n brief: `Write the search listing for ${subject.name.trim() || 'this page'}`,\n inputs,\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'The SEO proposal could not be started — try again.'),\n )\n return\n }\n const next = payload?.job as AiJobSummary | undefined\n if (!next) return\n setJob(next)\n if (!isTerminal(next)) await watch(next.id, orgId)\n } catch {\n setNotice('The SEO proposal could not be started — try again.')\n } finally {\n setBusy(false)\n }\n }, [orgId, hostId, subject, fields, keywords, values.title, values.description, watch])\n\n if (verdict !== 'ready') return null\n\n const proposal = aiSeoFieldsProposalOf(job)\n const offered = proposal ? aiSeoValuesForEditor(proposal.values, fields, hasImage) : {}\n const audited = fromAudit ? aiSeoValuesForEditor(fromAudit.values, fields, hasImage) : {}\n const running = Boolean(job && !isTerminal(job))\n const saveWord = subject.kind === 'product' ? 'Save product' : 'Save SEO'\n const unnamed = subject.kind === 'product' && !subject.name.trim()\n const help = pluginDocsHelp('aiSeo', {\n excerpt: 'Write a search listing from what the page says. It is put in the fields as unsaved changes; nothing is saved until you save.',\n })\n\n const stage = (offer: ConsoleSeoFieldValues, key: string) => {\n proposeValues(offer, key)\n setStaged(key)\n }\n\n return (\n <Box sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }} aria-label=\"SEO assistant\">\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline' }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Write with AI'}\n </Typography>\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" variant=\"caption\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Stack>\n <TextField\n size=\"small\"\n label=\"Target keywords (optional)\"\n placeholder=\"brass desk lamps, dimmable\"\n helperText=\"Comma-separated. Used only where the page is about them, never repeated to rank.\"\n value={keywords}\n onChange={(event) => setKeywords(event.target.value.slice(0, KEYWORDS_MAX_CHARS))}\n disabled={busy || running}\n />\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button size=\"small\" variant=\"outlined\" disabled={busy || running || unnamed || !orgId} onClick={() => void write()}>\n {proposal ? 'Write again' : 'Write SEO'}\n </Button>\n {running ? <CircularProgress size={16} aria-label=\"Writing the listing\" /> : null}\n {unnamed ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Name the product first.'}\n </Typography>\n ) : null}\n </Stack>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n {job?.error && !proposal ? (\n <Alert severity={job.status === 'failed' ? 'error' : 'warning'}>{job.error}</Alert>\n ) : null}\n\n {!proposal && fromAudit && Object.keys(audited).length ? (\n <Stack spacing={1}>\n <ProposedValues heading=\"Proposed by the site audit\" values={audited} fields={fields} />\n <Button\n size=\"small\"\n variant=\"contained\"\n sx={{ alignSelf: 'flex-start' }}\n disabled={staged === `audit:${fromAudit.jobId}`}\n onClick={() => stage(audited, `audit:${fromAudit.jobId}`)}\n >\n {'Put in the fields'}\n </Button>\n </Stack>\n ) : null}\n\n {proposal && job ? (\n <Stack spacing={1}>\n <ProposedValues heading=\"Proposed listing\" values={offered} fields={fields} />\n <Coverage keywords={proposal.keywords} />\n {proposal.notes.map((note) => (\n <Typography key={note} variant=\"caption\" color=\"text.secondary\">\n {note}\n </Typography>\n ))}\n {job.creditsSpent > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Used ${job.creditsSpent} ${job.creditsSpent === 1 ? 'credit' : 'credits'}.`}\n </Typography>\n ) : null}\n <Button\n size=\"small\"\n variant=\"contained\"\n sx={{ alignSelf: 'flex-start' }}\n disabled={!Object.keys(offered).length || staged === job.id}\n onClick={() => stage(offered, job.id)}\n >\n {'Put in the fields'}\n </Button>\n </Stack>\n ) : null}\n\n {staged ? (\n <Alert severity=\"success\">\n {`In the fields as unsaved changes. Review them, then press ${saveWord}.`}\n </Alert>\n ) : null}\n </Stack>\n </Box>\n )\n}\nAiSeoFieldsCard.displayName = 'AiSeoFieldsCard'\n\nexport default AiSeoFieldsCard\n"],"names":["lockdownRefusalText","parseLockdownRefusal","pluginDocsHelp","SEO_LISTING_FIELDS","seoListingFieldCount","seoListingFieldTooLong","authorizedFetch","useHostOrgId","useUser","Alert","Box","Button","Chip","CircularProgress","Link","Stack","TextField","Typography","useCallback","useEffect","useRef","useState","AI_JOB_TERMINAL_STATUSES","aiSeoAuditView","aiSeoFieldsProposalOf","readEventFrames","RECENT_JOBS_READ","PRODUCT_TEXT_MAX_CHARS","KEYWORDS_MAX_CHARS","isTerminal","job","includes","status","aiSeoValuesForEditor","values","fields","hasImage","out","key","value","trim","Coverage","keywords","length","direction","spacing","sx","flexWrap","rowGap","aria-label","map","entry","covered","inTitle","inDescription","size","variant","color","label","keyword","ProposedValues","props","heading","filter","alignItems","flexGrow","wordBreak","AiSeoFieldsCard","subject","hostId","proposeValues","hostOrgId","orgId","undefined","data","user","userRef","current","uid","subjectKind","kind","subjectId","id","verdict","setVerdict","notice","setNotice","setKeywords","busy","setBusy","setJob","fromAudit","setFromAudit","staged","setStaged","watchRef","abort","active","response","encodeURIComponent","payload","json","catch","ok","locked","String","error","jobs","latest","find","proposal","audit","applied","fix","outputs","fixes","Object","keys","jobId","watch","org","controller","AbortController","again","signal","aborted","body","event","write","common","join","inputs","target","screenId","versionId","productId","name","text","description","slice","currentTitle","title","currentDescription","method","headers","JSON","stringify","brief","next","offered","audited","running","Boolean","saveWord","unnamed","help","excerpt","stage","offer","border","borderColor","borderRadius","p","href","rel","placeholder","helperText","onChange","disabled","onClick","severity","alignSelf","notes","note","creditsSpent","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-seo-fields-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { SeoKeywordCoverage } from '@aglyn/aglyn/app-utils/seo-keywords'\nimport {\n SEO_LISTING_FIELDS,\n seoListingFieldCount,\n seoListingFieldTooLong,\n type SeoListingFieldKey,\n} from '@aglyn/aglyn/app-utils/seo-listing-fields'\nimport type {\n ConsoleSeoFieldsZoneProps,\n ConsoleSeoFieldValues,\n} from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useHostOrgId, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Link,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport {\n aiSeoAuditView,\n aiSeoFieldsProposalOf,\n type AiSeoFieldValues,\n} from '../model/ai-seo'\nimport { readEventFrames } from './assist-jobs-drawer.component'\n\n/**\n * \"Write SEO\" (AGL-2910), inside a search listing editor through the\n * `seoFields` zone: the screen detail page's SEO card, and the product\n * editor's search engine listing. A person asks; an `seo` job writes the\n * listing from what the page or the product says; the proposal is shown with\n * each field's count, and \"Put in the fields\" stages it in the editor above\n * as unsaved changes. The editor's own Save is the write — this card never\n * writes a listing, and a page's listing is what its live head serves.\n *\n * On a page, the card also offers what a site audit proposed for it, once\n * that audit was applied: its listing values wait here rather than being\n * written by the audit.\n *\n * It renders nothing until the jobs route has answered for this workspace:\n * the shell has decided the plan and the permission, but the release flag is\n * the route's, and a 404 there is this card staying absent.\n */\n\n/** How many recent jobs the card reads to find this listing's proposals. */\nconst RECENT_JOBS_READ = 20\n\n/** How much of a product's description a job carries. */\nconst PRODUCT_TEXT_MAX_CHARS = 2_000\n\nconst KEYWORDS_MAX_CHARS = 300\n\ntype Verdict = 'checking' | 'ready' | 'hidden'\n\nconst isTerminal = (job: AiJobSummary) => AI_JOB_TERMINAL_STATUSES.includes(job.status)\n\n/** The proposed values this editor can take: its own fields, and an image description only beside an image. */\nexport function aiSeoValuesForEditor(\n values: AiSeoFieldValues,\n fields: readonly SeoListingFieldKey[],\n hasImage: boolean,\n): ConsoleSeoFieldValues {\n const out: ConsoleSeoFieldValues = {}\n for (const key of fields) {\n const value = values[key]\n if (typeof value !== 'string' || !value.trim()) continue\n if (key === 'imageAlt' && !hasImage) continue\n out[key] = value\n }\n return out\n}\n\nfunction Coverage({ keywords }: { keywords: readonly SeoKeywordCoverage[] }) {\n if (!keywords.length) return null\n return (\n <Stack direction=\"row\" spacing={0.5} sx={{ flexWrap: 'wrap', rowGap: 0.5 }} aria-label=\"Keyword coverage\">\n {keywords.map((entry) => {\n const covered = entry.inTitle || entry.inDescription\n return (\n <Chip\n key={entry.keyword}\n size=\"small\"\n variant=\"outlined\"\n color={covered ? 'success' : 'default'}\n label={covered ? entry.keyword : `${entry.keyword} — not used`}\n />\n )\n })}\n </Stack>\n )\n}\n\nfunction ProposedValues(props: {\n heading: string\n values: ConsoleSeoFieldValues\n fields: readonly SeoListingFieldKey[]\n}) {\n return (\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{props.heading}</Typography>\n {props.fields\n .filter((key) => props.values[key])\n .map((key) => (\n <Stack key={key} spacing={0.25}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ flexGrow: 1 }}>\n {SEO_LISTING_FIELDS[key].label}\n </Typography>\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={seoListingFieldTooLong(key, props.values[key]) ? 'error' : 'default'}\n label={seoListingFieldCount(key, props.values[key])}\n />\n </Stack>\n <Typography variant=\"body2\" sx={{ wordBreak: 'break-word' }}>\n {props.values[key]}\n </Typography>\n </Stack>\n ))}\n </Stack>\n )\n}\n\nexport function AiSeoFieldsCard(props: ConsoleSeoFieldsZoneProps) {\n const { hostId, subject, fields, values, hasImage, proposeValues } = props\n // A plugin-hosted editor may not know the org; the site does.\n const hostOrgId = useHostOrgId(props.orgId ? undefined : hostId)\n const orgId = props.orgId ?? hostOrgId ?? undefined\n const { data: user } = useUser()\n // Held in a ref so the reads below key on WHO is signed in, never on the\n // identity of the object that says so.\n const userRef = useRef(user)\n userRef.current = user\n const uid = user?.uid ?? null\n const subjectKind = subject.kind\n const subjectId = subject.id ?? null\n\n const [verdict, setVerdict] = useState<Verdict>('checking')\n const [notice, setNotice] = useState<string | null>(null)\n const [keywords, setKeywords] = useState('')\n const [busy, setBusy] = useState(false)\n const [job, setJob] = useState<AiJobSummary | null>(null)\n const [fromAudit, setFromAudit] = useState<{ jobId: string; values: AiSeoFieldValues } | null>(null)\n const [staged, setStaged] = useState<string | null>(null)\n const watchRef = useRef<AbortController | null>(null)\n\n useEffect(() => () => watchRef.current?.abort(), [])\n\n useEffect(() => {\n if (!orgId || !hostId || !uid) return\n let active = true\n setVerdict('checking')\n setJob(null)\n setFromAudit(null)\n void (async () => {\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=${RECENT_JOBS_READ}`,\n )\n if (!active) return\n // The flag off (404) and the plan without generation (403): the\n // feature is not this workspace's, so the card is not here at all.\n if (response.status === 404 || response.status === 403) {\n setVerdict('hidden')\n return\n }\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'Recent SEO proposals could not be loaded.'),\n )\n } else if (subjectId) {\n const jobs = ((payload?.jobs ?? []) as AiJobSummary[]).filter(\n (entry) => entry.kind === 'seo' && entry.hostId === hostId,\n )\n const latest = jobs.find((entry) => {\n const proposal = aiSeoFieldsProposalOf(entry)\n return proposal?.subject.kind === subjectKind && proposal.subject.id === subjectId\n })\n if (latest) setJob(latest)\n if (subjectKind === 'screen') {\n const audit = jobs.find((entry) => entry.applied?.staged.includes(subjectId))\n const fix = audit ? aiSeoAuditView(audit.outputs)?.fixes[subjectId] : undefined\n if (audit && fix && Object.keys(fix.values).length) {\n setFromAudit({ jobId: audit.id, values: fix.values })\n }\n }\n }\n setVerdict('ready')\n } catch {\n if (!active) return\n setNotice('Recent SEO proposals could not be loaded.')\n setVerdict('ready')\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, hostId, uid, subjectKind, subjectId])\n\n /** Follows a job through the events route until it settles. */\n const watch = useCallback(async (jobId: string, org: string) => {\n watchRef.current?.abort()\n const controller = new AbortController()\n watchRef.current = controller\n let again = true\n while (again && !controller.signal.aborted) {\n again = false\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs/${encodeURIComponent(jobId)}/events?orgId=${encodeURIComponent(org)}`,\n { signal: controller.signal },\n )\n if (!response.ok || !response.body) return\n await readEventFrames(response.body, (event) => {\n if (event['type'] === 'state') setJob(event['job'] as AiJobSummary)\n else if (event['type'] === 'reconnect') again = true\n })\n } catch {\n return\n }\n }\n }, [])\n\n const write = useCallback(async () => {\n if (!orgId) return\n setBusy(true)\n setNotice(null)\n setStaged(null)\n const common = { fields: fields.join(','), keywords: keywords.trim() }\n const inputs =\n subject.kind === 'screen'\n ? { target: 'screen', screenId: subject.id, versionId: subject.versionId ?? '', ...common }\n : {\n target: 'product',\n productId: subject.id ?? '',\n name: subject.name.trim(),\n text: subject.description.slice(0, PRODUCT_TEXT_MAX_CHARS),\n currentTitle: values.title ?? '',\n currentDescription: values.description ?? '',\n ...common,\n }\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n kind: 'seo',\n brief: `Write the search listing for ${subject.name.trim() || 'this page'}`,\n inputs,\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'The SEO proposal could not be started — try again.'),\n )\n return\n }\n const next = payload?.job as AiJobSummary | undefined\n if (!next) return\n setJob(next)\n if (!isTerminal(next)) await watch(next.id, orgId)\n } catch {\n setNotice('The SEO proposal could not be started — try again.')\n } finally {\n setBusy(false)\n }\n }, [orgId, hostId, subject, fields, keywords, values.title, values.description, watch])\n\n if (verdict !== 'ready') return null\n\n const proposal = aiSeoFieldsProposalOf(job)\n const offered = proposal ? aiSeoValuesForEditor(proposal.values, fields, hasImage) : {}\n const audited = fromAudit ? aiSeoValuesForEditor(fromAudit.values, fields, hasImage) : {}\n const running = Boolean(job && !isTerminal(job))\n const saveWord = subject.kind === 'product' ? 'Save product' : 'Save SEO'\n const unnamed = subject.kind === 'product' && !subject.name.trim()\n const help = pluginDocsHelp('aiSeo', {\n excerpt: 'Write a search listing from what the page says. It is put in the fields as unsaved changes; nothing is saved until you save.',\n })\n\n const stage = (offer: ConsoleSeoFieldValues, key: string) => {\n proposeValues(offer, key)\n setStaged(key)\n }\n\n return (\n <Box sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }} aria-label=\"SEO assistant\">\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline' }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Write with AI'}\n </Typography>\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" variant=\"caption\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Stack>\n <TextField\n size=\"small\"\n label=\"Target keywords (optional)\"\n placeholder=\"brass desk lamps, dimmable\"\n helperText=\"Comma-separated. Used only where the page is about them, never repeated to rank.\"\n value={keywords}\n onChange={(event) => setKeywords(event.target.value.slice(0, KEYWORDS_MAX_CHARS))}\n disabled={busy || running}\n />\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button size=\"small\" variant=\"outlined\" disabled={busy || running || unnamed || !orgId} onClick={() => void write()}>\n {proposal ? 'Write again' : 'Write SEO'}\n </Button>\n {running ? <CircularProgress size={16} aria-label=\"Writing the listing\" /> : null}\n {unnamed ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Name the product first.'}\n </Typography>\n ) : null}\n </Stack>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n {job?.error && !proposal ? (\n <Alert severity={job.status === 'failed' ? 'error' : 'warning'}>{job.error}</Alert>\n ) : null}\n\n {!proposal && fromAudit && Object.keys(audited).length ? (\n <Stack spacing={1}>\n <ProposedValues heading=\"Proposed by the site audit\" values={audited} fields={fields} />\n <Button\n size=\"small\"\n variant=\"contained\"\n sx={{ alignSelf: 'flex-start' }}\n disabled={staged === `audit:${fromAudit.jobId}`}\n onClick={() => stage(audited, `audit:${fromAudit.jobId}`)}\n >\n {'Put in the fields'}\n </Button>\n </Stack>\n ) : null}\n\n {proposal && job ? (\n <Stack spacing={1}>\n <ProposedValues heading=\"Proposed listing\" values={offered} fields={fields} />\n <Coverage keywords={proposal.keywords} />\n {proposal.notes.map((note) => (\n <Typography key={note} variant=\"caption\" color=\"text.secondary\">\n {note}\n </Typography>\n ))}\n {job.creditsSpent > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Used ${job.creditsSpent} ${job.creditsSpent === 1 ? 'credit' : 'credits'}.`}\n </Typography>\n ) : null}\n <Button\n size=\"small\"\n variant=\"contained\"\n sx={{ alignSelf: 'flex-start' }}\n disabled={!Object.keys(offered).length || staged === job.id}\n onClick={() => stage(offered, job.id)}\n >\n {'Put in the fields'}\n </Button>\n </Stack>\n ) : null}\n\n {staged ? (\n <Alert severity=\"success\">\n {`In the fields as unsaved changes. Review them, then press ${saveWord}.`}\n </Alert>\n ) : null}\n </Stack>\n </Box>\n )\n}\nAiSeoFieldsCard.displayName = 'AiSeoFieldsCard'\n\nexport default AiSeoFieldsCard\n"],"names":["lockdownRefusalText","parseLockdownRefusal","pluginDocsHelp","SEO_LISTING_FIELDS","seoListingFieldCount","seoListingFieldTooLong","authorizedFetch","useHostOrgId","useUser","Alert","Box","Button","Chip","CircularProgress","Link","Stack","TextField","Typography","useCallback","useEffect","useRef","useState","AI_JOB_TERMINAL_STATUSES","aiSeoAuditView","aiSeoFieldsProposalOf","readEventFrames","RECENT_JOBS_READ","PRODUCT_TEXT_MAX_CHARS","KEYWORDS_MAX_CHARS","isTerminal","job","includes","status","aiSeoValuesForEditor","values","fields","hasImage","out","key","value","trim","Coverage","keywords","length","direction","spacing","sx","flexWrap","rowGap","aria-label","map","entry","covered","inTitle","inDescription","size","variant","color","label","keyword","ProposedValues","props","heading","filter","alignItems","flexGrow","wordBreak","AiSeoFieldsCard","subject","hostId","proposeValues","hostOrgId","orgId","undefined","data","user","userRef","current","uid","subjectKind","kind","subjectId","id","verdict","setVerdict","notice","setNotice","setKeywords","busy","setBusy","setJob","fromAudit","setFromAudit","staged","setStaged","watchRef","abort","active","response","encodeURIComponent","payload","json","catch","ok","locked","String","error","jobs","latest","find","proposal","audit","applied","fix","outputs","fixes","Object","keys","jobId","watch","org","controller","AbortController","again","signal","aborted","body","event","write","common","join","inputs","target","screenId","versionId","productId","name","text","description","slice","currentTitle","title","currentDescription","method","headers","JSON","stringify","brief","next","offered","audited","running","Boolean","saveWord","unnamed","help","excerpt","stage","offer","border","borderColor","borderRadius","p","href","rel","placeholder","helperText","onChange","disabled","onClick","severity","alignSelf","notes","note","creditsSpent","displayName"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-theme-proposal-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { HostThemeSource } from '@aglyn/aglyn/app-utils/marketplace-theme'\nimport type { ConsoleHostThemeZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport type { HostThemeScheme } from '@aglyn/shared-data-types'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { themeEditorControl } from '@aglyn/shared-ui-theme/util/theme-editor-fields'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Grid,\n Stack,\n Tab,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Tabs,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n AI_JOB_TERMINAL_STATUSES,\n type AiJobSummary,\n} from '../model/ai-jobs.types'\nimport {\n aiThemeColorToken,\n aiThemeProposalAfter,\n aiThemeStaleChanges,\n readAiThemeProposal,\n type AiThemeControlChange,\n type AiThemeCorrection,\n type AiThemeProposal,\n type AiThemeProposalMode,\n} from '../model/ai-theme-proposal'\nimport { readEventFrames } from './assist-jobs-drawer.component'\n\n/**\n * The theme assistant (AGL-2938), on the site's Theme section through the\n * `hostTheme` zone: a brief in, a theme proposal out, previewed before and\n * after with the editor's own preview, and put in the editor below as\n * unsaved changes when the person chooses. It never writes the theme — the\n * editor's Save does, through the same guarded path every edit takes.\n *\n * It renders nothing until the jobs route has answered for this workspace.\n * The shell has already decided the plan and the member's permission before\n * mounting it, but a released-off feature does not exist, and the release\n * flag is the route's to decide: a 404 there is this card staying absent.\n * The same read lists the site's recent proposals, so one made while the\n * person was elsewhere is still here to preview.\n */\n\n/** How many recent jobs the card reads to find this site's proposals. */\nconst RECENT_JOBS_READ = 20\n\n/** How many of this site's proposals the card offers again. */\nconst RECENT_PROPOSALS_SHOWN = 3\n\nconst BRIEF_MAX_CHARS = 4_000\n\nconst SOURCE_COPY: Record<HostThemeSource, string> = {\n installed:\n 'Saved as your changes on top of the installed theme. The publisher’s version stays as it is and can still take an update.',\n preset:\n 'Saved as your changes on top of the built-in theme, which stays as it is and can be restored.',\n custom:\n 'Saved as your changes on top of this theme of yours. Update it to keep them in the theme itself.',\n default:\n 'Only these values are stored. Everything else keeps following the default theme.',\n}\n\ntype Verdict = 'checking' | 'ready' | 'hidden'\n\nconst isTerminal = (job: AiJobSummary) => AI_JOB_TERMINAL_STATUSES.includes(job.status)\n\n/** The proposal a theme job produced, when it produced one. */\nfunction proposalOf(job: AiJobSummary | null): AiThemeProposal | null {\n const output = job?.outputs.find((entry) => entry.resource === 'theme')\n return output ? readAiThemeProposal(output.proposal) : null\n}\n\n/** A control's label, with its scheme when it has one. */\nfunction changeLabel(change: Pick<AiThemeControlChange, 'control' | 'scheme'>): string {\n return themeEditorControl(change.control).label\n}\n\nfunction Swatch({ value }: { value: string | number | null }) {\n if (value === null) {\n return (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Default'}\n </Typography>\n )\n }\n const text = String(value)\n const color = typeof value === 'string' && /^#[0-9a-f]{6}$/i.test(value)\n return (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n {color ? (\n <Box\n aria-hidden\n sx={{\n width: 14,\n height: 14,\n borderRadius: 0.5,\n border: 1,\n borderColor: 'divider',\n backgroundColor: text,\n flexShrink: 0,\n }}\n />\n ) : null}\n <Typography variant=\"body2\" sx={{ fontFamily: 'monospace', wordBreak: 'break-all' }}>\n {text}\n </Typography>\n </Stack>\n )\n}\n\nfunction correctionText(correction: AiThemeCorrection): string {\n const label = `${changeLabel(correction)} (${correction.scheme})`\n if (correction.reason === 'dark-value') {\n return `${label} set to ${correction.to} so the color keeps reading on the dark background.`\n }\n return (\n `${label} moved to ${correction.to} to reach ${correction.required}:1` +\n (correction.against?.length ? ` against ${correction.against.join(' and ')}` : '') +\n (correction.ratio ? ` (it was ${correction.ratio}:1).` : '.')\n )\n}\n\nexport function AiThemeProposalCard(props: ConsoleHostThemeZoneProps) {\n const { hostId, orgId, theme, themeSource, ThemePreview, proposeDraft } = props\n const { data: user } = useUser()\n // Held in a ref so the reads below key on WHO is signed in, never on the\n // identity of the object that says so.\n const userRef = useRef(user)\n userRef.current = user\n const uid = user?.uid ?? null\n\n const [verdict, setVerdict] = useState<Verdict>('checking')\n const [notice, setNotice] = useState<string | null>(null)\n const [recent, setRecent] = useState<AiJobSummary[]>([])\n const [brief, setBrief] = useState('')\n const [mode, setMode] = useState<AiThemeProposalMode>('modify')\n const [busy, setBusy] = useState(false)\n const [job, setJob] = useState<AiJobSummary | null>(null)\n const [scheme, setScheme] = useState<HostThemeScheme>('light')\n const [applied, setApplied] = useState<string | null>(null)\n const watchRef = useRef<AbortController | null>(null)\n\n useEffect(() => () => watchRef.current?.abort(), [])\n\n useEffect(() => {\n if (!orgId || !hostId || !uid) return\n let active = true\n setVerdict('checking')\n void (async () => {\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=${RECENT_JOBS_READ}`,\n )\n if (!active) return\n // The flag off (404) and the plan without generation (403): the\n // feature is not this workspace's, so the card is not here at all.\n if (response.status === 404 || response.status === 403) {\n setVerdict('hidden')\n return\n }\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'Recent theme proposals could not be loaded.'),\n )\n } else {\n const jobs = (payload?.jobs ?? []) as AiJobSummary[]\n setRecent(\n jobs\n .filter((entry) => entry.kind === 'theme' && entry.hostId === hostId)\n .filter((entry) => proposalOf(entry) !== null)\n .slice(0, RECENT_PROPOSALS_SHOWN),\n )\n }\n setVerdict('ready')\n } catch {\n if (!active) return\n setNotice('Recent theme proposals could not be loaded.')\n setVerdict('ready')\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, hostId, uid])\n\n /** Follows a job through the events route until it settles. */\n const watch = useCallback(async (jobId: string, org: string) => {\n watchRef.current?.abort()\n const controller = new AbortController()\n watchRef.current = controller\n let again = true\n while (again && !controller.signal.aborted) {\n again = false\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs/${encodeURIComponent(jobId)}/events?orgId=${encodeURIComponent(org)}`,\n { signal: controller.signal },\n )\n if (!response.ok || !response.body) return\n await readEventFrames(response.body, (event) => {\n if (event['type'] === 'state') setJob(event['job'] as AiJobSummary)\n else if (event['type'] === 'reconnect') again = true\n })\n } catch {\n return\n }\n }\n }, [])\n\n const propose = useCallback(async () => {\n if (!orgId || !brief.trim()) return\n setBusy(true)\n setNotice(null)\n setApplied(null)\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n kind: 'theme',\n brief: brief.trim(),\n inputs: { mode },\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'The theme proposal could not be started — try again.'),\n )\n return\n }\n const next = payload?.job as AiJobSummary | undefined\n if (!next) return\n setJob(next)\n if (!isTerminal(next)) await watch(next.id, orgId)\n } catch {\n setNotice('The theme proposal could not be started — try again.')\n } finally {\n setBusy(false)\n }\n }, [orgId, hostId, brief, mode, watch])\n\n const proposal = useMemo(() => proposalOf(job), [job])\n const after = useMemo(\n () => (proposal ? aiThemeProposalAfter(theme, proposal) : null),\n [theme, proposal],\n )\n const stale = useMemo(\n () => (proposal ? aiThemeStaleChanges(theme, proposal).length > 0 : false),\n [theme, proposal],\n )\n\n const apply = useCallback(() => {\n if (!job || !after) return\n proposeDraft(after.theme, job.id)\n setApplied(job.id)\n }, [job, after, proposeDraft])\n\n if (verdict !== 'ready') return null\n\n const running = Boolean(job && !isTerminal(job))\n\n return (\n <CardDisplay\n contentGutterX\n contentGutterY\n header=\"Theme assistant\"\n help={pluginDocsHelp('aiThemes', {\n excerpt:\n 'Describe the change and get a proposal for every control the editor has, previewed before and after. Nothing is saved until you save it in the editor.',\n })}\n >\n <Stack spacing={2}>\n <ToggleButtonGroup\n exclusive\n size=\"small\"\n value={mode}\n onChange={(_, value: AiThemeProposalMode | null) => value && setMode(value)}\n aria-label=\"What to propose\"\n >\n <ToggleButton value=\"modify\">{'Change what I describe'}</ToggleButton>\n <ToggleButton value=\"create\">{'Design a new theme'}</ToggleButton>\n </ToggleButtonGroup>\n <TextField\n multiline\n minRows={2}\n fullWidth\n label=\"Describe the change\"\n placeholder=\"Warmer, with a deep green primary and bigger headings on mobile\"\n helperText=\"A hex color in your description is used exactly. Paste a link to borrow a page’s colors.\"\n value={brief}\n onChange={(event) => setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS))}\n disabled={busy}\n />\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button variant=\"contained\" disabled={busy || !brief.trim()} onClick={() => void propose()}>\n {'Propose changes'}\n </Button>\n {running ? <CircularProgress size={18} aria-label=\"Working on a proposal\" /> : null}\n </Stack>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n\n {job && job.error && !proposal ? (\n <Alert severity={job.status === 'failed' ? 'error' : 'warning'}>{job.error}</Alert>\n ) : null}\n\n {!job && recent.length ? (\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{'Recent proposals for this site'}</Typography>\n {recent.map((entry) => (\n <Stack key={entry.id} direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ flexGrow: 1, minWidth: 0 }} noWrap>\n {entry.brief}\n </Typography>\n <Button size=\"small\" onClick={() => setJob(entry)}>\n {'Show'}\n </Button>\n </Stack>\n ))}\n </Stack>\n ) : null}\n\n {proposal && after ? (\n <Stack spacing={2} aria-label=\"Theme proposal\">\n {proposal.summary ? <Typography variant=\"body1\">{proposal.summary}</Typography> : null}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {SOURCE_COPY[proposal.source]}\n </Typography>\n {stale ? (\n <Alert severity=\"info\">\n {'The theme has changed since this proposal was made. The preview and the editor apply it to the theme as it is now.'}\n </Alert>\n ) : null}\n\n {proposal.changes.length || proposal.components.length || proposal.resetComponents ? (\n <Box>\n <ScrollTable size=\"small\" aria-label=\"Proposed changes\">\n <TableHead>\n <TableRow>\n <TableCell>{'Control'}</TableCell>\n <TableCell>{'Now'}</TableCell>\n <TableCell>{'Proposed'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {proposal.changes.map((change) => (\n <TableRow key={`${change.control}:${change.scheme ?? ''}`}>\n <TableCell>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\">{changeLabel(change)}</Typography>\n {aiThemeColorToken(change.control) && change.scheme ? (\n <Chip size=\"small\" variant=\"outlined\" label={change.scheme} />\n ) : null}\n </Stack>\n </TableCell>\n <TableCell>\n <Swatch value={change.before} />\n </TableCell>\n <TableCell>\n <Swatch value={change.value} />\n </TableCell>\n </TableRow>\n ))}\n {proposal.resetComponents ? (\n <TableRow>\n <TableCell>{themeEditorControl('components').label}</TableCell>\n <TableCell>{'This site’s overrides'}</TableCell>\n <TableCell>{'Theme defaults'}</TableCell>\n </TableRow>\n ) : null}\n {proposal.components.map((leaf) => (\n <TableRow key={`${leaf.component}:${leaf.target}:${leaf.slot}:${leaf.media}:${leaf.property}`}>\n <TableCell>\n <Typography variant=\"body2\">\n {[\n leaf.component,\n leaf.target === 'defaultProps' ? 'default' : leaf.slot,\n leaf.property,\n ].join(' · ')}\n </Typography>\n {leaf.media ? <Chip size=\"small\" variant=\"outlined\" label={leaf.media} /> : null}\n </TableCell>\n <TableCell>{'—'}</TableCell>\n <TableCell>\n <Swatch value={typeof leaf.value === 'boolean' ? String(leaf.value) : leaf.value} />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n </Box>\n ) : (\n <Typography variant=\"body2\">{'No changes proposed.'}</Typography>\n )}\n\n {[...proposal.corrections, ...after.corrections].length ? (\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Adjusted for readability'}</Typography>\n {[...proposal.corrections, ...after.corrections].map((correction) => (\n <Typography\n key={`${correction.control}:${correction.scheme}:${correction.to}`}\n variant=\"body2\"\n >\n {correctionText(correction)}\n </Typography>\n ))}\n </Stack>\n ) : null}\n\n {[...proposal.notes, ...after.notes, ...proposal.dropped].length ? (\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Worth knowing'}</Typography>\n {[...proposal.notes, ...after.notes].map((line) => (\n <Typography key={`note:${line}`} variant=\"body2\" color=\"text.secondary\">\n {line}\n </Typography>\n ))}\n {proposal.dropped.map((line) => (\n <Typography key={`dropped:${line}`} variant=\"body2\" color=\"text.secondary\">\n {`Left out — ${line}`}\n </Typography>\n ))}\n </Stack>\n ) : null}\n\n <Tabs\n value={scheme}\n onChange={(_, value: HostThemeScheme) => setScheme(value)}\n aria-label=\"Preview scheme\"\n >\n <Tab label=\"Light\" value=\"light\" />\n <Tab label=\"Dark\" value=\"dark\" />\n </Tabs>\n <Grid container spacing={2}>\n <Grid size={{ xs: 12, md: 6 }}>\n <Typography variant=\"subtitle2\" gutterBottom>\n {'Before'}\n </Typography>\n <Box data-preview=\"before\">\n <ThemePreview theme={theme ?? {}} scheme={scheme} />\n </Box>\n </Grid>\n <Grid size={{ xs: 12, md: 6 }}>\n <Typography variant=\"subtitle2\" gutterBottom>\n {'After'}\n </Typography>\n <Box data-preview=\"after\">\n <ThemePreview theme={after.theme} scheme={scheme} />\n </Box>\n </Grid>\n </Grid>\n\n {applied === job?.id ? (\n <Alert severity=\"success\">\n {'In the editor below as unsaved changes. Review them there, then Save — or Discard changes to go back.'}\n </Alert>\n ) : null}\n <Stack direction=\"row\" spacing={1}>\n <Button variant=\"contained\" onClick={apply} disabled={applied === job?.id}>\n {'Put in the editor'}\n </Button>\n <Button\n onClick={() => {\n setJob(null)\n setApplied(null)\n }}\n >\n {'Dismiss'}\n </Button>\n </Stack>\n </Stack>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nAiThemeProposalCard.displayName = 'AiThemeProposalCard'\n\nexport default AiThemeProposalCard\n"],"names":["lockdownRefusalText","parseLockdownRefusal","pluginDocsHelp","CardDisplay","ScrollTable","themeEditorControl","authorizedFetch","useUser","Alert","Box","Button","Chip","CircularProgress","Grid","Stack","Tab","TableBody","TableCell","TableHead","TableRow","Tabs","TextField","ToggleButton","ToggleButtonGroup","Typography","useCallback","useEffect","useMemo","useRef","useState","AI_JOB_TERMINAL_STATUSES","aiThemeColorToken","aiThemeProposalAfter","aiThemeStaleChanges","readAiThemeProposal","readEventFrames","RECENT_JOBS_READ","RECENT_PROPOSALS_SHOWN","BRIEF_MAX_CHARS","SOURCE_COPY","installed","preset","custom","default","isTerminal","job","includes","status","proposalOf","output","outputs","find","entry","resource","proposal","changeLabel","change","control","label","Swatch","value","variant","color","text","String","test","direction","spacing","sx","alignItems","aria-hidden","width","height","borderRadius","border","borderColor","backgroundColor","flexShrink","fontFamily","wordBreak","correctionText","correction","scheme","reason","to","required","against","length","join","ratio","AiThemeProposalCard","props","hostId","orgId","theme","themeSource","ThemePreview","proposeDraft","data","user","userRef","current","uid","verdict","setVerdict","notice","setNotice","recent","setRecent","brief","setBrief","mode","setMode","busy","setBusy","setJob","setScheme","applied","setApplied","watchRef","abort","active","response","encodeURIComponent","payload","json","catch","ok","locked","error","jobs","filter","kind","slice","watch","jobId","org","controller","AbortController","again","signal","aborted","body","event","propose","trim","method","headers","JSON","stringify","inputs","next","id","after","stale","apply","running","Boolean","contentGutterX","contentGutterY","header","help","excerpt","exclusive","size","onChange","_","aria-label","multiline","minRows","fullWidth","placeholder","helperText","target","disabled","onClick","severity","map","flexGrow","minWidth","noWrap","summary","source","changes","components","resetComponents","before","leaf","component","slot","property","media","corrections","notes","dropped","line","container","xs","md","gutterBottom","data-preview","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-theme-proposal-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { HostThemeSource } from '@aglyn/aglyn/app-utils/site-theme'\nimport type { ConsoleHostThemeZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport type { HostThemeScheme } from '@aglyn/shared-data-types'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { themeEditorControl } from '@aglyn/shared-ui-theme/util/theme-editor-fields'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Grid,\n Stack,\n Tab,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Tabs,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n AI_JOB_TERMINAL_STATUSES,\n type AiJobSummary,\n} from '../model/ai-jobs.types'\nimport {\n aiThemeColorToken,\n aiThemeProposalAfter,\n aiThemeStaleChanges,\n readAiThemeProposal,\n type AiThemeControlChange,\n type AiThemeCorrection,\n type AiThemeProposal,\n type AiThemeProposalMode,\n} from '../model/ai-theme-proposal'\nimport { readEventFrames } from './assist-jobs-drawer.component'\n\n/**\n * The theme assistant (AGL-2938), on the site's Theme section through the\n * `hostTheme` zone: a brief in, a theme proposal out, previewed before and\n * after with the editor's own preview, and put in the editor below as\n * unsaved changes when the person chooses. It never writes the theme — the\n * editor's Save does, through the same guarded path every edit takes.\n *\n * It renders nothing until the jobs route has answered for this workspace.\n * The shell has already decided the plan and the member's permission before\n * mounting it, but a released-off feature does not exist, and the release\n * flag is the route's to decide: a 404 there is this card staying absent.\n * The same read lists the site's recent proposals, so one made while the\n * person was elsewhere is still here to preview.\n */\n\n/** How many recent jobs the card reads to find this site's proposals. */\nconst RECENT_JOBS_READ = 20\n\n/** How many of this site's proposals the card offers again. */\nconst RECENT_PROPOSALS_SHOWN = 3\n\nconst BRIEF_MAX_CHARS = 4_000\n\nconst SOURCE_COPY: Record<HostThemeSource, string> = {\n installed:\n 'Saved as your changes on top of the installed theme. The publisher’s version stays as it is and can still take an update.',\n preset:\n 'Saved as your changes on top of the built-in theme, which stays as it is and can be restored.',\n custom:\n 'Saved as your changes on top of this theme of yours. Update it to keep them in the theme itself.',\n default:\n 'Only these values are stored. Everything else keeps following the default theme.',\n}\n\ntype Verdict = 'checking' | 'ready' | 'hidden'\n\nconst isTerminal = (job: AiJobSummary) => AI_JOB_TERMINAL_STATUSES.includes(job.status)\n\n/** The proposal a theme job produced, when it produced one. */\nfunction proposalOf(job: AiJobSummary | null): AiThemeProposal | null {\n const output = job?.outputs.find((entry) => entry.resource === 'theme')\n return output ? readAiThemeProposal(output.proposal) : null\n}\n\n/** A control's label, with its scheme when it has one. */\nfunction changeLabel(change: Pick<AiThemeControlChange, 'control' | 'scheme'>): string {\n return themeEditorControl(change.control).label\n}\n\nfunction Swatch({ value }: { value: string | number | null }) {\n if (value === null) {\n return (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Default'}\n </Typography>\n )\n }\n const text = String(value)\n const color = typeof value === 'string' && /^#[0-9a-f]{6}$/i.test(value)\n return (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n {color ? (\n <Box\n aria-hidden\n sx={{\n width: 14,\n height: 14,\n borderRadius: 0.5,\n border: 1,\n borderColor: 'divider',\n backgroundColor: text,\n flexShrink: 0,\n }}\n />\n ) : null}\n <Typography variant=\"body2\" sx={{ fontFamily: 'monospace', wordBreak: 'break-all' }}>\n {text}\n </Typography>\n </Stack>\n )\n}\n\nfunction correctionText(correction: AiThemeCorrection): string {\n const label = `${changeLabel(correction)} (${correction.scheme})`\n if (correction.reason === 'dark-value') {\n return `${label} set to ${correction.to} so the color keeps reading on the dark background.`\n }\n return (\n `${label} moved to ${correction.to} to reach ${correction.required}:1` +\n (correction.against?.length ? ` against ${correction.against.join(' and ')}` : '') +\n (correction.ratio ? ` (it was ${correction.ratio}:1).` : '.')\n )\n}\n\nexport function AiThemeProposalCard(props: ConsoleHostThemeZoneProps) {\n const { hostId, orgId, theme, themeSource, ThemePreview, proposeDraft } = props\n const { data: user } = useUser()\n // Held in a ref so the reads below key on WHO is signed in, never on the\n // identity of the object that says so.\n const userRef = useRef(user)\n userRef.current = user\n const uid = user?.uid ?? null\n\n const [verdict, setVerdict] = useState<Verdict>('checking')\n const [notice, setNotice] = useState<string | null>(null)\n const [recent, setRecent] = useState<AiJobSummary[]>([])\n const [brief, setBrief] = useState('')\n const [mode, setMode] = useState<AiThemeProposalMode>('modify')\n const [busy, setBusy] = useState(false)\n const [job, setJob] = useState<AiJobSummary | null>(null)\n const [scheme, setScheme] = useState<HostThemeScheme>('light')\n const [applied, setApplied] = useState<string | null>(null)\n const watchRef = useRef<AbortController | null>(null)\n\n useEffect(() => () => watchRef.current?.abort(), [])\n\n useEffect(() => {\n if (!orgId || !hostId || !uid) return\n let active = true\n setVerdict('checking')\n void (async () => {\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=${RECENT_JOBS_READ}`,\n )\n if (!active) return\n // The flag off (404) and the plan without generation (403): the\n // feature is not this workspace's, so the card is not here at all.\n if (response.status === 404 || response.status === 403) {\n setVerdict('hidden')\n return\n }\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'Recent theme proposals could not be loaded.'),\n )\n } else {\n const jobs = (payload?.jobs ?? []) as AiJobSummary[]\n setRecent(\n jobs\n .filter((entry) => entry.kind === 'theme' && entry.hostId === hostId)\n .filter((entry) => proposalOf(entry) !== null)\n .slice(0, RECENT_PROPOSALS_SHOWN),\n )\n }\n setVerdict('ready')\n } catch {\n if (!active) return\n setNotice('Recent theme proposals could not be loaded.')\n setVerdict('ready')\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, hostId, uid])\n\n /** Follows a job through the events route until it settles. */\n const watch = useCallback(async (jobId: string, org: string) => {\n watchRef.current?.abort()\n const controller = new AbortController()\n watchRef.current = controller\n let again = true\n while (again && !controller.signal.aborted) {\n again = false\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs/${encodeURIComponent(jobId)}/events?orgId=${encodeURIComponent(org)}`,\n { signal: controller.signal },\n )\n if (!response.ok || !response.body) return\n await readEventFrames(response.body, (event) => {\n if (event['type'] === 'state') setJob(event['job'] as AiJobSummary)\n else if (event['type'] === 'reconnect') again = true\n })\n } catch {\n return\n }\n }\n }, [])\n\n const propose = useCallback(async () => {\n if (!orgId || !brief.trim()) return\n setBusy(true)\n setNotice(null)\n setApplied(null)\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n kind: 'theme',\n brief: brief.trim(),\n inputs: { mode },\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'The theme proposal could not be started — try again.'),\n )\n return\n }\n const next = payload?.job as AiJobSummary | undefined\n if (!next) return\n setJob(next)\n if (!isTerminal(next)) await watch(next.id, orgId)\n } catch {\n setNotice('The theme proposal could not be started — try again.')\n } finally {\n setBusy(false)\n }\n }, [orgId, hostId, brief, mode, watch])\n\n const proposal = useMemo(() => proposalOf(job), [job])\n const after = useMemo(\n () => (proposal ? aiThemeProposalAfter(theme, proposal) : null),\n [theme, proposal],\n )\n const stale = useMemo(\n () => (proposal ? aiThemeStaleChanges(theme, proposal).length > 0 : false),\n [theme, proposal],\n )\n\n const apply = useCallback(() => {\n if (!job || !after) return\n proposeDraft(after.theme, job.id)\n setApplied(job.id)\n }, [job, after, proposeDraft])\n\n if (verdict !== 'ready') return null\n\n const running = Boolean(job && !isTerminal(job))\n\n return (\n <CardDisplay\n contentGutterX\n contentGutterY\n header=\"Theme assistant\"\n help={pluginDocsHelp('aiThemes', {\n excerpt:\n 'Describe the change and get a proposal for every control the editor has, previewed before and after. Nothing is saved until you save it in the editor.',\n })}\n >\n <Stack spacing={2}>\n <ToggleButtonGroup\n exclusive\n size=\"small\"\n value={mode}\n onChange={(_, value: AiThemeProposalMode | null) => value && setMode(value)}\n aria-label=\"What to propose\"\n >\n <ToggleButton value=\"modify\">{'Change what I describe'}</ToggleButton>\n <ToggleButton value=\"create\">{'Design a new theme'}</ToggleButton>\n </ToggleButtonGroup>\n <TextField\n multiline\n minRows={2}\n fullWidth\n label=\"Describe the change\"\n placeholder=\"Warmer, with a deep green primary and bigger headings on mobile\"\n helperText=\"A hex color in your description is used exactly. Paste a link to borrow a page’s colors.\"\n value={brief}\n onChange={(event) => setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS))}\n disabled={busy}\n />\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button variant=\"contained\" disabled={busy || !brief.trim()} onClick={() => void propose()}>\n {'Propose changes'}\n </Button>\n {running ? <CircularProgress size={18} aria-label=\"Working on a proposal\" /> : null}\n </Stack>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n\n {job && job.error && !proposal ? (\n <Alert severity={job.status === 'failed' ? 'error' : 'warning'}>{job.error}</Alert>\n ) : null}\n\n {!job && recent.length ? (\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{'Recent proposals for this site'}</Typography>\n {recent.map((entry) => (\n <Stack key={entry.id} direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ flexGrow: 1, minWidth: 0 }} noWrap>\n {entry.brief}\n </Typography>\n <Button size=\"small\" onClick={() => setJob(entry)}>\n {'Show'}\n </Button>\n </Stack>\n ))}\n </Stack>\n ) : null}\n\n {proposal && after ? (\n <Stack spacing={2} aria-label=\"Theme proposal\">\n {proposal.summary ? <Typography variant=\"body1\">{proposal.summary}</Typography> : null}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {SOURCE_COPY[proposal.source]}\n </Typography>\n {stale ? (\n <Alert severity=\"info\">\n {'The theme has changed since this proposal was made. The preview and the editor apply it to the theme as it is now.'}\n </Alert>\n ) : null}\n\n {proposal.changes.length || proposal.components.length || proposal.resetComponents ? (\n <Box>\n <ScrollTable size=\"small\" aria-label=\"Proposed changes\">\n <TableHead>\n <TableRow>\n <TableCell>{'Control'}</TableCell>\n <TableCell>{'Now'}</TableCell>\n <TableCell>{'Proposed'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {proposal.changes.map((change) => (\n <TableRow key={`${change.control}:${change.scheme ?? ''}`}>\n <TableCell>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\">{changeLabel(change)}</Typography>\n {aiThemeColorToken(change.control) && change.scheme ? (\n <Chip size=\"small\" variant=\"outlined\" label={change.scheme} />\n ) : null}\n </Stack>\n </TableCell>\n <TableCell>\n <Swatch value={change.before} />\n </TableCell>\n <TableCell>\n <Swatch value={change.value} />\n </TableCell>\n </TableRow>\n ))}\n {proposal.resetComponents ? (\n <TableRow>\n <TableCell>{themeEditorControl('components').label}</TableCell>\n <TableCell>{'This site’s overrides'}</TableCell>\n <TableCell>{'Theme defaults'}</TableCell>\n </TableRow>\n ) : null}\n {proposal.components.map((leaf) => (\n <TableRow key={`${leaf.component}:${leaf.target}:${leaf.slot}:${leaf.media}:${leaf.property}`}>\n <TableCell>\n <Typography variant=\"body2\">\n {[\n leaf.component,\n leaf.target === 'defaultProps' ? 'default' : leaf.slot,\n leaf.property,\n ].join(' · ')}\n </Typography>\n {leaf.media ? <Chip size=\"small\" variant=\"outlined\" label={leaf.media} /> : null}\n </TableCell>\n <TableCell>{'—'}</TableCell>\n <TableCell>\n <Swatch value={typeof leaf.value === 'boolean' ? String(leaf.value) : leaf.value} />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n </Box>\n ) : (\n <Typography variant=\"body2\">{'No changes proposed.'}</Typography>\n )}\n\n {[...proposal.corrections, ...after.corrections].length ? (\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Adjusted for readability'}</Typography>\n {[...proposal.corrections, ...after.corrections].map((correction) => (\n <Typography\n key={`${correction.control}:${correction.scheme}:${correction.to}`}\n variant=\"body2\"\n >\n {correctionText(correction)}\n </Typography>\n ))}\n </Stack>\n ) : null}\n\n {[...proposal.notes, ...after.notes, ...proposal.dropped].length ? (\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Worth knowing'}</Typography>\n {[...proposal.notes, ...after.notes].map((line) => (\n <Typography key={`note:${line}`} variant=\"body2\" color=\"text.secondary\">\n {line}\n </Typography>\n ))}\n {proposal.dropped.map((line) => (\n <Typography key={`dropped:${line}`} variant=\"body2\" color=\"text.secondary\">\n {`Left out — ${line}`}\n </Typography>\n ))}\n </Stack>\n ) : null}\n\n <Tabs\n value={scheme}\n onChange={(_, value: HostThemeScheme) => setScheme(value)}\n aria-label=\"Preview scheme\"\n >\n <Tab label=\"Light\" value=\"light\" />\n <Tab label=\"Dark\" value=\"dark\" />\n </Tabs>\n <Grid container spacing={2}>\n <Grid size={{ xs: 12, md: 6 }}>\n <Typography variant=\"subtitle2\" gutterBottom>\n {'Before'}\n </Typography>\n <Box data-preview=\"before\">\n <ThemePreview theme={theme ?? {}} scheme={scheme} />\n </Box>\n </Grid>\n <Grid size={{ xs: 12, md: 6 }}>\n <Typography variant=\"subtitle2\" gutterBottom>\n {'After'}\n </Typography>\n <Box data-preview=\"after\">\n <ThemePreview theme={after.theme} scheme={scheme} />\n </Box>\n </Grid>\n </Grid>\n\n {applied === job?.id ? (\n <Alert severity=\"success\">\n {'In the editor below as unsaved changes. Review them there, then Save — or Discard changes to go back.'}\n </Alert>\n ) : null}\n <Stack direction=\"row\" spacing={1}>\n <Button variant=\"contained\" onClick={apply} disabled={applied === job?.id}>\n {'Put in the editor'}\n </Button>\n <Button\n onClick={() => {\n setJob(null)\n setApplied(null)\n }}\n >\n {'Dismiss'}\n </Button>\n </Stack>\n </Stack>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nAiThemeProposalCard.displayName = 'AiThemeProposalCard'\n\nexport default AiThemeProposalCard\n"],"names":["lockdownRefusalText","parseLockdownRefusal","pluginDocsHelp","CardDisplay","ScrollTable","themeEditorControl","authorizedFetch","useUser","Alert","Box","Button","Chip","CircularProgress","Grid","Stack","Tab","TableBody","TableCell","TableHead","TableRow","Tabs","TextField","ToggleButton","ToggleButtonGroup","Typography","useCallback","useEffect","useMemo","useRef","useState","AI_JOB_TERMINAL_STATUSES","aiThemeColorToken","aiThemeProposalAfter","aiThemeStaleChanges","readAiThemeProposal","readEventFrames","RECENT_JOBS_READ","RECENT_PROPOSALS_SHOWN","BRIEF_MAX_CHARS","SOURCE_COPY","installed","preset","custom","default","isTerminal","job","includes","status","proposalOf","output","outputs","find","entry","resource","proposal","changeLabel","change","control","label","Swatch","value","variant","color","text","String","test","direction","spacing","sx","alignItems","aria-hidden","width","height","borderRadius","border","borderColor","backgroundColor","flexShrink","fontFamily","wordBreak","correctionText","correction","scheme","reason","to","required","against","length","join","ratio","AiThemeProposalCard","props","hostId","orgId","theme","themeSource","ThemePreview","proposeDraft","data","user","userRef","current","uid","verdict","setVerdict","notice","setNotice","recent","setRecent","brief","setBrief","mode","setMode","busy","setBusy","setJob","setScheme","applied","setApplied","watchRef","abort","active","response","encodeURIComponent","payload","json","catch","ok","locked","error","jobs","filter","kind","slice","watch","jobId","org","controller","AbortController","again","signal","aborted","body","event","propose","trim","method","headers","JSON","stringify","inputs","next","id","after","stale","apply","running","Boolean","contentGutterX","contentGutterY","header","help","excerpt","exclusive","size","onChange","_","aria-label","multiline","minRows","fullWidth","placeholder","helperText","target","disabled","onClick","severity","map","flexGrow","minWidth","noWrap","summary","source","changes","components","resetComponents","before","leaf","component","slot","property","media","corrections","notes","dropped","line","container","xs","md","gutterBottom","data-preview","displayName"],"mappings":"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"}
|
|
@@ -18,6 +18,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
18
18
|
import { trackEvent } from "@aglyn/aglyn/app-utils/analytics-events";
|
|
19
19
|
import { buildRoute, Route } from "@aglyn/aglyn/app-utils/console-routes";
|
|
20
20
|
import { formatBytes } from "@aglyn/aglyn/app-utils/measure-node-map";
|
|
21
|
+
import { pluginRecordHref, pluginRecordListHref } from "@aglyn/aglyn/plugin-manager/plugin-record-routes";
|
|
21
22
|
import { AI_JOB_TERMINAL_STATUSES } from "../model/ai-jobs.types.js";
|
|
22
23
|
import { mdiChevronDown, mdiChevronUp } from "@aglyn/shared-data-mdi";
|
|
23
24
|
import { AppLink, MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
@@ -91,15 +92,20 @@ const STATUS_COLOR = {
|
|
|
91
92
|
return output.versionId ? `${base}/versions/${output.versionId}/besigner` : base;
|
|
92
93
|
}
|
|
93
94
|
if (output.resource === 'product') {
|
|
94
|
-
|
|
95
|
+
// The catalog a drafted product lands in: the commerce plugin's address
|
|
96
|
+
// for it (AGL-3080), or no link where commerce is not loaded.
|
|
97
|
+
return pluginRecordListHref('product', {
|
|
98
|
+
orgSlug,
|
|
99
|
+
host
|
|
100
|
+
});
|
|
95
101
|
}
|
|
96
102
|
if (output.resource === 'workflow') {
|
|
97
103
|
// A drafted automation is an action (AGL-2919), listed switched off on
|
|
98
|
-
// the
|
|
99
|
-
return
|
|
104
|
+
// the workflows plugin's list of actions, or no link where it is not loaded.
|
|
105
|
+
return pluginRecordListHref('action', {
|
|
100
106
|
orgSlug,
|
|
101
107
|
host
|
|
102
|
-
})
|
|
108
|
+
});
|
|
103
109
|
}
|
|
104
110
|
if (output.resource === 'theme') {
|
|
105
111
|
return buildRoute(Route.HOST_SETUP_THEME, {
|
|
@@ -108,21 +114,21 @@ const STATUS_COLOR = {
|
|
|
108
114
|
});
|
|
109
115
|
}
|
|
110
116
|
if (output.resource === 'form') {
|
|
111
|
-
// A new form has no version for the besigner to open: its own page
|
|
112
|
-
// the first one, and holds the
|
|
113
|
-
|
|
117
|
+
// A new form has no version for the besigner to open: its own page — the
|
|
118
|
+
// forms plugin's address for it — mints the first one, and holds the
|
|
119
|
+
// routing and consent it declares.
|
|
120
|
+
return pluginRecordHref('form', {
|
|
114
121
|
orgSlug,
|
|
115
|
-
host
|
|
116
|
-
|
|
117
|
-
});
|
|
122
|
+
host
|
|
123
|
+
}, output.id);
|
|
118
124
|
}
|
|
119
125
|
if (output.resource === 'campaign') {
|
|
120
|
-
// A campaign's page
|
|
121
|
-
|
|
126
|
+
// A campaign's page is the marketing plugin's, under the site: its
|
|
127
|
+
// address for the campaign (AGL-3080), or no link where it is not loaded.
|
|
128
|
+
return pluginRecordHref('campaign', {
|
|
122
129
|
orgSlug,
|
|
123
|
-
host
|
|
124
|
-
|
|
125
|
-
})}/campaigns/${output.id}`;
|
|
130
|
+
host
|
|
131
|
+
}, output.id);
|
|
126
132
|
}
|
|
127
133
|
return null;
|
|
128
134
|
}
|
|
@@ -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 { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { formatBytes } from '@aglyn/aglyn/app-utils/measure-node-map'\nimport {\n AI_JOB_TERMINAL_STATUSES,\n type AiJobOutput,\n type AiJobStatus,\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 { AppLink, 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 type { AiInsightSurface } from '../model/ai-insight'\nimport { AiPageBriefDialog } from './ai-page-brief-dialog.component'\nimport { AiInsightDialog } from './ai-insight-dialog.component'\nimport { AiJobPlan } from './ai-job-plan.component'\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 * 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\nconst STATUS_LABEL: Record<AiJobStatus, string> = {\n queued: 'Queued',\n running: 'Running',\n needs_input: 'Needs attention',\n needs_review: 'Needs review',\n done: 'Done',\n failed: 'Failed',\n canceled: 'Canceled',\n}\n\nconst STATUS_COLOR: Record<\n AiJobStatus,\n 'default' | 'primary' | 'success' | 'error' | 'warning'\n> = {\n queued: 'default',\n running: 'primary',\n needs_input: 'warning',\n needs_review: 'warning',\n done: 'success',\n failed: 'error',\n canceled: 'default',\n}\n\n/** The besigner segment each versioned resource lives under. */\nconst BESIGNER_SEGMENT: Partial<Record<AiJobOutput['resource'], string>> = {\n screen: 'screens',\n reusableComponent: 'components',\n layout: 'layouts',\n template: 'templates',\n // An email design is a screen: it opens in the screen besigner, as the\n // Emails page's own Edit design does.\n emailScreen: 'screens',\n}\n\n/**\n * Where \"open draft\" goes, or `null` when the output has no page of its\n * own — a `text` output carries its copy on the job and is shown inline.\n * A versioned resource opens in the besigner on the version the job wrote;\n * one the console lists without a detail page opens its list; a `theme`\n * proposal opens the site's Theme section, where it is put in the editor.\n *\n * A console URL names a site by its SUBDOMAIN: the `[host]` segment resolves\n * through the member's host projection by subdomain, never by document id.\n * So an output that carries no `hostSubdomain` gets no link rather than one\n * that opens no site.\n */\nexport function aiJobOutputHref(output: AiJobOutput, orgSlug: string): string | null {\n const host = output.hostSubdomain\n if (!orgSlug || !host) return null\n const segment = BESIGNER_SEGMENT[output.resource]\n if (segment) {\n const base = `/${orgSlug}/hosts/${host}/${segment}/${output.id}`\n return output.versionId ? `${base}/versions/${output.versionId}/besigner` : base\n }\n if (output.resource === 'product') {\n return `/${orgSlug}/hosts/${host}/products`\n }\n if (output.resource === 'workflow') {\n // A drafted automation is an action (AGL-2919), listed switched off on\n // the Automation page's Actions.\n return `${buildRoute(Route.HOST_AUTOMATION, { orgSlug, host })}/actions`\n }\n if (output.resource === 'theme') {\n return buildRoute(Route.HOST_SETUP_THEME, { orgSlug, host })\n }\n if (output.resource === 'form') {\n // A new form has no version for the besigner to open: its own page mints\n // the first one, and holds the routing and consent it declares.\n return buildRoute(Route.FORM_DETAILS, { orgSlug, host, formId: output.id })\n }\n if (output.resource === 'campaign') {\n // A campaign's page belongs to the Marketing console, under the site.\n return `${buildRoute(Route.HOST_PLUGIN, { orgSlug, host, pluginSlug: 'marketing' })}/campaigns/${output.id}`\n }\n return null\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/**\n * `data:` frames out of an SSE body, one parsed event per frame. Shared with\n * every panel that watches a job through the events route.\n */\nexport async function readEventFrames(\n body: ReadableStream<Uint8Array>,\n onEvent: (event: Record<string, unknown>) => void,\n): Promise<void> {\n const reader = body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n const frames = buffer.split('\\n\\n')\n buffer = frames.pop() ?? ''\n for (const frame of frames) {\n const line = frame.split('\\n').find((part) => part.startsWith('data: '))\n if (!line) continue\n try {\n onEvent(JSON.parse(line.slice('data: '.length)))\n } catch {\n // A torn frame is dropped; the next state event carries the whole job.\n }\n }\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\nexport function AssistJobsDrawer({\n orgId,\n org,\n orgReady,\n orgSlug,\n user,\n visible,\n isStaff = false,\n hostId,\n insight,\n}: AssistJobsDrawerProps): JSX.Element | null {\n const entitled = orgReady && checkEntitlement(org as never, 'aiGenerative')\n const [expanded, setExpanded] = useState(false)\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 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 }\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 watchingRef.current = null\n }, [orgId])\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 const cancel = useCallback(\n async (jobId: string) => {\n if (!orgId) return\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs/${encodeURIComponent(jobId)}/cancel`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId }),\n },\n )\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n setNotice(String(payload?.error ?? 'The job could not be canceled — try again.'))\n return\n }\n if (payload?.job) patchJob(payload.job as AiJobSummary)\n } catch {\n setNotice('The job could not be canceled — try again.')\n }\n },\n [orgId, user, patchJob],\n )\n\n /** The job whose resume is in flight, so its button cannot send twice. */\n const [resuming, setResuming] = useState<string | 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) => {\n if (!orgId) return\n setResuming(job.id)\n setNotice(null)\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs/${encodeURIComponent(job.id)}/resume`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, hostId: job.hostId }),\n },\n )\n const payload = await response.json().catch(() => null)\n if (payload?.job) patchJob(payload.job as AiJobSummary)\n if (!response.ok) {\n setNotice(String(payload?.error ?? 'The job could not be resumed — try again.'))\n }\n } catch {\n setNotice('The job could not be resumed — try again.')\n } finally {\n setResuming(null)\n }\n },\n [orgId, user, patchJob],\n )\n\n if (!visible || !orgId || !entitled) return null\n\n const active = jobs.filter((job) => !AI_JOB_TERMINAL_STATUSES.includes(job.status)).length\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 {active > 0 && (\n <Chip size=\"small\" color=\"primary\" label={`${active} running`} sx={{ ml: 1 }} />\n )}\n </Typography>\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={() => setExpanded((prior) => !prior)}\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 <Stack spacing={1} sx={{ mt: 1 }} role=\"list\" aria-label=\"AI jobs\">\n {jobs.map((job) => {\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 return (\n <Box\n key={job.id}\n role=\"listitem\"\n sx={{ borderRadius: 1, bgcolor: 'action.hover', px: 1.5, py: 1 }}\n >\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Chip size=\"small\" variant=\"outlined\" label={job.kind} />\n <Chip size=\"small\" color={STATUS_COLOR[job.status]} label={STATUS_LABEL[job.status]} />\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ flexGrow: 1 }}>\n {stepsDone}/{job.steps.length} steps\n {current ? ` — ${current.name}` : ''}\n {job.creditsSpent > 0 ? ` · ${job.creditsSpent} credits` : ''}\n </Typography>\n {!terminal && (\n <Button size=\"small\" onClick={() => void cancel(job.id)}>\n 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 {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) => void resume(target)}\n busy={resuming === job.id}\n staff={isStaff}\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 />\n )}\n </Box>\n )\n}\n\nexport default AssistJobsDrawer\n"],"names":["checkEntitlement","trackEvent","buildRoute","Route","formatBytes","AI_JOB_TERMINAL_STATUSES","mdiChevronDown","mdiChevronUp","AppLink","MdiIcon","authorizedFetch","Box","Button","Chip","CircularProgress","Collapse","IconButton","Stack","Typography","useCallback","useEffect","useRef","useState","AiPageBriefDialog","AiInsightDialog","AiJobPlan","LIST_LIMIT","BRIEF_PREVIEW_CHARS","STATUS_LABEL","queued","running","needs_input","needs_review","done","failed","canceled","STATUS_COLOR","BESIGNER_SEGMENT","screen","reusableComponent","layout","template","emailScreen","aiJobOutputHref","output","orgSlug","host","hostSubdomain","segment","resource","base","id","versionId","HOST_AUTOMATION","HOST_SETUP_THEME","FORM_DETAILS","formId","HOST_PLUGIN","pluginSlug","aiJobNavigationProposal","navigation","proposal","label","trim","readEventFrames","body","onEvent","reader","getReader","decoder","TextDecoder","buffer","frames","value","read","decode","stream","split","pop","frame","line","find","part","startsWith","JSON","parse","slice","length","AssistJobsDrawer","orgId","org","orgReady","user","visible","isStaff","hostId","insight","entitled","expanded","setExpanded","jobs","setJobs","loading","setLoading","notice","setNotice","describing","setDescribing","insightOpen","setInsightOpen","trackedRef","Set","watchingRef","patchJob","next","prior","index","findIndex","job","copy","includes","status","current","has","add","kind","credits","creditsSpent","load","response","encodeURIComponent","ok","payload","json","catch","String","error","watched","watchedId","controller","AbortController","watch","again","signal","aborted","event","filter","finally","abort","cancel","jobId","method","headers","stringify","resuming","setResuming","resume","active","sx","mb","borderBottom","borderColor","pb","direction","spacing","alignItems","variant","flexGrow","size","color","ml","onClick","aria-label","aria-expanded","path","fontSize","in","unmountOnExit","component","mt","role","map","stepsDone","steps","step","terminal","borderRadius","bgcolor","px","py","name","wordBreak","brief","outputs","href","componentVariant","text","whiteSpace","fontFamily","m","totalBytes","note","onResume","target","busy","staff","open","onClose","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 { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { formatBytes } from '@aglyn/aglyn/app-utils/measure-node-map'\nimport {\n pluginRecordHref,\n pluginRecordListHref,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport {\n AI_JOB_TERMINAL_STATUSES,\n type AiJobOutput,\n type AiJobStatus,\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 { AppLink, 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 type { AiInsightSurface } from '../model/ai-insight'\nimport { AiPageBriefDialog } from './ai-page-brief-dialog.component'\nimport { AiInsightDialog } from './ai-insight-dialog.component'\nimport { AiJobPlan } from './ai-job-plan.component'\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 * 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\nconst STATUS_LABEL: Record<AiJobStatus, string> = {\n queued: 'Queued',\n running: 'Running',\n needs_input: 'Needs attention',\n needs_review: 'Needs review',\n done: 'Done',\n failed: 'Failed',\n canceled: 'Canceled',\n}\n\nconst STATUS_COLOR: Record<\n AiJobStatus,\n 'default' | 'primary' | 'success' | 'error' | 'warning'\n> = {\n queued: 'default',\n running: 'primary',\n needs_input: 'warning',\n needs_review: 'warning',\n done: 'success',\n failed: 'error',\n canceled: 'default',\n}\n\n/** The besigner segment each versioned resource lives under. */\nconst BESIGNER_SEGMENT: Partial<Record<AiJobOutput['resource'], string>> = {\n screen: 'screens',\n reusableComponent: 'components',\n layout: 'layouts',\n template: 'templates',\n // An email design is a screen: it opens in the screen besigner, as the\n // Emails page's own Edit design does.\n emailScreen: 'screens',\n}\n\n/**\n * Where \"open draft\" goes, or `null` when the output has no page of its\n * own — a `text` output carries its copy on the job and is shown inline.\n * A versioned resource opens in the besigner on the version the job wrote;\n * one the console lists without a detail page opens its list; a `theme`\n * proposal opens the site's Theme section, where it is put in the editor.\n *\n * A console URL names a site by its SUBDOMAIN: the `[host]` segment resolves\n * through the member's host projection by subdomain, never by document id.\n * So an output that carries no `hostSubdomain` gets no link rather than one\n * that opens no site.\n */\nexport function aiJobOutputHref(output: AiJobOutput, orgSlug: string): string | null {\n const host = output.hostSubdomain\n if (!orgSlug || !host) return null\n const segment = BESIGNER_SEGMENT[output.resource]\n if (segment) {\n const base = `/${orgSlug}/hosts/${host}/${segment}/${output.id}`\n return output.versionId ? `${base}/versions/${output.versionId}/besigner` : base\n }\n if (output.resource === 'product') {\n // The catalog a drafted product lands in: the commerce plugin's address\n // for it (AGL-3080), or no link where commerce is not loaded.\n return pluginRecordListHref('product', { orgSlug, host })\n }\n if (output.resource === 'workflow') {\n // A drafted automation is an action (AGL-2919), listed switched off on\n // the workflows plugin's list of actions, or no link where it is not loaded.\n return pluginRecordListHref('action', { orgSlug, host })\n }\n if (output.resource === 'theme') {\n return buildRoute(Route.HOST_SETUP_THEME, { orgSlug, host })\n }\n if (output.resource === 'form') {\n // A new form has no version for the besigner to open: its own page — the\n // forms plugin's address for it — mints the first one, and holds the\n // routing and consent it declares.\n return pluginRecordHref('form', { orgSlug, host }, output.id)\n }\n if (output.resource === 'campaign') {\n // A campaign's page is the marketing plugin's, under the site: its\n // address for the campaign (AGL-3080), or no link where it is not loaded.\n return pluginRecordHref('campaign', { orgSlug, host }, output.id)\n }\n return null\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/**\n * `data:` frames out of an SSE body, one parsed event per frame. Shared with\n * every panel that watches a job through the events route.\n */\nexport async function readEventFrames(\n body: ReadableStream<Uint8Array>,\n onEvent: (event: Record<string, unknown>) => void,\n): Promise<void> {\n const reader = body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n const frames = buffer.split('\\n\\n')\n buffer = frames.pop() ?? ''\n for (const frame of frames) {\n const line = frame.split('\\n').find((part) => part.startsWith('data: '))\n if (!line) continue\n try {\n onEvent(JSON.parse(line.slice('data: '.length)))\n } catch {\n // A torn frame is dropped; the next state event carries the whole job.\n }\n }\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\nexport function AssistJobsDrawer({\n orgId,\n org,\n orgReady,\n orgSlug,\n user,\n visible,\n isStaff = false,\n hostId,\n insight,\n}: AssistJobsDrawerProps): JSX.Element | null {\n const entitled = orgReady && checkEntitlement(org as never, 'aiGenerative')\n const [expanded, setExpanded] = useState(false)\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 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 }\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 watchingRef.current = null\n }, [orgId])\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 const cancel = useCallback(\n async (jobId: string) => {\n if (!orgId) return\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs/${encodeURIComponent(jobId)}/cancel`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId }),\n },\n )\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n setNotice(String(payload?.error ?? 'The job could not be canceled — try again.'))\n return\n }\n if (payload?.job) patchJob(payload.job as AiJobSummary)\n } catch {\n setNotice('The job could not be canceled — try again.')\n }\n },\n [orgId, user, patchJob],\n )\n\n /** The job whose resume is in flight, so its button cannot send twice. */\n const [resuming, setResuming] = useState<string | 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) => {\n if (!orgId) return\n setResuming(job.id)\n setNotice(null)\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs/${encodeURIComponent(job.id)}/resume`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, hostId: job.hostId }),\n },\n )\n const payload = await response.json().catch(() => null)\n if (payload?.job) patchJob(payload.job as AiJobSummary)\n if (!response.ok) {\n setNotice(String(payload?.error ?? 'The job could not be resumed — try again.'))\n }\n } catch {\n setNotice('The job could not be resumed — try again.')\n } finally {\n setResuming(null)\n }\n },\n [orgId, user, patchJob],\n )\n\n if (!visible || !orgId || !entitled) return null\n\n const active = jobs.filter((job) => !AI_JOB_TERMINAL_STATUSES.includes(job.status)).length\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 {active > 0 && (\n <Chip size=\"small\" color=\"primary\" label={`${active} running`} sx={{ ml: 1 }} />\n )}\n </Typography>\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={() => setExpanded((prior) => !prior)}\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 <Stack spacing={1} sx={{ mt: 1 }} role=\"list\" aria-label=\"AI jobs\">\n {jobs.map((job) => {\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 return (\n <Box\n key={job.id}\n role=\"listitem\"\n sx={{ borderRadius: 1, bgcolor: 'action.hover', px: 1.5, py: 1 }}\n >\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Chip size=\"small\" variant=\"outlined\" label={job.kind} />\n <Chip size=\"small\" color={STATUS_COLOR[job.status]} label={STATUS_LABEL[job.status]} />\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ flexGrow: 1 }}>\n {stepsDone}/{job.steps.length} steps\n {current ? ` — ${current.name}` : ''}\n {job.creditsSpent > 0 ? ` · ${job.creditsSpent} credits` : ''}\n </Typography>\n {!terminal && (\n <Button size=\"small\" onClick={() => void cancel(job.id)}>\n 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 {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) => void resume(target)}\n busy={resuming === job.id}\n staff={isStaff}\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 />\n )}\n </Box>\n )\n}\n\nexport default AssistJobsDrawer\n"],"names":["checkEntitlement","trackEvent","buildRoute","Route","formatBytes","pluginRecordHref","pluginRecordListHref","AI_JOB_TERMINAL_STATUSES","mdiChevronDown","mdiChevronUp","AppLink","MdiIcon","authorizedFetch","Box","Button","Chip","CircularProgress","Collapse","IconButton","Stack","Typography","useCallback","useEffect","useRef","useState","AiPageBriefDialog","AiInsightDialog","AiJobPlan","LIST_LIMIT","BRIEF_PREVIEW_CHARS","STATUS_LABEL","queued","running","needs_input","needs_review","done","failed","canceled","STATUS_COLOR","BESIGNER_SEGMENT","screen","reusableComponent","layout","template","emailScreen","aiJobOutputHref","output","orgSlug","host","hostSubdomain","segment","resource","base","id","versionId","HOST_SETUP_THEME","aiJobNavigationProposal","navigation","proposal","label","trim","readEventFrames","body","onEvent","reader","getReader","decoder","TextDecoder","buffer","frames","value","read","decode","stream","split","pop","frame","line","find","part","startsWith","JSON","parse","slice","length","AssistJobsDrawer","orgId","org","orgReady","user","visible","isStaff","hostId","insight","entitled","expanded","setExpanded","jobs","setJobs","loading","setLoading","notice","setNotice","describing","setDescribing","insightOpen","setInsightOpen","trackedRef","Set","watchingRef","patchJob","next","prior","index","findIndex","job","copy","includes","status","current","has","add","kind","credits","creditsSpent","load","response","encodeURIComponent","ok","payload","json","catch","String","error","watched","watchedId","controller","AbortController","watch","again","signal","aborted","event","filter","finally","abort","cancel","jobId","method","headers","stringify","resuming","setResuming","resume","active","sx","mb","borderBottom","borderColor","pb","direction","spacing","alignItems","variant","flexGrow","size","color","ml","onClick","aria-label","aria-expanded","path","fontSize","in","unmountOnExit","component","mt","role","map","stepsDone","steps","step","terminal","borderRadius","bgcolor","px","py","name","wordBreak","brief","outputs","href","componentVariant","text","whiteSpace","fontFamily","m","totalBytes","note","onResume","target","busy","staff","open","onClose","surface","uid"],"mappings":"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"}
|