@aglyn/plugins-ai 1.0.0-beta.236 → 1.0.0-beta.238

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 (164) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-credits-prompt.component.d.ts +44 -0
  3. package/src/lib/components/ai-credits-prompt.component.js +117 -0
  4. package/src/lib/components/ai-credits-prompt.component.js.map +1 -0
  5. package/src/lib/components/ai-job-cancel.component.d.ts +38 -0
  6. package/src/lib/components/ai-job-cancel.component.js +156 -0
  7. package/src/lib/components/ai-job-cancel.component.js.map +1 -0
  8. package/src/lib/components/ai-job-plan.component.d.ts +5 -4
  9. package/src/lib/components/ai-job-plan.component.js +48 -12
  10. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  11. package/src/lib/components/ai-job-requests.d.ts +10 -0
  12. package/src/lib/components/ai-job-requests.js +12 -4
  13. package/src/lib/components/ai-job-requests.js.map +1 -1
  14. package/src/lib/components/ai-jobs-list-page.component.js +141 -72
  15. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  16. package/src/lib/components/ai-site-build-page.component.js +48 -9
  17. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  18. package/src/lib/components/ai-site-start-card.component.js +68 -22
  19. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  20. package/src/lib/components/ai-usage-strip.component.d.ts +19 -2
  21. package/src/lib/components/ai-usage-strip.component.js +40 -9
  22. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  23. package/src/lib/components/assist-build-card.component.d.ts +3 -1
  24. package/src/lib/components/assist-build-card.component.js +58 -7
  25. package/src/lib/components/assist-build-card.component.js.map +1 -1
  26. package/src/lib/components/assist-jobs-drawer.component.js +66 -22
  27. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  28. package/src/lib/components/assist-panel.component.js +89 -52
  29. package/src/lib/components/assist-panel.component.js.map +1 -1
  30. package/src/lib/jobs/ai-job-admission.d.ts +22 -2
  31. package/src/lib/jobs/ai-job-admission.js.map +1 -1
  32. package/src/lib/jobs/ai-job-build-step.d.ts +24 -11
  33. package/src/lib/jobs/ai-job-build-step.js +54 -35
  34. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  35. package/src/lib/jobs/ai-job-cancel.d.ts +71 -0
  36. package/src/lib/jobs/ai-job-cancel.js +102 -0
  37. package/src/lib/jobs/ai-job-cancel.js.map +1 -0
  38. package/src/lib/jobs/ai-job-component-step.js +4 -1
  39. package/src/lib/jobs/ai-job-component-step.js.map +1 -1
  40. package/src/lib/jobs/ai-job-drafts.js +2 -0
  41. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  42. package/src/lib/jobs/ai-job-form-step.js +4 -1
  43. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  44. package/src/lib/jobs/ai-job-layout-language.d.ts +2 -0
  45. package/src/lib/jobs/ai-job-layout-language.js +13 -1
  46. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  47. package/src/lib/jobs/ai-job-layout-step.js +6 -1
  48. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  49. package/src/lib/jobs/ai-job-page-language.js +12 -0
  50. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  51. package/src/lib/jobs/ai-job-page-step.d.ts +13 -0
  52. package/src/lib/jobs/ai-job-page-step.js +46 -4
  53. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  54. package/src/lib/jobs/ai-job-plan-step.d.ts +42 -0
  55. package/src/lib/jobs/ai-job-plan-step.js +118 -18
  56. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  57. package/src/lib/jobs/ai-job-site-content.d.ts +49 -3
  58. package/src/lib/jobs/ai-job-site-content.js +196 -37
  59. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  60. package/src/lib/jobs/ai-job-site-step.js +49 -16
  61. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  62. package/src/lib/jobs/ai-job-template-step.js +4 -1
  63. package/src/lib/jobs/ai-job-template-step.js.map +1 -1
  64. package/src/lib/jobs/ai-job-text-step.d.ts +8 -0
  65. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  66. package/src/lib/jobs/ai-jobs.d.ts +55 -13
  67. package/src/lib/jobs/ai-jobs.js +213 -41
  68. package/src/lib/jobs/ai-jobs.js.map +1 -1
  69. package/src/lib/jobs/ai-layout-stock-photos.d.ts +194 -34
  70. package/src/lib/jobs/ai-layout-stock-photos.js +861 -146
  71. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  72. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.d.ts +30 -0
  73. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js +220 -0
  74. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js.map +1 -0
  75. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.d.ts +27 -0
  76. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js +151 -0
  77. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js.map +1 -0
  78. package/src/lib/layout-language/ai-layout-compiler.d.ts +33 -0
  79. package/src/lib/layout-language/ai-layout-compiler.js +381 -18
  80. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  81. package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
  82. package/src/lib/layout-language/ai-layout-design.js +5 -3
  83. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  84. package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
  85. package/src/lib/layout-language/ai-layout-frame.js +58 -3
  86. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  87. package/src/lib/layout-language/ai-layout-gaps.js +3 -1
  88. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  89. package/src/lib/layout-language/ai-layout-language.js +1 -1
  90. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  91. package/src/lib/layout-language/ai-layout-listings.d.ts +49 -1
  92. package/src/lib/layout-language/ai-layout-listings.js +117 -47
  93. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  94. package/src/lib/layout-language/ai-layout-pictures.d.ts +10 -1
  95. package/src/lib/layout-language/ai-layout-pictures.js +5 -2
  96. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  97. package/src/lib/layout-language/ai-layout-storefront.d.ts +82 -0
  98. package/src/lib/layout-language/ai-layout-storefront.js +223 -0
  99. package/src/lib/layout-language/ai-layout-storefront.js.map +1 -0
  100. package/src/lib/model/ai-build-job.d.ts +39 -0
  101. package/src/lib/model/ai-build-job.js +100 -1
  102. package/src/lib/model/ai-build-job.js.map +1 -1
  103. package/src/lib/model/ai-build-progress.d.ts +8 -0
  104. package/src/lib/model/ai-build-progress.js +35 -0
  105. package/src/lib/model/ai-build-progress.js.map +1 -1
  106. package/src/lib/model/ai-credit-estimate.d.ts +249 -0
  107. package/src/lib/model/ai-credit-estimate.js +278 -0
  108. package/src/lib/model/ai-credit-estimate.js.map +1 -0
  109. package/src/lib/model/ai-job-cancel-copy.d.ts +44 -0
  110. package/src/lib/model/ai-job-cancel-copy.js +53 -0
  111. package/src/lib/model/ai-job-cancel-copy.js.map +1 -0
  112. package/src/lib/model/ai-job-failure-copy.d.ts +4 -1
  113. package/src/lib/model/ai-job-failure-copy.js +8 -3
  114. package/src/lib/model/ai-job-failure-copy.js.map +1 -1
  115. package/src/lib/model/ai-jobs.types.d.ts +35 -0
  116. package/src/lib/model/ai-jobs.types.js.map +1 -1
  117. package/src/lib/model/ai-site-build-progress.js +7 -4
  118. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  119. package/src/lib/model/ai-site-job.d.ts +76 -22
  120. package/src/lib/model/ai-site-job.js +125 -23
  121. package/src/lib/model/ai-site-job.js.map +1 -1
  122. package/src/lib/model/ai-site-kinds.d.ts +4 -1
  123. package/src/lib/model/ai-site-kinds.js +35 -7
  124. package/src/lib/model/ai-site-kinds.js.map +1 -1
  125. package/src/lib/runtime/ai-doctrine-validators.d.ts +37 -1
  126. package/src/lib/runtime/ai-doctrine-validators.js +161 -7
  127. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  128. package/src/lib/runtime/ai-doctrine.js +4 -3
  129. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  130. package/src/lib/runtime/ai-node-tree.d.ts +11 -0
  131. package/src/lib/runtime/ai-node-tree.js +26 -0
  132. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  133. package/src/lib/runtime/ai-palette.generated.js +592 -14
  134. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  135. package/src/lib/server/ai-jobs-cancel.d.ts +13 -5
  136. package/src/lib/server/ai-jobs-cancel.js +27 -9
  137. package/src/lib/server/ai-jobs-cancel.js.map +1 -1
  138. package/src/lib/server/ai-jobs-gate.d.ts +12 -2
  139. package/src/lib/server/ai-jobs-gate.js +20 -9
  140. package/src/lib/server/ai-jobs-gate.js.map +1 -1
  141. package/src/lib/server/ai-jobs-resume.js +93 -35
  142. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  143. package/src/lib/server/ai-jobs-route.js +45 -4
  144. package/src/lib/server/ai-jobs-route.js.map +1 -1
  145. package/src/lib/server/assist-chat.js +4 -1
  146. package/src/lib/server/assist-chat.js.map +1 -1
  147. package/src/lib/server/assist-edit.js +3 -1
  148. package/src/lib/server/assist-edit.js.map +1 -1
  149. package/src/lib/server/assist-music.d.ts +36 -0
  150. package/src/lib/server/assist-music.js +36 -0
  151. package/src/lib/server/assist-music.js.map +1 -0
  152. package/src/lib/server.js +6 -0
  153. package/src/lib/server.js.map +1 -1
  154. package/src/lib/usage/ai-usage-meter.d.ts +2 -1
  155. package/src/lib/usage/ai-usage-meter.js +17 -1
  156. package/src/lib/usage/ai-usage-meter.js.map +1 -1
  157. package/src/lib/usage/ai-usage-wire.d.ts +6 -0
  158. package/src/lib/usage/ai-usage-wire.js.map +1 -1
  159. package/src/lib/usage/assist-usage.d.ts +7 -0
  160. package/src/lib/usage/assist-usage.js +14 -4
  161. package/src/lib/usage/assist-usage.js.map +1 -1
  162. package/src/lib/usage/free-ai-credits-left.d.ts +7 -0
  163. package/src/lib/usage/free-ai-credits-left.js +4 -2
  164. package/src/lib/usage/free-ai-credits-left.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-usage-strip.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 { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { AppLink, HelpTip } from '@aglyn/shared-ui-jsx'\nimport { Box, LinearProgress, Stack, Typography } from '@mui/material'\nimport type { AiUsageMeterWire } from '../usage/ai-usage-wire'\nimport { useAiUsageMeter } from './use-ai-usage-meter'\n\nexport interface AiUsageStripProps {\n /** The workspace the strip speaks for; nothing renders without one. */\n orgId?: string | null\n /** For the link to who can raise an allotment; the link is omitted without it. */\n orgSlug?: string | null\n}\n\nconst credits = (value: number): string => value.toLocaleString()\n\n/** The usage line's own section of the allotments guide (AGL-3660). */\nconst AI_USAGE_STRIP_HELP = pluginDocsHelp('aiAllotments', {\n anchor: '#usage-strip',\n excerpt:\n 'Your credits this month against your allotment, the workspace pool, and what your last request cost. It warns from 80%.',\n})\n\n/** The line about the reader's own month, in the words the strip uses. */\nexport function aiUsageStripMineLabel(meter: AiUsageMeterWire): string {\n const { mine } = meter\n if (mine.limit === null) return `You: ${credits(mine.used)} credits this month`\n const who = mine.scope === 'host' ? 'This site' : mine.scope === 'collab' ? 'You, on this site' : 'You'\n return `${who}: ${credits(mine.used)} of ${credits(mine.limit)} credits this month`\n}\n\n/** The line about the workspace's pool. */\nexport function aiUsageStripPoolLabel(meter: AiUsageMeterWire): string {\n const { pool } = meter\n return pool.limit === null\n ? `Workspace: ${credits(pool.used)} credits`\n : `Workspace: ${credits(pool.used)} of ${credits(pool.limit)} credits`\n}\n\n/**\n * THE USAGE STRIP (AGL-2942): the reader's AI credits while they use AI —\n * in the assistant panel and every generation dialog, with no dashboard to\n * visit and no read of its own.\n *\n * It shows the reader's month against the allotment that binds them (or\n * their month alone and the workspace's pool when none does), the pool, what\n * the last request cost, and the model that answered. From 80% it warns; at\n * a hard allotment it says who can raise it. It renders nothing until a door\n * has answered this reader in this workspace — see `use-ai-usage-meter.ts`.\n */\nexport function AiUsageStrip({ orgId, orgSlug }: AiUsageStripProps) {\n const meter = useAiUsageMeter(orgId)\n if (!meter) return null\n const { mine, pool } = meter\n const bound = mine.limit !== null\n const used = bound ? mine.used : pool.used\n const limit = bound ? mine.limit : pool.limit\n const pct = limit ? Math.min(100, Math.round((used / limit) * 100)) : null\n const tone = meter.state === 'capped' ? 'error' : meter.state === 'warn' ? 'warning' : 'primary'\n const allotmentReached = bound && mine.mode === 'hard' && mine.limit !== null && mine.used >= mine.limit\n const raiseHref =\n orgSlug && mine.scope !== 'collab'\n ? `${buildRoute(Route.MANAGE_BILLING_USAGE, { orgSlug })}#ai-allotments`\n : null\n const whose = !bound\n ? 'the workspace’s credits'\n : mine.scope === 'host'\n ? 'this site’s AI allotment'\n : 'your AI allotment'\n\n return (\n <Box\n role=\"status\"\n aria-live=\"polite\"\n data-testid=\"ai-usage-strip\"\n sx={{ px: 1, py: 0.75, borderRadius: 1, bgcolor: 'action.hover' }}\n >\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'baseline', flexWrap: 'wrap' }}>\n <Typography variant=\"caption\" sx={{ flexGrow: 1 }}>\n {aiUsageStripMineLabel(meter)}\n </Typography>\n {meter.last !== null ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Last request: ${credits(meter.last)} credits`}\n </Typography>\n ) : null}\n <HelpTip sx={{ alignSelf: 'center' }} {...AI_USAGE_STRIP_HELP} />\n </Stack>\n {pct !== null ? (\n <LinearProgress\n variant=\"determinate\"\n value={pct}\n color={tone}\n aria-label={`${pct}% of ${whose} used`}\n sx={{ my: 0.5, height: 4, borderRadius: 2 }}\n />\n ) : null}\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {aiUsageStripPoolLabel(meter)}\n {bound ? '' : ' this month'}\n {meter.model ? ` · ${meter.model.label}${meter.model.auto ? ' (Auto)' : ''}` : ''}\n </Typography>\n {meter.state === 'warn' ? (\n <Typography variant=\"caption\" color=\"warning.main\" component=\"div\">\n {`Past 80% of ${whose}.`}\n {bound && mine.mode === 'soft' ? ' It is soft, so requests keep working.' : ''}\n </Typography>\n ) : null}\n {meter.state === 'capped' ? (\n <Typography variant=\"caption\" color=\"error\" component=\"div\">\n {allotmentReached\n ? mine.scope === 'collab'\n ? 'Your AI allotment on this site is used for the month. The site’s admin, or an organization admin, can raise it.'\n : mine.scope === 'host'\n ? 'This site’s AI allotment is used for the month. An organization admin can raise it'\n : 'Your AI allotment is used for the month. An organization admin can raise it'\n : 'That request was stopped by a limit on this workspace.'}\n {allotmentReached && raiseHref ? (\n <>\n {' under '}\n <AppLink componentVariant=\"naked\" href={raiseHref}>\n {'Billing → Usage'}\n </AppLink>\n {'.'}\n </>\n ) : allotmentReached && mine.scope !== 'collab' ? (\n '.'\n ) : null}\n </Typography>\n ) : null}\n </Box>\n )\n}\nAiUsageStrip.displayName = 'AiUsageStrip'\n\nexport default AiUsageStrip\n"],"names":["buildRoute","Route","pluginDocsHelp","AppLink","HelpTip","Box","LinearProgress","Stack","Typography","useAiUsageMeter","credits","value","toLocaleString","AI_USAGE_STRIP_HELP","anchor","excerpt","aiUsageStripMineLabel","meter","mine","limit","used","who","scope","aiUsageStripPoolLabel","pool","AiUsageStrip","orgId","orgSlug","bound","pct","Math","min","round","tone","state","allotmentReached","mode","raiseHref","MANAGE_BILLING_USAGE","whose","role","aria-live","data-testid","sx","px","py","borderRadius","bgcolor","useFlexGap","direction","spacing","alignItems","flexWrap","variant","flexGrow","last","color","alignSelf","aria-label","my","height","component","model","label","auto","componentVariant","href","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-usage-strip.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 { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { AppLink, HelpTip } from '@aglyn/shared-ui-jsx'\nimport { Box, LinearProgress, Stack, Typography } from '@mui/material'\nimport type { AiUsageMeterWire } from '../usage/ai-usage-wire'\nimport { useAiUsageMeter } from './use-ai-usage-meter'\n\nexport interface AiUsageStripProps {\n /** The workspace the strip speaks for; nothing renders without one. */\n orgId?: string | null\n /** For the link to who can raise an allotment; the link is omitted without it. */\n orgSlug?: string | null\n}\n\nconst credits = (value: number): string => value.toLocaleString()\n\n/** The usage line's own section of the allotments guide (AGL-3660). */\nconst AI_USAGE_STRIP_HELP = pluginDocsHelp('aiAllotments', {\n anchor: '#usage-strip',\n excerpt:\n 'Your credits this month against your allotment, the workspace pool, and what your last request cost. It warns from 80%.',\n})\n\n/**\n * What a Free reader has left this month (AGL-3722): the less of their own\n * allowance — 300 credits a month across every Free workspace they hold, net\n * of give-backs — and this workspace's band. The same two walls the credits\n * prompt reads, so the strip and the prompt say one number. `null` off the\n * Free plan.\n */\nexport function aiUsageStripFreeLeft(meter: AiUsageMeterWire): number | null {\n const { mine, pool } = meter\n if (!mine.free || mine.limit === null) return null\n const balances = [mine.limit - mine.used, ...(pool.limit === null ? [] : [pool.limit - pool.used])]\n return Math.max(0, Math.min(...balances))\n}\n\n/**\n * The line about the reader's own month, in the words the strip uses. On the\n * Free plan it is what is LEFT (`aiUsageStripFreeLeft`), the figure the\n * credits prompt quotes, and the second line says which two meters it is\n * the less of (AGL-3722).\n */\nexport function aiUsageStripMineLabel(meter: AiUsageMeterWire): string {\n const { mine } = meter\n const freeLeft = aiUsageStripFreeLeft(meter)\n if (freeLeft !== null) return `You have ${credits(freeLeft)} free AI credits left this month`\n if (mine.limit === null) return `You: ${credits(mine.used)} credits this month`\n const who = mine.scope === 'host' ? 'This site' : mine.scope === 'collab' ? 'You, on this site' : 'You'\n return `${who}: ${credits(mine.used)} of ${credits(mine.limit)} credits this month`\n}\n\n/**\n * The line about the workspace's pool. On the Free plan both meters a Free\n * step is held to, each used of its limit and each for this month: the\n * person's across their Free workspaces, and this workspace's (AGL-3722).\n */\nexport function aiUsageStripPoolLabel(meter: AiUsageMeterWire): string {\n const { mine, pool } = meter\n const of = (used: number, limit: number | null) => `${credits(used)}${limit === null ? '' : ` of ${credits(limit)}`}`\n return mine.free\n ? `Used: you ${of(mine.used, mine.limit)} across your Free workspaces · this workspace ${of(pool.used, pool.limit)}`\n : `Workspace: ${of(pool.used, pool.limit)} credits`\n}\n\n/**\n * THE USAGE STRIP (AGL-2942): the reader's AI credits while they use AI —\n * in the assistant panel and every generation dialog, with no dashboard to\n * visit and no read of its own.\n *\n * It shows the reader's month against the allotment that binds them (or\n * their month alone and the workspace's pool when none does), the pool, what\n * the last request cost, and the model that answered. From 80% it warns; at\n * a hard allotment it says who can raise it. It renders nothing until a door\n * has answered this reader in this workspace — see `use-ai-usage-meter.ts`.\n */\nexport function AiUsageStrip({ orgId, orgSlug }: AiUsageStripProps) {\n const meter = useAiUsageMeter(orgId)\n if (!meter) return null\n const { mine, pool } = meter\n const bound = mine.limit !== null\n const freeLeft = aiUsageStripFreeLeft(meter)\n // On the Free plan the bar is what is used of the wall nearest to running out (AGL-3722).\n const used = freeLeft !== null && mine.limit !== null ? mine.limit - freeLeft : bound ? mine.used : pool.used\n const limit = bound ? mine.limit : pool.limit\n const pct = limit ? Math.min(100, Math.round((used / limit) * 100)) : null\n const tone = meter.state === 'capped' ? 'error' : meter.state === 'warn' ? 'warning' : 'primary'\n const allotmentReached =\n freeLeft !== null ? freeLeft <= 0 : bound && mine.mode === 'hard' && mine.limit !== null && mine.used >= mine.limit\n const raiseHref =\n orgSlug && mine.scope !== 'collab'\n ? `${buildRoute(Route.MANAGE_BILLING_USAGE, { orgSlug })}#ai-allotments`\n : null\n const whose = !bound\n ? 'the workspace’s credits'\n : mine.free\n ? 'your free AI credits'\n : mine.scope === 'host'\n ? 'this site’s AI allotment'\n : 'your AI allotment'\n\n return (\n <Box\n role=\"status\"\n aria-live=\"polite\"\n data-testid=\"ai-usage-strip\"\n sx={{ px: 1, py: 0.75, borderRadius: 1, bgcolor: 'action.hover' }}\n >\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'baseline', flexWrap: 'wrap' }}>\n <Typography variant=\"caption\" sx={{ flexGrow: 1 }}>\n {aiUsageStripMineLabel(meter)}\n </Typography>\n {meter.last !== null ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Last request: ${credits(meter.last)} credits`}\n </Typography>\n ) : null}\n <HelpTip sx={{ alignSelf: 'center' }} {...AI_USAGE_STRIP_HELP} />\n </Stack>\n {pct !== null ? (\n <LinearProgress\n variant=\"determinate\"\n value={pct}\n color={tone}\n aria-label={`${pct}% of ${whose} used`}\n sx={{ my: 0.5, height: 4, borderRadius: 2 }}\n />\n ) : null}\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\">\n {aiUsageStripPoolLabel(meter)}\n {bound ? '' : ' this month'}\n {meter.model ? ` · ${meter.model.label}${meter.model.auto ? ' (Auto)' : ''}` : ''}\n </Typography>\n {meter.state === 'warn' ? (\n <Typography variant=\"caption\" color=\"warning.main\" component=\"div\">\n {`Past 80% of ${whose}.`}\n {bound && mine.mode === 'soft' ? ' It is soft, so requests keep working.' : ''}\n </Typography>\n ) : null}\n {meter.state === 'capped' ? (\n <Typography variant=\"caption\" color=\"error\" component=\"div\">\n {allotmentReached && mine.free\n ? 'Your free AI credits for this month are used. They renew on the 1st.'\n : allotmentReached\n ? mine.scope === 'collab'\n ? 'Your AI allotment on this site is used for the month. The site’s admin, or an organization admin, can raise it.'\n : mine.scope === 'host'\n ? 'This site’s AI allotment is used for the month. An organization admin can raise it'\n : 'Your AI allotment is used for the month. An organization admin can raise it'\n : 'That request was stopped by a limit on this workspace.'}\n {allotmentReached && mine.free ? null : allotmentReached && raiseHref ? (\n <>\n {' under '}\n <AppLink componentVariant=\"naked\" href={raiseHref}>\n {'Billing → Usage'}\n </AppLink>\n {'.'}\n </>\n ) : allotmentReached && mine.scope !== 'collab' ? (\n '.'\n ) : null}\n </Typography>\n ) : null}\n </Box>\n )\n}\nAiUsageStrip.displayName = 'AiUsageStrip'\n\nexport default AiUsageStrip\n"],"names":["buildRoute","Route","pluginDocsHelp","AppLink","HelpTip","Box","LinearProgress","Stack","Typography","useAiUsageMeter","credits","value","toLocaleString","AI_USAGE_STRIP_HELP","anchor","excerpt","aiUsageStripFreeLeft","meter","mine","pool","free","limit","balances","used","Math","max","min","aiUsageStripMineLabel","freeLeft","who","scope","aiUsageStripPoolLabel","of","AiUsageStrip","orgId","orgSlug","bound","pct","round","tone","state","allotmentReached","mode","raiseHref","MANAGE_BILLING_USAGE","whose","role","aria-live","data-testid","sx","px","py","borderRadius","bgcolor","useFlexGap","direction","spacing","alignItems","flexWrap","variant","flexGrow","last","color","alignSelf","aria-label","my","height","component","model","label","auto","componentVariant","href","displayName"],"mappings":"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"}
@@ -38,5 +38,7 @@ export interface AssistBuildCardProps {
38
38
  /** The job as it moved, for the thread to keep. */
39
39
  onJob: (job: AiJobSummary) => void;
40
40
  onNotice: (notice: string | null) => void;
41
+ /** The workspace's path slug, for Upgrade on a Free build past what is left or paused (AGL-3722). */
42
+ orgSlug?: string | null;
41
43
  }
42
- export declare function AssistBuildCard({ proposal, job, notice, orgId, user, staff, onJob, onNotice, }: AssistBuildCardProps): JSX.Element;
44
+ export declare function AssistBuildCard({ proposal, job, notice, orgId, user, staff, onJob, onNotice, orgSlug, }: AssistBuildCardProps): JSX.Element;
@@ -18,9 +18,12 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
19
  import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
20
20
  import { HelpTip } from "@aglyn/shared-ui-jsx";
21
- import { Box, Button, CircularProgress, Paper, Stack, Typography } from "@mui/material";
21
+ import { Alert, Box, Button, CircularProgress, Paper, Stack, Typography } from "@mui/material";
22
22
  import { useEffect, useRef, useState } from "react";
23
- import { aiBuildCanRetry, aiBuildItemRows, aiBuildOutcomeLine } from "../model/ai-build-progress.js";
23
+ import { aiBuildCanRetry, aiBuildItemRows, aiBuildOutcomeLine, aiBuildRetryCreditRange } from "../model/ai-build-progress.js";
24
+ import { aiCreditRangeText } from "../model/ai-credit-estimate.js";
25
+ import { AiCreditsPromptNotice } from "./ai-credits-prompt.component.js";
26
+ import { aiCreditsBillingHref } from "./ai-job-links.js";
24
27
  import { AI_JOB_TERMINAL_STATUSES } from "../model/ai-jobs.types.js";
25
28
  import { followAiJobEvents } from "./ai-job-events.js";
26
29
  import { AiJobPlan } from "./ai-job-plan.component.js";
@@ -37,17 +40,21 @@ const ITEM_STATES = {
37
40
  failed: 'failed',
38
41
  skipped: 'not built'
39
42
  };
40
- export function AssistBuildCard({ proposal, job, notice, orgId, user, staff = false, onJob, onNotice }) {
41
- var _ref, _ref1, _ref2;
43
+ export function AssistBuildCard({ proposal, job, notice, orgId, user, staff = false, onJob, onNotice, orgSlug }) {
44
+ var _ref, _ref1, _job_error, _ref2;
42
45
  var _job_plan, _job_items;
43
46
  const [busy, setBusy] = useState(false);
47
+ // A Free Try again past what is left (AGL-3722): its prompt, until chosen.
48
+ const [retryPrompt, setRetryPrompt] = useState(null);
44
49
  const userRef = useRef(user);
45
50
  userRef.current = user;
46
51
  const onJobRef = useRef(onJob);
47
52
  onJobRef.current = onJob;
48
53
  // Follow a job that is still planning or building; a plan waiting for its
49
54
  // confirmation and a settled job need nothing more from the stream.
50
- const following = Boolean(job && !AI_JOB_TERMINAL_STATUSES.includes(job.status) && job.status !== 'needs_review');
55
+ // A build the meter paused (AGL-3722) waits for Resume, not the stream.
56
+ const paused = (job == null ? void 0 : job.status) === 'needs_input';
57
+ const following = Boolean(job && !AI_JOB_TERMINAL_STATUSES.includes(job.status) && job.status !== 'needs_review' && !paused);
51
58
  const jobId = (_ref = job == null ? void 0 : job.id) != null ? _ref : null;
52
59
  useEffect(()=>{
53
60
  if (!following || !jobId) return;
@@ -62,11 +69,16 @@ export function AssistBuildCard({ proposal, job, notice, orgId, user, staff = fa
62
69
  const resume = async (target, options)=>{
63
70
  setBusy(true);
64
71
  onNotice(null);
72
+ setRetryPrompt(null);
65
73
  const decision = await resumeAiJobRequest(user, orgId, target, options);
66
74
  if (decision.job) onJob(decision.job);
67
- if (decision.error) onNotice(decision.error);
75
+ // A Free Try again past what is left asks with its choices; a plan's
76
+ // card draws its own from the job the door answered with.
77
+ if (decision.credits && (options == null ? void 0 : options.retry)) setRetryPrompt(decision.credits);
78
+ else if (decision.error && !(decision.credits && target.status === 'needs_review')) onNotice(decision.error);
68
79
  setBusy(false);
69
80
  };
81
+ const retryRange = job && aiBuildCanRetry(job) ? aiBuildRetryCreditRange(job) : null;
70
82
  const settled = job ? AI_JOB_TERMINAL_STATUSES.includes(job.status) : false;
71
83
  const outcome = job ? aiBuildOutcomeLine(job) : null;
72
84
  return /*#__PURE__*/ _jsxs(Paper, {
@@ -123,6 +135,7 @@ export function AssistBuildCard({ proposal, job, notice, orgId, user, staff = fa
123
135
  busy: busy,
124
136
  staff: staff,
125
137
  user: user,
138
+ orgSlug: orgSlug,
126
139
  onResume: (target, options)=>void resume(target, options)
127
140
  }),
128
141
  job && following && /*#__PURE__*/ _jsxs(Stack, {
@@ -161,6 +174,44 @@ export function AssistBuildCard({ proposal, job, notice, orgId, user, staff = fa
161
174
  ]
162
175
  }, row.slot))
163
176
  }),
177
+ job && paused && // Out of credits mid-build (AGL-3722): paused, never failed. What is
178
+ // built is kept, and Resume carries on from the next item.
179
+ /*#__PURE__*/ _jsx(Alert, {
180
+ severity: "warning",
181
+ sx: {
182
+ mt: 0.5
183
+ },
184
+ action: /*#__PURE__*/ _jsxs(Stack, {
185
+ direction: "row",
186
+ spacing: 1,
187
+ children: [
188
+ orgSlug ? /*#__PURE__*/ _jsx(Button, {
189
+ color: "inherit",
190
+ size: "small",
191
+ href: aiCreditsBillingHref(orgSlug),
192
+ children: 'Upgrade'
193
+ }) : null,
194
+ /*#__PURE__*/ _jsx(Button, {
195
+ color: "inherit",
196
+ size: "small",
197
+ disabled: busy,
198
+ onClick: ()=>void resume(job),
199
+ children: 'Resume'
200
+ })
201
+ ]
202
+ }),
203
+ children: `Paused. ${(_job_error = job.error) != null ? _job_error : 'Your AI credits ran out.'} What is built so far is kept, and Resume carries on from the next item once you upgrade or your credits renew.`
204
+ }),
205
+ job && retryPrompt && /*#__PURE__*/ _jsx(AiCreditsPromptNotice, {
206
+ prompt: retryPrompt,
207
+ noun: "build",
208
+ orgSlug: orgSlug,
209
+ busy: busy,
210
+ onBuildWhatFits: ()=>void resume(job, {
211
+ retry: 'failed-items',
212
+ creditsConfirmed: true
213
+ })
214
+ }),
164
215
  settled && /*#__PURE__*/ _jsx(Typography, {
165
216
  variant: "body2",
166
217
  sx: {
@@ -182,7 +233,7 @@ export function AssistBuildCard({ proposal, job, notice, orgId, user, staff = fa
182
233
  onClick: ()=>void resume(job, {
183
234
  retry: 'failed-items'
184
235
  }),
185
- children: 'Try again what failed'
236
+ children: retryRange ? `Try again what failed · ${aiCreditRangeText(retryRange)}` : 'Try again what failed'
186
237
  }),
187
238
  /*#__PURE__*/ _jsx(Button, {
188
239
  size: "small",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/assist-build-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport type { MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { HelpTip } from '@aglyn/shared-ui-jsx'\nimport { Box, Button, CircularProgress, Paper, Stack, Typography } from '@mui/material'\nimport { useEffect, useRef, useState } from 'react'\nimport { aiBuildCanRetry, aiBuildItemRows, aiBuildOutcomeLine } from '../model/ai-build-progress'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport type { AssistBuildProposal } from '../model/assist-build'\nimport { followAiJobEvents } from './ai-job-events'\nimport { AiJobPlan } from './ai-job-plan.component'\nimport { resumeAiJobRequest } from './ai-job-requests'\nimport { openAiJobs } from './ai-jobs-store'\n\n/**\n * The one card a build proposed in the chat becomes (AGL-3616): while its\n * job plans, a line saying so; then the job's own plan card, with its one\n * price and Confirm — the same `AiJobPlan` the AI jobs drawer shows, so\n * confirming here and there is one door; then where the build stands, item\n * by item, with AI jobs a click away. The job itself is started by the\n * panel, once, when the turn that proposed it ends; this card only follows\n * it, so re-rendering a stored thread never starts a second build.\n */\nexport interface AssistBuildCardProps {\n proposal: AssistBuildProposal\n /** The job the proposal started, as last seen; `null` while it is being created. */\n job: AiJobSummary | null\n /** Why the job could not be created or confirmed, when it could not. */\n notice: string | null\n orgId: string\n user: MaybeTokenSource\n staff?: boolean\n /** The job as it moved, for the thread to keep. */\n onJob: (job: AiJobSummary) => void\n onNotice: (notice: string | null) => void\n}\n\n/** The card's own section of the Assist builds guide (AGL-3660). */\nconst ASSIST_BUILD_HELP = pluginDocsHelp('aiAssistBuilds', {\n anchor: '#the-plan-card',\n excerpt:\n 'What one request will build, item by item, with its estimate. Nothing is built until you choose Confirm plan, and what it makes is a draft.',\n})\n\nconst ITEM_STATES = { done: 'built', active: 'building', waiting: 'waiting', failed: 'failed', skipped: 'not built' } as const\n\nexport function AssistBuildCard({\n proposal,\n job,\n notice,\n orgId,\n user,\n staff = false,\n onJob,\n onNotice,\n}: AssistBuildCardProps): JSX.Element {\n const [busy, setBusy] = useState(false)\n const userRef = useRef(user)\n userRef.current = user\n const onJobRef = useRef(onJob)\n onJobRef.current = onJob\n\n // Follow a job that is still planning or building; a plan waiting for its\n // confirmation and a settled job need nothing more from the stream.\n const following = Boolean(job && !AI_JOB_TERMINAL_STATUSES.includes(job.status) && job.status !== 'needs_review')\n const jobId = job?.id ?? null\n useEffect(() => {\n if (!following || !jobId) return\n const controller = new AbortController()\n void followAiJobEvents(() => userRef.current, orgId, jobId, controller.signal, (next) => onJobRef.current(next))\n return () => controller.abort()\n }, [following, jobId, orgId])\n\n const resume = async (target: AiJobSummary, options?: { publish?: boolean; retry?: 'failed-items' }) => {\n setBusy(true)\n onNotice(null)\n const decision = await resumeAiJobRequest(user, orgId, target, options)\n if (decision.job) onJob(decision.job)\n if (decision.error) onNotice(decision.error)\n setBusy(false)\n }\n\n const settled = job ? AI_JOB_TERMINAL_STATUSES.includes(job.status) : false\n const outcome = job ? aiBuildOutcomeLine(job) : null\n return (\n <Paper variant=\"outlined\" sx={{ mt: 1, p: 1.5 }} aria-label=\"Build plan\">\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center' }}>\n <Typography variant=\"subtitle2\">{proposal.summary}</Typography>\n <HelpTip {...ASSIST_BUILD_HELP} />\n </Stack>\n {!job && !notice && (\n <Stack direction=\"row\" spacing={1} sx={{ mt: 0.5, alignItems: 'center' }}>\n <CircularProgress size={14} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Planning what to build on this site…'}\n </Typography>\n </Stack>\n )}\n {notice && (\n <Typography variant=\"body2\" color=\"error\" sx={{ mt: 0.5 }} role=\"alert\">\n {notice}\n </Typography>\n )}\n {job && job.status === 'needs_review' && (\n <AiJobPlan job={job} busy={busy} staff={staff} user={user} onResume={(target, options) => void resume(target, options)} />\n )}\n {job && following && (\n <Stack direction=\"row\" spacing={1} sx={{ mt: 0.5, alignItems: 'center' }}>\n <CircularProgress size={14} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {job.plan?.status === 'confirmed'\n ? 'Building each part in turn. What it makes is an unpublished draft unless you chose to publish.'\n : 'Planning what to build on this site…'}\n </Typography>\n </Stack>\n )}\n {job && (job.items?.length ?? 0) > 0 && job.status !== 'needs_review' && (\n <Box component=\"ul\" aria-label=\"Items\" sx={{ m: 0, mt: 0.5, pl: 2 }}>\n {aiBuildItemRows(job).map((row) => (\n <Typography key={row.slot} component=\"li\" variant=\"caption\" data-item-state={row.state}>\n {`${row.label} — ${ITEM_STATES[row.state]}`}\n {row.detail ? `. ${row.detail}` : ''}\n </Typography>\n ))}\n </Box>\n )}\n {settled && (\n <Typography variant=\"body2\" sx={{ mt: 0.5 }}>\n {outcome ?? job?.error ?? ''}\n </Typography>\n )}\n {job && (\n <Stack direction=\"row\" spacing={1} sx={{ mt: 1 }}>\n {job && aiBuildCanRetry(job) && (\n <Button size=\"small\" variant=\"contained\" disabled={busy} onClick={() => void resume(job, { retry: 'failed-items' })}>\n {'Try again what failed'}\n </Button>\n )}\n <Button size=\"small\" onClick={() => openAiJobs({ jobId: job.id })}>\n {'Open in AI jobs'}\n </Button>\n </Stack>\n )}\n </Paper>\n )\n}\n"],"names":["pluginDocsHelp","HelpTip","Box","Button","CircularProgress","Paper","Stack","Typography","useEffect","useRef","useState","aiBuildCanRetry","aiBuildItemRows","aiBuildOutcomeLine","AI_JOB_TERMINAL_STATUSES","followAiJobEvents","AiJobPlan","resumeAiJobRequest","openAiJobs","ASSIST_BUILD_HELP","anchor","excerpt","ITEM_STATES","done","active","waiting","failed","skipped","AssistBuildCard","proposal","job","notice","orgId","user","staff","onJob","onNotice","outcome","busy","setBusy","userRef","current","onJobRef","following","Boolean","includes","status","jobId","id","controller","AbortController","signal","next","abort","resume","target","options","decision","error","settled","variant","sx","mt","p","aria-label","direction","spacing","alignItems","summary","size","color","role","onResume","plan","items","length","component","m","pl","map","row","data-item-state","state","label","detail","slot","disabled","onClick","retry"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/assist-build-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport type { MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport { HelpTip } from '@aglyn/shared-ui-jsx'\nimport { Alert, Box, Button, CircularProgress, Paper, Stack, Typography } from '@mui/material'\nimport { useEffect, useRef, useState } from 'react'\nimport { aiBuildCanRetry, aiBuildItemRows, aiBuildOutcomeLine, aiBuildRetryCreditRange } from '../model/ai-build-progress'\nimport { aiCreditRangeText, type AiCreditsPrompt } from '../model/ai-credit-estimate'\nimport { AiCreditsPromptNotice } from './ai-credits-prompt.component'\nimport { aiCreditsBillingHref } from './ai-job-links'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport type { AssistBuildProposal } from '../model/assist-build'\nimport { followAiJobEvents } from './ai-job-events'\nimport { AiJobPlan } from './ai-job-plan.component'\nimport { resumeAiJobRequest, type AiJobResumeOptions } from './ai-job-requests'\nimport { openAiJobs } from './ai-jobs-store'\n\n/**\n * The one card a build proposed in the chat becomes (AGL-3616): while its\n * job plans, a line saying so; then the job's own plan card, with its one\n * price and Confirm — the same `AiJobPlan` the AI jobs drawer shows, so\n * confirming here and there is one door; then where the build stands, item\n * by item, with AI jobs a click away. The job itself is started by the\n * panel, once, when the turn that proposed it ends; this card only follows\n * it, so re-rendering a stored thread never starts a second build.\n */\nexport interface AssistBuildCardProps {\n proposal: AssistBuildProposal\n /** The job the proposal started, as last seen; `null` while it is being created. */\n job: AiJobSummary | null\n /** Why the job could not be created or confirmed, when it could not. */\n notice: string | null\n orgId: string\n user: MaybeTokenSource\n staff?: boolean\n /** The job as it moved, for the thread to keep. */\n onJob: (job: AiJobSummary) => void\n onNotice: (notice: string | null) => void\n /** The workspace's path slug, for Upgrade on a Free build past what is left or paused (AGL-3722). */\n orgSlug?: string | null\n}\n\n/** The card's own section of the Assist builds guide (AGL-3660). */\nconst ASSIST_BUILD_HELP = pluginDocsHelp('aiAssistBuilds', {\n anchor: '#the-plan-card',\n excerpt:\n 'What one request will build, item by item, with its estimate. Nothing is built until you choose Confirm plan, and what it makes is a draft.',\n})\n\nconst ITEM_STATES = { done: 'built', active: 'building', waiting: 'waiting', failed: 'failed', skipped: 'not built' } as const\n\nexport function AssistBuildCard({\n proposal,\n job,\n notice,\n orgId,\n user,\n staff = false,\n onJob,\n onNotice,\n orgSlug,\n}: AssistBuildCardProps): JSX.Element {\n const [busy, setBusy] = useState(false)\n // A Free Try again past what is left (AGL-3722): its prompt, until chosen.\n const [retryPrompt, setRetryPrompt] = useState<AiCreditsPrompt | null>(null)\n const userRef = useRef(user)\n userRef.current = user\n const onJobRef = useRef(onJob)\n onJobRef.current = onJob\n\n // Follow a job that is still planning or building; a plan waiting for its\n // confirmation and a settled job need nothing more from the stream.\n // A build the meter paused (AGL-3722) waits for Resume, not the stream.\n const paused = job?.status === 'needs_input'\n const following = Boolean(\n job && !AI_JOB_TERMINAL_STATUSES.includes(job.status) && job.status !== 'needs_review' && !paused,\n )\n const jobId = job?.id ?? null\n useEffect(() => {\n if (!following || !jobId) return\n const controller = new AbortController()\n void followAiJobEvents(() => userRef.current, orgId, jobId, controller.signal, (next) => onJobRef.current(next))\n return () => controller.abort()\n }, [following, jobId, orgId])\n\n const resume = async (target: AiJobSummary, options?: AiJobResumeOptions) => {\n setBusy(true)\n onNotice(null)\n setRetryPrompt(null)\n const decision = await resumeAiJobRequest(user, orgId, target, options)\n if (decision.job) onJob(decision.job)\n // A Free Try again past what is left asks with its choices; a plan's\n // card draws its own from the job the door answered with.\n if (decision.credits && options?.retry) setRetryPrompt(decision.credits)\n else if (decision.error && !(decision.credits && target.status === 'needs_review')) onNotice(decision.error)\n setBusy(false)\n }\n const retryRange = job && aiBuildCanRetry(job) ? aiBuildRetryCreditRange(job) : null\n\n const settled = job ? AI_JOB_TERMINAL_STATUSES.includes(job.status) : false\n const outcome = job ? aiBuildOutcomeLine(job) : null\n return (\n <Paper variant=\"outlined\" sx={{ mt: 1, p: 1.5 }} aria-label=\"Build plan\">\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center' }}>\n <Typography variant=\"subtitle2\">{proposal.summary}</Typography>\n <HelpTip {...ASSIST_BUILD_HELP} />\n </Stack>\n {!job && !notice && (\n <Stack direction=\"row\" spacing={1} sx={{ mt: 0.5, alignItems: 'center' }}>\n <CircularProgress size={14} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Planning what to build on this site…'}\n </Typography>\n </Stack>\n )}\n {notice && (\n <Typography variant=\"body2\" color=\"error\" sx={{ mt: 0.5 }} role=\"alert\">\n {notice}\n </Typography>\n )}\n {job && job.status === 'needs_review' && (\n <AiJobPlan\n job={job}\n busy={busy}\n staff={staff}\n user={user}\n orgSlug={orgSlug}\n onResume={(target, options) => void resume(target, options)}\n />\n )}\n {job && following && (\n <Stack direction=\"row\" spacing={1} sx={{ mt: 0.5, alignItems: 'center' }}>\n <CircularProgress size={14} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {job.plan?.status === 'confirmed'\n ? 'Building each part in turn. What it makes is an unpublished draft unless you chose to publish.'\n : 'Planning what to build on this site…'}\n </Typography>\n </Stack>\n )}\n {job && (job.items?.length ?? 0) > 0 && job.status !== 'needs_review' && (\n <Box component=\"ul\" aria-label=\"Items\" sx={{ m: 0, mt: 0.5, pl: 2 }}>\n {aiBuildItemRows(job).map((row) => (\n <Typography key={row.slot} component=\"li\" variant=\"caption\" data-item-state={row.state}>\n {`${row.label} — ${ITEM_STATES[row.state]}`}\n {row.detail ? `. ${row.detail}` : ''}\n </Typography>\n ))}\n </Box>\n )}\n {job && paused && (\n // Out of credits mid-build (AGL-3722): paused, never failed. What is\n // built is kept, and Resume carries on from the next item.\n <Alert\n severity=\"warning\"\n sx={{ mt: 0.5 }}\n action={\n <Stack direction=\"row\" spacing={1}>\n {orgSlug ? (\n <Button color=\"inherit\" size=\"small\" href={aiCreditsBillingHref(orgSlug)}>\n {'Upgrade'}\n </Button>\n ) : null}\n <Button color=\"inherit\" size=\"small\" disabled={busy} onClick={() => void resume(job)}>\n {'Resume'}\n </Button>\n </Stack>\n }\n >\n {`Paused. ${job.error ?? 'Your AI credits ran out.'} What is built so far is kept, and Resume carries on from the next item once you upgrade or your credits renew.`}\n </Alert>\n )}\n {job && retryPrompt && (\n <AiCreditsPromptNotice\n prompt={retryPrompt}\n noun=\"build\"\n orgSlug={orgSlug}\n busy={busy}\n onBuildWhatFits={() => void resume(job, { retry: 'failed-items', creditsConfirmed: true })}\n />\n )}\n {settled && (\n <Typography variant=\"body2\" sx={{ mt: 0.5 }}>\n {outcome ?? job?.error ?? ''}\n </Typography>\n )}\n {job && (\n <Stack direction=\"row\" spacing={1} sx={{ mt: 1 }}>\n {job && aiBuildCanRetry(job) && (\n <Button size=\"small\" variant=\"contained\" disabled={busy} onClick={() => void resume(job, { retry: 'failed-items' })}>\n {retryRange ? `Try again what failed · ${aiCreditRangeText(retryRange)}` : 'Try again what failed'}\n </Button>\n )}\n <Button size=\"small\" onClick={() => openAiJobs({ jobId: job.id })}>\n {'Open in AI jobs'}\n </Button>\n </Stack>\n )}\n </Paper>\n )\n}\n"],"names":["pluginDocsHelp","HelpTip","Alert","Box","Button","CircularProgress","Paper","Stack","Typography","useEffect","useRef","useState","aiBuildCanRetry","aiBuildItemRows","aiBuildOutcomeLine","aiBuildRetryCreditRange","aiCreditRangeText","AiCreditsPromptNotice","aiCreditsBillingHref","AI_JOB_TERMINAL_STATUSES","followAiJobEvents","AiJobPlan","resumeAiJobRequest","openAiJobs","ASSIST_BUILD_HELP","anchor","excerpt","ITEM_STATES","done","active","waiting","failed","skipped","AssistBuildCard","proposal","job","notice","orgId","user","staff","onJob","onNotice","orgSlug","outcome","busy","setBusy","retryPrompt","setRetryPrompt","userRef","current","onJobRef","paused","status","following","Boolean","includes","jobId","id","controller","AbortController","signal","next","abort","resume","target","options","decision","credits","retry","error","retryRange","settled","variant","sx","mt","p","aria-label","direction","spacing","alignItems","summary","size","color","role","onResume","plan","items","length","component","m","pl","map","row","data-item-state","state","label","detail","slot","severity","action","href","disabled","onClick","prompt","noun","onBuildWhatFits","creditsConfirmed"],"mappings":"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"}
@@ -26,15 +26,22 @@ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
26
26
  import { Box, Button, Chip, CircularProgress, Collapse, IconButton, Stack, Typography } from "@mui/material";
27
27
  import { useCallback, useEffect, useRef, useState } from "react";
28
28
  import { AI_JOB_PHASE_LABELS, aiJobActivityCounts, aiJobPhase, aiJobsHeaderChips } from "../model/ai-job-activity.js";
29
- import { cancelAiJobRequest, resumeAiJobRequest } from "./ai-job-requests.js";
30
- import { aiBuildCanRetry, aiBuildItemRows, aiSitePartialCopy } from "../model/ai-build-progress.js";
29
+ import { resumeAiJobRequest } from "./ai-job-requests.js";
30
+ import { useAiJobCancel } from "./ai-job-cancel.component.js";
31
+ import { aiBuildCanRetry, aiBuildItemRows, aiBuildRetryCreditRange, aiSitePartialCopy } from "../model/ai-build-progress.js";
32
+ import { aiCreditRangeText } from "../model/ai-credit-estimate.js";
33
+ import { AiCreditsPromptNotice } from "./ai-credits-prompt.component.js";
31
34
  import { publishAiJob } from "./ai-jobs-store.js";
32
35
  import { AiPageBriefDialog } from "./ai-page-brief-dialog.component.js";
33
36
  import { AiInsightDialog } from "./ai-insight-dialog.component.js";
34
37
  import { AiJobPlan } from "./ai-job-plan.component.js";
35
- import { aiJobOutputHref, aiJobPrimaryLink, aiSiteBuildHref } from "./ai-job-links.js";
38
+ import { aiCreditsBillingHref, aiJobOutputHref, aiJobPrimaryLink, aiSiteBuildHref } from "./ai-job-links.js";
36
39
  import { aiJobRefundCopy } from "../model/ai-job-failure-copy.js";
37
40
  import { readEventFrames } from "./ai-job-events.js";
41
+ /** Try again, with what it is likely to cost (AGL-3722). */ function aiBuildRetryLabel(job) {
42
+ const range = aiBuildRetryCreditRange(job);
43
+ return range ? `Try again what failed · ${aiCreditRangeText(range)}` : 'Try again what failed';
44
+ }
38
45
  // Where an output opens, and the stream reader, live beside the dialogs that
39
46
  // share them (AGL-3593); this module has always exported them.
40
47
  export { aiJobOutputHref, aiJobPrimaryLink, readEventFrames };
@@ -306,21 +313,16 @@ export function AssistJobsDrawer({ orgId, org, orgReady, orgSlug, user, visible,
306
313
  user,
307
314
  patchJob
308
315
  ]);
309
- // The same door the dialog that started the job cancels through (AGL-3593).
310
- const cancel = useCallback(async (jobId)=>{
311
- if (!orgId) return;
312
- const { job, error } = await cancelAiJobRequest(user, orgId, jobId);
313
- if (error) {
314
- setNotice(error);
315
- return;
316
- }
317
- if (job) patchJob(job);
318
- }, [
319
- orgId,
316
+ // The same door the dialog that started the job cancels through (AGL-3593),
317
+ // behind the one confirm every Cancel shows (AGL-3616).
318
+ const canceling = useAiJobCancel({
320
319
  user,
321
- patchJob
322
- ]);
320
+ orgId,
321
+ onJob: patchJob
322
+ });
323
323
  /** The job whose resume is in flight, so its button cannot send twice. */ const [resuming, setResuming] = useState(null);
324
+ // A Free Try again past what is left (AGL-3722): its prompt, by job, until chosen.
325
+ const [retryPrompt, setRetryPrompt] = useState(null);
324
326
  // Confirms a plan, or tries again a step whose answer broke a building
325
327
  // rule (AGL-2935). The door runs the next step inline and answers with the
326
328
  // job, so the row moves on without waiting for the stream.
@@ -328,10 +330,17 @@ export function AssistJobsDrawer({ orgId, org, orgReady, orgSlug, user, visible,
328
330
  if (!orgId) return;
329
331
  setResuming(job.id);
330
332
  setNotice(null);
333
+ setRetryPrompt(null);
331
334
  // The same door the dialog that started the job confirms through (AGL-3593).
332
- const { job: next, error } = await resumeAiJobRequest(user, orgId, job, options);
335
+ const { job: next, error, credits } = await resumeAiJobRequest(user, orgId, job, options);
333
336
  if (next) patchJob(next);
334
- if (error) setNotice(error);
337
+ // A Free job past what is left asks with its choices (AGL-3722): a
338
+ // retry here, a plan on its own card from the job the door answered with.
339
+ if (credits && (options == null ? void 0 : options.retry)) setRetryPrompt({
340
+ jobId: job.id,
341
+ prompt: credits
342
+ });
343
+ else if (error && !(credits && job.status === 'needs_review')) setNotice(error);
335
344
  setResuming(null);
336
345
  }, [
337
346
  orgId,
@@ -434,6 +443,7 @@ export function AssistJobsDrawer({ orgId, org, orgReady, orgSlug, user, visible,
434
443
  },
435
444
  children: "No AI jobs yet."
436
445
  }),
446
+ canceling.dialog,
437
447
  /*#__PURE__*/ _jsx(Stack, {
438
448
  ref: listRef,
439
449
  spacing: 1,
@@ -508,8 +518,9 @@ export function AssistJobsDrawer({ orgId, org, orgReady, orgSlug, user, visible,
508
518
  }),
509
519
  !terminal && /*#__PURE__*/ _jsx(Button, {
510
520
  size: "small",
511
- onClick: ()=>void cancel(job.id),
512
- children: "Cancel"
521
+ disabled: Boolean(job.cancelRequested) || canceling.busyJobId === job.id,
522
+ onClick: ()=>canceling.ask(job),
523
+ children: job.cancelRequested ? 'Stopping…' : 'Cancel'
513
524
  })
514
525
  ]
515
526
  }),
@@ -567,7 +578,39 @@ export function AssistJobsDrawer({ orgId, org, orgReady, orgSlug, user, visible,
567
578
  sx: {
568
579
  mt: 0.5
569
580
  },
570
- children: 'Try again what failed'
581
+ children: aiBuildRetryLabel(job)
582
+ }) : null,
583
+ (retryPrompt == null ? void 0 : retryPrompt.jobId) === job.id ? /*#__PURE__*/ _jsx(AiCreditsPromptNotice, {
584
+ prompt: retryPrompt.prompt,
585
+ noun: "build",
586
+ orgSlug: orgSlug,
587
+ busy: resuming === job.id,
588
+ onBuildWhatFits: ()=>void resume(job, {
589
+ retry: 'failed-items',
590
+ creditsConfirmed: true
591
+ })
592
+ }) : null,
593
+ job.status === 'needs_input' ? /*#__PURE__*/ _jsxs(Stack, {
594
+ direction: "row",
595
+ spacing: 1,
596
+ sx: {
597
+ mt: 0.5
598
+ },
599
+ children: [
600
+ /*#__PURE__*/ _jsx(Button, {
601
+ size: "small",
602
+ variant: "contained",
603
+ disabled: resuming === job.id,
604
+ onClick: ()=>void resume(job),
605
+ children: 'Resume'
606
+ }),
607
+ /*#__PURE__*/ _jsx(Button, {
608
+ size: "small",
609
+ component: AppLink,
610
+ href: aiCreditsBillingHref(orgSlug),
611
+ children: 'Upgrade'
612
+ })
613
+ ]
571
614
  }) : null,
572
615
  restartHref(job) ? /*#__PURE__*/ _jsx(Button, {
573
616
  size: "small",
@@ -656,7 +699,8 @@ export function AssistJobsDrawer({ orgId, org, orgReady, orgSlug, user, visible,
656
699
  onResume: (target, options)=>void resume(target, options),
657
700
  busy: resuming === job.id,
658
701
  staff: isStaff,
659
- user: user
702
+ user: user,
703
+ orgSlug: orgSlug
660
704
  })
661
705
  ]
662
706
  }, job.id);