@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/billing/ai-overage-standing.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 * A WORKSPACE'S OVERAGE STANDING (AGL-3011) — the pure half.\n *\n * AI credits past a plan's included band are sold on every paid plan. What\n * bounds them is not one number but four facts about the workspace, and this\n * module is the one derivation of all four: whether a card can be charged,\n * whether accrual is paused, how much overage this month may reach, and how\n * much may be outstanding at once.\n *\n * Nothing here reads Firestore, calls Stripe or knows what a month cost.\n * `ai-overage-ledger.ts` holds the documents and `ai-overage-charge.ts` the\n * charging; the gate and the staff card both resolve their figures here, so\n * the ceiling a customer is refused at is the ceiling the card displays.\n *\n * ## The bound, stated once\n *\n * A workspace may leave at most `AI_OVERAGE_UNPAID_LIMIT_USD` of overage\n * unpaid. That is enforced by REFUSING, not by charging — the gate compares\n * what a month accrued against what it paid, inside the reservation\n * transaction, every request. So the bound holds when a charge is late, when\n * Stripe is down, and when the charge path never ran at all. Charging at\n * `AI_OVERAGE_THRESHOLD_USD` exists to keep a workspace working, by settling\n * the balance before it reaches the bound; it is not what makes the bound\n * true.\n */\n\nimport { resolveAssistOverageCapUsd } from '@aglyn/aglyn/app-utils/assist-credits'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\n\n/** The billing fields of an org, as every resolver in this folder takes it. */\nexport type AiOverageOrg = Partial<AglynOrgBilling> | null | undefined\n\n/**\n * The unbilled overage at which the card on file is charged.\n *\n * Fifty times Stripe's minimum charge, so no charge is refused for being too\n * small; low enough that what is outstanding between charges stays near the\n * bound below; high enough that per-invoice fees stay a few percent.\n */\nexport const AI_OVERAGE_THRESHOLD_USD = 25\n\n/**\n * The most overage a workspace may have accrued and not paid for.\n *\n * Twice the threshold, so one charge in flight plus one accruing is inside\n * it, and a workspace doing ordinary work never meets it. Meeting it means\n * the charges are not settling, and that is exactly when accrual must stop.\n */\nexport const AI_OVERAGE_UNPAID_LIMIT_USD = 50\n\n/**\n * The monthly ceiling, by step. It rises one step for each later calendar\n * month in which the workspace paid an invoice with a real charge.\n *\n * Months, not invoices: every paid workspace pays an invoice the moment it\n * subscribes, and threshold invoices can arrive several times a day, so\n * counting invoices would reach the top step in an afternoon. Counting\n * months takes at least two, which covers much of the window in which a\n * stolen card is reported.\n */\nexport const AI_OVERAGE_CEILING_LADDER_USD = [50, 150, 500] as const\n\n/** The top index of the ladder; `step` is clamped to it. */\nexport const AI_OVERAGE_MAX_STEP = AI_OVERAGE_CEILING_LADDER_USD.length - 1\n\n/**\n * The smallest amount worth an invoice — Stripe's own minimum charge. A\n * close-out remainder under it is carried, not billed: an invoice that\n * cannot be paid is a document that dunning chases forever.\n */\nexport const AI_OVERAGE_MIN_CHARGE_USD = 0.5\n\n/** Bounds on a ceiling staff may write by hand, the customer ceiling's. */\nexport const AI_OVERAGE_STAFF_CEILING_MIN_USD = 1\nexport const AI_OVERAGE_STAFF_CEILING_MAX_USD = 100_000\n\n/** Why overage is paused. Each one is payment trust dropping, differently. */\nexport type AiOveragePauseReason =\n | 'charge_failed'\n | 'past_due'\n | 'dispute'\n | 'staff'\n\n/** A staff ceiling that outranks the ladder. */\nexport interface AiOverageStaffOverride {\n ceilingUsd: number\n reason: string\n setBy: string\n setAt: string | null\n /** ISO instant, or `null` to stand until staff clear it. */\n expiresAt: string | null\n}\n\n/** Accrual stopped, and why. */\nexport interface AiOveragePause {\n reason: AiOveragePauseReason\n /** The invoice that failed, when a charge is what stopped it. */\n invoiceId: string | null\n since: string | null\n}\n\n/**\n * `orgs/{orgId}/aiBilling/standing`, normalized.\n *\n * Written only by the server, read by the gate on every request past the\n * band. The Firestore rules deny every client write and allow the same reads\n * as `aiAllotments`.\n */\nexport interface AiOverageStanding {\n /**\n * The workspace's default payment method type, `null` when it has none,\n * and `undefined` when nobody has looked yet.\n *\n * THREE states, not two, and the third is the important one. A workspace\n * that has been paying for a year has no standing document until something\n * writes one, and reading that absence as \"no card\" would refuse a paying\n * customer's work over a document we never wrote. So \"never observed\"\n * declines to answer the card question and lets the other guards bind —\n * the unpaid bound does not rest on the card check, which is why it can\n * afford to abstain. A pre-cutover backfill is what turns `undefined` into\n * a real answer for existing workspaces.\n */\n paymentMethodType: string | null | undefined\n /** The first month (`YYYY-MM`) an invoice of this workspace's was paid. */\n firstPaidMonth: string | null\n /**\n * Later months with a paid invoice, ascending, the most recent 12 kept. A\n * month is dropped again if an AI charge of that month was still unpaid\n * when the month ended.\n */\n qualifyingMonths: string[]\n staffOverride: AiOverageStaffOverride | null\n pause: AiOveragePause | null\n lastDisputeAt: string | null\n}\n\n/** The standing of a workspace nothing has written a document for. */\nexport const EMPTY_AI_OVERAGE_STANDING: AiOverageStanding = {\n paymentMethodType: undefined,\n firstPaidMonth: null,\n qualifyingMonths: [],\n staffOverride: null,\n pause: null,\n lastDisputeAt: null,\n}\n\n/** How many qualifying months the document keeps. */\nexport const AI_OVERAGE_QUALIFYING_MONTHS_KEPT = 12\n\nconst MONTH = /^\\d{4}-\\d{2}$/\n\nfunction readString(value: unknown): string | null {\n return typeof value === 'string' && value.trim() ? value.trim() : null\n}\n\nfunction readInstant(value: unknown): string | null {\n if (typeof value === 'string' && value.trim()) return value.trim()\n // A Firestore Timestamp, as the Admin SDK hands one back.\n const toDate = (value as { toDate?: () => Date } | null)?.toDate\n if (typeof toDate === 'function') {\n const date = toDate.call(value)\n return date instanceof Date && !Number.isNaN(date.getTime())\n ? date.toISOString()\n : null\n }\n if (value instanceof Date && !Number.isNaN(value.getTime())) {\n return value.toISOString()\n }\n return null\n}\n\nconst PAUSE_REASONS: readonly AiOveragePauseReason[] = [\n 'charge_failed',\n 'past_due',\n 'dispute',\n 'staff',\n]\n\n/**\n * Reads the stored document into the shape above, strictly.\n *\n * Strictly, because every field here decides whether a workspace may spend\n * money: a string `\"true\"` that read as a boolean, or a `NaN` ceiling that\n * every comparison passes, is a guard that is not there. Anything that does\n * not narrow is the empty standing's value for that field — which is the\n * conservative answer in each case except the card, where the deliberate\n * answer is \"nobody has looked\".\n */\nexport function readAiOverageStanding(\n data: Record<string, unknown> | null | undefined,\n): AiOverageStanding {\n if (!data || typeof data !== 'object') return EMPTY_AI_OVERAGE_STANDING\n const rawType = data['paymentMethodType']\n const paymentMethodType =\n rawType === undefined\n ? undefined\n : typeof rawType === 'string' && rawType.trim()\n ? rawType.trim()\n : null\n const firstPaidMonth = readString(data['firstPaidMonth'])\n const months = Array.isArray(data['qualifyingMonths'])\n ? (data['qualifyingMonths'] as unknown[])\n .filter((month): month is string => typeof month === 'string' && MONTH.test(month))\n .filter((month, index, all) => all.indexOf(month) === index)\n .sort()\n : []\n const override = data['staffOverride'] as Record<string, unknown> | null | undefined\n const ceilingUsd = Number(override?.['ceilingUsd'])\n const staffOverride: AiOverageStaffOverride | null =\n override && Number.isFinite(ceilingUsd) && ceilingUsd > 0\n ? {\n ceilingUsd,\n reason: readString(override['reason']) ?? '',\n setBy: readString(override['setBy']) ?? '',\n setAt: readInstant(override['setAt']),\n expiresAt: readInstant(override['expiresAt']),\n }\n : null\n const rawPause = data['pause'] as Record<string, unknown> | null | undefined\n const pauseReason = readString(rawPause?.['reason']) as AiOveragePauseReason | null\n const pause: AiOveragePause | null =\n rawPause && pauseReason && PAUSE_REASONS.includes(pauseReason)\n ? {\n reason: pauseReason,\n invoiceId: readString(rawPause['invoiceId']),\n since: readInstant(rawPause['since']),\n }\n : null\n return {\n paymentMethodType,\n firstPaidMonth: firstPaidMonth && MONTH.test(firstPaidMonth) ? firstPaidMonth : null,\n qualifyingMonths: months,\n staffOverride,\n pause,\n lastDisputeAt: readInstant(data['lastDisputeAt']),\n }\n}\n\n/**\n * The workspace's step on the ladder — one per qualifying month, clamped.\n *\n * Derived rather than stored, so a stored `step` cannot disagree with the\n * months it was supposed to have been derived from. The document carries a\n * mirrored copy for readers that want one figure, and this is what writes it.\n */\nexport function aiOverageStep(standing: AiOverageStanding): number {\n return Math.min(AI_OVERAGE_MAX_STEP, standing.qualifyingMonths.length)\n}\n\n/** Is a staff override still standing at this instant? */\nexport function aiOverageOverrideApplies(\n override: AiOverageStaffOverride | null,\n now: Date,\n): boolean {\n if (!override) return false\n if (!override.expiresAt) return true\n const expires = Date.parse(override.expiresAt)\n // An unparseable expiry expires the override rather than extending it: a\n // ceiling that was meant to end and cannot say when must not outlive its\n // reason because a timestamp did not read back.\n if (!Number.isFinite(expires)) return false\n return now.getTime() < expires\n}\n\n/**\n * Aglyn's own ceiling on this month's overage, in retail dollars.\n *\n * The staff override when one stands, and the ladder otherwise. This is the\n * platform's limit; the customer's own `capUsd` is a separate figure and the\n * lower of the two binds — see `aiOverageEffectiveCeilingUsd`.\n */\nexport function aiOverageStandingCeilingUsd(\n standing: AiOverageStanding,\n now: Date,\n): number {\n if (aiOverageOverrideApplies(standing.staffOverride, now)) {\n return standing.staffOverride!.ceilingUsd\n }\n return AI_OVERAGE_CEILING_LADDER_USD[aiOverageStep(standing)]\n}\n\n/**\n * The ceiling that actually binds: the lower of the workspace's own and\n * Aglyn's. For display and for staff; the gate consults the two separately,\n * because the customer's ceiling and the platform's need different words.\n */\nexport function aiOverageEffectiveCeilingUsd(\n org: AiOverageOrg,\n standing: AiOverageStanding,\n now: Date,\n): number {\n const own = resolveAssistOverageCapUsd(org)\n const ours = aiOverageStandingCeilingUsd(standing, now)\n return own === null ? ours : Math.min(own, ours)\n}\n\n/**\n * Whether a card is the default payment method, as far as anyone has looked.\n *\n * `undefined` when nobody has, and callers must handle that third answer\n * rather than coercing it — see `paymentMethodType`.\n */\nexport function aiOverageCardOnFile(\n standing: AiOverageStanding,\n): boolean | undefined {\n if (standing.paymentMethodType === undefined) return undefined\n return standing.paymentMethodType === 'card'\n}\n\n/**\n * The month a qualifying step is recorded under, from a paid invoice.\n *\n * `null` when the payment is not evidence that money moved or that time has\n * passed: an invoice marked paid out of band is a staff action, a zero\n * payment is a credit, and the first paid month is the baseline the ladder\n * counts FROM rather than a step on it.\n */\nexport function aiOverageQualifyingMonth(\n standing: AiOverageStanding,\n payment: { month: string; amountPaidCents: number; paidOutOfBand: boolean },\n): string | null {\n if (!MONTH.test(payment.month)) return null\n if (payment.paidOutOfBand) return null\n if (!(payment.amountPaidCents > 0)) return null\n if (!standing.firstPaidMonth) return null\n if (!(payment.month > standing.firstPaidMonth)) return null\n return payment.month\n}\n"],"names":["resolveAssistOverageCapUsd","AI_OVERAGE_THRESHOLD_USD","AI_OVERAGE_UNPAID_LIMIT_USD","AI_OVERAGE_CEILING_LADDER_USD","AI_OVERAGE_MAX_STEP","length","AI_OVERAGE_MIN_CHARGE_USD","AI_OVERAGE_STAFF_CEILING_MIN_USD","AI_OVERAGE_STAFF_CEILING_MAX_USD","EMPTY_AI_OVERAGE_STANDING","paymentMethodType","undefined","firstPaidMonth","qualifyingMonths","staffOverride","pause","lastDisputeAt","AI_OVERAGE_QUALIFYING_MONTHS_KEPT","MONTH","readString","value","trim","readInstant","toDate","date","call","Date","Number","isNaN","getTime","toISOString","PAUSE_REASONS","readAiOverageStanding","data","rawType","months","Array","isArray","filter","month","test","index","all","indexOf","sort","override","ceilingUsd","isFinite","reason","setBy","setAt","expiresAt","rawPause","pauseReason","includes","invoiceId","since","aiOverageStep","standing","Math","min","aiOverageOverrideApplies","now","expires","parse","aiOverageStandingCeilingUsd","aiOverageEffectiveCeilingUsd","org","own","ours","aiOverageCardOnFile","aiOverageQualifyingMonth","payment","paidOutOfBand","amountPaidCents"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/billing/ai-overage-standing.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 * A WORKSPACE'S OVERAGE STANDING (AGL-3011) — the pure half.\n *\n * AI credits past a plan's included band are sold on every paid plan. What\n * bounds them is not one number but four facts about the workspace, and this\n * module is the one derivation of all four: whether a card can be charged,\n * whether accrual is paused, how much overage this month may reach, and how\n * much may be outstanding at once.\n *\n * Nothing here reads Firestore, calls Stripe or knows what a month cost.\n * `ai-overage-ledger.ts` holds the documents and `ai-overage-charge.ts` the\n * charging; the gate and the staff card both resolve their figures here, so\n * the ceiling a customer is refused at is the ceiling the card displays.\n *\n * ## The bound, stated once\n *\n * A workspace may leave at most `AI_OVERAGE_UNPAID_LIMIT_USD` of overage\n * unpaid. That is enforced by REFUSING, not by charging — the gate compares\n * what a month accrued against what it paid, inside the reservation\n * transaction, every request. So the bound holds when a charge is late, when\n * Stripe is down, and when the charge path never ran at all. Charging at\n * `AI_OVERAGE_THRESHOLD_USD` exists to keep a workspace working, by settling\n * the balance before it reaches the bound; it is not what makes the bound\n * true.\n */\n\nimport { resolveAssistOverageCapUsd } from '../usage/assist-credits'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\n\n/** The billing fields of an org, as every resolver in this folder takes it. */\nexport type AiOverageOrg = Partial<AglynOrgBilling> | null | undefined\n\n/**\n * The unbilled overage at which the card on file is charged.\n *\n * Fifty times Stripe's minimum charge, so no charge is refused for being too\n * small; low enough that what is outstanding between charges stays near the\n * bound below; high enough that per-invoice fees stay a few percent.\n */\nexport const AI_OVERAGE_THRESHOLD_USD = 25\n\n/**\n * The most overage a workspace may have accrued and not paid for.\n *\n * Twice the threshold, so one charge in flight plus one accruing is inside\n * it, and a workspace doing ordinary work never meets it. Meeting it means\n * the charges are not settling, and that is exactly when accrual must stop.\n */\nexport const AI_OVERAGE_UNPAID_LIMIT_USD = 50\n\n/**\n * The monthly ceiling, by step. It rises one step for each later calendar\n * month in which the workspace paid an invoice with a real charge.\n *\n * Months, not invoices: every paid workspace pays an invoice the moment it\n * subscribes, and threshold invoices can arrive several times a day, so\n * counting invoices would reach the top step in an afternoon. Counting\n * months takes at least two, which covers much of the window in which a\n * stolen card is reported.\n */\nexport const AI_OVERAGE_CEILING_LADDER_USD = [50, 150, 500] as const\n\n/** The top index of the ladder; `step` is clamped to it. */\nexport const AI_OVERAGE_MAX_STEP = AI_OVERAGE_CEILING_LADDER_USD.length - 1\n\n/**\n * The smallest amount worth an invoice — Stripe's own minimum charge. A\n * close-out remainder under it is carried, not billed: an invoice that\n * cannot be paid is a document that dunning chases forever.\n */\nexport const AI_OVERAGE_MIN_CHARGE_USD = 0.5\n\n/** Bounds on a ceiling staff may write by hand, the customer ceiling's. */\nexport const AI_OVERAGE_STAFF_CEILING_MIN_USD = 1\nexport const AI_OVERAGE_STAFF_CEILING_MAX_USD = 100_000\n\n/** Why overage is paused. Each one is payment trust dropping, differently. */\nexport type AiOveragePauseReason =\n | 'charge_failed'\n | 'past_due'\n | 'dispute'\n | 'staff'\n\n/** A staff ceiling that outranks the ladder. */\nexport interface AiOverageStaffOverride {\n ceilingUsd: number\n reason: string\n setBy: string\n setAt: string | null\n /** ISO instant, or `null` to stand until staff clear it. */\n expiresAt: string | null\n}\n\n/** Accrual stopped, and why. */\nexport interface AiOveragePause {\n reason: AiOveragePauseReason\n /** The invoice that failed, when a charge is what stopped it. */\n invoiceId: string | null\n since: string | null\n}\n\n/**\n * `orgs/{orgId}/aiBilling/standing`, normalized.\n *\n * Written only by the server, read by the gate on every request past the\n * band. The Firestore rules deny every client write and allow the same reads\n * as `aiAllotments`.\n */\nexport interface AiOverageStanding {\n /**\n * The workspace's default payment method type, `null` when it has none,\n * and `undefined` when nobody has looked yet.\n *\n * THREE states, not two, and the third is the important one. A workspace\n * that has been paying for a year has no standing document until something\n * writes one, and reading that absence as \"no card\" would refuse a paying\n * customer's work over a document we never wrote. So \"never observed\"\n * declines to answer the card question and lets the other guards bind —\n * the unpaid bound does not rest on the card check, which is why it can\n * afford to abstain. A pre-cutover backfill is what turns `undefined` into\n * a real answer for existing workspaces.\n */\n paymentMethodType: string | null | undefined\n /** The first month (`YYYY-MM`) an invoice of this workspace's was paid. */\n firstPaidMonth: string | null\n /**\n * Later months with a paid invoice, ascending, the most recent 12 kept. A\n * month is dropped again if an AI charge of that month was still unpaid\n * when the month ended.\n */\n qualifyingMonths: string[]\n staffOverride: AiOverageStaffOverride | null\n pause: AiOveragePause | null\n lastDisputeAt: string | null\n}\n\n/** The standing of a workspace nothing has written a document for. */\nexport const EMPTY_AI_OVERAGE_STANDING: AiOverageStanding = {\n paymentMethodType: undefined,\n firstPaidMonth: null,\n qualifyingMonths: [],\n staffOverride: null,\n pause: null,\n lastDisputeAt: null,\n}\n\n/** How many qualifying months the document keeps. */\nexport const AI_OVERAGE_QUALIFYING_MONTHS_KEPT = 12\n\nconst MONTH = /^\\d{4}-\\d{2}$/\n\nfunction readString(value: unknown): string | null {\n return typeof value === 'string' && value.trim() ? value.trim() : null\n}\n\nfunction readInstant(value: unknown): string | null {\n if (typeof value === 'string' && value.trim()) return value.trim()\n // A Firestore Timestamp, as the Admin SDK hands one back.\n const toDate = (value as { toDate?: () => Date } | null)?.toDate\n if (typeof toDate === 'function') {\n const date = toDate.call(value)\n return date instanceof Date && !Number.isNaN(date.getTime())\n ? date.toISOString()\n : null\n }\n if (value instanceof Date && !Number.isNaN(value.getTime())) {\n return value.toISOString()\n }\n return null\n}\n\nconst PAUSE_REASONS: readonly AiOveragePauseReason[] = [\n 'charge_failed',\n 'past_due',\n 'dispute',\n 'staff',\n]\n\n/**\n * Reads the stored document into the shape above, strictly.\n *\n * Strictly, because every field here decides whether a workspace may spend\n * money: a string `\"true\"` that read as a boolean, or a `NaN` ceiling that\n * every comparison passes, is a guard that is not there. Anything that does\n * not narrow is the empty standing's value for that field — which is the\n * conservative answer in each case except the card, where the deliberate\n * answer is \"nobody has looked\".\n */\nexport function readAiOverageStanding(\n data: Record<string, unknown> | null | undefined,\n): AiOverageStanding {\n if (!data || typeof data !== 'object') return EMPTY_AI_OVERAGE_STANDING\n const rawType = data['paymentMethodType']\n const paymentMethodType =\n rawType === undefined\n ? undefined\n : typeof rawType === 'string' && rawType.trim()\n ? rawType.trim()\n : null\n const firstPaidMonth = readString(data['firstPaidMonth'])\n const months = Array.isArray(data['qualifyingMonths'])\n ? (data['qualifyingMonths'] as unknown[])\n .filter((month): month is string => typeof month === 'string' && MONTH.test(month))\n .filter((month, index, all) => all.indexOf(month) === index)\n .sort()\n : []\n const override = data['staffOverride'] as Record<string, unknown> | null | undefined\n const ceilingUsd = Number(override?.['ceilingUsd'])\n const staffOverride: AiOverageStaffOverride | null =\n override && Number.isFinite(ceilingUsd) && ceilingUsd > 0\n ? {\n ceilingUsd,\n reason: readString(override['reason']) ?? '',\n setBy: readString(override['setBy']) ?? '',\n setAt: readInstant(override['setAt']),\n expiresAt: readInstant(override['expiresAt']),\n }\n : null\n const rawPause = data['pause'] as Record<string, unknown> | null | undefined\n const pauseReason = readString(rawPause?.['reason']) as AiOveragePauseReason | null\n const pause: AiOveragePause | null =\n rawPause && pauseReason && PAUSE_REASONS.includes(pauseReason)\n ? {\n reason: pauseReason,\n invoiceId: readString(rawPause['invoiceId']),\n since: readInstant(rawPause['since']),\n }\n : null\n return {\n paymentMethodType,\n firstPaidMonth: firstPaidMonth && MONTH.test(firstPaidMonth) ? firstPaidMonth : null,\n qualifyingMonths: months,\n staffOverride,\n pause,\n lastDisputeAt: readInstant(data['lastDisputeAt']),\n }\n}\n\n/**\n * The workspace's step on the ladder — one per qualifying month, clamped.\n *\n * Derived rather than stored, so a stored `step` cannot disagree with the\n * months it was supposed to have been derived from. The document carries a\n * mirrored copy for readers that want one figure, and this is what writes it.\n */\nexport function aiOverageStep(standing: AiOverageStanding): number {\n return Math.min(AI_OVERAGE_MAX_STEP, standing.qualifyingMonths.length)\n}\n\n/** Is a staff override still standing at this instant? */\nexport function aiOverageOverrideApplies(\n override: AiOverageStaffOverride | null,\n now: Date,\n): boolean {\n if (!override) return false\n if (!override.expiresAt) return true\n const expires = Date.parse(override.expiresAt)\n // An unparseable expiry expires the override rather than extending it: a\n // ceiling that was meant to end and cannot say when must not outlive its\n // reason because a timestamp did not read back.\n if (!Number.isFinite(expires)) return false\n return now.getTime() < expires\n}\n\n/**\n * Aglyn's own ceiling on this month's overage, in retail dollars.\n *\n * The staff override when one stands, and the ladder otherwise. This is the\n * platform's limit; the customer's own `capUsd` is a separate figure and the\n * lower of the two binds — see `aiOverageEffectiveCeilingUsd`.\n */\nexport function aiOverageStandingCeilingUsd(\n standing: AiOverageStanding,\n now: Date,\n): number {\n if (aiOverageOverrideApplies(standing.staffOverride, now)) {\n return standing.staffOverride!.ceilingUsd\n }\n return AI_OVERAGE_CEILING_LADDER_USD[aiOverageStep(standing)]\n}\n\n/**\n * The ceiling that actually binds: the lower of the workspace's own and\n * Aglyn's. For display and for staff; the gate consults the two separately,\n * because the customer's ceiling and the platform's need different words.\n */\nexport function aiOverageEffectiveCeilingUsd(\n org: AiOverageOrg,\n standing: AiOverageStanding,\n now: Date,\n): number {\n const own = resolveAssistOverageCapUsd(org)\n const ours = aiOverageStandingCeilingUsd(standing, now)\n return own === null ? ours : Math.min(own, ours)\n}\n\n/**\n * Whether a card is the default payment method, as far as anyone has looked.\n *\n * `undefined` when nobody has, and callers must handle that third answer\n * rather than coercing it — see `paymentMethodType`.\n */\nexport function aiOverageCardOnFile(\n standing: AiOverageStanding,\n): boolean | undefined {\n if (standing.paymentMethodType === undefined) return undefined\n return standing.paymentMethodType === 'card'\n}\n\n/**\n * The month a qualifying step is recorded under, from a paid invoice.\n *\n * `null` when the payment is not evidence that money moved or that time has\n * passed: an invoice marked paid out of band is a staff action, a zero\n * payment is a credit, and the first paid month is the baseline the ladder\n * counts FROM rather than a step on it.\n */\nexport function aiOverageQualifyingMonth(\n standing: AiOverageStanding,\n payment: { month: string; amountPaidCents: number; paidOutOfBand: boolean },\n): string | null {\n if (!MONTH.test(payment.month)) return null\n if (payment.paidOutOfBand) return null\n if (!(payment.amountPaidCents > 0)) return null\n if (!standing.firstPaidMonth) return null\n if (!(payment.month > standing.firstPaidMonth)) return null\n return payment.month\n}\n"],"names":["resolveAssistOverageCapUsd","AI_OVERAGE_THRESHOLD_USD","AI_OVERAGE_UNPAID_LIMIT_USD","AI_OVERAGE_CEILING_LADDER_USD","AI_OVERAGE_MAX_STEP","length","AI_OVERAGE_MIN_CHARGE_USD","AI_OVERAGE_STAFF_CEILING_MIN_USD","AI_OVERAGE_STAFF_CEILING_MAX_USD","EMPTY_AI_OVERAGE_STANDING","paymentMethodType","undefined","firstPaidMonth","qualifyingMonths","staffOverride","pause","lastDisputeAt","AI_OVERAGE_QUALIFYING_MONTHS_KEPT","MONTH","readString","value","trim","readInstant","toDate","date","call","Date","Number","isNaN","getTime","toISOString","PAUSE_REASONS","readAiOverageStanding","data","rawType","months","Array","isArray","filter","month","test","index","all","indexOf","sort","override","ceilingUsd","isFinite","reason","setBy","setAt","expiresAt","rawPause","pauseReason","includes","invoiceId","since","aiOverageStep","standing","Math","min","aiOverageOverrideApplies","now","expires","parse","aiOverageStandingCeilingUsd","aiOverageEffectiveCeilingUsd","org","own","ours","aiOverageCardOnFile","aiOverageQualifyingMonth","payment","paidOutOfBand","amountPaidCents"],"mappings":"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"}
@@ -134,7 +134,7 @@ import { AI_ALLOTMENT_MAX_CREDITS } from "../model/ai-allotments.js";
134
134
  size: "small"
135
135
  }),
136
136
  disabled: busy,
137
- label: "Keep going and alert at 80% and 100% (soft)"
137
+ label: "Keep going and alert at 75%, 80%, 90% and 100% (soft)"
138
138
  })
139
139
  ]
140
140
  })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-allotment-editor.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 {\n Alert,\n Button,\n Checkbox,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n FormGroup,\n FormHelperText,\n FormLabel,\n Radio,\n RadioGroup,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport { AI_ALLOTMENT_MAX_CREDITS, type AiAllotmentMode } from '../model/ai-allotments'\n\nexport interface AiAllotmentValue {\n credits: number | null\n mode: AiAllotmentMode\n models: string[] | null\n}\n\nexport interface AiAllotmentEditorProps {\n open: boolean\n title: string\n /** One sentence under the title: whose allotment, and what it bounds. */\n description?: string\n /** The allotment as it stands, or `null` for a new one. */\n initial: AiAllotmentValue | null\n /** The models an allowlist may name, from the allotments route. */\n models: ReadonlyArray<{ id: string; label: string }>\n /** The org-wide restriction: models only, no credits and no mode. */\n modelsOnly?: boolean\n /** The workspace's pool, for the note that an allotment never grants past it. */\n poolLimit?: number | null\n busy?: boolean\n error?: string | null\n onClose: () => void\n onSave: (value: AiAllotmentValue) => void\n /** Offered when the allotment exists. */\n onRemove?: () => void\n}\n\n/**\n * THE ALLOTMENT EDITOR (AGL-2942): credits a month, hard or soft, and an\n * optional model allowlist — one dialog for a member, a collaborator, a\n * site, a bulk selection and the org-wide restriction, so the four places a\n * manager sets an allotment ask the same questions in the same words.\n */\nexport function AiAllotmentEditor(props: AiAllotmentEditorProps) {\n const { open, initial, models, modelsOnly, poolLimit, busy, error } = props\n const [credits, setCredits] = useState('')\n const [mode, setMode] = useState<AiAllotmentMode>('hard')\n const [allowed, setAllowed] = useState<string[]>([])\n const [invalid, setInvalid] = useState<string | null>(null)\n\n // Reset from the allotment each time the dialog opens, keyed on the open\n // flag and the values rather than on the object's identity.\n const initialCredits = initial?.credits ?? null\n const initialMode = initial?.mode ?? 'hard'\n const initialModels = (initial?.models ?? []).join('\\n')\n useEffect(() => {\n if (!open) return\n setCredits(initialCredits === null ? '' : String(initialCredits))\n setMode(initialMode)\n setAllowed(initialModels ? initialModels.split('\\n') : [])\n setInvalid(null)\n }, [open, initialCredits, initialMode, initialModels])\n\n const parsedCredits = credits.trim() === '' ? null : Number(credits)\n const save = () => {\n if (!modelsOnly) {\n if (\n parsedCredits !== null &&\n (!Number.isInteger(parsedCredits) || parsedCredits < 1 || parsedCredits > AI_ALLOTMENT_MAX_CREDITS)\n ) {\n setInvalid('Enter a whole number of credits, 1 or more.')\n return\n }\n if (parsedCredits === null && !allowed.length) {\n setInvalid('Set credits, choose models, or remove the allotment.')\n return\n }\n }\n setInvalid(null)\n props.onSave({\n credits: modelsOnly ? null : parsedCredits,\n mode,\n models: allowed.length ? allowed : null,\n })\n }\n const toggle = (id: string) =>\n setAllowed((current) =>\n current.includes(id) ? current.filter((entry) => entry !== id) : [...current, id],\n )\n\n return (\n <Dialog open={open} onClose={busy ? undefined : props.onClose} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{props.title}</DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}>\n {props.description ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {props.description}\n </Typography>\n ) : null}\n {modelsOnly ? null : (\n <>\n <TextField\n label=\"Credits a month\"\n type=\"number\"\n size=\"small\"\n value={credits}\n onChange={(event) => setCredits(event.target.value)}\n disabled={busy}\n slotProps={{ htmlInput: { min: 1, step: 100 } }}\n helperText={\n poolLimit && parsedCredits && parsedCredits > poolLimit\n ? `Larger than the workspace's ${poolLimit.toLocaleString()} credits — the workspace's pool still stops first.`\n : 'Counted from the first of the month (UTC). Leave empty to limit models only.'\n }\n />\n <div>\n <FormLabel id=\"ai-allotment-mode\">{'At the allotment'}</FormLabel>\n <RadioGroup\n aria-labelledby=\"ai-allotment-mode\"\n value={mode}\n onChange={(event) => setMode(event.target.value as AiAllotmentMode)}\n >\n <FormControlLabel\n value=\"hard\"\n control={<Radio size=\"small\" />}\n disabled={busy}\n label=\"Stop AI requests (hard)\"\n />\n <FormControlLabel\n value=\"soft\"\n control={<Radio size=\"small\" />}\n disabled={busy}\n label=\"Keep going and alert at 80% and 100% (soft)\"\n />\n </RadioGroup>\n </div>\n </>\n )}\n {models.length ? (\n <div>\n <FormLabel component=\"legend\">\n {modelsOnly ? 'Models the organization may use' : 'Limit to these models (optional)'}\n </FormLabel>\n <FormGroup>\n {models.map((model) => (\n <FormControlLabel\n key={model.id}\n control={\n <Checkbox\n size=\"small\"\n checked={allowed.includes(model.id)}\n onChange={() => toggle(model.id)}\n disabled={busy}\n />\n }\n label={model.label}\n />\n ))}\n </FormGroup>\n <FormHelperText>\n {modelsOnly\n ? 'None checked means no restriction. Auto keeps choosing among the checked models.'\n : 'None checked means every model the plan offers. Auto keeps choosing among the checked models.'}\n </FormHelperText>\n </div>\n ) : null}\n {invalid || error ? <Alert severity=\"error\">{invalid ?? error}</Alert> : null}\n </DialogContent>\n <DialogActions>\n {props.onRemove ? (\n <Button color=\"error\" disabled={busy} onClick={props.onRemove} sx={{ mr: 'auto' }}>\n {modelsOnly ? 'Clear restriction' : 'Remove allotment'}\n </Button>\n ) : null}\n <Button disabled={busy} onClick={props.onClose}>\n {'Cancel'}\n </Button>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button\n variant=\"contained\"\n disabled={busy}\n onClick={save}\n startIcon={busy ? <CircularProgress size={16} /> : undefined}\n >\n {busy ? 'Saving…' : 'Save'}\n </Button>\n </Stack>\n </DialogActions>\n </Dialog>\n )\n}\nAiAllotmentEditor.displayName = 'AiAllotmentEditor'\n\nexport default AiAllotmentEditor\n"],"names":["Alert","Button","Checkbox","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","FormGroup","FormHelperText","FormLabel","Radio","RadioGroup","Stack","TextField","Typography","useEffect","useState","AI_ALLOTMENT_MAX_CREDITS","AiAllotmentEditor","props","open","initial","models","modelsOnly","poolLimit","busy","error","credits","setCredits","mode","setMode","allowed","setAllowed","invalid","setInvalid","initialCredits","initialMode","initialModels","join","String","split","parsedCredits","trim","Number","save","isInteger","length","onSave","toggle","id","current","includes","filter","entry","onClose","undefined","maxWidth","fullWidth","title","sx","display","flexDirection","gap","pt","description","variant","color","label","type","size","value","onChange","event","target","disabled","slotProps","htmlInput","min","step","helperText","toLocaleString","div","aria-labelledby","control","component","map","model","checked","severity","onRemove","onClick","mr","direction","spacing","alignItems","startIcon","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-allotment-editor.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 {\n Alert,\n Button,\n Checkbox,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n FormGroup,\n FormHelperText,\n FormLabel,\n Radio,\n RadioGroup,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport { AI_ALLOTMENT_MAX_CREDITS, type AiAllotmentMode } from '../model/ai-allotments'\n\nexport interface AiAllotmentValue {\n credits: number | null\n mode: AiAllotmentMode\n models: string[] | null\n}\n\nexport interface AiAllotmentEditorProps {\n open: boolean\n title: string\n /** One sentence under the title: whose allotment, and what it bounds. */\n description?: string\n /** The allotment as it stands, or `null` for a new one. */\n initial: AiAllotmentValue | null\n /** The models an allowlist may name, from the allotments route. */\n models: ReadonlyArray<{ id: string; label: string }>\n /** The org-wide restriction: models only, no credits and no mode. */\n modelsOnly?: boolean\n /** The workspace's pool, for the note that an allotment never grants past it. */\n poolLimit?: number | null\n busy?: boolean\n error?: string | null\n onClose: () => void\n onSave: (value: AiAllotmentValue) => void\n /** Offered when the allotment exists. */\n onRemove?: () => void\n}\n\n/**\n * THE ALLOTMENT EDITOR (AGL-2942): credits a month, hard or soft, and an\n * optional model allowlist — one dialog for a member, a collaborator, a\n * site, a bulk selection and the org-wide restriction, so the four places a\n * manager sets an allotment ask the same questions in the same words.\n */\nexport function AiAllotmentEditor(props: AiAllotmentEditorProps) {\n const { open, initial, models, modelsOnly, poolLimit, busy, error } = props\n const [credits, setCredits] = useState('')\n const [mode, setMode] = useState<AiAllotmentMode>('hard')\n const [allowed, setAllowed] = useState<string[]>([])\n const [invalid, setInvalid] = useState<string | null>(null)\n\n // Reset from the allotment each time the dialog opens, keyed on the open\n // flag and the values rather than on the object's identity.\n const initialCredits = initial?.credits ?? null\n const initialMode = initial?.mode ?? 'hard'\n const initialModels = (initial?.models ?? []).join('\\n')\n useEffect(() => {\n if (!open) return\n setCredits(initialCredits === null ? '' : String(initialCredits))\n setMode(initialMode)\n setAllowed(initialModels ? initialModels.split('\\n') : [])\n setInvalid(null)\n }, [open, initialCredits, initialMode, initialModels])\n\n const parsedCredits = credits.trim() === '' ? null : Number(credits)\n const save = () => {\n if (!modelsOnly) {\n if (\n parsedCredits !== null &&\n (!Number.isInteger(parsedCredits) || parsedCredits < 1 || parsedCredits > AI_ALLOTMENT_MAX_CREDITS)\n ) {\n setInvalid('Enter a whole number of credits, 1 or more.')\n return\n }\n if (parsedCredits === null && !allowed.length) {\n setInvalid('Set credits, choose models, or remove the allotment.')\n return\n }\n }\n setInvalid(null)\n props.onSave({\n credits: modelsOnly ? null : parsedCredits,\n mode,\n models: allowed.length ? allowed : null,\n })\n }\n const toggle = (id: string) =>\n setAllowed((current) =>\n current.includes(id) ? current.filter((entry) => entry !== id) : [...current, id],\n )\n\n return (\n <Dialog open={open} onClose={busy ? undefined : props.onClose} maxWidth=\"xs\" fullWidth>\n <DialogTitle>{props.title}</DialogTitle>\n <DialogContent sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}>\n {props.description ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {props.description}\n </Typography>\n ) : null}\n {modelsOnly ? null : (\n <>\n <TextField\n label=\"Credits a month\"\n type=\"number\"\n size=\"small\"\n value={credits}\n onChange={(event) => setCredits(event.target.value)}\n disabled={busy}\n slotProps={{ htmlInput: { min: 1, step: 100 } }}\n helperText={\n poolLimit && parsedCredits && parsedCredits > poolLimit\n ? `Larger than the workspace's ${poolLimit.toLocaleString()} credits — the workspace's pool still stops first.`\n : 'Counted from the first of the month (UTC). Leave empty to limit models only.'\n }\n />\n <div>\n <FormLabel id=\"ai-allotment-mode\">{'At the allotment'}</FormLabel>\n <RadioGroup\n aria-labelledby=\"ai-allotment-mode\"\n value={mode}\n onChange={(event) => setMode(event.target.value as AiAllotmentMode)}\n >\n <FormControlLabel\n value=\"hard\"\n control={<Radio size=\"small\" />}\n disabled={busy}\n label=\"Stop AI requests (hard)\"\n />\n <FormControlLabel\n value=\"soft\"\n control={<Radio size=\"small\" />}\n disabled={busy}\n label=\"Keep going and alert at 75%, 80%, 90% and 100% (soft)\"\n />\n </RadioGroup>\n </div>\n </>\n )}\n {models.length ? (\n <div>\n <FormLabel component=\"legend\">\n {modelsOnly ? 'Models the organization may use' : 'Limit to these models (optional)'}\n </FormLabel>\n <FormGroup>\n {models.map((model) => (\n <FormControlLabel\n key={model.id}\n control={\n <Checkbox\n size=\"small\"\n checked={allowed.includes(model.id)}\n onChange={() => toggle(model.id)}\n disabled={busy}\n />\n }\n label={model.label}\n />\n ))}\n </FormGroup>\n <FormHelperText>\n {modelsOnly\n ? 'None checked means no restriction. Auto keeps choosing among the checked models.'\n : 'None checked means every model the plan offers. Auto keeps choosing among the checked models.'}\n </FormHelperText>\n </div>\n ) : null}\n {invalid || error ? <Alert severity=\"error\">{invalid ?? error}</Alert> : null}\n </DialogContent>\n <DialogActions>\n {props.onRemove ? (\n <Button color=\"error\" disabled={busy} onClick={props.onRemove} sx={{ mr: 'auto' }}>\n {modelsOnly ? 'Clear restriction' : 'Remove allotment'}\n </Button>\n ) : null}\n <Button disabled={busy} onClick={props.onClose}>\n {'Cancel'}\n </Button>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button\n variant=\"contained\"\n disabled={busy}\n onClick={save}\n startIcon={busy ? <CircularProgress size={16} /> : undefined}\n >\n {busy ? 'Saving…' : 'Save'}\n </Button>\n </Stack>\n </DialogActions>\n </Dialog>\n )\n}\nAiAllotmentEditor.displayName = 'AiAllotmentEditor'\n\nexport default AiAllotmentEditor\n"],"names":["Alert","Button","Checkbox","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","FormGroup","FormHelperText","FormLabel","Radio","RadioGroup","Stack","TextField","Typography","useEffect","useState","AI_ALLOTMENT_MAX_CREDITS","AiAllotmentEditor","props","open","initial","models","modelsOnly","poolLimit","busy","error","credits","setCredits","mode","setMode","allowed","setAllowed","invalid","setInvalid","initialCredits","initialMode","initialModels","join","String","split","parsedCredits","trim","Number","save","isInteger","length","onSave","toggle","id","current","includes","filter","entry","onClose","undefined","maxWidth","fullWidth","title","sx","display","flexDirection","gap","pt","description","variant","color","label","type","size","value","onChange","event","target","disabled","slotProps","htmlInput","min","step","helperText","toLocaleString","div","aria-labelledby","control","component","map","model","checked","severity","onRemove","onClick","mr","direction","spacing","alignItems","startIcon","displayName"],"mappings":"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"}
@@ -20,6 +20,7 @@ import * as Aglyn from "@aglyn/aglyn";
20
20
  import { checkEntitlement, lockdownRefusalText, parseLockdownRefusal } from "@aglyn/aglyn";
21
21
  import { portableDefinitionToNested } from "@aglyn/aglyn/app-utils/node-definition-sanitizer";
22
22
  import { AiAssistActionsContext } from "./ai-assist-actions-context.js";
23
+ import { assistNotOnPlanText } from "../usage/assist-credits.js";
23
24
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
24
25
  import { Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, MenuItem, TextField, Typography } from "@mui/material";
25
26
  import { useCallback, useMemo, useState } from "react";
@@ -121,7 +122,7 @@ const AI_GENERATE_REFUSED = 'Your role does not include AI generation — ask an
121
122
  });
122
123
  }
123
124
  if (!checkEntitlement(org, 'aiAssist')) {
124
- return void enqueueSnackbar('AI assist requires a Pro plan — see Billing to upgrade', {
125
+ return void enqueueSnackbar(assistNotOnPlanText(), {
125
126
  variant: 'warning',
126
127
  persist: false
127
128
  });
@@ -238,7 +239,7 @@ const AI_GENERATE_REFUSED = 'Your role does not include AI generation — ask an
238
239
  });
239
240
  }
240
241
  if (!checkEntitlement(org, 'aiAssist')) {
241
- return void enqueueSnackbar('AI assist requires a Pro plan — see Billing to upgrade', {
242
+ return void enqueueSnackbar(assistNotOnPlanText(), {
242
243
  variant: 'warning',
243
244
  persist: false
244
245
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-assist-provider.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 * as Aglyn from '@aglyn/aglyn'\nimport {\n checkEntitlement,\n lockdownRefusalText,\n parseLockdownRefusal,\n} from '@aglyn/aglyn'\nimport { portableDefinitionToNested } from '@aglyn/aglyn/app-utils/node-definition-sanitizer'\nimport { AiAssistActionsContext } from './ai-assist-actions-context'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n MenuItem,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { AiModelSelector } from './ai-model-selector.component'\nimport { AiUsageStrip } from './ai-usage-strip.component'\nimport { useAiModelChoice } from './use-ai-model-choice'\nimport { usePublishAiUsageMeter } from './use-ai-usage-meter'\n\n\nexport interface AiAssistProviderProps {\n /** Org billing doc for the entitlement gate (mounted by the shell). */\n org?: unknown\n /**\n * True once `org` is a trustworthy answer (AGL-1380). The shell mounts\n * this provider above the whole console route tree, so it cannot hold its\n * render until the billing doc settles the way the plugin-page route does\n * — and an undefined `org` is not \"unknown\" to `checkEntitlement`, it is\n * the free tier. Without this flag the gates below told a Pro org that AI\n * assist is not on its plan for as long as the read was in flight.\n */\n orgReady?: boolean\n /**\n * The org every assist call is metered against (AGL-2073). The route no\n * longer resolves it from the signed-in user — a user with one paid org was\n * getting assist on all of their free ones, and their spend was landing on\n * whichever org the server happened to resolve.\n */\n orgId?: string\n /**\n * The site the canvas belongs to (AGL-2927), sent with every assist call\n * so the door can decide a site collaborator's permission on THAT site.\n * Null off a host route, where there is no canvas to assist on.\n */\n hostId?: string | null\n /**\n * The reader's AI verdict, resolved by the shell (AGL-2927). The gates\n * below HOLD until `loaded` — a door opened on a pending answer would\n * spend the workspace's credits on a permission nobody has confirmed —\n * and a refused key removes its callback from the context, so the\n * designer draws no control for it. Omitted, the answer is pending.\n */\n aiPermissions?: { loaded: boolean; use: boolean; generate: boolean }\n children?: JSX.Children\n}\n\nconst PERMISSIONS_PENDING = 'Checking your permissions — try again in a moment'\nconst AI_USE_REFUSED =\n 'Your role does not include AI assistance — ask an organization admin'\nconst AI_GENERATE_REFUSED =\n 'Your role does not include AI generation — ask an organization admin'\n\n/**\n * Console-side AI copy assist (AGL-89): provides the designer's \"Rewrite\n * with AI\" callback, hosts the instruction dialog, and calls the assist API\n * (which 501-degrades when no AI provider is configured). The commit\n * spreads the node's current props — `updateNodeProps` REPLACES the props\n * object — and clears any stale rich-text `html` so the new text renders.\n */\nexport function AiAssistProvider(props: AiAssistProviderProps) {\n const org = props.org\n const orgReady = props.orgReady\n const orgId = props.orgId\n const hostId = props.hostId ?? null\n const aiPermissions = props.aiPermissions\n const { children } = props\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const [node, setNode] = useState<Aglyn.NodeSchema<any> | null>(null)\n const [instruction, setInstruction] = useState('')\n // Rewrite target (AGL-130): 'children' for text-editable elements, or\n // any text attribute the element's schema declares (alt text, labels…).\n const [targetProp, setTargetProp] = useState('children')\n const [busy, setBusy] = useState(false)\n // Generate section (AGL-169).\n const [sectionOpen, setSectionOpen] = useState(false)\n const [sectionPrompt, setSectionPrompt] = useState('')\n // The model switch and the usage strip in both dialogs (AGL-2942). Each\n // dialog remembers its own pick; one envelope store serves both strips\n // and the assistant panel's.\n const copyModel = useAiModelChoice({ orgId, hostId, surface: 'copy', kind: 'copy.element' })\n const sectionModel = useAiModelChoice({\n orgId,\n hostId,\n surface: 'section',\n kind: 'copy.section',\n })\n const publishMeter = usePublishAiUsageMeter(orgId)\n\n const textTargets = useMemo(() => {\n const schema = node?.componentSchema\n const textEditable =\n ((schema?.flags?.textEditable ?? Aglyn.FEATURE_FLAG.DISABLED) &\n Aglyn.FEATURE_FLAG.ENABLED) !==\n 0\n const attributes = (schema?.attributes ?? [])\n .filter(\n (field: any) =>\n field.component === Aglyn.FieldComponentType.TEXT_FIELD ||\n field.component === Aglyn.FieldComponentType.TEXTAREA,\n )\n .map((field: any) => ({\n prop: String(field.name),\n label: String(field.label ?? field.name),\n }))\n return textEditable\n ? [{ prop: 'children', label: 'Element text' }, ...attributes]\n : attributes\n }, [node])\n\n const handleRewrite = useCallback(\n (target: Aglyn.NodeSchema<any>) => {\n // The permission first (AGL-2927): it is a fact about the reader, and\n // pending asserts nothing here either. The refusal is unreachable\n // through the designer — a refused key publishes no callback — and\n // stands for any other caller of the context.\n if (!aiPermissions?.loaded) {\n return void enqueueSnackbar(PERMISSIONS_PENDING, {\n variant: 'info',\n persist: false,\n })\n }\n if (!aiPermissions.use) {\n return void enqueueSnackbar(AI_USE_REFUSED, {\n variant: 'warning',\n persist: false,\n })\n }\n // AGL-1380: pending asserts nothing. See `orgReady` on the props.\n if (!orgReady) {\n return void enqueueSnackbar(\n 'Checking your plan — try again in a moment',\n { variant: 'info', persist: false },\n )\n }\n if (!checkEntitlement(org as never, 'aiAssist')) {\n return void enqueueSnackbar(\n 'AI assist requires a Pro plan — see Billing to upgrade',\n { variant: 'warning', persist: false },\n )\n }\n setInstruction('')\n setTargetProp('children')\n setNode(target)\n },\n [org, orgReady, aiPermissions, enqueueSnackbar],\n )\n\n // Keep the target valid for the selected element.\n const effectiveTarget = textTargets.some((item) => item.prop === targetProp)\n ? targetProp\n : (textTargets[0]?.prop ?? 'children')\n\n const handleConfirm = useCallback(async () => {\n if (!node || !instruction.trim() || busy) return\n setBusy(true)\n try {\n const current = (Aglyn.canvas.toJSON().nodes as Record<string, any>)[\n node.$id\n ]\n const text =\n typeof current?.props?.[effectiveTarget] === 'string'\n ? (current.props[effectiveTarget] as string)\n : ''\n const response = await authorizedFetch(user, '/api/ai/assist', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n text,\n instruction: instruction.trim(),\n ...(copyModel.model ? { model: copyModel.model } : {}),\n }),\n })\n const payload = await response.json()\n publishMeter(payload?.meter)\n // An ai-assist feature lockdown (AGL-1510/1532) reads as a pause, not\n // as \"AI request failed\" — a provider incident or a cost runaway is\n // something WE turned off, and saying so keeps the user's content out\n // of the story.\n const locked = parseLockdownRefusal(response.status, payload)\n if (locked) {\n return void enqueueSnackbar(lockdownRefusalText(locked), {\n variant: 'warning',\n persist: true,\n })\n }\n if (response.status === 501) {\n return void enqueueSnackbar(\n 'AI assist is not configured on this deployment',\n { variant: 'info', persist: false },\n )\n }\n if (!response.ok || !payload?.text) {\n return void enqueueSnackbar(payload?.error ?? 'AI request failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n Aglyn.canvas.updateNodeProps(node, {\n ...current?.props,\n [effectiveTarget]: payload.text,\n // Rich-text elements render `html` over `children`; drop it so the\n // rewrite is what shows (the user can re-format inline afterwards).\n ...(effectiveTarget === 'children' ? { html: undefined } : {}),\n })\n setNode(null)\n enqueueSnackbar('Text rewritten — undo restores the original', {\n variant: 'success',\n persist: false,\n })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [\n node,\n instruction,\n busy,\n user,\n effectiveTarget,\n orgId,\n hostId,\n copyModel.model,\n publishMeter,\n enqueueSnackbar,\n ])\n\n const handleGenerateSection = useCallback(() => {\n // The permission first (AGL-2927) — a section is a generation, so this\n // door sells under `ai.generate`; see `handleRewrite`.\n if (!aiPermissions?.loaded) {\n return void enqueueSnackbar(PERMISSIONS_PENDING, {\n variant: 'info',\n persist: false,\n })\n }\n if (!aiPermissions.generate) {\n return void enqueueSnackbar(AI_GENERATE_REFUSED, {\n variant: 'warning',\n persist: false,\n })\n }\n // AGL-1380: same gate as `handleRewrite`.\n if (!orgReady) {\n return void enqueueSnackbar('Checking your plan — try again in a moment', {\n variant: 'info',\n persist: false,\n })\n }\n if (!checkEntitlement(org as never, 'aiAssist')) {\n return void enqueueSnackbar(\n 'AI assist requires a Pro plan — see Billing to upgrade',\n { variant: 'warning', persist: false },\n )\n }\n setSectionPrompt('')\n setSectionOpen(true)\n }, [org, orgReady, aiPermissions, enqueueSnackbar])\n\n const handleSectionConfirm = useCallback(async () => {\n if (!sectionPrompt.trim() || busy) return\n setBusy(true)\n try {\n const response = await authorizedFetch(user, '/api/ai/assist', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n mode: 'section',\n instruction: sectionPrompt.trim(),\n ...(sectionModel.model ? { model: sectionModel.model } : {}),\n }),\n })\n const payload = await response.json()\n publishMeter(payload?.meter)\n // Same pause notice on the section-generation door (AGL-1532).\n const locked = parseLockdownRefusal(response.status, payload)\n if (locked) {\n return void enqueueSnackbar(lockdownRefusalText(locked), {\n variant: 'warning',\n persist: true,\n })\n }\n if (response.status === 501) {\n return void enqueueSnackbar(\n 'AI assist is not configured on this deployment',\n { variant: 'info', persist: false },\n )\n }\n if (!response.ok || !payload?.section?.rootId) {\n return void enqueueSnackbar(payload?.error ?? 'AI request failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n const nested = portableDefinitionToNested(\n payload.section.rootId,\n payload.section.nodes,\n )\n if (!nested) {\n return void enqueueSnackbar('AI returned an empty section', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n // Graft through the preset path: ids regenerate, history is saved,\n // and the subtree lands at the end of the canvas root.\n const stamped = (function stamp(node: any): any {\n const schema = Aglyn.components.getSchema(node.componentId)\n return {\n ...node,\n ...(schema?.pluginId ? { pluginId: schema.pluginId } : {}),\n nodes: (node.nodes ?? []).map(stamp),\n }\n })(nested)\n Aglyn.canvas.addNodeFromPreset(\n { type: Aglyn.NodeType.PRESET, data: stamped } as any,\n Aglyn.canvas.rootNode as any,\n )\n setSectionOpen(false)\n enqueueSnackbar('Section added — undo removes it', {\n variant: 'success',\n persist: false,\n })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [\n sectionPrompt,\n busy,\n user,\n orgId,\n hostId,\n sectionModel.model,\n publishMeter,\n enqueueSnackbar,\n ])\n\n // A REFUSED key publishes no callback (AGL-2927): the besigner controls\n // render only when the callback exists, so the refusal removes\n // the button rather than dimming it. A PENDING answer keeps both\n // callbacks, and each holds with a notice when pressed — a control that\n // vanishes and reappears while the read lands is worse than one that is\n // briefly inert (the `useHostRole` argument, applied here).\n const refusedUse = aiPermissions?.loaded === true && !aiPermissions.use\n const refusedGenerate =\n aiPermissions?.loaded === true && !aiPermissions.generate\n const contextValue = useMemo(\n () => ({\n ...(refusedUse ? {} : { onRewrite: handleRewrite }),\n ...(refusedGenerate ? {} : { onGenerateSection: handleGenerateSection }),\n }),\n [handleRewrite, handleGenerateSection, refusedUse, refusedGenerate],\n )\n\n return (\n <AiAssistActionsContext.Provider value={contextValue}>\n {children}\n <Dialog\n open={Boolean(node)}\n onClose={() => (busy ? null : setNode(null))}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{'Rewrite with AI'}</DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Describe how the text should change — tone, length, message.'}\n </Typography>\n {textTargets.length > 1 ? (\n <TextField\n select\n label=\"Field\"\n value={effectiveTarget}\n onChange={(event) => setTargetProp(event.target.value)}\n size=\"small\"\n disabled={busy}\n >\n {textTargets.map((item) => (\n <MenuItem key={item.prop} value={item.prop}>\n {item.label}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <TextField\n label=\"Instruction\"\n placeholder=\"e.g. Make it punchier and half as long\"\n value={instruction}\n onChange={(event) => setInstruction(event.target.value)}\n size=\"small\"\n autoFocus\n multiline\n minRows={2}\n disabled={busy}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n handleConfirm()\n }\n }}\n />\n <AiModelSelector choice={copyModel} disabled={busy} />\n <AiUsageStrip orgId={orgId} />\n </DialogContent>\n <DialogActions>\n <Button disabled={busy} onClick={() => setNode(null)}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!instruction.trim() || busy}\n startIcon={busy ? <CircularProgress size={16} /> : undefined}\n onClick={handleConfirm}\n >\n {busy ? 'Rewriting…' : 'Rewrite'}\n </Button>\n </DialogActions>\n </Dialog>\n <Dialog\n open={sectionOpen}\n onClose={() => (busy ? null : setSectionOpen(false))}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{'Generate a section with AI'}</DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Describe the section — e.g. \"hero with headline, subtext, ' +\n 'and a call-to-action button\" or \"three-column feature grid\".'}\n </Typography>\n <TextField\n label=\"Section\"\n value={sectionPrompt}\n onChange={(event) => setSectionPrompt(event.target.value)}\n size=\"small\"\n autoFocus\n multiline\n minRows={2}\n disabled={busy}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n handleSectionConfirm()\n }\n }}\n />\n <AiModelSelector choice={sectionModel} disabled={busy} />\n <AiUsageStrip orgId={orgId} />\n </DialogContent>\n <DialogActions>\n <Button disabled={busy} onClick={() => setSectionOpen(false)}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!sectionPrompt.trim() || busy}\n startIcon={busy ? <CircularProgress size={16} /> : undefined}\n onClick={handleSectionConfirm}\n >\n {busy ? 'Generating…' : 'Generate'}\n </Button>\n </DialogActions>\n </Dialog>\n </AiAssistActionsContext.Provider>\n )\n}\nAiAssistProvider.displayName = 'AiAssistProvider'\n\nexport default AiAssistProvider\n"],"names":["Aglyn","checkEntitlement","lockdownRefusalText","parseLockdownRefusal","portableDefinitionToNested","AiAssistActionsContext","useSnackbar","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","MenuItem","TextField","Typography","useCallback","useMemo","useState","useUser","authorizedFetch","AiModelSelector","AiUsageStrip","useAiModelChoice","usePublishAiUsageMeter","PERMISSIONS_PENDING","AI_USE_REFUSED","AI_GENERATE_REFUSED","AiAssistProvider","props","textTargets","org","orgReady","orgId","hostId","aiPermissions","children","enqueueSnackbar","data","user","node","setNode","instruction","setInstruction","targetProp","setTargetProp","busy","setBusy","sectionOpen","setSectionOpen","sectionPrompt","setSectionPrompt","copyModel","surface","kind","sectionModel","publishMeter","schema","componentSchema","textEditable","flags","FEATURE_FLAG","DISABLED","ENABLED","attributes","filter","field","component","FieldComponentType","TEXT_FIELD","TEXTAREA","map","prop","String","name","label","handleRewrite","target","loaded","variant","persist","use","effectiveTarget","some","item","handleConfirm","trim","current","canvas","toJSON","nodes","$id","text","response","method","headers","body","JSON","stringify","model","payload","json","meter","locked","status","ok","error","allowDuplicate","updateNodeProps","html","undefined","console","handleGenerateSection","generate","handleSectionConfirm","mode","section","rootId","nested","stamped","stamp","components","getSchema","componentId","pluginId","addNodeFromPreset","type","NodeType","PRESET","rootNode","refusedUse","refusedGenerate","contextValue","onRewrite","onGenerateSection","Provider","value","open","Boolean","onClose","maxWidth","fullWidth","sx","display","flexDirection","gap","pt","color","length","select","onChange","event","size","disabled","placeholder","autoFocus","multiline","minRows","onKeyDown","key","metaKey","ctrlKey","preventDefault","choice","onClick","startIcon","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-assist-provider.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 * as Aglyn from '@aglyn/aglyn'\nimport {\n checkEntitlement,\n lockdownRefusalText,\n parseLockdownRefusal,\n} from '@aglyn/aglyn'\nimport { portableDefinitionToNested } from '@aglyn/aglyn/app-utils/node-definition-sanitizer'\nimport { AiAssistActionsContext } from './ai-assist-actions-context'\nimport { assistNotOnPlanText } from '../usage/assist-credits'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n MenuItem,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useMemo, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { AiModelSelector } from './ai-model-selector.component'\nimport { AiUsageStrip } from './ai-usage-strip.component'\nimport { useAiModelChoice } from './use-ai-model-choice'\nimport { usePublishAiUsageMeter } from './use-ai-usage-meter'\n\n\nexport interface AiAssistProviderProps {\n /** Org billing doc for the entitlement gate (mounted by the shell). */\n org?: unknown\n /**\n * True once `org` is a trustworthy answer (AGL-1380). The shell mounts\n * this provider above the whole console route tree, so it cannot hold its\n * render until the billing doc settles the way the plugin-page route does\n * — and an undefined `org` is not \"unknown\" to `checkEntitlement`, it is\n * the free tier. Without this flag the gates below told a Pro org that AI\n * assist is not on its plan for as long as the read was in flight.\n */\n orgReady?: boolean\n /**\n * The org every assist call is metered against (AGL-2073). The route no\n * longer resolves it from the signed-in user — a user with one paid org was\n * getting assist on all of their free ones, and their spend was landing on\n * whichever org the server happened to resolve.\n */\n orgId?: string\n /**\n * The site the canvas belongs to (AGL-2927), sent with every assist call\n * so the door can decide a site collaborator's permission on THAT site.\n * Null off a host route, where there is no canvas to assist on.\n */\n hostId?: string | null\n /**\n * The reader's AI verdict, resolved by the shell (AGL-2927). The gates\n * below HOLD until `loaded` — a door opened on a pending answer would\n * spend the workspace's credits on a permission nobody has confirmed —\n * and a refused key removes its callback from the context, so the\n * designer draws no control for it. Omitted, the answer is pending.\n */\n aiPermissions?: { loaded: boolean; use: boolean; generate: boolean }\n children?: JSX.Children\n}\n\nconst PERMISSIONS_PENDING = 'Checking your permissions — try again in a moment'\nconst AI_USE_REFUSED =\n 'Your role does not include AI assistance — ask an organization admin'\nconst AI_GENERATE_REFUSED =\n 'Your role does not include AI generation — ask an organization admin'\n\n/**\n * Console-side AI copy assist (AGL-89): provides the designer's \"Rewrite\n * with AI\" callback, hosts the instruction dialog, and calls the assist API\n * (which 501-degrades when no AI provider is configured). The commit\n * spreads the node's current props — `updateNodeProps` REPLACES the props\n * object — and clears any stale rich-text `html` so the new text renders.\n */\nexport function AiAssistProvider(props: AiAssistProviderProps) {\n const org = props.org\n const orgReady = props.orgReady\n const orgId = props.orgId\n const hostId = props.hostId ?? null\n const aiPermissions = props.aiPermissions\n const { children } = props\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const [node, setNode] = useState<Aglyn.NodeSchema<any> | null>(null)\n const [instruction, setInstruction] = useState('')\n // Rewrite target (AGL-130): 'children' for text-editable elements, or\n // any text attribute the element's schema declares (alt text, labels…).\n const [targetProp, setTargetProp] = useState('children')\n const [busy, setBusy] = useState(false)\n // Generate section (AGL-169).\n const [sectionOpen, setSectionOpen] = useState(false)\n const [sectionPrompt, setSectionPrompt] = useState('')\n // The model switch and the usage strip in both dialogs (AGL-2942). Each\n // dialog remembers its own pick; one envelope store serves both strips\n // and the assistant panel's.\n const copyModel = useAiModelChoice({ orgId, hostId, surface: 'copy', kind: 'copy.element' })\n const sectionModel = useAiModelChoice({\n orgId,\n hostId,\n surface: 'section',\n kind: 'copy.section',\n })\n const publishMeter = usePublishAiUsageMeter(orgId)\n\n const textTargets = useMemo(() => {\n const schema = node?.componentSchema\n const textEditable =\n ((schema?.flags?.textEditable ?? Aglyn.FEATURE_FLAG.DISABLED) &\n Aglyn.FEATURE_FLAG.ENABLED) !==\n 0\n const attributes = (schema?.attributes ?? [])\n .filter(\n (field: any) =>\n field.component === Aglyn.FieldComponentType.TEXT_FIELD ||\n field.component === Aglyn.FieldComponentType.TEXTAREA,\n )\n .map((field: any) => ({\n prop: String(field.name),\n label: String(field.label ?? field.name),\n }))\n return textEditable\n ? [{ prop: 'children', label: 'Element text' }, ...attributes]\n : attributes\n }, [node])\n\n const handleRewrite = useCallback(\n (target: Aglyn.NodeSchema<any>) => {\n // The permission first (AGL-2927): it is a fact about the reader, and\n // pending asserts nothing here either. The refusal is unreachable\n // through the designer — a refused key publishes no callback — and\n // stands for any other caller of the context.\n if (!aiPermissions?.loaded) {\n return void enqueueSnackbar(PERMISSIONS_PENDING, {\n variant: 'info',\n persist: false,\n })\n }\n if (!aiPermissions.use) {\n return void enqueueSnackbar(AI_USE_REFUSED, {\n variant: 'warning',\n persist: false,\n })\n }\n // AGL-1380: pending asserts nothing. See `orgReady` on the props.\n if (!orgReady) {\n return void enqueueSnackbar(\n 'Checking your plan — try again in a moment',\n { variant: 'info', persist: false },\n )\n }\n if (!checkEntitlement(org as never, 'aiAssist')) {\n return void enqueueSnackbar(assistNotOnPlanText(), {\n variant: 'warning',\n persist: false,\n })\n }\n setInstruction('')\n setTargetProp('children')\n setNode(target)\n },\n [org, orgReady, aiPermissions, enqueueSnackbar],\n )\n\n // Keep the target valid for the selected element.\n const effectiveTarget = textTargets.some((item) => item.prop === targetProp)\n ? targetProp\n : (textTargets[0]?.prop ?? 'children')\n\n const handleConfirm = useCallback(async () => {\n if (!node || !instruction.trim() || busy) return\n setBusy(true)\n try {\n const current = (Aglyn.canvas.toJSON().nodes as Record<string, any>)[\n node.$id\n ]\n const text =\n typeof current?.props?.[effectiveTarget] === 'string'\n ? (current.props[effectiveTarget] as string)\n : ''\n const response = await authorizedFetch(user, '/api/ai/assist', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n text,\n instruction: instruction.trim(),\n ...(copyModel.model ? { model: copyModel.model } : {}),\n }),\n })\n const payload = await response.json()\n publishMeter(payload?.meter)\n // An ai-assist feature lockdown (AGL-1510/1532) reads as a pause, not\n // as \"AI request failed\" — a provider incident or a cost runaway is\n // something WE turned off, and saying so keeps the user's content out\n // of the story.\n const locked = parseLockdownRefusal(response.status, payload)\n if (locked) {\n return void enqueueSnackbar(lockdownRefusalText(locked), {\n variant: 'warning',\n persist: true,\n })\n }\n if (response.status === 501) {\n return void enqueueSnackbar(\n 'AI assist is not configured on this deployment',\n { variant: 'info', persist: false },\n )\n }\n if (!response.ok || !payload?.text) {\n return void enqueueSnackbar(payload?.error ?? 'AI request failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n Aglyn.canvas.updateNodeProps(node, {\n ...current?.props,\n [effectiveTarget]: payload.text,\n // Rich-text elements render `html` over `children`; drop it so the\n // rewrite is what shows (the user can re-format inline afterwards).\n ...(effectiveTarget === 'children' ? { html: undefined } : {}),\n })\n setNode(null)\n enqueueSnackbar('Text rewritten — undo restores the original', {\n variant: 'success',\n persist: false,\n })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [\n node,\n instruction,\n busy,\n user,\n effectiveTarget,\n orgId,\n hostId,\n copyModel.model,\n publishMeter,\n enqueueSnackbar,\n ])\n\n const handleGenerateSection = useCallback(() => {\n // The permission first (AGL-2927) — a section is a generation, so this\n // door sells under `ai.generate`; see `handleRewrite`.\n if (!aiPermissions?.loaded) {\n return void enqueueSnackbar(PERMISSIONS_PENDING, {\n variant: 'info',\n persist: false,\n })\n }\n if (!aiPermissions.generate) {\n return void enqueueSnackbar(AI_GENERATE_REFUSED, {\n variant: 'warning',\n persist: false,\n })\n }\n // AGL-1380: same gate as `handleRewrite`.\n if (!orgReady) {\n return void enqueueSnackbar('Checking your plan — try again in a moment', {\n variant: 'info',\n persist: false,\n })\n }\n if (!checkEntitlement(org as never, 'aiAssist')) {\n return void enqueueSnackbar(assistNotOnPlanText(), {\n variant: 'warning',\n persist: false,\n })\n }\n setSectionPrompt('')\n setSectionOpen(true)\n }, [org, orgReady, aiPermissions, enqueueSnackbar])\n\n const handleSectionConfirm = useCallback(async () => {\n if (!sectionPrompt.trim() || busy) return\n setBusy(true)\n try {\n const response = await authorizedFetch(user, '/api/ai/assist', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId,\n mode: 'section',\n instruction: sectionPrompt.trim(),\n ...(sectionModel.model ? { model: sectionModel.model } : {}),\n }),\n })\n const payload = await response.json()\n publishMeter(payload?.meter)\n // Same pause notice on the section-generation door (AGL-1532).\n const locked = parseLockdownRefusal(response.status, payload)\n if (locked) {\n return void enqueueSnackbar(lockdownRefusalText(locked), {\n variant: 'warning',\n persist: true,\n })\n }\n if (response.status === 501) {\n return void enqueueSnackbar(\n 'AI assist is not configured on this deployment',\n { variant: 'info', persist: false },\n )\n }\n if (!response.ok || !payload?.section?.rootId) {\n return void enqueueSnackbar(payload?.error ?? 'AI request failed', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n const nested = portableDefinitionToNested(\n payload.section.rootId,\n payload.section.nodes,\n )\n if (!nested) {\n return void enqueueSnackbar('AI returned an empty section', {\n variant: 'error',\n allowDuplicate: true,\n })\n }\n // Graft through the preset path: ids regenerate, history is saved,\n // and the subtree lands at the end of the canvas root.\n const stamped = (function stamp(node: any): any {\n const schema = Aglyn.components.getSchema(node.componentId)\n return {\n ...node,\n ...(schema?.pluginId ? { pluginId: schema.pluginId } : {}),\n nodes: (node.nodes ?? []).map(stamp),\n }\n })(nested)\n Aglyn.canvas.addNodeFromPreset(\n { type: Aglyn.NodeType.PRESET, data: stamped } as any,\n Aglyn.canvas.rootNode as any,\n )\n setSectionOpen(false)\n enqueueSnackbar('Section added — undo removes it', {\n variant: 'success',\n persist: false,\n })\n } catch (error) {\n console.error(error)\n enqueueSnackbar('An error has occurred', {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(false)\n }\n }, [\n sectionPrompt,\n busy,\n user,\n orgId,\n hostId,\n sectionModel.model,\n publishMeter,\n enqueueSnackbar,\n ])\n\n // A REFUSED key publishes no callback (AGL-2927): the besigner controls\n // render only when the callback exists, so the refusal removes\n // the button rather than dimming it. A PENDING answer keeps both\n // callbacks, and each holds with a notice when pressed — a control that\n // vanishes and reappears while the read lands is worse than one that is\n // briefly inert (the `useHostRole` argument, applied here).\n const refusedUse = aiPermissions?.loaded === true && !aiPermissions.use\n const refusedGenerate =\n aiPermissions?.loaded === true && !aiPermissions.generate\n const contextValue = useMemo(\n () => ({\n ...(refusedUse ? {} : { onRewrite: handleRewrite }),\n ...(refusedGenerate ? {} : { onGenerateSection: handleGenerateSection }),\n }),\n [handleRewrite, handleGenerateSection, refusedUse, refusedGenerate],\n )\n\n return (\n <AiAssistActionsContext.Provider value={contextValue}>\n {children}\n <Dialog\n open={Boolean(node)}\n onClose={() => (busy ? null : setNode(null))}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{'Rewrite with AI'}</DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Describe how the text should change — tone, length, message.'}\n </Typography>\n {textTargets.length > 1 ? (\n <TextField\n select\n label=\"Field\"\n value={effectiveTarget}\n onChange={(event) => setTargetProp(event.target.value)}\n size=\"small\"\n disabled={busy}\n >\n {textTargets.map((item) => (\n <MenuItem key={item.prop} value={item.prop}>\n {item.label}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n <TextField\n label=\"Instruction\"\n placeholder=\"e.g. Make it punchier and half as long\"\n value={instruction}\n onChange={(event) => setInstruction(event.target.value)}\n size=\"small\"\n autoFocus\n multiline\n minRows={2}\n disabled={busy}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n handleConfirm()\n }\n }}\n />\n <AiModelSelector choice={copyModel} disabled={busy} />\n <AiUsageStrip orgId={orgId} />\n </DialogContent>\n <DialogActions>\n <Button disabled={busy} onClick={() => setNode(null)}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!instruction.trim() || busy}\n startIcon={busy ? <CircularProgress size={16} /> : undefined}\n onClick={handleConfirm}\n >\n {busy ? 'Rewriting…' : 'Rewrite'}\n </Button>\n </DialogActions>\n </Dialog>\n <Dialog\n open={sectionOpen}\n onClose={() => (busy ? null : setSectionOpen(false))}\n maxWidth=\"xs\"\n fullWidth\n >\n <DialogTitle>{'Generate a section with AI'}</DialogTitle>\n <DialogContent\n sx={{ display: 'flex', flexDirection: 'column', gap: 2, pt: 1 }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Describe the section — e.g. \"hero with headline, subtext, ' +\n 'and a call-to-action button\" or \"three-column feature grid\".'}\n </Typography>\n <TextField\n label=\"Section\"\n value={sectionPrompt}\n onChange={(event) => setSectionPrompt(event.target.value)}\n size=\"small\"\n autoFocus\n multiline\n minRows={2}\n disabled={busy}\n onKeyDown={(event) => {\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n handleSectionConfirm()\n }\n }}\n />\n <AiModelSelector choice={sectionModel} disabled={busy} />\n <AiUsageStrip orgId={orgId} />\n </DialogContent>\n <DialogActions>\n <Button disabled={busy} onClick={() => setSectionOpen(false)}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={!sectionPrompt.trim() || busy}\n startIcon={busy ? <CircularProgress size={16} /> : undefined}\n onClick={handleSectionConfirm}\n >\n {busy ? 'Generating…' : 'Generate'}\n </Button>\n </DialogActions>\n </Dialog>\n </AiAssistActionsContext.Provider>\n )\n}\nAiAssistProvider.displayName = 'AiAssistProvider'\n\nexport default AiAssistProvider\n"],"names":["Aglyn","checkEntitlement","lockdownRefusalText","parseLockdownRefusal","portableDefinitionToNested","AiAssistActionsContext","assistNotOnPlanText","useSnackbar","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","MenuItem","TextField","Typography","useCallback","useMemo","useState","useUser","authorizedFetch","AiModelSelector","AiUsageStrip","useAiModelChoice","usePublishAiUsageMeter","PERMISSIONS_PENDING","AI_USE_REFUSED","AI_GENERATE_REFUSED","AiAssistProvider","props","textTargets","org","orgReady","orgId","hostId","aiPermissions","children","enqueueSnackbar","data","user","node","setNode","instruction","setInstruction","targetProp","setTargetProp","busy","setBusy","sectionOpen","setSectionOpen","sectionPrompt","setSectionPrompt","copyModel","surface","kind","sectionModel","publishMeter","schema","componentSchema","textEditable","flags","FEATURE_FLAG","DISABLED","ENABLED","attributes","filter","field","component","FieldComponentType","TEXT_FIELD","TEXTAREA","map","prop","String","name","label","handleRewrite","target","loaded","variant","persist","use","effectiveTarget","some","item","handleConfirm","trim","current","canvas","toJSON","nodes","$id","text","response","method","headers","body","JSON","stringify","model","payload","json","meter","locked","status","ok","error","allowDuplicate","updateNodeProps","html","undefined","console","handleGenerateSection","generate","handleSectionConfirm","mode","section","rootId","nested","stamped","stamp","components","getSchema","componentId","pluginId","addNodeFromPreset","type","NodeType","PRESET","rootNode","refusedUse","refusedGenerate","contextValue","onRewrite","onGenerateSection","Provider","value","open","Boolean","onClose","maxWidth","fullWidth","sx","display","flexDirection","gap","pt","color","length","select","onChange","event","size","disabled","placeholder","autoFocus","multiline","minRows","onKeyDown","key","metaKey","ctrlKey","preventDefault","choice","onClick","startIcon","displayName"],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * What the workflows plugin's Automation page zones hand this plugin's
19
+ * widgets.
20
+ *
21
+ * `hostAutomations`, `automationEditor` and `automationRun` are zones the
22
+ * workflows plugin hosts and declares (`registerPluginZone`), with their props
23
+ * on its own zone tokens. A plugin never imports another plugin, so the
24
+ * widgets registered there read the props through the shapes restated here —
25
+ * the zone's props as the host documents them, by the same names. A widget is
26
+ * handed whatever the host passes, so this restates what the host SENDS; every
27
+ * member below is one the host documents in the injection zones reference.
28
+ *
29
+ * Types only: nothing here reaches a bundle.
30
+ */
31
+ /** What the `hostAutomations` zone hands a widget. */
32
+ export interface ConsoleHostAutomationsZoneProps {
33
+ hostId: string;
34
+ /** The org the page names; `undefined` while it resolves. */
35
+ orgId: string | undefined;
36
+ /**
37
+ * Opens the Actions editor on a listed action. `false` when the id names no
38
+ * action the list has read yet.
39
+ */
40
+ openAction: (actionId: string) => boolean;
41
+ }
42
+ /** One automation on the Automation page: an action, or a workflow. */
43
+ export interface ConsoleAutomationTarget {
44
+ type: 'action' | 'workflow';
45
+ id: string;
46
+ name: string;
47
+ }
48
+ /** What the `automationEditor` zone hands a widget. */
49
+ export interface ConsoleAutomationEditorZoneProps {
50
+ hostId: string;
51
+ orgId: string | undefined;
52
+ /** The automation the editor has open, as it is stored. */
53
+ target: ConsoleAutomationTarget;
54
+ }
55
+ /** What the `automationRun` zone hands a widget. */
56
+ export interface ConsoleAutomationRunZoneProps {
57
+ hostId: string;
58
+ orgId: string | undefined;
59
+ /** The automation the run belongs to. */
60
+ target: ConsoleAutomationTarget;
61
+ /** The run's entry in the site's activity log. */
62
+ runId: string;
63
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * What the workflows plugin's Automation page zones hand this plugin's
18
+ * widgets.
19
+ *
20
+ * `hostAutomations`, `automationEditor` and `automationRun` are zones the
21
+ * workflows plugin hosts and declares (`registerPluginZone`), with their props
22
+ * on its own zone tokens. A plugin never imports another plugin, so the
23
+ * widgets registered there read the props through the shapes restated here —
24
+ * the zone's props as the host documents them, by the same names. A widget is
25
+ * handed whatever the host passes, so this restates what the host SENDS; every
26
+ * member below is one the host documents in the injection zones reference.
27
+ *
28
+ * Types only: nothing here reaches a bundle.
29
+ */ /** What the `hostAutomations` zone hands a widget. */ /** What the `automationRun` zone hands a widget. */ export { };
30
+
31
+ //# sourceMappingURL=ai-automation-zones.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-automation-zones.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 * What the workflows plugin's Automation page zones hand this plugin's\n * widgets.\n *\n * `hostAutomations`, `automationEditor` and `automationRun` are zones the\n * workflows plugin hosts and declares (`registerPluginZone`), with their props\n * on its own zone tokens. A plugin never imports another plugin, so the\n * widgets registered there read the props through the shapes restated here —\n * the zone's props as the host documents them, by the same names. A widget is\n * handed whatever the host passes, so this restates what the host SENDS; every\n * member below is one the host documents in the injection zones reference.\n *\n * Types only: nothing here reaches a bundle.\n */\n\n/** What the `hostAutomations` zone hands a widget. */\nexport interface ConsoleHostAutomationsZoneProps {\n hostId: string\n /** The org the page names; `undefined` while it resolves. */\n orgId: string | undefined\n /**\n * Opens the Actions editor on a listed action. `false` when the id names no\n * action the list has read yet.\n */\n openAction: (actionId: string) => boolean\n}\n\n/** One automation on the Automation page: an action, or a workflow. */\nexport interface ConsoleAutomationTarget {\n type: 'action' | 'workflow'\n id: string\n name: string\n}\n\n/** What the `automationEditor` zone hands a widget. */\nexport interface ConsoleAutomationEditorZoneProps {\n hostId: string\n orgId: string | undefined\n /** The automation the editor has open, as it is stored. */\n target: ConsoleAutomationTarget\n}\n\n/** What the `automationRun` zone hands a widget. */\nexport interface ConsoleAutomationRunZoneProps {\n hostId: string\n orgId: string | undefined\n /** The automation the run belongs to. */\n target: ConsoleAutomationTarget\n /** The run's entry in the site's activity log. */\n runId: string\n}\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;;;;;;CAaC,GAED,oDAAoD,GA2BpD,kDAAkD,GAClD,WAOC"}
@@ -1,4 +1,4 @@
1
- import type { ConsoleHostAutomationsZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins';
1
+ import type { ConsoleHostAutomationsZoneProps } from './ai-automation-zones';
2
2
  import { useAiJobRun } from './use-ai-job-run';
3
3
  export declare const AI_AUTOMATION_BRIEF_COPY: {
4
4
  readonly title: "Describe an automation";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-describe-automation.component.tsx"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { ConsoleHostAutomationsZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Link,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport { aiJobProblem, useAiJobRun, useAiJobsVerdict } from './use-ai-job-run'\n\n/**\n * \"Describe it\" on the Automation page's Actions (AGL-2919), beside Add action\n * and Recipes through the `hostAutomations` zone: a description becomes a\n * `workflow` job that drafts one automation, switched off.\n *\n * The dialog follows the shared brief dialog's shape — one text box, what\n * happens next, and the action that starts the job — and then follows the\n * job, because an automation's draft needs no plan to confirm: when it is\n * written, the dialog says what to fill in and opens it in the Actions editor.\n */\n\n/** The longest description a job admits. */\nconst BRIEF_MAX_CHARS = 4_000\n\nexport const AI_AUTOMATION_BRIEF_COPY = {\n title: 'Describe an automation',\n briefLabel: 'What should happen, and when?',\n placeholder:\n 'When someone submits the newsletter form, add them to the newsletter list, make them ' +\n 'a lead and send them a welcome email',\n next:\n 'The automation is drafted switched off, using only the triggers and steps your plan ' +\n 'includes. Anything it needs that your site does not have yet is left for you to fill in.',\n submit: 'Draft the automation',\n started:\n 'The automation is being drafted. This can take a few minutes. You can close this ' +\n 'window: the draft appears in the list, switched off, when it is ready.',\n drafted: 'Drafted',\n review: 'Review it',\n notListed: 'The draft is saved. It appears in the list in a moment.',\n failed: 'The automation could not be drafted. Try again.',\n} as const\n\nexport interface AiAutomationBriefDialogProps {\n open: boolean\n onClose: () => void\n orgId: string | undefined\n hostId: string\n /** Who is signed in, whose token the requests carry. */\n user: Parameters<typeof useAiJobRun>[0]\n /** Opens a listed action in the Actions editor; `false` when the list has not read it yet. */\n openAction: (actionId: string) => boolean\n}\n\nexport function AiAutomationBriefDialog(props: AiAutomationBriefDialogProps) {\n const { open, onClose, orgId, hostId, user, openAction } = props\n const copy = AI_AUTOMATION_BRIEF_COPY\n const help = pluginDocsHelp('aiAutomations')\n const [brief, setBrief] = useState('')\n const [notListed, setNotListed] = useState(false)\n const run = useAiJobRun(user, copy.failed)\n const { job, starting, running, notice, reset } = run\n\n // Opening or closing starts over and stops following: a job still drafting\n // carries on in AI jobs, and its draft appears in the list when it is written.\n useEffect(() => {\n reset()\n setNotListed(false)\n }, [open, reset])\n\n const draft = job?.status === 'done' ? job.outputs.find((output) => output.resource === 'workflow') : undefined\n const problem = aiJobProblem(job, copy.failed) ?? (job?.status === 'done' && !draft ? copy.failed : null)\n const asking = !job || Boolean(notice)\n\n const start = async () => {\n const trimmed = brief.trim()\n if (!orgId || !trimmed) return\n setNotListed(false)\n if (await run.start({ orgId, hostId, kind: 'workflow', brief: trimmed, inputs: { mode: 'draft' } })) {\n setBrief('')\n }\n }\n\n const review = () => {\n if (!draft) return\n if (openAction(draft.id)) onClose()\n else setNotListed(true)\n }\n\n return (\n <Dialog open={open} onClose={starting ? undefined : onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle>{copy.title}</DialogTitle>\n <DialogContent>\n {asking ? (\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n label={copy.briefLabel}\n placeholder={copy.placeholder}\n multiline\n minRows={4}\n value={brief}\n onChange={(event) => setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS))}\n helperText={`${brief.length.toLocaleString('en-US')} / ${BRIEF_MAX_CHARS.toLocaleString('en-US')}`}\n disabled={starting}\n autoFocus\n />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {copy.next}{' '}\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Typography>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n </Stack>\n ) : (\n <Stack spacing={2} sx={{ pt: 1 }}>\n {running ? (\n <Alert severity=\"info\" icon={<CircularProgress size={18} aria-label=\"Drafting\" />}>\n {copy.started}\n </Alert>\n ) : null}\n {draft ? (\n <Alert severity=\"success\">\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {`${copy.drafted}: ${draft.label}`}\n </Typography>\n {draft.note ? <Typography variant=\"body2\">{draft.note}</Typography> : null}\n </Alert>\n ) : null}\n {problem ? <Alert severity={job?.status === 'failed' ? 'error' : 'warning'}>{problem}</Alert> : null}\n {notListed ? <Alert severity=\"info\">{copy.notListed}</Alert> : null}\n </Stack>\n )}\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose} disabled={starting}>\n {asking ? 'Cancel' : 'Close'}\n </Button>\n {asking ? (\n <Button\n variant=\"contained\"\n onClick={() => void start()}\n disabled={starting || !orgId || !brief.trim()}\n >\n {copy.submit}\n </Button>\n ) : draft ? (\n <Button variant=\"contained\" onClick={review}>\n {copy.review}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\n/**\n * Renders nothing until the jobs route has answered for this workspace, and\n * nothing when it says the feature is not this workspace's.\n */\nexport function AiDescribeAutomationButton({ hostId, orgId, openAction }: ConsoleHostAutomationsZoneProps) {\n const { data: user } = useUser()\n const verdict = useAiJobsVerdict(user, orgId)\n const [open, setOpen] = useState(false)\n\n if (verdict !== 'ready') return null\n return (\n <>\n <Button size=\"small\" variant=\"outlined\" onClick={() => setOpen(true)}>\n {'Describe it'}\n </Button>\n <AiAutomationBriefDialog\n open={open}\n onClose={() => setOpen(false)}\n orgId={orgId}\n hostId={hostId}\n user={user}\n openAction={openAction}\n />\n </>\n )\n}\n\nexport default AiDescribeAutomationButton\n"],"names":["pluginDocsHelp","useUser","Alert","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","Link","Stack","TextField","Typography","useEffect","useState","aiJobProblem","useAiJobRun","useAiJobsVerdict","BRIEF_MAX_CHARS","AI_AUTOMATION_BRIEF_COPY","title","briefLabel","placeholder","next","submit","started","drafted","review","notListed","failed","AiAutomationBriefDialog","props","open","onClose","orgId","hostId","user","openAction","copy","help","brief","setBrief","setNotListed","run","job","starting","running","notice","reset","draft","status","outputs","find","output","resource","undefined","problem","asking","Boolean","start","trimmed","trim","kind","inputs","mode","id","fullWidth","maxWidth","spacing","sx","pt","label","multiline","minRows","value","onChange","event","target","slice","helperText","length","toLocaleString","disabled","autoFocus","variant","color","href","rel","excerpt","severity","icon","size","aria-label","fontWeight","note","onClick","AiDescribeAutomationButton","data","verdict","setOpen"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-describe-automation.component.tsx"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { ConsoleHostAutomationsZoneProps } from './ai-automation-zones'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Link,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport { aiJobProblem, useAiJobRun, useAiJobsVerdict } from './use-ai-job-run'\n\n/**\n * \"Describe it\" on the Automation page's Actions (AGL-2919), beside Add action\n * and Recipes through the `hostAutomations` zone: a description becomes a\n * `workflow` job that drafts one automation, switched off.\n *\n * The dialog follows the shared brief dialog's shape — one text box, what\n * happens next, and the action that starts the job — and then follows the\n * job, because an automation's draft needs no plan to confirm: when it is\n * written, the dialog says what to fill in and opens it in the Actions editor.\n */\n\n/** The longest description a job admits. */\nconst BRIEF_MAX_CHARS = 4_000\n\nexport const AI_AUTOMATION_BRIEF_COPY = {\n title: 'Describe an automation',\n briefLabel: 'What should happen, and when?',\n placeholder:\n 'When someone submits the newsletter form, add them to the newsletter list, make them ' +\n 'a lead and send them a welcome email',\n next:\n 'The automation is drafted switched off, using only the triggers and steps your plan ' +\n 'includes. Anything it needs that your site does not have yet is left for you to fill in.',\n submit: 'Draft the automation',\n started:\n 'The automation is being drafted. This can take a few minutes. You can close this ' +\n 'window: the draft appears in the list, switched off, when it is ready.',\n drafted: 'Drafted',\n review: 'Review it',\n notListed: 'The draft is saved. It appears in the list in a moment.',\n failed: 'The automation could not be drafted. Try again.',\n} as const\n\nexport interface AiAutomationBriefDialogProps {\n open: boolean\n onClose: () => void\n orgId: string | undefined\n hostId: string\n /** Who is signed in, whose token the requests carry. */\n user: Parameters<typeof useAiJobRun>[0]\n /** Opens a listed action in the Actions editor; `false` when the list has not read it yet. */\n openAction: (actionId: string) => boolean\n}\n\nexport function AiAutomationBriefDialog(props: AiAutomationBriefDialogProps) {\n const { open, onClose, orgId, hostId, user, openAction } = props\n const copy = AI_AUTOMATION_BRIEF_COPY\n const help = pluginDocsHelp('aiAutomations')\n const [brief, setBrief] = useState('')\n const [notListed, setNotListed] = useState(false)\n const run = useAiJobRun(user, copy.failed)\n const { job, starting, running, notice, reset } = run\n\n // Opening or closing starts over and stops following: a job still drafting\n // carries on in AI jobs, and its draft appears in the list when it is written.\n useEffect(() => {\n reset()\n setNotListed(false)\n }, [open, reset])\n\n const draft = job?.status === 'done' ? job.outputs.find((output) => output.resource === 'workflow') : undefined\n const problem = aiJobProblem(job, copy.failed) ?? (job?.status === 'done' && !draft ? copy.failed : null)\n const asking = !job || Boolean(notice)\n\n const start = async () => {\n const trimmed = brief.trim()\n if (!orgId || !trimmed) return\n setNotListed(false)\n if (await run.start({ orgId, hostId, kind: 'workflow', brief: trimmed, inputs: { mode: 'draft' } })) {\n setBrief('')\n }\n }\n\n const review = () => {\n if (!draft) return\n if (openAction(draft.id)) onClose()\n else setNotListed(true)\n }\n\n return (\n <Dialog open={open} onClose={starting ? undefined : onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle>{copy.title}</DialogTitle>\n <DialogContent>\n {asking ? (\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n label={copy.briefLabel}\n placeholder={copy.placeholder}\n multiline\n minRows={4}\n value={brief}\n onChange={(event) => setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS))}\n helperText={`${brief.length.toLocaleString('en-US')} / ${BRIEF_MAX_CHARS.toLocaleString('en-US')}`}\n disabled={starting}\n autoFocus\n />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {copy.next}{' '}\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Typography>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n </Stack>\n ) : (\n <Stack spacing={2} sx={{ pt: 1 }}>\n {running ? (\n <Alert severity=\"info\" icon={<CircularProgress size={18} aria-label=\"Drafting\" />}>\n {copy.started}\n </Alert>\n ) : null}\n {draft ? (\n <Alert severity=\"success\">\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {`${copy.drafted}: ${draft.label}`}\n </Typography>\n {draft.note ? <Typography variant=\"body2\">{draft.note}</Typography> : null}\n </Alert>\n ) : null}\n {problem ? <Alert severity={job?.status === 'failed' ? 'error' : 'warning'}>{problem}</Alert> : null}\n {notListed ? <Alert severity=\"info\">{copy.notListed}</Alert> : null}\n </Stack>\n )}\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose} disabled={starting}>\n {asking ? 'Cancel' : 'Close'}\n </Button>\n {asking ? (\n <Button\n variant=\"contained\"\n onClick={() => void start()}\n disabled={starting || !orgId || !brief.trim()}\n >\n {copy.submit}\n </Button>\n ) : draft ? (\n <Button variant=\"contained\" onClick={review}>\n {copy.review}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\n/**\n * Renders nothing until the jobs route has answered for this workspace, and\n * nothing when it says the feature is not this workspace's.\n */\nexport function AiDescribeAutomationButton({ hostId, orgId, openAction }: ConsoleHostAutomationsZoneProps) {\n const { data: user } = useUser()\n const verdict = useAiJobsVerdict(user, orgId)\n const [open, setOpen] = useState(false)\n\n if (verdict !== 'ready') return null\n return (\n <>\n <Button size=\"small\" variant=\"outlined\" onClick={() => setOpen(true)}>\n {'Describe it'}\n </Button>\n <AiAutomationBriefDialog\n open={open}\n onClose={() => setOpen(false)}\n orgId={orgId}\n hostId={hostId}\n user={user}\n openAction={openAction}\n />\n </>\n )\n}\n\nexport default AiDescribeAutomationButton\n"],"names":["pluginDocsHelp","useUser","Alert","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","Link","Stack","TextField","Typography","useEffect","useState","aiJobProblem","useAiJobRun","useAiJobsVerdict","BRIEF_MAX_CHARS","AI_AUTOMATION_BRIEF_COPY","title","briefLabel","placeholder","next","submit","started","drafted","review","notListed","failed","AiAutomationBriefDialog","props","open","onClose","orgId","hostId","user","openAction","copy","help","brief","setBrief","setNotListed","run","job","starting","running","notice","reset","draft","status","outputs","find","output","resource","undefined","problem","asking","Boolean","start","trimmed","trim","kind","inputs","mode","id","fullWidth","maxWidth","spacing","sx","pt","label","multiline","minRows","value","onChange","event","target","slice","helperText","length","toLocaleString","disabled","autoFocus","variant","color","href","rel","excerpt","severity","icon","size","aria-label","fontWeight","note","onClick","AiDescribeAutomationButton","data","verdict","setOpen"],"mappings":"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"}
@@ -14,7 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import type { ConsoleHostComponentsZoneProps, ConsoleHostFormsZoneProps, ConsoleHostLayoutsZoneProps, ConsoleHostScreensZoneProps, ConsoleHostTemplatesZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins';
17
+ import type { ConsoleHostComponentsZoneProps, ConsoleHostLayoutsZoneProps, ConsoleHostScreensZoneProps, ConsoleHostTemplatesZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins';
18
18
  import { type AiBriefKind } from './ai-brief-dialog.component';
19
19
  export interface AiDescribeButtonProps extends ConsoleHostScreensZoneProps {
20
20
  /** The job the brief starts: what the page the button sits on lists. */
@@ -32,8 +32,11 @@ export declare function AiDescribeButton({ kind, hostId, orgId }: AiDescribeButt
32
32
  export declare function AiDescribeTemplateButton(props: ConsoleHostTemplatesZoneProps): import("react").JSX.Element;
33
33
  /** A layout from a brief, on the Layouts page (`hostLayouts`). */
34
34
  export declare function AiDescribeLayoutButton(props: ConsoleHostLayoutsZoneProps): import("react").JSX.Element;
35
- /** A form from a brief, on the Forms page (`hostForms`). */
36
- export declare function AiDescribeFormButton(props: ConsoleHostFormsZoneProps): import("react").JSX.Element;
35
+ /**
36
+ * A form from a brief, on the Forms page (`hostForms`, which the forms plugin
37
+ * hosts on the `hostScreens` contract).
38
+ */
39
+ export declare function AiDescribeFormButton(props: ConsoleHostScreensZoneProps): import("react").JSX.Element;
37
40
  /** A reusable component from a brief, on the Components page (`hostComponents`). */
38
41
  export declare function AiDescribeComponentButton(props: ConsoleHostComponentsZoneProps): import("react").JSX.Element;
39
42
  export default AiDescribeButton;
@@ -72,7 +72,10 @@ import { AiBriefDialog } from "./ai-brief-dialog.component.js";
72
72
  kind: "layout"
73
73
  }));
74
74
  }
75
- /** A form from a brief, on the Forms page (`hostForms`). */ export function AiDescribeFormButton(props) {
75
+ /**
76
+ * A form from a brief, on the Forms page (`hostForms`, which the forms plugin
77
+ * hosts on the `hostScreens` contract).
78
+ */ export function AiDescribeFormButton(props) {
76
79
  return /*#__PURE__*/ _jsx(AiDescribeButton, _extends({}, props, {
77
80
  kind: "form"
78
81
  }));