@aglyn/plugins-ai 1.0.0-beta.231 → 1.0.0-beta.232
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/activity/ai-activity-actions.js +8 -4
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/components/ai-job-follow.component.js +1 -0
- package/src/lib/components/ai-job-follow.component.js.map +1 -1
- package/src/lib/components/ai-job-links.js +13 -0
- package/src/lib/components/ai-job-links.js.map +1 -1
- package/src/lib/components/ai-jobs-list-page.component.js +1 -0
- package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
- package/src/lib/components/ai-media-create.component.d.ts +20 -7
- package/src/lib/components/ai-media-create.component.js +107 -55
- package/src/lib/components/ai-media-create.component.js.map +1 -1
- package/src/lib/components/ai-seo-fields-card.component.js +1 -0
- package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
- package/src/lib/components/ai-site-memory-card.component.d.ts +32 -0
- package/src/lib/components/ai-site-memory-card.component.js +187 -0
- package/src/lib/components/ai-site-memory-card.component.js.map +1 -0
- package/src/lib/components/ai-site-start-card.component.js +80 -77
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-theme-proposal-card.component.js +4 -0
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/components/ai-usage-strip.component.js +1 -0
- package/src/lib/components/ai-usage-strip.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.d.ts +10 -0
- package/src/lib/components/assist-panel.component.js +119 -9
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/assist-signals-page.component.js +2 -0
- package/src/lib/components/assist-signals-page.component.js.map +1 -1
- package/src/lib/components/billing-ai-allotments.component.js +3 -0
- package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
- package/src/lib/components/billing-ai-top-users.component.js +1 -0
- package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
- package/src/lib/components/billing-assist-overage-card.component.js +2 -0
- package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
- package/src/lib/components/member-ai-usage-card.component.js +10 -0
- package/src/lib/components/member-ai-usage-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-card.component.js +1 -0
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
- package/src/lib/components/staff-org-ai-conversations.component.d.ts +23 -0
- package/src/lib/components/staff-org-ai-conversations.component.js +346 -0
- package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -0
- package/src/lib/jobs/ai-build-capabilities.d.ts +13 -1
- package/src/lib/jobs/ai-build-capabilities.js +101 -7
- package/src/lib/jobs/ai-build-capabilities.js.map +1 -1
- package/src/lib/jobs/ai-business-profile-prefill.d.ts +29 -0
- package/src/lib/jobs/ai-business-profile-prefill.js +85 -0
- package/src/lib/jobs/ai-business-profile-prefill.js.map +1 -0
- package/src/lib/jobs/ai-job-build-step.js +3 -1
- package/src/lib/jobs/ai-job-build-step.js.map +1 -1
- package/src/lib/jobs/ai-job-edit-step.d.ts +225 -0
- package/src/lib/jobs/ai-job-edit-step.js +631 -0
- package/src/lib/jobs/ai-job-edit-step.js.map +1 -0
- package/src/lib/jobs/ai-job-insight-step.js +21 -4
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-language.d.ts +8 -3
- package/src/lib/jobs/ai-job-layout-language.js +8 -2
- package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.js +7 -6
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-logic-step.d.ts +20 -0
- package/src/lib/jobs/ai-job-logic-step.js +87 -6
- package/src/lib/jobs/ai-job-logic-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +52 -7
- package/src/lib/jobs/ai-job-page-language.js +72 -9
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +12 -2
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +19 -5
- package/src/lib/jobs/ai-job-plan-step.js +54 -20
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-content.d.ts +163 -0
- package/src/lib/jobs/ai-job-site-content.js +443 -0
- package/src/lib/jobs/ai-job-site-content.js.map +1 -0
- package/src/lib/jobs/ai-job-site-look.d.ts +112 -0
- package/src/lib/jobs/ai-job-site-look.js +322 -0
- package/src/lib/jobs/ai-job-site-look.js.map +1 -0
- package/src/lib/jobs/ai-job-site-step.d.ts +26 -5
- package/src/lib/jobs/ai-job-site-step.js +163 -24
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +6 -1
- package/src/lib/jobs/ai-jobs-notify.js +15 -3
- package/src/lib/jobs/ai-jobs-notify.js.map +1 -1
- package/src/lib/layout-language/ai-layout-compiler.d.ts +9 -0
- package/src/lib/layout-language/ai-layout-compiler.js +120 -33
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-frame.js +5 -4
- package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
- package/src/lib/layout-language/ai-layout-language.d.ts +12 -5
- package/src/lib/layout-language/ai-layout-language.js +92 -9
- package/src/lib/layout-language/ai-layout-language.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +144 -0
- package/src/lib/layout-language/ai-layout-pictures.js +214 -0
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -0
- package/src/lib/layout-language/ai-layout-store.d.ts +2 -0
- package/src/lib/layout-language/ai-layout-store.js +7 -1
- package/src/lib/layout-language/ai-layout-store.js.map +1 -1
- package/src/lib/model/ai-build-progress.js +4 -1
- package/src/lib/model/ai-build-progress.js.map +1 -1
- package/src/lib/model/ai-edit-job.d.ts +109 -0
- package/src/lib/model/ai-edit-job.js +157 -0
- package/src/lib/model/ai-edit-job.js.map +1 -0
- package/src/lib/model/ai-job-activity.js +1 -0
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-jobs.types.d.ts +3 -1
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-logic-job.d.ts +18 -1
- package/src/lib/model/ai-logic-job.js +24 -3
- package/src/lib/model/ai-logic-job.js.map +1 -1
- package/src/lib/model/ai-media-credits.d.ts +8 -4
- package/src/lib/model/ai-media-credits.js +11 -4
- package/src/lib/model/ai-media-credits.js.map +1 -1
- package/src/lib/model/ai-media-kinds.d.ts +68 -0
- package/src/lib/model/ai-media-kinds.js +98 -0
- package/src/lib/model/ai-media-kinds.js.map +1 -0
- package/src/lib/model/ai-site-build-progress.d.ts +4 -0
- package/src/lib/model/ai-site-build-progress.js +28 -11
- package/src/lib/model/ai-site-build-progress.js.map +1 -1
- package/src/lib/model/ai-site-context.d.ts +111 -0
- package/src/lib/model/ai-site-context.js +222 -0
- package/src/lib/model/ai-site-context.js.map +1 -0
- package/src/lib/model/ai-site-job.d.ts +10 -2
- package/src/lib/model/ai-site-job.js +12 -5
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.d.ts +164 -0
- package/src/lib/model/ai-site-kinds.js +1942 -0
- package/src/lib/model/ai-site-kinds.js.map +1 -0
- package/src/lib/model/ai-site-look.d.ts +150 -0
- package/src/lib/model/ai-site-look.js +1236 -0
- package/src/lib/model/ai-site-look.js.map +1 -0
- package/src/lib/model/ai-site-memory.d.ts +80 -0
- package/src/lib/model/ai-site-memory.js +171 -0
- package/src/lib/model/ai-site-memory.js.map +1 -0
- package/src/lib/model/ai-site-start.d.ts +10 -1
- package/src/lib/model/ai-site-start.js +16 -2
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/model/assist-follow-up.d.ts +92 -0
- package/src/lib/model/assist-follow-up.js +128 -0
- package/src/lib/model/assist-follow-up.js.map +1 -0
- package/src/lib/plugin-config.js +2 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +20 -1
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/anthropic.d.ts +9 -1
- package/src/lib/providers/anthropic.js +143 -9
- package/src/lib/providers/anthropic.js.map +1 -1
- package/src/lib/providers/catalog.d.ts +37 -7
- package/src/lib/providers/catalog.js +103 -11
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/contract.d.ts +29 -0
- package/src/lib/providers/contract.js.map +1 -1
- package/src/lib/providers/image-contract.d.ts +22 -1
- package/src/lib/providers/image-contract.js +40 -1
- package/src/lib/providers/image-contract.js.map +1 -1
- package/src/lib/providers/image-endpoints.d.ts +24 -0
- package/src/lib/providers/image-endpoints.js +28 -0
- package/src/lib/providers/image-endpoints.js.map +1 -0
- package/src/lib/providers/model-choice.js +23 -2
- package/src/lib/providers/model-choice.js.map +1 -1
- package/src/lib/providers/routing.js +18 -0
- package/src/lib/providers/routing.js.map +1 -1
- package/src/lib/providers/vertex-image-endpoint.d.ts +48 -0
- package/src/lib/providers/vertex-image-endpoint.js +80 -0
- package/src/lib/providers/vertex-image-endpoint.js.map +1 -0
- package/src/lib/providers/vertex-image.d.ts +13 -27
- package/src/lib/providers/vertex-image.js +15 -42
- package/src/lib/providers/vertex-image.js.map +1 -1
- package/src/lib/runtime/ai-blog-post-generation.d.ts +62 -0
- package/src/lib/runtime/ai-blog-post-generation.js +89 -0
- package/src/lib/runtime/ai-blog-post-generation.js.map +1 -0
- package/src/lib/runtime/ai-dev-env.d.ts +53 -0
- package/src/lib/runtime/ai-dev-env.js +72 -0
- package/src/lib/runtime/ai-dev-env.js.map +1 -0
- package/src/lib/runtime/ai-dev-replay.d.ts +116 -0
- package/src/lib/runtime/ai-dev-replay.js +313 -0
- package/src/lib/runtime/ai-dev-replay.js.map +1 -0
- package/src/lib/runtime/ai-doctrine.js +5 -1
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +4 -0
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +13 -1
- package/src/lib/runtime/ai-eval.js +84 -1
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-live-batch.d.ts +45 -0
- package/src/lib/runtime/ai-live-batch.js +89 -0
- package/src/lib/runtime/ai-live-batch.js.map +1 -0
- package/src/lib/runtime/ai-runtime.js +46 -2
- package/src/lib/runtime/ai-runtime.js.map +1 -1
- package/src/lib/runtime/site-context.d.ts +58 -0
- package/src/lib/runtime/site-context.js +127 -0
- package/src/lib/runtime/site-context.js.map +1 -0
- package/src/lib/server/ai-admin-conversations.d.ts +22 -0
- package/src/lib/server/ai-admin-conversations.js +197 -0
- package/src/lib/server/ai-admin-conversations.js.map +1 -0
- package/src/lib/server/ai-media-image.d.ts +5 -5
- package/src/lib/server/ai-media-image.js +25 -11
- package/src/lib/server/ai-media-image.js.map +1 -1
- package/src/lib/server/ai-media-raster-prompt.d.ts +34 -0
- package/src/lib/server/ai-media-raster-prompt.js +53 -0
- package/src/lib/server/ai-media-raster-prompt.js.map +1 -0
- package/src/lib/server/assist-chat.d.ts +11 -0
- package/src/lib/server/assist-chat.js +57 -18
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-edit-applied.js +16 -3
- package/src/lib/server/assist-edit-applied.js.map +1 -1
- package/src/lib/server/assist-view-context.d.ts +19 -1
- package/src/lib/server/assist-view-context.js +38 -1
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server.js +10 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/subprocessors.js +17 -1
- package/src/lib/subprocessors.js.map +1 -1
- package/src/lib/tools/ai-blog-post-tool.d.ts +60 -0
- package/src/lib/tools/ai-blog-post-tool.js +175 -0
- package/src/lib/tools/ai-blog-post-tool.js.map +1 -0
- package/src/lib/usage/assist-usage.d.ts +3 -0
- package/src/lib/usage/assist-usage.js +4 -2
- package/src/lib/usage/assist-usage.js.map +1 -1
- package/src/lib/usage/staff-org-ai-conversations.d.ts +103 -0
- package/src/lib/usage/staff-org-ai-conversations.js +152 -0
- package/src/lib/usage/staff-org-ai-conversations.js.map +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/assist-signals-page.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, type ConsoleStaffPageProps } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport {\n Accordion,\n AccordionDetails,\n AccordionSummary,\n Alert,\n Box,\n Chip,\n LinearProgress,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Typography,\n} from '@mui/material'\nimport { useEffect, useRef, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport type {\n AssistMiningReport,\n AssistSpendRow,\n} from '../usage/assist-signal-mining'\nimport {\n costSplitRows,\n freeTasteRefusals,\n kindTokenRows,\n} from '../usage/assist-signal-mining'\n\n/** The month leaderboard as the route serves it beside the report. */\ninterface AssistSpendLeaderboard {\n month: string\n rows: AssistSpendRow[]\n ranked: number\n truncated: boolean\n failed: boolean\n}\n\n/**\n * The Assist docs-gap and cost board (AGL-1860, AGL-2252) — the read side of\n * the data loop, and until now the half that did not exist.\n *\n * AGL-1972 split every assist turn into prose that expires and a signal that\n * does not, on the reasoning that \"the corpus the loop needs outlives the\n * words that produced it\". That is only true once something reads the corpus.\n * This is that something.\n *\n * Four panels, in the order the questions actually get asked:\n *\n * 1. **Docs gaps** — cited pages ranked by thumbs-down, then volume. A page\n * high on this list is being found and is not answering, which is a docs\n * issue with its evidence attached. The `orgs` column is the sanity\n * check: a gap one workspace has is a workspace, not a gap.\n * 2. **Ungrounded questions** — turns where retrieval matched nothing at\n * all, grouped by the screen the person was on. These cite no page, so\n * they can never appear in the ranking above; a missing page is invisible\n * to anything keyed on paths, and this panel is the only place it shows.\n * 3. **Cost** — per org, dearest first, plus the fleet totals and the\n * cache-read rate. The rate is not decoration: the cached system prefix\n * measures 1,030–1,190 tokens against Sonnet 5's 1,024-token minimum, so\n * whether it caches is an empirical question the chat route could only\n * pose, and this is the evidence.\n * 4. **Tokens by kind** — what each kind of model request costs per\n * request, what it sends and generates, how much of its prompt the cache\n * served, and the p95 output its `max_tokens` is sized against\n * (AGL-2937).\n *\n * Read through `/api/ai/admin/signals` rather than Firestore directly.\n * `assistSignals` is absent from the rules file on purpose — default-deny for\n * every client — which is exactly what lets it hold cross-tenant analytics.\n *\n * The AI plugin's staff page (AGL-2939): the console's generic staff route\n * draws the header, the breadcrumbs and the staff gate around this body, and\n * gives it the tab it had.\n */\n\nconst money = (value: number) =>\n `$${(Number(value) || 0).toFixed(Math.abs(Number(value) || 0) < 1 ? 4 : 2)}`\n\nconst percent = (value: number | null) =>\n value == null ? '—' : `${Math.round(value * 100)}%`\n\n/**\n * One cost breakdown — tier or model, dearest first (AGL-2340).\n *\n * The `share` column is why this is a table rather than a row of chips. The\n * decision it feeds is always comparative — \"is free eating the margin\" is a\n * question about proportion, not about a dollar figure — and a reader asked\n * to divide two numbers in their head does not do it.\n *\n * `turns` is kept beside the money on purpose: a tier that is a third of the\n * traffic and two thirds of the bill is the finding, and neither column says\n * that alone.\n *\n * ## Full width, and stacked rather than side by side\n *\n * Both breakdowns split the SAME total, so the reader's act is scanning one\n * against the other: does the tier carrying the turns also carry the money,\n * and would a cheaper model on the common path move it. Two tables squeezed\n * into thirds of a row put each Cost column at a different horizontal\n * position, so that comparison becomes two separate readings held in the\n * head. Stacked full-width, the Cost and Share columns of both land on the\n * same axis and the comparison is a glance down the page.\n *\n * ## Why the caption is not a row of the table\n *\n * It used to be a `colSpan={4}` cell in `TableHead`, which gave it header\n * styling and made the header two rows deep — so this table's head sat at a\n * different weight and height from the four sibling tables on the same page,\n * which is most of what made the card read as a foreign fragment.\n *\n * ## No footer, and why\n *\n * A breakdown has one row per tier and one per model — single digits, and\n * bounded by the vocabulary rather than by a collection that grows. There is\n * no page two to go to, and a pager over four rows is furniture that teaches\n * a reader to look for pages that never exist.\n */\nfunction CostSplitTable({\n caption,\n label,\n rows,\n totalUsd,\n}: {\n caption: string\n label: string\n rows: { key: string; messages: number; providerCostUsd: number }[]\n totalUsd: number\n}) {\n return (\n <Stack spacing={1} sx={{ width: '100%' }}>\n <Typography variant=\"subtitle2\">{caption}</Typography>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{label}</TableCell>\n <TableCell align=\"right\">Turns</TableCell>\n <TableCell align=\"right\">Cost</TableCell>\n <TableCell align=\"right\">Share</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {!rows.length ? (\n <TableRow>\n <TableCell colSpan={4}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n No turns in this sample.\n </Typography>\n </TableCell>\n </TableRow>\n ) : (\n rows.map((row) => (\n <TableRow key={row.key}>\n <TableCell>{row.key}</TableCell>\n <TableCell align=\"right\">\n {row.messages.toLocaleString()}\n </TableCell>\n <TableCell align=\"right\">{money(row.providerCostUsd)}</TableCell>\n <TableCell align=\"right\">\n {percent(totalUsd > 0 ? row.providerCostUsd / totalUsd : null)}\n </TableCell>\n </TableRow>\n ))\n )}\n </TableBody>\n </ScrollTable>\n </Stack>\n )\n}\n\n/**\n * The count line under a RANKED table — how much of the ranking is on screen.\n *\n * These three tables are not paged, and the reason is the same for all of\n * them: each is a ranking whose value is its top. Page two of \"where are the\n * docs thin\" is, by construction, the pages least worth rewriting, and a\n * pager over a ranking invites a reader to walk it as if position carried no\n * meaning. What a pager WOULD have given honestly is the count — how much of\n * this am I looking at, and is there more — so that is what this says, and it\n * says it whether or not the ranking was cut.\n *\n * Rendered even when nothing was cut. A disclosure that appears only on\n * truncation is one a reader has never seen before the day it matters, and\n * cannot tell from a card that simply has no footer.\n */\nfunction RankingFootnote({\n shown,\n total,\n noun,\n}: {\n shown: number\n total: number\n /** Both forms: a fleet of one is the common case on a young install. */\n noun: { one: string; many: string }\n}) {\n if (!total) return null\n const counted = `${total.toLocaleString()} ${total === 1 ? noun.one : noun.many}`\n return (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {shown >= total\n ? `Showing all ${counted}.`\n : `Showing the top ${shown.toLocaleString()} of ${counted}. This is a ranking, not a page — what is past the cut is what it ranked lowest.`}\n </Typography>\n )\n}\n\nexport function AssistSignalsPage(_props: ConsoleStaffPageProps) {\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 [report, setReport] = useState<AssistMiningReport | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [loading, setLoading] = useState(false)\n\n useEffect(() => {\n // Mounted only for a confirmed staff reader: the generic staff route\n // renders this inside `StaffOnly`, which draws nothing while the claim\n // loads, so no request is fired from the loading default.\n const user = userRef.current\n if (!signedInUid || !user) return undefined\n let active = true\n void (async () => {\n setLoading(true)\n setError(null)\n try {\n const response = await authorizedFetch(user, '/api/ai/admin/signals')\n const body = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n setError(body?.error ?? 'Assist signal lookup failed')\n } else {\n setReport(body as AssistMiningReport)\n }\n } catch {\n if (active) setError('Assist signal lookup failed')\n } finally {\n if (active) setLoading(false)\n }\n })()\n return () => {\n active = false\n }\n }, [signedInUid])\n\n const totals = report?.totals\n // The route serves the prose beside the report (AGL-2314) — it is not part\n // of the pure miner's output, because the miner never touches Firestore.\n const prose = ((report as any)?.prose ?? []) as Array<Record<string, unknown>>\n // The month's spend leaderboard (AGL-2930), likewise served beside the\n // report: it reads the month documents, which the miner never sees.\n const spend = ((report as any)?.spend ?? null) as AssistSpendLeaderboard | null\n // Absent from a route older than the page, which reads as no kinds.\n const kindRows = totals?.byKind ? kindTokenRows(totals.byKind) : []\n\n return (\n <Stack spacing={3}>\n {loading && <LinearProgress />}\n {error && <Alert severity=\"error\">{error}</Alert>}\n {report?.truncated && (\n // Never a silent partial. A ranking cut short looks exactly\n // like a complete one, and this one decides where docs effort\n // goes.\n <Alert severity=\"warning\">\n {`Ranked the first ${report.scanned.toLocaleString()} signals only — there are more. Every number below describes that sample, not the fleet.`}\n </Alert>\n )}\n\n <CardDisplay\n header={'Fleet'}\n help={pluginDocsHelp('assistSignals', { anchor: '#fleet' })}\n contentGutterX\n contentGutterY\n >\n {!totals ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {loading ? 'Reading signals…' : 'No assist turns recorded yet.'}\n </Typography>\n ) : (\n // Neither `gap` nor `flexWrap` is a Stack prop under MUI 9\n // — both go through `sx` or it is a typecheck error.\n // AGL-1891 is the same drift, in the Assist panel itself.\n <Stack direction=\"row\" sx={{ flexWrap: 'wrap', gap: 1 }}>\n <Chip label={`${totals.messages.toLocaleString()} turns`} />\n <Chip label={`${money(totals.providerCostUsd)} estimated`} />\n {/*\n * The headline number for whether Assist is affordable to\n * leave on (AGL-2486): the share of turns answered from the\n * docs index or the answer cache, with no model call and no\n * provider spend. Placed beside the money rather than with\n * the token chips because it is the thing that MOVES the\n * money — every point of it is a turn that cost nothing.\n *\n * Success above half, because that is the point at which\n * the cheap path is carrying the feature rather than\n * trimming it.\n */}\n <Chip\n // `?? 0` on the count, and it is not defensive noise: this\n // whole object is JSON off `/api/ai/admin/signals`,\n // and a chip that throws on a missing number takes the\n // ENTIRE staff page down with it rather than rendering a\n // dash. The rate beside it is legitimately nullable —\n // `percent` renders that — so the two are handled\n // differently on purpose.\n label={`answered free ${percent(totals.deflectionRate)} (${(\n totals.deflected ?? 0\n ).toLocaleString()})`}\n color={\n totals.deflectionRate != null && totals.deflectionRate > 0.5\n ? 'success'\n : 'default'\n }\n />\n <Chip\n label={`${totals.inputTokens.toLocaleString()} in / ${totals.outputTokens.toLocaleString()} out`}\n />\n <Chip\n label={`cache reads ${percent(totals.cacheReadRate)}`}\n color={\n totals.cacheReadRate != null && totals.cacheReadRate > 0.5\n ? 'success'\n : 'default'\n }\n />\n {/*\n * Cache WRITES, beside the reads (AGL-2340). The pair only\n * means anything together: a write is billed at a premium\n * over a fresh input token and pays for itself only if\n * enough reads follow it. A board that showed the cheap\n * half alone made a prefix that is re-written every turn —\n * the expensive failure mode — look like a caching win.\n */}\n <Chip\n label={`cache writes ${totals.cacheWriteTokens.toLocaleString()}`}\n />\n <Chip\n label={`👍 ${totals.feedback.up} · 👎 ${totals.feedback.down} · unrated ${totals.feedback.none}`}\n />\n {Object.entries(totals.stopReasons).map(([reason, count]) => (\n <Chip\n key={reason}\n size=\"small\"\n // A refusal and a truncation both read as a short\n // answer in the data and need opposite fixes: a rising\n // refusal rate is a prompt problem, a rising max_tokens\n // rate is a ceiling problem.\n color={\n reason === 'refusal' || reason === 'max_tokens'\n ? 'warning'\n : 'default'\n }\n label={`${reason}: ${count}`}\n />\n ))}\n {/*\n * Today's FREE-TIER spend against the platform ceiling\n * (AGL-2925). The one AI band with no invoice behind it,\n * and the one figure that can switch every Free\n * workspace's generation off at once — so it sits with\n * the fleet money rather than on a page of its own. Red\n * once paused, amber once staff were mailed at 80%.\n */}\n {report.freeSpend ? (\n <Chip\n color={\n report.freeSpend.paused\n ? 'error'\n : report.freeSpend.alerted\n ? 'warning'\n : 'default'\n }\n label={\n `free tier today ${money(report.freeSpend.estCostUsd)} of ` +\n `${money(report.freeSpend.ceilingUsd)} ceiling · ` +\n `${report.freeSpend.requests.toLocaleString()} requests` +\n (report.freeSpend.paused ? ' · PAUSED until tomorrow' : '')\n }\n />\n ) : null}\n </Stack>\n )}\n </CardDisplay>\n\n {/* The month's spend, per workspace (AGL-2930) — ABOVE the\n tier and model splits, because \"which org\" is the question\n staff open this board with, and the two tables below split\n one fleet total that this ranking names the parts of. */}\n <CardDisplay\n header={`AI spend this month, by workspace${spend ? ` · ${spend.month}` : ''}`}\n help={pluginDocsHelp('aiMonitoring', { anchor: '#the-spend-leaderboard' })}\n contentGutterX\n contentGutterY\n >\n {spend?.failed ? (\n <Alert severity=\"warning\" sx={{ mb: 1 }}>\n {'Could not read this month’s spend documents — a failed read, not a month in which nobody spent.'}\n </Alert>\n ) : spend?.truncated ? (\n <Alert severity=\"warning\" sx={{ mb: 1 }}>\n {'Ranked over a sample of the month documents — there are more. This is not the whole fleet.'}\n </Alert>\n ) : null}\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Workspace</TableCell>\n <TableCell>Plan</TableCell>\n <TableCell>{aiAddonName()}</TableCell>\n <TableCell align=\"right\">Credits</TableCell>\n <TableCell align=\"right\">Cost</TableCell>\n <TableCell align=\"right\">Refusals</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {!spend?.rows?.length ? (\n <TableRow>\n <TableCell colSpan={6}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n No workspace has spent AI credits this month.\n </Typography>\n </TableCell>\n </TableRow>\n ) : (\n spend.rows.map((row) => (\n <TableRow key={row.orgId}>\n <TableCell>{row.orgLabel ?? row.orgId}</TableCell>\n <TableCell>{row.plan}</TableCell>\n <TableCell>\n <Chip\n size=\"small\"\n variant={row.aiAddon ? 'filled' : 'outlined'}\n color={row.aiAddon ? 'primary' : 'default'}\n label={row.aiAddon ? 'on' : 'off'}\n />\n </TableCell>\n <TableCell align=\"right\">\n {row.credits.toLocaleString()}\n </TableCell>\n <TableCell align=\"right\" sx={{ fontFamily: 'monospace' }}>\n {money(row.providerCostUsd)}\n </TableCell>\n {/* The total, with the reasons on hover-free display:\n a reader comparing spend to refusals needs both\n on one row, and the reasons are what turn a count\n into a diagnosis. */}\n <TableCell align=\"right\">\n {row.refusals.total.toLocaleString()}\n {row.refusals.total > 0 ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block' }}\n >\n {`band ${row.refusals.band} · ceiling ${row.refusals.cap} · messages ${row.refusals.messages} · backstop ${row.refusals.budget}${row.refusals.allotment > 0 ? ` · allotments ${row.refusals.allotment}` : ''}${freeTasteRefusals(row.refusals) > 0 ? ` · free taste ${freeTasteRefusals(row.refusals)}` : ''}`}\n </Typography>\n ) : null}\n </TableCell>\n </TableRow>\n ))\n )}\n </TableBody>\n </ScrollTable>\n <RankingFootnote\n shown={spend?.rows?.length ?? 0}\n total={spend?.ranked ?? 0}\n noun={{ one: 'workspace', many: 'workspaces' }}\n />\n </CardDisplay>\n\n <CardDisplay\n header={'Where the money goes'}\n help={pluginDocsHelp('assistSignals', { anchor: '#where-the-money-goes' })}\n contentGutterX\n contentGutterY\n >\n <Typography variant=\"body2\" color=\"text.secondary\" gutterBottom>\n The same spend split two ways. By tier answers whether the free\n cap or the paid price is the lever; by model answers whether a\n cheaper model on the common path would do — the one lever that\n never reaches the customer.\n </Typography>\n {!totals ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {loading ? 'Reading signals…' : 'No assist turns recorded yet.'}\n </Typography>\n ) : totals.providerCostUsd === 0 ? (\n /*\n * ZERO SPEND IS A FINDING, NOT AN EMPTY TABLE.\n *\n * Rendered as a sentence rather than two grids of `$0.0000`\n * and em dashes. A share column is a proportion of the total,\n * and there is no proportion of nothing — so the grid could\n * only ever print a dash in every cell, which reads as a card\n * that failed rather than as a bill that is genuinely zero.\n *\n * The two zeros mean OPPOSITE things and are separated here.\n * Every turn deflected is the deflection path working: those\n * turns were answered from the docs index or the answer cache\n * with no provider call, so nothing was spent because nothing\n * was bought. A turn that reached a model and still priced at\n * zero is the other case — the model is missing from the\n * model catalog's rates and its spend is landing nowhere,\n * which is a hole in the meter and must not be reported as\n * thrift.\n */\n <Typography variant=\"body2\" color=\"text.secondary\">\n {totals.deflected >= totals.messages\n ? `Nothing spent on these ${totals.messages.toLocaleString()} turns — every one was answered from the docs index or the answer cache, with no model call. There is no split to show until a turn reaches a provider.`\n : `${(totals.messages - totals.deflected).toLocaleString()} of ${totals.messages.toLocaleString()} turns reached a model and every one priced at $0. That is a gap in the rate table, not a saving — the serving model has no entry in the cost model, so its spend is landing nowhere.`}\n </Typography>\n ) : (\n <Stack spacing={3}>\n <CostSplitTable\n caption=\"By tier\"\n label=\"Tier\"\n rows={costSplitRows(totals.byTier)}\n totalUsd={totals.providerCostUsd}\n />\n <CostSplitTable\n caption=\"By model\"\n label=\"Model\"\n rows={costSplitRows(totals.byModel)}\n totalUsd={totals.providerCostUsd}\n />\n </Stack>\n )}\n </CardDisplay>\n\n {/*\n * TOKENS BY KIND (AGL-2937): what a kind of request costs and weighs,\n * and the p95 output its `max_tokens` is sized against. Model turns\n * only — a docs answer has no tokens to split.\n */}\n <CardDisplay\n header={'Tokens by kind'}\n help={pluginDocsHelp('assistSignals', { anchor: '#tokens-by-kind' })}\n contentGutterX\n contentGutterY\n >\n <Typography variant=\"body2\" color=\"text.secondary\" gutterBottom>\n What each kind of model request costs per request, what its prompts\n and answers weigh, how much of its prompt the cache served, and the\n answer size 95 of every 100 requests stayed within — the figure its\n output ceiling is sized against.\n </Typography>\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\">In</TableCell>\n <TableCell align=\"right\">Cache reads</TableCell>\n <TableCell align=\"right\">Cache writes</TableCell>\n <TableCell align=\"right\">Out</TableCell>\n <TableCell align=\"right\">p95 out</TableCell>\n <TableCell align=\"right\">Cache hit</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {!kindRows.length ? (\n <TableRow>\n <TableCell colSpan={9}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n No model turns in this sample.\n </Typography>\n </TableCell>\n </TableRow>\n ) : (\n kindRows.map((row) => (\n <TableRow key={row.kind}>\n <TableCell>{row.kind}</TableCell>\n <TableCell align=\"right\">{row.messages.toLocaleString()}</TableCell>\n <TableCell align=\"right\" sx={{ fontFamily: 'monospace' }}>\n {money(row.messages ? row.providerCostUsd / row.messages : 0)}\n </TableCell>\n <TableCell align=\"right\">{row.inputTokens.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{row.cacheReadTokens.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{row.cacheWriteTokens.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{row.outputTokens.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{row.outputP95.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{percent(row.cacheHitRate)}</TableCell>\n </TableRow>\n ))\n )}\n </TableBody>\n </ScrollTable>\n </CardDisplay>\n\n <CardDisplay\n header={'Docs gaps'}\n help={pluginDocsHelp('assistSignals', { anchor: '#docs-gaps' })}\n contentGutterX\n contentGutterY\n >\n <Typography variant=\"body2\" color=\"text.secondary\" gutterBottom>\n Cited pages ranked by thumbs-down, then by how often a question\n landed there. A page near the top is being found and is not\n answering — that is a docs issue with its evidence attached.\n </Typography>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Docs page</TableCell>\n <TableCell align=\"right\">Questions</TableCell>\n <TableCell align=\"right\">👍</TableCell>\n <TableCell align=\"right\">👎</TableCell>\n <TableCell align=\"right\">Down rate</TableCell>\n <TableCell align=\"right\">Orgs</TableCell>\n <TableCell align=\"right\">Cost</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {!report?.docsGaps?.length ? (\n <TableRow>\n <TableCell colSpan={7}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n Nothing cited yet.\n </Typography>\n </TableCell>\n </TableRow>\n ) : (\n report.docsGaps.map((row) => (\n <TableRow key={row.path}>\n <TableCell sx={{ fontFamily: 'monospace' }}>\n {row.path}\n </TableCell>\n <TableCell align=\"right\">{row.questions}</TableCell>\n <TableCell align=\"right\">{row.up}</TableCell>\n <TableCell align=\"right\">{row.down}</TableCell>\n <TableCell align=\"right\">\n {percent(row.downRate)}\n </TableCell>\n <TableCell align=\"right\">{row.orgs}</TableCell>\n <TableCell align=\"right\" sx={{ fontFamily: 'monospace' }}>\n {money(row.providerCostUsd)}\n </TableCell>\n </TableRow>\n ))\n )}\n </TableBody>\n </ScrollTable>\n <RankingFootnote\n shown={report?.docsGaps?.length ?? 0}\n total={report?.ranked?.docsGaps ?? 0}\n noun={{ one: 'cited page', many: 'cited pages' }}\n />\n </CardDisplay>\n\n <CardDisplay\n header={'Questions the docs could not answer'}\n help={pluginDocsHelp('assistSignals', { anchor: '#questions-the-docs-could-not-answer' })}\n contentGutterX\n contentGutterY\n >\n <Typography variant=\"body2\" color=\"text.secondary\" gutterBottom>\n {report\n ? `${report.ungrounded.questions.toLocaleString()} turns matched no documentation at all, ${report.ungrounded.down} of them rated down. These cite no page, so they cannot appear in the ranking above — a missing page is invisible to anything keyed on paths.`\n : 'Turns where retrieval matched no documentation at all.'}\n </Typography>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Asked from</TableCell>\n <TableCell align=\"right\">Questions</TableCell>\n <TableCell align=\"right\">👎</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {!report?.ungrounded?.routes?.length ? (\n <TableRow>\n <TableCell colSpan={3}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n Every question so far matched something.\n </Typography>\n </TableCell>\n </TableRow>\n ) : (\n report.ungrounded.routes.map((row) => (\n <TableRow key={row.route}>\n <TableCell sx={{ fontFamily: 'monospace' }}>\n {row.route}\n </TableCell>\n <TableCell align=\"right\">{row.questions}</TableCell>\n <TableCell align=\"right\">{row.down}</TableCell>\n </TableRow>\n ))\n )}\n </TableBody>\n </ScrollTable>\n <RankingFootnote\n shown={report?.ungrounded?.routes?.length ?? 0}\n total={report?.ranked?.ungroundedRoutes ?? 0}\n noun={{ one: 'console page', many: 'console pages' }}\n />\n </CardDisplay>\n\n {/*\n * THE WORDS THEMSELVES (AGL-2314).\n *\n * `assistExchanges` held the question a customer typed and the\n * answer we gave, for 180 days, and `assist-usage.ts` called it\n * \"the data loop's corpus\". Nothing read it — so we retained\n * customers' words, and committed publicly in the privacy policy\n * to retaining them, for zero product value. The two panels above\n * can say a page fails and cannot say what people were trying to\n * do; only this can.\n *\n * Behind a disclosure, and only for turns that FAILED — the shape\n * AGL-2294 used for churn free text. The route fetches nothing\n * else, so the panel being closed is not the privacy control; the\n * shortlist is.\n */}\n <CardDisplay\n header={'What people actually asked'}\n help={pluginDocsHelp('assistSignals', { anchor: '#what-people-actually-asked' })}\n contentGutterX\n contentGutterY\n >\n <Typography variant=\"body2\" color=\"text.secondary\" gutterBottom>\n {'The verbatim question behind each failing turn — rated down, ' +\n 'or grounded in nothing. Kept for 180 days and then deleted, ' +\n 'so an older failure shows its counts with the words gone. ' +\n 'Who asked is deliberately not shown: the question is what ' +\n 'this is for.'}\n </Typography>\n {!prose.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {loading\n ? 'Reading exchanges…'\n : 'No failing turns in this sample — nothing to read.'}\n </Typography>\n ) : (\n <Accordion>\n <AccordionSummary expandIcon={<span aria-hidden>{'▾'}</span>}>\n <Typography variant=\"body2\">\n {`Show ${prose.length} question${prose.length === 1 ? '' : 's'}`}\n </Typography>\n </AccordionSummary>\n <AccordionDetails>\n <Stack spacing={2}>\n {prose.map((row: any) => (\n <Stack key={`${row.orgId}:${row.exchangeId}`} spacing={0.5}>\n <Stack\n direction=\"row\"\n sx={{ flexWrap: 'wrap', gap: 1, alignItems: 'center' }}\n >\n <Chip\n size=\"small\"\n color={row.feedback === 'down' ? 'error' : 'default'}\n label={\n row.feedback === 'down'\n ? 'rated down'\n : 'no documentation matched'\n }\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n <Box\n component=\"span\"\n sx={{ fontFamily: 'monospace' }}\n >\n {row.route}\n </Box>\n {` · ${row.orgLabel ?? row.orgId}`}\n </Typography>\n </Stack>\n {row.expired ? (\n // Said out loud. A shortlist quietly shorter than\n // the failure count it came from would read as\n // \"these are all the failures\".\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The words are past their 180-day retention and ' +\n 'have been deleted. The counts above still ' +\n 'include this turn.'}\n </Typography>\n ) : (\n <>\n <Typography variant=\"body2\">\n {row.question || '(no question recorded)'}\n </Typography>\n {row.answer ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n >\n {`We said: ${row.answer}${row.answerTruncated ? '…' : ''}`}\n </Typography>\n ) : null}\n </>\n )}\n </Stack>\n ))}\n </Stack>\n </AccordionDetails>\n </Accordion>\n )}\n </CardDisplay>\n\n <CardDisplay\n header={'What Assist costs, by workspace'}\n help={pluginDocsHelp('assistSignals', { anchor: '#what-assist-costs-by-workspace' })}\n contentGutterX\n contentGutterY\n >\n <Typography variant=\"body2\" color=\"text.secondary\" gutterBottom>\n Our estimated cost at the serving model's list rates —\n telemetry for tuning price against margin, not a bill.\n </Typography>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Workspace</TableCell>\n <TableCell align=\"right\">Turns</TableCell>\n <TableCell align=\"right\">In</TableCell>\n <TableCell align=\"right\">Out</TableCell>\n <TableCell align=\"right\">Cache reads</TableCell>\n <TableCell align=\"right\">👎</TableCell>\n <TableCell align=\"right\">Cost</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {!report?.orgs?.length ? (\n <TableRow>\n <TableCell colSpan={7}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n No workspace has used Assist yet.\n </Typography>\n </TableCell>\n </TableRow>\n ) : (\n report.orgs.map((row) => (\n <TableRow key={row.orgId}>\n {/*\n The workspace's NAME. The route resolves it and\n falls back to the id, so a deleted workspace still\n renders a lead a staff member can search for rather\n than an empty cell. Not monospaced: monospace is how\n this page marks a value you copy, and a customer's\n name is one you read.\n */}\n <TableCell>{row.orgLabel ?? row.orgId}</TableCell>\n <TableCell align=\"right\">{row.messages}</TableCell>\n <TableCell align=\"right\">\n {row.inputTokens.toLocaleString()}\n </TableCell>\n <TableCell align=\"right\">\n {row.outputTokens.toLocaleString()}\n </TableCell>\n <TableCell align=\"right\">\n {row.cacheReadTokens.toLocaleString()}\n </TableCell>\n <TableCell align=\"right\">{row.down}</TableCell>\n <TableCell align=\"right\" sx={{ fontFamily: 'monospace' }}>\n {money(row.providerCostUsd)}\n </TableCell>\n </TableRow>\n ))\n )}\n </TableBody>\n </ScrollTable>\n <RankingFootnote\n shown={report?.orgs?.length ?? 0}\n total={report?.ranked?.orgs ?? 0}\n noun={{ one: 'workspace', many: 'workspaces' }}\n />\n </CardDisplay>\n </Stack>\n )\n}\n\nexport default AssistSignalsPage\n"],"names":["aiAddonName","pluginDocsHelp","CardDisplay","ScrollTable","Accordion","AccordionDetails","AccordionSummary","Alert","Box","Chip","LinearProgress","Stack","TableBody","TableCell","TableHead","TableRow","Typography","useEffect","useRef","useState","useUser","authorizedFetch","costSplitRows","freeTasteRefusals","kindTokenRows","money","value","Number","toFixed","Math","abs","percent","round","CostSplitTable","caption","label","rows","totalUsd","spacing","sx","width","variant","size","align","length","colSpan","color","map","row","key","messages","toLocaleString","providerCostUsd","RankingFootnote","shown","total","noun","counted","one","many","AssistSignalsPage","_props","totals","spend","report","data","user","signedInUid","uid","userRef","current","setReport","error","setError","loading","setLoading","undefined","active","response","body","json","catch","ok","prose","kindRows","byKind","severity","truncated","scanned","header","help","anchor","contentGutterX","contentGutterY","direction","flexWrap","gap","deflectionRate","deflected","inputTokens","outputTokens","cacheReadRate","cacheWriteTokens","feedback","up","down","none","Object","entries","stopReasons","reason","count","freeSpend","paused","alerted","estCostUsd","ceilingUsd","requests","month","failed","mb","orgLabel","orgId","plan","aiAddon","credits","fontFamily","refusals","display","band","cap","budget","allotment","ranked","gutterBottom","byTier","byModel","kind","cacheReadTokens","outputP95","cacheHitRate","docsGaps","path","questions","downRate","orgs","ungrounded","routes","route","ungroundedRoutes","expandIcon","span","aria-hidden","alignItems","component","expired","question","answer","answerTruncated","exchangeId"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/assist-signals-page.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, type ConsoleStaffPageProps } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport {\n Accordion,\n AccordionDetails,\n AccordionSummary,\n Alert,\n Box,\n Chip,\n LinearProgress,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Typography,\n} from '@mui/material'\nimport { useEffect, useRef, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport type {\n AssistMiningReport,\n AssistSpendRow,\n} from '../usage/assist-signal-mining'\nimport {\n costSplitRows,\n freeTasteRefusals,\n kindTokenRows,\n} from '../usage/assist-signal-mining'\n\n/** The month leaderboard as the route serves it beside the report. */\ninterface AssistSpendLeaderboard {\n month: string\n rows: AssistSpendRow[]\n ranked: number\n truncated: boolean\n failed: boolean\n}\n\n/**\n * The Assist docs-gap and cost board (AGL-1860, AGL-2252) — the read side of\n * the data loop, and until now the half that did not exist.\n *\n * AGL-1972 split every assist turn into prose that expires and a signal that\n * does not, on the reasoning that \"the corpus the loop needs outlives the\n * words that produced it\". That is only true once something reads the corpus.\n * This is that something.\n *\n * Four panels, in the order the questions actually get asked:\n *\n * 1. **Docs gaps** — cited pages ranked by thumbs-down, then volume. A page\n * high on this list is being found and is not answering, which is a docs\n * issue with its evidence attached. The `orgs` column is the sanity\n * check: a gap one workspace has is a workspace, not a gap.\n * 2. **Ungrounded questions** — turns where retrieval matched nothing at\n * all, grouped by the screen the person was on. These cite no page, so\n * they can never appear in the ranking above; a missing page is invisible\n * to anything keyed on paths, and this panel is the only place it shows.\n * 3. **Cost** — per org, dearest first, plus the fleet totals and the\n * cache-read rate. The rate is not decoration: the cached system prefix\n * measures 1,030–1,190 tokens against Sonnet 5's 1,024-token minimum, so\n * whether it caches is an empirical question the chat route could only\n * pose, and this is the evidence.\n * 4. **Tokens by kind** — what each kind of model request costs per\n * request, what it sends and generates, how much of its prompt the cache\n * served, and the p95 output its `max_tokens` is sized against\n * (AGL-2937).\n *\n * Read through `/api/ai/admin/signals` rather than Firestore directly.\n * `assistSignals` is absent from the rules file on purpose — default-deny for\n * every client — which is exactly what lets it hold cross-tenant analytics.\n *\n * The AI plugin's staff page (AGL-2939): the console's generic staff route\n * draws the header, the breadcrumbs and the staff gate around this body, and\n * gives it the tab it had.\n */\n\nconst money = (value: number) =>\n `$${(Number(value) || 0).toFixed(Math.abs(Number(value) || 0) < 1 ? 4 : 2)}`\n\nconst percent = (value: number | null) =>\n value == null ? '—' : `${Math.round(value * 100)}%`\n\n/**\n * One cost breakdown — tier or model, dearest first (AGL-2340).\n *\n * The `share` column is why this is a table rather than a row of chips. The\n * decision it feeds is always comparative — \"is free eating the margin\" is a\n * question about proportion, not about a dollar figure — and a reader asked\n * to divide two numbers in their head does not do it.\n *\n * `turns` is kept beside the money on purpose: a tier that is a third of the\n * traffic and two thirds of the bill is the finding, and neither column says\n * that alone.\n *\n * ## Full width, and stacked rather than side by side\n *\n * Both breakdowns split the SAME total, so the reader's act is scanning one\n * against the other: does the tier carrying the turns also carry the money,\n * and would a cheaper model on the common path move it. Two tables squeezed\n * into thirds of a row put each Cost column at a different horizontal\n * position, so that comparison becomes two separate readings held in the\n * head. Stacked full-width, the Cost and Share columns of both land on the\n * same axis and the comparison is a glance down the page.\n *\n * ## Why the caption is not a row of the table\n *\n * It used to be a `colSpan={4}` cell in `TableHead`, which gave it header\n * styling and made the header two rows deep — so this table's head sat at a\n * different weight and height from the four sibling tables on the same page,\n * which is most of what made the card read as a foreign fragment.\n *\n * ## No footer, and why\n *\n * A breakdown has one row per tier and one per model — single digits, and\n * bounded by the vocabulary rather than by a collection that grows. There is\n * no page two to go to, and a pager over four rows is furniture that teaches\n * a reader to look for pages that never exist.\n */\nfunction CostSplitTable({\n caption,\n label,\n rows,\n totalUsd,\n}: {\n caption: string\n label: string\n rows: { key: string; messages: number; providerCostUsd: number }[]\n totalUsd: number\n}) {\n return (\n <Stack spacing={1} sx={{ width: '100%' }}>\n <Typography variant=\"subtitle2\">{caption}</Typography>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{label}</TableCell>\n <TableCell align=\"right\">Turns</TableCell>\n <TableCell align=\"right\">Cost</TableCell>\n <TableCell align=\"right\">Share</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {!rows.length ? (\n <TableRow>\n <TableCell colSpan={4}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n No turns in this sample.\n </Typography>\n </TableCell>\n </TableRow>\n ) : (\n rows.map((row) => (\n <TableRow key={row.key}>\n <TableCell>{row.key}</TableCell>\n <TableCell align=\"right\">\n {row.messages.toLocaleString()}\n </TableCell>\n <TableCell align=\"right\">{money(row.providerCostUsd)}</TableCell>\n <TableCell align=\"right\">\n {percent(totalUsd > 0 ? row.providerCostUsd / totalUsd : null)}\n </TableCell>\n </TableRow>\n ))\n )}\n </TableBody>\n </ScrollTable>\n </Stack>\n )\n}\n\n/**\n * The count line under a RANKED table — how much of the ranking is on screen.\n *\n * These three tables are not paged, and the reason is the same for all of\n * them: each is a ranking whose value is its top. Page two of \"where are the\n * docs thin\" is, by construction, the pages least worth rewriting, and a\n * pager over a ranking invites a reader to walk it as if position carried no\n * meaning. What a pager WOULD have given honestly is the count — how much of\n * this am I looking at, and is there more — so that is what this says, and it\n * says it whether or not the ranking was cut.\n *\n * Rendered even when nothing was cut. A disclosure that appears only on\n * truncation is one a reader has never seen before the day it matters, and\n * cannot tell from a card that simply has no footer.\n */\nfunction RankingFootnote({\n shown,\n total,\n noun,\n}: {\n shown: number\n total: number\n /** Both forms: a fleet of one is the common case on a young install. */\n noun: { one: string; many: string }\n}) {\n if (!total) return null\n const counted = `${total.toLocaleString()} ${total === 1 ? noun.one : noun.many}`\n return (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {shown >= total\n ? `Showing all ${counted}.`\n : `Showing the top ${shown.toLocaleString()} of ${counted}. This is a ranking, not a page — what is past the cut is what it ranked lowest.`}\n </Typography>\n )\n}\n\nexport function AssistSignalsPage(_props: ConsoleStaffPageProps) {\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 [report, setReport] = useState<AssistMiningReport | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [loading, setLoading] = useState(false)\n\n useEffect(() => {\n // Mounted only for a confirmed staff reader: the generic staff route\n // renders this inside `StaffOnly`, which draws nothing while the claim\n // loads, so no request is fired from the loading default.\n const user = userRef.current\n if (!signedInUid || !user) return undefined\n let active = true\n void (async () => {\n setLoading(true)\n setError(null)\n try {\n const response = await authorizedFetch(user, '/api/ai/admin/signals')\n const body = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n setError(body?.error ?? 'Assist signal lookup failed')\n } else {\n setReport(body as AssistMiningReport)\n }\n } catch {\n if (active) setError('Assist signal lookup failed')\n } finally {\n if (active) setLoading(false)\n }\n })()\n return () => {\n active = false\n }\n }, [signedInUid])\n\n const totals = report?.totals\n // The route serves the prose beside the report (AGL-2314) — it is not part\n // of the pure miner's output, because the miner never touches Firestore.\n const prose = ((report as any)?.prose ?? []) as Array<Record<string, unknown>>\n // The month's spend leaderboard (AGL-2930), likewise served beside the\n // report: it reads the month documents, which the miner never sees.\n const spend = ((report as any)?.spend ?? null) as AssistSpendLeaderboard | null\n // Absent from a route older than the page, which reads as no kinds.\n const kindRows = totals?.byKind ? kindTokenRows(totals.byKind) : []\n\n return (\n <Stack spacing={3}>\n {loading && <LinearProgress />}\n {error && <Alert severity=\"error\">{error}</Alert>}\n {report?.truncated && (\n // Never a silent partial. A ranking cut short looks exactly\n // like a complete one, and this one decides where docs effort\n // goes.\n <Alert severity=\"warning\">\n {`Ranked the first ${report.scanned.toLocaleString()} signals only — there are more. Every number below describes that sample, not the fleet.`}\n </Alert>\n )}\n\n <CardDisplay\n header={'Fleet'}\n help={pluginDocsHelp('assistSignals', { anchor: '#fleet' })}\n contentGutterX\n contentGutterY\n >\n {!totals ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {loading ? 'Reading signals…' : 'No assist turns recorded yet.'}\n </Typography>\n ) : (\n // Neither `gap` nor `flexWrap` is a Stack prop under MUI 9\n // — both go through `sx` or it is a typecheck error.\n // AGL-1891 is the same drift, in the Assist panel itself.\n <Stack useFlexGap direction=\"row\" sx={{ flexWrap: 'wrap', gap: 1 }}>\n <Chip label={`${totals.messages.toLocaleString()} turns`} />\n <Chip label={`${money(totals.providerCostUsd)} estimated`} />\n {/*\n * The headline number for whether Assist is affordable to\n * leave on (AGL-2486): the share of turns answered from the\n * docs index or the answer cache, with no model call and no\n * provider spend. Placed beside the money rather than with\n * the token chips because it is the thing that MOVES the\n * money — every point of it is a turn that cost nothing.\n *\n * Success above half, because that is the point at which\n * the cheap path is carrying the feature rather than\n * trimming it.\n */}\n <Chip\n // `?? 0` on the count, and it is not defensive noise: this\n // whole object is JSON off `/api/ai/admin/signals`,\n // and a chip that throws on a missing number takes the\n // ENTIRE staff page down with it rather than rendering a\n // dash. The rate beside it is legitimately nullable —\n // `percent` renders that — so the two are handled\n // differently on purpose.\n label={`answered free ${percent(totals.deflectionRate)} (${(\n totals.deflected ?? 0\n ).toLocaleString()})`}\n color={\n totals.deflectionRate != null && totals.deflectionRate > 0.5\n ? 'success'\n : 'default'\n }\n />\n <Chip\n label={`${totals.inputTokens.toLocaleString()} in / ${totals.outputTokens.toLocaleString()} out`}\n />\n <Chip\n label={`cache reads ${percent(totals.cacheReadRate)}`}\n color={\n totals.cacheReadRate != null && totals.cacheReadRate > 0.5\n ? 'success'\n : 'default'\n }\n />\n {/*\n * Cache WRITES, beside the reads (AGL-2340). The pair only\n * means anything together: a write is billed at a premium\n * over a fresh input token and pays for itself only if\n * enough reads follow it. A board that showed the cheap\n * half alone made a prefix that is re-written every turn —\n * the expensive failure mode — look like a caching win.\n */}\n <Chip\n label={`cache writes ${totals.cacheWriteTokens.toLocaleString()}`}\n />\n <Chip\n label={`👍 ${totals.feedback.up} · 👎 ${totals.feedback.down} · unrated ${totals.feedback.none}`}\n />\n {Object.entries(totals.stopReasons).map(([reason, count]) => (\n <Chip\n key={reason}\n size=\"small\"\n // A refusal and a truncation both read as a short\n // answer in the data and need opposite fixes: a rising\n // refusal rate is a prompt problem, a rising max_tokens\n // rate is a ceiling problem.\n color={\n reason === 'refusal' || reason === 'max_tokens'\n ? 'warning'\n : 'default'\n }\n label={`${reason}: ${count}`}\n />\n ))}\n {/*\n * Today's FREE-TIER spend against the platform ceiling\n * (AGL-2925). The one AI band with no invoice behind it,\n * and the one figure that can switch every Free\n * workspace's generation off at once — so it sits with\n * the fleet money rather than on a page of its own. Red\n * once paused, amber once staff were mailed at 80%.\n */}\n {report.freeSpend ? (\n <Chip\n color={\n report.freeSpend.paused\n ? 'error'\n : report.freeSpend.alerted\n ? 'warning'\n : 'default'\n }\n label={\n `free tier today ${money(report.freeSpend.estCostUsd)} of ` +\n `${money(report.freeSpend.ceilingUsd)} ceiling · ` +\n `${report.freeSpend.requests.toLocaleString()} requests` +\n (report.freeSpend.paused ? ' · PAUSED until tomorrow' : '')\n }\n />\n ) : null}\n </Stack>\n )}\n </CardDisplay>\n\n {/* The month's spend, per workspace (AGL-2930) — ABOVE the\n tier and model splits, because \"which org\" is the question\n staff open this board with, and the two tables below split\n one fleet total that this ranking names the parts of. */}\n <CardDisplay\n header={`AI spend this month, by workspace${spend ? ` · ${spend.month}` : ''}`}\n help={pluginDocsHelp('aiMonitoring', { anchor: '#the-spend-leaderboard' })}\n contentGutterX\n contentGutterY\n >\n {spend?.failed ? (\n <Alert severity=\"warning\" sx={{ mb: 1 }}>\n {'Could not read this month’s spend documents — a failed read, not a month in which nobody spent.'}\n </Alert>\n ) : spend?.truncated ? (\n <Alert severity=\"warning\" sx={{ mb: 1 }}>\n {'Ranked over a sample of the month documents — there are more. This is not the whole fleet.'}\n </Alert>\n ) : null}\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Workspace</TableCell>\n <TableCell>Plan</TableCell>\n <TableCell>{aiAddonName()}</TableCell>\n <TableCell align=\"right\">Credits</TableCell>\n <TableCell align=\"right\">Cost</TableCell>\n <TableCell align=\"right\">Refusals</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {!spend?.rows?.length ? (\n <TableRow>\n <TableCell colSpan={6}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n No workspace has spent AI credits this month.\n </Typography>\n </TableCell>\n </TableRow>\n ) : (\n spend.rows.map((row) => (\n <TableRow key={row.orgId}>\n <TableCell>{row.orgLabel ?? row.orgId}</TableCell>\n <TableCell>{row.plan}</TableCell>\n <TableCell>\n <Chip\n size=\"small\"\n variant={row.aiAddon ? 'filled' : 'outlined'}\n color={row.aiAddon ? 'primary' : 'default'}\n label={row.aiAddon ? 'on' : 'off'}\n />\n </TableCell>\n <TableCell align=\"right\">\n {row.credits.toLocaleString()}\n </TableCell>\n <TableCell align=\"right\" sx={{ fontFamily: 'monospace' }}>\n {money(row.providerCostUsd)}\n </TableCell>\n {/* The total, with the reasons on hover-free display:\n a reader comparing spend to refusals needs both\n on one row, and the reasons are what turn a count\n into a diagnosis. */}\n <TableCell align=\"right\">\n {row.refusals.total.toLocaleString()}\n {row.refusals.total > 0 ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block' }}\n >\n {`band ${row.refusals.band} · ceiling ${row.refusals.cap} · messages ${row.refusals.messages} · backstop ${row.refusals.budget}${row.refusals.allotment > 0 ? ` · allotments ${row.refusals.allotment}` : ''}${freeTasteRefusals(row.refusals) > 0 ? ` · free taste ${freeTasteRefusals(row.refusals)}` : ''}`}\n </Typography>\n ) : null}\n </TableCell>\n </TableRow>\n ))\n )}\n </TableBody>\n </ScrollTable>\n <RankingFootnote\n shown={spend?.rows?.length ?? 0}\n total={spend?.ranked ?? 0}\n noun={{ one: 'workspace', many: 'workspaces' }}\n />\n </CardDisplay>\n\n <CardDisplay\n header={'Where the money goes'}\n help={pluginDocsHelp('assistSignals', { anchor: '#where-the-money-goes' })}\n contentGutterX\n contentGutterY\n >\n <Typography variant=\"body2\" color=\"text.secondary\" gutterBottom>\n The same spend split two ways. By tier answers whether the free\n cap or the paid price is the lever; by model answers whether a\n cheaper model on the common path would do — the one lever that\n never reaches the customer.\n </Typography>\n {!totals ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {loading ? 'Reading signals…' : 'No assist turns recorded yet.'}\n </Typography>\n ) : totals.providerCostUsd === 0 ? (\n /*\n * ZERO SPEND IS A FINDING, NOT AN EMPTY TABLE.\n *\n * Rendered as a sentence rather than two grids of `$0.0000`\n * and em dashes. A share column is a proportion of the total,\n * and there is no proportion of nothing — so the grid could\n * only ever print a dash in every cell, which reads as a card\n * that failed rather than as a bill that is genuinely zero.\n *\n * The two zeros mean OPPOSITE things and are separated here.\n * Every turn deflected is the deflection path working: those\n * turns were answered from the docs index or the answer cache\n * with no provider call, so nothing was spent because nothing\n * was bought. A turn that reached a model and still priced at\n * zero is the other case — the model is missing from the\n * model catalog's rates and its spend is landing nowhere,\n * which is a hole in the meter and must not be reported as\n * thrift.\n */\n <Typography variant=\"body2\" color=\"text.secondary\">\n {totals.deflected >= totals.messages\n ? `Nothing spent on these ${totals.messages.toLocaleString()} turns — every one was answered from the docs index or the answer cache, with no model call. There is no split to show until a turn reaches a provider.`\n : `${(totals.messages - totals.deflected).toLocaleString()} of ${totals.messages.toLocaleString()} turns reached a model and every one priced at $0. That is a gap in the rate table, not a saving — the serving model has no entry in the cost model, so its spend is landing nowhere.`}\n </Typography>\n ) : (\n <Stack spacing={3}>\n <CostSplitTable\n caption=\"By tier\"\n label=\"Tier\"\n rows={costSplitRows(totals.byTier)}\n totalUsd={totals.providerCostUsd}\n />\n <CostSplitTable\n caption=\"By model\"\n label=\"Model\"\n rows={costSplitRows(totals.byModel)}\n totalUsd={totals.providerCostUsd}\n />\n </Stack>\n )}\n </CardDisplay>\n\n {/*\n * TOKENS BY KIND (AGL-2937): what a kind of request costs and weighs,\n * and the p95 output its `max_tokens` is sized against. Model turns\n * only — a docs answer has no tokens to split.\n */}\n <CardDisplay\n header={'Tokens by kind'}\n help={pluginDocsHelp('assistSignals', { anchor: '#tokens-by-kind' })}\n contentGutterX\n contentGutterY\n >\n <Typography variant=\"body2\" color=\"text.secondary\" gutterBottom>\n What each kind of model request costs per request, what its prompts\n and answers weigh, how much of its prompt the cache served, and the\n answer size 95 of every 100 requests stayed within — the figure its\n output ceiling is sized against.\n </Typography>\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\">In</TableCell>\n <TableCell align=\"right\">Cache reads</TableCell>\n <TableCell align=\"right\">Cache writes</TableCell>\n <TableCell align=\"right\">Out</TableCell>\n <TableCell align=\"right\">p95 out</TableCell>\n <TableCell align=\"right\">Cache hit</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {!kindRows.length ? (\n <TableRow>\n <TableCell colSpan={9}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n No model turns in this sample.\n </Typography>\n </TableCell>\n </TableRow>\n ) : (\n kindRows.map((row) => (\n <TableRow key={row.kind}>\n <TableCell>{row.kind}</TableCell>\n <TableCell align=\"right\">{row.messages.toLocaleString()}</TableCell>\n <TableCell align=\"right\" sx={{ fontFamily: 'monospace' }}>\n {money(row.messages ? row.providerCostUsd / row.messages : 0)}\n </TableCell>\n <TableCell align=\"right\">{row.inputTokens.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{row.cacheReadTokens.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{row.cacheWriteTokens.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{row.outputTokens.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{row.outputP95.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{percent(row.cacheHitRate)}</TableCell>\n </TableRow>\n ))\n )}\n </TableBody>\n </ScrollTable>\n </CardDisplay>\n\n <CardDisplay\n header={'Docs gaps'}\n help={pluginDocsHelp('assistSignals', { anchor: '#docs-gaps' })}\n contentGutterX\n contentGutterY\n >\n <Typography variant=\"body2\" color=\"text.secondary\" gutterBottom>\n Cited pages ranked by thumbs-down, then by how often a question\n landed there. A page near the top is being found and is not\n answering — that is a docs issue with its evidence attached.\n </Typography>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Docs page</TableCell>\n <TableCell align=\"right\">Questions</TableCell>\n <TableCell align=\"right\">👍</TableCell>\n <TableCell align=\"right\">👎</TableCell>\n <TableCell align=\"right\">Down rate</TableCell>\n <TableCell align=\"right\">Orgs</TableCell>\n <TableCell align=\"right\">Cost</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {!report?.docsGaps?.length ? (\n <TableRow>\n <TableCell colSpan={7}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n Nothing cited yet.\n </Typography>\n </TableCell>\n </TableRow>\n ) : (\n report.docsGaps.map((row) => (\n <TableRow key={row.path}>\n <TableCell sx={{ fontFamily: 'monospace' }}>\n {row.path}\n </TableCell>\n <TableCell align=\"right\">{row.questions}</TableCell>\n <TableCell align=\"right\">{row.up}</TableCell>\n <TableCell align=\"right\">{row.down}</TableCell>\n <TableCell align=\"right\">\n {percent(row.downRate)}\n </TableCell>\n <TableCell align=\"right\">{row.orgs}</TableCell>\n <TableCell align=\"right\" sx={{ fontFamily: 'monospace' }}>\n {money(row.providerCostUsd)}\n </TableCell>\n </TableRow>\n ))\n )}\n </TableBody>\n </ScrollTable>\n <RankingFootnote\n shown={report?.docsGaps?.length ?? 0}\n total={report?.ranked?.docsGaps ?? 0}\n noun={{ one: 'cited page', many: 'cited pages' }}\n />\n </CardDisplay>\n\n <CardDisplay\n header={'Questions the docs could not answer'}\n help={pluginDocsHelp('assistSignals', { anchor: '#questions-the-docs-could-not-answer' })}\n contentGutterX\n contentGutterY\n >\n <Typography variant=\"body2\" color=\"text.secondary\" gutterBottom>\n {report\n ? `${report.ungrounded.questions.toLocaleString()} turns matched no documentation at all, ${report.ungrounded.down} of them rated down. These cite no page, so they cannot appear in the ranking above — a missing page is invisible to anything keyed on paths.`\n : 'Turns where retrieval matched no documentation at all.'}\n </Typography>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Asked from</TableCell>\n <TableCell align=\"right\">Questions</TableCell>\n <TableCell align=\"right\">👎</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {!report?.ungrounded?.routes?.length ? (\n <TableRow>\n <TableCell colSpan={3}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n Every question so far matched something.\n </Typography>\n </TableCell>\n </TableRow>\n ) : (\n report.ungrounded.routes.map((row) => (\n <TableRow key={row.route}>\n <TableCell sx={{ fontFamily: 'monospace' }}>\n {row.route}\n </TableCell>\n <TableCell align=\"right\">{row.questions}</TableCell>\n <TableCell align=\"right\">{row.down}</TableCell>\n </TableRow>\n ))\n )}\n </TableBody>\n </ScrollTable>\n <RankingFootnote\n shown={report?.ungrounded?.routes?.length ?? 0}\n total={report?.ranked?.ungroundedRoutes ?? 0}\n noun={{ one: 'console page', many: 'console pages' }}\n />\n </CardDisplay>\n\n {/*\n * THE WORDS THEMSELVES (AGL-2314).\n *\n * `assistExchanges` held the question a customer typed and the\n * answer we gave, for 180 days, and `assist-usage.ts` called it\n * \"the data loop's corpus\". Nothing read it — so we retained\n * customers' words, and committed publicly in the privacy policy\n * to retaining them, for zero product value. The two panels above\n * can say a page fails and cannot say what people were trying to\n * do; only this can.\n *\n * Behind a disclosure, and only for turns that FAILED — the shape\n * AGL-2294 used for churn free text. The route fetches nothing\n * else, so the panel being closed is not the privacy control; the\n * shortlist is.\n */}\n <CardDisplay\n header={'What people actually asked'}\n help={pluginDocsHelp('assistSignals', { anchor: '#what-people-actually-asked' })}\n contentGutterX\n contentGutterY\n >\n <Typography variant=\"body2\" color=\"text.secondary\" gutterBottom>\n {'The verbatim question behind each failing turn — rated down, ' +\n 'or grounded in nothing. Kept for 180 days and then deleted, ' +\n 'so an older failure shows its counts with the words gone. ' +\n 'Who asked is deliberately not shown: the question is what ' +\n 'this is for.'}\n </Typography>\n {!prose.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {loading\n ? 'Reading exchanges…'\n : 'No failing turns in this sample — nothing to read.'}\n </Typography>\n ) : (\n <Accordion>\n <AccordionSummary expandIcon={<span aria-hidden>{'▾'}</span>}>\n <Typography variant=\"body2\">\n {`Show ${prose.length} question${prose.length === 1 ? '' : 's'}`}\n </Typography>\n </AccordionSummary>\n <AccordionDetails>\n <Stack spacing={2}>\n {prose.map((row: any) => (\n <Stack key={`${row.orgId}:${row.exchangeId}`} spacing={0.5}>\n <Stack\n useFlexGap\n direction=\"row\"\n sx={{ flexWrap: 'wrap', gap: 1, alignItems: 'center' }}\n >\n <Chip\n size=\"small\"\n color={row.feedback === 'down' ? 'error' : 'default'}\n label={\n row.feedback === 'down'\n ? 'rated down'\n : 'no documentation matched'\n }\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n <Box\n component=\"span\"\n sx={{ fontFamily: 'monospace' }}\n >\n {row.route}\n </Box>\n {` · ${row.orgLabel ?? row.orgId}`}\n </Typography>\n </Stack>\n {row.expired ? (\n // Said out loud. A shortlist quietly shorter than\n // the failure count it came from would read as\n // \"these are all the failures\".\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The words are past their 180-day retention and ' +\n 'have been deleted. The counts above still ' +\n 'include this turn.'}\n </Typography>\n ) : (\n <>\n <Typography variant=\"body2\">\n {row.question || '(no question recorded)'}\n </Typography>\n {row.answer ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n >\n {`We said: ${row.answer}${row.answerTruncated ? '…' : ''}`}\n </Typography>\n ) : null}\n </>\n )}\n </Stack>\n ))}\n </Stack>\n </AccordionDetails>\n </Accordion>\n )}\n </CardDisplay>\n\n <CardDisplay\n header={'What Assist costs, by workspace'}\n help={pluginDocsHelp('assistSignals', { anchor: '#what-assist-costs-by-workspace' })}\n contentGutterX\n contentGutterY\n >\n <Typography variant=\"body2\" color=\"text.secondary\" gutterBottom>\n Our estimated cost at the serving model's list rates —\n telemetry for tuning price against margin, not a bill.\n </Typography>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Workspace</TableCell>\n <TableCell align=\"right\">Turns</TableCell>\n <TableCell align=\"right\">In</TableCell>\n <TableCell align=\"right\">Out</TableCell>\n <TableCell align=\"right\">Cache reads</TableCell>\n <TableCell align=\"right\">👎</TableCell>\n <TableCell align=\"right\">Cost</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {!report?.orgs?.length ? (\n <TableRow>\n <TableCell colSpan={7}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n No workspace has used Assist yet.\n </Typography>\n </TableCell>\n </TableRow>\n ) : (\n report.orgs.map((row) => (\n <TableRow key={row.orgId}>\n {/*\n The workspace's NAME. The route resolves it and\n falls back to the id, so a deleted workspace still\n renders a lead a staff member can search for rather\n than an empty cell. Not monospaced: monospace is how\n this page marks a value you copy, and a customer's\n name is one you read.\n */}\n <TableCell>{row.orgLabel ?? row.orgId}</TableCell>\n <TableCell align=\"right\">{row.messages}</TableCell>\n <TableCell align=\"right\">\n {row.inputTokens.toLocaleString()}\n </TableCell>\n <TableCell align=\"right\">\n {row.outputTokens.toLocaleString()}\n </TableCell>\n <TableCell align=\"right\">\n {row.cacheReadTokens.toLocaleString()}\n </TableCell>\n <TableCell align=\"right\">{row.down}</TableCell>\n <TableCell align=\"right\" sx={{ fontFamily: 'monospace' }}>\n {money(row.providerCostUsd)}\n </TableCell>\n </TableRow>\n ))\n )}\n </TableBody>\n </ScrollTable>\n <RankingFootnote\n shown={report?.orgs?.length ?? 0}\n total={report?.ranked?.orgs ?? 0}\n noun={{ one: 'workspace', many: 'workspaces' }}\n />\n </CardDisplay>\n </Stack>\n )\n}\n\nexport default AssistSignalsPage\n"],"names":["aiAddonName","pluginDocsHelp","CardDisplay","ScrollTable","Accordion","AccordionDetails","AccordionSummary","Alert","Box","Chip","LinearProgress","Stack","TableBody","TableCell","TableHead","TableRow","Typography","useEffect","useRef","useState","useUser","authorizedFetch","costSplitRows","freeTasteRefusals","kindTokenRows","money","value","Number","toFixed","Math","abs","percent","round","CostSplitTable","caption","label","rows","totalUsd","spacing","sx","width","variant","size","align","length","colSpan","color","map","row","key","messages","toLocaleString","providerCostUsd","RankingFootnote","shown","total","noun","counted","one","many","AssistSignalsPage","_props","totals","spend","report","data","user","signedInUid","uid","userRef","current","setReport","error","setError","loading","setLoading","undefined","active","response","body","json","catch","ok","prose","kindRows","byKind","severity","truncated","scanned","header","help","anchor","contentGutterX","contentGutterY","useFlexGap","direction","flexWrap","gap","deflectionRate","deflected","inputTokens","outputTokens","cacheReadRate","cacheWriteTokens","feedback","up","down","none","Object","entries","stopReasons","reason","count","freeSpend","paused","alerted","estCostUsd","ceilingUsd","requests","month","failed","mb","orgLabel","orgId","plan","aiAddon","credits","fontFamily","refusals","display","band","cap","budget","allotment","ranked","gutterBottom","byTier","byModel","kind","cacheReadTokens","outputP95","cacheHitRate","docsGaps","path","questions","downRate","orgs","ungrounded","routes","route","ungroundedRoutes","expandIcon","span","aria-hidden","alignItems","component","expired","question","answer","answerTruncated","exchangeId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,WAAW,QAAoC,eAAc;AACtE,SAASC,cAAc,QAAQ,mCAAkC;AACjE,SAASC,WAAW,QAAQ,uBAAsB;AAClD,SAASC,WAAW,QAAQ,yDAAwD;AACpF,SACEC,SAAS,EACTC,gBAAgB,EAChBC,gBAAgB,EAChBC,KAAK,EACLC,GAAG,EACHC,IAAI,EACJC,cAAc,EACdC,KAAK,EACLC,SAAS,EACTC,SAAS,EACTC,SAAS,EACTC,QAAQ,EACRC,UAAU,QACL,gBAAe;AACtB,SAASC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AACnD,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SAASC,eAAe,QAAQ,2CAA0C;AAK1E,SACEC,aAAa,EACbC,iBAAiB,EACjBC,aAAa,QACR,mCAA+B;AAWtC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoCC,GAED,MAAMC,QAAQ,CAACC,QACb,CAAC,CAAC,EAAE,AAACC,CAAAA,OAAOD,UAAU,CAAA,EAAGE,OAAO,CAACC,KAAKC,GAAG,CAACH,OAAOD,UAAU,KAAK,IAAI,IAAI,IAAI;AAE9E,MAAMK,UAAU,CAACL,QACfA,SAAS,OAAO,MAAM,GAAGG,KAAKG,KAAK,CAACN,QAAQ,KAAK,CAAC,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmCC,GACD,SAASO,eAAe,EACtBC,OAAO,EACPC,KAAK,EACLC,IAAI,EACJC,QAAQ,EAMT;IACC,qBACE,MAAC1B;QAAM2B,SAAS;QAAGC,IAAI;YAAEC,OAAO;QAAO;;0BACrC,KAACxB;gBAAWyB,SAAQ;0BAAaP;;0BACjC,MAAC/B;gBAAYuC,MAAK;;kCAChB,KAAC5B;kCACC,cAAA,MAACC;;8CACC,KAACF;8CAAWsB;;8CACZ,KAACtB;oCAAU8B,OAAM;8CAAQ;;8CACzB,KAAC9B;oCAAU8B,OAAM;8CAAQ;;8CACzB,KAAC9B;oCAAU8B,OAAM;8CAAQ;;;;;kCAG7B,KAAC/B;kCACE,CAACwB,KAAKQ,MAAM,iBACX,KAAC7B;sCACC,cAAA,KAACF;gCAAUgC,SAAS;0CAClB,cAAA,KAAC7B;oCAAWyB,SAAQ;oCAAQK,OAAM;8CAAiB;;;6BAMvDV,KAAKW,GAAG,CAAC,CAACC,oBACR,MAACjC;;kDACC,KAACF;kDAAWmC,IAAIC,GAAG;;kDACnB,KAACpC;wCAAU8B,OAAM;kDACdK,IAAIE,QAAQ,CAACC,cAAc;;kDAE9B,KAACtC;wCAAU8B,OAAM;kDAASlB,MAAMuB,IAAII,eAAe;;kDACnD,KAACvC;wCAAU8B,OAAM;kDACdZ,QAAQM,WAAW,IAAIW,IAAII,eAAe,GAAGf,WAAW;;;+BAP9CW,IAAIC,GAAG;;;;;;AAgBpC;AAEA;;;;;;;;;;;;;;CAcC,GACD,SAASI,gBAAgB,EACvBC,KAAK,EACLC,KAAK,EACLC,IAAI,EAML;IACC,IAAI,CAACD,OAAO,OAAO;IACnB,MAAME,UAAU,GAAGF,MAAMJ,cAAc,GAAG,CAAC,EAAEI,UAAU,IAAIC,KAAKE,GAAG,GAAGF,KAAKG,IAAI,EAAE;IACjF,qBACE,KAAC3C;QAAWyB,SAAQ;QAAUK,OAAM;kBACjCQ,SAASC,QACN,CAAC,YAAY,EAAEE,QAAQ,CAAC,CAAC,GACzB,CAAC,gBAAgB,EAAEH,MAAMH,cAAc,GAAG,IAAI,EAAEM,QAAQ,gFAAgF,CAAC;;AAGnJ;AAEA,OAAO,SAASG,kBAAkBC,MAA6B;4BAqG/CC;QA6GFC,aAiDGA,cAsJHC,kBA8BGA,mBACAA,gBAyBHA,2BAAAA,oBAsBGA,4BAAAA,qBACAA,iBAkIHA,cAwCGA,eACAA;IAlpBf,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAG9C;IACvB,gEAAgE;IAChE,MAAM+C,sBAAcD,wBAAAA,KAAME,GAAG,mBAAI;IACjC,MAAMC,UAAUnD,OAAOgD;IACvBG,QAAQC,OAAO,GAAGJ;IAClB,MAAM,CAACF,QAAQO,UAAU,GAAGpD,SAAoC;IAChE,MAAM,CAACqD,OAAOC,SAAS,GAAGtD,SAAwB;IAClD,MAAM,CAACuD,SAASC,WAAW,GAAGxD,SAAS;IAEvCF,UAAU;QACR,qEAAqE;QACrE,uEAAuE;QACvE,0DAA0D;QAC1D,MAAMiD,OAAOG,QAAQC,OAAO;QAC5B,IAAI,CAACH,eAAe,CAACD,MAAM,OAAOU;QAClC,IAAIC,SAAS;QACb,KAAK,AAAC,CAAA;YACJF,WAAW;YACXF,SAAS;YACT,IAAI;gBACF,MAAMK,WAAW,MAAMzD,gBAAgB6C,MAAM;gBAC7C,MAAMa,OAAO,MAAMD,SAASE,IAAI,GAAGC,KAAK,CAAC,IAAM;gBAC/C,IAAI,CAACJ,QAAQ;gBACb,IAAI,CAACC,SAASI,EAAE,EAAE;;oBAChBT,iBAASM,wBAAAA,KAAMP,KAAK,mBAAI;gBAC1B,OAAO;oBACLD,UAAUQ;gBACZ;YACF,EAAE,eAAM;gBACN,IAAIF,QAAQJ,SAAS;YACvB,SAAU;gBACR,IAAII,QAAQF,WAAW;YACzB;QACF,CAAA;QACA,OAAO;YACLE,SAAS;QACX;IACF,GAAG;QAACV;KAAY;IAEhB,MAAML,SAASE,0BAAAA,OAAQF,MAAM;IAC7B,2EAA2E;IAC3E,yEAAyE;IACzE,MAAMqB,iBAAUnB,0BAAD,AAACA,OAAgBmB,KAAK,oBAAI,EAAE;IAC3C,uEAAuE;IACvE,oEAAoE;IACpE,MAAMpB,iBAAUC,0BAAD,AAACA,OAAgBD,KAAK,oBAAI;IACzC,oEAAoE;IACpE,MAAMqB,WAAWtB,CAAAA,0BAAAA,OAAQuB,MAAM,IAAG7D,cAAcsC,OAAOuB,MAAM,IAAI,EAAE;IAEnE,qBACE,MAAC1E;QAAM2B,SAAS;;YACboC,yBAAW,KAAChE;YACZ8D,uBAAS,KAACjE;gBAAM+E,UAAS;0BAASd;;YAClCR,CAAAA,0BAAAA,OAAQuB,SAAS,KAChB,4DAA4D;YAC5D,8DAA8D;YAC9D,QAAQ;0BACR,KAAChF;gBAAM+E,UAAS;0BACb,CAAC,iBAAiB,EAAEtB,OAAOwB,OAAO,CAACrC,cAAc,GAAG,wFAAwF,CAAC;;0BAIlJ,KAACjD;gBACCuF,QAAQ;gBACRC,MAAMzF,eAAe,iBAAiB;oBAAE0F,QAAQ;gBAAS;gBACzDC,cAAc;gBACdC,cAAc;0BAEb,CAAC/B,uBACA,KAAC9C;oBAAWyB,SAAQ;oBAAQK,OAAM;8BAC/B4B,UAAU,qBAAqB;qBAGlC,2DAA2D;gBAC3D,qDAAqD;gBACrD,0DAA0D;8BAC1D,MAAC/D;oBAAMmF,UAAU;oBAACC,WAAU;oBAAMxD,IAAI;wBAAEyD,UAAU;wBAAQC,KAAK;oBAAE;;sCAC/D,KAACxF;4BAAK0B,OAAO,GAAG2B,OAAOZ,QAAQ,CAACC,cAAc,GAAG,MAAM,CAAC;;sCACxD,KAAC1C;4BAAK0B,OAAO,GAAGV,MAAMqC,OAAOV,eAAe,EAAE,UAAU,CAAC;;sCAazD,KAAC3C;4BACC,2DAA2D;4BAC3D,oDAAoD;4BACpD,uDAAuD;4BACvD,yDAAyD;4BACzD,sDAAsD;4BACtD,kDAAkD;4BAClD,0BAA0B;4BAC1B0B,OAAO,CAAC,cAAc,EAAEJ,QAAQ+B,OAAOoC,cAAc,EAAE,EAAE,EAAE,EACzDpC,oBAAAA,OAAOqC,SAAS,YAAhBrC,oBAAoB,GACpBX,cAAc,GAAG,CAAC,CAAC;4BACrBL,OACEgB,OAAOoC,cAAc,IAAI,QAAQpC,OAAOoC,cAAc,GAAG,MACrD,YACA;;sCAGR,KAACzF;4BACC0B,OAAO,GAAG2B,OAAOsC,WAAW,CAACjD,cAAc,GAAG,MAAM,EAAEW,OAAOuC,YAAY,CAAClD,cAAc,GAAG,IAAI,CAAC;;sCAElG,KAAC1C;4BACC0B,OAAO,CAAC,YAAY,EAAEJ,QAAQ+B,OAAOwC,aAAa,GAAG;4BACrDxD,OACEgB,OAAOwC,aAAa,IAAI,QAAQxC,OAAOwC,aAAa,GAAG,MACnD,YACA;;sCAWR,KAAC7F;4BACC0B,OAAO,CAAC,aAAa,EAAE2B,OAAOyC,gBAAgB,CAACpD,cAAc,IAAI;;sCAEnE,KAAC1C;4BACC0B,OAAO,CAAC,GAAG,EAAE2B,OAAO0C,QAAQ,CAACC,EAAE,CAAC,MAAM,EAAE3C,OAAO0C,QAAQ,CAACE,IAAI,CAAC,WAAW,EAAE5C,OAAO0C,QAAQ,CAACG,IAAI,EAAE;;wBAEjGC,OAAOC,OAAO,CAAC/C,OAAOgD,WAAW,EAAE/D,GAAG,CAAC,CAAC,CAACgE,QAAQC,MAAM,iBACtD,KAACvG;gCAECiC,MAAK;gCACL,kDAAkD;gCAClD,uDAAuD;gCACvD,wDAAwD;gCACxD,6BAA6B;gCAC7BI,OACEiE,WAAW,aAAaA,WAAW,eAC/B,YACA;gCAEN5E,OAAO,GAAG4E,OAAO,EAAE,EAAEC,OAAO;+BAXvBD;wBAsBR/C,OAAOiD,SAAS,iBACf,KAACxG;4BACCqC,OACEkB,OAAOiD,SAAS,CAACC,MAAM,GACnB,UACAlD,OAAOiD,SAAS,CAACE,OAAO,GACtB,YACA;4BAERhF,OACE,CAAC,gBAAgB,EAAEV,MAAMuC,OAAOiD,SAAS,CAACG,UAAU,EAAE,IAAI,CAAC,GAC3D,GAAG3F,MAAMuC,OAAOiD,SAAS,CAACI,UAAU,EAAE,WAAW,CAAC,GAClD,GAAGrD,OAAOiD,SAAS,CAACK,QAAQ,CAACnE,cAAc,GAAG,SAAS,CAAC,GACvDa,CAAAA,OAAOiD,SAAS,CAACC,MAAM,GAAG,6BAA6B,EAAC;6BAG3D;;;;0BASV,MAAChH;gBACCuF,QAAQ,CAAC,iCAAiC,EAAE1B,QAAQ,CAAC,GAAG,EAAEA,MAAMwD,KAAK,EAAE,GAAG,IAAI;gBAC9E7B,MAAMzF,eAAe,gBAAgB;oBAAE0F,QAAQ;gBAAyB;gBACxEC,cAAc;gBACdC,cAAc;;oBAEb9B,CAAAA,yBAAAA,MAAOyD,MAAM,kBACZ,KAACjH;wBAAM+E,UAAS;wBAAU/C,IAAI;4BAAEkF,IAAI;wBAAE;kCACnC;yBAED1D,CAAAA,yBAAAA,MAAOwB,SAAS,kBAClB,KAAChF;wBAAM+E,UAAS;wBAAU/C,IAAI;4BAAEkF,IAAI;wBAAE;kCACnC;yBAED;kCACJ,MAACtH;wBAAYuC,MAAK;;0CAChB,KAAC5B;0CACC,cAAA,MAACC;;sDACC,KAACF;sDAAU;;sDACX,KAACA;sDAAU;;sDACX,KAACA;sDAAWb;;sDACZ,KAACa;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;;;;0CAG7B,KAAC/B;0CACE,EAACmD,0BAAAA,cAAAA,MAAO3B,IAAI,qBAAX2B,YAAanB,MAAM,kBACnB,KAAC7B;8CACC,cAAA,KAACF;wCAAUgC,SAAS;kDAClB,cAAA,KAAC7B;4CAAWyB,SAAQ;4CAAQK,OAAM;sDAAiB;;;qCAMvDiB,MAAM3B,IAAI,CAACW,GAAG,CAAC,CAACC;wCAEAA;yDADd,MAACjC;;0DACC,KAACF;2DAAWmC,gBAAAA,IAAI0E,QAAQ,YAAZ1E,gBAAgBA,IAAI2E,KAAK;;0DACrC,KAAC9G;0DAAWmC,IAAI4E,IAAI;;0DACpB,KAAC/G;0DACC,cAAA,KAACJ;oDACCiC,MAAK;oDACLD,SAASO,IAAI6E,OAAO,GAAG,WAAW;oDAClC/E,OAAOE,IAAI6E,OAAO,GAAG,YAAY;oDACjC1F,OAAOa,IAAI6E,OAAO,GAAG,OAAO;;;0DAGhC,KAAChH;gDAAU8B,OAAM;0DACdK,IAAI8E,OAAO,CAAC3E,cAAc;;0DAE7B,KAACtC;gDAAU8B,OAAM;gDAAQJ,IAAI;oDAAEwF,YAAY;gDAAY;0DACpDtG,MAAMuB,IAAII,eAAe;;0DAM5B,MAACvC;gDAAU8B,OAAM;;oDACdK,IAAIgF,QAAQ,CAACzE,KAAK,CAACJ,cAAc;oDACjCH,IAAIgF,QAAQ,CAACzE,KAAK,GAAG,kBACpB,KAACvC;wDACCyB,SAAQ;wDACRK,OAAM;wDACNP,IAAI;4DAAE0F,SAAS;wDAAQ;kEAEtB,CAAC,KAAK,EAAEjF,IAAIgF,QAAQ,CAACE,IAAI,CAAC,WAAW,EAAElF,IAAIgF,QAAQ,CAACG,GAAG,CAAC,YAAY,EAAEnF,IAAIgF,QAAQ,CAAC9E,QAAQ,CAAC,YAAY,EAAEF,IAAIgF,QAAQ,CAACI,MAAM,GAAGpF,IAAIgF,QAAQ,CAACK,SAAS,GAAG,IAAI,CAAC,cAAc,EAAErF,IAAIgF,QAAQ,CAACK,SAAS,EAAE,GAAG,KAAK9G,kBAAkByB,IAAIgF,QAAQ,IAAI,IAAI,CAAC,cAAc,EAAEzG,kBAAkByB,IAAIgF,QAAQ,GAAG,GAAG,IAAI;yDAE9S;;;;uCA/BOhF,IAAI2E,KAAK;;;;;kCAsChC,KAACtE;wBACCC,KAAK,WAAES,0BAAAA,eAAAA,MAAO3B,IAAI,qBAAX2B,aAAanB,MAAM,oBAAI;wBAC9BW,KAAK,WAAEQ,yBAAAA,MAAOuE,MAAM,oBAAI;wBACxB9E,MAAM;4BAAEE,KAAK;4BAAaC,MAAM;wBAAa;;;;0BAIjD,MAACzD;gBACCuF,QAAQ;gBACRC,MAAMzF,eAAe,iBAAiB;oBAAE0F,QAAQ;gBAAwB;gBACxEC,cAAc;gBACdC,cAAc;;kCAEd,KAAC7E;wBAAWyB,SAAQ;wBAAQK,OAAM;wBAAiByF,YAAY;kCAAC;;oBAM/D,CAACzE,uBACA,KAAC9C;wBAAWyB,SAAQ;wBAAQK,OAAM;kCAC/B4B,UAAU,qBAAqB;yBAEhCZ,OAAOV,eAAe,KAAK,IAC7B;;;;;;;;;;;;;;;;;;WAkBC,iBACD,KAACpC;wBAAWyB,SAAQ;wBAAQK,OAAM;kCAC/BgB,OAAOqC,SAAS,IAAIrC,OAAOZ,QAAQ,GAChC,CAAC,uBAAuB,EAAEY,OAAOZ,QAAQ,CAACC,cAAc,GAAG,uJAAuJ,CAAC,GACnN,GAAG,AAACW,CAAAA,OAAOZ,QAAQ,GAAGY,OAAOqC,SAAS,AAAD,EAAGhD,cAAc,GAAG,IAAI,EAAEW,OAAOZ,QAAQ,CAACC,cAAc,GAAG,qLAAqL,CAAC;uCAG5R,MAACxC;wBAAM2B,SAAS;;0CACd,KAACL;gCACCC,SAAQ;gCACRC,OAAM;gCACNC,MAAMd,cAAcwC,OAAO0E,MAAM;gCACjCnG,UAAUyB,OAAOV,eAAe;;0CAElC,KAACnB;gCACCC,SAAQ;gCACRC,OAAM;gCACNC,MAAMd,cAAcwC,OAAO2E,OAAO;gCAClCpG,UAAUyB,OAAOV,eAAe;;;;;;0BAWxC,MAAClD;gBACCuF,QAAQ;gBACRC,MAAMzF,eAAe,iBAAiB;oBAAE0F,QAAQ;gBAAkB;gBAClEC,cAAc;gBACdC,cAAc;;kCAEd,KAAC7E;wBAAWyB,SAAQ;wBAAQK,OAAM;wBAAiByF,YAAY;kCAAC;;kCAMhE,MAACpI;wBAAYuC,MAAK;;0CAChB,KAAC5B;0CACC,cAAA,MAACC;;sDACC,KAACF;sDAAU;;sDACX,KAACA;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;;;;0CAG7B,KAAC/B;0CACE,CAACwE,SAASxC,MAAM,iBACf,KAAC7B;8CACC,cAAA,KAACF;wCAAUgC,SAAS;kDAClB,cAAA,KAAC7B;4CAAWyB,SAAQ;4CAAQK,OAAM;sDAAiB;;;qCAMvDsC,SAASrC,GAAG,CAAC,CAACC,oBACZ,MAACjC;;0DACC,KAACF;0DAAWmC,IAAI0F,IAAI;;0DACpB,KAAC7H;gDAAU8B,OAAM;0DAASK,IAAIE,QAAQ,CAACC,cAAc;;0DACrD,KAACtC;gDAAU8B,OAAM;gDAAQJ,IAAI;oDAAEwF,YAAY;gDAAY;0DACpDtG,MAAMuB,IAAIE,QAAQ,GAAGF,IAAII,eAAe,GAAGJ,IAAIE,QAAQ,GAAG;;0DAE7D,KAACrC;gDAAU8B,OAAM;0DAASK,IAAIoD,WAAW,CAACjD,cAAc;;0DACxD,KAACtC;gDAAU8B,OAAM;0DAASK,IAAI2F,eAAe,CAACxF,cAAc;;0DAC5D,KAACtC;gDAAU8B,OAAM;0DAASK,IAAIuD,gBAAgB,CAACpD,cAAc;;0DAC7D,KAACtC;gDAAU8B,OAAM;0DAASK,IAAIqD,YAAY,CAAClD,cAAc;;0DACzD,KAACtC;gDAAU8B,OAAM;0DAASK,IAAI4F,SAAS,CAACzF,cAAc;;0DACtD,KAACtC;gDAAU8B,OAAM;0DAASZ,QAAQiB,IAAI6F,YAAY;;;uCAXrC7F,IAAI0F,IAAI;;;;;;0BAmBjC,MAACxI;gBACCuF,QAAQ;gBACRC,MAAMzF,eAAe,iBAAiB;oBAAE0F,QAAQ;gBAAa;gBAC7DC,cAAc;gBACdC,cAAc;;kCAEd,KAAC7E;wBAAWyB,SAAQ;wBAAQK,OAAM;wBAAiByF,YAAY;kCAAC;;kCAKhE,MAACpI;wBAAYuC,MAAK;;0CAChB,KAAC5B;0CACC,cAAA,MAACC;;sDACC,KAACF;sDAAU;;sDACX,KAACA;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;;;;0CAG7B,KAAC/B;0CACE,EAACoD,2BAAAA,mBAAAA,OAAQ8E,QAAQ,qBAAhB9E,iBAAkBpB,MAAM,kBACxB,KAAC7B;8CACC,cAAA,KAACF;wCAAUgC,SAAS;kDAClB,cAAA,KAAC7B;4CAAWyB,SAAQ;4CAAQK,OAAM;sDAAiB;;;qCAMvDkB,OAAO8E,QAAQ,CAAC/F,GAAG,CAAC,CAACC,oBACnB,MAACjC;;0DACC,KAACF;gDAAU0B,IAAI;oDAAEwF,YAAY;gDAAY;0DACtC/E,IAAI+F,IAAI;;0DAEX,KAAClI;gDAAU8B,OAAM;0DAASK,IAAIgG,SAAS;;0DACvC,KAACnI;gDAAU8B,OAAM;0DAASK,IAAIyD,EAAE;;0DAChC,KAAC5F;gDAAU8B,OAAM;0DAASK,IAAI0D,IAAI;;0DAClC,KAAC7F;gDAAU8B,OAAM;0DACdZ,QAAQiB,IAAIiG,QAAQ;;0DAEvB,KAACpI;gDAAU8B,OAAM;0DAASK,IAAIkG,IAAI;;0DAClC,KAACrI;gDAAU8B,OAAM;gDAAQJ,IAAI;oDAAEwF,YAAY;gDAAY;0DACpDtG,MAAMuB,IAAII,eAAe;;;uCAZfJ,IAAI+F,IAAI;;;;kCAmB/B,KAAC1F;wBACCC,KAAK,WAAEU,2BAAAA,oBAAAA,OAAQ8E,QAAQ,qBAAhB9E,kBAAkBpB,MAAM,oBAAI;wBACnCW,KAAK,WAAES,2BAAAA,iBAAAA,OAAQsE,MAAM,qBAAdtE,eAAgB8E,QAAQ,oBAAI;wBACnCtF,MAAM;4BAAEE,KAAK;4BAAcC,MAAM;wBAAc;;;;0BAInD,MAACzD;gBACCuF,QAAQ;gBACRC,MAAMzF,eAAe,iBAAiB;oBAAE0F,QAAQ;gBAAuC;gBACvFC,cAAc;gBACdC,cAAc;;kCAEd,KAAC7E;wBAAWyB,SAAQ;wBAAQK,OAAM;wBAAiByF,YAAY;kCAC5DvE,SACG,GAAGA,OAAOmF,UAAU,CAACH,SAAS,CAAC7F,cAAc,GAAG,wCAAwC,EAAEa,OAAOmF,UAAU,CAACzC,IAAI,CAAC,6IAA6I,CAAC,GAC/P;;kCAEN,MAACvG;wBAAYuC,MAAK;;0CAChB,KAAC5B;0CACC,cAAA,MAACC;;sDACC,KAACF;sDAAU;;sDACX,KAACA;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;;;;0CAG7B,KAAC/B;0CACE,EAACoD,2BAAAA,qBAAAA,OAAQmF,UAAU,sBAAlBnF,4BAAAA,mBAAoBoF,MAAM,qBAA1BpF,0BAA4BpB,MAAM,kBAClC,KAAC7B;8CACC,cAAA,KAACF;wCAAUgC,SAAS;kDAClB,cAAA,KAAC7B;4CAAWyB,SAAQ;4CAAQK,OAAM;sDAAiB;;;qCAMvDkB,OAAOmF,UAAU,CAACC,MAAM,CAACrG,GAAG,CAAC,CAACC,oBAC5B,MAACjC;;0DACC,KAACF;gDAAU0B,IAAI;oDAAEwF,YAAY;gDAAY;0DACtC/E,IAAIqG,KAAK;;0DAEZ,KAACxI;gDAAU8B,OAAM;0DAASK,IAAIgG,SAAS;;0DACvC,KAACnI;gDAAU8B,OAAM;0DAASK,IAAI0D,IAAI;;;uCALrB1D,IAAIqG,KAAK;;;;kCAWhC,KAAChG;wBACCC,KAAK,WAAEU,2BAAAA,sBAAAA,OAAQmF,UAAU,sBAAlBnF,6BAAAA,oBAAoBoF,MAAM,qBAA1BpF,2BAA4BpB,MAAM,oBAAI;wBAC7CW,KAAK,WAAES,2BAAAA,kBAAAA,OAAQsE,MAAM,qBAAdtE,gBAAgBsF,gBAAgB,oBAAI;wBAC3C9F,MAAM;4BAAEE,KAAK;4BAAgBC,MAAM;wBAAgB;;;;0BAoBvD,MAACzD;gBACCuF,QAAQ;gBACRC,MAAMzF,eAAe,iBAAiB;oBAAE0F,QAAQ;gBAA8B;gBAC9EC,cAAc;gBACdC,cAAc;;kCAEd,KAAC7E;wBAAWyB,SAAQ;wBAAQK,OAAM;wBAAiByF,YAAY;kCAC5D,kEACC,iEACA,+DACA,+DACA;;oBAEH,CAACpD,MAAMvC,MAAM,iBACZ,KAAC5B;wBAAWyB,SAAQ;wBAAQK,OAAM;kCAC/B4B,UACG,uBACA;uCAGN,MAACtE;;0CACC,KAACE;gCAAiBiJ,0BAAY,KAACC;oCAAKC,aAAW;8CAAE;;0CAC/C,cAAA,KAACzI;oCAAWyB,SAAQ;8CACjB,CAAC,KAAK,EAAE0C,MAAMvC,MAAM,CAAC,SAAS,EAAEuC,MAAMvC,MAAM,KAAK,IAAI,KAAK,KAAK;;;0CAGpE,KAACvC;0CACC,cAAA,KAACM;oCAAM2B,SAAS;8CACb6C,MAAMpC,GAAG,CAAC,CAACC;4CAuBGA;6DAtBb,MAACrC;4CAA6C2B,SAAS;;8DACrD,MAAC3B;oDACCmF,UAAU;oDACVC,WAAU;oDACVxD,IAAI;wDAAEyD,UAAU;wDAAQC,KAAK;wDAAGyD,YAAY;oDAAS;;sEAErD,KAACjJ;4DACCiC,MAAK;4DACLI,OAAOE,IAAIwD,QAAQ,KAAK,SAAS,UAAU;4DAC3CrE,OACEa,IAAIwD,QAAQ,KAAK,SACb,eACA;;sEAGR,MAACxF;4DAAWyB,SAAQ;4DAAUK,OAAM;;8EAClC,KAACtC;oEACCmJ,WAAU;oEACVpH,IAAI;wEAAEwF,YAAY;oEAAY;8EAE7B/E,IAAIqG,KAAK;;gEAEX,CAAC,GAAG,GAAErG,gBAAAA,IAAI0E,QAAQ,YAAZ1E,gBAAgBA,IAAI2E,KAAK,EAAE;;;;;gDAGrC3E,IAAI4G,OAAO,GACV,kDAAkD;gDAClD,+CAA+C;gDAC/C,gCAAgC;8DAChC,KAAC5I;oDAAWyB,SAAQ;oDAAQK,OAAM;8DAC/B,oDACC,+CACA;mEAGJ;;sEACE,KAAC9B;4DAAWyB,SAAQ;sEACjBO,IAAI6G,QAAQ,IAAI;;wDAElB7G,IAAI8G,MAAM,iBACT,KAAC9I;4DACCyB,SAAQ;4DACRK,OAAM;sEAEL,CAAC,SAAS,EAAEE,IAAI8G,MAAM,GAAG9G,IAAI+G,eAAe,GAAG,MAAM,IAAI;6DAE1D;;;;2CA9CE,GAAG/G,IAAI2E,KAAK,CAAC,CAAC,EAAE3E,IAAIgH,UAAU,EAAE;;;;;;;;0BAyDxD,MAAC9J;gBACCuF,QAAQ;gBACRC,MAAMzF,eAAe,iBAAiB;oBAAE0F,QAAQ;gBAAkC;gBAClFC,cAAc;gBACdC,cAAc;;kCAEd,KAAC7E;wBAAWyB,SAAQ;wBAAQK,OAAM;wBAAiByF,YAAY;kCAAC;;kCAIhE,MAACpI;wBAAYuC,MAAK;;0CAChB,KAAC5B;0CACC,cAAA,MAACC;;sDACC,KAACF;sDAAU;;sDACX,KAACA;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;sDACzB,KAAC9B;4CAAU8B,OAAM;sDAAQ;;;;;0CAG7B,KAAC/B;0CACE,EAACoD,2BAAAA,eAAAA,OAAQkF,IAAI,qBAAZlF,aAAcpB,MAAM,kBACpB,KAAC7B;8CACC,cAAA,KAACF;wCAAUgC,SAAS;kDAClB,cAAA,KAAC7B;4CAAWyB,SAAQ;4CAAQK,OAAM;sDAAiB;;;qCAMvDkB,OAAOkF,IAAI,CAACnG,GAAG,CAAC,CAACC;wCAUDA;yDATd,MAACjC;;0DASC,KAACF;2DAAWmC,gBAAAA,IAAI0E,QAAQ,YAAZ1E,gBAAgBA,IAAI2E,KAAK;;0DACrC,KAAC9G;gDAAU8B,OAAM;0DAASK,IAAIE,QAAQ;;0DACtC,KAACrC;gDAAU8B,OAAM;0DACdK,IAAIoD,WAAW,CAACjD,cAAc;;0DAEjC,KAACtC;gDAAU8B,OAAM;0DACdK,IAAIqD,YAAY,CAAClD,cAAc;;0DAElC,KAACtC;gDAAU8B,OAAM;0DACdK,IAAI2F,eAAe,CAACxF,cAAc;;0DAErC,KAACtC;gDAAU8B,OAAM;0DAASK,IAAI0D,IAAI;;0DAClC,KAAC7F;gDAAU8B,OAAM;gDAAQJ,IAAI;oDAAEwF,YAAY;gDAAY;0DACpDtG,MAAMuB,IAAII,eAAe;;;uCAtBfJ,IAAI2E,KAAK;;;;;kCA6BhC,KAACtE;wBACCC,KAAK,WAAEU,2BAAAA,gBAAAA,OAAQkF,IAAI,qBAAZlF,cAAcpB,MAAM,oBAAI;wBAC/BW,KAAK,YAAES,2BAAAA,kBAAAA,OAAQsE,MAAM,qBAAdtE,gBAAgBkF,IAAI,qBAAI;wBAC/B1F,MAAM;4BAAEE,KAAK;4BAAaC,MAAM;wBAAa;;;;;;AAKvD;AAEA,eAAeC,kBAAiB"}
|
|
@@ -308,6 +308,7 @@ const creditsColumn = (credits)=>({
|
|
|
308
308
|
children: 'Give a member or a whole site a share of the workspace’s AI credits each month. ' + 'A hard allotment stops AI requests at the line; a soft one keeps going and alerts ' + 'you at 75%, 80%, 90% and 100%. The workspace’s own credits still stop first.'
|
|
309
309
|
}),
|
|
310
310
|
/*#__PURE__*/ _jsxs(Stack, {
|
|
311
|
+
useFlexGap: true,
|
|
311
312
|
direction: "row",
|
|
312
313
|
spacing: 1,
|
|
313
314
|
sx: {
|
|
@@ -347,6 +348,7 @@ const creditsColumn = (credits)=>({
|
|
|
347
348
|
onOpen: (_id, row)=>editMember(row)
|
|
348
349
|
} : {})),
|
|
349
350
|
/*#__PURE__*/ _jsxs(Stack, {
|
|
351
|
+
useFlexGap: true,
|
|
350
352
|
direction: "row",
|
|
351
353
|
spacing: 1,
|
|
352
354
|
sx: {
|
|
@@ -419,6 +421,7 @@ const creditsColumn = (credits)=>({
|
|
|
419
421
|
]
|
|
420
422
|
}) : null,
|
|
421
423
|
data.restrictionAvailable ? /*#__PURE__*/ _jsxs(Stack, {
|
|
424
|
+
useFlexGap: true,
|
|
422
425
|
direction: "row",
|
|
423
426
|
spacing: 1,
|
|
424
427
|
sx: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/billing-ai-allotments.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiPencilOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Button, Stack, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useMemo, useState } from 'react'\nimport { AI_ALLOTMENT_ORG_SUBJECT, aiAllotmentState } from '../model/ai-allotments'\nimport type {\n AiAllotmentRowWire,\n AiAllotmentsHostWire,\n AiAllotmentsMemberWire,\n} from '../usage/ai-usage-wire'\nimport { AiAllotmentEditor, type AiAllotmentValue } from './ai-allotment-editor.component'\nimport { saveAiAllotments, useAiAllotments } from './use-ai-allotments'\n\n/** An allotment as a table cell reads it: used of credits, mode, models. */\nexport function aiAllotmentSummary(\n row: Pick<AiAllotmentRowWire, 'credits' | 'mode' | 'models' | 'used'> | null | undefined,\n): string {\n if (!row) return '—'\n const parts: string[] = []\n if (row.credits !== null) {\n parts.push(`${row.used.toLocaleString()} of ${row.credits.toLocaleString()} (${row.mode})`)\n }\n if (row.models?.length) {\n parts.push(`${row.models.length} model${row.models.length === 1 ? '' : 's'}`)\n }\n return parts.join(' · ') || '—'\n}\n\n/** The color an allotment's figure reads in: warning from 80%, error at it. */\nexport function aiAllotmentTone(\n row: Pick<AiAllotmentRowWire, 'credits' | 'used'> | null | undefined,\n): 'text.primary' | 'warning.main' | 'error.main' {\n if (!row || row.credits === null) return 'text.primary'\n const state = aiAllotmentState(row.used, row.credits)\n return state === 'reached' ? 'error.main' : state === 'warn' ? 'warning.main' : 'text.primary'\n}\n\ntype Editing =\n | {\n kind: 'subjects'\n subjects: string[]\n title: string\n description: string\n initial: AiAllotmentValue | null\n removable: boolean\n }\n | { kind: 'everySite' }\n | { kind: 'restriction'; initial: AiAllotmentValue | null; removable: boolean }\n\nconst valueOf = (row: AiAllotmentRowWire | undefined): AiAllotmentValue | null =>\n row ? { credits: row.credits, mode: row.mode, models: row.models } : null\n\n/**\n * A row of one of the three lists: the subject its allotment is stored under,\n * that subject's allotment when it has one, and `$id` — the id `ListTable`\n * selects by, which is the member's uid, the site's id, or the collaborator's\n * subject.\n */\ntype AllotmentListRow = {\n $id: string\n subject: string\n allotment: AiAllotmentRowWire | undefined\n}\ntype MemberRow = AllotmentListRow & { member: AiAllotmentsMemberWire }\ntype SiteRow = AllotmentListRow & { host: AiAllotmentsHostWire }\ntype CollaboratorRow = AllotmentListRow & { name: string; site: string }\n\n/** The allotment column: used of credits, in the tone its share has reached. */\nconst allotmentColumn = <Row extends AllotmentListRow>(): GridColDef<Row> => ({\n field: 'allotment',\n headerName: 'Allotment',\n flex: 1,\n minWidth: 200,\n sortable: false,\n valueGetter: (_value, row) => aiAllotmentSummary(row.allotment),\n renderCell: ({ row }) => (\n <Typography variant=\"body2\" noWrap sx={{ color: aiAllotmentTone(row.allotment) }}>\n {aiAllotmentSummary(row.allotment)}\n </Typography>\n ),\n})\n\nconst creditsColumn = <Row extends AllotmentListRow>(\n credits: (row: Row) => number,\n): GridColDef<Row> => ({\n field: 'credits',\n headerName: 'Credits this month',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 170,\n valueGetter: (_value, row) => credits(row),\n valueFormatter: (value: number) => value.toLocaleString(),\n})\n\n/**\n * AI ALLOTMENTS ON BILLING → USAGE (AGL-2942): who may draw how much of the\n * workspace's AI pool.\n *\n * Team members and sites, each with this month's credits and its allotment;\n * select several and set one allotment for all of them, or set the same\n * allotment on every site at once — the agency's \"same for every client\n * site\". Collaborators' allotments, which their sites' Users cards set, are\n * listed below. On a plan that offers it, the organization's model\n * restriction sits at the end.\n *\n * Read with `billing.view`; the controls appear with `billing.manage`, and\n * the route refuses a write without it whatever this card draws.\n *\n * The three tables are the route's whole answer — the roster the plan's\n * seats bound, the organization's sites, the collaborator allotments — so\n * they offer no Filters panel and no search: a bounded readout is read\n * whole, and a grid that narrowed the rows it holds is the pattern every\n * list is leaving (AGL-3321).\n */\nexport function BillingAiAllotments({ orgId }: { orgId?: string }) {\n const { data: user } = useUser()\n const allotments = useAiAllotments({ orgId })\n const [members, setMembers] = useState<string[]>([])\n const [sites, setSites] = useState<string[]>([])\n const [editing, setEditing] = useState<Editing | null>(null)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const data = allotments.data\n const bySubject = useMemo(\n () => new Map((data?.allotments ?? []).map((row) => [row.subject, row])),\n [data],\n )\n\n if (allotments.status === 'refused') {\n return (\n <Alert severity=\"info\">{'Seeing AI allotments requires the View billing permission.'}</Alert>\n )\n }\n if (allotments.status === 'error' && !data) {\n return (\n <Alert severity=\"warning\">\n {'Could not read the AI allotments — a failed read, not an empty list.'}\n </Alert>\n )\n }\n if (!data || !orgId) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n )\n }\n\n const canEdit = data.canEdit.billing\n const team = data.members.filter((member) => member.orgWide)\n const nameOf = (uid: string | null) =>\n data.members.find((member) => member.uid === uid)?.name ?? uid ?? ''\n const siteName = (hostId: string | null) =>\n data.hosts.find((host) => host.hostId === hostId)?.name ?? hostId ?? ''\n const collaborators = data.allotments.filter((row) => row.scope === 'collab')\n const restriction = bySubject.get(AI_ALLOTMENT_ORG_SUBJECT)\n /*\n * Each list pages and sorts on its own, and a selection is kept by member\n * and site id rather than by row, so it survives a page change.\n */\n const teamRows: MemberRow[] = team.map((member) => ({\n $id: member.uid,\n subject: `member:${member.uid}`,\n allotment: bySubject.get(`member:${member.uid}`),\n member,\n }))\n const siteRows: SiteRow[] = data.hosts.map((host) => ({\n $id: host.hostId,\n subject: `host:${host.hostId}`,\n allotment: bySubject.get(`host:${host.hostId}`),\n host,\n }))\n const collaboratorRows: CollaboratorRow[] = collaborators.map((row) => ({\n $id: row.subject,\n subject: row.subject,\n allotment: row,\n name: nameOf(row.uid),\n site: siteName(row.hostId),\n }))\n\n const editSubjects = (subjects: string[], title: string, description: string) => {\n const rows = subjects.map((subject) => bySubject.get(subject))\n setError(null)\n setEditing({\n kind: 'subjects',\n subjects,\n title,\n description,\n initial: subjects.length === 1 ? valueOf(rows[0]) : null,\n removable: rows.every(Boolean),\n })\n }\n\n const run = async (body: Parameters<typeof saveAiAllotments>[1]) => {\n setBusy(true)\n setError(null)\n const result = await saveAiAllotments(user, body)\n setBusy(false)\n if (!result.ok) {\n setError(result.error)\n return\n }\n setEditing(null)\n setMembers([])\n setSites([])\n }\n const save = (value: AiAllotmentValue) => {\n if (!editing) return\n if (editing.kind === 'everySite') return void run({ orgId, everySite: value })\n if (editing.kind === 'restriction') {\n return void run(\n value.models\n ? { orgId, set: [{ subject: AI_ALLOTMENT_ORG_SUBJECT, ...value, credits: null }] }\n : { orgId, remove: [AI_ALLOTMENT_ORG_SUBJECT] },\n )\n }\n return void run({\n orgId,\n set: editing.subjects.map((subject) => ({ subject, ...value })),\n })\n }\n const remove = () => {\n if (!editing || editing.kind === 'everySite') return\n return void run({\n orgId,\n remove: editing.kind === 'restriction' ? [AI_ALLOTMENT_ORG_SUBJECT] : editing.subjects,\n })\n }\n const editMember = (row: MemberRow) =>\n editSubjects([row.subject], `AI allotment — ${row.member.name}`, 'Counts what this person draws across every site.')\n const editSite = (row: SiteRow) =>\n editSubjects([row.subject], `AI allotment — ${row.host.name}`, 'Counts what everyone draws on the site, together.')\n const editCollaborator = (row: CollaboratorRow) =>\n editSubjects(\n [row.subject],\n `AI allotment — ${row.name} on ${row.site}`,\n 'Counts what this collaborator draws on this site.',\n )\n /** The one action a row has: set its allotment, or edit the one it has. */\n const editColumn = <Row extends AllotmentListRow>(label: (row: Row) => string, edit: (row: Row) => void) =>\n listActionsColumn((row: Row) => (\n <ListRowActions\n label={label(row)}\n quick={{\n icon: mdiPencilOutline.path,\n label: row.allotment ? 'Edit allotment' : 'Set allotment',\n onClick: () => edit(row),\n }}\n items={[]}\n />\n ))\n\n const teamColumns: GridColDef<MemberRow>[] = [\n { field: 'name', headerName: 'Member', flex: 1, minWidth: 180, valueGetter: (_value, row) => row.member.name },\n creditsColumn<MemberRow>((row) => row.member.credits),\n allotmentColumn<MemberRow>(),\n ...(canEdit ? [editColumn<MemberRow>((row) => row.member.name, editMember)] : []),\n ]\n const siteColumns: GridColDef<SiteRow>[] = [\n { field: 'name', headerName: 'Site', flex: 1, minWidth: 180, valueGetter: (_value, row) => row.host.name },\n creditsColumn<SiteRow>((row) => row.host.credits),\n allotmentColumn<SiteRow>(),\n ...(canEdit ? [editColumn<SiteRow>((row) => row.host.name, editSite)] : []),\n ]\n const collaboratorColumns: GridColDef<CollaboratorRow>[] = [\n { field: 'name', headerName: 'Collaborator', flex: 1, minWidth: 180 },\n { field: 'site', headerName: 'Site', flex: 1, minWidth: 160 },\n allotmentColumn<CollaboratorRow>(),\n ...(canEdit ? [editColumn<CollaboratorRow>((row) => `${row.name} on ${row.site}`, editCollaborator)] : []),\n ]\n\n return (\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Give a member or a whole site a share of the workspace’s AI credits each month. ' +\n 'A hard allotment stops AI requests at the line; a soft one keeps going and alerts ' +\n 'you at 75%, 80%, 90% and 100%. The workspace’s own credits still stop first.'}\n </Typography>\n\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Team members'}\n </Typography>\n {canEdit ? (\n <Button\n size=\"small\"\n disabled={!members.length}\n onClick={() =>\n editSubjects(\n members.map((uid) => `member:${uid}`),\n members.length === 1 ? `AI allotment — ${nameOf(members[0])}` : `AI allotment — ${members.length} members`,\n 'Counts what each person draws across every site.',\n )\n }\n >\n {members.length > 1 ? `Set for ${members.length} selected` : 'Set for selected'}\n </Button>\n ) : null}\n </Stack>\n <ListTable\n aria-label=\"Team members' AI allotments\"\n rows={teamRows}\n columns={teamColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No team members\"\n disableColumnFilter\n quickFilter={false}\n {...(canEdit\n ? { selectable: { selected: members, onChange: setMembers }, onOpen: (_id: string, row: MemberRow) => editMember(row) }\n : {})}\n />\n\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Sites'}\n </Typography>\n {canEdit ? (\n <>\n <Button\n size=\"small\"\n disabled={!sites.length}\n onClick={() =>\n editSubjects(\n sites.map((hostId) => `host:${hostId}`),\n sites.length === 1 ? `AI allotment — ${siteName(sites[0])}` : `AI allotment — ${sites.length} sites`,\n 'Counts what everyone draws on the site, together.',\n )\n }\n >\n {sites.length > 1 ? `Set for ${sites.length} selected` : 'Set for selected'}\n </Button>\n <Button\n size=\"small\"\n disabled={!data.hosts.length}\n onClick={() => {\n setError(null)\n setEditing({ kind: 'everySite' })\n }}\n >\n {'Same for every client site'}\n </Button>\n </>\n ) : null}\n </Stack>\n <ListTable\n aria-label=\"Sites' AI allotments\"\n rows={siteRows}\n columns={siteColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No sites\"\n disableColumnFilter\n quickFilter={false}\n {...(canEdit\n ? { selectable: { selected: sites, onChange: setSites }, onOpen: (_id: string, row: SiteRow) => editSite(row) }\n : {})}\n />\n\n {collaborators.length ? (\n <>\n <Typography variant=\"subtitle2\">{'Site collaborators'}</Typography>\n <ListTable\n aria-label=\"Site collaborators' AI allotments\"\n rows={collaboratorRows}\n columns={collaboratorColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n disableColumnFilter\n quickFilter={false}\n {...(canEdit ? { onOpen: (_id: string, row: CollaboratorRow) => editCollaborator(row) } : {})}\n />\n </>\n ) : null}\n\n {data.restrictionAvailable ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"body2\" sx={{ flexGrow: 1 }}>\n {'Models for the whole organization: '}\n {restriction?.models?.length\n ? data.models\n .filter((model) => restriction.models?.includes(model.id))\n .map((model) => model.label)\n .join(', ') || `${restriction.models.length} models`\n : 'every model the plan offers'}\n </Typography>\n {canEdit ? (\n <Button\n size=\"small\"\n onClick={() => {\n setError(null)\n setEditing({ kind: 'restriction', initial: valueOf(restriction), removable: Boolean(restriction) })\n }}\n >\n {'Restrict models'}\n </Button>\n ) : null}\n </Stack>\n ) : null}\n\n <AiAllotmentEditor\n open={editing !== null}\n title={\n editing?.kind === 'everySite'\n ? 'Same allotment for every site'\n : editing?.kind === 'restriction'\n ? 'Models for the whole organization'\n : (editing?.title ?? '')\n }\n description={\n editing?.kind === 'everySite'\n ? `Sets this allotment on all ${data.hosts.length} sites, replacing any each one had.`\n : editing?.kind === 'restriction'\n ? 'Every member, collaborator and Auto choose only among these models.'\n : editing?.description\n }\n initial={editing && editing.kind !== 'everySite' ? editing.initial : null}\n models={data.models}\n modelsOnly={editing?.kind === 'restriction'}\n poolLimit={data.pool.limit}\n busy={busy}\n error={error}\n onClose={() => setEditing(null)}\n onSave={save}\n onRemove={editing && editing.kind !== 'everySite' && editing.removable ? remove : undefined}\n />\n </Stack>\n )\n}\nBillingAiAllotments.displayName = 'BillingAiAllotments'\n\n/**\n * The card on Billing → Usage, through the `orgBillingUsage` zone, beneath\n * the table of who drew the credits. Registered with `billing.view`.\n */\nexport function AiAllotmentsCard(props: { orgId?: string }) {\n return (\n <div id=\"ai-allotments\">\n <CardDisplay\n header={'AI allotments'}\n help={pluginDocsHelp('billing', {\n anchor: '#ai-allotments',\n excerpt:\n 'A monthly share of the workspace’s AI credits for a member or a site — ' +\n 'hard or soft, with an optional list of models.',\n })}\n contentGutterX\n contentGutterY\n >\n <BillingAiAllotments orgId={props.orgId} />\n </CardDisplay>\n </div>\n )\n}\nAiAllotmentsCard.displayName = 'AiAllotmentsCard'\n\nexport default AiAllotmentsCard\n"],"names":["pluginDocsHelp","mdiPencilOutline","CardDisplay","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","useUser","Alert","Button","Stack","Typography","useMemo","useState","AI_ALLOTMENT_ORG_SUBJECT","aiAllotmentState","AiAllotmentEditor","saveAiAllotments","useAiAllotments","aiAllotmentSummary","row","parts","credits","push","used","toLocaleString","mode","models","length","join","aiAllotmentTone","state","valueOf","allotmentColumn","field","headerName","flex","minWidth","sortable","valueGetter","_value","allotment","renderCell","variant","noWrap","sx","color","creditsColumn","type","align","headerAlign","width","valueFormatter","value","BillingAiAllotments","orgId","restriction","data","user","allotments","members","setMembers","sites","setSites","editing","setEditing","busy","setBusy","error","setError","bySubject","Map","map","subject","status","severity","canEdit","billing","team","filter","member","orgWide","nameOf","uid","find","name","siteName","hostId","hosts","host","collaborators","scope","get","teamRows","$id","siteRows","collaboratorRows","site","editSubjects","subjects","title","description","rows","kind","initial","removable","every","Boolean","run","body","result","ok","save","everySite","set","remove","editMember","editSite","editCollaborator","editColumn","label","edit","quick","icon","path","onClick","items","teamColumns","siteColumns","collaboratorColumns","spacing","direction","alignItems","flexWrap","rowGap","flexGrow","size","disabled","aria-label","columns","rowHeight","noRowsLabel","disableColumnFilter","quickFilter","selectable","selected","onChange","onOpen","_id","restrictionAvailable","model","includes","id","open","modelsOnly","poolLimit","pool","limit","onClose","onSave","onRemove","undefined","displayName","AiAllotmentsCard","props","div","header","help","anchor","excerpt","contentGutterX","contentGutterY"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/billing-ai-allotments.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiPencilOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Button, Stack, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useMemo, useState } from 'react'\nimport { AI_ALLOTMENT_ORG_SUBJECT, aiAllotmentState } from '../model/ai-allotments'\nimport type {\n AiAllotmentRowWire,\n AiAllotmentsHostWire,\n AiAllotmentsMemberWire,\n} from '../usage/ai-usage-wire'\nimport { AiAllotmentEditor, type AiAllotmentValue } from './ai-allotment-editor.component'\nimport { saveAiAllotments, useAiAllotments } from './use-ai-allotments'\n\n/** An allotment as a table cell reads it: used of credits, mode, models. */\nexport function aiAllotmentSummary(\n row: Pick<AiAllotmentRowWire, 'credits' | 'mode' | 'models' | 'used'> | null | undefined,\n): string {\n if (!row) return '—'\n const parts: string[] = []\n if (row.credits !== null) {\n parts.push(`${row.used.toLocaleString()} of ${row.credits.toLocaleString()} (${row.mode})`)\n }\n if (row.models?.length) {\n parts.push(`${row.models.length} model${row.models.length === 1 ? '' : 's'}`)\n }\n return parts.join(' · ') || '—'\n}\n\n/** The color an allotment's figure reads in: warning from 80%, error at it. */\nexport function aiAllotmentTone(\n row: Pick<AiAllotmentRowWire, 'credits' | 'used'> | null | undefined,\n): 'text.primary' | 'warning.main' | 'error.main' {\n if (!row || row.credits === null) return 'text.primary'\n const state = aiAllotmentState(row.used, row.credits)\n return state === 'reached' ? 'error.main' : state === 'warn' ? 'warning.main' : 'text.primary'\n}\n\ntype Editing =\n | {\n kind: 'subjects'\n subjects: string[]\n title: string\n description: string\n initial: AiAllotmentValue | null\n removable: boolean\n }\n | { kind: 'everySite' }\n | { kind: 'restriction'; initial: AiAllotmentValue | null; removable: boolean }\n\nconst valueOf = (row: AiAllotmentRowWire | undefined): AiAllotmentValue | null =>\n row ? { credits: row.credits, mode: row.mode, models: row.models } : null\n\n/**\n * A row of one of the three lists: the subject its allotment is stored under,\n * that subject's allotment when it has one, and `$id` — the id `ListTable`\n * selects by, which is the member's uid, the site's id, or the collaborator's\n * subject.\n */\ntype AllotmentListRow = {\n $id: string\n subject: string\n allotment: AiAllotmentRowWire | undefined\n}\ntype MemberRow = AllotmentListRow & { member: AiAllotmentsMemberWire }\ntype SiteRow = AllotmentListRow & { host: AiAllotmentsHostWire }\ntype CollaboratorRow = AllotmentListRow & { name: string; site: string }\n\n/** The allotment column: used of credits, in the tone its share has reached. */\nconst allotmentColumn = <Row extends AllotmentListRow>(): GridColDef<Row> => ({\n field: 'allotment',\n headerName: 'Allotment',\n flex: 1,\n minWidth: 200,\n sortable: false,\n valueGetter: (_value, row) => aiAllotmentSummary(row.allotment),\n renderCell: ({ row }) => (\n <Typography variant=\"body2\" noWrap sx={{ color: aiAllotmentTone(row.allotment) }}>\n {aiAllotmentSummary(row.allotment)}\n </Typography>\n ),\n})\n\nconst creditsColumn = <Row extends AllotmentListRow>(\n credits: (row: Row) => number,\n): GridColDef<Row> => ({\n field: 'credits',\n headerName: 'Credits this month',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 170,\n valueGetter: (_value, row) => credits(row),\n valueFormatter: (value: number) => value.toLocaleString(),\n})\n\n/**\n * AI ALLOTMENTS ON BILLING → USAGE (AGL-2942): who may draw how much of the\n * workspace's AI pool.\n *\n * Team members and sites, each with this month's credits and its allotment;\n * select several and set one allotment for all of them, or set the same\n * allotment on every site at once — the agency's \"same for every client\n * site\". Collaborators' allotments, which their sites' Users cards set, are\n * listed below. On a plan that offers it, the organization's model\n * restriction sits at the end.\n *\n * Read with `billing.view`; the controls appear with `billing.manage`, and\n * the route refuses a write without it whatever this card draws.\n *\n * The three tables are the route's whole answer — the roster the plan's\n * seats bound, the organization's sites, the collaborator allotments — so\n * they offer no Filters panel and no search: a bounded readout is read\n * whole, and a grid that narrowed the rows it holds is the pattern every\n * list is leaving (AGL-3321).\n */\nexport function BillingAiAllotments({ orgId }: { orgId?: string }) {\n const { data: user } = useUser()\n const allotments = useAiAllotments({ orgId })\n const [members, setMembers] = useState<string[]>([])\n const [sites, setSites] = useState<string[]>([])\n const [editing, setEditing] = useState<Editing | null>(null)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const data = allotments.data\n const bySubject = useMemo(\n () => new Map((data?.allotments ?? []).map((row) => [row.subject, row])),\n [data],\n )\n\n if (allotments.status === 'refused') {\n return (\n <Alert severity=\"info\">{'Seeing AI allotments requires the View billing permission.'}</Alert>\n )\n }\n if (allotments.status === 'error' && !data) {\n return (\n <Alert severity=\"warning\">\n {'Could not read the AI allotments — a failed read, not an empty list.'}\n </Alert>\n )\n }\n if (!data || !orgId) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n )\n }\n\n const canEdit = data.canEdit.billing\n const team = data.members.filter((member) => member.orgWide)\n const nameOf = (uid: string | null) =>\n data.members.find((member) => member.uid === uid)?.name ?? uid ?? ''\n const siteName = (hostId: string | null) =>\n data.hosts.find((host) => host.hostId === hostId)?.name ?? hostId ?? ''\n const collaborators = data.allotments.filter((row) => row.scope === 'collab')\n const restriction = bySubject.get(AI_ALLOTMENT_ORG_SUBJECT)\n /*\n * Each list pages and sorts on its own, and a selection is kept by member\n * and site id rather than by row, so it survives a page change.\n */\n const teamRows: MemberRow[] = team.map((member) => ({\n $id: member.uid,\n subject: `member:${member.uid}`,\n allotment: bySubject.get(`member:${member.uid}`),\n member,\n }))\n const siteRows: SiteRow[] = data.hosts.map((host) => ({\n $id: host.hostId,\n subject: `host:${host.hostId}`,\n allotment: bySubject.get(`host:${host.hostId}`),\n host,\n }))\n const collaboratorRows: CollaboratorRow[] = collaborators.map((row) => ({\n $id: row.subject,\n subject: row.subject,\n allotment: row,\n name: nameOf(row.uid),\n site: siteName(row.hostId),\n }))\n\n const editSubjects = (subjects: string[], title: string, description: string) => {\n const rows = subjects.map((subject) => bySubject.get(subject))\n setError(null)\n setEditing({\n kind: 'subjects',\n subjects,\n title,\n description,\n initial: subjects.length === 1 ? valueOf(rows[0]) : null,\n removable: rows.every(Boolean),\n })\n }\n\n const run = async (body: Parameters<typeof saveAiAllotments>[1]) => {\n setBusy(true)\n setError(null)\n const result = await saveAiAllotments(user, body)\n setBusy(false)\n if (!result.ok) {\n setError(result.error)\n return\n }\n setEditing(null)\n setMembers([])\n setSites([])\n }\n const save = (value: AiAllotmentValue) => {\n if (!editing) return\n if (editing.kind === 'everySite') return void run({ orgId, everySite: value })\n if (editing.kind === 'restriction') {\n return void run(\n value.models\n ? { orgId, set: [{ subject: AI_ALLOTMENT_ORG_SUBJECT, ...value, credits: null }] }\n : { orgId, remove: [AI_ALLOTMENT_ORG_SUBJECT] },\n )\n }\n return void run({\n orgId,\n set: editing.subjects.map((subject) => ({ subject, ...value })),\n })\n }\n const remove = () => {\n if (!editing || editing.kind === 'everySite') return\n return void run({\n orgId,\n remove: editing.kind === 'restriction' ? [AI_ALLOTMENT_ORG_SUBJECT] : editing.subjects,\n })\n }\n const editMember = (row: MemberRow) =>\n editSubjects([row.subject], `AI allotment — ${row.member.name}`, 'Counts what this person draws across every site.')\n const editSite = (row: SiteRow) =>\n editSubjects([row.subject], `AI allotment — ${row.host.name}`, 'Counts what everyone draws on the site, together.')\n const editCollaborator = (row: CollaboratorRow) =>\n editSubjects(\n [row.subject],\n `AI allotment — ${row.name} on ${row.site}`,\n 'Counts what this collaborator draws on this site.',\n )\n /** The one action a row has: set its allotment, or edit the one it has. */\n const editColumn = <Row extends AllotmentListRow>(label: (row: Row) => string, edit: (row: Row) => void) =>\n listActionsColumn((row: Row) => (\n <ListRowActions\n label={label(row)}\n quick={{\n icon: mdiPencilOutline.path,\n label: row.allotment ? 'Edit allotment' : 'Set allotment',\n onClick: () => edit(row),\n }}\n items={[]}\n />\n ))\n\n const teamColumns: GridColDef<MemberRow>[] = [\n { field: 'name', headerName: 'Member', flex: 1, minWidth: 180, valueGetter: (_value, row) => row.member.name },\n creditsColumn<MemberRow>((row) => row.member.credits),\n allotmentColumn<MemberRow>(),\n ...(canEdit ? [editColumn<MemberRow>((row) => row.member.name, editMember)] : []),\n ]\n const siteColumns: GridColDef<SiteRow>[] = [\n { field: 'name', headerName: 'Site', flex: 1, minWidth: 180, valueGetter: (_value, row) => row.host.name },\n creditsColumn<SiteRow>((row) => row.host.credits),\n allotmentColumn<SiteRow>(),\n ...(canEdit ? [editColumn<SiteRow>((row) => row.host.name, editSite)] : []),\n ]\n const collaboratorColumns: GridColDef<CollaboratorRow>[] = [\n { field: 'name', headerName: 'Collaborator', flex: 1, minWidth: 180 },\n { field: 'site', headerName: 'Site', flex: 1, minWidth: 160 },\n allotmentColumn<CollaboratorRow>(),\n ...(canEdit ? [editColumn<CollaboratorRow>((row) => `${row.name} on ${row.site}`, editCollaborator)] : []),\n ]\n\n return (\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Give a member or a whole site a share of the workspace’s AI credits each month. ' +\n 'A hard allotment stops AI requests at the line; a soft one keeps going and alerts ' +\n 'you at 75%, 80%, 90% and 100%. The workspace’s own credits still stop first.'}\n </Typography>\n\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Team members'}\n </Typography>\n {canEdit ? (\n <Button\n size=\"small\"\n disabled={!members.length}\n onClick={() =>\n editSubjects(\n members.map((uid) => `member:${uid}`),\n members.length === 1 ? `AI allotment — ${nameOf(members[0])}` : `AI allotment — ${members.length} members`,\n 'Counts what each person draws across every site.',\n )\n }\n >\n {members.length > 1 ? `Set for ${members.length} selected` : 'Set for selected'}\n </Button>\n ) : null}\n </Stack>\n <ListTable\n aria-label=\"Team members' AI allotments\"\n rows={teamRows}\n columns={teamColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No team members\"\n disableColumnFilter\n quickFilter={false}\n {...(canEdit\n ? { selectable: { selected: members, onChange: setMembers }, onOpen: (_id: string, row: MemberRow) => editMember(row) }\n : {})}\n />\n\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Sites'}\n </Typography>\n {canEdit ? (\n <>\n <Button\n size=\"small\"\n disabled={!sites.length}\n onClick={() =>\n editSubjects(\n sites.map((hostId) => `host:${hostId}`),\n sites.length === 1 ? `AI allotment — ${siteName(sites[0])}` : `AI allotment — ${sites.length} sites`,\n 'Counts what everyone draws on the site, together.',\n )\n }\n >\n {sites.length > 1 ? `Set for ${sites.length} selected` : 'Set for selected'}\n </Button>\n <Button\n size=\"small\"\n disabled={!data.hosts.length}\n onClick={() => {\n setError(null)\n setEditing({ kind: 'everySite' })\n }}\n >\n {'Same for every client site'}\n </Button>\n </>\n ) : null}\n </Stack>\n <ListTable\n aria-label=\"Sites' AI allotments\"\n rows={siteRows}\n columns={siteColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No sites\"\n disableColumnFilter\n quickFilter={false}\n {...(canEdit\n ? { selectable: { selected: sites, onChange: setSites }, onOpen: (_id: string, row: SiteRow) => editSite(row) }\n : {})}\n />\n\n {collaborators.length ? (\n <>\n <Typography variant=\"subtitle2\">{'Site collaborators'}</Typography>\n <ListTable\n aria-label=\"Site collaborators' AI allotments\"\n rows={collaboratorRows}\n columns={collaboratorColumns}\n rowHeight={TABLE_ROW_HEIGHT}\n disableColumnFilter\n quickFilter={false}\n {...(canEdit ? { onOpen: (_id: string, row: CollaboratorRow) => editCollaborator(row) } : {})}\n />\n </>\n ) : null}\n\n {data.restrictionAvailable ? (\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <Typography variant=\"body2\" sx={{ flexGrow: 1 }}>\n {'Models for the whole organization: '}\n {restriction?.models?.length\n ? data.models\n .filter((model) => restriction.models?.includes(model.id))\n .map((model) => model.label)\n .join(', ') || `${restriction.models.length} models`\n : 'every model the plan offers'}\n </Typography>\n {canEdit ? (\n <Button\n size=\"small\"\n onClick={() => {\n setError(null)\n setEditing({ kind: 'restriction', initial: valueOf(restriction), removable: Boolean(restriction) })\n }}\n >\n {'Restrict models'}\n </Button>\n ) : null}\n </Stack>\n ) : null}\n\n <AiAllotmentEditor\n open={editing !== null}\n title={\n editing?.kind === 'everySite'\n ? 'Same allotment for every site'\n : editing?.kind === 'restriction'\n ? 'Models for the whole organization'\n : (editing?.title ?? '')\n }\n description={\n editing?.kind === 'everySite'\n ? `Sets this allotment on all ${data.hosts.length} sites, replacing any each one had.`\n : editing?.kind === 'restriction'\n ? 'Every member, collaborator and Auto choose only among these models.'\n : editing?.description\n }\n initial={editing && editing.kind !== 'everySite' ? editing.initial : null}\n models={data.models}\n modelsOnly={editing?.kind === 'restriction'}\n poolLimit={data.pool.limit}\n busy={busy}\n error={error}\n onClose={() => setEditing(null)}\n onSave={save}\n onRemove={editing && editing.kind !== 'everySite' && editing.removable ? remove : undefined}\n />\n </Stack>\n )\n}\nBillingAiAllotments.displayName = 'BillingAiAllotments'\n\n/**\n * The card on Billing → Usage, through the `orgBillingUsage` zone, beneath\n * the table of who drew the credits. Registered with `billing.view`.\n */\nexport function AiAllotmentsCard(props: { orgId?: string }) {\n return (\n <div id=\"ai-allotments\">\n <CardDisplay\n header={'AI allotments'}\n help={pluginDocsHelp('billing', {\n anchor: '#ai-allotments',\n excerpt:\n 'A monthly share of the workspace’s AI credits for a member or a site — ' +\n 'hard or soft, with an optional list of models.',\n })}\n contentGutterX\n contentGutterY\n >\n <BillingAiAllotments orgId={props.orgId} />\n </CardDisplay>\n </div>\n )\n}\nAiAllotmentsCard.displayName = 'AiAllotmentsCard'\n\nexport default AiAllotmentsCard\n"],"names":["pluginDocsHelp","mdiPencilOutline","CardDisplay","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","useUser","Alert","Button","Stack","Typography","useMemo","useState","AI_ALLOTMENT_ORG_SUBJECT","aiAllotmentState","AiAllotmentEditor","saveAiAllotments","useAiAllotments","aiAllotmentSummary","row","parts","credits","push","used","toLocaleString","mode","models","length","join","aiAllotmentTone","state","valueOf","allotmentColumn","field","headerName","flex","minWidth","sortable","valueGetter","_value","allotment","renderCell","variant","noWrap","sx","color","creditsColumn","type","align","headerAlign","width","valueFormatter","value","BillingAiAllotments","orgId","restriction","data","user","allotments","members","setMembers","sites","setSites","editing","setEditing","busy","setBusy","error","setError","bySubject","Map","map","subject","status","severity","canEdit","billing","team","filter","member","orgWide","nameOf","uid","find","name","siteName","hostId","hosts","host","collaborators","scope","get","teamRows","$id","siteRows","collaboratorRows","site","editSubjects","subjects","title","description","rows","kind","initial","removable","every","Boolean","run","body","result","ok","save","everySite","set","remove","editMember","editSite","editCollaborator","editColumn","label","edit","quick","icon","path","onClick","items","teamColumns","siteColumns","collaboratorColumns","spacing","useFlexGap","direction","alignItems","flexWrap","rowGap","flexGrow","size","disabled","aria-label","columns","rowHeight","noRowsLabel","disableColumnFilter","quickFilter","selectable","selected","onChange","onOpen","_id","restrictionAvailable","model","includes","id","open","modelsOnly","poolLimit","pool","limit","onClose","onSave","onRemove","undefined","displayName","AiAllotmentsCard","props","div","header","help","anchor","excerpt","contentGutterX","contentGutterY"],"mappings":"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"}
|
|
@@ -213,6 +213,7 @@ import { AI_USAGE_EXPORT_ROWS_HEADER, aiUsageMonthLabel, formatAiUsageShare } fr
|
|
|
213
213
|
children: `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.`
|
|
214
214
|
}),
|
|
215
215
|
/*#__PURE__*/ _jsxs(Stack, {
|
|
216
|
+
useFlexGap: true,
|
|
216
217
|
direction: "row",
|
|
217
218
|
spacing: 1,
|
|
218
219
|
sx: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/billing-ai-top-users.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { aiAddonName, countCsvDataRows } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiOpenInNew } from '@aglyn/shared-data-mdi'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useMemo, useState } from 'react'\nimport { topAiUsageKinds } from '../model/ai-usage-by-user'\nimport { useOrgAiUsage } from './use-org-ai-usage'\nimport {\n AI_USAGE_EXPORT_ROWS_HEADER,\n aiUsageMonthLabel,\n formatAiUsageShare,\n type OrgAiUsageRowWire,\n} from '../usage/ai-usage-wire'\n\nexport interface BillingAiTopUsersProps {\n orgId: string | undefined\n /** The org's slug, for the links into each person's page. */\n orgSlug: string\n}\n\n/**\n * WHO IS GENERATING WHAT (AGL-2928) — the per-person table beneath the\n * credit meter on Billing → Usage.\n *\n * The meter above says how much of the pool the workspace has drawn; this\n * says who drew it. Each row is one member's month: credits, their share of\n * the workspace's spend, requests, and how often the gate refused them. A\n * month picker walks the retention window, and the file button hands the\n * same month to a spreadsheet through the streamed export.\n *\n * ## The file is checked, not trusted\n *\n * The route promises its row count in a header taken before the first row\n * is written. A stream that dies halfway yields a well-formed shorter file,\n * so a body with fewer rows than promised is refused rather than saved.\n *\n * The copy stays descriptive on purpose — who, how much, on which site — and\n * never characterizes what the numbers mean about a person.\n */\nexport function BillingAiTopUsersComponent(props: BillingAiTopUsersProps) {\n const { orgId, orgSlug } = props\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [month, setMonth] = useState<string | undefined>(undefined)\n const [exporting, setExporting] = useState(false)\n const usage = useOrgAiUsage(orgId, { month })\n const router = useRouter()\n const name = aiAddonName()\n\n const handleExport = useCallback(async () => {\n if (!orgId || exporting) return\n setExporting(true)\n try {\n const params = new URLSearchParams({ orgId, month: usage.month, format: 'csv' })\n const response = await authorizedFetch(\n user,\n `/api/ai/usage?${params.toString()}`,\n )\n if (!response.ok) {\n const failure = await response.json().catch(() => ({}))\n throw new Error(failure?.error ?? 'Export failed')\n }\n const text = await response.text()\n const promised = Number(response.headers.get(AI_USAGE_EXPORT_ROWS_HEADER) ?? '')\n const received = countCsvDataRows(text)\n if (Number.isFinite(promised) && received < promised) {\n enqueueSnackbar(\n `Export incomplete — ${received} of ${promised} rows arrived. ` +\n 'Nothing was saved; try again.',\n { variant: 'error' },\n )\n return\n }\n const url = URL.createObjectURL(new Blob([text], { type: 'text/csv' }))\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = `ai-usage-${usage.month}.csv`\n anchor.click()\n URL.revokeObjectURL(url)\n } catch (error) {\n enqueueSnackbar(error instanceof Error ? error.message : 'Export failed', {\n variant: 'error',\n })\n } finally {\n setExporting(false)\n }\n }, [orgId, exporting, usage.month, user, enqueueSnackbar])\n\n const rows = usage.data?.rows ?? []\n const memberHref = useCallback(\n (uid: string) => buildRoute(Route.MANAGE_TEAM_MEMBER, { orgSlug, uid }),\n [orgSlug],\n )\n\n /*\n * One row per member, and the whole month is in hand, so the grid pages\n * and sorts it itself. The row opens the member's page, which is\n * where the split by site and by kind lives.\n */\n const columns = useMemo<GridColDef<OrgAiUsageRowWire>[]>(\n () => [\n {\n field: 'name',\n headerName: 'Member',\n flex: 1,\n minWidth: 220,\n renderCell: ({ row }) => (\n <Stack sx={{ minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap title={row.name}>\n {row.name}\n </Typography>\n {row.email && row.email !== row.name ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {row.email}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'credits',\n headerName: 'Credits',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'share',\n headerName: 'Share',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 90,\n valueFormatter: (value: number) => formatAiUsageShare(value),\n },\n {\n field: 'requests',\n headerName: 'Requests',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'refusals',\n headerName: 'Refusals',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'mostly',\n headerName: 'Mostly',\n flex: 1,\n minWidth: 160,\n sortable: false,\n valueGetter: (_value, row) =>\n topAiUsageKinds(row.byKind, 2)\n .map((entry) => entry.kind)\n .join(', ') || '—',\n },\n listActionsColumn((row: OrgAiUsageRowWire) => (\n <ListRowActions\n label={row.name}\n quick={{\n icon: mdiOpenInNew.path,\n label: 'View member',\n to: memberHref(row.uid),\n }}\n items={[]}\n />\n )),\n ],\n [memberHref],\n )\n\n return (\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Who is generating what, per site: each member's ${name} credits for the month and their share of the workspace's spend. Open a member for the split by site and by kind.`}\n </Typography>\n <Stack 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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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/billing-ai-top-users.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { aiAddonName, countCsvDataRows } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiOpenInNew } from '@aglyn/shared-data-mdi'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useMemo, useState } from 'react'\nimport { topAiUsageKinds } from '../model/ai-usage-by-user'\nimport { useOrgAiUsage } from './use-org-ai-usage'\nimport {\n AI_USAGE_EXPORT_ROWS_HEADER,\n aiUsageMonthLabel,\n formatAiUsageShare,\n type OrgAiUsageRowWire,\n} from '../usage/ai-usage-wire'\n\nexport interface BillingAiTopUsersProps {\n orgId: string | undefined\n /** The org's slug, for the links into each person's page. */\n orgSlug: string\n}\n\n/**\n * WHO IS GENERATING WHAT (AGL-2928) — the per-person table beneath the\n * credit meter on Billing → Usage.\n *\n * The meter above says how much of the pool the workspace has drawn; this\n * says who drew it. Each row is one member's month: credits, their share of\n * the workspace's spend, requests, and how often the gate refused them. A\n * month picker walks the retention window, and the file button hands the\n * same month to a spreadsheet through the streamed export.\n *\n * ## The file is checked, not trusted\n *\n * The route promises its row count in a header taken before the first row\n * is written. A stream that dies halfway yields a well-formed shorter file,\n * so a body with fewer rows than promised is refused rather than saved.\n *\n * The copy stays descriptive on purpose — who, how much, on which site — and\n * never characterizes what the numbers mean about a person.\n */\nexport function BillingAiTopUsersComponent(props: BillingAiTopUsersProps) {\n const { orgId, orgSlug } = props\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const [month, setMonth] = useState<string | undefined>(undefined)\n const [exporting, setExporting] = useState(false)\n const usage = useOrgAiUsage(orgId, { month })\n const router = useRouter()\n const name = aiAddonName()\n\n const handleExport = useCallback(async () => {\n if (!orgId || exporting) return\n setExporting(true)\n try {\n const params = new URLSearchParams({ orgId, month: usage.month, format: 'csv' })\n const response = await authorizedFetch(\n user,\n `/api/ai/usage?${params.toString()}`,\n )\n if (!response.ok) {\n const failure = await response.json().catch(() => ({}))\n throw new Error(failure?.error ?? 'Export failed')\n }\n const text = await response.text()\n const promised = Number(response.headers.get(AI_USAGE_EXPORT_ROWS_HEADER) ?? '')\n const received = countCsvDataRows(text)\n if (Number.isFinite(promised) && received < promised) {\n enqueueSnackbar(\n `Export incomplete — ${received} of ${promised} rows arrived. ` +\n 'Nothing was saved; try again.',\n { variant: 'error' },\n )\n return\n }\n const url = URL.createObjectURL(new Blob([text], { type: 'text/csv' }))\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = `ai-usage-${usage.month}.csv`\n anchor.click()\n URL.revokeObjectURL(url)\n } catch (error) {\n enqueueSnackbar(error instanceof Error ? error.message : 'Export failed', {\n variant: 'error',\n })\n } finally {\n setExporting(false)\n }\n }, [orgId, exporting, usage.month, user, enqueueSnackbar])\n\n const rows = usage.data?.rows ?? []\n const memberHref = useCallback(\n (uid: string) => buildRoute(Route.MANAGE_TEAM_MEMBER, { orgSlug, uid }),\n [orgSlug],\n )\n\n /*\n * One row per member, and the whole month is in hand, so the grid pages\n * and sorts it itself. The row opens the member's page, which is\n * where the split by site and by kind lives.\n */\n const columns = useMemo<GridColDef<OrgAiUsageRowWire>[]>(\n () => [\n {\n field: 'name',\n headerName: 'Member',\n flex: 1,\n minWidth: 220,\n renderCell: ({ row }) => (\n <Stack sx={{ minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap title={row.name}>\n {row.name}\n </Typography>\n {row.email && row.email !== row.name ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {row.email}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'credits',\n headerName: 'Credits',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'share',\n headerName: 'Share',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 90,\n valueFormatter: (value: number) => formatAiUsageShare(value),\n },\n {\n field: 'requests',\n headerName: 'Requests',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'refusals',\n headerName: 'Refusals',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'mostly',\n headerName: 'Mostly',\n flex: 1,\n minWidth: 160,\n sortable: false,\n valueGetter: (_value, row) =>\n topAiUsageKinds(row.byKind, 2)\n .map((entry) => entry.kind)\n .join(', ') || '—',\n },\n listActionsColumn((row: OrgAiUsageRowWire) => (\n <ListRowActions\n label={row.name}\n quick={{\n icon: mdiOpenInNew.path,\n label: 'View member',\n to: memberHref(row.uid),\n }}\n items={[]}\n />\n )),\n ],\n [memberHref],\n )\n\n return (\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Who is generating what, per site: each member's ${name} credits for the month and their share of the workspace's spend. Open a member for the split by site and by kind.`}\n </Typography>\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap', rowGap: 1 }}>\n <TextField\n select\n size=\"small\"\n label=\"Month\"\n value={usage.month}\n onChange={(event) => setMonth(event.target.value)}\n sx={{ minWidth: 180 }}\n >\n {usage.months.map((key) => (\n <MenuItem key={key} value={key}>\n {aiUsageMonthLabel(key)}\n </MenuItem>\n ))}\n </TextField>\n <Button\n size=\"small\"\n disabled={exporting || usage.status !== 'ready' || rows.length === 0}\n onClick={() => void handleExport()}\n >\n {exporting ? 'Exporting…' : 'Export CSV'}\n </Button>\n {usage.data ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${usage.data.orgCredits.toLocaleString()} credits drawn by the workspace in ${aiUsageMonthLabel(usage.month)}`}\n </Typography>\n ) : null}\n </Stack>\n {usage.status === 'refused' ? (\n <Alert severity=\"info\">\n {'Seeing who used AI credits requires the View billing permission.'}\n </Alert>\n ) : usage.status === 'error' ? (\n <Alert severity=\"warning\">\n {'Could not read this month’s per-member usage — a failed read, not zero usage.'}\n </Alert>\n ) : usage.status === 'loading' && !usage.data ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : rows.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`No ${name} usage attributed to a member in ${aiUsageMonthLabel(usage.month)}.`}\n </Typography>\n ) : (\n <ListTable\n // Another month is another list: it opens on its first page.\n key={usage.month}\n rows={rows}\n columns={columns}\n getRowId={(row: OrgAiUsageRowWire) => row.uid}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(uid) => router.push(memberHref(uid))}\n // The month for every current member, computed whole by the route\n // from the roster the plan's seats bound: a readout, not a list to\n // filter — the grid would narrow only the rows it holds (AGL-3321).\n disableColumnFilter\n quickFilter={false}\n />\n )}\n </Stack>\n )\n}\nBillingAiTopUsersComponent.displayName = 'BillingAiTopUsersComponent'\n\n/**\n * The card on Billing → Usage, through the `orgBillingUsage` zone: its own\n * band beneath the meters, full width, because it is a table of people rather\n * than a gauge. Registered with `billing.view`, so a manager without it sees\n * no card rather than a refusal.\n */\nexport function AiTopUsersCard(props: { orgId?: string; org?: { slug?: string } | null }) {\n return (\n <div id=\"ai-usage-by-member\">\n <CardDisplay\n header={'Who is generating what'}\n help={pluginDocsHelp('billing', {\n anchor: '#who-is-generating-what',\n excerpt:\n 'Each member’s AI credits for a month, their share of the ' +\n 'workspace’s spend, requests and refusals — with a month picker ' +\n 'and a CSV export.',\n })}\n contentGutterX\n contentGutterY\n >\n <BillingAiTopUsersComponent orgId={props.orgId} orgSlug={props.org?.slug ?? ''} />\n </CardDisplay>\n </div>\n )\n}\nAiTopUsersCard.displayName = 'AiTopUsersCard'\n\nexport default BillingAiTopUsersComponent\n"],"names":["aiAddonName","countCsvDataRows","pluginDocsHelp","mdiOpenInNew","CardDisplay","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","useSnackbar","authorizedFetch","useUser","Alert","Button","MenuItem","Stack","TextField","Typography","buildRoute","Route","useRouter","useCallback","useMemo","useState","topAiUsageKinds","useOrgAiUsage","AI_USAGE_EXPORT_ROWS_HEADER","aiUsageMonthLabel","formatAiUsageShare","BillingAiTopUsersComponent","props","usage","orgId","orgSlug","data","user","enqueueSnackbar","month","setMonth","undefined","exporting","setExporting","router","name","handleExport","response","params","URLSearchParams","format","toString","ok","failure","json","catch","Error","error","text","promised","Number","headers","get","received","isFinite","variant","url","URL","createObjectURL","Blob","type","anchor","document","createElement","href","download","click","revokeObjectURL","message","rows","memberHref","uid","MANAGE_TEAM_MEMBER","columns","field","headerName","flex","minWidth","renderCell","row","sx","noWrap","title","email","color","align","headerAlign","width","valueFormatter","value","toLocaleString","sortable","valueGetter","_value","byKind","map","entry","kind","join","label","quick","icon","path","to","items","spacing","useFlexGap","direction","alignItems","flexWrap","rowGap","select","size","onChange","event","target","months","key","disabled","status","length","onClick","orgCredits","severity","getRowId","rowHeight","onOpen","push","disableColumnFilter","quickFilter","displayName","AiTopUsersCard","div","id","header","help","excerpt","contentGutterX","contentGutterY","org","slug"],"mappings":"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"}
|