@aglyn/plugins-ai 1.0.0-beta.234 → 1.0.0-beta.236
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/activity/ai-activity-actions.d.ts +25 -0
- package/src/lib/activity/ai-activity-actions.js +85 -4
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-brief-dialog.component.js +46 -2
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-campaign-create.component.js +1 -1
- package/src/lib/components/ai-campaign-create.component.js.map +1 -1
- package/src/lib/components/ai-credits-card.component.js +3 -3
- package/src/lib/components/ai-credits-card.component.js.map +1 -1
- package/src/lib/components/ai-crm-email-draft.component.js +1 -1
- package/src/lib/components/ai-crm-email-draft.component.js.map +1 -1
- package/src/lib/components/ai-crm-import-mapping.component.js +1 -1
- package/src/lib/components/ai-crm-import-mapping.component.js.map +1 -1
- package/src/lib/components/ai-crm-record-card.component.js +1 -1
- package/src/lib/components/ai-crm-record-card.component.js.map +1 -1
- package/src/lib/components/ai-describe-automation.component.js +4 -2
- package/src/lib/components/ai-describe-automation.component.js.map +1 -1
- package/src/lib/components/ai-describe-org-automation.component.js +1 -1
- package/src/lib/components/ai-describe-org-automation.component.js.map +1 -1
- package/src/lib/components/ai-experiment-cards.component.js +2 -2
- package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
- package/src/lib/components/ai-explain-automation.component.js +1 -1
- package/src/lib/components/ai-explain-automation.component.js.map +1 -1
- package/src/lib/components/ai-insight-card.component.js +3 -1
- package/src/lib/components/ai-insight-card.component.js.map +1 -1
- package/src/lib/components/ai-jobs-list-page.component.d.ts +6 -1
- package/src/lib/components/ai-jobs-list-page.component.js +15 -2
- package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
- package/src/lib/components/ai-logic.component.js +5 -3
- package/src/lib/components/ai-logic.component.js.map +1 -1
- package/src/lib/components/ai-media-create.component.js +88 -16
- package/src/lib/components/ai-media-create.component.js.map +1 -1
- package/src/lib/components/ai-model-selector.component.js +8 -0
- package/src/lib/components/ai-model-selector.component.js.map +1 -1
- package/src/lib/components/ai-overlay-cards.component.js +2 -2
- package/src/lib/components/ai-overlay-cards.component.js.map +1 -1
- package/src/lib/components/ai-product-copy-card.component.js +2 -1
- package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
- package/src/lib/components/ai-products-hub-card.component.js +2 -1
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-revise-automation.component.js +1 -1
- package/src/lib/components/ai-revise-automation.component.js.map +1 -1
- package/src/lib/components/ai-seo-fields-card.component.js +7 -3
- package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
- package/src/lib/components/ai-site-batch-card.component.js +7 -4
- package/src/lib/components/ai-site-batch-card.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.js +7 -3
- package/src/lib/components/ai-site-build-page.component.js.map +1 -1
- package/src/lib/components/ai-site-seo-start-card.component.js +7 -4
- package/src/lib/components/ai-site-seo-start-card.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.js +62 -29
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-theme-proposal-card.component.js +1 -0
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/components/ai-upsell-dialog.component.js +17 -3
- package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
- package/src/lib/components/ai-usage-strip.component.js +13 -2
- package/src/lib/components/ai-usage-strip.component.js.map +1 -1
- package/src/lib/components/assist-build-card.component.js +20 -3
- package/src/lib/components/assist-build-card.component.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +7 -1
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/billing-ai-allotments.component.js +2 -2
- package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
- package/src/lib/components/billing-ai-top-users.component.js +1 -0
- package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
- package/src/lib/components/billing-assist-overage-card.component.js +2 -2
- package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
- package/src/lib/components/host-ai-allotments.component.js +2 -2
- package/src/lib/components/host-ai-allotments.component.js.map +1 -1
- package/src/lib/components/member-ai-allotment-card.component.js +2 -2
- package/src/lib/components/member-ai-allotment-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-card.component.js +1 -0
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-conversations.component.d.ts +9 -3
- package/src/lib/components/staff-org-ai-conversations.component.js +174 -83
- package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -1
- package/src/lib/components/staff-user-ai-usage-card.component.js +2 -1
- package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.js +4 -1
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-site-pages.d.ts +12 -0
- package/src/lib/jobs/ai-job-layout-site-pages.js +20 -0
- package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +10 -1
- package/src/lib/jobs/ai-job-page-language.js +41 -8
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +14 -3
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +75 -7
- package/src/lib/jobs/ai-job-site-content.js +205 -32
- package/src/lib/jobs/ai-job-site-content.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +8 -0
- package/src/lib/jobs/ai-job-site-step.js +61 -12
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/layout-language/ai-layout-compiler.d.ts +8 -0
- package/src/lib/layout-language/ai-layout-compiler.js +308 -10
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +7 -0
- package/src/lib/layout-language/ai-layout-design.js +23 -11
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.d.ts +6 -0
- package/src/lib/layout-language/ai-layout-frame.js +12 -3
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-gaps.js +18 -2
- package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
- package/src/lib/layout-language/ai-layout-links.d.ts +26 -1
- package/src/lib/layout-language/ai-layout-links.js +24 -4
- package/src/lib/layout-language/ai-layout-links.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +125 -0
- package/src/lib/layout-language/ai-layout-listings.js +187 -0
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -0
- package/src/lib/model/ai-site-build-progress.js +27 -12
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +33 -8
- package/src/lib/model/ai-site-job.js +38 -18
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.d.ts +30 -8
- package/src/lib/model/ai-site-kinds.js +731 -117
- package/src/lib/model/ai-site-kinds.js.map +1 -1
- package/src/lib/model/ai-site-start-seo.d.ts +30 -0
- package/src/lib/model/ai-site-start-seo.js +132 -11
- package/src/lib/model/ai-site-start-seo.js.map +1 -1
- package/src/lib/model/ai-site-start.d.ts +2 -2
- package/src/lib/model/ai-site-start.js +3 -3
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/plugin.js +15 -3
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +13 -3
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/server/ai-admin-conversations.d.ts +9 -0
- package/src/lib/server/ai-admin-conversations.js +151 -9
- package/src/lib/server/ai-admin-conversations.js.map +1 -1
- package/src/lib/usage/staff-org-ai-conversations.d.ts +23 -0
- package/src/lib/usage/staff-org-ai-conversations.js +45 -0
- package/src/lib/usage/staff-org-ai-conversations.js.map +1 -1
|
@@ -261,7 +261,7 @@ const VERSIONS_FAILED_COPY = 'The draft versions could not be made. Try again.';
|
|
|
261
261
|
target: "_blank",
|
|
262
262
|
rel: "noopener",
|
|
263
263
|
variant: "caption",
|
|
264
|
-
title: variantsHelp.
|
|
264
|
+
title: variantsHelp.excerptText,
|
|
265
265
|
children: 'How it works'
|
|
266
266
|
})
|
|
267
267
|
]
|
|
@@ -609,7 +609,7 @@ AiExperimentExplanation.displayName = 'AiExperimentExplanation';
|
|
|
609
609
|
target: "_blank",
|
|
610
610
|
rel: "noopener",
|
|
611
611
|
variant: "caption",
|
|
612
|
-
title: resultHelp.
|
|
612
|
+
title: resultHelp.excerptText,
|
|
613
613
|
children: 'How it works'
|
|
614
614
|
}),
|
|
615
615
|
/*#__PURE__*/ _jsx(Button, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-experiment-cards.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 { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { useHostOrgId, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Link,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useMemo, useState } from 'react'\nimport {\n AI_EXPERIMENT_SUBJECT_MAX_CHARS,\n type AiExperimentArm,\n type AiExperimentTarget,\n} from '../model/ai-experiment'\nimport {\n aiExperimentArmIsNamed,\n aiExperimentVerdictDisplay,\n readAiExperimentExplanation,\n readAiExperimentVariants,\n type AiExperimentExplanationProposal,\n type AiExperimentVariantsProposalView,\n} from '../model/ai-experiment-proposal'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { aiJobProblem, useAiJobRun } from './use-ai-job-run'\n\n/**\n * A/B tests by AI in the console (AGL-2914): the two doors a person reaches\n * the `experiment` job through, and the surfaces that render what it answers.\n *\n * Both are widgets in zones the A/B TESTING card hosts, not surfaces of this\n * plugin's own. The test, the experiment document, the traffic split and the\n * counters belong to the plugin that draws that card, and neither plugin\n * imports the other: the zone's id and the props it hands a widget are the\n * whole contract, and the interfaces below restate the halves this widget\n * reads rather than importing the owner's.\n *\n * ## The result surface may not decide anything\n *\n * The step reached the verdict from the counts before a model was asked, and\n * cut the answer back to it. A card can undo all of that silently — a\n * confident heading, a highlighted row, a badge saying which to ship — and\n * the reader will take the layout for the claim. So {@link AiExperimentResultCard}\n * asks `aiExperimentVerdictDisplay` what the verdict permits before it draws\n * anything: an undecided result gets a neutral chip, a sentence saying it\n * settled nothing ABOVE the explanation, no marked arm and no recommendation.\n * Nothing on it is conditioned on which arm has the best rate.\n *\n * ## Neither card writes\n *\n * The variants card fills the experiment editor's own fields, unsaved, and\n * the dialog's Save is the write. The result card has nothing to apply.\n */\n\nconst VARIANTS_FAILED_COPY = 'Variants could not be written. Try again.'\nconst EXPLAIN_FAILED_COPY = 'The result could not be explained. Try again.'\n\n/** What the test varies, in the words this card's own copy uses. */\nconst TARGET_NOUN: Record<AiExperimentTarget, string> = {\n screen: 'page',\n section: 'section',\n email: 'email',\n}\n\n/** One of the experiment editor's variants, as the zone hands it over. */\nexport interface AiExperimentZoneVariant {\n id: string\n name: string\n subject: string\n body: string\n /** The screen version a page or section variant pins; empty for the published one. */\n versionId?: string\n}\n\n/** What the variants zone hands this widget. */\nexport interface AiExperimentVariantsCardProps {\n hostId: string\n experimentId: string\n name: string\n target: AiExperimentTarget\n goal: string\n /** A page or section test's page; empty until one is picked (AGL-3603). */\n screenId?: string\n /** A section test's element; empty until one is picked. */\n nodeId?: string\n variants: AiExperimentZoneVariant[]\n proposeVariants: (\n variants: readonly AiExperimentZoneVariant[],\n key: string,\n ) => void\n}\n\n/** What the result zone hands this widget. */\nexport interface AiExperimentResultCardProps {\n hostId: string\n experimentId: string\n test: string\n}\n\n/** The proposal a finished experiment job carries, whichever task it ran. */\nfunction proposalOf(job: AiJobSummary | null): Record<string, unknown> | null {\n if (job?.status !== 'done') return null\n return job.outputs.find((output) => output.resource === 'experiment')?.proposal ?? null\n}\n\nconst rateText = (value: number | null): string =>\n value === null ? '—' : `${value}%`\n\nconst liftText = (value: number | null): string =>\n value === null ? '—' : `${value >= 0 ? '+' : ''}${value}%`\n\n/**\n * The copy an email test varies, as the editor holds it: the FIRST variant's\n * subject and body, which is the control. A screen or a section varies a\n * screen version, which the editor does not hold, so there is nothing to\n * prefill and the person pastes what is under test.\n */\nexport function aiExperimentSubjectFrom(\n target: AiExperimentTarget,\n variants: readonly AiExperimentZoneVariant[],\n): string {\n if (target !== 'email') return ''\n const control = variants[0]\n if (!control) return ''\n return [control.subject, control.body].filter(Boolean).join('\\n\\n')\n}\n\n/**\n * A proposal mapped onto the editor's variants, in order. Only the fields\n * that target has a place for are filled: an email varies its subject and\n * body, and a screen or a section varies a screen version, so there only the\n * name is filled and the copy is read on the card.\n */\nexport function aiExperimentVariantDrafts(\n target: AiExperimentTarget,\n variants: readonly AiExperimentZoneVariant[],\n proposal: AiExperimentVariantsProposalView,\n /** Draft versions made for a page or section test (AGL-3603), by the proposal's index. */\n versions: ReadonlyMap<number, string> = new Map(),\n): AiExperimentZoneVariant[] {\n return variants.map((variant, index) => {\n const proposed = proposal.variants[index]\n if (!proposed) return variant\n const versionId = target === 'email' ? undefined : versions.get(index)\n return {\n id: variant.id,\n name: proposed.name,\n subject: target === 'email' ? proposed.subject : '',\n body: target === 'email' ? proposed.body : '',\n ...(versionId ? { versionId } : {}),\n }\n })\n}\n\n/** What the draft versions door answers. */\ninterface AiExperimentVersionsAnswer {\n versions?: Array<{ index: number; name: string; versionId: string }>\n skipped?: Array<{ index: number; reason: string }>\n error?: string\n}\n\n/** Where a proposed variant's copy lands, said before the person presses the button. */\nexport function aiExperimentApplyCopy(target: AiExperimentTarget): string {\n return target === 'email'\n ? 'Each variant above takes its name, subject and body. Nothing is saved until you save the experiment.'\n : 'Each variant above takes its name. Make draft versions to put each variant’s copy into a new unpublished version of the page, pinned to its variant; the first stays on the published page as the control.'\n}\n\nconst VERSIONS_FAILED_COPY = 'The draft versions could not be made. Try again.'\n\n/**\n * \"Write variants with AI\", in the experiment editor beneath the variants it\n * writes for. It proposes COPY: no weight, no traffic split, no goal and no\n * schedule, and the first variant it writes is the copy as it stands so the\n * test has something to measure against.\n */\nexport function AiExperimentVariantsCard(props: AiExperimentVariantsCardProps) {\n const { hostId, target, variants, proposeVariants } = props\n // The zone hands the SITE, which is what the card it is drawn in knows. The\n // organization is this widget's to resolve, as every AI widget in another\n // plugin's zone resolves it.\n const orgId = useHostOrgId(hostId) ?? undefined\n const { data: user } = useUser()\n const run = useAiJobRun(user, VARIANTS_FAILED_COPY)\n const prefill = useMemo(\n () => aiExperimentSubjectFrom(target, variants),\n [target, variants],\n )\n const [subject, setSubject] = useState(prefill)\n const [applied, setApplied] = useState<string | null>(null)\n const [versioning, setVersioning] = useState(false)\n const [versionsNote, setVersionsNote] = useState<{ ok: boolean; text: string } | null>(null)\n // The control's copy follows the editor until the person edits this field,\n // so switching a draft test from a page to an email fills it rather than\n // leaving the earlier target's copy under a question about a new one.\n const [typed, setTyped] = useState(false)\n useEffect(() => {\n if (!typed) setSubject(prefill)\n }, [prefill, typed])\n\n const proposal = useMemo(\n () => readAiExperimentVariants(proposalOf(run.job)),\n [run.job],\n )\n const problem =\n aiJobProblem(run.job, VARIANTS_FAILED_COPY) ??\n (run.job?.status === 'done' && !proposal ? VARIANTS_FAILED_COPY : null)\n\n\n const busy = run.starting || run.running\n const noun = TARGET_NOUN[target]\n const variantsHelp = pluginDocsHelp('aiExperiments', {\n anchor: '#write-variants',\n excerpt:\n 'Writes two to four variants of the copy under test, the first of them your copy unchanged. They go into the editor’s fields unsaved; nothing is saved until you save the experiment.',\n })\n const write = () => {\n if (!orgId || !subject.trim()) return\n setApplied(null)\n void run.start({\n orgId,\n hostId,\n kind: 'experiment',\n brief:\n `Write variants of this ${noun} for the A/B test ` +\n `“${props.name.trim() || 'this test'}”.`,\n inputs: {\n task: 'variants',\n target,\n subject: subject.trim().slice(0, AI_EXPERIMENT_SUBJECT_MAX_CHARS),\n goal: props.goal,\n ...(props.experimentId ? { experimentId: props.experimentId } : {}),\n },\n })\n }\n\n const apply = () => {\n if (!proposal || !run.job) return\n proposeVariants(\n aiExperimentVariantDrafts(target, variants, proposal),\n run.job.id,\n )\n setApplied(run.job.id)\n }\n\n /*\n * A page or a section varies a VERSION of the page (AGL-3603): the door\n * copies the published version once per variant past the control, puts\n * the variant's copy into it, and stores it unpublished; the versions are\n * then pinned to the variants here as unsaved edits.\n */\n const screenId = props.screenId ?? ''\n const nodeId = props.nodeId ?? ''\n const versionsBlocked =\n target === 'email'\n ? null\n : !screenId\n ? 'Pick the page under test above to make draft versions.'\n : target === 'section' && !nodeId\n ? 'Pick the section under test above to make draft versions.'\n : null\n const makeVersions = async () => {\n if (!proposal || !run.job || !orgId || versionsBlocked) return\n const jobId = run.job.id\n setVersioning(true)\n setVersionsNote(null)\n try {\n const response = await authorizedFetch(user, '/api/ai/experiments/versions', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, hostId, jobId, screenId, ...(nodeId ? { nodeId } : {}) }),\n })\n const answer = ((await response.json().catch(() => null)) ?? {}) as AiExperimentVersionsAnswer\n if (!response.ok) {\n setVersionsNote({ ok: false, text: answer.error || VERSIONS_FAILED_COPY })\n return\n }\n const made = new Map((answer.versions ?? []).map((entry) => [entry.index, entry.versionId]))\n proposeVariants(aiExperimentVariantDrafts(target, variants, proposal, made), jobId)\n setApplied(jobId)\n const names = (answer.versions ?? []).map((entry) => entry.name)\n const skipped = answer.skipped ?? []\n setVersionsNote({\n ok: names.length > 0,\n text: [\n names.length\n ? `Draft versions made and pinned above: ${names.join(', ')}. Open each in the editor to check it, then Save the experiment. Nothing is published.`\n : 'No draft version could be made.',\n ...skipped.map((entry) => `Variant ${entry.index + 1}: ${entry.reason}`),\n ].join(' '),\n })\n } catch {\n setVersionsNote({ ok: false, text: VERSIONS_FAILED_COPY })\n } finally {\n setVersioning(false)\n }\n }\n\n return (\n <Box\n sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }}\n aria-label=\"Write variants with AI\"\n >\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline' }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Write variants with AI'}\n </Typography>\n <Link\n href={variantsHelp.href}\n target=\"_blank\"\n rel=\"noopener\"\n variant=\"caption\"\n title={variantsHelp.excerpt}\n >\n {'How it works'}\n </Link>\n </Stack>\n <TextField\n size=\"small\"\n multiline\n minRows={2}\n label={`The ${noun} copy under test`}\n placeholder={\n target === 'email'\n ? 'The subject line and message as they stand'\n : 'The headline and copy as they stand on the page'\n }\n helperText={\n 'Variants are written from this. The first one it writes is this copy unchanged, so the test has a control.'\n }\n value={subject}\n onChange={(event) => {\n setTyped(true)\n setSubject(event.target.value.slice(0, AI_EXPERIMENT_SUBJECT_MAX_CHARS))\n }}\n disabled={busy}\n />\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={busy || !orgId || !subject.trim()}\n onClick={write}\n >\n {run.job && !busy ? 'Write them again' : 'Write variants'}\n </Button>\n {busy ? <CircularProgress size={16} aria-label=\"Writing variants\" /> : null}\n </Stack>\n {run.notice ? <Alert severity=\"warning\">{run.notice}</Alert> : null}\n {problem ? (\n <Alert severity={run.job?.status === 'failed' ? 'error' : 'warning'}>\n {problem}\n </Alert>\n ) : null}\n\n {proposal ? (\n <Stack spacing={1} aria-label=\"Proposed variants\">\n {proposal.goal ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Written to move: ${proposal.goal}`}\n </Typography>\n ) : null}\n {proposal.variants.map((variant, index) => (\n <Box\n key={`${index}:${variant.name}`}\n sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1 }}\n >\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">\n {variant.name || `Variant ${index + 1}`}\n </Typography>\n {variant.subject ? (\n <Typography variant=\"body2\">{`Subject: ${variant.subject}`}</Typography>\n ) : null}\n {variant.preheader ? (\n <Typography variant=\"body2\">{`Preheader: ${variant.preheader}`}</Typography>\n ) : null}\n {variant.headline ? (\n <Typography variant=\"body2\">{variant.headline}</Typography>\n ) : null}\n {variant.body ? (\n <Typography\n variant=\"body2\"\n sx={{ whiteSpace: 'pre-line', wordBreak: 'break-word' }}\n >\n {variant.body}\n </Typography>\n ) : null}\n {variant.rationale ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {variant.rationale}\n </Typography>\n ) : null}\n </Stack>\n </Box>\n ))}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {aiExperimentApplyCopy(target)}\n </Typography>\n <Stack direction=\"row\" spacing={1}>\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={applied === run.job?.id}\n onClick={apply}\n >\n {'Put into the variants'}\n </Button>\n {target === 'email' ? null : (\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={versioning || Boolean(versionsBlocked)}\n onClick={() => void makeVersions()}\n >\n {'Make draft versions'}\n </Button>\n )}\n {versioning ? <CircularProgress size={16} aria-label=\"Making draft versions\" /> : null}\n </Stack>\n {versionsBlocked && target !== 'email' ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {versionsBlocked}\n </Typography>\n ) : null}\n {versionsNote ? (\n <Alert severity={versionsNote.ok ? 'success' : 'warning'}>{versionsNote.text}</Alert>\n ) : null}\n {applied === run.job?.id ? (\n <Alert severity=\"success\">\n {'In the variants above. Review them, then Save the experiment — or Cancel to leave it as it was.'}\n </Alert>\n ) : null}\n </Stack>\n ) : null}\n </Stack>\n </Box>\n )\n}\nAiExperimentVariantsCard.displayName = 'AiExperimentVariantsCard'\n\n/** One arm, drawn the same way whatever the figures say about it. */\nfunction ArmRow({\n arm,\n named,\n}: {\n arm: AiExperimentArm\n named: boolean\n}) {\n return (\n <TableRow>\n <TableCell>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\">{arm.id}</Typography>\n {named ? <Chip size=\"small\" color=\"success\" label=\"Ahead\" /> : null}\n </Stack>\n </TableCell>\n <TableCell>{arm.exposures}</TableCell>\n <TableCell>{arm.conversions}</TableCell>\n <TableCell>{rateText(arm.rate)}</TableCell>\n <TableCell>{liftText(arm.lift)}</TableCell>\n <TableCell>{rateText(arm.confidence)}</TableCell>\n </TableRow>\n )\n}\n\n/**\n * The explanation, drawn to the verdict rather than to the numbers.\n *\n * The chip and the caveat come FIRST, above the prose: a reader who stops\n * after one line must stop on what the result settled, not on a sentence\n * about one variant. No row is marked unless the verdict names an arm, and\n * `aiExperimentArmIsNamed` is asked per row rather than `winnerId` compared\n * directly, so a stored winner beside an undecided verdict marks nothing.\n */\nexport function AiExperimentExplanation({\n explanation,\n}: {\n explanation: AiExperimentExplanationProposal\n}) {\n const display = aiExperimentVerdictDisplay(explanation.verdict)\n return (\n <Stack spacing={1} aria-label=\"What this result shows\">\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Chip size=\"small\" color={display.tone} label={display.status} />\n {display.namesAnArm ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Called at ${explanation.threshold}% confidence.`}\n </Typography>\n ) : null}\n </Stack>\n {display.caveat ? (\n <Typography variant=\"body2\">{display.caveat}</Typography>\n ) : null}\n <Typography variant=\"body2\" sx={{ wordBreak: 'break-word' }}>\n {explanation.headline}\n </Typography>\n {explanation.points.map((point) => (\n <Typography key={point} variant=\"body2\" color=\"text.secondary\">\n {point}\n </Typography>\n ))}\n {explanation.arms.length ? (\n <ScrollTable size=\"small\" aria-label=\"The figures this reads\">\n <TableHead>\n <TableRow>\n <TableCell>{'Variant'}</TableCell>\n <TableCell>{'Shown'}</TableCell>\n <TableCell>{'Conversions'}</TableCell>\n <TableCell>{'Rate'}</TableCell>\n <TableCell>{'Lift'}</TableCell>\n <TableCell>{'Confidence'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {explanation.arms.map((arm) => (\n <ArmRow\n key={arm.id}\n arm={arm}\n named={aiExperimentArmIsNamed(explanation, arm.id)}\n />\n ))}\n </TableBody>\n </ScrollTable>\n ) : null}\n {explanation.next ? (\n <Typography variant=\"body2\">{explanation.next}</Typography>\n ) : null}\n </Stack>\n )\n}\nAiExperimentExplanation.displayName = 'AiExperimentExplanation'\n\n/**\n * \"Explain this result\", below one test's figures. It starts an `experiment`\n * job, which reads the same figures the dialog above it shows, decides what\n * they support in code, and asks a model only for the words.\n */\nexport function AiExperimentResultCard(props: AiExperimentResultCardProps) {\n const { hostId, test } = props\n const orgId = useHostOrgId(hostId) ?? undefined\n const { data: user } = useUser()\n const run = useAiJobRun(user, EXPLAIN_FAILED_COPY)\n const explanation = useMemo(\n () => readAiExperimentExplanation(proposalOf(run.job)),\n [run.job],\n )\n const problem =\n aiJobProblem(run.job, EXPLAIN_FAILED_COPY) ??\n (run.job?.status === 'done' && !explanation ? EXPLAIN_FAILED_COPY : null)\n\n\n const busy = run.starting || run.running\n const resultHelp = pluginDocsHelp('aiExperiments', {\n anchor: '#the-verdict',\n excerpt:\n 'What the figures support is worked out from the counts before a model is asked anything, and the explanation is held to it — so an undecided test reads as undecided and no variant is named.',\n })\n const explain = () => {\n if (!orgId || !test) return\n void run.start({\n orgId,\n hostId,\n kind: 'experiment',\n brief: `Explain the result of the A/B test “${test}” in plain words.`,\n inputs: {\n task: 'explain',\n test,\n ...(props.experimentId ? { experimentId: props.experimentId } : {}),\n },\n })\n }\n\n return (\n <Box\n sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5, mt: 2 }}\n aria-label=\"Explain this result with AI\"\n >\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ flexGrow: 1 }}>\n {'Read these figures in plain words, including whether they settle anything yet.'}\n </Typography>\n <Link\n href={resultHelp.href}\n target=\"_blank\"\n rel=\"noopener\"\n variant=\"caption\"\n title={resultHelp.excerpt}\n >\n {'How it works'}\n </Link>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={busy || !orgId || !test}\n onClick={explain}\n >\n {run.job && !busy ? 'Ask again' : 'Explain this result'}\n </Button>\n </Stack>\n {busy ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={16} aria-label=\"Reading the result\" />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Reading the result.'}\n </Typography>\n </Stack>\n ) : null}\n {run.notice ? <Alert severity=\"warning\">{run.notice}</Alert> : null}\n {problem ? (\n <Alert severity={run.job?.status === 'failed' ? 'error' : 'warning'}>\n {problem}\n </Alert>\n ) : null}\n {explanation ? <AiExperimentExplanation explanation={explanation} /> : null}\n </Stack>\n </Box>\n )\n}\nAiExperimentResultCard.displayName = 'AiExperimentResultCard'\n"],"names":["pluginDocsHelp","authorizedFetch","ScrollTable","useHostOrgId","useUser","Alert","Box","Button","Chip","CircularProgress","Link","Stack","TableBody","TableCell","TableHead","TableRow","TextField","Typography","useEffect","useMemo","useState","AI_EXPERIMENT_SUBJECT_MAX_CHARS","aiExperimentArmIsNamed","aiExperimentVerdictDisplay","readAiExperimentExplanation","readAiExperimentVariants","aiJobProblem","useAiJobRun","VARIANTS_FAILED_COPY","EXPLAIN_FAILED_COPY","TARGET_NOUN","screen","section","email","proposalOf","job","status","outputs","find","output","resource","proposal","rateText","value","liftText","aiExperimentSubjectFrom","target","variants","control","subject","body","filter","Boolean","join","aiExperimentVariantDrafts","versions","Map","map","variant","index","proposed","versionId","undefined","get","id","name","aiExperimentApplyCopy","VERSIONS_FAILED_COPY","AiExperimentVariantsCard","props","run","hostId","proposeVariants","orgId","data","user","prefill","setSubject","applied","setApplied","versioning","setVersioning","versionsNote","setVersionsNote","typed","setTyped","problem","busy","starting","running","noun","variantsHelp","anchor","excerpt","write","trim","start","kind","brief","inputs","task","slice","goal","experimentId","apply","screenId","nodeId","versionsBlocked","makeVersions","jobId","answer","response","method","headers","JSON","stringify","json","catch","ok","text","error","made","entry","names","skipped","length","reason","sx","border","borderColor","borderRadius","p","aria-label","spacing","direction","alignItems","flexGrow","href","rel","title","size","multiline","minRows","label","placeholder","helperText","onChange","event","disabled","onClick","notice","severity","color","preheader","headline","whiteSpace","wordBreak","rationale","displayName","ArmRow","arm","named","exposures","conversions","rate","lift","confidence","AiExperimentExplanation","explanation","display","verdict","tone","namesAnArm","threshold","caveat","points","point","arms","next","AiExperimentResultCard","test","resultHelp","explain","mt"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-experiment-cards.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 { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { useHostOrgId, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Link,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useMemo, useState } from 'react'\nimport {\n AI_EXPERIMENT_SUBJECT_MAX_CHARS,\n type AiExperimentArm,\n type AiExperimentTarget,\n} from '../model/ai-experiment'\nimport {\n aiExperimentArmIsNamed,\n aiExperimentVerdictDisplay,\n readAiExperimentExplanation,\n readAiExperimentVariants,\n type AiExperimentExplanationProposal,\n type AiExperimentVariantsProposalView,\n} from '../model/ai-experiment-proposal'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { aiJobProblem, useAiJobRun } from './use-ai-job-run'\n\n/**\n * A/B tests by AI in the console (AGL-2914): the two doors a person reaches\n * the `experiment` job through, and the surfaces that render what it answers.\n *\n * Both are widgets in zones the A/B TESTING card hosts, not surfaces of this\n * plugin's own. The test, the experiment document, the traffic split and the\n * counters belong to the plugin that draws that card, and neither plugin\n * imports the other: the zone's id and the props it hands a widget are the\n * whole contract, and the interfaces below restate the halves this widget\n * reads rather than importing the owner's.\n *\n * ## The result surface may not decide anything\n *\n * The step reached the verdict from the counts before a model was asked, and\n * cut the answer back to it. A card can undo all of that silently — a\n * confident heading, a highlighted row, a badge saying which to ship — and\n * the reader will take the layout for the claim. So {@link AiExperimentResultCard}\n * asks `aiExperimentVerdictDisplay` what the verdict permits before it draws\n * anything: an undecided result gets a neutral chip, a sentence saying it\n * settled nothing ABOVE the explanation, no marked arm and no recommendation.\n * Nothing on it is conditioned on which arm has the best rate.\n *\n * ## Neither card writes\n *\n * The variants card fills the experiment editor's own fields, unsaved, and\n * the dialog's Save is the write. The result card has nothing to apply.\n */\n\nconst VARIANTS_FAILED_COPY = 'Variants could not be written. Try again.'\nconst EXPLAIN_FAILED_COPY = 'The result could not be explained. Try again.'\n\n/** What the test varies, in the words this card's own copy uses. */\nconst TARGET_NOUN: Record<AiExperimentTarget, string> = {\n screen: 'page',\n section: 'section',\n email: 'email',\n}\n\n/** One of the experiment editor's variants, as the zone hands it over. */\nexport interface AiExperimentZoneVariant {\n id: string\n name: string\n subject: string\n body: string\n /** The screen version a page or section variant pins; empty for the published one. */\n versionId?: string\n}\n\n/** What the variants zone hands this widget. */\nexport interface AiExperimentVariantsCardProps {\n hostId: string\n experimentId: string\n name: string\n target: AiExperimentTarget\n goal: string\n /** A page or section test's page; empty until one is picked (AGL-3603). */\n screenId?: string\n /** A section test's element; empty until one is picked. */\n nodeId?: string\n variants: AiExperimentZoneVariant[]\n proposeVariants: (\n variants: readonly AiExperimentZoneVariant[],\n key: string,\n ) => void\n}\n\n/** What the result zone hands this widget. */\nexport interface AiExperimentResultCardProps {\n hostId: string\n experimentId: string\n test: string\n}\n\n/** The proposal a finished experiment job carries, whichever task it ran. */\nfunction proposalOf(job: AiJobSummary | null): Record<string, unknown> | null {\n if (job?.status !== 'done') return null\n return job.outputs.find((output) => output.resource === 'experiment')?.proposal ?? null\n}\n\nconst rateText = (value: number | null): string =>\n value === null ? '—' : `${value}%`\n\nconst liftText = (value: number | null): string =>\n value === null ? '—' : `${value >= 0 ? '+' : ''}${value}%`\n\n/**\n * The copy an email test varies, as the editor holds it: the FIRST variant's\n * subject and body, which is the control. A screen or a section varies a\n * screen version, which the editor does not hold, so there is nothing to\n * prefill and the person pastes what is under test.\n */\nexport function aiExperimentSubjectFrom(\n target: AiExperimentTarget,\n variants: readonly AiExperimentZoneVariant[],\n): string {\n if (target !== 'email') return ''\n const control = variants[0]\n if (!control) return ''\n return [control.subject, control.body].filter(Boolean).join('\\n\\n')\n}\n\n/**\n * A proposal mapped onto the editor's variants, in order. Only the fields\n * that target has a place for are filled: an email varies its subject and\n * body, and a screen or a section varies a screen version, so there only the\n * name is filled and the copy is read on the card.\n */\nexport function aiExperimentVariantDrafts(\n target: AiExperimentTarget,\n variants: readonly AiExperimentZoneVariant[],\n proposal: AiExperimentVariantsProposalView,\n /** Draft versions made for a page or section test (AGL-3603), by the proposal's index. */\n versions: ReadonlyMap<number, string> = new Map(),\n): AiExperimentZoneVariant[] {\n return variants.map((variant, index) => {\n const proposed = proposal.variants[index]\n if (!proposed) return variant\n const versionId = target === 'email' ? undefined : versions.get(index)\n return {\n id: variant.id,\n name: proposed.name,\n subject: target === 'email' ? proposed.subject : '',\n body: target === 'email' ? proposed.body : '',\n ...(versionId ? { versionId } : {}),\n }\n })\n}\n\n/** What the draft versions door answers. */\ninterface AiExperimentVersionsAnswer {\n versions?: Array<{ index: number; name: string; versionId: string }>\n skipped?: Array<{ index: number; reason: string }>\n error?: string\n}\n\n/** Where a proposed variant's copy lands, said before the person presses the button. */\nexport function aiExperimentApplyCopy(target: AiExperimentTarget): string {\n return target === 'email'\n ? 'Each variant above takes its name, subject and body. Nothing is saved until you save the experiment.'\n : 'Each variant above takes its name. Make draft versions to put each variant’s copy into a new unpublished version of the page, pinned to its variant; the first stays on the published page as the control.'\n}\n\nconst VERSIONS_FAILED_COPY = 'The draft versions could not be made. Try again.'\n\n/**\n * \"Write variants with AI\", in the experiment editor beneath the variants it\n * writes for. It proposes COPY: no weight, no traffic split, no goal and no\n * schedule, and the first variant it writes is the copy as it stands so the\n * test has something to measure against.\n */\nexport function AiExperimentVariantsCard(props: AiExperimentVariantsCardProps) {\n const { hostId, target, variants, proposeVariants } = props\n // The zone hands the SITE, which is what the card it is drawn in knows. The\n // organization is this widget's to resolve, as every AI widget in another\n // plugin's zone resolves it.\n const orgId = useHostOrgId(hostId) ?? undefined\n const { data: user } = useUser()\n const run = useAiJobRun(user, VARIANTS_FAILED_COPY)\n const prefill = useMemo(\n () => aiExperimentSubjectFrom(target, variants),\n [target, variants],\n )\n const [subject, setSubject] = useState(prefill)\n const [applied, setApplied] = useState<string | null>(null)\n const [versioning, setVersioning] = useState(false)\n const [versionsNote, setVersionsNote] = useState<{ ok: boolean; text: string } | null>(null)\n // The control's copy follows the editor until the person edits this field,\n // so switching a draft test from a page to an email fills it rather than\n // leaving the earlier target's copy under a question about a new one.\n const [typed, setTyped] = useState(false)\n useEffect(() => {\n if (!typed) setSubject(prefill)\n }, [prefill, typed])\n\n const proposal = useMemo(\n () => readAiExperimentVariants(proposalOf(run.job)),\n [run.job],\n )\n const problem =\n aiJobProblem(run.job, VARIANTS_FAILED_COPY) ??\n (run.job?.status === 'done' && !proposal ? VARIANTS_FAILED_COPY : null)\n\n\n const busy = run.starting || run.running\n const noun = TARGET_NOUN[target]\n const variantsHelp = pluginDocsHelp('aiExperiments', {\n anchor: '#write-variants',\n excerpt:\n 'Writes two to four variants of the copy under test, the first of them your copy unchanged. They go into the editor’s fields unsaved; nothing is saved until you save the experiment.',\n })\n const write = () => {\n if (!orgId || !subject.trim()) return\n setApplied(null)\n void run.start({\n orgId,\n hostId,\n kind: 'experiment',\n brief:\n `Write variants of this ${noun} for the A/B test ` +\n `“${props.name.trim() || 'this test'}”.`,\n inputs: {\n task: 'variants',\n target,\n subject: subject.trim().slice(0, AI_EXPERIMENT_SUBJECT_MAX_CHARS),\n goal: props.goal,\n ...(props.experimentId ? { experimentId: props.experimentId } : {}),\n },\n })\n }\n\n const apply = () => {\n if (!proposal || !run.job) return\n proposeVariants(\n aiExperimentVariantDrafts(target, variants, proposal),\n run.job.id,\n )\n setApplied(run.job.id)\n }\n\n /*\n * A page or a section varies a VERSION of the page (AGL-3603): the door\n * copies the published version once per variant past the control, puts\n * the variant's copy into it, and stores it unpublished; the versions are\n * then pinned to the variants here as unsaved edits.\n */\n const screenId = props.screenId ?? ''\n const nodeId = props.nodeId ?? ''\n const versionsBlocked =\n target === 'email'\n ? null\n : !screenId\n ? 'Pick the page under test above to make draft versions.'\n : target === 'section' && !nodeId\n ? 'Pick the section under test above to make draft versions.'\n : null\n const makeVersions = async () => {\n if (!proposal || !run.job || !orgId || versionsBlocked) return\n const jobId = run.job.id\n setVersioning(true)\n setVersionsNote(null)\n try {\n const response = await authorizedFetch(user, '/api/ai/experiments/versions', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, hostId, jobId, screenId, ...(nodeId ? { nodeId } : {}) }),\n })\n const answer = ((await response.json().catch(() => null)) ?? {}) as AiExperimentVersionsAnswer\n if (!response.ok) {\n setVersionsNote({ ok: false, text: answer.error || VERSIONS_FAILED_COPY })\n return\n }\n const made = new Map((answer.versions ?? []).map((entry) => [entry.index, entry.versionId]))\n proposeVariants(aiExperimentVariantDrafts(target, variants, proposal, made), jobId)\n setApplied(jobId)\n const names = (answer.versions ?? []).map((entry) => entry.name)\n const skipped = answer.skipped ?? []\n setVersionsNote({\n ok: names.length > 0,\n text: [\n names.length\n ? `Draft versions made and pinned above: ${names.join(', ')}. Open each in the editor to check it, then Save the experiment. Nothing is published.`\n : 'No draft version could be made.',\n ...skipped.map((entry) => `Variant ${entry.index + 1}: ${entry.reason}`),\n ].join(' '),\n })\n } catch {\n setVersionsNote({ ok: false, text: VERSIONS_FAILED_COPY })\n } finally {\n setVersioning(false)\n }\n }\n\n return (\n <Box\n sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }}\n aria-label=\"Write variants with AI\"\n >\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline' }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Write variants with AI'}\n </Typography>\n <Link\n href={variantsHelp.href}\n target=\"_blank\"\n rel=\"noopener\"\n variant=\"caption\"\n title={variantsHelp.excerptText}\n >\n {'How it works'}\n </Link>\n </Stack>\n <TextField\n size=\"small\"\n multiline\n minRows={2}\n label={`The ${noun} copy under test`}\n placeholder={\n target === 'email'\n ? 'The subject line and message as they stand'\n : 'The headline and copy as they stand on the page'\n }\n helperText={\n 'Variants are written from this. The first one it writes is this copy unchanged, so the test has a control.'\n }\n value={subject}\n onChange={(event) => {\n setTyped(true)\n setSubject(event.target.value.slice(0, AI_EXPERIMENT_SUBJECT_MAX_CHARS))\n }}\n disabled={busy}\n />\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={busy || !orgId || !subject.trim()}\n onClick={write}\n >\n {run.job && !busy ? 'Write them again' : 'Write variants'}\n </Button>\n {busy ? <CircularProgress size={16} aria-label=\"Writing variants\" /> : null}\n </Stack>\n {run.notice ? <Alert severity=\"warning\">{run.notice}</Alert> : null}\n {problem ? (\n <Alert severity={run.job?.status === 'failed' ? 'error' : 'warning'}>\n {problem}\n </Alert>\n ) : null}\n\n {proposal ? (\n <Stack spacing={1} aria-label=\"Proposed variants\">\n {proposal.goal ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Written to move: ${proposal.goal}`}\n </Typography>\n ) : null}\n {proposal.variants.map((variant, index) => (\n <Box\n key={`${index}:${variant.name}`}\n sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1 }}\n >\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">\n {variant.name || `Variant ${index + 1}`}\n </Typography>\n {variant.subject ? (\n <Typography variant=\"body2\">{`Subject: ${variant.subject}`}</Typography>\n ) : null}\n {variant.preheader ? (\n <Typography variant=\"body2\">{`Preheader: ${variant.preheader}`}</Typography>\n ) : null}\n {variant.headline ? (\n <Typography variant=\"body2\">{variant.headline}</Typography>\n ) : null}\n {variant.body ? (\n <Typography\n variant=\"body2\"\n sx={{ whiteSpace: 'pre-line', wordBreak: 'break-word' }}\n >\n {variant.body}\n </Typography>\n ) : null}\n {variant.rationale ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {variant.rationale}\n </Typography>\n ) : null}\n </Stack>\n </Box>\n ))}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {aiExperimentApplyCopy(target)}\n </Typography>\n <Stack direction=\"row\" spacing={1}>\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={applied === run.job?.id}\n onClick={apply}\n >\n {'Put into the variants'}\n </Button>\n {target === 'email' ? null : (\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={versioning || Boolean(versionsBlocked)}\n onClick={() => void makeVersions()}\n >\n {'Make draft versions'}\n </Button>\n )}\n {versioning ? <CircularProgress size={16} aria-label=\"Making draft versions\" /> : null}\n </Stack>\n {versionsBlocked && target !== 'email' ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {versionsBlocked}\n </Typography>\n ) : null}\n {versionsNote ? (\n <Alert severity={versionsNote.ok ? 'success' : 'warning'}>{versionsNote.text}</Alert>\n ) : null}\n {applied === run.job?.id ? (\n <Alert severity=\"success\">\n {'In the variants above. Review them, then Save the experiment — or Cancel to leave it as it was.'}\n </Alert>\n ) : null}\n </Stack>\n ) : null}\n </Stack>\n </Box>\n )\n}\nAiExperimentVariantsCard.displayName = 'AiExperimentVariantsCard'\n\n/** One arm, drawn the same way whatever the figures say about it. */\nfunction ArmRow({\n arm,\n named,\n}: {\n arm: AiExperimentArm\n named: boolean\n}) {\n return (\n <TableRow>\n <TableCell>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\">{arm.id}</Typography>\n {named ? <Chip size=\"small\" color=\"success\" label=\"Ahead\" /> : null}\n </Stack>\n </TableCell>\n <TableCell>{arm.exposures}</TableCell>\n <TableCell>{arm.conversions}</TableCell>\n <TableCell>{rateText(arm.rate)}</TableCell>\n <TableCell>{liftText(arm.lift)}</TableCell>\n <TableCell>{rateText(arm.confidence)}</TableCell>\n </TableRow>\n )\n}\n\n/**\n * The explanation, drawn to the verdict rather than to the numbers.\n *\n * The chip and the caveat come FIRST, above the prose: a reader who stops\n * after one line must stop on what the result settled, not on a sentence\n * about one variant. No row is marked unless the verdict names an arm, and\n * `aiExperimentArmIsNamed` is asked per row rather than `winnerId` compared\n * directly, so a stored winner beside an undecided verdict marks nothing.\n */\nexport function AiExperimentExplanation({\n explanation,\n}: {\n explanation: AiExperimentExplanationProposal\n}) {\n const display = aiExperimentVerdictDisplay(explanation.verdict)\n return (\n <Stack spacing={1} aria-label=\"What this result shows\">\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Chip size=\"small\" color={display.tone} label={display.status} />\n {display.namesAnArm ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Called at ${explanation.threshold}% confidence.`}\n </Typography>\n ) : null}\n </Stack>\n {display.caveat ? (\n <Typography variant=\"body2\">{display.caveat}</Typography>\n ) : null}\n <Typography variant=\"body2\" sx={{ wordBreak: 'break-word' }}>\n {explanation.headline}\n </Typography>\n {explanation.points.map((point) => (\n <Typography key={point} variant=\"body2\" color=\"text.secondary\">\n {point}\n </Typography>\n ))}\n {explanation.arms.length ? (\n <ScrollTable size=\"small\" aria-label=\"The figures this reads\">\n <TableHead>\n <TableRow>\n <TableCell>{'Variant'}</TableCell>\n <TableCell>{'Shown'}</TableCell>\n <TableCell>{'Conversions'}</TableCell>\n <TableCell>{'Rate'}</TableCell>\n <TableCell>{'Lift'}</TableCell>\n <TableCell>{'Confidence'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {explanation.arms.map((arm) => (\n <ArmRow\n key={arm.id}\n arm={arm}\n named={aiExperimentArmIsNamed(explanation, arm.id)}\n />\n ))}\n </TableBody>\n </ScrollTable>\n ) : null}\n {explanation.next ? (\n <Typography variant=\"body2\">{explanation.next}</Typography>\n ) : null}\n </Stack>\n )\n}\nAiExperimentExplanation.displayName = 'AiExperimentExplanation'\n\n/**\n * \"Explain this result\", below one test's figures. It starts an `experiment`\n * job, which reads the same figures the dialog above it shows, decides what\n * they support in code, and asks a model only for the words.\n */\nexport function AiExperimentResultCard(props: AiExperimentResultCardProps) {\n const { hostId, test } = props\n const orgId = useHostOrgId(hostId) ?? undefined\n const { data: user } = useUser()\n const run = useAiJobRun(user, EXPLAIN_FAILED_COPY)\n const explanation = useMemo(\n () => readAiExperimentExplanation(proposalOf(run.job)),\n [run.job],\n )\n const problem =\n aiJobProblem(run.job, EXPLAIN_FAILED_COPY) ??\n (run.job?.status === 'done' && !explanation ? EXPLAIN_FAILED_COPY : null)\n\n\n const busy = run.starting || run.running\n const resultHelp = pluginDocsHelp('aiExperiments', {\n anchor: '#the-verdict',\n excerpt:\n 'What the figures support is worked out from the counts before a model is asked anything, and the explanation is held to it — so an undecided test reads as undecided and no variant is named.',\n })\n const explain = () => {\n if (!orgId || !test) return\n void run.start({\n orgId,\n hostId,\n kind: 'experiment',\n brief: `Explain the result of the A/B test “${test}” in plain words.`,\n inputs: {\n task: 'explain',\n test,\n ...(props.experimentId ? { experimentId: props.experimentId } : {}),\n },\n })\n }\n\n return (\n <Box\n sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5, mt: 2 }}\n aria-label=\"Explain this result with AI\"\n >\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ flexGrow: 1 }}>\n {'Read these figures in plain words, including whether they settle anything yet.'}\n </Typography>\n <Link\n href={resultHelp.href}\n target=\"_blank\"\n rel=\"noopener\"\n variant=\"caption\"\n title={resultHelp.excerptText}\n >\n {'How it works'}\n </Link>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={busy || !orgId || !test}\n onClick={explain}\n >\n {run.job && !busy ? 'Ask again' : 'Explain this result'}\n </Button>\n </Stack>\n {busy ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={16} aria-label=\"Reading the result\" />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Reading the result.'}\n </Typography>\n </Stack>\n ) : null}\n {run.notice ? <Alert severity=\"warning\">{run.notice}</Alert> : null}\n {problem ? (\n <Alert severity={run.job?.status === 'failed' ? 'error' : 'warning'}>\n {problem}\n </Alert>\n ) : null}\n {explanation ? <AiExperimentExplanation explanation={explanation} /> : null}\n </Stack>\n </Box>\n )\n}\nAiExperimentResultCard.displayName = 'AiExperimentResultCard'\n"],"names":["pluginDocsHelp","authorizedFetch","ScrollTable","useHostOrgId","useUser","Alert","Box","Button","Chip","CircularProgress","Link","Stack","TableBody","TableCell","TableHead","TableRow","TextField","Typography","useEffect","useMemo","useState","AI_EXPERIMENT_SUBJECT_MAX_CHARS","aiExperimentArmIsNamed","aiExperimentVerdictDisplay","readAiExperimentExplanation","readAiExperimentVariants","aiJobProblem","useAiJobRun","VARIANTS_FAILED_COPY","EXPLAIN_FAILED_COPY","TARGET_NOUN","screen","section","email","proposalOf","job","status","outputs","find","output","resource","proposal","rateText","value","liftText","aiExperimentSubjectFrom","target","variants","control","subject","body","filter","Boolean","join","aiExperimentVariantDrafts","versions","Map","map","variant","index","proposed","versionId","undefined","get","id","name","aiExperimentApplyCopy","VERSIONS_FAILED_COPY","AiExperimentVariantsCard","props","run","hostId","proposeVariants","orgId","data","user","prefill","setSubject","applied","setApplied","versioning","setVersioning","versionsNote","setVersionsNote","typed","setTyped","problem","busy","starting","running","noun","variantsHelp","anchor","excerpt","write","trim","start","kind","brief","inputs","task","slice","goal","experimentId","apply","screenId","nodeId","versionsBlocked","makeVersions","jobId","answer","response","method","headers","JSON","stringify","json","catch","ok","text","error","made","entry","names","skipped","length","reason","sx","border","borderColor","borderRadius","p","aria-label","spacing","direction","alignItems","flexGrow","href","rel","title","excerptText","size","multiline","minRows","label","placeholder","helperText","onChange","event","disabled","onClick","notice","severity","color","preheader","headline","whiteSpace","wordBreak","rationale","displayName","ArmRow","arm","named","exposures","conversions","rate","lift","confidence","AiExperimentExplanation","explanation","display","verdict","tone","namesAnArm","threshold","caveat","points","point","arms","next","AiExperimentResultCard","test","resultHelp","explain","mt"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-explain-automation.component.tsx"],"sourcesContent":["'use client'\n\n/**\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 { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Link,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useState } from 'react'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { aiJobProblem, useAiJobRun, type AiJobRun } from './use-ai-job-run'\nimport type {\n ConsoleAutomationEditorZoneProps,\n ConsoleAutomationRunZoneProps,\n} from './ai-automation-zones'\n\n/**\n * \"Explain it\" in the editor of a saved automation, and \"Why did this fail?\"\n * on a failed run in its run history (AGL-2919), through the\n * `automationEditor` and `automationRun` zones. Each starts a `workflow` job\n * that reads the automation as it is saved — and, for a run, what the run\n * history recorded — and answers in plain words. Nothing is changed.\n */\n\nexport const AI_AUTOMATION_EXPLAIN_COPY = {\n explain: {\n action: 'Explain it',\n intro: 'Get a plain-words account of what this automation does, and anything in it worth checking.',\n saved: 'It reads the automation as it is saved, not changes you have not saved yet.',\n running: 'Reading the automation. This can take a few minutes.',\n again: 'Explain it again',\n failed: 'The automation could not be explained. Try again.',\n brief: (name: string) => `Explain what “${name}” does, and anything in it worth checking.`,\n },\n diagnose: {\n action: 'Why did this fail?',\n title: 'Why this run failed',\n running: 'Reading the run and the automation. This can take a few minutes.',\n again: 'Ask again',\n failed: 'The run could not be explained. Try again.',\n brief: (name: string) => `Why did a run of “${name}” fail, and how can it be fixed?`,\n },\n} as const\n\n/** The explanation a finished job wrote. */\nfunction explanationOf(job: AiJobSummary | null): string | null {\n if (job?.status !== 'done') return null\n return job.outputs.find((output) => output.resource === 'text' && output.text)?.text ?? null\n}\n\n/** Progress, the explanation, or why there is none. */\nfunction Answer({ run, running, failed }: { run: AiJobRun; running: string; failed: string }) {\n const text = explanationOf(run.job)\n const problem =\n aiJobProblem(run.job, failed) ?? (run.job?.status === 'done' && !text ? failed : null)\n return (\n <>\n {run.running ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={16} aria-label=\"Explaining\" />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {running}\n </Typography>\n </Stack>\n ) : null}\n {text ? (\n <Typography variant=\"body2\" component=\"div\" sx={{ whiteSpace: 'pre-line', wordBreak: 'break-word' }}>\n {text}\n </Typography>\n ) : null}\n {problem ? <Alert severity={run.job?.status === 'failed' ? 'error' : 'warning'}>{problem}</Alert> : null}\n {run.notice ? <Alert severity=\"warning\">{run.notice}</Alert> : null}\n </>\n )\n}\n\n/** \"Explain it\", at the top of the editor of a saved action or workflow. */\nexport function AiExplainAutomation({ hostId, orgId, target }: ConsoleAutomationEditorZoneProps) {\n const copy = AI_AUTOMATION_EXPLAIN_COPY.explain\n const help = pluginDocsHelp('aiAutomations', { anchor: '#explain' })\n const { data: user } = useUser()\n const run = useAiJobRun(user, copy.failed)\n\n const asked = Boolean(run.job || run.notice)\n const busy = run.starting || run.running\n\n const explain = () => {\n if (!orgId) return\n void run.start({\n orgId,\n hostId,\n kind: 'workflow',\n brief: copy.brief(target.name),\n inputs: { mode: 'explain', targetType: target.type, targetId: target.id },\n })\n }\n\n return (\n <Box sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }} aria-label=\"Explain this automation\">\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ flexGrow: 1 }}>\n {asked ? copy.saved : copy.intro}{' '}\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Typography>\n <Button size=\"small\" variant=\"outlined\" disabled={busy || !orgId} onClick={explain}>\n {asked && !busy ? copy.again : copy.action}\n </Button>\n </Stack>\n <Answer run={run} running={copy.running} failed={copy.failed} />\n </Stack>\n </Box>\n )\n}\n\n/** \"Why did this fail?\", on a failed run: the answer opens over the run history. */\nexport function AiExplainRunFailure({ hostId, orgId, target, runId }: ConsoleAutomationRunZoneProps) {\n const copy = AI_AUTOMATION_EXPLAIN_COPY.diagnose\n const { data: user } = useUser()\n const run = useAiJobRun(user, copy.failed)\n const [open, setOpen] = useState(false)\n\n const busy = run.starting || run.running\n\n const ask = () => {\n if (!orgId) return\n void run.start({\n orgId,\n hostId,\n kind: 'workflow',\n brief: copy.brief(target.name),\n inputs: { mode: 'diagnose', targetType: target.type, targetId: target.id, runId },\n })\n }\n\n const openAndAsk = () => {\n setOpen(true)\n // A run already explained, or being explained, is shown again rather than asked again.\n if (!run.job) ask()\n }\n\n const close = () => {\n setOpen(false)\n // A refusal is not kept: asking again later should ask the route again.\n if (run.notice) run.reset()\n }\n\n return (\n <>\n <Button size=\"small\" variant=\"text\" sx={{ px: 0.5, minWidth: 0 }} onClick={openAndAsk}>\n {copy.action}\n </Button>\n <Dialog open={open} onClose={close} fullWidth maxWidth=\"sm\">\n <DialogTitle>{copy.title}</DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n <Answer run={run} running={copy.running} failed={copy.failed} />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={close}>{'Close'}</Button>\n {!busy && (run.notice || aiJobProblem(run.job, copy.failed)) ? (\n <Button variant=\"outlined\" onClick={ask} disabled={!orgId}>\n {copy.again}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n </>\n )\n}\n"],"names":["pluginDocsHelp","useUser","Alert","Box","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","Link","Stack","Typography","useState","aiJobProblem","useAiJobRun","AI_AUTOMATION_EXPLAIN_COPY","explain","action","intro","saved","running","again","failed","brief","name","diagnose","title","explanationOf","job","status","outputs","find","output","resource","text","Answer","run","problem","direction","spacing","sx","alignItems","size","aria-label","variant","color","component","whiteSpace","wordBreak","severity","notice","AiExplainAutomation","hostId","orgId","target","copy","help","anchor","data","user","asked","Boolean","busy","starting","start","kind","inputs","mode","targetType","type","targetId","id","border","borderColor","borderRadius","p","flexGrow","href","rel","excerpt","disabled","onClick","AiExplainRunFailure","runId","open","setOpen","ask","openAndAsk","close","reset","px","minWidth","onClose","fullWidth","maxWidth","pt"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-explain-automation.component.tsx"],"sourcesContent":["'use client'\n\n/**\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 { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Link,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useState } from 'react'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { aiJobProblem, useAiJobRun, type AiJobRun } from './use-ai-job-run'\nimport type {\n ConsoleAutomationEditorZoneProps,\n ConsoleAutomationRunZoneProps,\n} from './ai-automation-zones'\n\n/**\n * \"Explain it\" in the editor of a saved automation, and \"Why did this fail?\"\n * on a failed run in its run history (AGL-2919), through the\n * `automationEditor` and `automationRun` zones. Each starts a `workflow` job\n * that reads the automation as it is saved — and, for a run, what the run\n * history recorded — and answers in plain words. Nothing is changed.\n */\n\nexport const AI_AUTOMATION_EXPLAIN_COPY = {\n explain: {\n action: 'Explain it',\n intro: 'Get a plain-words account of what this automation does, and anything in it worth checking.',\n saved: 'It reads the automation as it is saved, not changes you have not saved yet.',\n running: 'Reading the automation. This can take a few minutes.',\n again: 'Explain it again',\n failed: 'The automation could not be explained. Try again.',\n brief: (name: string) => `Explain what “${name}” does, and anything in it worth checking.`,\n },\n diagnose: {\n action: 'Why did this fail?',\n title: 'Why this run failed',\n running: 'Reading the run and the automation. This can take a few minutes.',\n again: 'Ask again',\n failed: 'The run could not be explained. Try again.',\n brief: (name: string) => `Why did a run of “${name}” fail, and how can it be fixed?`,\n },\n} as const\n\n/** The explanation a finished job wrote. */\nfunction explanationOf(job: AiJobSummary | null): string | null {\n if (job?.status !== 'done') return null\n return job.outputs.find((output) => output.resource === 'text' && output.text)?.text ?? null\n}\n\n/** Progress, the explanation, or why there is none. */\nfunction Answer({ run, running, failed }: { run: AiJobRun; running: string; failed: string }) {\n const text = explanationOf(run.job)\n const problem =\n aiJobProblem(run.job, failed) ?? (run.job?.status === 'done' && !text ? failed : null)\n return (\n <>\n {run.running ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={16} aria-label=\"Explaining\" />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {running}\n </Typography>\n </Stack>\n ) : null}\n {text ? (\n <Typography variant=\"body2\" component=\"div\" sx={{ whiteSpace: 'pre-line', wordBreak: 'break-word' }}>\n {text}\n </Typography>\n ) : null}\n {problem ? <Alert severity={run.job?.status === 'failed' ? 'error' : 'warning'}>{problem}</Alert> : null}\n {run.notice ? <Alert severity=\"warning\">{run.notice}</Alert> : null}\n </>\n )\n}\n\n/** \"Explain it\", at the top of the editor of a saved action or workflow. */\nexport function AiExplainAutomation({ hostId, orgId, target }: ConsoleAutomationEditorZoneProps) {\n const copy = AI_AUTOMATION_EXPLAIN_COPY.explain\n const help = pluginDocsHelp('aiAutomations', { anchor: '#explain' })\n const { data: user } = useUser()\n const run = useAiJobRun(user, copy.failed)\n\n const asked = Boolean(run.job || run.notice)\n const busy = run.starting || run.running\n\n const explain = () => {\n if (!orgId) return\n void run.start({\n orgId,\n hostId,\n kind: 'workflow',\n brief: copy.brief(target.name),\n inputs: { mode: 'explain', targetType: target.type, targetId: target.id },\n })\n }\n\n return (\n <Box sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }} aria-label=\"Explain this automation\">\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ flexGrow: 1 }}>\n {asked ? copy.saved : copy.intro}{' '}\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" title={help.excerptText}>\n {'How it works'}\n </Link>\n </Typography>\n <Button size=\"small\" variant=\"outlined\" disabled={busy || !orgId} onClick={explain}>\n {asked && !busy ? copy.again : copy.action}\n </Button>\n </Stack>\n <Answer run={run} running={copy.running} failed={copy.failed} />\n </Stack>\n </Box>\n )\n}\n\n/** \"Why did this fail?\", on a failed run: the answer opens over the run history. */\nexport function AiExplainRunFailure({ hostId, orgId, target, runId }: ConsoleAutomationRunZoneProps) {\n const copy = AI_AUTOMATION_EXPLAIN_COPY.diagnose\n const { data: user } = useUser()\n const run = useAiJobRun(user, copy.failed)\n const [open, setOpen] = useState(false)\n\n const busy = run.starting || run.running\n\n const ask = () => {\n if (!orgId) return\n void run.start({\n orgId,\n hostId,\n kind: 'workflow',\n brief: copy.brief(target.name),\n inputs: { mode: 'diagnose', targetType: target.type, targetId: target.id, runId },\n })\n }\n\n const openAndAsk = () => {\n setOpen(true)\n // A run already explained, or being explained, is shown again rather than asked again.\n if (!run.job) ask()\n }\n\n const close = () => {\n setOpen(false)\n // A refusal is not kept: asking again later should ask the route again.\n if (run.notice) run.reset()\n }\n\n return (\n <>\n <Button size=\"small\" variant=\"text\" sx={{ px: 0.5, minWidth: 0 }} onClick={openAndAsk}>\n {copy.action}\n </Button>\n <Dialog open={open} onClose={close} fullWidth maxWidth=\"sm\">\n <DialogTitle>{copy.title}</DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n <Answer run={run} running={copy.running} failed={copy.failed} />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={close}>{'Close'}</Button>\n {!busy && (run.notice || aiJobProblem(run.job, copy.failed)) ? (\n <Button variant=\"outlined\" onClick={ask} disabled={!orgId}>\n {copy.again}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n </>\n )\n}\n"],"names":["pluginDocsHelp","useUser","Alert","Box","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","Link","Stack","Typography","useState","aiJobProblem","useAiJobRun","AI_AUTOMATION_EXPLAIN_COPY","explain","action","intro","saved","running","again","failed","brief","name","diagnose","title","explanationOf","job","status","outputs","find","output","resource","text","Answer","run","problem","direction","spacing","sx","alignItems","size","aria-label","variant","color","component","whiteSpace","wordBreak","severity","notice","AiExplainAutomation","hostId","orgId","target","copy","help","anchor","data","user","asked","Boolean","busy","starting","start","kind","inputs","mode","targetType","type","targetId","id","border","borderColor","borderRadius","p","flexGrow","href","rel","excerptText","disabled","onClick","AiExplainRunFailure","runId","open","setOpen","ask","openAndAsk","close","reset","px","minWidth","onClose","fullWidth","maxWidth","pt"],"mappings":"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"}
|
|
@@ -24,7 +24,9 @@ import { AiUpsellDialog } from "./ai-upsell-dialog.component.js";
|
|
|
24
24
|
action: 'Ask a question'
|
|
25
25
|
};
|
|
26
26
|
function InsightTile(props) {
|
|
27
|
-
const help = pluginDocsHelp('aiInsights'
|
|
27
|
+
const help = pluginDocsHelp('aiInsights', {
|
|
28
|
+
anchor: '#asking-a-question'
|
|
29
|
+
});
|
|
28
30
|
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
29
31
|
header: AI_INSIGHT_CARD_COPY.title,
|
|
30
32
|
help: help,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-insight-card.component.tsx"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ConsoleWidgetEntitlementProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport type { ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useConsoleHostRoute, useHostOrgId, useUser } from '@aglyn/tenant-feature-instance'\nimport { Button, Stack, Typography } from '@mui/material'\nimport { useState } from 'react'\nimport { AiInsightDialog } from './ai-insight-dialog.component'\nimport { AiUpsellDialog } from './ai-upsell-dialog.component'\n\n/**\n * \"Ask AI about these numbers\" (AGL-3603): a tile on a site's dashboard and\n * Analytics page (`hostDashboard`, which both draw) and on the workspace's\n * sites page (`orgDashboard`), opening the insight dialog the Assist panel\n * opens. A question becomes an `insight` job — the figures read by code\n * through the readers the surface offers, every insight traced to the rows\n * it cites — charged as that job is. Drawn from the shell's gates alone\n * (AGL-3601): nothing asks a server until a question is asked, and on a plan\n * without the AI add-on the same tile opens the add-on's dialog.\n */\n\nexport const AI_INSIGHT_CARD_COPY = {\n title: 'Ask AI about these numbers',\n site: 'Ask a question about this site’s traffic, sales, bookings, automation runs and pipeline. Every answer shows the figures it is built from.',\n workspace: 'Ask a question about your workspace’s pipeline, the deals it closed and your datasets. Every answer shows the figures it is built from.',\n action: 'Ask a question',\n} as const\n\nfunction InsightTile(props: {\n text: string\n onAsk: () => void\n}) {\n const help = pluginDocsHelp('aiInsights')\n return (\n <CardDisplay header={AI_INSIGHT_CARD_COPY.title} help={help} contentGutterX contentGutterY>\n <Stack spacing={1.5} sx={{ alignItems: 'flex-start' }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {props.text}\n </Typography>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={props.onAsk}\n >\n {AI_INSIGHT_CARD_COPY.action}\n </Button>\n </Stack>\n </CardDisplay>\n )\n}\n\n/** The tile on a plan without the AI add-on: the same tile, opening the add-on's dialog. */\nfunction InsightUpsellTile({ text, upgrade }: { text: string; upgrade: ConsoleWidgetEntitlementProps['upgrade'] }) {\n const [open, setOpen] = useState(false)\n if (!upgrade) return null\n return (\n <>\n <InsightTile text={text} onAsk={() => setOpen(true)} />\n <AiUpsellDialog kind=\"insight\" open={open} onClose={() => setOpen(false)} upgrade={upgrade} />\n </>\n )\n}\n\n/** On a site's dashboard and its Analytics page. */\nexport function AiInsightHostCard({\n hostId,\n entitled,\n upgrade,\n}: { hostId: string } & ConsoleWidgetEntitlementProps) {\n const { data: user } = useUser()\n const orgId = useHostOrgId(hostId)\n const route = useConsoleHostRoute(hostId)\n const [open, setOpen] = useState(false)\n if (entitled === false) return <InsightUpsellTile text={AI_INSIGHT_CARD_COPY.site} upgrade={upgrade} />\n if (!orgId) return null\n return (\n <>\n <InsightTile text={AI_INSIGHT_CARD_COPY.site} onAsk={() => setOpen(true)} />\n {open ? (\n <AiInsightDialog\n open\n onClose={() => setOpen(false)}\n orgId={orgId}\n orgSlug={route.orgSlug ?? ''}\n hostId={hostId}\n host={route.subdomain}\n surface=\"analytics\"\n user={user}\n uid={(user as { uid?: string } | null | undefined)?.uid ?? null}\n />\n ) : null}\n </>\n )\n}\n\n/** On the workspace's sites page, totaling the organization rather than one site. */\nexport function AiInsightOrgCard({\n orgMount,\n entitled,\n upgrade,\n}: { orgMount?: ConsolePluginOrgMount | null } & ConsoleWidgetEntitlementProps) {\n const { data: user } = useUser()\n const orgId = orgMount?.orgId ?? null\n const [open, setOpen] = useState(false)\n if (entitled === false) return <InsightUpsellTile text={AI_INSIGHT_CARD_COPY.workspace} upgrade={upgrade} />\n if (!orgId) return null\n return (\n <>\n <InsightTile text={AI_INSIGHT_CARD_COPY.workspace} onAsk={() => setOpen(true)} />\n {open ? (\n <AiInsightDialog\n open\n onClose={() => setOpen(false)}\n orgId={orgId}\n orgSlug={orgMount?.orgSlug ?? ''}\n hostId={null}\n host={null}\n surface=\"workspace\"\n user={user}\n uid={(user as { uid?: string } | null | undefined)?.uid ?? null}\n />\n ) : null}\n </>\n )\n}\n"],"names":["pluginDocsHelp","mdiCreation","CardDisplay","MdiIcon","useConsoleHostRoute","useHostOrgId","useUser","Button","Stack","Typography","useState","AiInsightDialog","AiUpsellDialog","AI_INSIGHT_CARD_COPY","title","site","workspace","action","InsightTile","props","help","header","contentGutterX","contentGutterY","spacing","sx","alignItems","variant","color","text","size","startIcon","path","onClick","onAsk","InsightUpsellTile","upgrade","open","setOpen","kind","onClose","AiInsightHostCard","hostId","entitled","route","data","user","orgId","orgSlug","host","subdomain","surface","uid","AiInsightOrgCard","orgMount"],"mappings":"AAAA;;AAqBA,SAASA,cAAc,QAAQ,mCAAkC;AACjE,SAASC,WAAW,QAAQ,yBAAwB;AACpD,SAASC,WAAW,EAAEC,OAAO,QAAQ,uBAAsB;AAC3D,SAASC,mBAAmB,EAAEC,YAAY,EAAEC,OAAO,QAAQ,iCAAgC;AAC3F,SAASC,MAAM,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AACzD,SAASC,QAAQ,QAAQ,QAAO;AAChC,SAASC,eAAe,QAAQ,mCAA+B;AAC/D,SAASC,cAAc,QAAQ,kCAA8B;AAE7D;;;;;;;;;CASC,GAED,OAAO,MAAMC,uBAAuB;IAClCC,OAAO;IACPC,MAAM;IACNC,WAAW;IACXC,QAAQ;AACV,EAAU;AAEV,SAASC,YAAYC,KAGpB;IACC,MAAMC,OAAOpB,eAAe;
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-insight-card.component.tsx"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ConsoleWidgetEntitlementProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport type { ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useConsoleHostRoute, useHostOrgId, useUser } from '@aglyn/tenant-feature-instance'\nimport { Button, Stack, Typography } from '@mui/material'\nimport { useState } from 'react'\nimport { AiInsightDialog } from './ai-insight-dialog.component'\nimport { AiUpsellDialog } from './ai-upsell-dialog.component'\n\n/**\n * \"Ask AI about these numbers\" (AGL-3603): a tile on a site's dashboard and\n * Analytics page (`hostDashboard`, which both draw) and on the workspace's\n * sites page (`orgDashboard`), opening the insight dialog the Assist panel\n * opens. A question becomes an `insight` job — the figures read by code\n * through the readers the surface offers, every insight traced to the rows\n * it cites — charged as that job is. Drawn from the shell's gates alone\n * (AGL-3601): nothing asks a server until a question is asked, and on a plan\n * without the AI add-on the same tile opens the add-on's dialog.\n */\n\nexport const AI_INSIGHT_CARD_COPY = {\n title: 'Ask AI about these numbers',\n site: 'Ask a question about this site’s traffic, sales, bookings, automation runs and pipeline. Every answer shows the figures it is built from.',\n workspace: 'Ask a question about your workspace’s pipeline, the deals it closed and your datasets. Every answer shows the figures it is built from.',\n action: 'Ask a question',\n} as const\n\nfunction InsightTile(props: {\n text: string\n onAsk: () => void\n}) {\n const help = pluginDocsHelp('aiInsights', { anchor: '#asking-a-question' })\n return (\n <CardDisplay header={AI_INSIGHT_CARD_COPY.title} help={help} contentGutterX contentGutterY>\n <Stack spacing={1.5} sx={{ alignItems: 'flex-start' }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {props.text}\n </Typography>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={props.onAsk}\n >\n {AI_INSIGHT_CARD_COPY.action}\n </Button>\n </Stack>\n </CardDisplay>\n )\n}\n\n/** The tile on a plan without the AI add-on: the same tile, opening the add-on's dialog. */\nfunction InsightUpsellTile({ text, upgrade }: { text: string; upgrade: ConsoleWidgetEntitlementProps['upgrade'] }) {\n const [open, setOpen] = useState(false)\n if (!upgrade) return null\n return (\n <>\n <InsightTile text={text} onAsk={() => setOpen(true)} />\n <AiUpsellDialog kind=\"insight\" open={open} onClose={() => setOpen(false)} upgrade={upgrade} />\n </>\n )\n}\n\n/** On a site's dashboard and its Analytics page. */\nexport function AiInsightHostCard({\n hostId,\n entitled,\n upgrade,\n}: { hostId: string } & ConsoleWidgetEntitlementProps) {\n const { data: user } = useUser()\n const orgId = useHostOrgId(hostId)\n const route = useConsoleHostRoute(hostId)\n const [open, setOpen] = useState(false)\n if (entitled === false) return <InsightUpsellTile text={AI_INSIGHT_CARD_COPY.site} upgrade={upgrade} />\n if (!orgId) return null\n return (\n <>\n <InsightTile text={AI_INSIGHT_CARD_COPY.site} onAsk={() => setOpen(true)} />\n {open ? (\n <AiInsightDialog\n open\n onClose={() => setOpen(false)}\n orgId={orgId}\n orgSlug={route.orgSlug ?? ''}\n hostId={hostId}\n host={route.subdomain}\n surface=\"analytics\"\n user={user}\n uid={(user as { uid?: string } | null | undefined)?.uid ?? null}\n />\n ) : null}\n </>\n )\n}\n\n/** On the workspace's sites page, totaling the organization rather than one site. */\nexport function AiInsightOrgCard({\n orgMount,\n entitled,\n upgrade,\n}: { orgMount?: ConsolePluginOrgMount | null } & ConsoleWidgetEntitlementProps) {\n const { data: user } = useUser()\n const orgId = orgMount?.orgId ?? null\n const [open, setOpen] = useState(false)\n if (entitled === false) return <InsightUpsellTile text={AI_INSIGHT_CARD_COPY.workspace} upgrade={upgrade} />\n if (!orgId) return null\n return (\n <>\n <InsightTile text={AI_INSIGHT_CARD_COPY.workspace} onAsk={() => setOpen(true)} />\n {open ? (\n <AiInsightDialog\n open\n onClose={() => setOpen(false)}\n orgId={orgId}\n orgSlug={orgMount?.orgSlug ?? ''}\n hostId={null}\n host={null}\n surface=\"workspace\"\n user={user}\n uid={(user as { uid?: string } | null | undefined)?.uid ?? null}\n />\n ) : null}\n </>\n )\n}\n"],"names":["pluginDocsHelp","mdiCreation","CardDisplay","MdiIcon","useConsoleHostRoute","useHostOrgId","useUser","Button","Stack","Typography","useState","AiInsightDialog","AiUpsellDialog","AI_INSIGHT_CARD_COPY","title","site","workspace","action","InsightTile","props","help","anchor","header","contentGutterX","contentGutterY","spacing","sx","alignItems","variant","color","text","size","startIcon","path","onClick","onAsk","InsightUpsellTile","upgrade","open","setOpen","kind","onClose","AiInsightHostCard","hostId","entitled","route","data","user","orgId","orgSlug","host","subdomain","surface","uid","AiInsightOrgCard","orgMount"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ConsolePluginPageProps } from '@aglyn/aglyn/plugin-manager/feature-plugins';
|
|
2
|
-
import type
|
|
2
|
+
import { type AiJobSummary } from '../model/ai-jobs.types';
|
|
3
3
|
/**
|
|
4
4
|
* A site's AI jobs (AGL-3596), at `/ai-jobs` under the site: every job that
|
|
5
5
|
* worked on this site, newest first, each linking to its own page.
|
|
@@ -19,5 +19,10 @@ export declare const AI_JOBS_LIST_TIMEOUT_MS = 20000;
|
|
|
19
19
|
export declare function aiJobNetCredits(job: Pick<AiJobSummary, 'creditsSpent' | 'refundedCredits'>): number;
|
|
20
20
|
/** The kind as a row names it: `Site`, `Page`, `AI job`. */
|
|
21
21
|
export declare function aiJobKindLabel(kind: AiJobSummary['kind']): string;
|
|
22
|
+
/**
|
|
23
|
+
* Every kind a row can name, in the kinds registry's order: the labels the
|
|
24
|
+
* rows' chips read, so the empty state never falls behind a new kind.
|
|
25
|
+
*/
|
|
26
|
+
export declare function aiJobKindLabels(): string[];
|
|
22
27
|
export declare function AiJobsListPage({ hostId, basePath }: ConsolePluginPageProps): import("react").JSX.Element;
|
|
23
28
|
export default AiJobsListPage;
|
|
@@ -23,6 +23,7 @@ import { useUser } from "@aglyn/tenant-feature-instance";
|
|
|
23
23
|
import { Alert, Box, Button, Card, Chip, CircularProgress, Container, List, ListItemButton, Stack, Typography } from "@mui/material";
|
|
24
24
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
25
25
|
import { AI_JOB_PHASE_LABELS, aiJobKindNoun, aiJobPhase } from "../model/ai-job-activity.js";
|
|
26
|
+
import { AI_JOB_KINDS } from "../model/ai-jobs.types.js";
|
|
26
27
|
import { publishAiJob } from "./ai-jobs-store.js";
|
|
27
28
|
import { useAiJobSite } from "./ai-job-site.js";
|
|
28
29
|
/**
|
|
@@ -60,6 +61,18 @@ const WHEN = new Intl.DateTimeFormat('en-US', {
|
|
|
60
61
|
const noun = aiJobKindNoun(kind);
|
|
61
62
|
return noun === 'AI job' ? noun : noun.charAt(0).toUpperCase() + noun.slice(1);
|
|
62
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Every kind a row can name, in the kinds registry's order: the labels the
|
|
66
|
+
* rows' chips read, so the empty state never falls behind a new kind.
|
|
67
|
+
*/ export function aiJobKindLabels() {
|
|
68
|
+
const labels = AI_JOB_KINDS.map(aiJobKindLabel).filter((label)=>label !== 'AI job');
|
|
69
|
+
return [
|
|
70
|
+
...new Set(labels)
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
/** `A, B and C`. */ function listOf(items) {
|
|
74
|
+
return items.length < 2 ? items.join('') : `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`;
|
|
75
|
+
}
|
|
63
76
|
const LOAD_ERROR = 'This site’s AI jobs could not be loaded. Try again in a moment.';
|
|
64
77
|
async function readSiteJobs(user, orgId, hostId, signal) {
|
|
65
78
|
try {
|
|
@@ -209,12 +222,12 @@ export function AiJobsListPage({ hostId, basePath }) {
|
|
|
209
222
|
/*#__PURE__*/ _jsx(Typography, {
|
|
210
223
|
variant: "body1",
|
|
211
224
|
color: "text.secondary",
|
|
212
|
-
children: `An AI job is ${PLATFORM_BRAND_NAME} AI
|
|
225
|
+
children: `An AI job is ${PLATFORM_BRAND_NAME} AI making something for this site from a short ` + `description you write. Each job is one of these kinds: ${listOf(aiJobKindLabels())}. ` + 'Each one gets a page here that shows its progress and what it made.'
|
|
213
226
|
}),
|
|
214
227
|
/*#__PURE__*/ _jsx(Typography, {
|
|
215
228
|
variant: "body1",
|
|
216
229
|
color: "text.secondary",
|
|
217
|
-
children: 'To start one, choose Create with AI on
|
|
230
|
+
children: 'To start one, choose Create with AI on any list that offers it, such as this site’s ' + 'Pages or Forms. A new site can also be built with AI when it is created.'
|
|
218
231
|
})
|
|
219
232
|
]
|
|
220
233
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-jobs-list-page.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 { PLATFORM_BRAND_NAME } from '@aglyn/aglyn'\nimport type { ConsolePluginPageProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { aiJobRefundCopy } from '../model/ai-job-failure-copy'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch, type MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Card,\n Chip,\n CircularProgress,\n Container,\n List,\n ListItemButton,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { AI_JOB_PHASE_LABELS, aiJobKindNoun, aiJobPhase, type AiJobPhase } from '../model/ai-job-activity'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { publishAiJob } from './ai-jobs-store'\nimport { useAiJobSite } from './ai-job-site'\n\n/**\n * A site's AI jobs (AGL-3596), at `/ai-jobs` under the site: every job that\n * worked on this site, newest first, each linking to its own page.\n *\n * Read through the jobs route, never Firestore, so the membership, flag and\n * plan rungs that answer every other jobs read answer this one. The site is\n * a query on the route (`hostId`), not a filter of the workspace's list.\n *\n * Every wait ends: the site's read and the list's each settle on content,\n * the empty state, or a sentence with Try again.\n */\n\n/** The most jobs the page lists; the newest, which is what a person comes for. */\nexport const AI_JOBS_PAGE_LIMIT = 50\n\n/** How long the list may take before the page says it could not be loaded. */\nexport const AI_JOBS_LIST_TIMEOUT_MS = 20_000\n\n/** How much of a brief a row shows. */\nconst BRIEF_PREVIEW_CHARS = 120\n\n/** A row's chip, colored as the AI jobs drawer colors it. */\nconst PHASE_COLOR: Record<AiJobPhase, 'default' | 'primary' | 'success' | 'error' | 'warning'> = {\n queued: 'default',\n planning: 'primary',\n 'plan-ready': 'warning',\n building: 'primary',\n attention: 'warning',\n done: 'success',\n failed: 'error',\n canceled: 'default',\n}\n\nconst WHEN = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'short' })\n\n/** What a job cost the person: what it spent, less what we refunded. */\nexport function aiJobNetCredits(job: Pick<AiJobSummary, 'creditsSpent' | 'refundedCredits'>): number {\n return Math.max(0, (job.creditsSpent ?? 0) - (job.refundedCredits ?? 0))\n}\n\n/** The kind as a row names it: `Site`, `Page`, `AI job`. */\nexport function aiJobKindLabel(kind: AiJobSummary['kind']): string {\n const noun = aiJobKindNoun(kind)\n return noun === 'AI job' ? noun : noun.charAt(0).toUpperCase() + noun.slice(1)\n}\n\ntype ListState =\n | { status: 'loading' }\n | { status: 'ready'; jobs: AiJobSummary[] }\n | { status: 'error'; message: string }\n\nconst LOAD_ERROR = 'This site’s AI jobs could not be loaded. Try again in a moment.'\n\nasync function readSiteJobs(\n user: MaybeTokenSource,\n orgId: string,\n hostId: string,\n signal: AbortSignal,\n): Promise<ListState> {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&hostId=${encodeURIComponent(hostId)}&limit=${AI_JOBS_PAGE_LIMIT}`,\n { signal },\n )\n const payload = (await response.json().catch(() => null)) as { jobs?: AiJobSummary[]; error?: string } | null\n if (!response.ok) return { status: 'error', message: String(payload?.error ?? LOAD_ERROR) }\n const jobs = payload?.jobs ?? []\n // A job the top-bar indicator still counts that has settled since leaves it.\n for (const job of jobs) publishAiJob(job)\n return { status: 'ready', jobs }\n } catch {\n return { status: 'error', message: LOAD_ERROR }\n }\n}\n\nexport function AiJobsListPage({ hostId, basePath }: ConsolePluginPageProps) {\n const { data: user } = useUser()\n const userRef = useRef<MaybeTokenSource>(user)\n userRef.current = user\n const site = useAiJobSite(hostId)\n const orgId = site.status === 'ready' ? site.orgId : null\n const [list, setList] = useState<ListState>({ status: 'loading' })\n const [attempt, setAttempt] = useState(0)\n const uid = user?.uid ?? null\n\n useEffect(() => {\n if (!orgId || !hostId || !uid) return undefined\n const controller = new AbortController()\n let settled = false\n setList({ status: 'loading' })\n const timer = setTimeout(() => {\n if (settled) return\n settled = true\n controller.abort()\n setList({ status: 'error', message: LOAD_ERROR })\n }, AI_JOBS_LIST_TIMEOUT_MS)\n void readSiteJobs(userRef.current, orgId, hostId, controller.signal).then((next) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n setList(next)\n })\n return () => {\n settled = true\n clearTimeout(timer)\n controller.abort()\n }\n }, [orgId, hostId, uid, attempt])\n\n const retry = useCallback(() => setAttempt((count) => count + 1), [])\n\n const siteName = site.status === 'ready' ? site.name : ''\n const state: ListState =\n site.status === 'error'\n ? { status: 'error', message: 'This site could not be read. Check your connection and reload the page.' }\n : list\n\n return (\n <Container maxWidth=\"md\" sx={{ py: { xs: 3, sm: 5 }, px: { xs: 2, sm: 3 } }}>\n <Stack spacing={3}>\n <Stack spacing={1}>\n {siteName ? (\n <Typography variant=\"overline\" color=\"text.secondary\">\n {siteName}\n </Typography>\n ) : null}\n <Typography variant=\"h4\" component=\"h1\">\n {'AI jobs'}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n {`What ${PLATFORM_BRAND_NAME} AI has done and is doing on this site, newest first.`}\n </Typography>\n </Stack>\n {state.status === 'loading' ? (\n <Stack sx={{ alignItems: 'center', py: 4 }}>\n <CircularProgress aria-label=\"Loading AI jobs\" />\n </Stack>\n ) : state.status === 'error' ? (\n <Alert\n severity=\"warning\"\n action={\n site.status === 'error' ? undefined : (\n <Button color=\"inherit\" size=\"small\" onClick={retry}>\n {'Try again'}\n </Button>\n )\n }\n >\n {state.message}\n </Alert>\n ) : state.jobs.length === 0 ? (\n <Card variant=\"outlined\" sx={{ borderRadius: 2, p: { xs: 2, sm: 3 } }}>\n <Stack spacing={1}>\n <Typography variant=\"h6\" component=\"h2\">\n {'No AI jobs on this site yet'}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n {`An AI job is ${PLATFORM_BRAND_NAME} AI building something for this site from a short ` +\n 'description you write: a page, a page template, a layout, a form or a reusable component. ' +\n 'Each one gets a page here that shows its progress and what it built.'}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n {'To start one, choose Create with AI on this site’s Pages, Templates, Layouts, Forms or ' +\n 'Components page. A new site can also be built with AI when it is created.'}\n </Typography>\n </Stack>\n </Card>\n ) : (\n <Card variant=\"outlined\" sx={{ borderRadius: 2 }}>\n <List disablePadding aria-label=\"AI jobs\">\n {state.jobs.map((job, index) => {\n const phase = aiJobPhase(job)\n const credits = aiJobNetCredits(job)\n const refund = aiJobRefundCopy(job)\n const brief =\n job.brief.length > BRIEF_PREVIEW_CHARS ? `${job.brief.slice(0, BRIEF_PREVIEW_CHARS)}…` : job.brief\n const created = new Date(job.createdAt)\n return (\n <ListItemButton\n key={job.id}\n component={AppLink}\n href={`${basePath ?? ''}/${encodeURIComponent(job.id)}`}\n divider={index < state.jobs.length - 1}\n sx={{ display: 'block', py: 1.5 }}\n >\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 0.5 }}>\n <Chip size=\"small\" variant=\"outlined\" label={aiJobKindLabel(job.kind)} />\n <Chip size=\"small\" color={PHASE_COLOR[phase]} label={AI_JOB_PHASE_LABELS[phase]} />\n <Box sx={{ flexGrow: 1 }} />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {credits > 0 ? `${credits.toLocaleString('en-US')} credits · ` : ''}\n {Number.isNaN(created.getTime()) ? null : (\n <time dateTime={job.createdAt}>{WHEN.format(created)}</time>\n )}\n </Typography>\n </Stack>\n <Typography variant=\"body2\" sx={{ mt: 0.75, wordBreak: 'break-word' }}>\n {brief}\n </Typography>\n {/* What became of its credits, from the job's recorded give-back (AGL-3596). */}\n {refund ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\" sx={{ mt: 0.5 }}>\n {refund}\n </Typography>\n ) : null}\n </ListItemButton>\n )\n })}\n </List>\n </Card>\n )}\n {state.status === 'ready' && state.jobs.length >= AI_JOBS_PAGE_LIMIT ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Showing the ${AI_JOBS_PAGE_LIMIT} most recent jobs.`}\n </Typography>\n ) : null}\n </Stack>\n </Container>\n )\n}\n\nexport default AiJobsListPage\n"],"names":["PLATFORM_BRAND_NAME","aiJobRefundCopy","AppLink","authorizedFetch","useUser","Alert","Box","Button","Card","Chip","CircularProgress","Container","List","ListItemButton","Stack","Typography","useCallback","useEffect","useRef","useState","AI_JOB_PHASE_LABELS","aiJobKindNoun","aiJobPhase","publishAiJob","useAiJobSite","AI_JOBS_PAGE_LIMIT","AI_JOBS_LIST_TIMEOUT_MS","BRIEF_PREVIEW_CHARS","PHASE_COLOR","queued","planning","building","attention","done","failed","canceled","WHEN","Intl","DateTimeFormat","dateStyle","timeStyle","aiJobNetCredits","job","Math","max","creditsSpent","refundedCredits","aiJobKindLabel","kind","noun","charAt","toUpperCase","slice","LOAD_ERROR","readSiteJobs","user","orgId","hostId","signal","response","encodeURIComponent","payload","json","catch","ok","status","message","String","error","jobs","AiJobsListPage","basePath","data","userRef","current","site","list","setList","attempt","setAttempt","uid","undefined","controller","AbortController","settled","timer","setTimeout","abort","then","next","clearTimeout","retry","count","siteName","name","state","maxWidth","sx","py","xs","sm","px","spacing","variant","color","component","alignItems","aria-label","severity","action","size","onClick","length","borderRadius","p","disablePadding","map","index","phase","credits","refund","brief","created","Date","createdAt","href","id","divider","display","direction","useFlexGap","flexWrap","rowGap","label","flexGrow","toLocaleString","Number","isNaN","getTime","time","dateTime","format","mt","wordBreak"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-jobs-list-page.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 { PLATFORM_BRAND_NAME } from '@aglyn/aglyn'\nimport type { ConsolePluginPageProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { aiJobRefundCopy } from '../model/ai-job-failure-copy'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch, type MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Card,\n Chip,\n CircularProgress,\n Container,\n List,\n ListItemButton,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { AI_JOB_PHASE_LABELS, aiJobKindNoun, aiJobPhase, type AiJobPhase } from '../model/ai-job-activity'\nimport { AI_JOB_KINDS, type AiJobSummary } from '../model/ai-jobs.types'\nimport { publishAiJob } from './ai-jobs-store'\nimport { useAiJobSite } from './ai-job-site'\n\n/**\n * A site's AI jobs (AGL-3596), at `/ai-jobs` under the site: every job that\n * worked on this site, newest first, each linking to its own page.\n *\n * Read through the jobs route, never Firestore, so the membership, flag and\n * plan rungs that answer every other jobs read answer this one. The site is\n * a query on the route (`hostId`), not a filter of the workspace's list.\n *\n * Every wait ends: the site's read and the list's each settle on content,\n * the empty state, or a sentence with Try again.\n */\n\n/** The most jobs the page lists; the newest, which is what a person comes for. */\nexport const AI_JOBS_PAGE_LIMIT = 50\n\n/** How long the list may take before the page says it could not be loaded. */\nexport const AI_JOBS_LIST_TIMEOUT_MS = 20_000\n\n/** How much of a brief a row shows. */\nconst BRIEF_PREVIEW_CHARS = 120\n\n/** A row's chip, colored as the AI jobs drawer colors it. */\nconst PHASE_COLOR: Record<AiJobPhase, 'default' | 'primary' | 'success' | 'error' | 'warning'> = {\n queued: 'default',\n planning: 'primary',\n 'plan-ready': 'warning',\n building: 'primary',\n attention: 'warning',\n done: 'success',\n failed: 'error',\n canceled: 'default',\n}\n\nconst WHEN = new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeStyle: 'short' })\n\n/** What a job cost the person: what it spent, less what we refunded. */\nexport function aiJobNetCredits(job: Pick<AiJobSummary, 'creditsSpent' | 'refundedCredits'>): number {\n return Math.max(0, (job.creditsSpent ?? 0) - (job.refundedCredits ?? 0))\n}\n\n/** The kind as a row names it: `Site`, `Page`, `AI job`. */\nexport function aiJobKindLabel(kind: AiJobSummary['kind']): string {\n const noun = aiJobKindNoun(kind)\n return noun === 'AI job' ? noun : noun.charAt(0).toUpperCase() + noun.slice(1)\n}\n\n/**\n * Every kind a row can name, in the kinds registry's order: the labels the\n * rows' chips read, so the empty state never falls behind a new kind.\n */\nexport function aiJobKindLabels(): string[] {\n const labels = AI_JOB_KINDS.map(aiJobKindLabel).filter((label) => label !== 'AI job')\n return [...new Set(labels)]\n}\n\n/** `A, B and C`. */\nfunction listOf(items: readonly string[]): string {\n return items.length < 2 ? items.join('') : `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`\n}\n\ntype ListState =\n | { status: 'loading' }\n | { status: 'ready'; jobs: AiJobSummary[] }\n | { status: 'error'; message: string }\n\nconst LOAD_ERROR = 'This site’s AI jobs could not be loaded. Try again in a moment.'\n\nasync function readSiteJobs(\n user: MaybeTokenSource,\n orgId: string,\n hostId: string,\n signal: AbortSignal,\n): Promise<ListState> {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&hostId=${encodeURIComponent(hostId)}&limit=${AI_JOBS_PAGE_LIMIT}`,\n { signal },\n )\n const payload = (await response.json().catch(() => null)) as { jobs?: AiJobSummary[]; error?: string } | null\n if (!response.ok) return { status: 'error', message: String(payload?.error ?? LOAD_ERROR) }\n const jobs = payload?.jobs ?? []\n // A job the top-bar indicator still counts that has settled since leaves it.\n for (const job of jobs) publishAiJob(job)\n return { status: 'ready', jobs }\n } catch {\n return { status: 'error', message: LOAD_ERROR }\n }\n}\n\nexport function AiJobsListPage({ hostId, basePath }: ConsolePluginPageProps) {\n const { data: user } = useUser()\n const userRef = useRef<MaybeTokenSource>(user)\n userRef.current = user\n const site = useAiJobSite(hostId)\n const orgId = site.status === 'ready' ? site.orgId : null\n const [list, setList] = useState<ListState>({ status: 'loading' })\n const [attempt, setAttempt] = useState(0)\n const uid = user?.uid ?? null\n\n useEffect(() => {\n if (!orgId || !hostId || !uid) return undefined\n const controller = new AbortController()\n let settled = false\n setList({ status: 'loading' })\n const timer = setTimeout(() => {\n if (settled) return\n settled = true\n controller.abort()\n setList({ status: 'error', message: LOAD_ERROR })\n }, AI_JOBS_LIST_TIMEOUT_MS)\n void readSiteJobs(userRef.current, orgId, hostId, controller.signal).then((next) => {\n if (settled) return\n settled = true\n clearTimeout(timer)\n setList(next)\n })\n return () => {\n settled = true\n clearTimeout(timer)\n controller.abort()\n }\n }, [orgId, hostId, uid, attempt])\n\n const retry = useCallback(() => setAttempt((count) => count + 1), [])\n\n const siteName = site.status === 'ready' ? site.name : ''\n const state: ListState =\n site.status === 'error'\n ? { status: 'error', message: 'This site could not be read. Check your connection and reload the page.' }\n : list\n\n return (\n <Container maxWidth=\"md\" sx={{ py: { xs: 3, sm: 5 }, px: { xs: 2, sm: 3 } }}>\n <Stack spacing={3}>\n <Stack spacing={1}>\n {siteName ? (\n <Typography variant=\"overline\" color=\"text.secondary\">\n {siteName}\n </Typography>\n ) : null}\n <Typography variant=\"h4\" component=\"h1\">\n {'AI jobs'}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n {`What ${PLATFORM_BRAND_NAME} AI has done and is doing on this site, newest first.`}\n </Typography>\n </Stack>\n {state.status === 'loading' ? (\n <Stack sx={{ alignItems: 'center', py: 4 }}>\n <CircularProgress aria-label=\"Loading AI jobs\" />\n </Stack>\n ) : state.status === 'error' ? (\n <Alert\n severity=\"warning\"\n action={\n site.status === 'error' ? undefined : (\n <Button color=\"inherit\" size=\"small\" onClick={retry}>\n {'Try again'}\n </Button>\n )\n }\n >\n {state.message}\n </Alert>\n ) : state.jobs.length === 0 ? (\n <Card variant=\"outlined\" sx={{ borderRadius: 2, p: { xs: 2, sm: 3 } }}>\n <Stack spacing={1}>\n <Typography variant=\"h6\" component=\"h2\">\n {'No AI jobs on this site yet'}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n {`An AI job is ${PLATFORM_BRAND_NAME} AI making something for this site from a short ` +\n `description you write. Each job is one of these kinds: ${listOf(aiJobKindLabels())}. ` +\n 'Each one gets a page here that shows its progress and what it made.'}\n </Typography>\n <Typography variant=\"body1\" color=\"text.secondary\">\n {'To start one, choose Create with AI on any list that offers it, such as this site’s ' +\n 'Pages or Forms. A new site can also be built with AI when it is created.'}\n </Typography>\n </Stack>\n </Card>\n ) : (\n <Card variant=\"outlined\" sx={{ borderRadius: 2 }}>\n <List disablePadding aria-label=\"AI jobs\">\n {state.jobs.map((job, index) => {\n const phase = aiJobPhase(job)\n const credits = aiJobNetCredits(job)\n const refund = aiJobRefundCopy(job)\n const brief =\n job.brief.length > BRIEF_PREVIEW_CHARS ? `${job.brief.slice(0, BRIEF_PREVIEW_CHARS)}…` : job.brief\n const created = new Date(job.createdAt)\n return (\n <ListItemButton\n key={job.id}\n component={AppLink}\n href={`${basePath ?? ''}/${encodeURIComponent(job.id)}`}\n divider={index < state.jobs.length - 1}\n sx={{ display: 'block', py: 1.5 }}\n >\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 0.5 }}>\n <Chip size=\"small\" variant=\"outlined\" label={aiJobKindLabel(job.kind)} />\n <Chip size=\"small\" color={PHASE_COLOR[phase]} label={AI_JOB_PHASE_LABELS[phase]} />\n <Box sx={{ flexGrow: 1 }} />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {credits > 0 ? `${credits.toLocaleString('en-US')} credits · ` : ''}\n {Number.isNaN(created.getTime()) ? null : (\n <time dateTime={job.createdAt}>{WHEN.format(created)}</time>\n )}\n </Typography>\n </Stack>\n <Typography variant=\"body2\" sx={{ mt: 0.75, wordBreak: 'break-word' }}>\n {brief}\n </Typography>\n {/* What became of its credits, from the job's recorded give-back (AGL-3596). */}\n {refund ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\" sx={{ mt: 0.5 }}>\n {refund}\n </Typography>\n ) : null}\n </ListItemButton>\n )\n })}\n </List>\n </Card>\n )}\n {state.status === 'ready' && state.jobs.length >= AI_JOBS_PAGE_LIMIT ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Showing the ${AI_JOBS_PAGE_LIMIT} most recent jobs.`}\n </Typography>\n ) : null}\n </Stack>\n </Container>\n )\n}\n\nexport default AiJobsListPage\n"],"names":["PLATFORM_BRAND_NAME","aiJobRefundCopy","AppLink","authorizedFetch","useUser","Alert","Box","Button","Card","Chip","CircularProgress","Container","List","ListItemButton","Stack","Typography","useCallback","useEffect","useRef","useState","AI_JOB_PHASE_LABELS","aiJobKindNoun","aiJobPhase","AI_JOB_KINDS","publishAiJob","useAiJobSite","AI_JOBS_PAGE_LIMIT","AI_JOBS_LIST_TIMEOUT_MS","BRIEF_PREVIEW_CHARS","PHASE_COLOR","queued","planning","building","attention","done","failed","canceled","WHEN","Intl","DateTimeFormat","dateStyle","timeStyle","aiJobNetCredits","job","Math","max","creditsSpent","refundedCredits","aiJobKindLabel","kind","noun","charAt","toUpperCase","slice","aiJobKindLabels","labels","map","filter","label","Set","listOf","items","length","join","LOAD_ERROR","readSiteJobs","user","orgId","hostId","signal","response","encodeURIComponent","payload","json","catch","ok","status","message","String","error","jobs","AiJobsListPage","basePath","data","userRef","current","site","list","setList","attempt","setAttempt","uid","undefined","controller","AbortController","settled","timer","setTimeout","abort","then","next","clearTimeout","retry","count","siteName","name","state","maxWidth","sx","py","xs","sm","px","spacing","variant","color","component","alignItems","aria-label","severity","action","size","onClick","borderRadius","p","disablePadding","index","phase","credits","refund","brief","created","Date","createdAt","href","id","divider","display","direction","useFlexGap","flexWrap","rowGap","flexGrow","toLocaleString","Number","isNaN","getTime","time","dateTime","format","mt","wordBreak"],"mappings":"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"}
|
|
@@ -121,7 +121,9 @@ function nameOf(proposal) {
|
|
|
121
121
|
}
|
|
122
122
|
/** "Create with AI" in the Functions or the Variables card's header. */ export function AiLogicCreateButton({ hostId, orgId, kind, propose, entitled, upgrade }) {
|
|
123
123
|
const copy = AI_LOGIC_COPY;
|
|
124
|
-
const help = pluginDocsHelp('aiLogic'
|
|
124
|
+
const help = pluginDocsHelp('aiLogic', {
|
|
125
|
+
anchor: kind === 'variable' ? '#variable' : '#function'
|
|
126
|
+
});
|
|
125
127
|
const { data: user } = useUser();
|
|
126
128
|
const run = useAiJobRun(user, copy.failed);
|
|
127
129
|
const { reset } = run;
|
|
@@ -211,7 +213,7 @@ function nameOf(proposal) {
|
|
|
211
213
|
href: help.href,
|
|
212
214
|
target: "_blank",
|
|
213
215
|
rel: "noopener",
|
|
214
|
-
title: help.
|
|
216
|
+
title: help.excerptText,
|
|
215
217
|
children: 'How it works'
|
|
216
218
|
})
|
|
217
219
|
]
|
|
@@ -347,7 +349,7 @@ function nameOf(proposal) {
|
|
|
347
349
|
href: help.href,
|
|
348
350
|
target: "_blank",
|
|
349
351
|
rel: "noopener",
|
|
350
|
-
title: help.
|
|
352
|
+
title: help.excerptText,
|
|
351
353
|
children: 'How it works'
|
|
352
354
|
})
|
|
353
355
|
]
|