@aglyn/plugins-ai 1.0.0-beta.219 → 1.0.0-beta.221

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 (181) hide show
  1. package/package.json +18 -18
  2. package/src/lib/billing/ai-month-meter.d.ts +51 -0
  3. package/src/lib/billing/ai-month-meter.js +89 -0
  4. package/src/lib/billing/ai-month-meter.js.map +1 -0
  5. package/src/lib/billing/ai-overage-close.d.ts +11 -33
  6. package/src/lib/billing/ai-overage-close.js +4 -3
  7. package/src/lib/billing/ai-overage-close.js.map +1 -1
  8. package/src/lib/billing/ai-overage-cutover.d.ts +28 -12
  9. package/src/lib/billing/ai-overage-cutover.js +1 -28
  10. package/src/lib/billing/ai-overage-cutover.js.map +1 -1
  11. package/src/lib/billing/ai-overage-ledger.js +1 -1
  12. package/src/lib/billing/ai-overage-ledger.js.map +1 -1
  13. package/src/lib/billing/ai-overage-standing.js +1 -1
  14. package/src/lib/billing/ai-overage-standing.js.map +1 -1
  15. package/src/lib/components/ai-allotment-editor.component.js +1 -1
  16. package/src/lib/components/ai-allotment-editor.component.js.map +1 -1
  17. package/src/lib/components/ai-assist-provider.component.js +3 -2
  18. package/src/lib/components/ai-assist-provider.component.js.map +1 -1
  19. package/src/lib/components/ai-automation-zones.d.ts +63 -0
  20. package/src/lib/components/ai-automation-zones.js +31 -0
  21. package/src/lib/components/ai-automation-zones.js.map +1 -0
  22. package/src/lib/components/ai-describe-automation.component.d.ts +1 -1
  23. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  24. package/src/lib/components/ai-describe-button.component.d.ts +6 -3
  25. package/src/lib/components/ai-describe-button.component.js +4 -1
  26. package/src/lib/components/ai-describe-button.component.js.map +1 -1
  27. package/src/lib/components/ai-explain-automation.component.d.ts +1 -1
  28. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  29. package/src/lib/components/billing-ai-allotments.component.js +1 -1
  30. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  31. package/src/lib/components/billing-assist-overage-card.component.js +1 -1
  32. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  33. package/src/lib/constants.d.ts +5 -0
  34. package/src/lib/constants.js +4 -0
  35. package/src/lib/constants.js.map +1 -1
  36. package/src/lib/declarations.js +2 -0
  37. package/src/lib/declarations.js.map +1 -1
  38. package/src/lib/declarations.server.js +24 -10
  39. package/src/lib/declarations.server.js.map +1 -1
  40. package/src/lib/insights/ai-dataset-catalog.d.ts +49 -0
  41. package/src/lib/insights/ai-dataset-catalog.js +57 -0
  42. package/src/lib/insights/ai-dataset-catalog.js.map +1 -0
  43. package/src/lib/insights/ai-figure-readers.d.ts +6 -39
  44. package/src/lib/insights/ai-figure-readers.js +2 -391
  45. package/src/lib/insights/ai-figure-readers.js.map +1 -1
  46. package/src/lib/insights/ai-insight-digest.d.ts +10 -1
  47. package/src/lib/insights/ai-insight-digest.js +16 -4
  48. package/src/lib/insights/ai-insight-digest.js.map +1 -1
  49. package/src/lib/insights/ai-insight-readers.d.ts +1 -1
  50. package/src/lib/insights/ai-insight-readers.js.map +1 -1
  51. package/src/lib/jobs/ai-job-component-step.d.ts +6 -1
  52. package/src/lib/jobs/ai-job-component-step.js +23 -6
  53. package/src/lib/jobs/ai-job-component-step.js.map +1 -1
  54. package/src/lib/jobs/ai-job-draft-ids.js +11 -0
  55. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  56. package/src/lib/jobs/ai-job-email-step.d.ts +5 -1
  57. package/src/lib/jobs/ai-job-email-step.js +10 -1
  58. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  59. package/src/lib/jobs/ai-job-form-step.d.ts +17 -1
  60. package/src/lib/jobs/ai-job-form-step.js +60 -8
  61. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  62. package/src/lib/jobs/ai-job-generation.d.ts +21 -0
  63. package/src/lib/jobs/ai-job-generation.js +53 -1
  64. package/src/lib/jobs/ai-job-generation.js.map +1 -1
  65. package/src/lib/jobs/ai-job-insight-step.js +1 -1
  66. package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
  67. package/src/lib/jobs/ai-job-layout-step.d.ts +15 -4
  68. package/src/lib/jobs/ai-job-layout-step.js +45 -12
  69. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  70. package/src/lib/jobs/ai-job-plan-conformance.d.ts +5 -0
  71. package/src/lib/jobs/ai-job-plan-conformance.js +7 -1
  72. package/src/lib/jobs/ai-job-plan-conformance.js.map +1 -1
  73. package/src/lib/jobs/ai-job-plan-creations.d.ts +80 -0
  74. package/src/lib/jobs/ai-job-plan-creations.js +199 -0
  75. package/src/lib/jobs/ai-job-plan-creations.js.map +1 -0
  76. package/src/lib/jobs/ai-job-plan-step.d.ts +8 -2
  77. package/src/lib/jobs/ai-job-plan-step.js +20 -6
  78. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  79. package/src/lib/jobs/ai-job-site-step.d.ts +2 -0
  80. package/src/lib/jobs/ai-job-site-step.js +6 -6
  81. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  82. package/src/lib/jobs/ai-job-template-step.d.ts +9 -6
  83. package/src/lib/jobs/ai-job-template-step.js +72 -15
  84. package/src/lib/jobs/ai-job-template-step.js.map +1 -1
  85. package/src/lib/jobs/ai-jobs.js +1 -1
  86. package/src/lib/jobs/ai-jobs.js.map +1 -1
  87. package/src/lib/jobs/ai-workflow-records.d.ts +5 -4
  88. package/src/lib/jobs/ai-workflow-records.js +26 -22
  89. package/src/lib/jobs/ai-workflow-records.js.map +1 -1
  90. package/src/lib/model/ai-allotments.d.ts +16 -3
  91. package/src/lib/model/ai-allotments.js +34 -11
  92. package/src/lib/model/ai-allotments.js.map +1 -1
  93. package/src/lib/model/ai-job-creations.d.ts +55 -0
  94. package/src/lib/model/ai-job-creations.js +85 -0
  95. package/src/lib/model/ai-job-creations.js.map +1 -0
  96. package/src/lib/model/ai-page-job.d.ts +3 -2
  97. package/src/lib/model/ai-page-job.js +4 -3
  98. package/src/lib/model/ai-page-job.js.map +1 -1
  99. package/src/lib/model/ai-site-job.d.ts +4 -2
  100. package/src/lib/model/ai-site-job.js +8 -4
  101. package/src/lib/model/ai-site-job.js.map +1 -1
  102. package/src/lib/model/ai-template-subjects.d.ts +10 -0
  103. package/src/lib/model/ai-template-subjects.js +14 -0
  104. package/src/lib/model/ai-template-subjects.js.map +1 -1
  105. package/src/lib/model/ai-tokens.js +1 -1
  106. package/src/lib/model/ai-tokens.js.map +1 -1
  107. package/src/lib/model/ai-usage-by-user.js +1 -1
  108. package/src/lib/model/ai-usage-by-user.js.map +1 -1
  109. package/src/lib/providers/catalog.js +1 -1
  110. package/src/lib/providers/catalog.js.map +1 -1
  111. package/src/lib/providers/model-choice.js +1 -1
  112. package/src/lib/providers/model-choice.js.map +1 -1
  113. package/src/lib/runtime/ai-device-audit.d.ts +1 -1
  114. package/src/lib/runtime/ai-device-audit.js +1 -1
  115. package/src/lib/runtime/ai-device-audit.js.map +1 -1
  116. package/src/lib/runtime/ai-eval-live.js +1 -1
  117. package/src/lib/runtime/ai-eval-live.js.map +1 -1
  118. package/src/lib/runtime/ai-eval.d.ts +1 -1
  119. package/src/lib/runtime/ai-eval.js.map +1 -1
  120. package/src/lib/runtime/ai-gate.js +1 -1
  121. package/src/lib/runtime/ai-gate.js.map +1 -1
  122. package/src/lib/runtime/ai-palette.d.ts +1 -1
  123. package/src/lib/runtime/ai-palette.generated.d.ts +1 -1
  124. package/src/lib/runtime/ai-palette.generated.js +1 -1
  125. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  126. package/src/lib/runtime/ai-palette.js.map +1 -1
  127. package/src/lib/runtime/site-inventory.js +28 -22
  128. package/src/lib/runtime/site-inventory.js.map +1 -1
  129. package/src/lib/server/ai-admin-org.js +1 -1
  130. package/src/lib/server/ai-admin-org.js.map +1 -1
  131. package/src/lib/server/ai-admin-orgs-spend.js +1 -1
  132. package/src/lib/server/ai-admin-orgs-spend.js.map +1 -1
  133. package/src/lib/server/ai-admin-signals.js +1 -1
  134. package/src/lib/server/ai-admin-signals.js.map +1 -1
  135. package/src/lib/server/ai-allotments.js +1 -1
  136. package/src/lib/server/ai-allotments.js.map +1 -1
  137. package/src/lib/server/ai-assist.js +2 -2
  138. package/src/lib/server/ai-assist.js.map +1 -1
  139. package/src/lib/server/assist-chat.d.ts +1 -1
  140. package/src/lib/server/assist-chat.js +2 -2
  141. package/src/lib/server/assist-chat.js.map +1 -1
  142. package/src/lib/server/billing-credits.d.ts +5 -0
  143. package/src/lib/server/billing-credits.js +8 -2
  144. package/src/lib/server/billing-credits.js.map +1 -1
  145. package/src/lib/server/billing-overage.js +2 -2
  146. package/src/lib/server/billing-overage.js.map +1 -1
  147. package/src/lib/testing/stand-in-dataset-index.d.ts +20 -0
  148. package/src/lib/testing/stand-in-dataset-index.js +80 -0
  149. package/src/lib/testing/stand-in-dataset-index.js.map +1 -0
  150. package/src/lib/usage/ai-allotment-alerts.d.ts +10 -7
  151. package/src/lib/usage/ai-allotment-alerts.js +31 -14
  152. package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
  153. package/src/lib/usage/ai-quota-checks.d.ts +37 -0
  154. package/src/lib/usage/ai-quota-checks.js +69 -0
  155. package/src/lib/usage/ai-quota-checks.js.map +1 -0
  156. package/src/lib/usage/ai-usage-alerts.js +22 -10
  157. package/src/lib/usage/ai-usage-alerts.js.map +1 -1
  158. package/src/lib/usage/ai-usage-by-user.js +1 -1
  159. package/src/lib/usage/ai-usage-by-user.js.map +1 -1
  160. package/src/lib/usage/assist-ceiling.d.ts +2 -2
  161. package/src/lib/usage/assist-ceiling.js +4 -4
  162. package/src/lib/usage/assist-ceiling.js.map +1 -1
  163. package/src/lib/usage/assist-credits.d.ts +494 -0
  164. package/src/lib/usage/assist-credits.js +524 -0
  165. package/src/lib/usage/assist-credits.js.map +1 -0
  166. package/src/lib/usage/assist-free-taste.d.ts +1 -1
  167. package/src/lib/usage/assist-free-taste.js +1 -1
  168. package/src/lib/usage/assist-free-taste.js.map +1 -1
  169. package/src/lib/usage/assist-signal-mining.js +1 -1
  170. package/src/lib/usage/assist-signal-mining.js.map +1 -1
  171. package/src/lib/usage/assist-spend-guards.d.ts +153 -0
  172. package/src/lib/usage/assist-spend-guards.js +182 -0
  173. package/src/lib/usage/assist-spend-guards.js.map +1 -0
  174. package/src/lib/usage/assist-usage.d.ts +1 -1
  175. package/src/lib/usage/assist-usage.js +1 -1
  176. package/src/lib/usage/assist-usage.js.map +1 -1
  177. package/src/lib/usage/staff-org-ai.js +1 -1
  178. package/src/lib/usage/staff-org-ai.js.map +1 -1
  179. package/src/lib/usage-axes.d.ts +10 -0
  180. package/src/lib/usage-axes.js +40 -2
  181. package/src/lib/usage-axes.js.map +1 -1
@@ -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 ConsoleHostFormsZoneProps,\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/** A form from a brief, on the Forms page (`hostForms`). */\nexport function AiDescribeFormButton(props: ConsoleHostFormsZoneProps) {\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;;;AA0BA,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,0DAA0D,GAC1D,OAAO,SAASuB,qBAAqBF,KAAgC;IACnE,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 { 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,4 +1,4 @@
1
- import type { ConsoleAutomationEditorZoneProps, ConsoleAutomationRunZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins';
1
+ import type { ConsoleAutomationEditorZoneProps, ConsoleAutomationRunZoneProps } from './ai-automation-zones';
2
2
  /**
3
3
  * "Explain it" in the editor of a saved automation, and "Why did this fail?"
4
4
  * on a failed run in its run history (AGL-2919), through the
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-explain-automation.component.tsx"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type {\n ConsoleAutomationEditorZoneProps,\n ConsoleAutomationRunZoneProps,\n} from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Link,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useState } from 'react'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { aiJobProblem, useAiJobRun, useAiJobsVerdict, type AiJobRun } from './use-ai-job-run'\n\n/**\n * \"Explain it\" in the editor of a saved automation, and \"Why did this fail?\"\n * on a failed run in its run history (AGL-2919), through the\n * `automationEditor` and `automationRun` zones. Each starts a `workflow` job\n * that reads the automation as it is saved — and, for a run, what the run\n * history recorded — and answers in plain words. Nothing is changed.\n */\n\nexport const AI_AUTOMATION_EXPLAIN_COPY = {\n explain: {\n action: 'Explain it',\n intro: 'Get a plain-words account of what this automation does, and anything in it worth checking.',\n saved: 'It reads the automation as it is saved, not changes you have not saved yet.',\n running: 'Reading the automation. This can take a few minutes.',\n again: 'Explain it again',\n failed: 'The automation could not be explained. Try again.',\n brief: (name: string) => `Explain what “${name}” does, and anything in it worth checking.`,\n },\n diagnose: {\n action: 'Why did this fail?',\n title: 'Why this run failed',\n running: 'Reading the run and the automation. This can take a few minutes.',\n again: 'Ask again',\n failed: 'The run could not be explained. Try again.',\n brief: (name: string) => `Why did a run of “${name}” fail, and how can it be fixed?`,\n },\n} as const\n\n/** The explanation a finished job wrote. */\nfunction explanationOf(job: AiJobSummary | null): string | null {\n if (job?.status !== 'done') return null\n return job.outputs.find((output) => output.resource === 'text' && output.text)?.text ?? null\n}\n\n/** Progress, the explanation, or why there is none. */\nfunction Answer({ run, running, failed }: { run: AiJobRun; running: string; failed: string }) {\n const text = explanationOf(run.job)\n const problem =\n aiJobProblem(run.job, failed) ?? (run.job?.status === 'done' && !text ? failed : null)\n return (\n <>\n {run.running ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={16} aria-label=\"Explaining\" />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {running}\n </Typography>\n </Stack>\n ) : null}\n {text ? (\n <Typography variant=\"body2\" component=\"div\" sx={{ whiteSpace: 'pre-line', wordBreak: 'break-word' }}>\n {text}\n </Typography>\n ) : null}\n {problem ? <Alert severity={run.job?.status === 'failed' ? 'error' : 'warning'}>{problem}</Alert> : null}\n {run.notice ? <Alert severity=\"warning\">{run.notice}</Alert> : null}\n </>\n )\n}\n\n/** \"Explain it\", at the top of the editor of a saved action or workflow. */\nexport function AiExplainAutomation({ hostId, orgId, target }: ConsoleAutomationEditorZoneProps) {\n const copy = AI_AUTOMATION_EXPLAIN_COPY.explain\n const help = pluginDocsHelp('aiAutomations', { anchor: '#explain' })\n const { data: user } = useUser()\n const verdict = useAiJobsVerdict(user, orgId)\n const run = useAiJobRun(user, copy.failed)\n\n if (verdict !== 'ready') return null\n const asked = Boolean(run.job || run.notice)\n const busy = run.starting || run.running\n\n const explain = () => {\n if (!orgId) return\n void run.start({\n orgId,\n hostId,\n kind: 'workflow',\n brief: copy.brief(target.name),\n inputs: { mode: 'explain', targetType: target.type, targetId: target.id },\n })\n }\n\n return (\n <Box sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }} aria-label=\"Explain this automation\">\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ flexGrow: 1 }}>\n {asked ? copy.saved : copy.intro}{' '}\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Typography>\n <Button size=\"small\" variant=\"outlined\" disabled={busy || !orgId} onClick={explain}>\n {asked && !busy ? copy.again : copy.action}\n </Button>\n </Stack>\n <Answer run={run} running={copy.running} failed={copy.failed} />\n </Stack>\n </Box>\n )\n}\n\n/** \"Why did this fail?\", on a failed run: the answer opens over the run history. */\nexport function AiExplainRunFailure({ hostId, orgId, target, runId }: ConsoleAutomationRunZoneProps) {\n const copy = AI_AUTOMATION_EXPLAIN_COPY.diagnose\n const { data: user } = useUser()\n const verdict = useAiJobsVerdict(user, orgId)\n const run = useAiJobRun(user, copy.failed)\n const [open, setOpen] = useState(false)\n\n if (verdict !== 'ready') return null\n const busy = run.starting || run.running\n\n const ask = () => {\n if (!orgId) return\n void run.start({\n orgId,\n hostId,\n kind: 'workflow',\n brief: copy.brief(target.name),\n inputs: { mode: 'diagnose', targetType: target.type, targetId: target.id, runId },\n })\n }\n\n const openAndAsk = () => {\n setOpen(true)\n // A run already explained, or being explained, is shown again rather than asked again.\n if (!run.job) ask()\n }\n\n const close = () => {\n setOpen(false)\n // A refusal is not kept: asking again later should ask the route again.\n if (run.notice) run.reset()\n }\n\n return (\n <>\n <Button size=\"small\" variant=\"text\" sx={{ px: 0.5, minWidth: 0 }} onClick={openAndAsk}>\n {copy.action}\n </Button>\n <Dialog open={open} onClose={close} fullWidth maxWidth=\"sm\">\n <DialogTitle>{copy.title}</DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n <Answer run={run} running={copy.running} failed={copy.failed} />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={close}>{'Close'}</Button>\n {!busy && (run.notice || aiJobProblem(run.job, copy.failed)) ? (\n <Button variant=\"outlined\" onClick={ask} disabled={!orgId}>\n {copy.again}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n </>\n )\n}\n"],"names":["pluginDocsHelp","useUser","Alert","Box","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","Link","Stack","Typography","useState","aiJobProblem","useAiJobRun","useAiJobsVerdict","AI_AUTOMATION_EXPLAIN_COPY","explain","action","intro","saved","running","again","failed","brief","name","diagnose","title","explanationOf","job","status","outputs","find","output","resource","text","Answer","run","problem","direction","spacing","sx","alignItems","size","aria-label","variant","color","component","whiteSpace","wordBreak","severity","notice","AiExplainAutomation","hostId","orgId","target","copy","help","anchor","data","user","verdict","asked","Boolean","busy","starting","start","kind","inputs","mode","targetType","type","targetId","id","border","borderColor","borderRadius","p","flexGrow","href","rel","excerpt","disabled","onClick","AiExplainRunFailure","runId","open","setOpen","ask","openAndAsk","close","reset","px","minWidth","onClose","fullWidth","maxWidth","pt"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-explain-automation.component.tsx"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Link,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useState } from 'react'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { aiJobProblem, useAiJobRun, useAiJobsVerdict, type AiJobRun } from './use-ai-job-run'\nimport type {\n ConsoleAutomationEditorZoneProps,\n ConsoleAutomationRunZoneProps,\n} from './ai-automation-zones'\n\n/**\n * \"Explain it\" in the editor of a saved automation, and \"Why did this fail?\"\n * on a failed run in its run history (AGL-2919), through the\n * `automationEditor` and `automationRun` zones. Each starts a `workflow` job\n * that reads the automation as it is saved — and, for a run, what the run\n * history recorded — and answers in plain words. Nothing is changed.\n */\n\nexport const AI_AUTOMATION_EXPLAIN_COPY = {\n explain: {\n action: 'Explain it',\n intro: 'Get a plain-words account of what this automation does, and anything in it worth checking.',\n saved: 'It reads the automation as it is saved, not changes you have not saved yet.',\n running: 'Reading the automation. This can take a few minutes.',\n again: 'Explain it again',\n failed: 'The automation could not be explained. Try again.',\n brief: (name: string) => `Explain what “${name}” does, and anything in it worth checking.`,\n },\n diagnose: {\n action: 'Why did this fail?',\n title: 'Why this run failed',\n running: 'Reading the run and the automation. This can take a few minutes.',\n again: 'Ask again',\n failed: 'The run could not be explained. Try again.',\n brief: (name: string) => `Why did a run of “${name}” fail, and how can it be fixed?`,\n },\n} as const\n\n/** The explanation a finished job wrote. */\nfunction explanationOf(job: AiJobSummary | null): string | null {\n if (job?.status !== 'done') return null\n return job.outputs.find((output) => output.resource === 'text' && output.text)?.text ?? null\n}\n\n/** Progress, the explanation, or why there is none. */\nfunction Answer({ run, running, failed }: { run: AiJobRun; running: string; failed: string }) {\n const text = explanationOf(run.job)\n const problem =\n aiJobProblem(run.job, failed) ?? (run.job?.status === 'done' && !text ? failed : null)\n return (\n <>\n {run.running ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={16} aria-label=\"Explaining\" />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {running}\n </Typography>\n </Stack>\n ) : null}\n {text ? (\n <Typography variant=\"body2\" component=\"div\" sx={{ whiteSpace: 'pre-line', wordBreak: 'break-word' }}>\n {text}\n </Typography>\n ) : null}\n {problem ? <Alert severity={run.job?.status === 'failed' ? 'error' : 'warning'}>{problem}</Alert> : null}\n {run.notice ? <Alert severity=\"warning\">{run.notice}</Alert> : null}\n </>\n )\n}\n\n/** \"Explain it\", at the top of the editor of a saved action or workflow. */\nexport function AiExplainAutomation({ hostId, orgId, target }: ConsoleAutomationEditorZoneProps) {\n const copy = AI_AUTOMATION_EXPLAIN_COPY.explain\n const help = pluginDocsHelp('aiAutomations', { anchor: '#explain' })\n const { data: user } = useUser()\n const verdict = useAiJobsVerdict(user, orgId)\n const run = useAiJobRun(user, copy.failed)\n\n if (verdict !== 'ready') return null\n const asked = Boolean(run.job || run.notice)\n const busy = run.starting || run.running\n\n const explain = () => {\n if (!orgId) return\n void run.start({\n orgId,\n hostId,\n kind: 'workflow',\n brief: copy.brief(target.name),\n inputs: { mode: 'explain', targetType: target.type, targetId: target.id },\n })\n }\n\n return (\n <Box sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }} aria-label=\"Explain this automation\">\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ flexGrow: 1 }}>\n {asked ? copy.saved : copy.intro}{' '}\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Typography>\n <Button size=\"small\" variant=\"outlined\" disabled={busy || !orgId} onClick={explain}>\n {asked && !busy ? copy.again : copy.action}\n </Button>\n </Stack>\n <Answer run={run} running={copy.running} failed={copy.failed} />\n </Stack>\n </Box>\n )\n}\n\n/** \"Why did this fail?\", on a failed run: the answer opens over the run history. */\nexport function AiExplainRunFailure({ hostId, orgId, target, runId }: ConsoleAutomationRunZoneProps) {\n const copy = AI_AUTOMATION_EXPLAIN_COPY.diagnose\n const { data: user } = useUser()\n const verdict = useAiJobsVerdict(user, orgId)\n const run = useAiJobRun(user, copy.failed)\n const [open, setOpen] = useState(false)\n\n if (verdict !== 'ready') return null\n const busy = run.starting || run.running\n\n const ask = () => {\n if (!orgId) return\n void run.start({\n orgId,\n hostId,\n kind: 'workflow',\n brief: copy.brief(target.name),\n inputs: { mode: 'diagnose', targetType: target.type, targetId: target.id, runId },\n })\n }\n\n const openAndAsk = () => {\n setOpen(true)\n // A run already explained, or being explained, is shown again rather than asked again.\n if (!run.job) ask()\n }\n\n const close = () => {\n setOpen(false)\n // A refusal is not kept: asking again later should ask the route again.\n if (run.notice) run.reset()\n }\n\n return (\n <>\n <Button size=\"small\" variant=\"text\" sx={{ px: 0.5, minWidth: 0 }} onClick={openAndAsk}>\n {copy.action}\n </Button>\n <Dialog open={open} onClose={close} fullWidth maxWidth=\"sm\">\n <DialogTitle>{copy.title}</DialogTitle>\n <DialogContent>\n <Stack spacing={1.5} sx={{ pt: 1 }}>\n <Answer run={run} running={copy.running} failed={copy.failed} />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={close}>{'Close'}</Button>\n {!busy && (run.notice || aiJobProblem(run.job, copy.failed)) ? (\n <Button variant=\"outlined\" onClick={ask} disabled={!orgId}>\n {copy.again}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n </>\n )\n}\n"],"names":["pluginDocsHelp","useUser","Alert","Box","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","Link","Stack","Typography","useState","aiJobProblem","useAiJobRun","useAiJobsVerdict","AI_AUTOMATION_EXPLAIN_COPY","explain","action","intro","saved","running","again","failed","brief","name","diagnose","title","explanationOf","job","status","outputs","find","output","resource","text","Answer","run","problem","direction","spacing","sx","alignItems","size","aria-label","variant","color","component","whiteSpace","wordBreak","severity","notice","AiExplainAutomation","hostId","orgId","target","copy","help","anchor","data","user","verdict","asked","Boolean","busy","starting","start","kind","inputs","mode","targetType","type","targetId","id","border","borderColor","borderRadius","p","flexGrow","href","rel","excerpt","disabled","onClick","AiExplainRunFailure","runId","open","setOpen","ask","openAndAsk","close","reset","px","minWidth","onClose","fullWidth","maxWidth","pt"],"mappings":"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"}
@@ -305,7 +305,7 @@ const creditsColumn = (credits)=>({
305
305
  /*#__PURE__*/ _jsx(Typography, {
306
306
  variant: "body2",
307
307
  color: "text.secondary",
308
- children: 'Give a member or a whole site a share of the workspace’s AI credits each month. ' + 'A hard allotment stops AI requests at the line; a soft one keeps going and alerts ' + 'you at 80% and 100%. The workspace’s own credits still stop first.'
308
+ children: 'Give a member or a whole site a share of the workspace’s AI credits each month. ' + 'A hard allotment stops AI requests at the line; a soft one keeps going and alerts ' + 'you at 75%, 80%, 90% and 100%. The workspace’s own credits still stop first.'
309
309
  }),
310
310
  /*#__PURE__*/ _jsxs(Stack, {
311
311
  direction: "row",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/billing-ai-allotments.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiPencilOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Button, Stack, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useMemo, useState } from 'react'\nimport { AI_ALLOTMENT_ORG_SUBJECT, aiAllotmentState } from '../model/ai-allotments'\nimport type {\n AiAllotmentRowWire,\n AiAllotmentsHostWire,\n AiAllotmentsMemberWire,\n} from '../usage/ai-usage-wire'\nimport { AiAllotmentEditor, type AiAllotmentValue } from './ai-allotment-editor.component'\nimport { saveAiAllotments, useAiAllotments } from './use-ai-allotments'\n\n/** An allotment as a table cell reads it: used of credits, mode, models. */\nexport function aiAllotmentSummary(\n row: Pick<AiAllotmentRowWire, 'credits' | 'mode' | 'models' | 'used'> | null | undefined,\n): string {\n if (!row) return '—'\n const parts: string[] = []\n if (row.credits !== null) {\n parts.push(`${row.used.toLocaleString()} of ${row.credits.toLocaleString()} (${row.mode})`)\n }\n if (row.models?.length) {\n parts.push(`${row.models.length} model${row.models.length === 1 ? '' : 's'}`)\n }\n return parts.join(' · ') || '—'\n}\n\n/** The color an allotment's figure reads in: warning from 80%, error at it. */\nexport function aiAllotmentTone(\n row: Pick<AiAllotmentRowWire, 'credits' | 'used'> | null | undefined,\n): 'text.primary' | 'warning.main' | 'error.main' {\n if (!row || row.credits === null) return 'text.primary'\n const state = aiAllotmentState(row.used, row.credits)\n return state === 'reached' ? 'error.main' : state === 'warn' ? 'warning.main' : 'text.primary'\n}\n\ntype Editing =\n | {\n kind: 'subjects'\n subjects: string[]\n title: string\n description: string\n initial: AiAllotmentValue | null\n removable: boolean\n }\n | { kind: 'everySite' }\n | { kind: 'restriction'; initial: AiAllotmentValue | null; removable: boolean }\n\nconst valueOf = (row: AiAllotmentRowWire | undefined): AiAllotmentValue | null =>\n row ? { credits: row.credits, mode: row.mode, models: row.models } : null\n\n/**\n * A row of one of the three lists: the subject its allotment is stored under,\n * that subject's allotment when it has one, and `$id` — the id `ListTable`\n * selects by, which is the member's uid, the site's id, or the collaborator's\n * subject.\n */\ntype AllotmentListRow = {\n $id: string\n subject: string\n allotment: AiAllotmentRowWire | undefined\n}\ntype MemberRow = AllotmentListRow & { member: AiAllotmentsMemberWire }\ntype SiteRow = AllotmentListRow & { host: AiAllotmentsHostWire }\ntype CollaboratorRow = AllotmentListRow & { name: string; site: string }\n\n/** The allotment column: used of credits, in the tone its share has reached. */\nconst allotmentColumn = <Row extends AllotmentListRow>(): GridColDef<Row> => ({\n field: 'allotment',\n headerName: 'Allotment',\n flex: 1,\n minWidth: 200,\n sortable: false,\n valueGetter: (_value, row) => aiAllotmentSummary(row.allotment),\n renderCell: ({ row }) => (\n <Typography variant=\"body2\" noWrap sx={{ color: aiAllotmentTone(row.allotment) }}>\n {aiAllotmentSummary(row.allotment)}\n </Typography>\n ),\n})\n\nconst creditsColumn = <Row extends AllotmentListRow>(\n credits: (row: Row) => number,\n): GridColDef<Row> => ({\n field: 'credits',\n headerName: 'Credits this month',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 170,\n valueGetter: (_value, row) => credits(row),\n valueFormatter: (value: number) => value.toLocaleString(),\n})\n\n/**\n * AI ALLOTMENTS ON BILLING → USAGE (AGL-2942): who may draw how much of the\n * workspace's AI pool.\n *\n * Team members and sites, each with this month's credits and its allotment;\n * select several and set one allotment for all of them, or set the same\n * allotment on every site at once — the agency's \"same for every client\n * site\". Collaborators' allotments, which their sites' Users cards set, are\n * listed below. On a plan that offers it, the organization's model\n * restriction sits at the end.\n *\n * Read with `billing.view`; the controls appear with `billing.manage`, and\n * the route refuses a write without it whatever this card draws.\n *\n * The three tables are the route's whole answer — the roster the plan's\n * seats bound, the organization's sites, the collaborator allotments — so\n * they offer no Filters panel and no search: a bounded readout is read\n * whole, and a grid that narrowed the rows it holds is the pattern every\n * list is leaving (AGL-3321).\n */\nexport function BillingAiAllotments({ orgId }: { orgId?: string }) {\n const { data: user } = useUser()\n const allotments = useAiAllotments({ orgId })\n const [members, setMembers] = useState<string[]>([])\n const [sites, setSites] = useState<string[]>([])\n const [editing, setEditing] = useState<Editing | null>(null)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const data = allotments.data\n const bySubject = useMemo(\n () => new Map((data?.allotments ?? []).map((row) => [row.subject, row])),\n [data],\n )\n\n if (allotments.status === 'refused') {\n return (\n <Alert severity=\"info\">{'Seeing AI allotments requires the View billing permission.'}</Alert>\n )\n }\n if (allotments.status === 'error' && !data) {\n return (\n <Alert severity=\"warning\">\n {'Could not read the AI allotments — a failed read, not an empty list.'}\n </Alert>\n )\n }\n if (!data || !orgId) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n )\n }\n\n const canEdit = data.canEdit.billing\n const team = data.members.filter((member) => member.orgWide)\n const nameOf = (uid: string | null) =>\n data.members.find((member) => member.uid === uid)?.name ?? uid ?? ''\n const siteName = (hostId: string | null) =>\n data.hosts.find((host) => host.hostId === hostId)?.name ?? hostId ?? ''\n const collaborators = data.allotments.filter((row) => row.scope === 'collab')\n const restriction = bySubject.get(AI_ALLOTMENT_ORG_SUBJECT)\n /*\n * Each list pages and sorts on its own, and a selection is kept by member\n * and site id rather than by row, so it survives a page change.\n */\n const teamRows: MemberRow[] = team.map((member) => ({\n $id: member.uid,\n subject: `member:${member.uid}`,\n allotment: bySubject.get(`member:${member.uid}`),\n member,\n }))\n const siteRows: SiteRow[] = data.hosts.map((host) => ({\n $id: host.hostId,\n subject: `host:${host.hostId}`,\n allotment: bySubject.get(`host:${host.hostId}`),\n host,\n }))\n const collaboratorRows: CollaboratorRow[] = collaborators.map((row) => ({\n $id: row.subject,\n subject: row.subject,\n allotment: row,\n name: nameOf(row.uid),\n site: siteName(row.hostId),\n }))\n\n const editSubjects = (subjects: string[], title: string, description: string) => {\n const rows = subjects.map((subject) => bySubject.get(subject))\n setError(null)\n setEditing({\n kind: 'subjects',\n subjects,\n title,\n description,\n initial: subjects.length === 1 ? valueOf(rows[0]) : null,\n removable: rows.every(Boolean),\n })\n }\n\n const run = async (body: Parameters<typeof saveAiAllotments>[1]) => {\n setBusy(true)\n setError(null)\n const result = await saveAiAllotments(user, body)\n setBusy(false)\n if (!result.ok) {\n setError(result.error)\n return\n }\n setEditing(null)\n setMembers([])\n setSites([])\n }\n const save = (value: AiAllotmentValue) => {\n if (!editing) return\n if (editing.kind === 'everySite') return void run({ orgId, everySite: value })\n if (editing.kind === 'restriction') {\n return void run(\n value.models\n ? { orgId, set: [{ subject: AI_ALLOTMENT_ORG_SUBJECT, ...value, credits: null }] }\n : { orgId, remove: [AI_ALLOTMENT_ORG_SUBJECT] },\n )\n }\n return void run({\n orgId,\n set: editing.subjects.map((subject) => ({ subject, ...value })),\n })\n }\n const remove = () => {\n if (!editing || editing.kind === 'everySite') return\n return void run({\n orgId,\n remove: editing.kind === 'restriction' ? [AI_ALLOTMENT_ORG_SUBJECT] : editing.subjects,\n })\n }\n const editMember = (row: MemberRow) =>\n editSubjects([row.subject], `AI allotment — ${row.member.name}`, 'Counts what this person draws across every site.')\n const editSite = (row: SiteRow) =>\n editSubjects([row.subject], `AI allotment — ${row.host.name}`, 'Counts what everyone draws on the site, together.')\n const editCollaborator = (row: CollaboratorRow) =>\n editSubjects(\n [row.subject],\n `AI allotment — ${row.name} on ${row.site}`,\n 'Counts what this collaborator draws on this site.',\n )\n /** The one action a row has: set its allotment, or edit the one it has. */\n const editColumn = <Row extends AllotmentListRow>(label: (row: Row) => string, edit: (row: Row) => void) =>\n listActionsColumn((row: Row) => (\n <ListRowActions\n label={label(row)}\n quick={{\n icon: mdiPencilOutline.path,\n label: row.allotment ? 'Edit allotment' : 'Set allotment',\n onClick: () => edit(row),\n }}\n items={[]}\n />\n ))\n\n const teamColumns: GridColDef<MemberRow>[] = [\n { field: 'name', headerName: 'Member', flex: 1, minWidth: 180, valueGetter: (_value, row) => row.member.name },\n creditsColumn<MemberRow>((row) => row.member.credits),\n allotmentColumn<MemberRow>(),\n ...(canEdit ? [editColumn<MemberRow>((row) => row.member.name, editMember)] : []),\n ]\n const siteColumns: GridColDef<SiteRow>[] = [\n { field: 'name', headerName: 'Site', flex: 1, minWidth: 180, valueGetter: (_value, row) => row.host.name },\n creditsColumn<SiteRow>((row) => row.host.credits),\n allotmentColumn<SiteRow>(),\n ...(canEdit ? [editColumn<SiteRow>((row) => row.host.name, editSite)] : []),\n ]\n const collaboratorColumns: GridColDef<CollaboratorRow>[] = [\n { field: 'name', headerName: 'Collaborator', flex: 1, minWidth: 180 },\n { field: 'site', headerName: 'Site', flex: 1, minWidth: 160 },\n allotmentColumn<CollaboratorRow>(),\n ...(canEdit ? [editColumn<CollaboratorRow>((row) => `${row.name} on ${row.site}`, editCollaborator)] : []),\n ]\n\n return (\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Give a member or a whole site a share of the workspace’s AI credits each month. ' +\n 'A hard allotment stops AI requests at the line; a soft one keeps going and alerts ' +\n 'you at 80% and 100%. The workspace’s own credits still stop first.'}\n </Typography>\n\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Team members'}\n </Typography>\n {canEdit ? (\n <Button\n size=\"small\"\n disabled={!members.length}\n onClick={() =>\n editSubjects(\n members.map((uid) => `member:${uid}`),\n members.length === 1 ? `AI allotment — ${nameOf(members[0])}` : `AI allotment — ${members.length} members`,\n 'Counts what each person draws across every site.',\n )\n }\n >\n {members.length > 1 ? `Set for ${members.length} selected` : 'Set for selected'}\n </Button>\n ) : null}\n </Stack>\n <ListTable\n aria-label=\"Team members' AI allotments\"\n rows={teamRows}\n columns={teamColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No team members\"\n disableColumnFilter\n quickFilter={false}\n {...(canEdit\n ? { selectable: { selected: members, onChange: setMembers }, onOpen: (_id: string, row: MemberRow) => editMember(row) }\n : {})}\n />\n\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Sites'}\n </Typography>\n {canEdit ? (\n <>\n <Button\n size=\"small\"\n disabled={!sites.length}\n onClick={() =>\n editSubjects(\n sites.map((hostId) => `host:${hostId}`),\n sites.length === 1 ? `AI allotment — ${siteName(sites[0])}` : `AI allotment — ${sites.length} sites`,\n 'Counts what everyone draws on the site, together.',\n )\n }\n >\n {sites.length > 1 ? `Set for ${sites.length} selected` : 'Set for selected'}\n </Button>\n <Button\n size=\"small\"\n disabled={!data.hosts.length}\n onClick={() => {\n setError(null)\n setEditing({ kind: 'everySite' })\n }}\n >\n {'Same for every client site'}\n </Button>\n </>\n ) : null}\n </Stack>\n <ListTable\n aria-label=\"Sites' AI allotments\"\n rows={siteRows}\n columns={siteColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No sites\"\n disableColumnFilter\n quickFilter={false}\n {...(canEdit\n ? { selectable: { selected: sites, onChange: setSites }, onOpen: (_id: string, row: SiteRow) => editSite(row) }\n : {})}\n />\n\n {collaborators.length ? (\n <>\n <Typography variant=\"subtitle2\">{'Site collaborators'}</Typography>\n <ListTable\n aria-label=\"Site collaborators' AI allotments\"\n rows={collaboratorRows}\n columns={collaboratorColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n disableColumnFilter\n quickFilter={false}\n {...(canEdit ? { onOpen: (_id: string, row: CollaboratorRow) => editCollaborator(row) } : {})}\n />\n </>\n ) : null}\n\n {data.restrictionAvailable ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"body2\" sx={{ flexGrow: 1 }}>\n {'Models for the whole organization: '}\n {restriction?.models?.length\n ? data.models\n .filter((model) => restriction.models?.includes(model.id))\n .map((model) => model.label)\n .join(', ') || `${restriction.models.length} models`\n : 'every model the plan offers'}\n </Typography>\n {canEdit ? (\n <Button\n size=\"small\"\n onClick={() => {\n setError(null)\n setEditing({ kind: 'restriction', initial: valueOf(restriction), removable: Boolean(restriction) })\n }}\n >\n {'Restrict models'}\n </Button>\n ) : null}\n </Stack>\n ) : null}\n\n <AiAllotmentEditor\n open={editing !== null}\n title={\n editing?.kind === 'everySite'\n ? 'Same allotment for every site'\n : editing?.kind === 'restriction'\n ? 'Models for the whole organization'\n : (editing?.title ?? '')\n }\n description={\n editing?.kind === 'everySite'\n ? `Sets this allotment on all ${data.hosts.length} sites, replacing any each one had.`\n : editing?.kind === 'restriction'\n ? 'Every member, collaborator and Auto choose only among these models.'\n : editing?.description\n }\n initial={editing && editing.kind !== 'everySite' ? editing.initial : null}\n models={data.models}\n modelsOnly={editing?.kind === 'restriction'}\n poolLimit={data.pool.limit}\n busy={busy}\n error={error}\n onClose={() => setEditing(null)}\n onSave={save}\n onRemove={editing && editing.kind !== 'everySite' && editing.removable ? remove : undefined}\n />\n </Stack>\n )\n}\nBillingAiAllotments.displayName = 'BillingAiAllotments'\n\n/**\n * The card on Billing → Usage, through the `orgBillingUsage` zone, beneath\n * the table of who drew the credits. Registered with `billing.view`.\n */\nexport function AiAllotmentsCard(props: { orgId?: string }) {\n return (\n <div id=\"ai-allotments\">\n <CardDisplay\n header={'AI allotments'}\n help={pluginDocsHelp('billing', {\n anchor: '#ai-allotments',\n excerpt:\n 'A monthly share of the workspace’s AI credits for a member or a site — ' +\n 'hard or soft, with an optional list of models.',\n })}\n contentGutterX\n contentGutterY\n >\n <BillingAiAllotments orgId={props.orgId} />\n </CardDisplay>\n </div>\n )\n}\nAiAllotmentsCard.displayName = 'AiAllotmentsCard'\n\nexport default AiAllotmentsCard\n"],"names":["pluginDocsHelp","mdiPencilOutline","CardDisplay","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","useUser","Alert","Button","Stack","Typography","useMemo","useState","AI_ALLOTMENT_ORG_SUBJECT","aiAllotmentState","AiAllotmentEditor","saveAiAllotments","useAiAllotments","aiAllotmentSummary","row","parts","credits","push","used","toLocaleString","mode","models","length","join","aiAllotmentTone","state","valueOf","allotmentColumn","field","headerName","flex","minWidth","sortable","valueGetter","_value","allotment","renderCell","variant","noWrap","sx","color","creditsColumn","type","align","headerAlign","width","valueFormatter","value","BillingAiAllotments","orgId","restriction","data","user","allotments","members","setMembers","sites","setSites","editing","setEditing","busy","setBusy","error","setError","bySubject","Map","map","subject","status","severity","canEdit","billing","team","filter","member","orgWide","nameOf","uid","find","name","siteName","hostId","hosts","host","collaborators","scope","get","teamRows","$id","siteRows","collaboratorRows","site","editSubjects","subjects","title","description","rows","kind","initial","removable","every","Boolean","run","body","result","ok","save","everySite","set","remove","editMember","editSite","editCollaborator","editColumn","label","edit","quick","icon","path","onClick","items","teamColumns","siteColumns","collaboratorColumns","spacing","direction","alignItems","flexWrap","rowGap","flexGrow","size","disabled","aria-label","columns","rowHeight","noRowsLabel","disableColumnFilter","quickFilter","selectable","selected","onChange","onOpen","_id","restrictionAvailable","model","includes","id","open","modelsOnly","poolLimit","pool","limit","onClose","onSave","onRemove","undefined","displayName","AiAllotmentsCard","props","div","header","help","anchor","excerpt","contentGutterX","contentGutterY"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/billing-ai-allotments.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiPencilOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Button, Stack, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useMemo, useState } from 'react'\nimport { AI_ALLOTMENT_ORG_SUBJECT, aiAllotmentState } from '../model/ai-allotments'\nimport type {\n AiAllotmentRowWire,\n AiAllotmentsHostWire,\n AiAllotmentsMemberWire,\n} from '../usage/ai-usage-wire'\nimport { AiAllotmentEditor, type AiAllotmentValue } from './ai-allotment-editor.component'\nimport { saveAiAllotments, useAiAllotments } from './use-ai-allotments'\n\n/** An allotment as a table cell reads it: used of credits, mode, models. */\nexport function aiAllotmentSummary(\n row: Pick<AiAllotmentRowWire, 'credits' | 'mode' | 'models' | 'used'> | null | undefined,\n): string {\n if (!row) return '—'\n const parts: string[] = []\n if (row.credits !== null) {\n parts.push(`${row.used.toLocaleString()} of ${row.credits.toLocaleString()} (${row.mode})`)\n }\n if (row.models?.length) {\n parts.push(`${row.models.length} model${row.models.length === 1 ? '' : 's'}`)\n }\n return parts.join(' · ') || '—'\n}\n\n/** The color an allotment's figure reads in: warning from 80%, error at it. */\nexport function aiAllotmentTone(\n row: Pick<AiAllotmentRowWire, 'credits' | 'used'> | null | undefined,\n): 'text.primary' | 'warning.main' | 'error.main' {\n if (!row || row.credits === null) return 'text.primary'\n const state = aiAllotmentState(row.used, row.credits)\n return state === 'reached' ? 'error.main' : state === 'warn' ? 'warning.main' : 'text.primary'\n}\n\ntype Editing =\n | {\n kind: 'subjects'\n subjects: string[]\n title: string\n description: string\n initial: AiAllotmentValue | null\n removable: boolean\n }\n | { kind: 'everySite' }\n | { kind: 'restriction'; initial: AiAllotmentValue | null; removable: boolean }\n\nconst valueOf = (row: AiAllotmentRowWire | undefined): AiAllotmentValue | null =>\n row ? { credits: row.credits, mode: row.mode, models: row.models } : null\n\n/**\n * A row of one of the three lists: the subject its allotment is stored under,\n * that subject's allotment when it has one, and `$id` — the id `ListTable`\n * selects by, which is the member's uid, the site's id, or the collaborator's\n * subject.\n */\ntype AllotmentListRow = {\n $id: string\n subject: string\n allotment: AiAllotmentRowWire | undefined\n}\ntype MemberRow = AllotmentListRow & { member: AiAllotmentsMemberWire }\ntype SiteRow = AllotmentListRow & { host: AiAllotmentsHostWire }\ntype CollaboratorRow = AllotmentListRow & { name: string; site: string }\n\n/** The allotment column: used of credits, in the tone its share has reached. */\nconst allotmentColumn = <Row extends AllotmentListRow>(): GridColDef<Row> => ({\n field: 'allotment',\n headerName: 'Allotment',\n flex: 1,\n minWidth: 200,\n sortable: false,\n valueGetter: (_value, row) => aiAllotmentSummary(row.allotment),\n renderCell: ({ row }) => (\n <Typography variant=\"body2\" noWrap sx={{ color: aiAllotmentTone(row.allotment) }}>\n {aiAllotmentSummary(row.allotment)}\n </Typography>\n ),\n})\n\nconst creditsColumn = <Row extends AllotmentListRow>(\n credits: (row: Row) => number,\n): GridColDef<Row> => ({\n field: 'credits',\n headerName: 'Credits this month',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 170,\n valueGetter: (_value, row) => credits(row),\n valueFormatter: (value: number) => value.toLocaleString(),\n})\n\n/**\n * AI ALLOTMENTS ON BILLING → USAGE (AGL-2942): who may draw how much of the\n * workspace's AI pool.\n *\n * Team members and sites, each with this month's credits and its allotment;\n * select several and set one allotment for all of them, or set the same\n * allotment on every site at once — the agency's \"same for every client\n * site\". Collaborators' allotments, which their sites' Users cards set, are\n * listed below. On a plan that offers it, the organization's model\n * restriction sits at the end.\n *\n * Read with `billing.view`; the controls appear with `billing.manage`, and\n * the route refuses a write without it whatever this card draws.\n *\n * The three tables are the route's whole answer — the roster the plan's\n * seats bound, the organization's sites, the collaborator allotments — so\n * they offer no Filters panel and no search: a bounded readout is read\n * whole, and a grid that narrowed the rows it holds is the pattern every\n * list is leaving (AGL-3321).\n */\nexport function BillingAiAllotments({ orgId }: { orgId?: string }) {\n const { data: user } = useUser()\n const allotments = useAiAllotments({ orgId })\n const [members, setMembers] = useState<string[]>([])\n const [sites, setSites] = useState<string[]>([])\n const [editing, setEditing] = useState<Editing | null>(null)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const data = allotments.data\n const bySubject = useMemo(\n () => new Map((data?.allotments ?? []).map((row) => [row.subject, row])),\n [data],\n )\n\n if (allotments.status === 'refused') {\n return (\n <Alert severity=\"info\">{'Seeing AI allotments requires the View billing permission.'}</Alert>\n )\n }\n if (allotments.status === 'error' && !data) {\n return (\n <Alert severity=\"warning\">\n {'Could not read the AI allotments — a failed read, not an empty list.'}\n </Alert>\n )\n }\n if (!data || !orgId) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n )\n }\n\n const canEdit = data.canEdit.billing\n const team = data.members.filter((member) => member.orgWide)\n const nameOf = (uid: string | null) =>\n data.members.find((member) => member.uid === uid)?.name ?? uid ?? ''\n const siteName = (hostId: string | null) =>\n data.hosts.find((host) => host.hostId === hostId)?.name ?? hostId ?? ''\n const collaborators = data.allotments.filter((row) => row.scope === 'collab')\n const restriction = bySubject.get(AI_ALLOTMENT_ORG_SUBJECT)\n /*\n * Each list pages and sorts on its own, and a selection is kept by member\n * and site id rather than by row, so it survives a page change.\n */\n const teamRows: MemberRow[] = team.map((member) => ({\n $id: member.uid,\n subject: `member:${member.uid}`,\n allotment: bySubject.get(`member:${member.uid}`),\n member,\n }))\n const siteRows: SiteRow[] = data.hosts.map((host) => ({\n $id: host.hostId,\n subject: `host:${host.hostId}`,\n allotment: bySubject.get(`host:${host.hostId}`),\n host,\n }))\n const collaboratorRows: CollaboratorRow[] = collaborators.map((row) => ({\n $id: row.subject,\n subject: row.subject,\n allotment: row,\n name: nameOf(row.uid),\n site: siteName(row.hostId),\n }))\n\n const editSubjects = (subjects: string[], title: string, description: string) => {\n const rows = subjects.map((subject) => bySubject.get(subject))\n setError(null)\n setEditing({\n kind: 'subjects',\n subjects,\n title,\n description,\n initial: subjects.length === 1 ? valueOf(rows[0]) : null,\n removable: rows.every(Boolean),\n })\n }\n\n const run = async (body: Parameters<typeof saveAiAllotments>[1]) => {\n setBusy(true)\n setError(null)\n const result = await saveAiAllotments(user, body)\n setBusy(false)\n if (!result.ok) {\n setError(result.error)\n return\n }\n setEditing(null)\n setMembers([])\n setSites([])\n }\n const save = (value: AiAllotmentValue) => {\n if (!editing) return\n if (editing.kind === 'everySite') return void run({ orgId, everySite: value })\n if (editing.kind === 'restriction') {\n return void run(\n value.models\n ? { orgId, set: [{ subject: AI_ALLOTMENT_ORG_SUBJECT, ...value, credits: null }] }\n : { orgId, remove: [AI_ALLOTMENT_ORG_SUBJECT] },\n )\n }\n return void run({\n orgId,\n set: editing.subjects.map((subject) => ({ subject, ...value })),\n })\n }\n const remove = () => {\n if (!editing || editing.kind === 'everySite') return\n return void run({\n orgId,\n remove: editing.kind === 'restriction' ? [AI_ALLOTMENT_ORG_SUBJECT] : editing.subjects,\n })\n }\n const editMember = (row: MemberRow) =>\n editSubjects([row.subject], `AI allotment — ${row.member.name}`, 'Counts what this person draws across every site.')\n const editSite = (row: SiteRow) =>\n editSubjects([row.subject], `AI allotment — ${row.host.name}`, 'Counts what everyone draws on the site, together.')\n const editCollaborator = (row: CollaboratorRow) =>\n editSubjects(\n [row.subject],\n `AI allotment — ${row.name} on ${row.site}`,\n 'Counts what this collaborator draws on this site.',\n )\n /** The one action a row has: set its allotment, or edit the one it has. */\n const editColumn = <Row extends AllotmentListRow>(label: (row: Row) => string, edit: (row: Row) => void) =>\n listActionsColumn((row: Row) => (\n <ListRowActions\n label={label(row)}\n quick={{\n icon: mdiPencilOutline.path,\n label: row.allotment ? 'Edit allotment' : 'Set allotment',\n onClick: () => edit(row),\n }}\n items={[]}\n />\n ))\n\n const teamColumns: GridColDef<MemberRow>[] = [\n { field: 'name', headerName: 'Member', flex: 1, minWidth: 180, valueGetter: (_value, row) => row.member.name },\n creditsColumn<MemberRow>((row) => row.member.credits),\n allotmentColumn<MemberRow>(),\n ...(canEdit ? [editColumn<MemberRow>((row) => row.member.name, editMember)] : []),\n ]\n const siteColumns: GridColDef<SiteRow>[] = [\n { field: 'name', headerName: 'Site', flex: 1, minWidth: 180, valueGetter: (_value, row) => row.host.name },\n creditsColumn<SiteRow>((row) => row.host.credits),\n allotmentColumn<SiteRow>(),\n ...(canEdit ? [editColumn<SiteRow>((row) => row.host.name, editSite)] : []),\n ]\n const collaboratorColumns: GridColDef<CollaboratorRow>[] = [\n { field: 'name', headerName: 'Collaborator', flex: 1, minWidth: 180 },\n { field: 'site', headerName: 'Site', flex: 1, minWidth: 160 },\n allotmentColumn<CollaboratorRow>(),\n ...(canEdit ? [editColumn<CollaboratorRow>((row) => `${row.name} on ${row.site}`, editCollaborator)] : []),\n ]\n\n return (\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Give a member or a whole site a share of the workspace’s AI credits each month. ' +\n 'A hard allotment stops AI requests at the line; a soft one keeps going and alerts ' +\n 'you at 75%, 80%, 90% and 100%. The workspace’s own credits still stop first.'}\n </Typography>\n\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Team members'}\n </Typography>\n {canEdit ? (\n <Button\n size=\"small\"\n disabled={!members.length}\n onClick={() =>\n editSubjects(\n members.map((uid) => `member:${uid}`),\n members.length === 1 ? `AI allotment — ${nameOf(members[0])}` : `AI allotment — ${members.length} members`,\n 'Counts what each person draws across every site.',\n )\n }\n >\n {members.length > 1 ? `Set for ${members.length} selected` : 'Set for selected'}\n </Button>\n ) : null}\n </Stack>\n <ListTable\n aria-label=\"Team members' AI allotments\"\n rows={teamRows}\n columns={teamColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No team members\"\n disableColumnFilter\n quickFilter={false}\n {...(canEdit\n ? { selectable: { selected: members, onChange: setMembers }, onOpen: (_id: string, row: MemberRow) => editMember(row) }\n : {})}\n />\n\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Sites'}\n </Typography>\n {canEdit ? (\n <>\n <Button\n size=\"small\"\n disabled={!sites.length}\n onClick={() =>\n editSubjects(\n sites.map((hostId) => `host:${hostId}`),\n sites.length === 1 ? `AI allotment — ${siteName(sites[0])}` : `AI allotment — ${sites.length} sites`,\n 'Counts what everyone draws on the site, together.',\n )\n }\n >\n {sites.length > 1 ? `Set for ${sites.length} selected` : 'Set for selected'}\n </Button>\n <Button\n size=\"small\"\n disabled={!data.hosts.length}\n onClick={() => {\n setError(null)\n setEditing({ kind: 'everySite' })\n }}\n >\n {'Same for every client site'}\n </Button>\n </>\n ) : null}\n </Stack>\n <ListTable\n aria-label=\"Sites' AI allotments\"\n rows={siteRows}\n columns={siteColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No sites\"\n disableColumnFilter\n quickFilter={false}\n {...(canEdit\n ? { selectable: { selected: sites, onChange: setSites }, onOpen: (_id: string, row: SiteRow) => editSite(row) }\n : {})}\n />\n\n {collaborators.length ? (\n <>\n <Typography variant=\"subtitle2\">{'Site collaborators'}</Typography>\n <ListTable\n aria-label=\"Site collaborators' AI allotments\"\n rows={collaboratorRows}\n columns={collaboratorColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n disableColumnFilter\n quickFilter={false}\n {...(canEdit ? { onOpen: (_id: string, row: CollaboratorRow) => editCollaborator(row) } : {})}\n />\n </>\n ) : null}\n\n {data.restrictionAvailable ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"body2\" sx={{ flexGrow: 1 }}>\n {'Models for the whole organization: '}\n {restriction?.models?.length\n ? data.models\n .filter((model) => restriction.models?.includes(model.id))\n .map((model) => model.label)\n .join(', ') || `${restriction.models.length} models`\n : 'every model the plan offers'}\n </Typography>\n {canEdit ? (\n <Button\n size=\"small\"\n onClick={() => {\n setError(null)\n setEditing({ kind: 'restriction', initial: valueOf(restriction), removable: Boolean(restriction) })\n }}\n >\n {'Restrict models'}\n </Button>\n ) : null}\n </Stack>\n ) : null}\n\n <AiAllotmentEditor\n open={editing !== null}\n title={\n editing?.kind === 'everySite'\n ? 'Same allotment for every site'\n : editing?.kind === 'restriction'\n ? 'Models for the whole organization'\n : (editing?.title ?? '')\n }\n description={\n editing?.kind === 'everySite'\n ? `Sets this allotment on all ${data.hosts.length} sites, replacing any each one had.`\n : editing?.kind === 'restriction'\n ? 'Every member, collaborator and Auto choose only among these models.'\n : editing?.description\n }\n initial={editing && editing.kind !== 'everySite' ? editing.initial : null}\n models={data.models}\n modelsOnly={editing?.kind === 'restriction'}\n poolLimit={data.pool.limit}\n busy={busy}\n error={error}\n onClose={() => setEditing(null)}\n onSave={save}\n onRemove={editing && editing.kind !== 'everySite' && editing.removable ? remove : undefined}\n />\n </Stack>\n )\n}\nBillingAiAllotments.displayName = 'BillingAiAllotments'\n\n/**\n * The card on Billing → Usage, through the `orgBillingUsage` zone, beneath\n * the table of who drew the credits. Registered with `billing.view`.\n */\nexport function AiAllotmentsCard(props: { orgId?: string }) {\n return (\n <div id=\"ai-allotments\">\n <CardDisplay\n header={'AI allotments'}\n help={pluginDocsHelp('billing', {\n anchor: '#ai-allotments',\n excerpt:\n 'A monthly share of the workspace’s AI credits for a member or a site — ' +\n 'hard or soft, with an optional list of models.',\n })}\n contentGutterX\n contentGutterY\n >\n <BillingAiAllotments orgId={props.orgId} />\n </CardDisplay>\n </div>\n )\n}\nAiAllotmentsCard.displayName = 'AiAllotmentsCard'\n\nexport default AiAllotmentsCard\n"],"names":["pluginDocsHelp","mdiPencilOutline","CardDisplay","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","useUser","Alert","Button","Stack","Typography","useMemo","useState","AI_ALLOTMENT_ORG_SUBJECT","aiAllotmentState","AiAllotmentEditor","saveAiAllotments","useAiAllotments","aiAllotmentSummary","row","parts","credits","push","used","toLocaleString","mode","models","length","join","aiAllotmentTone","state","valueOf","allotmentColumn","field","headerName","flex","minWidth","sortable","valueGetter","_value","allotment","renderCell","variant","noWrap","sx","color","creditsColumn","type","align","headerAlign","width","valueFormatter","value","BillingAiAllotments","orgId","restriction","data","user","allotments","members","setMembers","sites","setSites","editing","setEditing","busy","setBusy","error","setError","bySubject","Map","map","subject","status","severity","canEdit","billing","team","filter","member","orgWide","nameOf","uid","find","name","siteName","hostId","hosts","host","collaborators","scope","get","teamRows","$id","siteRows","collaboratorRows","site","editSubjects","subjects","title","description","rows","kind","initial","removable","every","Boolean","run","body","result","ok","save","everySite","set","remove","editMember","editSite","editCollaborator","editColumn","label","edit","quick","icon","path","onClick","items","teamColumns","siteColumns","collaboratorColumns","spacing","direction","alignItems","flexWrap","rowGap","flexGrow","size","disabled","aria-label","columns","rowHeight","noRowsLabel","disableColumnFilter","quickFilter","selectable","selected","onChange","onOpen","_id","restrictionAvailable","model","includes","id","open","modelsOnly","poolLimit","pool","limit","onClose","onSave","onRemove","undefined","displayName","AiAllotmentsCard","props","div","header","help","anchor","excerpt","contentGutterX","contentGutterY"],"mappings":"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"}
@@ -24,7 +24,7 @@ import { useCallback, useEffect, useState } from "react";
24
24
  import { useUser } from "@aglyn/tenant-feature-instance";
25
25
  import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
26
26
  import { aiAddonName } from "@aglyn/aglyn";
27
- import { ASSIST_OVERAGE_CAP_CONTROL_LABEL, ASSIST_OVERAGE_CAP_MAX_USD, ASSIST_OVERAGE_CAP_MIN_USD } from "@aglyn/aglyn/app-utils/assist-credits";
27
+ import { ASSIST_OVERAGE_CAP_CONTROL_LABEL, ASSIST_OVERAGE_CAP_MAX_USD, ASSIST_OVERAGE_CAP_MIN_USD } from "../usage/assist-credits.js";
28
28
  /**
29
29
  * The org's own controls on AI assist spend: the hard cap at the band
30
30
  * (AGL-2653) and the dollar ceiling on overage (AGL-2898).
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/billing-assist-overage-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { CardDisplay, useConfirmationContext, useLoading } from '@aglyn/shared-ui-jsx'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Chip,\n FormControlLabel,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { aiAddonName } from '@aglyn/aglyn'\nimport {\n ASSIST_OVERAGE_CAP_CONTROL_LABEL,\n ASSIST_OVERAGE_CAP_MAX_USD,\n ASSIST_OVERAGE_CAP_MIN_USD,\n} from '@aglyn/aglyn/app-utils/assist-credits'\n\n/** The `get` payload of `/api/billing/assist-overage`. */\ninterface AssistOverageState {\n /** Whether the org asked to be stopped at its band. Absent by default. */\n hardCap: boolean\n /** The org's ceiling on this month's overage in USD, or `null` for none. */\n capUsd: number | null\n /** The plan's included credits a month, or `null` where it sells none. */\n bandCredits: number | null\n /** The per-1,000 rate credits past the band bill at, or `null`. */\n overageRateUsdPer1k: number | null\n /** True when there is a band AND a rate to sell past it at. */\n sellsOverage: boolean\n /** The switch's label, served so the refusal and the switch share it. */\n label: string\n /** The ceiling's label, served for the same reason. */\n capLabel: string\n /** The range the route accepts for the ceiling. */\n minCapUsd: number\n maxCapUsd: number\n}\n\nexport interface BillingAssistOverageCardProps {\n orgId?: string | null\n /** billing.manage: the switch acts; view-only otherwise. */\n canManage: boolean\n}\n\n/**\n * The org's own controls on AI assist spend: the hard cap at the band\n * (AGL-2653) and the dollar ceiling on overage (AGL-2898).\n *\n * ## What this card is\n *\n * Credits past a plan's included assist band are SOLD by default, at the\n * plan's per-1,000 rate, the way storage past its band bills by default. This\n * card offers the two controls a customer has over that: a switch that makes\n * the band a wall, so the assistant refuses there and nothing past it is ever\n * billed; and, with the switch off, a monthly ceiling in dollars on the\n * overage the org will buy before the assistant stops. Both are optional and\n * unset unless chosen — the storage cap's siblings, for the same reason a\n * ceiling on metered spend is the END USER's control, offered rather than\n * imposed.\n *\n * ## Why a switch AND a number\n *\n * The switch answers \"sell past the band at all?\" and needs no figure: the\n * band is the plan's, and a second band here would drift from it. The\n * ceiling answers \"and if so, how much?\", which is a figure only the customer\n * can name — it is a bound on THEIR invoice, not a quantity the plan sold.\n * The ceiling is offered only while the switch is off, because past a wall\n * there is no overage to bound.\n *\n * ## Why the switch is edited here and enforced there\n *\n * The route owns every refusal: a plan that sells no overage, a value that\n * is not a boolean, a missing permission. This card sends the boolean and\n * renders the route's own answer rather than pre-validating against constants\n * that would then be a second source of truth. The rate it quotes arrives on\n * the same payload, so what the card says the overage costs is what the\n * invoice bills.\n *\n * ## Why turning it OFF is confirmed and turning it ON is not\n *\n * Off is the direction that can raise a bill, so it asks; on never raises\n * one, so it is a plain click. Off is also always available, on any plan and\n * from any state — an org that wants the sale back must not have to argue\n * with a precondition.\n */\n/**\n * The card as the Billing → Usage page's plugin band mounts it (AGL-2939):\n * the overage controls under the heading and help the page used to draw\n * around them.\n */\nexport default function BillingAssistOverageCard(props: BillingAssistOverageCardProps) {\n return (\n <div id=\"assist-overage\">\n <CardDisplay\n header={'AI credits overage'}\n subheader={\n 'Extra AI credits past your included band are billed on your ' +\n 'monthly invoice. Stop at the band, or stop once the overage ' +\n 'reaches an amount you choose.'\n }\n help={pluginDocsHelp('billing', {\n anchor: '#assist-overage',\n excerpt:\n 'On a paid plan the assistant keeps answering past your ' +\n 'included credits and the extra is billed at your plan\\'s ' +\n 'per-1,000 rate, unless you switch on the stop at the included band.',\n })}\n contentGutterX\n contentGutterY\n >\n <BillingAssistOverageControls {...props} />\n </CardDisplay>\n </div>\n )\n}\n\nexport function BillingAssistOverageControls({\n orgId,\n canManage,\n}: BillingAssistOverageCardProps) {\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { queueLoading } = useLoading()\n const { confirm } = useConfirmationContext()\n const [state, setState] = useState<AssistOverageState | null>(null)\n const [busy, setBusy] = useState(false)\n /** The ceiling field, as typed; the route validates, this only carries. */\n const [capInput, setCapInput] = useState('')\n /**\n * Three outcomes, not two (the AGL-1380 rule). \"The assistant keeps going\n * past your band\" is a claim about this org's billing settings; rendering\n * it because a request FAILED would tell an org that asked to be stopped\n * that it had not.\n */\n const [loadState, setLoadState] = useState<'pending' | 'error' | 'loaded'>(\n 'pending',\n )\n /** Bumped by Retry, and after every write, to re-run the load effect. */\n const [retryNonce, setRetryNonce] = useState(0)\n\n const sendRequest = useCallback(\n async (\n body: Record<string, unknown>,\n ): Promise<{ ok: boolean; payload?: any }> => {\n const response = await authorizedFetch(\n user,\n '/api/ai/billing/overage',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, ...body }),\n },\n )\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n // The route's own sentence, verbatim: its 409 explains why a plan\n // with no rate has nothing to switch, which is more specific than\n // anything this card could invent.\n enqueueSnackbar(payload?.error ?? 'AI assist setting request failed', {\n variant: 'warning',\n persist: false,\n })\n return { ok: false, payload }\n }\n return { ok: true, payload }\n },\n [user, orgId, enqueueSnackbar],\n )\n\n useEffect(() => {\n if (!orgId || !user) return\n let cancelled = false\n setLoadState('pending')\n sendRequest({ action: 'get' })\n .then((outcome) => {\n if (cancelled) return\n if (!outcome.ok) return void setLoadState('error')\n const next = outcome.payload as AssistOverageState\n // A card that cannot name the price must not quote one. The rate\n // appears in the sentence beside the switch whenever overage is\n // sold, and a defaulted 0 would tell a person about to leave the\n // switch off that the extra costs \"$0.00 per 1,000\". A payload that\n // says overage is sold but cannot say at what is a LOAD FAILURE.\n if (\n typeof next?.hardCap !== 'boolean' ||\n typeof next?.sellsOverage !== 'boolean' ||\n (next.sellsOverage &&\n (!Number.isFinite(Number(next.overageRateUsdPer1k)) ||\n !Number.isFinite(Number(next.bandCredits))))\n ) {\n return void setLoadState('error')\n }\n // The ceiling half of the payload is read strictly, the way the\n // resolver reads the stored field: anything that is not a finite\n // number is NO ceiling, never a ceiling of `undefined` that renders\n // as \"$NaN\". The label and bounds fall back to the same constants the\n // route serves them from.\n const capUsd =\n typeof next.capUsd === 'number' && Number.isFinite(next.capUsd)\n ? next.capUsd\n : null\n setState({\n ...next,\n capUsd,\n capLabel:\n typeof next.capLabel === 'string' && next.capLabel\n ? next.capLabel\n : ASSIST_OVERAGE_CAP_CONTROL_LABEL,\n minCapUsd: Number.isFinite(Number(next.minCapUsd))\n ? Number(next.minCapUsd)\n : ASSIST_OVERAGE_CAP_MIN_USD,\n maxCapUsd: Number.isFinite(Number(next.maxCapUsd))\n ? Number(next.maxCapUsd)\n : ASSIST_OVERAGE_CAP_MAX_USD,\n })\n setCapInput(capUsd === null ? '' : String(capUsd))\n setLoadState('loaded')\n })\n // A rejected fetch — offline, a wedged route — never reached the\n // snackbar inside the request, so this is the only place it can become\n // something the card renders.\n .catch(() => {\n if (!cancelled) setLoadState('error')\n })\n return () => {\n cancelled = true\n }\n }, [orgId, user, sendRequest, retryNonce])\n\n const setHardCap = useCallback(\n async (hardCap: boolean) => {\n const rate = state?.overageRateUsdPer1k\n if (!hardCap) {\n // The loading overlay must drop BEFORE the confirm dialog opens — it\n // sits above the dialog and swallows the Confirm click (AGL-535). So\n // the confirm runs first and `queueLoading` only wraps the write.\n const accepted = await confirm({\n title: 'Keep AI assist going past your included credits?',\n description:\n 'The assistant will keep answering once your included credits ' +\n 'are used, and the extra credits will be billed on your monthly ' +\n `invoice${\n rate != null ? ` at $${rate.toFixed(2)} per 1,000` : ''\n }. You can turn the stop back on at any time.`,\n confirmationText: 'Keep going',\n })\n .then(() => true)\n .catch(() => false)\n if (!accepted) return\n }\n\n setBusy(true)\n const dequeue = queueLoading()\n try {\n const outcome = await sendRequest({ action: 'setHardCap', hardCap })\n if (!outcome.ok) return\n enqueueSnackbar(\n hardCap\n ? 'AI assist will stop at your included credits. Nothing past ' +\n 'the band is billed.'\n : 'AI assist keeps going past your included credits' +\n (rate != null\n ? `, billed at $${rate.toFixed(2)} per 1,000.`\n : '.'),\n { variant: 'success', persist: false },\n )\n setRetryNonce((nonce) => nonce + 1)\n } catch {\n enqueueSnackbar('Could not update your AI assist setting', {\n variant: 'warning',\n persist: false,\n })\n } finally {\n dequeue()\n setBusy(false)\n }\n },\n [state, confirm, queueLoading, sendRequest, enqueueSnackbar],\n )\n\n /**\n * Set or clear the ceiling. Neither direction asks first: setting one\n * only ever lowers a bill, and clearing one returns the org to the\n * open-ended overage the switch already told it about. The route owns the\n * range check, so a figure it refuses comes back as its own sentence.\n */\n const setCap = useCallback(\n async (capUsd: number | null) => {\n setBusy(true)\n const dequeue = queueLoading()\n try {\n const outcome = await sendRequest({ action: 'setCap', capUsd })\n if (!outcome.ok) return\n enqueueSnackbar(\n capUsd === null\n ? 'AI assist overage ceiling removed. Extra credits keep billing ' +\n 'at your plan’s rate with no monthly stop.'\n : `AI assist will stop once this month’s overage reaches ` +\n `$${capUsd.toFixed(2)}.`,\n { variant: 'success', persist: false },\n )\n setRetryNonce((nonce) => nonce + 1)\n } catch {\n enqueueSnackbar('Could not update your AI assist ceiling', {\n variant: 'warning',\n persist: false,\n })\n } finally {\n dequeue()\n setBusy(false)\n }\n },\n [queueLoading, sendRequest, enqueueSnackbar],\n )\n\n if (loadState === 'pending') {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Loading your AI assist settings…\n </Typography>\n )\n }\n\n if (loadState === 'error' || !state) {\n return (\n <Alert\n severity=\"warning\"\n action={\n <Button\n size=\"small\"\n color=\"inherit\"\n onClick={() => setRetryNonce((nonce) => nonce + 1)}\n >\n Retry\n </Button>\n }\n >\n We couldn’t load your AI assist settings. Nothing has changed.\n </Alert>\n )\n }\n\n const {\n hardCap,\n capUsd,\n bandCredits,\n overageRateUsdPer1k,\n sellsOverage,\n label,\n capLabel,\n minCapUsd,\n maxCapUsd,\n } = state\n const band = bandCredits == null ? null : Math.round(bandCredits)\n const rate = overageRateUsdPer1k == null ? null : overageRateUsdPer1k.toFixed(2)\n // The ceiling is a bound on overage, and overage exists only while the\n // switch is off on a plan that sells past its band. It stays reachable to\n // CLEAR whenever one is set, so an org that turned the switch on afterwards\n // is not left carrying a figure it cannot remove.\n const offersCeiling = sellsOverage && !hardCap\n const showsCeiling = offersCeiling || capUsd !== null\n\n // A plan that sells no overage has nothing for the switch to stop, so\n // there is nothing to configure. Say that plainly rather than rendering a\n // switch the route would refuse with a 409 — but keep the switch reachable\n // for an org that turned it on and then moved to a plan that sells none,\n // so it can turn it off.\n const nothingToStop = !sellsOverage && !hardCap\n\n return (\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', gap: 1 }}>\n <Chip\n label={\n hardCap\n ? 'Stops at the included band'\n : nothingToStop\n ? band === null\n ? 'No AI assist credits'\n : 'Included credits only'\n : capUsd !== null\n ? `Stops at $${capUsd.toFixed(2)} of overage`\n : 'No stop — extra credits bill'\n }\n size=\"small\"\n color={hardCap || capUsd !== null ? 'primary' : 'default'}\n />\n {band !== null ? (\n <Chip label={`${band.toLocaleString()} credits/mo included`} size=\"small\" />\n ) : null}\n </Stack>\n\n {nothingToStop && band === null ? (\n // No band at all, and nothing ever billed for AI, so neither stop\n // applies — the switch has no band to stop at and the ceiling has no\n // overage to bound. Since AGL-3203 every plan row carries a band, so\n // this is an org whose band was overridden to zero.\n <Alert severity=\"info\">\n Your plan includes no AI credits and is never charged for any, so\n there is no band to stop at, no overage to put a dollar ceiling on,\n and nothing to configure here. Upgrade above, or add{' '}\n {aiAddonName()} on a paid plan, to get a band.\n </Alert>\n ) : nothingToStop ? (\n // Enterprise: a band, and no rate to sell past it at. The assistant\n // stops at the band on its own and nothing is ever billed for it.\n <Alert severity=\"info\">\n Your plan includes <strong>{band?.toLocaleString()}</strong> AI assist\n credits a month and sells none past them: the assistant stops at the\n included band and you are <strong>never charged</strong> for AI\n assist, so there is nothing to switch.\n </Alert>\n ) : (\n <>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {sellsOverage\n ? `Your plan includes ${band?.toLocaleString()} AI assist credits ` +\n `a month. Past that, the assistant keeps answering and the ` +\n `extra credits are billed at $${rate} per 1,000 on your ` +\n `monthly invoice.`\n : `Your plan no longer sells AI assist credits past its band, ` +\n `so this switch is not doing anything. You can safely turn ` +\n `it off.`}\n </Typography>\n {sellsOverage ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n If you would rather the assistant stopped at the included band,\n turn this on. This is optional — most people leave it off.\n </Typography>\n ) : null}\n <Stack spacing={0.5}>\n <FormControlLabel\n control={\n <Switch\n checked={hardCap}\n disabled={!canManage || busy || (!hardCap && !sellsOverage)}\n onChange={(event) => void setHardCap(event.target.checked)}\n />\n }\n label={label}\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {hardCap\n ? `On: the assistant stops once your included credits are ` +\n `used, and you are never billed for AI assist.` +\n (sellsOverage\n ? ` Turn it off to keep going at $${rate} per 1,000 credits.`\n : '')\n : `Off: extra credits past the included band are billed at ` +\n `$${rate} per 1,000.`}\n </Typography>\n </Stack>\n\n {showsCeiling ? (\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {offersCeiling\n ? capUsd === null\n ? 'Or keep going, but only so far: set a monthly ceiling ' +\n 'on the extra credits you will pay for. The assistant ' +\n 'stops for the rest of the month once this month’s ' +\n 'overage reaches it. Optional, and separate from the ' +\n 'switch above.'\n : `The assistant stops for the rest of the month once ` +\n `this month’s overage reaches $${capUsd.toFixed(2)}. ` +\n `Raise, lower or remove the ceiling at any time.`\n : `A ceiling of $${(capUsd ?? 0).toFixed(2)} is set, but ` +\n `with the assistant stopping at the included band there ` +\n `is no overage for it to bound. You can safely remove it.`}\n </Typography>\n <Stack\n direction=\"row\"\n spacing={2}\n sx={{ alignItems: 'flex-start', flexWrap: 'wrap', rowGap: 2 }}\n >\n {offersCeiling ? (\n <>\n <TextField\n label={capLabel}\n type=\"number\"\n size=\"small\"\n value={capInput}\n disabled={!canManage || busy}\n onChange={(event) => setCapInput(event.target.value)}\n slotProps={{\n htmlInput: { min: minCapUsd, max: maxCapUsd, step: 1 },\n }}\n helperText={`In USD, between $${minCapUsd} and $${maxCapUsd.toLocaleString()}.`}\n />\n <Button\n variant=\"outlined\"\n disabled={!canManage || busy || capInput.trim() === ''}\n onClick={() => void setCap(Number(capInput))}\n >\n {capUsd === null ? 'Set a monthly ceiling' : 'Save ceiling'}\n </Button>\n </>\n ) : null}\n {capUsd !== null ? (\n <Button\n variant=\"text\"\n color=\"warning\"\n disabled={!canManage || busy}\n onClick={() => void setCap(null)}\n >\n Remove ceiling\n </Button>\n ) : null}\n </Stack>\n </Stack>\n ) : null}\n </>\n )}\n\n {!canManage ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n You need the Manage billing permission to change this.\n </Typography>\n ) : null}\n </Stack>\n )\n}\n"],"names":["CardDisplay","useConfirmationContext","useLoading","pluginDocsHelp","useSnackbar","Alert","Button","Chip","FormControlLabel","Stack","Switch","TextField","Typography","useCallback","useEffect","useState","useUser","authorizedFetch","aiAddonName","ASSIST_OVERAGE_CAP_CONTROL_LABEL","ASSIST_OVERAGE_CAP_MAX_USD","ASSIST_OVERAGE_CAP_MIN_USD","BillingAssistOverageCard","props","div","id","header","subheader","help","anchor","excerpt","contentGutterX","contentGutterY","BillingAssistOverageControls","orgId","canManage","data","user","enqueueSnackbar","queueLoading","confirm","state","setState","busy","setBusy","capInput","setCapInput","loadState","setLoadState","retryNonce","setRetryNonce","sendRequest","body","response","method","headers","JSON","stringify","payload","json","catch","ok","error","variant","persist","cancelled","action","then","outcome","next","hardCap","sellsOverage","Number","isFinite","overageRateUsdPer1k","bandCredits","capUsd","capLabel","minCapUsd","maxCapUsd","String","setHardCap","rate","accepted","title","description","toFixed","confirmationText","dequeue","nonce","setCap","color","severity","size","onClick","label","band","Math","round","offersCeiling","showsCeiling","nothingToStop","spacing","direction","sx","flexWrap","gap","toLocaleString","strong","control","checked","disabled","onChange","event","target","alignItems","rowGap","type","value","slotProps","htmlInput","min","max","step","helperText","trim"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/billing-assist-overage-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport { CardDisplay, useConfirmationContext, useLoading } from '@aglyn/shared-ui-jsx'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Chip,\n FormControlLabel,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { aiAddonName } from '@aglyn/aglyn'\nimport {\n ASSIST_OVERAGE_CAP_CONTROL_LABEL,\n ASSIST_OVERAGE_CAP_MAX_USD,\n ASSIST_OVERAGE_CAP_MIN_USD,\n} from '../usage/assist-credits'\n\n/** The `get` payload of `/api/billing/assist-overage`. */\ninterface AssistOverageState {\n /** Whether the org asked to be stopped at its band. Absent by default. */\n hardCap: boolean\n /** The org's ceiling on this month's overage in USD, or `null` for none. */\n capUsd: number | null\n /** The plan's included credits a month, or `null` where it sells none. */\n bandCredits: number | null\n /** The per-1,000 rate credits past the band bill at, or `null`. */\n overageRateUsdPer1k: number | null\n /** True when there is a band AND a rate to sell past it at. */\n sellsOverage: boolean\n /** The switch's label, served so the refusal and the switch share it. */\n label: string\n /** The ceiling's label, served for the same reason. */\n capLabel: string\n /** The range the route accepts for the ceiling. */\n minCapUsd: number\n maxCapUsd: number\n}\n\nexport interface BillingAssistOverageCardProps {\n orgId?: string | null\n /** billing.manage: the switch acts; view-only otherwise. */\n canManage: boolean\n}\n\n/**\n * The org's own controls on AI assist spend: the hard cap at the band\n * (AGL-2653) and the dollar ceiling on overage (AGL-2898).\n *\n * ## What this card is\n *\n * Credits past a plan's included assist band are SOLD by default, at the\n * plan's per-1,000 rate, the way storage past its band bills by default. This\n * card offers the two controls a customer has over that: a switch that makes\n * the band a wall, so the assistant refuses there and nothing past it is ever\n * billed; and, with the switch off, a monthly ceiling in dollars on the\n * overage the org will buy before the assistant stops. Both are optional and\n * unset unless chosen — the storage cap's siblings, for the same reason a\n * ceiling on metered spend is the END USER's control, offered rather than\n * imposed.\n *\n * ## Why a switch AND a number\n *\n * The switch answers \"sell past the band at all?\" and needs no figure: the\n * band is the plan's, and a second band here would drift from it. The\n * ceiling answers \"and if so, how much?\", which is a figure only the customer\n * can name — it is a bound on THEIR invoice, not a quantity the plan sold.\n * The ceiling is offered only while the switch is off, because past a wall\n * there is no overage to bound.\n *\n * ## Why the switch is edited here and enforced there\n *\n * The route owns every refusal: a plan that sells no overage, a value that\n * is not a boolean, a missing permission. This card sends the boolean and\n * renders the route's own answer rather than pre-validating against constants\n * that would then be a second source of truth. The rate it quotes arrives on\n * the same payload, so what the card says the overage costs is what the\n * invoice bills.\n *\n * ## Why turning it OFF is confirmed and turning it ON is not\n *\n * Off is the direction that can raise a bill, so it asks; on never raises\n * one, so it is a plain click. Off is also always available, on any plan and\n * from any state — an org that wants the sale back must not have to argue\n * with a precondition.\n */\n/**\n * The card as the Billing → Usage page's plugin band mounts it (AGL-2939):\n * the overage controls under the heading and help the page used to draw\n * around them.\n */\nexport default function BillingAssistOverageCard(props: BillingAssistOverageCardProps) {\n return (\n <div id=\"assist-overage\">\n <CardDisplay\n header={'AI credits overage'}\n subheader={\n 'Extra AI credits past your included band are billed on your ' +\n 'monthly invoice. Stop at the band, or stop once the overage ' +\n 'reaches an amount you choose.'\n }\n help={pluginDocsHelp('billing', {\n anchor: '#assist-overage',\n excerpt:\n 'On a paid plan the assistant keeps answering past your ' +\n 'included credits and the extra is billed at your plan\\'s ' +\n 'per-1,000 rate, unless you switch on the stop at the included band.',\n })}\n contentGutterX\n contentGutterY\n >\n <BillingAssistOverageControls {...props} />\n </CardDisplay>\n </div>\n )\n}\n\nexport function BillingAssistOverageControls({\n orgId,\n canManage,\n}: BillingAssistOverageCardProps) {\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { queueLoading } = useLoading()\n const { confirm } = useConfirmationContext()\n const [state, setState] = useState<AssistOverageState | null>(null)\n const [busy, setBusy] = useState(false)\n /** The ceiling field, as typed; the route validates, this only carries. */\n const [capInput, setCapInput] = useState('')\n /**\n * Three outcomes, not two (the AGL-1380 rule). \"The assistant keeps going\n * past your band\" is a claim about this org's billing settings; rendering\n * it because a request FAILED would tell an org that asked to be stopped\n * that it had not.\n */\n const [loadState, setLoadState] = useState<'pending' | 'error' | 'loaded'>(\n 'pending',\n )\n /** Bumped by Retry, and after every write, to re-run the load effect. */\n const [retryNonce, setRetryNonce] = useState(0)\n\n const sendRequest = useCallback(\n async (\n body: Record<string, unknown>,\n ): Promise<{ ok: boolean; payload?: any }> => {\n const response = await authorizedFetch(\n user,\n '/api/ai/billing/overage',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, ...body }),\n },\n )\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n // The route's own sentence, verbatim: its 409 explains why a plan\n // with no rate has nothing to switch, which is more specific than\n // anything this card could invent.\n enqueueSnackbar(payload?.error ?? 'AI assist setting request failed', {\n variant: 'warning',\n persist: false,\n })\n return { ok: false, payload }\n }\n return { ok: true, payload }\n },\n [user, orgId, enqueueSnackbar],\n )\n\n useEffect(() => {\n if (!orgId || !user) return\n let cancelled = false\n setLoadState('pending')\n sendRequest({ action: 'get' })\n .then((outcome) => {\n if (cancelled) return\n if (!outcome.ok) return void setLoadState('error')\n const next = outcome.payload as AssistOverageState\n // A card that cannot name the price must not quote one. The rate\n // appears in the sentence beside the switch whenever overage is\n // sold, and a defaulted 0 would tell a person about to leave the\n // switch off that the extra costs \"$0.00 per 1,000\". A payload that\n // says overage is sold but cannot say at what is a LOAD FAILURE.\n if (\n typeof next?.hardCap !== 'boolean' ||\n typeof next?.sellsOverage !== 'boolean' ||\n (next.sellsOverage &&\n (!Number.isFinite(Number(next.overageRateUsdPer1k)) ||\n !Number.isFinite(Number(next.bandCredits))))\n ) {\n return void setLoadState('error')\n }\n // The ceiling half of the payload is read strictly, the way the\n // resolver reads the stored field: anything that is not a finite\n // number is NO ceiling, never a ceiling of `undefined` that renders\n // as \"$NaN\". The label and bounds fall back to the same constants the\n // route serves them from.\n const capUsd =\n typeof next.capUsd === 'number' && Number.isFinite(next.capUsd)\n ? next.capUsd\n : null\n setState({\n ...next,\n capUsd,\n capLabel:\n typeof next.capLabel === 'string' && next.capLabel\n ? next.capLabel\n : ASSIST_OVERAGE_CAP_CONTROL_LABEL,\n minCapUsd: Number.isFinite(Number(next.minCapUsd))\n ? Number(next.minCapUsd)\n : ASSIST_OVERAGE_CAP_MIN_USD,\n maxCapUsd: Number.isFinite(Number(next.maxCapUsd))\n ? Number(next.maxCapUsd)\n : ASSIST_OVERAGE_CAP_MAX_USD,\n })\n setCapInput(capUsd === null ? '' : String(capUsd))\n setLoadState('loaded')\n })\n // A rejected fetch — offline, a wedged route — never reached the\n // snackbar inside the request, so this is the only place it can become\n // something the card renders.\n .catch(() => {\n if (!cancelled) setLoadState('error')\n })\n return () => {\n cancelled = true\n }\n }, [orgId, user, sendRequest, retryNonce])\n\n const setHardCap = useCallback(\n async (hardCap: boolean) => {\n const rate = state?.overageRateUsdPer1k\n if (!hardCap) {\n // The loading overlay must drop BEFORE the confirm dialog opens — it\n // sits above the dialog and swallows the Confirm click (AGL-535). So\n // the confirm runs first and `queueLoading` only wraps the write.\n const accepted = await confirm({\n title: 'Keep AI assist going past your included credits?',\n description:\n 'The assistant will keep answering once your included credits ' +\n 'are used, and the extra credits will be billed on your monthly ' +\n `invoice${\n rate != null ? ` at $${rate.toFixed(2)} per 1,000` : ''\n }. You can turn the stop back on at any time.`,\n confirmationText: 'Keep going',\n })\n .then(() => true)\n .catch(() => false)\n if (!accepted) return\n }\n\n setBusy(true)\n const dequeue = queueLoading()\n try {\n const outcome = await sendRequest({ action: 'setHardCap', hardCap })\n if (!outcome.ok) return\n enqueueSnackbar(\n hardCap\n ? 'AI assist will stop at your included credits. Nothing past ' +\n 'the band is billed.'\n : 'AI assist keeps going past your included credits' +\n (rate != null\n ? `, billed at $${rate.toFixed(2)} per 1,000.`\n : '.'),\n { variant: 'success', persist: false },\n )\n setRetryNonce((nonce) => nonce + 1)\n } catch {\n enqueueSnackbar('Could not update your AI assist setting', {\n variant: 'warning',\n persist: false,\n })\n } finally {\n dequeue()\n setBusy(false)\n }\n },\n [state, confirm, queueLoading, sendRequest, enqueueSnackbar],\n )\n\n /**\n * Set or clear the ceiling. Neither direction asks first: setting one\n * only ever lowers a bill, and clearing one returns the org to the\n * open-ended overage the switch already told it about. The route owns the\n * range check, so a figure it refuses comes back as its own sentence.\n */\n const setCap = useCallback(\n async (capUsd: number | null) => {\n setBusy(true)\n const dequeue = queueLoading()\n try {\n const outcome = await sendRequest({ action: 'setCap', capUsd })\n if (!outcome.ok) return\n enqueueSnackbar(\n capUsd === null\n ? 'AI assist overage ceiling removed. Extra credits keep billing ' +\n 'at your plan’s rate with no monthly stop.'\n : `AI assist will stop once this month’s overage reaches ` +\n `$${capUsd.toFixed(2)}.`,\n { variant: 'success', persist: false },\n )\n setRetryNonce((nonce) => nonce + 1)\n } catch {\n enqueueSnackbar('Could not update your AI assist ceiling', {\n variant: 'warning',\n persist: false,\n })\n } finally {\n dequeue()\n setBusy(false)\n }\n },\n [queueLoading, sendRequest, enqueueSnackbar],\n )\n\n if (loadState === 'pending') {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Loading your AI assist settings…\n </Typography>\n )\n }\n\n if (loadState === 'error' || !state) {\n return (\n <Alert\n severity=\"warning\"\n action={\n <Button\n size=\"small\"\n color=\"inherit\"\n onClick={() => setRetryNonce((nonce) => nonce + 1)}\n >\n Retry\n </Button>\n }\n >\n We couldn’t load your AI assist settings. Nothing has changed.\n </Alert>\n )\n }\n\n const {\n hardCap,\n capUsd,\n bandCredits,\n overageRateUsdPer1k,\n sellsOverage,\n label,\n capLabel,\n minCapUsd,\n maxCapUsd,\n } = state\n const band = bandCredits == null ? null : Math.round(bandCredits)\n const rate = overageRateUsdPer1k == null ? null : overageRateUsdPer1k.toFixed(2)\n // The ceiling is a bound on overage, and overage exists only while the\n // switch is off on a plan that sells past its band. It stays reachable to\n // CLEAR whenever one is set, so an org that turned the switch on afterwards\n // is not left carrying a figure it cannot remove.\n const offersCeiling = sellsOverage && !hardCap\n const showsCeiling = offersCeiling || capUsd !== null\n\n // A plan that sells no overage has nothing for the switch to stop, so\n // there is nothing to configure. Say that plainly rather than rendering a\n // switch the route would refuse with a 409 — but keep the switch reachable\n // for an org that turned it on and then moved to a plan that sells none,\n // so it can turn it off.\n const nothingToStop = !sellsOverage && !hardCap\n\n return (\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', gap: 1 }}>\n <Chip\n label={\n hardCap\n ? 'Stops at the included band'\n : nothingToStop\n ? band === null\n ? 'No AI assist credits'\n : 'Included credits only'\n : capUsd !== null\n ? `Stops at $${capUsd.toFixed(2)} of overage`\n : 'No stop — extra credits bill'\n }\n size=\"small\"\n color={hardCap || capUsd !== null ? 'primary' : 'default'}\n />\n {band !== null ? (\n <Chip label={`${band.toLocaleString()} credits/mo included`} size=\"small\" />\n ) : null}\n </Stack>\n\n {nothingToStop && band === null ? (\n // No band at all, and nothing ever billed for AI, so neither stop\n // applies — the switch has no band to stop at and the ceiling has no\n // overage to bound. Since AGL-3203 every plan row carries a band, so\n // this is an org whose band was overridden to zero.\n <Alert severity=\"info\">\n Your plan includes no AI credits and is never charged for any, so\n there is no band to stop at, no overage to put a dollar ceiling on,\n and nothing to configure here. Upgrade above, or add{' '}\n {aiAddonName()} on a paid plan, to get a band.\n </Alert>\n ) : nothingToStop ? (\n // Enterprise: a band, and no rate to sell past it at. The assistant\n // stops at the band on its own and nothing is ever billed for it.\n <Alert severity=\"info\">\n Your plan includes <strong>{band?.toLocaleString()}</strong> AI assist\n credits a month and sells none past them: the assistant stops at the\n included band and you are <strong>never charged</strong> for AI\n assist, so there is nothing to switch.\n </Alert>\n ) : (\n <>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {sellsOverage\n ? `Your plan includes ${band?.toLocaleString()} AI assist credits ` +\n `a month. Past that, the assistant keeps answering and the ` +\n `extra credits are billed at $${rate} per 1,000 on your ` +\n `monthly invoice.`\n : `Your plan no longer sells AI assist credits past its band, ` +\n `so this switch is not doing anything. You can safely turn ` +\n `it off.`}\n </Typography>\n {sellsOverage ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n If you would rather the assistant stopped at the included band,\n turn this on. This is optional — most people leave it off.\n </Typography>\n ) : null}\n <Stack spacing={0.5}>\n <FormControlLabel\n control={\n <Switch\n checked={hardCap}\n disabled={!canManage || busy || (!hardCap && !sellsOverage)}\n onChange={(event) => void setHardCap(event.target.checked)}\n />\n }\n label={label}\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {hardCap\n ? `On: the assistant stops once your included credits are ` +\n `used, and you are never billed for AI assist.` +\n (sellsOverage\n ? ` Turn it off to keep going at $${rate} per 1,000 credits.`\n : '')\n : `Off: extra credits past the included band are billed at ` +\n `$${rate} per 1,000.`}\n </Typography>\n </Stack>\n\n {showsCeiling ? (\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {offersCeiling\n ? capUsd === null\n ? 'Or keep going, but only so far: set a monthly ceiling ' +\n 'on the extra credits you will pay for. The assistant ' +\n 'stops for the rest of the month once this month’s ' +\n 'overage reaches it. Optional, and separate from the ' +\n 'switch above.'\n : `The assistant stops for the rest of the month once ` +\n `this month’s overage reaches $${capUsd.toFixed(2)}. ` +\n `Raise, lower or remove the ceiling at any time.`\n : `A ceiling of $${(capUsd ?? 0).toFixed(2)} is set, but ` +\n `with the assistant stopping at the included band there ` +\n `is no overage for it to bound. You can safely remove it.`}\n </Typography>\n <Stack\n direction=\"row\"\n spacing={2}\n sx={{ alignItems: 'flex-start', flexWrap: 'wrap', rowGap: 2 }}\n >\n {offersCeiling ? (\n <>\n <TextField\n label={capLabel}\n type=\"number\"\n size=\"small\"\n value={capInput}\n disabled={!canManage || busy}\n onChange={(event) => setCapInput(event.target.value)}\n slotProps={{\n htmlInput: { min: minCapUsd, max: maxCapUsd, step: 1 },\n }}\n helperText={`In USD, between $${minCapUsd} and $${maxCapUsd.toLocaleString()}.`}\n />\n <Button\n variant=\"outlined\"\n disabled={!canManage || busy || capInput.trim() === ''}\n onClick={() => void setCap(Number(capInput))}\n >\n {capUsd === null ? 'Set a monthly ceiling' : 'Save ceiling'}\n </Button>\n </>\n ) : null}\n {capUsd !== null ? (\n <Button\n variant=\"text\"\n color=\"warning\"\n disabled={!canManage || busy}\n onClick={() => void setCap(null)}\n >\n Remove ceiling\n </Button>\n ) : null}\n </Stack>\n </Stack>\n ) : null}\n </>\n )}\n\n {!canManage ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n You need the Manage billing permission to change this.\n </Typography>\n ) : null}\n </Stack>\n )\n}\n"],"names":["CardDisplay","useConfirmationContext","useLoading","pluginDocsHelp","useSnackbar","Alert","Button","Chip","FormControlLabel","Stack","Switch","TextField","Typography","useCallback","useEffect","useState","useUser","authorizedFetch","aiAddonName","ASSIST_OVERAGE_CAP_CONTROL_LABEL","ASSIST_OVERAGE_CAP_MAX_USD","ASSIST_OVERAGE_CAP_MIN_USD","BillingAssistOverageCard","props","div","id","header","subheader","help","anchor","excerpt","contentGutterX","contentGutterY","BillingAssistOverageControls","orgId","canManage","data","user","enqueueSnackbar","queueLoading","confirm","state","setState","busy","setBusy","capInput","setCapInput","loadState","setLoadState","retryNonce","setRetryNonce","sendRequest","body","response","method","headers","JSON","stringify","payload","json","catch","ok","error","variant","persist","cancelled","action","then","outcome","next","hardCap","sellsOverage","Number","isFinite","overageRateUsdPer1k","bandCredits","capUsd","capLabel","minCapUsd","maxCapUsd","String","setHardCap","rate","accepted","title","description","toFixed","confirmationText","dequeue","nonce","setCap","color","severity","size","onClick","label","band","Math","round","offersCeiling","showsCeiling","nothingToStop","spacing","direction","sx","flexWrap","gap","toLocaleString","strong","control","checked","disabled","onChange","event","target","alignItems","rowGap","type","value","slotProps","htmlInput","min","max","step","helperText","trim"],"mappings":"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"}
@@ -22,3 +22,8 @@
22
22
  export declare const AI_PLUGIN_ID = "ai";
23
23
  /** The `pluginSettings/ai` document's schema id — the same string. */
24
24
  export declare const AI_PLUGIN_SETTINGS_ID = "ai";
25
+ /**
26
+ * The plugin's meter in the monthly usage sweep, as its `usageAxes`
27
+ * declaration names it and its server declarations register it.
28
+ */
29
+ export declare const AI_USAGE_METER_ID = "assist";
@@ -19,5 +19,9 @@
19
19
  * `plugins.config.json` and `FIRST_PARTY_PLUGINS`.
20
20
  */ export const AI_PLUGIN_ID = 'ai';
21
21
  /** The `pluginSettings/ai` document's schema id — the same string. */ export const AI_PLUGIN_SETTINGS_ID = AI_PLUGIN_ID;
22
+ /**
23
+ * The plugin's meter in the monthly usage sweep, as its `usageAxes`
24
+ * declaration names it and its server declarations register it.
25
+ */ export const AI_USAGE_METER_ID = 'assist';
22
26
 
23
27
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/constants.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\n/**\n * The plugin's stable id — persisted in `org.enabledPlugins`, the document\n * id under `pluginSettings`, and the owner every registry records. Matches\n * `plugins.config.json` and `FIRST_PARTY_PLUGINS`.\n */\nexport const AI_PLUGIN_ID = 'ai'\n\n/** The `pluginSettings/ai` document's schema id — the same string. */\nexport const AI_PLUGIN_SETTINGS_ID = AI_PLUGIN_ID\n"],"names":["AI_PLUGIN_ID","AI_PLUGIN_SETTINGS_ID"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;CAIC,GACD,OAAO,MAAMA,eAAe,KAAI;AAEhC,oEAAoE,GACpE,OAAO,MAAMC,wBAAwBD,aAAY"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/constants.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\n/**\n * The plugin's stable id — persisted in `org.enabledPlugins`, the document\n * id under `pluginSettings`, and the owner every registry records. Matches\n * `plugins.config.json` and `FIRST_PARTY_PLUGINS`.\n */\nexport const AI_PLUGIN_ID = 'ai'\n\n/** The `pluginSettings/ai` document's schema id — the same string. */\nexport const AI_PLUGIN_SETTINGS_ID = AI_PLUGIN_ID\n\n/**\n * The plugin's meter in the monthly usage sweep, as its `usageAxes`\n * declaration names it and its server declarations register it.\n */\nexport const AI_USAGE_METER_ID = 'assist'\n"],"names":["AI_PLUGIN_ID","AI_PLUGIN_SETTINGS_ID","AI_USAGE_METER_ID"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;CAIC,GACD,OAAO,MAAMA,eAAe,KAAI;AAEhC,oEAAoE,GACpE,OAAO,MAAMC,wBAAwBD,aAAY;AAEjD;;;CAGC,GACD,OAAO,MAAME,oBAAoB,SAAQ"}
@@ -74,6 +74,7 @@ import { registerAiActivityActions } from "./activity/ai-activity-actions.js";
74
74
  {
75
75
  key: 'ai-assist',
76
76
  label: 'AI assist',
77
+ customerName: 'AI assist',
77
78
  staffBypass: true,
78
79
  notice: {
79
80
  title: 'AI assist is temporarily unavailable',
@@ -91,6 +92,7 @@ import { registerAiActivityActions } from "./activity/ai-activity-actions.js";
91
92
  {
92
93
  key: 'ai-generate',
93
94
  label: 'AI generation',
95
+ customerName: 'AI generation',
94
96
  staffBypass: true,
95
97
  notice: {
96
98
  title: 'AI generation is temporarily unavailable',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/declarations.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\n// Named modules rather than the `@aglyn/aglyn` barrel: both apps load this\n// on the server at boot, and the full barrel carries client-only contexts\n// (AGL-405).\nimport { AI_ADDON_CREDITS_PER_MONTH } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { registerPluginConfigSchema } from '@aglyn/aglyn/plugin-manager/plugin-config'\nimport {\n registerPluginEntitlements,\n type PluginEntitlementRegistration,\n} from '@aglyn/aglyn/plugin-manager/plugin-entitlements'\nimport { AI_PLUGIN_ID } from './constants'\nimport { AI_ORG_PERMISSIONS } from './model/ai-permissions'\nimport { AI_CONFIG_SCHEMA } from './plugin-config'\nimport { registerAiActivityActions } from './activity/ai-activity-actions'\n\n/**\n * The plugin's DECLARATIONS (AGL-2939): what core must know about this\n * plugin before any of its surfaces load — its billing and access keys,\n * the AI permission keys it adds to the org catalog (AGL-2984), its\n * activity codes, its settings schema. Loaded eagerly by both apps\n * through the generated declarations manifest, at boot on the server and\n * with the plugin loader on the client, so a core billing route folds the\n * add-on and the staff lockdown page lists the levers whether or not a\n * request has touched the plugin yet. Light by construction: nothing here\n * reaches a provider, Firestore or React.\n *\n * The plan tables keep the per-plan defaults for `aiGenerative` and\n * `aiAssist`, and `PLAN_PRICING` keeps the add-on's price, so this names\n * keys and bands and restates no price.\n *\n * `ai-assist` and `ai-generate` are separate levers because the two doors\n * spend at different rates and an incident on one need not stop the other\n * (AGL-2903). Both grant the staff bypass: a provider incident is verified\n * recovered by staff making one real call, not by lifting the lock and\n * watching customers find out.\n */\nexport const AI_PLUGIN_ENTITLEMENTS: PluginEntitlementRegistration = {\n pluginId: AI_PLUGIN_ID,\n seatAddons: [\n {\n key: 'aiAddon',\n label: 'AI add-on',\n maxUnits: 1,\n quota: {\n key: 'assistCreditsPerMonth',\n perUnitByPlan: AI_ADDON_CREDITS_PER_MONTH,\n },\n features: ['aiGenerative', 'aiAssist'],\n },\n ],\n features: [\n { key: 'aiAssist', label: 'AI assist' },\n { key: 'aiGenerative', label: 'AI generation' },\n ],\n lockdownFeatures: [\n {\n key: 'ai-assist',\n label: 'AI assist',\n staffBypass: true,\n notice: {\n title: 'AI assist is temporarily unavailable',\n body: 'AI assist is temporarily unavailable. Your content is unaffected — please try again shortly.',\n },\n // Gated even while the route 501s without a provider key — the switch\n // predates the key on purpose.\n apiPaths: { exact: ['ai/assist', 'assist/chat'] },\n },\n {\n key: 'ai-generate',\n label: 'AI generation',\n staffBypass: true,\n notice: {\n title: 'AI generation is temporarily unavailable',\n body: 'Generating sections, pages and automations with AI is temporarily unavailable. Everything already built is unaffected — please try again shortly.',\n },\n // `ai/seo` (AGL-2910) applies a finished audit's drafts; it spends\n // nothing, and it is still a generative door the switch stops.\n apiPaths: { prefixes: ['ai/generate', 'ai/jobs', 'ai/seo'] },\n },\n ],\n orgPermissions: AI_ORG_PERMISSIONS,\n}\n\nlet declared = false\n\n/**\n * Registers everything above, and the activity codes. Idempotent: the\n * declarations manifest runs it once per process, and a surface's register\n * fn may call it again. The codes are a call rather than an import made\n * for what the module does as it loads, because the browser's manifest\n * reaches that module through nothing else, and a bundler honoring this\n * package's `sideEffects` deletes such an import (AGL-3025).\n */\nexport function registerAiDeclarations(): void {\n if (declared) return\n declared = true\n registerPluginEntitlements(AI_PLUGIN_ENTITLEMENTS)\n registerPluginConfigSchema(AI_CONFIG_SCHEMA)\n registerAiActivityActions()\n}\n\nregisterAiDeclarations()\n"],"names":["AI_ADDON_CREDITS_PER_MONTH","registerPluginConfigSchema","registerPluginEntitlements","AI_PLUGIN_ID","AI_ORG_PERMISSIONS","AI_CONFIG_SCHEMA","registerAiActivityActions","AI_PLUGIN_ENTITLEMENTS","pluginId","seatAddons","key","label","maxUnits","quota","perUnitByPlan","features","lockdownFeatures","staffBypass","notice","title","body","apiPaths","exact","prefixes","orgPermissions","declared","registerAiDeclarations"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,2EAA2E;AAC3E,0EAA0E;AAC1E,aAAa;AACb,SAASA,0BAA0B,QAAQ,2CAA0C;AACrF,SAASC,0BAA0B,QAAQ,4CAA2C;AACtF,SACEC,0BAA0B,QAErB,kDAAiD;AACxD,SAASC,YAAY,QAAQ,iBAAa;AAC1C,SAASC,kBAAkB,QAAQ,4BAAwB;AAC3D,SAASC,gBAAgB,QAAQ,qBAAiB;AAClD,SAASC,yBAAyB,QAAQ,oCAAgC;AAE1E;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,MAAMC,yBAAwD;IACnEC,UAAUL;IACVM,YAAY;QACV;YACEC,KAAK;YACLC,OAAO;YACPC,UAAU;YACVC,OAAO;gBACLH,KAAK;gBACLI,eAAed;YACjB;YACAe,UAAU;gBAAC;gBAAgB;aAAW;QACxC;KACD;IACDA,UAAU;QACR;YAAEL,KAAK;YAAYC,OAAO;QAAY;QACtC;YAAED,KAAK;YAAgBC,OAAO;QAAgB;KAC/C;IACDK,kBAAkB;QAChB;YACEN,KAAK;YACLC,OAAO;YACPM,aAAa;YACbC,QAAQ;gBACNC,OAAO;gBACPC,MAAM;YACR;YACA,sEAAsE;YACtE,+BAA+B;YAC/BC,UAAU;gBAAEC,OAAO;oBAAC;oBAAa;iBAAc;YAAC;QAClD;QACA;YACEZ,KAAK;YACLC,OAAO;YACPM,aAAa;YACbC,QAAQ;gBACNC,OAAO;gBACPC,MAAM;YACR;YACA,mEAAmE;YACnE,+DAA+D;YAC/DC,UAAU;gBAAEE,UAAU;oBAAC;oBAAe;oBAAW;iBAAS;YAAC;QAC7D;KACD;IACDC,gBAAgBpB;AAClB,EAAC;AAED,IAAIqB,WAAW;AAEf;;;;;;;CAOC,GACD,OAAO,SAASC;IACd,IAAID,UAAU;IACdA,WAAW;IACXvB,2BAA2BK;IAC3BN,2BAA2BI;IAC3BC;AACF;AAEAoB"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/declarations.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\n// Named modules rather than the `@aglyn/aglyn` barrel: both apps load this\n// on the server at boot, and the full barrel carries client-only contexts\n// (AGL-405).\nimport { AI_ADDON_CREDITS_PER_MONTH } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { registerPluginConfigSchema } from '@aglyn/aglyn/plugin-manager/plugin-config'\nimport {\n registerPluginEntitlements,\n type PluginEntitlementRegistration,\n} from '@aglyn/aglyn/plugin-manager/plugin-entitlements'\nimport { AI_PLUGIN_ID } from './constants'\nimport { AI_ORG_PERMISSIONS } from './model/ai-permissions'\nimport { AI_CONFIG_SCHEMA } from './plugin-config'\nimport { registerAiActivityActions } from './activity/ai-activity-actions'\n\n/**\n * The plugin's DECLARATIONS (AGL-2939): what core must know about this\n * plugin before any of its surfaces load — its billing and access keys,\n * the AI permission keys it adds to the org catalog (AGL-2984), its\n * activity codes, its settings schema. Loaded eagerly by both apps\n * through the generated declarations manifest, at boot on the server and\n * with the plugin loader on the client, so a core billing route folds the\n * add-on and the staff lockdown page lists the levers whether or not a\n * request has touched the plugin yet. Light by construction: nothing here\n * reaches a provider, Firestore or React.\n *\n * The plan tables keep the per-plan defaults for `aiGenerative` and\n * `aiAssist`, and `PLAN_PRICING` keeps the add-on's price, so this names\n * keys and bands and restates no price.\n *\n * `ai-assist` and `ai-generate` are separate levers because the two doors\n * spend at different rates and an incident on one need not stop the other\n * (AGL-2903). Both grant the staff bypass: a provider incident is verified\n * recovered by staff making one real call, not by lifting the lock and\n * watching customers find out.\n */\nexport const AI_PLUGIN_ENTITLEMENTS: PluginEntitlementRegistration = {\n pluginId: AI_PLUGIN_ID,\n seatAddons: [\n {\n key: 'aiAddon',\n label: 'AI add-on',\n maxUnits: 1,\n quota: {\n key: 'assistCreditsPerMonth',\n perUnitByPlan: AI_ADDON_CREDITS_PER_MONTH,\n },\n features: ['aiGenerative', 'aiAssist'],\n },\n ],\n features: [\n { key: 'aiAssist', label: 'AI assist' },\n { key: 'aiGenerative', label: 'AI generation' },\n ],\n lockdownFeatures: [\n {\n key: 'ai-assist',\n label: 'AI assist',\n customerName: 'AI assist',\n staffBypass: true,\n notice: {\n title: 'AI assist is temporarily unavailable',\n body: 'AI assist is temporarily unavailable. Your content is unaffected — please try again shortly.',\n },\n // Gated even while the route 501s without a provider key — the switch\n // predates the key on purpose.\n apiPaths: { exact: ['ai/assist', 'assist/chat'] },\n },\n {\n key: 'ai-generate',\n label: 'AI generation',\n customerName: 'AI generation',\n staffBypass: true,\n notice: {\n title: 'AI generation is temporarily unavailable',\n body: 'Generating sections, pages and automations with AI is temporarily unavailable. Everything already built is unaffected — please try again shortly.',\n },\n // `ai/seo` (AGL-2910) applies a finished audit's drafts; it spends\n // nothing, and it is still a generative door the switch stops.\n apiPaths: { prefixes: ['ai/generate', 'ai/jobs', 'ai/seo'] },\n },\n ],\n orgPermissions: AI_ORG_PERMISSIONS,\n}\n\nlet declared = false\n\n/**\n * Registers everything above, and the activity codes. Idempotent: the\n * declarations manifest runs it once per process, and a surface's register\n * fn may call it again. The codes are a call rather than an import made\n * for what the module does as it loads, because the browser's manifest\n * reaches that module through nothing else, and a bundler honoring this\n * package's `sideEffects` deletes such an import (AGL-3025).\n */\nexport function registerAiDeclarations(): void {\n if (declared) return\n declared = true\n registerPluginEntitlements(AI_PLUGIN_ENTITLEMENTS)\n registerPluginConfigSchema(AI_CONFIG_SCHEMA)\n registerAiActivityActions()\n}\n\nregisterAiDeclarations()\n"],"names":["AI_ADDON_CREDITS_PER_MONTH","registerPluginConfigSchema","registerPluginEntitlements","AI_PLUGIN_ID","AI_ORG_PERMISSIONS","AI_CONFIG_SCHEMA","registerAiActivityActions","AI_PLUGIN_ENTITLEMENTS","pluginId","seatAddons","key","label","maxUnits","quota","perUnitByPlan","features","lockdownFeatures","customerName","staffBypass","notice","title","body","apiPaths","exact","prefixes","orgPermissions","declared","registerAiDeclarations"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,2EAA2E;AAC3E,0EAA0E;AAC1E,aAAa;AACb,SAASA,0BAA0B,QAAQ,2CAA0C;AACrF,SAASC,0BAA0B,QAAQ,4CAA2C;AACtF,SACEC,0BAA0B,QAErB,kDAAiD;AACxD,SAASC,YAAY,QAAQ,iBAAa;AAC1C,SAASC,kBAAkB,QAAQ,4BAAwB;AAC3D,SAASC,gBAAgB,QAAQ,qBAAiB;AAClD,SAASC,yBAAyB,QAAQ,oCAAgC;AAE1E;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,MAAMC,yBAAwD;IACnEC,UAAUL;IACVM,YAAY;QACV;YACEC,KAAK;YACLC,OAAO;YACPC,UAAU;YACVC,OAAO;gBACLH,KAAK;gBACLI,eAAed;YACjB;YACAe,UAAU;gBAAC;gBAAgB;aAAW;QACxC;KACD;IACDA,UAAU;QACR;YAAEL,KAAK;YAAYC,OAAO;QAAY;QACtC;YAAED,KAAK;YAAgBC,OAAO;QAAgB;KAC/C;IACDK,kBAAkB;QAChB;YACEN,KAAK;YACLC,OAAO;YACPM,cAAc;YACdC,aAAa;YACbC,QAAQ;gBACNC,OAAO;gBACPC,MAAM;YACR;YACA,sEAAsE;YACtE,+BAA+B;YAC/BC,UAAU;gBAAEC,OAAO;oBAAC;oBAAa;iBAAc;YAAC;QAClD;QACA;YACEb,KAAK;YACLC,OAAO;YACPM,cAAc;YACdC,aAAa;YACbC,QAAQ;gBACNC,OAAO;gBACPC,MAAM;YACR;YACA,mEAAmE;YACnE,+DAA+D;YAC/DC,UAAU;gBAAEE,UAAU;oBAAC;oBAAe;oBAAW;iBAAS;YAAC;QAC7D;KACD;IACDC,gBAAgBrB;AAClB,EAAC;AAED,IAAIsB,WAAW;AAEf;;;;;;;CAOC,GACD,OAAO,SAASC;IACd,IAAID,UAAU;IACdA,WAAW;IACXxB,2BAA2BK;IAC3BN,2BAA2BI;IAC3BC;AACF;AAEAqB"}