@aglyn/plugins-ai 1.0.0-beta.233 → 1.0.0-beta.235

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.
Files changed (199) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +25 -0
  3. package/src/lib/activity/ai-activity-actions.js +85 -4
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/components/ai-brief-dialog.component.js +46 -2
  6. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  7. package/src/lib/components/ai-campaign-create.component.js +1 -1
  8. package/src/lib/components/ai-campaign-create.component.js.map +1 -1
  9. package/src/lib/components/ai-credits-card.component.js +3 -3
  10. package/src/lib/components/ai-credits-card.component.js.map +1 -1
  11. package/src/lib/components/ai-crm-email-draft.component.js +1 -1
  12. package/src/lib/components/ai-crm-email-draft.component.js.map +1 -1
  13. package/src/lib/components/ai-crm-import-mapping.component.js +1 -1
  14. package/src/lib/components/ai-crm-import-mapping.component.js.map +1 -1
  15. package/src/lib/components/ai-crm-record-card.component.js +1 -1
  16. package/src/lib/components/ai-crm-record-card.component.js.map +1 -1
  17. package/src/lib/components/ai-describe-automation.component.js +4 -2
  18. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  19. package/src/lib/components/ai-describe-org-automation.component.js +1 -1
  20. package/src/lib/components/ai-describe-org-automation.component.js.map +1 -1
  21. package/src/lib/components/ai-experiment-cards.component.js +2 -2
  22. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  23. package/src/lib/components/ai-explain-automation.component.js +1 -1
  24. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  25. package/src/lib/components/ai-insight-card.component.js +3 -1
  26. package/src/lib/components/ai-insight-card.component.js.map +1 -1
  27. package/src/lib/components/ai-job-links.d.ts +6 -0
  28. package/src/lib/components/ai-job-links.js +9 -0
  29. package/src/lib/components/ai-job-links.js.map +1 -1
  30. package/src/lib/components/ai-jobs-list-page.component.d.ts +6 -1
  31. package/src/lib/components/ai-jobs-list-page.component.js +15 -2
  32. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  33. package/src/lib/components/ai-logic.component.js +5 -3
  34. package/src/lib/components/ai-logic.component.js.map +1 -1
  35. package/src/lib/components/ai-media-create.component.js +88 -16
  36. package/src/lib/components/ai-media-create.component.js.map +1 -1
  37. package/src/lib/components/ai-model-selector.component.js +8 -0
  38. package/src/lib/components/ai-model-selector.component.js.map +1 -1
  39. package/src/lib/components/ai-overlay-cards.component.js +2 -2
  40. package/src/lib/components/ai-overlay-cards.component.js.map +1 -1
  41. package/src/lib/components/ai-product-copy-card.component.js +2 -1
  42. package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
  43. package/src/lib/components/ai-products-hub-card.component.js +2 -1
  44. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  45. package/src/lib/components/ai-revise-automation.component.js +1 -1
  46. package/src/lib/components/ai-revise-automation.component.js.map +1 -1
  47. package/src/lib/components/ai-seo-fields-card.component.js +7 -3
  48. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  49. package/src/lib/components/ai-site-batch-card.component.js +7 -4
  50. package/src/lib/components/ai-site-batch-card.component.js.map +1 -1
  51. package/src/lib/components/ai-site-build-page.component.js +48 -6
  52. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  53. package/src/lib/components/ai-site-seo-start-card.component.js +7 -4
  54. package/src/lib/components/ai-site-seo-start-card.component.js.map +1 -1
  55. package/src/lib/components/ai-site-start-card.component.js +152 -36
  56. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  57. package/src/lib/components/ai-theme-proposal-card.component.js +1 -0
  58. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  59. package/src/lib/components/ai-upsell-dialog.component.js +17 -3
  60. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  61. package/src/lib/components/ai-usage-strip.component.js +13 -2
  62. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  63. package/src/lib/components/assist-build-card.component.js +20 -3
  64. package/src/lib/components/assist-build-card.component.js.map +1 -1
  65. package/src/lib/components/assist-jobs-drawer.component.js +7 -1
  66. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  67. package/src/lib/components/billing-ai-allotments.component.js +2 -2
  68. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  69. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  70. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  71. package/src/lib/components/billing-assist-overage-card.component.js +2 -2
  72. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  73. package/src/lib/components/host-ai-allotments.component.js +2 -2
  74. package/src/lib/components/host-ai-allotments.component.js.map +1 -1
  75. package/src/lib/components/member-ai-allotment-card.component.js +2 -2
  76. package/src/lib/components/member-ai-allotment-card.component.js.map +1 -1
  77. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  78. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  79. package/src/lib/components/staff-org-ai-conversations.component.d.ts +9 -3
  80. package/src/lib/components/staff-org-ai-conversations.component.js +174 -83
  81. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -1
  82. package/src/lib/components/staff-user-ai-usage-card.component.js +2 -1
  83. package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
  84. package/src/lib/jobs/ai-job-admission.d.ts +2 -1
  85. package/src/lib/jobs/ai-job-admission.js.map +1 -1
  86. package/src/lib/jobs/ai-job-email-step.d.ts +39 -0
  87. package/src/lib/jobs/ai-job-email-step.js +116 -5
  88. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  89. package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
  90. package/src/lib/jobs/ai-job-layout-language.js +6 -1
  91. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  92. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +14 -0
  93. package/src/lib/jobs/ai-job-layout-site-pages.js +43 -11
  94. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  95. package/src/lib/jobs/ai-job-layout-step.js +3 -2
  96. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  97. package/src/lib/jobs/ai-job-page-language.d.ts +26 -1
  98. package/src/lib/jobs/ai-job-page-language.js +75 -13
  99. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  100. package/src/lib/jobs/ai-job-page-step.d.ts +3 -0
  101. package/src/lib/jobs/ai-job-page-step.js +32 -7
  102. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  103. package/src/lib/jobs/ai-job-plan-step.d.ts +34 -0
  104. package/src/lib/jobs/ai-job-plan-step.js +106 -14
  105. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  106. package/src/lib/jobs/ai-job-site-content.d.ts +81 -13
  107. package/src/lib/jobs/ai-job-site-content.js +210 -43
  108. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  109. package/src/lib/jobs/ai-job-site-step.d.ts +19 -0
  110. package/src/lib/jobs/ai-job-site-step.js +152 -51
  111. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  112. package/src/lib/jobs/ai-jobs.d.ts +8 -4
  113. package/src/lib/jobs/ai-jobs.js +19 -2
  114. package/src/lib/jobs/ai-jobs.js.map +1 -1
  115. package/src/lib/jobs/ai-layout-stock-photos.d.ts +98 -0
  116. package/src/lib/jobs/ai-layout-stock-photos.js +248 -0
  117. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -0
  118. package/src/lib/jobs/ai-site-publish.d.ts +11 -0
  119. package/src/lib/jobs/ai-site-publish.js +31 -1
  120. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  121. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.d.ts +151 -0
  122. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js +183 -0
  123. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js.map +1 -0
  124. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.d.ts +27 -0
  125. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js +192 -0
  126. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js.map +1 -0
  127. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.d.ts +27 -0
  128. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js +222 -0
  129. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js.map +1 -0
  130. package/src/lib/layout-language/ai-layout-compiler.d.ts +37 -0
  131. package/src/lib/layout-language/ai-layout-compiler.js +1003 -19
  132. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  133. package/src/lib/layout-language/ai-layout-design.d.ts +114 -0
  134. package/src/lib/layout-language/ai-layout-design.js +209 -0
  135. package/src/lib/layout-language/ai-layout-design.js.map +1 -0
  136. package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
  137. package/src/lib/layout-language/ai-layout-frame.js +33 -8
  138. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  139. package/src/lib/layout-language/ai-layout-gaps.js +18 -2
  140. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  141. package/src/lib/layout-language/ai-layout-language.d.ts +2 -0
  142. package/src/lib/layout-language/ai-layout-language.js +15 -1
  143. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  144. package/src/lib/layout-language/ai-layout-links.d.ts +36 -0
  145. package/src/lib/layout-language/ai-layout-links.js +26 -2
  146. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  147. package/src/lib/layout-language/ai-layout-listings.d.ts +125 -0
  148. package/src/lib/layout-language/ai-layout-listings.js +187 -0
  149. package/src/lib/layout-language/ai-layout-listings.js.map +1 -0
  150. package/src/lib/layout-language/ai-layout-pictures.d.ts +11 -5
  151. package/src/lib/layout-language/ai-layout-pictures.js +80 -17
  152. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  153. package/src/lib/model/ai-job-notice.d.ts +9 -1
  154. package/src/lib/model/ai-job-notice.js +23 -0
  155. package/src/lib/model/ai-job-notice.js.map +1 -1
  156. package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
  157. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  158. package/src/lib/model/ai-site-build-progress.d.ts +12 -2
  159. package/src/lib/model/ai-site-build-progress.js +104 -15
  160. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  161. package/src/lib/model/ai-site-job.d.ts +236 -24
  162. package/src/lib/model/ai-site-job.js +269 -22
  163. package/src/lib/model/ai-site-job.js.map +1 -1
  164. package/src/lib/model/ai-site-kinds.d.ts +30 -8
  165. package/src/lib/model/ai-site-kinds.js +735 -116
  166. package/src/lib/model/ai-site-kinds.js.map +1 -1
  167. package/src/lib/model/ai-site-look.d.ts +21 -0
  168. package/src/lib/model/ai-site-look.js +63 -14
  169. package/src/lib/model/ai-site-look.js.map +1 -1
  170. package/src/lib/model/ai-site-start-seo.d.ts +30 -0
  171. package/src/lib/model/ai-site-start-seo.js +132 -11
  172. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  173. package/src/lib/model/ai-site-start.d.ts +2 -2
  174. package/src/lib/model/ai-site-start.js +3 -3
  175. package/src/lib/model/ai-site-start.js.map +1 -1
  176. package/src/lib/plugin.js +15 -3
  177. package/src/lib/plugin.js.map +1 -1
  178. package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -1
  179. package/src/lib/runtime/ai-doctrine-validators.js +227 -4
  180. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  181. package/src/lib/runtime/ai-doctrine.js +5 -3
  182. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  183. package/src/lib/runtime/ai-palette.generated.d.ts +2 -0
  184. package/src/lib/runtime/ai-palette.generated.js +31 -5
  185. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  186. package/src/lib/server/ai-admin-conversations.d.ts +9 -0
  187. package/src/lib/server/ai-admin-conversations.js +151 -9
  188. package/src/lib/server/ai-admin-conversations.js.map +1 -1
  189. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  190. package/src/lib/server/ai-jobs-route.js +15 -3
  191. package/src/lib/server/ai-jobs-route.js.map +1 -1
  192. package/src/lib/server/assist-edit.js +5 -2
  193. package/src/lib/server/assist-edit.js.map +1 -1
  194. package/src/lib/usage/free-ai-credits-left.d.ts +54 -0
  195. package/src/lib/usage/free-ai-credits-left.js +62 -0
  196. package/src/lib/usage/free-ai-credits-left.js.map +1 -0
  197. package/src/lib/usage/staff-org-ai-conversations.d.ts +23 -0
  198. package/src/lib/usage/staff-org-ai-conversations.js +45 -0
  199. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -1
@@ -261,6 +261,7 @@ export function AiThemeProposalCard(props) {
261
261
  contentGutterY: true,
262
262
  header: "Theme assistant",
263
263
  help: pluginDocsHelp('aiThemes', {
264
+ anchor: '#describe-a-change',
264
265
  excerpt: 'Describe the change and get a proposal for every control the editor has, previewed before and after. Nothing is saved until you save it in the editor.'
265
266
  }),
266
267
  children: /*#__PURE__*/ _jsxs(Stack, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-theme-proposal-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { HostThemeSource } from '@aglyn/aglyn/app-utils/site-theme'\nimport type { ConsoleHostThemeZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport type { HostThemeScheme } from '@aglyn/shared-data-types'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { themeEditorControl } from '@aglyn/shared-ui-theme/util/theme-editor-fields'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Grid,\n Stack,\n Tab,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Tabs,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n AI_JOB_TERMINAL_STATUSES,\n type AiJobSummary,\n} from '../model/ai-jobs.types'\nimport {\n aiThemeColorToken,\n aiThemeProposalAfter,\n aiThemeStaleChanges,\n readAiThemeProposal,\n type AiThemeControlChange,\n type AiThemeCorrection,\n type AiThemeProposal,\n type AiThemeProposalMode,\n} from '../model/ai-theme-proposal'\nimport { readEventFrames } from './assist-jobs-drawer.component'\n\n/**\n * The theme assistant (AGL-2938), on the site's Theme section through the\n * `hostTheme` zone: a brief in, a theme proposal out, previewed before and\n * after with the editor's own preview, and put in the editor below as\n * unsaved changes when the person chooses. It never writes the theme — the\n * editor's Save does, through the same guarded path every edit takes.\n *\n * It renders nothing until the jobs route has answered for this workspace.\n * The shell has already decided the plan and the member's permission before\n * mounting it, but a released-off feature does not exist, and the release\n * flag is the route's to decide: a 404 there is this card staying absent.\n * The same read lists the site's recent proposals, so one made while the\n * person was elsewhere is still here to preview.\n */\n\n/** How many recent jobs the card reads to find this site's proposals. */\nconst RECENT_JOBS_READ = 20\n\n/** How many of this site's proposals the card offers again. */\nconst RECENT_PROPOSALS_SHOWN = 3\n\nconst BRIEF_MAX_CHARS = 4_000\n\nconst SOURCE_COPY: Record<HostThemeSource, string> = {\n installed:\n 'Saved as your changes on top of the installed theme. The publisher’s version stays as it is and can still take an update.',\n preset:\n 'Saved as your changes on top of the built-in theme, which stays as it is and can be restored.',\n custom:\n 'Saved as your changes on top of this theme of yours. Update it to keep them in the theme itself.',\n default:\n 'Only these values are stored. Everything else keeps following the default theme.',\n}\n\ntype Verdict = 'checking' | 'ready' | 'hidden'\n\nconst isTerminal = (job: AiJobSummary) => AI_JOB_TERMINAL_STATUSES.includes(job.status)\n\n/** The proposal a theme job produced, when it produced one. */\nfunction proposalOf(job: AiJobSummary | null): AiThemeProposal | null {\n const output = job?.outputs.find((entry) => entry.resource === 'theme')\n return output ? readAiThemeProposal(output.proposal) : null\n}\n\n/** A control's label, with its scheme when it has one. */\nfunction changeLabel(change: Pick<AiThemeControlChange, 'control' | 'scheme'>): string {\n return themeEditorControl(change.control).label\n}\n\nfunction Swatch({ value }: { value: string | number | null }) {\n if (value === null) {\n return (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Default'}\n </Typography>\n )\n }\n const text = String(value)\n const color = typeof value === 'string' && /^#[0-9a-f]{6}$/i.test(value)\n return (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n {color ? (\n <Box\n aria-hidden\n sx={{\n width: 14,\n height: 14,\n borderRadius: 0.5,\n border: 1,\n borderColor: 'divider',\n backgroundColor: text,\n flexShrink: 0,\n }}\n />\n ) : null}\n <Typography variant=\"body2\" sx={{ fontFamily: 'monospace', wordBreak: 'break-all' }}>\n {text}\n </Typography>\n </Stack>\n )\n}\n\nfunction correctionText(correction: AiThemeCorrection): string {\n const label = `${changeLabel(correction)} (${correction.scheme})`\n if (correction.reason === 'dark-value') {\n return `${label} set to ${correction.to} so the color keeps reading on the dark background.`\n }\n return (\n `${label} moved to ${correction.to} to reach ${correction.required}:1` +\n (correction.against?.length ? ` against ${correction.against.join(' and ')}` : '') +\n (correction.ratio ? ` (it was ${correction.ratio}:1).` : '.')\n )\n}\n\nexport function AiThemeProposalCard(props: ConsoleHostThemeZoneProps) {\n const { hostId, orgId, theme, themeSource, ThemePreview, proposeDraft } = props\n const { data: user } = useUser()\n // Held in a ref so the reads below key on WHO is signed in, never on the\n // identity of the object that says so.\n const userRef = useRef(user)\n userRef.current = user\n const uid = user?.uid ?? null\n\n const [verdict, setVerdict] = useState<Verdict>('checking')\n const [notice, setNotice] = useState<string | null>(null)\n const [recent, setRecent] = useState<AiJobSummary[]>([])\n const [brief, setBrief] = useState('')\n const [mode, setMode] = useState<AiThemeProposalMode>('modify')\n const [busy, setBusy] = useState(false)\n const [job, setJob] = useState<AiJobSummary | null>(null)\n const [scheme, setScheme] = useState<HostThemeScheme>('light')\n const [applied, setApplied] = useState<string | null>(null)\n const watchRef = useRef<AbortController | null>(null)\n\n useEffect(() => () => watchRef.current?.abort(), [])\n\n useEffect(() => {\n if (!orgId || !hostId || !uid) return\n let active = true\n setVerdict('checking')\n void (async () => {\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=${RECENT_JOBS_READ}`,\n )\n if (!active) return\n // The flag off (404) and the plan without generation (403): the\n // feature is not this workspace's, so the card is not here at all.\n if (response.status === 404 || response.status === 403) {\n setVerdict('hidden')\n return\n }\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'Recent theme proposals could not be loaded.'),\n )\n } else {\n const jobs = (payload?.jobs ?? []) as AiJobSummary[]\n setRecent(\n jobs\n .filter((entry) => entry.kind === 'theme' && entry.hostId === hostId)\n .filter((entry) => proposalOf(entry) !== null)\n .slice(0, RECENT_PROPOSALS_SHOWN),\n )\n }\n setVerdict('ready')\n } catch {\n if (!active) return\n setNotice('Recent theme proposals could not be loaded.')\n setVerdict('ready')\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, hostId, uid])\n\n /** Follows a job through the events route until it settles. */\n const watch = useCallback(async (jobId: string, org: string) => {\n watchRef.current?.abort()\n const controller = new AbortController()\n watchRef.current = controller\n let again = true\n while (again && !controller.signal.aborted) {\n again = false\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs/${encodeURIComponent(jobId)}/events?orgId=${encodeURIComponent(org)}`,\n { signal: controller.signal },\n )\n if (!response.ok || !response.body) return\n await readEventFrames(response.body, (event) => {\n if (event['type'] === 'state') setJob(event['job'] as AiJobSummary)\n else if (event['type'] === 'reconnect') again = true\n })\n } catch {\n return\n }\n }\n }, [])\n\n const propose = useCallback(async () => {\n if (!orgId || !brief.trim()) return\n setBusy(true)\n setNotice(null)\n setApplied(null)\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n kind: 'theme',\n brief: brief.trim(),\n inputs: { mode },\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'The theme proposal could not be started — try again.'),\n )\n return\n }\n const next = payload?.job as AiJobSummary | undefined\n if (!next) return\n setJob(next)\n if (!isTerminal(next)) await watch(next.id, orgId)\n } catch {\n setNotice('The theme proposal could not be started — try again.')\n } finally {\n setBusy(false)\n }\n }, [orgId, hostId, brief, mode, watch])\n\n const proposal = useMemo(() => proposalOf(job), [job])\n const after = useMemo(\n () => (proposal ? aiThemeProposalAfter(theme, proposal) : null),\n [theme, proposal],\n )\n const stale = useMemo(\n () => (proposal ? aiThemeStaleChanges(theme, proposal).length > 0 : false),\n [theme, proposal],\n )\n\n const apply = useCallback(() => {\n if (!job || !after) return\n proposeDraft(after.theme, job.id)\n setApplied(job.id)\n }, [job, after, proposeDraft])\n\n if (verdict !== 'ready') return null\n\n const running = Boolean(job && !isTerminal(job))\n\n return (\n <CardDisplay\n contentGutterX\n contentGutterY\n header=\"Theme assistant\"\n help={pluginDocsHelp('aiThemes', {\n excerpt:\n 'Describe the change and get a proposal for every control the editor has, previewed before and after. Nothing is saved until you save it in the editor.',\n })}\n >\n <Stack spacing={2}>\n <ToggleButtonGroup\n exclusive\n size=\"small\"\n value={mode}\n onChange={(_, value: AiThemeProposalMode | null) => value && setMode(value)}\n aria-label=\"What to propose\"\n >\n <ToggleButton value=\"modify\">{'Change what I describe'}</ToggleButton>\n <ToggleButton value=\"create\">{'Design a new theme'}</ToggleButton>\n </ToggleButtonGroup>\n <TextField\n multiline\n minRows={2}\n fullWidth\n label=\"Describe the change\"\n placeholder=\"Warmer, with a deep green primary and bigger headings on mobile\"\n helperText=\"A hex color in your description is used exactly. Paste a link to borrow a page’s colors.\"\n value={brief}\n onChange={(event) => setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS))}\n disabled={busy}\n />\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button variant=\"contained\" disabled={busy || !brief.trim()} onClick={() => void propose()}>\n {'Propose changes'}\n </Button>\n {running ? <CircularProgress size={18} aria-label=\"Working on a proposal\" /> : null}\n </Stack>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n\n {job && job.error && !proposal ? (\n <Alert severity={job.status === 'failed' ? 'error' : 'warning'}>{job.error}</Alert>\n ) : null}\n\n {!job && recent.length ? (\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{'Recent proposals for this site'}</Typography>\n {recent.map((entry) => (\n <Stack key={entry.id} direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ flexGrow: 1, minWidth: 0 }} noWrap>\n {entry.brief}\n </Typography>\n <Button size=\"small\" onClick={() => setJob(entry)}>\n {'Show'}\n </Button>\n </Stack>\n ))}\n </Stack>\n ) : null}\n\n {proposal && after ? (\n <Stack spacing={2} aria-label=\"Theme proposal\">\n {proposal.summary ? <Typography variant=\"body1\">{proposal.summary}</Typography> : null}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {SOURCE_COPY[proposal.source]}\n </Typography>\n {stale ? (\n <Alert severity=\"info\">\n {'The theme has changed since this proposal was made. The preview and the editor apply it to the theme as it is now.'}\n </Alert>\n ) : null}\n\n {proposal.changes.length || proposal.components.length || proposal.resetComponents ? (\n <Box>\n <ScrollTable size=\"small\" aria-label=\"Proposed changes\">\n <TableHead>\n <TableRow>\n <TableCell>{'Control'}</TableCell>\n <TableCell>{'Now'}</TableCell>\n <TableCell>{'Proposed'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {proposal.changes.map((change) => (\n <TableRow key={`${change.control}:${change.scheme ?? ''}`}>\n <TableCell>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\">{changeLabel(change)}</Typography>\n {aiThemeColorToken(change.control) && change.scheme ? (\n <Chip size=\"small\" variant=\"outlined\" label={change.scheme} />\n ) : null}\n </Stack>\n </TableCell>\n <TableCell>\n <Swatch value={change.before} />\n </TableCell>\n <TableCell>\n <Swatch value={change.value} />\n </TableCell>\n </TableRow>\n ))}\n {proposal.resetComponents ? (\n <TableRow>\n <TableCell>{themeEditorControl('components').label}</TableCell>\n <TableCell>{'This site’s overrides'}</TableCell>\n <TableCell>{'Theme defaults'}</TableCell>\n </TableRow>\n ) : null}\n {proposal.components.map((leaf) => (\n <TableRow key={`${leaf.component}:${leaf.target}:${leaf.slot}:${leaf.media}:${leaf.property}`}>\n <TableCell>\n <Typography variant=\"body2\">\n {[\n leaf.component,\n leaf.target === 'defaultProps' ? 'default' : leaf.slot,\n leaf.property,\n ].join(' · ')}\n </Typography>\n {leaf.media ? <Chip size=\"small\" variant=\"outlined\" label={leaf.media} /> : null}\n </TableCell>\n <TableCell>{'—'}</TableCell>\n <TableCell>\n <Swatch value={typeof leaf.value === 'boolean' ? String(leaf.value) : leaf.value} />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n </Box>\n ) : (\n <Typography variant=\"body2\">{'No changes proposed.'}</Typography>\n )}\n\n {[...proposal.corrections, ...after.corrections].length ? (\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Adjusted for readability'}</Typography>\n {[...proposal.corrections, ...after.corrections].map((correction) => (\n <Typography\n key={`${correction.control}:${correction.scheme}:${correction.to}`}\n variant=\"body2\"\n >\n {correctionText(correction)}\n </Typography>\n ))}\n </Stack>\n ) : null}\n\n {[...proposal.notes, ...after.notes, ...proposal.dropped].length ? (\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Worth knowing'}</Typography>\n {[...proposal.notes, ...after.notes].map((line) => (\n <Typography key={`note:${line}`} variant=\"body2\" color=\"text.secondary\">\n {line}\n </Typography>\n ))}\n {proposal.dropped.map((line) => (\n <Typography key={`dropped:${line}`} variant=\"body2\" color=\"text.secondary\">\n {`Left out — ${line}`}\n </Typography>\n ))}\n </Stack>\n ) : null}\n\n <Tabs\n value={scheme}\n onChange={(_, value: HostThemeScheme) => setScheme(value)}\n aria-label=\"Preview scheme\"\n >\n <Tab label=\"Light\" value=\"light\" />\n <Tab label=\"Dark\" value=\"dark\" />\n </Tabs>\n <Grid container spacing={2}>\n <Grid size={{ xs: 12, md: 6 }}>\n <Typography variant=\"subtitle2\" gutterBottom>\n {'Before'}\n </Typography>\n <Box data-preview=\"before\">\n <ThemePreview theme={theme ?? {}} scheme={scheme} />\n </Box>\n </Grid>\n <Grid size={{ xs: 12, md: 6 }}>\n <Typography variant=\"subtitle2\" gutterBottom>\n {'After'}\n </Typography>\n <Box data-preview=\"after\">\n <ThemePreview theme={after.theme} scheme={scheme} />\n </Box>\n </Grid>\n </Grid>\n\n {applied === job?.id ? (\n <Alert severity=\"success\">\n {'In the editor below as unsaved changes. Review them there, then Save — or Discard changes to go back.'}\n </Alert>\n ) : null}\n <Stack\n direction=\"row\"\n spacing={1}\n useFlexGap\n sx={{ flexWrap: 'wrap' }}\n >\n <Button variant=\"contained\" onClick={apply} disabled={applied === job?.id}>\n {'Put in the editor'}\n </Button>\n <Button\n onClick={() => {\n setJob(null)\n setApplied(null)\n }}\n >\n {'Dismiss'}\n </Button>\n </Stack>\n </Stack>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nAiThemeProposalCard.displayName = 'AiThemeProposalCard'\n\nexport default AiThemeProposalCard\n"],"names":["lockdownRefusalText","parseLockdownRefusal","pluginDocsHelp","CardDisplay","ScrollTable","themeEditorControl","authorizedFetch","useUser","Alert","Box","Button","Chip","CircularProgress","Grid","Stack","Tab","TableBody","TableCell","TableHead","TableRow","Tabs","TextField","ToggleButton","ToggleButtonGroup","Typography","useCallback","useEffect","useMemo","useRef","useState","AI_JOB_TERMINAL_STATUSES","aiThemeColorToken","aiThemeProposalAfter","aiThemeStaleChanges","readAiThemeProposal","readEventFrames","RECENT_JOBS_READ","RECENT_PROPOSALS_SHOWN","BRIEF_MAX_CHARS","SOURCE_COPY","installed","preset","custom","default","isTerminal","job","includes","status","proposalOf","output","outputs","find","entry","resource","proposal","changeLabel","change","control","label","Swatch","value","variant","color","text","String","test","direction","spacing","sx","alignItems","aria-hidden","width","height","borderRadius","border","borderColor","backgroundColor","flexShrink","fontFamily","wordBreak","correctionText","correction","scheme","reason","to","required","against","length","join","ratio","AiThemeProposalCard","props","hostId","orgId","theme","themeSource","ThemePreview","proposeDraft","data","user","userRef","current","uid","verdict","setVerdict","notice","setNotice","recent","setRecent","brief","setBrief","mode","setMode","busy","setBusy","setJob","setScheme","applied","setApplied","watchRef","abort","active","response","encodeURIComponent","payload","json","catch","ok","locked","error","jobs","filter","kind","slice","watch","jobId","org","controller","AbortController","again","signal","aborted","body","event","propose","trim","method","headers","JSON","stringify","inputs","next","id","after","stale","apply","running","Boolean","contentGutterX","contentGutterY","header","help","excerpt","exclusive","size","onChange","_","aria-label","multiline","minRows","fullWidth","placeholder","helperText","target","disabled","onClick","severity","map","flexGrow","minWidth","noWrap","summary","source","changes","components","resetComponents","before","leaf","component","slot","property","media","corrections","notes","dropped","line","container","xs","md","gutterBottom","data-preview","useFlexGap","flexWrap","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-theme-proposal-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { HostThemeSource } from '@aglyn/aglyn/app-utils/site-theme'\nimport type { ConsoleHostThemeZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport type { HostThemeScheme } from '@aglyn/shared-data-types'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { themeEditorControl } from '@aglyn/shared-ui-theme/util/theme-editor-fields'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Grid,\n Stack,\n Tab,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Tabs,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n AI_JOB_TERMINAL_STATUSES,\n type AiJobSummary,\n} from '../model/ai-jobs.types'\nimport {\n aiThemeColorToken,\n aiThemeProposalAfter,\n aiThemeStaleChanges,\n readAiThemeProposal,\n type AiThemeControlChange,\n type AiThemeCorrection,\n type AiThemeProposal,\n type AiThemeProposalMode,\n} from '../model/ai-theme-proposal'\nimport { readEventFrames } from './assist-jobs-drawer.component'\n\n/**\n * The theme assistant (AGL-2938), on the site's Theme section through the\n * `hostTheme` zone: a brief in, a theme proposal out, previewed before and\n * after with the editor's own preview, and put in the editor below as\n * unsaved changes when the person chooses. It never writes the theme — the\n * editor's Save does, through the same guarded path every edit takes.\n *\n * It renders nothing until the jobs route has answered for this workspace.\n * The shell has already decided the plan and the member's permission before\n * mounting it, but a released-off feature does not exist, and the release\n * flag is the route's to decide: a 404 there is this card staying absent.\n * The same read lists the site's recent proposals, so one made while the\n * person was elsewhere is still here to preview.\n */\n\n/** How many recent jobs the card reads to find this site's proposals. */\nconst RECENT_JOBS_READ = 20\n\n/** How many of this site's proposals the card offers again. */\nconst RECENT_PROPOSALS_SHOWN = 3\n\nconst BRIEF_MAX_CHARS = 4_000\n\nconst SOURCE_COPY: Record<HostThemeSource, string> = {\n installed:\n 'Saved as your changes on top of the installed theme. The publisher’s version stays as it is and can still take an update.',\n preset:\n 'Saved as your changes on top of the built-in theme, which stays as it is and can be restored.',\n custom:\n 'Saved as your changes on top of this theme of yours. Update it to keep them in the theme itself.',\n default:\n 'Only these values are stored. Everything else keeps following the default theme.',\n}\n\ntype Verdict = 'checking' | 'ready' | 'hidden'\n\nconst isTerminal = (job: AiJobSummary) => AI_JOB_TERMINAL_STATUSES.includes(job.status)\n\n/** The proposal a theme job produced, when it produced one. */\nfunction proposalOf(job: AiJobSummary | null): AiThemeProposal | null {\n const output = job?.outputs.find((entry) => entry.resource === 'theme')\n return output ? readAiThemeProposal(output.proposal) : null\n}\n\n/** A control's label, with its scheme when it has one. */\nfunction changeLabel(change: Pick<AiThemeControlChange, 'control' | 'scheme'>): string {\n return themeEditorControl(change.control).label\n}\n\nfunction Swatch({ value }: { value: string | number | null }) {\n if (value === null) {\n return (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Default'}\n </Typography>\n )\n }\n const text = String(value)\n const color = typeof value === 'string' && /^#[0-9a-f]{6}$/i.test(value)\n return (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n {color ? (\n <Box\n aria-hidden\n sx={{\n width: 14,\n height: 14,\n borderRadius: 0.5,\n border: 1,\n borderColor: 'divider',\n backgroundColor: text,\n flexShrink: 0,\n }}\n />\n ) : null}\n <Typography variant=\"body2\" sx={{ fontFamily: 'monospace', wordBreak: 'break-all' }}>\n {text}\n </Typography>\n </Stack>\n )\n}\n\nfunction correctionText(correction: AiThemeCorrection): string {\n const label = `${changeLabel(correction)} (${correction.scheme})`\n if (correction.reason === 'dark-value') {\n return `${label} set to ${correction.to} so the color keeps reading on the dark background.`\n }\n return (\n `${label} moved to ${correction.to} to reach ${correction.required}:1` +\n (correction.against?.length ? ` against ${correction.against.join(' and ')}` : '') +\n (correction.ratio ? ` (it was ${correction.ratio}:1).` : '.')\n )\n}\n\nexport function AiThemeProposalCard(props: ConsoleHostThemeZoneProps) {\n const { hostId, orgId, theme, themeSource, ThemePreview, proposeDraft } = props\n const { data: user } = useUser()\n // Held in a ref so the reads below key on WHO is signed in, never on the\n // identity of the object that says so.\n const userRef = useRef(user)\n userRef.current = user\n const uid = user?.uid ?? null\n\n const [verdict, setVerdict] = useState<Verdict>('checking')\n const [notice, setNotice] = useState<string | null>(null)\n const [recent, setRecent] = useState<AiJobSummary[]>([])\n const [brief, setBrief] = useState('')\n const [mode, setMode] = useState<AiThemeProposalMode>('modify')\n const [busy, setBusy] = useState(false)\n const [job, setJob] = useState<AiJobSummary | null>(null)\n const [scheme, setScheme] = useState<HostThemeScheme>('light')\n const [applied, setApplied] = useState<string | null>(null)\n const watchRef = useRef<AbortController | null>(null)\n\n useEffect(() => () => watchRef.current?.abort(), [])\n\n useEffect(() => {\n if (!orgId || !hostId || !uid) return\n let active = true\n setVerdict('checking')\n void (async () => {\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=${RECENT_JOBS_READ}`,\n )\n if (!active) return\n // The flag off (404) and the plan without generation (403): the\n // feature is not this workspace's, so the card is not here at all.\n if (response.status === 404 || response.status === 403) {\n setVerdict('hidden')\n return\n }\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'Recent theme proposals could not be loaded.'),\n )\n } else {\n const jobs = (payload?.jobs ?? []) as AiJobSummary[]\n setRecent(\n jobs\n .filter((entry) => entry.kind === 'theme' && entry.hostId === hostId)\n .filter((entry) => proposalOf(entry) !== null)\n .slice(0, RECENT_PROPOSALS_SHOWN),\n )\n }\n setVerdict('ready')\n } catch {\n if (!active) return\n setNotice('Recent theme proposals could not be loaded.')\n setVerdict('ready')\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, hostId, uid])\n\n /** Follows a job through the events route until it settles. */\n const watch = useCallback(async (jobId: string, org: string) => {\n watchRef.current?.abort()\n const controller = new AbortController()\n watchRef.current = controller\n let again = true\n while (again && !controller.signal.aborted) {\n again = false\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs/${encodeURIComponent(jobId)}/events?orgId=${encodeURIComponent(org)}`,\n { signal: controller.signal },\n )\n if (!response.ok || !response.body) return\n await readEventFrames(response.body, (event) => {\n if (event['type'] === 'state') setJob(event['job'] as AiJobSummary)\n else if (event['type'] === 'reconnect') again = true\n })\n } catch {\n return\n }\n }\n }, [])\n\n const propose = useCallback(async () => {\n if (!orgId || !brief.trim()) return\n setBusy(true)\n setNotice(null)\n setApplied(null)\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n kind: 'theme',\n brief: brief.trim(),\n inputs: { mode },\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(\n locked\n ? lockdownRefusalText(locked)\n : String(payload?.error ?? 'The theme proposal could not be started — try again.'),\n )\n return\n }\n const next = payload?.job as AiJobSummary | undefined\n if (!next) return\n setJob(next)\n if (!isTerminal(next)) await watch(next.id, orgId)\n } catch {\n setNotice('The theme proposal could not be started — try again.')\n } finally {\n setBusy(false)\n }\n }, [orgId, hostId, brief, mode, watch])\n\n const proposal = useMemo(() => proposalOf(job), [job])\n const after = useMemo(\n () => (proposal ? aiThemeProposalAfter(theme, proposal) : null),\n [theme, proposal],\n )\n const stale = useMemo(\n () => (proposal ? aiThemeStaleChanges(theme, proposal).length > 0 : false),\n [theme, proposal],\n )\n\n const apply = useCallback(() => {\n if (!job || !after) return\n proposeDraft(after.theme, job.id)\n setApplied(job.id)\n }, [job, after, proposeDraft])\n\n if (verdict !== 'ready') return null\n\n const running = Boolean(job && !isTerminal(job))\n\n return (\n <CardDisplay\n contentGutterX\n contentGutterY\n header=\"Theme assistant\"\n help={pluginDocsHelp('aiThemes', {\n anchor: '#describe-a-change',\n excerpt:\n 'Describe the change and get a proposal for every control the editor has, previewed before and after. Nothing is saved until you save it in the editor.',\n })}\n >\n <Stack spacing={2}>\n <ToggleButtonGroup\n exclusive\n size=\"small\"\n value={mode}\n onChange={(_, value: AiThemeProposalMode | null) => value && setMode(value)}\n aria-label=\"What to propose\"\n >\n <ToggleButton value=\"modify\">{'Change what I describe'}</ToggleButton>\n <ToggleButton value=\"create\">{'Design a new theme'}</ToggleButton>\n </ToggleButtonGroup>\n <TextField\n multiline\n minRows={2}\n fullWidth\n label=\"Describe the change\"\n placeholder=\"Warmer, with a deep green primary and bigger headings on mobile\"\n helperText=\"A hex color in your description is used exactly. Paste a link to borrow a page’s colors.\"\n value={brief}\n onChange={(event) => setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS))}\n disabled={busy}\n />\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button variant=\"contained\" disabled={busy || !brief.trim()} onClick={() => void propose()}>\n {'Propose changes'}\n </Button>\n {running ? <CircularProgress size={18} aria-label=\"Working on a proposal\" /> : null}\n </Stack>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n\n {job && job.error && !proposal ? (\n <Alert severity={job.status === 'failed' ? 'error' : 'warning'}>{job.error}</Alert>\n ) : null}\n\n {!job && recent.length ? (\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">{'Recent proposals for this site'}</Typography>\n {recent.map((entry) => (\n <Stack key={entry.id} direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" sx={{ flexGrow: 1, minWidth: 0 }} noWrap>\n {entry.brief}\n </Typography>\n <Button size=\"small\" onClick={() => setJob(entry)}>\n {'Show'}\n </Button>\n </Stack>\n ))}\n </Stack>\n ) : null}\n\n {proposal && after ? (\n <Stack spacing={2} aria-label=\"Theme proposal\">\n {proposal.summary ? <Typography variant=\"body1\">{proposal.summary}</Typography> : null}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {SOURCE_COPY[proposal.source]}\n </Typography>\n {stale ? (\n <Alert severity=\"info\">\n {'The theme has changed since this proposal was made. The preview and the editor apply it to the theme as it is now.'}\n </Alert>\n ) : null}\n\n {proposal.changes.length || proposal.components.length || proposal.resetComponents ? (\n <Box>\n <ScrollTable size=\"small\" aria-label=\"Proposed changes\">\n <TableHead>\n <TableRow>\n <TableCell>{'Control'}</TableCell>\n <TableCell>{'Now'}</TableCell>\n <TableCell>{'Proposed'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {proposal.changes.map((change) => (\n <TableRow key={`${change.control}:${change.scheme ?? ''}`}>\n <TableCell>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\">{changeLabel(change)}</Typography>\n {aiThemeColorToken(change.control) && change.scheme ? (\n <Chip size=\"small\" variant=\"outlined\" label={change.scheme} />\n ) : null}\n </Stack>\n </TableCell>\n <TableCell>\n <Swatch value={change.before} />\n </TableCell>\n <TableCell>\n <Swatch value={change.value} />\n </TableCell>\n </TableRow>\n ))}\n {proposal.resetComponents ? (\n <TableRow>\n <TableCell>{themeEditorControl('components').label}</TableCell>\n <TableCell>{'This site’s overrides'}</TableCell>\n <TableCell>{'Theme defaults'}</TableCell>\n </TableRow>\n ) : null}\n {proposal.components.map((leaf) => (\n <TableRow key={`${leaf.component}:${leaf.target}:${leaf.slot}:${leaf.media}:${leaf.property}`}>\n <TableCell>\n <Typography variant=\"body2\">\n {[\n leaf.component,\n leaf.target === 'defaultProps' ? 'default' : leaf.slot,\n leaf.property,\n ].join(' · ')}\n </Typography>\n {leaf.media ? <Chip size=\"small\" variant=\"outlined\" label={leaf.media} /> : null}\n </TableCell>\n <TableCell>{'—'}</TableCell>\n <TableCell>\n <Swatch value={typeof leaf.value === 'boolean' ? String(leaf.value) : leaf.value} />\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n </Box>\n ) : (\n <Typography variant=\"body2\">{'No changes proposed.'}</Typography>\n )}\n\n {[...proposal.corrections, ...after.corrections].length ? (\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Adjusted for readability'}</Typography>\n {[...proposal.corrections, ...after.corrections].map((correction) => (\n <Typography\n key={`${correction.control}:${correction.scheme}:${correction.to}`}\n variant=\"body2\"\n >\n {correctionText(correction)}\n </Typography>\n ))}\n </Stack>\n ) : null}\n\n {[...proposal.notes, ...after.notes, ...proposal.dropped].length ? (\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">{'Worth knowing'}</Typography>\n {[...proposal.notes, ...after.notes].map((line) => (\n <Typography key={`note:${line}`} variant=\"body2\" color=\"text.secondary\">\n {line}\n </Typography>\n ))}\n {proposal.dropped.map((line) => (\n <Typography key={`dropped:${line}`} variant=\"body2\" color=\"text.secondary\">\n {`Left out — ${line}`}\n </Typography>\n ))}\n </Stack>\n ) : null}\n\n <Tabs\n value={scheme}\n onChange={(_, value: HostThemeScheme) => setScheme(value)}\n aria-label=\"Preview scheme\"\n >\n <Tab label=\"Light\" value=\"light\" />\n <Tab label=\"Dark\" value=\"dark\" />\n </Tabs>\n <Grid container spacing={2}>\n <Grid size={{ xs: 12, md: 6 }}>\n <Typography variant=\"subtitle2\" gutterBottom>\n {'Before'}\n </Typography>\n <Box data-preview=\"before\">\n <ThemePreview theme={theme ?? {}} scheme={scheme} />\n </Box>\n </Grid>\n <Grid size={{ xs: 12, md: 6 }}>\n <Typography variant=\"subtitle2\" gutterBottom>\n {'After'}\n </Typography>\n <Box data-preview=\"after\">\n <ThemePreview theme={after.theme} scheme={scheme} />\n </Box>\n </Grid>\n </Grid>\n\n {applied === job?.id ? (\n <Alert severity=\"success\">\n {'In the editor below as unsaved changes. Review them there, then Save — or Discard changes to go back.'}\n </Alert>\n ) : null}\n <Stack\n direction=\"row\"\n spacing={1}\n useFlexGap\n sx={{ flexWrap: 'wrap' }}\n >\n <Button variant=\"contained\" onClick={apply} disabled={applied === job?.id}>\n {'Put in the editor'}\n </Button>\n <Button\n onClick={() => {\n setJob(null)\n setApplied(null)\n }}\n >\n {'Dismiss'}\n </Button>\n </Stack>\n </Stack>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nAiThemeProposalCard.displayName = 'AiThemeProposalCard'\n\nexport default AiThemeProposalCard\n"],"names":["lockdownRefusalText","parseLockdownRefusal","pluginDocsHelp","CardDisplay","ScrollTable","themeEditorControl","authorizedFetch","useUser","Alert","Box","Button","Chip","CircularProgress","Grid","Stack","Tab","TableBody","TableCell","TableHead","TableRow","Tabs","TextField","ToggleButton","ToggleButtonGroup","Typography","useCallback","useEffect","useMemo","useRef","useState","AI_JOB_TERMINAL_STATUSES","aiThemeColorToken","aiThemeProposalAfter","aiThemeStaleChanges","readAiThemeProposal","readEventFrames","RECENT_JOBS_READ","RECENT_PROPOSALS_SHOWN","BRIEF_MAX_CHARS","SOURCE_COPY","installed","preset","custom","default","isTerminal","job","includes","status","proposalOf","output","outputs","find","entry","resource","proposal","changeLabel","change","control","label","Swatch","value","variant","color","text","String","test","direction","spacing","sx","alignItems","aria-hidden","width","height","borderRadius","border","borderColor","backgroundColor","flexShrink","fontFamily","wordBreak","correctionText","correction","scheme","reason","to","required","against","length","join","ratio","AiThemeProposalCard","props","hostId","orgId","theme","themeSource","ThemePreview","proposeDraft","data","user","userRef","current","uid","verdict","setVerdict","notice","setNotice","recent","setRecent","brief","setBrief","mode","setMode","busy","setBusy","setJob","setScheme","applied","setApplied","watchRef","abort","active","response","encodeURIComponent","payload","json","catch","ok","locked","error","jobs","filter","kind","slice","watch","jobId","org","controller","AbortController","again","signal","aborted","body","event","propose","trim","method","headers","JSON","stringify","inputs","next","id","after","stale","apply","running","Boolean","contentGutterX","contentGutterY","header","help","anchor","excerpt","exclusive","size","onChange","_","aria-label","multiline","minRows","fullWidth","placeholder","helperText","target","disabled","onClick","severity","map","flexGrow","minWidth","noWrap","summary","source","changes","components","resetComponents","before","leaf","component","slot","property","media","corrections","notes","dropped","line","container","xs","md","gutterBottom","data-preview","useFlexGap","flexWrap","displayName"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  'use client';
2
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
3
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
4
  /**
4
5
  * @license
@@ -18,7 +19,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
18
19
  */ import { aiAddonName } from "@aglyn/aglyn";
19
20
  import { trackEvent } from "@aglyn/aglyn/app-utils/analytics-events";
20
21
  import { mdiCreation } from "@aglyn/shared-data-mdi";
21
- import { MdiIcon } from "@aglyn/shared-ui-jsx";
22
+ import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
23
+ import { HelpTip, MdiIcon } from "@aglyn/shared-ui-jsx";
22
24
  import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, Typography } from "@mui/material";
23
25
  import { useEffect, useState } from "react";
24
26
  /** The label every "Create with AI" entry reads. */ export const AI_UPSELL_CREATE_LABEL = 'Create with AI';
@@ -88,6 +90,10 @@ import { useEffect, useState } from "react";
88
90
  const addon = aiAddonName();
89
91
  return canManageBilling ? `${feature} comes with the ${addon} add-on, which you can add to this ` + 'workspace from Billing.' : `${feature} comes with the ${addon} add-on. Ask a workspace owner or admin ` + 'to add it from Billing.';
90
92
  }
93
+ /** The add-on's own section of the Create with AI guide (AGL-3660). */ const aiUpsellHelp = ()=>pluginDocsHelp('aiCreate', {
94
+ anchor: '#without-the-add-on',
95
+ excerpt: `Create with AI needs the ${aiAddonName()} add-on on a paid plan. An owner or admin adds it from Billing; on Free, the monthly AI credits cover it.`
96
+ });
91
97
  export function AiUpsellDialog({ kind, open, onClose, upgrade }) {
92
98
  const copy = AI_UPSELL_COPY[kind];
93
99
  const { canManageBilling, billingHref } = upgrade;
@@ -108,8 +114,16 @@ export function AiUpsellDialog({ kind, open, onClose, upgrade }) {
108
114
  fullWidth: true,
109
115
  maxWidth: "xs",
110
116
  children: [
111
- /*#__PURE__*/ _jsx(DialogTitle, {
112
- children: copy.title
117
+ /*#__PURE__*/ _jsxs(DialogTitle, {
118
+ sx: {
119
+ display: 'flex',
120
+ alignItems: 'center',
121
+ gap: 0.5
122
+ },
123
+ children: [
124
+ copy.title,
125
+ /*#__PURE__*/ _jsx(HelpTip, _extends({}, aiUpsellHelp()))
126
+ ]
113
127
  }),
114
128
  /*#__PURE__*/ _jsx(DialogContent, {
115
129
  children: /*#__PURE__*/ _jsxs(Stack, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-upsell-dialog.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 { aiAddonName } from '@aglyn/aglyn'\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport type { ConsoleWidgetUpgrade } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState, type ReactNode } from 'react'\nimport type { AiBriefKind } from './ai-brief-dialog.component'\n\n/**\n * \"Create with AI\" on a plan that could buy the AI add-on and has not\n * (AGL-3601). The shell mounts the entry with `entitled={false}` only where\n * the add-on is sold to this workspace, and the jobs route has said the plan\n * is the one thing missing; the entry then keeps its place on the page and\n * opens this instead of the brief, so the reader sees where the add-on is used.\n */\n\n/**\n * What a \"Create with AI\" or \"Ask AI\" entry makes: the brief kinds, an\n * automation, products, a function or variable, an overlay, and an answer\n * about the figures on a page.\n */\nexport type AiUpsellKind =\n | AiBriefKind\n | 'workflow'\n | 'product'\n | 'logic'\n | 'overlay'\n | 'funnel'\n | 'insight'\n\n/** What one entry's dialog says. */\nexport interface AiUpsellCopy {\n title: string\n does: string\n /** The entry's own label, which the button reads and the offer names; \"Create with AI\" when absent. */\n label?: string\n}\n\n/** The label every \"Create with AI\" entry reads. */\nexport const AI_UPSELL_CREATE_LABEL = 'Create with AI'\n\n/** What the dialog says one entry would do here. */\nexport const AI_UPSELL_COPY: Readonly<Record<AiUpsellKind, AiUpsellCopy>> = {\n page: {\n title: 'Create a page with AI',\n does:\n 'Describe the page you need and AI plans it from your brief, using your theme and ' +\n 'what the site already has. You confirm the plan, and the page is built as an ' +\n 'unpublished draft.',\n },\n template: {\n title: 'Create a page template with AI',\n does:\n 'Describe what each page should show and AI plans the template, filling what ' +\n 'changes from page to page from each record. You confirm the plan, and the template ' +\n 'is built as a draft in your library.',\n },\n layout: {\n title: 'Create a layout with AI',\n does:\n 'Describe what the layout should hold and AI plans it, linking your pages and placing ' +\n 'your navigation or menu component when the site has one. You confirm the plan, and ' +\n 'the layout is built as a draft no page uses until you assign it.',\n },\n form: {\n title: 'Create a form with AI',\n does:\n 'Describe what the form is for and AI plans its fields, its marketing consent and ' +\n 'where each submission goes. You confirm the plan, and the form is built as a draft.',\n },\n component: {\n title: 'Create a reusable component with AI',\n does:\n 'Describe what the component should show and AI plans it, making what each page can ' +\n 'change into its properties. You confirm the plan, and the component is built as a ' +\n 'draft.',\n },\n workflow: {\n title: 'Create an automation with AI',\n does:\n 'Describe what should happen, and when, and AI drafts the automation from the ' +\n 'triggers and steps your plan includes. It is saved switched off for you to review.',\n },\n email: {\n title: 'Create an email with AI',\n does:\n 'Describe the email you need and AI plans a design from the email blocks, with three ' +\n 'subject lines and three preheaders to choose between. You confirm the plan, and the ' +\n 'design is built as a draft that is sent to nobody.',\n },\n campaign: {\n title: 'Create a campaign with AI',\n does:\n 'Describe what the campaign is for and AI writes its email design and a draft campaign ' +\n 'that would send it. Nothing is sent or scheduled, and the campaign is aimed at nobody ' +\n 'until you pick its lists.',\n },\n product: {\n title: 'Create products with AI',\n does:\n 'Describe what the store sells and AI proposes up to twelve products. You review them ' +\n 'before any is created, and each is created as a draft that is not on your storefront ' +\n 'until you price it and activate it.',\n },\n logic: {\n title: 'Create a function or variable with AI',\n does:\n 'Describe what it should work out or hold and AI drafts the function or variable. It ' +\n 'opens unsaved in the editor for you to check and save.',\n },\n overlay: {\n title: 'Create an overlay with AI',\n does:\n 'Describe the announcement bar or popup you want and AI writes its copy within the ' +\n 'overlay’s limits. It is saved switched off, for you to check and turn on.',\n },\n funnel: {\n title: 'Create a funnel with AI',\n does:\n 'Describe the journey you want to measure and AI proposes its steps from the site’s ' +\n 'real pages, forms and products. It opens in the funnel editor for you to check and save.',\n },\n insight: {\n title: 'Ask AI about these numbers',\n label: 'Ask a question',\n does:\n 'Ask a question about the figures on this page and AI answers from them, every answer ' +\n 'traced to the figures it is built from. It reads; it changes nothing.',\n },\n}\n\n/** The label an entry's button reads. */\nexport function aiUpsellLabel(kind: AiUpsellKind): string {\n return AI_UPSELL_COPY[kind].label ?? AI_UPSELL_CREATE_LABEL\n}\n\n/**\n * The sentence that says where the feature comes from, and who can add it.\n * `feature` is what the entry is called: \"Create with AI\" unless it is an ask.\n */\nexport function aiUpsellOffer(canManageBilling: boolean, feature = AI_UPSELL_CREATE_LABEL): string {\n const addon = aiAddonName()\n return canManageBilling\n ? `${feature} comes with the ${addon} add-on, which you can add to this ` +\n 'workspace from Billing.'\n : `${feature} comes with the ${addon} add-on. Ask a workspace owner or admin ` +\n 'to add it from Billing.'\n}\n\nexport interface AiUpsellDialogProps {\n kind: AiUpsellKind\n open: boolean\n onClose: () => void\n upgrade: ConsoleWidgetUpgrade\n}\n\nexport function AiUpsellDialog({ kind, open, onClose, upgrade }: AiUpsellDialogProps) {\n const copy = AI_UPSELL_COPY[kind]\n const { canManageBilling, billingHref } = upgrade\n\n // Counted once per opening: the denominator the click is a rate against.\n useEffect(() => {\n if (open) trackEvent('ai_upsell_shown', { kind, can_manage: canManageBilling })\n }, [open, kind, canManageBilling])\n\n return (\n <Dialog open={open} onClose={onClose} fullWidth maxWidth=\"xs\">\n <DialogTitle>{copy.title}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Typography variant=\"body2\">{copy.does}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {aiUpsellOffer(canManageBilling, kind === 'insight' ? copy.title : AI_UPSELL_CREATE_LABEL)}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{canManageBilling ? 'Not now' : 'Close'}</Button>\n {canManageBilling ? (\n <Button\n variant=\"contained\"\n href={billingHref}\n onClick={() => trackEvent('ai_upsell_clicked', { kind })}\n >\n {`Add ${aiAddonName()}`}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\n/**\n * The entry itself on a plan without the add-on: the same button, label and\n * icon as the entitled entry, opening {@link AiUpsellDialog}. Every entry that\n * draws it renders from the shell's client-side gates alone and asks the\n * server nothing until it is used (AGL-3601).\n */\nexport function AiUpsellButton({\n kind,\n upgrade,\n label,\n}: {\n kind: AiUpsellKind\n upgrade: ConsoleWidgetUpgrade\n /** The entitled entry's own label, when it is not the kind's. */\n label?: string\n}): ReactNode {\n const [open, setOpen] = useState(false)\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {label ?? aiUpsellLabel(kind)}\n </Button>\n <AiUpsellDialog kind={kind} open={open} onClose={() => setOpen(false)} upgrade={upgrade} />\n </>\n )\n}\n"],"names":["aiAddonName","trackEvent","mdiCreation","MdiIcon","Button","Dialog","DialogActions","DialogContent","DialogTitle","Stack","Typography","useEffect","useState","AI_UPSELL_CREATE_LABEL","AI_UPSELL_COPY","page","title","does","template","layout","form","component","workflow","email","campaign","product","logic","overlay","funnel","insight","label","aiUpsellLabel","kind","aiUpsellOffer","canManageBilling","feature","addon","AiUpsellDialog","open","onClose","upgrade","copy","billingHref","can_manage","fullWidth","maxWidth","spacing","variant","color","onClick","href","AiUpsellButton","setOpen","size","startIcon","path"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-upsell-dialog.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 { aiAddonName } from '@aglyn/aglyn'\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport type { ConsoleWidgetUpgrade } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { HelpTip, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState, type ReactNode } from 'react'\nimport type { AiBriefKind } from './ai-brief-dialog.component'\n\n/**\n * \"Create with AI\" on a plan that could buy the AI add-on and has not\n * (AGL-3601). The shell mounts the entry with `entitled={false}` only where\n * the add-on is sold to this workspace, and the jobs route has said the plan\n * is the one thing missing; the entry then keeps its place on the page and\n * opens this instead of the brief, so the reader sees where the add-on is used.\n */\n\n/**\n * What a \"Create with AI\" or \"Ask AI\" entry makes: the brief kinds, an\n * automation, products, a function or variable, an overlay, and an answer\n * about the figures on a page.\n */\nexport type AiUpsellKind =\n | AiBriefKind\n | 'workflow'\n | 'product'\n | 'logic'\n | 'overlay'\n | 'funnel'\n | 'insight'\n\n/** What one entry's dialog says. */\nexport interface AiUpsellCopy {\n title: string\n does: string\n /** The entry's own label, which the button reads and the offer names; \"Create with AI\" when absent. */\n label?: string\n}\n\n/** The label every \"Create with AI\" entry reads. */\nexport const AI_UPSELL_CREATE_LABEL = 'Create with AI'\n\n/** What the dialog says one entry would do here. */\nexport const AI_UPSELL_COPY: Readonly<Record<AiUpsellKind, AiUpsellCopy>> = {\n page: {\n title: 'Create a page with AI',\n does:\n 'Describe the page you need and AI plans it from your brief, using your theme and ' +\n 'what the site already has. You confirm the plan, and the page is built as an ' +\n 'unpublished draft.',\n },\n template: {\n title: 'Create a page template with AI',\n does:\n 'Describe what each page should show and AI plans the template, filling what ' +\n 'changes from page to page from each record. You confirm the plan, and the template ' +\n 'is built as a draft in your library.',\n },\n layout: {\n title: 'Create a layout with AI',\n does:\n 'Describe what the layout should hold and AI plans it, linking your pages and placing ' +\n 'your navigation or menu component when the site has one. You confirm the plan, and ' +\n 'the layout is built as a draft no page uses until you assign it.',\n },\n form: {\n title: 'Create a form with AI',\n does:\n 'Describe what the form is for and AI plans its fields, its marketing consent and ' +\n 'where each submission goes. You confirm the plan, and the form is built as a draft.',\n },\n component: {\n title: 'Create a reusable component with AI',\n does:\n 'Describe what the component should show and AI plans it, making what each page can ' +\n 'change into its properties. You confirm the plan, and the component is built as a ' +\n 'draft.',\n },\n workflow: {\n title: 'Create an automation with AI',\n does:\n 'Describe what should happen, and when, and AI drafts the automation from the ' +\n 'triggers and steps your plan includes. It is saved switched off for you to review.',\n },\n email: {\n title: 'Create an email with AI',\n does:\n 'Describe the email you need and AI plans a design from the email blocks, with three ' +\n 'subject lines and three preheaders to choose between. You confirm the plan, and the ' +\n 'design is built as a draft that is sent to nobody.',\n },\n campaign: {\n title: 'Create a campaign with AI',\n does:\n 'Describe what the campaign is for and AI writes its email design and a draft campaign ' +\n 'that would send it. Nothing is sent or scheduled, and the campaign is aimed at nobody ' +\n 'until you pick its lists.',\n },\n product: {\n title: 'Create products with AI',\n does:\n 'Describe what the store sells and AI proposes up to twelve products. You review them ' +\n 'before any is created, and each is created as a draft that is not on your storefront ' +\n 'until you price it and activate it.',\n },\n logic: {\n title: 'Create a function or variable with AI',\n does:\n 'Describe what it should work out or hold and AI drafts the function or variable. It ' +\n 'opens unsaved in the editor for you to check and save.',\n },\n overlay: {\n title: 'Create an overlay with AI',\n does:\n 'Describe the announcement bar or popup you want and AI writes its copy within the ' +\n 'overlay’s limits. It is saved switched off, for you to check and turn on.',\n },\n funnel: {\n title: 'Create a funnel with AI',\n does:\n 'Describe the journey you want to measure and AI proposes its steps from the site’s ' +\n 'real pages, forms and products. It opens in the funnel editor for you to check and save.',\n },\n insight: {\n title: 'Ask AI about these numbers',\n label: 'Ask a question',\n does:\n 'Ask a question about the figures on this page and AI answers from them, every answer ' +\n 'traced to the figures it is built from. It reads; it changes nothing.',\n },\n}\n\n/** The label an entry's button reads. */\nexport function aiUpsellLabel(kind: AiUpsellKind): string {\n return AI_UPSELL_COPY[kind].label ?? AI_UPSELL_CREATE_LABEL\n}\n\n/**\n * The sentence that says where the feature comes from, and who can add it.\n * `feature` is what the entry is called: \"Create with AI\" unless it is an ask.\n */\nexport function aiUpsellOffer(canManageBilling: boolean, feature = AI_UPSELL_CREATE_LABEL): string {\n const addon = aiAddonName()\n return canManageBilling\n ? `${feature} comes with the ${addon} add-on, which you can add to this ` +\n 'workspace from Billing.'\n : `${feature} comes with the ${addon} add-on. Ask a workspace owner or admin ` +\n 'to add it from Billing.'\n}\n\nexport interface AiUpsellDialogProps {\n kind: AiUpsellKind\n open: boolean\n onClose: () => void\n upgrade: ConsoleWidgetUpgrade\n}\n\n/** The add-on's own section of the Create with AI guide (AGL-3660). */\nconst aiUpsellHelp = () =>\n pluginDocsHelp('aiCreate', {\n anchor: '#without-the-add-on',\n excerpt: `Create with AI needs the ${aiAddonName()} add-on on a paid plan. An owner or admin adds it from Billing; on Free, the monthly AI credits cover it.`,\n })\n\nexport function AiUpsellDialog({ kind, open, onClose, upgrade }: AiUpsellDialogProps) {\n const copy = AI_UPSELL_COPY[kind]\n const { canManageBilling, billingHref } = upgrade\n\n // Counted once per opening: the denominator the click is a rate against.\n useEffect(() => {\n if (open) trackEvent('ai_upsell_shown', { kind, can_manage: canManageBilling })\n }, [open, kind, canManageBilling])\n\n return (\n <Dialog open={open} onClose={onClose} fullWidth maxWidth=\"xs\">\n <DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>\n {copy.title}\n <HelpTip {...aiUpsellHelp()} />\n </DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Typography variant=\"body2\">{copy.does}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {aiUpsellOffer(canManageBilling, kind === 'insight' ? copy.title : AI_UPSELL_CREATE_LABEL)}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{canManageBilling ? 'Not now' : 'Close'}</Button>\n {canManageBilling ? (\n <Button\n variant=\"contained\"\n href={billingHref}\n onClick={() => trackEvent('ai_upsell_clicked', { kind })}\n >\n {`Add ${aiAddonName()}`}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\n/**\n * The entry itself on a plan without the add-on: the same button, label and\n * icon as the entitled entry, opening {@link AiUpsellDialog}. Every entry that\n * draws it renders from the shell's client-side gates alone and asks the\n * server nothing until it is used (AGL-3601).\n */\nexport function AiUpsellButton({\n kind,\n upgrade,\n label,\n}: {\n kind: AiUpsellKind\n upgrade: ConsoleWidgetUpgrade\n /** The entitled entry's own label, when it is not the kind's. */\n label?: string\n}): ReactNode {\n const [open, setOpen] = useState(false)\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {label ?? aiUpsellLabel(kind)}\n </Button>\n <AiUpsellDialog kind={kind} open={open} onClose={() => setOpen(false)} upgrade={upgrade} />\n </>\n )\n}\n"],"names":["aiAddonName","trackEvent","mdiCreation","pluginDocsHelp","HelpTip","MdiIcon","Button","Dialog","DialogActions","DialogContent","DialogTitle","Stack","Typography","useEffect","useState","AI_UPSELL_CREATE_LABEL","AI_UPSELL_COPY","page","title","does","template","layout","form","component","workflow","email","campaign","product","logic","overlay","funnel","insight","label","aiUpsellLabel","kind","aiUpsellOffer","canManageBilling","feature","addon","aiUpsellHelp","anchor","excerpt","AiUpsellDialog","open","onClose","upgrade","copy","billingHref","can_manage","fullWidth","maxWidth","sx","display","alignItems","gap","spacing","variant","color","onClick","href","AiUpsellButton","setOpen","size","startIcon","path"],"mappings":"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"}
@@ -14,12 +14,18 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
17
18
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
18
19
  import { buildRoute, Route } from "@aglyn/aglyn/app-utils/console-routes";
19
- import { AppLink } from "@aglyn/shared-ui-jsx";
20
+ import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
21
+ import { AppLink, HelpTip } from "@aglyn/shared-ui-jsx";
20
22
  import { Box, LinearProgress, Stack, Typography } from "@mui/material";
21
23
  import { useAiUsageMeter } from "./use-ai-usage-meter.js";
22
24
  const credits = (value)=>value.toLocaleString();
25
+ /** The usage line's own section of the allotments guide (AGL-3660). */ const AI_USAGE_STRIP_HELP = pluginDocsHelp('aiAllotments', {
26
+ anchor: '#usage-strip',
27
+ excerpt: 'Your credits this month against your allotment, the workspace pool, and what your last request cost. It warns from 80%.'
28
+ });
23
29
  /** The line about the reader's own month, in the words the strip uses. */ export function aiUsageStripMineLabel(meter) {
24
30
  const { mine } = meter;
25
31
  if (mine.limit === null) return `You: ${credits(mine.used)} credits this month`;
@@ -85,7 +91,12 @@ const credits = (value)=>value.toLocaleString();
85
91
  variant: "caption",
86
92
  color: "text.secondary",
87
93
  children: `Last request: ${credits(meter.last)} credits`
88
- }) : null
94
+ }) : null,
95
+ /*#__PURE__*/ _jsx(HelpTip, _extends({
96
+ sx: {
97
+ alignSelf: 'center'
98
+ }
99
+ }, AI_USAGE_STRIP_HELP))
89
100
  ]
90
101
  }),
91
102
  pct !== null ? /*#__PURE__*/ _jsx(LinearProgress, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-usage-strip.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 { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { Box, LinearProgress, Stack, Typography } from '@mui/material'\nimport type { AiUsageMeterWire } from '../usage/ai-usage-wire'\nimport { useAiUsageMeter } from './use-ai-usage-meter'\n\nexport interface AiUsageStripProps {\n /** The workspace the strip speaks for; nothing renders without one. */\n orgId?: string | null\n /** For the link to who can raise an allotment; the link is omitted without it. */\n orgSlug?: string | null\n}\n\nconst credits = (value: number): string => value.toLocaleString()\n\n/** The line about the reader's own month, in the words the strip uses. */\nexport function aiUsageStripMineLabel(meter: AiUsageMeterWire): string {\n const { mine } = meter\n if (mine.limit === null) return `You: ${credits(mine.used)} credits this month`\n const who = mine.scope === 'host' ? 'This site' : mine.scope === 'collab' ? 'You, on this site' : 'You'\n return `${who}: ${credits(mine.used)} of ${credits(mine.limit)} credits this month`\n}\n\n/** The line about the workspace's pool. */\nexport function aiUsageStripPoolLabel(meter: AiUsageMeterWire): string {\n const { pool } = meter\n return pool.limit === null\n ? `Workspace: ${credits(pool.used)} credits`\n : `Workspace: ${credits(pool.used)} of ${credits(pool.limit)} credits`\n}\n\n/**\n * THE USAGE STRIP (AGL-2942): the reader's AI credits while they use AI —\n * in the assistant panel and every generation dialog, with no dashboard to\n * visit and no read of its own.\n *\n * It shows the reader's month against the allotment that binds them (or\n * their month alone and the workspace's pool when none does), the pool, what\n * the last request cost, and the model that answered. From 80% it warns; at\n * a hard allotment it says who can raise it. It renders nothing until a door\n * has answered this reader in this workspace — see `use-ai-usage-meter.ts`.\n */\nexport function AiUsageStrip({ orgId, orgSlug }: AiUsageStripProps) {\n const meter = useAiUsageMeter(orgId)\n if (!meter) return null\n const { mine, pool } = meter\n const bound = mine.limit !== null\n const used = bound ? mine.used : pool.used\n const limit = bound ? mine.limit : pool.limit\n const pct = limit ? Math.min(100, Math.round((used / limit) * 100)) : null\n const tone = meter.state === 'capped' ? 'error' : meter.state === 'warn' ? 'warning' : 'primary'\n const allotmentReached = bound && mine.mode === 'hard' && mine.limit !== null && mine.used >= mine.limit\n const raiseHref =\n orgSlug && mine.scope !== 'collab'\n ? `${buildRoute(Route.MANAGE_BILLING_USAGE, { orgSlug })}#ai-allotments`\n : null\n const whose = !bound\n ? 'the workspace’s credits'\n : mine.scope === 'host'\n ? 'this site’s AI allotment'\n : 'your AI allotment'\n\n return (\n <Box\n role=\"status\"\n aria-live=\"polite\"\n data-testid=\"ai-usage-strip\"\n sx={{ px: 1, py: 0.75, borderRadius: 1, bgcolor: 'action.hover' }}\n >\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'baseline', flexWrap: 'wrap' }}>\n <Typography variant=\"caption\" sx={{ flexGrow: 1 }}>\n {aiUsageStripMineLabel(meter)}\n </Typography>\n {meter.last !== null ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Last request: ${credits(meter.last)} credits`}\n </Typography>\n ) : null}\n </Stack>\n {pct !== null ? (\n <LinearProgress\n variant=\"determinate\"\n value={pct}\n color={tone}\n aria-label={`${pct}% of ${whose} used`}\n sx={{ my: 0.5, height: 4, borderRadius: 2 }}\n />\n ) : null}\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {aiUsageStripPoolLabel(meter)}\n {bound ? '' : ' this month'}\n {meter.model ? ` · ${meter.model.label}${meter.model.auto ? ' (Auto)' : ''}` : ''}\n </Typography>\n {meter.state === 'warn' ? (\n <Typography variant=\"caption\" color=\"warning.main\" component=\"div\">\n {`Past 80% of ${whose}.`}\n {bound && mine.mode === 'soft' ? ' It is soft, so requests keep working.' : ''}\n </Typography>\n ) : null}\n {meter.state === 'capped' ? (\n <Typography variant=\"caption\" color=\"error\" component=\"div\">\n {allotmentReached\n ? mine.scope === 'collab'\n ? 'Your AI allotment on this site is used for the month. The site’s admin, or an organization admin, can raise it.'\n : mine.scope === 'host'\n ? 'This site’s AI allotment is used for the month. An organization admin can raise it'\n : 'Your AI allotment is used for the month. An organization admin can raise it'\n : 'That request was stopped by a limit on this workspace.'}\n {allotmentReached && raiseHref ? (\n <>\n {' under '}\n <AppLink componentVariant=\"naked\" href={raiseHref}>\n {'Billing → Usage'}\n </AppLink>\n {'.'}\n </>\n ) : allotmentReached && mine.scope !== 'collab' ? (\n '.'\n ) : null}\n </Typography>\n ) : null}\n </Box>\n )\n}\nAiUsageStrip.displayName = 'AiUsageStrip'\n\nexport default AiUsageStrip\n"],"names":["buildRoute","Route","AppLink","Box","LinearProgress","Stack","Typography","useAiUsageMeter","credits","value","toLocaleString","aiUsageStripMineLabel","meter","mine","limit","used","who","scope","aiUsageStripPoolLabel","pool","AiUsageStrip","orgId","orgSlug","bound","pct","Math","min","round","tone","state","allotmentReached","mode","raiseHref","MANAGE_BILLING_USAGE","whose","role","aria-live","data-testid","sx","px","py","borderRadius","bgcolor","useFlexGap","direction","spacing","alignItems","flexWrap","variant","flexGrow","last","color","aria-label","my","height","component","model","label","auto","componentVariant","href","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-usage-strip.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 { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { AppLink, HelpTip } from '@aglyn/shared-ui-jsx'\nimport { Box, LinearProgress, Stack, Typography } from '@mui/material'\nimport type { AiUsageMeterWire } from '../usage/ai-usage-wire'\nimport { useAiUsageMeter } from './use-ai-usage-meter'\n\nexport interface AiUsageStripProps {\n /** The workspace the strip speaks for; nothing renders without one. */\n orgId?: string | null\n /** For the link to who can raise an allotment; the link is omitted without it. */\n orgSlug?: string | null\n}\n\nconst credits = (value: number): string => value.toLocaleString()\n\n/** The usage line's own section of the allotments guide (AGL-3660). */\nconst AI_USAGE_STRIP_HELP = pluginDocsHelp('aiAllotments', {\n anchor: '#usage-strip',\n excerpt:\n 'Your credits this month against your allotment, the workspace pool, and what your last request cost. It warns from 80%.',\n})\n\n/** The line about the reader's own month, in the words the strip uses. */\nexport function aiUsageStripMineLabel(meter: AiUsageMeterWire): string {\n const { mine } = meter\n if (mine.limit === null) return `You: ${credits(mine.used)} credits this month`\n const who = mine.scope === 'host' ? 'This site' : mine.scope === 'collab' ? 'You, on this site' : 'You'\n return `${who}: ${credits(mine.used)} of ${credits(mine.limit)} credits this month`\n}\n\n/** The line about the workspace's pool. */\nexport function aiUsageStripPoolLabel(meter: AiUsageMeterWire): string {\n const { pool } = meter\n return pool.limit === null\n ? `Workspace: ${credits(pool.used)} credits`\n : `Workspace: ${credits(pool.used)} of ${credits(pool.limit)} credits`\n}\n\n/**\n * THE USAGE STRIP (AGL-2942): the reader's AI credits while they use AI —\n * in the assistant panel and every generation dialog, with no dashboard to\n * visit and no read of its own.\n *\n * It shows the reader's month against the allotment that binds them (or\n * their month alone and the workspace's pool when none does), the pool, what\n * the last request cost, and the model that answered. From 80% it warns; at\n * a hard allotment it says who can raise it. It renders nothing until a door\n * has answered this reader in this workspace — see `use-ai-usage-meter.ts`.\n */\nexport function AiUsageStrip({ orgId, orgSlug }: AiUsageStripProps) {\n const meter = useAiUsageMeter(orgId)\n if (!meter) return null\n const { mine, pool } = meter\n const bound = mine.limit !== null\n const used = bound ? mine.used : pool.used\n const limit = bound ? mine.limit : pool.limit\n const pct = limit ? Math.min(100, Math.round((used / limit) * 100)) : null\n const tone = meter.state === 'capped' ? 'error' : meter.state === 'warn' ? 'warning' : 'primary'\n const allotmentReached = bound && mine.mode === 'hard' && mine.limit !== null && mine.used >= mine.limit\n const raiseHref =\n orgSlug && mine.scope !== 'collab'\n ? `${buildRoute(Route.MANAGE_BILLING_USAGE, { orgSlug })}#ai-allotments`\n : null\n const whose = !bound\n ? 'the workspace’s credits'\n : mine.scope === 'host'\n ? 'this site’s AI allotment'\n : 'your AI allotment'\n\n return (\n <Box\n role=\"status\"\n aria-live=\"polite\"\n data-testid=\"ai-usage-strip\"\n sx={{ px: 1, py: 0.75, borderRadius: 1, bgcolor: 'action.hover' }}\n >\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'baseline', flexWrap: 'wrap' }}>\n <Typography variant=\"caption\" sx={{ flexGrow: 1 }}>\n {aiUsageStripMineLabel(meter)}\n </Typography>\n {meter.last !== null ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Last request: ${credits(meter.last)} credits`}\n </Typography>\n ) : null}\n <HelpTip sx={{ alignSelf: 'center' }} {...AI_USAGE_STRIP_HELP} />\n </Stack>\n {pct !== null ? (\n <LinearProgress\n variant=\"determinate\"\n value={pct}\n color={tone}\n aria-label={`${pct}% of ${whose} used`}\n sx={{ my: 0.5, height: 4, borderRadius: 2 }}\n />\n ) : null}\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {aiUsageStripPoolLabel(meter)}\n {bound ? '' : ' this month'}\n {meter.model ? ` · ${meter.model.label}${meter.model.auto ? ' (Auto)' : ''}` : ''}\n </Typography>\n {meter.state === 'warn' ? (\n <Typography variant=\"caption\" color=\"warning.main\" component=\"div\">\n {`Past 80% of ${whose}.`}\n {bound && mine.mode === 'soft' ? ' It is soft, so requests keep working.' : ''}\n </Typography>\n ) : null}\n {meter.state === 'capped' ? (\n <Typography variant=\"caption\" color=\"error\" component=\"div\">\n {allotmentReached\n ? mine.scope === 'collab'\n ? 'Your AI allotment on this site is used for the month. The site’s admin, or an organization admin, can raise it.'\n : mine.scope === 'host'\n ? 'This site’s AI allotment is used for the month. An organization admin can raise it'\n : 'Your AI allotment is used for the month. An organization admin can raise it'\n : 'That request was stopped by a limit on this workspace.'}\n {allotmentReached && raiseHref ? (\n <>\n {' under '}\n <AppLink componentVariant=\"naked\" href={raiseHref}>\n {'Billing → Usage'}\n </AppLink>\n {'.'}\n </>\n ) : allotmentReached && mine.scope !== 'collab' ? (\n '.'\n ) : null}\n </Typography>\n ) : null}\n </Box>\n )\n}\nAiUsageStrip.displayName = 'AiUsageStrip'\n\nexport default AiUsageStrip\n"],"names":["buildRoute","Route","pluginDocsHelp","AppLink","HelpTip","Box","LinearProgress","Stack","Typography","useAiUsageMeter","credits","value","toLocaleString","AI_USAGE_STRIP_HELP","anchor","excerpt","aiUsageStripMineLabel","meter","mine","limit","used","who","scope","aiUsageStripPoolLabel","pool","AiUsageStrip","orgId","orgSlug","bound","pct","Math","min","round","tone","state","allotmentReached","mode","raiseHref","MANAGE_BILLING_USAGE","whose","role","aria-live","data-testid","sx","px","py","borderRadius","bgcolor","useFlexGap","direction","spacing","alignItems","flexWrap","variant","flexGrow","last","color","alignSelf","aria-label","my","height","component","model","label","auto","componentVariant","href","displayName"],"mappings":"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"}
@@ -14,7 +14,10 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
17
18
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
+ import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
20
+ import { HelpTip } from "@aglyn/shared-ui-jsx";
18
21
  import { Box, Button, CircularProgress, Paper, Stack, Typography } from "@mui/material";
19
22
  import { useEffect, useRef, useState } from "react";
20
23
  import { aiBuildCanRetry, aiBuildItemRows, aiBuildOutcomeLine } from "../model/ai-build-progress.js";
@@ -23,6 +26,10 @@ import { followAiJobEvents } from "./ai-job-events.js";
23
26
  import { AiJobPlan } from "./ai-job-plan.component.js";
24
27
  import { resumeAiJobRequest } from "./ai-job-requests.js";
25
28
  import { openAiJobs } from "./ai-jobs-store.js";
29
+ /** The card's own section of the Assist builds guide (AGL-3660). */ const ASSIST_BUILD_HELP = pluginDocsHelp('aiAssistBuilds', {
30
+ anchor: '#the-plan-card',
31
+ excerpt: 'What one request will build, item by item, with its estimate. Nothing is built until you choose Confirm plan, and what it makes is a draft.'
32
+ });
26
33
  const ITEM_STATES = {
27
34
  done: 'built',
28
35
  active: 'building',
@@ -70,9 +77,19 @@ export function AssistBuildCard({ proposal, job, notice, orgId, user, staff = fa
70
77
  },
71
78
  "aria-label": "Build plan",
72
79
  children: [
73
- /*#__PURE__*/ _jsx(Typography, {
74
- variant: "subtitle2",
75
- children: proposal.summary
80
+ /*#__PURE__*/ _jsxs(Stack, {
81
+ direction: "row",
82
+ spacing: 0.5,
83
+ sx: {
84
+ alignItems: 'center'
85
+ },
86
+ children: [
87
+ /*#__PURE__*/ _jsx(Typography, {
88
+ variant: "subtitle2",
89
+ children: proposal.summary
90
+ }),
91
+ /*#__PURE__*/ _jsx(HelpTip, _extends({}, ASSIST_BUILD_HELP))
92
+ ]
76
93
  }),
77
94
  !job && !notice && /*#__PURE__*/ _jsxs(Stack, {
78
95
  direction: "row",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/assist-build-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport type { MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport { Box, Button, CircularProgress, Paper, Stack, Typography } from '@mui/material'\nimport { useEffect, useRef, useState } from 'react'\nimport { aiBuildCanRetry, aiBuildItemRows, aiBuildOutcomeLine } from '../model/ai-build-progress'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport type { AssistBuildProposal } from '../model/assist-build'\nimport { followAiJobEvents } from './ai-job-events'\nimport { AiJobPlan } from './ai-job-plan.component'\nimport { resumeAiJobRequest } from './ai-job-requests'\nimport { openAiJobs } from './ai-jobs-store'\n\n/**\n * The one card a build proposed in the chat becomes (AGL-3616): while its\n * job plans, a line saying so; then the job's own plan card, with its one\n * price and Confirm — the same `AiJobPlan` the AI jobs drawer shows, so\n * confirming here and there is one door; then where the build stands, item\n * by item, with AI jobs a click away. The job itself is started by the\n * panel, once, when the turn that proposed it ends; this card only follows\n * it, so re-rendering a stored thread never starts a second build.\n */\nexport interface AssistBuildCardProps {\n proposal: AssistBuildProposal\n /** The job the proposal started, as last seen; `null` while it is being created. */\n job: AiJobSummary | null\n /** Why the job could not be created or confirmed, when it could not. */\n notice: string | null\n orgId: string\n user: MaybeTokenSource\n staff?: boolean\n /** The job as it moved, for the thread to keep. */\n onJob: (job: AiJobSummary) => void\n onNotice: (notice: string | null) => void\n}\n\nconst ITEM_STATES = { done: 'built', active: 'building', waiting: 'waiting', failed: 'failed', skipped: 'not built' } as const\n\nexport function AssistBuildCard({\n proposal,\n job,\n notice,\n orgId,\n user,\n staff = false,\n onJob,\n onNotice,\n}: AssistBuildCardProps): JSX.Element {\n const [busy, setBusy] = useState(false)\n const userRef = useRef(user)\n userRef.current = user\n const onJobRef = useRef(onJob)\n onJobRef.current = onJob\n\n // Follow a job that is still planning or building; a plan waiting for its\n // confirmation and a settled job need nothing more from the stream.\n const following = Boolean(job && !AI_JOB_TERMINAL_STATUSES.includes(job.status) && job.status !== 'needs_review')\n const jobId = job?.id ?? null\n useEffect(() => {\n if (!following || !jobId) return\n const controller = new AbortController()\n void followAiJobEvents(() => userRef.current, orgId, jobId, controller.signal, (next) => onJobRef.current(next))\n return () => controller.abort()\n }, [following, jobId, orgId])\n\n const resume = async (target: AiJobSummary, options?: { publish?: boolean; retry?: 'failed-items' }) => {\n setBusy(true)\n onNotice(null)\n const decision = await resumeAiJobRequest(user, orgId, target, options)\n if (decision.job) onJob(decision.job)\n if (decision.error) onNotice(decision.error)\n setBusy(false)\n }\n\n const settled = job ? AI_JOB_TERMINAL_STATUSES.includes(job.status) : false\n const outcome = job ? aiBuildOutcomeLine(job) : null\n return (\n <Paper variant=\"outlined\" sx={{ mt: 1, p: 1.5 }} aria-label=\"Build plan\">\n <Typography variant=\"subtitle2\">{proposal.summary}</Typography>\n {!job && !notice && (\n <Stack direction=\"row\" spacing={1} sx={{ mt: 0.5, alignItems: 'center' }}>\n <CircularProgress size={14} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Planning what to build on this site…'}\n </Typography>\n </Stack>\n )}\n {notice && (\n <Typography variant=\"body2\" color=\"error\" sx={{ mt: 0.5 }} role=\"alert\">\n {notice}\n </Typography>\n )}\n {job && job.status === 'needs_review' && (\n <AiJobPlan job={job} busy={busy} staff={staff} user={user} onResume={(target, options) => void resume(target, options)} />\n )}\n {job && following && (\n <Stack direction=\"row\" spacing={1} sx={{ mt: 0.5, alignItems: 'center' }}>\n <CircularProgress size={14} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {job.plan?.status === 'confirmed'\n ? 'Building each part in turn. What it makes is an unpublished draft unless you chose to publish.'\n : 'Planning what to build on this site…'}\n </Typography>\n </Stack>\n )}\n {job && (job.items?.length ?? 0) > 0 && job.status !== 'needs_review' && (\n <Box component=\"ul\" aria-label=\"Items\" sx={{ m: 0, mt: 0.5, pl: 2 }}>\n {aiBuildItemRows(job).map((row) => (\n <Typography key={row.slot} component=\"li\" variant=\"caption\" data-item-state={row.state}>\n {`${row.label} — ${ITEM_STATES[row.state]}`}\n {row.detail ? `. ${row.detail}` : ''}\n </Typography>\n ))}\n </Box>\n )}\n {settled && (\n <Typography variant=\"body2\" sx={{ mt: 0.5 }}>\n {outcome ?? job?.error ?? ''}\n </Typography>\n )}\n {job && (\n <Stack direction=\"row\" spacing={1} sx={{ mt: 1 }}>\n {job && aiBuildCanRetry(job) && (\n <Button size=\"small\" variant=\"contained\" disabled={busy} onClick={() => void resume(job, { retry: 'failed-items' })}>\n {'Try again what failed'}\n </Button>\n )}\n <Button size=\"small\" onClick={() => openAiJobs({ jobId: job.id })}>\n {'Open in AI jobs'}\n </Button>\n </Stack>\n )}\n </Paper>\n )\n}\n"],"names":["Box","Button","CircularProgress","Paper","Stack","Typography","useEffect","useRef","useState","aiBuildCanRetry","aiBuildItemRows","aiBuildOutcomeLine","AI_JOB_TERMINAL_STATUSES","followAiJobEvents","AiJobPlan","resumeAiJobRequest","openAiJobs","ITEM_STATES","done","active","waiting","failed","skipped","AssistBuildCard","proposal","job","notice","orgId","user","staff","onJob","onNotice","outcome","busy","setBusy","userRef","current","onJobRef","following","Boolean","includes","status","jobId","id","controller","AbortController","signal","next","abort","resume","target","options","decision","error","settled","variant","sx","mt","p","aria-label","summary","direction","spacing","alignItems","size","color","role","onResume","plan","items","length","component","m","pl","map","row","data-item-state","state","label","detail","slot","disabled","onClick","retry"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/assist-build-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport type { MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { HelpTip } from '@aglyn/shared-ui-jsx'\nimport { Box, Button, CircularProgress, Paper, Stack, Typography } from '@mui/material'\nimport { useEffect, useRef, useState } from 'react'\nimport { aiBuildCanRetry, aiBuildItemRows, aiBuildOutcomeLine } from '../model/ai-build-progress'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport type { AssistBuildProposal } from '../model/assist-build'\nimport { followAiJobEvents } from './ai-job-events'\nimport { AiJobPlan } from './ai-job-plan.component'\nimport { resumeAiJobRequest } from './ai-job-requests'\nimport { openAiJobs } from './ai-jobs-store'\n\n/**\n * The one card a build proposed in the chat becomes (AGL-3616): while its\n * job plans, a line saying so; then the job's own plan card, with its one\n * price and Confirm — the same `AiJobPlan` the AI jobs drawer shows, so\n * confirming here and there is one door; then where the build stands, item\n * by item, with AI jobs a click away. The job itself is started by the\n * panel, once, when the turn that proposed it ends; this card only follows\n * it, so re-rendering a stored thread never starts a second build.\n */\nexport interface AssistBuildCardProps {\n proposal: AssistBuildProposal\n /** The job the proposal started, as last seen; `null` while it is being created. */\n job: AiJobSummary | null\n /** Why the job could not be created or confirmed, when it could not. */\n notice: string | null\n orgId: string\n user: MaybeTokenSource\n staff?: boolean\n /** The job as it moved, for the thread to keep. */\n onJob: (job: AiJobSummary) => void\n onNotice: (notice: string | null) => void\n}\n\n/** The card's own section of the Assist builds guide (AGL-3660). */\nconst ASSIST_BUILD_HELP = pluginDocsHelp('aiAssistBuilds', {\n anchor: '#the-plan-card',\n excerpt:\n 'What one request will build, item by item, with its estimate. Nothing is built until you choose Confirm plan, and what it makes is a draft.',\n})\n\nconst ITEM_STATES = { done: 'built', active: 'building', waiting: 'waiting', failed: 'failed', skipped: 'not built' } as const\n\nexport function AssistBuildCard({\n proposal,\n job,\n notice,\n orgId,\n user,\n staff = false,\n onJob,\n onNotice,\n}: AssistBuildCardProps): JSX.Element {\n const [busy, setBusy] = useState(false)\n const userRef = useRef(user)\n userRef.current = user\n const onJobRef = useRef(onJob)\n onJobRef.current = onJob\n\n // Follow a job that is still planning or building; a plan waiting for its\n // confirmation and a settled job need nothing more from the stream.\n const following = Boolean(job && !AI_JOB_TERMINAL_STATUSES.includes(job.status) && job.status !== 'needs_review')\n const jobId = job?.id ?? null\n useEffect(() => {\n if (!following || !jobId) return\n const controller = new AbortController()\n void followAiJobEvents(() => userRef.current, orgId, jobId, controller.signal, (next) => onJobRef.current(next))\n return () => controller.abort()\n }, [following, jobId, orgId])\n\n const resume = async (target: AiJobSummary, options?: { publish?: boolean; retry?: 'failed-items' }) => {\n setBusy(true)\n onNotice(null)\n const decision = await resumeAiJobRequest(user, orgId, target, options)\n if (decision.job) onJob(decision.job)\n if (decision.error) onNotice(decision.error)\n setBusy(false)\n }\n\n const settled = job ? AI_JOB_TERMINAL_STATUSES.includes(job.status) : false\n const outcome = job ? aiBuildOutcomeLine(job) : null\n return (\n <Paper variant=\"outlined\" sx={{ mt: 1, p: 1.5 }} aria-label=\"Build plan\">\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center' }}>\n <Typography variant=\"subtitle2\">{proposal.summary}</Typography>\n <HelpTip {...ASSIST_BUILD_HELP} />\n </Stack>\n {!job && !notice && (\n <Stack direction=\"row\" spacing={1} sx={{ mt: 0.5, alignItems: 'center' }}>\n <CircularProgress size={14} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Planning what to build on this site…'}\n </Typography>\n </Stack>\n )}\n {notice && (\n <Typography variant=\"body2\" color=\"error\" sx={{ mt: 0.5 }} role=\"alert\">\n {notice}\n </Typography>\n )}\n {job && job.status === 'needs_review' && (\n <AiJobPlan job={job} busy={busy} staff={staff} user={user} onResume={(target, options) => void resume(target, options)} />\n )}\n {job && following && (\n <Stack direction=\"row\" spacing={1} sx={{ mt: 0.5, alignItems: 'center' }}>\n <CircularProgress size={14} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {job.plan?.status === 'confirmed'\n ? 'Building each part in turn. What it makes is an unpublished draft unless you chose to publish.'\n : 'Planning what to build on this site…'}\n </Typography>\n </Stack>\n )}\n {job && (job.items?.length ?? 0) > 0 && job.status !== 'needs_review' && (\n <Box component=\"ul\" aria-label=\"Items\" sx={{ m: 0, mt: 0.5, pl: 2 }}>\n {aiBuildItemRows(job).map((row) => (\n <Typography key={row.slot} component=\"li\" variant=\"caption\" data-item-state={row.state}>\n {`${row.label} — ${ITEM_STATES[row.state]}`}\n {row.detail ? `. ${row.detail}` : ''}\n </Typography>\n ))}\n </Box>\n )}\n {settled && (\n <Typography variant=\"body2\" sx={{ mt: 0.5 }}>\n {outcome ?? job?.error ?? ''}\n </Typography>\n )}\n {job && (\n <Stack direction=\"row\" spacing={1} sx={{ mt: 1 }}>\n {job && aiBuildCanRetry(job) && (\n <Button size=\"small\" variant=\"contained\" disabled={busy} onClick={() => void resume(job, { retry: 'failed-items' })}>\n {'Try again what failed'}\n </Button>\n )}\n <Button size=\"small\" onClick={() => openAiJobs({ jobId: job.id })}>\n {'Open in AI jobs'}\n </Button>\n </Stack>\n )}\n </Paper>\n )\n}\n"],"names":["pluginDocsHelp","HelpTip","Box","Button","CircularProgress","Paper","Stack","Typography","useEffect","useRef","useState","aiBuildCanRetry","aiBuildItemRows","aiBuildOutcomeLine","AI_JOB_TERMINAL_STATUSES","followAiJobEvents","AiJobPlan","resumeAiJobRequest","openAiJobs","ASSIST_BUILD_HELP","anchor","excerpt","ITEM_STATES","done","active","waiting","failed","skipped","AssistBuildCard","proposal","job","notice","orgId","user","staff","onJob","onNotice","outcome","busy","setBusy","userRef","current","onJobRef","following","Boolean","includes","status","jobId","id","controller","AbortController","signal","next","abort","resume","target","options","decision","error","settled","variant","sx","mt","p","aria-label","direction","spacing","alignItems","summary","size","color","role","onResume","plan","items","length","component","m","pl","map","row","data-item-state","state","label","detail","slot","disabled","onClick","retry"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  /**
3
4
  * @license
@@ -19,7 +20,8 @@ import { trackEvent } from "@aglyn/aglyn/app-utils/analytics-events";
19
20
  import { formatBytes } from "@aglyn/aglyn/app-utils/measure-node-map";
20
21
  import { AI_JOB_TERMINAL_STATUSES } from "../model/ai-jobs.types.js";
21
22
  import { mdiChevronDown, mdiChevronUp } from "@aglyn/shared-data-mdi";
22
- import { AppLink, MdiIcon } from "@aglyn/shared-ui-jsx";
23
+ import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
24
+ import { AppLink, HelpTip, MdiIcon } from "@aglyn/shared-ui-jsx";
23
25
  import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
24
26
  import { Box, Button, Chip, CircularProgress, Collapse, IconButton, Stack, Typography } from "@mui/material";
25
27
  import { useCallback, useEffect, useRef, useState } from "react";
@@ -372,6 +374,10 @@ export function AssistJobsDrawer({ orgId, org, orgReady, orgSlug, user, visible,
372
374
  }, chip.state))
373
375
  ]
374
376
  }),
377
+ /*#__PURE__*/ _jsx(HelpTip, _extends({}, pluginDocsHelp('aiJobs', {
378
+ anchor: '#ai-jobs-in-assist',
379
+ excerpt: 'Every AI job in this workspace, newest first: where it stands, the credits it used, and its plan to confirm, cancel or try again.'
380
+ }))),
375
381
  insight && /*#__PURE__*/ _jsx(Button, {
376
382
  size: "small",
377
383
  onClick: ()=>setInsightOpen('ask'),