@aglyn/plugins-ai 1.0.0-beta.218 → 1.0.0-beta.220
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 -2
- package/package.json +18 -18
- package/src/index.d.ts +1 -0
- package/src/index.js.map +1 -1
- package/src/lib/activity/ai-activity-actions.js +8 -1
- package/src/lib/activity/ai-activity-actions.js.map +1 -1
- package/src/lib/billing/ai-month-meter.d.ts +51 -0
- package/src/lib/billing/ai-month-meter.js +89 -0
- package/src/lib/billing/ai-month-meter.js.map +1 -0
- package/src/lib/billing/ai-overage-close.d.ts +11 -33
- package/src/lib/billing/ai-overage-close.js +4 -3
- package/src/lib/billing/ai-overage-close.js.map +1 -1
- package/src/lib/billing/ai-overage-cutover.d.ts +28 -12
- package/src/lib/billing/ai-overage-cutover.js +1 -28
- package/src/lib/billing/ai-overage-cutover.js.map +1 -1
- package/src/lib/billing/ai-overage-ledger.js +1 -1
- package/src/lib/billing/ai-overage-ledger.js.map +1 -1
- package/src/lib/billing/ai-overage-standing.js +1 -1
- package/src/lib/billing/ai-overage-standing.js.map +1 -1
- package/src/lib/components/ai-allotment-editor.component.js +1 -1
- package/src/lib/components/ai-allotment-editor.component.js.map +1 -1
- package/src/lib/components/ai-assist-provider.component.js +5 -4
- package/src/lib/components/ai-assist-provider.component.js.map +1 -1
- package/src/lib/components/ai-automation-zones.d.ts +63 -0
- package/src/lib/components/ai-automation-zones.js +31 -0
- package/src/lib/components/ai-automation-zones.js.map +1 -0
- package/src/lib/components/ai-brief-dialog.component.js +3 -3
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-describe-automation.component.d.ts +1 -1
- package/src/lib/components/ai-describe-automation.component.js.map +1 -1
- package/src/lib/components/ai-describe-button.component.d.ts +6 -3
- package/src/lib/components/ai-describe-button.component.js +4 -1
- package/src/lib/components/ai-describe-button.component.js.map +1 -1
- package/src/lib/components/ai-experiment-cards.component.js +1 -1
- package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
- package/src/lib/components/ai-explain-automation.component.d.ts +1 -1
- package/src/lib/components/ai-explain-automation.component.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.d.ts +8 -0
- package/src/lib/components/ai-job-plan.component.js +21 -4
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-permissions-on-host.component.d.ts +12 -2
- package/src/lib/components/ai-permissions-on-host.component.js +12 -3
- package/src/lib/components/ai-permissions-on-host.component.js.map +1 -1
- package/src/lib/components/ai-product-copy-card.component.d.ts +1 -1
- package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
- package/src/lib/components/ai-product-import-option.component.d.ts +1 -1
- package/src/lib/components/ai-product-import-option.component.js.map +1 -1
- package/src/lib/components/ai-product-zones.d.ts +124 -0
- package/src/lib/components/ai-product-zones.js +32 -0
- package/src/lib/components/ai-product-zones.js.map +1 -0
- package/src/lib/components/ai-products-hub-card.component.d.ts +1 -1
- package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
- package/src/lib/components/ai-products-proposals.component.d.ts +1 -1
- package/src/lib/components/ai-products-proposals.component.js.map +1 -1
- package/src/lib/components/ai-seo-audit-card.component.js +35 -92
- package/src/lib/components/ai-seo-audit-card.component.js.map +1 -1
- package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
- package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
- package/src/lib/components/assist-jobs-drawer.component.js +21 -15
- package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
- package/src/lib/components/assist-panel.component.js +2 -2
- package/src/lib/components/assist-panel.component.js.map +1 -1
- package/src/lib/components/assist-signals-page.component.js +2 -2
- package/src/lib/components/assist-signals-page.component.js.map +1 -1
- package/src/lib/components/billing-ai-allotments.component.js +1 -1
- package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
- package/src/lib/components/billing-assist-overage-card.component.js +1 -1
- package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
- package/src/lib/constants.d.ts +5 -0
- package/src/lib/constants.js +4 -0
- package/src/lib/constants.js.map +1 -1
- package/src/lib/declarations.js +2 -0
- package/src/lib/declarations.js.map +1 -1
- package/src/lib/declarations.server.js +24 -10
- package/src/lib/declarations.server.js.map +1 -1
- package/src/lib/insights/ai-dataset-catalog.d.ts +49 -0
- package/src/lib/insights/ai-dataset-catalog.js +57 -0
- package/src/lib/insights/ai-dataset-catalog.js.map +1 -0
- package/src/lib/insights/ai-figure-readers.d.ts +6 -39
- package/src/lib/insights/ai-figure-readers.js +2 -391
- package/src/lib/insights/ai-figure-readers.js.map +1 -1
- package/src/lib/insights/ai-insight-digest.d.ts +10 -1
- package/src/lib/insights/ai-insight-digest.js +16 -4
- package/src/lib/insights/ai-insight-digest.js.map +1 -1
- package/src/lib/insights/ai-insight-readers.d.ts +1 -1
- package/src/lib/insights/ai-insight-readers.js.map +1 -1
- package/src/lib/jobs/ai-email-bindings.d.ts +28 -21
- package/src/lib/jobs/ai-email-bindings.js +75 -42
- package/src/lib/jobs/ai-email-bindings.js.map +1 -1
- package/src/lib/jobs/ai-job-campaign-step.d.ts +0 -2
- package/src/lib/jobs/ai-job-campaign-step.js +5 -10
- package/src/lib/jobs/ai-job-campaign-step.js.map +1 -1
- package/src/lib/jobs/ai-job-component-step.d.ts +6 -1
- package/src/lib/jobs/ai-job-component-step.js +32 -9
- package/src/lib/jobs/ai-job-component-step.js.map +1 -1
- package/src/lib/jobs/ai-job-draft-ids.js +11 -0
- package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
- package/src/lib/jobs/ai-job-drafts.js +1 -1
- package/src/lib/jobs/ai-job-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-email-step.d.ts +5 -3
- package/src/lib/jobs/ai-job-email-step.js +11 -4
- package/src/lib/jobs/ai-job-email-step.js.map +1 -1
- package/src/lib/jobs/ai-job-experiment-step.d.ts +1 -1
- package/src/lib/jobs/ai-job-experiment-step.js +1 -1
- package/src/lib/jobs/ai-job-experiment-step.js.map +1 -1
- package/src/lib/jobs/ai-job-form-step.d.ts +17 -1
- package/src/lib/jobs/ai-job-form-step.js +60 -8
- package/src/lib/jobs/ai-job-form-step.js.map +1 -1
- package/src/lib/jobs/ai-job-generation.d.ts +22 -1
- package/src/lib/jobs/ai-job-generation.js +63 -3
- package/src/lib/jobs/ai-job-generation.js.map +1 -1
- package/src/lib/jobs/ai-job-insight-step.js +1 -1
- package/src/lib/jobs/ai-job-insight-step.js.map +1 -1
- package/src/lib/jobs/ai-job-layout-step.d.ts +15 -4
- package/src/lib/jobs/ai-job-layout-step.js +46 -13
- package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.d.ts +4 -2
- package/src/lib/jobs/ai-job-page-sections.js +7 -4
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-page-step.js +7 -2
- package/src/lib/jobs/ai-job-page-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-conformance.d.ts +5 -0
- package/src/lib/jobs/ai-job-plan-conformance.js +40 -1
- package/src/lib/jobs/ai-job-plan-conformance.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-creations.d.ts +80 -0
- package/src/lib/jobs/ai-job-plan-creations.js +199 -0
- package/src/lib/jobs/ai-job-plan-creations.js.map +1 -0
- package/src/lib/jobs/ai-job-plan-step.d.ts +11 -2
- package/src/lib/jobs/ai-job-plan-step.js +49 -23
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-plugin-drafts.d.ts +14 -6
- package/src/lib/jobs/ai-job-plugin-drafts.js +12 -4
- package/src/lib/jobs/ai-job-plugin-drafts.js.map +1 -1
- package/src/lib/jobs/ai-job-products-step.d.ts +7 -0
- package/src/lib/jobs/ai-job-products-step.js +54 -32
- package/src/lib/jobs/ai-job-products-step.js.map +1 -1
- package/src/lib/jobs/ai-job-seo-step.d.ts +3 -24
- package/src/lib/jobs/ai-job-seo-step.js +35 -108
- package/src/lib/jobs/ai-job-seo-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.d.ts +2 -0
- package/src/lib/jobs/ai-job-site-step.js +6 -6
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-job-template-step.d.ts +9 -6
- package/src/lib/jobs/ai-job-template-step.js +73 -15
- package/src/lib/jobs/ai-job-template-step.js.map +1 -1
- package/src/lib/jobs/ai-job-theme-step.d.ts +1 -1
- package/src/lib/jobs/ai-job-theme-step.js +1 -1
- package/src/lib/jobs/ai-job-theme-step.js.map +1 -1
- package/src/lib/jobs/ai-job-workflow-step.js +17 -9
- package/src/lib/jobs/ai-job-workflow-step.js.map +1 -1
- package/src/lib/jobs/ai-jobs.js +1 -1
- package/src/lib/jobs/ai-jobs.js.map +1 -1
- package/src/lib/jobs/ai-workflow-records.d.ts +20 -11
- package/src/lib/jobs/ai-workflow-records.js +80 -43
- package/src/lib/jobs/ai-workflow-records.js.map +1 -1
- package/src/lib/model/ai-allotments.d.ts +27 -6
- package/src/lib/model/ai-allotments.js +54 -14
- package/src/lib/model/ai-allotments.js.map +1 -1
- package/src/lib/model/ai-automation-outline.d.ts +21 -2
- package/src/lib/model/ai-automation-outline.js.map +1 -1
- package/src/lib/model/ai-build-plan.d.ts +57 -0
- package/src/lib/model/ai-build-plan.js +116 -7
- package/src/lib/model/ai-build-plan.js.map +1 -1
- package/src/lib/model/ai-job-creations.d.ts +55 -0
- package/src/lib/model/ai-job-creations.js +85 -0
- package/src/lib/model/ai-job-creations.js.map +1 -0
- package/src/lib/model/ai-page-job.d.ts +3 -2
- package/src/lib/model/ai-page-job.js +4 -3
- package/src/lib/model/ai-page-job.js.map +1 -1
- package/src/lib/model/ai-seo.d.ts +11 -51
- package/src/lib/model/ai-seo.js +0 -17
- package/src/lib/model/ai-seo.js.map +1 -1
- package/src/lib/model/ai-site-inventory.js +1 -1
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +4 -2
- package/src/lib/model/ai-site-job.js +8 -4
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-template-subjects.d.ts +10 -0
- package/src/lib/model/ai-template-subjects.js +16 -2
- package/src/lib/model/ai-template-subjects.js.map +1 -1
- package/src/lib/model/ai-theme-proposal.d.ts +1 -1
- package/src/lib/model/ai-theme-proposal.js +1 -1
- package/src/lib/model/ai-theme-proposal.js.map +1 -1
- package/src/lib/model/ai-tokens.js +1 -1
- package/src/lib/model/ai-tokens.js.map +1 -1
- package/src/lib/model/ai-usage-by-user.js +1 -1
- package/src/lib/model/ai-usage-by-user.js.map +1 -1
- package/src/lib/model/ai-workflow-job.d.ts +1 -3
- package/src/lib/model/ai-workflow-job.js +1 -2
- package/src/lib/model/ai-workflow-job.js.map +1 -1
- package/src/lib/plan-entitlements.d.ts +149 -0
- package/src/lib/plan-entitlements.js +168 -0
- package/src/lib/plan-entitlements.js.map +1 -0
- package/src/lib/plugin.js +1 -1
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/providers/catalog.js +1 -1
- package/src/lib/providers/catalog.js.map +1 -1
- package/src/lib/providers/model-choice.js +1 -1
- package/src/lib/providers/model-choice.js.map +1 -1
- package/src/lib/runtime/ai-device-audit.d.ts +1 -1
- package/src/lib/runtime/ai-device-audit.js +1 -1
- package/src/lib/runtime/ai-device-audit.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +25 -4
- package/src/lib/runtime/ai-doctrine-validators.js +178 -21
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +5 -5
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-live.js +30 -15
- package/src/lib/runtime/ai-eval-live.js.map +1 -1
- package/src/lib/runtime/ai-eval.d.ts +9 -2
- package/src/lib/runtime/ai-eval.js +29 -2
- package/src/lib/runtime/ai-eval.js.map +1 -1
- package/src/lib/runtime/ai-gate.js +1 -1
- package/src/lib/runtime/ai-gate.js.map +1 -1
- package/src/lib/runtime/ai-node-tree.js +3 -3
- package/src/lib/runtime/ai-node-tree.js.map +1 -1
- package/src/lib/runtime/ai-palette.d.ts +7 -1
- package/src/lib/runtime/ai-palette.generated.d.ts +1 -1
- package/src/lib/runtime/ai-palette.generated.js +16 -13
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/ai-palette.js +7 -1
- package/src/lib/runtime/ai-palette.js.map +1 -1
- package/src/lib/runtime/seo-audit.d.ts +13 -63
- package/src/lib/runtime/seo-audit.js +22 -207
- package/src/lib/runtime/seo-audit.js.map +1 -1
- package/src/lib/runtime/site-inventory.js +29 -23
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server/ai-admin-org.js +1 -1
- package/src/lib/server/ai-admin-org.js.map +1 -1
- package/src/lib/server/ai-admin-orgs-spend.js +1 -1
- package/src/lib/server/ai-admin-orgs-spend.js.map +1 -1
- package/src/lib/server/ai-admin-signals.js +1 -1
- package/src/lib/server/ai-admin-signals.js.map +1 -1
- package/src/lib/server/ai-allotments.js +1 -1
- package/src/lib/server/ai-allotments.js.map +1 -1
- package/src/lib/server/ai-assist-prompts.js +3 -3
- package/src/lib/server/ai-assist-prompts.js.map +1 -1
- package/src/lib/server/ai-assist.js +2 -2
- package/src/lib/server/ai-assist.js.map +1 -1
- package/src/lib/server/assist-chat.d.ts +1 -1
- package/src/lib/server/assist-chat.js +13 -12
- package/src/lib/server/assist-chat.js.map +1 -1
- package/src/lib/server/assist-view-context.js +23 -23
- package/src/lib/server/assist-view-context.js.map +1 -1
- package/src/lib/server/billing-credits.d.ts +5 -0
- package/src/lib/server/billing-credits.js +8 -2
- package/src/lib/server/billing-credits.js.map +1 -1
- package/src/lib/server/billing-overage.js +2 -2
- package/src/lib/server/billing-overage.js.map +1 -1
- package/src/lib/server.d.ts +1 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-automation-index.d.ts +20 -0
- package/src/lib/testing/stand-in-automation-index.js +102 -0
- package/src/lib/testing/stand-in-automation-index.js.map +1 -0
- package/src/lib/testing/stand-in-dataset-index.d.ts +20 -0
- package/src/lib/testing/stand-in-dataset-index.js +80 -0
- package/src/lib/testing/stand-in-dataset-index.js.map +1 -0
- package/src/lib/testing/stand-in-list-send-time.d.ts +4 -0
- package/src/lib/testing/stand-in-list-send-time.js +77 -0
- package/src/lib/testing/stand-in-list-send-time.js.map +1 -0
- package/src/lib/testing/stand-in-pipeline-index.d.ts +32 -0
- package/src/lib/testing/stand-in-pipeline-index.js +59 -0
- package/src/lib/testing/stand-in-pipeline-index.js.map +1 -0
- package/src/lib/testing/stand-in-product-index.d.ts +20 -0
- package/src/lib/testing/stand-in-product-index.js +105 -0
- package/src/lib/testing/stand-in-product-index.js.map +1 -0
- package/src/lib/tools/ai-component-tool.js +2 -2
- package/src/lib/tools/ai-component-tool.js.map +1 -1
- package/src/lib/tools/ai-duplicate-resource-tool.js +1 -1
- package/src/lib/tools/ai-duplicate-resource-tool.js.map +1 -1
- package/src/lib/tools/ai-seo-tool.d.ts +6 -18
- package/src/lib/tools/ai-seo-tool.js +7 -38
- package/src/lib/tools/ai-seo-tool.js.map +1 -1
- package/src/lib/tools/ai-workflow-tool.d.ts +1 -1
- package/src/lib/tools/ai-workflow-tool.js +1 -1
- package/src/lib/tools/ai-workflow-tool.js.map +1 -1
- package/src/lib/usage/ai-allotment-alerts.d.ts +10 -7
- package/src/lib/usage/ai-allotment-alerts.js +39 -15
- package/src/lib/usage/ai-allotment-alerts.js.map +1 -1
- package/src/lib/usage/ai-quota-checks.d.ts +37 -0
- package/src/lib/usage/ai-quota-checks.js +69 -0
- package/src/lib/usage/ai-quota-checks.js.map +1 -0
- package/src/lib/usage/ai-usage-alerts.js +22 -10
- package/src/lib/usage/ai-usage-alerts.js.map +1 -1
- package/src/lib/usage/ai-usage-by-user.js +1 -1
- package/src/lib/usage/ai-usage-by-user.js.map +1 -1
- package/src/lib/usage/assist-ceiling.d.ts +2 -2
- package/src/lib/usage/assist-ceiling.js +4 -4
- package/src/lib/usage/assist-ceiling.js.map +1 -1
- package/src/lib/usage/assist-credits.d.ts +494 -0
- package/src/lib/usage/assist-credits.js +524 -0
- package/src/lib/usage/assist-credits.js.map +1 -0
- package/src/lib/usage/assist-free-taste.d.ts +1 -1
- package/src/lib/usage/assist-free-taste.js +3 -2
- package/src/lib/usage/assist-free-taste.js.map +1 -1
- package/src/lib/usage/assist-signal-mining.js +1 -1
- package/src/lib/usage/assist-signal-mining.js.map +1 -1
- package/src/lib/usage/assist-spend-guards.d.ts +153 -0
- package/src/lib/usage/assist-spend-guards.js +182 -0
- package/src/lib/usage/assist-spend-guards.js.map +1 -0
- package/src/lib/usage/assist-usage.d.ts +1 -1
- package/src/lib/usage/assist-usage.js +1 -1
- package/src/lib/usage/assist-usage.js.map +1 -1
- package/src/lib/usage/staff-org-ai.js +1 -1
- package/src/lib/usage/staff-org-ai.js.map +1 -1
- package/src/lib/usage-axes.d.ts +49 -0
- package/src/lib/usage-axes.js +108 -0
- package/src/lib/usage-axes.js.map +1 -0
- package/src/lib/runtime/seo-page-facts.d.ts +0 -61
- package/src/lib/runtime/seo-page-facts.js +0 -150
- package/src/lib/runtime/seo-page-facts.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/billing-assist-overage-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\n'use client'\n\nimport { CardDisplay, useConfirmationContext, useLoading } from '@aglyn/shared-ui-jsx'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Chip,\n FormControlLabel,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { aiAddonName } from '@aglyn/aglyn'\nimport {\n ASSIST_OVERAGE_CAP_CONTROL_LABEL,\n ASSIST_OVERAGE_CAP_MAX_USD,\n ASSIST_OVERAGE_CAP_MIN_USD,\n} from '@aglyn/aglyn/app-utils/assist-credits'\n\n/** The `get` payload of `/api/billing/assist-overage`. */\ninterface AssistOverageState {\n /** Whether the org asked to be stopped at its band. Absent by default. */\n hardCap: boolean\n /** The org's ceiling on this month's overage in USD, or `null` for none. */\n capUsd: number | null\n /** The plan's included credits a month, or `null` where it sells none. */\n bandCredits: number | null\n /** The per-1,000 rate credits past the band bill at, or `null`. */\n overageRateUsdPer1k: number | null\n /** True when there is a band AND a rate to sell past it at. */\n sellsOverage: boolean\n /** The switch's label, served so the refusal and the switch share it. */\n label: string\n /** The ceiling's label, served for the same reason. */\n capLabel: string\n /** The range the route accepts for the ceiling. */\n minCapUsd: number\n maxCapUsd: number\n}\n\nexport interface BillingAssistOverageCardProps {\n orgId?: string | null\n /** billing.manage: the switch acts; view-only otherwise. */\n canManage: boolean\n}\n\n/**\n * The org's own controls on AI assist spend: the hard cap at the band\n * (AGL-2653) and the dollar ceiling on overage (AGL-2898).\n *\n * ## What this card is\n *\n * Credits past a plan's included assist band are SOLD by default, at the\n * plan's per-1,000 rate, the way storage past its band bills by default. This\n * card offers the two controls a customer has over that: a switch that makes\n * the band a wall, so the assistant refuses there and nothing past it is ever\n * billed; and, with the switch off, a monthly ceiling in dollars on the\n * overage the org will buy before the assistant stops. Both are optional and\n * unset unless chosen — the storage cap's siblings, for the same reason a\n * ceiling on metered spend is the END USER's control, offered rather than\n * imposed.\n *\n * ## Why a switch AND a number\n *\n * The switch answers \"sell past the band at all?\" and needs no figure: the\n * band is the plan's, and a second band here would drift from it. The\n * ceiling answers \"and if so, how much?\", which is a figure only the customer\n * can name — it is a bound on THEIR invoice, not a quantity the plan sold.\n * The ceiling is offered only while the switch is off, because past a wall\n * there is no overage to bound.\n *\n * ## Why the switch is edited here and enforced there\n *\n * The route owns every refusal: a plan that sells no overage, a value that\n * is not a boolean, a missing permission. This card sends the boolean and\n * renders the route's own answer rather than pre-validating against constants\n * that would then be a second source of truth. The rate it quotes arrives on\n * the same payload, so what the card says the overage costs is what the\n * invoice bills.\n *\n * ## Why turning it OFF is confirmed and turning it ON is not\n *\n * Off is the direction that can raise a bill, so it asks; on never raises\n * one, so it is a plain click. Off is also always available, on any plan and\n * from any state — an org that wants the sale back must not have to argue\n * with a precondition.\n */\n/**\n * The card as the Billing → Usage page's plugin band mounts it (AGL-2939):\n * the overage controls under the heading and help the page used to draw\n * around them.\n */\nexport default function BillingAssistOverageCard(props: BillingAssistOverageCardProps) {\n return (\n <div id=\"assist-overage\">\n <CardDisplay\n header={'AI credits overage'}\n subheader={\n 'Extra AI credits past your included band are billed on your ' +\n 'monthly invoice. Stop at the band, or stop once the overage ' +\n 'reaches an amount you choose.'\n }\n help={pluginDocsHelp('billing', {\n anchor: '#assist-overage',\n excerpt:\n 'On a paid plan the assistant keeps answering past your ' +\n 'included credits and the extra is billed at your plan\\'s ' +\n 'per-1,000 rate, unless you switch on the stop at the included band.',\n })}\n contentGutterX\n contentGutterY\n >\n <BillingAssistOverageControls {...props} />\n </CardDisplay>\n </div>\n )\n}\n\nexport function BillingAssistOverageControls({\n orgId,\n canManage,\n}: BillingAssistOverageCardProps) {\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { queueLoading } = useLoading()\n const { confirm } = useConfirmationContext()\n const [state, setState] = useState<AssistOverageState | null>(null)\n const [busy, setBusy] = useState(false)\n /** The ceiling field, as typed; the route validates, this only carries. */\n const [capInput, setCapInput] = useState('')\n /**\n * Three outcomes, not two (the AGL-1380 rule). \"The assistant keeps going\n * past your band\" is a claim about this org's billing settings; rendering\n * it because a request FAILED would tell an org that asked to be stopped\n * that it had not.\n */\n const [loadState, setLoadState] = useState<'pending' | 'error' | 'loaded'>(\n 'pending',\n )\n /** Bumped by Retry, and after every write, to re-run the load effect. */\n const [retryNonce, setRetryNonce] = useState(0)\n\n const sendRequest = useCallback(\n async (\n body: Record<string, unknown>,\n ): Promise<{ ok: boolean; payload?: any }> => {\n const response = await authorizedFetch(\n user,\n '/api/ai/billing/overage',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, ...body }),\n },\n )\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n // The route's own sentence, verbatim: its 409 explains why a plan\n // with no rate has nothing to switch, which is more specific than\n // anything this card could invent.\n enqueueSnackbar(payload?.error ?? 'AI assist setting request failed', {\n variant: 'warning',\n persist: false,\n })\n return { ok: false, payload }\n }\n return { ok: true, payload }\n },\n [user, orgId, enqueueSnackbar],\n )\n\n useEffect(() => {\n if (!orgId || !user) return\n let cancelled = false\n setLoadState('pending')\n sendRequest({ action: 'get' })\n .then((outcome) => {\n if (cancelled) return\n if (!outcome.ok) return void setLoadState('error')\n const next = outcome.payload as AssistOverageState\n // A card that cannot name the price must not quote one. The rate\n // appears in the sentence beside the switch whenever overage is\n // sold, and a defaulted 0 would tell a person about to leave the\n // switch off that the extra costs \"$0.00 per 1,000\". A payload that\n // says overage is sold but cannot say at what is a LOAD FAILURE.\n if (\n typeof next?.hardCap !== 'boolean' ||\n typeof next?.sellsOverage !== 'boolean' ||\n (next.sellsOverage &&\n (!Number.isFinite(Number(next.overageRateUsdPer1k)) ||\n !Number.isFinite(Number(next.bandCredits))))\n ) {\n return void setLoadState('error')\n }\n // The ceiling half of the payload is read strictly, the way the\n // resolver reads the stored field: anything that is not a finite\n // number is NO ceiling, never a ceiling of `undefined` that renders\n // as \"$NaN\". The label and bounds fall back to the same constants the\n // route serves them from.\n const capUsd =\n typeof next.capUsd === 'number' && Number.isFinite(next.capUsd)\n ? next.capUsd\n : null\n setState({\n ...next,\n capUsd,\n capLabel:\n typeof next.capLabel === 'string' && next.capLabel\n ? next.capLabel\n : ASSIST_OVERAGE_CAP_CONTROL_LABEL,\n minCapUsd: Number.isFinite(Number(next.minCapUsd))\n ? Number(next.minCapUsd)\n : ASSIST_OVERAGE_CAP_MIN_USD,\n maxCapUsd: Number.isFinite(Number(next.maxCapUsd))\n ? Number(next.maxCapUsd)\n : ASSIST_OVERAGE_CAP_MAX_USD,\n })\n setCapInput(capUsd === null ? '' : String(capUsd))\n setLoadState('loaded')\n })\n // A rejected fetch — offline, a wedged route — never reached the\n // snackbar inside the request, so this is the only place it can become\n // something the card renders.\n .catch(() => {\n if (!cancelled) setLoadState('error')\n })\n return () => {\n cancelled = true\n }\n }, [orgId, user, sendRequest, retryNonce])\n\n const setHardCap = useCallback(\n async (hardCap: boolean) => {\n const rate = state?.overageRateUsdPer1k\n if (!hardCap) {\n // The loading overlay must drop BEFORE the confirm dialog opens — it\n // sits above the dialog and swallows the Confirm click (AGL-535). So\n // the confirm runs first and `queueLoading` only wraps the write.\n const accepted = await confirm({\n title: 'Keep AI assist going past your included credits?',\n description:\n 'The assistant will keep answering once your included credits ' +\n 'are used, and the extra credits will be billed on your monthly ' +\n `invoice${\n rate != null ? ` at $${rate.toFixed(2)} per 1,000` : ''\n }. You can turn the stop back on at any time.`,\n confirmationText: 'Keep going',\n })\n .then(() => true)\n .catch(() => false)\n if (!accepted) return\n }\n\n setBusy(true)\n const dequeue = queueLoading()\n try {\n const outcome = await sendRequest({ action: 'setHardCap', hardCap })\n if (!outcome.ok) return\n enqueueSnackbar(\n hardCap\n ? 'AI assist will stop at your included credits. Nothing past ' +\n 'the band is billed.'\n : 'AI assist keeps going past your included credits' +\n (rate != null\n ? `, billed at $${rate.toFixed(2)} per 1,000.`\n : '.'),\n { variant: 'success', persist: false },\n )\n setRetryNonce((nonce) => nonce + 1)\n } catch {\n enqueueSnackbar('Could not update your AI assist setting', {\n variant: 'warning',\n persist: false,\n })\n } finally {\n dequeue()\n setBusy(false)\n }\n },\n [state, confirm, queueLoading, sendRequest, enqueueSnackbar],\n )\n\n /**\n * Set or clear the ceiling. Neither direction asks first: setting one\n * only ever lowers a bill, and clearing one returns the org to the\n * open-ended overage the switch already told it about. The route owns the\n * range check, so a figure it refuses comes back as its own sentence.\n */\n const setCap = useCallback(\n async (capUsd: number | null) => {\n setBusy(true)\n const dequeue = queueLoading()\n try {\n const outcome = await sendRequest({ action: 'setCap', capUsd })\n if (!outcome.ok) return\n enqueueSnackbar(\n capUsd === null\n ? 'AI assist overage ceiling removed. Extra credits keep billing ' +\n 'at your plan’s rate with no monthly stop.'\n : `AI assist will stop once this month’s overage reaches ` +\n `$${capUsd.toFixed(2)}.`,\n { variant: 'success', persist: false },\n )\n setRetryNonce((nonce) => nonce + 1)\n } catch {\n enqueueSnackbar('Could not update your AI assist ceiling', {\n variant: 'warning',\n persist: false,\n })\n } finally {\n dequeue()\n setBusy(false)\n }\n },\n [queueLoading, sendRequest, enqueueSnackbar],\n )\n\n if (loadState === 'pending') {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Loading your AI assist settings…\n </Typography>\n )\n }\n\n if (loadState === 'error' || !state) {\n return (\n <Alert\n severity=\"warning\"\n action={\n <Button\n size=\"small\"\n color=\"inherit\"\n onClick={() => setRetryNonce((nonce) => nonce + 1)}\n >\n Retry\n </Button>\n }\n >\n We couldn’t load your AI assist settings. Nothing has changed.\n </Alert>\n )\n }\n\n const {\n hardCap,\n capUsd,\n bandCredits,\n overageRateUsdPer1k,\n sellsOverage,\n label,\n capLabel,\n minCapUsd,\n maxCapUsd,\n } = state\n const band = bandCredits == null ? null : Math.round(bandCredits)\n const rate = overageRateUsdPer1k == null ? null : overageRateUsdPer1k.toFixed(2)\n // The ceiling is a bound on overage, and overage exists only while the\n // switch is off on a plan that sells past its band. It stays reachable to\n // CLEAR whenever one is set, so an org that turned the switch on afterwards\n // is not left carrying a figure it cannot remove.\n const offersCeiling = sellsOverage && !hardCap\n const showsCeiling = offersCeiling || capUsd !== null\n\n // A plan that sells no overage has nothing for the switch to stop, so\n // there is nothing to configure. Say that plainly rather than rendering a\n // switch the route would refuse with a 409 — but keep the switch reachable\n // for an org that turned it on and then moved to a plan that sells none,\n // so it can turn it off.\n const nothingToStop = !sellsOverage && !hardCap\n\n return (\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', gap: 1 }}>\n <Chip\n label={\n hardCap\n ? 'Stops at the included band'\n : nothingToStop\n ? band === null\n ? 'No AI assist credits'\n : 'Included credits only'\n : capUsd !== null\n ? `Stops at $${capUsd.toFixed(2)} of overage`\n : 'No stop — extra credits bill'\n }\n size=\"small\"\n color={hardCap || capUsd !== null ? 'primary' : 'default'}\n />\n {band !== null ? (\n <Chip label={`${band.toLocaleString()} credits/mo included`} size=\"small\" />\n ) : null}\n </Stack>\n\n {nothingToStop && band === null ? (\n // No band at all, and nothing ever billed for AI, so neither stop\n // applies — the switch has no band to stop at and the ceiling has no\n // overage to bound. Since AGL-3203 every plan row carries a band, so\n // this is an org whose band was overridden to zero.\n <Alert severity=\"info\">\n Your plan includes no AI credits and is never charged for any, so\n there is no band to stop at, no overage to put a dollar ceiling on,\n and nothing to configure here. Upgrade above, or add{' '}\n {aiAddonName()} on a paid plan, to get a band.\n </Alert>\n ) : nothingToStop ? (\n // Enterprise: a band, and no rate to sell past it at. The assistant\n // stops at the band on its own and nothing is ever billed for it.\n <Alert severity=\"info\">\n Your plan includes <strong>{band?.toLocaleString()}</strong> AI assist\n credits a month and sells none past them: the assistant stops at the\n included band and you are <strong>never charged</strong> for AI\n assist, so there is nothing to switch.\n </Alert>\n ) : (\n <>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {sellsOverage\n ? `Your plan includes ${band?.toLocaleString()} AI assist credits ` +\n `a month. Past that, the assistant keeps answering and the ` +\n `extra credits are billed at $${rate} per 1,000 on your ` +\n `monthly invoice.`\n : `Your plan no longer sells AI assist credits past its band, ` +\n `so this switch is not doing anything. You can safely turn ` +\n `it off.`}\n </Typography>\n {sellsOverage ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n If you would rather the assistant stopped at the included band,\n turn this on. This is optional — most people leave it off.\n </Typography>\n ) : null}\n <Stack spacing={0.5}>\n <FormControlLabel\n control={\n <Switch\n checked={hardCap}\n disabled={!canManage || busy || (!hardCap && !sellsOverage)}\n onChange={(event) => void setHardCap(event.target.checked)}\n />\n }\n label={label}\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {hardCap\n ? `On: the assistant stops once your included credits are ` +\n `used, and you are never billed for AI assist.` +\n (sellsOverage\n ? ` Turn it off to keep going at $${rate} per 1,000 credits.`\n : '')\n : `Off: extra credits past the included band are billed at ` +\n `$${rate} per 1,000.`}\n </Typography>\n </Stack>\n\n {showsCeiling ? (\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {offersCeiling\n ? capUsd === null\n ? 'Or keep going, but only so far: set a monthly ceiling ' +\n 'on the extra credits you will pay for. The assistant ' +\n 'stops for the rest of the month once this month’s ' +\n 'overage reaches it. Optional, and separate from the ' +\n 'switch above.'\n : `The assistant stops for the rest of the month once ` +\n `this month’s overage reaches $${capUsd.toFixed(2)}. ` +\n `Raise, lower or remove the ceiling at any time.`\n : `A ceiling of $${(capUsd ?? 0).toFixed(2)} is set, but ` +\n `with the assistant stopping at the included band there ` +\n `is no overage for it to bound. You can safely remove it.`}\n </Typography>\n <Stack\n direction=\"row\"\n spacing={2}\n sx={{ alignItems: 'flex-start', flexWrap: 'wrap', rowGap: 2 }}\n >\n {offersCeiling ? (\n <>\n <TextField\n label={capLabel}\n type=\"number\"\n size=\"small\"\n value={capInput}\n disabled={!canManage || busy}\n onChange={(event) => setCapInput(event.target.value)}\n slotProps={{\n htmlInput: { min: minCapUsd, max: maxCapUsd, step: 1 },\n }}\n helperText={`In USD, between $${minCapUsd} and $${maxCapUsd.toLocaleString()}.`}\n />\n <Button\n variant=\"outlined\"\n disabled={!canManage || busy || capInput.trim() === ''}\n onClick={() => void setCap(Number(capInput))}\n >\n {capUsd === null ? 'Set a monthly ceiling' : 'Save ceiling'}\n </Button>\n </>\n ) : null}\n {capUsd !== null ? (\n <Button\n variant=\"text\"\n color=\"warning\"\n disabled={!canManage || busy}\n onClick={() => void setCap(null)}\n >\n Remove ceiling\n </Button>\n ) : null}\n </Stack>\n </Stack>\n ) : null}\n </>\n )}\n\n {!canManage ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n You need the Manage billing permission to change this.\n </Typography>\n ) : null}\n </Stack>\n )\n}\n"],"names":["CardDisplay","useConfirmationContext","useLoading","pluginDocsHelp","useSnackbar","Alert","Button","Chip","FormControlLabel","Stack","Switch","TextField","Typography","useCallback","useEffect","useState","useUser","authorizedFetch","aiAddonName","ASSIST_OVERAGE_CAP_CONTROL_LABEL","ASSIST_OVERAGE_CAP_MAX_USD","ASSIST_OVERAGE_CAP_MIN_USD","BillingAssistOverageCard","props","div","id","header","subheader","help","anchor","excerpt","contentGutterX","contentGutterY","BillingAssistOverageControls","orgId","canManage","data","user","enqueueSnackbar","queueLoading","confirm","state","setState","busy","setBusy","capInput","setCapInput","loadState","setLoadState","retryNonce","setRetryNonce","sendRequest","body","response","method","headers","JSON","stringify","payload","json","catch","ok","error","variant","persist","cancelled","action","then","outcome","next","hardCap","sellsOverage","Number","isFinite","overageRateUsdPer1k","bandCredits","capUsd","capLabel","minCapUsd","maxCapUsd","String","setHardCap","rate","accepted","title","description","toFixed","confirmationText","dequeue","nonce","setCap","color","severity","size","onClick","label","band","Math","round","offersCeiling","showsCeiling","nothingToStop","spacing","direction","sx","flexWrap","gap","toLocaleString","strong","control","checked","disabled","onChange","event","target","alignItems","rowGap","type","value","slotProps","htmlInput","min","max","step","helperText","trim"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/billing-assist-overage-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\n'use client'\n\nimport { CardDisplay, useConfirmationContext, useLoading } from '@aglyn/shared-ui-jsx'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Chip,\n FormControlLabel,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { aiAddonName } from '@aglyn/aglyn'\nimport {\n ASSIST_OVERAGE_CAP_CONTROL_LABEL,\n ASSIST_OVERAGE_CAP_MAX_USD,\n ASSIST_OVERAGE_CAP_MIN_USD,\n} from '../usage/assist-credits'\n\n/** The `get` payload of `/api/billing/assist-overage`. */\ninterface AssistOverageState {\n /** Whether the org asked to be stopped at its band. Absent by default. */\n hardCap: boolean\n /** The org's ceiling on this month's overage in USD, or `null` for none. */\n capUsd: number | null\n /** The plan's included credits a month, or `null` where it sells none. */\n bandCredits: number | null\n /** The per-1,000 rate credits past the band bill at, or `null`. */\n overageRateUsdPer1k: number | null\n /** True when there is a band AND a rate to sell past it at. */\n sellsOverage: boolean\n /** The switch's label, served so the refusal and the switch share it. */\n label: string\n /** The ceiling's label, served for the same reason. */\n capLabel: string\n /** The range the route accepts for the ceiling. */\n minCapUsd: number\n maxCapUsd: number\n}\n\nexport interface BillingAssistOverageCardProps {\n orgId?: string | null\n /** billing.manage: the switch acts; view-only otherwise. */\n canManage: boolean\n}\n\n/**\n * The org's own controls on AI assist spend: the hard cap at the band\n * (AGL-2653) and the dollar ceiling on overage (AGL-2898).\n *\n * ## What this card is\n *\n * Credits past a plan's included assist band are SOLD by default, at the\n * plan's per-1,000 rate, the way storage past its band bills by default. This\n * card offers the two controls a customer has over that: a switch that makes\n * the band a wall, so the assistant refuses there and nothing past it is ever\n * billed; and, with the switch off, a monthly ceiling in dollars on the\n * overage the org will buy before the assistant stops. Both are optional and\n * unset unless chosen — the storage cap's siblings, for the same reason a\n * ceiling on metered spend is the END USER's control, offered rather than\n * imposed.\n *\n * ## Why a switch AND a number\n *\n * The switch answers \"sell past the band at all?\" and needs no figure: the\n * band is the plan's, and a second band here would drift from it. The\n * ceiling answers \"and if so, how much?\", which is a figure only the customer\n * can name — it is a bound on THEIR invoice, not a quantity the plan sold.\n * The ceiling is offered only while the switch is off, because past a wall\n * there is no overage to bound.\n *\n * ## Why the switch is edited here and enforced there\n *\n * The route owns every refusal: a plan that sells no overage, a value that\n * is not a boolean, a missing permission. This card sends the boolean and\n * renders the route's own answer rather than pre-validating against constants\n * that would then be a second source of truth. The rate it quotes arrives on\n * the same payload, so what the card says the overage costs is what the\n * invoice bills.\n *\n * ## Why turning it OFF is confirmed and turning it ON is not\n *\n * Off is the direction that can raise a bill, so it asks; on never raises\n * one, so it is a plain click. Off is also always available, on any plan and\n * from any state — an org that wants the sale back must not have to argue\n * with a precondition.\n */\n/**\n * The card as the Billing → Usage page's plugin band mounts it (AGL-2939):\n * the overage controls under the heading and help the page used to draw\n * around them.\n */\nexport default function BillingAssistOverageCard(props: BillingAssistOverageCardProps) {\n return (\n <div id=\"assist-overage\">\n <CardDisplay\n header={'AI credits overage'}\n subheader={\n 'Extra AI credits past your included band are billed on your ' +\n 'monthly invoice. Stop at the band, or stop once the overage ' +\n 'reaches an amount you choose.'\n }\n help={pluginDocsHelp('billing', {\n anchor: '#assist-overage',\n excerpt:\n 'On a paid plan the assistant keeps answering past your ' +\n 'included credits and the extra is billed at your plan\\'s ' +\n 'per-1,000 rate, unless you switch on the stop at the included band.',\n })}\n contentGutterX\n contentGutterY\n >\n <BillingAssistOverageControls {...props} />\n </CardDisplay>\n </div>\n )\n}\n\nexport function BillingAssistOverageControls({\n orgId,\n canManage,\n}: BillingAssistOverageCardProps) {\n const { data: user } = useUser()\n const { enqueueSnackbar } = useSnackbar()\n const { queueLoading } = useLoading()\n const { confirm } = useConfirmationContext()\n const [state, setState] = useState<AssistOverageState | null>(null)\n const [busy, setBusy] = useState(false)\n /** The ceiling field, as typed; the route validates, this only carries. */\n const [capInput, setCapInput] = useState('')\n /**\n * Three outcomes, not two (the AGL-1380 rule). \"The assistant keeps going\n * past your band\" is a claim about this org's billing settings; rendering\n * it because a request FAILED would tell an org that asked to be stopped\n * that it had not.\n */\n const [loadState, setLoadState] = useState<'pending' | 'error' | 'loaded'>(\n 'pending',\n )\n /** Bumped by Retry, and after every write, to re-run the load effect. */\n const [retryNonce, setRetryNonce] = useState(0)\n\n const sendRequest = useCallback(\n async (\n body: Record<string, unknown>,\n ): Promise<{ ok: boolean; payload?: any }> => {\n const response = await authorizedFetch(\n user,\n '/api/ai/billing/overage',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, ...body }),\n },\n )\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n // The route's own sentence, verbatim: its 409 explains why a plan\n // with no rate has nothing to switch, which is more specific than\n // anything this card could invent.\n enqueueSnackbar(payload?.error ?? 'AI assist setting request failed', {\n variant: 'warning',\n persist: false,\n })\n return { ok: false, payload }\n }\n return { ok: true, payload }\n },\n [user, orgId, enqueueSnackbar],\n )\n\n useEffect(() => {\n if (!orgId || !user) return\n let cancelled = false\n setLoadState('pending')\n sendRequest({ action: 'get' })\n .then((outcome) => {\n if (cancelled) return\n if (!outcome.ok) return void setLoadState('error')\n const next = outcome.payload as AssistOverageState\n // A card that cannot name the price must not quote one. The rate\n // appears in the sentence beside the switch whenever overage is\n // sold, and a defaulted 0 would tell a person about to leave the\n // switch off that the extra costs \"$0.00 per 1,000\". A payload that\n // says overage is sold but cannot say at what is a LOAD FAILURE.\n if (\n typeof next?.hardCap !== 'boolean' ||\n typeof next?.sellsOverage !== 'boolean' ||\n (next.sellsOverage &&\n (!Number.isFinite(Number(next.overageRateUsdPer1k)) ||\n !Number.isFinite(Number(next.bandCredits))))\n ) {\n return void setLoadState('error')\n }\n // The ceiling half of the payload is read strictly, the way the\n // resolver reads the stored field: anything that is not a finite\n // number is NO ceiling, never a ceiling of `undefined` that renders\n // as \"$NaN\". The label and bounds fall back to the same constants the\n // route serves them from.\n const capUsd =\n typeof next.capUsd === 'number' && Number.isFinite(next.capUsd)\n ? next.capUsd\n : null\n setState({\n ...next,\n capUsd,\n capLabel:\n typeof next.capLabel === 'string' && next.capLabel\n ? next.capLabel\n : ASSIST_OVERAGE_CAP_CONTROL_LABEL,\n minCapUsd: Number.isFinite(Number(next.minCapUsd))\n ? Number(next.minCapUsd)\n : ASSIST_OVERAGE_CAP_MIN_USD,\n maxCapUsd: Number.isFinite(Number(next.maxCapUsd))\n ? Number(next.maxCapUsd)\n : ASSIST_OVERAGE_CAP_MAX_USD,\n })\n setCapInput(capUsd === null ? '' : String(capUsd))\n setLoadState('loaded')\n })\n // A rejected fetch — offline, a wedged route — never reached the\n // snackbar inside the request, so this is the only place it can become\n // something the card renders.\n .catch(() => {\n if (!cancelled) setLoadState('error')\n })\n return () => {\n cancelled = true\n }\n }, [orgId, user, sendRequest, retryNonce])\n\n const setHardCap = useCallback(\n async (hardCap: boolean) => {\n const rate = state?.overageRateUsdPer1k\n if (!hardCap) {\n // The loading overlay must drop BEFORE the confirm dialog opens — it\n // sits above the dialog and swallows the Confirm click (AGL-535). So\n // the confirm runs first and `queueLoading` only wraps the write.\n const accepted = await confirm({\n title: 'Keep AI assist going past your included credits?',\n description:\n 'The assistant will keep answering once your included credits ' +\n 'are used, and the extra credits will be billed on your monthly ' +\n `invoice${\n rate != null ? ` at $${rate.toFixed(2)} per 1,000` : ''\n }. You can turn the stop back on at any time.`,\n confirmationText: 'Keep going',\n })\n .then(() => true)\n .catch(() => false)\n if (!accepted) return\n }\n\n setBusy(true)\n const dequeue = queueLoading()\n try {\n const outcome = await sendRequest({ action: 'setHardCap', hardCap })\n if (!outcome.ok) return\n enqueueSnackbar(\n hardCap\n ? 'AI assist will stop at your included credits. Nothing past ' +\n 'the band is billed.'\n : 'AI assist keeps going past your included credits' +\n (rate != null\n ? `, billed at $${rate.toFixed(2)} per 1,000.`\n : '.'),\n { variant: 'success', persist: false },\n )\n setRetryNonce((nonce) => nonce + 1)\n } catch {\n enqueueSnackbar('Could not update your AI assist setting', {\n variant: 'warning',\n persist: false,\n })\n } finally {\n dequeue()\n setBusy(false)\n }\n },\n [state, confirm, queueLoading, sendRequest, enqueueSnackbar],\n )\n\n /**\n * Set or clear the ceiling. Neither direction asks first: setting one\n * only ever lowers a bill, and clearing one returns the org to the\n * open-ended overage the switch already told it about. The route owns the\n * range check, so a figure it refuses comes back as its own sentence.\n */\n const setCap = useCallback(\n async (capUsd: number | null) => {\n setBusy(true)\n const dequeue = queueLoading()\n try {\n const outcome = await sendRequest({ action: 'setCap', capUsd })\n if (!outcome.ok) return\n enqueueSnackbar(\n capUsd === null\n ? 'AI assist overage ceiling removed. Extra credits keep billing ' +\n 'at your plan’s rate with no monthly stop.'\n : `AI assist will stop once this month’s overage reaches ` +\n `$${capUsd.toFixed(2)}.`,\n { variant: 'success', persist: false },\n )\n setRetryNonce((nonce) => nonce + 1)\n } catch {\n enqueueSnackbar('Could not update your AI assist ceiling', {\n variant: 'warning',\n persist: false,\n })\n } finally {\n dequeue()\n setBusy(false)\n }\n },\n [queueLoading, sendRequest, enqueueSnackbar],\n )\n\n if (loadState === 'pending') {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Loading your AI assist settings…\n </Typography>\n )\n }\n\n if (loadState === 'error' || !state) {\n return (\n <Alert\n severity=\"warning\"\n action={\n <Button\n size=\"small\"\n color=\"inherit\"\n onClick={() => setRetryNonce((nonce) => nonce + 1)}\n >\n Retry\n </Button>\n }\n >\n We couldn’t load your AI assist settings. Nothing has changed.\n </Alert>\n )\n }\n\n const {\n hardCap,\n capUsd,\n bandCredits,\n overageRateUsdPer1k,\n sellsOverage,\n label,\n capLabel,\n minCapUsd,\n maxCapUsd,\n } = state\n const band = bandCredits == null ? null : Math.round(bandCredits)\n const rate = overageRateUsdPer1k == null ? null : overageRateUsdPer1k.toFixed(2)\n // The ceiling is a bound on overage, and overage exists only while the\n // switch is off on a plan that sells past its band. It stays reachable to\n // CLEAR whenever one is set, so an org that turned the switch on afterwards\n // is not left carrying a figure it cannot remove.\n const offersCeiling = sellsOverage && !hardCap\n const showsCeiling = offersCeiling || capUsd !== null\n\n // A plan that sells no overage has nothing for the switch to stop, so\n // there is nothing to configure. Say that plainly rather than rendering a\n // switch the route would refuse with a 409 — but keep the switch reachable\n // for an org that turned it on and then moved to a plan that sells none,\n // so it can turn it off.\n const nothingToStop = !sellsOverage && !hardCap\n\n return (\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', gap: 1 }}>\n <Chip\n label={\n hardCap\n ? 'Stops at the included band'\n : nothingToStop\n ? band === null\n ? 'No AI assist credits'\n : 'Included credits only'\n : capUsd !== null\n ? `Stops at $${capUsd.toFixed(2)} of overage`\n : 'No stop — extra credits bill'\n }\n size=\"small\"\n color={hardCap || capUsd !== null ? 'primary' : 'default'}\n />\n {band !== null ? (\n <Chip label={`${band.toLocaleString()} credits/mo included`} size=\"small\" />\n ) : null}\n </Stack>\n\n {nothingToStop && band === null ? (\n // No band at all, and nothing ever billed for AI, so neither stop\n // applies — the switch has no band to stop at and the ceiling has no\n // overage to bound. Since AGL-3203 every plan row carries a band, so\n // this is an org whose band was overridden to zero.\n <Alert severity=\"info\">\n Your plan includes no AI credits and is never charged for any, so\n there is no band to stop at, no overage to put a dollar ceiling on,\n and nothing to configure here. Upgrade above, or add{' '}\n {aiAddonName()} on a paid plan, to get a band.\n </Alert>\n ) : nothingToStop ? (\n // Enterprise: a band, and no rate to sell past it at. The assistant\n // stops at the band on its own and nothing is ever billed for it.\n <Alert severity=\"info\">\n Your plan includes <strong>{band?.toLocaleString()}</strong> AI assist\n credits a month and sells none past them: the assistant stops at the\n included band and you are <strong>never charged</strong> for AI\n assist, so there is nothing to switch.\n </Alert>\n ) : (\n <>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {sellsOverage\n ? `Your plan includes ${band?.toLocaleString()} AI assist credits ` +\n `a month. Past that, the assistant keeps answering and the ` +\n `extra credits are billed at $${rate} per 1,000 on your ` +\n `monthly invoice.`\n : `Your plan no longer sells AI assist credits past its band, ` +\n `so this switch is not doing anything. You can safely turn ` +\n `it off.`}\n </Typography>\n {sellsOverage ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n If you would rather the assistant stopped at the included band,\n turn this on. This is optional — most people leave it off.\n </Typography>\n ) : null}\n <Stack spacing={0.5}>\n <FormControlLabel\n control={\n <Switch\n checked={hardCap}\n disabled={!canManage || busy || (!hardCap && !sellsOverage)}\n onChange={(event) => void setHardCap(event.target.checked)}\n />\n }\n label={label}\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {hardCap\n ? `On: the assistant stops once your included credits are ` +\n `used, and you are never billed for AI assist.` +\n (sellsOverage\n ? ` Turn it off to keep going at $${rate} per 1,000 credits.`\n : '')\n : `Off: extra credits past the included band are billed at ` +\n `$${rate} per 1,000.`}\n </Typography>\n </Stack>\n\n {showsCeiling ? (\n <Stack spacing={1}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {offersCeiling\n ? capUsd === null\n ? 'Or keep going, but only so far: set a monthly ceiling ' +\n 'on the extra credits you will pay for. The assistant ' +\n 'stops for the rest of the month once this month’s ' +\n 'overage reaches it. Optional, and separate from the ' +\n 'switch above.'\n : `The assistant stops for the rest of the month once ` +\n `this month’s overage reaches $${capUsd.toFixed(2)}. ` +\n `Raise, lower or remove the ceiling at any time.`\n : `A ceiling of $${(capUsd ?? 0).toFixed(2)} is set, but ` +\n `with the assistant stopping at the included band there ` +\n `is no overage for it to bound. You can safely remove it.`}\n </Typography>\n <Stack\n direction=\"row\"\n spacing={2}\n sx={{ alignItems: 'flex-start', flexWrap: 'wrap', rowGap: 2 }}\n >\n {offersCeiling ? (\n <>\n <TextField\n label={capLabel}\n type=\"number\"\n size=\"small\"\n value={capInput}\n disabled={!canManage || busy}\n onChange={(event) => setCapInput(event.target.value)}\n slotProps={{\n htmlInput: { min: minCapUsd, max: maxCapUsd, step: 1 },\n }}\n helperText={`In USD, between $${minCapUsd} and $${maxCapUsd.toLocaleString()}.`}\n />\n <Button\n variant=\"outlined\"\n disabled={!canManage || busy || capInput.trim() === ''}\n onClick={() => void setCap(Number(capInput))}\n >\n {capUsd === null ? 'Set a monthly ceiling' : 'Save ceiling'}\n </Button>\n </>\n ) : null}\n {capUsd !== null ? (\n <Button\n variant=\"text\"\n color=\"warning\"\n disabled={!canManage || busy}\n onClick={() => void setCap(null)}\n >\n Remove ceiling\n </Button>\n ) : null}\n </Stack>\n </Stack>\n ) : null}\n </>\n )}\n\n {!canManage ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n You need the Manage billing permission to change this.\n </Typography>\n ) : null}\n </Stack>\n )\n}\n"],"names":["CardDisplay","useConfirmationContext","useLoading","pluginDocsHelp","useSnackbar","Alert","Button","Chip","FormControlLabel","Stack","Switch","TextField","Typography","useCallback","useEffect","useState","useUser","authorizedFetch","aiAddonName","ASSIST_OVERAGE_CAP_CONTROL_LABEL","ASSIST_OVERAGE_CAP_MAX_USD","ASSIST_OVERAGE_CAP_MIN_USD","BillingAssistOverageCard","props","div","id","header","subheader","help","anchor","excerpt","contentGutterX","contentGutterY","BillingAssistOverageControls","orgId","canManage","data","user","enqueueSnackbar","queueLoading","confirm","state","setState","busy","setBusy","capInput","setCapInput","loadState","setLoadState","retryNonce","setRetryNonce","sendRequest","body","response","method","headers","JSON","stringify","payload","json","catch","ok","error","variant","persist","cancelled","action","then","outcome","next","hardCap","sellsOverage","Number","isFinite","overageRateUsdPer1k","bandCredits","capUsd","capLabel","minCapUsd","maxCapUsd","String","setHardCap","rate","accepted","title","description","toFixed","confirmationText","dequeue","nonce","setCap","color","severity","size","onClick","label","band","Math","round","offersCeiling","showsCeiling","nothingToStop","spacing","direction","sx","flexWrap","gap","toLocaleString","strong","control","checked","disabled","onChange","event","target","alignItems","rowGap","type","value","slotProps","htmlInput","min","max","step","helperText","trim"],"mappings":"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"}
|
package/src/lib/constants.d.ts
CHANGED
|
@@ -22,3 +22,8 @@
|
|
|
22
22
|
export declare const AI_PLUGIN_ID = "ai";
|
|
23
23
|
/** The `pluginSettings/ai` document's schema id — the same string. */
|
|
24
24
|
export declare const AI_PLUGIN_SETTINGS_ID = "ai";
|
|
25
|
+
/**
|
|
26
|
+
* The plugin's meter in the monthly usage sweep, as its `usageAxes`
|
|
27
|
+
* declaration names it and its server declarations register it.
|
|
28
|
+
*/
|
|
29
|
+
export declare const AI_USAGE_METER_ID = "assist";
|
package/src/lib/constants.js
CHANGED
|
@@ -19,5 +19,9 @@
|
|
|
19
19
|
* `plugins.config.json` and `FIRST_PARTY_PLUGINS`.
|
|
20
20
|
*/ export const AI_PLUGIN_ID = 'ai';
|
|
21
21
|
/** The `pluginSettings/ai` document's schema id — the same string. */ export const AI_PLUGIN_SETTINGS_ID = AI_PLUGIN_ID;
|
|
22
|
+
/**
|
|
23
|
+
* The plugin's meter in the monthly usage sweep, as its `usageAxes`
|
|
24
|
+
* declaration names it and its server declarations register it.
|
|
25
|
+
*/ export const AI_USAGE_METER_ID = 'assist';
|
|
22
26
|
|
|
23
27
|
//# sourceMappingURL=constants.js.map
|
package/src/lib/constants.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/constants.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The plugin's stable id — persisted in `org.enabledPlugins`, the document\n * id under `pluginSettings`, and the owner every registry records. Matches\n * `plugins.config.json` and `FIRST_PARTY_PLUGINS`.\n */\nexport const AI_PLUGIN_ID = 'ai'\n\n/** The `pluginSettings/ai` document's schema id — the same string. */\nexport const AI_PLUGIN_SETTINGS_ID = AI_PLUGIN_ID\n"],"names":["AI_PLUGIN_ID","AI_PLUGIN_SETTINGS_ID"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;CAIC,GACD,OAAO,MAAMA,eAAe,KAAI;AAEhC,oEAAoE,GACpE,OAAO,MAAMC,wBAAwBD,aAAY"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/constants.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * The plugin's stable id — persisted in `org.enabledPlugins`, the document\n * id under `pluginSettings`, and the owner every registry records. Matches\n * `plugins.config.json` and `FIRST_PARTY_PLUGINS`.\n */\nexport const AI_PLUGIN_ID = 'ai'\n\n/** The `pluginSettings/ai` document's schema id — the same string. */\nexport const AI_PLUGIN_SETTINGS_ID = AI_PLUGIN_ID\n\n/**\n * The plugin's meter in the monthly usage sweep, as its `usageAxes`\n * declaration names it and its server declarations register it.\n */\nexport const AI_USAGE_METER_ID = 'assist'\n"],"names":["AI_PLUGIN_ID","AI_PLUGIN_SETTINGS_ID","AI_USAGE_METER_ID"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;CAIC,GACD,OAAO,MAAMA,eAAe,KAAI;AAEhC,oEAAoE,GACpE,OAAO,MAAMC,wBAAwBD,aAAY;AAEjD;;;CAGC,GACD,OAAO,MAAME,oBAAoB,SAAQ"}
|
package/src/lib/declarations.js
CHANGED
|
@@ -74,6 +74,7 @@ import { registerAiActivityActions } from "./activity/ai-activity-actions.js";
|
|
|
74
74
|
{
|
|
75
75
|
key: 'ai-assist',
|
|
76
76
|
label: 'AI assist',
|
|
77
|
+
customerName: 'AI assist',
|
|
77
78
|
staffBypass: true,
|
|
78
79
|
notice: {
|
|
79
80
|
title: 'AI assist is temporarily unavailable',
|
|
@@ -91,6 +92,7 @@ import { registerAiActivityActions } from "./activity/ai-activity-actions.js";
|
|
|
91
92
|
{
|
|
92
93
|
key: 'ai-generate',
|
|
93
94
|
label: 'AI generation',
|
|
95
|
+
customerName: 'AI generation',
|
|
94
96
|
staffBypass: true,
|
|
95
97
|
notice: {
|
|
96
98
|
title: 'AI generation is temporarily unavailable',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/declarations.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Named modules rather than the `@aglyn/aglyn` barrel: both apps load this\n// on the server at boot, and the full barrel carries client-only contexts\n// (AGL-405).\nimport { AI_ADDON_CREDITS_PER_MONTH } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { registerPluginConfigSchema } from '@aglyn/aglyn/plugin-manager/plugin-config'\nimport {\n registerPluginEntitlements,\n type PluginEntitlementRegistration,\n} from '@aglyn/aglyn/plugin-manager/plugin-entitlements'\nimport { AI_PLUGIN_ID } from './constants'\nimport { AI_ORG_PERMISSIONS } from './model/ai-permissions'\nimport { AI_CONFIG_SCHEMA } from './plugin-config'\nimport { registerAiActivityActions } from './activity/ai-activity-actions'\n\n/**\n * The plugin's DECLARATIONS (AGL-2939): what core must know about this\n * plugin before any of its surfaces load — its billing and access keys,\n * the AI permission keys it adds to the org catalog (AGL-2984), its\n * activity codes, its settings schema. Loaded eagerly by both apps\n * through the generated declarations manifest, at boot on the server and\n * with the plugin loader on the client, so a core billing route folds the\n * add-on and the staff lockdown page lists the levers whether or not a\n * request has touched the plugin yet. Light by construction: nothing here\n * reaches a provider, Firestore or React.\n *\n * The plan tables keep the per-plan defaults for `aiGenerative` and\n * `aiAssist`, and `PLAN_PRICING` keeps the add-on's price, so this names\n * keys and bands and restates no price.\n *\n * `ai-assist` and `ai-generate` are separate levers because the two doors\n * spend at different rates and an incident on one need not stop the other\n * (AGL-2903). Both grant the staff bypass: a provider incident is verified\n * recovered by staff making one real call, not by lifting the lock and\n * watching customers find out.\n */\nexport const AI_PLUGIN_ENTITLEMENTS: PluginEntitlementRegistration = {\n pluginId: AI_PLUGIN_ID,\n seatAddons: [\n {\n key: 'aiAddon',\n label: 'AI add-on',\n maxUnits: 1,\n quota: {\n key: 'assistCreditsPerMonth',\n perUnitByPlan: AI_ADDON_CREDITS_PER_MONTH,\n },\n features: ['aiGenerative', 'aiAssist'],\n },\n ],\n features: [\n { key: 'aiAssist', label: 'AI assist' },\n { key: 'aiGenerative', label: 'AI generation' },\n ],\n lockdownFeatures: [\n {\n key: 'ai-assist',\n label: 'AI assist',\n staffBypass: true,\n notice: {\n title: 'AI assist is temporarily unavailable',\n body: 'AI assist is temporarily unavailable. Your content is unaffected — please try again shortly.',\n },\n // Gated even while the route 501s without a provider key — the switch\n // predates the key on purpose.\n apiPaths: { exact: ['ai/assist', 'assist/chat'] },\n },\n {\n key: 'ai-generate',\n label: 'AI generation',\n staffBypass: true,\n notice: {\n title: 'AI generation is temporarily unavailable',\n body: 'Generating sections, pages and automations with AI is temporarily unavailable. Everything already built is unaffected — please try again shortly.',\n },\n // `ai/seo` (AGL-2910) applies a finished audit's drafts; it spends\n // nothing, and it is still a generative door the switch stops.\n apiPaths: { prefixes: ['ai/generate', 'ai/jobs', 'ai/seo'] },\n },\n ],\n orgPermissions: AI_ORG_PERMISSIONS,\n}\n\nlet declared = false\n\n/**\n * Registers everything above, and the activity codes. Idempotent: the\n * declarations manifest runs it once per process, and a surface's register\n * fn may call it again. The codes are a call rather than an import made\n * for what the module does as it loads, because the browser's manifest\n * reaches that module through nothing else, and a bundler honoring this\n * package's `sideEffects` deletes such an import (AGL-3025).\n */\nexport function registerAiDeclarations(): void {\n if (declared) return\n declared = true\n registerPluginEntitlements(AI_PLUGIN_ENTITLEMENTS)\n registerPluginConfigSchema(AI_CONFIG_SCHEMA)\n registerAiActivityActions()\n}\n\nregisterAiDeclarations()\n"],"names":["AI_ADDON_CREDITS_PER_MONTH","registerPluginConfigSchema","registerPluginEntitlements","AI_PLUGIN_ID","AI_ORG_PERMISSIONS","AI_CONFIG_SCHEMA","registerAiActivityActions","AI_PLUGIN_ENTITLEMENTS","pluginId","seatAddons","key","label","maxUnits","quota","perUnitByPlan","features","lockdownFeatures","staffBypass","notice","title","body","apiPaths","exact","prefixes","orgPermissions","declared","registerAiDeclarations"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,2EAA2E;AAC3E,0EAA0E;AAC1E,aAAa;AACb,SAASA,0BAA0B,QAAQ,2CAA0C;AACrF,SAASC,0BAA0B,QAAQ,4CAA2C;AACtF,SACEC,0BAA0B,QAErB,kDAAiD;AACxD,SAASC,YAAY,QAAQ,iBAAa;AAC1C,SAASC,kBAAkB,QAAQ,4BAAwB;AAC3D,SAASC,gBAAgB,QAAQ,qBAAiB;AAClD,SAASC,yBAAyB,QAAQ,oCAAgC;AAE1E;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,MAAMC,yBAAwD;IACnEC,UAAUL;IACVM,YAAY;QACV;YACEC,KAAK;YACLC,OAAO;YACPC,UAAU;YACVC,OAAO;gBACLH,KAAK;gBACLI,eAAed;YACjB;YACAe,UAAU;gBAAC;gBAAgB;aAAW;QACxC;KACD;IACDA,UAAU;QACR;YAAEL,KAAK;YAAYC,OAAO;QAAY;QACtC;YAAED,KAAK;YAAgBC,OAAO;QAAgB;KAC/C;IACDK,kBAAkB;QAChB;YACEN,KAAK;YACLC,OAAO;YACPM,aAAa;YACbC,QAAQ;gBACNC,OAAO;gBACPC,MAAM;YACR;YACA,sEAAsE;YACtE,+BAA+B;YAC/BC,UAAU;gBAAEC,OAAO;oBAAC;oBAAa;iBAAc;YAAC;QAClD;QACA;
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/declarations.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Named modules rather than the `@aglyn/aglyn` barrel: both apps load this\n// on the server at boot, and the full barrel carries client-only contexts\n// (AGL-405).\nimport { AI_ADDON_CREDITS_PER_MONTH } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { registerPluginConfigSchema } from '@aglyn/aglyn/plugin-manager/plugin-config'\nimport {\n registerPluginEntitlements,\n type PluginEntitlementRegistration,\n} from '@aglyn/aglyn/plugin-manager/plugin-entitlements'\nimport { AI_PLUGIN_ID } from './constants'\nimport { AI_ORG_PERMISSIONS } from './model/ai-permissions'\nimport { AI_CONFIG_SCHEMA } from './plugin-config'\nimport { registerAiActivityActions } from './activity/ai-activity-actions'\n\n/**\n * The plugin's DECLARATIONS (AGL-2939): what core must know about this\n * plugin before any of its surfaces load — its billing and access keys,\n * the AI permission keys it adds to the org catalog (AGL-2984), its\n * activity codes, its settings schema. Loaded eagerly by both apps\n * through the generated declarations manifest, at boot on the server and\n * with the plugin loader on the client, so a core billing route folds the\n * add-on and the staff lockdown page lists the levers whether or not a\n * request has touched the plugin yet. Light by construction: nothing here\n * reaches a provider, Firestore or React.\n *\n * The plan tables keep the per-plan defaults for `aiGenerative` and\n * `aiAssist`, and `PLAN_PRICING` keeps the add-on's price, so this names\n * keys and bands and restates no price.\n *\n * `ai-assist` and `ai-generate` are separate levers because the two doors\n * spend at different rates and an incident on one need not stop the other\n * (AGL-2903). Both grant the staff bypass: a provider incident is verified\n * recovered by staff making one real call, not by lifting the lock and\n * watching customers find out.\n */\nexport const AI_PLUGIN_ENTITLEMENTS: PluginEntitlementRegistration = {\n pluginId: AI_PLUGIN_ID,\n seatAddons: [\n {\n key: 'aiAddon',\n label: 'AI add-on',\n maxUnits: 1,\n quota: {\n key: 'assistCreditsPerMonth',\n perUnitByPlan: AI_ADDON_CREDITS_PER_MONTH,\n },\n features: ['aiGenerative', 'aiAssist'],\n },\n ],\n features: [\n { key: 'aiAssist', label: 'AI assist' },\n { key: 'aiGenerative', label: 'AI generation' },\n ],\n lockdownFeatures: [\n {\n key: 'ai-assist',\n label: 'AI assist',\n customerName: 'AI assist',\n staffBypass: true,\n notice: {\n title: 'AI assist is temporarily unavailable',\n body: 'AI assist is temporarily unavailable. Your content is unaffected — please try again shortly.',\n },\n // Gated even while the route 501s without a provider key — the switch\n // predates the key on purpose.\n apiPaths: { exact: ['ai/assist', 'assist/chat'] },\n },\n {\n key: 'ai-generate',\n label: 'AI generation',\n customerName: 'AI generation',\n staffBypass: true,\n notice: {\n title: 'AI generation is temporarily unavailable',\n body: 'Generating sections, pages and automations with AI is temporarily unavailable. Everything already built is unaffected — please try again shortly.',\n },\n // `ai/seo` (AGL-2910) applies a finished audit's drafts; it spends\n // nothing, and it is still a generative door the switch stops.\n apiPaths: { prefixes: ['ai/generate', 'ai/jobs', 'ai/seo'] },\n },\n ],\n orgPermissions: AI_ORG_PERMISSIONS,\n}\n\nlet declared = false\n\n/**\n * Registers everything above, and the activity codes. Idempotent: the\n * declarations manifest runs it once per process, and a surface's register\n * fn may call it again. The codes are a call rather than an import made\n * for what the module does as it loads, because the browser's manifest\n * reaches that module through nothing else, and a bundler honoring this\n * package's `sideEffects` deletes such an import (AGL-3025).\n */\nexport function registerAiDeclarations(): void {\n if (declared) return\n declared = true\n registerPluginEntitlements(AI_PLUGIN_ENTITLEMENTS)\n registerPluginConfigSchema(AI_CONFIG_SCHEMA)\n registerAiActivityActions()\n}\n\nregisterAiDeclarations()\n"],"names":["AI_ADDON_CREDITS_PER_MONTH","registerPluginConfigSchema","registerPluginEntitlements","AI_PLUGIN_ID","AI_ORG_PERMISSIONS","AI_CONFIG_SCHEMA","registerAiActivityActions","AI_PLUGIN_ENTITLEMENTS","pluginId","seatAddons","key","label","maxUnits","quota","perUnitByPlan","features","lockdownFeatures","customerName","staffBypass","notice","title","body","apiPaths","exact","prefixes","orgPermissions","declared","registerAiDeclarations"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,2EAA2E;AAC3E,0EAA0E;AAC1E,aAAa;AACb,SAASA,0BAA0B,QAAQ,2CAA0C;AACrF,SAASC,0BAA0B,QAAQ,4CAA2C;AACtF,SACEC,0BAA0B,QAErB,kDAAiD;AACxD,SAASC,YAAY,QAAQ,iBAAa;AAC1C,SAASC,kBAAkB,QAAQ,4BAAwB;AAC3D,SAASC,gBAAgB,QAAQ,qBAAiB;AAClD,SAASC,yBAAyB,QAAQ,oCAAgC;AAE1E;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,MAAMC,yBAAwD;IACnEC,UAAUL;IACVM,YAAY;QACV;YACEC,KAAK;YACLC,OAAO;YACPC,UAAU;YACVC,OAAO;gBACLH,KAAK;gBACLI,eAAed;YACjB;YACAe,UAAU;gBAAC;gBAAgB;aAAW;QACxC;KACD;IACDA,UAAU;QACR;YAAEL,KAAK;YAAYC,OAAO;QAAY;QACtC;YAAED,KAAK;YAAgBC,OAAO;QAAgB;KAC/C;IACDK,kBAAkB;QAChB;YACEN,KAAK;YACLC,OAAO;YACPM,cAAc;YACdC,aAAa;YACbC,QAAQ;gBACNC,OAAO;gBACPC,MAAM;YACR;YACA,sEAAsE;YACtE,+BAA+B;YAC/BC,UAAU;gBAAEC,OAAO;oBAAC;oBAAa;iBAAc;YAAC;QAClD;QACA;YACEb,KAAK;YACLC,OAAO;YACPM,cAAc;YACdC,aAAa;YACbC,QAAQ;gBACNC,OAAO;gBACPC,MAAM;YACR;YACA,mEAAmE;YACnE,+DAA+D;YAC/DC,UAAU;gBAAEE,UAAU;oBAAC;oBAAe;oBAAW;iBAAS;YAAC;QAC7D;KACD;IACDC,gBAAgBrB;AAClB,EAAC;AAED,IAAIsB,WAAW;AAEf;;;;;;;CAOC,GACD,OAAO,SAASC;IACd,IAAID,UAAU;IACdA,WAAW;IACXxB,2BAA2BK;IAC3BN,2BAA2BI;IAC3BC;AACF;AAEAqB"}
|
|
@@ -20,12 +20,11 @@
|
|
|
20
20
|
import { listPluginEventHandlers, registerPluginEventHandler } from "@aglyn/aglyn/plugin-manager/plugin-events";
|
|
21
21
|
import { listPluginUserErasers, registerPluginUserEraser } from "@aglyn/aglyn/plugin-manager/plugin-user-erasure";
|
|
22
22
|
import { listUsageAlertContributors, registerUsageAlertContributor } from "@aglyn/aglyn/plugin-manager/usage-alert-contributors";
|
|
23
|
-
import {
|
|
23
|
+
import { registerPluginUsageMeter } from "@aglyn/aglyn/plugin-manager/plugin-usage-meters";
|
|
24
24
|
import { registerOperatorAlerts } from "@aglyn/aglyn/plugin-manager/operator-alerts";
|
|
25
|
-
import { AI_PLUGIN_ID } from "./constants.js";
|
|
25
|
+
import { AI_PLUGIN_ID, AI_USAGE_METER_ID } from "./constants.js";
|
|
26
26
|
import { AI_PROVIDER_UNAVAILABLE } from "./operator-alerts.js";
|
|
27
27
|
import { registerAiDeclarations } from "./declarations.js";
|
|
28
|
-
import { AI_OVERAGE_METER_LINE_ID, registerAiOverageMeteredLine } from "./billing/ai-overage-cutover.js";
|
|
29
28
|
/** The provider-spend staff alerts' contributor id under the plugin. */ const AI_USAGE_ALERTS_ID = 'provider-spend';
|
|
30
29
|
/**
|
|
31
30
|
* The plugin's SERVER declarations (AGL-2939): what the server must know
|
|
@@ -109,13 +108,24 @@ import { AI_OVERAGE_METER_LINE_ID, registerAiOverageMeteredLine } from "./billin
|
|
|
109
108
|
pluginId: AI_PLUGIN_ID
|
|
110
109
|
});
|
|
111
110
|
}
|
|
112
|
-
// The meter
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
111
|
+
// The plugin's meter in the monthly usage sweep: the month's provider spend
|
|
112
|
+
// onto the rollup, the credits past the band onto the invoice, and — from
|
|
113
|
+
// the cutover month (AGL-3011) — the close-out of what the plugin invoices
|
|
114
|
+
// itself. Registered at boot because the sweep is a core cron, and declared
|
|
115
|
+
// in `usageAxes` so the sweep refuses to bill a month without it. The same
|
|
116
|
+
// registration again replaces itself.
|
|
117
|
+
registerPluginUsageMeter({
|
|
118
|
+
pluginId: AI_PLUGIN_ID,
|
|
119
|
+
id: AI_USAGE_METER_ID,
|
|
120
|
+
measure: async (context)=>{
|
|
121
|
+
const { measureAiMonth } = await import("./billing/ai-month-meter.js");
|
|
122
|
+
return measureAiMonth(context);
|
|
123
|
+
},
|
|
124
|
+
closeMonth: async (context)=>{
|
|
125
|
+
const { closeAiMonth } = await import("./billing/ai-month-meter.js");
|
|
126
|
+
await closeAiMonth(context);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
119
129
|
if (!listPluginUserErasers().includes(AI_PLUGIN_ID)) {
|
|
120
130
|
registerPluginUserEraser(async (request)=>{
|
|
121
131
|
const { eraseAiUsageForUser } = await import("./usage/ai-usage-eraser.js");
|
|
@@ -138,6 +148,10 @@ import { AI_OVERAGE_METER_LINE_ID, registerAiOverageMeteredLine } from "./billin
|
|
|
138
148
|
evaluate: async (context)=>{
|
|
139
149
|
const { evaluateAiUsageAlerts } = await import("./usage/ai-usage-alerts.js");
|
|
140
150
|
await evaluateAiUsageAlerts(context);
|
|
151
|
+
},
|
|
152
|
+
quotaChecks: async (context)=>{
|
|
153
|
+
const { aiQuotaChecks } = await import("./usage/ai-quota-checks.js");
|
|
154
|
+
return aiQuotaChecks(context);
|
|
141
155
|
}
|
|
142
156
|
});
|
|
143
157
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/declarations.server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Each registry from its own module, not the `@aglyn/aglyn/server` barrel:\n// boot needs three registries rather than the whole server surface, and a\n// suite that stubs that barrel wholesale still loads these declarations\n// against the real registries.\nimport {\n listPluginEventHandlers,\n registerPluginEventHandler,\n} from '@aglyn/aglyn/plugin-manager/plugin-events'\nimport {\n listPluginUserErasers,\n registerPluginUserEraser,\n} from '@aglyn/aglyn/plugin-manager/plugin-user-erasure'\nimport {\n listUsageAlertContributors,\n registerUsageAlertContributor,\n} from '@aglyn/aglyn/plugin-manager/usage-alert-contributors'\nimport { pluginMeteredLineOwner } from '@aglyn/aglyn/plugin-manager/plugin-metered-lines'\nimport { registerOperatorAlerts } from '@aglyn/aglyn/plugin-manager/operator-alerts'\nimport { AI_PLUGIN_ID } from './constants'\nimport { AI_PROVIDER_UNAVAILABLE } from './operator-alerts'\nimport { registerAiDeclarations } from './declarations'\nimport {\n AI_OVERAGE_METER_LINE_ID,\n registerAiOverageMeteredLine,\n} from './billing/ai-overage-cutover'\n\n/** The provider-spend staff alerts' contributor id under the plugin. */\nconst AI_USAGE_ALERTS_ID = 'provider-spend'\n\n/**\n * The plugin's SERVER declarations (AGL-2939): what the server must know\n * at boot, before any door of the plugin has been called. The two platform\n * events the plugin writes activity for — the AI add-on bought or dropped,\n * an `ai.*` permission moved — are raised by core billing and membership\n * routes that never load the plugin's API surface, so the subscriptions\n * have to be in place from the first request. So does the eraser: an\n * account erasure is a core route too, and a person's AI usage months sit\n * under each org, keyed by the uid, where no core delete reaches them. So\n * does the usage alert contributor (AGL-2984): the usage-alerts sweep is a\n * core cron, and it evaluates the plugin's staff alerts on provider spend for\n * every org it reads.\n *\n * Light at boot by construction: the writers behind the handlers are\n * imported when the first event arrives, and the alert rules when the sweep\n * first evaluates an org, not when the process starts, so the boot cost is\n * the registration and nothing that touches Firestore.\n */\nexport function registerAiServerDeclarations(): void {\n registerAiDeclarations()\n // Idempotent against each REGISTRY rather than a module flag, so a\n // registry reset (a spec) or a second module instance registers again.\n if (!listPluginEventHandlers('org.seatAddons.changed').includes(AI_PLUGIN_ID)) {\n registerPluginEventHandler(\n 'org.seatAddons.changed',\n async ({ orgId, actor, before, after }) => {\n const { logAiAddonChanged } = await import('./activity/ai-activity')\n await logAiAddonChanged(orgId, actor, { before, after })\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'org.permissions.changed',\n async ({ orgId, actor, subject, permission, granted }) => {\n if (!permission.startsWith('ai.')) return\n const { logAiPermissionChanged } = await import('./activity/ai-activity')\n await logAiPermissionChanged(orgId, actor, { subject, permission, granted })\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // THE PLATFORM'S BILLING EVENTS (AGL-3011). Subscribed at boot for the\n // same reason as the two above: they are raised by the billing webhook,\n // which never loads an AI door, and a workspace's overage standing moving\n // late is a workspace charged or refused against a fact we already had.\n //\n // The handler modules are imported when the first event arrives, so a\n // process that never receives one pays only for the registration.\n if (!listPluginEventHandlers('billing.invoice.paid').includes(AI_PLUGIN_ID)) {\n registerPluginEventHandler(\n 'billing.invoice.paid',\n async (payload) => {\n const { onAiBillingInvoicePaid } = await import('./billing/ai-overage-events')\n await onAiBillingInvoicePaid(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.invoice.failed',\n async (payload) => {\n const { onAiBillingInvoiceFailed } = await import('./billing/ai-overage-events')\n await onAiBillingInvoiceFailed(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.invoice.closed',\n async (payload) => {\n const { onAiBillingInvoiceClosed } = await import('./billing/ai-overage-events')\n await onAiBillingInvoiceClosed(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.dispute.opened',\n async (payload) => {\n const { onAiBillingDisputeOpened } = await import('./billing/ai-overage-events')\n await onAiBillingDisputeOpened(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.paymentMethod.changed',\n async (payload) => {\n const { onAiBillingPaymentMethodChanged } = await import(\n './billing/ai-overage-events'\n )\n await onAiBillingPaymentMethodChanged(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // The meter line the plugin bills for itself from the cutover month\n // (AGL-3011). Registered at boot because the monthly usage sweep is a core\n // cron; the claim reads its own cutover month on every call, so it is\n // inert until the month is configured.\n if (pluginMeteredLineOwner(AI_OVERAGE_METER_LINE_ID) !== AI_PLUGIN_ID) {\n registerAiOverageMeteredLine()\n }\n if (!listPluginUserErasers().includes(AI_PLUGIN_ID)) {\n registerPluginUserEraser(\n async (request) => {\n const { eraseAiUsageForUser } = await import('./usage/ai-usage-eraser')\n return eraseAiUsageForUser(request)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // The platform provider account alert (AGL-3377), listed on Staff →\n // Operator alerts before the first one is ever raised.\n registerOperatorAlerts([AI_PROVIDER_UNAVAILABLE], { pluginId: AI_PLUGIN_ID })\n if (\n !listUsageAlertContributors().some(\n (contributor) =>\n contributor.pluginId === AI_PLUGIN_ID &&\n contributor.id === AI_USAGE_ALERTS_ID,\n )\n ) {\n registerUsageAlertContributor({\n pluginId: AI_PLUGIN_ID,\n id: AI_USAGE_ALERTS_ID,\n evaluate: async (context) => {\n const { evaluateAiUsageAlerts } = await import('./usage/ai-usage-alerts')\n await evaluateAiUsageAlerts(context)\n },\n })\n }\n}\n\nregisterAiServerDeclarations()\n"],"names":["listPluginEventHandlers","registerPluginEventHandler","listPluginUserErasers","registerPluginUserEraser","listUsageAlertContributors","registerUsageAlertContributor","pluginMeteredLineOwner","registerOperatorAlerts","AI_PLUGIN_ID","AI_PROVIDER_UNAVAILABLE","registerAiDeclarations","AI_OVERAGE_METER_LINE_ID","registerAiOverageMeteredLine","AI_USAGE_ALERTS_ID","registerAiServerDeclarations","includes","orgId","actor","before","after","logAiAddonChanged","pluginId","subject","permission","granted","startsWith","logAiPermissionChanged","payload","onAiBillingInvoicePaid","onAiBillingInvoiceFailed","onAiBillingInvoiceClosed","onAiBillingDisputeOpened","onAiBillingPaymentMethodChanged","request","eraseAiUsageForUser","some","contributor","id","evaluate","context","evaluateAiUsageAlerts"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/plugins/ai/src/lib/declarations.server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Each registry from its own module, not the `@aglyn/aglyn/server` barrel:\n// boot needs three registries rather than the whole server surface, and a\n// suite that stubs that barrel wholesale still loads these declarations\n// against the real registries.\nimport {\n listPluginEventHandlers,\n registerPluginEventHandler,\n} from '@aglyn/aglyn/plugin-manager/plugin-events'\nimport {\n listPluginUserErasers,\n registerPluginUserEraser,\n} from '@aglyn/aglyn/plugin-manager/plugin-user-erasure'\nimport {\n listUsageAlertContributors,\n registerUsageAlertContributor,\n} from '@aglyn/aglyn/plugin-manager/usage-alert-contributors'\nimport { registerPluginUsageMeter } from '@aglyn/aglyn/plugin-manager/plugin-usage-meters'\nimport { registerOperatorAlerts } from '@aglyn/aglyn/plugin-manager/operator-alerts'\nimport { AI_PLUGIN_ID, AI_USAGE_METER_ID } from './constants'\nimport { AI_PROVIDER_UNAVAILABLE } from './operator-alerts'\nimport { registerAiDeclarations } from './declarations'\n\n/** The provider-spend staff alerts' contributor id under the plugin. */\nconst AI_USAGE_ALERTS_ID = 'provider-spend'\n\n/**\n * The plugin's SERVER declarations (AGL-2939): what the server must know\n * at boot, before any door of the plugin has been called. The two platform\n * events the plugin writes activity for — the AI add-on bought or dropped,\n * an `ai.*` permission moved — are raised by core billing and membership\n * routes that never load the plugin's API surface, so the subscriptions\n * have to be in place from the first request. So does the eraser: an\n * account erasure is a core route too, and a person's AI usage months sit\n * under each org, keyed by the uid, where no core delete reaches them. So\n * does the usage alert contributor (AGL-2984): the usage-alerts sweep is a\n * core cron, and it evaluates the plugin's staff alerts on provider spend for\n * every org it reads.\n *\n * Light at boot by construction: the writers behind the handlers are\n * imported when the first event arrives, and the alert rules when the sweep\n * first evaluates an org, not when the process starts, so the boot cost is\n * the registration and nothing that touches Firestore.\n */\nexport function registerAiServerDeclarations(): void {\n registerAiDeclarations()\n // Idempotent against each REGISTRY rather than a module flag, so a\n // registry reset (a spec) or a second module instance registers again.\n if (!listPluginEventHandlers('org.seatAddons.changed').includes(AI_PLUGIN_ID)) {\n registerPluginEventHandler(\n 'org.seatAddons.changed',\n async ({ orgId, actor, before, after }) => {\n const { logAiAddonChanged } = await import('./activity/ai-activity')\n await logAiAddonChanged(orgId, actor, { before, after })\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'org.permissions.changed',\n async ({ orgId, actor, subject, permission, granted }) => {\n if (!permission.startsWith('ai.')) return\n const { logAiPermissionChanged } = await import('./activity/ai-activity')\n await logAiPermissionChanged(orgId, actor, { subject, permission, granted })\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // THE PLATFORM'S BILLING EVENTS (AGL-3011). Subscribed at boot for the\n // same reason as the two above: they are raised by the billing webhook,\n // which never loads an AI door, and a workspace's overage standing moving\n // late is a workspace charged or refused against a fact we already had.\n //\n // The handler modules are imported when the first event arrives, so a\n // process that never receives one pays only for the registration.\n if (!listPluginEventHandlers('billing.invoice.paid').includes(AI_PLUGIN_ID)) {\n registerPluginEventHandler(\n 'billing.invoice.paid',\n async (payload) => {\n const { onAiBillingInvoicePaid } = await import('./billing/ai-overage-events')\n await onAiBillingInvoicePaid(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.invoice.failed',\n async (payload) => {\n const { onAiBillingInvoiceFailed } = await import('./billing/ai-overage-events')\n await onAiBillingInvoiceFailed(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.invoice.closed',\n async (payload) => {\n const { onAiBillingInvoiceClosed } = await import('./billing/ai-overage-events')\n await onAiBillingInvoiceClosed(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.dispute.opened',\n async (payload) => {\n const { onAiBillingDisputeOpened } = await import('./billing/ai-overage-events')\n await onAiBillingDisputeOpened(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n registerPluginEventHandler(\n 'billing.paymentMethod.changed',\n async (payload) => {\n const { onAiBillingPaymentMethodChanged } = await import(\n './billing/ai-overage-events'\n )\n await onAiBillingPaymentMethodChanged(payload)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // The plugin's meter in the monthly usage sweep: the month's provider spend\n // onto the rollup, the credits past the band onto the invoice, and — from\n // the cutover month (AGL-3011) — the close-out of what the plugin invoices\n // itself. Registered at boot because the sweep is a core cron, and declared\n // in `usageAxes` so the sweep refuses to bill a month without it. The same\n // registration again replaces itself.\n registerPluginUsageMeter({\n pluginId: AI_PLUGIN_ID,\n id: AI_USAGE_METER_ID,\n measure: async (context) => {\n const { measureAiMonth } = await import('./billing/ai-month-meter')\n return measureAiMonth(context)\n },\n closeMonth: async (context) => {\n const { closeAiMonth } = await import('./billing/ai-month-meter')\n await closeAiMonth(context)\n },\n })\n if (!listPluginUserErasers().includes(AI_PLUGIN_ID)) {\n registerPluginUserEraser(\n async (request) => {\n const { eraseAiUsageForUser } = await import('./usage/ai-usage-eraser')\n return eraseAiUsageForUser(request)\n },\n { pluginId: AI_PLUGIN_ID },\n )\n }\n // The platform provider account alert (AGL-3377), listed on Staff →\n // Operator alerts before the first one is ever raised.\n registerOperatorAlerts([AI_PROVIDER_UNAVAILABLE], { pluginId: AI_PLUGIN_ID })\n if (\n !listUsageAlertContributors().some(\n (contributor) =>\n contributor.pluginId === AI_PLUGIN_ID &&\n contributor.id === AI_USAGE_ALERTS_ID,\n )\n ) {\n registerUsageAlertContributor({\n pluginId: AI_PLUGIN_ID,\n id: AI_USAGE_ALERTS_ID,\n evaluate: async (context) => {\n const { evaluateAiUsageAlerts } = await import('./usage/ai-usage-alerts')\n await evaluateAiUsageAlerts(context)\n },\n quotaChecks: async (context) => {\n const { aiQuotaChecks } = await import('./usage/ai-quota-checks')\n return aiQuotaChecks(context)\n },\n })\n }\n}\n\nregisterAiServerDeclarations()\n"],"names":["listPluginEventHandlers","registerPluginEventHandler","listPluginUserErasers","registerPluginUserEraser","listUsageAlertContributors","registerUsageAlertContributor","registerPluginUsageMeter","registerOperatorAlerts","AI_PLUGIN_ID","AI_USAGE_METER_ID","AI_PROVIDER_UNAVAILABLE","registerAiDeclarations","AI_USAGE_ALERTS_ID","registerAiServerDeclarations","includes","orgId","actor","before","after","logAiAddonChanged","pluginId","subject","permission","granted","startsWith","logAiPermissionChanged","payload","onAiBillingInvoicePaid","onAiBillingInvoiceFailed","onAiBillingInvoiceClosed","onAiBillingDisputeOpened","onAiBillingPaymentMethodChanged","id","measure","context","measureAiMonth","closeMonth","closeAiMonth","request","eraseAiUsageForUser","some","contributor","evaluate","evaluateAiUsageAlerts","quotaChecks","aiQuotaChecks"],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The datasets an insight question may name (AGL-2915), with their fields:
|
|
19
|
+
* what the model picks a dataset breakdown from.
|
|
20
|
+
*
|
|
21
|
+
* The datasets are the data plugin's, so they are listed through the
|
|
22
|
+
* `dataset` index it publishes (AGL-3080), never read from its collection:
|
|
23
|
+
* where no plugin keeps datasets, there are none to name. Narrowed to a site,
|
|
24
|
+
* the index answers only the datasets shared with it. Who may see which is the
|
|
25
|
+
* platform's own rule over the scope tokens each dataset carries
|
|
26
|
+
* (`memberCanSee`), applied for the member who asked — with no member, only
|
|
27
|
+
* datasets shared with every site. The figures themselves are read by the
|
|
28
|
+
* data plugin's own figure readers, under the same rule.
|
|
29
|
+
*/
|
|
30
|
+
type Firestore = FirebaseFirestore.Firestore;
|
|
31
|
+
/** Datasets a catalog lists: the dataset summary's window. */
|
|
32
|
+
export declare const AI_DATASET_CATALOG_LIMIT = 50;
|
|
33
|
+
/** A dataset a question may name, with its fields, as the read prompt lists it. */
|
|
34
|
+
export interface AiDatasetCatalogEntry {
|
|
35
|
+
id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
fields: Array<{
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
type: string;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
/** The datasets a request may read, with their fields, by name. */
|
|
44
|
+
export declare function aiDatasetCatalog(firestore: Firestore, request: {
|
|
45
|
+
orgId: string;
|
|
46
|
+
hostId: string | null;
|
|
47
|
+
uid: string | null;
|
|
48
|
+
}): Promise<AiDatasetCatalogEntry[]>;
|
|
49
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { hostRoleFor, isOrgWideMember, memberCanSee } from "@aglyn/aglyn/app-utils/organizations";
|
|
18
|
+
import { ORG_SCOPE_TOKEN } from "@aglyn/aglyn/app-utils/scope-tokens";
|
|
19
|
+
import { pluginRecordIndex } from "@aglyn/aglyn/plugin-manager/plugin-record-index";
|
|
20
|
+
/** Datasets a catalog lists: the dataset summary's window. */ export const AI_DATASET_CATALOG_LIMIT = 50;
|
|
21
|
+
const text = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
22
|
+
function fieldsOf(value) {
|
|
23
|
+
return (Array.isArray(value) ? value : []).map((field)=>({
|
|
24
|
+
id: text(field == null ? void 0 : field['id']),
|
|
25
|
+
name: text(field == null ? void 0 : field['name']),
|
|
26
|
+
type: text(field == null ? void 0 : field['type']) || 'text'
|
|
27
|
+
})).filter((field)=>field.id).map((field)=>_extends({}, field, {
|
|
28
|
+
name: field.name || field.id
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
/** The datasets a request may read, with their fields, by name. */ export async function aiDatasetCatalog(firestore, request) {
|
|
32
|
+
const datasets = pluginRecordIndex('dataset');
|
|
33
|
+
if (!datasets) return [];
|
|
34
|
+
const [{ records }, memberSnapshot] = await Promise.all([
|
|
35
|
+
datasets.index.list({
|
|
36
|
+
orgId: request.orgId,
|
|
37
|
+
hostId: request.hostId,
|
|
38
|
+
limit: AI_DATASET_CATALOG_LIMIT
|
|
39
|
+
}),
|
|
40
|
+
request.uid ? firestore.collection('orgs').doc(request.orgId).collection('members').doc(request.uid).get() : Promise.resolve(null)
|
|
41
|
+
]);
|
|
42
|
+
const member = (memberSnapshot == null ? void 0 : memberSnapshot.exists) ? memberSnapshot.data() : null;
|
|
43
|
+
if (request.uid && !member) return [];
|
|
44
|
+
if (request.hostId && member && !isOrgWideMember(member) && hostRoleFor(member, request.hostId) === null) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
return records.filter((record)=>{
|
|
48
|
+
const visibleTo = Array.isArray(record.facts['visibleTo']) ? record.facts['visibleTo'] : [];
|
|
49
|
+
return member ? memberCanSee(member, visibleTo) : visibleTo.includes(ORG_SCOPE_TOKEN);
|
|
50
|
+
}).map((record)=>({
|
|
51
|
+
id: record.id,
|
|
52
|
+
name: record.name,
|
|
53
|
+
fields: fieldsOf(record.facts['fields'])
|
|
54
|
+
})).sort((a, b)=>a.name.localeCompare(b.name));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
//# sourceMappingURL=ai-dataset-catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/insights/ai-dataset-catalog.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { hostRoleFor, isOrgWideMember, memberCanSee } from '@aglyn/aglyn/app-utils/organizations'\nimport { ORG_SCOPE_TOKEN } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport type { AglynOrgMember } from '@aglyn/aglyn/foundation/definitions/organization.types'\nimport { pluginRecordIndex } from '@aglyn/aglyn/plugin-manager/plugin-record-index'\n\n/**\n * The datasets an insight question may name (AGL-2915), with their fields:\n * what the model picks a dataset breakdown from.\n *\n * The datasets are the data plugin's, so they are listed through the\n * `dataset` index it publishes (AGL-3080), never read from its collection:\n * where no plugin keeps datasets, there are none to name. Narrowed to a site,\n * the index answers only the datasets shared with it. Who may see which is the\n * platform's own rule over the scope tokens each dataset carries\n * (`memberCanSee`), applied for the member who asked — with no member, only\n * datasets shared with every site. The figures themselves are read by the\n * data plugin's own figure readers, under the same rule.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** Datasets a catalog lists: the dataset summary's window. */\nexport const AI_DATASET_CATALOG_LIMIT = 50\n\n/** A dataset a question may name, with its fields, as the read prompt lists it. */\nexport interface AiDatasetCatalogEntry {\n id: string\n name: string\n fields: Array<{ id: string; name: string; type: string }>\n}\n\nconst text = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\nfunction fieldsOf(value: unknown): AiDatasetCatalogEntry['fields'] {\n return (Array.isArray(value) ? (value as Array<Record<string, unknown>>) : [])\n .map((field) => ({ id: text(field?.['id']), name: text(field?.['name']), type: text(field?.['type']) || 'text' }))\n .filter((field) => field.id)\n .map((field) => ({ ...field, name: field.name || field.id }))\n}\n\n/** The datasets a request may read, with their fields, by name. */\nexport async function aiDatasetCatalog(\n firestore: Firestore,\n request: { orgId: string; hostId: string | null; uid: string | null },\n): Promise<AiDatasetCatalogEntry[]> {\n const datasets = pluginRecordIndex('dataset')\n if (!datasets) return []\n const [{ records }, memberSnapshot] = await Promise.all([\n datasets.index.list({ orgId: request.orgId, hostId: request.hostId, limit: AI_DATASET_CATALOG_LIMIT }),\n request.uid\n ? firestore.collection('orgs').doc(request.orgId).collection('members').doc(request.uid).get()\n : Promise.resolve(null),\n ])\n const member = memberSnapshot?.exists ? (memberSnapshot.data() as Partial<AglynOrgMember>) : null\n if (request.uid && !member) return []\n if (request.hostId && member && !isOrgWideMember(member) && hostRoleFor(member, request.hostId) === null) {\n return []\n }\n return records\n .filter((record) => {\n const visibleTo = Array.isArray(record.facts['visibleTo']) ? (record.facts['visibleTo'] as string[]) : []\n return member ? memberCanSee(member, visibleTo) : visibleTo.includes(ORG_SCOPE_TOKEN)\n })\n .map((record) => ({ id: record.id, name: record.name, fields: fieldsOf(record.facts['fields']) }))\n .sort((a, b) => a.name.localeCompare(b.name))\n}\n"],"names":["hostRoleFor","isOrgWideMember","memberCanSee","ORG_SCOPE_TOKEN","pluginRecordIndex","AI_DATASET_CATALOG_LIMIT","text","value","trim","fieldsOf","Array","isArray","map","field","id","name","type","filter","aiDatasetCatalog","firestore","request","datasets","records","memberSnapshot","Promise","all","index","list","orgId","hostId","limit","uid","collection","doc","get","resolve","member","exists","data","record","visibleTo","facts","includes","fields","sort","a","b","localeCompare"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,WAAW,EAAEC,eAAe,EAAEC,YAAY,QAAQ,uCAAsC;AACjG,SAASC,eAAe,QAAQ,sCAAqC;AAErE,SAASC,iBAAiB,QAAQ,kDAAiD;AAkBnF,4DAA4D,GAC5D,OAAO,MAAMC,2BAA2B,GAAE;AAS1C,MAAMC,OAAO,CAACC,QAA4B,OAAOA,UAAU,WAAWA,MAAMC,IAAI,KAAK;AAErF,SAASC,SAASF,KAAc;IAC9B,OAAO,AAACG,CAAAA,MAAMC,OAAO,CAACJ,SAAUA,QAA2C,EAAE,AAAD,EACzEK,GAAG,CAAC,CAACC,QAAW,CAAA;YAAEC,IAAIR,KAAKO,yBAAAA,KAAO,CAAC,KAAK;YAAGE,MAAMT,KAAKO,yBAAAA,KAAO,CAAC,OAAO;YAAGG,MAAMV,KAAKO,yBAAAA,KAAO,CAAC,OAAO,KAAK;QAAO,CAAA,GAC9GI,MAAM,CAAC,CAACJ,QAAUA,MAAMC,EAAE,EAC1BF,GAAG,CAAC,CAACC,QAAW,aAAKA;YAAOE,MAAMF,MAAME,IAAI,IAAIF,MAAMC,EAAE;;AAC7D;AAEA,iEAAiE,GACjE,OAAO,eAAeI,iBACpBC,SAAoB,EACpBC,OAAqE;IAErE,MAAMC,WAAWjB,kBAAkB;IACnC,IAAI,CAACiB,UAAU,OAAO,EAAE;IACxB,MAAM,CAAC,EAAEC,OAAO,EAAE,EAAEC,eAAe,GAAG,MAAMC,QAAQC,GAAG,CAAC;QACtDJ,SAASK,KAAK,CAACC,IAAI,CAAC;YAAEC,OAAOR,QAAQQ,KAAK;YAAEC,QAAQT,QAAQS,MAAM;YAAEC,OAAOzB;QAAyB;QACpGe,QAAQW,GAAG,GACPZ,UAAUa,UAAU,CAAC,QAAQC,GAAG,CAACb,QAAQQ,KAAK,EAAEI,UAAU,CAAC,WAAWC,GAAG,CAACb,QAAQW,GAAG,EAAEG,GAAG,KAC1FV,QAAQW,OAAO,CAAC;KACrB;IACD,MAAMC,SAASb,CAAAA,kCAAAA,eAAgBc,MAAM,IAAId,eAAee,IAAI,KAAiC;IAC7F,IAAIlB,QAAQW,GAAG,IAAI,CAACK,QAAQ,OAAO,EAAE;IACrC,IAAIhB,QAAQS,MAAM,IAAIO,UAAU,CAACnC,gBAAgBmC,WAAWpC,YAAYoC,QAAQhB,QAAQS,MAAM,MAAM,MAAM;QACxG,OAAO,EAAE;IACX;IACA,OAAOP,QACJL,MAAM,CAAC,CAACsB;QACP,MAAMC,YAAY9B,MAAMC,OAAO,CAAC4B,OAAOE,KAAK,CAAC,YAAY,IAAKF,OAAOE,KAAK,CAAC,YAAY,GAAgB,EAAE;QACzG,OAAOL,SAASlC,aAAakC,QAAQI,aAAaA,UAAUE,QAAQ,CAACvC;IACvE,GACCS,GAAG,CAAC,CAAC2B,SAAY,CAAA;YAAEzB,IAAIyB,OAAOzB,EAAE;YAAEC,MAAMwB,OAAOxB,IAAI;YAAE4B,QAAQlC,SAAS8B,OAAOE,KAAK,CAAC,SAAS;QAAE,CAAA,GAC9FG,IAAI,CAAC,CAACC,GAAGC,IAAMD,EAAE9B,IAAI,CAACgC,aAAa,CAACD,EAAE/B,IAAI;AAC/C"}
|
|
@@ -14,22 +14,17 @@
|
|
|
14
14
|
* See the License for the specific language governing permissions and
|
|
15
15
|
* limitations under the License.
|
|
16
16
|
*/
|
|
17
|
-
import { type PluginFigureReader
|
|
17
|
+
import { type PluginFigureReader } from '@aglyn/aglyn/plugin-manager/plugin-figures';
|
|
18
18
|
/**
|
|
19
19
|
* The figure readers this plugin registers for records the PLATFORM keeps
|
|
20
|
-
* (AGL-2915): a site's page-view counters
|
|
21
|
-
*
|
|
22
|
-
*
|
|
20
|
+
* (AGL-2915): a site's page-view counters and its forms' counters. Every
|
|
21
|
+
* other reader an insight reads is registered by the plugin that owns its
|
|
22
|
+
* records — commerce, bookings, marketing, the workspace's datasets — on the
|
|
23
23
|
* `plugin-figures` seam, and nothing here reads their documents.
|
|
24
24
|
*
|
|
25
25
|
* Each reader answers with one compact table of aggregates, read-only and
|
|
26
|
-
* about nobody: counts, sums and rates, labeled by a page path, a form's name
|
|
27
|
-
* a referring site
|
|
28
|
-
* a record. A dataset breakdown refuses a field with more than
|
|
29
|
-
* `AI_DATASET_MAX_GROUP_VALUES` different values — on a dataset of any size, a
|
|
30
|
-
* name, an address or an order number — and folds every group of fewer than
|
|
31
|
-
* `AI_DATASET_MIN_GROUP` records into one row, so each label it shows is a
|
|
32
|
-
* value at least that many records share.
|
|
26
|
+
* about nobody: counts, sums and rates, labeled by a page path, a form's name
|
|
27
|
+
* or a referring site — never a visitor or a submission.
|
|
33
28
|
*
|
|
34
29
|
* The readers use the arithmetic their console cards use: the Traffic card's
|
|
35
30
|
* windows and change (`pluginFigureWindows`, `pluginFigureChange`, which are
|
|
@@ -50,36 +45,8 @@ export declare const AI_TRAFFIC_TOP_REFERRERS = 8;
|
|
|
50
45
|
export declare const AI_TRAFFIC_TOP_CAMPAIGNS = 5;
|
|
51
46
|
/** Forms a forms table reads. */
|
|
52
47
|
export declare const AI_FORMS_READ_LIMIT = 100;
|
|
53
|
-
/** Datasets a dataset summary reads. */
|
|
54
|
-
export declare const AI_DATASETS_READ_LIMIT = 50;
|
|
55
|
-
/** Records a dataset breakdown or field summary reads, in document order; the rest are named in a note. */
|
|
56
|
-
export declare const AI_DATASET_RECORDS_READ_LIMIT = 2000;
|
|
57
|
-
/** The fewest records a breakdown shows a group of by its own label. */
|
|
58
|
-
export declare const AI_DATASET_MIN_GROUP = 3;
|
|
59
|
-
/**
|
|
60
|
-
* The most different values a breakdown groups by: every state and territory,
|
|
61
|
-
* a status, a category. A field with more tells records apart rather than
|
|
62
|
-
* sorting them, so it is refused.
|
|
63
|
-
*/
|
|
64
|
-
export declare const AI_DATASET_MAX_GROUP_VALUES = 60;
|
|
65
|
-
/** The aggregates a breakdown computes. */
|
|
66
|
-
export declare const AI_DATASET_OPERATIONS: readonly ["count", "sum", "average", "min", "max"];
|
|
67
|
-
export type AiDatasetOperation = (typeof AI_DATASET_OPERATIONS)[number];
|
|
68
48
|
export declare function trafficFigureReaders(firestore: AiFigureFirestore): PluginFigureReader[];
|
|
69
49
|
export declare function formsFigureReader(firestore: AiFigureFirestore): PluginFigureReader;
|
|
70
|
-
/** A dataset a question may name, with its fields, as the read prompt lists it. */
|
|
71
|
-
export interface AiDatasetCatalogEntry {
|
|
72
|
-
id: string;
|
|
73
|
-
name: string;
|
|
74
|
-
fields: Array<{
|
|
75
|
-
id: string;
|
|
76
|
-
name: string;
|
|
77
|
-
type: string;
|
|
78
|
-
}>;
|
|
79
|
-
}
|
|
80
|
-
/** The datasets a request may read, with their fields: what the model picks a breakdown from. */
|
|
81
|
-
export declare function aiDatasetCatalog(firestore: Firestore, request: Pick<PluginFigureRequest, 'orgId' | 'hostId' | 'uid'>): Promise<AiDatasetCatalogEntry[]>;
|
|
82
|
-
export declare function datasetFigureReaders(firestore: AiFigureFirestore): PluginFigureReader[];
|
|
83
50
|
/** Every reader this plugin registers, for the platform's own records. */
|
|
84
51
|
export declare function aiFigureReaders(firestore: AiFigureFirestore): PluginFigureReader[];
|
|
85
52
|
/**
|