@aglyn/plugins-ai 1.0.0-beta.226 → 1.0.0-beta.228

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 (203) hide show
  1. package/README.md +3 -2
  2. package/package.json +17 -17
  3. package/src/lib/billing/ai-month-meter.js +5 -3
  4. package/src/lib/billing/ai-month-meter.js.map +1 -1
  5. package/src/lib/billing/ai-overage-ledger.js +3 -1
  6. package/src/lib/billing/ai-overage-ledger.js.map +1 -1
  7. package/src/lib/components/ai-brief-dialog.component.d.ts +6 -2
  8. package/src/lib/components/ai-brief-dialog.component.js +36 -18
  9. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  10. package/src/lib/components/ai-describe-automation.component.js +24 -4
  11. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  12. package/src/lib/components/ai-describe-button.component.js +6 -1
  13. package/src/lib/components/ai-describe-button.component.js.map +1 -1
  14. package/src/lib/components/ai-describe-page.component.d.ts +1 -1
  15. package/src/lib/components/ai-describe-page.component.js +1 -1
  16. package/src/lib/components/ai-describe-page.component.js.map +1 -1
  17. package/src/lib/components/ai-insight-dialog.component.js +24 -2
  18. package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
  19. package/src/lib/components/ai-job-events.d.ts +20 -0
  20. package/src/lib/components/ai-job-events.js +78 -0
  21. package/src/lib/components/ai-job-events.js.map +1 -0
  22. package/src/lib/components/ai-job-follow.component.d.ts +49 -0
  23. package/src/lib/components/ai-job-follow.component.js +210 -0
  24. package/src/lib/components/ai-job-follow.component.js.map +1 -0
  25. package/src/lib/components/ai-job-links.d.ts +42 -0
  26. package/src/lib/components/ai-job-links.js +140 -0
  27. package/src/lib/components/ai-job-links.js.map +1 -0
  28. package/src/lib/components/ai-job-plan.component.d.ts +13 -6
  29. package/src/lib/components/ai-job-plan.component.js +29 -3
  30. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  31. package/src/lib/components/ai-job-requests.d.ts +24 -0
  32. package/src/lib/components/ai-job-requests.js +76 -0
  33. package/src/lib/components/ai-job-requests.js.map +1 -0
  34. package/src/lib/components/ai-job-site.d.ts +22 -0
  35. package/src/lib/components/ai-job-site.js +73 -0
  36. package/src/lib/components/ai-job-site.js.map +1 -0
  37. package/src/lib/components/ai-jobs-indicator.component.d.ts +25 -0
  38. package/src/lib/components/ai-jobs-indicator.component.js +80 -0
  39. package/src/lib/components/ai-jobs-indicator.component.js.map +1 -0
  40. package/src/lib/components/ai-jobs-list-page.component.d.ts +23 -0
  41. package/src/lib/components/ai-jobs-list-page.component.js +303 -0
  42. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -0
  43. package/src/lib/components/ai-jobs-page.component.d.ts +7 -0
  44. package/src/lib/components/ai-jobs-page.component.js +30 -0
  45. package/src/lib/components/ai-jobs-page.component.js.map +1 -0
  46. package/src/lib/components/ai-jobs-store.d.ts +81 -0
  47. package/src/lib/components/ai-jobs-store.js +218 -0
  48. package/src/lib/components/ai-jobs-store.js.map +1 -0
  49. package/src/lib/components/ai-page-brief-dialog.component.d.ts +4 -4
  50. package/src/lib/components/ai-page-brief-dialog.component.js.map +1 -1
  51. package/src/lib/components/ai-site-build-page.component.d.ts +51 -0
  52. package/src/lib/components/ai-site-build-page.component.js +495 -0
  53. package/src/lib/components/ai-site-build-page.component.js.map +1 -0
  54. package/src/lib/components/ai-site-start-card.component.d.ts +9 -1
  55. package/src/lib/components/ai-site-start-card.component.js +533 -132
  56. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  57. package/src/lib/components/ai-site-starter-fallback.component.d.ts +24 -0
  58. package/src/lib/components/ai-site-starter-fallback.component.js +112 -0
  59. package/src/lib/components/ai-site-starter-fallback.component.js.map +1 -0
  60. package/src/lib/components/assist-jobs-drawer.component.d.ts +18 -20
  61. package/src/lib/components/assist-jobs-drawer.component.js +152 -172
  62. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  63. package/src/lib/components/assist-panel.component.js +69 -8
  64. package/src/lib/components/assist-panel.component.js.map +1 -1
  65. package/src/lib/components/staff-ai-credit-return.component.d.ts +55 -0
  66. package/src/lib/components/staff-ai-credit-return.component.js +346 -0
  67. package/src/lib/components/staff-ai-credit-return.component.js.map +1 -0
  68. package/src/lib/components/staff-org-ai-card.component.js +364 -279
  69. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  70. package/src/lib/components/staff-user-ai-usage-card.component.d.ts +4 -0
  71. package/src/lib/components/staff-user-ai-usage-card.component.js +83 -28
  72. package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
  73. package/src/lib/components/use-ai-job-run.js +5 -18
  74. package/src/lib/components/use-ai-job-run.js.map +1 -1
  75. package/src/lib/jobs/ai-job-auto-confirm.d.ts +52 -0
  76. package/src/lib/jobs/ai-job-auto-confirm.js +62 -0
  77. package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -0
  78. package/src/lib/jobs/ai-job-draft-ids.d.ts +6 -0
  79. package/src/lib/jobs/ai-job-draft-ids.js +9 -0
  80. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  81. package/src/lib/jobs/ai-job-drafts.d.ts +7 -0
  82. package/src/lib/jobs/ai-job-drafts.js +16 -7
  83. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  84. package/src/lib/jobs/ai-job-layout-step.js +2 -1
  85. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  86. package/src/lib/jobs/ai-job-page-sections.d.ts +6 -5
  87. package/src/lib/jobs/ai-job-page-sections.js +3 -3
  88. package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
  89. package/src/lib/jobs/ai-job-page-step.d.ts +19 -1
  90. package/src/lib/jobs/ai-job-page-step.js +50 -8
  91. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  92. package/src/lib/jobs/ai-job-plan-step.d.ts +33 -2
  93. package/src/lib/jobs/ai-job-plan-step.js +167 -18
  94. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  95. package/src/lib/jobs/ai-job-site-step.d.ts +28 -0
  96. package/src/lib/jobs/ai-job-site-step.js +130 -21
  97. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  98. package/src/lib/jobs/ai-job-text-step.d.ts +14 -1
  99. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  100. package/src/lib/jobs/ai-jobs-notify.d.ts +37 -0
  101. package/src/lib/jobs/ai-jobs-notify.js +51 -0
  102. package/src/lib/jobs/ai-jobs-notify.js.map +1 -0
  103. package/src/lib/jobs/ai-jobs.d.ts +39 -4
  104. package/src/lib/jobs/ai-jobs.js +205 -34
  105. package/src/lib/jobs/ai-jobs.js.map +1 -1
  106. package/src/lib/jobs/ai-site-publish.d.ts +98 -0
  107. package/src/lib/jobs/ai-site-publish.js +372 -0
  108. package/src/lib/jobs/ai-site-publish.js.map +1 -0
  109. package/src/lib/jobs/fixtures/ai-page-briefs.d.ts +7 -6
  110. package/src/lib/jobs/fixtures/ai-page-briefs.js +23 -56
  111. package/src/lib/jobs/fixtures/ai-page-briefs.js.map +1 -1
  112. package/src/lib/model/ai-job-activity.d.ts +112 -0
  113. package/src/lib/model/ai-job-activity.js +144 -0
  114. package/src/lib/model/ai-job-activity.js.map +1 -0
  115. package/src/lib/model/ai-job-failure-copy.d.ts +69 -0
  116. package/src/lib/model/ai-job-failure-copy.js +78 -0
  117. package/src/lib/model/ai-job-failure-copy.js.map +1 -0
  118. package/src/lib/model/ai-job-notice.d.ts +63 -0
  119. package/src/lib/model/ai-job-notice.js +129 -0
  120. package/src/lib/model/ai-job-notice.js.map +1 -0
  121. package/src/lib/model/ai-jobs.types.d.ts +53 -0
  122. package/src/lib/model/ai-jobs.types.js +1 -0
  123. package/src/lib/model/ai-jobs.types.js.map +1 -1
  124. package/src/lib/model/ai-plan-capabilities.d.ts +32 -10
  125. package/src/lib/model/ai-plan-capabilities.js +15 -2
  126. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  127. package/src/lib/model/ai-site-build-progress.d.ts +61 -0
  128. package/src/lib/model/ai-site-build-progress.js +136 -0
  129. package/src/lib/model/ai-site-build-progress.js.map +1 -0
  130. package/src/lib/model/ai-site-inventory.d.ts +9 -0
  131. package/src/lib/model/ai-site-inventory.js +7 -2
  132. package/src/lib/model/ai-site-inventory.js.map +1 -1
  133. package/src/lib/model/ai-site-job.d.ts +93 -2
  134. package/src/lib/model/ai-site-job.js +88 -7
  135. package/src/lib/model/ai-site-job.js.map +1 -1
  136. package/src/lib/model/ai-site-start-seo.d.ts +19 -0
  137. package/src/lib/model/ai-site-start-seo.js +91 -3
  138. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  139. package/src/lib/model/ai-site-start.d.ts +3 -1
  140. package/src/lib/model/ai-site-start.js +6 -4
  141. package/src/lib/model/ai-site-start.js.map +1 -1
  142. package/src/lib/plan-entitlements.d.ts +3 -3
  143. package/src/lib/plan-entitlements.js +3 -3
  144. package/src/lib/plan-entitlements.js.map +1 -1
  145. package/src/lib/plugin.js +38 -2
  146. package/src/lib/plugin.js.map +1 -1
  147. package/src/lib/runtime/ai-abuse-guards.d.ts +9 -6
  148. package/src/lib/runtime/ai-abuse-guards.js +10 -7
  149. package/src/lib/runtime/ai-abuse-guards.js.map +1 -1
  150. package/src/lib/runtime/ai-doctrine-validators.d.ts +16 -12
  151. package/src/lib/runtime/ai-doctrine-validators.js +50 -40
  152. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  153. package/src/lib/runtime/ai-doctrine.js +1 -1
  154. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  155. package/src/lib/runtime/ai-eval-live.d.ts +1 -1
  156. package/src/lib/runtime/ai-eval-live.js +1 -1
  157. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  158. package/src/lib/runtime/ai-gate.d.ts +3 -2
  159. package/src/lib/runtime/ai-gate.js +3 -2
  160. package/src/lib/runtime/ai-gate.js.map +1 -1
  161. package/src/lib/runtime/site-inventory.d.ts +16 -1
  162. package/src/lib/runtime/site-inventory.js +43 -3
  163. package/src/lib/runtime/site-inventory.js.map +1 -1
  164. package/src/lib/server/ai-admin-credits.d.ts +21 -0
  165. package/src/lib/server/ai-admin-credits.js +213 -0
  166. package/src/lib/server/ai-admin-credits.js.map +1 -0
  167. package/src/lib/server/ai-admin-org.js +12 -5
  168. package/src/lib/server/ai-admin-org.js.map +1 -1
  169. package/src/lib/server/ai-admin-signals.js +3 -1
  170. package/src/lib/server/ai-admin-signals.js.map +1 -1
  171. package/src/lib/server/ai-admin-user.js +12 -1
  172. package/src/lib/server/ai-admin-user.js.map +1 -1
  173. package/src/lib/server/ai-allotments.js +3 -2
  174. package/src/lib/server/ai-allotments.js.map +1 -1
  175. package/src/lib/server/ai-jobs-live-sites.d.ts +26 -0
  176. package/src/lib/server/ai-jobs-live-sites.js +58 -0
  177. package/src/lib/server/ai-jobs-live-sites.js.map +1 -0
  178. package/src/lib/server/ai-jobs-route.js +42 -5
  179. package/src/lib/server/ai-jobs-route.js.map +1 -1
  180. package/src/lib/server/assist-chat.js +3 -2
  181. package/src/lib/server/assist-chat.js.map +1 -1
  182. package/src/lib/server/billing-credits.js +4 -3
  183. package/src/lib/server/billing-credits.js.map +1 -1
  184. package/src/lib/server/billing-overage.js +3 -2
  185. package/src/lib/server/billing-overage.js.map +1 -1
  186. package/src/lib/server.js +11 -0
  187. package/src/lib/server.js.map +1 -1
  188. package/src/lib/usage/assist-credit-returns-write.d.ts +75 -0
  189. package/src/lib/usage/assist-credit-returns-write.js +150 -0
  190. package/src/lib/usage/assist-credit-returns-write.js.map +1 -0
  191. package/src/lib/usage/assist-credit-returns.d.ts +68 -0
  192. package/src/lib/usage/assist-credit-returns.js +79 -0
  193. package/src/lib/usage/assist-credit-returns.js.map +1 -0
  194. package/src/lib/usage/assist-free-taste.js +6 -3
  195. package/src/lib/usage/assist-free-taste.js.map +1 -1
  196. package/src/lib/usage/assist-job-refund.d.ts +100 -0
  197. package/src/lib/usage/assist-job-refund.js +109 -0
  198. package/src/lib/usage/assist-job-refund.js.map +1 -0
  199. package/src/lib/usage/assist-usage.js +5 -2
  200. package/src/lib/usage/assist-usage.js.map +1 -1
  201. package/src/lib/usage/staff-org-ai.d.ts +31 -1
  202. package/src/lib/usage/staff-org-ai.js +19 -2
  203. package/src/lib/usage/staff-org-ai.js.map +1 -1
@@ -16,12 +16,15 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
16
16
  * See the License for the specific language governing permissions and
17
17
  * limitations under the License.
18
18
  */ import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
19
+ import { mdiCreation } from "@aglyn/shared-data-mdi";
20
+ import { MdiIcon } from "@aglyn/shared-ui-jsx";
19
21
  import { useUser } from "@aglyn/tenant-feature-instance";
20
22
  import { Alert, Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, Link, Stack, TextField, Typography } from "@mui/material";
21
23
  import { useEffect, useState } from "react";
24
+ import { openAiJobs } from "./ai-jobs-store.js";
22
25
  import { aiJobProblem, useAiJobRun, useAiJobsVerdict } from "./use-ai-job-run.js";
23
26
  /**
24
- * "Describe it" on the Automation page's Actions (AGL-2919), beside Add action
27
+ * "Create with AI" on the Automation page's Actions (AGL-2919), beside Add action
25
28
  * and Recipes through the `hostAutomations` zone: a description becomes a
26
29
  * `workflow` job that drafts one automation, switched off.
27
30
  *
@@ -36,7 +39,7 @@ export const AI_AUTOMATION_BRIEF_COPY = {
36
39
  placeholder: 'When someone submits the newsletter form, add them to the newsletter list, make them ' + 'a lead and send them a welcome email',
37
40
  next: 'The automation is drafted switched off, using only the triggers and steps your plan ' + 'includes. Anything it needs that your site does not have yet is left for you to fill in.',
38
41
  submit: 'Draft the automation',
39
- started: 'The automation is being drafted. This can take a few minutes. You can close this ' + 'window: the draft appears in the list, switched off, when it is ready.',
42
+ started: 'The automation is being drafted. This can take a few minutes. You can close this ' + 'window: the draft appears in the list, switched off, when it is ready. Open AI ' + 'jobs to follow it meanwhile.',
40
43
  drafted: 'Drafted',
41
44
  review: 'Review it',
42
45
  notListed: 'The draft is saved. It appears in the list in a moment.',
@@ -137,12 +140,26 @@ export function AiAutomationBriefDialog(props) {
137
140
  pt: 1
138
141
  },
139
142
  children: [
140
- running ? /*#__PURE__*/ _jsx(Alert, {
143
+ running && job ? /*#__PURE__*/ _jsx(Alert, {
141
144
  severity: "info",
142
145
  icon: /*#__PURE__*/ _jsx(CircularProgress, {
143
146
  size: 18,
144
147
  "aria-label": "Drafting"
145
148
  }),
149
+ action: // Follows the job in the Assist panel (AGL-3593); the dialog
150
+ // covers it, so it closes.
151
+ /*#__PURE__*/ _jsx(Button, {
152
+ color: "inherit",
153
+ size: "small",
154
+ variant: "outlined",
155
+ onClick: ()=>{
156
+ openAiJobs({
157
+ jobId: job.id
158
+ });
159
+ onClose();
160
+ },
161
+ children: 'Open AI jobs'
162
+ }),
146
163
  children: copy.started
147
164
  }) : null,
148
165
  draft ? /*#__PURE__*/ _jsxs(Alert, {
@@ -207,8 +224,11 @@ export function AiAutomationBriefDialog(props) {
207
224
  /*#__PURE__*/ _jsx(Button, {
208
225
  size: "small",
209
226
  variant: "outlined",
227
+ startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
228
+ path: mdiCreation.path
229
+ }),
210
230
  onClick: ()=>setOpen(true),
211
- children: 'Describe it'
231
+ children: 'Create with AI'
212
232
  }),
213
233
  /*#__PURE__*/ _jsx(AiAutomationBriefDialog, {
214
234
  open: open,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-describe-automation.component.tsx"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { ConsoleHostAutomationsZoneProps } from './ai-automation-zones'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Link,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport { aiJobProblem, useAiJobRun, useAiJobsVerdict } from './use-ai-job-run'\n\n/**\n * \"Describe it\" on the Automation page's Actions (AGL-2919), beside Add action\n * and Recipes through the `hostAutomations` zone: a description becomes a\n * `workflow` job that drafts one automation, switched off.\n *\n * The dialog follows the shared brief dialog's shape — one text box, what\n * happens next, and the action that starts the job — and then follows the\n * job, because an automation's draft needs no plan to confirm: when it is\n * written, the dialog says what to fill in and opens it in the Actions editor.\n */\n\n/** The longest description a job admits. */\nconst BRIEF_MAX_CHARS = 4_000\n\nexport const AI_AUTOMATION_BRIEF_COPY = {\n title: 'Describe an automation',\n briefLabel: 'What should happen, and when?',\n placeholder:\n 'When someone submits the newsletter form, add them to the newsletter list, make them ' +\n 'a lead and send them a welcome email',\n next:\n 'The automation is drafted switched off, using only the triggers and steps your plan ' +\n 'includes. Anything it needs that your site does not have yet is left for you to fill in.',\n submit: 'Draft the automation',\n started:\n 'The automation is being drafted. This can take a few minutes. You can close this ' +\n 'window: the draft appears in the list, switched off, when it is ready.',\n drafted: 'Drafted',\n review: 'Review it',\n notListed: 'The draft is saved. It appears in the list in a moment.',\n failed: 'The automation could not be drafted. Try again.',\n} as const\n\nexport interface AiAutomationBriefDialogProps {\n open: boolean\n onClose: () => void\n orgId: string | undefined\n hostId: string\n /** Who is signed in, whose token the requests carry. */\n user: Parameters<typeof useAiJobRun>[0]\n /** Opens a listed action in the Actions editor; `false` when the list has not read it yet. */\n openAction: (actionId: string) => boolean\n}\n\nexport function AiAutomationBriefDialog(props: AiAutomationBriefDialogProps) {\n const { open, onClose, orgId, hostId, user, openAction } = props\n const copy = AI_AUTOMATION_BRIEF_COPY\n const help = pluginDocsHelp('aiAutomations')\n const [brief, setBrief] = useState('')\n const [notListed, setNotListed] = useState(false)\n const run = useAiJobRun(user, copy.failed)\n const { job, starting, running, notice, reset } = run\n\n // Opening or closing starts over and stops following: a job still drafting\n // carries on in AI jobs, and its draft appears in the list when it is written.\n useEffect(() => {\n reset()\n setNotListed(false)\n }, [open, reset])\n\n const draft = job?.status === 'done' ? job.outputs.find((output) => output.resource === 'workflow') : undefined\n const problem = aiJobProblem(job, copy.failed) ?? (job?.status === 'done' && !draft ? copy.failed : null)\n const asking = !job || Boolean(notice)\n\n const start = async () => {\n const trimmed = brief.trim()\n if (!orgId || !trimmed) return\n setNotListed(false)\n if (await run.start({ orgId, hostId, kind: 'workflow', brief: trimmed, inputs: { mode: 'draft' } })) {\n setBrief('')\n }\n }\n\n const review = () => {\n if (!draft) return\n if (openAction(draft.id)) onClose()\n else setNotListed(true)\n }\n\n return (\n <Dialog open={open} onClose={starting ? undefined : onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle>{copy.title}</DialogTitle>\n <DialogContent>\n {asking ? (\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n label={copy.briefLabel}\n placeholder={copy.placeholder}\n multiline\n minRows={4}\n value={brief}\n onChange={(event) => setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS))}\n helperText={`${brief.length.toLocaleString('en-US')} / ${BRIEF_MAX_CHARS.toLocaleString('en-US')}`}\n disabled={starting}\n autoFocus\n />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {copy.next}{' '}\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Typography>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n </Stack>\n ) : (\n <Stack spacing={2} sx={{ pt: 1 }}>\n {running ? (\n <Alert severity=\"info\" icon={<CircularProgress size={18} aria-label=\"Drafting\" />}>\n {copy.started}\n </Alert>\n ) : null}\n {draft ? (\n <Alert severity=\"success\">\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {`${copy.drafted}: ${draft.label}`}\n </Typography>\n {draft.note ? <Typography variant=\"body2\">{draft.note}</Typography> : null}\n </Alert>\n ) : null}\n {problem ? <Alert severity={job?.status === 'failed' ? 'error' : 'warning'}>{problem}</Alert> : null}\n {notListed ? <Alert severity=\"info\">{copy.notListed}</Alert> : null}\n </Stack>\n )}\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose} disabled={starting}>\n {asking ? 'Cancel' : 'Close'}\n </Button>\n {asking ? (\n <Button\n variant=\"contained\"\n onClick={() => void start()}\n disabled={starting || !orgId || !brief.trim()}\n >\n {copy.submit}\n </Button>\n ) : draft ? (\n <Button variant=\"contained\" onClick={review}>\n {copy.review}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\n/**\n * Renders nothing until the jobs route has answered for this workspace, and\n * nothing when it says the feature is not this workspace's.\n */\nexport function AiDescribeAutomationButton({ hostId, orgId, openAction }: ConsoleHostAutomationsZoneProps) {\n const { data: user } = useUser()\n const verdict = useAiJobsVerdict(user, orgId)\n const [open, setOpen] = useState(false)\n\n if (verdict !== 'ready') return null\n return (\n <>\n <Button size=\"small\" variant=\"outlined\" onClick={() => setOpen(true)}>\n {'Describe it'}\n </Button>\n <AiAutomationBriefDialog\n open={open}\n onClose={() => setOpen(false)}\n orgId={orgId}\n hostId={hostId}\n user={user}\n openAction={openAction}\n />\n </>\n )\n}\n\nexport default AiDescribeAutomationButton\n"],"names":["pluginDocsHelp","useUser","Alert","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","Link","Stack","TextField","Typography","useEffect","useState","aiJobProblem","useAiJobRun","useAiJobsVerdict","BRIEF_MAX_CHARS","AI_AUTOMATION_BRIEF_COPY","title","briefLabel","placeholder","next","submit","started","drafted","review","notListed","failed","AiAutomationBriefDialog","props","open","onClose","orgId","hostId","user","openAction","copy","help","brief","setBrief","setNotListed","run","job","starting","running","notice","reset","draft","status","outputs","find","output","resource","undefined","problem","asking","Boolean","start","trimmed","trim","kind","inputs","mode","id","fullWidth","maxWidth","spacing","sx","pt","label","multiline","minRows","value","onChange","event","target","slice","helperText","length","toLocaleString","disabled","autoFocus","variant","color","href","rel","excerpt","severity","icon","size","aria-label","fontWeight","note","onClick","AiDescribeAutomationButton","data","verdict","setOpen"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-describe-automation.component.tsx"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { ConsoleHostAutomationsZoneProps } from './ai-automation-zones'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Link,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport { openAiJobs } from './ai-jobs-store'\nimport { aiJobProblem, useAiJobRun, useAiJobsVerdict } from './use-ai-job-run'\n\n/**\n * \"Create with AI\" on the Automation page's Actions (AGL-2919), beside Add action\n * and Recipes through the `hostAutomations` zone: a description becomes a\n * `workflow` job that drafts one automation, switched off.\n *\n * The dialog follows the shared brief dialog's shape — one text box, what\n * happens next, and the action that starts the job — and then follows the\n * job, because an automation's draft needs no plan to confirm: when it is\n * written, the dialog says what to fill in and opens it in the Actions editor.\n */\n\n/** The longest description a job admits. */\nconst BRIEF_MAX_CHARS = 4_000\n\nexport const AI_AUTOMATION_BRIEF_COPY = {\n title: 'Describe an automation',\n briefLabel: 'What should happen, and when?',\n placeholder:\n 'When someone submits the newsletter form, add them to the newsletter list, make them ' +\n 'a lead and send them a welcome email',\n next:\n 'The automation is drafted switched off, using only the triggers and steps your plan ' +\n 'includes. Anything it needs that your site does not have yet is left for you to fill in.',\n submit: 'Draft the automation',\n started:\n 'The automation is being drafted. This can take a few minutes. You can close this ' +\n 'window: the draft appears in the list, switched off, when it is ready. Open AI ' +\n 'jobs to follow it meanwhile.',\n drafted: 'Drafted',\n review: 'Review it',\n notListed: 'The draft is saved. It appears in the list in a moment.',\n failed: 'The automation could not be drafted. Try again.',\n} as const\n\nexport interface AiAutomationBriefDialogProps {\n open: boolean\n onClose: () => void\n orgId: string | undefined\n hostId: string\n /** Who is signed in, whose token the requests carry. */\n user: Parameters<typeof useAiJobRun>[0]\n /** Opens a listed action in the Actions editor; `false` when the list has not read it yet. */\n openAction: (actionId: string) => boolean\n}\n\nexport function AiAutomationBriefDialog(props: AiAutomationBriefDialogProps) {\n const { open, onClose, orgId, hostId, user, openAction } = props\n const copy = AI_AUTOMATION_BRIEF_COPY\n const help = pluginDocsHelp('aiAutomations')\n const [brief, setBrief] = useState('')\n const [notListed, setNotListed] = useState(false)\n const run = useAiJobRun(user, copy.failed)\n const { job, starting, running, notice, reset } = run\n\n // Opening or closing starts over and stops following: a job still drafting\n // carries on in AI jobs, and its draft appears in the list when it is written.\n useEffect(() => {\n reset()\n setNotListed(false)\n }, [open, reset])\n\n const draft = job?.status === 'done' ? job.outputs.find((output) => output.resource === 'workflow') : undefined\n const problem = aiJobProblem(job, copy.failed) ?? (job?.status === 'done' && !draft ? copy.failed : null)\n const asking = !job || Boolean(notice)\n\n const start = async () => {\n const trimmed = brief.trim()\n if (!orgId || !trimmed) return\n setNotListed(false)\n if (await run.start({ orgId, hostId, kind: 'workflow', brief: trimmed, inputs: { mode: 'draft' } })) {\n setBrief('')\n }\n }\n\n const review = () => {\n if (!draft) return\n if (openAction(draft.id)) onClose()\n else setNotListed(true)\n }\n\n return (\n <Dialog open={open} onClose={starting ? undefined : onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle>{copy.title}</DialogTitle>\n <DialogContent>\n {asking ? (\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n label={copy.briefLabel}\n placeholder={copy.placeholder}\n multiline\n minRows={4}\n value={brief}\n onChange={(event) => setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS))}\n helperText={`${brief.length.toLocaleString('en-US')} / ${BRIEF_MAX_CHARS.toLocaleString('en-US')}`}\n disabled={starting}\n autoFocus\n />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {copy.next}{' '}\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Typography>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n </Stack>\n ) : (\n <Stack spacing={2} sx={{ pt: 1 }}>\n {running && job ? (\n <Alert\n severity=\"info\"\n icon={<CircularProgress size={18} aria-label=\"Drafting\" />}\n action={\n // Follows the job in the Assist panel (AGL-3593); the dialog\n // covers it, so it closes.\n <Button\n color=\"inherit\"\n size=\"small\"\n variant=\"outlined\"\n onClick={() => {\n openAiJobs({ jobId: job.id })\n onClose()\n }}\n >\n {'Open AI jobs'}\n </Button>\n }\n >\n {copy.started}\n </Alert>\n ) : null}\n {draft ? (\n <Alert severity=\"success\">\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {`${copy.drafted}: ${draft.label}`}\n </Typography>\n {draft.note ? <Typography variant=\"body2\">{draft.note}</Typography> : null}\n </Alert>\n ) : null}\n {problem ? <Alert severity={job?.status === 'failed' ? 'error' : 'warning'}>{problem}</Alert> : null}\n {notListed ? <Alert severity=\"info\">{copy.notListed}</Alert> : null}\n </Stack>\n )}\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose} disabled={starting}>\n {asking ? 'Cancel' : 'Close'}\n </Button>\n {asking ? (\n <Button\n variant=\"contained\"\n onClick={() => void start()}\n disabled={starting || !orgId || !brief.trim()}\n >\n {copy.submit}\n </Button>\n ) : draft ? (\n <Button variant=\"contained\" onClick={review}>\n {copy.review}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\n/**\n * Renders nothing until the jobs route has answered for this workspace, and\n * nothing when it says the feature is not this workspace's.\n */\nexport function AiDescribeAutomationButton({ hostId, orgId, openAction }: ConsoleHostAutomationsZoneProps) {\n const { data: user } = useUser()\n const verdict = useAiJobsVerdict(user, orgId)\n const [open, setOpen] = useState(false)\n\n if (verdict !== 'ready') return null\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {'Create with AI'}\n </Button>\n <AiAutomationBriefDialog\n open={open}\n onClose={() => setOpen(false)}\n orgId={orgId}\n hostId={hostId}\n user={user}\n openAction={openAction}\n />\n </>\n )\n}\n\nexport default AiDescribeAutomationButton\n"],"names":["pluginDocsHelp","mdiCreation","MdiIcon","useUser","Alert","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","Link","Stack","TextField","Typography","useEffect","useState","openAiJobs","aiJobProblem","useAiJobRun","useAiJobsVerdict","BRIEF_MAX_CHARS","AI_AUTOMATION_BRIEF_COPY","title","briefLabel","placeholder","next","submit","started","drafted","review","notListed","failed","AiAutomationBriefDialog","props","open","onClose","orgId","hostId","user","openAction","copy","help","brief","setBrief","setNotListed","run","job","starting","running","notice","reset","draft","status","outputs","find","output","resource","undefined","problem","asking","Boolean","start","trimmed","trim","kind","inputs","mode","id","fullWidth","maxWidth","spacing","sx","pt","label","multiline","minRows","value","onChange","event","target","slice","helperText","length","toLocaleString","disabled","autoFocus","variant","color","href","rel","excerpt","severity","icon","size","aria-label","action","onClick","jobId","fontWeight","note","AiDescribeAutomationButton","data","verdict","setOpen","startIcon","path"],"mappings":"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"}
@@ -1,6 +1,8 @@
1
1
  'use client';
2
2
  import { _ as _extends } from "@swc/helpers/_/_extends";
3
3
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
4
+ import { mdiCreation } from "@aglyn/shared-data-mdi";
5
+ import { MdiIcon } from "@aglyn/shared-ui-jsx";
4
6
  import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
5
7
  import { useUser } from "@aglyn/tenant-feature-instance";
6
8
  import { Button } from "@mui/material";
@@ -48,8 +50,11 @@ import { AiBriefDialog } from "./ai-brief-dialog.component.js";
48
50
  /*#__PURE__*/ _jsx(Button, {
49
51
  size: "small",
50
52
  variant: "outlined",
53
+ startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
54
+ path: mdiCreation.path
55
+ }),
51
56
  onClick: ()=>setOpen(true),
52
- children: 'Describe it'
57
+ children: 'Create with AI'
53
58
  }),
54
59
  /*#__PURE__*/ _jsx(AiBriefDialog, {
55
60
  kind: kind,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-describe-button.component.tsx"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n ConsoleHostComponentsZoneProps,\n ConsoleHostLayoutsZoneProps,\n ConsoleHostScreensZoneProps,\n ConsoleHostTemplatesZoneProps,\n} from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { Button } from '@mui/material'\nimport { useEffect, useRef, useState } from 'react'\nimport { AiBriefDialog, type AiBriefKind } from './ai-brief-dialog.component'\n\n/**\n * \"Describe it\" (AGL-2907, AGL-3043, AGL-3051): a job from a brief, beside the\n * create actions of the page that lists what the job makes — a page on\n * Screens, a page template on Templates, a layout on Layouts, a form on Forms\n * and a reusable component on Components — each mounted through that page's\n * zone. The Assist panel's AI jobs opens the same dialog for a page.\n */\n\ntype Verdict = 'checking' | 'ready' | 'hidden'\n\nexport interface AiDescribeButtonProps extends ConsoleHostScreensZoneProps {\n /** The job the brief starts: what the page the button sits on lists. */\n kind: AiBriefKind\n}\n\n/**\n * Renders nothing until the jobs route has answered for this workspace: the\n * shell decided the plan, the member's permission and the site's AI switch\n * before mounting it, and the release flag is the route's to decide, so a 404\n * or a 403 there is this control staying absent. A lockdown is the start\n * door's to say, in the dialog, in its own words.\n */\nexport function AiDescribeButton({ kind, hostId, orgId }: AiDescribeButtonProps) {\n const { data: user } = useUser()\n // Held in a ref so the probe keys on WHO is signed in, never on the\n // identity of the object that says so.\n const userRef = useRef(user)\n userRef.current = user\n const uid = user?.uid ?? null\n const [verdict, setVerdict] = useState<Verdict>('checking')\n const [open, setOpen] = useState(false)\n\n useEffect(() => {\n if (!orgId || !uid) return\n let active = true\n void (async () => {\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=1`,\n )\n if (active) {\n setVerdict(response.status === 404 || response.status === 403 ? 'hidden' : 'ready')\n }\n } catch {\n if (active) setVerdict('hidden')\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, uid])\n\n if (verdict !== 'ready') return null\n return (\n <>\n <Button size=\"small\" variant=\"outlined\" onClick={() => setOpen(true)}>\n {'Describe it'}\n </Button>\n <AiBriefDialog\n kind={kind}\n open={open}\n onClose={() => setOpen(false)}\n orgId={orgId}\n hostId={hostId}\n user={user}\n />\n </>\n )\n}\n\n/** A page template from a brief, on the Templates page (`hostTemplates`). */\nexport function AiDescribeTemplateButton(props: ConsoleHostTemplatesZoneProps) {\n return <AiDescribeButton {...props} kind=\"template\" />\n}\n\n/** A layout from a brief, on the Layouts page (`hostLayouts`). */\nexport function AiDescribeLayoutButton(props: ConsoleHostLayoutsZoneProps) {\n return <AiDescribeButton {...props} kind=\"layout\" />\n}\n\n/**\n * A form from a brief, on the Forms page (`hostForms`, which the forms plugin\n * hosts on the `hostScreens` contract).\n */\nexport function AiDescribeFormButton(props: ConsoleHostScreensZoneProps) {\n return <AiDescribeButton {...props} kind=\"form\" />\n}\n\n/** A reusable component from a brief, on the Components page (`hostComponents`). */\nexport function AiDescribeComponentButton(props: ConsoleHostComponentsZoneProps) {\n return <AiDescribeButton {...props} kind=\"component\" />\n}\n\nexport default AiDescribeButton\n"],"names":["authorizedFetch","useUser","Button","useEffect","useRef","useState","AiBriefDialog","AiDescribeButton","kind","hostId","orgId","data","user","userRef","current","uid","verdict","setVerdict","open","setOpen","active","response","encodeURIComponent","status","size","variant","onClick","onClose","AiDescribeTemplateButton","props","AiDescribeLayoutButton","AiDescribeFormButton","AiDescribeComponentButton"],"mappings":"AAAA;;;AAyBA,SAASA,eAAe,QAAQ,2CAA0C;AAC1E,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SAASC,MAAM,QAAQ,gBAAe;AACtC,SAASC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AACnD,SAASC,aAAa,QAA0B,iCAA6B;AAiB7E;;;;;;CAMC,GACD,OAAO,SAASC,iBAAiB,EAAEC,IAAI,EAAEC,MAAM,EAAEC,KAAK,EAAyB;;IAC7E,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAGX;IACvB,oEAAoE;IACpE,uCAAuC;IACvC,MAAMY,UAAUT,OAAOQ;IACvBC,QAAQC,OAAO,GAAGF;IAClB,MAAMG,cAAMH,wBAAAA,KAAMG,GAAG,mBAAI;IACzB,MAAM,CAACC,SAASC,WAAW,GAAGZ,SAAkB;IAChD,MAAM,CAACa,MAAMC,QAAQ,GAAGd,SAAS;IAEjCF,UAAU;QACR,IAAI,CAACO,SAAS,CAACK,KAAK;QACpB,IAAIK,SAAS;QACb,KAAK,AAAC,CAAA;YACJ,IAAI;gBACF,MAAMC,WAAW,MAAMrB,gBACrBa,QAAQC,OAAO,EACf,CAAC,mBAAmB,EAAEQ,mBAAmBZ,OAAO,QAAQ,CAAC;gBAE3D,IAAIU,QAAQ;oBACVH,WAAWI,SAASE,MAAM,KAAK,OAAOF,SAASE,MAAM,KAAK,MAAM,WAAW;gBAC7E;YACF,EAAE,eAAM;gBACN,IAAIH,QAAQH,WAAW;YACzB;QACF,CAAA;QACA,OAAO;YACLG,SAAS;QACX;IACF,GAAG;QAACV;QAAOK;KAAI;IAEf,IAAIC,YAAY,SAAS,OAAO;IAChC,qBACE;;0BACE,KAACd;gBAAOsB,MAAK;gBAAQC,SAAQ;gBAAWC,SAAS,IAAMP,QAAQ;0BAC5D;;0BAEH,KAACb;gBACCE,MAAMA;gBACNU,MAAMA;gBACNS,SAAS,IAAMR,QAAQ;gBACvBT,OAAOA;gBACPD,QAAQA;gBACRG,MAAMA;;;;AAId;AAEA,2EAA2E,GAC3E,OAAO,SAASgB,yBAAyBC,KAAoC;IAC3E,qBAAO,KAACtB,+BAAqBsB;QAAOrB,MAAK;;AAC3C;AAEA,gEAAgE,GAChE,OAAO,SAASsB,uBAAuBD,KAAkC;IACvE,qBAAO,KAACtB,+BAAqBsB;QAAOrB,MAAK;;AAC3C;AAEA;;;CAGC,GACD,OAAO,SAASuB,qBAAqBF,KAAkC;IACrE,qBAAO,KAACtB,+BAAqBsB;QAAOrB,MAAK;;AAC3C;AAEA,kFAAkF,GAClF,OAAO,SAASwB,0BAA0BH,KAAqC;IAC7E,qBAAO,KAACtB,+BAAqBsB;QAAOrB,MAAK;;AAC3C;AAEA,eAAeD,iBAAgB"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-describe-button.component.tsx"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type {\n ConsoleHostComponentsZoneProps,\n ConsoleHostLayoutsZoneProps,\n ConsoleHostScreensZoneProps,\n ConsoleHostTemplatesZoneProps,\n} from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { Button } from '@mui/material'\nimport { useEffect, useRef, useState } from 'react'\nimport { AiBriefDialog, type AiBriefKind } from './ai-brief-dialog.component'\n\n/**\n * \"Create with AI\" (AGL-2907, AGL-3043, AGL-3051): a job from a brief, beside the\n * create actions of the page that lists what the job makes — a page on\n * Screens, a page template on Templates, a layout on Layouts, a form on Forms\n * and a reusable component on Components — each mounted through that page's\n * zone. The Assist panel's AI jobs opens the same dialog for a page.\n */\n\ntype Verdict = 'checking' | 'ready' | 'hidden'\n\nexport interface AiDescribeButtonProps extends ConsoleHostScreensZoneProps {\n /** The job the brief starts: what the page the button sits on lists. */\n kind: AiBriefKind\n}\n\n/**\n * Renders nothing until the jobs route has answered for this workspace: the\n * shell decided the plan, the member's permission and the site's AI switch\n * before mounting it, and the release flag is the route's to decide, so a 404\n * or a 403 there is this control staying absent. A lockdown is the start\n * door's to say, in the dialog, in its own words.\n */\nexport function AiDescribeButton({ kind, hostId, orgId }: AiDescribeButtonProps) {\n const { data: user } = useUser()\n // Held in a ref so the probe keys on WHO is signed in, never on the\n // identity of the object that says so.\n const userRef = useRef(user)\n userRef.current = user\n const uid = user?.uid ?? null\n const [verdict, setVerdict] = useState<Verdict>('checking')\n const [open, setOpen] = useState(false)\n\n useEffect(() => {\n if (!orgId || !uid) return\n let active = true\n void (async () => {\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=1`,\n )\n if (active) {\n setVerdict(response.status === 404 || response.status === 403 ? 'hidden' : 'ready')\n }\n } catch {\n if (active) setVerdict('hidden')\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, uid])\n\n if (verdict !== 'ready') return null\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {'Create with AI'}\n </Button>\n <AiBriefDialog\n kind={kind}\n open={open}\n onClose={() => setOpen(false)}\n orgId={orgId}\n hostId={hostId}\n user={user}\n />\n </>\n )\n}\n\n/** A page template from a brief, on the Templates page (`hostTemplates`). */\nexport function AiDescribeTemplateButton(props: ConsoleHostTemplatesZoneProps) {\n return <AiDescribeButton {...props} kind=\"template\" />\n}\n\n/** A layout from a brief, on the Layouts page (`hostLayouts`). */\nexport function AiDescribeLayoutButton(props: ConsoleHostLayoutsZoneProps) {\n return <AiDescribeButton {...props} kind=\"layout\" />\n}\n\n/**\n * A form from a brief, on the Forms page (`hostForms`, which the forms plugin\n * hosts on the `hostScreens` contract).\n */\nexport function AiDescribeFormButton(props: ConsoleHostScreensZoneProps) {\n return <AiDescribeButton {...props} kind=\"form\" />\n}\n\n/** A reusable component from a brief, on the Components page (`hostComponents`). */\nexport function AiDescribeComponentButton(props: ConsoleHostComponentsZoneProps) {\n return <AiDescribeButton {...props} kind=\"component\" />\n}\n\nexport default AiDescribeButton\n"],"names":["mdiCreation","MdiIcon","authorizedFetch","useUser","Button","useEffect","useRef","useState","AiBriefDialog","AiDescribeButton","kind","hostId","orgId","data","user","userRef","current","uid","verdict","setVerdict","open","setOpen","active","response","encodeURIComponent","status","size","variant","startIcon","path","onClick","onClose","AiDescribeTemplateButton","props","AiDescribeLayoutButton","AiDescribeFormButton","AiDescribeComponentButton"],"mappings":"AAAA;;;AAyBA,SAASA,WAAW,QAAQ,yBAAwB;AACpD,SAASC,OAAO,QAAQ,uBAAsB;AAC9C,SAASC,eAAe,QAAQ,2CAA0C;AAC1E,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SAASC,MAAM,QAAQ,gBAAe;AACtC,SAASC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AACnD,SAASC,aAAa,QAA0B,iCAA6B;AAiB7E;;;;;;CAMC,GACD,OAAO,SAASC,iBAAiB,EAAEC,IAAI,EAAEC,MAAM,EAAEC,KAAK,EAAyB;;IAC7E,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAGX;IACvB,oEAAoE;IACpE,uCAAuC;IACvC,MAAMY,UAAUT,OAAOQ;IACvBC,QAAQC,OAAO,GAAGF;IAClB,MAAMG,cAAMH,wBAAAA,KAAMG,GAAG,mBAAI;IACzB,MAAM,CAACC,SAASC,WAAW,GAAGZ,SAAkB;IAChD,MAAM,CAACa,MAAMC,QAAQ,GAAGd,SAAS;IAEjCF,UAAU;QACR,IAAI,CAACO,SAAS,CAACK,KAAK;QACpB,IAAIK,SAAS;QACb,KAAK,AAAC,CAAA;YACJ,IAAI;gBACF,MAAMC,WAAW,MAAMrB,gBACrBa,QAAQC,OAAO,EACf,CAAC,mBAAmB,EAAEQ,mBAAmBZ,OAAO,QAAQ,CAAC;gBAE3D,IAAIU,QAAQ;oBACVH,WAAWI,SAASE,MAAM,KAAK,OAAOF,SAASE,MAAM,KAAK,MAAM,WAAW;gBAC7E;YACF,EAAE,eAAM;gBACN,IAAIH,QAAQH,WAAW;YACzB;QACF,CAAA;QACA,OAAO;YACLG,SAAS;QACX;IACF,GAAG;QAACV;QAAOK;KAAI;IAEf,IAAIC,YAAY,SAAS,OAAO;IAChC,qBACE;;0BACE,KAACd;gBACCsB,MAAK;gBACLC,SAAQ;gBACRC,yBAAW,KAAC3B;oBAAQ4B,MAAM7B,YAAY6B,IAAI;;gBAC1CC,SAAS,IAAMT,QAAQ;0BAEtB;;0BAEH,KAACb;gBACCE,MAAMA;gBACNU,MAAMA;gBACNW,SAAS,IAAMV,QAAQ;gBACvBT,OAAOA;gBACPD,QAAQA;gBACRG,MAAMA;;;;AAId;AAEA,2EAA2E,GAC3E,OAAO,SAASkB,yBAAyBC,KAAoC;IAC3E,qBAAO,KAACxB,+BAAqBwB;QAAOvB,MAAK;;AAC3C;AAEA,gEAAgE,GAChE,OAAO,SAASwB,uBAAuBD,KAAkC;IACvE,qBAAO,KAACxB,+BAAqBwB;QAAOvB,MAAK;;AAC3C;AAEA;;;CAGC,GACD,OAAO,SAASyB,qBAAqBF,KAAkC;IACrE,qBAAO,KAACxB,+BAAqBwB;QAAOvB,MAAK;;AAC3C;AAEA,kFAAkF,GAClF,OAAO,SAAS0B,0BAA0BH,KAAqC;IAC7E,qBAAO,KAACxB,+BAAqBwB;QAAOvB,MAAK;;AAC3C;AAEA,eAAeD,iBAAgB"}
@@ -16,7 +16,7 @@
16
16
  */
17
17
  import type { ConsoleHostScreensZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins';
18
18
  /**
19
- * "Describe it" (AGL-2907): a page from a brief, beside Templates and Create
19
+ * "Create with AI" (AGL-2907): a page from a brief, beside Templates and Create
20
20
  * New Screen on a site's Screens page, mounted through the `hostScreens`
21
21
  * zone. The Assist panel's AI jobs opens the same dialog. The button, its
22
22
  * probe and its dialog are the ones every describe entry shares
@@ -3,7 +3,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
3
3
  import { jsx as _jsx } from "react/jsx-runtime";
4
4
  import { AiDescribeButton } from "./ai-describe-button.component.js";
5
5
  /**
6
- * "Describe it" (AGL-2907): a page from a brief, beside Templates and Create
6
+ * "Create with AI" (AGL-2907): a page from a brief, beside Templates and Create
7
7
  * New Screen on a site's Screens page, mounted through the `hostScreens`
8
8
  * zone. The Assist panel's AI jobs opens the same dialog. The button, its
9
9
  * probe and its dialog are the ones every describe entry shares
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-describe-page.component.tsx"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ConsoleHostScreensZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { AiDescribeButton } from './ai-describe-button.component'\n\n/**\n * \"Describe it\" (AGL-2907): a page from a brief, beside Templates and Create\n * New Screen on a site's Screens page, mounted through the `hostScreens`\n * zone. The Assist panel's AI jobs opens the same dialog. The button, its\n * probe and its dialog are the ones every describe entry shares\n * (`ai-describe-button.component.tsx`, AGL-3043).\n */\nexport function AiDescribePageButton(props: ConsoleHostScreensZoneProps) {\n return <AiDescribeButton {...props} kind=\"page\" />\n}\n\nexport default AiDescribePageButton\n"],"names":["AiDescribeButton","AiDescribePageButton","props","kind"],"mappings":"AAAA;;;AAoBA,SAASA,gBAAgB,QAAQ,oCAAgC;AAEjE;;;;;;CAMC,GACD,OAAO,SAASC,qBAAqBC,KAAkC;IACrE,qBAAO,KAACF,+BAAqBE;QAAOC,MAAK;;AAC3C;AAEA,eAAeF,qBAAoB"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-describe-page.component.tsx"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ConsoleHostScreensZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { AiDescribeButton } from './ai-describe-button.component'\n\n/**\n * \"Create with AI\" (AGL-2907): a page from a brief, beside Templates and Create\n * New Screen on a site's Screens page, mounted through the `hostScreens`\n * zone. The Assist panel's AI jobs opens the same dialog. The button, its\n * probe and its dialog are the ones every describe entry shares\n * (`ai-describe-button.component.tsx`, AGL-3043).\n */\nexport function AiDescribePageButton(props: ConsoleHostScreensZoneProps) {\n return <AiDescribeButton {...props} kind=\"page\" />\n}\n\nexport default AiDescribePageButton\n"],"names":["AiDescribeButton","AiDescribePageButton","props","kind"],"mappings":"AAAA;;;AAoBA,SAASA,gBAAgB,QAAQ,oCAAgC;AAEjE;;;;;;CAMC,GACD,OAAO,SAASC,qBAAqBC,KAAkC;IACrE,qBAAO,KAACF,+BAAqBE;QAAOC,MAAK;;AAC3C;AAEA,eAAeF,qBAAoB"}
@@ -24,7 +24,8 @@ import { doc, getDoc, setDoc } from "firebase/firestore";
24
24
  import { useCallback, useEffect, useRef, useState } from "react";
25
25
  import { AI_INSIGHT_DEFAULT_DAYS, AI_INSIGHT_QUESTION_MAX_CHARS, AI_INSIGHT_WINDOWS, aiInsightRowText, aiInsightSourceHref } from "../model/ai-insight.js";
26
26
  import { AI_JOB_TERMINAL_STATUSES } from "../model/ai-jobs.types.js";
27
- import { readEventFrames } from "./assist-jobs-drawer.component.js";
27
+ import { readEventFrames } from "./ai-job-events.js";
28
+ import { openAiJobs, publishAiJob } from "./ai-jobs-store.js";
28
29
  /**
29
30
  * "Ask about your numbers" (AGL-2915): the Assist panel's question box on a
30
31
  * site's Analytics, Data and CRM Reports pages and the workspace's Data page,
@@ -56,6 +57,10 @@ export function AiInsightDialog(props) {
56
57
  const [answer, setAnswer] = useState(null);
57
58
  const [openCitations, setOpenCitations] = useState(null);
58
59
  const [subscribed, setSubscribed] = useState(null);
60
+ /**
61
+ * The job whose answer was still being written when the dialog stopped
62
+ * waiting (AGL-3593): its notice offers to open AI jobs on it.
63
+ */ const [stillWriting, setStillWriting] = useState(null);
59
64
  const watchRef = useRef(null);
60
65
  const readAnswer = useCallback(async (id)=>{
61
66
  const response = await authorizedFetch(userRef.current, `/api/ai/insights/${encodeURIComponent(id)}?orgId=${encodeURIComponent(orgId)}`);
@@ -99,6 +104,7 @@ export function AiInsightDialog(props) {
99
104
  if (event['type'] === 'reconnect') again = true;
100
105
  if (event['type'] !== 'state') return;
101
106
  const next = event['job'];
107
+ publishAiJob(next);
102
108
  if (next.status === 'needs_input') {
103
109
  var _next_error;
104
110
  setNotice((_next_error = next.error) != null ? _next_error : 'This workspace cannot run AI jobs right now.');
@@ -112,7 +118,8 @@ export function AiInsightDialog(props) {
112
118
  if (settled) return settle(settled);
113
119
  }
114
120
  if (!controller.signal.aborted) {
115
- setNotice('The answer is still being written. Find it under AI jobs in a minute.');
121
+ setNotice('The answer is still being written. Find it in AI jobs in a minute.');
122
+ setStillWriting(job.id);
116
123
  setPhase('ask');
117
124
  }
118
125
  }, [
@@ -126,6 +133,7 @@ export function AiInsightDialog(props) {
126
133
  return;
127
134
  }
128
135
  setNotice(null);
136
+ setStillWriting(null);
129
137
  setOpenCitations(null);
130
138
  if (jobId) {
131
139
  setPhase('working');
@@ -183,6 +191,7 @@ export function AiInsightDialog(props) {
183
191
  const text = question.trim();
184
192
  if (!text) return;
185
193
  setNotice(null);
194
+ setStillWriting(null);
186
195
  setPhase('working');
187
196
  try {
188
197
  const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {
@@ -209,6 +218,7 @@ export function AiInsightDialog(props) {
209
218
  setPhase('ask');
210
219
  return;
211
220
  }
221
+ publishAiJob(payload.job);
212
222
  await follow(payload.job);
213
223
  } catch (unused) {
214
224
  setNotice('The question could not be asked. Try again.');
@@ -399,6 +409,18 @@ export function AiInsightDialog(props) {
399
409
  }) : null,
400
410
  notice ? /*#__PURE__*/ _jsx(Alert, {
401
411
  severity: "warning",
412
+ action: stillWriting ? /*#__PURE__*/ _jsx(Button, {
413
+ color: "inherit",
414
+ size: "small",
415
+ variant: "outlined",
416
+ onClick: ()=>{
417
+ openAiJobs({
418
+ jobId: stillWriting
419
+ });
420
+ onClose();
421
+ },
422
+ children: 'Open AI jobs'
423
+ }) : undefined,
402
424
  children: notice
403
425
  }) : null,
404
426
  surface !== 'datasets' && subscribed !== null ? /*#__PURE__*/ _jsx(FormControlLabel, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-insight-dialog.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { INSIGHT_DIGESTS_FIELD, insightDigestSubscribed } from '@aglyn/aglyn/app-utils/notifications'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { doc, getDoc, setDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n AI_INSIGHT_DEFAULT_DAYS,\n AI_INSIGHT_QUESTION_MAX_CHARS,\n AI_INSIGHT_WINDOWS,\n aiInsightRowText,\n aiInsightSourceHref,\n type AiInsightAnswerWire,\n type AiInsightSurface,\n} from '../model/ai-insight'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport { readEventFrames } from './assist-jobs-drawer.component'\n\n/**\n * \"Ask about your numbers\" (AGL-2915): the Assist panel's question box on a\n * site's Analytics, Data and CRM Reports pages and the workspace's Data page,\n * and the answer an `insight` job wrote.\n *\n * A question starts a job, which reads the figures and answers on the jobs\n * beat; the dialog watches the job and then reads the answer through\n * `/api/ai/insights/{jobId}`, the only door to it. Every insight shows the\n * rows it was traced to and links the console page they come from, so a\n * person can check a number before acting on it. Nothing here writes anything\n * but the person's own weekly-insights switch.\n */\n\n/** Placeholder questions by surface, in the words a person would use. */\nconst PLACEHOLDERS: Record<Exclude<AiInsightSurface, 'digest'>, string> = {\n analytics: 'Which pages brought in the most visitors, and did anything change?',\n datasets: 'What is the average order total by state?',\n 'crm-reports': 'Which campaign or form brought in the most people this month?',\n}\n\nexport interface AiInsightDialogProps {\n open: boolean\n onClose: () => void\n orgId: string\n /** The org's slug, for the links to the pages a table's figures come from. */\n orgSlug: string\n /** The site the question is about; `null` on the workspace's Data page. */\n hostId: string | null\n /** The site's subdomain, which a console link names a site by. */\n host: string | null\n surface: Exclude<AiInsightSurface, 'digest'>\n user: Parameters<typeof authorizedFetch>[0]\n /** The signed-in person, whose weekly-insights switch the dialog shows. */\n uid: string | null\n /** An answered job to show rather than a question to ask. */\n jobId?: string | null\n}\n\ntype Phase = 'ask' | 'working' | 'answer'\n\nexport function AiInsightDialog(props: AiInsightDialogProps) {\n const { open, onClose, orgId, orgSlug, hostId, host, surface, jobId } = props\n // Held in a ref: a request reads who is signed in, and nothing keys on the\n // identity of the object that says so.\n const userRef = useRef(props.user)\n userRef.current = props.user\n const uid = props.uid\n const firestore = useFirestore()\n const [phase, setPhase] = useState<Phase>('ask')\n const [question, setQuestion] = useState('')\n const [days, setDays] = useState(AI_INSIGHT_DEFAULT_DAYS)\n const [notice, setNotice] = useState<string | null>(null)\n const [answer, setAnswer] = useState<AiInsightAnswerWire | null>(null)\n const [openCitations, setOpenCitations] = useState<number | null>(null)\n const [subscribed, setSubscribed] = useState<boolean | null>(null)\n const watchRef = useRef<AbortController | null>(null)\n\n const readAnswer = useCallback(\n async (id: string) => {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/insights/${encodeURIComponent(id)}?orgId=${encodeURIComponent(orgId)}`,\n )\n const payload = await response.json().catch(() => null)\n if (!response.ok || !payload?.answer) {\n setNotice(String(payload?.error ?? 'The answer could not be loaded. Try again.'))\n setPhase('ask')\n return\n }\n setAnswer(payload.answer as AiInsightAnswerWire)\n setPhase('answer')\n },\n [orgId],\n )\n\n /** Follows a job until it settles, then reads its answer or says why there is none. */\n const follow = useCallback(\n async (job: AiJobSummary) => {\n const settle = async (settled: AiJobSummary) => {\n if (settled.status === 'done') await readAnswer(settled.id)\n else {\n setNotice(settled.error ?? 'The question could not be answered. Try again.')\n setPhase('ask')\n }\n }\n if (AI_JOB_TERMINAL_STATUSES.includes(job.status)) return settle(job)\n setPhase('working')\n const controller = new AbortController()\n watchRef.current?.abort()\n watchRef.current = controller\n let again = true\n while (again && !controller.signal.aborted) {\n again = false\n let settled: AiJobSummary | null = null\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs/${encodeURIComponent(job.id)}/events?orgId=${encodeURIComponent(orgId)}`,\n { signal: controller.signal },\n )\n if (!response.ok || !response.body) break\n await readEventFrames(response.body, (event) => {\n if (event['type'] === 'reconnect') again = true\n if (event['type'] !== 'state') return\n const next = event['job'] as AiJobSummary\n if (next.status === 'needs_input') {\n setNotice(next.error ?? 'This workspace cannot run AI jobs right now.')\n }\n if (AI_JOB_TERMINAL_STATUSES.includes(next.status)) settled = next\n })\n } catch {\n if (controller.signal.aborted) return\n break\n }\n if (settled) return settle(settled)\n }\n if (!controller.signal.aborted) {\n setNotice('The answer is still being written. Find it under AI jobs in a minute.')\n setPhase('ask')\n }\n },\n [orgId, readAnswer],\n )\n\n useEffect(() => {\n if (!open) {\n watchRef.current?.abort()\n return\n }\n setNotice(null)\n setOpenCitations(null)\n if (jobId) {\n setPhase('working')\n void readAnswer(jobId)\n } else {\n setAnswer(null)\n setPhase('ask')\n }\n }, [open, jobId, readAnswer])\n\n // The person's own weekly-insights switch, read when the dialog opens.\n useEffect(() => {\n if (!open || !uid || !orgId) return\n let active = true\n void getDoc(doc(firestore, 'users', uid))\n .then((snapshot) => {\n if (active) {\n setSubscribed(\n insightDigestSubscribed(snapshot.get(INSIGHT_DIGESTS_FIELD) as Record<string, boolean>, orgId),\n )\n }\n })\n .catch(() => undefined)\n return () => {\n active = false\n }\n }, [open, uid, orgId, firestore])\n\n const toggleDigest = useCallback(async () => {\n if (!uid || subscribed === null) return\n const next = !subscribed\n setSubscribed(next)\n try {\n await setDoc(doc(firestore, 'users', uid), { [INSIGHT_DIGESTS_FIELD]: { [orgId]: next } }, { merge: true })\n } catch {\n setSubscribed(!next)\n setNotice('Your weekly insights setting could not be saved. Try again.')\n }\n }, [uid, subscribed, firestore, orgId])\n\n const ask = useCallback(async () => {\n const text = question.trim()\n if (!text) return\n setNotice(null)\n setPhase('working')\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: 'insight',\n brief: text,\n inputs: { surface, days },\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok || !payload?.job) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(locked ? lockdownRefusalText(locked) : String(payload?.error ?? 'The question could not be asked. Try again.'))\n setPhase('ask')\n return\n }\n await follow(payload.job as AiJobSummary)\n } catch {\n setNotice('The question could not be asked. Try again.')\n setPhase('ask')\n }\n }, [question, orgId, hostId, surface, days, follow])\n\n const tableByRef = (ref: string) => answer?.tables.find((table) => table.ref === ref) ?? null\n\n return (\n <Dialog open={open} onClose={onClose} fullWidth maxWidth=\"sm\" aria-labelledby=\"ai-insight-title\">\n <DialogTitle id=\"ai-insight-title\">{'Ask about your numbers'}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n {phase === 'ask' ? (\n <>\n <TextField\n label=\"What do you want to know?\"\n placeholder={PLACEHOLDERS[surface]}\n multiline\n minRows={2}\n value={question}\n onChange={(event) => setQuestion(event.target.value.slice(0, AI_INSIGHT_QUESTION_MAX_CHARS))}\n autoFocus\n />\n {surface !== 'datasets' ? (\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\" id=\"ai-insight-window-label\">\n {'Over the last'}\n </Typography>\n <Stack direction=\"row\" useFlexGap role=\"group\" aria-labelledby=\"ai-insight-window-label\" sx={{ flexWrap: 'wrap', gap: 1 }}>\n {AI_INSIGHT_WINDOWS.map((window) => (\n <Chip\n key={window}\n label={`${window} days`}\n color={days === window ? 'primary' : 'default'}\n variant={days === window ? 'filled' : 'outlined'}\n onClick={() => setDays(window)}\n aria-pressed={days === window}\n />\n ))}\n </Stack>\n </Stack>\n ) : null}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The answer is read from your own figures, and every number in it links to where it comes from. '}\n {'Nothing is changed or sent.'}\n </Typography>\n </>\n ) : null}\n {phase === 'working' ? (\n <Stack direction=\"row\" spacing={1.5} sx={{ alignItems: 'center' }} role=\"status\">\n <CircularProgress size={18} />\n <Typography variant=\"body2\">{'Reading your figures. This usually takes a minute.'}</Typography>\n </Stack>\n ) : null}\n {phase === 'answer' && answer ? (\n <Stack spacing={1.5}>\n <Typography variant=\"subtitle2\">{answer.question}</Typography>\n {answer.insights.length ? (\n <Stack component=\"ol\" spacing={1.5} sx={{ pl: 2.5, m: 0 }}>\n {answer.insights.map((insight, index) => (\n <Box component=\"li\" key={index}>\n <Typography variant=\"body2\">{insight.text}</Typography>\n <Button\n size=\"small\"\n onClick={() => setOpenCitations((current) => (current === index ? null : index))}\n aria-expanded={openCitations === index}\n >\n {openCitations === index ? 'Hide the figures' : 'Show the figures'}\n </Button>\n {openCitations === index ? (\n <Stack spacing={1} sx={{ pl: 1, borderLeft: 2, borderColor: 'divider' }}>\n {insight.cites.map((cite) => {\n const table = tableByRef(cite.table)\n if (!table) return null\n const href = aiInsightSourceHref(table, { orgSlug, host })\n return (\n <Box key={cite.table}>\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {table.period\n ? `${table.title} · ${table.period.from} to ${table.period.to}`\n : table.title}\n {href ? (\n <>\n {' · '}\n <AppLink componentVariant=\"naked\" href={href}>\n {`Open ${table.source.label}`}\n </AppLink>\n </>\n ) : null}\n </Typography>\n {cite.rows.map((row) => (\n <Typography key={row} variant=\"caption\" component=\"div\">\n {aiInsightRowText(table, row)}\n </Typography>\n ))}\n </Box>\n )\n })}\n </Stack>\n ) : null}\n </Box>\n ))}\n </Stack>\n ) : (\n <Alert severity=\"info\">{'The figures read here did not answer this question.'}</Alert>\n )}\n {answer.gap ? <Alert severity=\"info\">{answer.gap}</Alert> : null}\n {answer.left > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${answer.left} ${answer.left === 1 ? 'insight was' : 'insights were'} left out because ${\n answer.left === 1 ? 'its numbers' : 'their numbers'\n } could not be traced to the figures.`}\n </Typography>\n ) : null}\n </Stack>\n ) : null}\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n {surface !== 'datasets' && subscribed !== null ? (\n <FormControlLabel\n control={<Switch checked={subscribed} onChange={() => void toggleDigest()} />}\n label=\"Send me weekly insights for this workspace every Monday\"\n />\n ) : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n {phase === 'answer' && !jobId ? (\n <Button\n onClick={() => {\n setAnswer(null)\n setQuestion('')\n setPhase('ask')\n }}\n >\n {'Ask another'}\n </Button>\n ) : null}\n <Button onClick={onClose}>{'Close'}</Button>\n {phase === 'ask' ? (\n <Button variant=\"contained\" onClick={() => void ask()} disabled={!question.trim()}>\n {'Ask'}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\nexport default AiInsightDialog\n"],"names":["lockdownRefusalText","parseLockdownRefusal","INSIGHT_DIGESTS_FIELD","insightDigestSubscribed","AppLink","authorizedFetch","useFirestore","Alert","Box","Button","Chip","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","Stack","Switch","TextField","Typography","doc","getDoc","setDoc","useCallback","useEffect","useRef","useState","AI_INSIGHT_DEFAULT_DAYS","AI_INSIGHT_QUESTION_MAX_CHARS","AI_INSIGHT_WINDOWS","aiInsightRowText","aiInsightSourceHref","AI_JOB_TERMINAL_STATUSES","readEventFrames","PLACEHOLDERS","analytics","datasets","AiInsightDialog","props","open","onClose","orgId","orgSlug","hostId","host","surface","jobId","userRef","user","current","uid","firestore","phase","setPhase","question","setQuestion","days","setDays","notice","setNotice","answer","setAnswer","openCitations","setOpenCitations","subscribed","setSubscribed","watchRef","readAnswer","id","response","encodeURIComponent","payload","json","catch","ok","String","error","follow","job","settle","settled","status","includes","controller","AbortController","abort","again","signal","aborted","body","event","next","active","then","snapshot","get","undefined","toggleDigest","merge","ask","text","trim","method","headers","JSON","stringify","kind","brief","inputs","locked","tableByRef","ref","tables","find","table","fullWidth","maxWidth","aria-labelledby","spacing","sx","pt","label","placeholder","multiline","minRows","value","onChange","target","slice","autoFocus","variant","color","direction","useFlexGap","role","flexWrap","gap","map","window","onClick","aria-pressed","alignItems","size","insights","length","component","pl","m","insight","index","aria-expanded","borderLeft","borderColor","cites","cite","href","period","title","from","to","componentVariant","source","rows","row","severity","left","control","checked","disabled"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-insight-dialog.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { INSIGHT_DIGESTS_FIELD, insightDigestSubscribed } from '@aglyn/aglyn/app-utils/notifications'\nimport { AppLink } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n Chip,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { doc, getDoc, setDoc } from 'firebase/firestore'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n AI_INSIGHT_DEFAULT_DAYS,\n AI_INSIGHT_QUESTION_MAX_CHARS,\n AI_INSIGHT_WINDOWS,\n aiInsightRowText,\n aiInsightSourceHref,\n type AiInsightAnswerWire,\n type AiInsightSurface,\n} from '../model/ai-insight'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport { readEventFrames } from './ai-job-events'\nimport { openAiJobs, publishAiJob } from './ai-jobs-store'\n\n/**\n * \"Ask about your numbers\" (AGL-2915): the Assist panel's question box on a\n * site's Analytics, Data and CRM Reports pages and the workspace's Data page,\n * and the answer an `insight` job wrote.\n *\n * A question starts a job, which reads the figures and answers on the jobs\n * beat; the dialog watches the job and then reads the answer through\n * `/api/ai/insights/{jobId}`, the only door to it. Every insight shows the\n * rows it was traced to and links the console page they come from, so a\n * person can check a number before acting on it. Nothing here writes anything\n * but the person's own weekly-insights switch.\n */\n\n/** Placeholder questions by surface, in the words a person would use. */\nconst PLACEHOLDERS: Record<Exclude<AiInsightSurface, 'digest'>, string> = {\n analytics: 'Which pages brought in the most visitors, and did anything change?',\n datasets: 'What is the average order total by state?',\n 'crm-reports': 'Which campaign or form brought in the most people this month?',\n}\n\nexport interface AiInsightDialogProps {\n open: boolean\n onClose: () => void\n orgId: string\n /** The org's slug, for the links to the pages a table's figures come from. */\n orgSlug: string\n /** The site the question is about; `null` on the workspace's Data page. */\n hostId: string | null\n /** The site's subdomain, which a console link names a site by. */\n host: string | null\n surface: Exclude<AiInsightSurface, 'digest'>\n user: Parameters<typeof authorizedFetch>[0]\n /** The signed-in person, whose weekly-insights switch the dialog shows. */\n uid: string | null\n /** An answered job to show rather than a question to ask. */\n jobId?: string | null\n}\n\ntype Phase = 'ask' | 'working' | 'answer'\n\nexport function AiInsightDialog(props: AiInsightDialogProps) {\n const { open, onClose, orgId, orgSlug, hostId, host, surface, jobId } = props\n // Held in a ref: a request reads who is signed in, and nothing keys on the\n // identity of the object that says so.\n const userRef = useRef(props.user)\n userRef.current = props.user\n const uid = props.uid\n const firestore = useFirestore()\n const [phase, setPhase] = useState<Phase>('ask')\n const [question, setQuestion] = useState('')\n const [days, setDays] = useState(AI_INSIGHT_DEFAULT_DAYS)\n const [notice, setNotice] = useState<string | null>(null)\n const [answer, setAnswer] = useState<AiInsightAnswerWire | null>(null)\n const [openCitations, setOpenCitations] = useState<number | null>(null)\n const [subscribed, setSubscribed] = useState<boolean | null>(null)\n /**\n * The job whose answer was still being written when the dialog stopped\n * waiting (AGL-3593): its notice offers to open AI jobs on it.\n */\n const [stillWriting, setStillWriting] = useState<string | null>(null)\n const watchRef = useRef<AbortController | null>(null)\n\n const readAnswer = useCallback(\n async (id: string) => {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/insights/${encodeURIComponent(id)}?orgId=${encodeURIComponent(orgId)}`,\n )\n const payload = await response.json().catch(() => null)\n if (!response.ok || !payload?.answer) {\n setNotice(String(payload?.error ?? 'The answer could not be loaded. Try again.'))\n setPhase('ask')\n return\n }\n setAnswer(payload.answer as AiInsightAnswerWire)\n setPhase('answer')\n },\n [orgId],\n )\n\n /** Follows a job until it settles, then reads its answer or says why there is none. */\n const follow = useCallback(\n async (job: AiJobSummary) => {\n const settle = async (settled: AiJobSummary) => {\n if (settled.status === 'done') await readAnswer(settled.id)\n else {\n setNotice(settled.error ?? 'The question could not be answered. Try again.')\n setPhase('ask')\n }\n }\n if (AI_JOB_TERMINAL_STATUSES.includes(job.status)) return settle(job)\n setPhase('working')\n const controller = new AbortController()\n watchRef.current?.abort()\n watchRef.current = controller\n let again = true\n while (again && !controller.signal.aborted) {\n again = false\n let settled: AiJobSummary | null = null\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs/${encodeURIComponent(job.id)}/events?orgId=${encodeURIComponent(orgId)}`,\n { signal: controller.signal },\n )\n if (!response.ok || !response.body) break\n await readEventFrames(response.body, (event) => {\n if (event['type'] === 'reconnect') again = true\n if (event['type'] !== 'state') return\n const next = event['job'] as AiJobSummary\n publishAiJob(next)\n if (next.status === 'needs_input') {\n setNotice(next.error ?? 'This workspace cannot run AI jobs right now.')\n }\n if (AI_JOB_TERMINAL_STATUSES.includes(next.status)) settled = next\n })\n } catch {\n if (controller.signal.aborted) return\n break\n }\n if (settled) return settle(settled)\n }\n if (!controller.signal.aborted) {\n setNotice('The answer is still being written. Find it in AI jobs in a minute.')\n setStillWriting(job.id)\n setPhase('ask')\n }\n },\n [orgId, readAnswer],\n )\n\n useEffect(() => {\n if (!open) {\n watchRef.current?.abort()\n return\n }\n setNotice(null)\n setStillWriting(null)\n setOpenCitations(null)\n if (jobId) {\n setPhase('working')\n void readAnswer(jobId)\n } else {\n setAnswer(null)\n setPhase('ask')\n }\n }, [open, jobId, readAnswer])\n\n // The person's own weekly-insights switch, read when the dialog opens.\n useEffect(() => {\n if (!open || !uid || !orgId) return\n let active = true\n void getDoc(doc(firestore, 'users', uid))\n .then((snapshot) => {\n if (active) {\n setSubscribed(\n insightDigestSubscribed(snapshot.get(INSIGHT_DIGESTS_FIELD) as Record<string, boolean>, orgId),\n )\n }\n })\n .catch(() => undefined)\n return () => {\n active = false\n }\n }, [open, uid, orgId, firestore])\n\n const toggleDigest = useCallback(async () => {\n if (!uid || subscribed === null) return\n const next = !subscribed\n setSubscribed(next)\n try {\n await setDoc(doc(firestore, 'users', uid), { [INSIGHT_DIGESTS_FIELD]: { [orgId]: next } }, { merge: true })\n } catch {\n setSubscribed(!next)\n setNotice('Your weekly insights setting could not be saved. Try again.')\n }\n }, [uid, subscribed, firestore, orgId])\n\n const ask = useCallback(async () => {\n const text = question.trim()\n if (!text) return\n setNotice(null)\n setStillWriting(null)\n setPhase('working')\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: 'insight',\n brief: text,\n inputs: { surface, days },\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok || !payload?.job) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(locked ? lockdownRefusalText(locked) : String(payload?.error ?? 'The question could not be asked. Try again.'))\n setPhase('ask')\n return\n }\n publishAiJob(payload.job as AiJobSummary)\n await follow(payload.job as AiJobSummary)\n } catch {\n setNotice('The question could not be asked. Try again.')\n setPhase('ask')\n }\n }, [question, orgId, hostId, surface, days, follow])\n\n const tableByRef = (ref: string) => answer?.tables.find((table) => table.ref === ref) ?? null\n\n return (\n <Dialog open={open} onClose={onClose} fullWidth maxWidth=\"sm\" aria-labelledby=\"ai-insight-title\">\n <DialogTitle id=\"ai-insight-title\">{'Ask about your numbers'}</DialogTitle>\n <DialogContent>\n <Stack spacing={2} sx={{ pt: 1 }}>\n {phase === 'ask' ? (\n <>\n <TextField\n label=\"What do you want to know?\"\n placeholder={PLACEHOLDERS[surface]}\n multiline\n minRows={2}\n value={question}\n onChange={(event) => setQuestion(event.target.value.slice(0, AI_INSIGHT_QUESTION_MAX_CHARS))}\n autoFocus\n />\n {surface !== 'datasets' ? (\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\" id=\"ai-insight-window-label\">\n {'Over the last'}\n </Typography>\n <Stack direction=\"row\" useFlexGap role=\"group\" aria-labelledby=\"ai-insight-window-label\" sx={{ flexWrap: 'wrap', gap: 1 }}>\n {AI_INSIGHT_WINDOWS.map((window) => (\n <Chip\n key={window}\n label={`${window} days`}\n color={days === window ? 'primary' : 'default'}\n variant={days === window ? 'filled' : 'outlined'}\n onClick={() => setDays(window)}\n aria-pressed={days === window}\n />\n ))}\n </Stack>\n </Stack>\n ) : null}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The answer is read from your own figures, and every number in it links to where it comes from. '}\n {'Nothing is changed or sent.'}\n </Typography>\n </>\n ) : null}\n {phase === 'working' ? (\n <Stack direction=\"row\" spacing={1.5} sx={{ alignItems: 'center' }} role=\"status\">\n <CircularProgress size={18} />\n <Typography variant=\"body2\">{'Reading your figures. This usually takes a minute.'}</Typography>\n </Stack>\n ) : null}\n {phase === 'answer' && answer ? (\n <Stack spacing={1.5}>\n <Typography variant=\"subtitle2\">{answer.question}</Typography>\n {answer.insights.length ? (\n <Stack component=\"ol\" spacing={1.5} sx={{ pl: 2.5, m: 0 }}>\n {answer.insights.map((insight, index) => (\n <Box component=\"li\" key={index}>\n <Typography variant=\"body2\">{insight.text}</Typography>\n <Button\n size=\"small\"\n onClick={() => setOpenCitations((current) => (current === index ? null : index))}\n aria-expanded={openCitations === index}\n >\n {openCitations === index ? 'Hide the figures' : 'Show the figures'}\n </Button>\n {openCitations === index ? (\n <Stack spacing={1} sx={{ pl: 1, borderLeft: 2, borderColor: 'divider' }}>\n {insight.cites.map((cite) => {\n const table = tableByRef(cite.table)\n if (!table) return null\n const href = aiInsightSourceHref(table, { orgSlug, host })\n return (\n <Box key={cite.table}>\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {table.period\n ? `${table.title} · ${table.period.from} to ${table.period.to}`\n : table.title}\n {href ? (\n <>\n {' · '}\n <AppLink componentVariant=\"naked\" href={href}>\n {`Open ${table.source.label}`}\n </AppLink>\n </>\n ) : null}\n </Typography>\n {cite.rows.map((row) => (\n <Typography key={row} variant=\"caption\" component=\"div\">\n {aiInsightRowText(table, row)}\n </Typography>\n ))}\n </Box>\n )\n })}\n </Stack>\n ) : null}\n </Box>\n ))}\n </Stack>\n ) : (\n <Alert severity=\"info\">{'The figures read here did not answer this question.'}</Alert>\n )}\n {answer.gap ? <Alert severity=\"info\">{answer.gap}</Alert> : null}\n {answer.left > 0 ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${answer.left} ${answer.left === 1 ? 'insight was' : 'insights were'} left out because ${\n answer.left === 1 ? 'its numbers' : 'their numbers'\n } could not be traced to the figures.`}\n </Typography>\n ) : null}\n </Stack>\n ) : null}\n {notice ? (\n <Alert\n severity=\"warning\"\n action={\n stillWriting ? (\n <Button\n color=\"inherit\"\n size=\"small\"\n variant=\"outlined\"\n onClick={() => {\n openAiJobs({ jobId: stillWriting })\n onClose()\n }}\n >\n {'Open AI jobs'}\n </Button>\n ) : undefined\n }\n >\n {notice}\n </Alert>\n ) : null}\n {surface !== 'datasets' && subscribed !== null ? (\n <FormControlLabel\n control={<Switch checked={subscribed} onChange={() => void toggleDigest()} />}\n label=\"Send me weekly insights for this workspace every Monday\"\n />\n ) : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n {phase === 'answer' && !jobId ? (\n <Button\n onClick={() => {\n setAnswer(null)\n setQuestion('')\n setPhase('ask')\n }}\n >\n {'Ask another'}\n </Button>\n ) : null}\n <Button onClick={onClose}>{'Close'}</Button>\n {phase === 'ask' ? (\n <Button variant=\"contained\" onClick={() => void ask()} disabled={!question.trim()}>\n {'Ask'}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\nexport default AiInsightDialog\n"],"names":["lockdownRefusalText","parseLockdownRefusal","INSIGHT_DIGESTS_FIELD","insightDigestSubscribed","AppLink","authorizedFetch","useFirestore","Alert","Box","Button","Chip","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","Stack","Switch","TextField","Typography","doc","getDoc","setDoc","useCallback","useEffect","useRef","useState","AI_INSIGHT_DEFAULT_DAYS","AI_INSIGHT_QUESTION_MAX_CHARS","AI_INSIGHT_WINDOWS","aiInsightRowText","aiInsightSourceHref","AI_JOB_TERMINAL_STATUSES","readEventFrames","openAiJobs","publishAiJob","PLACEHOLDERS","analytics","datasets","AiInsightDialog","props","open","onClose","orgId","orgSlug","hostId","host","surface","jobId","userRef","user","current","uid","firestore","phase","setPhase","question","setQuestion","days","setDays","notice","setNotice","answer","setAnswer","openCitations","setOpenCitations","subscribed","setSubscribed","stillWriting","setStillWriting","watchRef","readAnswer","id","response","encodeURIComponent","payload","json","catch","ok","String","error","follow","job","settle","settled","status","includes","controller","AbortController","abort","again","signal","aborted","body","event","next","active","then","snapshot","get","undefined","toggleDigest","merge","ask","text","trim","method","headers","JSON","stringify","kind","brief","inputs","locked","tableByRef","ref","tables","find","table","fullWidth","maxWidth","aria-labelledby","spacing","sx","pt","label","placeholder","multiline","minRows","value","onChange","target","slice","autoFocus","variant","color","direction","useFlexGap","role","flexWrap","gap","map","window","onClick","aria-pressed","alignItems","size","insights","length","component","pl","m","insight","index","aria-expanded","borderLeft","borderColor","cites","cite","href","period","title","from","to","componentVariant","source","rows","row","severity","left","action","control","checked","disabled"],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token';
2
+ import type { AiJobSummary } from '../model/ai-jobs.types';
3
+ /**
4
+ * `data:` frames out of an SSE body, one parsed event per frame. Shared with
5
+ * every panel that watches a job through the events route.
6
+ */
7
+ export declare function readEventFrames(body: ReadableStream<Uint8Array>, onEvent: (event: Record<string, unknown>) => void): Promise<void>;
8
+ /**
9
+ * How a follow ended (AGL-3596): the stream ran its course (`ok`), the route
10
+ * answered that the job is not this reader's to see or does not exist
11
+ * (`not-found`), or the route could not be reached or failed (`error`).
12
+ */
13
+ export type AiJobFollowEnd = 'ok' | 'not-found' | 'error';
14
+ /**
15
+ * Follows one job through the events route until it settles, the signal
16
+ * aborts or the route stops answering, handing each state to `onJob` and to
17
+ * every surface counting the workspace's jobs (AGL-3593). The server closes a
18
+ * stream at its deadline with `reconnect`; this re-opens it.
19
+ */
20
+ export declare function followAiJobEvents(user: () => MaybeTokenSource, orgId: string, jobId: string, signal: AbortSignal, onJob: (job: AiJobSummary) => void): Promise<AiJobFollowEnd>;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
18
+ import { publishAiJob } from "./ai-jobs-store.js";
19
+ // Reading a job's events stream (AGL-2904), shared by the AI jobs drawer and
20
+ // every dialog that follows the job it started (AGL-3593).
21
+ /**
22
+ * `data:` frames out of an SSE body, one parsed event per frame. Shared with
23
+ * every panel that watches a job through the events route.
24
+ */ export async function readEventFrames(body, onEvent) {
25
+ const reader = body.getReader();
26
+ const decoder = new TextDecoder();
27
+ let buffer = '';
28
+ for(;;){
29
+ var _frames_pop;
30
+ const { done, value } = await reader.read();
31
+ if (done) break;
32
+ buffer += decoder.decode(value, {
33
+ stream: true
34
+ });
35
+ const frames = buffer.split('\n\n');
36
+ buffer = (_frames_pop = frames.pop()) != null ? _frames_pop : '';
37
+ for (const frame of frames){
38
+ const line = frame.split('\n').find((part)=>part.startsWith('data: '));
39
+ if (!line) continue;
40
+ try {
41
+ onEvent(JSON.parse(line.slice('data: '.length)));
42
+ } catch (unused) {
43
+ // A torn frame is dropped; the next state event carries the whole job.
44
+ }
45
+ }
46
+ }
47
+ }
48
+ /**
49
+ * Follows one job through the events route until it settles, the signal
50
+ * aborts or the route stops answering, handing each state to `onJob` and to
51
+ * every surface counting the workspace's jobs (AGL-3593). The server closes a
52
+ * stream at its deadline with `reconnect`; this re-opens it.
53
+ */ export async function followAiJobEvents(user, orgId, jobId, signal, onJob) {
54
+ let again = true;
55
+ while(again && !signal.aborted){
56
+ again = false;
57
+ try {
58
+ const response = await authorizedFetch(user(), `/api/ai/jobs/${encodeURIComponent(jobId)}/events?orgId=${encodeURIComponent(orgId)}`, {
59
+ signal
60
+ });
61
+ if (response.status === 404 || response.status === 403) return 'not-found';
62
+ if (!response.ok || !response.body) return 'error';
63
+ await readEventFrames(response.body, (event)=>{
64
+ if (signal.aborted) return;
65
+ if (event['type'] === 'state') {
66
+ const next = event['job'];
67
+ publishAiJob(next);
68
+ onJob(next);
69
+ } else if (event['type'] === 'reconnect') again = true;
70
+ });
71
+ } catch (unused) {
72
+ return 'error';
73
+ }
74
+ }
75
+ return 'ok';
76
+ }
77
+
78
+ //# sourceMappingURL=ai-job-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-events.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport type { MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { publishAiJob } from './ai-jobs-store'\n\n// Reading a job's events stream (AGL-2904), shared by the AI jobs drawer and\n// every dialog that follows the job it started (AGL-3593).\n\n/**\n * `data:` frames out of an SSE body, one parsed event per frame. Shared with\n * every panel that watches a job through the events route.\n */\nexport async function readEventFrames(\n body: ReadableStream<Uint8Array>,\n onEvent: (event: Record<string, unknown>) => void,\n): Promise<void> {\n const reader = body.getReader()\n const decoder = new TextDecoder()\n let buffer = ''\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n const frames = buffer.split('\\n\\n')\n buffer = frames.pop() ?? ''\n for (const frame of frames) {\n const line = frame.split('\\n').find((part) => part.startsWith('data: '))\n if (!line) continue\n try {\n onEvent(JSON.parse(line.slice('data: '.length)))\n } catch {\n // A torn frame is dropped; the next state event carries the whole job.\n }\n }\n }\n}\n\n/**\n * How a follow ended (AGL-3596): the stream ran its course (`ok`), the route\n * answered that the job is not this reader's to see or does not exist\n * (`not-found`), or the route could not be reached or failed (`error`).\n */\nexport type AiJobFollowEnd = 'ok' | 'not-found' | 'error'\n\n/**\n * Follows one job through the events route until it settles, the signal\n * aborts or the route stops answering, handing each state to `onJob` and to\n * every surface counting the workspace's jobs (AGL-3593). The server closes a\n * stream at its deadline with `reconnect`; this re-opens it.\n */\nexport async function followAiJobEvents(\n user: () => MaybeTokenSource,\n orgId: string,\n jobId: string,\n signal: AbortSignal,\n onJob: (job: AiJobSummary) => void,\n): Promise<AiJobFollowEnd> {\n let again = true\n while (again && !signal.aborted) {\n again = false\n try {\n const response = await authorizedFetch(\n user(),\n `/api/ai/jobs/${encodeURIComponent(jobId)}/events?orgId=${encodeURIComponent(orgId)}`,\n { signal },\n )\n if (response.status === 404 || response.status === 403) return 'not-found'\n if (!response.ok || !response.body) return 'error'\n await readEventFrames(response.body, (event) => {\n if (signal.aborted) return\n if (event['type'] === 'state') {\n const next = event['job'] as AiJobSummary\n publishAiJob(next)\n onJob(next)\n } else if (event['type'] === 'reconnect') again = true\n })\n } catch {\n return 'error'\n }\n }\n return 'ok'\n}\n"],"names":["authorizedFetch","publishAiJob","readEventFrames","body","onEvent","reader","getReader","decoder","TextDecoder","buffer","frames","done","value","read","decode","stream","split","pop","frame","line","find","part","startsWith","JSON","parse","slice","length","followAiJobEvents","user","orgId","jobId","signal","onJob","again","aborted","response","encodeURIComponent","status","ok","event","next"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;AAGA,SAASA,eAAe,QAAQ,2CAA0C;AAE1E,SAASC,YAAY,QAAQ,qBAAiB;AAE9C,6EAA6E;AAC7E,2DAA2D;AAE3D;;;CAGC,GACD,OAAO,eAAeC,gBACpBC,IAAgC,EAChCC,OAAiD;IAEjD,MAAMC,SAASF,KAAKG,SAAS;IAC7B,MAAMC,UAAU,IAAIC;IACpB,IAAIC,SAAS;IACb,OAAS;YAKEC;QAJT,MAAM,EAAEC,IAAI,EAAEC,KAAK,EAAE,GAAG,MAAMP,OAAOQ,IAAI;QACzC,IAAIF,MAAM;QACVF,UAAUF,QAAQO,MAAM,CAACF,OAAO;YAAEG,QAAQ;QAAK;QAC/C,MAAML,SAASD,OAAOO,KAAK,CAAC;QAC5BP,UAASC,cAAAA,OAAOO,GAAG,cAAVP,cAAgB;QACzB,KAAK,MAAMQ,SAASR,OAAQ;YAC1B,MAAMS,OAAOD,MAAMF,KAAK,CAAC,MAAMI,IAAI,CAAC,CAACC,OAASA,KAAKC,UAAU,CAAC;YAC9D,IAAI,CAACH,MAAM;YACX,IAAI;gBACFf,QAAQmB,KAAKC,KAAK,CAACL,KAAKM,KAAK,CAAC,SAASC,MAAM;YAC/C,EAAE,eAAM;YACN,uEAAuE;YACzE;QACF;IACF;AACF;AASA;;;;;CAKC,GACD,OAAO,eAAeC,kBACpBC,IAA4B,EAC5BC,KAAa,EACbC,KAAa,EACbC,MAAmB,EACnBC,KAAkC;IAElC,IAAIC,QAAQ;IACZ,MAAOA,SAAS,CAACF,OAAOG,OAAO,CAAE;QAC/BD,QAAQ;QACR,IAAI;YACF,MAAME,WAAW,MAAMnC,gBACrB4B,QACA,CAAC,aAAa,EAAEQ,mBAAmBN,OAAO,cAAc,EAAEM,mBAAmBP,QAAQ,EACrF;gBAAEE;YAAO;YAEX,IAAII,SAASE,MAAM,KAAK,OAAOF,SAASE,MAAM,KAAK,KAAK,OAAO;YAC/D,IAAI,CAACF,SAASG,EAAE,IAAI,CAACH,SAAShC,IAAI,EAAE,OAAO;YAC3C,MAAMD,gBAAgBiC,SAAShC,IAAI,EAAE,CAACoC;gBACpC,IAAIR,OAAOG,OAAO,EAAE;gBACpB,IAAIK,KAAK,CAAC,OAAO,KAAK,SAAS;oBAC7B,MAAMC,OAAOD,KAAK,CAAC,MAAM;oBACzBtC,aAAauC;oBACbR,MAAMQ;gBACR,OAAO,IAAID,KAAK,CAAC,OAAO,KAAK,aAAaN,QAAQ;YACpD;QACF,EAAE,eAAM;YACN,OAAO;QACT;IACF;IACA,OAAO;AACT"}