@aglyn/plugins-ai 1.0.0-beta.198 → 1.0.0-beta.200
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.
- package/package.json +17 -17
- package/src/lib/components/ai-products-hub-card.component.js +3 -0
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-products-proposals.component.js +4 -0
- package/src/lib/components/ai-products-proposals.component.js.map +1 -1
- package/src/lib/components/ai-site-batch-card.component.js +5 -1
- package/src/lib/components/ai-site-batch-card.component.js.map +1 -1
- package/src/lib/components/billing-ai-allotments.component.d.ts +6 -0
- package/src/lib/components/billing-ai-allotments.component.js +17 -5
- package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
- package/src/lib/components/billing-ai-top-users.component.js +8 -3
- package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-card.component.js +12 -4
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
- package/src/lib/components/staff-user-ai-usage-card.component.js +7 -3
- package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.d.ts +3 -1
- package/src/lib/jobs/ai-job-drafts.js +23 -7
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-jobs.js +2 -1
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/server/ai-allotments.js +2 -1
- package/src/lib/server/ai-allotments.js.map +1 -1
- package/src/lib/server/ai-usage.js +2 -1
- package/src/lib/server/ai-usage.js.map +1 -1
- package/src/lib/server/billing-overage.js +3 -2
- package/src/lib/server/billing-overage.js.map +1 -1
- package/src/lib/usage/assist-free-taste.js +2 -1
- package/src/lib/usage/assist-free-taste.js.map +1 -1
|
@@ -88,6 +88,12 @@ const creditsColumn = (credits)=>({
|
|
|
88
88
|
*
|
|
89
89
|
* Read with `billing.view`; the controls appear with `billing.manage`, and
|
|
90
90
|
* the route refuses a write without it whatever this card draws.
|
|
91
|
+
*
|
|
92
|
+
* The three tables are the route's whole answer — the roster the plan's
|
|
93
|
+
* seats bound, the organization's sites, the collaborator allotments — so
|
|
94
|
+
* they offer no Filters panel and no search: a bounded readout is read
|
|
95
|
+
* whole, and a grid that narrowed the rows it holds is the pattern every
|
|
96
|
+
* list is leaving (AGL-3321).
|
|
91
97
|
*/ export function BillingAiAllotments({ orgId }) {
|
|
92
98
|
var _ref;
|
|
93
99
|
var _restriction_models;
|
|
@@ -144,8 +150,8 @@ const creditsColumn = (credits)=>({
|
|
|
144
150
|
const collaborators = data.allotments.filter((row)=>row.scope === 'collab');
|
|
145
151
|
const restriction = bySubject.get(AI_ALLOTMENT_ORG_SUBJECT);
|
|
146
152
|
/*
|
|
147
|
-
* Each list pages
|
|
148
|
-
*
|
|
153
|
+
* Each list pages and sorts on its own, and a selection is kept by member
|
|
154
|
+
* and site id rather than by row, so it survives a page change.
|
|
149
155
|
*/ const teamRows = team.map((member)=>({
|
|
150
156
|
$id: member.uid,
|
|
151
157
|
subject: `member:${member.uid}`,
|
|
@@ -330,7 +336,9 @@ const creditsColumn = (credits)=>({
|
|
|
330
336
|
rows: teamRows,
|
|
331
337
|
columns: teamColumns,
|
|
332
338
|
rowHeight: TABLE_ROW_HEIGHT,
|
|
333
|
-
noRowsLabel: "No team members"
|
|
339
|
+
noRowsLabel: "No team members",
|
|
340
|
+
disableColumnFilter: true,
|
|
341
|
+
quickFilter: false
|
|
334
342
|
}, canEdit ? {
|
|
335
343
|
selectable: {
|
|
336
344
|
selected: members,
|
|
@@ -382,7 +390,9 @@ const creditsColumn = (credits)=>({
|
|
|
382
390
|
rows: siteRows,
|
|
383
391
|
columns: siteColumns,
|
|
384
392
|
rowHeight: TABLE_ROW_HEIGHT,
|
|
385
|
-
noRowsLabel: "No sites"
|
|
393
|
+
noRowsLabel: "No sites",
|
|
394
|
+
disableColumnFilter: true,
|
|
395
|
+
quickFilter: false
|
|
386
396
|
}, canEdit ? {
|
|
387
397
|
selectable: {
|
|
388
398
|
selected: sites,
|
|
@@ -400,7 +410,9 @@ const creditsColumn = (credits)=>({
|
|
|
400
410
|
"aria-label": "Site collaborators' AI allotments",
|
|
401
411
|
rows: collaboratorRows,
|
|
402
412
|
columns: collaboratorColumns,
|
|
403
|
-
rowHeight: TABLE_ROW_HEIGHT
|
|
413
|
+
rowHeight: TABLE_ROW_HEIGHT,
|
|
414
|
+
disableColumnFilter: true,
|
|
415
|
+
quickFilter: false
|
|
404
416
|
}, canEdit ? {
|
|
405
417
|
onOpen: (_id, row)=>editCollaborator(row)
|
|
406
418
|
} : {}))
|
|
@@ -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 */\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, sorts and searches on its own, and a selection is kept\n * by member and site id rather than by row, so it survives a page change.\n */\n const teamRows: MemberRow[] = team.map((member) => ({\n $id: member.uid,\n subject: `member:${member.uid}`,\n allotment: bySubject.get(`member:${member.uid}`),\n member,\n }))\n const siteRows: SiteRow[] = data.hosts.map((host) => ({\n $id: host.hostId,\n subject: `host:${host.hostId}`,\n allotment: bySubject.get(`host:${host.hostId}`),\n host,\n }))\n const collaboratorRows: CollaboratorRow[] = collaborators.map((row) => ({\n $id: row.subject,\n subject: row.subject,\n allotment: row,\n name: nameOf(row.uid),\n site: siteName(row.hostId),\n }))\n\n const editSubjects = (subjects: string[], title: string, description: string) => {\n const rows = subjects.map((subject) => bySubject.get(subject))\n setError(null)\n setEditing({\n kind: 'subjects',\n subjects,\n title,\n description,\n initial: subjects.length === 1 ? valueOf(rows[0]) : null,\n removable: rows.every(Boolean),\n })\n }\n\n const run = async (body: Parameters<typeof saveAiAllotments>[1]) => {\n setBusy(true)\n setError(null)\n const result = await saveAiAllotments(user, body)\n setBusy(false)\n if (!result.ok) {\n setError(result.error)\n return\n }\n setEditing(null)\n setMembers([])\n setSites([])\n }\n const save = (value: AiAllotmentValue) => {\n if (!editing) return\n if (editing.kind === 'everySite') return void run({ orgId, everySite: value })\n if (editing.kind === 'restriction') {\n return void run(\n value.models\n ? { orgId, set: [{ subject: AI_ALLOTMENT_ORG_SUBJECT, ...value, credits: null }] }\n : { orgId, remove: [AI_ALLOTMENT_ORG_SUBJECT] },\n )\n }\n return void run({\n orgId,\n set: editing.subjects.map((subject) => ({ subject, ...value })),\n })\n }\n const remove = () => {\n if (!editing || editing.kind === 'everySite') return\n return void run({\n orgId,\n remove: editing.kind === 'restriction' ? [AI_ALLOTMENT_ORG_SUBJECT] : editing.subjects,\n })\n }\n const editMember = (row: MemberRow) =>\n editSubjects([row.subject], `AI allotment — ${row.member.name}`, 'Counts what this person draws across every site.')\n const editSite = (row: SiteRow) =>\n editSubjects([row.subject], `AI allotment — ${row.host.name}`, 'Counts what everyone draws on the site, together.')\n const editCollaborator = (row: CollaboratorRow) =>\n editSubjects(\n [row.subject],\n `AI allotment — ${row.name} on ${row.site}`,\n 'Counts what this collaborator draws on this site.',\n )\n /** The one action a row has: set its allotment, or edit the one it has. */\n const editColumn = <Row extends AllotmentListRow>(label: (row: Row) => string, edit: (row: Row) => void) =>\n listActionsColumn((row: Row) => (\n <ListRowActions\n label={label(row)}\n quick={{\n icon: mdiPencilOutline.path,\n label: row.allotment ? 'Edit allotment' : 'Set allotment',\n onClick: () => edit(row),\n }}\n items={[]}\n />\n ))\n\n const teamColumns: GridColDef<MemberRow>[] = [\n { field: 'name', headerName: 'Member', flex: 1, minWidth: 180, valueGetter: (_value, row) => row.member.name },\n creditsColumn<MemberRow>((row) => row.member.credits),\n allotmentColumn<MemberRow>(),\n ...(canEdit ? [editColumn<MemberRow>((row) => row.member.name, editMember)] : []),\n ]\n const siteColumns: GridColDef<SiteRow>[] = [\n { field: 'name', headerName: 'Site', flex: 1, minWidth: 180, valueGetter: (_value, row) => row.host.name },\n creditsColumn<SiteRow>((row) => row.host.credits),\n allotmentColumn<SiteRow>(),\n ...(canEdit ? [editColumn<SiteRow>((row) => row.host.name, editSite)] : []),\n ]\n const collaboratorColumns: GridColDef<CollaboratorRow>[] = [\n { field: 'name', headerName: 'Collaborator', flex: 1, minWidth: 180 },\n { field: 'site', headerName: 'Site', flex: 1, minWidth: 160 },\n allotmentColumn<CollaboratorRow>(),\n ...(canEdit ? [editColumn<CollaboratorRow>((row) => `${row.name} on ${row.site}`, editCollaborator)] : []),\n ]\n\n return (\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Give a member or a whole site a share of the workspace’s AI credits each month. ' +\n 'A hard allotment stops AI requests at the line; a soft one keeps going and alerts ' +\n 'you at 80% and 100%. The workspace’s own credits still stop first.'}\n </Typography>\n\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Team members'}\n </Typography>\n {canEdit ? (\n <Button\n size=\"small\"\n disabled={!members.length}\n onClick={() =>\n editSubjects(\n members.map((uid) => `member:${uid}`),\n members.length === 1 ? `AI allotment — ${nameOf(members[0])}` : `AI allotment — ${members.length} members`,\n 'Counts what each person draws across every site.',\n )\n }\n >\n {members.length > 1 ? `Set for ${members.length} selected` : 'Set for selected'}\n </Button>\n ) : null}\n </Stack>\n <ListTable\n aria-label=\"Team members' AI allotments\"\n rows={teamRows}\n columns={teamColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No team members\"\n {...(canEdit\n ? { selectable: { selected: members, onChange: setMembers }, onOpen: (_id: string, row: MemberRow) => editMember(row) }\n : {})}\n />\n\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Sites'}\n </Typography>\n {canEdit ? (\n <>\n <Button\n size=\"small\"\n disabled={!sites.length}\n onClick={() =>\n editSubjects(\n sites.map((hostId) => `host:${hostId}`),\n sites.length === 1 ? `AI allotment — ${siteName(sites[0])}` : `AI allotment — ${sites.length} sites`,\n 'Counts what everyone draws on the site, together.',\n )\n }\n >\n {sites.length > 1 ? `Set for ${sites.length} selected` : 'Set for selected'}\n </Button>\n <Button\n size=\"small\"\n disabled={!data.hosts.length}\n onClick={() => {\n setError(null)\n setEditing({ kind: 'everySite' })\n }}\n >\n {'Same for every client site'}\n </Button>\n </>\n ) : null}\n </Stack>\n <ListTable\n aria-label=\"Sites' AI allotments\"\n rows={siteRows}\n columns={siteColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No sites\"\n {...(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 {...(canEdit ? { onOpen: (_id: string, row: CollaboratorRow) => editCollaborator(row) } : {})}\n />\n </>\n ) : null}\n\n {data.restrictionAvailable ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"body2\" sx={{ flexGrow: 1 }}>\n {'Models for the whole organization: '}\n {restriction?.models?.length\n ? data.models\n .filter((model) => restriction.models?.includes(model.id))\n .map((model) => model.label)\n .join(', ') || `${restriction.models.length} models`\n : 'every model the plan offers'}\n </Typography>\n {canEdit ? (\n <Button\n size=\"small\"\n onClick={() => {\n setError(null)\n setEditing({ kind: 'restriction', initial: valueOf(restriction), removable: Boolean(restriction) })\n }}\n >\n {'Restrict models'}\n </Button>\n ) : null}\n </Stack>\n ) : null}\n\n <AiAllotmentEditor\n open={editing !== null}\n title={\n editing?.kind === 'everySite'\n ? 'Same allotment for every site'\n : editing?.kind === 'restriction'\n ? 'Models for the whole organization'\n : (editing?.title ?? '')\n }\n description={\n editing?.kind === 'everySite'\n ? `Sets this allotment on all ${data.hosts.length} sites, replacing any each one had.`\n : editing?.kind === 'restriction'\n ? 'Every member, collaborator and Auto choose only among these models.'\n : editing?.description\n }\n initial={editing && editing.kind !== 'everySite' ? editing.initial : null}\n models={data.models}\n modelsOnly={editing?.kind === 'restriction'}\n poolLimit={data.pool.limit}\n busy={busy}\n error={error}\n onClose={() => setEditing(null)}\n onSave={save}\n onRemove={editing && editing.kind !== 'everySite' && editing.removable ? remove : undefined}\n />\n </Stack>\n )\n}\nBillingAiAllotments.displayName = 'BillingAiAllotments'\n\n/**\n * The card on Billing → Usage, through the `orgBillingUsage` zone, beneath\n * the table of who drew the credits. Registered with `billing.view`.\n */\nexport function AiAllotmentsCard(props: { orgId?: string }) {\n return (\n <div id=\"ai-allotments\">\n <CardDisplay\n header={'AI allotments'}\n help={pluginDocsHelp('billing', {\n anchor: '#ai-allotments',\n excerpt:\n 'A monthly share of the workspace’s AI credits for a member or a site — ' +\n 'hard or soft, with an optional list of models.',\n })}\n contentGutterX\n contentGutterY\n >\n <BillingAiAllotments orgId={props.orgId} />\n </CardDisplay>\n </div>\n )\n}\nAiAllotmentsCard.displayName = 'AiAllotmentsCard'\n\nexport default AiAllotmentsCard\n"],"names":["pluginDocsHelp","mdiPencilOutline","CardDisplay","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","useUser","Alert","Button","Stack","Typography","useMemo","useState","AI_ALLOTMENT_ORG_SUBJECT","aiAllotmentState","AiAllotmentEditor","saveAiAllotments","useAiAllotments","aiAllotmentSummary","row","parts","credits","push","used","toLocaleString","mode","models","length","join","aiAllotmentTone","state","valueOf","allotmentColumn","field","headerName","flex","minWidth","sortable","valueGetter","_value","allotment","renderCell","variant","noWrap","sx","color","creditsColumn","type","align","headerAlign","width","valueFormatter","value","BillingAiAllotments","orgId","restriction","data","user","allotments","members","setMembers","sites","setSites","editing","setEditing","busy","setBusy","error","setError","bySubject","Map","map","subject","status","severity","canEdit","billing","team","filter","member","orgWide","nameOf","uid","find","name","siteName","hostId","hosts","host","collaborators","scope","get","teamRows","$id","siteRows","collaboratorRows","site","editSubjects","subjects","title","description","rows","kind","initial","removable","every","Boolean","run","body","result","ok","save","everySite","set","remove","editMember","editSite","editCollaborator","editColumn","label","edit","quick","icon","path","onClick","items","teamColumns","siteColumns","collaboratorColumns","spacing","direction","alignItems","flexWrap","rowGap","flexGrow","size","disabled","aria-label","columns","rowHeight","noRowsLabel","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":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SAASA,cAAc,QAAQ,mCAAkC;AACjE,SAASC,gBAAgB,QAAQ,yBAAwB;AACzD,SAASC,WAAW,QAAQ,uBAAsB;AAClD,SACEC,cAAc,EACdC,SAAS,EACTC,iBAAiB,QACZ,uDAAsD;AAC7D,SAASC,gBAAgB,QAAQ,8CAA6C;AAC9E,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SAASC,KAAK,EAAEC,MAAM,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AAEhE,SAASC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACzC,SAASC,wBAAwB,EAAEC,gBAAgB,QAAQ,4BAAwB;AAMnF,SAASC,iBAAiB,QAA+B,qCAAiC;AAC1F,SAASC,gBAAgB,EAAEC,eAAe,QAAQ,yBAAqB;AAEvE,0EAA0E,GAC1E,OAAO,SAASC,mBACdC,GAAwF;QAOpFA;IALJ,IAAI,CAACA,KAAK,OAAO;IACjB,MAAMC,QAAkB,EAAE;IAC1B,IAAID,IAAIE,OAAO,KAAK,MAAM;QACxBD,MAAME,IAAI,CAAC,GAAGH,IAAII,IAAI,CAACC,cAAc,GAAG,IAAI,EAAEL,IAAIE,OAAO,CAACG,cAAc,GAAG,EAAE,EAAEL,IAAIM,IAAI,CAAC,CAAC,CAAC;IAC5F;IACA,KAAIN,cAAAA,IAAIO,MAAM,qBAAVP,YAAYQ,MAAM,EAAE;QACtBP,MAAME,IAAI,CAAC,GAAGH,IAAIO,MAAM,CAACC,MAAM,CAAC,MAAM,EAAER,IAAIO,MAAM,CAACC,MAAM,KAAK,IAAI,KAAK,KAAK;IAC9E;IACA,OAAOP,MAAMQ,IAAI,CAAC,UAAU;AAC9B;AAEA,6EAA6E,GAC7E,OAAO,SAASC,gBACdV,GAAoE;IAEpE,IAAI,CAACA,OAAOA,IAAIE,OAAO,KAAK,MAAM,OAAO;IACzC,MAAMS,QAAQhB,iBAAiBK,IAAII,IAAI,EAAEJ,IAAIE,OAAO;IACpD,OAAOS,UAAU,YAAY,eAAeA,UAAU,SAAS,iBAAiB;AAClF;AAcA,MAAMC,UAAU,CAACZ,MACfA,MAAM;QAAEE,SAASF,IAAIE,OAAO;QAAEI,MAAMN,IAAIM,IAAI;QAAEC,QAAQP,IAAIO,MAAM;IAAC,IAAI;AAiBvE,8EAA8E,GAC9E,MAAMM,kBAAkB,IAAsD,CAAA;QAC5EC,OAAO;QACPC,YAAY;QACZC,MAAM;QACNC,UAAU;QACVC,UAAU;QACVC,aAAa,CAACC,QAAQpB,MAAQD,mBAAmBC,IAAIqB,SAAS;QAC9DC,YAAY,CAAC,EAAEtB,GAAG,EAAE,iBAClB,KAACT;gBAAWgC,SAAQ;gBAAQC,MAAM;gBAACC,IAAI;oBAAEC,OAAOhB,gBAAgBV,IAAIqB,SAAS;gBAAE;0BAC5EtB,mBAAmBC,IAAIqB,SAAS;;IAGvC,CAAA;AAEA,MAAMM,gBAAgB,CACpBzB,UACqB,CAAA;QACrBY,OAAO;QACPC,YAAY;QACZa,MAAM;QACNC,OAAO;QACPC,aAAa;QACbC,OAAO;QACPZ,aAAa,CAACC,QAAQpB,MAAQE,QAAQF;QACtCgC,gBAAgB,CAACC,QAAkBA,MAAM5B,cAAc;IACzD,CAAA;AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAAS6B,oBAAoB,EAAEC,KAAK,EAAsB;;QAgQpDC;IA/PX,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAGnD;IACvB,MAAMoD,aAAazC,gBAAgB;QAAEqC;IAAM;IAC3C,MAAM,CAACK,SAASC,WAAW,GAAGhD,SAAmB,EAAE;IACnD,MAAM,CAACiD,OAAOC,SAAS,GAAGlD,SAAmB,EAAE;IAC/C,MAAM,CAACmD,SAASC,WAAW,GAAGpD,SAAyB;IACvD,MAAM,CAACqD,MAAMC,QAAQ,GAAGtD,SAAS;IACjC,MAAM,CAACuD,OAAOC,SAAS,GAAGxD,SAAwB;IAClD,MAAM4C,OAAOE,WAAWF,IAAI;IAC5B,MAAMa,YAAY1D,QAChB;;eAAM,IAAI2D,IAAI,SAACd,wBAAAA,KAAME,UAAU,mBAAI,EAAE,EAAEa,GAAG,CAAC,CAACpD,MAAQ;gBAACA,IAAIqD,OAAO;gBAAErD;aAAI;OACtE;QAACqC;KAAK;IAGR,IAAIE,WAAWe,MAAM,KAAK,WAAW;QACnC,qBACE,KAAClE;YAAMmE,UAAS;sBAAQ;;IAE5B;IACA,IAAIhB,WAAWe,MAAM,KAAK,WAAW,CAACjB,MAAM;QAC1C,qBACE,KAACjD;YAAMmE,UAAS;sBACb;;IAGP;IACA,IAAI,CAAClB,QAAQ,CAACF,OAAO;QACnB,qBACE,KAAC5C;YAAWgC,SAAQ;YAAQG,OAAM;sBAC/B;;IAGP;IAEA,MAAM8B,UAAUnB,KAAKmB,OAAO,CAACC,OAAO;IACpC,MAAMC,OAAOrB,KAAKG,OAAO,CAACmB,MAAM,CAAC,CAACC,SAAWA,OAAOC,OAAO;IAC3D,MAAMC,SAAS,CAACC;YACd1B;YAAAA;gBAAAA,iBAAAA,qBAAAA,KAAKG,OAAO,CAACwB,IAAI,CAAC,CAACJ,SAAWA,OAAOG,GAAG,KAAKA,yBAA7C1B,mBAAmD4B,IAAI,oBAAIF,eAA3D1B,OAAkE;;IACpE,MAAM6B,WAAW,CAACC;YAChB9B;YAAAA;gBAAAA,iBAAAA,mBAAAA,KAAK+B,KAAK,CAACJ,IAAI,CAAC,CAACK,OAASA,KAAKF,MAAM,KAAKA,4BAA1C9B,iBAAmD4B,IAAI,oBAAIE,kBAA3D9B,OAAqE;;IACvE,MAAMiC,gBAAgBjC,KAAKE,UAAU,CAACoB,MAAM,CAAC,CAAC3D,MAAQA,IAAIuE,KAAK,KAAK;IACpE,MAAMnC,cAAcc,UAAUsB,GAAG,CAAC9E;IAClC;;;GAGC,GACD,MAAM+E,WAAwBf,KAAKN,GAAG,CAAC,CAACQ,SAAY,CAAA;YAClDc,KAAKd,OAAOG,GAAG;YACfV,SAAS,CAAC,OAAO,EAAEO,OAAOG,GAAG,EAAE;YAC/B1C,WAAW6B,UAAUsB,GAAG,CAAC,CAAC,OAAO,EAAEZ,OAAOG,GAAG,EAAE;YAC/CH;QACF,CAAA;IACA,MAAMe,WAAsBtC,KAAK+B,KAAK,CAAChB,GAAG,CAAC,CAACiB,OAAU,CAAA;YACpDK,KAAKL,KAAKF,MAAM;YAChBd,SAAS,CAAC,KAAK,EAAEgB,KAAKF,MAAM,EAAE;YAC9B9C,WAAW6B,UAAUsB,GAAG,CAAC,CAAC,KAAK,EAAEH,KAAKF,MAAM,EAAE;YAC9CE;QACF,CAAA;IACA,MAAMO,mBAAsCN,cAAclB,GAAG,CAAC,CAACpD,MAAS,CAAA;YACtE0E,KAAK1E,IAAIqD,OAAO;YAChBA,SAASrD,IAAIqD,OAAO;YACpBhC,WAAWrB;YACXiE,MAAMH,OAAO9D,IAAI+D,GAAG;YACpBc,MAAMX,SAASlE,IAAImE,MAAM;QAC3B,CAAA;IAEA,MAAMW,eAAe,CAACC,UAAoBC,OAAeC;QACvD,MAAMC,OAAOH,SAAS3B,GAAG,CAAC,CAACC,UAAYH,UAAUsB,GAAG,CAACnB;QACrDJ,SAAS;QACTJ,WAAW;YACTsC,MAAM;YACNJ;YACAC;YACAC;YACAG,SAASL,SAASvE,MAAM,KAAK,IAAII,QAAQsE,IAAI,CAAC,EAAE,IAAI;YACpDG,WAAWH,KAAKI,KAAK,CAACC;QACxB;IACF;IAEA,MAAMC,MAAM,OAAOC;QACjB1C,QAAQ;QACRE,SAAS;QACT,MAAMyC,SAAS,MAAM7F,iBAAiByC,MAAMmD;QAC5C1C,QAAQ;QACR,IAAI,CAAC2C,OAAOC,EAAE,EAAE;YACd1C,SAASyC,OAAO1C,KAAK;YACrB;QACF;QACAH,WAAW;QACXJ,WAAW,EAAE;QACbE,SAAS,EAAE;IACb;IACA,MAAMiD,OAAO,CAAC3D;QACZ,IAAI,CAACW,SAAS;QACd,IAAIA,QAAQuC,IAAI,KAAK,aAAa,OAAO,KAAKK,IAAI;YAAErD;YAAO0D,WAAW5D;QAAM;QAC5E,IAAIW,QAAQuC,IAAI,KAAK,eAAe;YAClC,OAAO,KAAKK,IACVvD,MAAM1B,MAAM,GACR;gBAAE4B;gBAAO2D,KAAK;oBAAC;wBAAEzC,SAAS3D;uBAA6BuC;wBAAO/B,SAAS;;iBAAO;YAAC,IAC/E;gBAAEiC;gBAAO4D,QAAQ;oBAACrG;iBAAyB;YAAC;QAEpD;QACA,OAAO,KAAK8F,IAAI;YACdrD;YACA2D,KAAKlD,QAAQmC,QAAQ,CAAC3B,GAAG,CAAC,CAACC,UAAa;oBAAEA;mBAAYpB;QACxD;IACF;IACA,MAAM8D,SAAS;QACb,IAAI,CAACnD,WAAWA,QAAQuC,IAAI,KAAK,aAAa;QAC9C,OAAO,KAAKK,IAAI;YACdrD;YACA4D,QAAQnD,QAAQuC,IAAI,KAAK,gBAAgB;gBAACzF;aAAyB,GAAGkD,QAAQmC,QAAQ;QACxF;IACF;IACA,MAAMiB,aAAa,CAAChG,MAClB8E,aAAa;YAAC9E,IAAIqD,OAAO;SAAC,EAAE,CAAC,eAAe,EAAErD,IAAI4D,MAAM,CAACK,IAAI,EAAE,EAAE;IACnE,MAAMgC,WAAW,CAACjG,MAChB8E,aAAa;YAAC9E,IAAIqD,OAAO;SAAC,EAAE,CAAC,eAAe,EAAErD,IAAIqE,IAAI,CAACJ,IAAI,EAAE,EAAE;IACjE,MAAMiC,mBAAmB,CAAClG,MACxB8E,aACE;YAAC9E,IAAIqD,OAAO;SAAC,EACb,CAAC,eAAe,EAAErD,IAAIiE,IAAI,CAAC,IAAI,EAAEjE,IAAI6E,IAAI,EAAE,EAC3C;IAEJ,yEAAyE,GACzE,MAAMsB,aAAa,CAA+BC,OAA6BC,OAC7EpH,kBAAkB,CAACe,oBACjB,KAACjB;gBACCqH,OAAOA,MAAMpG;gBACbsG,OAAO;oBACLC,MAAM1H,iBAAiB2H,IAAI;oBAC3BJ,OAAOpG,IAAIqB,SAAS,GAAG,mBAAmB;oBAC1CoF,SAAS,IAAMJ,KAAKrG;gBACtB;gBACA0G,OAAO,EAAE;;IAIf,MAAMC,cAAuC;QAC3C;YAAE7F,OAAO;YAAQC,YAAY;YAAUC,MAAM;YAAGC,UAAU;YAAKE,aAAa,CAACC,QAAQpB,MAAQA,IAAI4D,MAAM,CAACK,IAAI;QAAC;QAC7GtC,cAAyB,CAAC3B,MAAQA,IAAI4D,MAAM,CAAC1D,OAAO;QACpDW;WACI2C,UAAU;YAAC2C,WAAsB,CAACnG,MAAQA,IAAI4D,MAAM,CAACK,IAAI,EAAE+B;SAAY,GAAG,EAAE;KACjF;IACD,MAAMY,cAAqC;QACzC;YAAE9F,OAAO;YAAQC,YAAY;YAAQC,MAAM;YAAGC,UAAU;YAAKE,aAAa,CAACC,QAAQpB,MAAQA,IAAIqE,IAAI,CAACJ,IAAI;QAAC;QACzGtC,cAAuB,CAAC3B,MAAQA,IAAIqE,IAAI,CAACnE,OAAO;QAChDW;WACI2C,UAAU;YAAC2C,WAAoB,CAACnG,MAAQA,IAAIqE,IAAI,CAACJ,IAAI,EAAEgC;SAAU,GAAG,EAAE;KAC3E;IACD,MAAMY,sBAAqD;QACzD;YAAE/F,OAAO;YAAQC,YAAY;YAAgBC,MAAM;YAAGC,UAAU;QAAI;QACpE;YAAEH,OAAO;YAAQC,YAAY;YAAQC,MAAM;YAAGC,UAAU;QAAI;QAC5DJ;WACI2C,UAAU;YAAC2C,WAA4B,CAACnG,MAAQ,GAAGA,IAAIiE,IAAI,CAAC,IAAI,EAAEjE,IAAI6E,IAAI,EAAE,EAAEqB;SAAkB,GAAG,EAAE;KAC1G;IAED,qBACE,MAAC5G;QAAMwH,SAAS;;0BACd,KAACvH;gBAAWgC,SAAQ;gBAAQG,OAAM;0BAC/B,qFACC,uFACA;;0BAGJ,MAACpC;gBAAMyH,WAAU;gBAAMD,SAAS;gBAAGrF,IAAI;oBAAEuF,YAAY;oBAAUC,UAAU;oBAAQC,QAAQ;gBAAE;;kCACzF,KAAC3H;wBAAWgC,SAAQ;wBAAYE,IAAI;4BAAE0F,UAAU;wBAAE;kCAC/C;;oBAEF3D,wBACC,KAACnE;wBACC+H,MAAK;wBACLC,UAAU,CAAC7E,QAAQhC,MAAM;wBACzBiG,SAAS,IACP3B,aACEtC,QAAQY,GAAG,CAAC,CAACW,MAAQ,CAAC,OAAO,EAAEA,KAAK,GACpCvB,QAAQhC,MAAM,KAAK,IAAI,CAAC,eAAe,EAAEsD,OAAOtB,OAAO,CAAC,EAAE,GAAG,GAAG,CAAC,eAAe,EAAEA,QAAQhC,MAAM,CAAC,QAAQ,CAAC,EAC1G;kCAIHgC,QAAQhC,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAEgC,QAAQhC,MAAM,CAAC,SAAS,CAAC,GAAG;yBAE7D;;;0BAEN,KAACxB;gBACCsI,cAAW;gBACXpC,MAAMT;gBACN8C,SAASZ;gBACTa,WAAWtI;gBACXuI,aAAY;eACPjE,UACD;gBAAEkE,YAAY;oBAAEC,UAAUnF;oBAASoF,UAAUnF;gBAAW;gBAAGoF,QAAQ,CAACC,KAAa9H,MAAmBgG,WAAWhG;YAAK,IACpH,CAAC;0BAGP,MAACV;gBAAMyH,WAAU;gBAAMD,SAAS;gBAAGrF,IAAI;oBAAEuF,YAAY;oBAAUC,UAAU;oBAAQC,QAAQ;gBAAE;;kCACzF,KAAC3H;wBAAWgC,SAAQ;wBAAYE,IAAI;4BAAE0F,UAAU;wBAAE;kCAC/C;;oBAEF3D,wBACC;;0CACE,KAACnE;gCACC+H,MAAK;gCACLC,UAAU,CAAC3E,MAAMlC,MAAM;gCACvBiG,SAAS,IACP3B,aACEpC,MAAMU,GAAG,CAAC,CAACe,SAAW,CAAC,KAAK,EAAEA,QAAQ,GACtCzB,MAAMlC,MAAM,KAAK,IAAI,CAAC,eAAe,EAAE0D,SAASxB,KAAK,CAAC,EAAE,GAAG,GAAG,CAAC,eAAe,EAAEA,MAAMlC,MAAM,CAAC,MAAM,CAAC,EACpG;0CAIHkC,MAAMlC,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAEkC,MAAMlC,MAAM,CAAC,SAAS,CAAC,GAAG;;0CAE3D,KAACnB;gCACC+H,MAAK;gCACLC,UAAU,CAAChF,KAAK+B,KAAK,CAAC5D,MAAM;gCAC5BiG,SAAS;oCACPxD,SAAS;oCACTJ,WAAW;wCAAEsC,MAAM;oCAAY;gCACjC;0CAEC;;;yBAGH;;;0BAEN,KAACnG;gBACCsI,cAAW;gBACXpC,MAAMP;gBACN4C,SAASX;gBACTY,WAAWtI;gBACXuI,aAAY;eACPjE,UACD;gBAAEkE,YAAY;oBAAEC,UAAUjF;oBAAOkF,UAAUjF;gBAAS;gBAAGkF,QAAQ,CAACC,KAAa9H,MAAiBiG,SAASjG;YAAK,IAC5G,CAAC;YAGNsE,cAAc9D,MAAM,iBACnB;;kCACE,KAACjB;wBAAWgC,SAAQ;kCAAa;;kCACjC,KAACvC;wBACCsI,cAAW;wBACXpC,MAAMN;wBACN2C,SAASV;wBACTW,WAAWtI;uBACNsE,UAAU;wBAAEqE,QAAQ,CAACC,KAAa9H,MAAyBkG,iBAAiBlG;oBAAK,IAAI,CAAC;;iBAG7F;YAEHqC,KAAK0F,oBAAoB,iBACxB,MAACzI;gBAAMyH,WAAU;gBAAMD,SAAS;gBAAGrF,IAAI;oBAAEuF,YAAY;oBAAUC,UAAU;oBAAQC,QAAQ;gBAAE;;kCACzF,MAAC3H;wBAAWgC,SAAQ;wBAAQE,IAAI;4BAAE0F,UAAU;wBAAE;;4BAC3C;4BACA/E,CAAAA,gCAAAA,sBAAAA,YAAa7B,MAAM,qBAAnB6B,oBAAqB5B,MAAM,IACxB6B,KAAK9B,MAAM,CACRoD,MAAM,CAAC,CAACqE;oCAAU5F;wCAAAA,sBAAAA,YAAY7B,MAAM,qBAAlB6B,oBAAoB6F,QAAQ,CAACD,MAAME,EAAE;+BACvD9E,GAAG,CAAC,CAAC4E,QAAUA,MAAM5B,KAAK,EAC1B3F,IAAI,CAAC,SAAS,GAAG2B,YAAY7B,MAAM,CAACC,MAAM,CAAC,OAAO,CAAC,GACtD;;;oBAELgD,wBACC,KAACnE;wBACC+H,MAAK;wBACLX,SAAS;4BACPxD,SAAS;4BACTJ,WAAW;gCAAEsC,MAAM;gCAAeC,SAASxE,QAAQwB;gCAAciD,WAAWE,QAAQnD;4BAAa;wBACnG;kCAEC;yBAED;;iBAEJ;0BAEJ,KAACxC;gBACCuI,MAAMvF,YAAY;gBAClBoC,OACEpC,CAAAA,2BAAAA,QAASuC,IAAI,MAAK,cACd,kCACAvC,CAAAA,2BAAAA,QAASuC,IAAI,MAAK,gBAChB,8CACCvC,2BAAAA,QAASoC,KAAK,mBAAI;gBAE3BC,aACErC,CAAAA,2BAAAA,QAASuC,IAAI,MAAK,cACd,CAAC,2BAA2B,EAAE9C,KAAK+B,KAAK,CAAC5D,MAAM,CAAC,mCAAmC,CAAC,GACpFoC,CAAAA,2BAAAA,QAASuC,IAAI,MAAK,gBAChB,wEACAvC,2BAAAA,QAASqC,WAAW;gBAE5BG,SAASxC,WAAWA,QAAQuC,IAAI,KAAK,cAAcvC,QAAQwC,OAAO,GAAG;gBACrE7E,QAAQ8B,KAAK9B,MAAM;gBACnB6H,YAAYxF,CAAAA,2BAAAA,QAASuC,IAAI,MAAK;gBAC9BkD,WAAWhG,KAAKiG,IAAI,CAACC,KAAK;gBAC1BzF,MAAMA;gBACNE,OAAOA;gBACPwF,SAAS,IAAM3F,WAAW;gBAC1B4F,QAAQ7C;gBACR8C,UAAU9F,WAAWA,QAAQuC,IAAI,KAAK,eAAevC,QAAQyC,SAAS,GAAGU,SAAS4C;;;;AAI1F;AACAzG,oBAAoB0G,WAAW,GAAG;AAElC;;;CAGC,GACD,OAAO,SAASC,iBAAiBC,KAAyB;IACxD,qBACE,KAACC;QAAIb,IAAG;kBACN,cAAA,KAACpJ;YACCkK,QAAQ;YACRC,MAAMrK,eAAe,WAAW;gBAC9BsK,QAAQ;gBACRC,SACE,4EACA;YACJ;YACAC,cAAc;YACdC,cAAc;sBAEd,cAAA,KAACnH;gBAAoBC,OAAO2G,MAAM3G,KAAK;;;;AAI/C;AACA0G,iBAAiBD,WAAW,GAAG;AAE/B,eAAeC,iBAAgB"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/billing-ai-allotments.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiPencilOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Button, Stack, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useMemo, useState } from 'react'\nimport { AI_ALLOTMENT_ORG_SUBJECT, aiAllotmentState } from '../model/ai-allotments'\nimport type {\n AiAllotmentRowWire,\n AiAllotmentsHostWire,\n AiAllotmentsMemberWire,\n} from '../usage/ai-usage-wire'\nimport { AiAllotmentEditor, type AiAllotmentValue } from './ai-allotment-editor.component'\nimport { saveAiAllotments, useAiAllotments } from './use-ai-allotments'\n\n/** An allotment as a table cell reads it: used of credits, mode, models. */\nexport function aiAllotmentSummary(\n row: Pick<AiAllotmentRowWire, 'credits' | 'mode' | 'models' | 'used'> | null | undefined,\n): string {\n if (!row) return '—'\n const parts: string[] = []\n if (row.credits !== null) {\n parts.push(`${row.used.toLocaleString()} of ${row.credits.toLocaleString()} (${row.mode})`)\n }\n if (row.models?.length) {\n parts.push(`${row.models.length} model${row.models.length === 1 ? '' : 's'}`)\n }\n return parts.join(' · ') || '—'\n}\n\n/** The color an allotment's figure reads in: warning from 80%, error at it. */\nexport function aiAllotmentTone(\n row: Pick<AiAllotmentRowWire, 'credits' | 'used'> | null | undefined,\n): 'text.primary' | 'warning.main' | 'error.main' {\n if (!row || row.credits === null) return 'text.primary'\n const state = aiAllotmentState(row.used, row.credits)\n return state === 'reached' ? 'error.main' : state === 'warn' ? 'warning.main' : 'text.primary'\n}\n\ntype Editing =\n | {\n kind: 'subjects'\n subjects: string[]\n title: string\n description: string\n initial: AiAllotmentValue | null\n removable: boolean\n }\n | { kind: 'everySite' }\n | { kind: 'restriction'; initial: AiAllotmentValue | null; removable: boolean }\n\nconst valueOf = (row: AiAllotmentRowWire | undefined): AiAllotmentValue | null =>\n row ? { credits: row.credits, mode: row.mode, models: row.models } : null\n\n/**\n * A row of one of the three lists: the subject its allotment is stored under,\n * that subject's allotment when it has one, and `$id` — the id `ListTable`\n * selects by, which is the member's uid, the site's id, or the collaborator's\n * subject.\n */\ntype AllotmentListRow = {\n $id: string\n subject: string\n allotment: AiAllotmentRowWire | undefined\n}\ntype MemberRow = AllotmentListRow & { member: AiAllotmentsMemberWire }\ntype SiteRow = AllotmentListRow & { host: AiAllotmentsHostWire }\ntype CollaboratorRow = AllotmentListRow & { name: string; site: string }\n\n/** The allotment column: used of credits, in the tone its share has reached. */\nconst allotmentColumn = <Row extends AllotmentListRow>(): GridColDef<Row> => ({\n field: 'allotment',\n headerName: 'Allotment',\n flex: 1,\n minWidth: 200,\n sortable: false,\n valueGetter: (_value, row) => aiAllotmentSummary(row.allotment),\n renderCell: ({ row }) => (\n <Typography variant=\"body2\" noWrap sx={{ color: aiAllotmentTone(row.allotment) }}>\n {aiAllotmentSummary(row.allotment)}\n </Typography>\n ),\n})\n\nconst creditsColumn = <Row extends AllotmentListRow>(\n credits: (row: Row) => number,\n): GridColDef<Row> => ({\n field: 'credits',\n headerName: 'Credits this month',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 170,\n valueGetter: (_value, row) => credits(row),\n valueFormatter: (value: number) => value.toLocaleString(),\n})\n\n/**\n * AI ALLOTMENTS ON BILLING → USAGE (AGL-2942): who may draw how much of the\n * workspace's AI pool.\n *\n * Team members and sites, each with this month's credits and its allotment;\n * select several and set one allotment for all of them, or set the same\n * allotment on every site at once — the agency's \"same for every client\n * site\". Collaborators' allotments, which their sites' Users cards set, are\n * listed below. On a plan that offers it, the organization's model\n * restriction sits at the end.\n *\n * Read with `billing.view`; the controls appear with `billing.manage`, and\n * the route refuses a write without it whatever this card draws.\n *\n * The three tables are the route's whole answer — the roster the plan's\n * seats bound, the organization's sites, the collaborator allotments — so\n * they offer no Filters panel and no search: a bounded readout is read\n * whole, and a grid that narrowed the rows it holds is the pattern every\n * list is leaving (AGL-3321).\n */\nexport function BillingAiAllotments({ orgId }: { orgId?: string }) {\n const { data: user } = useUser()\n const allotments = useAiAllotments({ orgId })\n const [members, setMembers] = useState<string[]>([])\n const [sites, setSites] = useState<string[]>([])\n const [editing, setEditing] = useState<Editing | null>(null)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const data = allotments.data\n const bySubject = useMemo(\n () => new Map((data?.allotments ?? []).map((row) => [row.subject, row])),\n [data],\n )\n\n if (allotments.status === 'refused') {\n return (\n <Alert severity=\"info\">{'Seeing AI allotments requires the View billing permission.'}</Alert>\n )\n }\n if (allotments.status === 'error' && !data) {\n return (\n <Alert severity=\"warning\">\n {'Could not read the AI allotments — a failed read, not an empty list.'}\n </Alert>\n )\n }\n if (!data || !orgId) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n )\n }\n\n const canEdit = data.canEdit.billing\n const team = data.members.filter((member) => member.orgWide)\n const nameOf = (uid: string | null) =>\n data.members.find((member) => member.uid === uid)?.name ?? uid ?? ''\n const siteName = (hostId: string | null) =>\n data.hosts.find((host) => host.hostId === hostId)?.name ?? hostId ?? ''\n const collaborators = data.allotments.filter((row) => row.scope === 'collab')\n const restriction = bySubject.get(AI_ALLOTMENT_ORG_SUBJECT)\n /*\n * Each list pages and sorts on its own, and a selection is kept by member\n * and site id rather than by row, so it survives a page change.\n */\n const teamRows: MemberRow[] = team.map((member) => ({\n $id: member.uid,\n subject: `member:${member.uid}`,\n allotment: bySubject.get(`member:${member.uid}`),\n member,\n }))\n const siteRows: SiteRow[] = data.hosts.map((host) => ({\n $id: host.hostId,\n subject: `host:${host.hostId}`,\n allotment: bySubject.get(`host:${host.hostId}`),\n host,\n }))\n const collaboratorRows: CollaboratorRow[] = collaborators.map((row) => ({\n $id: row.subject,\n subject: row.subject,\n allotment: row,\n name: nameOf(row.uid),\n site: siteName(row.hostId),\n }))\n\n const editSubjects = (subjects: string[], title: string, description: string) => {\n const rows = subjects.map((subject) => bySubject.get(subject))\n setError(null)\n setEditing({\n kind: 'subjects',\n subjects,\n title,\n description,\n initial: subjects.length === 1 ? valueOf(rows[0]) : null,\n removable: rows.every(Boolean),\n })\n }\n\n const run = async (body: Parameters<typeof saveAiAllotments>[1]) => {\n setBusy(true)\n setError(null)\n const result = await saveAiAllotments(user, body)\n setBusy(false)\n if (!result.ok) {\n setError(result.error)\n return\n }\n setEditing(null)\n setMembers([])\n setSites([])\n }\n const save = (value: AiAllotmentValue) => {\n if (!editing) return\n if (editing.kind === 'everySite') return void run({ orgId, everySite: value })\n if (editing.kind === 'restriction') {\n return void run(\n value.models\n ? { orgId, set: [{ subject: AI_ALLOTMENT_ORG_SUBJECT, ...value, credits: null }] }\n : { orgId, remove: [AI_ALLOTMENT_ORG_SUBJECT] },\n )\n }\n return void run({\n orgId,\n set: editing.subjects.map((subject) => ({ subject, ...value })),\n })\n }\n const remove = () => {\n if (!editing || editing.kind === 'everySite') return\n return void run({\n orgId,\n remove: editing.kind === 'restriction' ? [AI_ALLOTMENT_ORG_SUBJECT] : editing.subjects,\n })\n }\n const editMember = (row: MemberRow) =>\n editSubjects([row.subject], `AI allotment — ${row.member.name}`, 'Counts what this person draws across every site.')\n const editSite = (row: SiteRow) =>\n editSubjects([row.subject], `AI allotment — ${row.host.name}`, 'Counts what everyone draws on the site, together.')\n const editCollaborator = (row: CollaboratorRow) =>\n editSubjects(\n [row.subject],\n `AI allotment — ${row.name} on ${row.site}`,\n 'Counts what this collaborator draws on this site.',\n )\n /** The one action a row has: set its allotment, or edit the one it has. */\n const editColumn = <Row extends AllotmentListRow>(label: (row: Row) => string, edit: (row: Row) => void) =>\n listActionsColumn((row: Row) => (\n <ListRowActions\n label={label(row)}\n quick={{\n icon: mdiPencilOutline.path,\n label: row.allotment ? 'Edit allotment' : 'Set allotment',\n onClick: () => edit(row),\n }}\n items={[]}\n />\n ))\n\n const teamColumns: GridColDef<MemberRow>[] = [\n { field: 'name', headerName: 'Member', flex: 1, minWidth: 180, valueGetter: (_value, row) => row.member.name },\n creditsColumn<MemberRow>((row) => row.member.credits),\n allotmentColumn<MemberRow>(),\n ...(canEdit ? [editColumn<MemberRow>((row) => row.member.name, editMember)] : []),\n ]\n const siteColumns: GridColDef<SiteRow>[] = [\n { field: 'name', headerName: 'Site', flex: 1, minWidth: 180, valueGetter: (_value, row) => row.host.name },\n creditsColumn<SiteRow>((row) => row.host.credits),\n allotmentColumn<SiteRow>(),\n ...(canEdit ? [editColumn<SiteRow>((row) => row.host.name, editSite)] : []),\n ]\n const collaboratorColumns: GridColDef<CollaboratorRow>[] = [\n { field: 'name', headerName: 'Collaborator', flex: 1, minWidth: 180 },\n { field: 'site', headerName: 'Site', flex: 1, minWidth: 160 },\n allotmentColumn<CollaboratorRow>(),\n ...(canEdit ? [editColumn<CollaboratorRow>((row) => `${row.name} on ${row.site}`, editCollaborator)] : []),\n ]\n\n return (\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Give a member or a whole site a share of the workspace’s AI credits each month. ' +\n 'A hard allotment stops AI requests at the line; a soft one keeps going and alerts ' +\n 'you at 80% and 100%. The workspace’s own credits still stop first.'}\n </Typography>\n\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Team members'}\n </Typography>\n {canEdit ? (\n <Button\n size=\"small\"\n disabled={!members.length}\n onClick={() =>\n editSubjects(\n members.map((uid) => `member:${uid}`),\n members.length === 1 ? `AI allotment — ${nameOf(members[0])}` : `AI allotment — ${members.length} members`,\n 'Counts what each person draws across every site.',\n )\n }\n >\n {members.length > 1 ? `Set for ${members.length} selected` : 'Set for selected'}\n </Button>\n ) : null}\n </Stack>\n <ListTable\n aria-label=\"Team members' AI allotments\"\n rows={teamRows}\n columns={teamColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No team members\"\n disableColumnFilter\n quickFilter={false}\n {...(canEdit\n ? { selectable: { selected: members, onChange: setMembers }, onOpen: (_id: string, row: MemberRow) => editMember(row) }\n : {})}\n />\n\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Sites'}\n </Typography>\n {canEdit ? (\n <>\n <Button\n size=\"small\"\n disabled={!sites.length}\n onClick={() =>\n editSubjects(\n sites.map((hostId) => `host:${hostId}`),\n sites.length === 1 ? `AI allotment — ${siteName(sites[0])}` : `AI allotment — ${sites.length} sites`,\n 'Counts what everyone draws on the site, together.',\n )\n }\n >\n {sites.length > 1 ? `Set for ${sites.length} selected` : 'Set for selected'}\n </Button>\n <Button\n size=\"small\"\n disabled={!data.hosts.length}\n onClick={() => {\n setError(null)\n setEditing({ kind: 'everySite' })\n }}\n >\n {'Same for every client site'}\n </Button>\n </>\n ) : null}\n </Stack>\n <ListTable\n aria-label=\"Sites' AI allotments\"\n rows={siteRows}\n columns={siteColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No sites\"\n disableColumnFilter\n quickFilter={false}\n {...(canEdit\n ? { selectable: { selected: sites, onChange: setSites }, onOpen: (_id: string, row: SiteRow) => editSite(row) }\n : {})}\n />\n\n {collaborators.length ? (\n <>\n <Typography variant=\"subtitle2\">{'Site collaborators'}</Typography>\n <ListTable\n aria-label=\"Site collaborators' AI allotments\"\n rows={collaboratorRows}\n columns={collaboratorColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n disableColumnFilter\n quickFilter={false}\n {...(canEdit ? { onOpen: (_id: string, row: CollaboratorRow) => editCollaborator(row) } : {})}\n />\n </>\n ) : null}\n\n {data.restrictionAvailable ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"body2\" sx={{ flexGrow: 1 }}>\n {'Models for the whole organization: '}\n {restriction?.models?.length\n ? data.models\n .filter((model) => restriction.models?.includes(model.id))\n .map((model) => model.label)\n .join(', ') || `${restriction.models.length} models`\n : 'every model the plan offers'}\n </Typography>\n {canEdit ? (\n <Button\n size=\"small\"\n onClick={() => {\n setError(null)\n setEditing({ kind: 'restriction', initial: valueOf(restriction), removable: Boolean(restriction) })\n }}\n >\n {'Restrict models'}\n </Button>\n ) : null}\n </Stack>\n ) : null}\n\n <AiAllotmentEditor\n open={editing !== null}\n title={\n editing?.kind === 'everySite'\n ? 'Same allotment for every site'\n : editing?.kind === 'restriction'\n ? 'Models for the whole organization'\n : (editing?.title ?? '')\n }\n description={\n editing?.kind === 'everySite'\n ? `Sets this allotment on all ${data.hosts.length} sites, replacing any each one had.`\n : editing?.kind === 'restriction'\n ? 'Every member, collaborator and Auto choose only among these models.'\n : editing?.description\n }\n initial={editing && editing.kind !== 'everySite' ? editing.initial : null}\n models={data.models}\n modelsOnly={editing?.kind === 'restriction'}\n poolLimit={data.pool.limit}\n busy={busy}\n error={error}\n onClose={() => setEditing(null)}\n onSave={save}\n onRemove={editing && editing.kind !== 'everySite' && editing.removable ? remove : undefined}\n />\n </Stack>\n )\n}\nBillingAiAllotments.displayName = 'BillingAiAllotments'\n\n/**\n * The card on Billing → Usage, through the `orgBillingUsage` zone, beneath\n * the table of who drew the credits. Registered with `billing.view`.\n */\nexport function AiAllotmentsCard(props: { orgId?: string }) {\n return (\n <div id=\"ai-allotments\">\n <CardDisplay\n header={'AI allotments'}\n help={pluginDocsHelp('billing', {\n anchor: '#ai-allotments',\n excerpt:\n 'A monthly share of the workspace’s AI credits for a member or a site — ' +\n 'hard or soft, with an optional list of models.',\n })}\n contentGutterX\n contentGutterY\n >\n <BillingAiAllotments orgId={props.orgId} />\n </CardDisplay>\n </div>\n )\n}\nAiAllotmentsCard.displayName = 'AiAllotmentsCard'\n\nexport default AiAllotmentsCard\n"],"names":["pluginDocsHelp","mdiPencilOutline","CardDisplay","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","useUser","Alert","Button","Stack","Typography","useMemo","useState","AI_ALLOTMENT_ORG_SUBJECT","aiAllotmentState","AiAllotmentEditor","saveAiAllotments","useAiAllotments","aiAllotmentSummary","row","parts","credits","push","used","toLocaleString","mode","models","length","join","aiAllotmentTone","state","valueOf","allotmentColumn","field","headerName","flex","minWidth","sortable","valueGetter","_value","allotment","renderCell","variant","noWrap","sx","color","creditsColumn","type","align","headerAlign","width","valueFormatter","value","BillingAiAllotments","orgId","restriction","data","user","allotments","members","setMembers","sites","setSites","editing","setEditing","busy","setBusy","error","setError","bySubject","Map","map","subject","status","severity","canEdit","billing","team","filter","member","orgWide","nameOf","uid","find","name","siteName","hostId","hosts","host","collaborators","scope","get","teamRows","$id","siteRows","collaboratorRows","site","editSubjects","subjects","title","description","rows","kind","initial","removable","every","Boolean","run","body","result","ok","save","everySite","set","remove","editMember","editSite","editCollaborator","editColumn","label","edit","quick","icon","path","onClick","items","teamColumns","siteColumns","collaboratorColumns","spacing","direction","alignItems","flexWrap","rowGap","flexGrow","size","disabled","aria-label","columns","rowHeight","noRowsLabel","disableColumnFilter","quickFilter","selectable","selected","onChange","onOpen","_id","restrictionAvailable","model","includes","id","open","modelsOnly","poolLimit","pool","limit","onClose","onSave","onRemove","undefined","displayName","AiAllotmentsCard","props","div","header","help","anchor","excerpt","contentGutterX","contentGutterY"],"mappings":"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"}
|
|
@@ -119,8 +119,8 @@ import { AI_USAGE_EXPORT_ROWS_HEADER, aiUsageMonthLabel, formatAiUsageShare } fr
|
|
|
119
119
|
orgSlug
|
|
120
120
|
]);
|
|
121
121
|
/*
|
|
122
|
-
* One row per member, and the whole month is in hand, so the grid pages
|
|
123
|
-
*
|
|
122
|
+
* One row per member, and the whole month is in hand, so the grid pages
|
|
123
|
+
* and sorts it itself. The row opens the member's page, which is
|
|
124
124
|
* where the split by site and by kind lives.
|
|
125
125
|
*/ const columns = useMemo(()=>[
|
|
126
126
|
{
|
|
@@ -267,7 +267,12 @@ import { AI_USAGE_EXPORT_ROWS_HEADER, aiUsageMonthLabel, formatAiUsageShare } fr
|
|
|
267
267
|
columns: columns,
|
|
268
268
|
getRowId: (row)=>row.uid,
|
|
269
269
|
rowHeight: TABLE_ROW_HEIGHT,
|
|
270
|
-
onOpen: (uid)=>router.push(memberHref(uid))
|
|
270
|
+
onOpen: (uid)=>router.push(memberHref(uid)),
|
|
271
|
+
// The month for every current member, computed whole by the route
|
|
272
|
+
// from the roster the plan's seats bound: a readout, not a list to
|
|
273
|
+
// filter — the grid would narrow only the rows it holds (AGL-3321).
|
|
274
|
+
disableColumnFilter: true,
|
|
275
|
+
quickFilter: false
|
|
271
276
|
}, usage.month)
|
|
272
277
|
]
|
|
273
278
|
});
|
|
@@ -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 * sorts and searches 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 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 />\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","direction","alignItems","flexWrap","rowGap","select","size","onChange","event","target","months","key","disabled","status","length","onClick","orgCredits","severity","getRowId","rowHeight","onOpen","push","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 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 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","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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"}
|
|
@@ -185,15 +185,19 @@ function JobsSection({ jobs }) {
|
|
|
185
185
|
columns: JOB_COLUMNS,
|
|
186
186
|
getRowId: (job)=>job.id,
|
|
187
187
|
rowHeight: TABLE_ROW_HEIGHT,
|
|
188
|
-
noRowsLabel: "No recent jobs"
|
|
188
|
+
noRowsLabel: "No recent jobs",
|
|
189
|
+
// The route's ten newest jobs: a bounded readout, so no filter over
|
|
190
|
+
// the rows it holds (AGL-3321).
|
|
191
|
+
disableColumnFilter: true,
|
|
192
|
+
quickFilter: false
|
|
189
193
|
})
|
|
190
194
|
]
|
|
191
195
|
});
|
|
192
196
|
}
|
|
193
197
|
/**
|
|
194
198
|
* The recent jobs, one column per fact. Who started a job is a column of
|
|
195
|
-
* its own rather than a link tucked beside the date, so it sorts
|
|
196
|
-
*
|
|
199
|
+
* its own rather than a link tucked beside the date, so it sorts like the
|
|
200
|
+
* rest; the credits sort by what was SPENT, which is the figure the
|
|
197
201
|
* pool was charged.
|
|
198
202
|
*/ const JOB_COLUMNS = [
|
|
199
203
|
{
|
|
@@ -460,7 +464,11 @@ const rate = (value)=>value == null ? '—' : `${Math.round(value * 100)}%`;
|
|
|
460
464
|
rowHeight: TABLE_ROW_HEIGHT,
|
|
461
465
|
onOpen: (uid)=>router.push(buildRoute(Route.ADMIN_USER_DETAIL, {
|
|
462
466
|
uid
|
|
463
|
-
}))
|
|
467
|
+
})),
|
|
468
|
+
// The route's ten dearest members: a bounded readout, so no filter
|
|
469
|
+
// over the rows it holds (AGL-3321).
|
|
470
|
+
disableColumnFilter: true,
|
|
471
|
+
quickFilter: false
|
|
464
472
|
});
|
|
465
473
|
}
|
|
466
474
|
const StaffOrgAiCard = ({ orgId })=>{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/staff-org-ai-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { aiAddonName } from '@aglyn/aglyn'\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiOpenInNew } from '@aglyn/shared-data-mdi'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Alert,\n Chip,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport type {\n StaffOrgAiJob,\n StaffOrgAiJobs,\n StaffOrgAiMargin,\n StaffOrgAiOverage,\n StaffOrgAiPool,\n StaffOrgAiRefusals,\n StaffOrgAiResponse,\n StaffOrgAiTokens,\n StaffOrgAiUser,\n} from '../usage/staff-org-ai'\n\n/**\n * THE STAFF AI CARD (AGL-2930): everything about one org's AI usage on the\n * page staff already open to look at that org.\n *\n * Read through `/api/ai/admin/org`, one request on mount. Every figure on it\n * is the route's, and every figure the route serves is composed from the\n * same helpers the meter refuses with — see `usage/staff-org-ai.ts`. This\n * file only lays them out.\n *\n * ## What each section is for\n *\n * - **Add-on** — is the org paying for AI, at what, since when.\n * - **Credit pool** — the band as three NAMED parts, because \"why does\n * this org have 13,000 credits\" has three answers (the plan, an override,\n * the add-on) and a single total cannot say which.\n * - **Overage** — what is past the band, what it is sold at, and the two\n * controls the org itself holds over it.\n * - **Refusals** — how often, and why, the gate said no this month.\n * - **Jobs** and **Top users** — the generative and per-person halves,\n * each of which says plainly when its rollup has nothing yet.\n * - **Tokens** — what each kind of request costs per request, what it\n * sends and generates, and how much of its prompt the cache served.\n * - **Margin** — spend against what AI brings in, red when it is over.\n * - **Actions** — where the override editor and the AI pause live.\n */\n\nconst credits = (value: number | null | undefined): string =>\n value == null ? '—' : Math.round(value).toLocaleString()\n\n/**\n * Four decimals under a dollar, two above (the assist-signals page's rule):\n * a month that really cost eight cents must not read as `$0.00`.\n */\nconst usd = (value: number | null | undefined): string => {\n if (value == null) return '—'\n const amount = Number(value) || 0\n return `$${amount.toFixed(Math.abs(amount) < 1 ? 4 : 2)}`\n}\n\nconst when = (iso: string | null): string =>\n iso ? new Date(iso).toLocaleDateString() : '—'\n\n/** The pool as one sentence naming every part that is non-zero. */\nexport function poolPartsSentence(pool: StaffOrgAiPool): string {\n const parts: string[] = []\n if (pool.overrideCredits !== null) {\n parts.push(`${credits(pool.overrideCredits)} staff override`)\n } else {\n parts.push(`${credits(pool.planCredits)} plan band`)\n }\n if (pool.addonCredits > 0) {\n parts.push(`${credits(pool.addonCredits)} ${aiAddonName()} add-on`)\n }\n return parts.join(' + ')\n}\n\n/**\n * The overage's state as one line: sold, walled, or capped — or, for an\n * uncapped staff comp (AGL-3049), nothing to sell past at all.\n */\nexport function overageStateSentence(\n overage: StaffOrgAiOverage,\n uncapped = false,\n): string {\n if (uncapped) {\n return 'Nothing is sold past a band: an uncapped staff comp has none, and bills nothing.'\n }\n if (overage.hardCap) return 'Stopped at the band — the org’s band switch is on.'\n if (!overage.sellsOverage) return 'Not sold past the band on this plan.'\n const rate =\n overage.rateUsdPer1k === null ? '' : ` at ${usd(overage.rateUsdPer1k)} per 1,000`\n if (overage.capUsd !== null) {\n return overage.capReached\n ? `Stopped — the org’s ${usd(overage.capUsd)} overage ceiling is reached.`\n : `Sold${rate}, up to the org’s ${usd(overage.capUsd)} ceiling.`\n }\n return `Sold${rate}, no ceiling set.`\n}\n\n/** Whether the margin section renders red. */\nexport function marginTone(margin: StaffOrgAiMargin): 'error' | 'warning' | 'success' {\n if (margin.underwater) return 'error'\n if (margin.multiple >= 1) return 'warning'\n return 'success'\n}\n\nfunction RefusalsRow({ refusals }: { refusals: StaffOrgAiRefusals }) {\n const labels: Record<keyof Omit<StaffOrgAiRefusals, 'total'>, string> = {\n band: 'band',\n cap: 'ceiling',\n messages: 'messages',\n budget: 'operator backstop',\n // A hard allotment a manager set on a member, a collaborator or a site\n // (AGL-2942).\n allotment: 'allotments',\n // The Free taste's rungs (AGL-2925): only a Free workspace collects these.\n account: 'free account',\n requests: 'free requests/day',\n refusals: 'free refusals/day',\n platform: 'platform ceiling',\n }\n return (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {(Object.keys(labels) as Array<keyof typeof labels>).map((reason) => {\n // A reason the answering server does not yet count reads as zero,\n // so a console newer than its route draws the row rather than\n // failing the card.\n const count = Number(refusals[reason] ?? 0)\n return (\n <Chip\n key={reason}\n size=\"small\"\n variant={count > 0 ? 'filled' : 'outlined'}\n color={count > 0 ? 'warning' : 'default'}\n label={`${labels[reason]} ${count.toLocaleString()}`}\n />\n )\n })}\n </Stack>\n )\n}\n\nfunction JobsSection({ jobs }: { jobs: StaffOrgAiJobs | null }) {\n if (jobs === null) {\n return (\n <Alert severity=\"warning\">\n {'Could not read the generation jobs — a failed read, not an idle org.'}\n </Alert>\n )\n }\n const total = Object.values(jobs.counts).reduce((sum, n) => sum + n, 0)\n if (total === 0) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No generation jobs yet.'}\n </Typography>\n )\n }\n return (\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {(['queued', 'running', 'needs_input', 'needs_review', 'failed'] as const).map((status) => (\n <Chip\n key={status}\n size=\"small\"\n color={\n status === 'failed' && jobs.counts[status] > 0\n ? 'error'\n : (status === 'needs_input' || status === 'needs_review') &&\n jobs.counts[status] > 0\n ? 'warning'\n : 'default'\n }\n label={`${status.replace('_', ' ')} ${jobs.counts[status]}`}\n />\n ))}\n <Chip size=\"small\" variant=\"outlined\" label={`done ${jobs.counts.done}`} />\n </Stack>\n {jobs.truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Counted from the newest jobs only — these counts are a floor.'}\n </Typography>\n ) : null}\n {/* A record list: the grid scrolls its own columns, so a job id and a\n starter's uid never push the card's content past its edge. */}\n <ListTable\n rows={jobs.recent}\n columns={JOB_COLUMNS}\n getRowId={(job: StaffOrgAiJob) => job.id}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No recent jobs\"\n />\n </Stack>\n )\n}\n\n/**\n * The recent jobs, one column per fact. Who started a job is a column of\n * its own rather than a link tucked beside the date, so it sorts and filters\n * like the rest; the credits sort by what was SPENT, which is the figure the\n * pool was charged.\n */\nconst JOB_COLUMNS: GridColDef<StaffOrgAiJob>[] = [\n {\n field: 'id',\n headerName: 'Job',\n flex: 1,\n minWidth: 200,\n renderCell: ({ row }) => (\n <Typography variant=\"body2\" noWrap title={row.id} sx={{ fontFamily: 'monospace' }}>\n {row.id}\n </Typography>\n ),\n },\n {\n field: 'kind',\n headerName: 'Kind',\n width: 120,\n valueFormatter: (value: string) => value || '—',\n },\n {\n field: 'status',\n headerName: 'Status',\n width: 130,\n valueFormatter: (value: string) => String(value ?? '').replace('_', ' '),\n },\n {\n field: 'creditsSpent',\n headerName: 'Credits',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 140,\n valueFormatter: (value: number, row) =>\n `${credits(value)} / ${credits(row.creditsReserved)}`,\n },\n {\n field: 'createdAt',\n headerName: 'Started',\n width: 120,\n valueFormatter: (value: string | null) => when(value),\n },\n {\n field: 'createdBy',\n headerName: 'Started by',\n flex: 1,\n minWidth: 240,\n renderCell: ({ row }) =>\n row.createdBy ? (\n <AppLink\n href={buildRoute(Route.ADMIN_USER_DETAIL, { uid: row.createdBy })}\n variant=\"body2\"\n underline=\"hover\"\n noWrap\n sx={{ fontFamily: 'monospace' }}\n >\n {row.createdBy}\n </AppLink>\n ) : (\n '—'\n ),\n },\n]\n\nconst tokenCount = (value: number): string => Math.round(value).toLocaleString()\n\nconst rate = (value: number | null): string =>\n value == null ? '—' : `${Math.round(value * 100)}%`\n\n/** The month's tokens, and what each kind of request spends of them (AGL-2937). */\nfunction TokensSection({ tokens }: { tokens: StaffOrgAiTokens }) {\n const { total } = tokens\n return (\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Typography variant=\"body2\">\n {`${tokenCount(total.input)} sent · ${tokenCount(total.cached)} read from cache · ${tokenCount(total.cacheWrite)} written to cache · ${tokenCount(total.output)} generated`}\n </Typography>\n <Chip\n size=\"small\"\n color={tokens.cacheHitRate != null && tokens.cacheHitRate >= 0.5 ? 'success' : 'default'}\n label={`cache hit rate ${rate(tokens.cacheHitRate)}`}\n />\n </Stack>\n {tokens.kinds.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No model requests recorded by kind this month.'}\n </Typography>\n ) : (\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Kind'}</TableCell>\n <TableCell align=\"right\">{'Requests'}</TableCell>\n <TableCell align=\"right\">{'Per request'}</TableCell>\n <TableCell align=\"right\">{'Sent'}</TableCell>\n <TableCell align=\"right\">{'From cache'}</TableCell>\n <TableCell align=\"right\">{'Generated'}</TableCell>\n <TableCell align=\"right\">{'Cache hit'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {tokens.kinds.map((row) => (\n <TableRow key={row.kind}>\n <TableCell>{row.kind}</TableCell>\n <TableCell align=\"right\">{row.requests.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{usd(row.costPerRequestUsd)}</TableCell>\n <TableCell align=\"right\">{tokenCount(row.tokens.input)}</TableCell>\n <TableCell align=\"right\">{tokenCount(row.tokens.cached)}</TableCell>\n <TableCell align=\"right\">{tokenCount(row.tokens.output)}</TableCell>\n <TableCell align=\"right\">{rate(row.cacheHitRate)}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n )}\n </Stack>\n )\n}\n\n/**\n * The month's top spenders, one row per person. The row opens the account;\n * the uid rides beneath the roster's name because two people can share one.\n */\nfunction TopUsersSection({ users }: { users: StaffOrgAiUser[] }) {\n const router = useRouter()\n const columns = useMemo<GridColDef<StaffOrgAiUser>[]>(\n () => [\n {\n field: 'name',\n headerName: 'Person',\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.name !== row.uid ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n noWrap\n sx={{ fontFamily: 'monospace' }}\n >\n {row.uid}\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) => credits(value),\n },\n {\n field: 'share',\n headerName: 'Share',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 90,\n valueFormatter: (value: number) => `${Math.round(value * 100)}%`,\n },\n {\n field: 'estCostUsd',\n headerName: 'Provider $',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 120,\n valueFormatter: (value: number) => usd(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 listActionsColumn((row: StaffOrgAiUser) => (\n <ListRowActions\n label={row.name}\n quick={{\n icon: mdiOpenInNew.path,\n label: 'View account',\n to: buildRoute(Route.ADMIN_USER_DETAIL, { uid: row.uid }),\n }}\n items={[]}\n />\n )),\n ],\n [],\n )\n return (\n <ListTable\n rows={users}\n columns={columns}\n getRowId={(row: StaffOrgAiUser) => row.uid}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(uid) => router.push(buildRoute(Route.ADMIN_USER_DETAIL, { uid }))}\n />\n )\n}\n\nconst StaffOrgAiCard = ({ orgId }: { orgId: string }) => {\n const { data: user } = useUser()\n // Keyed on who is signed in, not on the user object's identity.\n const signedInUid = user?.uid ?? null\n const userRef = useRef(user)\n userRef.current = user\n const [data, setData] = useState<StaffOrgAiResponse | null>(null)\n const [ready, setReady] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n useEffect(() => {\n // Mounted only for a confirmed staff reader — the `staffOrg` zone sits\n // inside the page's `StaffOnly`, which renders nothing while the claim\n // loads — because the route records an access row on every successful\n // open, and an open the page did not mean to make would be an access\n // nobody made.\n const user = userRef.current\n if (!orgId || !signedInUid || !user) return undefined\n let active = true\n setReady(false)\n setError(null)\n void (async () => {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/admin/org?orgId=${encodeURIComponent(orgId)}`,\n )\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n setError(payload?.error ?? 'AI lookup failed')\n setData(null)\n } else {\n setData(payload as StaffOrgAiResponse)\n }\n } catch {\n if (active) {\n setError('AI lookup failed')\n setData(null)\n }\n } finally {\n if (active) setReady(true)\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, signedInUid])\n\n const name = aiAddonName()\n return (\n <CardDisplay\n header={name}\n help={pluginDocsHelp('aiMonitoring', {\n anchor: '#the-ai-card',\n excerpt:\n 'The add-on, the credit pool and its parts, this month’s overage and refusals, generation jobs, tokens and the cache hit rate by kind, the people spending the most, and the margin — for this organization.',\n })}\n contentGutterX\n contentGutterY\n >\n {!ready ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : error || !data ? (\n <Alert severity=\"warning\">\n {`Could not read this organization’s AI usage — ${error ?? 'a failed read'}, not zero usage.`}\n </Alert>\n ) : (\n <Stack spacing={2}>\n {/* Add-on */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Add-on'}\n </Typography>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Chip\n size=\"small\"\n color={data.addon.on ? 'primary' : 'default'}\n label={data.addon.on ? `${name} on` : `${name} off`}\n />\n {data.addon.priceUsd !== null ? (\n <Typography variant=\"body2\">\n {`${usd(data.addon.priceUsd)}/mo on this plan`}\n </Typography>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Not sold on this plan'}\n </Typography>\n )}\n {data.addon.on ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {data.addon.since\n ? `since ${when(data.addon.since)}`\n : data.addon.sinceSource === 'unavailable'\n ? 'since — (Stripe not reachable)'\n : 'since — (no subscription item)'}\n </Typography>\n ) : null}\n </Stack>\n </Stack>\n\n {/* Credit pool */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {`Credit pool · ${data.month}`}\n </Typography>\n {data.pool.totalCredits === null && data.pool.uncapped ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Uncapped staff comp — no AI credit band. Bounded only by the message cap and an operator’s explicit spend ceiling.'}\n </Typography>\n ) : data.pool.totalCredits === null ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No AI credit band on this plan — bounded by the message cap and the operator backstop.'}\n </Typography>\n ) : (\n <Typography variant=\"body2\">\n {`${credits(data.pool.totalCredits)} credits = ${poolPartsSentence(data.pool)}`}\n </Typography>\n )}\n <Typography variant=\"body2\">\n {`Used ${credits(data.pool.usedCredits)} credits (${usd(data.pool.providerUsd)} provider spend)`}\n {data.pool.remainingCredits !== null\n ? ` · ${credits(data.pool.remainingCredits)} remaining`\n : ''}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Projected month-end at this pace: ${credits(data.pool.projectedCredits)} credits (${usd(data.pool.projectedUsd)}) · ${data.pool.messages.toLocaleString()} model turns, ${data.pool.deflected.toLocaleString()} answered from the docs`}\n </Typography>\n </Stack>\n\n {/* Overage */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Overage'}\n </Typography>\n <Typography variant=\"body2\">\n {`${credits(data.overage.overageCredits)} credits over the band · ${usd(data.overage.accruedUsd)} accrued`}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {overageStateSentence(data.overage, data.pool.uncapped === true)}\n </Typography>\n </Stack>\n\n {/* Refusals */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {`Refusals this month · ${data.refusals.total.toLocaleString()}`}\n </Typography>\n <RefusalsRow refusals={data.refusals} />\n </Stack>\n\n {/* Jobs */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Generation jobs'}\n </Typography>\n <JobsSection jobs={data.jobs} />\n </Stack>\n\n {/* Tokens — absent from a route older than the card */}\n {data.tokens ? (\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {`Tokens · ${data.month}`}\n </Typography>\n <TokensSection tokens={data.tokens} />\n </Stack>\n ) : null}\n\n {/* Top users */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Top users this month'}\n </Typography>\n {data.users.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No AI usage attributed to a person this month.'}\n </Typography>\n ) : (\n <TopUsersSection users={data.users} />\n )}\n </Stack>\n\n {/* Margin */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Margin'}\n </Typography>\n <Alert severity={marginTone(data.margin)}>\n {`Spend ${usd(data.margin.spendUsd)} against ${usd(data.margin.revenueUsd)} in — ` +\n `${usd(data.margin.addonRevenueUsd)} add-on, ${usd(data.margin.planAssistShareUsd)} plan assist share, ` +\n `${usd(data.margin.overageRevenueUsd)} overage.`}\n {data.margin.underwater\n ? ' Spend exceeds the add-on plus the plan’s assist share.'\n : ''}\n {data.margin.multiple >= 1\n ? ` ${data.margin.multiple}× the ${usd(data.margin.thresholdUsd)} staff review threshold.`\n : ''}\n </Alert>\n {data.margin.contribution.marginPct !== null ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Whole-org contribution margin ${Math.round(data.margin.contribution.marginPct * 100)}% on the ${data.margin.contribution.month} rollup with this month’s AI spend.`}\n </Typography>\n ) : null}\n </Stack>\n\n {/* Actions */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Actions'}\n </Typography>\n <Typography variant=\"body2\">\n {'Credit override and feature toggles: '}\n <AppLink href={buildRoute(Route.ADMIN_ORGS)} underline=\"hover\">\n {'the entitlement override editor'}\n </AppLink>\n {' (also under Staff actions above). Pause AI for this org: '}\n <AppLink href={buildRoute(Route.ADMIN_LOCKDOWN)} underline=\"hover\">\n {'Lockdown'}\n </AppLink>\n {', feature keys ai-assist and ai-generate scoped to the org.'}\n </Typography>\n </Stack>\n </Stack>\n )}\n </CardDisplay>\n )\n}\nStaffOrgAiCard.displayName = 'StaffOrgAiCard'\n\nexport default StaffOrgAiCard\n"],"names":["aiAddonName","buildRoute","Route","pluginDocsHelp","mdiOpenInNew","AppLink","CardDisplay","ListRowActions","ListTable","listActionsColumn","ScrollTable","TABLE_ROW_HEIGHT","useUser","authorizedFetch","Alert","Chip","Stack","TableBody","TableCell","TableHead","TableRow","Typography","useRouter","useEffect","useMemo","useRef","useState","credits","value","Math","round","toLocaleString","usd","amount","Number","toFixed","abs","when","iso","Date","toLocaleDateString","poolPartsSentence","pool","parts","overrideCredits","push","planCredits","addonCredits","join","overageStateSentence","overage","uncapped","hardCap","sellsOverage","rate","rateUsdPer1k","capUsd","capReached","marginTone","margin","underwater","multiple","RefusalsRow","refusals","labels","band","cap","messages","budget","allotment","account","requests","platform","direction","spacing","useFlexGap","sx","flexWrap","Object","keys","map","reason","count","size","variant","color","label","JobsSection","jobs","severity","total","values","counts","reduce","sum","n","status","replace","done","truncated","rows","recent","columns","JOB_COLUMNS","getRowId","job","id","rowHeight","noRowsLabel","field","headerName","flex","minWidth","renderCell","row","noWrap","title","fontFamily","width","valueFormatter","String","type","align","headerAlign","creditsReserved","createdBy","href","ADMIN_USER_DETAIL","uid","underline","tokenCount","TokensSection","tokens","alignItems","input","cached","cacheWrite","output","cacheHitRate","kinds","length","kind","costPerRequestUsd","TopUsersSection","users","router","name","quick","icon","path","to","items","onOpen","StaffOrgAiCard","orgId","data","user","signedInUid","userRef","current","setData","ready","setReady","error","setError","undefined","active","response","encodeURIComponent","payload","json","catch","ok","header","help","anchor","excerpt","contentGutterX","contentGutterY","addon","on","priceUsd","since","sinceSource","month","totalCredits","usedCredits","providerUsd","remainingCredits","projectedCredits","projectedUsd","deflected","overageCredits","accruedUsd","spendUsd","revenueUsd","addonRevenueUsd","planAssistShareUsd","overageRevenueUsd","thresholdUsd","contribution","marginPct","ADMIN_ORGS","ADMIN_LOCKDOWN","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,WAAW,QAAQ,eAAc;AAC1C,SAASC,UAAU,EAAEC,KAAK,QAAQ,wCAAuC;AACzE,SAASC,cAAc,QAAQ,mCAAkC;AACjE,SAASC,YAAY,QAAQ,yBAAwB;AACrD,SAASC,OAAO,EAAEC,WAAW,QAAQ,uBAAsB;AAC3D,SACEC,cAAc,EACdC,SAAS,EACTC,iBAAiB,QACZ,uDAAsD;AAC7D,SAASC,WAAW,QAAQ,yDAAwD;AACpF,SAASC,gBAAgB,QAAQ,8CAA6C;AAC9E,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SAASC,eAAe,QAAQ,2CAA0C;AAC1E,SACEC,KAAK,EACLC,IAAI,EACJC,KAAK,EACLC,SAAS,EACTC,SAAS,EACTC,SAAS,EACTC,QAAQ,EACRC,UAAU,QACL,gBAAe;AAEtB,SAASC,SAAS,QAAQ,kBAAiB;AAC3C,SAASC,SAAS,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AAa5D;;;;;;;;;;;;;;;;;;;;;;;;CAwBC,GAED,MAAMC,UAAU,CAACC,QACfA,SAAS,OAAO,MAAMC,KAAKC,KAAK,CAACF,OAAOG,cAAc;AAExD;;;CAGC,GACD,MAAMC,MAAM,CAACJ;IACX,IAAIA,SAAS,MAAM,OAAO;IAC1B,MAAMK,SAASC,OAAON,UAAU;IAChC,OAAO,CAAC,CAAC,EAAEK,OAAOE,OAAO,CAACN,KAAKO,GAAG,CAACH,UAAU,IAAI,IAAI,IAAI;AAC3D;AAEA,MAAMI,OAAO,CAACC,MACZA,MAAM,IAAIC,KAAKD,KAAKE,kBAAkB,KAAK;AAE7C,iEAAiE,GACjE,OAAO,SAASC,kBAAkBC,IAAoB;IACpD,MAAMC,QAAkB,EAAE;IAC1B,IAAID,KAAKE,eAAe,KAAK,MAAM;QACjCD,MAAME,IAAI,CAAC,GAAGlB,QAAQe,KAAKE,eAAe,EAAE,eAAe,CAAC;IAC9D,OAAO;QACLD,MAAME,IAAI,CAAC,GAAGlB,QAAQe,KAAKI,WAAW,EAAE,UAAU,CAAC;IACrD;IACA,IAAIJ,KAAKK,YAAY,GAAG,GAAG;QACzBJ,MAAME,IAAI,CAAC,GAAGlB,QAAQe,KAAKK,YAAY,EAAE,CAAC,EAAE/C,cAAc,OAAO,CAAC;IACpE;IACA,OAAO2C,MAAMK,IAAI,CAAC;AACpB;AAEA;;;CAGC,GACD,OAAO,SAASC,qBACdC,OAA0B,EAC1BC,WAAW,KAAK;IAEhB,IAAIA,UAAU;QACZ,OAAO;IACT;IACA,IAAID,QAAQE,OAAO,EAAE,OAAO;IAC5B,IAAI,CAACF,QAAQG,YAAY,EAAE,OAAO;IAClC,MAAMC,OACJJ,QAAQK,YAAY,KAAK,OAAO,KAAK,CAAC,IAAI,EAAEvB,IAAIkB,QAAQK,YAAY,EAAE,UAAU,CAAC;IACnF,IAAIL,QAAQM,MAAM,KAAK,MAAM;QAC3B,OAAON,QAAQO,UAAU,GACrB,CAAC,oBAAoB,EAAEzB,IAAIkB,QAAQM,MAAM,EAAE,4BAA4B,CAAC,GACxE,CAAC,IAAI,EAAEF,KAAK,kBAAkB,EAAEtB,IAAIkB,QAAQM,MAAM,EAAE,SAAS,CAAC;IACpE;IACA,OAAO,CAAC,IAAI,EAAEF,KAAK,iBAAiB,CAAC;AACvC;AAEA,4CAA4C,GAC5C,OAAO,SAASI,WAAWC,MAAwB;IACjD,IAAIA,OAAOC,UAAU,EAAE,OAAO;IAC9B,IAAID,OAAOE,QAAQ,IAAI,GAAG,OAAO;IACjC,OAAO;AACT;AAEA,SAASC,YAAY,EAAEC,QAAQ,EAAoC;IACjE,MAAMC,SAAkE;QACtEC,MAAM;QACNC,KAAK;QACLC,UAAU;QACVC,QAAQ;QACR,uEAAuE;QACvE,cAAc;QACdC,WAAW;QACX,2EAA2E;QAC3EC,SAAS;QACTC,UAAU;QACVR,UAAU;QACVS,UAAU;IACZ;IACA,qBACE,KAACxD;QAAMyD,WAAU;QAAMC,SAAS;QAAGC,UAAU;QAACC,IAAI;YAAEC,UAAU;QAAO;kBAClE,AAACC,OAAOC,IAAI,CAACf,QAAuCgB,GAAG,CAAC,CAACC;gBAInClB;YAHrB,kEAAkE;YAClE,8DAA8D;YAC9D,oBAAoB;YACpB,MAAMmB,QAAQhD,QAAO6B,mBAAAA,QAAQ,CAACkB,OAAO,YAAhBlB,mBAAoB;YACzC,qBACE,KAAChD;gBAECoE,MAAK;gBACLC,SAASF,QAAQ,IAAI,WAAW;gBAChCG,OAAOH,QAAQ,IAAI,YAAY;gBAC/BI,OAAO,GAAGtB,MAAM,CAACiB,OAAO,CAAC,CAAC,EAAEC,MAAMnD,cAAc,IAAI;eAJ/CkD;QAOX;;AAGN;AAEA,SAASM,YAAY,EAAEC,IAAI,EAAmC;IAC5D,IAAIA,SAAS,MAAM;QACjB,qBACE,KAAC1E;YAAM2E,UAAS;sBACb;;IAGP;IACA,MAAMC,QAAQZ,OAAOa,MAAM,CAACH,KAAKI,MAAM,EAAEC,MAAM,CAAC,CAACC,KAAKC,IAAMD,MAAMC,GAAG;IACrE,IAAIL,UAAU,GAAG;QACf,qBACE,KAACrE;YAAW+D,SAAQ;YAAQC,OAAM;sBAC/B;;IAGP;IACA,qBACE,MAACrE;QAAM0D,SAAS;;0BACd,MAAC1D;gBAAMyD,WAAU;gBAAMC,SAAS;gBAAGC,UAAU;gBAACC,IAAI;oBAAEC,UAAU;gBAAO;;oBACjE;wBAAC;wBAAU;wBAAW;wBAAe;wBAAgB;qBAAS,CAAWG,GAAG,CAAC,CAACgB,uBAC9E,KAACjF;4BAECoE,MAAK;4BACLE,OACEW,WAAW,YAAYR,KAAKI,MAAM,CAACI,OAAO,GAAG,IACzC,UACA,AAACA,CAAAA,WAAW,iBAAiBA,WAAW,cAAa,KACnDR,KAAKI,MAAM,CAACI,OAAO,GAAG,IACtB,YACA;4BAERV,OAAO,GAAGU,OAAOC,OAAO,CAAC,KAAK,KAAK,CAAC,EAAET,KAAKI,MAAM,CAACI,OAAO,EAAE;2BAVtDA;kCAaT,KAACjF;wBAAKoE,MAAK;wBAAQC,SAAQ;wBAAWE,OAAO,CAAC,KAAK,EAAEE,KAAKI,MAAM,CAACM,IAAI,EAAE;;;;YAExEV,KAAKW,SAAS,iBACb,KAAC9E;gBAAW+D,SAAQ;gBAAUC,OAAM;0BACjC;iBAED;0BAGJ,KAAC7E;gBACC4F,MAAMZ,KAAKa,MAAM;gBACjBC,SAASC;gBACTC,UAAU,CAACC,MAAuBA,IAAIC,EAAE;gBACxCC,WAAWhG;gBACXiG,aAAY;;;;AAIpB;AAEA;;;;;CAKC,GACD,MAAML,cAA2C;IAC/C;QACEM,OAAO;QACPC,YAAY;QACZC,MAAM;QACNC,UAAU;QACVC,YAAY,CAAC,EAAEC,GAAG,EAAE,iBAClB,KAAC7F;gBAAW+D,SAAQ;gBAAQ+B,MAAM;gBAACC,OAAOF,IAAIR,EAAE;gBAAE9B,IAAI;oBAAEyC,YAAY;gBAAY;0BAC7EH,IAAIR,EAAE;;IAGb;IACA;QACEG,OAAO;QACPC,YAAY;QACZQ,OAAO;QACPC,gBAAgB,CAAC3F,QAAkBA,SAAS;IAC9C;IACA;QACEiF,OAAO;QACPC,YAAY;QACZQ,OAAO;QACPC,gBAAgB,CAAC3F,QAAkB4F,OAAO5F,gBAAAA,QAAS,IAAIqE,OAAO,CAAC,KAAK;IACtE;IACA;QACEY,OAAO;QACPC,YAAY;QACZW,MAAM;QACNC,OAAO;QACPC,aAAa;QACbL,OAAO;QACPC,gBAAgB,CAAC3F,OAAesF,MAC9B,GAAGvF,QAAQC,OAAO,GAAG,EAAED,QAAQuF,IAAIU,eAAe,GAAG;IACzD;IACA;QACEf,OAAO;QACPC,YAAY;QACZQ,OAAO;QACPC,gBAAgB,CAAC3F,QAAyBS,KAAKT;IACjD;IACA;QACEiF,OAAO;QACPC,YAAY;QACZC,MAAM;QACNC,UAAU;QACVC,YAAY,CAAC,EAAEC,GAAG,EAAE,GAClBA,IAAIW,SAAS,iBACX,KAACxH;gBACCyH,MAAM7H,WAAWC,MAAM6H,iBAAiB,EAAE;oBAAEC,KAAKd,IAAIW,SAAS;gBAAC;gBAC/DzC,SAAQ;gBACR6C,WAAU;gBACVd,MAAM;gBACNvC,IAAI;oBAAEyC,YAAY;gBAAY;0BAE7BH,IAAIW,SAAS;iBAGhB;IAEN;CACD;AAED,MAAMK,aAAa,CAACtG,QAA0BC,KAAKC,KAAK,CAACF,OAAOG,cAAc;AAE9E,MAAMuB,OAAO,CAAC1B,QACZA,SAAS,OAAO,MAAM,GAAGC,KAAKC,KAAK,CAACF,QAAQ,KAAK,CAAC,CAAC;AAErD,iFAAiF,GACjF,SAASuG,cAAc,EAAEC,MAAM,EAAgC;IAC7D,MAAM,EAAE1C,KAAK,EAAE,GAAG0C;IAClB,qBACE,MAACpH;QAAM0D,SAAS;;0BACd,MAAC1D;gBAAMyD,WAAU;gBAAMC,SAAS;gBAAGC,UAAU;gBAACC,IAAI;oBAAEyD,YAAY;oBAAUxD,UAAU;gBAAO;;kCACzF,KAACxD;wBAAW+D,SAAQ;kCACjB,GAAG8C,WAAWxC,MAAM4C,KAAK,EAAE,QAAQ,EAAEJ,WAAWxC,MAAM6C,MAAM,EAAE,mBAAmB,EAAEL,WAAWxC,MAAM8C,UAAU,EAAE,oBAAoB,EAAEN,WAAWxC,MAAM+C,MAAM,EAAE,UAAU,CAAC;;kCAE7K,KAAC1H;wBACCoE,MAAK;wBACLE,OAAO+C,OAAOM,YAAY,IAAI,QAAQN,OAAOM,YAAY,IAAI,MAAM,YAAY;wBAC/EpD,OAAO,CAAC,eAAe,EAAEhC,KAAK8E,OAAOM,YAAY,GAAG;;;;YAGvDN,OAAOO,KAAK,CAACC,MAAM,KAAK,kBACvB,KAACvH;gBAAW+D,SAAQ;gBAAQC,OAAM;0BAC/B;+BAGH,MAAC3E;gBAAYyE,MAAK;;kCAChB,KAAChE;kCACC,cAAA,MAACC;;8CACC,KAACF;8CAAW;;8CACZ,KAACA;oCAAUwG,OAAM;8CAAS;;8CAC1B,KAACxG;oCAAUwG,OAAM;8CAAS;;8CAC1B,KAACxG;oCAAUwG,OAAM;8CAAS;;8CAC1B,KAACxG;oCAAUwG,OAAM;8CAAS;;8CAC1B,KAACxG;oCAAUwG,OAAM;8CAAS;;8CAC1B,KAACxG;oCAAUwG,OAAM;8CAAS;;;;;kCAG9B,KAACzG;kCACEmH,OAAOO,KAAK,CAAC3D,GAAG,CAAC,CAACkC,oBACjB,MAAC9F;;kDACC,KAACF;kDAAWgG,IAAI2B,IAAI;;kDACpB,KAAC3H;wCAAUwG,OAAM;kDAASR,IAAI3C,QAAQ,CAACxC,cAAc;;kDACrD,KAACb;wCAAUwG,OAAM;kDAAS1F,IAAIkF,IAAI4B,iBAAiB;;kDACnD,KAAC5H;wCAAUwG,OAAM;kDAASQ,WAAWhB,IAAIkB,MAAM,CAACE,KAAK;;kDACrD,KAACpH;wCAAUwG,OAAM;kDAASQ,WAAWhB,IAAIkB,MAAM,CAACG,MAAM;;kDACtD,KAACrH;wCAAUwG,OAAM;kDAASQ,WAAWhB,IAAIkB,MAAM,CAACK,MAAM;;kDACtD,KAACvH;wCAAUwG,OAAM;kDAASpE,KAAK4D,IAAIwB,YAAY;;;+BAPlCxB,IAAI2B,IAAI;;;;;;AAerC;AAEA;;;CAGC,GACD,SAASE,gBAAgB,EAAEC,KAAK,EAA+B;IAC7D,MAAMC,SAAS3H;IACf,MAAMgF,UAAU9E,QACd,IAAM;YACJ;gBACEqF,OAAO;gBACPC,YAAY;gBACZC,MAAM;gBACNC,UAAU;gBACVC,YAAY,CAAC,EAAEC,GAAG,EAAE,iBAClB,MAAClG;wBAAM4D,IAAI;4BAAEoC,UAAU;wBAAE;;0CACvB,KAAC3F;gCAAW+D,SAAQ;gCAAQ+B,MAAM;gCAACC,OAAOF,IAAIgC,IAAI;0CAC/ChC,IAAIgC,IAAI;;4BAEVhC,IAAIgC,IAAI,KAAKhC,IAAIc,GAAG,iBACnB,KAAC3G;gCACC+D,SAAQ;gCACRC,OAAM;gCACN8B,MAAM;gCACNvC,IAAI;oCAAEyC,YAAY;gCAAY;0CAE7BH,IAAIc,GAAG;iCAER;;;YAGV;YACA;gBACEnB,OAAO;gBACPC,YAAY;gBACZW,MAAM;gBACNC,OAAO;gBACPC,aAAa;gBACbL,OAAO;gBACPC,gBAAgB,CAAC3F,QAAkBD,QAAQC;YAC7C;YACA;gBACEiF,OAAO;gBACPC,YAAY;gBACZW,MAAM;gBACNC,OAAO;gBACPC,aAAa;gBACbL,OAAO;gBACPC,gBAAgB,CAAC3F,QAAkB,GAAGC,KAAKC,KAAK,CAACF,QAAQ,KAAK,CAAC,CAAC;YAClE;YACA;gBACEiF,OAAO;gBACPC,YAAY;gBACZW,MAAM;gBACNC,OAAO;gBACPC,aAAa;gBACbL,OAAO;gBACPC,gBAAgB,CAAC3F,QAAkBI,IAAIJ;YACzC;YACA;gBACEiF,OAAO;gBACPC,YAAY;gBACZW,MAAM;gBACNC,OAAO;gBACPC,aAAa;gBACbL,OAAO;gBACPC,gBAAgB,CAAC3F,QAAkBA,MAAMG,cAAc;YACzD;YACA;gBACE8E,OAAO;gBACPC,YAAY;gBACZW,MAAM;gBACNC,OAAO;gBACPC,aAAa;gBACbL,OAAO;gBACPC,gBAAgB,CAAC3F,QAAkBA,MAAMG,cAAc;YACzD;YACAtB,kBAAkB,CAACyG,oBACjB,KAAC3G;oBACC+E,OAAO4B,IAAIgC,IAAI;oBACfC,OAAO;wBACLC,MAAMhJ,aAAaiJ,IAAI;wBACvB/D,OAAO;wBACPgE,IAAIrJ,WAAWC,MAAM6H,iBAAiB,EAAE;4BAAEC,KAAKd,IAAIc,GAAG;wBAAC;oBACzD;oBACAuB,OAAO,EAAE;;SAGd,EACD,EAAE;IAEJ,qBACE,KAAC/I;QACC4F,MAAM4C;QACN1C,SAASA;QACTE,UAAU,CAACU,MAAwBA,IAAIc,GAAG;QAC1CrB,WAAWhG;QACX6I,QAAQ,CAACxB,MAAQiB,OAAOpG,IAAI,CAAC5C,WAAWC,MAAM6H,iBAAiB,EAAE;gBAAEC;YAAI;;AAG7E;AAEA,MAAMyB,iBAAiB,CAAC,EAAEC,KAAK,EAAqB;;IAClD,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAGhJ;IACvB,gEAAgE;IAChE,MAAMiJ,sBAAcD,wBAAAA,KAAM5B,GAAG,mBAAI;IACjC,MAAM8B,UAAUrI,OAAOmI;IACvBE,QAAQC,OAAO,GAAGH;IAClB,MAAM,CAACD,MAAMK,QAAQ,GAAGtI,SAAoC;IAC5D,MAAM,CAACuI,OAAOC,SAAS,GAAGxI,SAAS;IACnC,MAAM,CAACyI,OAAOC,SAAS,GAAG1I,SAAwB;IAElDH,UAAU;QACR,uEAAuE;QACvE,uEAAuE;QACvE,sEAAsE;QACtE,qEAAqE;QACrE,eAAe;QACf,MAAMqI,OAAOE,QAAQC,OAAO;QAC5B,IAAI,CAACL,SAAS,CAACG,eAAe,CAACD,MAAM,OAAOS;QAC5C,IAAIC,SAAS;QACbJ,SAAS;QACTE,SAAS;QACT,KAAK,AAAC,CAAA;YACJ,IAAI;gBACF,MAAMG,WAAW,MAAM1J,gBACrB+I,MACA,CAAC,wBAAwB,EAAEY,mBAAmBd,QAAQ;gBAExD,MAAMe,UAAU,MAAMF,SAASG,IAAI,GAAGC,KAAK,CAAC,IAAM;gBAClD,IAAI,CAACL,QAAQ;gBACb,IAAI,CAACC,SAASK,EAAE,EAAE;;oBAChBR,iBAASK,2BAAAA,QAASN,KAAK,mBAAI;oBAC3BH,QAAQ;gBACV,OAAO;oBACLA,QAAQS;gBACV;YACF,EAAE,eAAM;gBACN,IAAIH,QAAQ;oBACVF,SAAS;oBACTJ,QAAQ;gBACV;YACF,SAAU;gBACR,IAAIM,QAAQJ,SAAS;YACvB;QACF,CAAA;QACA,OAAO;YACLI,SAAS;QACX;IACF,GAAG;QAACZ;QAAOG;KAAY;IAEvB,MAAMX,OAAOlJ;IACb,qBACE,KAACM;QACCuK,QAAQ3B;QACR4B,MAAM3K,eAAe,gBAAgB;YACnC4K,QAAQ;YACRC,SACE;QACJ;QACAC,cAAc;QACdC,cAAc;kBAEb,CAACjB,sBACA,KAAC5I;YAAW+D,SAAQ;YAAQC,OAAM;sBAC/B;aAED8E,SAAS,CAACR,qBACZ,KAAC7I;YAAM2E,UAAS;sBACb,CAAC,8CAA8C,EAAE0E,gBAAAA,QAAS,gBAAgB,iBAAiB,CAAC;2BAG/F,MAACnJ;YAAM0D,SAAS;;8BAEd,MAAC1D;oBAAM0D,SAAS;;sCACd,KAACrD;4BAAW+D,SAAQ;4BAAWC,OAAM;sCAClC;;sCAEH,MAACrE;4BAAMyD,WAAU;4BAAMC,SAAS;4BAAGE,IAAI;gCAAEyD,YAAY;gCAAUxD,UAAU;4BAAO;;8CAC9E,KAAC9D;oCACCoE,MAAK;oCACLE,OAAOsE,KAAKwB,KAAK,CAACC,EAAE,GAAG,YAAY;oCACnC9F,OAAOqE,KAAKwB,KAAK,CAACC,EAAE,GAAG,GAAGlC,KAAK,GAAG,CAAC,GAAG,GAAGA,KAAK,IAAI,CAAC;;gCAEpDS,KAAKwB,KAAK,CAACE,QAAQ,KAAK,qBACvB,KAAChK;oCAAW+D,SAAQ;8CACjB,GAAGpD,IAAI2H,KAAKwB,KAAK,CAACE,QAAQ,EAAE,gBAAgB,CAAC;mDAGhD,KAAChK;oCAAW+D,SAAQ;oCAAQC,OAAM;8CAC/B;;gCAGJsE,KAAKwB,KAAK,CAACC,EAAE,iBACZ,KAAC/J;oCAAW+D,SAAQ;oCAAQC,OAAM;8CAC/BsE,KAAKwB,KAAK,CAACG,KAAK,GACb,CAAC,MAAM,EAAEjJ,KAAKsH,KAAKwB,KAAK,CAACG,KAAK,GAAG,GACjC3B,KAAKwB,KAAK,CAACI,WAAW,KAAK,gBACzB,mCACA;qCAEN;;;;;8BAKR,MAACvK;oBAAM0D,SAAS;;sCACd,KAACrD;4BAAW+D,SAAQ;4BAAWC,OAAM;sCAClC,CAAC,cAAc,EAAEsE,KAAK6B,KAAK,EAAE;;wBAE/B7B,KAAKjH,IAAI,CAAC+I,YAAY,KAAK,QAAQ9B,KAAKjH,IAAI,CAACS,QAAQ,iBACpD,KAAC9B;4BAAW+D,SAAQ;4BAAQC,OAAM;sCAC/B;6BAEDsE,KAAKjH,IAAI,CAAC+I,YAAY,KAAK,qBAC7B,KAACpK;4BAAW+D,SAAQ;4BAAQC,OAAM;sCAC/B;2CAGH,KAAChE;4BAAW+D,SAAQ;sCACjB,GAAGzD,QAAQgI,KAAKjH,IAAI,CAAC+I,YAAY,EAAE,WAAW,EAAEhJ,kBAAkBkH,KAAKjH,IAAI,GAAG;;sCAGnF,MAACrB;4BAAW+D,SAAQ;;gCACjB,CAAC,KAAK,EAAEzD,QAAQgI,KAAKjH,IAAI,CAACgJ,WAAW,EAAE,UAAU,EAAE1J,IAAI2H,KAAKjH,IAAI,CAACiJ,WAAW,EAAE,gBAAgB,CAAC;gCAC/FhC,KAAKjH,IAAI,CAACkJ,gBAAgB,KAAK,OAC5B,CAAC,GAAG,EAAEjK,QAAQgI,KAAKjH,IAAI,CAACkJ,gBAAgB,EAAE,UAAU,CAAC,GACrD;;;sCAEN,KAACvK;4BAAW+D,SAAQ;4BAAQC,OAAM;sCAC/B,CAAC,kCAAkC,EAAE1D,QAAQgI,KAAKjH,IAAI,CAACmJ,gBAAgB,EAAE,UAAU,EAAE7J,IAAI2H,KAAKjH,IAAI,CAACoJ,YAAY,EAAE,IAAI,EAAEnC,KAAKjH,IAAI,CAACyB,QAAQ,CAACpC,cAAc,GAAG,cAAc,EAAE4H,KAAKjH,IAAI,CAACqJ,SAAS,CAAChK,cAAc,GAAG,uBAAuB,CAAC;;;;8BAK7O,MAACf;oBAAM0D,SAAS;;sCACd,KAACrD;4BAAW+D,SAAQ;4BAAWC,OAAM;sCAClC;;sCAEH,KAAChE;4BAAW+D,SAAQ;sCACjB,GAAGzD,QAAQgI,KAAKzG,OAAO,CAAC8I,cAAc,EAAE,yBAAyB,EAAEhK,IAAI2H,KAAKzG,OAAO,CAAC+I,UAAU,EAAE,QAAQ,CAAC;;sCAE5G,KAAC5K;4BAAW+D,SAAQ;4BAAQC,OAAM;sCAC/BpC,qBAAqB0G,KAAKzG,OAAO,EAAEyG,KAAKjH,IAAI,CAACS,QAAQ,KAAK;;;;8BAK/D,MAACnC;oBAAM0D,SAAS;;sCACd,KAACrD;4BAAW+D,SAAQ;4BAAWC,OAAM;sCAClC,CAAC,sBAAsB,EAAEsE,KAAK5F,QAAQ,CAAC2B,KAAK,CAAC3D,cAAc,IAAI;;sCAElE,KAAC+B;4BAAYC,UAAU4F,KAAK5F,QAAQ;;;;8BAItC,MAAC/C;oBAAM0D,SAAS;;sCACd,KAACrD;4BAAW+D,SAAQ;4BAAWC,OAAM;sCAClC;;sCAEH,KAACE;4BAAYC,MAAMmE,KAAKnE,IAAI;;;;gBAI7BmE,KAAKvB,MAAM,iBACV,MAACpH;oBAAM0D,SAAS;;sCACd,KAACrD;4BAAW+D,SAAQ;4BAAWC,OAAM;sCAClC,CAAC,SAAS,EAAEsE,KAAK6B,KAAK,EAAE;;sCAE3B,KAACrD;4BAAcC,QAAQuB,KAAKvB,MAAM;;;qBAElC;8BAGJ,MAACpH;oBAAM0D,SAAS;;sCACd,KAACrD;4BAAW+D,SAAQ;4BAAWC,OAAM;sCAClC;;wBAEFsE,KAAKX,KAAK,CAACJ,MAAM,KAAK,kBACrB,KAACvH;4BAAW+D,SAAQ;4BAAQC,OAAM;sCAC/B;2CAGH,KAAC0D;4BAAgBC,OAAOW,KAAKX,KAAK;;;;8BAKtC,MAAChI;oBAAM0D,SAAS;;sCACd,KAACrD;4BAAW+D,SAAQ;4BAAWC,OAAM;sCAClC;;sCAEH,MAACvE;4BAAM2E,UAAU/B,WAAWiG,KAAKhG,MAAM;;gCACpC,CAAC,MAAM,EAAE3B,IAAI2H,KAAKhG,MAAM,CAACuI,QAAQ,EAAE,SAAS,EAAElK,IAAI2H,KAAKhG,MAAM,CAACwI,UAAU,EAAE,MAAM,CAAC,GAChF,GAAGnK,IAAI2H,KAAKhG,MAAM,CAACyI,eAAe,EAAE,SAAS,EAAEpK,IAAI2H,KAAKhG,MAAM,CAAC0I,kBAAkB,EAAE,oBAAoB,CAAC,GACxG,GAAGrK,IAAI2H,KAAKhG,MAAM,CAAC2I,iBAAiB,EAAE,SAAS,CAAC;gCACjD3C,KAAKhG,MAAM,CAACC,UAAU,GACnB,4DACA;gCACH+F,KAAKhG,MAAM,CAACE,QAAQ,IAAI,IACrB,CAAC,CAAC,EAAE8F,KAAKhG,MAAM,CAACE,QAAQ,CAAC,MAAM,EAAE7B,IAAI2H,KAAKhG,MAAM,CAAC4I,YAAY,EAAE,wBAAwB,CAAC,GACxF;;;wBAEL5C,KAAKhG,MAAM,CAAC6I,YAAY,CAACC,SAAS,KAAK,qBACtC,KAACpL;4BAAW+D,SAAQ;4BAAUC,OAAM;sCACjC,CAAC,8BAA8B,EAAExD,KAAKC,KAAK,CAAC6H,KAAKhG,MAAM,CAAC6I,YAAY,CAACC,SAAS,GAAG,KAAK,SAAS,EAAE9C,KAAKhG,MAAM,CAAC6I,YAAY,CAAChB,KAAK,CAAC,mCAAmC,CAAC;6BAErK;;;8BAIN,MAACxK;oBAAM0D,SAAS;;sCACd,KAACrD;4BAAW+D,SAAQ;4BAAWC,OAAM;sCAClC;;sCAEH,MAAChE;4BAAW+D,SAAQ;;gCACjB;8CACD,KAAC/E;oCAAQyH,MAAM7H,WAAWC,MAAMwM,UAAU;oCAAGzE,WAAU;8CACpD;;gCAEF;8CACD,KAAC5H;oCAAQyH,MAAM7H,WAAWC,MAAMyM,cAAc;oCAAG1E,WAAU;8CACxD;;gCAEF;;;;;;;;AAOf;AACAwB,eAAemD,WAAW,GAAG;AAE7B,eAAenD,eAAc"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/staff-org-ai-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { aiAddonName } from '@aglyn/aglyn'\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiOpenInNew } from '@aglyn/shared-data-mdi'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Alert,\n Chip,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport type {\n StaffOrgAiJob,\n StaffOrgAiJobs,\n StaffOrgAiMargin,\n StaffOrgAiOverage,\n StaffOrgAiPool,\n StaffOrgAiRefusals,\n StaffOrgAiResponse,\n StaffOrgAiTokens,\n StaffOrgAiUser,\n} from '../usage/staff-org-ai'\n\n/**\n * THE STAFF AI CARD (AGL-2930): everything about one org's AI usage on the\n * page staff already open to look at that org.\n *\n * Read through `/api/ai/admin/org`, one request on mount. Every figure on it\n * is the route's, and every figure the route serves is composed from the\n * same helpers the meter refuses with — see `usage/staff-org-ai.ts`. This\n * file only lays them out.\n *\n * ## What each section is for\n *\n * - **Add-on** — is the org paying for AI, at what, since when.\n * - **Credit pool** — the band as three NAMED parts, because \"why does\n * this org have 13,000 credits\" has three answers (the plan, an override,\n * the add-on) and a single total cannot say which.\n * - **Overage** — what is past the band, what it is sold at, and the two\n * controls the org itself holds over it.\n * - **Refusals** — how often, and why, the gate said no this month.\n * - **Jobs** and **Top users** — the generative and per-person halves,\n * each of which says plainly when its rollup has nothing yet.\n * - **Tokens** — what each kind of request costs per request, what it\n * sends and generates, and how much of its prompt the cache served.\n * - **Margin** — spend against what AI brings in, red when it is over.\n * - **Actions** — where the override editor and the AI pause live.\n */\n\nconst credits = (value: number | null | undefined): string =>\n value == null ? '—' : Math.round(value).toLocaleString()\n\n/**\n * Four decimals under a dollar, two above (the assist-signals page's rule):\n * a month that really cost eight cents must not read as `$0.00`.\n */\nconst usd = (value: number | null | undefined): string => {\n if (value == null) return '—'\n const amount = Number(value) || 0\n return `$${amount.toFixed(Math.abs(amount) < 1 ? 4 : 2)}`\n}\n\nconst when = (iso: string | null): string =>\n iso ? new Date(iso).toLocaleDateString() : '—'\n\n/** The pool as one sentence naming every part that is non-zero. */\nexport function poolPartsSentence(pool: StaffOrgAiPool): string {\n const parts: string[] = []\n if (pool.overrideCredits !== null) {\n parts.push(`${credits(pool.overrideCredits)} staff override`)\n } else {\n parts.push(`${credits(pool.planCredits)} plan band`)\n }\n if (pool.addonCredits > 0) {\n parts.push(`${credits(pool.addonCredits)} ${aiAddonName()} add-on`)\n }\n return parts.join(' + ')\n}\n\n/**\n * The overage's state as one line: sold, walled, or capped — or, for an\n * uncapped staff comp (AGL-3049), nothing to sell past at all.\n */\nexport function overageStateSentence(\n overage: StaffOrgAiOverage,\n uncapped = false,\n): string {\n if (uncapped) {\n return 'Nothing is sold past a band: an uncapped staff comp has none, and bills nothing.'\n }\n if (overage.hardCap) return 'Stopped at the band — the org’s band switch is on.'\n if (!overage.sellsOverage) return 'Not sold past the band on this plan.'\n const rate =\n overage.rateUsdPer1k === null ? '' : ` at ${usd(overage.rateUsdPer1k)} per 1,000`\n if (overage.capUsd !== null) {\n return overage.capReached\n ? `Stopped — the org’s ${usd(overage.capUsd)} overage ceiling is reached.`\n : `Sold${rate}, up to the org’s ${usd(overage.capUsd)} ceiling.`\n }\n return `Sold${rate}, no ceiling set.`\n}\n\n/** Whether the margin section renders red. */\nexport function marginTone(margin: StaffOrgAiMargin): 'error' | 'warning' | 'success' {\n if (margin.underwater) return 'error'\n if (margin.multiple >= 1) return 'warning'\n return 'success'\n}\n\nfunction RefusalsRow({ refusals }: { refusals: StaffOrgAiRefusals }) {\n const labels: Record<keyof Omit<StaffOrgAiRefusals, 'total'>, string> = {\n band: 'band',\n cap: 'ceiling',\n messages: 'messages',\n budget: 'operator backstop',\n // A hard allotment a manager set on a member, a collaborator or a site\n // (AGL-2942).\n allotment: 'allotments',\n // The Free taste's rungs (AGL-2925): only a Free workspace collects these.\n account: 'free account',\n requests: 'free requests/day',\n refusals: 'free refusals/day',\n platform: 'platform ceiling',\n }\n return (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {(Object.keys(labels) as Array<keyof typeof labels>).map((reason) => {\n // A reason the answering server does not yet count reads as zero,\n // so a console newer than its route draws the row rather than\n // failing the card.\n const count = Number(refusals[reason] ?? 0)\n return (\n <Chip\n key={reason}\n size=\"small\"\n variant={count > 0 ? 'filled' : 'outlined'}\n color={count > 0 ? 'warning' : 'default'}\n label={`${labels[reason]} ${count.toLocaleString()}`}\n />\n )\n })}\n </Stack>\n )\n}\n\nfunction JobsSection({ jobs }: { jobs: StaffOrgAiJobs | null }) {\n if (jobs === null) {\n return (\n <Alert severity=\"warning\">\n {'Could not read the generation jobs — a failed read, not an idle org.'}\n </Alert>\n )\n }\n const total = Object.values(jobs.counts).reduce((sum, n) => sum + n, 0)\n if (total === 0) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No generation jobs yet.'}\n </Typography>\n )\n }\n return (\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {(['queued', 'running', 'needs_input', 'needs_review', 'failed'] as const).map((status) => (\n <Chip\n key={status}\n size=\"small\"\n color={\n status === 'failed' && jobs.counts[status] > 0\n ? 'error'\n : (status === 'needs_input' || status === 'needs_review') &&\n jobs.counts[status] > 0\n ? 'warning'\n : 'default'\n }\n label={`${status.replace('_', ' ')} ${jobs.counts[status]}`}\n />\n ))}\n <Chip size=\"small\" variant=\"outlined\" label={`done ${jobs.counts.done}`} />\n </Stack>\n {jobs.truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Counted from the newest jobs only — these counts are a floor.'}\n </Typography>\n ) : null}\n {/* A record list: the grid scrolls its own columns, so a job id and a\n starter's uid never push the card's content past its edge. */}\n <ListTable\n rows={jobs.recent}\n columns={JOB_COLUMNS}\n getRowId={(job: StaffOrgAiJob) => job.id}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No recent jobs\"\n // The route's ten newest jobs: a bounded readout, so no filter over\n // the rows it holds (AGL-3321).\n disableColumnFilter\n quickFilter={false}\n />\n </Stack>\n )\n}\n\n/**\n * The recent jobs, one column per fact. Who started a job is a column of\n * its own rather than a link tucked beside the date, so it sorts like the\n * rest; the credits sort by what was SPENT, which is the figure the\n * pool was charged.\n */\nconst JOB_COLUMNS: GridColDef<StaffOrgAiJob>[] = [\n {\n field: 'id',\n headerName: 'Job',\n flex: 1,\n minWidth: 200,\n renderCell: ({ row }) => (\n <Typography variant=\"body2\" noWrap title={row.id} sx={{ fontFamily: 'monospace' }}>\n {row.id}\n </Typography>\n ),\n },\n {\n field: 'kind',\n headerName: 'Kind',\n width: 120,\n valueFormatter: (value: string) => value || '—',\n },\n {\n field: 'status',\n headerName: 'Status',\n width: 130,\n valueFormatter: (value: string) => String(value ?? '').replace('_', ' '),\n },\n {\n field: 'creditsSpent',\n headerName: 'Credits',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 140,\n valueFormatter: (value: number, row) =>\n `${credits(value)} / ${credits(row.creditsReserved)}`,\n },\n {\n field: 'createdAt',\n headerName: 'Started',\n width: 120,\n valueFormatter: (value: string | null) => when(value),\n },\n {\n field: 'createdBy',\n headerName: 'Started by',\n flex: 1,\n minWidth: 240,\n renderCell: ({ row }) =>\n row.createdBy ? (\n <AppLink\n href={buildRoute(Route.ADMIN_USER_DETAIL, { uid: row.createdBy })}\n variant=\"body2\"\n underline=\"hover\"\n noWrap\n sx={{ fontFamily: 'monospace' }}\n >\n {row.createdBy}\n </AppLink>\n ) : (\n '—'\n ),\n },\n]\n\nconst tokenCount = (value: number): string => Math.round(value).toLocaleString()\n\nconst rate = (value: number | null): string =>\n value == null ? '—' : `${Math.round(value * 100)}%`\n\n/** The month's tokens, and what each kind of request spends of them (AGL-2937). */\nfunction TokensSection({ tokens }: { tokens: StaffOrgAiTokens }) {\n const { total } = tokens\n return (\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Typography variant=\"body2\">\n {`${tokenCount(total.input)} sent · ${tokenCount(total.cached)} read from cache · ${tokenCount(total.cacheWrite)} written to cache · ${tokenCount(total.output)} generated`}\n </Typography>\n <Chip\n size=\"small\"\n color={tokens.cacheHitRate != null && tokens.cacheHitRate >= 0.5 ? 'success' : 'default'}\n label={`cache hit rate ${rate(tokens.cacheHitRate)}`}\n />\n </Stack>\n {tokens.kinds.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No model requests recorded by kind this month.'}\n </Typography>\n ) : (\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Kind'}</TableCell>\n <TableCell align=\"right\">{'Requests'}</TableCell>\n <TableCell align=\"right\">{'Per request'}</TableCell>\n <TableCell align=\"right\">{'Sent'}</TableCell>\n <TableCell align=\"right\">{'From cache'}</TableCell>\n <TableCell align=\"right\">{'Generated'}</TableCell>\n <TableCell align=\"right\">{'Cache hit'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {tokens.kinds.map((row) => (\n <TableRow key={row.kind}>\n <TableCell>{row.kind}</TableCell>\n <TableCell align=\"right\">{row.requests.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{usd(row.costPerRequestUsd)}</TableCell>\n <TableCell align=\"right\">{tokenCount(row.tokens.input)}</TableCell>\n <TableCell align=\"right\">{tokenCount(row.tokens.cached)}</TableCell>\n <TableCell align=\"right\">{tokenCount(row.tokens.output)}</TableCell>\n <TableCell align=\"right\">{rate(row.cacheHitRate)}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n )}\n </Stack>\n )\n}\n\n/**\n * The month's top spenders, one row per person. The row opens the account;\n * the uid rides beneath the roster's name because two people can share one.\n */\nfunction TopUsersSection({ users }: { users: StaffOrgAiUser[] }) {\n const router = useRouter()\n const columns = useMemo<GridColDef<StaffOrgAiUser>[]>(\n () => [\n {\n field: 'name',\n headerName: 'Person',\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.name !== row.uid ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n noWrap\n sx={{ fontFamily: 'monospace' }}\n >\n {row.uid}\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) => credits(value),\n },\n {\n field: 'share',\n headerName: 'Share',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 90,\n valueFormatter: (value: number) => `${Math.round(value * 100)}%`,\n },\n {\n field: 'estCostUsd',\n headerName: 'Provider $',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 120,\n valueFormatter: (value: number) => usd(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 listActionsColumn((row: StaffOrgAiUser) => (\n <ListRowActions\n label={row.name}\n quick={{\n icon: mdiOpenInNew.path,\n label: 'View account',\n to: buildRoute(Route.ADMIN_USER_DETAIL, { uid: row.uid }),\n }}\n items={[]}\n />\n )),\n ],\n [],\n )\n return (\n <ListTable\n rows={users}\n columns={columns}\n getRowId={(row: StaffOrgAiUser) => row.uid}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(uid) => router.push(buildRoute(Route.ADMIN_USER_DETAIL, { uid }))}\n // The route's ten dearest members: a bounded readout, so no filter\n // over the rows it holds (AGL-3321).\n disableColumnFilter\n quickFilter={false}\n />\n )\n}\n\nconst StaffOrgAiCard = ({ orgId }: { orgId: string }) => {\n const { data: user } = useUser()\n // Keyed on who is signed in, not on the user object's identity.\n const signedInUid = user?.uid ?? null\n const userRef = useRef(user)\n userRef.current = user\n const [data, setData] = useState<StaffOrgAiResponse | null>(null)\n const [ready, setReady] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n useEffect(() => {\n // Mounted only for a confirmed staff reader — the `staffOrg` zone sits\n // inside the page's `StaffOnly`, which renders nothing while the claim\n // loads — because the route records an access row on every successful\n // open, and an open the page did not mean to make would be an access\n // nobody made.\n const user = userRef.current\n if (!orgId || !signedInUid || !user) return undefined\n let active = true\n setReady(false)\n setError(null)\n void (async () => {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/admin/org?orgId=${encodeURIComponent(orgId)}`,\n )\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n setError(payload?.error ?? 'AI lookup failed')\n setData(null)\n } else {\n setData(payload as StaffOrgAiResponse)\n }\n } catch {\n if (active) {\n setError('AI lookup failed')\n setData(null)\n }\n } finally {\n if (active) setReady(true)\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, signedInUid])\n\n const name = aiAddonName()\n return (\n <CardDisplay\n header={name}\n help={pluginDocsHelp('aiMonitoring', {\n anchor: '#the-ai-card',\n excerpt:\n 'The add-on, the credit pool and its parts, this month’s overage and refusals, generation jobs, tokens and the cache hit rate by kind, the people spending the most, and the margin — for this organization.',\n })}\n contentGutterX\n contentGutterY\n >\n {!ready ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : error || !data ? (\n <Alert severity=\"warning\">\n {`Could not read this organization’s AI usage — ${error ?? 'a failed read'}, not zero usage.`}\n </Alert>\n ) : (\n <Stack spacing={2}>\n {/* Add-on */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Add-on'}\n </Typography>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Chip\n size=\"small\"\n color={data.addon.on ? 'primary' : 'default'}\n label={data.addon.on ? `${name} on` : `${name} off`}\n />\n {data.addon.priceUsd !== null ? (\n <Typography variant=\"body2\">\n {`${usd(data.addon.priceUsd)}/mo on this plan`}\n </Typography>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Not sold on this plan'}\n </Typography>\n )}\n {data.addon.on ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {data.addon.since\n ? `since ${when(data.addon.since)}`\n : data.addon.sinceSource === 'unavailable'\n ? 'since — (Stripe not reachable)'\n : 'since — (no subscription item)'}\n </Typography>\n ) : null}\n </Stack>\n </Stack>\n\n {/* Credit pool */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {`Credit pool · ${data.month}`}\n </Typography>\n {data.pool.totalCredits === null && data.pool.uncapped ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Uncapped staff comp — no AI credit band. Bounded only by the message cap and an operator’s explicit spend ceiling.'}\n </Typography>\n ) : data.pool.totalCredits === null ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No AI credit band on this plan — bounded by the message cap and the operator backstop.'}\n </Typography>\n ) : (\n <Typography variant=\"body2\">\n {`${credits(data.pool.totalCredits)} credits = ${poolPartsSentence(data.pool)}`}\n </Typography>\n )}\n <Typography variant=\"body2\">\n {`Used ${credits(data.pool.usedCredits)} credits (${usd(data.pool.providerUsd)} provider spend)`}\n {data.pool.remainingCredits !== null\n ? ` · ${credits(data.pool.remainingCredits)} remaining`\n : ''}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Projected month-end at this pace: ${credits(data.pool.projectedCredits)} credits (${usd(data.pool.projectedUsd)}) · ${data.pool.messages.toLocaleString()} model turns, ${data.pool.deflected.toLocaleString()} answered from the docs`}\n </Typography>\n </Stack>\n\n {/* Overage */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Overage'}\n </Typography>\n <Typography variant=\"body2\">\n {`${credits(data.overage.overageCredits)} credits over the band · ${usd(data.overage.accruedUsd)} accrued`}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {overageStateSentence(data.overage, data.pool.uncapped === true)}\n </Typography>\n </Stack>\n\n {/* Refusals */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {`Refusals this month · ${data.refusals.total.toLocaleString()}`}\n </Typography>\n <RefusalsRow refusals={data.refusals} />\n </Stack>\n\n {/* Jobs */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Generation jobs'}\n </Typography>\n <JobsSection jobs={data.jobs} />\n </Stack>\n\n {/* Tokens — absent from a route older than the card */}\n {data.tokens ? (\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {`Tokens · ${data.month}`}\n </Typography>\n <TokensSection tokens={data.tokens} />\n </Stack>\n ) : null}\n\n {/* Top users */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Top users this month'}\n </Typography>\n {data.users.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No AI usage attributed to a person this month.'}\n </Typography>\n ) : (\n <TopUsersSection users={data.users} />\n )}\n </Stack>\n\n {/* Margin */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Margin'}\n </Typography>\n <Alert severity={marginTone(data.margin)}>\n {`Spend ${usd(data.margin.spendUsd)} against ${usd(data.margin.revenueUsd)} in — ` +\n `${usd(data.margin.addonRevenueUsd)} add-on, ${usd(data.margin.planAssistShareUsd)} plan assist share, ` +\n `${usd(data.margin.overageRevenueUsd)} overage.`}\n {data.margin.underwater\n ? ' Spend exceeds the add-on plus the plan’s assist share.'\n : ''}\n {data.margin.multiple >= 1\n ? ` ${data.margin.multiple}× the ${usd(data.margin.thresholdUsd)} staff review threshold.`\n : ''}\n </Alert>\n {data.margin.contribution.marginPct !== null ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Whole-org contribution margin ${Math.round(data.margin.contribution.marginPct * 100)}% on the ${data.margin.contribution.month} rollup with this month’s AI spend.`}\n </Typography>\n ) : null}\n </Stack>\n\n {/* Actions */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Actions'}\n </Typography>\n <Typography variant=\"body2\">\n {'Credit override and feature toggles: '}\n <AppLink href={buildRoute(Route.ADMIN_ORGS)} underline=\"hover\">\n {'the entitlement override editor'}\n </AppLink>\n {' (also under Staff actions above). Pause AI for this org: '}\n <AppLink href={buildRoute(Route.ADMIN_LOCKDOWN)} underline=\"hover\">\n {'Lockdown'}\n </AppLink>\n {', feature keys ai-assist and ai-generate scoped to the org.'}\n </Typography>\n </Stack>\n </Stack>\n )}\n </CardDisplay>\n )\n}\nStaffOrgAiCard.displayName = 'StaffOrgAiCard'\n\nexport default StaffOrgAiCard\n"],"names":["aiAddonName","buildRoute","Route","pluginDocsHelp","mdiOpenInNew","AppLink","CardDisplay","ListRowActions","ListTable","listActionsColumn","ScrollTable","TABLE_ROW_HEIGHT","useUser","authorizedFetch","Alert","Chip","Stack","TableBody","TableCell","TableHead","TableRow","Typography","useRouter","useEffect","useMemo","useRef","useState","credits","value","Math","round","toLocaleString","usd","amount","Number","toFixed","abs","when","iso","Date","toLocaleDateString","poolPartsSentence","pool","parts","overrideCredits","push","planCredits","addonCredits","join","overageStateSentence","overage","uncapped","hardCap","sellsOverage","rate","rateUsdPer1k","capUsd","capReached","marginTone","margin","underwater","multiple","RefusalsRow","refusals","labels","band","cap","messages","budget","allotment","account","requests","platform","direction","spacing","useFlexGap","sx","flexWrap","Object","keys","map","reason","count","size","variant","color","label","JobsSection","jobs","severity","total","values","counts","reduce","sum","n","status","replace","done","truncated","rows","recent","columns","JOB_COLUMNS","getRowId","job","id","rowHeight","noRowsLabel","disableColumnFilter","quickFilter","field","headerName","flex","minWidth","renderCell","row","noWrap","title","fontFamily","width","valueFormatter","String","type","align","headerAlign","creditsReserved","createdBy","href","ADMIN_USER_DETAIL","uid","underline","tokenCount","TokensSection","tokens","alignItems","input","cached","cacheWrite","output","cacheHitRate","kinds","length","kind","costPerRequestUsd","TopUsersSection","users","router","name","quick","icon","path","to","items","onOpen","StaffOrgAiCard","orgId","data","user","signedInUid","userRef","current","setData","ready","setReady","error","setError","undefined","active","response","encodeURIComponent","payload","json","catch","ok","header","help","anchor","excerpt","contentGutterX","contentGutterY","addon","on","priceUsd","since","sinceSource","month","totalCredits","usedCredits","providerUsd","remainingCredits","projectedCredits","projectedUsd","deflected","overageCredits","accruedUsd","spendUsd","revenueUsd","addonRevenueUsd","planAssistShareUsd","overageRevenueUsd","thresholdUsd","contribution","marginPct","ADMIN_ORGS","ADMIN_LOCKDOWN","displayName"],"mappings":"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"}
|