@aglyn/plugins-ai 1.0.0-beta.260 → 1.0.0-beta.262
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/components/ai-brief-dialog.component.d.ts +21 -58
- package/src/lib/components/ai-brief-dialog.component.js +8 -132
- package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
- package/src/lib/components/ai-job-follow.component.js +2 -35
- package/src/lib/components/ai-job-follow.component.js.map +1 -1
- package/src/lib/components/ai-job-plan.component.d.ts +2 -8
- package/src/lib/components/ai-job-plan.component.js +13 -74
- package/src/lib/components/ai-job-plan.component.js.map +1 -1
- package/src/lib/components/ai-site-start-card.component.js +43 -34
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-template-collection-field.component.d.ts +2 -12
- package/src/lib/components/ai-template-collection-field.component.js +13 -10
- package/src/lib/components/ai-template-collection-field.component.js.map +1 -1
- package/src/lib/components/ai-upsell-dialog.component.d.ts +3 -25
- package/src/lib/components/ai-upsell-dialog.component.js +9 -66
- package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
- package/src/lib/components/use-ai-job-run.d.ts +3 -8
- package/src/lib/components/use-ai-job-run.js +4 -15
- package/src/lib/components/use-ai-job-run.js.map +1 -1
- package/src/lib/insights/ai-figure-readers.js +15 -5
- package/src/lib/insights/ai-figure-readers.js.map +1 -1
- package/src/lib/insights/ai-figure-summaries.d.ts +27 -0
- package/src/lib/insights/ai-figure-summaries.js +79 -0
- package/src/lib/insights/ai-figure-summaries.js.map +1 -0
- package/src/lib/insights/ai-insight-readers.d.ts +2 -1
- package/src/lib/insights/ai-insight-readers.js +11 -26
- package/src/lib/insights/ai-insight-readers.js.map +1 -1
- package/src/lib/jobs/ai-job-admission.d.ts +6 -0
- package/src/lib/jobs/ai-job-admission.js.map +1 -1
- package/src/lib/jobs/ai-job-site-step.js +10 -6
- package/src/lib/jobs/ai-job-site-step.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +43 -11
- package/src/lib/jobs/ai-layout-stock-photos.js +329 -32
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/ai-stock-photo-picker.js +13 -7
- package/src/lib/jobs/ai-stock-photo-picker.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-recordings.d.ts +35 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-recordings.js +404 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-motion.d.ts +9 -3
- package/src/lib/layout-language/ai-layout-motion.js +9 -3
- package/src/lib/layout-language/ai-layout-motion.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +10 -1
- package/src/lib/layout-language/ai-layout-pictures.js +12 -6
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-brief-copy.d.ts +85 -0
- package/src/lib/model/ai-brief-copy.js +152 -0
- package/src/lib/model/ai-brief-copy.js.map +1 -0
- package/src/lib/model/ai-create-view.d.ts +200 -0
- package/src/lib/model/ai-create-view.js +263 -0
- package/src/lib/model/ai-create-view.js.map +1 -0
- package/src/lib/model/ai-insight.d.ts +1 -1
- package/src/lib/model/ai-insight.js +4 -39
- package/src/lib/model/ai-insight.js.map +1 -1
- package/src/lib/model/ai-job-activity.d.ts +24 -0
- package/src/lib/model/ai-job-activity.js +52 -1
- package/src/lib/model/ai-job-activity.js.map +1 -1
- package/src/lib/model/ai-job-plan-lines.d.ts +46 -0
- package/src/lib/model/ai-job-plan-lines.js +70 -0
- package/src/lib/model/ai-job-plan-lines.js.map +1 -0
- package/src/lib/model/ai-template-collections.d.ts +28 -0
- package/src/lib/model/ai-template-collections.js +28 -0
- package/src/lib/model/ai-template-collections.js.map +1 -0
- package/src/lib/model/ai-upsell-copy.d.ts +41 -0
- package/src/lib/model/ai-upsell-copy.js +85 -0
- package/src/lib/model/ai-upsell-copy.js.map +1 -0
- package/src/lib/runtime/ai-doctrine-validators.d.ts +40 -0
- package/src/lib/runtime/ai-doctrine-validators.js +148 -3
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.js +6 -2
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-eval-memory-firestore.js +2 -0
- package/src/lib/runtime/ai-eval-memory-firestore.js.map +1 -1
- package/src/lib/server/ai-create-route.d.ts +32 -0
- package/src/lib/server/ai-create-route.js +295 -0
- package/src/lib/server/ai-create-route.js.map +1 -0
- package/src/lib/server/ai-jobs-route.js +19 -0
- package/src/lib/server/ai-jobs-route.js.map +1 -1
- package/src/lib/server.js +14 -0
- package/src/lib/server.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-upsell-dialog.component.tsx"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { aiAddonName } from '@aglyn/aglyn'\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport type { ConsoleWidgetUpgrade } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { HelpTip, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState, type ReactNode } from 'react'\nimport type { AiBriefKind } from './ai-brief-dialog.component'\n\n/**\n * \"Create with AI\" on a plan that could buy the AI add-on and has not\n * (AGL-3601). The shell mounts the entry with `entitled={false}` only where\n * the add-on is sold to this workspace, and the jobs route has said the plan\n * is the one thing missing; the entry then keeps its place on the page and\n * opens this instead of the brief, so the reader sees where the add-on is used.\n */\n\n/**\n * What a \"Create with AI\" or \"Ask AI\" entry makes: the brief kinds, an\n * automation, products, a function or variable, an overlay, and an answer\n * about the figures on a page.\n */\nexport type AiUpsellKind =\n | AiBriefKind\n | 'workflow'\n | 'product'\n | 'logic'\n | 'overlay'\n | 'funnel'\n | 'insight'\n\n/** What one entry's dialog says. */\nexport interface AiUpsellCopy {\n title: string\n does: string\n /** The entry's own label, which the button reads and the offer names; \"Create with AI\" when absent. */\n label?: string\n}\n\n/** The label every \"Create with AI\" entry reads. */\nexport const AI_UPSELL_CREATE_LABEL = 'Create with AI'\n\n/** What the dialog says one entry would do here. */\nexport const AI_UPSELL_COPY: Readonly<Record<AiUpsellKind, AiUpsellCopy>> = {\n page: {\n title: 'Create a page with AI',\n does:\n 'Describe the page you need and AI plans it from your brief, using your theme and ' +\n 'what the site already has. You confirm the plan, and the page is built as an ' +\n 'unpublished draft.',\n },\n template: {\n title: 'Create a page template with AI',\n does:\n 'Describe what each page should show and AI plans the template, filling what ' +\n 'changes from page to page from each record. You confirm the plan, and the template ' +\n 'is built as a draft in your library.',\n },\n layout: {\n title: 'Create a layout with AI',\n does:\n 'Describe what the layout should hold and AI plans it, linking your pages and placing ' +\n 'your navigation or menu component when the site has one. You confirm the plan, and ' +\n 'the layout is built as a draft no page uses until you assign it.',\n },\n form: {\n title: 'Create a form with AI',\n does:\n 'Describe what the form is for and AI plans its fields, its marketing consent and ' +\n 'where each submission goes. You confirm the plan, and the form is built as a draft.',\n },\n component: {\n title: 'Create a reusable component with AI',\n does:\n 'Describe what the component should show and AI plans it, making what each page can ' +\n 'change into its properties. You confirm the plan, and the component is built as a ' +\n 'draft.',\n },\n workflow: {\n title: 'Create an automation with AI',\n does:\n 'Describe what should happen, and when, and AI drafts the automation from the ' +\n 'triggers and steps your plan includes. It is saved switched off for you to review.',\n },\n email: {\n title: 'Create an email with AI',\n does:\n 'Describe the email you need and AI plans a design from the email blocks, with three ' +\n 'subject lines and three preheaders to choose between. You confirm the plan, and the ' +\n 'design is built as a draft that is sent to nobody.',\n },\n campaign: {\n title: 'Create a campaign with AI',\n does:\n 'Describe what the campaign is for and AI writes its email design and a draft campaign ' +\n 'that would send it. Nothing is sent or scheduled, and the campaign is aimed at nobody ' +\n 'until you pick its lists.',\n },\n product: {\n title: 'Create products with AI',\n does:\n 'Describe what the store sells and AI proposes up to twelve products. You review them ' +\n 'before any is created, and each is created as a draft at a suggested price that is not on ' +\n 'your storefront until you review it and activate it.',\n },\n logic: {\n title: 'Create a function or variable with AI',\n does:\n 'Describe what it should work out or hold and AI drafts the function or variable. It ' +\n 'opens unsaved in the editor for you to check and save.',\n },\n overlay: {\n title: 'Create an overlay with AI',\n does:\n 'Describe the announcement bar or popup you want and AI writes its copy within the ' +\n 'overlay’s limits. It is saved switched off, for you to check and turn on.',\n },\n funnel: {\n title: 'Create a funnel with AI',\n does:\n 'Describe the journey you want to measure and AI proposes its steps from the site’s ' +\n 'real pages, forms and products. It opens in the funnel editor for you to check and save.',\n },\n insight: {\n title: 'Ask AI about these numbers',\n label: 'Ask a question',\n does:\n 'Ask a question about the figures on this page and AI answers from them, every answer ' +\n 'traced to the figures it is built from. It reads; it changes nothing.',\n },\n}\n\n/** The label an entry's button reads. */\nexport function aiUpsellLabel(kind: AiUpsellKind): string {\n return AI_UPSELL_COPY[kind].label ?? AI_UPSELL_CREATE_LABEL\n}\n\n/**\n * The sentence that says where the feature comes from, and who can add it.\n * `feature` is what the entry is called: \"Create with AI\" unless it is an ask.\n */\nexport function aiUpsellOffer(canManageBilling: boolean, feature = AI_UPSELL_CREATE_LABEL): string {\n const addon = aiAddonName()\n return canManageBilling\n ? `${feature} comes with the ${addon} add-on, which you can add to this ` +\n 'workspace from Billing.'\n : `${feature} comes with the ${addon} add-on. Ask a workspace owner or admin ` +\n 'to add it from Billing.'\n}\n\nexport interface AiUpsellDialogProps {\n kind: AiUpsellKind\n open: boolean\n onClose: () => void\n upgrade: ConsoleWidgetUpgrade\n}\n\n/** The add-on's own section of the Create with AI guide (AGL-3660). */\nconst aiUpsellHelp = () =>\n pluginDocsHelp('aiCreate', {\n anchor: '#without-the-add-on',\n excerpt: `Create with AI needs the ${aiAddonName()} add-on on a paid plan. An owner or admin adds it from Billing; on Free, the monthly AI credits cover it.`,\n })\n\nexport function AiUpsellDialog({ kind, open, onClose, upgrade }: AiUpsellDialogProps) {\n const copy = AI_UPSELL_COPY[kind]\n const { canManageBilling, billingHref } = upgrade\n\n // Counted once per opening: the denominator the click is a rate against.\n useEffect(() => {\n if (open) trackEvent('ai_upsell_shown', { kind, can_manage: canManageBilling })\n }, [open, kind, canManageBilling])\n\n return (\n <Dialog open={open} onClose={onClose} fullWidth maxWidth=\"xs\">\n <DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>\n {copy.title}\n <HelpTip {...aiUpsellHelp()} />\n </DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Typography variant=\"body2\">{copy.does}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {aiUpsellOffer(canManageBilling, kind === 'insight' ? copy.title : AI_UPSELL_CREATE_LABEL)}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{canManageBilling ? 'Not now' : 'Close'}</Button>\n {canManageBilling ? (\n <Button\n variant=\"contained\"\n href={billingHref}\n onClick={() => trackEvent('ai_upsell_clicked', { kind })}\n >\n {`Add ${aiAddonName()}`}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\n/**\n * The entry itself on a plan without the add-on: the same button, label and\n * icon as the entitled entry, opening {@link AiUpsellDialog}. Every entry that\n * draws it renders from the shell's client-side gates alone and asks the\n * server nothing until it is used (AGL-3601).\n */\nexport function AiUpsellButton({\n kind,\n upgrade,\n label,\n}: {\n kind: AiUpsellKind\n upgrade: ConsoleWidgetUpgrade\n /** The entitled entry's own label, when it is not the kind's. */\n label?: string\n}): ReactNode {\n const [open, setOpen] = useState(false)\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {label ?? aiUpsellLabel(kind)}\n </Button>\n <AiUpsellDialog kind={kind} open={open} onClose={() => setOpen(false)} upgrade={upgrade} />\n </>\n )\n}\n"],"names":["aiAddonName","trackEvent","mdiCreation","pluginDocsHelp","HelpTip","MdiIcon","Button","Dialog","DialogActions","DialogContent","DialogTitle","Stack","Typography","useEffect","useState","AI_UPSELL_CREATE_LABEL","AI_UPSELL_COPY","page","title","does","template","layout","form","component","workflow","email","campaign","product","logic","overlay","funnel","insight","label","aiUpsellLabel","kind","aiUpsellOffer","canManageBilling","feature","addon","aiUpsellHelp","anchor","excerpt","AiUpsellDialog","open","onClose","upgrade","copy","billingHref","can_manage","fullWidth","maxWidth","sx","display","alignItems","gap","spacing","variant","color","onClick","href","AiUpsellButton","setOpen","size","startIcon","path"],"mappings":"AAAA;;;AAEA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,WAAW,QAAQ,eAAc;AAC1C,SAASC,UAAU,QAAQ,0CAAyC;AAEpE,SAASC,WAAW,QAAQ,yBAAwB;AACpD,SAASC,cAAc,QAAQ,mCAAkC;AACjE,SAASC,OAAO,EAAEC,OAAO,QAAQ,uBAAsB;AACvD,SACEC,MAAM,EACNC,MAAM,EACNC,aAAa,EACbC,aAAa,EACbC,WAAW,EACXC,KAAK,EACLC,UAAU,QACL,gBAAe;AACtB,SAASC,SAAS,EAAEC,QAAQ,QAAwB,QAAO;AAiC3D,kDAAkD,GAClD,OAAO,MAAMC,yBAAyB,iBAAgB;AAEtD,kDAAkD,GAClD,OAAO,MAAMC,iBAA+D;IAC1EC,MAAM;QACJC,OAAO;QACPC,MACE,sFACA,kFACA;IACJ;IACAC,UAAU;QACRF,OAAO;QACPC,MACE,iFACA,wFACA;IACJ;IACAE,QAAQ;QACNH,OAAO;QACPC,MACE,0FACA,wFACA;IACJ;IACAG,MAAM;QACJJ,OAAO;QACPC,MACE,sFACA;IACJ;IACAI,WAAW;QACTL,OAAO;QACPC,MACE,wFACA,uFACA;IACJ;IACAK,UAAU;QACRN,OAAO;QACPC,MACE,kFACA;IACJ;IACAM,OAAO;QACLP,OAAO;QACPC,MACE,yFACA,yFACA;IACJ;IACAO,UAAU;QACRR,OAAO;QACPC,MACE,2FACA,2FACA;IACJ;IACAQ,SAAS;QACPT,OAAO;QACPC,MACE,0FACA,+FACA;IACJ;IACAS,OAAO;QACLV,OAAO;QACPC,MACE,yFACA;IACJ;IACAU,SAAS;QACPX,OAAO;QACPC,MACE,uFACA;IACJ;IACAW,QAAQ;QACNZ,OAAO;QACPC,MACE,wFACA;IACJ;IACAY,SAAS;QACPb,OAAO;QACPc,OAAO;QACPb,MACE,0FACA;IACJ;AACF,EAAC;AAED,uCAAuC,GACvC,OAAO,SAASc,cAAcC,IAAkB;QACvClB;IAAP,QAAOA,6BAAAA,cAAc,CAACkB,KAAK,CAACF,KAAK,YAA1BhB,6BAA8BD;AACvC;AAEA;;;CAGC,GACD,OAAO,SAASoB,cAAcC,gBAAyB,EAAEC,UAAUtB,sBAAsB;IACvF,MAAMuB,QAAQtC;IACd,OAAOoC,mBACH,GAAGC,QAAQ,gBAAgB,EAAEC,MAAM,mCAAmC,CAAC,GACrE,4BACF,GAAGD,QAAQ,gBAAgB,EAAEC,MAAM,wCAAwC,CAAC,GAC1E;AACR;AASA,qEAAqE,GACrE,MAAMC,eAAe,IACnBpC,eAAe,YAAY;QACzBqC,QAAQ;QACRC,SAAS,CAAC,yBAAyB,EAAEzC,cAAc,yGAAyG,CAAC;IAC/J;AAEF,OAAO,SAAS0C,eAAe,EAAER,IAAI,EAAES,IAAI,EAAEC,OAAO,EAAEC,OAAO,EAAuB;IAClF,MAAMC,OAAO9B,cAAc,CAACkB,KAAK;IACjC,MAAM,EAAEE,gBAAgB,EAAEW,WAAW,EAAE,GAAGF;IAE1C,yEAAyE;IACzEhC,UAAU;QACR,IAAI8B,MAAM1C,WAAW,mBAAmB;YAAEiC;YAAMc,YAAYZ;QAAiB;IAC/E,GAAG;QAACO;QAAMT;QAAME;KAAiB;IAEjC,qBACE,MAAC7B;QAAOoC,MAAMA;QAAMC,SAASA;QAASK,SAAS;QAACC,UAAS;;0BACvD,MAACxC;gBAAYyC,IAAI;oBAAEC,SAAS;oBAAQC,YAAY;oBAAUC,KAAK;gBAAI;;oBAChER,KAAK5B,KAAK;kCACX,KAACd,sBAAYmC;;;0BAEf,KAAC9B;0BACC,cAAA,MAACE;oBAAM4C,SAAS;;sCACd,KAAC3C;4BAAW4C,SAAQ;sCAASV,KAAK3B,IAAI;;sCACtC,KAACP;4BAAW4C,SAAQ;4BAAQC,OAAM;sCAC/BtB,cAAcC,kBAAkBF,SAAS,YAAYY,KAAK5B,KAAK,GAAGH;;;;;0BAIzE,MAACP;;kCACC,KAACF;wBAAOoD,SAASd;kCAAUR,mBAAmB,YAAY;;oBACzDA,iCACC,KAAC9B;wBACCkD,SAAQ;wBACRG,MAAMZ;wBACNW,SAAS,IAAMzD,WAAW,qBAAqB;gCAAEiC;4BAAK;kCAErD,CAAC,IAAI,EAAElC,eAAe;yBAEvB;;;;;AAIZ;AAEA;;;;;CAKC,GACD,OAAO,SAAS4D,eAAe,EAC7B1B,IAAI,EACJW,OAAO,EACPb,KAAK,EAMN;IACC,MAAM,CAACW,MAAMkB,QAAQ,GAAG/C,SAAS;IACjC,qBACE;;0BACE,KAACR;gBACCwD,MAAK;gBACLN,SAAQ;gBACRO,yBAAW,KAAC1D;oBAAQ2D,MAAM9D,YAAY8D,IAAI;;gBAC1CN,SAAS,IAAMG,QAAQ;0BAEtB7B,gBAAAA,QAASC,cAAcC;;0BAE1B,KAACQ;gBAAeR,MAAMA;gBAAMS,MAAMA;gBAAMC,SAAS,IAAMiB,QAAQ;gBAAQhB,SAASA;;;;AAGtF"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-upsell-dialog.component.tsx"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { aiAddonName } from '@aglyn/aglyn'\nimport { trackEvent } from '@aglyn/aglyn/app-utils/analytics-events'\nimport type { ConsoleWidgetUpgrade } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { HelpTip, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport {\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState, type ReactNode } from 'react'\nimport {\n AI_UPSELL_COPY,\n AI_UPSELL_CREATE_LABEL,\n aiUpsellLabel,\n aiUpsellOffer,\n type AiUpsellKind,\n} from '../model/ai-upsell-copy'\n\n/**\n * \"Create with AI\" on a plan that could buy the AI add-on and has not\n * (AGL-3601). The shell mounts the entry with `entitled={false}` only where\n * the add-on is sold to this workspace, and the jobs route has said the plan\n * is the one thing missing; the entry then keeps its place on the page and\n * opens this instead of the brief, so the reader sees where the add-on is used.\n */\n\n// The entries' words are the model's, which the native apps' door reads too\n// (AGL-3854); re-exported for this dialog's existing readers.\nexport { AI_UPSELL_COPY, AI_UPSELL_CREATE_LABEL, aiUpsellLabel, aiUpsellOffer }\nexport type { AiUpsellCopy, AiUpsellKind } from '../model/ai-upsell-copy'\n\nexport interface AiUpsellDialogProps {\n kind: AiUpsellKind\n open: boolean\n onClose: () => void\n upgrade: ConsoleWidgetUpgrade\n}\n\n/** The add-on's own section of the Create with AI guide (AGL-3660). */\nconst aiUpsellHelp = () =>\n pluginDocsHelp('aiCreate', {\n anchor: '#without-the-add-on',\n excerpt: `Create with AI needs the ${aiAddonName()} add-on on a paid plan. An owner or admin adds it from Billing; on Free, the monthly AI credits cover it.`,\n })\n\nexport function AiUpsellDialog({ kind, open, onClose, upgrade }: AiUpsellDialogProps) {\n const copy = AI_UPSELL_COPY[kind]\n const { canManageBilling, billingHref } = upgrade\n\n // Counted once per opening: the denominator the click is a rate against.\n useEffect(() => {\n if (open) trackEvent('ai_upsell_shown', { kind, can_manage: canManageBilling })\n }, [open, kind, canManageBilling])\n\n return (\n <Dialog open={open} onClose={onClose} fullWidth maxWidth=\"xs\">\n <DialogTitle sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>\n {copy.title}\n <HelpTip {...aiUpsellHelp()} />\n </DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Typography variant=\"body2\">{copy.does}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {aiUpsellOffer(canManageBilling, kind === 'insight' ? copy.title : AI_UPSELL_CREATE_LABEL)}\n </Typography>\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{canManageBilling ? 'Not now' : 'Close'}</Button>\n {canManageBilling ? (\n <Button\n variant=\"contained\"\n href={billingHref}\n onClick={() => trackEvent('ai_upsell_clicked', { kind })}\n >\n {`Add ${aiAddonName()}`}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\n/**\n * The entry itself on a plan without the add-on: the same button, label and\n * icon as the entitled entry, opening {@link AiUpsellDialog}. Every entry that\n * draws it renders from the shell's client-side gates alone and asks the\n * server nothing until it is used (AGL-3601).\n */\nexport function AiUpsellButton({\n kind,\n upgrade,\n label,\n}: {\n kind: AiUpsellKind\n upgrade: ConsoleWidgetUpgrade\n /** The entitled entry's own label, when it is not the kind's. */\n label?: string\n}): ReactNode {\n const [open, setOpen] = useState(false)\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {label ?? aiUpsellLabel(kind)}\n </Button>\n <AiUpsellDialog kind={kind} open={open} onClose={() => setOpen(false)} upgrade={upgrade} />\n </>\n )\n}\n"],"names":["aiAddonName","trackEvent","mdiCreation","pluginDocsHelp","HelpTip","MdiIcon","Button","Dialog","DialogActions","DialogContent","DialogTitle","Stack","Typography","useEffect","useState","AI_UPSELL_COPY","AI_UPSELL_CREATE_LABEL","aiUpsellLabel","aiUpsellOffer","aiUpsellHelp","anchor","excerpt","AiUpsellDialog","kind","open","onClose","upgrade","copy","canManageBilling","billingHref","can_manage","fullWidth","maxWidth","sx","display","alignItems","gap","title","spacing","variant","does","color","onClick","href","AiUpsellButton","label","setOpen","size","startIcon","path"],"mappings":"AAAA;;;AAEA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,WAAW,QAAQ,eAAc;AAC1C,SAASC,UAAU,QAAQ,0CAAyC;AAEpE,SAASC,WAAW,QAAQ,yBAAwB;AACpD,SAASC,cAAc,QAAQ,mCAAkC;AACjE,SAASC,OAAO,EAAEC,OAAO,QAAQ,uBAAsB;AACvD,SACEC,MAAM,EACNC,MAAM,EACNC,aAAa,EACbC,aAAa,EACbC,WAAW,EACXC,KAAK,EACLC,UAAU,QACL,gBAAe;AACtB,SAASC,SAAS,EAAEC,QAAQ,QAAwB,QAAO;AAC3D,SACEC,cAAc,EACdC,sBAAsB,EACtBC,aAAa,EACbC,aAAa,QAER,6BAAyB;AAEhC;;;;;;CAMC,GAED,4EAA4E;AAC5E,8DAA8D;AAC9D,SAASH,cAAc,EAAEC,sBAAsB,EAAEC,aAAa,EAAEC,aAAa,GAAE;AAU/E,qEAAqE,GACrE,MAAMC,eAAe,IACnBhB,eAAe,YAAY;QACzBiB,QAAQ;QACRC,SAAS,CAAC,yBAAyB,EAAErB,cAAc,yGAAyG,CAAC;IAC/J;AAEF,OAAO,SAASsB,eAAe,EAAEC,IAAI,EAAEC,IAAI,EAAEC,OAAO,EAAEC,OAAO,EAAuB;IAClF,MAAMC,OAAOZ,cAAc,CAACQ,KAAK;IACjC,MAAM,EAAEK,gBAAgB,EAAEC,WAAW,EAAE,GAAGH;IAE1C,yEAAyE;IACzEb,UAAU;QACR,IAAIW,MAAMvB,WAAW,mBAAmB;YAAEsB;YAAMO,YAAYF;QAAiB;IAC/E,GAAG;QAACJ;QAAMD;QAAMK;KAAiB;IAEjC,qBACE,MAACrB;QAAOiB,MAAMA;QAAMC,SAASA;QAASM,SAAS;QAACC,UAAS;;0BACvD,MAACtB;gBAAYuB,IAAI;oBAAEC,SAAS;oBAAQC,YAAY;oBAAUC,KAAK;gBAAI;;oBAChET,KAAKU,KAAK;kCACX,KAACjC,sBAAYe;;;0BAEf,KAACV;0BACC,cAAA,MAACE;oBAAM2B,SAAS;;sCACd,KAAC1B;4BAAW2B,SAAQ;sCAASZ,KAAKa,IAAI;;sCACtC,KAAC5B;4BAAW2B,SAAQ;4BAAQE,OAAM;sCAC/BvB,cAAcU,kBAAkBL,SAAS,YAAYI,KAAKU,KAAK,GAAGrB;;;;;0BAIzE,MAACR;;kCACC,KAACF;wBAAOoC,SAASjB;kCAAUG,mBAAmB,YAAY;;oBACzDA,iCACC,KAACtB;wBACCiC,SAAQ;wBACRI,MAAMd;wBACNa,SAAS,IAAMzC,WAAW,qBAAqB;gCAAEsB;4BAAK;kCAErD,CAAC,IAAI,EAAEvB,eAAe;yBAEvB;;;;;AAIZ;AAEA;;;;;CAKC,GACD,OAAO,SAAS4C,eAAe,EAC7BrB,IAAI,EACJG,OAAO,EACPmB,KAAK,EAMN;IACC,MAAM,CAACrB,MAAMsB,QAAQ,GAAGhC,SAAS;IACjC,qBACE;;0BACE,KAACR;gBACCyC,MAAK;gBACLR,SAAQ;gBACRS,yBAAW,KAAC3C;oBAAQ4C,MAAM/C,YAAY+C,IAAI;;gBAC1CP,SAAS,IAAMI,QAAQ;0BAEtBD,gBAAAA,QAAS5B,cAAcM;;0BAE1B,KAACD;gBAAeC,MAAMA;gBAAMC,MAAMA;gBAAMC,SAAS,IAAMqB,QAAQ;gBAAQpB,SAASA;;;;AAGtF"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token';
|
|
2
|
-
import {
|
|
2
|
+
import type { AiJobKind, AiJobSummary } from '../model/ai-jobs.types';
|
|
3
|
+
import { aiJobProblem, aiJobSettled } from '../model/ai-job-activity';
|
|
3
4
|
/**
|
|
4
5
|
* Starting one AI job from a console control, and following it until it
|
|
5
6
|
* settles (AGL-2919): the jobs route's create door, then its events door, the
|
|
@@ -16,13 +17,7 @@ export interface AiJobRequest {
|
|
|
16
17
|
brief: string;
|
|
17
18
|
inputs: Record<string, string>;
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
-
export declare function aiJobSettled(job: AiJobSummary | null): boolean;
|
|
21
|
-
/**
|
|
22
|
-
* Why a settled job has no result to show, in words a person can act on, or
|
|
23
|
-
* `null` for a job that finished.
|
|
24
|
-
*/
|
|
25
|
-
export declare function aiJobProblem(job: AiJobSummary | null, failed: string): string | null;
|
|
20
|
+
export { aiJobProblem, aiJobSettled };
|
|
26
21
|
export interface AiJobRun {
|
|
27
22
|
/** The job as it last arrived; `null` before one is started. */
|
|
28
23
|
job: AiJobSummary | null;
|
|
@@ -17,23 +17,12 @@
|
|
|
17
17
|
*/ import { lockdownRefusalText, parseLockdownRefusal } from "@aglyn/aglyn";
|
|
18
18
|
import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
19
19
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
20
|
-
import {
|
|
20
|
+
import { aiJobProblem, aiJobSettled } from "../model/ai-job-activity.js";
|
|
21
21
|
import { followAiJobEvents } from "./ai-job-events.js";
|
|
22
22
|
import { publishAiJob } from "./ai-jobs-store.js";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Why a settled job has no result to show, in words a person can act on, or
|
|
29
|
-
* `null` for a job that finished.
|
|
30
|
-
*/ export function aiJobProblem(job, failed) {
|
|
31
|
-
var _job_review;
|
|
32
|
-
if (!job || !aiJobSettled(job) || job.status === 'done') return null;
|
|
33
|
-
if (job.status === 'canceled') return 'The job was canceled.';
|
|
34
|
-
if (job.status === 'needs_review') return ((_job_review = job.review) == null ? void 0 : _job_review.message) || job.error || failed;
|
|
35
|
-
return job.error || failed;
|
|
36
|
-
}
|
|
23
|
+
// Whether a job settled, and why it has nothing to show, are the model's
|
|
24
|
+
// (AGL-3854), which the native apps' follow view reads too.
|
|
25
|
+
export { aiJobProblem, aiJobSettled };
|
|
37
26
|
export function useAiJobRun(user, failed) {
|
|
38
27
|
const userRef = useRef(user);
|
|
39
28
|
userRef.current = user;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/use-ai-job-run.ts"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport {\n authorizedFetch,\n type MaybeTokenSource,\n} from '@aglyn/shared-util-http/authorized-token'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/use-ai-job-run.ts"],"sourcesContent":["'use client'\n\n/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport {\n authorizedFetch,\n type MaybeTokenSource,\n} from '@aglyn/shared-util-http/authorized-token'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport type { AiJobKind, AiJobSummary } from '../model/ai-jobs.types'\nimport { aiJobProblem, aiJobSettled } from '../model/ai-job-activity'\nimport { followAiJobEvents } from './ai-job-events'\nimport { publishAiJob } from './ai-jobs-store'\n\n/**\n * Starting one AI job from a console control, and following it until it\n * settles (AGL-2919): the jobs route's create door, then its events door, the\n * way the SEO card follows its jobs. A control that starts a job the beat\n * runs shows its progress here and its result when it arrives; closing the\n * control stops following, and the job carries on in AI jobs.\n */\n\n/** What a control asks the create door for. */\nexport interface AiJobRequest {\n orgId: string\n /** `null` for a job about the workspace rather than one site. */\n hostId: string | null\n kind: AiJobKind\n brief: string\n inputs: Record<string, string>\n}\n\n// Whether a job settled, and why it has nothing to show, are the model's\n// (AGL-3854), which the native apps' follow view reads too.\nexport { aiJobProblem, aiJobSettled }\n\nexport interface AiJobRun {\n /** The job as it last arrived; `null` before one is started. */\n job: AiJobSummary | null\n /** The create door is being asked. */\n starting: boolean\n /** The job exists and has not settled. */\n running: boolean\n /** Why the create door refused, or could not be reached. */\n notice: string | null\n /** Starts the job, `true` once it exists, and follows it from there; `false` when the door refused. */\n start: (request: AiJobRequest) => Promise<boolean>\n /** Stops following and forgets the job, for a control asked again. */\n reset: () => void\n}\n\nexport function useAiJobRun(user: MaybeTokenSource, failed: string): AiJobRun {\n const userRef = useRef(user)\n userRef.current = user\n const [job, setJob] = useState<AiJobSummary | null>(null)\n const [starting, setStarting] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n const watchRef = useRef<AbortController | null>(null)\n\n useEffect(() => () => watchRef.current?.abort(), [])\n\n /** Follows a job through the events route until it settles or the control goes. */\n const watch = useCallback(async (jobId: string, orgId: string) => {\n watchRef.current?.abort()\n const controller = new AbortController()\n watchRef.current = controller\n await followAiJobEvents(() => userRef.current, orgId, jobId, controller.signal, setJob)\n }, [])\n\n const start = useCallback(\n async (request: AiJobRequest) => {\n watchRef.current?.abort()\n setStarting(true)\n setNotice(null)\n setJob(null)\n let next: AiJobSummary | undefined\n try {\n const response = await authorizedFetch(userRef.current, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(request),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n setNotice(locked ? lockdownRefusalText(locked) : String(payload?.error ?? failed))\n return false\n }\n next = payload?.job as AiJobSummary | undefined\n if (!next) {\n setNotice(failed)\n return false\n }\n setJob(next)\n // The indicator counts it from the moment it exists (AGL-3593).\n publishAiJob(next)\n } catch {\n setNotice(failed)\n return false\n } finally {\n setStarting(false)\n }\n if (!aiJobSettled(next)) void watch(next.id, request.orgId)\n return true\n },\n [failed, watch],\n )\n\n const reset = useCallback(() => {\n watchRef.current?.abort()\n setJob(null)\n setNotice(null)\n }, [])\n\n return { job, starting, running: Boolean(job && !aiJobSettled(job)), notice, start, reset }\n}\n"],"names":["lockdownRefusalText","parseLockdownRefusal","authorizedFetch","useCallback","useEffect","useRef","useState","aiJobProblem","aiJobSettled","followAiJobEvents","publishAiJob","useAiJobRun","user","failed","userRef","current","job","setJob","starting","setStarting","notice","setNotice","watchRef","abort","watch","jobId","orgId","controller","AbortController","signal","start","request","next","response","method","headers","body","JSON","stringify","payload","json","catch","ok","locked","status","String","error","id","reset","running","Boolean"],"mappings":"AAAA;AAEA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,mBAAmB,EAAEC,oBAAoB,QAAQ,eAAc;AACxE,SACEC,eAAe,QAEV,2CAA0C;AACjD,SAASC,WAAW,EAAEC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AAEhE,SAASC,YAAY,EAAEC,YAAY,QAAQ,8BAA0B;AACrE,SAASC,iBAAiB,QAAQ,qBAAiB;AACnD,SAASC,YAAY,QAAQ,qBAAiB;AAoB9C,yEAAyE;AACzE,4DAA4D;AAC5D,SAASH,YAAY,EAAEC,YAAY,GAAE;AAiBrC,OAAO,SAASG,YAAYC,IAAsB,EAAEC,MAAc;IAChE,MAAMC,UAAUT,OAAOO;IACvBE,QAAQC,OAAO,GAAGH;IAClB,MAAM,CAACI,KAAKC,OAAO,GAAGX,SAA8B;IACpD,MAAM,CAACY,UAAUC,YAAY,GAAGb,SAAS;IACzC,MAAM,CAACc,QAAQC,UAAU,GAAGf,SAAwB;IACpD,MAAMgB,WAAWjB,OAA+B;IAEhDD,UAAU,IAAM;gBAAMkB;oBAAAA,oBAAAA,SAASP,OAAO,qBAAhBO,kBAAkBC,KAAK;WAAI,EAAE;IAEnD,iFAAiF,GACjF,MAAMC,QAAQrB,YAAY,OAAOsB,OAAeC;YAC9CJ;SAAAA,oBAAAA,SAASP,OAAO,qBAAhBO,kBAAkBC,KAAK;QACvB,MAAMI,aAAa,IAAIC;QACvBN,SAASP,OAAO,GAAGY;QACnB,MAAMlB,kBAAkB,IAAMK,QAAQC,OAAO,EAAEW,OAAOD,OAAOE,WAAWE,MAAM,EAAEZ;IAClF,GAAG,EAAE;IAEL,MAAMa,QAAQ3B,YACZ,OAAO4B;YACLT;SAAAA,oBAAAA,SAASP,OAAO,qBAAhBO,kBAAkBC,KAAK;QACvBJ,YAAY;QACZE,UAAU;QACVJ,OAAO;QACP,IAAIe;QACJ,IAAI;YACF,MAAMC,WAAW,MAAM/B,gBAAgBY,QAAQC,OAAO,EAAE,gBAAgB;gBACtEmB,QAAQ;gBACRC,SAAS;oBAAE,gBAAgB;gBAAmB;gBAC9CC,MAAMC,KAAKC,SAAS,CAACP;YACvB;YACA,MAAMQ,UAAU,MAAMN,SAASO,IAAI,GAAGC,KAAK,CAAC,IAAM;YAClD,IAAI,CAACR,SAASS,EAAE,EAAE;;gBAChB,MAAMC,SAAS1C,qBAAqBgC,SAASW,MAAM,EAAEL;gBACrDlB,UAAUsB,SAAS3C,oBAAoB2C,UAAUE,eAAON,2BAAAA,QAASO,KAAK,mBAAIjC;gBAC1E,OAAO;YACT;YACAmB,OAAOO,2BAAAA,QAASvB,GAAG;YACnB,IAAI,CAACgB,MAAM;gBACTX,UAAUR;gBACV,OAAO;YACT;YACAI,OAAOe;YACP,gEAAgE;YAChEtB,aAAasB;QACf,EAAE,eAAM;YACNX,UAAUR;YACV,OAAO;QACT,SAAU;YACRM,YAAY;QACd;QACA,IAAI,CAACX,aAAawB,OAAO,KAAKR,MAAMQ,KAAKe,EAAE,EAAEhB,QAAQL,KAAK;QAC1D,OAAO;IACT,GACA;QAACb;QAAQW;KAAM;IAGjB,MAAMwB,QAAQ7C,YAAY;YACxBmB;SAAAA,oBAAAA,SAASP,OAAO,qBAAhBO,kBAAkBC,KAAK;QACvBN,OAAO;QACPI,UAAU;IACZ,GAAG,EAAE;IAEL,OAAO;QAAEL;QAAKE;QAAU+B,SAASC,QAAQlC,OAAO,CAACR,aAAaQ;QAAOI;QAAQU;QAAOkB;IAAM;AAC5F"}
|
|
@@ -17,6 +17,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
17
17
|
*/ import { formPeriodKey, formStatsTotals } from "@aglyn/aglyn/app-utils/forms";
|
|
18
18
|
import { PLUGIN_FIGURE_MAX_ROWS, pluginFigureChange, pluginFigureDay, pluginFigureWindows, registerPluginFigureReader } from "@aglyn/aglyn/plugin-manager/plugin-figures";
|
|
19
19
|
import { AI_PLUGIN_ID } from "../constants.js";
|
|
20
|
+
import { FORMS_WEEKLY_SUMMARY, SOURCES_WEEKLY_SUMMARY, TOP_PAGES_WEEKLY_SUMMARY, TRAFFIC_WEEKLY_SUMMARY } from "./ai-figure-summaries.js";
|
|
20
21
|
/** The traffic windows: the Traffic card's own ranges. */ export const AI_TRAFFIC_WINDOWS = [
|
|
21
22
|
7,
|
|
22
23
|
14,
|
|
@@ -129,7 +130,8 @@ export function trafficFigureReaders(firestore) {
|
|
|
129
130
|
id: 'traffic.summary',
|
|
130
131
|
label: 'Traffic',
|
|
131
132
|
description: 'Page views, visitors and views a day on the site over the window, each beside the window before it and the change.',
|
|
132
|
-
windows: AI_TRAFFIC_WINDOWS
|
|
133
|
+
windows: AI_TRAFFIC_WINDOWS,
|
|
134
|
+
weeklySummary: TRAFFIC_WEEKLY_SUMMARY
|
|
133
135
|
}, async (db, request)=>{
|
|
134
136
|
const ids = dayIds(request.now, request.days);
|
|
135
137
|
const [current, previous] = await Promise.all([
|
|
@@ -205,7 +207,8 @@ export function trafficFigureReaders(firestore) {
|
|
|
205
207
|
id: 'traffic.pages',
|
|
206
208
|
label: 'Top pages',
|
|
207
209
|
description: `The site's ${AI_TRAFFIC_TOP_PAGES} most viewed pages over the window, with each page's share of all views and its change from the window before.`,
|
|
208
|
-
windows: AI_TRAFFIC_WINDOWS
|
|
210
|
+
windows: AI_TRAFFIC_WINDOWS,
|
|
211
|
+
weeklySummary: TOP_PAGES_WEEKLY_SUMMARY
|
|
209
212
|
}, async (db, request)=>{
|
|
210
213
|
const ids = dayIds(request.now, request.days);
|
|
211
214
|
const [current, previous] = await Promise.all([
|
|
@@ -262,11 +265,17 @@ export function trafficFigureReaders(firestore) {
|
|
|
262
265
|
id: 'traffic.sources',
|
|
263
266
|
label: 'Traffic sources',
|
|
264
267
|
description: 'Where visits came from over the window: the referring sites, and the campaign sources and campaigns links were tagged with, with each one’s views.',
|
|
265
|
-
windows: AI_TRAFFIC_WINDOWS
|
|
268
|
+
windows: AI_TRAFFIC_WINDOWS,
|
|
269
|
+
weeklySummary: SOURCES_WEEKLY_SUMMARY
|
|
266
270
|
}, async (db, request)=>{
|
|
267
271
|
const days = await readTrafficDays(db, request.hostId, dayIds(request.now, request.days).current);
|
|
268
272
|
const all = days.reduce((sum, day)=>sum + day.total, 0);
|
|
269
|
-
|
|
273
|
+
// The collector stores a referring host with its dots as `_`, because a
|
|
274
|
+
// Firestore field path cannot hold one; a host is read back as written.
|
|
275
|
+
const referrers = ranked(sumMaps(days, (day)=>day.referrers)).map(([host, views])=>[
|
|
276
|
+
host.replace(/_/g, '.'),
|
|
277
|
+
views
|
|
278
|
+
]);
|
|
270
279
|
const sources = ranked(sumMaps(days, (day)=>{
|
|
271
280
|
var _day_utm_source;
|
|
272
281
|
return (_day_utm_source = day.utm['source']) != null ? _day_utm_source : {};
|
|
@@ -375,7 +384,8 @@ export function formsFigureReader(firestore) {
|
|
|
375
384
|
id: 'forms.performance',
|
|
376
385
|
label: 'Forms',
|
|
377
386
|
description: 'Each form’s views, submissions, the share of views that became a submission, and leads, over the calendar months the window falls in.',
|
|
378
|
-
windows: AI_TRAFFIC_WINDOWS
|
|
387
|
+
windows: AI_TRAFFIC_WINDOWS,
|
|
388
|
+
weeklySummary: FORMS_WEEKLY_SUMMARY
|
|
379
389
|
}, async (db, request)=>{
|
|
380
390
|
const { current } = pluginFigureWindows(request.now, request.days);
|
|
381
391
|
const from = formPeriodKey(current.startMs);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/insights/ai-figure-readers.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 { formPeriodKey, formStatsTotals, type FormStats } from '@aglyn/aglyn/app-utils/forms'\nimport {\n PLUGIN_FIGURE_MAX_ROWS,\n pluginFigureChange,\n pluginFigureDay,\n pluginFigureWindows,\n registerPluginFigureReader,\n type PluginFigureReader,\n type PluginFigureRead,\n type PluginFigureRequest,\n type PluginFigureRow,\n type PluginFigureTable,\n} from '@aglyn/aglyn/plugin-manager/plugin-figures'\nimport { AI_PLUGIN_ID } from '../constants'\n\n/**\n * The figure readers this plugin registers for records the PLATFORM keeps\n * (AGL-2915): a site's page-view counters and its forms' counters. Every\n * other reader an insight reads is registered by the plugin that owns its\n * records — commerce, bookings, marketing, the workspace's datasets — on the\n * `plugin-figures` seam, and nothing here reads their documents.\n *\n * Each reader answers with one compact table of aggregates, read-only and\n * about nobody: counts, sums and rates, labeled by a page path, a form's name\n * or a referring site — never a visitor or a submission.\n *\n * The readers use the arithmetic their console cards use: the Traffic card's\n * windows and change (`pluginFigureWindows`, `pluginFigureChange`, which are\n * that card's split and delta), and the forms plugin's month counters through\n * `formStatsTotals`, the one function every forms surface windows by.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** How a reader reaches Firestore: resolved when it reads, never when it registers. */\nexport type AiFigureFirestore = () => Firestore\n\n/** The traffic windows: the Traffic card's own ranges. */\nexport const AI_TRAFFIC_WINDOWS: readonly number[] = [7, 14, 30, 90]\n\n/** A day-by-day table fits a table's row bound only over a short window. */\nexport const AI_TRAFFIC_DAILY_WINDOWS: readonly number[] = [7, 14]\n\n/** Pages a pages table lists; the rest are counted in `omitted`. */\nexport const AI_TRAFFIC_TOP_PAGES = 10\n\n/** Referring sites, and campaign sources and campaigns, a sources table lists. */\nexport const AI_TRAFFIC_TOP_REFERRERS = 8\nexport const AI_TRAFFIC_TOP_CAMPAIGNS = 5\n\n/** Forms a forms table reads. */\nexport const AI_FORMS_READ_LIMIT = 100\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\nconst num = (value: unknown): number => {\n const parsed = typeof value === 'number' ? value : Number(value)\n return Number.isFinite(parsed) ? parsed : 0\n}\n\nconst round1 = (value: number): number => Math.round(value * 10) / 10\n\nconst share = (part: number, whole: number): number | null => (whole > 0 ? round1((part / whole) * 100) : null)\n\nfunction refused(status: 400 | 403 | 404, error: string): PluginFigureRead {\n return { ok: false, status, error }\n}\n\n/** A window's days as the table's period, oldest to newest. */\nfunction periodOf(request: PluginFigureRequest): PluginFigureTable['period'] {\n const { current } = pluginFigureWindows(request.now, request.days)\n return { from: current.from, to: current.to, days: request.days }\n}\n\n/** Every day id of the window and the one before it, newest first. */\nfunction dayIds(now: Date, days: number): { current: string[]; previous: string[] } {\n const { current, previous } = pluginFigureWindows(now, days)\n const walk = (startMs: number, endMs: number): string[] => {\n const ids: string[] = []\n for (let at = endMs - 86_400_000; at >= startMs; at -= 86_400_000) ids.push(pluginFigureDay(new Date(at)))\n return ids\n }\n return { current: walk(current.startMs, current.endMs), previous: walk(previous.startMs, previous.endMs) }\n}\n\n// ── Traffic ───────────────────────────────────────────────────────────────\n\ninterface TrafficDay {\n total: number\n visitors: number\n paths: Record<string, number>\n referrers: Record<string, number>\n utm: Record<string, Record<string, number>>\n}\n\nconst counts = (value: unknown): Record<string, number> => {\n const out: Record<string, number> = {}\n if (!value || typeof value !== 'object') return out\n for (const [key, count] of Object.entries(value as Record<string, unknown>)) {\n const n = num(count)\n if (n > 0) out[key] = n\n }\n return out\n}\n\nasync function readTrafficDays(\n firestore: Firestore,\n hostId: string,\n ids: readonly string[],\n): Promise<TrafficDay[]> {\n if (!ids.length) return []\n const collection = firestore.collection('hosts').doc(hostId).collection('analytics')\n const snapshots = await firestore.getAll(...ids.map((id) => collection.doc(id)))\n return snapshots.map((snapshot) => {\n const data = (snapshot.exists ? snapshot.data() : null) ?? {}\n const utm: Record<string, Record<string, number>> = {}\n for (const [param, values] of Object.entries((data['utm'] ?? {}) as Record<string, unknown>)) {\n utm[param] = counts(values)\n }\n return {\n total: num(data['total']),\n visitors: num(data['visitors']),\n paths: counts(data['paths']),\n referrers: counts(data['referrers']),\n utm,\n }\n })\n}\n\nfunction sumMaps(days: readonly TrafficDay[], pick: (day: TrafficDay) => Record<string, number>): Map<string, number> {\n const totals = new Map<string, number>()\n for (const day of days) {\n for (const [key, count] of Object.entries(pick(day))) totals.set(key, (totals.get(key) ?? 0) + count)\n }\n return totals\n}\n\nconst ranked = (totals: Map<string, number>): Array<[string, number]> =>\n [...totals.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))\n\nfunction siteReader(\n base: Omit<PluginFigureReader, 'read' | 'scope' | 'plugin'>,\n read: (firestore: Firestore, request: PluginFigureRequest & { hostId: string }) => Promise<PluginFigureRead>,\n firestore: AiFigureFirestore,\n plugin: string | null,\n): PluginFigureReader {\n return {\n ...base,\n scope: 'site',\n plugin,\n read: async (request) => {\n if (!request.hostId) return refused(400, 'Open a site to read its figures')\n if (!base.windows.includes(request.days)) return refused(400, 'That window is not one this reader covers')\n return read(firestore(), { ...request, hostId: request.hostId })\n },\n }\n}\n\nexport function trafficFigureReaders(firestore: AiFigureFirestore): PluginFigureReader[] {\n const source = { label: 'Analytics', path: 'analytics' }\n const visitorsNote =\n 'Visitors are counted once per browser tab per day, so one person on two days counts twice.'\n return [\n siteReader(\n {\n id: 'traffic.summary',\n label: 'Traffic',\n description:\n 'Page views, visitors and views a day on the site over the window, each beside the window before it and the change.',\n windows: AI_TRAFFIC_WINDOWS,\n },\n async (db, request) => {\n const ids = dayIds(request.now, request.days)\n const [current, previous] = await Promise.all([\n readTrafficDays(db, request.hostId, ids.current),\n readTrafficDays(db, request.hostId, ids.previous),\n ])\n const total = (days: TrafficDay[]) => days.reduce((sum, day) => sum + day.total, 0)\n const visitors = (days: TrafficDay[]) => days.reduce((sum, day) => sum + day.visitors, 0)\n const views = total(current)\n const viewsBefore = total(previous)\n const perDay = Math.round(views / request.days)\n const perDayBefore = Math.round(viewsBefore / request.days)\n const table: PluginFigureTable = {\n title: 'Traffic',\n source,\n period: periodOf(request),\n columns: [\n { key: 'figure', label: 'Figure', kind: 'text' },\n { key: 'current', label: 'This window', kind: 'count' },\n { key: 'previous', label: 'Window before', kind: 'count' },\n { key: 'change', label: 'Change', kind: 'change' },\n ],\n rows: [\n { figure: 'Page views', current: views, previous: viewsBefore, change: pluginFigureChange(views, viewsBefore) },\n {\n figure: 'Visitors',\n current: visitors(current),\n previous: visitors(previous),\n change: pluginFigureChange(visitors(current), visitors(previous)),\n },\n {\n figure: 'Page views a day',\n current: perDay,\n previous: perDayBefore,\n change: pluginFigureChange(perDay, perDayBefore),\n },\n ],\n omitted: 0,\n notes: [\n visitorsNote,\n ...(viewsBefore ? [] : ['The window before this one recorded no page views, so there is no change to compare.']),\n ],\n }\n return { ok: true, table }\n },\n firestore,\n null,\n ),\n siteReader(\n {\n id: 'traffic.pages',\n label: 'Top pages',\n description: `The site's ${AI_TRAFFIC_TOP_PAGES} most viewed pages over the window, with each page's share of all views and its change from the window before.`,\n windows: AI_TRAFFIC_WINDOWS,\n },\n async (db, request) => {\n const ids = dayIds(request.now, request.days)\n const [current, previous] = await Promise.all([\n readTrafficDays(db, request.hostId, ids.current),\n readTrafficDays(db, request.hostId, ids.previous),\n ])\n const now = ranked(sumMaps(current, (day) => day.paths))\n const before = sumMaps(previous, (day) => day.paths)\n const all = now.reduce((sum, [, count]) => sum + count, 0)\n const rows: PluginFigureRow[] = now.slice(0, AI_TRAFFIC_TOP_PAGES).map(([path, views]) => ({\n page: path,\n views,\n share: share(views, all),\n change: pluginFigureChange(views, before.get(path) ?? null),\n }))\n return {\n ok: true,\n table: {\n title: 'Top pages',\n source,\n period: periodOf(request),\n columns: [\n { key: 'page', label: 'Page', kind: 'text' },\n { key: 'views', label: 'Views', kind: 'count' },\n { key: 'share', label: 'Share of views', kind: 'percent' },\n { key: 'change', label: 'Change', kind: 'change' },\n ],\n rows,\n omitted: Math.max(0, now.length - rows.length),\n notes: [],\n },\n }\n },\n firestore,\n null,\n ),\n siteReader(\n {\n id: 'traffic.sources',\n label: 'Traffic sources',\n description:\n 'Where visits came from over the window: the referring sites, and the campaign sources and campaigns links were tagged with, with each one’s views.',\n windows: AI_TRAFFIC_WINDOWS,\n },\n async (db, request) => {\n const days = await readTrafficDays(db, request.hostId, dayIds(request.now, request.days).current)\n const all = days.reduce((sum, day) => sum + day.total, 0)\n const referrers = ranked(sumMaps(days, (day) => day.referrers))\n const sources = ranked(sumMaps(days, (day) => day.utm['source'] ?? {}))\n const campaigns = ranked(sumMaps(days, (day) => day.utm['campaign'] ?? {}))\n const rowsOf = (entries: Array<[string, number]>, limit: number, type: string) =>\n entries.slice(0, limit).map(([name, views]) => ({ source: name, type, views, share: share(views, all) }))\n const rows = [\n ...rowsOf(referrers, AI_TRAFFIC_TOP_REFERRERS, 'Referring site'),\n ...rowsOf(sources, AI_TRAFFIC_TOP_CAMPAIGNS, 'Campaign source'),\n ...rowsOf(campaigns, AI_TRAFFIC_TOP_CAMPAIGNS, 'Campaign'),\n ]\n return {\n ok: true,\n table: {\n title: 'Traffic sources',\n source,\n period: periodOf(request),\n columns: [\n { key: 'source', label: 'Source', kind: 'text' },\n { key: 'type', label: 'Kind', kind: 'text' },\n { key: 'views', label: 'Views', kind: 'count' },\n { key: 'share', label: 'Share of all views', kind: 'percent' },\n ],\n rows,\n omitted:\n Math.max(0, referrers.length - AI_TRAFFIC_TOP_REFERRERS) +\n Math.max(0, sources.length - AI_TRAFFIC_TOP_CAMPAIGNS) +\n Math.max(0, campaigns.length - AI_TRAFFIC_TOP_CAMPAIGNS),\n notes: ['A visit with no referring site, such as a typed address, is in no row.'],\n },\n }\n },\n firestore,\n null,\n ),\n siteReader(\n {\n id: 'traffic.daily',\n label: 'Traffic by day',\n description: 'Page views and visitors on each day of the window, oldest first, for spotting a day that stood out.',\n windows: AI_TRAFFIC_DAILY_WINDOWS,\n },\n async (db, request) => {\n const ids = [...dayIds(request.now, request.days).current].reverse()\n const days = await readTrafficDays(db, request.hostId, ids)\n return {\n ok: true,\n table: {\n title: 'Traffic by day',\n source,\n period: periodOf(request),\n columns: [\n { key: 'day', label: 'Day', kind: 'date' },\n { key: 'views', label: 'Page views', kind: 'count' },\n { key: 'visitors', label: 'Visitors', kind: 'count' },\n ],\n rows: days.map((day, index) => ({ day: ids[index], views: day.total, visitors: day.visitors })),\n omitted: 0,\n notes: [visitorsNote],\n },\n }\n },\n firestore,\n null,\n ),\n ]\n}\n\n// ── Forms ─────────────────────────────────────────────────────────────────\n\nexport function formsFigureReader(firestore: AiFigureFirestore): PluginFigureReader {\n return siteReader(\n {\n id: 'forms.performance',\n label: 'Forms',\n description:\n 'Each form’s views, submissions, the share of views that became a submission, and leads, over the calendar months the window falls in.',\n windows: AI_TRAFFIC_WINDOWS,\n },\n async (db, request) => {\n const { current } = pluginFigureWindows(request.now, request.days)\n const from = formPeriodKey(current.startMs)\n const to = formPeriodKey(current.endMs - 1)\n const snapshot = await db\n .collection('hosts')\n .doc(request.hostId)\n .collection('forms')\n .limit(AI_FORMS_READ_LIMIT)\n .get()\n const forms = snapshot.docs\n .map((doc) => ({ doc, data: (doc.data() ?? {}) as Record<string, unknown> }))\n .filter(({ data }) => !data['deletedAt'])\n .map(({ doc, data }) => {\n const totals = formStatsTotals(data['stats'] as FormStats | undefined, { from, to })\n // Every submission to a form is counted on it, so a form with none\n // recorded in the window had none: 0, not a blank the answer has to\n // hedge over (AGL-3834). Leads are counted only on a form that\n // routes them, so one that does reads 0 the same way, and a form\n // that never has keeps a blank, which the table's note explains.\n const submissions = totals.submissions ?? 0\n const routesLeads = (data['routing'] as { lead?: unknown } | undefined)?.lead === true\n return {\n form: str(data['displayName']) || doc.id,\n views: totals.views,\n submissions,\n completion: totals.views ? share(submissions, totals.views) : null,\n leads: totals.leads ?? (routesLeads ? 0 : null),\n }\n })\n .sort((a, b) => b.submissions - a.submissions || a.form.localeCompare(b.form))\n const monthStart = from ? `${from}-01` : current.from\n return {\n ok: true,\n table: {\n title: 'Forms',\n source: { label: 'Forms', path: 'forms' },\n period: {\n from: monthStart,\n to: current.to,\n days: Math.round((Date.parse(current.to) - Date.parse(monthStart)) / 86_400_000) + 1,\n },\n columns: [\n { key: 'form', label: 'Form', kind: 'text' },\n { key: 'views', label: 'Views', kind: 'count' },\n { key: 'submissions', label: 'Submissions', kind: 'count' },\n { key: 'completion', label: 'Views that became a submission', kind: 'percent' },\n { key: 'leads', label: 'Leads', kind: 'count' },\n ],\n rows: forms.slice(0, PLUGIN_FIGURE_MAX_ROWS),\n omitted: Math.max(0, forms.length - PLUGIN_FIGURE_MAX_ROWS),\n notes: [\n 'Form figures are kept by calendar month, so they cover whole months from the first day of the month the window starts in.',\n 'Views are counted in the visitor’s browser and submissions on the server, so a rate can pass 100%.',\n 'A blank Leads figure means the form does not add its submissions to Leads.',\n ],\n },\n }\n },\n firestore,\n 'forms',\n )\n}\n\n/** Every reader this plugin registers, for the platform's own records. */\nexport function aiFigureReaders(firestore: AiFigureFirestore): PluginFigureReader[] {\n return [...trafficFigureReaders(firestore), formsFigureReader(firestore)]\n}\n\n/**\n * Registers them from the console surface, the one surface that runs insight\n * jobs. By a call, never by an import (AGL-3025).\n */\nexport function registerAiFigureReaders(firestore: AiFigureFirestore): void {\n for (const reader of aiFigureReaders(firestore)) {\n registerPluginFigureReader(reader, { pluginId: AI_PLUGIN_ID })\n }\n}\n"],"names":["formPeriodKey","formStatsTotals","PLUGIN_FIGURE_MAX_ROWS","pluginFigureChange","pluginFigureDay","pluginFigureWindows","registerPluginFigureReader","AI_PLUGIN_ID","AI_TRAFFIC_WINDOWS","AI_TRAFFIC_DAILY_WINDOWS","AI_TRAFFIC_TOP_PAGES","AI_TRAFFIC_TOP_REFERRERS","AI_TRAFFIC_TOP_CAMPAIGNS","AI_FORMS_READ_LIMIT","str","value","trim","num","parsed","Number","isFinite","round1","Math","round","share","part","whole","refused","status","error","ok","periodOf","request","current","now","days","from","to","dayIds","previous","walk","startMs","endMs","ids","at","push","Date","counts","out","key","count","Object","entries","n","readTrafficDays","firestore","hostId","length","collection","doc","snapshots","getAll","map","id","snapshot","data","exists","utm","param","values","total","visitors","paths","referrers","sumMaps","pick","totals","Map","day","set","get","ranked","sort","a","b","localeCompare","siteReader","base","read","plugin","scope","windows","includes","trafficFigureReaders","source","label","path","visitorsNote","description","db","Promise","all","reduce","sum","views","viewsBefore","perDay","perDayBefore","table","title","period","columns","kind","rows","figure","change","omitted","notes","before","slice","page","max","sources","campaigns","rowsOf","limit","type","name","reverse","index","formsFigureReader","forms","docs","filter","submissions","routesLeads","lead","form","completion","leads","monthStart","parse","aiFigureReaders","registerAiFigureReaders","reader","pluginId"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/insights/ai-figure-readers.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 { formPeriodKey, formStatsTotals, type FormStats } from '@aglyn/aglyn/app-utils/forms'\nimport {\n PLUGIN_FIGURE_MAX_ROWS,\n pluginFigureChange,\n pluginFigureDay,\n pluginFigureWindows,\n registerPluginFigureReader,\n type PluginFigureReader,\n type PluginFigureRead,\n type PluginFigureRequest,\n type PluginFigureRow,\n type PluginFigureTable,\n} from '@aglyn/aglyn/plugin-manager/plugin-figures'\nimport { AI_PLUGIN_ID } from '../constants'\nimport {\n FORMS_WEEKLY_SUMMARY,\n SOURCES_WEEKLY_SUMMARY,\n TOP_PAGES_WEEKLY_SUMMARY,\n TRAFFIC_WEEKLY_SUMMARY,\n} from './ai-figure-summaries'\n\n/**\n * The figure readers this plugin registers for records the PLATFORM keeps\n * (AGL-2915): a site's page-view counters and its forms' counters. Every\n * other reader an insight reads is registered by the plugin that owns its\n * records — commerce, bookings, marketing, the workspace's datasets — on the\n * `plugin-figures` seam, and nothing here reads their documents.\n *\n * Each reader answers with one compact table of aggregates, read-only and\n * about nobody: counts, sums and rates, labeled by a page path, a form's name\n * or a referring site — never a visitor or a submission.\n *\n * The readers use the arithmetic their console cards use: the Traffic card's\n * windows and change (`pluginFigureWindows`, `pluginFigureChange`, which are\n * that card's split and delta), and the forms plugin's month counters through\n * `formStatsTotals`, the one function every forms surface windows by.\n */\n\ntype Firestore = FirebaseFirestore.Firestore\n\n/** How a reader reaches Firestore: resolved when it reads, never when it registers. */\nexport type AiFigureFirestore = () => Firestore\n\n/** The traffic windows: the Traffic card's own ranges. */\nexport const AI_TRAFFIC_WINDOWS: readonly number[] = [7, 14, 30, 90]\n\n/** A day-by-day table fits a table's row bound only over a short window. */\nexport const AI_TRAFFIC_DAILY_WINDOWS: readonly number[] = [7, 14]\n\n/** Pages a pages table lists; the rest are counted in `omitted`. */\nexport const AI_TRAFFIC_TOP_PAGES = 10\n\n/** Referring sites, and campaign sources and campaigns, a sources table lists. */\nexport const AI_TRAFFIC_TOP_REFERRERS = 8\nexport const AI_TRAFFIC_TOP_CAMPAIGNS = 5\n\n/** Forms a forms table reads. */\nexport const AI_FORMS_READ_LIMIT = 100\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\nconst num = (value: unknown): number => {\n const parsed = typeof value === 'number' ? value : Number(value)\n return Number.isFinite(parsed) ? parsed : 0\n}\n\nconst round1 = (value: number): number => Math.round(value * 10) / 10\n\nconst share = (part: number, whole: number): number | null => (whole > 0 ? round1((part / whole) * 100) : null)\n\nfunction refused(status: 400 | 403 | 404, error: string): PluginFigureRead {\n return { ok: false, status, error }\n}\n\n/** A window's days as the table's period, oldest to newest. */\nfunction periodOf(request: PluginFigureRequest): PluginFigureTable['period'] {\n const { current } = pluginFigureWindows(request.now, request.days)\n return { from: current.from, to: current.to, days: request.days }\n}\n\n/** Every day id of the window and the one before it, newest first. */\nfunction dayIds(now: Date, days: number): { current: string[]; previous: string[] } {\n const { current, previous } = pluginFigureWindows(now, days)\n const walk = (startMs: number, endMs: number): string[] => {\n const ids: string[] = []\n for (let at = endMs - 86_400_000; at >= startMs; at -= 86_400_000) ids.push(pluginFigureDay(new Date(at)))\n return ids\n }\n return { current: walk(current.startMs, current.endMs), previous: walk(previous.startMs, previous.endMs) }\n}\n\n// ── Traffic ───────────────────────────────────────────────────────────────\n\ninterface TrafficDay {\n total: number\n visitors: number\n paths: Record<string, number>\n referrers: Record<string, number>\n utm: Record<string, Record<string, number>>\n}\n\nconst counts = (value: unknown): Record<string, number> => {\n const out: Record<string, number> = {}\n if (!value || typeof value !== 'object') return out\n for (const [key, count] of Object.entries(value as Record<string, unknown>)) {\n const n = num(count)\n if (n > 0) out[key] = n\n }\n return out\n}\n\nasync function readTrafficDays(\n firestore: Firestore,\n hostId: string,\n ids: readonly string[],\n): Promise<TrafficDay[]> {\n if (!ids.length) return []\n const collection = firestore.collection('hosts').doc(hostId).collection('analytics')\n const snapshots = await firestore.getAll(...ids.map((id) => collection.doc(id)))\n return snapshots.map((snapshot) => {\n const data = (snapshot.exists ? snapshot.data() : null) ?? {}\n const utm: Record<string, Record<string, number>> = {}\n for (const [param, values] of Object.entries((data['utm'] ?? {}) as Record<string, unknown>)) {\n utm[param] = counts(values)\n }\n return {\n total: num(data['total']),\n visitors: num(data['visitors']),\n paths: counts(data['paths']),\n referrers: counts(data['referrers']),\n utm,\n }\n })\n}\n\nfunction sumMaps(days: readonly TrafficDay[], pick: (day: TrafficDay) => Record<string, number>): Map<string, number> {\n const totals = new Map<string, number>()\n for (const day of days) {\n for (const [key, count] of Object.entries(pick(day))) totals.set(key, (totals.get(key) ?? 0) + count)\n }\n return totals\n}\n\nconst ranked = (totals: Map<string, number>): Array<[string, number]> =>\n [...totals.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))\n\nfunction siteReader(\n base: Omit<PluginFigureReader, 'read' | 'scope' | 'plugin'>,\n read: (firestore: Firestore, request: PluginFigureRequest & { hostId: string }) => Promise<PluginFigureRead>,\n firestore: AiFigureFirestore,\n plugin: string | null,\n): PluginFigureReader {\n return {\n ...base,\n scope: 'site',\n plugin,\n read: async (request) => {\n if (!request.hostId) return refused(400, 'Open a site to read its figures')\n if (!base.windows.includes(request.days)) return refused(400, 'That window is not one this reader covers')\n return read(firestore(), { ...request, hostId: request.hostId })\n },\n }\n}\n\nexport function trafficFigureReaders(firestore: AiFigureFirestore): PluginFigureReader[] {\n const source = { label: 'Analytics', path: 'analytics' }\n const visitorsNote =\n 'Visitors are counted once per browser tab per day, so one person on two days counts twice.'\n return [\n siteReader(\n {\n id: 'traffic.summary',\n label: 'Traffic',\n description:\n 'Page views, visitors and views a day on the site over the window, each beside the window before it and the change.',\n windows: AI_TRAFFIC_WINDOWS,\n weeklySummary: TRAFFIC_WEEKLY_SUMMARY,\n },\n async (db, request) => {\n const ids = dayIds(request.now, request.days)\n const [current, previous] = await Promise.all([\n readTrafficDays(db, request.hostId, ids.current),\n readTrafficDays(db, request.hostId, ids.previous),\n ])\n const total = (days: TrafficDay[]) => days.reduce((sum, day) => sum + day.total, 0)\n const visitors = (days: TrafficDay[]) => days.reduce((sum, day) => sum + day.visitors, 0)\n const views = total(current)\n const viewsBefore = total(previous)\n const perDay = Math.round(views / request.days)\n const perDayBefore = Math.round(viewsBefore / request.days)\n const table: PluginFigureTable = {\n title: 'Traffic',\n source,\n period: periodOf(request),\n columns: [\n { key: 'figure', label: 'Figure', kind: 'text' },\n { key: 'current', label: 'This window', kind: 'count' },\n { key: 'previous', label: 'Window before', kind: 'count' },\n { key: 'change', label: 'Change', kind: 'change' },\n ],\n rows: [\n { figure: 'Page views', current: views, previous: viewsBefore, change: pluginFigureChange(views, viewsBefore) },\n {\n figure: 'Visitors',\n current: visitors(current),\n previous: visitors(previous),\n change: pluginFigureChange(visitors(current), visitors(previous)),\n },\n {\n figure: 'Page views a day',\n current: perDay,\n previous: perDayBefore,\n change: pluginFigureChange(perDay, perDayBefore),\n },\n ],\n omitted: 0,\n notes: [\n visitorsNote,\n ...(viewsBefore ? [] : ['The window before this one recorded no page views, so there is no change to compare.']),\n ],\n }\n return { ok: true, table }\n },\n firestore,\n null,\n ),\n siteReader(\n {\n id: 'traffic.pages',\n label: 'Top pages',\n description: `The site's ${AI_TRAFFIC_TOP_PAGES} most viewed pages over the window, with each page's share of all views and its change from the window before.`,\n windows: AI_TRAFFIC_WINDOWS,\n weeklySummary: TOP_PAGES_WEEKLY_SUMMARY,\n },\n async (db, request) => {\n const ids = dayIds(request.now, request.days)\n const [current, previous] = await Promise.all([\n readTrafficDays(db, request.hostId, ids.current),\n readTrafficDays(db, request.hostId, ids.previous),\n ])\n const now = ranked(sumMaps(current, (day) => day.paths))\n const before = sumMaps(previous, (day) => day.paths)\n const all = now.reduce((sum, [, count]) => sum + count, 0)\n const rows: PluginFigureRow[] = now.slice(0, AI_TRAFFIC_TOP_PAGES).map(([path, views]) => ({\n page: path,\n views,\n share: share(views, all),\n change: pluginFigureChange(views, before.get(path) ?? null),\n }))\n return {\n ok: true,\n table: {\n title: 'Top pages',\n source,\n period: periodOf(request),\n columns: [\n { key: 'page', label: 'Page', kind: 'text' },\n { key: 'views', label: 'Views', kind: 'count' },\n { key: 'share', label: 'Share of views', kind: 'percent' },\n { key: 'change', label: 'Change', kind: 'change' },\n ],\n rows,\n omitted: Math.max(0, now.length - rows.length),\n notes: [],\n },\n }\n },\n firestore,\n null,\n ),\n siteReader(\n {\n id: 'traffic.sources',\n label: 'Traffic sources',\n description:\n 'Where visits came from over the window: the referring sites, and the campaign sources and campaigns links were tagged with, with each one’s views.',\n windows: AI_TRAFFIC_WINDOWS,\n weeklySummary: SOURCES_WEEKLY_SUMMARY,\n },\n async (db, request) => {\n const days = await readTrafficDays(db, request.hostId, dayIds(request.now, request.days).current)\n const all = days.reduce((sum, day) => sum + day.total, 0)\n // The collector stores a referring host with its dots as `_`, because a\n // Firestore field path cannot hold one; a host is read back as written.\n const referrers = ranked(sumMaps(days, (day) => day.referrers)).map(\n ([host, views]): [string, number] => [host.replace(/_/g, '.'), views],\n )\n const sources = ranked(sumMaps(days, (day) => day.utm['source'] ?? {}))\n const campaigns = ranked(sumMaps(days, (day) => day.utm['campaign'] ?? {}))\n const rowsOf = (entries: Array<[string, number]>, limit: number, type: string) =>\n entries.slice(0, limit).map(([name, views]) => ({ source: name, type, views, share: share(views, all) }))\n const rows = [\n ...rowsOf(referrers, AI_TRAFFIC_TOP_REFERRERS, 'Referring site'),\n ...rowsOf(sources, AI_TRAFFIC_TOP_CAMPAIGNS, 'Campaign source'),\n ...rowsOf(campaigns, AI_TRAFFIC_TOP_CAMPAIGNS, 'Campaign'),\n ]\n return {\n ok: true,\n table: {\n title: 'Traffic sources',\n source,\n period: periodOf(request),\n columns: [\n { key: 'source', label: 'Source', kind: 'text' },\n { key: 'type', label: 'Kind', kind: 'text' },\n { key: 'views', label: 'Views', kind: 'count' },\n { key: 'share', label: 'Share of all views', kind: 'percent' },\n ],\n rows,\n omitted:\n Math.max(0, referrers.length - AI_TRAFFIC_TOP_REFERRERS) +\n Math.max(0, sources.length - AI_TRAFFIC_TOP_CAMPAIGNS) +\n Math.max(0, campaigns.length - AI_TRAFFIC_TOP_CAMPAIGNS),\n notes: ['A visit with no referring site, such as a typed address, is in no row.'],\n },\n }\n },\n firestore,\n null,\n ),\n siteReader(\n {\n id: 'traffic.daily',\n label: 'Traffic by day',\n description: 'Page views and visitors on each day of the window, oldest first, for spotting a day that stood out.',\n windows: AI_TRAFFIC_DAILY_WINDOWS,\n },\n async (db, request) => {\n const ids = [...dayIds(request.now, request.days).current].reverse()\n const days = await readTrafficDays(db, request.hostId, ids)\n return {\n ok: true,\n table: {\n title: 'Traffic by day',\n source,\n period: periodOf(request),\n columns: [\n { key: 'day', label: 'Day', kind: 'date' },\n { key: 'views', label: 'Page views', kind: 'count' },\n { key: 'visitors', label: 'Visitors', kind: 'count' },\n ],\n rows: days.map((day, index) => ({ day: ids[index], views: day.total, visitors: day.visitors })),\n omitted: 0,\n notes: [visitorsNote],\n },\n }\n },\n firestore,\n null,\n ),\n ]\n}\n\n// ── Forms ─────────────────────────────────────────────────────────────────\n\nexport function formsFigureReader(firestore: AiFigureFirestore): PluginFigureReader {\n return siteReader(\n {\n id: 'forms.performance',\n label: 'Forms',\n description:\n 'Each form’s views, submissions, the share of views that became a submission, and leads, over the calendar months the window falls in.',\n windows: AI_TRAFFIC_WINDOWS,\n weeklySummary: FORMS_WEEKLY_SUMMARY,\n },\n async (db, request) => {\n const { current } = pluginFigureWindows(request.now, request.days)\n const from = formPeriodKey(current.startMs)\n const to = formPeriodKey(current.endMs - 1)\n const snapshot = await db\n .collection('hosts')\n .doc(request.hostId)\n .collection('forms')\n .limit(AI_FORMS_READ_LIMIT)\n .get()\n const forms = snapshot.docs\n .map((doc) => ({ doc, data: (doc.data() ?? {}) as Record<string, unknown> }))\n .filter(({ data }) => !data['deletedAt'])\n .map(({ doc, data }) => {\n const totals = formStatsTotals(data['stats'] as FormStats | undefined, { from, to })\n // Every submission to a form is counted on it, so a form with none\n // recorded in the window had none: 0, not a blank the answer has to\n // hedge over (AGL-3834). Leads are counted only on a form that\n // routes them, so one that does reads 0 the same way, and a form\n // that never has keeps a blank, which the table's note explains.\n const submissions = totals.submissions ?? 0\n const routesLeads = (data['routing'] as { lead?: unknown } | undefined)?.lead === true\n return {\n form: str(data['displayName']) || doc.id,\n views: totals.views,\n submissions,\n completion: totals.views ? share(submissions, totals.views) : null,\n leads: totals.leads ?? (routesLeads ? 0 : null),\n }\n })\n .sort((a, b) => b.submissions - a.submissions || a.form.localeCompare(b.form))\n const monthStart = from ? `${from}-01` : current.from\n return {\n ok: true,\n table: {\n title: 'Forms',\n source: { label: 'Forms', path: 'forms' },\n period: {\n from: monthStart,\n to: current.to,\n days: Math.round((Date.parse(current.to) - Date.parse(monthStart)) / 86_400_000) + 1,\n },\n columns: [\n { key: 'form', label: 'Form', kind: 'text' },\n { key: 'views', label: 'Views', kind: 'count' },\n { key: 'submissions', label: 'Submissions', kind: 'count' },\n { key: 'completion', label: 'Views that became a submission', kind: 'percent' },\n { key: 'leads', label: 'Leads', kind: 'count' },\n ],\n rows: forms.slice(0, PLUGIN_FIGURE_MAX_ROWS),\n omitted: Math.max(0, forms.length - PLUGIN_FIGURE_MAX_ROWS),\n notes: [\n 'Form figures are kept by calendar month, so they cover whole months from the first day of the month the window starts in.',\n 'Views are counted in the visitor’s browser and submissions on the server, so a rate can pass 100%.',\n 'A blank Leads figure means the form does not add its submissions to Leads.',\n ],\n },\n }\n },\n firestore,\n 'forms',\n )\n}\n\n/** Every reader this plugin registers, for the platform's own records. */\nexport function aiFigureReaders(firestore: AiFigureFirestore): PluginFigureReader[] {\n return [...trafficFigureReaders(firestore), formsFigureReader(firestore)]\n}\n\n/**\n * Registers them from the console surface, the one surface that runs insight\n * jobs. By a call, never by an import (AGL-3025).\n */\nexport function registerAiFigureReaders(firestore: AiFigureFirestore): void {\n for (const reader of aiFigureReaders(firestore)) {\n registerPluginFigureReader(reader, { pluginId: AI_PLUGIN_ID })\n }\n}\n"],"names":["formPeriodKey","formStatsTotals","PLUGIN_FIGURE_MAX_ROWS","pluginFigureChange","pluginFigureDay","pluginFigureWindows","registerPluginFigureReader","AI_PLUGIN_ID","FORMS_WEEKLY_SUMMARY","SOURCES_WEEKLY_SUMMARY","TOP_PAGES_WEEKLY_SUMMARY","TRAFFIC_WEEKLY_SUMMARY","AI_TRAFFIC_WINDOWS","AI_TRAFFIC_DAILY_WINDOWS","AI_TRAFFIC_TOP_PAGES","AI_TRAFFIC_TOP_REFERRERS","AI_TRAFFIC_TOP_CAMPAIGNS","AI_FORMS_READ_LIMIT","str","value","trim","num","parsed","Number","isFinite","round1","Math","round","share","part","whole","refused","status","error","ok","periodOf","request","current","now","days","from","to","dayIds","previous","walk","startMs","endMs","ids","at","push","Date","counts","out","key","count","Object","entries","n","readTrafficDays","firestore","hostId","length","collection","doc","snapshots","getAll","map","id","snapshot","data","exists","utm","param","values","total","visitors","paths","referrers","sumMaps","pick","totals","Map","day","set","get","ranked","sort","a","b","localeCompare","siteReader","base","read","plugin","scope","windows","includes","trafficFigureReaders","source","label","path","visitorsNote","description","weeklySummary","db","Promise","all","reduce","sum","views","viewsBefore","perDay","perDayBefore","table","title","period","columns","kind","rows","figure","change","omitted","notes","before","slice","page","max","host","replace","sources","campaigns","rowsOf","limit","type","name","reverse","index","formsFigureReader","forms","docs","filter","submissions","routesLeads","lead","form","completion","leads","monthStart","parse","aiFigureReaders","registerAiFigureReaders","reader","pluginId"],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
import type { PluginFigureWeeklySummary } from '@aglyn/aglyn/plugin-manager/plugin-figures';
|
|
18
|
+
/**
|
|
19
|
+
* How the traffic and forms readers read in the weekly summary (AGL-3867).
|
|
20
|
+
* Each answers `[]` for a week it has nothing to say about, and for a table
|
|
21
|
+
* that is not the shape it was written for, so a changed reader drops out of
|
|
22
|
+
* the email rather than misreporting.
|
|
23
|
+
*/
|
|
24
|
+
export declare const TRAFFIC_WEEKLY_SUMMARY: PluginFigureWeeklySummary;
|
|
25
|
+
export declare const TOP_PAGES_WEEKLY_SUMMARY: PluginFigureWeeklySummary;
|
|
26
|
+
export declare const SOURCES_WEEKLY_SUMMARY: PluginFigureWeeklySummary;
|
|
27
|
+
export declare const FORMS_WEEKLY_SUMMARY: PluginFigureWeeklySummary;
|
|
@@ -0,0 +1,79 @@
|
|
|
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
|
+
*/ import { figureCount, figureHasColumn, figureRanked, figureRow, figureWithChange, weeklySummaryDay } from "@aglyn/aglyn/app-utils/weekly-summary";
|
|
17
|
+
/**
|
|
18
|
+
* How the traffic and forms readers read in the weekly summary (AGL-3867).
|
|
19
|
+
* Each answers `[]` for a week it has nothing to say about, and for a table
|
|
20
|
+
* that is not the shape it was written for, so a changed reader drops out of
|
|
21
|
+
* the email rather than misreporting.
|
|
22
|
+
*/ export const TRAFFIC_WEEKLY_SUMMARY = {
|
|
23
|
+
order: 10,
|
|
24
|
+
lines: (table)=>{
|
|
25
|
+
const visitors = figureRow(table, 'figure', 'Visitors');
|
|
26
|
+
const views = figureRow(table, 'figure', 'Page views');
|
|
27
|
+
if (!figureCount(visitors == null ? void 0 : visitors['current']) && !figureCount(views == null ? void 0 : views['current'])) return [];
|
|
28
|
+
return [
|
|
29
|
+
[
|
|
30
|
+
visitors ? `Visitors: ${figureWithChange(table, visitors, 'current', 'change')}` : null,
|
|
31
|
+
views ? `Page views: ${figureWithChange(table, views, 'current', 'change')}` : null
|
|
32
|
+
].filter(Boolean).join(' · ')
|
|
33
|
+
];
|
|
34
|
+
},
|
|
35
|
+
headline: (table)=>{
|
|
36
|
+
var _figureRow;
|
|
37
|
+
const count = figureCount((_figureRow = figureRow(table, 'figure', 'Visitors')) == null ? void 0 : _figureRow['current']);
|
|
38
|
+
return count ? {
|
|
39
|
+
count,
|
|
40
|
+
one: 'visitor',
|
|
41
|
+
many: 'visitors'
|
|
42
|
+
} : null;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
export const TOP_PAGES_WEEKLY_SUMMARY = {
|
|
46
|
+
order: 20,
|
|
47
|
+
lines: (table)=>{
|
|
48
|
+
const top = figureHasColumn(table, 'views') ? figureRanked(table, 'page', 'views', 'views') : null;
|
|
49
|
+
return top ? [
|
|
50
|
+
`Top pages: ${top}`
|
|
51
|
+
] : [];
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
export const SOURCES_WEEKLY_SUMMARY = {
|
|
55
|
+
order: 30,
|
|
56
|
+
lines: (table)=>{
|
|
57
|
+
const top = figureHasColumn(table, 'views') ? figureRanked(table, 'source', 'views', 'views') : null;
|
|
58
|
+
return top ? [
|
|
59
|
+
`Top sources: ${top}`
|
|
60
|
+
] : [];
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
export const FORMS_WEEKLY_SUMMARY = {
|
|
64
|
+
order: 40,
|
|
65
|
+
lines: (table)=>{
|
|
66
|
+
if (!figureHasColumn(table, 'submissions')) return [];
|
|
67
|
+
const total = table.rows.reduce((sum, row)=>sum + figureCount(row['submissions']), 0);
|
|
68
|
+
if (!total) return [];
|
|
69
|
+
// Form figures are kept by calendar month, so the line says so rather
|
|
70
|
+
// than calling a month-to-date count "last week".
|
|
71
|
+
const since = table.period ? ` since ${weeklySummaryDay(table.period.from)}` : '';
|
|
72
|
+
const top = figureRanked(table, 'form', 'submissions');
|
|
73
|
+
return [
|
|
74
|
+
`Form submissions${since}: ${total.toLocaleString('en-US')}${top ? ` (${top})` : ''}`
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
//# sourceMappingURL=ai-figure-summaries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/insights/ai-figure-summaries.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 type { PluginFigureWeeklySummary } from '@aglyn/aglyn/plugin-manager/plugin-figures'\nimport {\n figureCount,\n figureHasColumn,\n figureRanked,\n figureRow,\n figureWithChange,\n weeklySummaryDay,\n} from '@aglyn/aglyn/app-utils/weekly-summary'\n\n/**\n * How the traffic and forms readers read in the weekly summary (AGL-3867).\n * Each answers `[]` for a week it has nothing to say about, and for a table\n * that is not the shape it was written for, so a changed reader drops out of\n * the email rather than misreporting.\n */\n\nexport const TRAFFIC_WEEKLY_SUMMARY: PluginFigureWeeklySummary = {\n order: 10,\n lines: (table) => {\n const visitors = figureRow(table, 'figure', 'Visitors')\n const views = figureRow(table, 'figure', 'Page views')\n if (!figureCount(visitors?.['current']) && !figureCount(views?.['current'])) return []\n return [\n [\n visitors ? `Visitors: ${figureWithChange(table, visitors, 'current', 'change')}` : null,\n views ? `Page views: ${figureWithChange(table, views, 'current', 'change')}` : null,\n ]\n .filter(Boolean)\n .join(' · '),\n ]\n },\n headline: (table) => {\n const count = figureCount(figureRow(table, 'figure', 'Visitors')?.['current'])\n return count ? { count, one: 'visitor', many: 'visitors' } : null\n },\n}\n\nexport const TOP_PAGES_WEEKLY_SUMMARY: PluginFigureWeeklySummary = {\n order: 20,\n lines: (table) => {\n const top = figureHasColumn(table, 'views') ? figureRanked(table, 'page', 'views', 'views') : null\n return top ? [`Top pages: ${top}`] : []\n },\n}\n\nexport const SOURCES_WEEKLY_SUMMARY: PluginFigureWeeklySummary = {\n order: 30,\n lines: (table) => {\n const top = figureHasColumn(table, 'views') ? figureRanked(table, 'source', 'views', 'views') : null\n return top ? [`Top sources: ${top}`] : []\n },\n}\n\nexport const FORMS_WEEKLY_SUMMARY: PluginFigureWeeklySummary = {\n order: 40,\n lines: (table) => {\n if (!figureHasColumn(table, 'submissions')) return []\n const total = table.rows.reduce((sum, row) => sum + figureCount(row['submissions']), 0)\n if (!total) return []\n // Form figures are kept by calendar month, so the line says so rather\n // than calling a month-to-date count \"last week\".\n const since = table.period ? ` since ${weeklySummaryDay(table.period.from)}` : ''\n const top = figureRanked(table, 'form', 'submissions')\n return [`Form submissions${since}: ${total.toLocaleString('en-US')}${top ? ` (${top})` : ''}`]\n },\n}\n"],"names":["figureCount","figureHasColumn","figureRanked","figureRow","figureWithChange","weeklySummaryDay","TRAFFIC_WEEKLY_SUMMARY","order","lines","table","visitors","views","filter","Boolean","join","headline","count","one","many","TOP_PAGES_WEEKLY_SUMMARY","top","SOURCES_WEEKLY_SUMMARY","FORMS_WEEKLY_SUMMARY","total","rows","reduce","sum","row","since","period","from","toLocaleString"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SACEA,WAAW,EACXC,eAAe,EACfC,YAAY,EACZC,SAAS,EACTC,gBAAgB,EAChBC,gBAAgB,QACX,wCAAuC;AAE9C;;;;;CAKC,GAED,OAAO,MAAMC,yBAAoD;IAC/DC,OAAO;IACPC,OAAO,CAACC;QACN,MAAMC,WAAWP,UAAUM,OAAO,UAAU;QAC5C,MAAME,QAAQR,UAAUM,OAAO,UAAU;QACzC,IAAI,CAACT,YAAYU,4BAAAA,QAAU,CAAC,UAAU,KAAK,CAACV,YAAYW,yBAAAA,KAAO,CAAC,UAAU,GAAG,OAAO,EAAE;QACtF,OAAO;YACL;gBACED,WAAW,CAAC,UAAU,EAAEN,iBAAiBK,OAAOC,UAAU,WAAW,WAAW,GAAG;gBACnFC,QAAQ,CAAC,YAAY,EAAEP,iBAAiBK,OAAOE,OAAO,WAAW,WAAW,GAAG;aAChF,CACEC,MAAM,CAACC,SACPC,IAAI,CAAC;SACT;IACH;IACAC,UAAU,CAACN;YACiBN;QAA1B,MAAMa,QAAQhB,aAAYG,aAAAA,UAAUM,OAAO,UAAU,gCAA3BN,UAAwC,CAAC,UAAU;QAC7E,OAAOa,QAAQ;YAAEA;YAAOC,KAAK;YAAWC,MAAM;QAAW,IAAI;IAC/D;AACF,EAAC;AAED,OAAO,MAAMC,2BAAsD;IACjEZ,OAAO;IACPC,OAAO,CAACC;QACN,MAAMW,MAAMnB,gBAAgBQ,OAAO,WAAWP,aAAaO,OAAO,QAAQ,SAAS,WAAW;QAC9F,OAAOW,MAAM;YAAC,CAAC,WAAW,EAAEA,KAAK;SAAC,GAAG,EAAE;IACzC;AACF,EAAC;AAED,OAAO,MAAMC,yBAAoD;IAC/Dd,OAAO;IACPC,OAAO,CAACC;QACN,MAAMW,MAAMnB,gBAAgBQ,OAAO,WAAWP,aAAaO,OAAO,UAAU,SAAS,WAAW;QAChG,OAAOW,MAAM;YAAC,CAAC,aAAa,EAAEA,KAAK;SAAC,GAAG,EAAE;IAC3C;AACF,EAAC;AAED,OAAO,MAAME,uBAAkD;IAC7Df,OAAO;IACPC,OAAO,CAACC;QACN,IAAI,CAACR,gBAAgBQ,OAAO,gBAAgB,OAAO,EAAE;QACrD,MAAMc,QAAQd,MAAMe,IAAI,CAACC,MAAM,CAAC,CAACC,KAAKC,MAAQD,MAAM1B,YAAY2B,GAAG,CAAC,cAAc,GAAG;QACrF,IAAI,CAACJ,OAAO,OAAO,EAAE;QACrB,sEAAsE;QACtE,kDAAkD;QAClD,MAAMK,QAAQnB,MAAMoB,MAAM,GAAG,CAAC,OAAO,EAAExB,iBAAiBI,MAAMoB,MAAM,CAACC,IAAI,GAAG,GAAG;QAC/E,MAAMV,MAAMlB,aAAaO,OAAO,QAAQ;QACxC,OAAO;YAAC,CAAC,gBAAgB,EAAEmB,MAAM,EAAE,EAAEL,MAAMQ,cAAc,CAAC,WAAWX,MAAM,CAAC,EAAE,EAAEA,IAAI,CAAC,CAAC,GAAG,IAAI;SAAC;IAChG;AACF,EAAC"}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { type ResolvedPluginFigureReader } from '@aglyn/aglyn/plugin-manager/plugin-figures';
|
|
18
18
|
import { type AiInsightSurface, type AiInsightTable } from '../model/ai-insight';
|
|
19
|
+
import { type ReleasedFigurePlugins } from '@aglyn/tenant-data-admin/server/figure-readers';
|
|
19
20
|
import type { AiInsightReadRequest } from '../tools/ai-insight-tool';
|
|
20
21
|
import type { AiDatasetCatalogEntry } from './ai-dataset-catalog';
|
|
21
22
|
/**
|
|
@@ -30,7 +31,7 @@ import type { AiDatasetCatalogEntry } from './ai-dataset-catalog';
|
|
|
30
31
|
* to the job's workspace, and the member's reach, are the job's own checks.
|
|
31
32
|
*/
|
|
32
33
|
/** Which of a set of plugins are past their release flags for an org; the platform's flags by default. */
|
|
33
|
-
export type AiReleasedPlugins =
|
|
34
|
+
export type AiReleasedPlugins = ReleasedFigurePlugins;
|
|
34
35
|
export interface AiInsightReaderContext {
|
|
35
36
|
orgId: string;
|
|
36
37
|
/** The site the figures are about; `null` for a workspace-level question. */
|
|
@@ -14,35 +14,20 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
|
14
14
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
15
|
* See the License for the specific language governing permissions and
|
|
16
16
|
* limitations under the License.
|
|
17
|
-
*/ import {
|
|
18
|
-
import { isHostPluginEnabled, isPluginEnabled } from "@aglyn/aglyn/plugin-manager/enabled-plugins";
|
|
19
|
-
import { listPluginFigureReaders, normalizePluginFigureTable, pluginFigureParams, pluginFigureReaderPlugin } from "@aglyn/aglyn/plugin-manager/plugin-figures";
|
|
17
|
+
*/ import { normalizePluginFigureTable, pluginFigureParams } from "@aglyn/aglyn/plugin-manager/plugin-figures";
|
|
20
18
|
import { AI_INSIGHT_MAX_READS, AI_INSIGHT_SURFACE_READERS, aiInsightCellText } from "../model/ai-insight.js";
|
|
21
|
-
import {
|
|
22
|
-
const releasedByFlags = (pluginIds, orgId)=>filterEnabledPluginsByReleaseFlags(pluginIds, {
|
|
23
|
-
orgId,
|
|
24
|
-
authorization: null
|
|
25
|
-
});
|
|
19
|
+
import { eligibleFigureReaders } from "@aglyn/tenant-data-admin/server/figure-readers";
|
|
26
20
|
/** The readers a job on this surface may read, ordered by id so a prompt lists them in the same order every time. */ export async function aiInsightReaders(context) {
|
|
27
|
-
var _context_released;
|
|
28
21
|
const prefixes = AI_INSIGHT_SURFACE_READERS[context.surface];
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
];
|
|
39
|
-
const released = new Set(plugins.length ? await ((_context_released = context.released) != null ? _context_released : releasedByFlags)(plugins, context.orgId) : []);
|
|
40
|
-
return candidates.filter((resolved)=>{
|
|
41
|
-
const plugin = pluginFigureReaderPlugin(resolved);
|
|
42
|
-
if (!plugin) return true;
|
|
43
|
-
if (!released.has(plugin)) return false;
|
|
44
|
-
return context.hostId ? isHostPluginEnabled(org, context.host, plugin) : isPluginEnabled(org, plugin);
|
|
45
|
-
});
|
|
22
|
+
return eligibleFigureReaders(_extends({
|
|
23
|
+
orgId: context.orgId,
|
|
24
|
+
hostId: context.hostId,
|
|
25
|
+
org: context.org,
|
|
26
|
+
host: context.host,
|
|
27
|
+
accept: (reader)=>prefixes.includes(reader.id.split('.')[0])
|
|
28
|
+
}, context.released ? {
|
|
29
|
+
released: context.released
|
|
30
|
+
} : {}));
|
|
46
31
|
}
|
|
47
32
|
/** The window a reader is read over: the one asked for when it covers it, else the nearest it does. */ export function aiInsightReaderDays(windows, asked) {
|
|
48
33
|
if (!windows.length) return 0;
|