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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +25 -0
  3. package/src/lib/activity/ai-activity-actions.js +85 -4
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/components/ai-brief-dialog.component.js +46 -2
  6. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  7. package/src/lib/components/ai-campaign-create.component.js +1 -1
  8. package/src/lib/components/ai-campaign-create.component.js.map +1 -1
  9. package/src/lib/components/ai-credits-card.component.js +3 -3
  10. package/src/lib/components/ai-credits-card.component.js.map +1 -1
  11. package/src/lib/components/ai-crm-email-draft.component.js +1 -1
  12. package/src/lib/components/ai-crm-email-draft.component.js.map +1 -1
  13. package/src/lib/components/ai-crm-import-mapping.component.js +1 -1
  14. package/src/lib/components/ai-crm-import-mapping.component.js.map +1 -1
  15. package/src/lib/components/ai-crm-record-card.component.js +1 -1
  16. package/src/lib/components/ai-crm-record-card.component.js.map +1 -1
  17. package/src/lib/components/ai-describe-automation.component.js +4 -2
  18. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  19. package/src/lib/components/ai-describe-org-automation.component.js +1 -1
  20. package/src/lib/components/ai-describe-org-automation.component.js.map +1 -1
  21. package/src/lib/components/ai-experiment-cards.component.js +2 -2
  22. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  23. package/src/lib/components/ai-explain-automation.component.js +1 -1
  24. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  25. package/src/lib/components/ai-insight-card.component.js +3 -1
  26. package/src/lib/components/ai-insight-card.component.js.map +1 -1
  27. package/src/lib/components/ai-job-links.d.ts +6 -0
  28. package/src/lib/components/ai-job-links.js +9 -0
  29. package/src/lib/components/ai-job-links.js.map +1 -1
  30. package/src/lib/components/ai-jobs-list-page.component.d.ts +6 -1
  31. package/src/lib/components/ai-jobs-list-page.component.js +15 -2
  32. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  33. package/src/lib/components/ai-logic.component.js +5 -3
  34. package/src/lib/components/ai-logic.component.js.map +1 -1
  35. package/src/lib/components/ai-media-create.component.js +88 -16
  36. package/src/lib/components/ai-media-create.component.js.map +1 -1
  37. package/src/lib/components/ai-model-selector.component.js +8 -0
  38. package/src/lib/components/ai-model-selector.component.js.map +1 -1
  39. package/src/lib/components/ai-overlay-cards.component.js +2 -2
  40. package/src/lib/components/ai-overlay-cards.component.js.map +1 -1
  41. package/src/lib/components/ai-product-copy-card.component.js +2 -1
  42. package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
  43. package/src/lib/components/ai-products-hub-card.component.js +2 -1
  44. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  45. package/src/lib/components/ai-revise-automation.component.js +1 -1
  46. package/src/lib/components/ai-revise-automation.component.js.map +1 -1
  47. package/src/lib/components/ai-seo-fields-card.component.js +7 -3
  48. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  49. package/src/lib/components/ai-site-batch-card.component.js +7 -4
  50. package/src/lib/components/ai-site-batch-card.component.js.map +1 -1
  51. package/src/lib/components/ai-site-build-page.component.js +48 -6
  52. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  53. package/src/lib/components/ai-site-seo-start-card.component.js +7 -4
  54. package/src/lib/components/ai-site-seo-start-card.component.js.map +1 -1
  55. package/src/lib/components/ai-site-start-card.component.js +152 -36
  56. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  57. package/src/lib/components/ai-theme-proposal-card.component.js +1 -0
  58. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  59. package/src/lib/components/ai-upsell-dialog.component.js +17 -3
  60. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  61. package/src/lib/components/ai-usage-strip.component.js +13 -2
  62. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  63. package/src/lib/components/assist-build-card.component.js +20 -3
  64. package/src/lib/components/assist-build-card.component.js.map +1 -1
  65. package/src/lib/components/assist-jobs-drawer.component.js +7 -1
  66. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  67. package/src/lib/components/billing-ai-allotments.component.js +2 -2
  68. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  69. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  70. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  71. package/src/lib/components/billing-assist-overage-card.component.js +2 -2
  72. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  73. package/src/lib/components/host-ai-allotments.component.js +2 -2
  74. package/src/lib/components/host-ai-allotments.component.js.map +1 -1
  75. package/src/lib/components/member-ai-allotment-card.component.js +2 -2
  76. package/src/lib/components/member-ai-allotment-card.component.js.map +1 -1
  77. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  78. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  79. package/src/lib/components/staff-org-ai-conversations.component.d.ts +9 -3
  80. package/src/lib/components/staff-org-ai-conversations.component.js +174 -83
  81. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -1
  82. package/src/lib/components/staff-user-ai-usage-card.component.js +2 -1
  83. package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
  84. package/src/lib/jobs/ai-job-admission.d.ts +2 -1
  85. package/src/lib/jobs/ai-job-admission.js.map +1 -1
  86. package/src/lib/jobs/ai-job-email-step.d.ts +39 -0
  87. package/src/lib/jobs/ai-job-email-step.js +116 -5
  88. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  89. package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
  90. package/src/lib/jobs/ai-job-layout-language.js +6 -1
  91. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  92. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +14 -0
  93. package/src/lib/jobs/ai-job-layout-site-pages.js +43 -11
  94. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  95. package/src/lib/jobs/ai-job-layout-step.js +3 -2
  96. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  97. package/src/lib/jobs/ai-job-page-language.d.ts +26 -1
  98. package/src/lib/jobs/ai-job-page-language.js +75 -13
  99. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  100. package/src/lib/jobs/ai-job-page-step.d.ts +3 -0
  101. package/src/lib/jobs/ai-job-page-step.js +32 -7
  102. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  103. package/src/lib/jobs/ai-job-plan-step.d.ts +34 -0
  104. package/src/lib/jobs/ai-job-plan-step.js +106 -14
  105. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  106. package/src/lib/jobs/ai-job-site-content.d.ts +81 -13
  107. package/src/lib/jobs/ai-job-site-content.js +210 -43
  108. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  109. package/src/lib/jobs/ai-job-site-step.d.ts +19 -0
  110. package/src/lib/jobs/ai-job-site-step.js +152 -51
  111. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  112. package/src/lib/jobs/ai-jobs.d.ts +8 -4
  113. package/src/lib/jobs/ai-jobs.js +19 -2
  114. package/src/lib/jobs/ai-jobs.js.map +1 -1
  115. package/src/lib/jobs/ai-layout-stock-photos.d.ts +98 -0
  116. package/src/lib/jobs/ai-layout-stock-photos.js +248 -0
  117. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -0
  118. package/src/lib/jobs/ai-site-publish.d.ts +11 -0
  119. package/src/lib/jobs/ai-site-publish.js +31 -1
  120. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  121. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.d.ts +151 -0
  122. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js +183 -0
  123. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js.map +1 -0
  124. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.d.ts +27 -0
  125. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js +192 -0
  126. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js.map +1 -0
  127. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.d.ts +27 -0
  128. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js +222 -0
  129. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js.map +1 -0
  130. package/src/lib/layout-language/ai-layout-compiler.d.ts +37 -0
  131. package/src/lib/layout-language/ai-layout-compiler.js +1003 -19
  132. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  133. package/src/lib/layout-language/ai-layout-design.d.ts +114 -0
  134. package/src/lib/layout-language/ai-layout-design.js +209 -0
  135. package/src/lib/layout-language/ai-layout-design.js.map +1 -0
  136. package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
  137. package/src/lib/layout-language/ai-layout-frame.js +33 -8
  138. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  139. package/src/lib/layout-language/ai-layout-gaps.js +18 -2
  140. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  141. package/src/lib/layout-language/ai-layout-language.d.ts +2 -0
  142. package/src/lib/layout-language/ai-layout-language.js +15 -1
  143. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  144. package/src/lib/layout-language/ai-layout-links.d.ts +36 -0
  145. package/src/lib/layout-language/ai-layout-links.js +26 -2
  146. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  147. package/src/lib/layout-language/ai-layout-listings.d.ts +125 -0
  148. package/src/lib/layout-language/ai-layout-listings.js +187 -0
  149. package/src/lib/layout-language/ai-layout-listings.js.map +1 -0
  150. package/src/lib/layout-language/ai-layout-pictures.d.ts +11 -5
  151. package/src/lib/layout-language/ai-layout-pictures.js +80 -17
  152. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  153. package/src/lib/model/ai-job-notice.d.ts +9 -1
  154. package/src/lib/model/ai-job-notice.js +23 -0
  155. package/src/lib/model/ai-job-notice.js.map +1 -1
  156. package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
  157. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  158. package/src/lib/model/ai-site-build-progress.d.ts +12 -2
  159. package/src/lib/model/ai-site-build-progress.js +104 -15
  160. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  161. package/src/lib/model/ai-site-job.d.ts +236 -24
  162. package/src/lib/model/ai-site-job.js +269 -22
  163. package/src/lib/model/ai-site-job.js.map +1 -1
  164. package/src/lib/model/ai-site-kinds.d.ts +30 -8
  165. package/src/lib/model/ai-site-kinds.js +735 -116
  166. package/src/lib/model/ai-site-kinds.js.map +1 -1
  167. package/src/lib/model/ai-site-look.d.ts +21 -0
  168. package/src/lib/model/ai-site-look.js +63 -14
  169. package/src/lib/model/ai-site-look.js.map +1 -1
  170. package/src/lib/model/ai-site-start-seo.d.ts +30 -0
  171. package/src/lib/model/ai-site-start-seo.js +132 -11
  172. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  173. package/src/lib/model/ai-site-start.d.ts +2 -2
  174. package/src/lib/model/ai-site-start.js +3 -3
  175. package/src/lib/model/ai-site-start.js.map +1 -1
  176. package/src/lib/plugin.js +15 -3
  177. package/src/lib/plugin.js.map +1 -1
  178. package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -1
  179. package/src/lib/runtime/ai-doctrine-validators.js +227 -4
  180. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  181. package/src/lib/runtime/ai-doctrine.js +5 -3
  182. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  183. package/src/lib/runtime/ai-palette.generated.d.ts +2 -0
  184. package/src/lib/runtime/ai-palette.generated.js +31 -5
  185. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  186. package/src/lib/server/ai-admin-conversations.d.ts +9 -0
  187. package/src/lib/server/ai-admin-conversations.js +151 -9
  188. package/src/lib/server/ai-admin-conversations.js.map +1 -1
  189. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  190. package/src/lib/server/ai-jobs-route.js +15 -3
  191. package/src/lib/server/ai-jobs-route.js.map +1 -1
  192. package/src/lib/server/assist-edit.js +5 -2
  193. package/src/lib/server/assist-edit.js.map +1 -1
  194. package/src/lib/usage/free-ai-credits-left.d.ts +54 -0
  195. package/src/lib/usage/free-ai-credits-left.js +62 -0
  196. package/src/lib/usage/free-ai-credits-left.js.map +1 -0
  197. package/src/lib/usage/staff-org-ai-conversations.d.ts +23 -0
  198. package/src/lib/usage/staff-org-ai-conversations.js +45 -0
  199. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/assist-jobs-drawer.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { checkEntitlement } from '@aglyn/aglyn'\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport { formatBytes } from '@aglyn/aglyn/app-utils/measure-node-map'\nimport {\n AI_JOB_TERMINAL_STATUSES,\n type AiJobOutput,\n type AiJobSummary,\n} from '../model/ai-jobs.types'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { mdiChevronDown, mdiChevronUp } from '@aglyn/shared-data-mdi'\nimport { AppLink, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Box,\n Button,\n Chip,\n CircularProgress,\n Collapse,\n IconButton,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n AI_JOB_PHASE_LABELS,\n aiJobActivityCounts,\n aiJobPhase,\n aiJobsHeaderChips,\n type AiJobActivityState,\n type AiJobPhase,\n} from '../model/ai-job-activity'\nimport type { AiInsightSurface } from '../model/ai-insight'\nimport { cancelAiJobRequest, resumeAiJobRequest, type AiJobResumeOptions } from './ai-job-requests'\nimport { aiBuildCanRetry, aiBuildItemRows, aiSitePartialCopy } from '../model/ai-build-progress'\nimport { publishAiJob, type AiJobsOpenRequest } from './ai-jobs-store'\nimport { AiPageBriefDialog } from './ai-page-brief-dialog.component'\nimport { AiInsightDialog } from './ai-insight-dialog.component'\nimport { AiJobPlan } from './ai-job-plan.component'\nimport { aiJobOutputHref, aiJobPrimaryLink, aiSiteBuildHref } from './ai-job-links'\nimport { aiJobRefundCopy } from '../model/ai-job-failure-copy'\nimport { readEventFrames } from './ai-job-events'\n\n// Where an output opens, and the stream reader, live beside the dialogs that\n// share them (AGL-3593); this module has always exported them.\nexport { aiJobOutputHref, aiJobPrimaryLink, readEventFrames }\n\n/**\n * The AI jobs drawer inside the Assist panel (AGL-2904): the workspace's\n * running and recent generation jobs, each step's progress, an \"open\n * draft\" link per output, and cancel.\n *\n * Reads through the routes, never Firestore directly, so the same\n * membership, flag and entitlement rungs answer every read; progress comes\n * from the events route's server-sent stream, re-opened when the server\n * says `reconnect`. Everything loads on EXPAND rather than on mount: the\n * panel opens on every console page, and a list nobody asked for is a read\n * on every page view.\n *\n * It opens EXPANDED while any of the workspace's jobs is moving or waiting\n * for the person (AGL-3593) — the shared list the top-bar indicator reads\n * says so — because a plan waiting behind a collapsed row is a site that\n * never gets built. An \"Open AI jobs\" anywhere in the console expands it,\n * scrolls to the job it names and highlights it for a moment.\n *\n * Hidden — not disabled — unless the flag shows it AND the plan carries\n * `aiGenerative`: a released-off feature does not exist, and a plan without\n * it is sold the add-on on the Billing page, not here.\n */\n\n/** Jobs the drawer lists; the route caps higher, the drawer is a glance. */\nconst LIST_LIMIT = 10\n\n/** How much of a brief a row shows. */\nconst BRIEF_PREVIEW_CHARS = 90\n\n/**\n * A row's chip says where the job stands in the words every AI surface uses\n * (AGL-3593): a plan waiting to be confirmed is \"Plan ready\", never \"Running\"\n * and never the machine's `needs_review`.\n */\nconst PHASE_COLOR: Record<AiJobPhase, 'default' | 'primary' | 'success' | 'error' | 'warning'> = {\n queued: 'default',\n planning: 'primary',\n 'plan-ready': 'warning',\n building: 'primary',\n attention: 'warning',\n done: 'success',\n failed: 'error',\n canceled: 'default',\n}\n\n/** The header's count chips, colored as the rows they count. */\nconst STATE_COLOR: Record<AiJobActivityState, 'primary' | 'warning'> = {\n 'needs-you': 'warning',\n attention: 'warning',\n running: 'primary',\n}\n\n/** What each item state reads as in a row (AGL-3616). */\nconst ITEM_STATE_LABELS = {\n done: 'built',\n active: 'building',\n waiting: 'waiting',\n failed: 'failed',\n skipped: 'not built',\n} as const\n\n/** How long a job AI jobs was opened on stays highlighted. */\nconst HIGHLIGHT_MS = 2_500\n\n/**\n * Where a guided start that ended unbuilt is tried again (AGL-3596): its\n * \"Building your site\" page, opened on its own answers (`?retry=1`). Only for\n * a job of the site the console is on, whose subdomain the address names;\n * `null` otherwise.\n */\nexport function aiJobRestartHref(\n job: Pick<AiJobSummary, 'id' | 'kind' | 'status' | 'hostId' | 'siteInputs'>,\n input: { orgSlug: string; hostId?: string | null; pathname: string },\n): string | null {\n if (job.kind !== 'site' || (job.status !== 'failed' && job.status !== 'canceled')) return null\n if (!job.siteInputs || !job.hostId || job.hostId !== input.hostId) return null\n const subdomain = /^\\/[^/]+\\/hosts\\/([^/?#]+)/.exec(input.pathname)?.[1] ?? null\n const href = aiSiteBuildHref(input.orgSlug, subdomain, job.id)\n return href ? `${href}?retry=1` : null\n}\n\n/** What a job cost the person: what it spent, less what we refunded. */\nfunction netCredits(job: Pick<AiJobSummary, 'creditsSpent' | 'refundedCredits'>): number {\n return Math.max(0, (job.creditsSpent ?? 0) - (job.refundedCredits ?? 0))\n}\n\n/**\n * The navigation entry a page job proposes for the page it built (AGL-2907),\n * or `null`. The job writes no menu: the member adds the entry once the page\n * is live.\n */\nexport function aiJobNavigationProposal(output: AiJobOutput): string | null {\n if (output.resource !== 'screen') return null\n const navigation = output.proposal?.['navigation']\n if (!navigation || typeof navigation !== 'object') return null\n const label = (navigation as Record<string, unknown>)['label']\n return typeof label === 'string' && label.trim() ? label.trim() : null\n}\n\n\nexport interface AssistJobsDrawerProps {\n /** The org the panel is scoped to — `undefined` where the page named none. */\n orgId: string | undefined\n org: Partial<AglynOrgBilling> | undefined\n orgReady: boolean\n orgSlug: string\n user: Parameters<typeof authorizedFetch>[0]\n /** The `release_ai_generative` verdict, staff bypass applied (the shell's). */\n visible: boolean\n /**\n * The viewer is staff (AGL-3078): a job that stopped for a broken rule shows\n * them where the refused answer broke it. Who may read a job is unchanged.\n */\n isStaff?: boolean\n /** The site the console page is on, when it is on one: a page is described for a site. */\n hostId?: string | null\n /**\n * The page's insight surface (AGL-2915) — a site's Analytics, Data or CRM\n * Reports page, or the workspace's Data page — with the site's subdomain;\n * `null` elsewhere, where no question about the figures is offered.\n */\n insight?: { surface: Exclude<AiInsightSurface, 'digest'>; host: string | null } | null\n /**\n * The workspace's jobs that are not yet settled, from the shared store the\n * top-bar indicator reads (AGL-3593). The header counts them, and the\n * drawer opens expanded while there is any. Absent, the drawer counts what\n * it loaded itself.\n */\n inFlight?: readonly AiJobSummary[]\n /**\n * The latest request to open AI jobs (AGL-3593): a new `seq` expands the\n * drawer, and a named job is scrolled to and highlighted.\n */\n focus?: AiJobsOpenRequest | null\n}\n\nexport function AssistJobsDrawer({\n orgId,\n org,\n orgReady,\n orgSlug,\n user,\n visible,\n isStaff = false,\n hostId,\n insight,\n inFlight,\n focus,\n}: AssistJobsDrawerProps): JSX.Element | null {\n const entitled = orgReady && checkEntitlement(org as never, 'aiGenerative')\n const [expanded, setExpanded] = useState(false)\n /**\n * The person opened or closed the list themselves: from then on the\n * expanded-by-default rule leaves their choice alone until the workspace\n * changes.\n */\n const toggledRef = useRef(false)\n /** The job AI jobs was opened on, highlighted for a moment once it is listed. */\n const [highlight, setHighlight] = useState<string | null>(null)\n const listRef = useRef<HTMLDivElement | null>(null)\n const [jobs, setJobs] = useState<AiJobSummary[]>([])\n const [loading, setLoading] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n /** Whether the page brief dialog is open (AGL-2907). */\n const [describing, setDescribing] = useState(false)\n /**\n * The insight dialog (AGL-2915): `'ask'` for a new question, a job id for an\n * answer a job already wrote, `null` when closed.\n */\n const [insightOpen, setInsightOpen] = useState<string | null>(null)\n /** Job ids whose terminal event has been tracked, so a re-read does not count twice. */\n const trackedRef = useRef(new Set<string>())\n /** The job whose stream is open, so a re-render does not open a second. */\n const watchingRef = useRef<string | null>(null)\n\n const patchJob = useCallback((next: AiJobSummary) => {\n // Every counting surface moves with the row (AGL-3593).\n publishAiJob(next)\n setJobs((prior) => {\n const index = prior.findIndex((job) => job.id === next.id)\n if (index === -1) return [next, ...prior].slice(0, LIST_LIMIT)\n const copy = [...prior]\n copy[index] = next\n return copy\n })\n if (\n AI_JOB_TERMINAL_STATUSES.includes(next.status) &&\n !trackedRef.current.has(next.id)\n ) {\n trackedRef.current.add(next.id)\n if (next.status === 'done') {\n trackEvent('ai_job_completed', { kind: next.kind, credits: next.creditsSpent })\n } else if (next.status === 'failed') {\n trackEvent('ai_job_failed', { kind: next.kind })\n }\n }\n }, [])\n\n const load = useCallback(async () => {\n if (!orgId) return\n setLoading(true)\n setNotice(null)\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=${LIST_LIMIT}`,\n )\n if (!response.ok) {\n const payload = await response.json().catch(() => null)\n setNotice(String(payload?.error ?? 'The AI jobs could not be loaded — try again.'))\n return\n }\n const payload = (await response.json()) as { jobs: AiJobSummary[] }\n // Every job that arrives already terminal is one the drawer did not\n // watch finish, so its outcome is not this session's to count.\n for (const job of payload.jobs) {\n if (AI_JOB_TERMINAL_STATUSES.includes(job.status)) trackedRef.current.add(job.id)\n // A job the indicator still counts that has settled since leaves it.\n publishAiJob(job)\n }\n setJobs(payload.jobs)\n } catch {\n setNotice('The AI jobs could not be loaded — try again.')\n } finally {\n setLoading(false)\n }\n }, [orgId, user])\n\n useEffect(() => {\n if (expanded) void load()\n }, [expanded, load])\n\n // Reset when the panel's org changes: a list from one workspace must not\n // show under another's key while the new one loads.\n useEffect(() => {\n setJobs([])\n setExpanded(false)\n toggledRef.current = false\n watchingRef.current = null\n }, [orgId])\n\n // Expanded by default while anything is moving or waiting (AGL-3593): a\n // plan waiting to be confirmed behind a collapsed row is a site that never\n // gets built. Otherwise collapsed, which reads nothing until asked.\n const anyInFlight = (inFlight?.length ?? 0) > 0\n useEffect(() => {\n if (anyInFlight && !toggledRef.current) setExpanded(true)\n }, [anyInFlight])\n\n // A request to open AI jobs (AGL-3593): expand, read the list again so a\n // job created a moment ago is in it, and highlight the job named.\n const focusSeq = focus?.seq ?? 0\n const focusJobId = focus?.jobId ?? null\n const expandedRef = useRef(expanded)\n expandedRef.current = expanded\n useEffect(() => {\n if (!focusSeq) return\n toggledRef.current = true\n setHighlight(focusJobId)\n // An open list does not re-run the expand effect's read, so it reads here.\n if (expandedRef.current) void load()\n else setExpanded(true)\n // Only a new request runs this; `load` and the job are read as they stand.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [focusSeq])\n\n // Scroll to the highlighted job once it is listed, then let it fade.\n const highlightListed = highlight !== null && jobs.some((job) => job.id === highlight)\n useEffect(() => {\n if (!highlight || !highlightListed) return undefined\n const row = Array.from(\n listRef.current?.querySelectorAll<HTMLElement>('[data-ai-job-id]') ?? [],\n ).find((element) => element.dataset['aiJobId'] === highlight)\n row?.scrollIntoView?.({ block: 'nearest', behavior: 'smooth' })\n const timer = setTimeout(() => setHighlight(null), HIGHLIGHT_MS)\n return () => clearTimeout(timer)\n }, [highlight, highlightListed])\n\n // Watch the most recent job that is still moving. One stream at a time:\n // the route re-reads a document every two seconds per open stream, and a\n // drawer with three running jobs does not need three of them.\n const watched = jobs.find((job) => !AI_JOB_TERMINAL_STATUSES.includes(job.status))\n const watchedId = watched?.id ?? null\n useEffect(() => {\n if (!expanded || !orgId || !watchedId || watchingRef.current === watchedId) return\n watchingRef.current = watchedId\n const controller = new AbortController()\n const watch = async () => {\n // The server closes a stream at its deadline with `reconnect`; the\n // loop here re-opens it until the job settles or the drawer unmounts.\n let again = true\n while (again && !controller.signal.aborted) {\n again = false\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs/${encodeURIComponent(watchedId)}/events?orgId=${encodeURIComponent(orgId)}`,\n { signal: controller.signal },\n )\n if (!response.ok || !response.body) return\n await readEventFrames(response.body, (event) => {\n if (event['type'] === 'state') patchJob(event['job'] as AiJobSummary)\n else if (event['type'] === 'reconnect') again = true\n else if (event['type'] === 'gone') {\n setJobs((prior) => prior.filter((job) => job.id !== watchedId))\n }\n })\n } catch {\n // The stream ended without a terminal state — a network blip or\n // the drawer closing. The next expand reloads the list.\n return\n }\n }\n }\n void watch().finally(() => {\n if (watchingRef.current === watchedId) watchingRef.current = null\n })\n return () => {\n controller.abort()\n if (watchingRef.current === watchedId) watchingRef.current = null\n }\n }, [expanded, orgId, watchedId, user, patchJob])\n\n // The same door the dialog that started the job cancels through (AGL-3593).\n const cancel = useCallback(\n async (jobId: string) => {\n if (!orgId) return\n const { job, error } = await cancelAiJobRequest(user, orgId, jobId)\n if (error) {\n setNotice(error)\n return\n }\n if (job) patchJob(job)\n },\n [orgId, user, patchJob],\n )\n\n /** The job whose resume is in flight, so its button cannot send twice. */\n const [resuming, setResuming] = useState<string | null>(null)\n\n // Confirms a plan, or tries again a step whose answer broke a building\n // rule (AGL-2935). The door runs the next step inline and answers with the\n // job, so the row moves on without waiting for the stream.\n const resume = useCallback(\n async (job: AiJobSummary, options?: AiJobResumeOptions) => {\n if (!orgId) return\n setResuming(job.id)\n setNotice(null)\n // The same door the dialog that started the job confirms through (AGL-3593).\n const { job: next, error } = await resumeAiJobRequest(user, orgId, job, options)\n if (next) patchJob(next)\n if (error) setNotice(error)\n setResuming(null)\n },\n [orgId, user, patchJob],\n )\n\n if (!visible || !orgId || !entitled) return null\n\n // Counted by state, never as one \"running\" figure (AGL-3593): a plan waiting\n // to be confirmed needs the person, and a stopped job is not moving.\n const chips = aiJobsHeaderChips(aiJobActivityCounts(inFlight ?? jobs))\n\n return (\n <Box sx={{ mb: 2, borderBottom: 1, borderColor: 'divider', pb: 1 }}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n AI jobs\n {chips.map((chip) => (\n <Chip\n key={chip.state}\n size=\"small\"\n color={STATE_COLOR[chip.state]}\n label={chip.label}\n sx={{ ml: 1 }}\n />\n ))}\n </Typography>\n {/* A question about the page's figures (AGL-2915). */}\n {insight && (\n <Button size=\"small\" onClick={() => setInsightOpen('ask')}>\n Ask about your numbers\n </Button>\n )}\n {/* A page from a brief (AGL-2907), on a site's own routes. */}\n {hostId && (\n <Button size=\"small\" onClick={() => setDescribing(true)}>\n Describe a page\n </Button>\n )}\n <IconButton\n size=\"small\"\n aria-label={expanded ? 'Hide AI jobs' : 'Show AI jobs'}\n aria-expanded={expanded}\n onClick={() => {\n toggledRef.current = true\n setExpanded((prior) => !prior)\n }}\n >\n <MdiIcon\n path={expanded ? mdiChevronUp.path : mdiChevronDown.path}\n sx={{ fontSize: 18 }}\n />\n </IconButton>\n </Stack>\n <Collapse in={expanded} unmountOnExit>\n {notice && (\n <Typography variant=\"caption\" color=\"error\" component=\"div\" sx={{ mt: 0.5 }}>\n {notice}\n </Typography>\n )}\n {loading && !jobs.length && (\n <CircularProgress size={16} sx={{ mt: 1 }} aria-label=\"Loading AI jobs\" />\n )}\n {!loading && !jobs.length && !notice && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\" sx={{ mt: 0.5 }}>\n No AI jobs yet.\n </Typography>\n )}\n <Stack ref={listRef} spacing={1} sx={{ mt: 1 }} role=\"list\" aria-label=\"AI jobs\">\n {jobs.map((job) => {\n const restartHref = (row: AiJobSummary) =>\n aiJobRestartHref(row, {\n orgSlug,\n hostId,\n pathname: typeof window === 'undefined' ? '' : window.location.pathname,\n })\n const stepsDone = job.steps.filter((step) => step.status === 'done').length\n const current = job.steps.find((step) => step.status === 'running')\n const terminal = AI_JOB_TERMINAL_STATUSES.includes(job.status)\n const phase = aiJobPhase(job)\n const primary = aiJobPrimaryLink(job, orgSlug)\n const highlighted = highlight === job.id\n return (\n <Box\n key={job.id}\n role=\"listitem\"\n data-ai-job-id={job.id}\n aria-current={highlighted ? 'true' : undefined}\n sx={{\n borderRadius: 1,\n bgcolor: highlighted ? 'action.selected' : 'action.hover',\n outline: 2,\n outlineColor: highlighted ? 'primary.main' : 'transparent',\n transition: (theme) =>\n theme.transitions.create(['background-color', 'outline-color'], {\n duration: theme.transitions.duration.standard,\n }),\n px: 1.5,\n py: 1,\n }}\n >\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Chip size=\"small\" variant=\"outlined\" label={job.kind} />\n <Chip size=\"small\" color={PHASE_COLOR[phase]} label={AI_JOB_PHASE_LABELS[phase]} />\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ flexGrow: 1 }}>\n {stepsDone}/{job.steps.length} steps\n {current ? ` — ${current.name}` : ''}\n {/* Net of what a failure on our side gave back (AGL-3594). */}\n {netCredits(job) > 0 ? ` · ${netCredits(job)} credits` : ''}\n </Typography>\n {!terminal && (\n <Button size=\"small\" onClick={() => void cancel(job.id)}>\n Cancel\n </Button>\n )}\n </Stack>\n <Typography variant=\"body2\" sx={{ mt: 0.5, wordBreak: 'break-word' }}>\n {job.brief.length > BRIEF_PREVIEW_CHARS\n ? `${job.brief.slice(0, BRIEF_PREVIEW_CHARS)}…`\n : job.brief}\n </Typography>\n {job.error && (\n <Typography variant=\"caption\" color={job.status === 'failed' ? 'error' : 'warning.main'} component=\"div\">\n {job.error}\n </Typography>\n )}\n {/* What became of its credits, from the job's recorded give-back (AGL-3596). */}\n {aiJobRefundCopy(job) && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {aiJobRefundCopy(job)}\n </Typography>\n )}\n {/* A site that built part of itself says what, and that the rest was on us (AGL-3616). */}\n {aiSitePartialCopy(job) && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {aiSitePartialCopy(job)}\n </Typography>\n )}\n {/* A build, item by item: where each stands, and what became of its credits (AGL-3616). */}\n {job.items?.length ? (\n <Box component=\"ul\" aria-label=\"Items\" sx={{ m: 0, mt: 0.5, pl: 2 }}>\n {aiBuildItemRows(job).map((row) => (\n <Typography key={row.slot} component=\"li\" variant=\"caption\" data-item-state={row.state}>\n {`${row.label} — ${ITEM_STATE_LABELS[row.state]}`}\n {row.detail ? `. ${row.detail}` : ''}\n </Typography>\n ))}\n </Box>\n ) : null}\n {aiBuildCanRetry(job) ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={resuming === job.id}\n onClick={() => void resume(job, { retry: 'failed-items' })}\n sx={{ mt: 0.5 }}\n >\n {'Try again what failed'}\n </Button>\n ) : null}\n {restartHref(job) ? (\n <Button size=\"small\" variant=\"contained\" component={AppLink} href={restartHref(job) as string} sx={{ mt: 0.5 }}>\n {'Try again'}\n </Button>\n ) : null}\n {/* What a finished job built, first (AGL-3593). */}\n {primary ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n component={AppLink}\n href={primary.href}\n sx={{ mt: 0.5 }}\n >\n {primary.label}\n </Button>\n ) : null}\n {job.outputs.map((output, index) => {\n const href = aiJobOutputHref(output, orgSlug)\n const navigation = aiJobNavigationProposal(output)\n return (\n <Box key={`${output.resource}:${output.id}:${index}`} sx={{ mt: 0.5 }}>\n {output.resource === 'insight' ? (\n <Button size=\"small\" onClick={() => setInsightOpen(output.id)}>\n View answer\n </Button>\n ) : href ? (\n <AppLink componentVariant=\"naked\" href={href}>\n Open draft — {output.label}\n </AppLink>\n ) : output.text ? (\n <Typography\n variant=\"body2\"\n component=\"pre\"\n sx={{ whiteSpace: 'pre-wrap', fontFamily: 'inherit', m: 0 }}\n >\n {output.text}\n </Typography>\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {output.label}\n </Typography>\n )}\n {output.load ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n About {formatBytes(output.load.totalBytes)} on a first visit\n </Typography>\n ) : null}\n {output.note ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {output.note}\n </Typography>\n ) : null}\n {navigation ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n Add “{navigation}” to your navigation once the page is live.\n </Typography>\n ) : null}\n </Box>\n )\n })}\n <AiJobPlan\n job={job}\n onResume={(target, options) => void resume(target, options)}\n busy={resuming === job.id}\n staff={isStaff}\n user={user}\n />\n </Box>\n )\n })}\n </Stack>\n </Collapse>\n {/*\n The brief dialog (AGL-2907). It starts a job and closes; the list\n below it is the only place the job is watched, so an open list reads\n the new job back as soon as the dialog hands it over.\n */}\n {insightOpen !== null && (\n <AiInsightDialog\n open\n onClose={() => {\n setInsightOpen(null)\n if (expanded) void load()\n }}\n orgId={orgId}\n orgSlug={orgSlug}\n hostId={insight?.host ? (hostId ?? null) : null}\n host={insight?.host ?? null}\n surface={insight?.surface ?? 'analytics'}\n user={user}\n uid={(user as { uid?: string } | null | undefined)?.uid ?? null}\n jobId={insightOpen === 'ask' ? null : insightOpen}\n />\n )}\n {hostId && (\n <AiPageBriefDialog\n open={describing}\n onClose={() => {\n setDescribing(false)\n if (expanded) void load()\n }}\n orgId={orgId}\n hostId={hostId}\n user={user}\n orgSlug={orgSlug}\n isStaff={isStaff}\n />\n )}\n </Box>\n )\n}\n\nexport default AssistJobsDrawer\n"],"names":["checkEntitlement","trackEvent","formatBytes","AI_JOB_TERMINAL_STATUSES","mdiChevronDown","mdiChevronUp","AppLink","MdiIcon","authorizedFetch","Box","Button","Chip","CircularProgress","Collapse","IconButton","Stack","Typography","useCallback","useEffect","useRef","useState","AI_JOB_PHASE_LABELS","aiJobActivityCounts","aiJobPhase","aiJobsHeaderChips","cancelAiJobRequest","resumeAiJobRequest","aiBuildCanRetry","aiBuildItemRows","aiSitePartialCopy","publishAiJob","AiPageBriefDialog","AiInsightDialog","AiJobPlan","aiJobOutputHref","aiJobPrimaryLink","aiSiteBuildHref","aiJobRefundCopy","readEventFrames","LIST_LIMIT","BRIEF_PREVIEW_CHARS","PHASE_COLOR","queued","planning","building","attention","done","failed","canceled","STATE_COLOR","running","ITEM_STATE_LABELS","active","waiting","skipped","HIGHLIGHT_MS","aiJobRestartHref","job","input","kind","status","siteInputs","hostId","subdomain","exec","pathname","href","orgSlug","id","netCredits","Math","max","creditsSpent","refundedCredits","aiJobNavigationProposal","output","resource","navigation","proposal","label","trim","AssistJobsDrawer","orgId","org","orgReady","user","visible","isStaff","insight","inFlight","focus","entitled","expanded","setExpanded","toggledRef","highlight","setHighlight","listRef","jobs","setJobs","loading","setLoading","notice","setNotice","describing","setDescribing","insightOpen","setInsightOpen","trackedRef","Set","watchingRef","patchJob","next","prior","index","findIndex","slice","copy","includes","current","has","add","credits","load","response","encodeURIComponent","ok","payload","json","catch","String","error","anyInFlight","length","focusSeq","seq","focusJobId","jobId","expandedRef","highlightListed","some","row","undefined","Array","from","querySelectorAll","find","element","dataset","scrollIntoView","block","behavior","timer","setTimeout","clearTimeout","watched","watchedId","controller","AbortController","watch","again","signal","aborted","body","event","filter","finally","abort","cancel","resuming","setResuming","resume","options","chips","sx","mb","borderBottom","borderColor","pb","direction","spacing","alignItems","variant","flexGrow","map","chip","size","color","state","ml","onClick","aria-label","aria-expanded","path","fontSize","in","unmountOnExit","component","mt","ref","role","restartHref","window","location","stepsDone","steps","step","terminal","phase","primary","highlighted","data-ai-job-id","aria-current","borderRadius","bgcolor","outline","outlineColor","transition","theme","transitions","create","duration","standard","px","py","name","wordBreak","brief","items","m","pl","data-item-state","detail","slot","disabled","retry","outputs","componentVariant","text","whiteSpace","fontFamily","totalBytes","note","onResume","target","busy","staff","open","onClose","host","surface","uid"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/assist-jobs-drawer.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { checkEntitlement } from '@aglyn/aglyn'\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport { formatBytes } from '@aglyn/aglyn/app-utils/measure-node-map'\nimport {\n AI_JOB_TERMINAL_STATUSES,\n type AiJobOutput,\n type AiJobSummary,\n} from '../model/ai-jobs.types'\nimport type { AglynOrgBilling } from '@aglyn/aglyn/foundation/definitions/org-billing.types'\nimport { mdiChevronDown, mdiChevronUp } from '@aglyn/shared-data-mdi'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { AppLink, HelpTip, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Box,\n Button,\n Chip,\n CircularProgress,\n Collapse,\n IconButton,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n AI_JOB_PHASE_LABELS,\n aiJobActivityCounts,\n aiJobPhase,\n aiJobsHeaderChips,\n type AiJobActivityState,\n type AiJobPhase,\n} from '../model/ai-job-activity'\nimport type { AiInsightSurface } from '../model/ai-insight'\nimport { cancelAiJobRequest, resumeAiJobRequest, type AiJobResumeOptions } from './ai-job-requests'\nimport { aiBuildCanRetry, aiBuildItemRows, aiSitePartialCopy } from '../model/ai-build-progress'\nimport { publishAiJob, type AiJobsOpenRequest } from './ai-jobs-store'\nimport { AiPageBriefDialog } from './ai-page-brief-dialog.component'\nimport { AiInsightDialog } from './ai-insight-dialog.component'\nimport { AiJobPlan } from './ai-job-plan.component'\nimport { aiJobOutputHref, aiJobPrimaryLink, aiSiteBuildHref } from './ai-job-links'\nimport { aiJobRefundCopy } from '../model/ai-job-failure-copy'\nimport { readEventFrames } from './ai-job-events'\n\n// Where an output opens, and the stream reader, live beside the dialogs that\n// share them (AGL-3593); this module has always exported them.\nexport { aiJobOutputHref, aiJobPrimaryLink, readEventFrames }\n\n/**\n * The AI jobs drawer inside the Assist panel (AGL-2904): the workspace's\n * running and recent generation jobs, each step's progress, an \"open\n * draft\" link per output, and cancel.\n *\n * Reads through the routes, never Firestore directly, so the same\n * membership, flag and entitlement rungs answer every read; progress comes\n * from the events route's server-sent stream, re-opened when the server\n * says `reconnect`. Everything loads on EXPAND rather than on mount: the\n * panel opens on every console page, and a list nobody asked for is a read\n * on every page view.\n *\n * It opens EXPANDED while any of the workspace's jobs is moving or waiting\n * for the person (AGL-3593) — the shared list the top-bar indicator reads\n * says so — because a plan waiting behind a collapsed row is a site that\n * never gets built. An \"Open AI jobs\" anywhere in the console expands it,\n * scrolls to the job it names and highlights it for a moment.\n *\n * Hidden — not disabled — unless the flag shows it AND the plan carries\n * `aiGenerative`: a released-off feature does not exist, and a plan without\n * it is sold the add-on on the Billing page, not here.\n */\n\n/** Jobs the drawer lists; the route caps higher, the drawer is a glance. */\nconst LIST_LIMIT = 10\n\n/** How much of a brief a row shows. */\nconst BRIEF_PREVIEW_CHARS = 90\n\n/**\n * A row's chip says where the job stands in the words every AI surface uses\n * (AGL-3593): a plan waiting to be confirmed is \"Plan ready\", never \"Running\"\n * and never the machine's `needs_review`.\n */\nconst PHASE_COLOR: Record<AiJobPhase, 'default' | 'primary' | 'success' | 'error' | 'warning'> = {\n queued: 'default',\n planning: 'primary',\n 'plan-ready': 'warning',\n building: 'primary',\n attention: 'warning',\n done: 'success',\n failed: 'error',\n canceled: 'default',\n}\n\n/** The header's count chips, colored as the rows they count. */\nconst STATE_COLOR: Record<AiJobActivityState, 'primary' | 'warning'> = {\n 'needs-you': 'warning',\n attention: 'warning',\n running: 'primary',\n}\n\n/** What each item state reads as in a row (AGL-3616). */\nconst ITEM_STATE_LABELS = {\n done: 'built',\n active: 'building',\n waiting: 'waiting',\n failed: 'failed',\n skipped: 'not built',\n} as const\n\n/** How long a job AI jobs was opened on stays highlighted. */\nconst HIGHLIGHT_MS = 2_500\n\n/**\n * Where a guided start that ended unbuilt is tried again (AGL-3596): its\n * \"Building your site\" page, opened on its own answers (`?retry=1`). Only for\n * a job of the site the console is on, whose subdomain the address names;\n * `null` otherwise.\n */\nexport function aiJobRestartHref(\n job: Pick<AiJobSummary, 'id' | 'kind' | 'status' | 'hostId' | 'siteInputs'>,\n input: { orgSlug: string; hostId?: string | null; pathname: string },\n): string | null {\n if (job.kind !== 'site' || (job.status !== 'failed' && job.status !== 'canceled')) return null\n if (!job.siteInputs || !job.hostId || job.hostId !== input.hostId) return null\n const subdomain = /^\\/[^/]+\\/hosts\\/([^/?#]+)/.exec(input.pathname)?.[1] ?? null\n const href = aiSiteBuildHref(input.orgSlug, subdomain, job.id)\n return href ? `${href}?retry=1` : null\n}\n\n/** What a job cost the person: what it spent, less what we refunded. */\nfunction netCredits(job: Pick<AiJobSummary, 'creditsSpent' | 'refundedCredits'>): number {\n return Math.max(0, (job.creditsSpent ?? 0) - (job.refundedCredits ?? 0))\n}\n\n/**\n * The navigation entry a page job proposes for the page it built (AGL-2907),\n * or `null`. The job writes no menu: the member adds the entry once the page\n * is live.\n */\nexport function aiJobNavigationProposal(output: AiJobOutput): string | null {\n if (output.resource !== 'screen') return null\n const navigation = output.proposal?.['navigation']\n if (!navigation || typeof navigation !== 'object') return null\n const label = (navigation as Record<string, unknown>)['label']\n return typeof label === 'string' && label.trim() ? label.trim() : null\n}\n\n\nexport interface AssistJobsDrawerProps {\n /** The org the panel is scoped to — `undefined` where the page named none. */\n orgId: string | undefined\n org: Partial<AglynOrgBilling> | undefined\n orgReady: boolean\n orgSlug: string\n user: Parameters<typeof authorizedFetch>[0]\n /** The `release_ai_generative` verdict, staff bypass applied (the shell's). */\n visible: boolean\n /**\n * The viewer is staff (AGL-3078): a job that stopped for a broken rule shows\n * them where the refused answer broke it. Who may read a job is unchanged.\n */\n isStaff?: boolean\n /** The site the console page is on, when it is on one: a page is described for a site. */\n hostId?: string | null\n /**\n * The page's insight surface (AGL-2915) — a site's Analytics, Data or CRM\n * Reports page, or the workspace's Data page — with the site's subdomain;\n * `null` elsewhere, where no question about the figures is offered.\n */\n insight?: { surface: Exclude<AiInsightSurface, 'digest'>; host: string | null } | null\n /**\n * The workspace's jobs that are not yet settled, from the shared store the\n * top-bar indicator reads (AGL-3593). The header counts them, and the\n * drawer opens expanded while there is any. Absent, the drawer counts what\n * it loaded itself.\n */\n inFlight?: readonly AiJobSummary[]\n /**\n * The latest request to open AI jobs (AGL-3593): a new `seq` expands the\n * drawer, and a named job is scrolled to and highlighted.\n */\n focus?: AiJobsOpenRequest | null\n}\n\nexport function AssistJobsDrawer({\n orgId,\n org,\n orgReady,\n orgSlug,\n user,\n visible,\n isStaff = false,\n hostId,\n insight,\n inFlight,\n focus,\n}: AssistJobsDrawerProps): JSX.Element | null {\n const entitled = orgReady && checkEntitlement(org as never, 'aiGenerative')\n const [expanded, setExpanded] = useState(false)\n /**\n * The person opened or closed the list themselves: from then on the\n * expanded-by-default rule leaves their choice alone until the workspace\n * changes.\n */\n const toggledRef = useRef(false)\n /** The job AI jobs was opened on, highlighted for a moment once it is listed. */\n const [highlight, setHighlight] = useState<string | null>(null)\n const listRef = useRef<HTMLDivElement | null>(null)\n const [jobs, setJobs] = useState<AiJobSummary[]>([])\n const [loading, setLoading] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n /** Whether the page brief dialog is open (AGL-2907). */\n const [describing, setDescribing] = useState(false)\n /**\n * The insight dialog (AGL-2915): `'ask'` for a new question, a job id for an\n * answer a job already wrote, `null` when closed.\n */\n const [insightOpen, setInsightOpen] = useState<string | null>(null)\n /** Job ids whose terminal event has been tracked, so a re-read does not count twice. */\n const trackedRef = useRef(new Set<string>())\n /** The job whose stream is open, so a re-render does not open a second. */\n const watchingRef = useRef<string | null>(null)\n\n const patchJob = useCallback((next: AiJobSummary) => {\n // Every counting surface moves with the row (AGL-3593).\n publishAiJob(next)\n setJobs((prior) => {\n const index = prior.findIndex((job) => job.id === next.id)\n if (index === -1) return [next, ...prior].slice(0, LIST_LIMIT)\n const copy = [...prior]\n copy[index] = next\n return copy\n })\n if (\n AI_JOB_TERMINAL_STATUSES.includes(next.status) &&\n !trackedRef.current.has(next.id)\n ) {\n trackedRef.current.add(next.id)\n if (next.status === 'done') {\n trackEvent('ai_job_completed', { kind: next.kind, credits: next.creditsSpent })\n } else if (next.status === 'failed') {\n trackEvent('ai_job_failed', { kind: next.kind })\n }\n }\n }, [])\n\n const load = useCallback(async () => {\n if (!orgId) return\n setLoading(true)\n setNotice(null)\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=${LIST_LIMIT}`,\n )\n if (!response.ok) {\n const payload = await response.json().catch(() => null)\n setNotice(String(payload?.error ?? 'The AI jobs could not be loaded — try again.'))\n return\n }\n const payload = (await response.json()) as { jobs: AiJobSummary[] }\n // Every job that arrives already terminal is one the drawer did not\n // watch finish, so its outcome is not this session's to count.\n for (const job of payload.jobs) {\n if (AI_JOB_TERMINAL_STATUSES.includes(job.status)) trackedRef.current.add(job.id)\n // A job the indicator still counts that has settled since leaves it.\n publishAiJob(job)\n }\n setJobs(payload.jobs)\n } catch {\n setNotice('The AI jobs could not be loaded — try again.')\n } finally {\n setLoading(false)\n }\n }, [orgId, user])\n\n useEffect(() => {\n if (expanded) void load()\n }, [expanded, load])\n\n // Reset when the panel's org changes: a list from one workspace must not\n // show under another's key while the new one loads.\n useEffect(() => {\n setJobs([])\n setExpanded(false)\n toggledRef.current = false\n watchingRef.current = null\n }, [orgId])\n\n // Expanded by default while anything is moving or waiting (AGL-3593): a\n // plan waiting to be confirmed behind a collapsed row is a site that never\n // gets built. Otherwise collapsed, which reads nothing until asked.\n const anyInFlight = (inFlight?.length ?? 0) > 0\n useEffect(() => {\n if (anyInFlight && !toggledRef.current) setExpanded(true)\n }, [anyInFlight])\n\n // A request to open AI jobs (AGL-3593): expand, read the list again so a\n // job created a moment ago is in it, and highlight the job named.\n const focusSeq = focus?.seq ?? 0\n const focusJobId = focus?.jobId ?? null\n const expandedRef = useRef(expanded)\n expandedRef.current = expanded\n useEffect(() => {\n if (!focusSeq) return\n toggledRef.current = true\n setHighlight(focusJobId)\n // An open list does not re-run the expand effect's read, so it reads here.\n if (expandedRef.current) void load()\n else setExpanded(true)\n // Only a new request runs this; `load` and the job are read as they stand.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [focusSeq])\n\n // Scroll to the highlighted job once it is listed, then let it fade.\n const highlightListed = highlight !== null && jobs.some((job) => job.id === highlight)\n useEffect(() => {\n if (!highlight || !highlightListed) return undefined\n const row = Array.from(\n listRef.current?.querySelectorAll<HTMLElement>('[data-ai-job-id]') ?? [],\n ).find((element) => element.dataset['aiJobId'] === highlight)\n row?.scrollIntoView?.({ block: 'nearest', behavior: 'smooth' })\n const timer = setTimeout(() => setHighlight(null), HIGHLIGHT_MS)\n return () => clearTimeout(timer)\n }, [highlight, highlightListed])\n\n // Watch the most recent job that is still moving. One stream at a time:\n // the route re-reads a document every two seconds per open stream, and a\n // drawer with three running jobs does not need three of them.\n const watched = jobs.find((job) => !AI_JOB_TERMINAL_STATUSES.includes(job.status))\n const watchedId = watched?.id ?? null\n useEffect(() => {\n if (!expanded || !orgId || !watchedId || watchingRef.current === watchedId) return\n watchingRef.current = watchedId\n const controller = new AbortController()\n const watch = async () => {\n // The server closes a stream at its deadline with `reconnect`; the\n // loop here re-opens it until the job settles or the drawer unmounts.\n let again = true\n while (again && !controller.signal.aborted) {\n again = false\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs/${encodeURIComponent(watchedId)}/events?orgId=${encodeURIComponent(orgId)}`,\n { signal: controller.signal },\n )\n if (!response.ok || !response.body) return\n await readEventFrames(response.body, (event) => {\n if (event['type'] === 'state') patchJob(event['job'] as AiJobSummary)\n else if (event['type'] === 'reconnect') again = true\n else if (event['type'] === 'gone') {\n setJobs((prior) => prior.filter((job) => job.id !== watchedId))\n }\n })\n } catch {\n // The stream ended without a terminal state — a network blip or\n // the drawer closing. The next expand reloads the list.\n return\n }\n }\n }\n void watch().finally(() => {\n if (watchingRef.current === watchedId) watchingRef.current = null\n })\n return () => {\n controller.abort()\n if (watchingRef.current === watchedId) watchingRef.current = null\n }\n }, [expanded, orgId, watchedId, user, patchJob])\n\n // The same door the dialog that started the job cancels through (AGL-3593).\n const cancel = useCallback(\n async (jobId: string) => {\n if (!orgId) return\n const { job, error } = await cancelAiJobRequest(user, orgId, jobId)\n if (error) {\n setNotice(error)\n return\n }\n if (job) patchJob(job)\n },\n [orgId, user, patchJob],\n )\n\n /** The job whose resume is in flight, so its button cannot send twice. */\n const [resuming, setResuming] = useState<string | null>(null)\n\n // Confirms a plan, or tries again a step whose answer broke a building\n // rule (AGL-2935). The door runs the next step inline and answers with the\n // job, so the row moves on without waiting for the stream.\n const resume = useCallback(\n async (job: AiJobSummary, options?: AiJobResumeOptions) => {\n if (!orgId) return\n setResuming(job.id)\n setNotice(null)\n // The same door the dialog that started the job confirms through (AGL-3593).\n const { job: next, error } = await resumeAiJobRequest(user, orgId, job, options)\n if (next) patchJob(next)\n if (error) setNotice(error)\n setResuming(null)\n },\n [orgId, user, patchJob],\n )\n\n if (!visible || !orgId || !entitled) return null\n\n // Counted by state, never as one \"running\" figure (AGL-3593): a plan waiting\n // to be confirmed needs the person, and a stopped job is not moving.\n const chips = aiJobsHeaderChips(aiJobActivityCounts(inFlight ?? jobs))\n\n return (\n <Box sx={{ mb: 2, borderBottom: 1, borderColor: 'divider', pb: 1 }}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n AI jobs\n {chips.map((chip) => (\n <Chip\n key={chip.state}\n size=\"small\"\n color={STATE_COLOR[chip.state]}\n label={chip.label}\n sx={{ ml: 1 }}\n />\n ))}\n </Typography>\n <HelpTip\n {...pluginDocsHelp('aiJobs', {\n anchor: '#ai-jobs-in-assist',\n excerpt:\n 'Every AI job in this workspace, newest first: where it stands, the credits it used, and its plan to confirm, cancel or try again.',\n })}\n />\n {/* A question about the page's figures (AGL-2915). */}\n {insight && (\n <Button size=\"small\" onClick={() => setInsightOpen('ask')}>\n Ask about your numbers\n </Button>\n )}\n {/* A page from a brief (AGL-2907), on a site's own routes. */}\n {hostId && (\n <Button size=\"small\" onClick={() => setDescribing(true)}>\n Describe a page\n </Button>\n )}\n <IconButton\n size=\"small\"\n aria-label={expanded ? 'Hide AI jobs' : 'Show AI jobs'}\n aria-expanded={expanded}\n onClick={() => {\n toggledRef.current = true\n setExpanded((prior) => !prior)\n }}\n >\n <MdiIcon\n path={expanded ? mdiChevronUp.path : mdiChevronDown.path}\n sx={{ fontSize: 18 }}\n />\n </IconButton>\n </Stack>\n <Collapse in={expanded} unmountOnExit>\n {notice && (\n <Typography variant=\"caption\" color=\"error\" component=\"div\" sx={{ mt: 0.5 }}>\n {notice}\n </Typography>\n )}\n {loading && !jobs.length && (\n <CircularProgress size={16} sx={{ mt: 1 }} aria-label=\"Loading AI jobs\" />\n )}\n {!loading && !jobs.length && !notice && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\" sx={{ mt: 0.5 }}>\n No AI jobs yet.\n </Typography>\n )}\n <Stack ref={listRef} spacing={1} sx={{ mt: 1 }} role=\"list\" aria-label=\"AI jobs\">\n {jobs.map((job) => {\n const restartHref = (row: AiJobSummary) =>\n aiJobRestartHref(row, {\n orgSlug,\n hostId,\n pathname: typeof window === 'undefined' ? '' : window.location.pathname,\n })\n const stepsDone = job.steps.filter((step) => step.status === 'done').length\n const current = job.steps.find((step) => step.status === 'running')\n const terminal = AI_JOB_TERMINAL_STATUSES.includes(job.status)\n const phase = aiJobPhase(job)\n const primary = aiJobPrimaryLink(job, orgSlug)\n const highlighted = highlight === job.id\n return (\n <Box\n key={job.id}\n role=\"listitem\"\n data-ai-job-id={job.id}\n aria-current={highlighted ? 'true' : undefined}\n sx={{\n borderRadius: 1,\n bgcolor: highlighted ? 'action.selected' : 'action.hover',\n outline: 2,\n outlineColor: highlighted ? 'primary.main' : 'transparent',\n transition: (theme) =>\n theme.transitions.create(['background-color', 'outline-color'], {\n duration: theme.transitions.duration.standard,\n }),\n px: 1.5,\n py: 1,\n }}\n >\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Chip size=\"small\" variant=\"outlined\" label={job.kind} />\n <Chip size=\"small\" color={PHASE_COLOR[phase]} label={AI_JOB_PHASE_LABELS[phase]} />\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ flexGrow: 1 }}>\n {stepsDone}/{job.steps.length} steps\n {current ? ` — ${current.name}` : ''}\n {/* Net of what a failure on our side gave back (AGL-3594). */}\n {netCredits(job) > 0 ? ` · ${netCredits(job)} credits` : ''}\n </Typography>\n {!terminal && (\n <Button size=\"small\" onClick={() => void cancel(job.id)}>\n Cancel\n </Button>\n )}\n </Stack>\n <Typography variant=\"body2\" sx={{ mt: 0.5, wordBreak: 'break-word' }}>\n {job.brief.length > BRIEF_PREVIEW_CHARS\n ? `${job.brief.slice(0, BRIEF_PREVIEW_CHARS)}…`\n : job.brief}\n </Typography>\n {job.error && (\n <Typography variant=\"caption\" color={job.status === 'failed' ? 'error' : 'warning.main'} component=\"div\">\n {job.error}\n </Typography>\n )}\n {/* What became of its credits, from the job's recorded give-back (AGL-3596). */}\n {aiJobRefundCopy(job) && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {aiJobRefundCopy(job)}\n </Typography>\n )}\n {/* A site that built part of itself says what, and that the rest was on us (AGL-3616). */}\n {aiSitePartialCopy(job) && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {aiSitePartialCopy(job)}\n </Typography>\n )}\n {/* A build, item by item: where each stands, and what became of its credits (AGL-3616). */}\n {job.items?.length ? (\n <Box component=\"ul\" aria-label=\"Items\" sx={{ m: 0, mt: 0.5, pl: 2 }}>\n {aiBuildItemRows(job).map((row) => (\n <Typography key={row.slot} component=\"li\" variant=\"caption\" data-item-state={row.state}>\n {`${row.label} — ${ITEM_STATE_LABELS[row.state]}`}\n {row.detail ? `. ${row.detail}` : ''}\n </Typography>\n ))}\n </Box>\n ) : null}\n {aiBuildCanRetry(job) ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={resuming === job.id}\n onClick={() => void resume(job, { retry: 'failed-items' })}\n sx={{ mt: 0.5 }}\n >\n {'Try again what failed'}\n </Button>\n ) : null}\n {restartHref(job) ? (\n <Button size=\"small\" variant=\"contained\" component={AppLink} href={restartHref(job) as string} sx={{ mt: 0.5 }}>\n {'Try again'}\n </Button>\n ) : null}\n {/* What a finished job built, first (AGL-3593). */}\n {primary ? (\n <Button\n size=\"small\"\n variant=\"contained\"\n component={AppLink}\n href={primary.href}\n sx={{ mt: 0.5 }}\n >\n {primary.label}\n </Button>\n ) : null}\n {job.outputs.map((output, index) => {\n const href = aiJobOutputHref(output, orgSlug)\n const navigation = aiJobNavigationProposal(output)\n return (\n <Box key={`${output.resource}:${output.id}:${index}`} sx={{ mt: 0.5 }}>\n {output.resource === 'insight' ? (\n <Button size=\"small\" onClick={() => setInsightOpen(output.id)}>\n View answer\n </Button>\n ) : href ? (\n <AppLink componentVariant=\"naked\" href={href}>\n Open draft — {output.label}\n </AppLink>\n ) : output.text ? (\n <Typography\n variant=\"body2\"\n component=\"pre\"\n sx={{ whiteSpace: 'pre-wrap', fontFamily: 'inherit', m: 0 }}\n >\n {output.text}\n </Typography>\n ) : (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {output.label}\n </Typography>\n )}\n {output.load ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n About {formatBytes(output.load.totalBytes)} on a first visit\n </Typography>\n ) : null}\n {output.note ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {output.note}\n </Typography>\n ) : null}\n {navigation ? (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n Add “{navigation}” to your navigation once the page is live.\n </Typography>\n ) : null}\n </Box>\n )\n })}\n <AiJobPlan\n job={job}\n onResume={(target, options) => void resume(target, options)}\n busy={resuming === job.id}\n staff={isStaff}\n user={user}\n />\n </Box>\n )\n })}\n </Stack>\n </Collapse>\n {/*\n The brief dialog (AGL-2907). It starts a job and closes; the list\n below it is the only place the job is watched, so an open list reads\n the new job back as soon as the dialog hands it over.\n */}\n {insightOpen !== null && (\n <AiInsightDialog\n open\n onClose={() => {\n setInsightOpen(null)\n if (expanded) void load()\n }}\n orgId={orgId}\n orgSlug={orgSlug}\n hostId={insight?.host ? (hostId ?? null) : null}\n host={insight?.host ?? null}\n surface={insight?.surface ?? 'analytics'}\n user={user}\n uid={(user as { uid?: string } | null | undefined)?.uid ?? null}\n jobId={insightOpen === 'ask' ? null : insightOpen}\n />\n )}\n {hostId && (\n <AiPageBriefDialog\n open={describing}\n onClose={() => {\n setDescribing(false)\n if (expanded) void load()\n }}\n orgId={orgId}\n hostId={hostId}\n user={user}\n orgSlug={orgSlug}\n isStaff={isStaff}\n />\n )}\n </Box>\n )\n}\n\nexport default AssistJobsDrawer\n"],"names":["checkEntitlement","trackEvent","formatBytes","AI_JOB_TERMINAL_STATUSES","mdiChevronDown","mdiChevronUp","pluginDocsHelp","AppLink","HelpTip","MdiIcon","authorizedFetch","Box","Button","Chip","CircularProgress","Collapse","IconButton","Stack","Typography","useCallback","useEffect","useRef","useState","AI_JOB_PHASE_LABELS","aiJobActivityCounts","aiJobPhase","aiJobsHeaderChips","cancelAiJobRequest","resumeAiJobRequest","aiBuildCanRetry","aiBuildItemRows","aiSitePartialCopy","publishAiJob","AiPageBriefDialog","AiInsightDialog","AiJobPlan","aiJobOutputHref","aiJobPrimaryLink","aiSiteBuildHref","aiJobRefundCopy","readEventFrames","LIST_LIMIT","BRIEF_PREVIEW_CHARS","PHASE_COLOR","queued","planning","building","attention","done","failed","canceled","STATE_COLOR","running","ITEM_STATE_LABELS","active","waiting","skipped","HIGHLIGHT_MS","aiJobRestartHref","job","input","kind","status","siteInputs","hostId","subdomain","exec","pathname","href","orgSlug","id","netCredits","Math","max","creditsSpent","refundedCredits","aiJobNavigationProposal","output","resource","navigation","proposal","label","trim","AssistJobsDrawer","orgId","org","orgReady","user","visible","isStaff","insight","inFlight","focus","entitled","expanded","setExpanded","toggledRef","highlight","setHighlight","listRef","jobs","setJobs","loading","setLoading","notice","setNotice","describing","setDescribing","insightOpen","setInsightOpen","trackedRef","Set","watchingRef","patchJob","next","prior","index","findIndex","slice","copy","includes","current","has","add","credits","load","response","encodeURIComponent","ok","payload","json","catch","String","error","anyInFlight","length","focusSeq","seq","focusJobId","jobId","expandedRef","highlightListed","some","row","undefined","Array","from","querySelectorAll","find","element","dataset","scrollIntoView","block","behavior","timer","setTimeout","clearTimeout","watched","watchedId","controller","AbortController","watch","again","signal","aborted","body","event","filter","finally","abort","cancel","resuming","setResuming","resume","options","chips","sx","mb","borderBottom","borderColor","pb","direction","spacing","alignItems","variant","flexGrow","map","chip","size","color","state","ml","anchor","excerpt","onClick","aria-label","aria-expanded","path","fontSize","in","unmountOnExit","component","mt","ref","role","restartHref","window","location","stepsDone","steps","step","terminal","phase","primary","highlighted","data-ai-job-id","aria-current","borderRadius","bgcolor","outline","outlineColor","transition","theme","transitions","create","duration","standard","px","py","name","wordBreak","brief","items","m","pl","data-item-state","detail","slot","disabled","retry","outputs","componentVariant","text","whiteSpace","fontFamily","totalBytes","note","onResume","target","busy","staff","open","onClose","host","surface","uid"],"mappings":";;AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,gBAAgB,QAAQ,eAAc;AAC/C,SAASC,UAAU,QAAQ,0CAAyC;AACpE,SAASC,WAAW,QAAQ,0CAAyC;AACrE,SACEC,wBAAwB,QAGnB,4BAAwB;AAE/B,SAASC,cAAc,EAAEC,YAAY,QAAQ,yBAAwB;AACrE,SAASC,cAAc,QAAQ,mCAAkC;AACjE,SAASC,OAAO,EAAEC,OAAO,EAAEC,OAAO,QAAQ,uBAAsB;AAChE,SAASC,eAAe,QAAQ,2CAA0C;AAC1E,SACEC,GAAG,EACHC,MAAM,EACNC,IAAI,EACJC,gBAAgB,EAChBC,QAAQ,EACRC,UAAU,EACVC,KAAK,EACLC,UAAU,QACL,gBAAe;AACtB,SAASC,WAAW,EAAEC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AAChE,SACEC,mBAAmB,EACnBC,mBAAmB,EACnBC,UAAU,EACVC,iBAAiB,QAGZ,8BAA0B;AAEjC,SAASC,kBAAkB,EAAEC,kBAAkB,QAAiC,uBAAmB;AACnG,SAASC,eAAe,EAAEC,eAAe,EAAEC,iBAAiB,QAAQ,gCAA4B;AAChG,SAASC,YAAY,QAAgC,qBAAiB;AACtE,SAASC,iBAAiB,QAAQ,sCAAkC;AACpE,SAASC,eAAe,QAAQ,mCAA+B;AAC/D,SAASC,SAAS,QAAQ,6BAAyB;AACnD,SAASC,eAAe,EAAEC,gBAAgB,EAAEC,eAAe,QAAQ,oBAAgB;AACnF,SAASC,eAAe,QAAQ,kCAA8B;AAC9D,SAASC,eAAe,QAAQ,qBAAiB;AAEjD,6EAA6E;AAC7E,+DAA+D;AAC/D,SAASJ,eAAe,EAAEC,gBAAgB,EAAEG,eAAe,GAAE;AAE7D;;;;;;;;;;;;;;;;;;;;;CAqBC,GAED,0EAA0E,GAC1E,MAAMC,aAAa;AAEnB,qCAAqC,GACrC,MAAMC,sBAAsB;AAE5B;;;;CAIC,GACD,MAAMC,cAA2F;IAC/FC,QAAQ;IACRC,UAAU;IACV,cAAc;IACdC,UAAU;IACVC,WAAW;IACXC,MAAM;IACNC,QAAQ;IACRC,UAAU;AACZ;AAEA,8DAA8D,GAC9D,MAAMC,cAAiE;IACrE,aAAa;IACbJ,WAAW;IACXK,SAAS;AACX;AAEA,uDAAuD,GACvD,MAAMC,oBAAoB;IACxBL,MAAM;IACNM,QAAQ;IACRC,SAAS;IACTN,QAAQ;IACRO,SAAS;AACX;AAEA,4DAA4D,GAC5D,MAAMC,eAAe;AAErB;;;;;CAKC,GACD,OAAO,SAASC,iBACdC,GAA2E,EAC3EC,KAAoE;;QAIlD;IAFlB,IAAID,IAAIE,IAAI,KAAK,UAAWF,IAAIG,MAAM,KAAK,YAAYH,IAAIG,MAAM,KAAK,YAAa,OAAO;IAC1F,IAAI,CAACH,IAAII,UAAU,IAAI,CAACJ,IAAIK,MAAM,IAAIL,IAAIK,MAAM,KAAKJ,MAAMI,MAAM,EAAE,OAAO;IAC1E,MAAMC,qBAAY,QAAA,6BAA6BC,IAAI,CAACN,MAAMO,QAAQ,sBAAhD,KAAmD,CAAC,EAAE,mBAAI;IAC5E,MAAMC,OAAO9B,gBAAgBsB,MAAMS,OAAO,EAAEJ,WAAWN,IAAIW,EAAE;IAC7D,OAAOF,OAAO,GAAGA,KAAK,QAAQ,CAAC,GAAG;AACpC;AAEA,sEAAsE,GACtE,SAASG,WAAWZ,GAA2D;QACzDA,mBAA0BA;IAA9C,OAAOa,KAAKC,GAAG,CAAC,GAAG,EAACd,oBAAAA,IAAIe,YAAY,YAAhBf,oBAAoB,OAAMA,uBAAAA,IAAIgB,eAAe,YAAnBhB,uBAAuB;AACvE;AAEA;;;;CAIC,GACD,OAAO,SAASiB,wBAAwBC,MAAmB;QAEtCA;IADnB,IAAIA,OAAOC,QAAQ,KAAK,UAAU,OAAO;IACzC,MAAMC,cAAaF,mBAAAA,OAAOG,QAAQ,qBAAfH,gBAAiB,CAAC,aAAa;IAClD,IAAI,CAACE,cAAc,OAAOA,eAAe,UAAU,OAAO;IAC1D,MAAME,QAAQ,AAACF,UAAsC,CAAC,QAAQ;IAC9D,OAAO,OAAOE,UAAU,YAAYA,MAAMC,IAAI,KAAKD,MAAMC,IAAI,KAAK;AACpE;AAuCA,OAAO,SAASC,iBAAiB,EAC/BC,KAAK,EACLC,GAAG,EACHC,QAAQ,EACRjB,OAAO,EACPkB,IAAI,EACJC,OAAO,EACPC,UAAU,KAAK,EACfzB,MAAM,EACN0B,OAAO,EACPC,QAAQ,EACRC,KAAK,EACiB;;IACtB,MAAMC,WAAWP,YAAYtF,iBAAiBqF,KAAc;IAC5D,MAAM,CAACS,UAAUC,YAAY,GAAGzE,SAAS;IACzC;;;;GAIC,GACD,MAAM0E,aAAa3E,OAAO;IAC1B,+EAA+E,GAC/E,MAAM,CAAC4E,WAAWC,aAAa,GAAG5E,SAAwB;IAC1D,MAAM6E,UAAU9E,OAA8B;IAC9C,MAAM,CAAC+E,MAAMC,QAAQ,GAAG/E,SAAyB,EAAE;IACnD,MAAM,CAACgF,SAASC,WAAW,GAAGjF,SAAS;IACvC,MAAM,CAACkF,QAAQC,UAAU,GAAGnF,SAAwB;IACpD,sDAAsD,GACtD,MAAM,CAACoF,YAAYC,cAAc,GAAGrF,SAAS;IAC7C;;;GAGC,GACD,MAAM,CAACsF,aAAaC,eAAe,GAAGvF,SAAwB;IAC9D,sFAAsF,GACtF,MAAMwF,aAAazF,OAAO,IAAI0F;IAC9B,yEAAyE,GACzE,MAAMC,cAAc3F,OAAsB;IAE1C,MAAM4F,WAAW9F,YAAY,CAAC+F;QAC5B,wDAAwD;QACxDlF,aAAakF;QACbb,QAAQ,CAACc;YACP,MAAMC,QAAQD,MAAME,SAAS,CAAC,CAAC1D,MAAQA,IAAIW,EAAE,KAAK4C,KAAK5C,EAAE;YACzD,IAAI8C,UAAU,CAAC,GAAG,OAAO;gBAACF;mBAASC;aAAM,CAACG,KAAK,CAAC,GAAG7E;YACnD,MAAM8E,OAAO;mBAAIJ;aAAM;YACvBI,IAAI,CAACH,MAAM,GAAGF;YACd,OAAOK;QACT;QACA,IACEpH,yBAAyBqH,QAAQ,CAACN,KAAKpD,MAAM,KAC7C,CAACgD,WAAWW,OAAO,CAACC,GAAG,CAACR,KAAK5C,EAAE,GAC/B;YACAwC,WAAWW,OAAO,CAACE,GAAG,CAACT,KAAK5C,EAAE;YAC9B,IAAI4C,KAAKpD,MAAM,KAAK,QAAQ;gBAC1B7D,WAAW,oBAAoB;oBAAE4D,MAAMqD,KAAKrD,IAAI;oBAAE+D,SAASV,KAAKxC,YAAY;gBAAC;YAC/E,OAAO,IAAIwC,KAAKpD,MAAM,KAAK,UAAU;gBACnC7D,WAAW,iBAAiB;oBAAE4D,MAAMqD,KAAKrD,IAAI;gBAAC;YAChD;QACF;IACF,GAAG,EAAE;IAEL,MAAMgE,OAAO1G,YAAY;QACvB,IAAI,CAACiE,OAAO;QACZmB,WAAW;QACXE,UAAU;QACV,IAAI;YACF,MAAMqB,WAAW,MAAMpH,gBACrB6E,MACA,CAAC,mBAAmB,EAAEwC,mBAAmB3C,OAAO,OAAO,EAAE3C,YAAY;YAEvE,IAAI,CAACqF,SAASE,EAAE,EAAE;;gBAChB,MAAMC,UAAU,MAAMH,SAASI,IAAI,GAAGC,KAAK,CAAC,IAAM;gBAClD1B,UAAU2B,eAAOH,2BAAAA,QAASI,KAAK,mBAAI;gBACnC;YACF;YACA,MAAMJ,UAAW,MAAMH,SAASI,IAAI;YACpC,oEAAoE;YACpE,+DAA+D;YAC/D,KAAK,MAAMvE,OAAOsE,QAAQ7B,IAAI,CAAE;gBAC9B,IAAIjG,yBAAyBqH,QAAQ,CAAC7D,IAAIG,MAAM,GAAGgD,WAAWW,OAAO,CAACE,GAAG,CAAChE,IAAIW,EAAE;gBAChF,qEAAqE;gBACrEtC,aAAa2B;YACf;YACA0C,QAAQ4B,QAAQ7B,IAAI;QACtB,EAAE,eAAM;YACNK,UAAU;QACZ,SAAU;YACRF,WAAW;QACb;IACF,GAAG;QAACnB;QAAOG;KAAK;IAEhBnE,UAAU;QACR,IAAI0E,UAAU,KAAK+B;IACrB,GAAG;QAAC/B;QAAU+B;KAAK;IAEnB,yEAAyE;IACzE,oDAAoD;IACpDzG,UAAU;QACRiF,QAAQ,EAAE;QACVN,YAAY;QACZC,WAAWyB,OAAO,GAAG;QACrBT,YAAYS,OAAO,GAAG;IACxB,GAAG;QAACrC;KAAM;IAEV,wEAAwE;IACxE,2EAA2E;IAC3E,oEAAoE;IACpE,MAAMkD,cAAc,SAAC3C,4BAAAA,SAAU4C,MAAM,mBAAI,KAAK;IAC9CnH,UAAU;QACR,IAAIkH,eAAe,CAACtC,WAAWyB,OAAO,EAAE1B,YAAY;IACtD,GAAG;QAACuC;KAAY;IAEhB,yEAAyE;IACzE,kEAAkE;IAClE,MAAME,oBAAW5C,yBAAAA,MAAO6C,GAAG,oBAAI;IAC/B,MAAMC,sBAAa9C,yBAAAA,MAAO+C,KAAK,oBAAI;IACnC,MAAMC,cAAcvH,OAAOyE;IAC3B8C,YAAYnB,OAAO,GAAG3B;IACtB1E,UAAU;QACR,IAAI,CAACoH,UAAU;QACfxC,WAAWyB,OAAO,GAAG;QACrBvB,aAAawC;QACb,2EAA2E;QAC3E,IAAIE,YAAYnB,OAAO,EAAE,KAAKI;aACzB9B,YAAY;IACjB,2EAA2E;IAC3E,uDAAuD;IACzD,GAAG;QAACyC;KAAS;IAEb,qEAAqE;IACrE,MAAMK,kBAAkB5C,cAAc,QAAQG,KAAK0C,IAAI,CAAC,CAACnF,MAAQA,IAAIW,EAAE,KAAK2B;IAC5E7E,UAAU;;YAGN+E,kBAEF4C;QAJA,IAAI,CAAC9C,aAAa,CAAC4C,iBAAiB,OAAOG;QAC3C,MAAMD,MAAME,MAAMC,IAAI,UACpB/C,mBAAAA,QAAQsB,OAAO,qBAAftB,iBAAiBgD,gBAAgB,CAAc,sCAAuB,EAAE,EACxEC,IAAI,CAAC,CAACC,UAAYA,QAAQC,OAAO,CAAC,UAAU,KAAKrD;QACnD8C,wBAAAA,sBAAAA,IAAKQ,cAAc,qBAAnBR,yBAAAA,KAAsB;YAAES,OAAO;YAAWC,UAAU;QAAS;QAC7D,MAAMC,QAAQC,WAAW,IAAMzD,aAAa,OAAOzC;QACnD,OAAO,IAAMmG,aAAaF;IAC5B,GAAG;QAACzD;QAAW4C;KAAgB;IAE/B,wEAAwE;IACxE,yEAAyE;IACzE,8DAA8D;IAC9D,MAAMgB,UAAUzD,KAAKgD,IAAI,CAAC,CAACzF,MAAQ,CAACxD,yBAAyBqH,QAAQ,CAAC7D,IAAIG,MAAM;IAChF,MAAMgG,qBAAYD,2BAAAA,QAASvF,EAAE,oBAAI;IACjClD,UAAU;QACR,IAAI,CAAC0E,YAAY,CAACV,SAAS,CAAC0E,aAAa9C,YAAYS,OAAO,KAAKqC,WAAW;QAC5E9C,YAAYS,OAAO,GAAGqC;QACtB,MAAMC,aAAa,IAAIC;QACvB,MAAMC,QAAQ;YACZ,mEAAmE;YACnE,sEAAsE;YACtE,IAAIC,QAAQ;YACZ,MAAOA,SAAS,CAACH,WAAWI,MAAM,CAACC,OAAO,CAAE;gBAC1CF,QAAQ;gBACR,IAAI;oBACF,MAAMpC,WAAW,MAAMpH,gBACrB6E,MACA,CAAC,aAAa,EAAEwC,mBAAmB+B,WAAW,cAAc,EAAE/B,mBAAmB3C,QAAQ,EACzF;wBAAE+E,QAAQJ,WAAWI,MAAM;oBAAC;oBAE9B,IAAI,CAACrC,SAASE,EAAE,IAAI,CAACF,SAASuC,IAAI,EAAE;oBACpC,MAAM7H,gBAAgBsF,SAASuC,IAAI,EAAE,CAACC;wBACpC,IAAIA,KAAK,CAAC,OAAO,KAAK,SAASrD,SAASqD,KAAK,CAAC,MAAM;6BAC/C,IAAIA,KAAK,CAAC,OAAO,KAAK,aAAaJ,QAAQ;6BAC3C,IAAII,KAAK,CAAC,OAAO,KAAK,QAAQ;4BACjCjE,QAAQ,CAACc,QAAUA,MAAMoD,MAAM,CAAC,CAAC5G,MAAQA,IAAIW,EAAE,KAAKwF;wBACtD;oBACF;gBACF,EAAE,eAAM;oBACN,gEAAgE;oBAChE,wDAAwD;oBACxD;gBACF;YACF;QACF;QACA,KAAKG,QAAQO,OAAO,CAAC;YACnB,IAAIxD,YAAYS,OAAO,KAAKqC,WAAW9C,YAAYS,OAAO,GAAG;QAC/D;QACA,OAAO;YACLsC,WAAWU,KAAK;YAChB,IAAIzD,YAAYS,OAAO,KAAKqC,WAAW9C,YAAYS,OAAO,GAAG;QAC/D;IACF,GAAG;QAAC3B;QAAUV;QAAO0E;QAAWvE;QAAM0B;KAAS;IAE/C,4EAA4E;IAC5E,MAAMyD,SAASvJ,YACb,OAAOwH;QACL,IAAI,CAACvD,OAAO;QACZ,MAAM,EAAEzB,GAAG,EAAE0E,KAAK,EAAE,GAAG,MAAM1G,mBAAmB4D,MAAMH,OAAOuD;QAC7D,IAAIN,OAAO;YACT5B,UAAU4B;YACV;QACF;QACA,IAAI1E,KAAKsD,SAAStD;IACpB,GACA;QAACyB;QAAOG;QAAM0B;KAAS;IAGzB,wEAAwE,GACxE,MAAM,CAAC0D,UAAUC,YAAY,GAAGtJ,SAAwB;IAExD,uEAAuE;IACvE,2EAA2E;IAC3E,2DAA2D;IAC3D,MAAMuJ,SAAS1J,YACb,OAAOwC,KAAmBmH;QACxB,IAAI,CAAC1F,OAAO;QACZwF,YAAYjH,IAAIW,EAAE;QAClBmC,UAAU;QACV,6EAA6E;QAC7E,MAAM,EAAE9C,KAAKuD,IAAI,EAAEmB,KAAK,EAAE,GAAG,MAAMzG,mBAAmB2D,MAAMH,OAAOzB,KAAKmH;QACxE,IAAI5D,MAAMD,SAASC;QACnB,IAAImB,OAAO5B,UAAU4B;QACrBuC,YAAY;IACd,GACA;QAACxF;QAAOG;QAAM0B;KAAS;IAGzB,IAAI,CAACzB,WAAW,CAACJ,SAAS,CAACS,UAAU,OAAO;IAE5C,6EAA6E;IAC7E,qEAAqE;IACrE,MAAMkF,QAAQrJ,kBAAkBF,oBAAoBmE,mBAAAA,WAAYS;IAEhE,qBACE,MAACzF;QAAIqK,IAAI;YAAEC,IAAI;YAAGC,cAAc;YAAGC,aAAa;YAAWC,IAAI;QAAE;;0BAC/D,MAACnK;gBAAMoK,WAAU;gBAAMC,SAAS;gBAAGN,IAAI;oBAAEO,YAAY;gBAAS;;kCAC5D,MAACrK;wBAAWsK,SAAQ;wBAAYR,IAAI;4BAAES,UAAU;wBAAE;;4BAAG;4BAElDV,MAAMW,GAAG,CAAC,CAACC,qBACV,KAAC9K;oCAEC+K,MAAK;oCACLC,OAAO1I,WAAW,CAACwI,KAAKG,KAAK,CAAC;oCAC9B7G,OAAO0G,KAAK1G,KAAK;oCACjB+F,IAAI;wCAAEe,IAAI;oCAAE;mCAJPJ,KAAKG,KAAK;;;kCAQrB,KAACtL,sBACKF,eAAe,UAAU;wBAC3B0L,QAAQ;wBACRC,SACE;oBACJ;oBAGDvG,yBACC,KAAC9E;wBAAOgL,MAAK;wBAAQM,SAAS,IAAMrF,eAAe;kCAAQ;;oBAK5D7C,wBACC,KAACpD;wBAAOgL,MAAK;wBAAQM,SAAS,IAAMvF,cAAc;kCAAO;;kCAI3D,KAAC3F;wBACC4K,MAAK;wBACLO,cAAYrG,WAAW,iBAAiB;wBACxCsG,iBAAetG;wBACfoG,SAAS;4BACPlG,WAAWyB,OAAO,GAAG;4BACrB1B,YAAY,CAACoB,QAAU,CAACA;wBAC1B;kCAEA,cAAA,KAAC1G;4BACC4L,MAAMvG,WAAWzF,aAAagM,IAAI,GAAGjM,eAAeiM,IAAI;4BACxDrB,IAAI;gCAAEsB,UAAU;4BAAG;;;;;0BAIzB,MAACvL;gBAASwL,IAAIzG;gBAAU0G,aAAa;;oBAClChG,wBACC,KAACtF;wBAAWsK,SAAQ;wBAAUK,OAAM;wBAAQY,WAAU;wBAAMzB,IAAI;4BAAE0B,IAAI;wBAAI;kCACvElG;;oBAGJF,WAAW,CAACF,KAAKmC,MAAM,kBACtB,KAACzH;wBAAiB8K,MAAM;wBAAIZ,IAAI;4BAAE0B,IAAI;wBAAE;wBAAGP,cAAW;;oBAEvD,CAAC7F,WAAW,CAACF,KAAKmC,MAAM,IAAI,CAAC/B,wBAC5B,KAACtF;wBAAWsK,SAAQ;wBAAUK,OAAM;wBAAiBY,WAAU;wBAAMzB,IAAI;4BAAE0B,IAAI;wBAAI;kCAAG;;kCAIxF,KAACzL;wBAAM0L,KAAKxG;wBAASmF,SAAS;wBAAGN,IAAI;4BAAE0B,IAAI;wBAAE;wBAAGE,MAAK;wBAAOT,cAAW;kCACpE/F,KAAKsF,GAAG,CAAC,CAAC/H;gCAsEJA;4BArEL,MAAMkJ,cAAc,CAAC9D,MACnBrF,iBAAiBqF,KAAK;oCACpB1E;oCACAL;oCACAG,UAAU,OAAO2I,WAAW,cAAc,KAAKA,OAAOC,QAAQ,CAAC5I,QAAQ;gCACzE;4BACF,MAAM6I,YAAYrJ,IAAIsJ,KAAK,CAAC1C,MAAM,CAAC,CAAC2C,OAASA,KAAKpJ,MAAM,KAAK,QAAQyE,MAAM;4BAC3E,MAAMd,UAAU9D,IAAIsJ,KAAK,CAAC7D,IAAI,CAAC,CAAC8D,OAASA,KAAKpJ,MAAM,KAAK;4BACzD,MAAMqJ,WAAWhN,yBAAyBqH,QAAQ,CAAC7D,IAAIG,MAAM;4BAC7D,MAAMsJ,QAAQ3L,WAAWkC;4BACzB,MAAM0J,UAAUhL,iBAAiBsB,KAAKU;4BACtC,MAAMiJ,cAAcrH,cAActC,IAAIW,EAAE;4BACxC,qBACE,MAAC3D;gCAECiM,MAAK;gCACLW,kBAAgB5J,IAAIW,EAAE;gCACtBkJ,gBAAcF,cAAc,SAAStE;gCACrCgC,IAAI;oCACFyC,cAAc;oCACdC,SAASJ,cAAc,oBAAoB;oCAC3CK,SAAS;oCACTC,cAAcN,cAAc,iBAAiB;oCAC7CO,YAAY,CAACC,QACXA,MAAMC,WAAW,CAACC,MAAM,CAAC;4CAAC;4CAAoB;yCAAgB,EAAE;4CAC9DC,UAAUH,MAAMC,WAAW,CAACE,QAAQ,CAACC,QAAQ;wCAC/C;oCACFC,IAAI;oCACJC,IAAI;gCACN;;kDAEA,MAACnN;wCAAMoK,WAAU;wCAAMC,SAAS;wCAAGN,IAAI;4CAAEO,YAAY;wCAAS;;0DAC5D,KAAC1K;gDAAK+K,MAAK;gDAAQJ,SAAQ;gDAAWvG,OAAOtB,IAAIE,IAAI;;0DACrD,KAAChD;gDAAK+K,MAAK;gDAAQC,OAAOlJ,WAAW,CAACyK,MAAM;gDAAEnI,OAAO1D,mBAAmB,CAAC6L,MAAM;;0DAC/E,MAAClM;gDAAWsK,SAAQ;gDAAUK,OAAM;gDAAiBb,IAAI;oDAAES,UAAU;gDAAE;;oDACpEuB;oDAAU;oDAAErJ,IAAIsJ,KAAK,CAAC1E,MAAM;oDAAC;oDAC7Bd,UAAU,CAAC,GAAG,EAAEA,QAAQ4G,IAAI,EAAE,GAAG;oDAEjC9J,WAAWZ,OAAO,IAAI,CAAC,GAAG,EAAEY,WAAWZ,KAAK,QAAQ,CAAC,GAAG;;;4CAE1D,CAACwJ,0BACA,KAACvM;gDAAOgL,MAAK;gDAAQM,SAAS,IAAM,KAAKxB,OAAO/G,IAAIW,EAAE;0DAAG;;;;kDAK7D,KAACpD;wCAAWsK,SAAQ;wCAAQR,IAAI;4CAAE0B,IAAI;4CAAK4B,WAAW;wCAAa;kDAChE3K,IAAI4K,KAAK,CAAChG,MAAM,GAAG7F,sBAChB,GAAGiB,IAAI4K,KAAK,CAACjH,KAAK,CAAC,GAAG5E,qBAAqB,CAAC,CAAC,GAC7CiB,IAAI4K,KAAK;;oCAEd5K,IAAI0E,KAAK,kBACR,KAACnH;wCAAWsK,SAAQ;wCAAUK,OAAOlI,IAAIG,MAAM,KAAK,WAAW,UAAU;wCAAgB2I,WAAU;kDAChG9I,IAAI0E,KAAK;;oCAIb9F,gBAAgBoB,sBACf,KAACzC;wCAAWsK,SAAQ;wCAAUK,OAAM;wCAAiBY,WAAU;kDAC5DlK,gBAAgBoB;;oCAIpB5B,kBAAkB4B,sBACjB,KAACzC;wCAAWsK,SAAQ;wCAAUK,OAAM;wCAAiBY,WAAU;kDAC5D1K,kBAAkB4B;;oCAItBA,EAAAA,aAAAA,IAAI6K,KAAK,qBAAT7K,WAAW4E,MAAM,kBAChB,KAAC5H;wCAAI8L,WAAU;wCAAKN,cAAW;wCAAQnB,IAAI;4CAAEyD,GAAG;4CAAG/B,IAAI;4CAAKgC,IAAI;wCAAE;kDAC/D5M,gBAAgB6B,KAAK+H,GAAG,CAAC,CAAC3C,oBACzB,MAAC7H;gDAA0BuL,WAAU;gDAAKjB,SAAQ;gDAAUmD,mBAAiB5F,IAAI+C,KAAK;;oDACnF,GAAG/C,IAAI9D,KAAK,CAAC,GAAG,EAAE5B,iBAAiB,CAAC0F,IAAI+C,KAAK,CAAC,EAAE;oDAChD/C,IAAI6F,MAAM,GAAG,CAAC,EAAE,EAAE7F,IAAI6F,MAAM,EAAE,GAAG;;+CAFnB7F,IAAI8F,IAAI;yCAM3B;oCACHhN,gBAAgB8B,qBACf,KAAC/C;wCACCgL,MAAK;wCACLJ,SAAQ;wCACRsD,UAAUnE,aAAahH,IAAIW,EAAE;wCAC7B4H,SAAS,IAAM,KAAKrB,OAAOlH,KAAK;gDAAEoL,OAAO;4CAAe;wCACxD/D,IAAI;4CAAE0B,IAAI;wCAAI;kDAEb;yCAED;oCACHG,YAAYlJ,qBACX,KAAC/C;wCAAOgL,MAAK;wCAAQJ,SAAQ;wCAAYiB,WAAWlM;wCAAS6D,MAAMyI,YAAYlJ;wCAAgBqH,IAAI;4CAAE0B,IAAI;wCAAI;kDAC1G;yCAED;oCAEHW,wBACC,KAACzM;wCACCgL,MAAK;wCACLJ,SAAQ;wCACRiB,WAAWlM;wCACX6D,MAAMiJ,QAAQjJ,IAAI;wCAClB4G,IAAI;4CAAE0B,IAAI;wCAAI;kDAEbW,QAAQpI,KAAK;yCAEd;oCACHtB,IAAIqL,OAAO,CAACtD,GAAG,CAAC,CAAC7G,QAAQuC;wCACxB,MAAMhD,OAAOhC,gBAAgByC,QAAQR;wCACrC,MAAMU,aAAaH,wBAAwBC;wCAC3C,qBACE,MAAClE;4CAAqDqK,IAAI;gDAAE0B,IAAI;4CAAI;;gDACjE7H,OAAOC,QAAQ,KAAK,0BACnB,KAAClE;oDAAOgL,MAAK;oDAAQM,SAAS,IAAMrF,eAAehC,OAAOP,EAAE;8DAAG;qDAG7DF,qBACF,MAAC7D;oDAAQ0O,kBAAiB;oDAAQ7K,MAAMA;;wDAAM;wDAC9BS,OAAOI,KAAK;;qDAE1BJ,OAAOqK,IAAI,iBACb,KAAChO;oDACCsK,SAAQ;oDACRiB,WAAU;oDACVzB,IAAI;wDAAEmE,YAAY;wDAAYC,YAAY;wDAAWX,GAAG;oDAAE;8DAEzD5J,OAAOqK,IAAI;mEAGd,KAAChO;oDAAWsK,SAAQ;oDAAUK,OAAM;8DACjChH,OAAOI,KAAK;;gDAGhBJ,OAAOgD,IAAI,iBACV,MAAC3G;oDAAWsK,SAAQ;oDAAUK,OAAM;oDAAiBY,WAAU;;wDAAM;wDAC5DvM,YAAY2E,OAAOgD,IAAI,CAACwH,UAAU;wDAAE;;qDAE3C;gDACHxK,OAAOyK,IAAI,iBACV,KAACpO;oDAAWsK,SAAQ;oDAAUK,OAAM;oDAAiBY,WAAU;8DAC5D5H,OAAOyK,IAAI;qDAEZ;gDACHvK,2BACC,MAAC7D;oDAAWsK,SAAQ;oDAAUK,OAAM;oDAAiBY,WAAU;;wDAAM;wDAC7D1H;wDAAW;;qDAEjB;;2CApCI,GAAGF,OAAOC,QAAQ,CAAC,CAAC,EAAED,OAAOP,EAAE,CAAC,CAAC,EAAE8C,OAAO;oCAuCxD;kDACA,KAACjF;wCACCwB,KAAKA;wCACL4L,UAAU,CAACC,QAAQ1E,UAAY,KAAKD,OAAO2E,QAAQ1E;wCACnD2E,MAAM9E,aAAahH,IAAIW,EAAE;wCACzBoL,OAAOjK;wCACPF,MAAMA;;;+BA9IH5B,IAAIW,EAAE;wBAkJjB;;;;YAQHsC,gBAAgB,sBACf,KAAC1E;gBACCyN,IAAI;gBACJC,SAAS;oBACP/I,eAAe;oBACf,IAAIf,UAAU,KAAK+B;gBACrB;gBACAzC,OAAOA;gBACPf,SAASA;gBACTL,QAAQ0B,CAAAA,2BAAAA,QAASmK,IAAI,IAAI7L,iBAAAA,SAAU,OAAQ;gBAC3C6L,IAAI,WAAEnK,2BAAAA,QAASmK,IAAI,oBAAI;gBACvBC,OAAO,WAAEpK,2BAAAA,QAASoK,OAAO,oBAAI;gBAC7BvK,MAAMA;gBACNwK,GAAG,WAAGxK,wBAAD,AAACA,KAA8CwK,GAAG,oBAAI;gBAC3DpH,OAAO/B,gBAAgB,QAAQ,OAAOA;;YAGzC5C,wBACC,KAAC/B;gBACC0N,MAAMjJ;gBACNkJ,SAAS;oBACPjJ,cAAc;oBACd,IAAIb,UAAU,KAAK+B;gBACrB;gBACAzC,OAAOA;gBACPpB,QAAQA;gBACRuB,MAAMA;gBACNlB,SAASA;gBACToB,SAASA;;;;AAKnB;AAEA,eAAeN,iBAAgB"}
@@ -483,8 +483,8 @@ BillingAiAllotments.displayName = 'BillingAiAllotments';
483
483
  id: "ai-allotments",
484
484
  children: /*#__PURE__*/ _jsx(CardDisplay, {
485
485
  header: 'AI allotments',
486
- help: pluginDocsHelp('billing', {
487
- anchor: '#ai-allotments',
486
+ help: pluginDocsHelp('aiAllotments', {
487
+ anchor: '#allotments',
488
488
  excerpt: 'A monthly share of the workspace’s AI credits for a member or a site — ' + 'hard or soft, with an optional list of models.'
489
489
  }),
490
490
  contentGutterX: true,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/billing-ai-allotments.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiPencilOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Button, Stack, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useMemo, useState } from 'react'\nimport { AI_ALLOTMENT_ORG_SUBJECT, aiAllotmentState } from '../model/ai-allotments'\nimport type {\n AiAllotmentRowWire,\n AiAllotmentsHostWire,\n AiAllotmentsMemberWire,\n} from '../usage/ai-usage-wire'\nimport { AiAllotmentEditor, type AiAllotmentValue } from './ai-allotment-editor.component'\nimport { saveAiAllotments, useAiAllotments } from './use-ai-allotments'\n\n/** An allotment as a table cell reads it: used of credits, mode, models. */\nexport function aiAllotmentSummary(\n row: Pick<AiAllotmentRowWire, 'credits' | 'mode' | 'models' | 'used'> | null | undefined,\n): string {\n if (!row) return '—'\n const parts: string[] = []\n if (row.credits !== null) {\n parts.push(`${row.used.toLocaleString()} of ${row.credits.toLocaleString()} (${row.mode})`)\n }\n if (row.models?.length) {\n parts.push(`${row.models.length} model${row.models.length === 1 ? '' : 's'}`)\n }\n return parts.join(' · ') || '—'\n}\n\n/** The color an allotment's figure reads in: warning from 80%, error at it. */\nexport function aiAllotmentTone(\n row: Pick<AiAllotmentRowWire, 'credits' | 'used'> | null | undefined,\n): 'text.primary' | 'warning.main' | 'error.main' {\n if (!row || row.credits === null) return 'text.primary'\n const state = aiAllotmentState(row.used, row.credits)\n return state === 'reached' ? 'error.main' : state === 'warn' ? 'warning.main' : 'text.primary'\n}\n\ntype Editing =\n | {\n kind: 'subjects'\n subjects: string[]\n title: string\n description: string\n initial: AiAllotmentValue | null\n removable: boolean\n }\n | { kind: 'everySite' }\n | { kind: 'restriction'; initial: AiAllotmentValue | null; removable: boolean }\n\nconst valueOf = (row: AiAllotmentRowWire | undefined): AiAllotmentValue | null =>\n row ? { credits: row.credits, mode: row.mode, models: row.models } : null\n\n/**\n * A row of one of the three lists: the subject its allotment is stored under,\n * that subject's allotment when it has one, and `$id` — the id `ListTable`\n * selects by, which is the member's uid, the site's id, or the collaborator's\n * subject.\n */\ntype AllotmentListRow = {\n $id: string\n subject: string\n allotment: AiAllotmentRowWire | undefined\n}\ntype MemberRow = AllotmentListRow & { member: AiAllotmentsMemberWire }\ntype SiteRow = AllotmentListRow & { host: AiAllotmentsHostWire }\ntype CollaboratorRow = AllotmentListRow & { name: string; site: string }\n\n/** The allotment column: used of credits, in the tone its share has reached. */\nconst allotmentColumn = <Row extends AllotmentListRow>(): GridColDef<Row> => ({\n field: 'allotment',\n headerName: 'Allotment',\n flex: 1,\n minWidth: 200,\n sortable: false,\n valueGetter: (_value, row) => aiAllotmentSummary(row.allotment),\n renderCell: ({ row }) => (\n <Typography variant=\"body2\" noWrap sx={{ color: aiAllotmentTone(row.allotment) }}>\n {aiAllotmentSummary(row.allotment)}\n </Typography>\n ),\n})\n\nconst creditsColumn = <Row extends AllotmentListRow>(\n credits: (row: Row) => number,\n): GridColDef<Row> => ({\n field: 'credits',\n headerName: 'Credits this month',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 170,\n valueGetter: (_value, row) => credits(row),\n valueFormatter: (value: number) => value.toLocaleString(),\n})\n\n/**\n * AI ALLOTMENTS ON BILLING → USAGE (AGL-2942): who may draw how much of the\n * workspace's AI pool.\n *\n * Team members and sites, each with this month's credits and its allotment;\n * select several and set one allotment for all of them, or set the same\n * allotment on every site at once — the agency's \"same for every client\n * site\". Collaborators' allotments, which their sites' Users cards set, are\n * listed below. On a plan that offers it, the organization's model\n * restriction sits at the end.\n *\n * Read with `billing.view`; the controls appear with `billing.manage`, and\n * the route refuses a write without it whatever this card draws.\n *\n * The three tables are the route's whole answer — the roster the plan's\n * seats bound, the organization's sites, the collaborator allotments — so\n * they offer no Filters panel and no search: a bounded readout is read\n * whole, and a grid that narrowed the rows it holds is the pattern every\n * list is leaving (AGL-3321).\n */\nexport function BillingAiAllotments({ orgId }: { orgId?: string }) {\n const { data: user } = useUser()\n const allotments = useAiAllotments({ orgId })\n const [members, setMembers] = useState<string[]>([])\n const [sites, setSites] = useState<string[]>([])\n const [editing, setEditing] = useState<Editing | null>(null)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const data = allotments.data\n const bySubject = useMemo(\n () => new Map((data?.allotments ?? []).map((row) => [row.subject, row])),\n [data],\n )\n\n if (allotments.status === 'refused') {\n return (\n <Alert severity=\"info\">{'Seeing AI allotments requires the View billing permission.'}</Alert>\n )\n }\n if (allotments.status === 'error' && !data) {\n return (\n <Alert severity=\"warning\">\n {'Could not read the AI allotments — a failed read, not an empty list.'}\n </Alert>\n )\n }\n if (!data || !orgId) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n )\n }\n\n const canEdit = data.canEdit.billing\n const team = data.members.filter((member) => member.orgWide)\n const nameOf = (uid: string | null) =>\n data.members.find((member) => member.uid === uid)?.name ?? uid ?? ''\n const siteName = (hostId: string | null) =>\n data.hosts.find((host) => host.hostId === hostId)?.name ?? hostId ?? ''\n const collaborators = data.allotments.filter((row) => row.scope === 'collab')\n const restriction = bySubject.get(AI_ALLOTMENT_ORG_SUBJECT)\n /*\n * Each list pages and sorts on its own, and a selection is kept by member\n * and site id rather than by row, so it survives a page change.\n */\n const teamRows: MemberRow[] = team.map((member) => ({\n $id: member.uid,\n subject: `member:${member.uid}`,\n allotment: bySubject.get(`member:${member.uid}`),\n member,\n }))\n const siteRows: SiteRow[] = data.hosts.map((host) => ({\n $id: host.hostId,\n subject: `host:${host.hostId}`,\n allotment: bySubject.get(`host:${host.hostId}`),\n host,\n }))\n const collaboratorRows: CollaboratorRow[] = collaborators.map((row) => ({\n $id: row.subject,\n subject: row.subject,\n allotment: row,\n name: nameOf(row.uid),\n site: siteName(row.hostId),\n }))\n\n const editSubjects = (subjects: string[], title: string, description: string) => {\n const rows = subjects.map((subject) => bySubject.get(subject))\n setError(null)\n setEditing({\n kind: 'subjects',\n subjects,\n title,\n description,\n initial: subjects.length === 1 ? valueOf(rows[0]) : null,\n removable: rows.every(Boolean),\n })\n }\n\n const run = async (body: Parameters<typeof saveAiAllotments>[1]) => {\n setBusy(true)\n setError(null)\n const result = await saveAiAllotments(user, body)\n setBusy(false)\n if (!result.ok) {\n setError(result.error)\n return\n }\n setEditing(null)\n setMembers([])\n setSites([])\n }\n const save = (value: AiAllotmentValue) => {\n if (!editing) return\n if (editing.kind === 'everySite') return void run({ orgId, everySite: value })\n if (editing.kind === 'restriction') {\n return void run(\n value.models\n ? { orgId, set: [{ subject: AI_ALLOTMENT_ORG_SUBJECT, ...value, credits: null }] }\n : { orgId, remove: [AI_ALLOTMENT_ORG_SUBJECT] },\n )\n }\n return void run({\n orgId,\n set: editing.subjects.map((subject) => ({ subject, ...value })),\n })\n }\n const remove = () => {\n if (!editing || editing.kind === 'everySite') return\n return void run({\n orgId,\n remove: editing.kind === 'restriction' ? [AI_ALLOTMENT_ORG_SUBJECT] : editing.subjects,\n })\n }\n const editMember = (row: MemberRow) =>\n editSubjects([row.subject], `AI allotment — ${row.member.name}`, 'Counts what this person draws across every site.')\n const editSite = (row: SiteRow) =>\n editSubjects([row.subject], `AI allotment — ${row.host.name}`, 'Counts what everyone draws on the site, together.')\n const editCollaborator = (row: CollaboratorRow) =>\n editSubjects(\n [row.subject],\n `AI allotment — ${row.name} on ${row.site}`,\n 'Counts what this collaborator draws on this site.',\n )\n /** The one action a row has: set its allotment, or edit the one it has. */\n const editColumn = <Row extends AllotmentListRow>(label: (row: Row) => string, edit: (row: Row) => void) =>\n listActionsColumn((row: Row) => (\n <ListRowActions\n label={label(row)}\n quick={{\n icon: mdiPencilOutline.path,\n label: row.allotment ? 'Edit allotment' : 'Set allotment',\n onClick: () => edit(row),\n }}\n items={[]}\n />\n ))\n\n const teamColumns: GridColDef<MemberRow>[] = [\n { field: 'name', headerName: 'Member', flex: 1, minWidth: 180, valueGetter: (_value, row) => row.member.name },\n creditsColumn<MemberRow>((row) => row.member.credits),\n allotmentColumn<MemberRow>(),\n ...(canEdit ? [editColumn<MemberRow>((row) => row.member.name, editMember)] : []),\n ]\n const siteColumns: GridColDef<SiteRow>[] = [\n { field: 'name', headerName: 'Site', flex: 1, minWidth: 180, valueGetter: (_value, row) => row.host.name },\n creditsColumn<SiteRow>((row) => row.host.credits),\n allotmentColumn<SiteRow>(),\n ...(canEdit ? [editColumn<SiteRow>((row) => row.host.name, editSite)] : []),\n ]\n const collaboratorColumns: GridColDef<CollaboratorRow>[] = [\n { field: 'name', headerName: 'Collaborator', flex: 1, minWidth: 180 },\n { field: 'site', headerName: 'Site', flex: 1, minWidth: 160 },\n allotmentColumn<CollaboratorRow>(),\n ...(canEdit ? [editColumn<CollaboratorRow>((row) => `${row.name} on ${row.site}`, editCollaborator)] : []),\n ]\n\n return (\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Give a member or a whole site a share of the workspace’s AI credits each month. ' +\n 'A hard allotment stops AI requests at the line; a soft one keeps going and alerts ' +\n 'you at 75%, 80%, 90% and 100%. The workspace’s own credits still stop first.'}\n </Typography>\n\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Team members'}\n </Typography>\n {canEdit ? (\n <Button\n size=\"small\"\n disabled={!members.length}\n onClick={() =>\n editSubjects(\n members.map((uid) => `member:${uid}`),\n members.length === 1 ? `AI allotment — ${nameOf(members[0])}` : `AI allotment — ${members.length} members`,\n 'Counts what each person draws across every site.',\n )\n }\n >\n {members.length > 1 ? `Set for ${members.length} selected` : 'Set for selected'}\n </Button>\n ) : null}\n </Stack>\n <ListTable\n aria-label=\"Team members' AI allotments\"\n rows={teamRows}\n columns={teamColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No team members\"\n disableColumnFilter\n quickFilter={false}\n {...(canEdit\n ? { selectable: { selected: members, onChange: setMembers }, onOpen: (_id: string, row: MemberRow) => editMember(row) }\n : {})}\n />\n\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Sites'}\n </Typography>\n {canEdit ? (\n <>\n <Button\n size=\"small\"\n disabled={!sites.length}\n onClick={() =>\n editSubjects(\n sites.map((hostId) => `host:${hostId}`),\n sites.length === 1 ? `AI allotment — ${siteName(sites[0])}` : `AI allotment — ${sites.length} sites`,\n 'Counts what everyone draws on the site, together.',\n )\n }\n >\n {sites.length > 1 ? `Set for ${sites.length} selected` : 'Set for selected'}\n </Button>\n <Button\n size=\"small\"\n disabled={!data.hosts.length}\n onClick={() => {\n setError(null)\n setEditing({ kind: 'everySite' })\n }}\n >\n {'Same for every client site'}\n </Button>\n </>\n ) : null}\n </Stack>\n <ListTable\n aria-label=\"Sites' AI allotments\"\n rows={siteRows}\n columns={siteColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No sites\"\n disableColumnFilter\n quickFilter={false}\n {...(canEdit\n ? { selectable: { selected: sites, onChange: setSites }, onOpen: (_id: string, row: SiteRow) => editSite(row) }\n : {})}\n />\n\n {collaborators.length ? (\n <>\n <Typography variant=\"subtitle2\">{'Site collaborators'}</Typography>\n <ListTable\n aria-label=\"Site collaborators' AI allotments\"\n rows={collaboratorRows}\n columns={collaboratorColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n disableColumnFilter\n quickFilter={false}\n {...(canEdit ? { onOpen: (_id: string, row: CollaboratorRow) => editCollaborator(row) } : {})}\n />\n </>\n ) : null}\n\n {data.restrictionAvailable ? (\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"body2\" sx={{ flexGrow: 1 }}>\n {'Models for the whole organization: '}\n {restriction?.models?.length\n ? data.models\n .filter((model) => restriction.models?.includes(model.id))\n .map((model) => model.label)\n .join(', ') || `${restriction.models.length} models`\n : 'every model the plan offers'}\n </Typography>\n {canEdit ? (\n <Button\n size=\"small\"\n onClick={() => {\n setError(null)\n setEditing({ kind: 'restriction', initial: valueOf(restriction), removable: Boolean(restriction) })\n }}\n >\n {'Restrict models'}\n </Button>\n ) : null}\n </Stack>\n ) : null}\n\n <AiAllotmentEditor\n open={editing !== null}\n title={\n editing?.kind === 'everySite'\n ? 'Same allotment for every site'\n : editing?.kind === 'restriction'\n ? 'Models for the whole organization'\n : (editing?.title ?? '')\n }\n description={\n editing?.kind === 'everySite'\n ? `Sets this allotment on all ${data.hosts.length} sites, replacing any each one had.`\n : editing?.kind === 'restriction'\n ? 'Every member, collaborator and Auto choose only among these models.'\n : editing?.description\n }\n initial={editing && editing.kind !== 'everySite' ? editing.initial : null}\n models={data.models}\n modelsOnly={editing?.kind === 'restriction'}\n poolLimit={data.pool.limit}\n busy={busy}\n error={error}\n onClose={() => setEditing(null)}\n onSave={save}\n onRemove={editing && editing.kind !== 'everySite' && editing.removable ? remove : undefined}\n />\n </Stack>\n )\n}\nBillingAiAllotments.displayName = 'BillingAiAllotments'\n\n/**\n * The card on Billing → Usage, through the `orgBillingUsage` zone, beneath\n * the table of who drew the credits. Registered with `billing.view`.\n */\nexport function AiAllotmentsCard(props: { orgId?: string }) {\n return (\n <div id=\"ai-allotments\">\n <CardDisplay\n header={'AI allotments'}\n help={pluginDocsHelp('billing', {\n anchor: '#ai-allotments',\n excerpt:\n 'A monthly share of the workspace’s AI credits for a member or a site — ' +\n 'hard or soft, with an optional list of models.',\n })}\n contentGutterX\n contentGutterY\n >\n <BillingAiAllotments orgId={props.orgId} />\n </CardDisplay>\n </div>\n )\n}\nAiAllotmentsCard.displayName = 'AiAllotmentsCard'\n\nexport default AiAllotmentsCard\n"],"names":["pluginDocsHelp","mdiPencilOutline","CardDisplay","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","useUser","Alert","Button","Stack","Typography","useMemo","useState","AI_ALLOTMENT_ORG_SUBJECT","aiAllotmentState","AiAllotmentEditor","saveAiAllotments","useAiAllotments","aiAllotmentSummary","row","parts","credits","push","used","toLocaleString","mode","models","length","join","aiAllotmentTone","state","valueOf","allotmentColumn","field","headerName","flex","minWidth","sortable","valueGetter","_value","allotment","renderCell","variant","noWrap","sx","color","creditsColumn","type","align","headerAlign","width","valueFormatter","value","BillingAiAllotments","orgId","restriction","data","user","allotments","members","setMembers","sites","setSites","editing","setEditing","busy","setBusy","error","setError","bySubject","Map","map","subject","status","severity","canEdit","billing","team","filter","member","orgWide","nameOf","uid","find","name","siteName","hostId","hosts","host","collaborators","scope","get","teamRows","$id","siteRows","collaboratorRows","site","editSubjects","subjects","title","description","rows","kind","initial","removable","every","Boolean","run","body","result","ok","save","everySite","set","remove","editMember","editSite","editCollaborator","editColumn","label","edit","quick","icon","path","onClick","items","teamColumns","siteColumns","collaboratorColumns","spacing","useFlexGap","direction","alignItems","flexWrap","rowGap","flexGrow","size","disabled","aria-label","columns","rowHeight","noRowsLabel","disableColumnFilter","quickFilter","selectable","selected","onChange","onOpen","_id","restrictionAvailable","model","includes","id","open","modelsOnly","poolLimit","pool","limit","onClose","onSave","onRemove","undefined","displayName","AiAllotmentsCard","props","div","header","help","anchor","excerpt","contentGutterX","contentGutterY"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/billing-ai-allotments.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiPencilOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Button, Stack, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useMemo, useState } from 'react'\nimport { AI_ALLOTMENT_ORG_SUBJECT, aiAllotmentState } from '../model/ai-allotments'\nimport type {\n AiAllotmentRowWire,\n AiAllotmentsHostWire,\n AiAllotmentsMemberWire,\n} from '../usage/ai-usage-wire'\nimport { AiAllotmentEditor, type AiAllotmentValue } from './ai-allotment-editor.component'\nimport { saveAiAllotments, useAiAllotments } from './use-ai-allotments'\n\n/** An allotment as a table cell reads it: used of credits, mode, models. */\nexport function aiAllotmentSummary(\n row: Pick<AiAllotmentRowWire, 'credits' | 'mode' | 'models' | 'used'> | null | undefined,\n): string {\n if (!row) return '—'\n const parts: string[] = []\n if (row.credits !== null) {\n parts.push(`${row.used.toLocaleString()} of ${row.credits.toLocaleString()} (${row.mode})`)\n }\n if (row.models?.length) {\n parts.push(`${row.models.length} model${row.models.length === 1 ? '' : 's'}`)\n }\n return parts.join(' · ') || '—'\n}\n\n/** The color an allotment's figure reads in: warning from 80%, error at it. */\nexport function aiAllotmentTone(\n row: Pick<AiAllotmentRowWire, 'credits' | 'used'> | null | undefined,\n): 'text.primary' | 'warning.main' | 'error.main' {\n if (!row || row.credits === null) return 'text.primary'\n const state = aiAllotmentState(row.used, row.credits)\n return state === 'reached' ? 'error.main' : state === 'warn' ? 'warning.main' : 'text.primary'\n}\n\ntype Editing =\n | {\n kind: 'subjects'\n subjects: string[]\n title: string\n description: string\n initial: AiAllotmentValue | null\n removable: boolean\n }\n | { kind: 'everySite' }\n | { kind: 'restriction'; initial: AiAllotmentValue | null; removable: boolean }\n\nconst valueOf = (row: AiAllotmentRowWire | undefined): AiAllotmentValue | null =>\n row ? { credits: row.credits, mode: row.mode, models: row.models } : null\n\n/**\n * A row of one of the three lists: the subject its allotment is stored under,\n * that subject's allotment when it has one, and `$id` — the id `ListTable`\n * selects by, which is the member's uid, the site's id, or the collaborator's\n * subject.\n */\ntype AllotmentListRow = {\n $id: string\n subject: string\n allotment: AiAllotmentRowWire | undefined\n}\ntype MemberRow = AllotmentListRow & { member: AiAllotmentsMemberWire }\ntype SiteRow = AllotmentListRow & { host: AiAllotmentsHostWire }\ntype CollaboratorRow = AllotmentListRow & { name: string; site: string }\n\n/** The allotment column: used of credits, in the tone its share has reached. */\nconst allotmentColumn = <Row extends AllotmentListRow>(): GridColDef<Row> => ({\n field: 'allotment',\n headerName: 'Allotment',\n flex: 1,\n minWidth: 200,\n sortable: false,\n valueGetter: (_value, row) => aiAllotmentSummary(row.allotment),\n renderCell: ({ row }) => (\n <Typography variant=\"body2\" noWrap sx={{ color: aiAllotmentTone(row.allotment) }}>\n {aiAllotmentSummary(row.allotment)}\n </Typography>\n ),\n})\n\nconst creditsColumn = <Row extends AllotmentListRow>(\n credits: (row: Row) => number,\n): GridColDef<Row> => ({\n field: 'credits',\n headerName: 'Credits this month',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 170,\n valueGetter: (_value, row) => credits(row),\n valueFormatter: (value: number) => value.toLocaleString(),\n})\n\n/**\n * AI ALLOTMENTS ON BILLING → USAGE (AGL-2942): who may draw how much of the\n * workspace's AI pool.\n *\n * Team members and sites, each with this month's credits and its allotment;\n * select several and set one allotment for all of them, or set the same\n * allotment on every site at once — the agency's \"same for every client\n * site\". Collaborators' allotments, which their sites' Users cards set, are\n * listed below. On a plan that offers it, the organization's model\n * restriction sits at the end.\n *\n * Read with `billing.view`; the controls appear with `billing.manage`, and\n * the route refuses a write without it whatever this card draws.\n *\n * The three tables are the route's whole answer — the roster the plan's\n * seats bound, the organization's sites, the collaborator allotments — so\n * they offer no Filters panel and no search: a bounded readout is read\n * whole, and a grid that narrowed the rows it holds is the pattern every\n * list is leaving (AGL-3321).\n */\nexport function BillingAiAllotments({ orgId }: { orgId?: string }) {\n const { data: user } = useUser()\n const allotments = useAiAllotments({ orgId })\n const [members, setMembers] = useState<string[]>([])\n const [sites, setSites] = useState<string[]>([])\n const [editing, setEditing] = useState<Editing | null>(null)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const data = allotments.data\n const bySubject = useMemo(\n () => new Map((data?.allotments ?? []).map((row) => [row.subject, row])),\n [data],\n )\n\n if (allotments.status === 'refused') {\n return (\n <Alert severity=\"info\">{'Seeing AI allotments requires the View billing permission.'}</Alert>\n )\n }\n if (allotments.status === 'error' && !data) {\n return (\n <Alert severity=\"warning\">\n {'Could not read the AI allotments — a failed read, not an empty list.'}\n </Alert>\n )\n }\n if (!data || !orgId) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n )\n }\n\n const canEdit = data.canEdit.billing\n const team = data.members.filter((member) => member.orgWide)\n const nameOf = (uid: string | null) =>\n data.members.find((member) => member.uid === uid)?.name ?? uid ?? ''\n const siteName = (hostId: string | null) =>\n data.hosts.find((host) => host.hostId === hostId)?.name ?? hostId ?? ''\n const collaborators = data.allotments.filter((row) => row.scope === 'collab')\n const restriction = bySubject.get(AI_ALLOTMENT_ORG_SUBJECT)\n /*\n * Each list pages and sorts on its own, and a selection is kept by member\n * and site id rather than by row, so it survives a page change.\n */\n const teamRows: MemberRow[] = team.map((member) => ({\n $id: member.uid,\n subject: `member:${member.uid}`,\n allotment: bySubject.get(`member:${member.uid}`),\n member,\n }))\n const siteRows: SiteRow[] = data.hosts.map((host) => ({\n $id: host.hostId,\n subject: `host:${host.hostId}`,\n allotment: bySubject.get(`host:${host.hostId}`),\n host,\n }))\n const collaboratorRows: CollaboratorRow[] = collaborators.map((row) => ({\n $id: row.subject,\n subject: row.subject,\n allotment: row,\n name: nameOf(row.uid),\n site: siteName(row.hostId),\n }))\n\n const editSubjects = (subjects: string[], title: string, description: string) => {\n const rows = subjects.map((subject) => bySubject.get(subject))\n setError(null)\n setEditing({\n kind: 'subjects',\n subjects,\n title,\n description,\n initial: subjects.length === 1 ? valueOf(rows[0]) : null,\n removable: rows.every(Boolean),\n })\n }\n\n const run = async (body: Parameters<typeof saveAiAllotments>[1]) => {\n setBusy(true)\n setError(null)\n const result = await saveAiAllotments(user, body)\n setBusy(false)\n if (!result.ok) {\n setError(result.error)\n return\n }\n setEditing(null)\n setMembers([])\n setSites([])\n }\n const save = (value: AiAllotmentValue) => {\n if (!editing) return\n if (editing.kind === 'everySite') return void run({ orgId, everySite: value })\n if (editing.kind === 'restriction') {\n return void run(\n value.models\n ? { orgId, set: [{ subject: AI_ALLOTMENT_ORG_SUBJECT, ...value, credits: null }] }\n : { orgId, remove: [AI_ALLOTMENT_ORG_SUBJECT] },\n )\n }\n return void run({\n orgId,\n set: editing.subjects.map((subject) => ({ subject, ...value })),\n })\n }\n const remove = () => {\n if (!editing || editing.kind === 'everySite') return\n return void run({\n orgId,\n remove: editing.kind === 'restriction' ? [AI_ALLOTMENT_ORG_SUBJECT] : editing.subjects,\n })\n }\n const editMember = (row: MemberRow) =>\n editSubjects([row.subject], `AI allotment — ${row.member.name}`, 'Counts what this person draws across every site.')\n const editSite = (row: SiteRow) =>\n editSubjects([row.subject], `AI allotment — ${row.host.name}`, 'Counts what everyone draws on the site, together.')\n const editCollaborator = (row: CollaboratorRow) =>\n editSubjects(\n [row.subject],\n `AI allotment — ${row.name} on ${row.site}`,\n 'Counts what this collaborator draws on this site.',\n )\n /** The one action a row has: set its allotment, or edit the one it has. */\n const editColumn = <Row extends AllotmentListRow>(label: (row: Row) => string, edit: (row: Row) => void) =>\n listActionsColumn((row: Row) => (\n <ListRowActions\n label={label(row)}\n quick={{\n icon: mdiPencilOutline.path,\n label: row.allotment ? 'Edit allotment' : 'Set allotment',\n onClick: () => edit(row),\n }}\n items={[]}\n />\n ))\n\n const teamColumns: GridColDef<MemberRow>[] = [\n { field: 'name', headerName: 'Member', flex: 1, minWidth: 180, valueGetter: (_value, row) => row.member.name },\n creditsColumn<MemberRow>((row) => row.member.credits),\n allotmentColumn<MemberRow>(),\n ...(canEdit ? [editColumn<MemberRow>((row) => row.member.name, editMember)] : []),\n ]\n const siteColumns: GridColDef<SiteRow>[] = [\n { field: 'name', headerName: 'Site', flex: 1, minWidth: 180, valueGetter: (_value, row) => row.host.name },\n creditsColumn<SiteRow>((row) => row.host.credits),\n allotmentColumn<SiteRow>(),\n ...(canEdit ? [editColumn<SiteRow>((row) => row.host.name, editSite)] : []),\n ]\n const collaboratorColumns: GridColDef<CollaboratorRow>[] = [\n { field: 'name', headerName: 'Collaborator', flex: 1, minWidth: 180 },\n { field: 'site', headerName: 'Site', flex: 1, minWidth: 160 },\n allotmentColumn<CollaboratorRow>(),\n ...(canEdit ? [editColumn<CollaboratorRow>((row) => `${row.name} on ${row.site}`, editCollaborator)] : []),\n ]\n\n return (\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Give a member or a whole site a share of the workspace’s AI credits each month. ' +\n 'A hard allotment stops AI requests at the line; a soft one keeps going and alerts ' +\n 'you at 75%, 80%, 90% and 100%. The workspace’s own credits still stop first.'}\n </Typography>\n\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Team members'}\n </Typography>\n {canEdit ? (\n <Button\n size=\"small\"\n disabled={!members.length}\n onClick={() =>\n editSubjects(\n members.map((uid) => `member:${uid}`),\n members.length === 1 ? `AI allotment — ${nameOf(members[0])}` : `AI allotment — ${members.length} members`,\n 'Counts what each person draws across every site.',\n )\n }\n >\n {members.length > 1 ? `Set for ${members.length} selected` : 'Set for selected'}\n </Button>\n ) : null}\n </Stack>\n <ListTable\n aria-label=\"Team members' AI allotments\"\n rows={teamRows}\n columns={teamColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No team members\"\n disableColumnFilter\n quickFilter={false}\n {...(canEdit\n ? { selectable: { selected: members, onChange: setMembers }, onOpen: (_id: string, row: MemberRow) => editMember(row) }\n : {})}\n />\n\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Sites'}\n </Typography>\n {canEdit ? (\n <>\n <Button\n size=\"small\"\n disabled={!sites.length}\n onClick={() =>\n editSubjects(\n sites.map((hostId) => `host:${hostId}`),\n sites.length === 1 ? `AI allotment — ${siteName(sites[0])}` : `AI allotment — ${sites.length} sites`,\n 'Counts what everyone draws on the site, together.',\n )\n }\n >\n {sites.length > 1 ? `Set for ${sites.length} selected` : 'Set for selected'}\n </Button>\n <Button\n size=\"small\"\n disabled={!data.hosts.length}\n onClick={() => {\n setError(null)\n setEditing({ kind: 'everySite' })\n }}\n >\n {'Same for every client site'}\n </Button>\n </>\n ) : null}\n </Stack>\n <ListTable\n aria-label=\"Sites' AI allotments\"\n rows={siteRows}\n columns={siteColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No sites\"\n disableColumnFilter\n quickFilter={false}\n {...(canEdit\n ? { selectable: { selected: sites, onChange: setSites }, onOpen: (_id: string, row: SiteRow) => editSite(row) }\n : {})}\n />\n\n {collaborators.length ? (\n <>\n <Typography variant=\"subtitle2\">{'Site collaborators'}</Typography>\n <ListTable\n aria-label=\"Site collaborators' AI allotments\"\n rows={collaboratorRows}\n columns={collaboratorColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n disableColumnFilter\n quickFilter={false}\n {...(canEdit ? { onOpen: (_id: string, row: CollaboratorRow) => editCollaborator(row) } : {})}\n />\n </>\n ) : null}\n\n {data.restrictionAvailable ? (\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"body2\" sx={{ flexGrow: 1 }}>\n {'Models for the whole organization: '}\n {restriction?.models?.length\n ? data.models\n .filter((model) => restriction.models?.includes(model.id))\n .map((model) => model.label)\n .join(', ') || `${restriction.models.length} models`\n : 'every model the plan offers'}\n </Typography>\n {canEdit ? (\n <Button\n size=\"small\"\n onClick={() => {\n setError(null)\n setEditing({ kind: 'restriction', initial: valueOf(restriction), removable: Boolean(restriction) })\n }}\n >\n {'Restrict models'}\n </Button>\n ) : null}\n </Stack>\n ) : null}\n\n <AiAllotmentEditor\n open={editing !== null}\n title={\n editing?.kind === 'everySite'\n ? 'Same allotment for every site'\n : editing?.kind === 'restriction'\n ? 'Models for the whole organization'\n : (editing?.title ?? '')\n }\n description={\n editing?.kind === 'everySite'\n ? `Sets this allotment on all ${data.hosts.length} sites, replacing any each one had.`\n : editing?.kind === 'restriction'\n ? 'Every member, collaborator and Auto choose only among these models.'\n : editing?.description\n }\n initial={editing && editing.kind !== 'everySite' ? editing.initial : null}\n models={data.models}\n modelsOnly={editing?.kind === 'restriction'}\n poolLimit={data.pool.limit}\n busy={busy}\n error={error}\n onClose={() => setEditing(null)}\n onSave={save}\n onRemove={editing && editing.kind !== 'everySite' && editing.removable ? remove : undefined}\n />\n </Stack>\n )\n}\nBillingAiAllotments.displayName = 'BillingAiAllotments'\n\n/**\n * The card on Billing → Usage, through the `orgBillingUsage` zone, beneath\n * the table of who drew the credits. Registered with `billing.view`.\n */\nexport function AiAllotmentsCard(props: { orgId?: string }) {\n return (\n <div id=\"ai-allotments\">\n <CardDisplay\n header={'AI allotments'}\n help={pluginDocsHelp('aiAllotments', {\n anchor: '#allotments',\n excerpt:\n 'A monthly share of the workspace’s AI credits for a member or a site — ' +\n 'hard or soft, with an optional list of models.',\n })}\n contentGutterX\n contentGutterY\n >\n <BillingAiAllotments orgId={props.orgId} />\n </CardDisplay>\n </div>\n )\n}\nAiAllotmentsCard.displayName = 'AiAllotmentsCard'\n\nexport default AiAllotmentsCard\n"],"names":["pluginDocsHelp","mdiPencilOutline","CardDisplay","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","useUser","Alert","Button","Stack","Typography","useMemo","useState","AI_ALLOTMENT_ORG_SUBJECT","aiAllotmentState","AiAllotmentEditor","saveAiAllotments","useAiAllotments","aiAllotmentSummary","row","parts","credits","push","used","toLocaleString","mode","models","length","join","aiAllotmentTone","state","valueOf","allotmentColumn","field","headerName","flex","minWidth","sortable","valueGetter","_value","allotment","renderCell","variant","noWrap","sx","color","creditsColumn","type","align","headerAlign","width","valueFormatter","value","BillingAiAllotments","orgId","restriction","data","user","allotments","members","setMembers","sites","setSites","editing","setEditing","busy","setBusy","error","setError","bySubject","Map","map","subject","status","severity","canEdit","billing","team","filter","member","orgWide","nameOf","uid","find","name","siteName","hostId","hosts","host","collaborators","scope","get","teamRows","$id","siteRows","collaboratorRows","site","editSubjects","subjects","title","description","rows","kind","initial","removable","every","Boolean","run","body","result","ok","save","everySite","set","remove","editMember","editSite","editCollaborator","editColumn","label","edit","quick","icon","path","onClick","items","teamColumns","siteColumns","collaboratorColumns","spacing","useFlexGap","direction","alignItems","flexWrap","rowGap","flexGrow","size","disabled","aria-label","columns","rowHeight","noRowsLabel","disableColumnFilter","quickFilter","selectable","selected","onChange","onOpen","_id","restrictionAvailable","model","includes","id","open","modelsOnly","poolLimit","pool","limit","onClose","onSave","onRemove","undefined","displayName","AiAllotmentsCard","props","div","header","help","anchor","excerpt","contentGutterX","contentGutterY"],"mappings":"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"}
@@ -197,6 +197,7 @@ import { AI_USAGE_EXPORT_ROWS_HEADER, aiUsageMonthLabel, formatAiUsageShare } fr
197
197
  quick: {
198
198
  icon: mdiOpenInNew.path,
199
199
  label: 'View member',
200
+ newTab: true,
200
201
  to: memberHref(row.uid)
201
202
  },
202
203
  items: []
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/billing-ai-top-users.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 { aiAddonName, countCsvDataRows } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiOpenInNew } from '@aglyn/shared-data-mdi'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useMemo, useState } from 'react'\nimport { topAiUsageKinds } from '../model/ai-usage-by-user'\nimport { useOrgAiUsage } from './use-org-ai-usage'\nimport {\n AI_USAGE_EXPORT_ROWS_HEADER,\n aiUsageMonthLabel,\n formatAiUsageShare,\n type OrgAiUsageRowWire,\n} from '../usage/ai-usage-wire'\n\nexport interface BillingAiTopUsersProps {\n orgId: string | undefined\n /** The org's slug, for the links into each person's page. */\n orgSlug: string\n}\n\n/**\n * WHO IS GENERATING WHAT (AGL-2928) — the per-person table beneath the\n * credit meter on Billing → Usage.\n *\n * The meter above says how much of the pool the workspace has drawn; this\n * says who drew it. Each row is one member's month: credits, their share of\n * the workspace's spend, requests, and how often the gate refused them. A\n * month picker walks the retention window, and the file button hands the\n * same month to a spreadsheet through the streamed export.\n *\n * ## The file is checked, not trusted\n *\n * The route promises its row count in a header taken before the first row\n * is written. A stream that dies halfway yields a well-formed shorter file,\n * so a body with fewer rows than promised is refused rather than saved.\n *\n * The copy stays descriptive on purpose — who, how much, on which site — and\n * never characterizes what the numbers mean about a person.\n */\nexport function BillingAiTopUsersComponent(props: BillingAiTopUsersProps) {\n const { orgId, orgSlug } = props\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [month, setMonth] = useState<string | undefined>(undefined)\n const [exporting, setExporting] = useState(false)\n const usage = useOrgAiUsage(orgId, { month })\n const router = useRouter()\n const name = aiAddonName()\n\n const handleExport = useCallback(async () => {\n if (!orgId || exporting) return\n setExporting(true)\n try {\n const params = new URLSearchParams({ orgId, month: usage.month, format: 'csv' })\n const response = await authorizedFetch(\n user,\n `/api/ai/usage?${params.toString()}`,\n )\n if (!response.ok) {\n const failure = await response.json().catch(() => ({}))\n throw new Error(failure?.error ?? 'Export failed')\n }\n const text = await response.text()\n const promised = Number(response.headers.get(AI_USAGE_EXPORT_ROWS_HEADER) ?? '')\n const received = countCsvDataRows(text)\n if (Number.isFinite(promised) && received < promised) {\n enqueueSnackbar(\n `Export incomplete — ${received} of ${promised} rows arrived. ` +\n 'Nothing was saved; try again.',\n { variant: 'error' },\n )\n return\n }\n const url = URL.createObjectURL(new Blob([text], { type: 'text/csv' }))\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = `ai-usage-${usage.month}.csv`\n anchor.click()\n URL.revokeObjectURL(url)\n } catch (error) {\n enqueueSnackbar(error instanceof Error ? error.message : 'Export failed', {\n variant: 'error',\n })\n } finally {\n setExporting(false)\n }\n }, [orgId, exporting, usage.month, user, enqueueSnackbar])\n\n const rows = usage.data?.rows ?? []\n const memberHref = useCallback(\n (uid: string) => buildRoute(Route.MANAGE_TEAM_MEMBER, { orgSlug, uid }),\n [orgSlug],\n )\n\n /*\n * One row per member, and the whole month is in hand, so the grid pages\n * and sorts it itself. The row opens the member's page, which is\n * where the split by site and by kind lives.\n */\n const columns = useMemo<GridColDef<OrgAiUsageRowWire>[]>(\n () => [\n {\n field: 'name',\n headerName: 'Member',\n flex: 1,\n minWidth: 220,\n renderCell: ({ row }) => (\n <Stack sx={{ minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap title={row.name}>\n {row.name}\n </Typography>\n {row.email && row.email !== row.name ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {row.email}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'credits',\n headerName: 'Credits',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'share',\n headerName: 'Share',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 90,\n valueFormatter: (value: number) => formatAiUsageShare(value),\n },\n {\n field: 'requests',\n headerName: 'Requests',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'refusals',\n headerName: 'Refusals',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'mostly',\n headerName: 'Mostly',\n flex: 1,\n minWidth: 160,\n sortable: false,\n valueGetter: (_value, row) =>\n topAiUsageKinds(row.byKind, 2)\n .map((entry) => entry.kind)\n .join(', ') || '—',\n },\n listActionsColumn((row: OrgAiUsageRowWire) => (\n <ListRowActions\n label={row.name}\n quick={{\n icon: mdiOpenInNew.path,\n label: 'View member',\n to: memberHref(row.uid),\n }}\n items={[]}\n />\n )),\n ],\n [memberHref],\n )\n\n return (\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Who is generating what, per site: each member's ${name} credits for the month and their share of the workspace's spend. Open a member for the split by site and by kind.`}\n </Typography>\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <TextField\n select\n size=\"small\"\n label=\"Month\"\n value={usage.month}\n onChange={(event) => setMonth(event.target.value)}\n sx={{ minWidth: 180 }}\n >\n {usage.months.map((key) => (\n <MenuItem key={key} value={key}>\n {aiUsageMonthLabel(key)}\n </MenuItem>\n ))}\n </TextField>\n <Button\n size=\"small\"\n disabled={exporting || usage.status !== 'ready' || rows.length === 0}\n onClick={() => void handleExport()}\n >\n {exporting ? 'Exporting…' : 'Export CSV'}\n </Button>\n {usage.data ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${usage.data.orgCredits.toLocaleString()} credits drawn by the workspace in ${aiUsageMonthLabel(usage.month)}`}\n </Typography>\n ) : null}\n </Stack>\n {usage.status === 'refused' ? (\n <Alert severity=\"info\">\n {'Seeing who used AI credits requires the View billing permission.'}\n </Alert>\n ) : usage.status === 'error' ? (\n <Alert severity=\"warning\">\n {'Could not read this month’s per-member usage — a failed read, not zero usage.'}\n </Alert>\n ) : usage.status === 'loading' && !usage.data ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : rows.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`No ${name} usage attributed to a member in ${aiUsageMonthLabel(usage.month)}.`}\n </Typography>\n ) : (\n <ListTable\n // Another month is another list: it opens on its first page.\n key={usage.month}\n rows={rows}\n columns={columns}\n getRowId={(row: OrgAiUsageRowWire) => row.uid}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(uid) => router.push(memberHref(uid))}\n // The month for every current member, computed whole by the route\n // from the roster the plan's seats bound: a readout, not a list to\n // filter — the grid would narrow only the rows it holds (AGL-3321).\n disableColumnFilter\n quickFilter={false}\n />\n )}\n </Stack>\n )\n}\nBillingAiTopUsersComponent.displayName = 'BillingAiTopUsersComponent'\n\n/**\n * The card on Billing → Usage, through the `orgBillingUsage` zone: its own\n * band beneath the meters, full width, because it is a table of people rather\n * than a gauge. Registered with `billing.view`, so a manager without it sees\n * no card rather than a refusal.\n */\nexport function AiTopUsersCard(props: { orgId?: string; org?: { slug?: string } | null }) {\n return (\n <div id=\"ai-usage-by-member\">\n <CardDisplay\n header={'Who is generating what'}\n help={pluginDocsHelp('billing', {\n anchor: '#who-is-generating-what',\n excerpt:\n 'Each member’s AI credits for a month, their share of the ' +\n 'workspace’s spend, requests and refusals — with a month picker ' +\n 'and a CSV export.',\n })}\n contentGutterX\n contentGutterY\n >\n <BillingAiTopUsersComponent orgId={props.orgId} orgSlug={props.org?.slug ?? ''} />\n </CardDisplay>\n </div>\n )\n}\nAiTopUsersCard.displayName = 'AiTopUsersCard'\n\nexport default BillingAiTopUsersComponent\n"],"names":["aiAddonName","countCsvDataRows","pluginDocsHelp","mdiOpenInNew","CardDisplay","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","useSnackbar","authorizedFetch","useUser","Alert","Button","MenuItem","Stack","TextField","Typography","buildRoute","Route","useRouter","useCallback","useMemo","useState","topAiUsageKinds","useOrgAiUsage","AI_USAGE_EXPORT_ROWS_HEADER","aiUsageMonthLabel","formatAiUsageShare","BillingAiTopUsersComponent","props","usage","orgId","orgSlug","data","user","enqueueSnackbar","month","setMonth","undefined","exporting","setExporting","router","name","handleExport","response","params","URLSearchParams","format","toString","ok","failure","json","catch","Error","error","text","promised","Number","headers","get","received","isFinite","variant","url","URL","createObjectURL","Blob","type","anchor","document","createElement","href","download","click","revokeObjectURL","message","rows","memberHref","uid","MANAGE_TEAM_MEMBER","columns","field","headerName","flex","minWidth","renderCell","row","sx","noWrap","title","email","color","align","headerAlign","width","valueFormatter","value","toLocaleString","sortable","valueGetter","_value","byKind","map","entry","kind","join","label","quick","icon","path","to","items","spacing","useFlexGap","direction","alignItems","flexWrap","rowGap","select","size","onChange","event","target","months","key","disabled","status","length","onClick","orgCredits","severity","getRowId","rowHeight","onOpen","push","disableColumnFilter","quickFilter","displayName","AiTopUsersCard","div","id","header","help","excerpt","contentGutterX","contentGutterY","org","slug"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/billing-ai-top-users.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 { aiAddonName, countCsvDataRows } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiOpenInNew } from '@aglyn/shared-data-mdi'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useMemo, useState } from 'react'\nimport { topAiUsageKinds } from '../model/ai-usage-by-user'\nimport { useOrgAiUsage } from './use-org-ai-usage'\nimport {\n AI_USAGE_EXPORT_ROWS_HEADER,\n aiUsageMonthLabel,\n formatAiUsageShare,\n type OrgAiUsageRowWire,\n} from '../usage/ai-usage-wire'\n\nexport interface BillingAiTopUsersProps {\n orgId: string | undefined\n /** The org's slug, for the links into each person's page. */\n orgSlug: string\n}\n\n/**\n * WHO IS GENERATING WHAT (AGL-2928) — the per-person table beneath the\n * credit meter on Billing → Usage.\n *\n * The meter above says how much of the pool the workspace has drawn; this\n * says who drew it. Each row is one member's month: credits, their share of\n * the workspace's spend, requests, and how often the gate refused them. A\n * month picker walks the retention window, and the file button hands the\n * same month to a spreadsheet through the streamed export.\n *\n * ## The file is checked, not trusted\n *\n * The route promises its row count in a header taken before the first row\n * is written. A stream that dies halfway yields a well-formed shorter file,\n * so a body with fewer rows than promised is refused rather than saved.\n *\n * The copy stays descriptive on purpose — who, how much, on which site — and\n * never characterizes what the numbers mean about a person.\n */\nexport function BillingAiTopUsersComponent(props: BillingAiTopUsersProps) {\n const { orgId, orgSlug } = props\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [month, setMonth] = useState<string | undefined>(undefined)\n const [exporting, setExporting] = useState(false)\n const usage = useOrgAiUsage(orgId, { month })\n const router = useRouter()\n const name = aiAddonName()\n\n const handleExport = useCallback(async () => {\n if (!orgId || exporting) return\n setExporting(true)\n try {\n const params = new URLSearchParams({ orgId, month: usage.month, format: 'csv' })\n const response = await authorizedFetch(\n user,\n `/api/ai/usage?${params.toString()}`,\n )\n if (!response.ok) {\n const failure = await response.json().catch(() => ({}))\n throw new Error(failure?.error ?? 'Export failed')\n }\n const text = await response.text()\n const promised = Number(response.headers.get(AI_USAGE_EXPORT_ROWS_HEADER) ?? '')\n const received = countCsvDataRows(text)\n if (Number.isFinite(promised) && received < promised) {\n enqueueSnackbar(\n `Export incomplete — ${received} of ${promised} rows arrived. ` +\n 'Nothing was saved; try again.',\n { variant: 'error' },\n )\n return\n }\n const url = URL.createObjectURL(new Blob([text], { type: 'text/csv' }))\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = `ai-usage-${usage.month}.csv`\n anchor.click()\n URL.revokeObjectURL(url)\n } catch (error) {\n enqueueSnackbar(error instanceof Error ? error.message : 'Export failed', {\n variant: 'error',\n })\n } finally {\n setExporting(false)\n }\n }, [orgId, exporting, usage.month, user, enqueueSnackbar])\n\n const rows = usage.data?.rows ?? []\n const memberHref = useCallback(\n (uid: string) => buildRoute(Route.MANAGE_TEAM_MEMBER, { orgSlug, uid }),\n [orgSlug],\n )\n\n /*\n * One row per member, and the whole month is in hand, so the grid pages\n * and sorts it itself. The row opens the member's page, which is\n * where the split by site and by kind lives.\n */\n const columns = useMemo<GridColDef<OrgAiUsageRowWire>[]>(\n () => [\n {\n field: 'name',\n headerName: 'Member',\n flex: 1,\n minWidth: 220,\n renderCell: ({ row }) => (\n <Stack sx={{ minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap title={row.name}>\n {row.name}\n </Typography>\n {row.email && row.email !== row.name ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {row.email}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'credits',\n headerName: 'Credits',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'share',\n headerName: 'Share',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 90,\n valueFormatter: (value: number) => formatAiUsageShare(value),\n },\n {\n field: 'requests',\n headerName: 'Requests',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'refusals',\n headerName: 'Refusals',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'mostly',\n headerName: 'Mostly',\n flex: 1,\n minWidth: 160,\n sortable: false,\n valueGetter: (_value, row) =>\n topAiUsageKinds(row.byKind, 2)\n .map((entry) => entry.kind)\n .join(', ') || '—',\n },\n listActionsColumn((row: OrgAiUsageRowWire) => (\n <ListRowActions\n label={row.name}\n quick={{\n icon: mdiOpenInNew.path,\n label: 'View member',\n newTab: true,\n to: memberHref(row.uid),\n }}\n items={[]}\n />\n )),\n ],\n [memberHref],\n )\n\n return (\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Who is generating what, per site: each member's ${name} credits for the month and their share of the workspace's spend. Open a member for the split by site and by kind.`}\n </Typography>\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <TextField\n select\n size=\"small\"\n label=\"Month\"\n value={usage.month}\n onChange={(event) => setMonth(event.target.value)}\n sx={{ minWidth: 180 }}\n >\n {usage.months.map((key) => (\n <MenuItem key={key} value={key}>\n {aiUsageMonthLabel(key)}\n </MenuItem>\n ))}\n </TextField>\n <Button\n size=\"small\"\n disabled={exporting || usage.status !== 'ready' || rows.length === 0}\n onClick={() => void handleExport()}\n >\n {exporting ? 'Exporting…' : 'Export CSV'}\n </Button>\n {usage.data ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${usage.data.orgCredits.toLocaleString()} credits drawn by the workspace in ${aiUsageMonthLabel(usage.month)}`}\n </Typography>\n ) : null}\n </Stack>\n {usage.status === 'refused' ? (\n <Alert severity=\"info\">\n {'Seeing who used AI credits requires the View billing permission.'}\n </Alert>\n ) : usage.status === 'error' ? (\n <Alert severity=\"warning\">\n {'Could not read this month’s per-member usage — a failed read, not zero usage.'}\n </Alert>\n ) : usage.status === 'loading' && !usage.data ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : rows.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`No ${name} usage attributed to a member in ${aiUsageMonthLabel(usage.month)}.`}\n </Typography>\n ) : (\n <ListTable\n // Another month is another list: it opens on its first page.\n key={usage.month}\n rows={rows}\n columns={columns}\n getRowId={(row: OrgAiUsageRowWire) => row.uid}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(uid) => router.push(memberHref(uid))}\n // The month for every current member, computed whole by the route\n // from the roster the plan's seats bound: a readout, not a list to\n // filter — the grid would narrow only the rows it holds (AGL-3321).\n disableColumnFilter\n quickFilter={false}\n />\n )}\n </Stack>\n )\n}\nBillingAiTopUsersComponent.displayName = 'BillingAiTopUsersComponent'\n\n/**\n * The card on Billing → Usage, through the `orgBillingUsage` zone: its own\n * band beneath the meters, full width, because it is a table of people rather\n * than a gauge. Registered with `billing.view`, so a manager without it sees\n * no card rather than a refusal.\n */\nexport function AiTopUsersCard(props: { orgId?: string; org?: { slug?: string } | null }) {\n return (\n <div id=\"ai-usage-by-member\">\n <CardDisplay\n header={'Who is generating what'}\n help={pluginDocsHelp('billing', {\n anchor: '#who-is-generating-what',\n excerpt:\n 'Each member’s AI credits for a month, their share of the ' +\n 'workspace’s spend, requests and refusals — with a month picker ' +\n 'and a CSV export.',\n })}\n contentGutterX\n contentGutterY\n >\n <BillingAiTopUsersComponent orgId={props.orgId} orgSlug={props.org?.slug ?? ''} />\n </CardDisplay>\n </div>\n )\n}\nAiTopUsersCard.displayName = 'AiTopUsersCard'\n\nexport default BillingAiTopUsersComponent\n"],"names":["aiAddonName","countCsvDataRows","pluginDocsHelp","mdiOpenInNew","CardDisplay","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","useSnackbar","authorizedFetch","useUser","Alert","Button","MenuItem","Stack","TextField","Typography","buildRoute","Route","useRouter","useCallback","useMemo","useState","topAiUsageKinds","useOrgAiUsage","AI_USAGE_EXPORT_ROWS_HEADER","aiUsageMonthLabel","formatAiUsageShare","BillingAiTopUsersComponent","props","usage","orgId","orgSlug","data","user","enqueueSnackbar","month","setMonth","undefined","exporting","setExporting","router","name","handleExport","response","params","URLSearchParams","format","toString","ok","failure","json","catch","Error","error","text","promised","Number","headers","get","received","isFinite","variant","url","URL","createObjectURL","Blob","type","anchor","document","createElement","href","download","click","revokeObjectURL","message","rows","memberHref","uid","MANAGE_TEAM_MEMBER","columns","field","headerName","flex","minWidth","renderCell","row","sx","noWrap","title","email","color","align","headerAlign","width","valueFormatter","value","toLocaleString","sortable","valueGetter","_value","byKind","map","entry","kind","join","label","quick","icon","path","newTab","to","items","spacing","useFlexGap","direction","alignItems","flexWrap","rowGap","select","size","onChange","event","target","months","key","disabled","status","length","onClick","orgCredits","severity","getRowId","rowHeight","onOpen","push","disableColumnFilter","quickFilter","displayName","AiTopUsersCard","div","id","header","help","excerpt","contentGutterX","contentGutterY","org","slug"],"mappings":"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"}
@@ -75,8 +75,8 @@ import { ASSIST_OVERAGE_CAP_CONTROL_LABEL, ASSIST_OVERAGE_CAP_MAX_USD, ASSIST_OV
75
75
  children: /*#__PURE__*/ _jsx(CardDisplay, {
76
76
  header: 'AI credits overage',
77
77
  subheader: 'Extra AI credits past your included band are billed on your ' + 'monthly invoice. Stop at the band, or stop once the overage ' + 'reaches an amount you choose.',
78
- help: pluginDocsHelp('billing', {
79
- anchor: '#assist-overage',
78
+ help: pluginDocsHelp('aiCredits', {
79
+ anchor: '#past-the-band',
80
80
  excerpt: 'On a paid plan the assistant keeps answering past your ' + 'included credits and the extra is billed at your plan\'s ' + 'per-1,000 rate, unless you switch on the stop at the included band.'
81
81
  }),
82
82
  contentGutterX: true,