@aglyn/plugins-ai 1.0.0-beta.225 → 1.0.0-beta.227
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/README.md +2 -1
- package/package.json +17 -17
- package/src/lib/billing/ai-month-meter.js +5 -3
- package/src/lib/billing/ai-month-meter.js.map +1 -1
- package/src/lib/billing/ai-overage-ledger.js +3 -1
- package/src/lib/billing/ai-overage-ledger.js.map +1 -1
- package/src/lib/components/ai-brief-dialog.component.d.ts +6 -2
- package/src/lib/components/ai-brief-dialog.component.js +35 -17
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-describe-automation.component.js +17 -2
- package/src/lib/components/ai-describe-automation.component.js.map +1 -1
- package/src/lib/components/ai-insight-dialog.component.js +24 -2
- package/src/lib/components/ai-insight-dialog.component.js.map +1 -1
- package/src/lib/components/ai-job-events.d.ts +14 -0
- package/src/lib/components/ai-job-events.js +76 -0
- package/src/lib/components/ai-job-events.js.map +1 -0
- package/src/lib/components/ai-job-follow.component.d.ts +49 -0
- package/src/lib/components/ai-job-follow.component.js +210 -0
- package/src/lib/components/ai-job-follow.component.js.map +1 -0
- package/src/lib/components/ai-job-links.d.ts +42 -0
- package/src/lib/components/ai-job-links.js +140 -0
- package/src/lib/components/ai-job-links.js.map +1 -0
- package/src/lib/components/ai-job-plan.component.d.ts +13 -6
- package/src/lib/components/ai-job-plan.component.js +29 -3
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-job-requests.d.ts +24 -0
- package/src/lib/components/ai-job-requests.js +76 -0
- package/src/lib/components/ai-job-requests.js.map +1 -0
- package/src/lib/components/ai-jobs-indicator.component.d.ts +25 -0
- package/src/lib/components/ai-jobs-indicator.component.js +80 -0
- package/src/lib/components/ai-jobs-indicator.component.js.map +1 -0
- package/src/lib/components/ai-jobs-store.d.ts +81 -0
- package/src/lib/components/ai-jobs-store.js +218 -0
- package/src/lib/components/ai-jobs-store.js.map +1 -0
- package/src/lib/components/ai-page-brief-dialog.component.d.ts +3 -3
- package/src/lib/components/ai-page-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-site-build-page.component.d.ts +23 -0
- package/src/lib/components/ai-site-build-page.component.js +377 -0
- package/src/lib/components/ai-site-build-page.component.js.map +1 -0
- package/src/lib/components/ai-site-start-card.component.d.ts +1 -1
- package/src/lib/components/ai-site-start-card.component.js +522 -130
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-site-starter-fallback.component.d.ts +24 -0
- package/src/lib/components/ai-site-starter-fallback.component.js +112 -0
- package/src/lib/components/ai-site-starter-fallback.component.js.map +1 -0
- package/src/lib/components/assist-jobs-drawer.component.d.ts +18 -20
- package/src/lib/components/assist-jobs-drawer.component.js +152 -172
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +62 -6
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/staff-ai-credit-return.component.d.ts +55 -0
- package/src/lib/components/staff-ai-credit-return.component.js +346 -0
- package/src/lib/components/staff-ai-credit-return.component.js.map +1 -0
- package/src/lib/components/staff-org-ai-card.component.js +364 -279
- package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
- package/src/lib/components/staff-user-ai-usage-card.component.d.ts +4 -0
- package/src/lib/components/staff-user-ai-usage-card.component.js +83 -28
- package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
- package/src/lib/components/use-ai-job-run.js +5 -18
- package/src/lib/components/use-ai-job-run.js.map +1 -1
- package/src/lib/jobs/ai-job-auto-confirm.d.ts +52 -0
- package/src/lib/jobs/ai-job-auto-confirm.js +62 -0
- package/src/lib/jobs/ai-job-auto-confirm.js.map +1 -0
- package/src/lib/jobs/ai-job-plan-step.d.ts +33 -2
- package/src/lib/jobs/ai-job-plan-step.js +161 -17
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +7 -0
- package/src/lib/jobs/ai-job-site-step.js +34 -4
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-text-step.d.ts +7 -0
- package/src/lib/jobs/ai-job-text-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs-notify.d.ts +37 -0
- package/src/lib/jobs/ai-jobs-notify.js +51 -0
- package/src/lib/jobs/ai-jobs-notify.js.map +1 -0
- package/src/lib/jobs/ai-jobs.d.ts +31 -4
- package/src/lib/jobs/ai-jobs.js +196 -32
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/model/ai-job-activity.d.ts +112 -0
- package/src/lib/model/ai-job-activity.js +144 -0
- package/src/lib/model/ai-job-activity.js.map +1 -0
- package/src/lib/model/ai-job-failure-copy.d.ts +69 -0
- package/src/lib/model/ai-job-failure-copy.js +78 -0
- package/src/lib/model/ai-job-failure-copy.js.map +1 -0
- package/src/lib/model/ai-job-notice.d.ts +61 -0
- package/src/lib/model/ai-job-notice.js +112 -0
- package/src/lib/model/ai-job-notice.js.map +1 -0
- package/src/lib/model/ai-jobs.types.d.ts +26 -0
- package/src/lib/model/ai-jobs.types.js +1 -0
- package/src/lib/model/ai-jobs.types.js.map +1 -1
- package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
- package/src/lib/model/ai-plan-capabilities.js.map +1 -1
- package/src/lib/model/ai-site-build-progress.d.ts +45 -0
- package/src/lib/model/ai-site-build-progress.js +76 -0
- package/src/lib/model/ai-site-build-progress.js.map +1 -0
- package/src/lib/model/ai-site-inventory.d.ts +9 -0
- package/src/lib/model/ai-site-inventory.js +7 -2
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +93 -2
- package/src/lib/model/ai-site-job.js +88 -7
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-start.d.ts +3 -1
- package/src/lib/model/ai-site-start.js +6 -4
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/model/ai-template-subjects.d.ts +4 -3
- package/src/lib/model/ai-template-subjects.js +5 -4
- package/src/lib/model/ai-template-subjects.js.map +1 -1
- package/src/lib/plan-entitlements.d.ts +3 -3
- package/src/lib/plan-entitlements.js +3 -3
- package/src/lib/plan-entitlements.js.map +1 -1
- package/src/lib/plugin.js +28 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/runtime/ai-abuse-guards.d.ts +9 -6
- package/src/lib/runtime/ai-abuse-guards.js +10 -7
- package/src/lib/runtime/ai-abuse-guards.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.js +31 -4
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-gate.d.ts +3 -2
- package/src/lib/runtime/ai-gate.js +3 -2
- package/src/lib/runtime/ai-gate.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +12 -4
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/site-inventory.d.ts +16 -1
- package/src/lib/runtime/site-inventory.js +43 -3
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server/ai-admin-credits.d.ts +21 -0
- package/src/lib/server/ai-admin-credits.js +213 -0
- package/src/lib/server/ai-admin-credits.js.map +1 -0
- package/src/lib/server/ai-admin-org.js +12 -5
- package/src/lib/server/ai-admin-org.js.map +1 -1
- package/src/lib/server/ai-admin-signals.js +3 -1
- package/src/lib/server/ai-admin-signals.js.map +1 -1
- package/src/lib/server/ai-admin-user.js +12 -1
- package/src/lib/server/ai-admin-user.js.map +1 -1
- package/src/lib/server/ai-allotments.js +3 -2
- package/src/lib/server/ai-allotments.js.map +1 -1
- package/src/lib/server/ai-jobs-route.js +20 -2
- package/src/lib/server/ai-jobs-route.js.map +1 -1
- package/src/lib/server/assist-chat.js +3 -2
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/billing-credits.js +4 -3
- package/src/lib/server/billing-credits.js.map +1 -1
- package/src/lib/server/billing-overage.js +3 -2
- package/src/lib/server/billing-overage.js.map +1 -1
- package/src/lib/server.js +11 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/usage/assist-credit-returns-write.d.ts +75 -0
- package/src/lib/usage/assist-credit-returns-write.js +150 -0
- package/src/lib/usage/assist-credit-returns-write.js.map +1 -0
- package/src/lib/usage/assist-credit-returns.d.ts +68 -0
- package/src/lib/usage/assist-credit-returns.js +79 -0
- package/src/lib/usage/assist-credit-returns.js.map +1 -0
- package/src/lib/usage/assist-free-taste.js +6 -3
- package/src/lib/usage/assist-free-taste.js.map +1 -1
- package/src/lib/usage/assist-job-refund.d.ts +100 -0
- package/src/lib/usage/assist-job-refund.js +109 -0
- package/src/lib/usage/assist-job-refund.js.map +1 -0
- package/src/lib/usage/assist-usage.js +5 -2
- package/src/lib/usage/assist-usage.js.map +1 -1
- package/src/lib/usage/staff-org-ai.d.ts +31 -1
- package/src/lib/usage/staff-org-ai.js +19 -2
- package/src/lib/usage/staff-org-ai.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/staff-org-ai-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { aiAddonName } from '@aglyn/aglyn'\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiOpenInNew } from '@aglyn/shared-data-mdi'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Alert,\n Chip,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport type {\n StaffOrgAiJob,\n StaffOrgAiJobs,\n StaffOrgAiMargin,\n StaffOrgAiOverage,\n StaffOrgAiPool,\n StaffOrgAiRefusals,\n StaffOrgAiResponse,\n StaffOrgAiTokens,\n StaffOrgAiUser,\n} from '../usage/staff-org-ai'\n\n/**\n * THE STAFF AI CARD (AGL-2930): everything about one org's AI usage on the\n * page staff already open to look at that org.\n *\n * Read through `/api/ai/admin/org`, one request on mount. Every figure on it\n * is the route's, and every figure the route serves is composed from the\n * same helpers the meter refuses with — see `usage/staff-org-ai.ts`. This\n * file only lays them out.\n *\n * ## What each section is for\n *\n * - **Add-on** — is the org paying for AI, at what, since when.\n * - **Credit pool** — the band as three NAMED parts, because \"why does\n * this org have 13,000 credits\" has three answers (the plan, an override,\n * the add-on) and a single total cannot say which.\n * - **Overage** — what is past the band, what it is sold at, and the two\n * controls the org itself holds over it.\n * - **Refusals** — how often, and why, the gate said no this month.\n * - **Jobs** and **Top users** — the generative and per-person halves,\n * each of which says plainly when its rollup has nothing yet.\n * - **Tokens** — what each kind of request costs per request, what it\n * sends and generates, and how much of its prompt the cache served.\n * - **Margin** — spend against what AI brings in, red when it is over.\n * - **Actions** — where the override editor and the AI pause live.\n */\n\nconst credits = (value: number | null | undefined): string =>\n value == null ? '—' : Math.round(value).toLocaleString()\n\n/**\n * Four decimals under a dollar, two above (the assist-signals page's rule):\n * a month that really cost eight cents must not read as `$0.00`.\n */\nconst usd = (value: number | null | undefined): string => {\n if (value == null) return '—'\n const amount = Number(value) || 0\n return `$${amount.toFixed(Math.abs(amount) < 1 ? 4 : 2)}`\n}\n\nconst when = (iso: string | null): string =>\n iso ? new Date(iso).toLocaleDateString() : '—'\n\n/** The pool as one sentence naming every part that is non-zero. */\nexport function poolPartsSentence(pool: StaffOrgAiPool): string {\n const parts: string[] = []\n if (pool.overrideCredits !== null) {\n parts.push(`${credits(pool.overrideCredits)} staff override`)\n } else {\n parts.push(`${credits(pool.planCredits)} plan band`)\n }\n if (pool.addonCredits > 0) {\n parts.push(`${credits(pool.addonCredits)} ${aiAddonName()} add-on`)\n }\n return parts.join(' + ')\n}\n\n/**\n * The overage's state as one line: sold, walled, or capped — or, for an\n * uncapped staff comp (AGL-3049), nothing to sell past at all.\n */\nexport function overageStateSentence(\n overage: StaffOrgAiOverage,\n uncapped = false,\n): string {\n if (uncapped) {\n return 'Nothing is sold past a band: an uncapped staff comp has none, and bills nothing.'\n }\n if (overage.hardCap) return 'Stopped at the band — the org’s band switch is on.'\n if (!overage.sellsOverage) return 'Not sold past the band on this plan.'\n const rate =\n overage.rateUsdPer1k === null ? '' : ` at ${usd(overage.rateUsdPer1k)} per 1,000`\n if (overage.capUsd !== null) {\n return overage.capReached\n ? `Stopped — the org’s ${usd(overage.capUsd)} overage ceiling is reached.`\n : `Sold${rate}, up to the org’s ${usd(overage.capUsd)} ceiling.`\n }\n return `Sold${rate}, no ceiling set.`\n}\n\n/** Whether the margin section renders red. */\nexport function marginTone(margin: StaffOrgAiMargin): 'error' | 'warning' | 'success' {\n if (margin.underwater) return 'error'\n if (margin.multiple >= 1) return 'warning'\n return 'success'\n}\n\nfunction RefusalsRow({ refusals }: { refusals: StaffOrgAiRefusals }) {\n const labels: Record<keyof Omit<StaffOrgAiRefusals, 'total'>, string> = {\n band: 'band',\n cap: 'ceiling',\n messages: 'messages',\n budget: 'operator backstop',\n // A hard allotment a manager set on a member, a collaborator or a site\n // (AGL-2942).\n allotment: 'allotments',\n // The Free taste's rungs (AGL-2925): only a Free workspace collects these.\n account: 'free account',\n requests: 'free requests/day',\n refusals: 'free refusals/day',\n platform: 'platform ceiling',\n }\n return (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {(Object.keys(labels) as Array<keyof typeof labels>).map((reason) => {\n // A reason the answering server does not yet count reads as zero,\n // so a console newer than its route draws the row rather than\n // failing the card.\n const count = Number(refusals[reason] ?? 0)\n return (\n <Chip\n key={reason}\n size=\"small\"\n variant={count > 0 ? 'filled' : 'outlined'}\n color={count > 0 ? 'warning' : 'default'}\n label={`${labels[reason]} ${count.toLocaleString()}`}\n />\n )\n })}\n </Stack>\n )\n}\n\nfunction JobsSection({ jobs }: { jobs: StaffOrgAiJobs | null }) {\n if (jobs === null) {\n return (\n <Alert severity=\"warning\">\n {'Could not read the generation jobs — a failed read, not an idle org.'}\n </Alert>\n )\n }\n const total = Object.values(jobs.counts).reduce((sum, n) => sum + n, 0)\n if (total === 0) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No generation jobs yet.'}\n </Typography>\n )\n }\n return (\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {(['queued', 'running', 'needs_input', 'needs_review', 'failed'] as const).map((status) => (\n <Chip\n key={status}\n size=\"small\"\n color={\n status === 'failed' && jobs.counts[status] > 0\n ? 'error'\n : (status === 'needs_input' || status === 'needs_review') &&\n jobs.counts[status] > 0\n ? 'warning'\n : 'default'\n }\n label={`${status.replace('_', ' ')} ${jobs.counts[status]}`}\n />\n ))}\n <Chip size=\"small\" variant=\"outlined\" label={`done ${jobs.counts.done}`} />\n </Stack>\n {jobs.truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Counted from the newest jobs only — these counts are a floor.'}\n </Typography>\n ) : null}\n {/* A record list: the grid scrolls its own columns, so a job id and a\n starter's uid never push the card's content past its edge. */}\n <ListTable\n rows={jobs.recent}\n columns={JOB_COLUMNS}\n getRowId={(job: StaffOrgAiJob) => job.id}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No recent jobs\"\n // The route's ten newest jobs: a bounded readout, so no filter over\n // the rows it holds (AGL-3321).\n disableColumnFilter\n quickFilter={false}\n />\n </Stack>\n )\n}\n\n/**\n * The recent jobs, one column per fact. Who started a job is a column of\n * its own rather than a link tucked beside the date, so it sorts like the\n * rest; the credits sort by what was SPENT, which is the figure the\n * pool was charged.\n */\nconst JOB_COLUMNS: GridColDef<StaffOrgAiJob>[] = [\n {\n field: 'id',\n headerName: 'Job',\n flex: 1,\n minWidth: 200,\n renderCell: ({ row }) => (\n <Typography variant=\"body2\" noWrap title={row.id} sx={{ fontFamily: 'monospace' }}>\n {row.id}\n </Typography>\n ),\n },\n {\n field: 'kind',\n headerName: 'Kind',\n width: 120,\n valueFormatter: (value: string) => value || '—',\n },\n {\n field: 'status',\n headerName: 'Status',\n width: 130,\n valueFormatter: (value: string) => String(value ?? '').replace('_', ' '),\n },\n {\n field: 'creditsSpent',\n headerName: 'Credits',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 140,\n valueFormatter: (value: number, row) =>\n `${credits(value)} / ${credits(row.creditsReserved)}`,\n },\n {\n field: 'createdAt',\n headerName: 'Started',\n width: 120,\n valueFormatter: (value: string | null) => when(value),\n },\n {\n field: 'createdBy',\n headerName: 'Started by',\n flex: 1,\n minWidth: 240,\n renderCell: ({ row }) =>\n row.createdBy ? (\n <AppLink\n href={buildRoute(Route.ADMIN_USER_DETAIL, { uid: row.createdBy })}\n variant=\"body2\"\n underline=\"hover\"\n noWrap\n sx={{ fontFamily: 'monospace' }}\n >\n {row.createdBy}\n </AppLink>\n ) : (\n '—'\n ),\n },\n]\n\nconst tokenCount = (value: number): string => Math.round(value).toLocaleString()\n\nconst rate = (value: number | null): string =>\n value == null ? '—' : `${Math.round(value * 100)}%`\n\n/** The month's tokens, and what each kind of request spends of them (AGL-2937). */\nfunction TokensSection({ tokens }: { tokens: StaffOrgAiTokens }) {\n const { total } = tokens\n return (\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Typography variant=\"body2\">\n {`${tokenCount(total.input)} sent · ${tokenCount(total.cached)} read from cache · ${tokenCount(total.cacheWrite)} written to cache · ${tokenCount(total.output)} generated`}\n </Typography>\n <Chip\n size=\"small\"\n color={tokens.cacheHitRate != null && tokens.cacheHitRate >= 0.5 ? 'success' : 'default'}\n label={`cache hit rate ${rate(tokens.cacheHitRate)}`}\n />\n </Stack>\n {tokens.kinds.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No model requests recorded by kind this month.'}\n </Typography>\n ) : (\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Kind'}</TableCell>\n <TableCell align=\"right\">{'Requests'}</TableCell>\n <TableCell align=\"right\">{'Per request'}</TableCell>\n <TableCell align=\"right\">{'Sent'}</TableCell>\n <TableCell align=\"right\">{'From cache'}</TableCell>\n <TableCell align=\"right\">{'Generated'}</TableCell>\n <TableCell align=\"right\">{'Cache hit'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {tokens.kinds.map((row) => (\n <TableRow key={row.kind}>\n <TableCell>{row.kind}</TableCell>\n <TableCell align=\"right\">{row.requests.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{usd(row.costPerRequestUsd)}</TableCell>\n <TableCell align=\"right\">{tokenCount(row.tokens.input)}</TableCell>\n <TableCell align=\"right\">{tokenCount(row.tokens.cached)}</TableCell>\n <TableCell align=\"right\">{tokenCount(row.tokens.output)}</TableCell>\n <TableCell align=\"right\">{rate(row.cacheHitRate)}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n )}\n </Stack>\n )\n}\n\n/**\n * The month's top spenders, one row per person. The row opens the account;\n * the uid rides beneath the roster's name because two people can share one.\n */\nfunction TopUsersSection({ users }: { users: StaffOrgAiUser[] }) {\n const router = useRouter()\n const columns = useMemo<GridColDef<StaffOrgAiUser>[]>(\n () => [\n {\n field: 'name',\n headerName: 'Person',\n flex: 1,\n minWidth: 220,\n renderCell: ({ row }) => (\n <Stack sx={{ minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap title={row.name}>\n {row.name}\n </Typography>\n {row.name !== row.uid ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n noWrap\n sx={{ fontFamily: 'monospace' }}\n >\n {row.uid}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'credits',\n headerName: 'Credits',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => credits(value),\n },\n {\n field: 'share',\n headerName: 'Share',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 90,\n valueFormatter: (value: number) => `${Math.round(value * 100)}%`,\n },\n {\n field: 'estCostUsd',\n headerName: 'Provider $',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 120,\n valueFormatter: (value: number) => usd(value),\n },\n {\n field: 'requests',\n headerName: 'Requests',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'refusals',\n headerName: 'Refusals',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n listActionsColumn((row: StaffOrgAiUser) => (\n <ListRowActions\n label={row.name}\n quick={{\n icon: mdiOpenInNew.path,\n label: 'View account',\n to: buildRoute(Route.ADMIN_USER_DETAIL, { uid: row.uid }),\n }}\n items={[]}\n />\n )),\n ],\n [],\n )\n return (\n <ListTable\n rows={users}\n columns={columns}\n getRowId={(row: StaffOrgAiUser) => row.uid}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(uid) => router.push(buildRoute(Route.ADMIN_USER_DETAIL, { uid }))}\n // The route's ten dearest members: a bounded readout, so no filter\n // over the rows it holds (AGL-3321).\n disableColumnFilter\n quickFilter={false}\n />\n )\n}\n\nconst StaffOrgAiCard = ({ orgId }: { orgId: string }) => {\n const { data: user } = useUser()\n // Keyed on who is signed in, not on the user object's identity.\n const signedInUid = user?.uid ?? null\n const userRef = useRef(user)\n userRef.current = user\n const [data, setData] = useState<StaffOrgAiResponse | null>(null)\n const [ready, setReady] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n useEffect(() => {\n // Mounted only for a confirmed staff reader — the `staffOrg` zone sits\n // inside the page's `StaffOnly`, which renders nothing while the claim\n // loads — because the route records an access row on every successful\n // open, and an open the page did not mean to make would be an access\n // nobody made.\n const user = userRef.current\n if (!orgId || !signedInUid || !user) return undefined\n let active = true\n setReady(false)\n setError(null)\n void (async () => {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/admin/org?orgId=${encodeURIComponent(orgId)}`,\n )\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n setError(payload?.error ?? 'AI lookup failed')\n setData(null)\n } else {\n setData(payload as StaffOrgAiResponse)\n }\n } catch {\n if (active) {\n setError('AI lookup failed')\n setData(null)\n }\n } finally {\n if (active) setReady(true)\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, signedInUid])\n\n const name = aiAddonName()\n return (\n <CardDisplay\n header={name}\n help={pluginDocsHelp('aiMonitoring', {\n anchor: '#the-ai-card',\n excerpt:\n 'The add-on, the credit pool and its parts, this month’s overage and refusals, generation jobs, tokens and the cache hit rate by kind, the people spending the most, and the margin — for this organization.',\n })}\n contentGutterX\n contentGutterY\n >\n {!ready ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : error || !data ? (\n <Alert severity=\"warning\">\n {`Could not read this organization’s AI usage — ${error ?? 'a failed read'}, not zero usage.`}\n </Alert>\n ) : (\n <Stack spacing={2}>\n {/* Add-on */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Add-on'}\n </Typography>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Chip\n size=\"small\"\n color={data.addon.on ? 'primary' : 'default'}\n label={data.addon.on ? `${name} on` : `${name} off`}\n />\n {data.addon.priceUsd !== null ? (\n <Typography variant=\"body2\">\n {`${usd(data.addon.priceUsd)}/mo on this plan`}\n </Typography>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Not sold on this plan'}\n </Typography>\n )}\n {data.addon.on ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {data.addon.since\n ? `since ${when(data.addon.since)}`\n : data.addon.sinceSource === 'unavailable'\n ? 'since — (Stripe not reachable)'\n : 'since — (no subscription item)'}\n </Typography>\n ) : null}\n </Stack>\n </Stack>\n\n {/* Credit pool */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {`Credit pool · ${data.month}`}\n </Typography>\n {data.pool.totalCredits === null && data.pool.uncapped ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Uncapped staff comp — no AI credit band. Bounded only by the message cap and an operator’s explicit spend ceiling.'}\n </Typography>\n ) : data.pool.totalCredits === null ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No AI credit band on this plan — bounded by the message cap and the operator backstop.'}\n </Typography>\n ) : (\n <Typography variant=\"body2\">\n {`${credits(data.pool.totalCredits)} credits = ${poolPartsSentence(data.pool)}`}\n </Typography>\n )}\n <Typography variant=\"body2\">\n {`Used ${credits(data.pool.usedCredits)} credits (${usd(data.pool.providerUsd)} provider spend)`}\n {data.pool.remainingCredits !== null\n ? ` · ${credits(data.pool.remainingCredits)} remaining`\n : ''}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Projected month-end at this pace: ${credits(data.pool.projectedCredits)} credits (${usd(data.pool.projectedUsd)}) · ${data.pool.messages.toLocaleString()} model turns, ${data.pool.deflected.toLocaleString()} answered from the docs`}\n </Typography>\n </Stack>\n\n {/* Overage */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Overage'}\n </Typography>\n <Typography variant=\"body2\">\n {`${credits(data.overage.overageCredits)} credits over the band · ${usd(data.overage.accruedUsd)} accrued`}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {overageStateSentence(data.overage, data.pool.uncapped === true)}\n </Typography>\n </Stack>\n\n {/* Refusals */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {`Refusals this month · ${data.refusals.total.toLocaleString()}`}\n </Typography>\n <RefusalsRow refusals={data.refusals} />\n </Stack>\n\n {/* Jobs */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Generation jobs'}\n </Typography>\n <JobsSection jobs={data.jobs} />\n </Stack>\n\n {/* Tokens — absent from a route older than the card */}\n {data.tokens ? (\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {`Tokens · ${data.month}`}\n </Typography>\n <TokensSection tokens={data.tokens} />\n </Stack>\n ) : null}\n\n {/* Top users */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Top users this month'}\n </Typography>\n {data.users.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No AI usage attributed to a person this month.'}\n </Typography>\n ) : (\n <TopUsersSection users={data.users} />\n )}\n </Stack>\n\n {/* Margin */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Margin'}\n </Typography>\n <Alert severity={marginTone(data.margin)}>\n {`Spend ${usd(data.margin.spendUsd)} against ${usd(data.margin.revenueUsd)} in — ` +\n `${usd(data.margin.addonRevenueUsd)} add-on, ${usd(data.margin.planAssistShareUsd)} plan assist share, ` +\n `${usd(data.margin.overageRevenueUsd)} overage.`}\n {data.margin.underwater\n ? ' Spend exceeds the add-on plus the plan’s assist share.'\n : ''}\n {data.margin.multiple >= 1\n ? ` ${data.margin.multiple}× the ${usd(data.margin.thresholdUsd)} staff review threshold.`\n : ''}\n </Alert>\n {data.margin.contribution.marginPct !== null ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Whole-org contribution margin ${Math.round(data.margin.contribution.marginPct * 100)}% on the ${data.margin.contribution.month} rollup with this month’s AI spend.`}\n </Typography>\n ) : null}\n </Stack>\n\n {/* Actions */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Actions'}\n </Typography>\n <Typography variant=\"body2\">\n {'Credit override and feature toggles: '}\n <AppLink href={buildRoute(Route.ADMIN_ORGS)} underline=\"hover\">\n {'the entitlement override editor'}\n </AppLink>\n {' (also under Staff actions above). Pause AI for this org: '}\n <AppLink href={buildRoute(Route.ADMIN_LOCKDOWN)} underline=\"hover\">\n {'Lockdown'}\n </AppLink>\n {', feature keys ai-assist and ai-generate scoped to the org.'}\n </Typography>\n </Stack>\n </Stack>\n )}\n </CardDisplay>\n )\n}\nStaffOrgAiCard.displayName = 'StaffOrgAiCard'\n\nexport default StaffOrgAiCard\n"],"names":["aiAddonName","buildRoute","Route","pluginDocsHelp","mdiOpenInNew","AppLink","CardDisplay","ListRowActions","ListTable","listActionsColumn","ScrollTable","TABLE_ROW_HEIGHT","useUser","authorizedFetch","Alert","Chip","Stack","TableBody","TableCell","TableHead","TableRow","Typography","useRouter","useEffect","useMemo","useRef","useState","credits","value","Math","round","toLocaleString","usd","amount","Number","toFixed","abs","when","iso","Date","toLocaleDateString","poolPartsSentence","pool","parts","overrideCredits","push","planCredits","addonCredits","join","overageStateSentence","overage","uncapped","hardCap","sellsOverage","rate","rateUsdPer1k","capUsd","capReached","marginTone","margin","underwater","multiple","RefusalsRow","refusals","labels","band","cap","messages","budget","allotment","account","requests","platform","direction","spacing","useFlexGap","sx","flexWrap","Object","keys","map","reason","count","size","variant","color","label","JobsSection","jobs","severity","total","values","counts","reduce","sum","n","status","replace","done","truncated","rows","recent","columns","JOB_COLUMNS","getRowId","job","id","rowHeight","noRowsLabel","disableColumnFilter","quickFilter","field","headerName","flex","minWidth","renderCell","row","noWrap","title","fontFamily","width","valueFormatter","String","type","align","headerAlign","creditsReserved","createdBy","href","ADMIN_USER_DETAIL","uid","underline","tokenCount","TokensSection","tokens","alignItems","input","cached","cacheWrite","output","cacheHitRate","kinds","length","kind","costPerRequestUsd","TopUsersSection","users","router","name","quick","icon","path","to","items","onOpen","StaffOrgAiCard","orgId","data","user","signedInUid","userRef","current","setData","ready","setReady","error","setError","undefined","active","response","encodeURIComponent","payload","json","catch","ok","header","help","anchor","excerpt","contentGutterX","contentGutterY","addon","on","priceUsd","since","sinceSource","month","totalCredits","usedCredits","providerUsd","remainingCredits","projectedCredits","projectedUsd","deflected","overageCredits","accruedUsd","spendUsd","revenueUsd","addonRevenueUsd","planAssistShareUsd","overageRevenueUsd","thresholdUsd","contribution","marginPct","ADMIN_ORGS","ADMIN_LOCKDOWN","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/staff-org-ai-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { aiAddonName } from '@aglyn/aglyn'\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiOpenInNew } from '@aglyn/shared-data-mdi'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Alert,\n Chip,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport type {\n StaffOrgAiJob,\n StaffOrgAiJobs,\n StaffOrgAiMargin,\n StaffOrgAiOverage,\n StaffOrgAiPool,\n StaffOrgAiRefusals,\n StaffOrgAiResponse,\n StaffOrgAiTokens,\n StaffOrgAiUser,\n} from '../usage/staff-org-ai'\nimport {\n StaffCreditReturnActions,\n StaffCreditReturnDialog,\n} from './staff-ai-credit-return.component'\n\n/**\n * THE STAFF AI CARD (AGL-2930): everything about one org's AI usage on the\n * page staff already open to look at that org.\n *\n * Read through `/api/ai/admin/org`, one request on mount. Every figure on it\n * is the route's, and every figure the route serves is composed from the\n * same helpers the meter refuses with — see `usage/staff-org-ai.ts`. This\n * file only lays them out.\n *\n * ## What each section is for\n *\n * - **Add-on** — is the org paying for AI, at what, since when.\n * - **Credit pool** — the band as three NAMED parts, because \"why does\n * this org have 13,000 credits\" has three answers (the plan, an override,\n * the add-on) and a single total cannot say which.\n * - **Overage** — what is past the band, what it is sold at, and the two\n * controls the org itself holds over it.\n * - **Refusals** — how often, and why, the gate said no this month.\n * - **Jobs** and **Top users** — the generative and per-person halves,\n * each of which says plainly when its rollup has nothing yet.\n * - **Tokens** — what each kind of request costs per request, what it\n * sends and generates, and how much of its prompt the cache served.\n * - **Margin** — spend against what AI brings in, red when it is over.\n * - **Actions** — where the override editor and the AI pause live.\n *\n * The header carries the card's two acts (AGL-3595): give credits back to\n * the band (and, on a Free workspace, to the owner's allowance), or reset\n * the month — and each recent job offers the same give-back, prefilled with\n * that job's spend.\n */\n\nconst credits = (value: number | null | undefined): string =>\n value == null ? '—' : Math.round(value).toLocaleString()\n\n/**\n * Four decimals under a dollar, two above (the assist-signals page's rule):\n * a month that really cost eight cents must not read as `$0.00`.\n */\nconst usd = (value: number | null | undefined): string => {\n if (value == null) return '—'\n const amount = Number(value) || 0\n return `$${amount.toFixed(Math.abs(amount) < 1 ? 4 : 2)}`\n}\n\nconst when = (iso: string | null): string =>\n iso ? new Date(iso).toLocaleDateString() : '—'\n\n/** The pool as one sentence naming every part that is non-zero. */\nexport function poolPartsSentence(pool: StaffOrgAiPool): string {\n const parts: string[] = []\n if (pool.overrideCredits !== null) {\n parts.push(`${credits(pool.overrideCredits)} staff override`)\n } else {\n parts.push(`${credits(pool.planCredits)} plan band`)\n }\n if (pool.addonCredits > 0) {\n parts.push(`${credits(pool.addonCredits)} ${aiAddonName()} add-on`)\n }\n return parts.join(' + ')\n}\n\n/**\n * The overage's state as one line: sold, walled, or capped — or, for an\n * uncapped staff comp (AGL-3049), nothing to sell past at all.\n */\nexport function overageStateSentence(\n overage: StaffOrgAiOverage,\n uncapped = false,\n): string {\n if (uncapped) {\n return 'Nothing is sold past a band: an uncapped staff comp has none, and bills nothing.'\n }\n if (overage.hardCap) return 'Stopped at the band — the org’s band switch is on.'\n if (!overage.sellsOverage) return 'Not sold past the band on this plan.'\n const rate =\n overage.rateUsdPer1k === null ? '' : ` at ${usd(overage.rateUsdPer1k)} per 1,000`\n if (overage.capUsd !== null) {\n return overage.capReached\n ? `Stopped — the org’s ${usd(overage.capUsd)} overage ceiling is reached.`\n : `Sold${rate}, up to the org’s ${usd(overage.capUsd)} ceiling.`\n }\n return `Sold${rate}, no ceiling set.`\n}\n\n/** Whether the margin section renders red. */\nexport function marginTone(margin: StaffOrgAiMargin): 'error' | 'warning' | 'success' {\n if (margin.underwater) return 'error'\n if (margin.multiple >= 1) return 'warning'\n return 'success'\n}\n\nfunction RefusalsRow({ refusals }: { refusals: StaffOrgAiRefusals }) {\n const labels: Record<keyof Omit<StaffOrgAiRefusals, 'total'>, string> = {\n band: 'band',\n cap: 'ceiling',\n messages: 'messages',\n budget: 'operator backstop',\n // A hard allotment a manager set on a member, a collaborator or a site\n // (AGL-2942).\n allotment: 'allotments',\n // The Free taste's rungs (AGL-2925): only a Free workspace collects these.\n account: 'free account',\n requests: 'free requests/day',\n refusals: 'free refusals/day',\n platform: 'platform ceiling',\n }\n return (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {(Object.keys(labels) as Array<keyof typeof labels>).map((reason) => {\n // A reason the answering server does not yet count reads as zero,\n // so a console newer than its route draws the row rather than\n // failing the card.\n const count = Number(refusals[reason] ?? 0)\n return (\n <Chip\n key={reason}\n size=\"small\"\n variant={count > 0 ? 'filled' : 'outlined'}\n color={count > 0 ? 'warning' : 'default'}\n label={`${labels[reason]} ${count.toLocaleString()}`}\n />\n )\n })}\n </Stack>\n )\n}\n\nfunction JobsSection({\n jobs,\n onGiveBack,\n}: {\n jobs: StaffOrgAiJobs | null\n onGiveBack: (job: StaffOrgAiJob) => void\n}) {\n const columns = useMemo(() => jobColumns(onGiveBack), [onGiveBack])\n if (jobs === null) {\n return (\n <Alert severity=\"warning\">\n {'Could not read the generation jobs — a failed read, not an idle org.'}\n </Alert>\n )\n }\n const total = Object.values(jobs.counts).reduce((sum, n) => sum + n, 0)\n if (total === 0) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No generation jobs yet.'}\n </Typography>\n )\n }\n return (\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {(['queued', 'running', 'needs_input', 'needs_review', 'failed'] as const).map((status) => (\n <Chip\n key={status}\n size=\"small\"\n color={\n status === 'failed' && jobs.counts[status] > 0\n ? 'error'\n : (status === 'needs_input' || status === 'needs_review') &&\n jobs.counts[status] > 0\n ? 'warning'\n : 'default'\n }\n label={`${status.replace('_', ' ')} ${jobs.counts[status]}`}\n />\n ))}\n <Chip size=\"small\" variant=\"outlined\" label={`done ${jobs.counts.done}`} />\n </Stack>\n {jobs.truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Counted from the newest jobs only — these counts are a floor.'}\n </Typography>\n ) : null}\n {/* A record list: the grid scrolls its own columns, so a job id and a\n starter's uid never push the card's content past its edge. */}\n <ListTable\n rows={jobs.recent}\n columns={columns}\n getRowId={(job: StaffOrgAiJob) => job.id}\n rowHeight={TABLE_ROW_HEIGHT}\n noRowsLabel=\"No recent jobs\"\n // The route's ten newest jobs: a bounded readout, so no filter over\n // the rows it holds (AGL-3321).\n disableColumnFilter\n quickFilter={false}\n />\n </Stack>\n )\n}\n\n/**\n * The recent jobs, one column per fact. Who started a job is a column of\n * its own rather than a link tucked beside the date, so it sorts like the\n * rest; the credits sort by what was SPENT, which is the figure the\n * pool was charged. The row's action gives that spend back (AGL-3595).\n */\nconst jobColumns = (\n onGiveBack: (job: StaffOrgAiJob) => void,\n): GridColDef<StaffOrgAiJob>[] => [\n {\n field: 'id',\n headerName: 'Job',\n flex: 1,\n minWidth: 200,\n renderCell: ({ row }) => (\n <Typography variant=\"body2\" noWrap title={row.id} sx={{ fontFamily: 'monospace' }}>\n {row.id}\n </Typography>\n ),\n },\n {\n field: 'kind',\n headerName: 'Kind',\n width: 120,\n valueFormatter: (value: string) => value || '—',\n },\n {\n field: 'status',\n headerName: 'Status',\n width: 130,\n valueFormatter: (value: string) => String(value ?? '').replace('_', ' '),\n },\n {\n field: 'creditsSpent',\n headerName: 'Credits',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 140,\n valueFormatter: (value: number, row) =>\n `${credits(value)} / ${credits(row.creditsReserved)}`,\n },\n {\n field: 'createdAt',\n headerName: 'Started',\n width: 120,\n valueFormatter: (value: string | null) => when(value),\n },\n {\n field: 'createdBy',\n headerName: 'Started by',\n flex: 1,\n minWidth: 240,\n renderCell: ({ row }) =>\n row.createdBy ? (\n <AppLink\n href={buildRoute(Route.ADMIN_USER_DETAIL, { uid: row.createdBy })}\n variant=\"body2\"\n underline=\"hover\"\n noWrap\n sx={{ fontFamily: 'monospace' }}\n >\n {row.createdBy}\n </AppLink>\n ) : (\n '—'\n ),\n },\n listActionsColumn((row: StaffOrgAiJob) => (\n <ListRowActions\n label={row.id}\n items={[\n {\n key: 'give-back',\n label: 'Give back this job’s credits',\n disabled: row.creditsSpent <= 0,\n disabledReason: row.creditsSpent <= 0 ? 'This job spent no credits.' : undefined,\n onClick: () => onGiveBack(row),\n },\n ]}\n />\n )),\n]\n\nconst tokenCount = (value: number): string => Math.round(value).toLocaleString()\n\nconst rate = (value: number | null): string =>\n value == null ? '—' : `${Math.round(value * 100)}%`\n\n/** The month's tokens, and what each kind of request spends of them (AGL-2937). */\nfunction TokensSection({ tokens }: { tokens: StaffOrgAiTokens }) {\n const { total } = tokens\n return (\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Typography variant=\"body2\">\n {`${tokenCount(total.input)} sent · ${tokenCount(total.cached)} read from cache · ${tokenCount(total.cacheWrite)} written to cache · ${tokenCount(total.output)} generated`}\n </Typography>\n <Chip\n size=\"small\"\n color={tokens.cacheHitRate != null && tokens.cacheHitRate >= 0.5 ? 'success' : 'default'}\n label={`cache hit rate ${rate(tokens.cacheHitRate)}`}\n />\n </Stack>\n {tokens.kinds.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No model requests recorded by kind this month.'}\n </Typography>\n ) : (\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Kind'}</TableCell>\n <TableCell align=\"right\">{'Requests'}</TableCell>\n <TableCell align=\"right\">{'Per request'}</TableCell>\n <TableCell align=\"right\">{'Sent'}</TableCell>\n <TableCell align=\"right\">{'From cache'}</TableCell>\n <TableCell align=\"right\">{'Generated'}</TableCell>\n <TableCell align=\"right\">{'Cache hit'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {tokens.kinds.map((row) => (\n <TableRow key={row.kind}>\n <TableCell>{row.kind}</TableCell>\n <TableCell align=\"right\">{row.requests.toLocaleString()}</TableCell>\n <TableCell align=\"right\">{usd(row.costPerRequestUsd)}</TableCell>\n <TableCell align=\"right\">{tokenCount(row.tokens.input)}</TableCell>\n <TableCell align=\"right\">{tokenCount(row.tokens.cached)}</TableCell>\n <TableCell align=\"right\">{tokenCount(row.tokens.output)}</TableCell>\n <TableCell align=\"right\">{rate(row.cacheHitRate)}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n )}\n </Stack>\n )\n}\n\n/**\n * The month's top spenders, one row per person. The row opens the account;\n * the uid rides beneath the roster's name because two people can share one.\n */\nfunction TopUsersSection({ users }: { users: StaffOrgAiUser[] }) {\n const router = useRouter()\n const columns = useMemo<GridColDef<StaffOrgAiUser>[]>(\n () => [\n {\n field: 'name',\n headerName: 'Person',\n flex: 1,\n minWidth: 220,\n renderCell: ({ row }) => (\n <Stack sx={{ minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap title={row.name}>\n {row.name}\n </Typography>\n {row.name !== row.uid ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n noWrap\n sx={{ fontFamily: 'monospace' }}\n >\n {row.uid}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'credits',\n headerName: 'Credits',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => credits(value),\n },\n {\n field: 'share',\n headerName: 'Share',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 90,\n valueFormatter: (value: number) => `${Math.round(value * 100)}%`,\n },\n {\n field: 'estCostUsd',\n headerName: 'Provider $',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 120,\n valueFormatter: (value: number) => usd(value),\n },\n {\n field: 'requests',\n headerName: 'Requests',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'refusals',\n headerName: 'Refusals',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n listActionsColumn((row: StaffOrgAiUser) => (\n <ListRowActions\n label={row.name}\n quick={{\n icon: mdiOpenInNew.path,\n label: 'View account',\n to: buildRoute(Route.ADMIN_USER_DETAIL, { uid: row.uid }),\n }}\n items={[]}\n />\n )),\n ],\n [],\n )\n return (\n <ListTable\n rows={users}\n columns={columns}\n getRowId={(row: StaffOrgAiUser) => row.uid}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(uid) => router.push(buildRoute(Route.ADMIN_USER_DETAIL, { uid }))}\n // The route's ten dearest members: a bounded readout, so no filter\n // over the rows it holds (AGL-3321).\n disableColumnFilter\n quickFilter={false}\n />\n )\n}\n\nconst StaffOrgAiCard = ({ orgId }: { orgId: string }) => {\n const { data: user } = useUser()\n // Keyed on who is signed in, not on the user object's identity.\n const signedInUid = user?.uid ?? null\n const userRef = useRef(user)\n userRef.current = user\n const [data, setData] = useState<StaffOrgAiResponse | null>(null)\n const [ready, setReady] = useState(false)\n const [error, setError] = useState<string | null>(null)\n // Bumped after a give-back, so the card re-reads the figures it changed.\n const [version, setVersion] = useState(0)\n const [notice, setNotice] = useState<string | null>(null)\n const [returning, setReturning] = useState<{\n mode: 'give' | 'reset'\n jobId?: string\n jobCredits?: number\n } | null>(null)\n const giveBackJob = useCallback(\n (job: StaffOrgAiJob) =>\n setReturning({ mode: 'give', jobId: job.id, jobCredits: job.creditsSpent }),\n [],\n )\n\n useEffect(() => {\n // Mounted only for a confirmed staff reader — the `staffOrg` zone sits\n // inside the page's `StaffOnly`, which renders nothing while the claim\n // loads — because the route records an access row on every successful\n // open, and an open the page did not mean to make would be an access\n // nobody made.\n const user = userRef.current\n if (!orgId || !signedInUid || !user) return undefined\n let active = true\n setReady(false)\n setError(null)\n void (async () => {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/admin/org?orgId=${encodeURIComponent(orgId)}`,\n )\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n setError(payload?.error ?? 'AI lookup failed')\n setData(null)\n } else {\n setData(payload as StaffOrgAiResponse)\n }\n } catch {\n if (active) {\n setError('AI lookup failed')\n setData(null)\n }\n } finally {\n if (active) setReady(true)\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, signedInUid, version])\n\n const name = aiAddonName()\n return (\n <CardDisplay\n header={name}\n help={pluginDocsHelp('aiMonitoring', {\n anchor: '#the-ai-card',\n excerpt:\n 'The add-on, the credit pool and its parts, this month’s overage and refusals, generation jobs, tokens and the cache hit rate by kind, the people spending the most, and the margin — for this organization.',\n })}\n HeaderProps={{\n action: (\n <StaffCreditReturnActions\n disabled={!data}\n onGive={() => setReturning({ mode: 'give' })}\n onReset={() => setReturning({ mode: 'reset' })}\n />\n ),\n }}\n contentGutterX\n contentGutterY\n >\n {data ? (\n <StaffCreditReturnDialog\n open={returning !== null}\n mode={returning?.mode ?? 'give'}\n target={{ orgId }}\n workspace={{ used: data.pool.usedCredits, limit: data.pool.totalCredits }}\n account={\n data.account\n ? { used: data.account.usedCredits, limit: data.account.limitCredits }\n : null\n }\n jobId={returning?.jobId ?? null}\n jobCredits={returning?.jobCredits ?? null}\n onClose={() => setReturning(null)}\n onReturned={(message) => {\n setReturning(null)\n setNotice(message)\n setVersion((current) => current + 1)\n }}\n />\n ) : null}\n {notice ? (\n <Alert severity=\"success\" onClose={() => setNotice(null)} sx={{ mb: 2 }}>\n {notice}\n </Alert>\n ) : null}\n {!ready ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : error || !data ? (\n <Alert severity=\"warning\">\n {`Could not read this organization’s AI usage — ${error ?? 'a failed read'}, not zero usage.`}\n </Alert>\n ) : (\n <Stack spacing={2}>\n {/* Add-on */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Add-on'}\n </Typography>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Chip\n size=\"small\"\n color={data.addon.on ? 'primary' : 'default'}\n label={data.addon.on ? `${name} on` : `${name} off`}\n />\n {data.addon.priceUsd !== null ? (\n <Typography variant=\"body2\">\n {`${usd(data.addon.priceUsd)}/mo on this plan`}\n </Typography>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Not sold on this plan'}\n </Typography>\n )}\n {data.addon.on ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {data.addon.since\n ? `since ${when(data.addon.since)}`\n : data.addon.sinceSource === 'unavailable'\n ? 'since — (Stripe not reachable)'\n : 'since — (no subscription item)'}\n </Typography>\n ) : null}\n </Stack>\n </Stack>\n\n {/* Credit pool */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {`Credit pool · ${data.month}`}\n </Typography>\n {data.pool.totalCredits === null && data.pool.uncapped ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Uncapped staff comp — no AI credit band. Bounded only by the message cap and an operator’s explicit spend ceiling.'}\n </Typography>\n ) : data.pool.totalCredits === null ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No AI credit band on this plan — bounded by the message cap and the operator backstop.'}\n </Typography>\n ) : (\n <Typography variant=\"body2\">\n {`${credits(data.pool.totalCredits)} credits = ${poolPartsSentence(data.pool)}`}\n </Typography>\n )}\n <Typography variant=\"body2\">\n {`Used ${credits(data.pool.usedCredits)} credits (${usd(data.pool.providerUsd)} provider spend)`}\n {data.pool.remainingCredits !== null\n ? ` · ${credits(data.pool.remainingCredits)} remaining`\n : ''}\n </Typography>\n {data.pool.returnedCredits ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${credits(data.pool.returnedCredits)} credits given back this month — already taken off the figure above.`}\n </Typography>\n ) : null}\n {data.account ? (\n <Typography variant=\"body2\">\n {`Owner’s Free allowance: ${credits(data.account.usedCredits)} of ${credits(data.account.limitCredits)} used across their free workspaces` +\n (data.account.returnedCredits\n ? ` (${credits(data.account.returnedCredits)} given back)`\n : '')}\n </Typography>\n ) : null}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Projected month-end at this pace: ${credits(data.pool.projectedCredits)} credits (${usd(data.pool.projectedUsd)}) · ${data.pool.messages.toLocaleString()} model turns, ${data.pool.deflected.toLocaleString()} answered from the docs`}\n </Typography>\n </Stack>\n\n {/* Overage */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Overage'}\n </Typography>\n <Typography variant=\"body2\">\n {`${credits(data.overage.overageCredits)} credits over the band · ${usd(data.overage.accruedUsd)} accrued`}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {overageStateSentence(data.overage, data.pool.uncapped === true)}\n </Typography>\n </Stack>\n\n {/* Refusals */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {`Refusals this month · ${data.refusals.total.toLocaleString()}`}\n </Typography>\n <RefusalsRow refusals={data.refusals} />\n </Stack>\n\n {/* Jobs */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Generation jobs'}\n </Typography>\n <JobsSection jobs={data.jobs} onGiveBack={giveBackJob} />\n </Stack>\n\n {/* Tokens — absent from a route older than the card */}\n {data.tokens ? (\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {`Tokens · ${data.month}`}\n </Typography>\n <TokensSection tokens={data.tokens} />\n </Stack>\n ) : null}\n\n {/* Top users */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Top users this month'}\n </Typography>\n {data.users.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No AI usage attributed to a person this month.'}\n </Typography>\n ) : (\n <TopUsersSection users={data.users} />\n )}\n </Stack>\n\n {/* Margin */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Margin'}\n </Typography>\n <Alert severity={marginTone(data.margin)}>\n {`Spend ${usd(data.margin.spendUsd)} against ${usd(data.margin.revenueUsd)} in — ` +\n `${usd(data.margin.addonRevenueUsd)} add-on, ${usd(data.margin.planAssistShareUsd)} plan assist share, ` +\n `${usd(data.margin.overageRevenueUsd)} overage.`}\n {data.margin.underwater\n ? ' Spend exceeds the add-on plus the plan’s assist share.'\n : ''}\n {data.margin.multiple >= 1\n ? ` ${data.margin.multiple}× the ${usd(data.margin.thresholdUsd)} staff review threshold.`\n : ''}\n </Alert>\n {data.margin.contribution.marginPct !== null ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Whole-org contribution margin ${Math.round(data.margin.contribution.marginPct * 100)}% on the ${data.margin.contribution.month} rollup with this month’s AI spend.`}\n </Typography>\n ) : null}\n </Stack>\n\n {/* Actions */}\n <Stack spacing={0.5}>\n <Typography variant=\"overline\" color=\"text.secondary\">\n {'Actions'}\n </Typography>\n <Typography variant=\"body2\">\n {'Credit override and feature toggles: '}\n <AppLink href={buildRoute(Route.ADMIN_ORGS)} underline=\"hover\">\n {'the entitlement override editor'}\n </AppLink>\n {' (also under Staff actions above). Pause AI for this org: '}\n <AppLink href={buildRoute(Route.ADMIN_LOCKDOWN)} underline=\"hover\">\n {'Lockdown'}\n </AppLink>\n {', feature keys ai-assist and ai-generate scoped to the org.'}\n </Typography>\n </Stack>\n </Stack>\n )}\n </CardDisplay>\n )\n}\nStaffOrgAiCard.displayName = 'StaffOrgAiCard'\n\nexport default StaffOrgAiCard\n"],"names":["aiAddonName","buildRoute","Route","pluginDocsHelp","mdiOpenInNew","AppLink","CardDisplay","ListRowActions","ListTable","listActionsColumn","ScrollTable","TABLE_ROW_HEIGHT","useUser","authorizedFetch","Alert","Chip","Stack","TableBody","TableCell","TableHead","TableRow","Typography","useRouter","useCallback","useEffect","useMemo","useRef","useState","StaffCreditReturnActions","StaffCreditReturnDialog","credits","value","Math","round","toLocaleString","usd","amount","Number","toFixed","abs","when","iso","Date","toLocaleDateString","poolPartsSentence","pool","parts","overrideCredits","push","planCredits","addonCredits","join","overageStateSentence","overage","uncapped","hardCap","sellsOverage","rate","rateUsdPer1k","capUsd","capReached","marginTone","margin","underwater","multiple","RefusalsRow","refusals","labels","band","cap","messages","budget","allotment","account","requests","platform","direction","spacing","useFlexGap","sx","flexWrap","Object","keys","map","reason","count","size","variant","color","label","JobsSection","jobs","onGiveBack","columns","jobColumns","severity","total","values","counts","reduce","sum","n","status","replace","done","truncated","rows","recent","getRowId","job","id","rowHeight","noRowsLabel","disableColumnFilter","quickFilter","field","headerName","flex","minWidth","renderCell","row","noWrap","title","fontFamily","width","valueFormatter","String","type","align","headerAlign","creditsReserved","createdBy","href","ADMIN_USER_DETAIL","uid","underline","items","key","disabled","creditsSpent","disabledReason","undefined","onClick","tokenCount","TokensSection","tokens","alignItems","input","cached","cacheWrite","output","cacheHitRate","kinds","length","kind","costPerRequestUsd","TopUsersSection","users","router","name","quick","icon","path","to","onOpen","StaffOrgAiCard","orgId","data","user","signedInUid","userRef","current","setData","ready","setReady","error","setError","version","setVersion","notice","setNotice","returning","setReturning","giveBackJob","mode","jobId","jobCredits","active","response","encodeURIComponent","payload","json","catch","ok","header","help","anchor","excerpt","HeaderProps","action","onGive","onReset","contentGutterX","contentGutterY","open","target","workspace","used","usedCredits","limit","totalCredits","limitCredits","onClose","onReturned","message","mb","addon","on","priceUsd","since","sinceSource","month","providerUsd","remainingCredits","returnedCredits","projectedCredits","projectedUsd","deflected","overageCredits","accruedUsd","spendUsd","revenueUsd","addonRevenueUsd","planAssistShareUsd","overageRevenueUsd","thresholdUsd","contribution","marginPct","ADMIN_ORGS","ADMIN_LOCKDOWN","displayName"],"mappings":"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"}
|
|
@@ -17,6 +17,10 @@ export interface StaffUserAiUsageRow {
|
|
|
17
17
|
* while the claim is still loading. The route records an access row about
|
|
18
18
|
* this person on every open, so an open the page did not mean to make would
|
|
19
19
|
* be an access nobody made.
|
|
20
|
+
*
|
|
21
|
+
* Above the table, the account's own Free allowance this month — what every
|
|
22
|
+
* free workspace it owns draws on — and in the header the two acts that give
|
|
23
|
+
* credits back to it (AGL-3595).
|
|
20
24
|
*/
|
|
21
25
|
declare const StaffUserAiUsageCard: {
|
|
22
26
|
({ uid }: {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/ 'use client';
|
|
17
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
17
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
18
18
|
import { aiAddonName } from "@aglyn/aglyn";
|
|
19
19
|
import { buildRoute, Route } from "@aglyn/aglyn/app-utils/console-routes";
|
|
20
20
|
import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
|
|
@@ -28,6 +28,7 @@ import { Alert, Typography } from "@mui/material";
|
|
|
28
28
|
import { useRouter } from "next/navigation";
|
|
29
29
|
import { useEffect, useRef, useState } from "react";
|
|
30
30
|
import { aiUsageMonthLabel } from "../usage/ai-usage-wire.js";
|
|
31
|
+
import { StaffCreditReturnActions, StaffCreditReturnDialog } from "./staff-ai-credit-return.component.js";
|
|
31
32
|
const orgHref = (orgId)=>buildRoute(Route.ADMIN_ORG_DETAIL, {
|
|
32
33
|
orgId
|
|
33
34
|
});
|
|
@@ -101,6 +102,10 @@ const orgHref = (orgId)=>buildRoute(Route.ADMIN_ORG_DETAIL, {
|
|
|
101
102
|
* while the claim is still loading. The route records an access row about
|
|
102
103
|
* this person on every open, so an open the page did not mean to make would
|
|
103
104
|
* be an access nobody made.
|
|
105
|
+
*
|
|
106
|
+
* Above the table, the account's own Free allowance this month — what every
|
|
107
|
+
* free workspace it owns draws on — and in the header the two acts that give
|
|
108
|
+
* credits back to it (AGL-3595).
|
|
104
109
|
*/ const StaffUserAiUsageCard = ({ uid })=>{
|
|
105
110
|
var _ref;
|
|
106
111
|
const { data: user } = useUser();
|
|
@@ -111,6 +116,11 @@ const orgHref = (orgId)=>buildRoute(Route.ADMIN_ORG_DETAIL, {
|
|
|
111
116
|
const [rows, setRows] = useState(null);
|
|
112
117
|
const [error, setError] = useState(null);
|
|
113
118
|
const [ready, setReady] = useState(false);
|
|
119
|
+
const [allowance, setAllowance] = useState(null);
|
|
120
|
+
// Bumped after a give-back, so the card re-reads the allowance it changed.
|
|
121
|
+
const [version, setVersion] = useState(0);
|
|
122
|
+
const [notice, setNotice] = useState(null);
|
|
123
|
+
const [returning, setReturning] = useState(null);
|
|
114
124
|
const router = useRouter();
|
|
115
125
|
useEffect(()=>{
|
|
116
126
|
const user = userRef.current;
|
|
@@ -128,8 +138,10 @@ const orgHref = (orgId)=>buildRoute(Route.ADMIN_ORG_DETAIL, {
|
|
|
128
138
|
setError((_ref = payload == null ? void 0 : payload.error) != null ? _ref : 'AI usage lookup failed');
|
|
129
139
|
setRows(null);
|
|
130
140
|
} else {
|
|
131
|
-
var _ref1;
|
|
141
|
+
var _ref1, _ref2;
|
|
132
142
|
setRows((_ref1 = payload == null ? void 0 : payload.rows) != null ? _ref1 : []);
|
|
143
|
+
// Absent from a route older than give-backs.
|
|
144
|
+
setAllowance((_ref2 = payload == null ? void 0 : payload.allowance) != null ? _ref2 : null);
|
|
133
145
|
}
|
|
134
146
|
} catch (unused) {
|
|
135
147
|
if (active) {
|
|
@@ -145,40 +157,83 @@ const orgHref = (orgId)=>buildRoute(Route.ADMIN_ORG_DETAIL, {
|
|
|
145
157
|
};
|
|
146
158
|
}, [
|
|
147
159
|
uid,
|
|
148
|
-
signedInUid
|
|
160
|
+
signedInUid,
|
|
161
|
+
version
|
|
149
162
|
]);
|
|
150
|
-
return /*#__PURE__*/
|
|
163
|
+
return /*#__PURE__*/ _jsxs(CardDisplay, {
|
|
151
164
|
header: `${aiAddonName()} usage across organizations`,
|
|
152
165
|
help: pluginDocsHelp('aiMonitoring', {
|
|
153
166
|
anchor: '#one-account',
|
|
154
167
|
excerpt: 'This account’s AI credits in every workspace it belongs to, month by month. Opening it is recorded as a staff access about this person.'
|
|
155
168
|
}),
|
|
169
|
+
HeaderProps: {
|
|
170
|
+
action: /*#__PURE__*/ _jsx(StaffCreditReturnActions, {
|
|
171
|
+
disabled: !allowance,
|
|
172
|
+
onGive: ()=>setReturning('give'),
|
|
173
|
+
onReset: ()=>setReturning('reset')
|
|
174
|
+
})
|
|
175
|
+
},
|
|
156
176
|
contentGutterX: true,
|
|
157
177
|
contentGutterY: true,
|
|
158
|
-
children:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
children: [
|
|
179
|
+
allowance ? /*#__PURE__*/ _jsx(StaffCreditReturnDialog, {
|
|
180
|
+
open: returning !== null,
|
|
181
|
+
mode: returning != null ? returning : 'give',
|
|
182
|
+
target: {
|
|
183
|
+
uid
|
|
184
|
+
},
|
|
185
|
+
workspace: null,
|
|
186
|
+
account: {
|
|
187
|
+
used: allowance.usedCredits,
|
|
188
|
+
limit: allowance.limitCredits
|
|
189
|
+
},
|
|
190
|
+
onClose: ()=>setReturning(null),
|
|
191
|
+
onReturned: (message)=>{
|
|
192
|
+
setReturning(null);
|
|
193
|
+
setNotice(message);
|
|
194
|
+
setVersion((current)=>current + 1);
|
|
195
|
+
}
|
|
196
|
+
}) : null,
|
|
197
|
+
notice ? /*#__PURE__*/ _jsx(Alert, {
|
|
198
|
+
severity: "success",
|
|
199
|
+
onClose: ()=>setNotice(null),
|
|
200
|
+
sx: {
|
|
201
|
+
mb: 2
|
|
202
|
+
},
|
|
203
|
+
children: notice
|
|
204
|
+
}) : null,
|
|
205
|
+
allowance ? /*#__PURE__*/ _jsx(Typography, {
|
|
206
|
+
variant: "body2",
|
|
207
|
+
sx: {
|
|
208
|
+
mb: 1
|
|
209
|
+
},
|
|
210
|
+
children: `Free AI allowance this month: ${allowance.usedCredits.toLocaleString()} of ${allowance.limitCredits.toLocaleString()} credits used across the free workspaces this account owns` + (allowance.returnedCredits ? ` (${allowance.returnedCredits.toLocaleString()} given back)` : '') + '.'
|
|
211
|
+
}) : null,
|
|
212
|
+
!ready ? /*#__PURE__*/ _jsx(Typography, {
|
|
213
|
+
variant: "body2",
|
|
214
|
+
color: "text.secondary",
|
|
215
|
+
children: 'Loading…'
|
|
216
|
+
}) : error || !rows ? /*#__PURE__*/ _jsx(Alert, {
|
|
217
|
+
severity: "warning",
|
|
218
|
+
children: `Could not read this account’s AI usage — ${error != null ? error : 'a failed read'}, not zero usage.`
|
|
219
|
+
}) : rows.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
|
|
220
|
+
variant: "body2",
|
|
221
|
+
color: "text.secondary",
|
|
222
|
+
children: 'No AI usage attributed to this account in any workspace it belongs to.'
|
|
223
|
+
}) : // Every month kept is in hand — at most fifty workspaces times the
|
|
224
|
+
// retention window — so the grid pages and sorts the one read, and
|
|
225
|
+
// offers no filter or search: a bounded readout is read whole, not
|
|
226
|
+
// narrowed over the rows it holds (AGL-3321).
|
|
227
|
+
/*#__PURE__*/ _jsx(ListTable, {
|
|
228
|
+
rows: rows,
|
|
229
|
+
columns: COLUMNS,
|
|
230
|
+
getRowId: rowId,
|
|
231
|
+
rowHeight: TABLE_ROW_HEIGHT,
|
|
232
|
+
onOpen: (_id, row)=>router.push(orgHref(row.orgId)),
|
|
233
|
+
disableColumnFilter: true,
|
|
234
|
+
quickFilter: false
|
|
235
|
+
})
|
|
236
|
+
]
|
|
182
237
|
});
|
|
183
238
|
};
|
|
184
239
|
StaffUserAiUsageCard.displayName = 'StaffUserAiUsageCard';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/staff-user-ai-usage-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { aiAddonName } from '@aglyn/aglyn'\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiOpenInNew } from '@aglyn/shared-data-mdi'\nimport { 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 { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useEffect, useRef, useState } from 'react'\nimport { aiUsageMonthLabel } from '../usage/ai-usage-wire'\n\n/** One row, as `/api/ai/admin/user` answers it. */\nexport interface StaffUserAiUsageRow {\n orgId: string\n orgName: string | null\n slug: string | null\n month: string\n credits: number\n requests: number\n refusals: number\n}\n\nconst orgHref = (orgId: string) => buildRoute(Route.ADMIN_ORG_DETAIL, { orgId })\n\n/** A row's id: one workspace's one month. */\nconst rowId = (row: StaffUserAiUsageRow) => `${row.orgId}:${row.month}`\n\n/**\n * One row per workspace per month kept. The row opens the workspace; the\n * month sorts by its key, which orders chronologically.\n */\nconst COLUMNS: GridColDef<StaffUserAiUsageRow>[] = [\n {\n field: 'orgName',\n headerName: 'Organization',\n flex: 1,\n minWidth: 200,\n valueGetter: (_value, row) => row.orgName ?? row.orgId,\n },\n {\n field: 'month',\n headerName: 'Month',\n width: 140,\n valueFormatter: (value: string) => aiUsageMonthLabel(value),\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: 'requests',\n headerName: 'Requests',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'refusals',\n headerName: 'Refusals',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n listActionsColumn((row: StaffUserAiUsageRow) => (\n <ListRowActions\n label={row.orgName ?? row.orgId}\n quick={{ icon: mdiOpenInNew.path, label: 'View organization', to: orgHref(row.orgId) }}\n items={[]}\n />\n )),\n]\n\n/**\n * ONE ACCOUNT'S AI USAGE ACROSS ORGANIZATIONS (AGL-2928), on the staff user\n * page: workspace, month, credits — every month kept, newest first.\n *\n * Read on mount, and it mounts only for a confirmed staff reader: the\n * `staffUser` zone sits inside the page's `StaffOnly`, which renders nothing\n * while the claim is still loading. The route records an access row about\n * this person on every open, so an open the page did not mean to make would\n * be an access nobody made.\n */\nconst StaffUserAiUsageCard = ({ uid }: { uid: string }) => {\n const { data: user } = useUser()\n // Keyed on who is signed in, not on the user object's identity (AGL-2928).\n const signedInUid = user?.uid ?? null\n const userRef = useRef(user)\n userRef.current = user\n const [rows, setRows] = useState<StaffUserAiUsageRow[] | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [ready, setReady] = useState(false)\n const router = useRouter()\n\n useEffect(() => {\n const user = userRef.current\n if (!uid || !signedInUid || !user) return undefined\n let active = true\n setReady(false)\n setError(null)\n void (async () => {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/admin/user?uid=${encodeURIComponent(uid)}`,\n )\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n setError(payload?.error ?? 'AI usage lookup failed')\n setRows(null)\n } else {\n setRows((payload?.rows ?? []) as StaffUserAiUsageRow[])\n }\n } catch {\n if (active) {\n setError('AI usage lookup failed')\n setRows(null)\n }\n } finally {\n if (active) setReady(true)\n }\n })()\n return () => {\n active = false\n }\n }, [uid, signedInUid])\n\n return (\n <CardDisplay\n header={`${aiAddonName()} usage across organizations`}\n help={pluginDocsHelp('aiMonitoring', {\n anchor: '#one-account',\n excerpt:\n 'This account’s AI credits in every workspace it belongs to, month by month. Opening it is recorded as a staff access about this person.',\n })}\n contentGutterX\n contentGutterY\n >\n {!ready ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : error || !rows ? (\n <Alert severity=\"warning\">\n {`Could not read this account’s AI usage — ${error ?? 'a failed read'}, not zero usage.`}\n </Alert>\n ) : rows.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No AI usage attributed to this account in any workspace it belongs to.'}\n </Typography>\n ) : (\n // Every month kept is in hand — at most fifty workspaces times the\n // retention window — so the grid pages and sorts the one read, and\n // offers no filter or search: a bounded readout is read whole, not\n // narrowed over the rows it holds (AGL-3321).\n <ListTable\n rows={rows}\n columns={COLUMNS}\n getRowId={rowId}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(_id, row: StaffUserAiUsageRow) => router.push(orgHref(row.orgId))}\n disableColumnFilter\n quickFilter={false}\n />\n )}\n </CardDisplay>\n )\n}\nStaffUserAiUsageCard.displayName = 'StaffUserAiUsageCard'\n\nexport default StaffUserAiUsageCard\n"],"names":["aiAddonName","buildRoute","Route","pluginDocsHelp","mdiOpenInNew","CardDisplay","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","authorizedFetch","useUser","Alert","Typography","useRouter","useEffect","useRef","useState","aiUsageMonthLabel","orgHref","orgId","ADMIN_ORG_DETAIL","rowId","row","month","COLUMNS","field","headerName","flex","minWidth","valueGetter","_value","orgName","width","valueFormatter","value","type","align","headerAlign","toLocaleString","label","quick","icon","path","to","items","StaffUserAiUsageCard","uid","data","user","signedInUid","userRef","current","rows","setRows","error","setError","ready","setReady","router","undefined","active","response","encodeURIComponent","payload","json","catch","ok","header","help","anchor","excerpt","contentGutterX","contentGutterY","variant","color","severity","length","columns","getRowId","rowHeight","onOpen","_id","push","disableColumnFilter","quickFilter","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/staff-user-ai-usage-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { aiAddonName } from '@aglyn/aglyn'\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiOpenInNew } from '@aglyn/shared-data-mdi'\nimport { 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 { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Typography } from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { useRouter } from 'next/navigation'\nimport { useEffect, useRef, useState } from 'react'\nimport { aiUsageMonthLabel } from '../usage/ai-usage-wire'\nimport type { StaffAiAccountAllowance } from '../usage/staff-org-ai'\nimport {\n StaffCreditReturnActions,\n StaffCreditReturnDialog,\n} from './staff-ai-credit-return.component'\n\n/** One row, as `/api/ai/admin/user` answers it. */\nexport interface StaffUserAiUsageRow {\n orgId: string\n orgName: string | null\n slug: string | null\n month: string\n credits: number\n requests: number\n refusals: number\n}\n\nconst orgHref = (orgId: string) => buildRoute(Route.ADMIN_ORG_DETAIL, { orgId })\n\n/** A row's id: one workspace's one month. */\nconst rowId = (row: StaffUserAiUsageRow) => `${row.orgId}:${row.month}`\n\n/**\n * One row per workspace per month kept. The row opens the workspace; the\n * month sorts by its key, which orders chronologically.\n */\nconst COLUMNS: GridColDef<StaffUserAiUsageRow>[] = [\n {\n field: 'orgName',\n headerName: 'Organization',\n flex: 1,\n minWidth: 200,\n valueGetter: (_value, row) => row.orgName ?? row.orgId,\n },\n {\n field: 'month',\n headerName: 'Month',\n width: 140,\n valueFormatter: (value: string) => aiUsageMonthLabel(value),\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: 'requests',\n headerName: 'Requests',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n {\n field: 'refusals',\n headerName: 'Refusals',\n type: 'number',\n align: 'right',\n headerAlign: 'right',\n width: 110,\n valueFormatter: (value: number) => value.toLocaleString(),\n },\n listActionsColumn((row: StaffUserAiUsageRow) => (\n <ListRowActions\n label={row.orgName ?? row.orgId}\n quick={{ icon: mdiOpenInNew.path, label: 'View organization', to: orgHref(row.orgId) }}\n items={[]}\n />\n )),\n]\n\n/**\n * ONE ACCOUNT'S AI USAGE ACROSS ORGANIZATIONS (AGL-2928), on the staff user\n * page: workspace, month, credits — every month kept, newest first.\n *\n * Read on mount, and it mounts only for a confirmed staff reader: the\n * `staffUser` zone sits inside the page's `StaffOnly`, which renders nothing\n * while the claim is still loading. The route records an access row about\n * this person on every open, so an open the page did not mean to make would\n * be an access nobody made.\n *\n * Above the table, the account's own Free allowance this month — what every\n * free workspace it owns draws on — and in the header the two acts that give\n * credits back to it (AGL-3595).\n */\nconst StaffUserAiUsageCard = ({ uid }: { uid: string }) => {\n const { data: user } = useUser()\n // Keyed on who is signed in, not on the user object's identity (AGL-2928).\n const signedInUid = user?.uid ?? null\n const userRef = useRef(user)\n userRef.current = user\n const [rows, setRows] = useState<StaffUserAiUsageRow[] | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [ready, setReady] = useState(false)\n const [allowance, setAllowance] = useState<StaffAiAccountAllowance | null>(null)\n // Bumped after a give-back, so the card re-reads the allowance it changed.\n const [version, setVersion] = useState(0)\n const [notice, setNotice] = useState<string | null>(null)\n const [returning, setReturning] = useState<'give' | 'reset' | null>(null)\n const router = useRouter()\n\n useEffect(() => {\n const user = userRef.current\n if (!uid || !signedInUid || !user) return undefined\n let active = true\n setReady(false)\n setError(null)\n void (async () => {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/admin/user?uid=${encodeURIComponent(uid)}`,\n )\n const payload = await response.json().catch(() => null)\n if (!active) return\n if (!response.ok) {\n setError(payload?.error ?? 'AI usage lookup failed')\n setRows(null)\n } else {\n setRows((payload?.rows ?? []) as StaffUserAiUsageRow[])\n // Absent from a route older than give-backs.\n setAllowance((payload?.allowance ?? null) as StaffAiAccountAllowance | null)\n }\n } catch {\n if (active) {\n setError('AI usage lookup failed')\n setRows(null)\n }\n } finally {\n if (active) setReady(true)\n }\n })()\n return () => {\n active = false\n }\n }, [uid, signedInUid, version])\n\n return (\n <CardDisplay\n header={`${aiAddonName()} usage across organizations`}\n help={pluginDocsHelp('aiMonitoring', {\n anchor: '#one-account',\n excerpt:\n 'This account’s AI credits in every workspace it belongs to, month by month. Opening it is recorded as a staff access about this person.',\n })}\n HeaderProps={{\n action: (\n <StaffCreditReturnActions\n disabled={!allowance}\n onGive={() => setReturning('give')}\n onReset={() => setReturning('reset')}\n />\n ),\n }}\n contentGutterX\n contentGutterY\n >\n {allowance ? (\n <StaffCreditReturnDialog\n open={returning !== null}\n mode={returning ?? 'give'}\n target={{ uid }}\n workspace={null}\n account={{ used: allowance.usedCredits, limit: allowance.limitCredits }}\n onClose={() => setReturning(null)}\n onReturned={(message) => {\n setReturning(null)\n setNotice(message)\n setVersion((current) => current + 1)\n }}\n />\n ) : null}\n {notice ? (\n <Alert severity=\"success\" onClose={() => setNotice(null)} sx={{ mb: 2 }}>\n {notice}\n </Alert>\n ) : null}\n {allowance ? (\n <Typography variant=\"body2\" sx={{ mb: 1 }}>\n {`Free AI allowance this month: ${allowance.usedCredits.toLocaleString()} of ${allowance.limitCredits.toLocaleString()} credits used across the free workspaces this account owns` +\n (allowance.returnedCredits\n ? ` (${allowance.returnedCredits.toLocaleString()} given back)`\n : '') +\n '.'}\n </Typography>\n ) : null}\n {!ready ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : error || !rows ? (\n <Alert severity=\"warning\">\n {`Could not read this account’s AI usage — ${error ?? 'a failed read'}, not zero usage.`}\n </Alert>\n ) : rows.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No AI usage attributed to this account in any workspace it belongs to.'}\n </Typography>\n ) : (\n // Every month kept is in hand — at most fifty workspaces times the\n // retention window — so the grid pages and sorts the one read, and\n // offers no filter or search: a bounded readout is read whole, not\n // narrowed over the rows it holds (AGL-3321).\n <ListTable\n rows={rows}\n columns={COLUMNS}\n getRowId={rowId}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(_id, row: StaffUserAiUsageRow) => router.push(orgHref(row.orgId))}\n disableColumnFilter\n quickFilter={false}\n />\n )}\n </CardDisplay>\n )\n}\nStaffUserAiUsageCard.displayName = 'StaffUserAiUsageCard'\n\nexport default StaffUserAiUsageCard\n"],"names":["aiAddonName","buildRoute","Route","pluginDocsHelp","mdiOpenInNew","CardDisplay","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","authorizedFetch","useUser","Alert","Typography","useRouter","useEffect","useRef","useState","aiUsageMonthLabel","StaffCreditReturnActions","StaffCreditReturnDialog","orgHref","orgId","ADMIN_ORG_DETAIL","rowId","row","month","COLUMNS","field","headerName","flex","minWidth","valueGetter","_value","orgName","width","valueFormatter","value","type","align","headerAlign","toLocaleString","label","quick","icon","path","to","items","StaffUserAiUsageCard","uid","data","user","signedInUid","userRef","current","rows","setRows","error","setError","ready","setReady","allowance","setAllowance","version","setVersion","notice","setNotice","returning","setReturning","router","undefined","active","response","encodeURIComponent","payload","json","catch","ok","header","help","anchor","excerpt","HeaderProps","action","disabled","onGive","onReset","contentGutterX","contentGutterY","open","mode","target","workspace","account","used","usedCredits","limit","limitCredits","onClose","onReturned","message","severity","sx","mb","variant","returnedCredits","color","length","columns","getRowId","rowHeight","onOpen","_id","push","disableColumnFilter","quickFilter","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,WAAW,QAAQ,eAAc;AAC1C,SAASC,UAAU,EAAEC,KAAK,QAAQ,wCAAuC;AACzE,SAASC,cAAc,QAAQ,mCAAkC;AACjE,SAASC,YAAY,QAAQ,yBAAwB;AACrD,SAASC,WAAW,QAAQ,uBAAsB;AAClD,SACEC,cAAc,EACdC,SAAS,EACTC,iBAAiB,QACZ,uDAAsD;AAC7D,SAASC,gBAAgB,QAAQ,8CAA6C;AAC9E,SAASC,eAAe,QAAQ,2CAA0C;AAC1E,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SAASC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AAEjD,SAASC,SAAS,QAAQ,kBAAiB;AAC3C,SAASC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AACnD,SAASC,iBAAiB,QAAQ,4BAAwB;AAE1D,SACEC,wBAAwB,EACxBC,uBAAuB,QAClB,wCAAoC;AAa3C,MAAMC,UAAU,CAACC,QAAkBrB,WAAWC,MAAMqB,gBAAgB,EAAE;QAAED;IAAM;AAE9E,2CAA2C,GAC3C,MAAME,QAAQ,CAACC,MAA6B,GAAGA,IAAIH,KAAK,CAAC,CAAC,EAAEG,IAAIC,KAAK,EAAE;AAEvE;;;CAGC,GACD,MAAMC,UAA6C;IACjD;QACEC,OAAO;QACPC,YAAY;QACZC,MAAM;QACNC,UAAU;QACVC,aAAa,CAACC,QAAQR;gBAAQA;oBAAAA,eAAAA,IAAIS,OAAO,YAAXT,eAAeA,IAAIH,KAAK;;IACxD;IACA;QACEM,OAAO;QACPC,YAAY;QACZM,OAAO;QACPC,gBAAgB,CAACC,QAAkBnB,kBAAkBmB;IACvD;IACA;QACET,OAAO;QACPC,YAAY;QACZS,MAAM;QACNC,OAAO;QACPC,aAAa;QACbL,OAAO;QACPC,gBAAgB,CAACC,QAAkBA,MAAMI,cAAc;IACzD;IACA;QACEb,OAAO;QACPC,YAAY;QACZS,MAAM;QACNC,OAAO;QACPC,aAAa;QACbL,OAAO;QACPC,gBAAgB,CAACC,QAAkBA,MAAMI,cAAc;IACzD;IACA;QACEb,OAAO;QACPC,YAAY;QACZS,MAAM;QACNC,OAAO;QACPC,aAAa;QACbL,OAAO;QACPC,gBAAgB,CAACC,QAAkBA,MAAMI,cAAc;IACzD;IACAjC,kBAAkB,CAACiB;YAERA;6BADT,KAACnB;YACCoC,KAAK,GAAEjB,eAAAA,IAAIS,OAAO,YAAXT,eAAeA,IAAIH,KAAK;YAC/BqB,OAAO;gBAAEC,MAAMxC,aAAayC,IAAI;gBAAEH,OAAO;gBAAqBI,IAAIzB,QAAQI,IAAIH,KAAK;YAAE;YACrFyB,OAAO,EAAE;;;CAGd;AAED;;;;;;;;;;;;;CAaC,GACD,MAAMC,uBAAuB,CAAC,EAAEC,GAAG,EAAmB;;IACpD,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAGxC;IACvB,2EAA2E;IAC3E,MAAMyC,sBAAcD,wBAAAA,KAAMF,GAAG,mBAAI;IACjC,MAAMI,UAAUrC,OAAOmC;IACvBE,QAAQC,OAAO,GAAGH;IAClB,MAAM,CAACI,MAAMC,QAAQ,GAAGvC,SAAuC;IAC/D,MAAM,CAACwC,OAAOC,SAAS,GAAGzC,SAAwB;IAClD,MAAM,CAAC0C,OAAOC,SAAS,GAAG3C,SAAS;IACnC,MAAM,CAAC4C,WAAWC,aAAa,GAAG7C,SAAyC;IAC3E,2EAA2E;IAC3E,MAAM,CAAC8C,SAASC,WAAW,GAAG/C,SAAS;IACvC,MAAM,CAACgD,QAAQC,UAAU,GAAGjD,SAAwB;IACpD,MAAM,CAACkD,WAAWC,aAAa,GAAGnD,SAAkC;IACpE,MAAMoD,SAASvD;IAEfC,UAAU;QACR,MAAMoC,OAAOE,QAAQC,OAAO;QAC5B,IAAI,CAACL,OAAO,CAACG,eAAe,CAACD,MAAM,OAAOmB;QAC1C,IAAIC,SAAS;QACbX,SAAS;QACTF,SAAS;QACT,KAAK,AAAC,CAAA;YACJ,IAAI;gBACF,MAAMc,WAAW,MAAM9D,gBACrByC,MACA,CAAC,uBAAuB,EAAEsB,mBAAmBxB,MAAM;gBAErD,MAAMyB,UAAU,MAAMF,SAASG,IAAI,GAAGC,KAAK,CAAC,IAAM;gBAClD,IAAI,CAACL,QAAQ;gBACb,IAAI,CAACC,SAASK,EAAE,EAAE;;oBAChBnB,iBAASgB,2BAAAA,QAASjB,KAAK,mBAAI;oBAC3BD,QAAQ;gBACV,OAAO;;oBACLA,iBAASkB,2BAAAA,QAASnB,IAAI,oBAAI,EAAE;oBAC5B,6CAA6C;oBAC7CO,sBAAcY,2BAAAA,QAASb,SAAS,oBAAI;gBACtC;YACF,EAAE,eAAM;gBACN,IAAIU,QAAQ;oBACVb,SAAS;oBACTF,QAAQ;gBACV;YACF,SAAU;gBACR,IAAIe,QAAQX,SAAS;YACvB;QACF,CAAA;QACA,OAAO;YACLW,SAAS;QACX;IACF,GAAG;QAACtB;QAAKG;QAAaW;KAAQ;IAE9B,qBACE,MAAC1D;QACCyE,QAAQ,GAAG9E,cAAc,2BAA2B,CAAC;QACrD+E,MAAM5E,eAAe,gBAAgB;YACnC6E,QAAQ;YACRC,SACE;QACJ;QACAC,aAAa;YACXC,sBACE,KAAChE;gBACCiE,UAAU,CAACvB;gBACXwB,QAAQ,IAAMjB,aAAa;gBAC3BkB,SAAS,IAAMlB,aAAa;;QAGlC;QACAmB,cAAc;QACdC,cAAc;;YAEb3B,0BACC,KAACzC;gBACCqE,MAAMtB,cAAc;gBACpBuB,IAAI,EAAEvB,oBAAAA,YAAa;gBACnBwB,QAAQ;oBAAE1C;gBAAI;gBACd2C,WAAW;gBACXC,SAAS;oBAAEC,MAAMjC,UAAUkC,WAAW;oBAAEC,OAAOnC,UAAUoC,YAAY;gBAAC;gBACtEC,SAAS,IAAM9B,aAAa;gBAC5B+B,YAAY,CAACC;oBACXhC,aAAa;oBACbF,UAAUkC;oBACVpC,WAAW,CAACV,UAAYA,UAAU;gBACpC;iBAEA;YACHW,uBACC,KAACrD;gBAAMyF,UAAS;gBAAUH,SAAS,IAAMhC,UAAU;gBAAOoC,IAAI;oBAAEC,IAAI;gBAAE;0BACnEtC;iBAED;YACHJ,0BACC,KAAChD;gBAAW2F,SAAQ;gBAAQF,IAAI;oBAAEC,IAAI;gBAAE;0BACrC,CAAC,8BAA8B,EAAE1C,UAAUkC,WAAW,CAACtD,cAAc,GAAG,IAAI,EAAEoB,UAAUoC,YAAY,CAACxD,cAAc,GAAG,0DAA0D,CAAC,GAC/KoB,CAAAA,UAAU4C,eAAe,GACtB,CAAC,EAAE,EAAE5C,UAAU4C,eAAe,CAAChE,cAAc,GAAG,YAAY,CAAC,GAC7D,EAAC,IACL;iBAEF;YACH,CAACkB,sBACA,KAAC9C;gBAAW2F,SAAQ;gBAAQE,OAAM;0BAC/B;iBAEDjD,SAAS,CAACF,qBACZ,KAAC3C;gBAAMyF,UAAS;0BACb,CAAC,yCAAyC,EAAE5C,gBAAAA,QAAS,gBAAgB,iBAAiB,CAAC;iBAExFF,KAAKoD,MAAM,KAAK,kBAClB,KAAC9F;gBAAW2F,SAAQ;gBAAQE,OAAM;0BAC/B;iBAGH,mEAAmE;YACnE,mEAAmE;YACnE,mEAAmE;YACnE,8CAA8C;0BAC9C,KAACnG;gBACCgD,MAAMA;gBACNqD,SAASjF;gBACTkF,UAAUrF;gBACVsF,WAAWrG;gBACXsG,QAAQ,CAACC,KAAKvF,MAA6B4C,OAAO4C,IAAI,CAAC5F,QAAQI,IAAIH,KAAK;gBACxE4F,mBAAmB;gBACnBC,aAAa;;;;AAKvB;AACAnE,qBAAqBoE,WAAW,GAAG;AAEnC,eAAepE,qBAAoB"}
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
19
19
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
20
20
|
import { AI_JOB_TERMINAL_STATUSES } from "../model/ai-jobs.types.js";
|
|
21
|
-
import {
|
|
21
|
+
import { followAiJobEvents } from "./ai-job-events.js";
|
|
22
|
+
import { publishAiJob } from "./ai-jobs-store.js";
|
|
22
23
|
/**
|
|
23
24
|
* One answer per member and workspace, however many controls ask. The
|
|
24
25
|
* automation run history draws a control on every failed run, and each asking
|
|
@@ -99,23 +100,7 @@ export function useAiJobRun(user, failed) {
|
|
|
99
100
|
(_watchRef_current = watchRef.current) == null ? void 0 : _watchRef_current.abort();
|
|
100
101
|
const controller = new AbortController();
|
|
101
102
|
watchRef.current = controller;
|
|
102
|
-
|
|
103
|
-
while(again && !controller.signal.aborted){
|
|
104
|
-
again = false;
|
|
105
|
-
try {
|
|
106
|
-
const response = await authorizedFetch(userRef.current, `/api/ai/jobs/${encodeURIComponent(jobId)}/events?orgId=${encodeURIComponent(orgId)}`, {
|
|
107
|
-
signal: controller.signal
|
|
108
|
-
});
|
|
109
|
-
if (!response.ok || !response.body) return;
|
|
110
|
-
await readEventFrames(response.body, (event)=>{
|
|
111
|
-
if (controller.signal.aborted) return;
|
|
112
|
-
if (event['type'] === 'state') setJob(event['job']);
|
|
113
|
-
else if (event['type'] === 'reconnect') again = true;
|
|
114
|
-
});
|
|
115
|
-
} catch (unused) {
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
103
|
+
await followAiJobEvents(()=>userRef.current, orgId, jobId, controller.signal, setJob);
|
|
119
104
|
}, []);
|
|
120
105
|
const start = useCallback(async (request)=>{
|
|
121
106
|
var _watchRef_current;
|
|
@@ -145,6 +130,8 @@ export function useAiJobRun(user, failed) {
|
|
|
145
130
|
return false;
|
|
146
131
|
}
|
|
147
132
|
setJob(next);
|
|
133
|
+
// The indicator counts it from the moment it exists (AGL-3593).
|
|
134
|
+
publishAiJob(next);
|
|
148
135
|
} catch (unused) {
|
|
149
136
|
setNotice(failed);
|
|
150
137
|
return false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/use-ai-job-run.ts"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport {\n authorizedFetch,\n type MaybeTokenSource,\n type TokenSource,\n} from '@aglyn/shared-util-http/authorized-token'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n AI_JOB_TERMINAL_STATUSES,\n type AiJobKind,\n type AiJobSummary,\n} from '../model/ai-jobs.types'\nimport { readEventFrames } from './assist-jobs-drawer.component'\n\n/**\n * Starting one AI job from a console control, and following it until it\n * settles (AGL-2919): the jobs route's create door, then its events door, the\n * way the SEO card follows its jobs. A control that starts a job the beat\n * runs shows its progress here and its result when it arrives; closing the\n * control stops following, and the job carries on in AI jobs.\n */\n\n/** Whether the jobs route serves a workspace: still asking, yes, or no. */\nexport type AiJobsVerdict = 'checking' | 'ready' | 'hidden'\n\n/**\n * One answer per member and workspace, however many controls ask. The\n * automation run history draws a control on every failed run, and each asking\n * the route would be a request a row.\n */\nconst verdicts = new Map<string, Promise<Exclude<AiJobsVerdict, 'checking'>>>()\n\n/** Forgets every answer, so the next control to mount asks the route again. */\nexport function forgetAiJobsVerdicts(): void {\n verdicts.clear()\n}\n\nfunction askJobsRoute(\n user: MaybeTokenSource,\n uid: string,\n orgId: string,\n): Promise<Exclude<AiJobsVerdict, 'checking'>> {\n const key = `${uid}\\n${orgId}`\n const known = verdicts.get(key)\n if (known) return known\n const answer = authorizedFetch(user, `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=1`)\n // The release flag off (404) and a plan or member without generation\n // (403): the feature is not this workspace's, so the control is absent.\n .then((response): Exclude<AiJobsVerdict, 'checking'> =>\n response.status === 404 || response.status === 403 ? 'hidden' : 'ready',\n )\n .catch((): Exclude<AiJobsVerdict, 'checking'> => {\n // A request that never arrived is no answer, so the next control asks again.\n verdicts.delete(key)\n return 'hidden'\n })\n verdicts.set(key, answer)\n return answer\n}\n\n/**\n * Whether a control that starts a job should be drawn. The shell decided the\n * plan and the member's permission before mounting it; the release flag is the\n * route's to decide.\n */\nexport function useAiJobsVerdict(\n user: (TokenSource & { uid?: string | null }) | null | undefined,\n orgId: string | undefined,\n): AiJobsVerdict {\n // Held in a ref so the question keys on WHO is signed in, never on the\n // identity of the object that says so.\n const userRef = useRef(user)\n userRef.current = user\n const uid = user?.uid ?? null\n const [verdict, setVerdict] = useState<AiJobsVerdict>('checking')\n\n useEffect(() => {\n if (!orgId || !uid) return\n let active = true\n setVerdict('checking')\n void askJobsRoute(userRef.current, uid, orgId).then((answer) => {\n if (active) setVerdict(answer)\n })\n return () => {\n active = false\n }\n }, [orgId, uid])\n\n return verdict\n}\n\n/** What a control asks the create door for. */\nexport interface AiJobRequest {\n orgId: string\n hostId: string\n kind: AiJobKind\n brief: string\n inputs: Record<string, string>\n}\n\n/** Whether a job has stopped moving: finished, or waiting on something only a person can change. */\nexport function aiJobSettled(job: AiJobSummary | null): boolean {\n if (!job) return false\n return (\n AI_JOB_TERMINAL_STATUSES.includes(job.status) ||\n job.status === 'needs_input' ||\n job.status === 'needs_review'\n )\n}\n\n/**\n * Why a settled job has no result to show, in words a person can act on, or\n * `null` for a job that finished.\n */\nexport function aiJobProblem(job: AiJobSummary | null, failed: string): string | null {\n if (!job || !aiJobSettled(job) || job.status === 'done') return null\n if (job.status === 'canceled') return 'The job was canceled.'\n if (job.status === 'needs_review') return job.review?.message || job.error || failed\n return job.error || failed\n}\n\nexport interface AiJobRun {\n /** The job as it last arrived; `null` before one is started. */\n job: AiJobSummary | null\n /** The create door is being asked. */\n starting: boolean\n /** The job exists and has not settled. */\n running: boolean\n /** Why the create door refused, or could not be reached. */\n notice: string | null\n /** Starts the job, `true` once it exists, and follows it from there; `false` when the door refused. */\n start: (request: AiJobRequest) => Promise<boolean>\n /** Stops following and forgets the job, for a control asked again. */\n reset: () => void\n}\n\nexport function useAiJobRun(user: MaybeTokenSource, failed: string): AiJobRun {\n const userRef = useRef(user)\n userRef.current = user\n const [job, setJob] = useState<AiJobSummary | null>(null)\n const [starting, setStarting] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n const watchRef = useRef<AbortController | null>(null)\n\n useEffect(() => () => watchRef.current?.abort(), [])\n\n /** Follows a job through the events route until it settles or the control goes. */\n const watch = useCallback(async (jobId: string, orgId: string) => {\n watchRef.current?.abort()\n const controller = new AbortController()\n watchRef.current = controller\n let again = true\n while (again && !controller.signal.aborted) {\n again = false\n try {\n const response = await authorizedFetch(\n userRef.current,\n `/api/ai/jobs/${encodeURIComponent(jobId)}/events?orgId=${encodeURIComponent(orgId)}`,\n { signal: controller.signal },\n )\n if (!response.ok || !response.body) return\n await readEventFrames(response.body, (event) => {\n if (controller.signal.aborted) return\n if (event['type'] === 'state') setJob(event['job'] as AiJobSummary)\n else if (event['type'] === 'reconnect') again = true\n })\n } catch {\n return\n }\n }\n }, [])\n\n const start = useCallback(\n async (request: AiJobRequest) => {\n watchRef.current?.abort()\n setStarting(true)\n setNotice(null)\n setJob(null)\n let next: AiJobSummary | undefined\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(request),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(locked ? lockdownRefusalText(locked) : String(payload?.error ?? failed))\n return false\n }\n next = payload?.job as AiJobSummary | undefined\n if (!next) {\n setNotice(failed)\n return false\n }\n setJob(next)\n } catch {\n setNotice(failed)\n return false\n } finally {\n setStarting(false)\n }\n if (!aiJobSettled(next)) void watch(next.id, request.orgId)\n return true\n },\n [failed, watch],\n )\n\n const reset = useCallback(() => {\n watchRef.current?.abort()\n setJob(null)\n setNotice(null)\n }, [])\n\n return { job, starting, running: Boolean(job && !aiJobSettled(job)), notice, start, reset }\n}\n"],"names":["lockdownRefusalText","parseLockdownRefusal","authorizedFetch","useCallback","useEffect","useRef","useState","AI_JOB_TERMINAL_STATUSES","readEventFrames","verdicts","Map","forgetAiJobsVerdicts","clear","askJobsRoute","user","uid","orgId","key","known","get","answer","encodeURIComponent","then","response","status","catch","delete","set","useAiJobsVerdict","userRef","current","verdict","setVerdict","active","aiJobSettled","job","includes","aiJobProblem","failed","review","message","error","useAiJobRun","setJob","starting","setStarting","notice","setNotice","watchRef","abort","watch","jobId","controller","AbortController","again","signal","aborted","ok","body","event","start","request","next","method","headers","JSON","stringify","payload","json","locked","String","id","reset","running","Boolean"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/use-ai-job-run.ts"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport {\n authorizedFetch,\n type MaybeTokenSource,\n type TokenSource,\n} from '@aglyn/shared-util-http/authorized-token'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n AI_JOB_TERMINAL_STATUSES,\n type AiJobKind,\n type AiJobSummary,\n} from '../model/ai-jobs.types'\nimport { followAiJobEvents } from './ai-job-events'\nimport { publishAiJob } from './ai-jobs-store'\n\n/**\n * Starting one AI job from a console control, and following it until it\n * settles (AGL-2919): the jobs route's create door, then its events door, the\n * way the SEO card follows its jobs. A control that starts a job the beat\n * runs shows its progress here and its result when it arrives; closing the\n * control stops following, and the job carries on in AI jobs.\n */\n\n/** Whether the jobs route serves a workspace: still asking, yes, or no. */\nexport type AiJobsVerdict = 'checking' | 'ready' | 'hidden'\n\n/**\n * One answer per member and workspace, however many controls ask. The\n * automation run history draws a control on every failed run, and each asking\n * the route would be a request a row.\n */\nconst verdicts = new Map<string, Promise<Exclude<AiJobsVerdict, 'checking'>>>()\n\n/** Forgets every answer, so the next control to mount asks the route again. */\nexport function forgetAiJobsVerdicts(): void {\n verdicts.clear()\n}\n\nfunction askJobsRoute(\n user: MaybeTokenSource,\n uid: string,\n orgId: string,\n): Promise<Exclude<AiJobsVerdict, 'checking'>> {\n const key = `${uid}\\n${orgId}`\n const known = verdicts.get(key)\n if (known) return known\n const answer = authorizedFetch(user, `/api/ai/jobs?orgId=${encodeURIComponent(orgId)}&limit=1`)\n // The release flag off (404) and a plan or member without generation\n // (403): the feature is not this workspace's, so the control is absent.\n .then((response): Exclude<AiJobsVerdict, 'checking'> =>\n response.status === 404 || response.status === 403 ? 'hidden' : 'ready',\n )\n .catch((): Exclude<AiJobsVerdict, 'checking'> => {\n // A request that never arrived is no answer, so the next control asks again.\n verdicts.delete(key)\n return 'hidden'\n })\n verdicts.set(key, answer)\n return answer\n}\n\n/**\n * Whether a control that starts a job should be drawn. The shell decided the\n * plan and the member's permission before mounting it; the release flag is the\n * route's to decide.\n */\nexport function useAiJobsVerdict(\n user: (TokenSource & { uid?: string | null }) | null | undefined,\n orgId: string | undefined,\n): AiJobsVerdict {\n // Held in a ref so the question keys on WHO is signed in, never on the\n // identity of the object that says so.\n const userRef = useRef(user)\n userRef.current = user\n const uid = user?.uid ?? null\n const [verdict, setVerdict] = useState<AiJobsVerdict>('checking')\n\n useEffect(() => {\n if (!orgId || !uid) return\n let active = true\n setVerdict('checking')\n void askJobsRoute(userRef.current, uid, orgId).then((answer) => {\n if (active) setVerdict(answer)\n })\n return () => {\n active = false\n }\n }, [orgId, uid])\n\n return verdict\n}\n\n/** What a control asks the create door for. */\nexport interface AiJobRequest {\n orgId: string\n hostId: string\n kind: AiJobKind\n brief: string\n inputs: Record<string, string>\n}\n\n/** Whether a job has stopped moving: finished, or waiting on something only a person can change. */\nexport function aiJobSettled(job: AiJobSummary | null): boolean {\n if (!job) return false\n return (\n AI_JOB_TERMINAL_STATUSES.includes(job.status) ||\n job.status === 'needs_input' ||\n job.status === 'needs_review'\n )\n}\n\n/**\n * Why a settled job has no result to show, in words a person can act on, or\n * `null` for a job that finished.\n */\nexport function aiJobProblem(job: AiJobSummary | null, failed: string): string | null {\n if (!job || !aiJobSettled(job) || job.status === 'done') return null\n if (job.status === 'canceled') return 'The job was canceled.'\n if (job.status === 'needs_review') return job.review?.message || job.error || failed\n return job.error || failed\n}\n\nexport interface AiJobRun {\n /** The job as it last arrived; `null` before one is started. */\n job: AiJobSummary | null\n /** The create door is being asked. */\n starting: boolean\n /** The job exists and has not settled. */\n running: boolean\n /** Why the create door refused, or could not be reached. */\n notice: string | null\n /** Starts the job, `true` once it exists, and follows it from there; `false` when the door refused. */\n start: (request: AiJobRequest) => Promise<boolean>\n /** Stops following and forgets the job, for a control asked again. */\n reset: () => void\n}\n\nexport function useAiJobRun(user: MaybeTokenSource, failed: string): AiJobRun {\n const userRef = useRef(user)\n userRef.current = user\n const [job, setJob] = useState<AiJobSummary | null>(null)\n const [starting, setStarting] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n const watchRef = useRef<AbortController | null>(null)\n\n useEffect(() => () => watchRef.current?.abort(), [])\n\n /** Follows a job through the events route until it settles or the control goes. */\n const watch = useCallback(async (jobId: string, orgId: string) => {\n watchRef.current?.abort()\n const controller = new AbortController()\n watchRef.current = controller\n await followAiJobEvents(() => userRef.current, orgId, jobId, controller.signal, setJob)\n }, [])\n\n const start = useCallback(\n async (request: AiJobRequest) => {\n watchRef.current?.abort()\n setStarting(true)\n setNotice(null)\n setJob(null)\n let next: AiJobSummary | undefined\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(request),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(locked ? lockdownRefusalText(locked) : String(payload?.error ?? failed))\n return false\n }\n next = payload?.job as AiJobSummary | undefined\n if (!next) {\n setNotice(failed)\n return false\n }\n setJob(next)\n // The indicator counts it from the moment it exists (AGL-3593).\n publishAiJob(next)\n } catch {\n setNotice(failed)\n return false\n } finally {\n setStarting(false)\n }\n if (!aiJobSettled(next)) void watch(next.id, request.orgId)\n return true\n },\n [failed, watch],\n )\n\n const reset = useCallback(() => {\n watchRef.current?.abort()\n setJob(null)\n setNotice(null)\n }, [])\n\n return { job, starting, running: Boolean(job && !aiJobSettled(job)), notice, start, reset }\n}\n"],"names":["lockdownRefusalText","parseLockdownRefusal","authorizedFetch","useCallback","useEffect","useRef","useState","AI_JOB_TERMINAL_STATUSES","followAiJobEvents","publishAiJob","verdicts","Map","forgetAiJobsVerdicts","clear","askJobsRoute","user","uid","orgId","key","known","get","answer","encodeURIComponent","then","response","status","catch","delete","set","useAiJobsVerdict","userRef","current","verdict","setVerdict","active","aiJobSettled","job","includes","aiJobProblem","failed","review","message","error","useAiJobRun","setJob","starting","setStarting","notice","setNotice","watchRef","abort","watch","jobId","controller","AbortController","signal","start","request","next","method","headers","body","JSON","stringify","payload","json","ok","locked","String","id","reset","running","Boolean"],"mappings":"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"}
|