@aglyn/plugins-ai 1.0.0-beta.233 → 1.0.0-beta.235

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.
Files changed (199) hide show
  1. package/package.json +17 -17
  2. package/src/lib/activity/ai-activity-actions.d.ts +25 -0
  3. package/src/lib/activity/ai-activity-actions.js +85 -4
  4. package/src/lib/activity/ai-activity-actions.js.map +1 -1
  5. package/src/lib/components/ai-brief-dialog.component.js +46 -2
  6. package/src/lib/components/ai-brief-dialog.component.js.map +1 -1
  7. package/src/lib/components/ai-campaign-create.component.js +1 -1
  8. package/src/lib/components/ai-campaign-create.component.js.map +1 -1
  9. package/src/lib/components/ai-credits-card.component.js +3 -3
  10. package/src/lib/components/ai-credits-card.component.js.map +1 -1
  11. package/src/lib/components/ai-crm-email-draft.component.js +1 -1
  12. package/src/lib/components/ai-crm-email-draft.component.js.map +1 -1
  13. package/src/lib/components/ai-crm-import-mapping.component.js +1 -1
  14. package/src/lib/components/ai-crm-import-mapping.component.js.map +1 -1
  15. package/src/lib/components/ai-crm-record-card.component.js +1 -1
  16. package/src/lib/components/ai-crm-record-card.component.js.map +1 -1
  17. package/src/lib/components/ai-describe-automation.component.js +4 -2
  18. package/src/lib/components/ai-describe-automation.component.js.map +1 -1
  19. package/src/lib/components/ai-describe-org-automation.component.js +1 -1
  20. package/src/lib/components/ai-describe-org-automation.component.js.map +1 -1
  21. package/src/lib/components/ai-experiment-cards.component.js +2 -2
  22. package/src/lib/components/ai-experiment-cards.component.js.map +1 -1
  23. package/src/lib/components/ai-explain-automation.component.js +1 -1
  24. package/src/lib/components/ai-explain-automation.component.js.map +1 -1
  25. package/src/lib/components/ai-insight-card.component.js +3 -1
  26. package/src/lib/components/ai-insight-card.component.js.map +1 -1
  27. package/src/lib/components/ai-job-links.d.ts +6 -0
  28. package/src/lib/components/ai-job-links.js +9 -0
  29. package/src/lib/components/ai-job-links.js.map +1 -1
  30. package/src/lib/components/ai-jobs-list-page.component.d.ts +6 -1
  31. package/src/lib/components/ai-jobs-list-page.component.js +15 -2
  32. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  33. package/src/lib/components/ai-logic.component.js +5 -3
  34. package/src/lib/components/ai-logic.component.js.map +1 -1
  35. package/src/lib/components/ai-media-create.component.js +88 -16
  36. package/src/lib/components/ai-media-create.component.js.map +1 -1
  37. package/src/lib/components/ai-model-selector.component.js +8 -0
  38. package/src/lib/components/ai-model-selector.component.js.map +1 -1
  39. package/src/lib/components/ai-overlay-cards.component.js +2 -2
  40. package/src/lib/components/ai-overlay-cards.component.js.map +1 -1
  41. package/src/lib/components/ai-product-copy-card.component.js +2 -1
  42. package/src/lib/components/ai-product-copy-card.component.js.map +1 -1
  43. package/src/lib/components/ai-products-hub-card.component.js +2 -1
  44. package/src/lib/components/ai-products-hub-card.component.js.map +1 -1
  45. package/src/lib/components/ai-revise-automation.component.js +1 -1
  46. package/src/lib/components/ai-revise-automation.component.js.map +1 -1
  47. package/src/lib/components/ai-seo-fields-card.component.js +7 -3
  48. package/src/lib/components/ai-seo-fields-card.component.js.map +1 -1
  49. package/src/lib/components/ai-site-batch-card.component.js +7 -4
  50. package/src/lib/components/ai-site-batch-card.component.js.map +1 -1
  51. package/src/lib/components/ai-site-build-page.component.js +48 -6
  52. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  53. package/src/lib/components/ai-site-seo-start-card.component.js +7 -4
  54. package/src/lib/components/ai-site-seo-start-card.component.js.map +1 -1
  55. package/src/lib/components/ai-site-start-card.component.js +152 -36
  56. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  57. package/src/lib/components/ai-theme-proposal-card.component.js +1 -0
  58. package/src/lib/components/ai-theme-proposal-card.component.js.map +1 -1
  59. package/src/lib/components/ai-upsell-dialog.component.js +17 -3
  60. package/src/lib/components/ai-upsell-dialog.component.js.map +1 -1
  61. package/src/lib/components/ai-usage-strip.component.js +13 -2
  62. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  63. package/src/lib/components/assist-build-card.component.js +20 -3
  64. package/src/lib/components/assist-build-card.component.js.map +1 -1
  65. package/src/lib/components/assist-jobs-drawer.component.js +7 -1
  66. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  67. package/src/lib/components/billing-ai-allotments.component.js +2 -2
  68. package/src/lib/components/billing-ai-allotments.component.js.map +1 -1
  69. package/src/lib/components/billing-ai-top-users.component.js +1 -0
  70. package/src/lib/components/billing-ai-top-users.component.js.map +1 -1
  71. package/src/lib/components/billing-assist-overage-card.component.js +2 -2
  72. package/src/lib/components/billing-assist-overage-card.component.js.map +1 -1
  73. package/src/lib/components/host-ai-allotments.component.js +2 -2
  74. package/src/lib/components/host-ai-allotments.component.js.map +1 -1
  75. package/src/lib/components/member-ai-allotment-card.component.js +2 -2
  76. package/src/lib/components/member-ai-allotment-card.component.js.map +1 -1
  77. package/src/lib/components/staff-org-ai-card.component.js +1 -0
  78. package/src/lib/components/staff-org-ai-card.component.js.map +1 -1
  79. package/src/lib/components/staff-org-ai-conversations.component.d.ts +9 -3
  80. package/src/lib/components/staff-org-ai-conversations.component.js +174 -83
  81. package/src/lib/components/staff-org-ai-conversations.component.js.map +1 -1
  82. package/src/lib/components/staff-user-ai-usage-card.component.js +2 -1
  83. package/src/lib/components/staff-user-ai-usage-card.component.js.map +1 -1
  84. package/src/lib/jobs/ai-job-admission.d.ts +2 -1
  85. package/src/lib/jobs/ai-job-admission.js.map +1 -1
  86. package/src/lib/jobs/ai-job-email-step.d.ts +39 -0
  87. package/src/lib/jobs/ai-job-email-step.js +116 -5
  88. package/src/lib/jobs/ai-job-email-step.js.map +1 -1
  89. package/src/lib/jobs/ai-job-layout-language.d.ts +3 -0
  90. package/src/lib/jobs/ai-job-layout-language.js +6 -1
  91. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  92. package/src/lib/jobs/ai-job-layout-site-pages.d.ts +14 -0
  93. package/src/lib/jobs/ai-job-layout-site-pages.js +43 -11
  94. package/src/lib/jobs/ai-job-layout-site-pages.js.map +1 -1
  95. package/src/lib/jobs/ai-job-layout-step.js +3 -2
  96. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  97. package/src/lib/jobs/ai-job-page-language.d.ts +26 -1
  98. package/src/lib/jobs/ai-job-page-language.js +75 -13
  99. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  100. package/src/lib/jobs/ai-job-page-step.d.ts +3 -0
  101. package/src/lib/jobs/ai-job-page-step.js +32 -7
  102. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  103. package/src/lib/jobs/ai-job-plan-step.d.ts +34 -0
  104. package/src/lib/jobs/ai-job-plan-step.js +106 -14
  105. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  106. package/src/lib/jobs/ai-job-site-content.d.ts +81 -13
  107. package/src/lib/jobs/ai-job-site-content.js +210 -43
  108. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  109. package/src/lib/jobs/ai-job-site-step.d.ts +19 -0
  110. package/src/lib/jobs/ai-job-site-step.js +152 -51
  111. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  112. package/src/lib/jobs/ai-jobs.d.ts +8 -4
  113. package/src/lib/jobs/ai-jobs.js +19 -2
  114. package/src/lib/jobs/ai-jobs.js.map +1 -1
  115. package/src/lib/jobs/ai-layout-stock-photos.d.ts +98 -0
  116. package/src/lib/jobs/ai-layout-stock-photos.js +248 -0
  117. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -0
  118. package/src/lib/jobs/ai-site-publish.d.ts +11 -0
  119. package/src/lib/jobs/ai-site-publish.js +31 -1
  120. package/src/lib/jobs/ai-site-publish.js.map +1 -1
  121. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.d.ts +151 -0
  122. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js +183 -0
  123. package/src/lib/jobs/fixtures/ai-juniper-portfolio-page-recording.js.map +1 -0
  124. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.d.ts +27 -0
  125. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js +192 -0
  126. package/src/lib/jobs/fixtures/ai-slow-roads-site-plan-recording.js.map +1 -0
  127. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.d.ts +27 -0
  128. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js +222 -0
  129. package/src/lib/jobs/fixtures/ai-yoga-site-plan-recording.js.map +1 -0
  130. package/src/lib/layout-language/ai-layout-compiler.d.ts +37 -0
  131. package/src/lib/layout-language/ai-layout-compiler.js +1003 -19
  132. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  133. package/src/lib/layout-language/ai-layout-design.d.ts +114 -0
  134. package/src/lib/layout-language/ai-layout-design.js +209 -0
  135. package/src/lib/layout-language/ai-layout-design.js.map +1 -0
  136. package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
  137. package/src/lib/layout-language/ai-layout-frame.js +33 -8
  138. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  139. package/src/lib/layout-language/ai-layout-gaps.js +18 -2
  140. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  141. package/src/lib/layout-language/ai-layout-language.d.ts +2 -0
  142. package/src/lib/layout-language/ai-layout-language.js +15 -1
  143. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  144. package/src/lib/layout-language/ai-layout-links.d.ts +36 -0
  145. package/src/lib/layout-language/ai-layout-links.js +26 -2
  146. package/src/lib/layout-language/ai-layout-links.js.map +1 -1
  147. package/src/lib/layout-language/ai-layout-listings.d.ts +125 -0
  148. package/src/lib/layout-language/ai-layout-listings.js +187 -0
  149. package/src/lib/layout-language/ai-layout-listings.js.map +1 -0
  150. package/src/lib/layout-language/ai-layout-pictures.d.ts +11 -5
  151. package/src/lib/layout-language/ai-layout-pictures.js +80 -17
  152. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  153. package/src/lib/model/ai-job-notice.d.ts +9 -1
  154. package/src/lib/model/ai-job-notice.js +23 -0
  155. package/src/lib/model/ai-job-notice.js.map +1 -1
  156. package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
  157. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  158. package/src/lib/model/ai-site-build-progress.d.ts +12 -2
  159. package/src/lib/model/ai-site-build-progress.js +104 -15
  160. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  161. package/src/lib/model/ai-site-job.d.ts +236 -24
  162. package/src/lib/model/ai-site-job.js +269 -22
  163. package/src/lib/model/ai-site-job.js.map +1 -1
  164. package/src/lib/model/ai-site-kinds.d.ts +30 -8
  165. package/src/lib/model/ai-site-kinds.js +735 -116
  166. package/src/lib/model/ai-site-kinds.js.map +1 -1
  167. package/src/lib/model/ai-site-look.d.ts +21 -0
  168. package/src/lib/model/ai-site-look.js +63 -14
  169. package/src/lib/model/ai-site-look.js.map +1 -1
  170. package/src/lib/model/ai-site-start-seo.d.ts +30 -0
  171. package/src/lib/model/ai-site-start-seo.js +132 -11
  172. package/src/lib/model/ai-site-start-seo.js.map +1 -1
  173. package/src/lib/model/ai-site-start.d.ts +2 -2
  174. package/src/lib/model/ai-site-start.js +3 -3
  175. package/src/lib/model/ai-site-start.js.map +1 -1
  176. package/src/lib/plugin.js +15 -3
  177. package/src/lib/plugin.js.map +1 -1
  178. package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -1
  179. package/src/lib/runtime/ai-doctrine-validators.js +227 -4
  180. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  181. package/src/lib/runtime/ai-doctrine.js +5 -3
  182. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  183. package/src/lib/runtime/ai-palette.generated.d.ts +2 -0
  184. package/src/lib/runtime/ai-palette.generated.js +31 -5
  185. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  186. package/src/lib/server/ai-admin-conversations.d.ts +9 -0
  187. package/src/lib/server/ai-admin-conversations.js +151 -9
  188. package/src/lib/server/ai-admin-conversations.js.map +1 -1
  189. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  190. package/src/lib/server/ai-jobs-route.js +15 -3
  191. package/src/lib/server/ai-jobs-route.js.map +1 -1
  192. package/src/lib/server/assist-edit.js +5 -2
  193. package/src/lib/server/assist-edit.js.map +1 -1
  194. package/src/lib/usage/free-ai-credits-left.d.ts +54 -0
  195. package/src/lib/usage/free-ai-credits-left.js +62 -0
  196. package/src/lib/usage/free-ai-credits-left.js.map +1 -0
  197. package/src/lib/usage/staff-org-ai-conversations.d.ts +23 -0
  198. package/src/lib/usage/staff-org-ai-conversations.js +45 -0
  199. package/src/lib/usage/staff-org-ai-conversations.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-campaign-create.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport type { ConsoleWidgetEntitlementProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { checkQuota } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useHostOrgId, useOrgPlan, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Link,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { AiJobFollow } from './ai-job-follow.component'\nimport { publishAiJob } from './ai-jobs-store'\nimport { AiUpsellButton } from './ai-upsell-dialog.component'\n\n/**\n * \"Create with AI\" on the Campaigns section (AGL-3603), in the zone the\n * marketing plugin hosts beside Create campaign: a brief starts the\n * `campaign` job, which writes an email design and the DRAFT campaign that\n * would send it — aimed at nobody, scheduled for nothing, sent by nobody until\n * a member does — or, where the workspace's plan sends no campaign email, the\n * `email` job, which writes the design on its own.\n *\n * The choice is the runner's own: the campaign step and its admission ask\n * `checkQuota(org, 'emailSendsPerMonth', 0)`, and this widget asks the same of\n * the same org document before it offers either. A plan that changes between\n * the two is the door's to refuse, and the dialog then offers the design on\n * its own rather than a dead end.\n *\n * The campaign is the marketing plugin's and neither plugin imports the other:\n * the props below restate the half of the zone's contract this widget reads.\n */\n\n/** One site a campaign could be placed on, as the zone hands it over. */\nexport interface AiCampaignSite {\n id: string\n name: string\n}\n\n/** What the `hostCampaigns` zone hands this widget. */\nexport interface AiCreateCampaignButtonProps extends ConsoleWidgetEntitlementProps {\n hostId: string | null\n orgId: string | null\n sites: readonly AiCampaignSite[]\n}\n\n/** The longest brief a job admits. */\nconst BRIEF_MAX_CHARS = 4_000\n\n/** The longest name a campaign job reads. */\nconst NAME_MAX_CHARS = 120\n\nconst FAILED_COPY = 'The campaign could not be started. Try again.'\n\n/**\n * The campaign door's refusal for a plan that sends no campaign email, as the\n * campaign step words it, so a plan that changed after the dialog opened is\n * recognized and answered with the design-only path.\n */\nconst PLAN_REFUSAL_PREFIX = 'Campaign email is not included'\n\n/** What the dialog says for each of the two jobs it can start. */\nexport const AI_CAMPAIGN_CREATE_COPY = {\n campaign: {\n title: 'Create a campaign with AI',\n next:\n 'It writes an email design with three subject lines and three preheaders, and a draft ' +\n 'campaign that would send it. Nothing is sent or scheduled, and the campaign is aimed at ' +\n 'nobody until you pick its lists.',\n submit: 'Write the campaign',\n started:\n 'The campaign is being written. When it is done, open the draft campaign to pick who ' +\n 'receives it, check the email and schedule it.',\n },\n email: {\n title: 'Write a campaign email with AI',\n next:\n 'This workspace’s plan does not send campaign email, so this writes the email design on ' +\n 'its own, with three subject lines and three preheaders. Upgrade in Billing to draft a ' +\n 'campaign around it.',\n submit: 'Plan the email',\n started:\n 'The email is being planned. Review the plan here or in AI jobs, and confirm it to build. ' +\n 'The design is built as a draft and sent to nobody.',\n },\n} as const\n\nexport type AiCampaignJobKind = keyof typeof AI_CAMPAIGN_CREATE_COPY\n\n/** Which job a workspace's plan allows: a campaign where it sends campaign email, else the design alone. */\nexport function aiCampaignJobKind(org: object | null | undefined): AiCampaignJobKind {\n return checkQuota(org as never, 'emailSendsPerMonth', 0).allowed ? 'campaign' : 'email'\n}\n\n/** The job's inputs: the name the member gave it, when they gave one. */\nexport function aiCampaignJobInputs(name: string): Record<string, string> {\n const trimmed = name.replace(/\\s+/g, ' ').trim().slice(0, NAME_MAX_CHARS)\n return trimmed ? { name: trimmed } : {}\n}\n\nexport function AiCreateCampaignButton(props: AiCreateCampaignButtonProps) {\n const { hostId, sites } = props\n const { data: user } = useUser()\n const userRef = useRef(user)\n userRef.current = user\n const [open, setOpen] = useState(false)\n const [siteId, setSiteId] = useState<string>(hostId ?? sites[0]?.id ?? '')\n const site = hostId ?? siteId\n const siteOrgId = useHostOrgId(site || undefined) ?? undefined\n const orgId = props.orgId ?? siteOrgId\n const { org, ready } = useOrgPlan(site || undefined)\n const [refusedPlan, setRefusedPlan] = useState(false)\n const kind: AiCampaignJobKind = refusedPlan ? 'email' : aiCampaignJobKind(org)\n const copy = AI_CAMPAIGN_CREATE_COPY[kind]\n const [name, setName] = useState('')\n const [brief, setBrief] = useState('')\n const [busy, setBusy] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n const [started, setStarted] = useState<AiJobSummary | null>(null)\n\n // The org hub's sites settle after the widget mounts; take the first once they do.\n useEffect(() => {\n if (!hostId && !siteId && sites[0]) setSiteId(sites[0].id)\n }, [hostId, siteId, sites])\n\n useEffect(() => {\n if (!open) return\n setNotice(null)\n setStarted(null)\n setRefusedPlan(false)\n }, [open])\n\n const start = useCallback(async () => {\n if (!orgId || !site || !brief.trim()) return\n setBusy(true)\n setNotice(null)\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({\n orgId,\n hostId: site,\n kind,\n brief: brief.trim(),\n inputs: aiCampaignJobInputs(name),\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n const error = String(payload?.error ?? FAILED_COPY)\n if (kind === 'campaign' && response.status === 403 && error.startsWith(PLAN_REFUSAL_PREFIX)) {\n setRefusedPlan(true)\n }\n setNotice(locked ? lockdownRefusalText(locked) : error)\n return\n }\n const job = (payload?.job as AiJobSummary | undefined) ?? null\n if (!job) {\n setNotice(FAILED_COPY)\n return\n }\n publishAiJob(job)\n setStarted(job)\n setBrief('')\n setName('')\n } catch {\n setNotice(FAILED_COPY)\n } finally {\n setBusy(false)\n }\n }, [orgId, site, kind, brief, name])\n\n if (!hostId && !sites.length) return null\n // Drawn from the shell's gates alone (AGL-3601): nothing asks a server until\n // it is used, and a plan without the add-on gets the add-on's dialog.\n if (props.entitled === false) {\n return props.upgrade ? <AiUpsellButton kind=\"campaign\" upgrade={props.upgrade} /> : null\n }\n\n const help = pluginDocsHelp('aiMarketing', {\n anchor: '#create-a-campaign',\n excerpt:\n 'Writes an email design and a draft campaign that would send it, from a brief. Nothing is sent or scheduled, and the campaign is aimed at nobody until you pick its lists.',\n })\n\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {'Create with AI'}\n </Button>\n <Dialog open={open} onClose={busy ? undefined : () => setOpen(false)} fullWidth maxWidth=\"sm\">\n <DialogTitle>{copy.title}</DialogTitle>\n <DialogContent>\n {started && orgId ? (\n <AiJobFollow\n job={started}\n orgId={orgId}\n user={user}\n intro={AI_CAMPAIGN_CREATE_COPY[started.kind === 'email' ? 'email' : 'campaign'].started}\n onOpenJobs={() => setOpen(false)}\n />\n ) : (\n <Stack spacing={2} sx={{ pt: 1 }}>\n {hostId ? null : (\n <TextField\n select\n size=\"small\"\n label=\"Site\"\n helperText=\"The site the campaign is placed on and sent as.\"\n value={siteId}\n onChange={(event) => setSiteId(event.target.value)}\n disabled={busy}\n >\n {sites.map((entry) => (\n <MenuItem key={entry.id} value={entry.id}>\n {entry.name || entry.id}\n </MenuItem>\n ))}\n </TextField>\n )}\n <TextField\n size=\"small\"\n label=\"Campaign name (optional)\"\n value={name}\n onChange={(event) => setName(event.target.value.slice(0, NAME_MAX_CHARS))}\n disabled={busy}\n />\n <TextField\n label=\"What is the campaign for?\"\n placeholder=\"Announce the autumn menu to our regulars, and point them at the class page to book the Tuesday bread class\"\n multiline\n minRows={4}\n value={brief}\n onChange={(event) => setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS))}\n disabled={busy}\n autoFocus\n />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {copy.next}{' '}\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Typography>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n </Stack>\n )}\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setOpen(false)} disabled={busy}>\n {started ? 'Close' : 'Cancel'}\n </Button>\n {started ? null : (\n <Button\n variant=\"contained\"\n onClick={() => void start()}\n disabled={busy || !ready || !orgId || !site || !brief.trim()}\n >\n {copy.submit}\n </Button>\n )}\n </DialogActions>\n </Dialog>\n </>\n )\n}\nAiCreateCampaignButton.displayName = 'AiCreateCampaignButton'\n\nexport default AiCreateCampaignButton\n"],"names":["lockdownRefusalText","parseLockdownRefusal","pluginDocsHelp","checkQuota","mdiCreation","MdiIcon","authorizedFetch","useHostOrgId","useOrgPlan","useUser","Alert","Button","Dialog","DialogActions","DialogContent","DialogTitle","Link","MenuItem","Stack","TextField","Typography","useCallback","useEffect","useRef","useState","AiJobFollow","publishAiJob","AiUpsellButton","BRIEF_MAX_CHARS","NAME_MAX_CHARS","FAILED_COPY","PLAN_REFUSAL_PREFIX","AI_CAMPAIGN_CREATE_COPY","campaign","title","next","submit","started","email","aiCampaignJobKind","org","allowed","aiCampaignJobInputs","name","trimmed","replace","trim","slice","AiCreateCampaignButton","props","hostId","sites","data","user","userRef","current","open","setOpen","siteId","setSiteId","id","site","siteOrgId","undefined","orgId","ready","refusedPlan","setRefusedPlan","kind","copy","setName","brief","setBrief","busy","setBusy","notice","setNotice","setStarted","start","response","method","headers","body","JSON","stringify","inputs","payload","json","catch","ok","locked","status","error","String","startsWith","job","length","entitled","upgrade","help","anchor","excerpt","size","variant","startIcon","path","onClick","onClose","fullWidth","maxWidth","intro","onOpenJobs","spacing","sx","pt","select","label","helperText","value","onChange","event","target","disabled","map","entry","placeholder","multiline","minRows","autoFocus","color","href","rel","severity","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-campaign-create.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport type { ConsoleWidgetEntitlementProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { lockdownRefusalText, parseLockdownRefusal } from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { checkQuota } from '@aglyn/aglyn/app-utils/plan-entitlements'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useHostOrgId, useOrgPlan, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Link,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { AiJobFollow } from './ai-job-follow.component'\nimport { publishAiJob } from './ai-jobs-store'\nimport { AiUpsellButton } from './ai-upsell-dialog.component'\n\n/**\n * \"Create with AI\" on the Campaigns section (AGL-3603), in the zone the\n * marketing plugin hosts beside Create campaign: a brief starts the\n * `campaign` job, which writes an email design and the DRAFT campaign that\n * would send it — aimed at nobody, scheduled for nothing, sent by nobody until\n * a member does — or, where the workspace's plan sends no campaign email, the\n * `email` job, which writes the design on its own.\n *\n * The choice is the runner's own: the campaign step and its admission ask\n * `checkQuota(org, 'emailSendsPerMonth', 0)`, and this widget asks the same of\n * the same org document before it offers either. A plan that changes between\n * the two is the door's to refuse, and the dialog then offers the design on\n * its own rather than a dead end.\n *\n * The campaign is the marketing plugin's and neither plugin imports the other:\n * the props below restate the half of the zone's contract this widget reads.\n */\n\n/** One site a campaign could be placed on, as the zone hands it over. */\nexport interface AiCampaignSite {\n id: string\n name: string\n}\n\n/** What the `hostCampaigns` zone hands this widget. */\nexport interface AiCreateCampaignButtonProps extends ConsoleWidgetEntitlementProps {\n hostId: string | null\n orgId: string | null\n sites: readonly AiCampaignSite[]\n}\n\n/** The longest brief a job admits. */\nconst BRIEF_MAX_CHARS = 4_000\n\n/** The longest name a campaign job reads. */\nconst NAME_MAX_CHARS = 120\n\nconst FAILED_COPY = 'The campaign could not be started. Try again.'\n\n/**\n * The campaign door's refusal for a plan that sends no campaign email, as the\n * campaign step words it, so a plan that changed after the dialog opened is\n * recognized and answered with the design-only path.\n */\nconst PLAN_REFUSAL_PREFIX = 'Campaign email is not included'\n\n/** What the dialog says for each of the two jobs it can start. */\nexport const AI_CAMPAIGN_CREATE_COPY = {\n campaign: {\n title: 'Create a campaign with AI',\n next:\n 'It writes an email design with three subject lines and three preheaders, and a draft ' +\n 'campaign that would send it. Nothing is sent or scheduled, and the campaign is aimed at ' +\n 'nobody until you pick its lists.',\n submit: 'Write the campaign',\n started:\n 'The campaign is being written. When it is done, open the draft campaign to pick who ' +\n 'receives it, check the email and schedule it.',\n },\n email: {\n title: 'Write a campaign email with AI',\n next:\n 'This workspace’s plan does not send campaign email, so this writes the email design on ' +\n 'its own, with three subject lines and three preheaders. Upgrade in Billing to draft a ' +\n 'campaign around it.',\n submit: 'Plan the email',\n started:\n 'The email is being planned. Review the plan here or in AI jobs, and confirm it to build. ' +\n 'The design is built as a draft and sent to nobody.',\n },\n} as const\n\nexport type AiCampaignJobKind = keyof typeof AI_CAMPAIGN_CREATE_COPY\n\n/** Which job a workspace's plan allows: a campaign where it sends campaign email, else the design alone. */\nexport function aiCampaignJobKind(org: object | null | undefined): AiCampaignJobKind {\n return checkQuota(org as never, 'emailSendsPerMonth', 0).allowed ? 'campaign' : 'email'\n}\n\n/** The job's inputs: the name the member gave it, when they gave one. */\nexport function aiCampaignJobInputs(name: string): Record<string, string> {\n const trimmed = name.replace(/\\s+/g, ' ').trim().slice(0, NAME_MAX_CHARS)\n return trimmed ? { name: trimmed } : {}\n}\n\nexport function AiCreateCampaignButton(props: AiCreateCampaignButtonProps) {\n const { hostId, sites } = props\n const { data: user } = useUser()\n const userRef = useRef(user)\n userRef.current = user\n const [open, setOpen] = useState(false)\n const [siteId, setSiteId] = useState<string>(hostId ?? sites[0]?.id ?? '')\n const site = hostId ?? siteId\n const siteOrgId = useHostOrgId(site || undefined) ?? undefined\n const orgId = props.orgId ?? siteOrgId\n const { org, ready } = useOrgPlan(site || undefined)\n const [refusedPlan, setRefusedPlan] = useState(false)\n const kind: AiCampaignJobKind = refusedPlan ? 'email' : aiCampaignJobKind(org)\n const copy = AI_CAMPAIGN_CREATE_COPY[kind]\n const [name, setName] = useState('')\n const [brief, setBrief] = useState('')\n const [busy, setBusy] = useState(false)\n const [notice, setNotice] = useState<string | null>(null)\n const [started, setStarted] = useState<AiJobSummary | null>(null)\n\n // The org hub's sites settle after the widget mounts; take the first once they do.\n useEffect(() => {\n if (!hostId && !siteId && sites[0]) setSiteId(sites[0].id)\n }, [hostId, siteId, sites])\n\n useEffect(() => {\n if (!open) return\n setNotice(null)\n setStarted(null)\n setRefusedPlan(false)\n }, [open])\n\n const start = useCallback(async () => {\n if (!orgId || !site || !brief.trim()) return\n setBusy(true)\n setNotice(null)\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({\n orgId,\n hostId: site,\n kind,\n brief: brief.trim(),\n inputs: aiCampaignJobInputs(name),\n }),\n })\n const payload = await response.json().catch(() => null)\n if (!response.ok) {\n const locked = parseLockdownRefusal(response.status, payload)\n const error = String(payload?.error ?? FAILED_COPY)\n if (kind === 'campaign' && response.status === 403 && error.startsWith(PLAN_REFUSAL_PREFIX)) {\n setRefusedPlan(true)\n }\n setNotice(locked ? lockdownRefusalText(locked) : error)\n return\n }\n const job = (payload?.job as AiJobSummary | undefined) ?? null\n if (!job) {\n setNotice(FAILED_COPY)\n return\n }\n publishAiJob(job)\n setStarted(job)\n setBrief('')\n setName('')\n } catch {\n setNotice(FAILED_COPY)\n } finally {\n setBusy(false)\n }\n }, [orgId, site, kind, brief, name])\n\n if (!hostId && !sites.length) return null\n // Drawn from the shell's gates alone (AGL-3601): nothing asks a server until\n // it is used, and a plan without the add-on gets the add-on's dialog.\n if (props.entitled === false) {\n return props.upgrade ? <AiUpsellButton kind=\"campaign\" upgrade={props.upgrade} /> : null\n }\n\n const help = pluginDocsHelp('aiMarketing', {\n anchor: '#create-a-campaign',\n excerpt:\n 'Writes an email design and a draft campaign that would send it, from a brief. Nothing is sent or scheduled, and the campaign is aimed at nobody until you pick its lists.',\n })\n\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {'Create with AI'}\n </Button>\n <Dialog open={open} onClose={busy ? undefined : () => setOpen(false)} fullWidth maxWidth=\"sm\">\n <DialogTitle>{copy.title}</DialogTitle>\n <DialogContent>\n {started && orgId ? (\n <AiJobFollow\n job={started}\n orgId={orgId}\n user={user}\n intro={AI_CAMPAIGN_CREATE_COPY[started.kind === 'email' ? 'email' : 'campaign'].started}\n onOpenJobs={() => setOpen(false)}\n />\n ) : (\n <Stack spacing={2} sx={{ pt: 1 }}>\n {hostId ? null : (\n <TextField\n select\n size=\"small\"\n label=\"Site\"\n helperText=\"The site the campaign is placed on and sent as.\"\n value={siteId}\n onChange={(event) => setSiteId(event.target.value)}\n disabled={busy}\n >\n {sites.map((entry) => (\n <MenuItem key={entry.id} value={entry.id}>\n {entry.name || entry.id}\n </MenuItem>\n ))}\n </TextField>\n )}\n <TextField\n size=\"small\"\n label=\"Campaign name (optional)\"\n value={name}\n onChange={(event) => setName(event.target.value.slice(0, NAME_MAX_CHARS))}\n disabled={busy}\n />\n <TextField\n label=\"What is the campaign for?\"\n placeholder=\"Announce the autumn menu to our regulars, and point them at the class page to book the Tuesday bread class\"\n multiline\n minRows={4}\n value={brief}\n onChange={(event) => setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS))}\n disabled={busy}\n autoFocus\n />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {copy.next}{' '}\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" title={help.excerptText}>\n {'How it works'}\n </Link>\n </Typography>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n </Stack>\n )}\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setOpen(false)} disabled={busy}>\n {started ? 'Close' : 'Cancel'}\n </Button>\n {started ? null : (\n <Button\n variant=\"contained\"\n onClick={() => void start()}\n disabled={busy || !ready || !orgId || !site || !brief.trim()}\n >\n {copy.submit}\n </Button>\n )}\n </DialogActions>\n </Dialog>\n </>\n )\n}\nAiCreateCampaignButton.displayName = 'AiCreateCampaignButton'\n\nexport default AiCreateCampaignButton\n"],"names":["lockdownRefusalText","parseLockdownRefusal","pluginDocsHelp","checkQuota","mdiCreation","MdiIcon","authorizedFetch","useHostOrgId","useOrgPlan","useUser","Alert","Button","Dialog","DialogActions","DialogContent","DialogTitle","Link","MenuItem","Stack","TextField","Typography","useCallback","useEffect","useRef","useState","AiJobFollow","publishAiJob","AiUpsellButton","BRIEF_MAX_CHARS","NAME_MAX_CHARS","FAILED_COPY","PLAN_REFUSAL_PREFIX","AI_CAMPAIGN_CREATE_COPY","campaign","title","next","submit","started","email","aiCampaignJobKind","org","allowed","aiCampaignJobInputs","name","trimmed","replace","trim","slice","AiCreateCampaignButton","props","hostId","sites","data","user","userRef","current","open","setOpen","siteId","setSiteId","id","site","siteOrgId","undefined","orgId","ready","refusedPlan","setRefusedPlan","kind","copy","setName","brief","setBrief","busy","setBusy","notice","setNotice","setStarted","start","response","method","headers","body","JSON","stringify","inputs","payload","json","catch","ok","locked","status","error","String","startsWith","job","length","entitled","upgrade","help","anchor","excerpt","size","variant","startIcon","path","onClick","onClose","fullWidth","maxWidth","intro","onOpenJobs","spacing","sx","pt","select","label","helperText","value","onChange","event","target","disabled","map","entry","placeholder","multiline","minRows","autoFocus","color","href","rel","excerptText","severity","displayName"],"mappings":"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"}
@@ -81,9 +81,9 @@ import { useUser } from "@aglyn/tenant-feature-instance";
81
81
  const aiAddonSold = resolvePlanPricing(org).aiAddonMonthlyUsd != null;
82
82
  return /*#__PURE__*/ _jsxs(CardDisplay, {
83
83
  header: 'AI credits',
84
- help: pluginDocsHelp('billing', {
85
- anchor: '#usage-meters',
86
- excerpt: 'AI credits drawn this month against your included band, plan ' + 'plus the AI add-on where you have it.'
84
+ help: pluginDocsHelp('aiCredits', {
85
+ anchor: '#see-your-credits',
86
+ excerpt: 'AI credits drawn this month against your included band: your plan’s ' + 'credits plus the AI add-on’s. The band resets on the first of the month.'
87
87
  }),
88
88
  contentGutterX: true,
89
89
  contentGutterY: true,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-credits-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n AI_ADDON_CREDITS_PER_MONTH,\n aiAddonName,\n hasAiAddon,\n isUnlimitedQuota,\n resolveEffectivePlan,\n resolveOrgEntitlements,\n resolvePlanPricing,\n type AglynOrgBilling,\n} from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { UsageMeter } from '@aglyn/shared-ui-jsx/components/usage-meter.component'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { Link, Typography } from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\n\nexport interface AiCreditsCardProps {\n orgId?: string | null\n /** The billing-merged org doc the Usage page resolved. */\n org?: Partial<AglynOrgBilling> | null\n /** The Billing overview's path, for the one caption that points at the add-ons card. */\n billingHref?: string\n}\n\n/**\n * The AI credits meter (AGL-2899), on the Billing → Usage page's plugin\n * band below the platform's own meters (AGL-2939).\n *\n * Credits, because AI actions differ in cost by up to two orders of\n * magnitude — a question against generating a screen — and a message count\n * would price them the same. The band is refused at the cap, so this\n * readout is the only thing standing between a customer and learning their\n * limit by being turned down mid-build.\n *\n * Rendered only where a band is sold, which since AGL-3203 is every plan\n * row: Free carries the taste and Starter 750, so both render a meter even\n * though neither carries `aiAssist` — the band is credits, not the guided\n * rung. What still renders nothing is a band of ZERO, which now reaches this\n * component only as a per-org override, and a \"0 of 0\" meter is not a\n * readout of anything. ONE meter for one pool: the\n * add-on widens `assistCreditsPerMonth` rather than opening a second band,\n * so the limit here is already plan plus add-on and the caption under it\n * says how much of that the add-on is.\n *\n * From the server, not from Firestore like the platform's meters:\n * `orgs/{orgId}/assistUsage` is default-deny for every client, so a browser\n * read would fail silently and leave this reading \"not yet metered\" forever.\n * Credits, never dollars: the route is where the one conversion happens.\n */\nexport default function AiCreditsCard(props: AiCreditsCardProps) {\n const { orgId, org, billingHref } = props\n const { data: user } = useUser()\n const [credits, setCredits] = useState<{ used: number; limit: number | null } | null>(null)\n useEffect(() => {\n if (!orgId || !user) return undefined\n let active = true\n void (async () => {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/billing/credits?orgId=${encodeURIComponent(orgId)}`,\n )\n if (!response.ok) return\n const result = await response.json()\n if (active && result?.credits) setCredits(result.credits)\n } catch {\n // The meter keeps its \"not yet metered\" state on failure —\n // deliberately not 0, which reads as \"you have used none\".\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, user])\n\n const entitlements = resolveOrgEntitlements(org)\n if (entitlements.assistCreditsPerMonth <= 0) return null\n const plan = resolveEffectivePlan(org)\n const aiAddonCredits = hasAiAddon(org) ? AI_ADDON_CREDITS_PER_MONTH[plan] : 0\n // Sold to THIS workspace, not merely on its plan: a staff comp has no\n // subscription to add the item to, so its price list sells none (AGL-3034).\n const aiAddonSold = resolvePlanPricing(org).aiAddonMonthlyUsd != null\n return (\n <CardDisplay\n header={'AI credits'}\n help={pluginDocsHelp('billing', {\n anchor: '#usage-meters',\n excerpt:\n 'AI credits drawn this month against your included band, plan ' +\n 'plus the AI add-on where you have it.',\n })}\n contentGutterX\n contentGutterY\n >\n {/* An uncapped staff comp (AGL-3049) resolves this band `UNLIMITED`:\n the meter reads \"Unlimited\" and draws no bar, never \"/ Infinity\". */}\n <UsageMeter\n label=\"AI credits (this month)\"\n used={credits ? credits.used : null}\n limit={entitlements.assistCreditsPerMonth}\n unlimited={isUnlimitedQuota(entitlements.assistCreditsPerMonth)}\n upgradeHref={billingHref ? `${billingHref}#plans` : '#plans'}\n />\n {aiAddonCredits > 0 ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mt: -1.5, mb: 2 }}\n >\n {`Includes ${aiAddonCredits.toLocaleString()} credits a month from ` +\n `the ${aiAddonName()} add-on.`}\n </Typography>\n ) : null}\n {/* A plan with a band that sells the add-on and has not bought it:\n the one line on the meters that points at more capacity rather\n than at an upgrade. */}\n {!entitlements.features.aiGenerative && aiAddonSold ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mt: -1.5, mb: 2 }}\n >\n {`Generate pages, emails, campaigns and more with ${aiAddonName()}, ` +\n `and add ${AI_ADDON_CREDITS_PER_MONTH[plan].toLocaleString()} ` +\n 'credits a month to this pool: '}\n <Link href={`${billingHref ?? ''}#addons`} color=\"primary\" underline=\"hover\">\n {`Add ${aiAddonName()}`}\n </Link>\n {' in Billing → Plans.'}\n </Typography>\n ) : null}\n </CardDisplay>\n )\n}\n"],"names":["AI_ADDON_CREDITS_PER_MONTH","aiAddonName","hasAiAddon","isUnlimitedQuota","resolveEffectivePlan","resolveOrgEntitlements","resolvePlanPricing","pluginDocsHelp","CardDisplay","UsageMeter","authorizedFetch","Link","Typography","useEffect","useState","useUser","AiCreditsCard","props","orgId","org","billingHref","data","user","credits","setCredits","undefined","active","response","encodeURIComponent","ok","result","json","entitlements","assistCreditsPerMonth","plan","aiAddonCredits","aiAddonSold","aiAddonMonthlyUsd","header","help","anchor","excerpt","contentGutterX","contentGutterY","label","used","limit","unlimited","upgradeHref","variant","color","sx","display","mt","mb","toLocaleString","features","aiGenerative","href","underline"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-credits-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n AI_ADDON_CREDITS_PER_MONTH,\n aiAddonName,\n hasAiAddon,\n isUnlimitedQuota,\n resolveEffectivePlan,\n resolveOrgEntitlements,\n resolvePlanPricing,\n type AglynOrgBilling,\n} from '@aglyn/aglyn'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { UsageMeter } from '@aglyn/shared-ui-jsx/components/usage-meter.component'\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { Link, Typography } from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport { useUser } from '@aglyn/tenant-feature-instance'\n\nexport interface AiCreditsCardProps {\n orgId?: string | null\n /** The billing-merged org doc the Usage page resolved. */\n org?: Partial<AglynOrgBilling> | null\n /** The Billing overview's path, for the one caption that points at the add-ons card. */\n billingHref?: string\n}\n\n/**\n * The AI credits meter (AGL-2899), on the Billing → Usage page's plugin\n * band below the platform's own meters (AGL-2939).\n *\n * Credits, because AI actions differ in cost by up to two orders of\n * magnitude — a question against generating a screen — and a message count\n * would price them the same. The band is refused at the cap, so this\n * readout is the only thing standing between a customer and learning their\n * limit by being turned down mid-build.\n *\n * Rendered only where a band is sold, which since AGL-3203 is every plan\n * row: Free carries the taste and Starter 750, so both render a meter even\n * though neither carries `aiAssist` — the band is credits, not the guided\n * rung. What still renders nothing is a band of ZERO, which now reaches this\n * component only as a per-org override, and a \"0 of 0\" meter is not a\n * readout of anything. ONE meter for one pool: the\n * add-on widens `assistCreditsPerMonth` rather than opening a second band,\n * so the limit here is already plan plus add-on and the caption under it\n * says how much of that the add-on is.\n *\n * From the server, not from Firestore like the platform's meters:\n * `orgs/{orgId}/assistUsage` is default-deny for every client, so a browser\n * read would fail silently and leave this reading \"not yet metered\" forever.\n * Credits, never dollars: the route is where the one conversion happens.\n */\nexport default function AiCreditsCard(props: AiCreditsCardProps) {\n const { orgId, org, billingHref } = props\n const { data: user } = useUser()\n const [credits, setCredits] = useState<{ used: number; limit: number | null } | null>(null)\n useEffect(() => {\n if (!orgId || !user) return undefined\n let active = true\n void (async () => {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/billing/credits?orgId=${encodeURIComponent(orgId)}`,\n )\n if (!response.ok) return\n const result = await response.json()\n if (active && result?.credits) setCredits(result.credits)\n } catch {\n // The meter keeps its \"not yet metered\" state on failure —\n // deliberately not 0, which reads as \"you have used none\".\n }\n })()\n return () => {\n active = false\n }\n }, [orgId, user])\n\n const entitlements = resolveOrgEntitlements(org)\n if (entitlements.assistCreditsPerMonth <= 0) return null\n const plan = resolveEffectivePlan(org)\n const aiAddonCredits = hasAiAddon(org) ? AI_ADDON_CREDITS_PER_MONTH[plan] : 0\n // Sold to THIS workspace, not merely on its plan: a staff comp has no\n // subscription to add the item to, so its price list sells none (AGL-3034).\n const aiAddonSold = resolvePlanPricing(org).aiAddonMonthlyUsd != null\n return (\n <CardDisplay\n header={'AI credits'}\n help={pluginDocsHelp('aiCredits', {\n anchor: '#see-your-credits',\n excerpt:\n 'AI credits drawn this month against your included band: your plan’s ' +\n 'credits plus the AI add-on’s. The band resets on the first of the month.',\n })}\n contentGutterX\n contentGutterY\n >\n {/* An uncapped staff comp (AGL-3049) resolves this band `UNLIMITED`:\n the meter reads \"Unlimited\" and draws no bar, never \"/ Infinity\". */}\n <UsageMeter\n label=\"AI credits (this month)\"\n used={credits ? credits.used : null}\n limit={entitlements.assistCreditsPerMonth}\n unlimited={isUnlimitedQuota(entitlements.assistCreditsPerMonth)}\n upgradeHref={billingHref ? `${billingHref}#plans` : '#plans'}\n />\n {aiAddonCredits > 0 ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mt: -1.5, mb: 2 }}\n >\n {`Includes ${aiAddonCredits.toLocaleString()} credits a month from ` +\n `the ${aiAddonName()} add-on.`}\n </Typography>\n ) : null}\n {/* A plan with a band that sells the add-on and has not bought it:\n the one line on the meters that points at more capacity rather\n than at an upgrade. */}\n {!entitlements.features.aiGenerative && aiAddonSold ? (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mt: -1.5, mb: 2 }}\n >\n {`Generate pages, emails, campaigns and more with ${aiAddonName()}, ` +\n `and add ${AI_ADDON_CREDITS_PER_MONTH[plan].toLocaleString()} ` +\n 'credits a month to this pool: '}\n <Link href={`${billingHref ?? ''}#addons`} color=\"primary\" underline=\"hover\">\n {`Add ${aiAddonName()}`}\n </Link>\n {' in Billing → Plans.'}\n </Typography>\n ) : null}\n </CardDisplay>\n )\n}\n"],"names":["AI_ADDON_CREDITS_PER_MONTH","aiAddonName","hasAiAddon","isUnlimitedQuota","resolveEffectivePlan","resolveOrgEntitlements","resolvePlanPricing","pluginDocsHelp","CardDisplay","UsageMeter","authorizedFetch","Link","Typography","useEffect","useState","useUser","AiCreditsCard","props","orgId","org","billingHref","data","user","credits","setCredits","undefined","active","response","encodeURIComponent","ok","result","json","entitlements","assistCreditsPerMonth","plan","aiAddonCredits","aiAddonSold","aiAddonMonthlyUsd","header","help","anchor","excerpt","contentGutterX","contentGutterY","label","used","limit","unlimited","upgradeHref","variant","color","sx","display","mt","mb","toLocaleString","features","aiGenerative","href","underline"],"mappings":"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"}
@@ -105,7 +105,7 @@ const emailKindOf = (kind)=>AI_CRM_EMAIL_RECORD_KINDS.includes(kind) ? kind : nu
105
105
  target: "_blank",
106
106
  rel: "noopener",
107
107
  variant: "caption",
108
- title: help.excerpt,
108
+ title: help.excerptText,
109
109
  children: 'How it works'
110
110
  })
111
111
  ]
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-crm-email-draft.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { ConsoleRecordEmailZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { useHostOrgId } from '@aglyn/tenant-feature-instance'\nimport { Alert, Box, Button, CircularProgress, Link, Stack, TextField, Typography } from '@mui/material'\nimport { useMemo, useState } from 'react'\nimport {\n AI_CRM_EMAIL_RECORD_KINDS,\n AI_CRM_EMAIL_REQUEST_MAX_CHARS,\n type AiCrmAnswerWanted,\n type AiCrmRecordKind,\n} from '../model/ai-crm'\nimport { useAiCrmAnswer } from './use-ai-crm-answer'\n\nconst emailKindOf = (kind: string): AiCrmRecordKind | null =>\n (AI_CRM_EMAIL_RECORD_KINDS as readonly string[]).includes(kind) ? (kind as AiCrmRecordKind) : null\n\n/**\n * \"Draft the message\" (AGL-2917), in the CRM's one-to-one composer through\n * the `recordEmail` zone: the member says what the email should say, a `crm`\n * job drafts a subject and a message from that and the record's timeline, and\n * the draft goes into the composer through `proposeDraft`, which asks before\n * it replaces a written message.\n *\n * Nothing here sends anything, and nothing can: the composer's Send is the\n * member's. A draft is asked afresh each time, so the card recalls no earlier\n * one, and the answer door serves a draft only to the member who asked.\n */\nexport function AiCrmEmailDraft(props: ConsoleRecordEmailZoneProps) {\n const { hostId, record, proposeDraft } = props\n const hostOrgId = useHostOrgId(props.orgId || !hostId ? undefined : hostId)\n const orgId = props.orgId ?? hostOrgId ?? undefined\n const kind = emailKindOf(record.kind)\n const recordId = record.id\n const wanted = useMemo<AiCrmAnswerWanted | null>(\n () => (kind ? { kind: 'email', record: { kind, id: recordId } } : null),\n [kind, recordId],\n )\n const run = useAiCrmAnswer({ orgId, hostId, wanted, recall: 'none' })\n const [request, setRequest] = useState('')\n\n if (!kind || run.verdict !== 'ready') return null\n\n const working = run.busy || run.running\n const help = pluginDocsHelp('aiCrm', {\n anchor: '#draft-an-email',\n excerpt: 'Describe the email and a draft goes into the message. Nothing is sent until you press Send.',\n })\n\n const draft = async () => {\n const answer = await run.ask(request.trim(), { task: 'email', record: kind, recordId })\n if (answer?.proposal.kind !== 'email') return\n proposeDraft({ subject: answer.proposal.subject, body: answer.proposal.body }, answer.jobId)\n }\n\n return (\n <Box sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }} aria-label=\"Draft with AI\">\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline' }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Draft with AI'}\n </Typography>\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" variant=\"caption\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Stack>\n <TextField\n size=\"small\"\n multiline\n minRows={2}\n label=\"What should the email say?\"\n placeholder=\"Follow up on the quote and offer a call next week\"\n helperText=\"Written from what you ask and this record's timeline. Nothing is sent until you press Send.\"\n value={request}\n onChange={(event) => setRequest(event.target.value.slice(0, AI_CRM_EMAIL_REQUEST_MAX_CHARS))}\n disabled={working}\n />\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={working || !orgId || !request.trim()}\n onClick={() => void draft()}\n >\n {'Draft the message'}\n </Button>\n {working ? <CircularProgress size={16} aria-label=\"Drafting the message\" /> : null}\n </Stack>\n {run.notice ? <Alert severity=\"warning\">{run.notice}</Alert> : null}\n {run.job?.error && !run.answer ? (\n <Alert severity={run.job.status === 'failed' ? 'error' : 'warning'}>{run.job.error}</Alert>\n ) : null}\n </Stack>\n </Box>\n )\n}\nAiCrmEmailDraft.displayName = 'AiCrmEmailDraft'\n\nexport default AiCrmEmailDraft\n"],"names":["pluginDocsHelp","useHostOrgId","Alert","Box","Button","CircularProgress","Link","Stack","TextField","Typography","useMemo","useState","AI_CRM_EMAIL_RECORD_KINDS","AI_CRM_EMAIL_REQUEST_MAX_CHARS","useAiCrmAnswer","emailKindOf","kind","includes","AiCrmEmailDraft","props","run","hostId","record","proposeDraft","hostOrgId","orgId","undefined","recordId","id","wanted","recall","request","setRequest","verdict","working","busy","running","help","anchor","excerpt","draft","answer","ask","trim","task","proposal","subject","body","jobId","sx","border","borderColor","borderRadius","p","aria-label","spacing","direction","alignItems","variant","flexGrow","href","target","rel","title","size","multiline","minRows","label","placeholder","helperText","value","onChange","event","slice","disabled","onClick","notice","severity","job","error","status","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-crm-email-draft.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { ConsoleRecordEmailZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { useHostOrgId } from '@aglyn/tenant-feature-instance'\nimport { Alert, Box, Button, CircularProgress, Link, Stack, TextField, Typography } from '@mui/material'\nimport { useMemo, useState } from 'react'\nimport {\n AI_CRM_EMAIL_RECORD_KINDS,\n AI_CRM_EMAIL_REQUEST_MAX_CHARS,\n type AiCrmAnswerWanted,\n type AiCrmRecordKind,\n} from '../model/ai-crm'\nimport { useAiCrmAnswer } from './use-ai-crm-answer'\n\nconst emailKindOf = (kind: string): AiCrmRecordKind | null =>\n (AI_CRM_EMAIL_RECORD_KINDS as readonly string[]).includes(kind) ? (kind as AiCrmRecordKind) : null\n\n/**\n * \"Draft the message\" (AGL-2917), in the CRM's one-to-one composer through\n * the `recordEmail` zone: the member says what the email should say, a `crm`\n * job drafts a subject and a message from that and the record's timeline, and\n * the draft goes into the composer through `proposeDraft`, which asks before\n * it replaces a written message.\n *\n * Nothing here sends anything, and nothing can: the composer's Send is the\n * member's. A draft is asked afresh each time, so the card recalls no earlier\n * one, and the answer door serves a draft only to the member who asked.\n */\nexport function AiCrmEmailDraft(props: ConsoleRecordEmailZoneProps) {\n const { hostId, record, proposeDraft } = props\n const hostOrgId = useHostOrgId(props.orgId || !hostId ? undefined : hostId)\n const orgId = props.orgId ?? hostOrgId ?? undefined\n const kind = emailKindOf(record.kind)\n const recordId = record.id\n const wanted = useMemo<AiCrmAnswerWanted | null>(\n () => (kind ? { kind: 'email', record: { kind, id: recordId } } : null),\n [kind, recordId],\n )\n const run = useAiCrmAnswer({ orgId, hostId, wanted, recall: 'none' })\n const [request, setRequest] = useState('')\n\n if (!kind || run.verdict !== 'ready') return null\n\n const working = run.busy || run.running\n const help = pluginDocsHelp('aiCrm', {\n anchor: '#draft-an-email',\n excerpt: 'Describe the email and a draft goes into the message. Nothing is sent until you press Send.',\n })\n\n const draft = async () => {\n const answer = await run.ask(request.trim(), { task: 'email', record: kind, recordId })\n if (answer?.proposal.kind !== 'email') return\n proposeDraft({ subject: answer.proposal.subject, body: answer.proposal.body }, answer.jobId)\n }\n\n return (\n <Box sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }} aria-label=\"Draft with AI\">\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline' }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Draft with AI'}\n </Typography>\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" variant=\"caption\" title={help.excerptText}>\n {'How it works'}\n </Link>\n </Stack>\n <TextField\n size=\"small\"\n multiline\n minRows={2}\n label=\"What should the email say?\"\n placeholder=\"Follow up on the quote and offer a call next week\"\n helperText=\"Written from what you ask and this record's timeline. Nothing is sent until you press Send.\"\n value={request}\n onChange={(event) => setRequest(event.target.value.slice(0, AI_CRM_EMAIL_REQUEST_MAX_CHARS))}\n disabled={working}\n />\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={working || !orgId || !request.trim()}\n onClick={() => void draft()}\n >\n {'Draft the message'}\n </Button>\n {working ? <CircularProgress size={16} aria-label=\"Drafting the message\" /> : null}\n </Stack>\n {run.notice ? <Alert severity=\"warning\">{run.notice}</Alert> : null}\n {run.job?.error && !run.answer ? (\n <Alert severity={run.job.status === 'failed' ? 'error' : 'warning'}>{run.job.error}</Alert>\n ) : null}\n </Stack>\n </Box>\n )\n}\nAiCrmEmailDraft.displayName = 'AiCrmEmailDraft'\n\nexport default AiCrmEmailDraft\n"],"names":["pluginDocsHelp","useHostOrgId","Alert","Box","Button","CircularProgress","Link","Stack","TextField","Typography","useMemo","useState","AI_CRM_EMAIL_RECORD_KINDS","AI_CRM_EMAIL_REQUEST_MAX_CHARS","useAiCrmAnswer","emailKindOf","kind","includes","AiCrmEmailDraft","props","run","hostId","record","proposeDraft","hostOrgId","orgId","undefined","recordId","id","wanted","recall","request","setRequest","verdict","working","busy","running","help","anchor","excerpt","draft","answer","ask","trim","task","proposal","subject","body","jobId","sx","border","borderColor","borderRadius","p","aria-label","spacing","direction","alignItems","variant","flexGrow","href","target","rel","title","excerptText","size","multiline","minRows","label","placeholder","helperText","value","onChange","event","slice","disabled","onClick","notice","severity","job","error","status","displayName"],"mappings":"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"}
@@ -110,7 +110,7 @@ const collectionOf = (collection)=>AI_CRM_IMPORT_COLLECTIONS.includes(collection
110
110
  target: "_blank",
111
111
  rel: "noopener",
112
112
  variant: "caption",
113
- title: help.excerpt,
113
+ title: help.excerptText,
114
114
  children: 'How it works'
115
115
  })
116
116
  ]
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-crm-import-mapping.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { ConsoleImportMappingZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { useHostOrgId } from '@aglyn/tenant-feature-instance'\nimport { Alert, Box, Button, CircularProgress, Link, Stack, Typography } from '@mui/material'\nimport { useMemo, useState } from 'react'\nimport {\n AI_CRM_IMPORT_COLLECTIONS,\n AI_CRM_MAX_COLUMNS,\n aiCrmColumnsInput,\n type AiCrmAnswerWanted,\n type AiCrmColumnMatch,\n type AiCrmImportCollection,\n} from '../model/ai-crm'\nimport { useAiCrmAnswer } from './use-ai-crm-answer'\n\nconst collectionOf = (collection: string): AiCrmImportCollection | null =>\n (AI_CRM_IMPORT_COLLECTIONS as readonly string[]).includes(collection)\n ? (collection as AiCrmImportCollection)\n : null\n\n/** A job's matches as the import drawer's matching: column index to field key. */\nexport function aiCrmMappingOf(matches: readonly AiCrmColumnMatch[]): Record<number, string> {\n return Object.fromEntries(matches.map((match) => [match.column, match.field]))\n}\n\n/**\n * \"Match columns\" (AGL-2917), in a CRM spreadsheet import through the\n * `importMapping` zone: a `crm` job matches the file's columns to the fields\n * the import fills, and the matching goes into the drawer's own table and\n * preview through `proposeMapping`. The drawer's Import is the write.\n *\n * What is sent is what the zone hands over: each column's header and the\n * shape of its values, read in the browser, never a cell. A file is new each\n * time, so the card recalls no earlier matching, and the answer door serves a\n * matching only to the member who asked.\n */\nexport function AiCrmImportMapping(props: ConsoleImportMappingZoneProps) {\n const { hostId, columns, proposeMapping } = props\n const hostOrgId = useHostOrgId(props.orgId || !hostId ? undefined : hostId)\n const orgId = props.orgId ?? hostOrgId ?? undefined\n const collection = collectionOf(props.collection)\n const wanted = useMemo<AiCrmAnswerWanted | null>(\n () => (collection ? { kind: 'mapping', collection } : null),\n [collection],\n )\n const run = useAiCrmAnswer({ orgId, hostId, wanted, recall: 'none' })\n const [matched, setMatched] = useState<{ jobId: string; count: number; of: number } | null>(null)\n\n if (!collection || run.verdict !== 'ready') return null\n\n const working = run.busy || run.running\n const tooMany = columns.length > AI_CRM_MAX_COLUMNS\n const help = pluginDocsHelp('aiCrm', {\n anchor: '#match-columns',\n excerpt: \"Suggests which column fills which field. Only each column's header and the kind of values in it are sent.\",\n })\n\n const match = async () => {\n setMatched(null)\n const answer = await run.ask(`Match the columns of a ${collection} import`, {\n task: 'mapping',\n collection,\n columns: aiCrmColumnsInput(columns),\n })\n if (answer?.proposal.kind !== 'mapping') return\n proposeMapping(aiCrmMappingOf(answer.proposal.matches), answer.jobId)\n setMatched({ jobId: answer.jobId, count: answer.proposal.matches.length, of: answer.proposal.columns })\n }\n\n return (\n <Box sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }} aria-label=\"Match columns with AI\">\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline' }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Match columns with AI'}\n </Typography>\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" variant=\"caption\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Only each column’s header and the kind of values under it are sent, never a row of your file.'}\n </Typography>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={working || !orgId || !columns.length || tooMany}\n onClick={() => void match()}\n >\n {'Match columns'}\n </Button>\n {working ? <CircularProgress size={16} aria-label=\"Matching columns\" /> : null}\n </Stack>\n {tooMany ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`AI matches files of up to ${AI_CRM_MAX_COLUMNS} columns.`}\n </Typography>\n ) : null}\n {matched ? (\n <Alert severity={matched.count ? 'success' : 'info'}>\n {matched.count\n ? `Matched ${matched.count} of ${matched.of} columns. Check the matches below before you import.`\n : 'No column matched a field. Choose the matches below.'}\n </Alert>\n ) : null}\n {run.notice ? <Alert severity=\"warning\">{run.notice}</Alert> : null}\n {run.job?.error && !run.answer ? (\n <Alert severity={run.job.status === 'failed' ? 'error' : 'warning'}>{run.job.error}</Alert>\n ) : null}\n </Stack>\n </Box>\n )\n}\nAiCrmImportMapping.displayName = 'AiCrmImportMapping'\n\nexport default AiCrmImportMapping\n"],"names":["pluginDocsHelp","useHostOrgId","Alert","Box","Button","CircularProgress","Link","Stack","Typography","useMemo","useState","AI_CRM_IMPORT_COLLECTIONS","AI_CRM_MAX_COLUMNS","aiCrmColumnsInput","useAiCrmAnswer","collectionOf","collection","includes","aiCrmMappingOf","matches","Object","fromEntries","map","match","column","field","AiCrmImportMapping","props","run","hostId","columns","proposeMapping","hostOrgId","orgId","undefined","wanted","kind","recall","matched","setMatched","verdict","working","busy","running","tooMany","length","help","anchor","excerpt","answer","ask","task","proposal","jobId","count","of","sx","border","borderColor","borderRadius","p","aria-label","spacing","direction","alignItems","variant","flexGrow","href","target","rel","title","color","size","disabled","onClick","severity","notice","job","error","status","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-crm-import-mapping.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { ConsoleImportMappingZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { useHostOrgId } from '@aglyn/tenant-feature-instance'\nimport { Alert, Box, Button, CircularProgress, Link, Stack, Typography } from '@mui/material'\nimport { useMemo, useState } from 'react'\nimport {\n AI_CRM_IMPORT_COLLECTIONS,\n AI_CRM_MAX_COLUMNS,\n aiCrmColumnsInput,\n type AiCrmAnswerWanted,\n type AiCrmColumnMatch,\n type AiCrmImportCollection,\n} from '../model/ai-crm'\nimport { useAiCrmAnswer } from './use-ai-crm-answer'\n\nconst collectionOf = (collection: string): AiCrmImportCollection | null =>\n (AI_CRM_IMPORT_COLLECTIONS as readonly string[]).includes(collection)\n ? (collection as AiCrmImportCollection)\n : null\n\n/** A job's matches as the import drawer's matching: column index to field key. */\nexport function aiCrmMappingOf(matches: readonly AiCrmColumnMatch[]): Record<number, string> {\n return Object.fromEntries(matches.map((match) => [match.column, match.field]))\n}\n\n/**\n * \"Match columns\" (AGL-2917), in a CRM spreadsheet import through the\n * `importMapping` zone: a `crm` job matches the file's columns to the fields\n * the import fills, and the matching goes into the drawer's own table and\n * preview through `proposeMapping`. The drawer's Import is the write.\n *\n * What is sent is what the zone hands over: each column's header and the\n * shape of its values, read in the browser, never a cell. A file is new each\n * time, so the card recalls no earlier matching, and the answer door serves a\n * matching only to the member who asked.\n */\nexport function AiCrmImportMapping(props: ConsoleImportMappingZoneProps) {\n const { hostId, columns, proposeMapping } = props\n const hostOrgId = useHostOrgId(props.orgId || !hostId ? undefined : hostId)\n const orgId = props.orgId ?? hostOrgId ?? undefined\n const collection = collectionOf(props.collection)\n const wanted = useMemo<AiCrmAnswerWanted | null>(\n () => (collection ? { kind: 'mapping', collection } : null),\n [collection],\n )\n const run = useAiCrmAnswer({ orgId, hostId, wanted, recall: 'none' })\n const [matched, setMatched] = useState<{ jobId: string; count: number; of: number } | null>(null)\n\n if (!collection || run.verdict !== 'ready') return null\n\n const working = run.busy || run.running\n const tooMany = columns.length > AI_CRM_MAX_COLUMNS\n const help = pluginDocsHelp('aiCrm', {\n anchor: '#match-columns',\n excerpt: \"Suggests which column fills which field. Only each column's header and the kind of values in it are sent.\",\n })\n\n const match = async () => {\n setMatched(null)\n const answer = await run.ask(`Match the columns of a ${collection} import`, {\n task: 'mapping',\n collection,\n columns: aiCrmColumnsInput(columns),\n })\n if (answer?.proposal.kind !== 'mapping') return\n proposeMapping(aiCrmMappingOf(answer.proposal.matches), answer.jobId)\n setMatched({ jobId: answer.jobId, count: answer.proposal.matches.length, of: answer.proposal.columns })\n }\n\n return (\n <Box sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }} aria-label=\"Match columns with AI\">\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline' }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'Match columns with AI'}\n </Typography>\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" variant=\"caption\" title={help.excerptText}>\n {'How it works'}\n </Link>\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Only each column’s header and the kind of values under it are sent, never a row of your file.'}\n </Typography>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={working || !orgId || !columns.length || tooMany}\n onClick={() => void match()}\n >\n {'Match columns'}\n </Button>\n {working ? <CircularProgress size={16} aria-label=\"Matching columns\" /> : null}\n </Stack>\n {tooMany ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`AI matches files of up to ${AI_CRM_MAX_COLUMNS} columns.`}\n </Typography>\n ) : null}\n {matched ? (\n <Alert severity={matched.count ? 'success' : 'info'}>\n {matched.count\n ? `Matched ${matched.count} of ${matched.of} columns. Check the matches below before you import.`\n : 'No column matched a field. Choose the matches below.'}\n </Alert>\n ) : null}\n {run.notice ? <Alert severity=\"warning\">{run.notice}</Alert> : null}\n {run.job?.error && !run.answer ? (\n <Alert severity={run.job.status === 'failed' ? 'error' : 'warning'}>{run.job.error}</Alert>\n ) : null}\n </Stack>\n </Box>\n )\n}\nAiCrmImportMapping.displayName = 'AiCrmImportMapping'\n\nexport default AiCrmImportMapping\n"],"names":["pluginDocsHelp","useHostOrgId","Alert","Box","Button","CircularProgress","Link","Stack","Typography","useMemo","useState","AI_CRM_IMPORT_COLLECTIONS","AI_CRM_MAX_COLUMNS","aiCrmColumnsInput","useAiCrmAnswer","collectionOf","collection","includes","aiCrmMappingOf","matches","Object","fromEntries","map","match","column","field","AiCrmImportMapping","props","run","hostId","columns","proposeMapping","hostOrgId","orgId","undefined","wanted","kind","recall","matched","setMatched","verdict","working","busy","running","tooMany","length","help","anchor","excerpt","answer","ask","task","proposal","jobId","count","of","sx","border","borderColor","borderRadius","p","aria-label","spacing","direction","alignItems","variant","flexGrow","href","target","rel","title","excerptText","color","size","disabled","onClick","severity","notice","job","error","status","displayName"],"mappings":"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"}
@@ -124,7 +124,7 @@ const recordKindOf = (kind)=>AI_CRM_RECORD_KINDS.includes(kind) ? kind : null;
124
124
  target: "_blank",
125
125
  rel: "noopener",
126
126
  variant: "caption",
127
- title: help.excerpt,
127
+ title: help.excerptText,
128
128
  children: 'How it works'
129
129
  })
130
130
  ]
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-crm-record-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { ConsoleRecordInsightsZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport type { ConsoleProposedTask } from '@aglyn/aglyn/plugin-manager/record-zone-props'\nimport { useHostOrgId } from '@aglyn/tenant-feature-instance'\nimport { Alert, Box, Button, CircularProgress, Link, Stack, Typography } from '@mui/material'\nimport { useMemo } from 'react'\nimport {\n AI_CRM_RECORD_KINDS,\n type AiCrmAnswerWanted,\n type AiCrmNextStep,\n type AiCrmRecordKind,\n} from '../model/ai-crm'\nimport { useAiCrmAnswer } from './use-ai-crm-answer'\n\nconst TASK_KIND_WORDS: Record<AiCrmNextStep['kind'], string> = {\n call: 'Call',\n email: 'Email',\n meeting: 'Meeting',\n todo: 'To-do',\n}\n\n/** When a suggested task is due, in words. */\nexport function aiCrmDueWords(dueInDays: number): string {\n if (dueInDays <= 0) return 'due today'\n if (dueInDays === 1) return 'due tomorrow'\n return `due in ${dueInDays} days`\n}\n\n/** A suggested next step as the task form takes it. */\nexport function aiCrmTaskOf(step: AiCrmNextStep): ConsoleProposedTask {\n return { title: step.title, notes: step.reason, kind: step.kind, priority: step.priority, dueInDays: step.dueInDays }\n}\n\nconst recordKindOf = (kind: string): AiCrmRecordKind | null =>\n (AI_CRM_RECORD_KINDS as readonly string[]).includes(kind) ? (kind as AiCrmRecordKind) : null\n\n/**\n * \"Summarize this contact\" (AGL-2917), on a CRM record's page through the\n * `recordInsights` zone: a contact's, company's, deal's or lead's summary,\n * with a suggested next step, a deal's suggested stage and a lead's standing.\n *\n * The card writes nothing. A next step goes to the page's own task form\n * through `proposeTask`, whose Save is the write, and a stage to the page's\n * stage move through `proposeStage`, which asks the member first. A summary\n * any member asked for is shown to every member who can open the record, and\n * asking again for an unchanged record reuses it at no cost.\n *\n * It renders nothing until the jobs route has answered for this workspace:\n * the shell decided the plan and the permission, but the release flag is the\n * route's, and a 404 there is this card staying absent.\n */\nexport function AiCrmRecordCard(props: ConsoleRecordInsightsZoneProps) {\n const { hostId, record, proposeTask, stages, stageId, proposeStage } = props\n // A record page may not know the org yet; its site does.\n const hostOrgId = useHostOrgId(props.orgId || !hostId ? undefined : hostId)\n const orgId = props.orgId ?? hostOrgId ?? undefined\n const kind = recordKindOf(record.kind)\n const recordId = record.id\n const wanted = useMemo<AiCrmAnswerWanted | null>(\n () => (kind ? { kind: 'record', record: { kind, id: recordId } } : null),\n [kind, recordId],\n )\n const run = useAiCrmAnswer({ orgId, hostId, wanted, recall: 'any' })\n\n if (!kind || run.verdict !== 'ready') return null\n\n const proposal = run.answer?.proposal.kind === 'record' ? run.answer.proposal : null\n const jobId = run.answer?.jobId ?? ''\n const working = run.busy || run.running\n const nextStep = proposal?.nextStep && proposeTask ? proposal.nextStep : null\n const offeredStage =\n proposal?.stage && proposeStage && proposal.stage.stageId !== stageId\n ? (stages ?? []).find((stage) => stage.id === proposal.stage?.stageId) ?? null\n : null\n const help = pluginDocsHelp('aiCrm', {\n anchor: '#summarize-a-record',\n excerpt: 'A short summary of this record and a suggested next step, from its timeline. Nothing is saved until you save it.',\n })\n\n return (\n <Box sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }} aria-label=\"AI summary\">\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline' }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'AI summary'}\n </Typography>\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" variant=\"caption\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Stack>\n\n {proposal ? (\n <Stack spacing={0.5}>\n <Typography variant=\"body2\" sx={{ wordBreak: 'break-word' }}>\n {proposal.summary}\n </Typography>\n {proposal.standing ? (\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ wordBreak: 'break-word' }}>\n {proposal.standing}\n </Typography>\n ) : null}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Written from the timeline on ${proposal.asOf}.`}\n </Typography>\n </Stack>\n ) : null}\n\n {nextStep && proposeTask ? (\n <Stack spacing={0.5} aria-label=\"Suggested next step\">\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Suggested next step'}\n </Typography>\n <Typography variant=\"body2\" sx={{ wordBreak: 'break-word' }}>\n {`${nextStep.title} (${TASK_KIND_WORDS[nextStep.kind]}, ${nextStep.priority} priority, ${aiCrmDueWords(nextStep.dueInDays)})`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ wordBreak: 'break-word' }}>\n {nextStep.reason}\n </Typography>\n <Button\n size=\"small\"\n variant=\"contained\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => proposeTask(aiCrmTaskOf(nextStep), jobId)}\n >\n {'Create task'}\n </Button>\n </Stack>\n ) : null}\n\n {offeredStage && proposal?.stage && proposeStage ? (\n <Stack spacing={0.5} aria-label=\"Suggested stage\">\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Suggested stage'}\n </Typography>\n <Typography variant=\"body2\">{offeredStage.name}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ wordBreak: 'break-word' }}>\n {proposal.stage.reason}\n </Typography>\n <Button\n size=\"small\"\n variant=\"outlined\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => proposeStage(offeredStage.id, jobId)}\n >\n {`Move to ${offeredStage.name}`}\n </Button>\n </Stack>\n ) : null}\n\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={working || !orgId}\n onClick={() => void run.ask(`Summarize this ${kind}`, { task: 'record', record: kind, recordId })}\n >\n {proposal ? 'Summarize again' : `Summarize this ${kind}`}\n </Button>\n {working ? <CircularProgress size={16} aria-label=\"Summarizing\" /> : null}\n </Stack>\n {run.notice ? <Alert severity=\"warning\">{run.notice}</Alert> : null}\n {run.job?.error && !proposal ? (\n <Alert severity={run.job.status === 'failed' ? 'error' : 'warning'}>{run.job.error}</Alert>\n ) : null}\n </Stack>\n </Box>\n )\n}\nAiCrmRecordCard.displayName = 'AiCrmRecordCard'\n\nexport default AiCrmRecordCard\n"],"names":["pluginDocsHelp","useHostOrgId","Alert","Box","Button","CircularProgress","Link","Stack","Typography","useMemo","AI_CRM_RECORD_KINDS","useAiCrmAnswer","TASK_KIND_WORDS","call","email","meeting","todo","aiCrmDueWords","dueInDays","aiCrmTaskOf","step","title","notes","reason","kind","priority","recordKindOf","includes","AiCrmRecordCard","props","run","hostId","record","proposeTask","stages","stageId","proposeStage","hostOrgId","orgId","undefined","recordId","id","wanted","recall","verdict","proposal","answer","jobId","working","busy","running","nextStep","offeredStage","stage","find","help","anchor","excerpt","sx","border","borderColor","borderRadius","p","aria-label","spacing","direction","alignItems","variant","flexGrow","href","target","rel","wordBreak","summary","standing","color","asOf","size","alignSelf","onClick","name","disabled","ask","task","notice","severity","job","error","status","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-crm-record-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { ConsoleRecordInsightsZoneProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport type { ConsoleProposedTask } from '@aglyn/aglyn/plugin-manager/record-zone-props'\nimport { useHostOrgId } from '@aglyn/tenant-feature-instance'\nimport { Alert, Box, Button, CircularProgress, Link, Stack, Typography } from '@mui/material'\nimport { useMemo } from 'react'\nimport {\n AI_CRM_RECORD_KINDS,\n type AiCrmAnswerWanted,\n type AiCrmNextStep,\n type AiCrmRecordKind,\n} from '../model/ai-crm'\nimport { useAiCrmAnswer } from './use-ai-crm-answer'\n\nconst TASK_KIND_WORDS: Record<AiCrmNextStep['kind'], string> = {\n call: 'Call',\n email: 'Email',\n meeting: 'Meeting',\n todo: 'To-do',\n}\n\n/** When a suggested task is due, in words. */\nexport function aiCrmDueWords(dueInDays: number): string {\n if (dueInDays <= 0) return 'due today'\n if (dueInDays === 1) return 'due tomorrow'\n return `due in ${dueInDays} days`\n}\n\n/** A suggested next step as the task form takes it. */\nexport function aiCrmTaskOf(step: AiCrmNextStep): ConsoleProposedTask {\n return { title: step.title, notes: step.reason, kind: step.kind, priority: step.priority, dueInDays: step.dueInDays }\n}\n\nconst recordKindOf = (kind: string): AiCrmRecordKind | null =>\n (AI_CRM_RECORD_KINDS as readonly string[]).includes(kind) ? (kind as AiCrmRecordKind) : null\n\n/**\n * \"Summarize this contact\" (AGL-2917), on a CRM record's page through the\n * `recordInsights` zone: a contact's, company's, deal's or lead's summary,\n * with a suggested next step, a deal's suggested stage and a lead's standing.\n *\n * The card writes nothing. A next step goes to the page's own task form\n * through `proposeTask`, whose Save is the write, and a stage to the page's\n * stage move through `proposeStage`, which asks the member first. A summary\n * any member asked for is shown to every member who can open the record, and\n * asking again for an unchanged record reuses it at no cost.\n *\n * It renders nothing until the jobs route has answered for this workspace:\n * the shell decided the plan and the permission, but the release flag is the\n * route's, and a 404 there is this card staying absent.\n */\nexport function AiCrmRecordCard(props: ConsoleRecordInsightsZoneProps) {\n const { hostId, record, proposeTask, stages, stageId, proposeStage } = props\n // A record page may not know the org yet; its site does.\n const hostOrgId = useHostOrgId(props.orgId || !hostId ? undefined : hostId)\n const orgId = props.orgId ?? hostOrgId ?? undefined\n const kind = recordKindOf(record.kind)\n const recordId = record.id\n const wanted = useMemo<AiCrmAnswerWanted | null>(\n () => (kind ? { kind: 'record', record: { kind, id: recordId } } : null),\n [kind, recordId],\n )\n const run = useAiCrmAnswer({ orgId, hostId, wanted, recall: 'any' })\n\n if (!kind || run.verdict !== 'ready') return null\n\n const proposal = run.answer?.proposal.kind === 'record' ? run.answer.proposal : null\n const jobId = run.answer?.jobId ?? ''\n const working = run.busy || run.running\n const nextStep = proposal?.nextStep && proposeTask ? proposal.nextStep : null\n const offeredStage =\n proposal?.stage && proposeStage && proposal.stage.stageId !== stageId\n ? (stages ?? []).find((stage) => stage.id === proposal.stage?.stageId) ?? null\n : null\n const help = pluginDocsHelp('aiCrm', {\n anchor: '#summarize-a-record',\n excerpt: 'A short summary of this record and a suggested next step, from its timeline. Nothing is saved until you save it.',\n })\n\n return (\n <Box sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }} aria-label=\"AI summary\">\n <Stack spacing={1.5}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline' }}>\n <Typography variant=\"subtitle2\" sx={{ flexGrow: 1 }}>\n {'AI summary'}\n </Typography>\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" variant=\"caption\" title={help.excerptText}>\n {'How it works'}\n </Link>\n </Stack>\n\n {proposal ? (\n <Stack spacing={0.5}>\n <Typography variant=\"body2\" sx={{ wordBreak: 'break-word' }}>\n {proposal.summary}\n </Typography>\n {proposal.standing ? (\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ wordBreak: 'break-word' }}>\n {proposal.standing}\n </Typography>\n ) : null}\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Written from the timeline on ${proposal.asOf}.`}\n </Typography>\n </Stack>\n ) : null}\n\n {nextStep && proposeTask ? (\n <Stack spacing={0.5} aria-label=\"Suggested next step\">\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Suggested next step'}\n </Typography>\n <Typography variant=\"body2\" sx={{ wordBreak: 'break-word' }}>\n {`${nextStep.title} (${TASK_KIND_WORDS[nextStep.kind]}, ${nextStep.priority} priority, ${aiCrmDueWords(nextStep.dueInDays)})`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ wordBreak: 'break-word' }}>\n {nextStep.reason}\n </Typography>\n <Button\n size=\"small\"\n variant=\"contained\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => proposeTask(aiCrmTaskOf(nextStep), jobId)}\n >\n {'Create task'}\n </Button>\n </Stack>\n ) : null}\n\n {offeredStage && proposal?.stage && proposeStage ? (\n <Stack spacing={0.5} aria-label=\"Suggested stage\">\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Suggested stage'}\n </Typography>\n <Typography variant=\"body2\">{offeredStage.name}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ wordBreak: 'break-word' }}>\n {proposal.stage.reason}\n </Typography>\n <Button\n size=\"small\"\n variant=\"outlined\"\n sx={{ alignSelf: 'flex-start' }}\n onClick={() => proposeStage(offeredStage.id, jobId)}\n >\n {`Move to ${offeredStage.name}`}\n </Button>\n </Stack>\n ) : null}\n\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n disabled={working || !orgId}\n onClick={() => void run.ask(`Summarize this ${kind}`, { task: 'record', record: kind, recordId })}\n >\n {proposal ? 'Summarize again' : `Summarize this ${kind}`}\n </Button>\n {working ? <CircularProgress size={16} aria-label=\"Summarizing\" /> : null}\n </Stack>\n {run.notice ? <Alert severity=\"warning\">{run.notice}</Alert> : null}\n {run.job?.error && !proposal ? (\n <Alert severity={run.job.status === 'failed' ? 'error' : 'warning'}>{run.job.error}</Alert>\n ) : null}\n </Stack>\n </Box>\n )\n}\nAiCrmRecordCard.displayName = 'AiCrmRecordCard'\n\nexport default AiCrmRecordCard\n"],"names":["pluginDocsHelp","useHostOrgId","Alert","Box","Button","CircularProgress","Link","Stack","Typography","useMemo","AI_CRM_RECORD_KINDS","useAiCrmAnswer","TASK_KIND_WORDS","call","email","meeting","todo","aiCrmDueWords","dueInDays","aiCrmTaskOf","step","title","notes","reason","kind","priority","recordKindOf","includes","AiCrmRecordCard","props","run","hostId","record","proposeTask","stages","stageId","proposeStage","hostOrgId","orgId","undefined","recordId","id","wanted","recall","verdict","proposal","answer","jobId","working","busy","running","nextStep","offeredStage","stage","find","help","anchor","excerpt","sx","border","borderColor","borderRadius","p","aria-label","spacing","direction","alignItems","variant","flexGrow","href","target","rel","excerptText","wordBreak","summary","standing","color","asOf","size","alignSelf","onClick","name","disabled","ask","task","notice","severity","job","error","status","displayName"],"mappings":"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"}
@@ -50,7 +50,9 @@ export function AiAutomationBriefDialog(props) {
50
50
  var _aiJobProblem;
51
51
  const { open, onClose, orgId, hostId, user, openAction } = props;
52
52
  const copy = AI_AUTOMATION_BRIEF_COPY;
53
- const help = pluginDocsHelp('aiAutomations');
53
+ const help = pluginDocsHelp('aiAutomations', {
54
+ anchor: '#draft'
55
+ });
54
56
  const [brief, setBrief] = useState('');
55
57
  const [notListed, setNotListed] = useState(false);
56
58
  const run = useAiJobRun(user, copy.failed);
@@ -125,7 +127,7 @@ export function AiAutomationBriefDialog(props) {
125
127
  href: help.href,
126
128
  target: "_blank",
127
129
  rel: "noopener",
128
- title: help.excerpt,
130
+ title: help.excerptText,
129
131
  children: 'How it works'
130
132
  })
131
133
  ]
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-describe-automation.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 { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { ConsoleWidgetEntitlementProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport type { ConsoleHostAutomationsZoneProps } from './ai-automation-zones'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Link,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport { openAiJobs } from './ai-jobs-store'\nimport { AiUpsellButton } from './ai-upsell-dialog.component'\nimport { aiJobProblem, useAiJobRun } from './use-ai-job-run'\n\n/**\n * \"Create with AI\" on the Automation page's Actions (AGL-2919), beside Add action\n * and Recipes through the `hostAutomations` zone: a description becomes a\n * `workflow` job that drafts one automation, switched off.\n *\n * The dialog follows the shared brief dialog's shape — one text box, what\n * happens next, and the action that starts the job — and then follows the\n * job, because an automation's draft needs no plan to confirm: when it is\n * written, the dialog says what to fill in and opens it in the Actions editor.\n */\n\n/** The longest description a job admits. */\nconst BRIEF_MAX_CHARS = 4_000\n\nexport const AI_AUTOMATION_BRIEF_COPY = {\n title: 'Describe an automation',\n briefLabel: 'What should happen, and when?',\n placeholder:\n 'When someone submits the newsletter form, add them to the newsletter list, make them ' +\n 'a lead and send them a welcome email',\n next:\n 'The automation is drafted switched off, using only the triggers and steps your plan ' +\n 'includes. Anything it needs that your site does not have yet is left for you to fill in.',\n submit: 'Draft the automation',\n started:\n 'The automation is being drafted. This can take a few minutes. You can close this ' +\n 'window: the draft appears in the list, switched off, when it is ready. Open AI ' +\n 'jobs to follow it meanwhile.',\n drafted: 'Drafted',\n review: 'Review it',\n notListed: 'The draft is saved. It appears in the list in a moment.',\n failed: 'The automation could not be drafted. Try again.',\n} as const\n\nexport interface AiAutomationBriefDialogProps {\n open: boolean\n onClose: () => void\n orgId: string | undefined\n hostId: string\n /** Who is signed in, whose token the requests carry. */\n user: Parameters<typeof useAiJobRun>[0]\n /** Opens a listed action in the Actions editor; `false` when the list has not read it yet. */\n openAction: (actionId: string) => boolean\n}\n\nexport function AiAutomationBriefDialog(props: AiAutomationBriefDialogProps) {\n const { open, onClose, orgId, hostId, user, openAction } = props\n const copy = AI_AUTOMATION_BRIEF_COPY\n const help = pluginDocsHelp('aiAutomations')\n const [brief, setBrief] = useState('')\n const [notListed, setNotListed] = useState(false)\n const run = useAiJobRun(user, copy.failed)\n const { job, starting, running, notice, reset } = run\n\n // Opening or closing starts over and stops following: a job still drafting\n // carries on in AI jobs, and its draft appears in the list when it is written.\n useEffect(() => {\n reset()\n setNotListed(false)\n }, [open, reset])\n\n const draft = job?.status === 'done' ? job.outputs.find((output) => output.resource === 'workflow') : undefined\n const problem = aiJobProblem(job, copy.failed) ?? (job?.status === 'done' && !draft ? copy.failed : null)\n const asking = !job || Boolean(notice)\n\n const start = async () => {\n const trimmed = brief.trim()\n if (!orgId || !trimmed) return\n setNotListed(false)\n if (await run.start({ orgId, hostId, kind: 'workflow', brief: trimmed, inputs: { mode: 'draft' } })) {\n setBrief('')\n }\n }\n\n const review = () => {\n if (!draft) return\n if (openAction(draft.id)) onClose()\n else setNotListed(true)\n }\n\n return (\n <Dialog open={open} onClose={starting ? undefined : onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle>{copy.title}</DialogTitle>\n <DialogContent>\n {asking ? (\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n label={copy.briefLabel}\n placeholder={copy.placeholder}\n multiline\n minRows={4}\n value={brief}\n onChange={(event) => setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS))}\n helperText={`${brief.length.toLocaleString('en-US')} / ${BRIEF_MAX_CHARS.toLocaleString('en-US')}`}\n disabled={starting}\n autoFocus\n />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {copy.next}{' '}\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Typography>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n </Stack>\n ) : (\n <Stack spacing={2} sx={{ pt: 1 }}>\n {running && job ? (\n <Alert\n severity=\"info\"\n icon={<CircularProgress size={18} aria-label=\"Drafting\" />}\n action={\n // Follows the job in the Assist panel (AGL-3593); the dialog\n // covers it, so it closes.\n <Button\n color=\"inherit\"\n size=\"small\"\n variant=\"outlined\"\n onClick={() => {\n openAiJobs({ jobId: job.id })\n onClose()\n }}\n >\n {'Open AI jobs'}\n </Button>\n }\n >\n {copy.started}\n </Alert>\n ) : null}\n {draft ? (\n <Alert severity=\"success\">\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {`${copy.drafted}: ${draft.label}`}\n </Typography>\n {draft.note ? <Typography variant=\"body2\">{draft.note}</Typography> : null}\n </Alert>\n ) : null}\n {problem ? <Alert severity={job?.status === 'failed' ? 'error' : 'warning'}>{problem}</Alert> : null}\n {notListed ? <Alert severity=\"info\">{copy.notListed}</Alert> : null}\n </Stack>\n )}\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose} disabled={starting}>\n {asking ? 'Cancel' : 'Close'}\n </Button>\n {asking ? (\n <Button\n variant=\"contained\"\n onClick={() => void start()}\n disabled={starting || !orgId || !brief.trim()}\n >\n {copy.submit}\n </Button>\n ) : draft ? (\n <Button variant=\"contained\" onClick={review}>\n {copy.review}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\n/**\n * Drawn at once, from the gates the shell resolved before mounting it (the\n * plan, `ai.generate`, the site's AI switch and the release flag); nothing is\n * asked of a server to draw it (AGL-3601). The start door decides when the\n * description is sent, and says why in the dialog if it refuses. On a plan\n * that could buy the AI add-on, the shell mounts it with `entitled={false}`\n * and the same button opens the add-on's dialog.\n */\nexport function AiDescribeAutomationButton({\n hostId,\n orgId,\n openAction,\n entitled,\n upgrade,\n}: ConsoleHostAutomationsZoneProps & ConsoleWidgetEntitlementProps) {\n const { data: user } = useUser()\n const [open, setOpen] = useState(false)\n\n if (entitled === false) {\n return upgrade ? <AiUpsellButton kind=\"workflow\" upgrade={upgrade} /> : null\n }\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {'Create with AI'}\n </Button>\n <AiAutomationBriefDialog\n open={open}\n onClose={() => setOpen(false)}\n orgId={orgId}\n hostId={hostId}\n user={user}\n openAction={openAction}\n />\n </>\n )\n}\n\nexport default AiDescribeAutomationButton\n"],"names":["pluginDocsHelp","mdiCreation","MdiIcon","useUser","Alert","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","Link","Stack","TextField","Typography","useEffect","useState","openAiJobs","AiUpsellButton","aiJobProblem","useAiJobRun","BRIEF_MAX_CHARS","AI_AUTOMATION_BRIEF_COPY","title","briefLabel","placeholder","next","submit","started","drafted","review","notListed","failed","AiAutomationBriefDialog","props","open","onClose","orgId","hostId","user","openAction","copy","help","brief","setBrief","setNotListed","run","job","starting","running","notice","reset","draft","status","outputs","find","output","resource","undefined","problem","asking","Boolean","start","trimmed","trim","kind","inputs","mode","id","fullWidth","maxWidth","spacing","sx","pt","label","multiline","minRows","value","onChange","event","target","slice","helperText","length","toLocaleString","disabled","autoFocus","variant","color","href","rel","excerpt","severity","icon","size","aria-label","action","onClick","jobId","fontWeight","note","AiDescribeAutomationButton","entitled","upgrade","data","setOpen","startIcon","path"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-describe-automation.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 { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport type { ConsoleWidgetEntitlementProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport type { ConsoleHostAutomationsZoneProps } from './ai-automation-zones'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Link,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport { openAiJobs } from './ai-jobs-store'\nimport { AiUpsellButton } from './ai-upsell-dialog.component'\nimport { aiJobProblem, useAiJobRun } from './use-ai-job-run'\n\n/**\n * \"Create with AI\" on the Automation page's Actions (AGL-2919), beside Add action\n * and Recipes through the `hostAutomations` zone: a description becomes a\n * `workflow` job that drafts one automation, switched off.\n *\n * The dialog follows the shared brief dialog's shape — one text box, what\n * happens next, and the action that starts the job — and then follows the\n * job, because an automation's draft needs no plan to confirm: when it is\n * written, the dialog says what to fill in and opens it in the Actions editor.\n */\n\n/** The longest description a job admits. */\nconst BRIEF_MAX_CHARS = 4_000\n\nexport const AI_AUTOMATION_BRIEF_COPY = {\n title: 'Describe an automation',\n briefLabel: 'What should happen, and when?',\n placeholder:\n 'When someone submits the newsletter form, add them to the newsletter list, make them ' +\n 'a lead and send them a welcome email',\n next:\n 'The automation is drafted switched off, using only the triggers and steps your plan ' +\n 'includes. Anything it needs that your site does not have yet is left for you to fill in.',\n submit: 'Draft the automation',\n started:\n 'The automation is being drafted. This can take a few minutes. You can close this ' +\n 'window: the draft appears in the list, switched off, when it is ready. Open AI ' +\n 'jobs to follow it meanwhile.',\n drafted: 'Drafted',\n review: 'Review it',\n notListed: 'The draft is saved. It appears in the list in a moment.',\n failed: 'The automation could not be drafted. Try again.',\n} as const\n\nexport interface AiAutomationBriefDialogProps {\n open: boolean\n onClose: () => void\n orgId: string | undefined\n hostId: string\n /** Who is signed in, whose token the requests carry. */\n user: Parameters<typeof useAiJobRun>[0]\n /** Opens a listed action in the Actions editor; `false` when the list has not read it yet. */\n openAction: (actionId: string) => boolean\n}\n\nexport function AiAutomationBriefDialog(props: AiAutomationBriefDialogProps) {\n const { open, onClose, orgId, hostId, user, openAction } = props\n const copy = AI_AUTOMATION_BRIEF_COPY\n const help = pluginDocsHelp('aiAutomations', { anchor: '#draft' })\n const [brief, setBrief] = useState('')\n const [notListed, setNotListed] = useState(false)\n const run = useAiJobRun(user, copy.failed)\n const { job, starting, running, notice, reset } = run\n\n // Opening or closing starts over and stops following: a job still drafting\n // carries on in AI jobs, and its draft appears in the list when it is written.\n useEffect(() => {\n reset()\n setNotListed(false)\n }, [open, reset])\n\n const draft = job?.status === 'done' ? job.outputs.find((output) => output.resource === 'workflow') : undefined\n const problem = aiJobProblem(job, copy.failed) ?? (job?.status === 'done' && !draft ? copy.failed : null)\n const asking = !job || Boolean(notice)\n\n const start = async () => {\n const trimmed = brief.trim()\n if (!orgId || !trimmed) return\n setNotListed(false)\n if (await run.start({ orgId, hostId, kind: 'workflow', brief: trimmed, inputs: { mode: 'draft' } })) {\n setBrief('')\n }\n }\n\n const review = () => {\n if (!draft) return\n if (openAction(draft.id)) onClose()\n else setNotListed(true)\n }\n\n return (\n <Dialog open={open} onClose={starting ? undefined : onClose} fullWidth maxWidth=\"sm\">\n <DialogTitle>{copy.title}</DialogTitle>\n <DialogContent>\n {asking ? (\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n label={copy.briefLabel}\n placeholder={copy.placeholder}\n multiline\n minRows={4}\n value={brief}\n onChange={(event) => setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS))}\n helperText={`${brief.length.toLocaleString('en-US')} / ${BRIEF_MAX_CHARS.toLocaleString('en-US')}`}\n disabled={starting}\n autoFocus\n />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {copy.next}{' '}\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" title={help.excerptText}>\n {'How it works'}\n </Link>\n </Typography>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n </Stack>\n ) : (\n <Stack spacing={2} sx={{ pt: 1 }}>\n {running && job ? (\n <Alert\n severity=\"info\"\n icon={<CircularProgress size={18} aria-label=\"Drafting\" />}\n action={\n // Follows the job in the Assist panel (AGL-3593); the dialog\n // covers it, so it closes.\n <Button\n color=\"inherit\"\n size=\"small\"\n variant=\"outlined\"\n onClick={() => {\n openAiJobs({ jobId: job.id })\n onClose()\n }}\n >\n {'Open AI jobs'}\n </Button>\n }\n >\n {copy.started}\n </Alert>\n ) : null}\n {draft ? (\n <Alert severity=\"success\">\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {`${copy.drafted}: ${draft.label}`}\n </Typography>\n {draft.note ? <Typography variant=\"body2\">{draft.note}</Typography> : null}\n </Alert>\n ) : null}\n {problem ? <Alert severity={job?.status === 'failed' ? 'error' : 'warning'}>{problem}</Alert> : null}\n {notListed ? <Alert severity=\"info\">{copy.notListed}</Alert> : null}\n </Stack>\n )}\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose} disabled={starting}>\n {asking ? 'Cancel' : 'Close'}\n </Button>\n {asking ? (\n <Button\n variant=\"contained\"\n onClick={() => void start()}\n disabled={starting || !orgId || !brief.trim()}\n >\n {copy.submit}\n </Button>\n ) : draft ? (\n <Button variant=\"contained\" onClick={review}>\n {copy.review}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n )\n}\n\n/**\n * Drawn at once, from the gates the shell resolved before mounting it (the\n * plan, `ai.generate`, the site's AI switch and the release flag); nothing is\n * asked of a server to draw it (AGL-3601). The start door decides when the\n * description is sent, and says why in the dialog if it refuses. On a plan\n * that could buy the AI add-on, the shell mounts it with `entitled={false}`\n * and the same button opens the add-on's dialog.\n */\nexport function AiDescribeAutomationButton({\n hostId,\n orgId,\n openAction,\n entitled,\n upgrade,\n}: ConsoleHostAutomationsZoneProps & ConsoleWidgetEntitlementProps) {\n const { data: user } = useUser()\n const [open, setOpen] = useState(false)\n\n if (entitled === false) {\n return upgrade ? <AiUpsellButton kind=\"workflow\" upgrade={upgrade} /> : null\n }\n return (\n <>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCreation.path} />}\n onClick={() => setOpen(true)}\n >\n {'Create with AI'}\n </Button>\n <AiAutomationBriefDialog\n open={open}\n onClose={() => setOpen(false)}\n orgId={orgId}\n hostId={hostId}\n user={user}\n openAction={openAction}\n />\n </>\n )\n}\n\nexport default AiDescribeAutomationButton\n"],"names":["pluginDocsHelp","mdiCreation","MdiIcon","useUser","Alert","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","Link","Stack","TextField","Typography","useEffect","useState","openAiJobs","AiUpsellButton","aiJobProblem","useAiJobRun","BRIEF_MAX_CHARS","AI_AUTOMATION_BRIEF_COPY","title","briefLabel","placeholder","next","submit","started","drafted","review","notListed","failed","AiAutomationBriefDialog","props","open","onClose","orgId","hostId","user","openAction","copy","help","anchor","brief","setBrief","setNotListed","run","job","starting","running","notice","reset","draft","status","outputs","find","output","resource","undefined","problem","asking","Boolean","start","trimmed","trim","kind","inputs","mode","id","fullWidth","maxWidth","spacing","sx","pt","label","multiline","minRows","value","onChange","event","target","slice","helperText","length","toLocaleString","disabled","autoFocus","variant","color","href","rel","excerptText","severity","icon","size","aria-label","action","onClick","jobId","fontWeight","note","AiDescribeAutomationButton","entitled","upgrade","data","setOpen","startIcon","path"],"mappings":"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"}
@@ -145,7 +145,7 @@ export function AiDescribeOrgAutomationButton({ orgId, triggers, steps, propose,
145
145
  href: help.href,
146
146
  target: "_blank",
147
147
  rel: "noopener",
148
- title: help.excerpt,
148
+ title: help.excerptText,
149
149
  children: 'How it works'
150
150
  })
151
151
  ]
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-describe-org-automation.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 type { ConsoleWidgetEntitlementProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Link,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport {\n AI_ORG_AUTOMATION_RESOURCE,\n aiOrgAutomationInputs,\n readAiOrgAutomationProposal,\n type AiOrgAutomationProposal,\n} from '../model/ai-workflow-job'\nimport type { ConsoleOrgAutomationsZoneProps } from './ai-automation-zones'\nimport { openAiJobs } from './ai-jobs-store'\nimport { AiUpsellButton } from './ai-upsell-dialog.component'\nimport { aiJobProblem, useAiJobRun } from './use-ai-job-run'\n\n/**\n * \"Create with AI\" on the workspace's Org automations (AGL-3603), in that\n * section's card header through the `orgAutomations` zone: a description\n * becomes a `workflow` job drafting one of the WORKSPACE's automations, held\n * to the triggers and steps the zone says an org automation may use.\n *\n * Nothing is written. The job's proposal opens in the section's editor as a\n * new automation, unsaved and switched off; the person picks the sites it\n * runs on and saves it with the editor's own Save, through the section's\n * route — or closes the editor, and nothing changes.\n */\n\nconst BRIEF_MAX_CHARS = 4_000\n\nexport const AI_ORG_AUTOMATION_COPY = {\n create: 'Create with AI',\n title: 'Describe an org automation',\n label: 'What should happen, and when?',\n placeholder:\n 'When someone books on any of our sites, make them a contact, tag them booked and send them a welcome email',\n next:\n 'It is drafted from what an org automation can start on and do, and opens in the editor switched off, where you choose its sites and save it. Lists, campaigns or datasets your workspace does not have are left for you to pick.',\n submit: 'Draft the automation',\n running: 'Drafting it. This can take a few minutes.',\n ready: 'Drafted',\n open: 'Open in the editor',\n notOpened: 'It could not be opened in the editor. Check the page for why, then try again.',\n failed: 'The automation could not be drafted. Try again.',\n} as const\n\n/** The proposal a finished job wrote, with what to decide, or `null`. */\nfunction proposalOf(job: AiJobSummary | null): { automation: AiOrgAutomationProposal; label: string; note: string | null } | null {\n if (job?.status !== 'done') return null\n const output = job.outputs.find((one) => one.resource === AI_ORG_AUTOMATION_RESOURCE)\n const automation = readAiOrgAutomationProposal(output?.proposal)\n return automation && output ? { automation, label: output.label, note: output.note ?? null } : null\n}\n\nexport function AiDescribeOrgAutomationButton({\n orgId,\n triggers,\n steps,\n propose,\n entitled,\n upgrade,\n}: ConsoleOrgAutomationsZoneProps & ConsoleWidgetEntitlementProps) {\n const copy = AI_ORG_AUTOMATION_COPY\n const help = pluginDocsHelp('aiAutomations', { anchor: '#org-automations' })\n const { data: user } = useUser()\n const run = useAiJobRun(user, copy.failed)\n const { job, starting, running, notice, reset } = run\n const [open, setOpen] = useState(false)\n const [brief, setBrief] = useState('')\n const [notOpened, setNotOpened] = useState(false)\n\n // Opening or closing starts over and stops following: a job still drafting\n // carries on in AI jobs.\n useEffect(() => {\n reset()\n setNotOpened(false)\n }, [open, reset])\n\n // Drawn from the shell's gates alone (AGL-3601): nothing asks a server\n // until it is used, and a plan without the add-on gets the add-on's dialog.\n if (entitled === false) return upgrade ? <AiUpsellButton kind=\"workflow\" upgrade={upgrade} /> : null\n const ready = proposalOf(job)\n const problem = aiJobProblem(job, copy.failed) ?? (job?.status === 'done' && !ready ? copy.failed : null)\n const asking = !job || Boolean(notice)\n const close = () => setOpen(false)\n\n const start = async () => {\n const trimmed = brief.trim()\n if (!orgId || !trimmed) return\n setNotOpened(false)\n const inputs = aiOrgAutomationInputs({ triggers, steps })\n if (await run.start({ orgId, hostId: null, kind: 'workflow', brief: trimmed, inputs })) setBrief('')\n }\n const apply = () => {\n if (!ready) return\n if (propose(ready.automation)) close()\n else setNotOpened(true)\n }\n\n return (\n <>\n <Button size=\"small\" variant=\"outlined\" startIcon={<MdiIcon path={mdiCreation.path} />} onClick={() => setOpen(true)}>\n {copy.create}\n </Button>\n <Dialog open={open} onClose={starting ? undefined : close} fullWidth maxWidth=\"sm\">\n <DialogTitle>{copy.title}</DialogTitle>\n <DialogContent>\n {asking ? (\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n label={copy.label}\n placeholder={copy.placeholder}\n multiline\n minRows={4}\n value={brief}\n onChange={(event) => setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS))}\n helperText={`${brief.length.toLocaleString('en-US')} / ${BRIEF_MAX_CHARS.toLocaleString('en-US')}`}\n disabled={starting}\n autoFocus\n />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {copy.next}{' '}\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" title={help.excerpt}>\n {'How it works'}\n </Link>\n </Typography>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n </Stack>\n ) : (\n <Stack spacing={2} sx={{ pt: 1 }}>\n {running && job ? (\n <Alert\n severity=\"info\"\n icon={<CircularProgress size={18} aria-label=\"Drafting\" />}\n action={\n <Button\n color=\"inherit\"\n size=\"small\"\n variant=\"outlined\"\n onClick={() => {\n openAiJobs({ jobId: job.id })\n close()\n }}\n >\n {'Open AI jobs'}\n </Button>\n }\n >\n {copy.running}\n </Alert>\n ) : null}\n {ready ? (\n <Alert severity=\"success\">\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {`${copy.ready}: ${ready.label}`}\n </Typography>\n {ready.note ? <Typography variant=\"body2\">{ready.note}</Typography> : null}\n </Alert>\n ) : null}\n {problem ? <Alert severity={job?.status === 'failed' ? 'error' : 'warning'}>{problem}</Alert> : null}\n {notOpened ? <Alert severity=\"warning\">{copy.notOpened}</Alert> : null}\n </Stack>\n )}\n </DialogContent>\n <DialogActions>\n <Button onClick={close} disabled={starting}>\n {asking ? 'Cancel' : 'Close'}\n </Button>\n {asking ? (\n <Button variant=\"contained\" onClick={() => void start()} disabled={starting || !orgId || !brief.trim()}>\n {copy.submit}\n </Button>\n ) : ready ? (\n <Button variant=\"contained\" onClick={apply}>\n {copy.open}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n </>\n )\n}\nAiDescribeOrgAutomationButton.displayName = 'AiDescribeOrgAutomationButton'\n\nexport default AiDescribeOrgAutomationButton\n"],"names":["pluginDocsHelp","mdiCreation","MdiIcon","useUser","Alert","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","Link","Stack","TextField","Typography","useEffect","useState","AI_ORG_AUTOMATION_RESOURCE","aiOrgAutomationInputs","readAiOrgAutomationProposal","openAiJobs","AiUpsellButton","aiJobProblem","useAiJobRun","BRIEF_MAX_CHARS","AI_ORG_AUTOMATION_COPY","create","title","label","placeholder","next","submit","running","ready","open","notOpened","failed","proposalOf","job","output","status","outputs","find","one","resource","automation","proposal","note","AiDescribeOrgAutomationButton","orgId","triggers","steps","propose","entitled","upgrade","copy","help","anchor","data","user","run","starting","notice","reset","setOpen","brief","setBrief","setNotOpened","kind","problem","asking","Boolean","close","start","trimmed","trim","inputs","hostId","apply","size","variant","startIcon","path","onClick","onClose","undefined","fullWidth","maxWidth","spacing","sx","pt","multiline","minRows","value","onChange","event","target","slice","helperText","length","toLocaleString","disabled","autoFocus","color","href","rel","excerpt","severity","icon","aria-label","action","jobId","id","fontWeight","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-describe-org-automation.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 type { ConsoleWidgetEntitlementProps } from '@aglyn/aglyn/plugin-manager/feature-plugins'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { mdiCreation } from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Link,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useState } from 'react'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport {\n AI_ORG_AUTOMATION_RESOURCE,\n aiOrgAutomationInputs,\n readAiOrgAutomationProposal,\n type AiOrgAutomationProposal,\n} from '../model/ai-workflow-job'\nimport type { ConsoleOrgAutomationsZoneProps } from './ai-automation-zones'\nimport { openAiJobs } from './ai-jobs-store'\nimport { AiUpsellButton } from './ai-upsell-dialog.component'\nimport { aiJobProblem, useAiJobRun } from './use-ai-job-run'\n\n/**\n * \"Create with AI\" on the workspace's Org automations (AGL-3603), in that\n * section's card header through the `orgAutomations` zone: a description\n * becomes a `workflow` job drafting one of the WORKSPACE's automations, held\n * to the triggers and steps the zone says an org automation may use.\n *\n * Nothing is written. The job's proposal opens in the section's editor as a\n * new automation, unsaved and switched off; the person picks the sites it\n * runs on and saves it with the editor's own Save, through the section's\n * route — or closes the editor, and nothing changes.\n */\n\nconst BRIEF_MAX_CHARS = 4_000\n\nexport const AI_ORG_AUTOMATION_COPY = {\n create: 'Create with AI',\n title: 'Describe an org automation',\n label: 'What should happen, and when?',\n placeholder:\n 'When someone books on any of our sites, make them a contact, tag them booked and send them a welcome email',\n next:\n 'It is drafted from what an org automation can start on and do, and opens in the editor switched off, where you choose its sites and save it. Lists, campaigns or datasets your workspace does not have are left for you to pick.',\n submit: 'Draft the automation',\n running: 'Drafting it. This can take a few minutes.',\n ready: 'Drafted',\n open: 'Open in the editor',\n notOpened: 'It could not be opened in the editor. Check the page for why, then try again.',\n failed: 'The automation could not be drafted. Try again.',\n} as const\n\n/** The proposal a finished job wrote, with what to decide, or `null`. */\nfunction proposalOf(job: AiJobSummary | null): { automation: AiOrgAutomationProposal; label: string; note: string | null } | null {\n if (job?.status !== 'done') return null\n const output = job.outputs.find((one) => one.resource === AI_ORG_AUTOMATION_RESOURCE)\n const automation = readAiOrgAutomationProposal(output?.proposal)\n return automation && output ? { automation, label: output.label, note: output.note ?? null } : null\n}\n\nexport function AiDescribeOrgAutomationButton({\n orgId,\n triggers,\n steps,\n propose,\n entitled,\n upgrade,\n}: ConsoleOrgAutomationsZoneProps & ConsoleWidgetEntitlementProps) {\n const copy = AI_ORG_AUTOMATION_COPY\n const help = pluginDocsHelp('aiAutomations', { anchor: '#org-automations' })\n const { data: user } = useUser()\n const run = useAiJobRun(user, copy.failed)\n const { job, starting, running, notice, reset } = run\n const [open, setOpen] = useState(false)\n const [brief, setBrief] = useState('')\n const [notOpened, setNotOpened] = useState(false)\n\n // Opening or closing starts over and stops following: a job still drafting\n // carries on in AI jobs.\n useEffect(() => {\n reset()\n setNotOpened(false)\n }, [open, reset])\n\n // Drawn from the shell's gates alone (AGL-3601): nothing asks a server\n // until it is used, and a plan without the add-on gets the add-on's dialog.\n if (entitled === false) return upgrade ? <AiUpsellButton kind=\"workflow\" upgrade={upgrade} /> : null\n const ready = proposalOf(job)\n const problem = aiJobProblem(job, copy.failed) ?? (job?.status === 'done' && !ready ? copy.failed : null)\n const asking = !job || Boolean(notice)\n const close = () => setOpen(false)\n\n const start = async () => {\n const trimmed = brief.trim()\n if (!orgId || !trimmed) return\n setNotOpened(false)\n const inputs = aiOrgAutomationInputs({ triggers, steps })\n if (await run.start({ orgId, hostId: null, kind: 'workflow', brief: trimmed, inputs })) setBrief('')\n }\n const apply = () => {\n if (!ready) return\n if (propose(ready.automation)) close()\n else setNotOpened(true)\n }\n\n return (\n <>\n <Button size=\"small\" variant=\"outlined\" startIcon={<MdiIcon path={mdiCreation.path} />} onClick={() => setOpen(true)}>\n {copy.create}\n </Button>\n <Dialog open={open} onClose={starting ? undefined : close} fullWidth maxWidth=\"sm\">\n <DialogTitle>{copy.title}</DialogTitle>\n <DialogContent>\n {asking ? (\n <Stack spacing={2} sx={{ pt: 1 }}>\n <TextField\n label={copy.label}\n placeholder={copy.placeholder}\n multiline\n minRows={4}\n value={brief}\n onChange={(event) => setBrief(event.target.value.slice(0, BRIEF_MAX_CHARS))}\n helperText={`${brief.length.toLocaleString('en-US')} / ${BRIEF_MAX_CHARS.toLocaleString('en-US')}`}\n disabled={starting}\n autoFocus\n />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {copy.next}{' '}\n <Link href={help.href} target=\"_blank\" rel=\"noopener\" title={help.excerptText}>\n {'How it works'}\n </Link>\n </Typography>\n {notice ? <Alert severity=\"warning\">{notice}</Alert> : null}\n </Stack>\n ) : (\n <Stack spacing={2} sx={{ pt: 1 }}>\n {running && job ? (\n <Alert\n severity=\"info\"\n icon={<CircularProgress size={18} aria-label=\"Drafting\" />}\n action={\n <Button\n color=\"inherit\"\n size=\"small\"\n variant=\"outlined\"\n onClick={() => {\n openAiJobs({ jobId: job.id })\n close()\n }}\n >\n {'Open AI jobs'}\n </Button>\n }\n >\n {copy.running}\n </Alert>\n ) : null}\n {ready ? (\n <Alert severity=\"success\">\n <Typography variant=\"body2\" sx={{ fontWeight: 600 }}>\n {`${copy.ready}: ${ready.label}`}\n </Typography>\n {ready.note ? <Typography variant=\"body2\">{ready.note}</Typography> : null}\n </Alert>\n ) : null}\n {problem ? <Alert severity={job?.status === 'failed' ? 'error' : 'warning'}>{problem}</Alert> : null}\n {notOpened ? <Alert severity=\"warning\">{copy.notOpened}</Alert> : null}\n </Stack>\n )}\n </DialogContent>\n <DialogActions>\n <Button onClick={close} disabled={starting}>\n {asking ? 'Cancel' : 'Close'}\n </Button>\n {asking ? (\n <Button variant=\"contained\" onClick={() => void start()} disabled={starting || !orgId || !brief.trim()}>\n {copy.submit}\n </Button>\n ) : ready ? (\n <Button variant=\"contained\" onClick={apply}>\n {copy.open}\n </Button>\n ) : null}\n </DialogActions>\n </Dialog>\n </>\n )\n}\nAiDescribeOrgAutomationButton.displayName = 'AiDescribeOrgAutomationButton'\n\nexport default AiDescribeOrgAutomationButton\n"],"names":["pluginDocsHelp","mdiCreation","MdiIcon","useUser","Alert","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","Link","Stack","TextField","Typography","useEffect","useState","AI_ORG_AUTOMATION_RESOURCE","aiOrgAutomationInputs","readAiOrgAutomationProposal","openAiJobs","AiUpsellButton","aiJobProblem","useAiJobRun","BRIEF_MAX_CHARS","AI_ORG_AUTOMATION_COPY","create","title","label","placeholder","next","submit","running","ready","open","notOpened","failed","proposalOf","job","output","status","outputs","find","one","resource","automation","proposal","note","AiDescribeOrgAutomationButton","orgId","triggers","steps","propose","entitled","upgrade","copy","help","anchor","data","user","run","starting","notice","reset","setOpen","brief","setBrief","setNotOpened","kind","problem","asking","Boolean","close","start","trimmed","trim","inputs","hostId","apply","size","variant","startIcon","path","onClick","onClose","undefined","fullWidth","maxWidth","spacing","sx","pt","multiline","minRows","value","onChange","event","target","slice","helperText","length","toLocaleString","disabled","autoFocus","color","href","rel","excerptText","severity","icon","aria-label","action","jobId","id","fontWeight","displayName"],"mappings":"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"}