@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-ai",
3
- "version": "1.0.0-beta.236",
3
+ "version": "1.0.0-beta.238",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,22 +25,22 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.236",
29
- "@aglyn/shared-data-enums": "1.0.0-beta.236",
30
- "@aglyn/shared-data-mdi": "1.0.0-beta.236",
31
- "@aglyn/shared-data-types": "1.0.0-beta.236",
32
- "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.236",
33
- "@aglyn/shared-ui-jsx": "1.0.0-beta.236",
34
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.236",
35
- "@aglyn/shared-ui-theme": "1.0.0-beta.236",
36
- "@aglyn/shared-util-email": "1.0.0-beta.236",
37
- "@aglyn/shared-util-errors": "1.0.0-beta.236",
38
- "@aglyn/shared-util-http": "1.0.0-beta.236",
39
- "@aglyn/shared-util-timestamp": "1.0.0-beta.236",
40
- "@aglyn/shared-util-tools": "1.0.0-beta.236",
41
- "@aglyn/tenant-data-admin": "1.0.0-beta.236",
42
- "@aglyn/tenant-feature-instance": "1.0.0-beta.236",
43
- "@aglyn/tenant-runtime": "1.0.0-beta.236",
28
+ "@aglyn/aglyn": "1.0.0-beta.238",
29
+ "@aglyn/shared-data-enums": "1.0.0-beta.238",
30
+ "@aglyn/shared-data-mdi": "1.0.0-beta.238",
31
+ "@aglyn/shared-data-types": "1.0.0-beta.238",
32
+ "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.238",
33
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.238",
34
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.238",
35
+ "@aglyn/shared-ui-theme": "1.0.0-beta.238",
36
+ "@aglyn/shared-util-email": "1.0.0-beta.238",
37
+ "@aglyn/shared-util-errors": "1.0.0-beta.238",
38
+ "@aglyn/shared-util-http": "1.0.0-beta.238",
39
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.238",
40
+ "@aglyn/shared-util-tools": "1.0.0-beta.238",
41
+ "@aglyn/tenant-data-admin": "1.0.0-beta.238",
42
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.238",
43
+ "@aglyn/tenant-runtime": "1.0.0-beta.238",
44
44
  "@swc/helpers": "0.5.23"
45
45
  },
46
46
  "peerDependencies": {
@@ -0,0 +1,44 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type AiCreditsPrompt } from '../model/ai-credit-estimate';
18
+ /**
19
+ * What a Free job past what is left asks before it starts (AGL-3722), on the
20
+ * Assist build plan card, in the guided start, on a build's page and in the
21
+ * jobs drawer alike: the plain sentence — about what it costs, what is left,
22
+ * that it builds what it can and pauses — and its ways on. Only "Build what
23
+ * fits" (or the smaller first build) starts anything; nothing is started by
24
+ * showing this.
25
+ *
26
+ * The ways on are one set of option buttons (Zach, 2026-10-10): the same
27
+ * shape and height, full width so a long label never wraps beside another,
28
+ * sentence case, a short label with its figure under it, and the recommended
29
+ * one drawn as the primary button. Theme tokens only, so it reads the same in
30
+ * the Assist panel's narrow column and in light or dark.
31
+ */
32
+ export interface AiCreditsPromptProps {
33
+ prompt: AiCreditsPrompt;
34
+ noun: 'build' | 'site';
35
+ /** The workspace's path slug, for Upgrade; Upgrade is left out without it. */
36
+ orgSlug?: string | null;
37
+ busy?: boolean;
38
+ /** "Build what fits": start it, to pause where the credits run out. */
39
+ onBuildWhatFits: () => void;
40
+ /** The smaller first build; offered only when the prompt has one and this is given. */
41
+ onSmaller?: () => void;
42
+ }
43
+ export declare function AiCreditsPromptNotice({ prompt, noun, orgSlug, busy, onBuildWhatFits, onSmaller, }: AiCreditsPromptProps): JSX.Element;
44
+ export default AiCreditsPromptNotice;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
+ import { Box, Button, Stack, Typography } from "@mui/material";
20
+ import { useId } from "react";
21
+ import { aiCreditsChoices, aiCreditsPromptText } from "../model/ai-credit-estimate.js";
22
+ import { aiCreditsBillingHref } from "./ai-job-links.js";
23
+ function AiCreditsChoiceButton({ choice, busy, href, onClick }) {
24
+ const detailId = `${useId()}-detail`;
25
+ return /*#__PURE__*/ _jsxs(Button, _extends({
26
+ fullWidth: true,
27
+ size: "medium",
28
+ variant: choice.recommended ? 'contained' : 'outlined',
29
+ color: "primary",
30
+ disabled: busy && choice.key !== 'upgrade',
31
+ "aria-label": choice.label,
32
+ "aria-describedby": detailId,
33
+ "data-choice": choice.key
34
+ }, href ? {
35
+ href
36
+ } : {
37
+ onClick
38
+ }, {
39
+ sx: {
40
+ minHeight: 56,
41
+ px: 1.5,
42
+ py: 1,
43
+ display: 'flex',
44
+ flexDirection: 'column',
45
+ alignItems: 'flex-start',
46
+ justifyContent: 'center',
47
+ textAlign: 'left',
48
+ textTransform: 'none',
49
+ lineHeight: 1.3
50
+ },
51
+ children: [
52
+ /*#__PURE__*/ _jsx(Typography, {
53
+ component: "span",
54
+ variant: "body2",
55
+ sx: {
56
+ fontWeight: 600,
57
+ color: 'inherit'
58
+ },
59
+ children: choice.label
60
+ }),
61
+ /*#__PURE__*/ _jsx(Typography, {
62
+ id: detailId,
63
+ component: "span",
64
+ variant: "caption",
65
+ sx: {
66
+ color: 'inherit',
67
+ opacity: 0.8
68
+ },
69
+ children: choice.detail
70
+ })
71
+ ]
72
+ }));
73
+ }
74
+ export function AiCreditsPromptNotice({ prompt, noun, orgSlug, busy = false, onBuildWhatFits, onSmaller }) {
75
+ const choices = aiCreditsChoices(prompt, {
76
+ smaller: Boolean(onSmaller),
77
+ upgrade: Boolean(orgSlug)
78
+ });
79
+ return /*#__PURE__*/ _jsxs(Box, {
80
+ role: "alert",
81
+ "aria-label": "More than your credits left",
82
+ sx: {
83
+ mt: 1,
84
+ p: 1.5,
85
+ border: 1,
86
+ borderColor: 'warning.main',
87
+ borderRadius: 1,
88
+ bgcolor: 'background.paper'
89
+ },
90
+ children: [
91
+ /*#__PURE__*/ _jsx(Typography, {
92
+ variant: "body2",
93
+ sx: {
94
+ color: 'text.primary'
95
+ },
96
+ children: aiCreditsPromptText(prompt, noun)
97
+ }),
98
+ /*#__PURE__*/ _jsx(Stack, {
99
+ spacing: 1,
100
+ sx: {
101
+ mt: 1.5
102
+ },
103
+ children: choices.map((choice)=>/*#__PURE__*/ _jsx(AiCreditsChoiceButton, _extends({
104
+ choice: choice,
105
+ busy: busy
106
+ }, choice.key === 'upgrade' && orgSlug ? {
107
+ href: aiCreditsBillingHref(orgSlug)
108
+ } : {
109
+ onClick: choice.key === 'smaller' ? onSmaller : onBuildWhatFits
110
+ }), choice.key))
111
+ })
112
+ ]
113
+ });
114
+ }
115
+ export default AiCreditsPromptNotice;
116
+
117
+ //# sourceMappingURL=ai-credits-prompt.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-credits-prompt.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 { Box, Button, Stack, Typography } from '@mui/material'\nimport { useId } from 'react'\nimport {\n aiCreditsChoices,\n aiCreditsPromptText,\n type AiCreditsChoice,\n type AiCreditsPrompt,\n} from '../model/ai-credit-estimate'\nimport { aiCreditsBillingHref } from './ai-job-links'\n\n/**\n * What a Free job past what is left asks before it starts (AGL-3722), on the\n * Assist build plan card, in the guided start, on a build's page and in the\n * jobs drawer alike: the plain sentence — about what it costs, what is left,\n * that it builds what it can and pauses — and its ways on. Only \"Build what\n * fits\" (or the smaller first build) starts anything; nothing is started by\n * showing this.\n *\n * The ways on are one set of option buttons (Zach, 2026-10-10): the same\n * shape and height, full width so a long label never wraps beside another,\n * sentence case, a short label with its figure under it, and the recommended\n * one drawn as the primary button. Theme tokens only, so it reads the same in\n * the Assist panel's narrow column and in light or dark.\n */\nexport interface AiCreditsPromptProps {\n prompt: AiCreditsPrompt\n noun: 'build' | 'site'\n /** The workspace's path slug, for Upgrade; Upgrade is left out without it. */\n orgSlug?: string | null\n busy?: boolean\n /** \"Build what fits\": start it, to pause where the credits run out. */\n onBuildWhatFits: () => void\n /** The smaller first build; offered only when the prompt has one and this is given. */\n onSmaller?: () => void\n}\n\nfunction AiCreditsChoiceButton({\n choice,\n busy,\n href,\n onClick,\n}: {\n choice: AiCreditsChoice\n busy: boolean\n href?: string\n onClick?: () => void\n}): JSX.Element {\n const detailId = `${useId()}-detail`\n return (\n <Button\n fullWidth\n size=\"medium\"\n variant={choice.recommended ? 'contained' : 'outlined'}\n color=\"primary\"\n disabled={busy && choice.key !== 'upgrade'}\n aria-label={choice.label}\n aria-describedby={detailId}\n data-choice={choice.key}\n {...(href ? { href } : { onClick })}\n sx={{\n minHeight: 56,\n px: 1.5,\n py: 1,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-start',\n justifyContent: 'center',\n textAlign: 'left',\n textTransform: 'none',\n lineHeight: 1.3,\n }}\n >\n <Typography component=\"span\" variant=\"body2\" sx={{ fontWeight: 600, color: 'inherit' }}>\n {choice.label}\n </Typography>\n <Typography id={detailId} component=\"span\" variant=\"caption\" sx={{ color: 'inherit', opacity: 0.8 }}>\n {choice.detail}\n </Typography>\n </Button>\n )\n}\n\nexport function AiCreditsPromptNotice({\n prompt,\n noun,\n orgSlug,\n busy = false,\n onBuildWhatFits,\n onSmaller,\n}: AiCreditsPromptProps): JSX.Element {\n const choices = aiCreditsChoices(prompt, { smaller: Boolean(onSmaller), upgrade: Boolean(orgSlug) })\n return (\n <Box\n role=\"alert\"\n aria-label=\"More than your credits left\"\n sx={{\n mt: 1,\n p: 1.5,\n border: 1,\n borderColor: 'warning.main',\n borderRadius: 1,\n bgcolor: 'background.paper',\n }}\n >\n <Typography variant=\"body2\" sx={{ color: 'text.primary' }}>\n {aiCreditsPromptText(prompt, noun)}\n </Typography>\n <Stack spacing={1} sx={{ mt: 1.5 }}>\n {choices.map((choice) => (\n <AiCreditsChoiceButton\n key={choice.key}\n choice={choice}\n busy={busy}\n {...(choice.key === 'upgrade' && orgSlug\n ? { href: aiCreditsBillingHref(orgSlug) }\n : { onClick: choice.key === 'smaller' ? onSmaller : onBuildWhatFits })}\n />\n ))}\n </Stack>\n </Box>\n )\n}\n\nexport default AiCreditsPromptNotice\n"],"names":["Box","Button","Stack","Typography","useId","aiCreditsChoices","aiCreditsPromptText","aiCreditsBillingHref","AiCreditsChoiceButton","choice","busy","href","onClick","detailId","fullWidth","size","variant","recommended","color","disabled","key","aria-label","label","aria-describedby","data-choice","sx","minHeight","px","py","display","flexDirection","alignItems","justifyContent","textAlign","textTransform","lineHeight","component","fontWeight","id","opacity","detail","AiCreditsPromptNotice","prompt","noun","orgSlug","onBuildWhatFits","onSmaller","choices","smaller","Boolean","upgrade","role","mt","p","border","borderColor","borderRadius","bgcolor","spacing","map"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;AAEA,SAASA,GAAG,EAAEC,MAAM,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AAC9D,SAASC,KAAK,QAAQ,QAAO;AAC7B,SACEC,gBAAgB,EAChBC,mBAAmB,QAGd,iCAA6B;AACpC,SAASC,oBAAoB,QAAQ,oBAAgB;AA4BrD,SAASC,sBAAsB,EAC7BC,MAAM,EACNC,IAAI,EACJC,IAAI,EACJC,OAAO,EAMR;IACC,MAAMC,WAAW,GAAGT,QAAQ,OAAO,CAAC;IACpC,qBACE,MAACH;QACCa,SAAS;QACTC,MAAK;QACLC,SAASP,OAAOQ,WAAW,GAAG,cAAc;QAC5CC,OAAM;QACNC,UAAUT,QAAQD,OAAOW,GAAG,KAAK;QACjCC,cAAYZ,OAAOa,KAAK;QACxBC,oBAAkBV;QAClBW,eAAaf,OAAOW,GAAG;OAClBT,OAAO;QAAEA;IAAK,IAAI;QAAEC;IAAQ;QACjCa,IAAI;YACFC,WAAW;YACXC,IAAI;YACJC,IAAI;YACJC,SAAS;YACTC,eAAe;YACfC,YAAY;YACZC,gBAAgB;YAChBC,WAAW;YACXC,eAAe;YACfC,YAAY;QACd;;0BAEA,KAAChC;gBAAWiC,WAAU;gBAAOpB,SAAQ;gBAAQS,IAAI;oBAAEY,YAAY;oBAAKnB,OAAO;gBAAU;0BAClFT,OAAOa,KAAK;;0BAEf,KAACnB;gBAAWmC,IAAIzB;gBAAUuB,WAAU;gBAAOpB,SAAQ;gBAAUS,IAAI;oBAAEP,OAAO;oBAAWqB,SAAS;gBAAI;0BAC/F9B,OAAO+B,MAAM;;;;AAItB;AAEA,OAAO,SAASC,sBAAsB,EACpCC,MAAM,EACNC,IAAI,EACJC,OAAO,EACPlC,OAAO,KAAK,EACZmC,eAAe,EACfC,SAAS,EACY;IACrB,MAAMC,UAAU1C,iBAAiBqC,QAAQ;QAAEM,SAASC,QAAQH;QAAYI,SAASD,QAAQL;IAAS;IAClG,qBACE,MAAC5C;QACCmD,MAAK;QACL9B,cAAW;QACXI,IAAI;YACF2B,IAAI;YACJC,GAAG;YACHC,QAAQ;YACRC,aAAa;YACbC,cAAc;YACdC,SAAS;QACX;;0BAEA,KAACtD;gBAAWa,SAAQ;gBAAQS,IAAI;oBAAEP,OAAO;gBAAe;0BACrDZ,oBAAoBoC,QAAQC;;0BAE/B,KAACzC;gBAAMwD,SAAS;gBAAGjC,IAAI;oBAAE2B,IAAI;gBAAI;0BAC9BL,QAAQY,GAAG,CAAC,CAAClD,uBACZ,KAACD;wBAECC,QAAQA;wBACRC,MAAMA;uBACDD,OAAOW,GAAG,KAAK,aAAawB,UAC7B;wBAAEjC,MAAMJ,qBAAqBqC;oBAAS,IACtC;wBAAEhC,SAASH,OAAOW,GAAG,KAAK,YAAY0B,YAAYD;oBAAgB,IALjEpC,OAAOW,GAAG;;;;AAW3B;AAEA,eAAeqB,sBAAqB"}
@@ -0,0 +1,38 @@
1
+ import type { MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token';
2
+ import { type ButtonProps } from '@mui/material';
3
+ import { type ReactNode } from 'react';
4
+ import { type AiJobSummary } from '../model/ai-jobs.types';
5
+ /**
6
+ * Cancel an AI job, after saying what it does (AGL-3616). One confirm dialog
7
+ * and one request for every surface that offers Cancel — the job's page, a
8
+ * row of the site's AI jobs list, and the AI jobs list the top-bar chip
9
+ * opens — through the cancel door the Assist panel's list always used.
10
+ *
11
+ * `useAiJobCancel` owns the dialog: `ask(job)` opens it, Keep building
12
+ * closes it, and Cancel job posts the cancel and hands the job the door
13
+ * answered with to `onJob`, so the surface shows "Stopping…" or "Canceled"
14
+ * without waiting for its stream. A refusal stays in the dialog, in the
15
+ * door's own words.
16
+ */
17
+ export declare function useAiJobCancel({ user, orgId, onJob, }: {
18
+ user: MaybeTokenSource;
19
+ orgId: string | null;
20
+ onJob?: (job: AiJobSummary) => void;
21
+ }): {
22
+ ask: (job: AiJobSummary) => void;
23
+ busyJobId: string | null;
24
+ dialog: ReactNode;
25
+ };
26
+ /**
27
+ * The Cancel button: enabled while the job can still be stopped, and
28
+ * disabled with the reason as its tooltip once it is done, failed, canceled
29
+ * or already stopping.
30
+ */
31
+ export declare function AiJobCancelButton({ job, user, orgId, onJob, size, variant, }: {
32
+ job: AiJobSummary;
33
+ user: MaybeTokenSource;
34
+ orgId: string | null;
35
+ onJob?: (job: AiJobSummary) => void;
36
+ size?: ButtonProps['size'];
37
+ variant?: ButtonProps['variant'];
38
+ }): import("react").JSX.Element;
@@ -0,0 +1,156 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
18
+ import { Alert, Box, Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Tooltip } from "@mui/material";
19
+ import { useCallback, useRef, useState } from "react";
20
+ import { AI_JOB_CANCEL_CONFIRM_COPY, AI_JOB_CANCEL_TITLE, aiJobCancelBlockedReason } from "../model/ai-job-cancel-copy.js";
21
+ import { AI_JOB_TERMINAL_STATUSES } from "../model/ai-jobs.types.js";
22
+ import { cancelAiJobRequest } from "./ai-job-requests.js";
23
+ /**
24
+ * Cancel an AI job, after saying what it does (AGL-3616). One confirm dialog
25
+ * and one request for every surface that offers Cancel — the job's page, a
26
+ * row of the site's AI jobs list, and the AI jobs list the top-bar chip
27
+ * opens — through the cancel door the Assist panel's list always used.
28
+ *
29
+ * `useAiJobCancel` owns the dialog: `ask(job)` opens it, Keep building
30
+ * closes it, and Cancel job posts the cancel and hands the job the door
31
+ * answered with to `onJob`, so the surface shows "Stopping…" or "Canceled"
32
+ * without waiting for its stream. A refusal stays in the dialog, in the
33
+ * door's own words.
34
+ */ export function useAiJobCancel({ user, orgId, onJob }) {
35
+ var _ref;
36
+ const [target, setTarget] = useState(null);
37
+ const [busy, setBusy] = useState(false);
38
+ const [error, setError] = useState(null);
39
+ const onJobRef = useRef(onJob);
40
+ onJobRef.current = onJob;
41
+ const ask = useCallback((job)=>{
42
+ if (aiJobCancelBlockedReason(job)) return;
43
+ setError(null);
44
+ setTarget(job);
45
+ }, []);
46
+ const close = useCallback(()=>{
47
+ if (busy) return;
48
+ setTarget(null);
49
+ setError(null);
50
+ }, [
51
+ busy
52
+ ]);
53
+ const confirm = useCallback(async ()=>{
54
+ if (!target || !orgId) return;
55
+ setBusy(true);
56
+ setError(null);
57
+ const { job, error: refused } = await cancelAiJobRequest(user, orgId, target.id);
58
+ setBusy(false);
59
+ if (job) onJobRef.current == null ? void 0 : onJobRef.current.call(onJobRef, job);
60
+ if (refused) {
61
+ setError(refused);
62
+ return;
63
+ }
64
+ setTarget(null);
65
+ }, [
66
+ target,
67
+ orgId,
68
+ user
69
+ ]);
70
+ const dialog = /*#__PURE__*/ _jsxs(Dialog, {
71
+ open: target !== null,
72
+ onClose: close,
73
+ "aria-labelledby": "ai-job-cancel-title",
74
+ maxWidth: "xs",
75
+ fullWidth: true,
76
+ children: [
77
+ /*#__PURE__*/ _jsx(DialogTitle, {
78
+ id: "ai-job-cancel-title",
79
+ children: AI_JOB_CANCEL_TITLE
80
+ }),
81
+ /*#__PURE__*/ _jsxs(DialogContent, {
82
+ children: [
83
+ /*#__PURE__*/ _jsx(DialogContentText, {
84
+ children: AI_JOB_CANCEL_CONFIRM_COPY
85
+ }),
86
+ error ? /*#__PURE__*/ _jsx(Alert, {
87
+ severity: "warning",
88
+ sx: {
89
+ mt: 2
90
+ },
91
+ children: error
92
+ }) : null
93
+ ]
94
+ }),
95
+ /*#__PURE__*/ _jsxs(DialogActions, {
96
+ children: [
97
+ /*#__PURE__*/ _jsx(Button, {
98
+ onClick: close,
99
+ disabled: busy,
100
+ children: 'Keep building'
101
+ }),
102
+ /*#__PURE__*/ _jsx(Button, {
103
+ color: "error",
104
+ variant: "contained",
105
+ onClick: ()=>void confirm(),
106
+ disabled: busy || !orgId,
107
+ children: busy ? 'Canceling…' : 'Cancel job'
108
+ })
109
+ ]
110
+ })
111
+ ]
112
+ });
113
+ return {
114
+ ask,
115
+ busyJobId: busy ? (_ref = target == null ? void 0 : target.id) != null ? _ref : null : null,
116
+ dialog
117
+ };
118
+ }
119
+ /**
120
+ * The Cancel button: enabled while the job can still be stopped, and
121
+ * disabled with the reason as its tooltip once it is done, failed, canceled
122
+ * or already stopping.
123
+ */ export function AiJobCancelButton({ job, user, orgId, onJob, size = 'medium', variant = 'outlined' }) {
124
+ const { ask, busyJobId, dialog } = useAiJobCancel({
125
+ user,
126
+ orgId,
127
+ onJob
128
+ });
129
+ const blocked = aiJobCancelBlockedReason(job);
130
+ const stopping = Boolean(job.cancelRequested) && !AI_JOB_TERMINAL_STATUSES.includes(job.status);
131
+ const button = /*#__PURE__*/ _jsx(Button, {
132
+ size: size,
133
+ variant: variant,
134
+ color: "error",
135
+ disabled: Boolean(blocked) || busyJobId === job.id || !orgId,
136
+ onClick: ()=>ask(job),
137
+ children: stopping ? 'Stopping…' : 'Cancel'
138
+ });
139
+ return /*#__PURE__*/ _jsxs(_Fragment, {
140
+ children: [
141
+ blocked ? /*#__PURE__*/ _jsx(Tooltip, {
142
+ title: blocked,
143
+ children: /*#__PURE__*/ _jsx(Box, {
144
+ component: "span",
145
+ sx: {
146
+ display: 'inline-flex'
147
+ },
148
+ children: button
149
+ })
150
+ }) : button,
151
+ dialog
152
+ ]
153
+ });
154
+ }
155
+
156
+ //# sourceMappingURL=ai-job-cancel.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-cancel.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 { MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport {\n Alert,\n Box,\n Button,\n type ButtonProps,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Tooltip,\n} from '@mui/material'\nimport { type ReactNode, useCallback, useRef, useState } from 'react'\nimport {\n AI_JOB_CANCEL_CONFIRM_COPY,\n AI_JOB_CANCEL_TITLE,\n aiJobCancelBlockedReason,\n} from '../model/ai-job-cancel-copy'\nimport { AI_JOB_TERMINAL_STATUSES, type AiJobSummary } from '../model/ai-jobs.types'\nimport { cancelAiJobRequest } from './ai-job-requests'\n\n/**\n * Cancel an AI job, after saying what it does (AGL-3616). One confirm dialog\n * and one request for every surface that offers Cancel — the job's page, a\n * row of the site's AI jobs list, and the AI jobs list the top-bar chip\n * opens — through the cancel door the Assist panel's list always used.\n *\n * `useAiJobCancel` owns the dialog: `ask(job)` opens it, Keep building\n * closes it, and Cancel job posts the cancel and hands the job the door\n * answered with to `onJob`, so the surface shows \"Stopping…\" or \"Canceled\"\n * without waiting for its stream. A refusal stays in the dialog, in the\n * door's own words.\n */\nexport function useAiJobCancel({\n user,\n orgId,\n onJob,\n}: {\n user: MaybeTokenSource\n orgId: string | null\n onJob?: (job: AiJobSummary) => void\n}): { ask: (job: AiJobSummary) => void; busyJobId: string | null; dialog: ReactNode } {\n const [target, setTarget] = useState<AiJobSummary | null>(null)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const onJobRef = useRef(onJob)\n onJobRef.current = onJob\n\n const ask = useCallback((job: AiJobSummary) => {\n if (aiJobCancelBlockedReason(job)) return\n setError(null)\n setTarget(job)\n }, [])\n const close = useCallback(() => {\n if (busy) return\n setTarget(null)\n setError(null)\n }, [busy])\n const confirm = useCallback(async () => {\n if (!target || !orgId) return\n setBusy(true)\n setError(null)\n const { job, error: refused } = await cancelAiJobRequest(user, orgId, target.id)\n setBusy(false)\n if (job) onJobRef.current?.(job)\n if (refused) {\n setError(refused)\n return\n }\n setTarget(null)\n }, [target, orgId, user])\n\n const dialog = (\n <Dialog open={target !== null} onClose={close} aria-labelledby=\"ai-job-cancel-title\" maxWidth=\"xs\" fullWidth>\n <DialogTitle id=\"ai-job-cancel-title\">{AI_JOB_CANCEL_TITLE}</DialogTitle>\n <DialogContent>\n <DialogContentText>{AI_JOB_CANCEL_CONFIRM_COPY}</DialogContentText>\n {error ? (\n <Alert severity=\"warning\" sx={{ mt: 2 }}>\n {error}\n </Alert>\n ) : null}\n </DialogContent>\n <DialogActions>\n <Button onClick={close} disabled={busy}>\n {'Keep building'}\n </Button>\n <Button color=\"error\" variant=\"contained\" onClick={() => void confirm()} disabled={busy || !orgId}>\n {busy ? 'Canceling…' : 'Cancel job'}\n </Button>\n </DialogActions>\n </Dialog>\n )\n return { ask, busyJobId: busy ? (target?.id ?? null) : null, dialog }\n}\n\n/**\n * The Cancel button: enabled while the job can still be stopped, and\n * disabled with the reason as its tooltip once it is done, failed, canceled\n * or already stopping.\n */\nexport function AiJobCancelButton({\n job,\n user,\n orgId,\n onJob,\n size = 'medium',\n variant = 'outlined',\n}: {\n job: AiJobSummary\n user: MaybeTokenSource\n orgId: string | null\n onJob?: (job: AiJobSummary) => void\n size?: ButtonProps['size']\n variant?: ButtonProps['variant']\n}) {\n const { ask, busyJobId, dialog } = useAiJobCancel({ user, orgId, onJob })\n const blocked = aiJobCancelBlockedReason(job)\n const stopping = Boolean(job.cancelRequested) && !AI_JOB_TERMINAL_STATUSES.includes(job.status)\n const button = (\n <Button\n size={size}\n variant={variant}\n color=\"error\"\n disabled={Boolean(blocked) || busyJobId === job.id || !orgId}\n onClick={() => ask(job)}\n >\n {stopping ? 'Stopping…' : 'Cancel'}\n </Button>\n )\n return (\n <>\n {blocked ? (\n <Tooltip title={blocked}>\n {/* A disabled button fires no pointer events; the span carries the tooltip. */}\n <Box component=\"span\" sx={{ display: 'inline-flex' }}>\n {button}\n </Box>\n </Tooltip>\n ) : (\n button\n )}\n {dialog}\n </>\n )\n}\n"],"names":["Alert","Box","Button","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Tooltip","useCallback","useRef","useState","AI_JOB_CANCEL_CONFIRM_COPY","AI_JOB_CANCEL_TITLE","aiJobCancelBlockedReason","AI_JOB_TERMINAL_STATUSES","cancelAiJobRequest","useAiJobCancel","user","orgId","onJob","target","setTarget","busy","setBusy","error","setError","onJobRef","current","ask","job","close","confirm","refused","id","dialog","open","onClose","aria-labelledby","maxWidth","fullWidth","severity","sx","mt","onClick","disabled","color","variant","busyJobId","AiJobCancelButton","size","blocked","stopping","Boolean","cancelRequested","includes","status","button","title","component","display"],"mappings":"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"}
@@ -16,6 +16,7 @@
16
16
  */
17
17
  import { type AiBuildPlanEmbed } from '../model/ai-build-plan';
18
18
  import type { AiJobReview, AiJobSummary } from '../model/ai-jobs.types';
19
+ import type { AiJobResumeOptions } from './ai-job-requests';
19
20
  import { AiSiteStarterFallback } from './ai-site-starter-fallback.component';
20
21
  /**
21
22
  * A job's plan and what it waits for (AGL-2935), inside the AI jobs drawer
@@ -31,9 +32,7 @@ export interface AiJobPlanProps {
31
32
  * Confirms the plan, or tries the refused step again. A build whose request
32
33
  * asked to publish confirms with `publish` when its box is ticked (AGL-3616).
33
34
  */
34
- onResume: (job: AiJobSummary, options?: {
35
- publish?: boolean;
36
- }) => void;
35
+ onResume: (job: AiJobSummary, options?: AiJobResumeOptions) => void;
37
36
  /** A resume for this job is in flight. */
38
37
  busy?: boolean;
39
38
  /**
@@ -47,6 +46,8 @@ export interface AiJobPlanProps {
47
46
  * that did not work out (AGL-3594); the action is drawn only when given.
48
47
  */
49
48
  user?: Parameters<typeof AiSiteStarterFallback>[0]['user'];
49
+ /** The workspace's path slug, for the Upgrade a Free build past what is left offers (AGL-3722). */
50
+ orgSlug?: string | null;
50
51
  }
51
52
  /**
52
53
  * Where a refused answer broke its rules, and the outline of those parts, as
@@ -62,5 +63,5 @@ export declare function aiJobReviewDetails(review: AiJobReview | null): string[]
62
63
  * host's code loads when a visitor reaches it, whether or not they press play.
63
64
  */
64
65
  export declare function aiPlanEmbedLine(embed: AiBuildPlanEmbed, named: (ref: string) => string): string;
65
- export declare function AiJobPlan({ job, onResume, busy, staff, user, }: AiJobPlanProps): JSX.Element | null;
66
+ export declare function AiJobPlan({ job, onResume, busy, staff, user, orgSlug, }: AiJobPlanProps): JSX.Element | null;
66
67
  export default AiJobPlan;
@@ -1,3 +1,4 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
3
  /**
3
4
  * @license
@@ -15,7 +16,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
15
16
  * See the License for the specific language governing permissions and
16
17
  * limitations under the License.
17
18
  */ import { AI_BUILD_PLAN_CREATION_NOUNS, AI_BUILD_PLAN_EMBED_HOST_NAMES, isAiPlanNewRef } from "../model/ai-build-plan.js";
18
- import { aiJobCreditEstimate } from "../model/ai-build-job.js";
19
+ import { aiBuildFreePageCapNote, aiBuildSmaller, aiJobCreditRange } from "../model/ai-build-job.js";
20
+ import { aiCreditRangeText, aiCreditsPromptFor } from "../model/ai-credit-estimate.js";
21
+ import { aiFreeCreditsResetLabel } from "../model/ai-site-job.js";
22
+ import { AiCreditsPromptNotice } from "./ai-credits-prompt.component.js";
19
23
  import { aiBuildOpNoun } from "../model/ai-build-progress.js";
20
24
  import { aiJobConfirmingOwnPlan } from "../model/ai-job-activity.js";
21
25
  import { aiSiteStarterFallbackOffered } from "../model/ai-job-failure-copy.js";
@@ -67,8 +71,8 @@ import { useState } from "react";
67
71
  const link = embed.url ? ` playing ${embed.url}` : ', its link left for you to paste';
68
72
  return `Embeds a ${host} player on ${where}${link}, as you asked (“${embed.asked}”). It loads ${host}’s own code when a visitor reaches it, whether or not they press play.`;
69
73
  }
70
- export function AiJobPlan({ job, onResume, busy = false, staff = false, user }) {
71
- var _plan_items, _plan_embeds;
74
+ export function AiJobPlan({ job, onResume, busy = false, staff = false, user, orgSlug }) {
75
+ var _ref, _plan_items, _plan_embeds;
72
76
  const [detailsOpen, setDetailsOpen] = useState(false);
73
77
  // Unticked until the person ticks it: a build publishes only when its
74
78
  // request asked AND they confirm it here (AGL-3616).
@@ -84,11 +88,20 @@ export function AiJobPlan({ job, onResume, busy = false, staff = false, user })
84
88
  const waiting = job.status === 'needs_review' && review !== null && !aiJobConfirmingOwnPlan(job);
85
89
  // The guard rail (AGL-2911): what the plan is estimated to cost is read
86
90
  // before it is confirmed, not after it has been spent. An estimate, and
87
- // said to be one — the plan's own passes at the nominal credits a step
88
- // holds, where what a step really costs is its model's tokens. A page job
91
+ // said to be one: what it is likely to cost — what builds like it measured
92
+ // — and its ceiling, every pass at its reserve (AGL-3722). A page job
89
93
  // counts the creations it builds before its page (AGL-3031).
90
- const estimate = plan && waiting && review.reason === 'plan' ? aiJobCreditEstimate(job.kind, plan) : 0;
94
+ const range = plan && waiting && review.reason === 'plan' ? aiJobCreditRange(job.kind, plan) : null;
95
+ // A Free build past what is left asks first (AGL-3722): build what fits,
96
+ // the home page first, or upgrade — never a refusal with no way on.
97
+ const freeCredits = (_ref = review == null ? void 0 : review.freeCredits) != null ? _ref : null;
98
+ const prompt = plan && range && job.kind === 'build' ? aiCreditsPromptFor(range, freeCredits, aiBuildSmaller(plan)) : null;
99
+ // A Free build at its page cap says so (AGL-3722): pages past it were left for a later request.
100
+ const pageCapNote = plan && waiting && review.reason === 'plan' && job.kind === 'build' ? aiBuildFreePageCapNote(plan, Boolean(freeCredits)) : null;
91
101
  const offersPublish = job.kind === 'build' && job.publishAsked === true && waiting && review.reason === 'plan';
102
+ const publishing = offersPublish && publish ? {
103
+ publish: true
104
+ } : {};
92
105
  return /*#__PURE__*/ _jsxs(Box, {
93
106
  sx: {
94
107
  mt: 1
@@ -206,14 +219,26 @@ export function AiJobPlan({ job, onResume, busy = false, staff = false, user })
206
219
  })
207
220
  ]
208
221
  }),
209
- estimate > 0 && /*#__PURE__*/ _jsx(Typography, {
222
+ range && range.ceiling > 0 && /*#__PURE__*/ _jsxs(Typography, {
210
223
  variant: "caption",
211
224
  color: "text.secondary",
212
225
  sx: {
213
226
  display: 'block',
214
227
  mt: 1
215
228
  },
216
- children: `Estimated cost: about ${estimate.toLocaleString('en-US')} credits. What it costs is what its steps spend.`
229
+ children: [
230
+ `Estimated cost: ${aiCreditRangeText(range)}. What it costs is what its steps spend.`,
231
+ freeCredits && !prompt ? ` You have ${freeCredits.left.toLocaleString('en-US')} of your free AI credits left this month, until ${aiFreeCreditsResetLabel(freeCredits.resetsOn)}.` : ''
232
+ ]
233
+ }),
234
+ pageCapNote && /*#__PURE__*/ _jsx(Typography, {
235
+ variant: "caption",
236
+ color: "text.secondary",
237
+ sx: {
238
+ display: 'block',
239
+ mt: 0.5
240
+ },
241
+ children: pageCapNote
217
242
  }),
218
243
  waiting && review.retryRefusal && review.reason !== 'plan' && /*#__PURE__*/ _jsx(Typography, {
219
244
  variant: "caption",
@@ -239,15 +264,26 @@ export function AiJobPlan({ job, onResume, busy = false, staff = false, user })
239
264
  children: 'Publish the new pages when they are built'
240
265
  })
241
266
  }),
242
- waiting && /*#__PURE__*/ _jsx(Button, {
267
+ waiting && prompt && /*#__PURE__*/ _jsx(AiCreditsPromptNotice, {
268
+ prompt: prompt,
269
+ noun: "build",
270
+ orgSlug: orgSlug,
271
+ busy: busy,
272
+ onBuildWhatFits: ()=>onResume(job, _extends({}, publishing, {
273
+ creditsConfirmed: true
274
+ })),
275
+ onSmaller: ()=>onResume(job, _extends({}, publishing, {
276
+ creditsConfirmed: true,
277
+ reduce: 'first-page'
278
+ }))
279
+ }),
280
+ waiting && !prompt && /*#__PURE__*/ _jsx(Button, {
243
281
  size: "small",
244
282
  variant: "contained",
245
283
  // Disabled with its reason above when the Free allowance left
246
284
  // cannot pay for another try (AGL-3594).
247
285
  disabled: busy || review.reason !== 'plan' && Boolean(review.retryRefusal),
248
- onClick: ()=>onResume(job, offersPublish && publish ? {
249
- publish: true
250
- } : undefined),
286
+ onClick: ()=>onResume(job, offersPublish && publish ? publishing : undefined),
251
287
  sx: {
252
288
  mt: 1
253
289
  },