@aglyn/plugins-ai 1.0.0-beta.237 → 1.0.0-beta.239

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 (172) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-credits-prompt.component.d.ts +11 -4
  3. package/src/lib/components/ai-credits-prompt.component.js +84 -34
  4. package/src/lib/components/ai-credits-prompt.component.js.map +1 -1
  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-links.js +8 -0
  9. package/src/lib/components/ai-job-links.js.map +1 -1
  10. package/src/lib/components/ai-job-plan.component.js +12 -1
  11. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  12. package/src/lib/components/ai-jobs-list-page.component.js +141 -72
  13. package/src/lib/components/ai-jobs-list-page.component.js.map +1 -1
  14. package/src/lib/components/ai-jobs-store.d.ts +12 -0
  15. package/src/lib/components/ai-jobs-store.js +15 -0
  16. package/src/lib/components/ai-jobs-store.js.map +1 -1
  17. package/src/lib/components/ai-site-build-page.component.js +22 -2
  18. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  19. package/src/lib/components/ai-usage-meter-refresh.d.ts +35 -0
  20. package/src/lib/components/ai-usage-meter-refresh.js +63 -0
  21. package/src/lib/components/ai-usage-meter-refresh.js.map +1 -0
  22. package/src/lib/components/ai-usage-strip.component.d.ts +19 -2
  23. package/src/lib/components/ai-usage-strip.component.js +40 -9
  24. package/src/lib/components/ai-usage-strip.component.js.map +1 -1
  25. package/src/lib/components/assist-jobs-drawer.component.js +12 -16
  26. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  27. package/src/lib/components/assist-panel.component.js +91 -51
  28. package/src/lib/components/assist-panel.component.js.map +1 -1
  29. package/src/lib/components/use-ai-usage-meter.d.ts +2 -0
  30. package/src/lib/components/use-ai-usage-meter.js +8 -3
  31. package/src/lib/components/use-ai-usage-meter.js.map +1 -1
  32. package/src/lib/jobs/ai-build-unit-outcome.d.ts +3 -1
  33. package/src/lib/jobs/ai-build-unit-outcome.js +19 -3
  34. package/src/lib/jobs/ai-build-unit-outcome.js.map +1 -1
  35. package/src/lib/jobs/ai-job-build-step.js +33 -3
  36. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-cancel.d.ts +71 -0
  38. package/src/lib/jobs/ai-job-cancel.js +102 -0
  39. package/src/lib/jobs/ai-job-cancel.js.map +1 -0
  40. package/src/lib/jobs/ai-job-component-step.js +4 -1
  41. package/src/lib/jobs/ai-job-component-step.js.map +1 -1
  42. package/src/lib/jobs/ai-job-draft-ids.js +7 -2
  43. package/src/lib/jobs/ai-job-draft-ids.js.map +1 -1
  44. package/src/lib/jobs/ai-job-drafts.js +2 -0
  45. package/src/lib/jobs/ai-job-drafts.js.map +1 -1
  46. package/src/lib/jobs/ai-job-form-dataset.d.ts +100 -0
  47. package/src/lib/jobs/ai-job-form-dataset.js +161 -0
  48. package/src/lib/jobs/ai-job-form-dataset.js.map +1 -0
  49. package/src/lib/jobs/ai-job-form-step.d.ts +15 -3
  50. package/src/lib/jobs/ai-job-form-step.js +41 -10
  51. package/src/lib/jobs/ai-job-form-step.js.map +1 -1
  52. package/src/lib/jobs/ai-job-layout-language.d.ts +2 -0
  53. package/src/lib/jobs/ai-job-layout-language.js +13 -1
  54. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  55. package/src/lib/jobs/ai-job-layout-step.js +6 -1
  56. package/src/lib/jobs/ai-job-layout-step.js.map +1 -1
  57. package/src/lib/jobs/ai-job-page-language.d.ts +9 -0
  58. package/src/lib/jobs/ai-job-page-language.js +45 -2
  59. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  60. package/src/lib/jobs/ai-job-page-step.js +12 -1
  61. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  62. package/src/lib/jobs/ai-job-plan-step.d.ts +38 -1
  63. package/src/lib/jobs/ai-job-plan-step.js +90 -13
  64. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  65. package/src/lib/jobs/ai-job-site-content.d.ts +15 -0
  66. package/src/lib/jobs/ai-job-site-content.js +66 -7
  67. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  68. package/src/lib/jobs/ai-job-site-datasets.d.ts +170 -0
  69. package/src/lib/jobs/ai-job-site-datasets.js +499 -0
  70. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -0
  71. package/src/lib/jobs/ai-job-site-step.d.ts +15 -8
  72. package/src/lib/jobs/ai-job-site-step.js +152 -30
  73. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  74. package/src/lib/jobs/ai-job-template-step.js +4 -1
  75. package/src/lib/jobs/ai-job-template-step.js.map +1 -1
  76. package/src/lib/jobs/ai-jobs.d.ts +37 -12
  77. package/src/lib/jobs/ai-jobs.js +184 -28
  78. package/src/lib/jobs/ai-jobs.js.map +1 -1
  79. package/src/lib/jobs/ai-layout-stock-photos.d.ts +143 -70
  80. package/src/lib/jobs/ai-layout-stock-photos.js +649 -176
  81. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  82. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.d.ts +30 -0
  83. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js +220 -0
  84. package/src/lib/jobs/fixtures/ai-ceramics-portfolio-site-plan.js.map +1 -0
  85. package/src/lib/jobs/fixtures/ai-dataset-site-plan-recordings.d.ts +30 -0
  86. package/src/lib/jobs/fixtures/ai-dataset-site-plan-recordings.js +637 -0
  87. package/src/lib/jobs/fixtures/ai-dataset-site-plan-recordings.js.map +1 -0
  88. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.d.ts +27 -0
  89. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js +151 -0
  90. package/src/lib/jobs/fixtures/ai-stock-photo-pixabay-recordings.js.map +1 -0
  91. package/src/lib/layout-language/ai-layout-compiler.d.ts +16 -0
  92. package/src/lib/layout-language/ai-layout-compiler.js +397 -9
  93. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  94. package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
  95. package/src/lib/layout-language/ai-layout-design.js +5 -3
  96. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  97. package/src/lib/layout-language/ai-layout-frame.d.ts +12 -0
  98. package/src/lib/layout-language/ai-layout-frame.js +58 -3
  99. package/src/lib/layout-language/ai-layout-frame.js.map +1 -1
  100. package/src/lib/layout-language/ai-layout-gaps.js +17 -1
  101. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  102. package/src/lib/layout-language/ai-layout-listings.d.ts +44 -3
  103. package/src/lib/layout-language/ai-layout-listings.js +118 -50
  104. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  105. package/src/lib/layout-language/ai-layout-pictures.d.ts +8 -0
  106. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  107. package/src/lib/layout-language/ai-layout-storefront.d.ts +82 -0
  108. package/src/lib/layout-language/ai-layout-storefront.js +223 -0
  109. package/src/lib/layout-language/ai-layout-storefront.js.map +1 -0
  110. package/src/lib/model/ai-build-job.d.ts +11 -1
  111. package/src/lib/model/ai-build-job.js +35 -6
  112. package/src/lib/model/ai-build-job.js.map +1 -1
  113. package/src/lib/model/ai-build-plan.d.ts +23 -3
  114. package/src/lib/model/ai-build-plan.js +47 -6
  115. package/src/lib/model/ai-build-plan.js.map +1 -1
  116. package/src/lib/model/ai-build-progress.d.ts +2 -2
  117. package/src/lib/model/ai-build-progress.js +6 -4
  118. package/src/lib/model/ai-build-progress.js.map +1 -1
  119. package/src/lib/model/ai-credit-estimate.d.ts +69 -2
  120. package/src/lib/model/ai-credit-estimate.js +91 -3
  121. package/src/lib/model/ai-credit-estimate.js.map +1 -1
  122. package/src/lib/model/ai-job-cancel-copy.d.ts +44 -0
  123. package/src/lib/model/ai-job-cancel-copy.js +53 -0
  124. package/src/lib/model/ai-job-cancel-copy.js.map +1 -0
  125. package/src/lib/model/ai-job-failure-copy.d.ts +4 -1
  126. package/src/lib/model/ai-job-failure-copy.js +8 -3
  127. package/src/lib/model/ai-job-failure-copy.js.map +1 -1
  128. package/src/lib/model/ai-jobs.types.d.ts +20 -0
  129. package/src/lib/model/ai-jobs.types.js.map +1 -1
  130. package/src/lib/model/ai-plan-capabilities.d.ts +7 -0
  131. package/src/lib/model/ai-plan-capabilities.js.map +1 -1
  132. package/src/lib/model/ai-site-build-progress.js +7 -4
  133. package/src/lib/model/ai-site-build-progress.js.map +1 -1
  134. package/src/lib/model/ai-site-job.d.ts +38 -4
  135. package/src/lib/model/ai-site-job.js +50 -14
  136. package/src/lib/model/ai-site-job.js.map +1 -1
  137. package/src/lib/model/ai-site-kinds.d.ts +4 -1
  138. package/src/lib/model/ai-site-kinds.js +34 -6
  139. package/src/lib/model/ai-site-kinds.js.map +1 -1
  140. package/src/lib/runtime/ai-dataset-generation.d.ts +77 -0
  141. package/src/lib/runtime/ai-dataset-generation.js +127 -0
  142. package/src/lib/runtime/ai-dataset-generation.js.map +1 -0
  143. package/src/lib/runtime/ai-doctrine-validators.d.ts +77 -1
  144. package/src/lib/runtime/ai-doctrine-validators.js +280 -6
  145. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  146. package/src/lib/runtime/ai-doctrine.js +10 -4
  147. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  148. package/src/lib/runtime/ai-node-tree.d.ts +2 -0
  149. package/src/lib/runtime/ai-node-tree.js +16 -0
  150. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  151. package/src/lib/runtime/ai-palette.generated.js +53 -7
  152. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  153. package/src/lib/server/ai-jobs-cancel.d.ts +13 -5
  154. package/src/lib/server/ai-jobs-cancel.js +27 -9
  155. package/src/lib/server/ai-jobs-cancel.js.map +1 -1
  156. package/src/lib/server/ai-jobs-gate.d.ts +12 -2
  157. package/src/lib/server/ai-jobs-gate.js +20 -9
  158. package/src/lib/server/ai-jobs-gate.js.map +1 -1
  159. package/src/lib/tools/ai-dataset-tool.d.ts +68 -0
  160. package/src/lib/tools/ai-dataset-tool.js +231 -0
  161. package/src/lib/tools/ai-dataset-tool.js.map +1 -0
  162. package/src/lib/usage/ai-usage-meter.d.ts +2 -1
  163. package/src/lib/usage/ai-usage-meter.js +17 -1
  164. package/src/lib/usage/ai-usage-meter.js.map +1 -1
  165. package/src/lib/usage/ai-usage-wire.d.ts +6 -0
  166. package/src/lib/usage/ai-usage-wire.js.map +1 -1
  167. package/src/lib/usage/assist-usage.d.ts +7 -0
  168. package/src/lib/usage/assist-usage.js +14 -4
  169. package/src/lib/usage/assist-usage.js.map +1 -1
  170. package/src/lib/usage/free-ai-credits-left.d.ts +7 -0
  171. package/src/lib/usage/free-ai-credits-left.js +30 -4
  172. 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.237",
3
+ "version": "1.0.0-beta.239",
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.237",
29
- "@aglyn/shared-data-enums": "1.0.0-beta.237",
30
- "@aglyn/shared-data-mdi": "1.0.0-beta.237",
31
- "@aglyn/shared-data-types": "1.0.0-beta.237",
32
- "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.237",
33
- "@aglyn/shared-ui-jsx": "1.0.0-beta.237",
34
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.237",
35
- "@aglyn/shared-ui-theme": "1.0.0-beta.237",
36
- "@aglyn/shared-util-email": "1.0.0-beta.237",
37
- "@aglyn/shared-util-errors": "1.0.0-beta.237",
38
- "@aglyn/shared-util-http": "1.0.0-beta.237",
39
- "@aglyn/shared-util-timestamp": "1.0.0-beta.237",
40
- "@aglyn/shared-util-tools": "1.0.0-beta.237",
41
- "@aglyn/tenant-data-admin": "1.0.0-beta.237",
42
- "@aglyn/tenant-feature-instance": "1.0.0-beta.237",
43
- "@aglyn/tenant-runtime": "1.0.0-beta.237",
28
+ "@aglyn/aglyn": "1.0.0-beta.239",
29
+ "@aglyn/shared-data-enums": "1.0.0-beta.239",
30
+ "@aglyn/shared-data-mdi": "1.0.0-beta.239",
31
+ "@aglyn/shared-data-types": "1.0.0-beta.239",
32
+ "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.239",
33
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.239",
34
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.239",
35
+ "@aglyn/shared-ui-theme": "1.0.0-beta.239",
36
+ "@aglyn/shared-util-email": "1.0.0-beta.239",
37
+ "@aglyn/shared-util-errors": "1.0.0-beta.239",
38
+ "@aglyn/shared-util-http": "1.0.0-beta.239",
39
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.239",
40
+ "@aglyn/shared-util-tools": "1.0.0-beta.239",
41
+ "@aglyn/tenant-data-admin": "1.0.0-beta.239",
42
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.239",
43
+ "@aglyn/tenant-runtime": "1.0.0-beta.239",
44
44
  "@swc/helpers": "0.5.23"
45
45
  },
46
46
  "peerDependencies": {
@@ -17,10 +17,17 @@
17
17
  import { type AiCreditsPrompt } from '../model/ai-credit-estimate';
18
18
  /**
19
19
  * What a Free job past what is left asks before it starts (AGL-3722), on the
20
- * Assist build plan card and in the guided start alike: the plain sentence —
21
- * about what it costs, what is left, that it builds what it can and pauses —
22
- * and its three ways on. Only "Build what fits" (or the smaller first build)
23
- * starts anything; nothing is started by showing this.
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.
24
31
  */
25
32
  export interface AiCreditsPromptProps {
26
33
  prompt: AiCreditsPrompt;
@@ -14,50 +14,100 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
17
18
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
- import { Alert, Button, Stack } from "@mui/material";
19
- import { AI_CREDITS_BUILD_WHAT_FITS, aiCreditsPromptText, aiCreditsSmallerText } from "../model/ai-credit-estimate.js";
19
+ import { Box, Button, Stack, Typography } from "@mui/material";
20
+ import { useId } from "react";
21
+ import { aiCreditsChoices, aiCreditsPromptText } from "../model/ai-credit-estimate.js";
20
22
  import { aiCreditsBillingHref } from "./ai-job-links.js";
21
- export function AiCreditsPromptNotice({ prompt, noun, orgSlug, busy = false, onBuildWhatFits, onSmaller }) {
22
- return /*#__PURE__*/ _jsxs(Alert, {
23
- severity: "warning",
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
+ }, {
24
39
  sx: {
25
- mt: 1
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
26
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, {
27
80
  role: "alert",
28
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
+ },
29
90
  children: [
30
- aiCreditsPromptText(prompt, noun),
31
- /*#__PURE__*/ _jsxs(Stack, {
32
- direction: "row",
91
+ /*#__PURE__*/ _jsx(Typography, {
92
+ variant: "body2",
93
+ sx: {
94
+ color: 'text.primary'
95
+ },
96
+ children: aiCreditsPromptText(prompt, noun)
97
+ }),
98
+ /*#__PURE__*/ _jsx(Stack, {
33
99
  spacing: 1,
34
- useFlexGap: true,
35
100
  sx: {
36
- mt: 1,
37
- flexWrap: 'wrap'
101
+ mt: 1.5
38
102
  },
39
- children: [
40
- /*#__PURE__*/ _jsx(Button, {
41
- size: "small",
42
- variant: "contained",
43
- disabled: busy,
44
- onClick: onBuildWhatFits,
45
- children: AI_CREDITS_BUILD_WHAT_FITS
46
- }),
47
- prompt.smaller && onSmaller ? /*#__PURE__*/ _jsx(Button, {
48
- size: "small",
49
- variant: "outlined",
50
- disabled: busy,
51
- onClick: onSmaller,
52
- children: aiCreditsSmallerText(prompt.smaller)
53
- }) : null,
54
- orgSlug ? /*#__PURE__*/ _jsx(Button, {
55
- size: "small",
56
- color: "inherit",
57
- href: aiCreditsBillingHref(orgSlug),
58
- children: 'Upgrade'
59
- }) : null
60
- ]
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))
61
111
  })
62
112
  ]
63
113
  });
@@ -1 +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 { Alert, Button, Stack } from '@mui/material'\nimport {\n AI_CREDITS_BUILD_WHAT_FITS,\n aiCreditsPromptText,\n aiCreditsSmallerText,\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 and in the guided start alike: the plain sentence —\n * about what it costs, what is left, that it builds what it can and pauses —\n * and its three ways on. Only \"Build what fits\" (or the smaller first build)\n * starts anything; nothing is started by showing this.\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\nexport function AiCreditsPromptNotice({\n prompt,\n noun,\n orgSlug,\n busy = false,\n onBuildWhatFits,\n onSmaller,\n}: AiCreditsPromptProps): JSX.Element {\n return (\n <Alert severity=\"warning\" sx={{ mt: 1 }} role=\"alert\" aria-label=\"More than your credits left\">\n {aiCreditsPromptText(prompt, noun)}\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ mt: 1, flexWrap: 'wrap' }}>\n <Button size=\"small\" variant=\"contained\" disabled={busy} onClick={onBuildWhatFits}>\n {AI_CREDITS_BUILD_WHAT_FITS}\n </Button>\n {prompt.smaller && onSmaller ? (\n <Button size=\"small\" variant=\"outlined\" disabled={busy} onClick={onSmaller}>\n {aiCreditsSmallerText(prompt.smaller)}\n </Button>\n ) : null}\n {orgSlug ? (\n <Button size=\"small\" color=\"inherit\" href={aiCreditsBillingHref(orgSlug)}>\n {'Upgrade'}\n </Button>\n ) : null}\n </Stack>\n </Alert>\n )\n}\n\nexport default AiCreditsPromptNotice\n"],"names":["Alert","Button","Stack","AI_CREDITS_BUILD_WHAT_FITS","aiCreditsPromptText","aiCreditsSmallerText","aiCreditsBillingHref","AiCreditsPromptNotice","prompt","noun","orgSlug","busy","onBuildWhatFits","onSmaller","severity","sx","mt","role","aria-label","direction","spacing","useFlexGap","flexWrap","size","variant","disabled","onClick","smaller","color","href"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;AAEA,SAASA,KAAK,EAAEC,MAAM,EAAEC,KAAK,QAAQ,gBAAe;AACpD,SACEC,0BAA0B,EAC1BC,mBAAmB,EACnBC,oBAAoB,QAEf,iCAA6B;AACpC,SAASC,oBAAoB,QAAQ,oBAAgB;AAqBrD,OAAO,SAASC,sBAAsB,EACpCC,MAAM,EACNC,IAAI,EACJC,OAAO,EACPC,OAAO,KAAK,EACZC,eAAe,EACfC,SAAS,EACY;IACrB,qBACE,MAACb;QAAMc,UAAS;QAAUC,IAAI;YAAEC,IAAI;QAAE;QAAGC,MAAK;QAAQC,cAAW;;YAC9Dd,oBAAoBI,QAAQC;0BAC7B,MAACP;gBAAMiB,WAAU;gBAAMC,SAAS;gBAAGC,UAAU;gBAACN,IAAI;oBAAEC,IAAI;oBAAGM,UAAU;gBAAO;;kCAC1E,KAACrB;wBAAOsB,MAAK;wBAAQC,SAAQ;wBAAYC,UAAUd;wBAAMe,SAASd;kCAC/DT;;oBAEFK,OAAOmB,OAAO,IAAId,0BACjB,KAACZ;wBAAOsB,MAAK;wBAAQC,SAAQ;wBAAWC,UAAUd;wBAAMe,SAASb;kCAC9DR,qBAAqBG,OAAOmB,OAAO;yBAEpC;oBACHjB,wBACC,KAACT;wBAAOsB,MAAK;wBAAQK,OAAM;wBAAUC,MAAMvB,qBAAqBI;kCAC7D;yBAED;;;;;AAIZ;AAEA,eAAeH,sBAAqB"}
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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"}
@@ -72,6 +72,14 @@ import { AI_JOB_BESIGNER_SEGMENT as BESIGNER_SEGMENT, AI_SITE_BUILD_HREF } from
72
72
  host
73
73
  });
74
74
  }
75
+ if (output.resource === 'draft' && output.draftResource) {
76
+ // A draft another plugin wrote — a dataset a site start or a build made
77
+ // (AGL-3616) — opens where its owner lists it, or nowhere it is not loaded.
78
+ return pluginRecordListHref(output.draftResource, {
79
+ orgSlug,
80
+ host
81
+ });
82
+ }
75
83
  if (output.resource === 'form') {
76
84
  // A new form has no version for the besigner to open: its own page — the
77
85
  // forms plugin's address for it — mints the first one, and holds the
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-links.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport {\n pluginRecordHref,\n pluginRecordListHref,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { AI_JOB_BESIGNER_SEGMENT as BESIGNER_SEGMENT, AI_SITE_BUILD_HREF } from '../model/ai-job-notice'\nimport type { AiJobOutput, AiJobSummary } from '../model/ai-jobs.types'\n\n// Where a job's work opens in the console (AGL-2904), shared by the AI jobs\n// drawer and every dialog that follows the job it started (AGL-3593).\n\n/**\n * Where \"open draft\" goes, or `null` when the output has no page of its\n * own — a `text` output carries its copy on the job and is shown inline.\n * A versioned resource opens in the besigner on the version the job wrote;\n * one the console lists without a detail page opens its list; a `theme`\n * proposal opens the site's Theme section, where it is put in the editor.\n *\n * A console URL names a site by its SUBDOMAIN: the `[host]` segment resolves\n * through the member's host projection by subdomain, never by document id.\n * So an output that carries no `hostSubdomain` gets no link rather than one\n * that opens no site.\n */\nexport function aiJobOutputHref(output: AiJobOutput, orgSlug: string): string | null {\n const host = output.hostSubdomain\n if (!orgSlug || !host) return null\n const segment = BESIGNER_SEGMENT[output.resource]\n if (segment) {\n const base = `/${orgSlug}/hosts/${host}/${segment}/${output.id}`\n return output.versionId ? `${base}/versions/${output.versionId}/besigner` : base\n }\n if (output.resource === 'product') {\n // The catalog a drafted product lands in: the commerce plugin's address\n // for it (AGL-3080), or no link where commerce is not loaded.\n return pluginRecordListHref('product', { orgSlug, host })\n }\n if (output.resource === 'workflow') {\n // A drafted automation is an action (AGL-2919), listed switched off on\n // the workflows plugin's list of actions, or no link where it is not loaded.\n return pluginRecordListHref('action', { orgSlug, host })\n }\n if (output.resource === 'entry') {\n // A post a guided start wrote (AGL-3676) opens in its blog's list in Content.\n const collectionSlug = output.proposal?.['collectionSlug']\n return typeof collectionSlug === 'string' && collectionSlug\n ? buildRoute(Route.HOST_CONTENT_COLLECTION, { orgSlug, host, collectionSlug })\n : buildRoute(Route.HOST_CONTENT, { orgSlug, host })\n }\n if (output.resource === 'theme') {\n return buildRoute(Route.HOST_SETUP_THEME, { orgSlug, host })\n }\n if (output.resource === 'form') {\n // A new form has no version for the besigner to open: its own page — the\n // forms plugin's address for it — mints the first one, and holds the\n // routing and consent it declares.\n return pluginRecordHref('form', { orgSlug, host }, output.id)\n }\n if (output.resource === 'campaign') {\n // A campaign's page is the marketing plugin's, under the site: its\n // address for the campaign (AGL-3080), or no link where it is not loaded.\n return pluginRecordHref('campaign', { orgSlug, host }, output.id)\n }\n return null\n}\n\n/**\n * The ONE action a finished job's row, its notification and the dialog that\n * started it lead with (AGL-3593): what the job built. Several pages from one\n * job — a site's — open the site's Pages list, where they wait as drafts; one\n * output with a page of its own opens that page; anything else has no single\n * place to go, and the row's own links stand. `null` until the job is done.\n */\nexport function aiJobPrimaryLink(\n job: Pick<AiJobSummary, 'status' | 'outputs'>,\n orgSlug: string,\n): { href: string; label: string } | null {\n if (job.status !== 'done' || !orgSlug) return null\n const screens = job.outputs.filter(\n (output) => output.resource === 'screen' && output.hostSubdomain,\n )\n if (screens.length > 1) {\n return {\n href: buildRoute(Route.HOST_SCREENS, { orgSlug, host: String(screens[0].hostSubdomain) }),\n label: 'Open your draft pages',\n }\n }\n for (const output of job.outputs) {\n const href = aiJobOutputHref(output, orgSlug)\n if (href) {\n return {\n href,\n label: output.resource === 'screen' ? 'Open the draft page' : `Open ${output.label}`,\n }\n }\n }\n return null\n}\n\nexport { AI_SITE_BUILD_HREF }\n\n/**\n * The \"Building your site\" page for one site job (AGL-3594): linkable, so the\n * guided start lands on it, a reload keeps it and a notification opens it.\n * `null` without the org slug or the site's subdomain a console URL needs.\n */\nexport function aiSiteBuildHref(\n orgSlug: string | null | undefined,\n hostSubdomain: string | null | undefined,\n jobId: string,\n): string | null {\n if (!orgSlug || !hostSubdomain || !jobId) return null\n return `/${orgSlug}/hosts/${hostSubdomain}${AI_SITE_BUILD_HREF}/${encodeURIComponent(jobId)}`\n}\n\n/**\n * A finished site's two ways in (AGL-3594): its first page's draft, opened in\n * the console's preview of that version, and the Pages list every draft waits\n * in. Either is `null` where the job reported nothing to open.\n */\nexport function aiSiteBuildDoneLinks(\n job: Pick<AiJobSummary, 'outputs'>,\n orgSlug: string,\n): { view: string | null; pages: string | null } {\n const screens = job.outputs.filter((output) => output.resource === 'screen' && output.hostSubdomain)\n const first = screens[0]\n if (!first || !orgSlug) return { view: null, pages: null }\n const host = String(first.hostSubdomain)\n return {\n view: first.versionId\n ? `/${orgSlug}/hosts/${host}/screens/${first.id}/versions/${first.versionId}/view`\n : null,\n pages: buildRoute(Route.HOST_SCREENS, { orgSlug, host }),\n }\n}\n\n/**\n * Where a person gets more AI credits (AGL-3660): the workspace's Billing\n * page at its plans, where an upgrade and the AI add-on are both sold — the\n * door the AI credits meter's own upgrade link opens.\n */\nexport function aiCreditsBillingHref(orgSlug: string): string {\n return `${buildRoute(Route.MANAGE_BILLING, { orgSlug })}#plans`\n}\n"],"names":["buildRoute","Route","pluginRecordHref","pluginRecordListHref","AI_JOB_BESIGNER_SEGMENT","BESIGNER_SEGMENT","AI_SITE_BUILD_HREF","aiJobOutputHref","output","orgSlug","host","hostSubdomain","segment","resource","base","id","versionId","collectionSlug","proposal","HOST_CONTENT_COLLECTION","HOST_CONTENT","HOST_SETUP_THEME","aiJobPrimaryLink","job","status","screens","outputs","filter","length","href","HOST_SCREENS","String","label","aiSiteBuildHref","jobId","encodeURIComponent","aiSiteBuildDoneLinks","first","view","pages","aiCreditsBillingHref","MANAGE_BILLING"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD,SAASA,UAAU,EAAEC,KAAK,QAAQ,wCAAuC;AACzE,SACEC,gBAAgB,EAChBC,oBAAoB,QACf,mDAAkD;AACzD,SAASC,2BAA2BC,gBAAgB,EAAEC,kBAAkB,QAAQ,4BAAwB;AAGxG,4EAA4E;AAC5E,sEAAsE;AAEtE;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,gBAAgBC,MAAmB,EAAEC,OAAe;IAClE,MAAMC,OAAOF,OAAOG,aAAa;IACjC,IAAI,CAACF,WAAW,CAACC,MAAM,OAAO;IAC9B,MAAME,UAAUP,gBAAgB,CAACG,OAAOK,QAAQ,CAAC;IACjD,IAAID,SAAS;QACX,MAAME,OAAO,CAAC,CAAC,EAAEL,QAAQ,OAAO,EAAEC,KAAK,CAAC,EAAEE,QAAQ,CAAC,EAAEJ,OAAOO,EAAE,EAAE;QAChE,OAAOP,OAAOQ,SAAS,GAAG,GAAGF,KAAK,UAAU,EAAEN,OAAOQ,SAAS,CAAC,SAAS,CAAC,GAAGF;IAC9E;IACA,IAAIN,OAAOK,QAAQ,KAAK,WAAW;QACjC,wEAAwE;QACxE,8DAA8D;QAC9D,OAAOV,qBAAqB,WAAW;YAAEM;YAASC;QAAK;IACzD;IACA,IAAIF,OAAOK,QAAQ,KAAK,YAAY;QAClC,uEAAuE;QACvE,6EAA6E;QAC7E,OAAOV,qBAAqB,UAAU;YAAEM;YAASC;QAAK;IACxD;IACA,IAAIF,OAAOK,QAAQ,KAAK,SAAS;YAERL;QADvB,8EAA8E;QAC9E,MAAMS,kBAAiBT,mBAAAA,OAAOU,QAAQ,qBAAfV,gBAAiB,CAAC,iBAAiB;QAC1D,OAAO,OAAOS,mBAAmB,YAAYA,iBACzCjB,WAAWC,MAAMkB,uBAAuB,EAAE;YAAEV;YAASC;YAAMO;QAAe,KAC1EjB,WAAWC,MAAMmB,YAAY,EAAE;YAAEX;YAASC;QAAK;IACrD;IACA,IAAIF,OAAOK,QAAQ,KAAK,SAAS;QAC/B,OAAOb,WAAWC,MAAMoB,gBAAgB,EAAE;YAAEZ;YAASC;QAAK;IAC5D;IACA,IAAIF,OAAOK,QAAQ,KAAK,QAAQ;QAC9B,yEAAyE;QACzE,qEAAqE;QACrE,mCAAmC;QACnC,OAAOX,iBAAiB,QAAQ;YAAEO;YAASC;QAAK,GAAGF,OAAOO,EAAE;IAC9D;IACA,IAAIP,OAAOK,QAAQ,KAAK,YAAY;QAClC,mEAAmE;QACnE,0EAA0E;QAC1E,OAAOX,iBAAiB,YAAY;YAAEO;YAASC;QAAK,GAAGF,OAAOO,EAAE;IAClE;IACA,OAAO;AACT;AAEA;;;;;;CAMC,GACD,OAAO,SAASO,iBACdC,GAA6C,EAC7Cd,OAAe;IAEf,IAAIc,IAAIC,MAAM,KAAK,UAAU,CAACf,SAAS,OAAO;IAC9C,MAAMgB,UAAUF,IAAIG,OAAO,CAACC,MAAM,CAChC,CAACnB,SAAWA,OAAOK,QAAQ,KAAK,YAAYL,OAAOG,aAAa;IAElE,IAAIc,QAAQG,MAAM,GAAG,GAAG;QACtB,OAAO;YACLC,MAAM7B,WAAWC,MAAM6B,YAAY,EAAE;gBAAErB;gBAASC,MAAMqB,OAAON,OAAO,CAAC,EAAE,CAACd,aAAa;YAAE;YACvFqB,OAAO;QACT;IACF;IACA,KAAK,MAAMxB,UAAUe,IAAIG,OAAO,CAAE;QAChC,MAAMG,OAAOtB,gBAAgBC,QAAQC;QACrC,IAAIoB,MAAM;YACR,OAAO;gBACLA;gBACAG,OAAOxB,OAAOK,QAAQ,KAAK,WAAW,wBAAwB,CAAC,KAAK,EAAEL,OAAOwB,KAAK,EAAE;YACtF;QACF;IACF;IACA,OAAO;AACT;AAEA,SAAS1B,kBAAkB,GAAE;AAE7B;;;;CAIC,GACD,OAAO,SAAS2B,gBACdxB,OAAkC,EAClCE,aAAwC,EACxCuB,KAAa;IAEb,IAAI,CAACzB,WAAW,CAACE,iBAAiB,CAACuB,OAAO,OAAO;IACjD,OAAO,CAAC,CAAC,EAAEzB,QAAQ,OAAO,EAAEE,gBAAgBL,mBAAmB,CAAC,EAAE6B,mBAAmBD,QAAQ;AAC/F;AAEA;;;;CAIC,GACD,OAAO,SAASE,qBACdb,GAAkC,EAClCd,OAAe;IAEf,MAAMgB,UAAUF,IAAIG,OAAO,CAACC,MAAM,CAAC,CAACnB,SAAWA,OAAOK,QAAQ,KAAK,YAAYL,OAAOG,aAAa;IACnG,MAAM0B,QAAQZ,OAAO,CAAC,EAAE;IACxB,IAAI,CAACY,SAAS,CAAC5B,SAAS,OAAO;QAAE6B,MAAM;QAAMC,OAAO;IAAK;IACzD,MAAM7B,OAAOqB,OAAOM,MAAM1B,aAAa;IACvC,OAAO;QACL2B,MAAMD,MAAMrB,SAAS,GACjB,CAAC,CAAC,EAAEP,QAAQ,OAAO,EAAEC,KAAK,SAAS,EAAE2B,MAAMtB,EAAE,CAAC,UAAU,EAAEsB,MAAMrB,SAAS,CAAC,KAAK,CAAC,GAChF;QACJuB,OAAOvC,WAAWC,MAAM6B,YAAY,EAAE;YAAErB;YAASC;QAAK;IACxD;AACF;AAEA;;;;CAIC,GACD,OAAO,SAAS8B,qBAAqB/B,OAAe;IAClD,OAAO,GAAGT,WAAWC,MAAMwC,cAAc,EAAE;QAAEhC;IAAQ,GAAG,MAAM,CAAC;AACjE"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-links.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport {\n pluginRecordHref,\n pluginRecordListHref,\n} from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { AI_JOB_BESIGNER_SEGMENT as BESIGNER_SEGMENT, AI_SITE_BUILD_HREF } from '../model/ai-job-notice'\nimport type { AiJobOutput, AiJobSummary } from '../model/ai-jobs.types'\n\n// Where a job's work opens in the console (AGL-2904), shared by the AI jobs\n// drawer and every dialog that follows the job it started (AGL-3593).\n\n/**\n * Where \"open draft\" goes, or `null` when the output has no page of its\n * own — a `text` output carries its copy on the job and is shown inline.\n * A versioned resource opens in the besigner on the version the job wrote;\n * one the console lists without a detail page opens its list; a `theme`\n * proposal opens the site's Theme section, where it is put in the editor.\n *\n * A console URL names a site by its SUBDOMAIN: the `[host]` segment resolves\n * through the member's host projection by subdomain, never by document id.\n * So an output that carries no `hostSubdomain` gets no link rather than one\n * that opens no site.\n */\nexport function aiJobOutputHref(output: AiJobOutput, orgSlug: string): string | null {\n const host = output.hostSubdomain\n if (!orgSlug || !host) return null\n const segment = BESIGNER_SEGMENT[output.resource]\n if (segment) {\n const base = `/${orgSlug}/hosts/${host}/${segment}/${output.id}`\n return output.versionId ? `${base}/versions/${output.versionId}/besigner` : base\n }\n if (output.resource === 'product') {\n // The catalog a drafted product lands in: the commerce plugin's address\n // for it (AGL-3080), or no link where commerce is not loaded.\n return pluginRecordListHref('product', { orgSlug, host })\n }\n if (output.resource === 'workflow') {\n // A drafted automation is an action (AGL-2919), listed switched off on\n // the workflows plugin's list of actions, or no link where it is not loaded.\n return pluginRecordListHref('action', { orgSlug, host })\n }\n if (output.resource === 'entry') {\n // A post a guided start wrote (AGL-3676) opens in its blog's list in Content.\n const collectionSlug = output.proposal?.['collectionSlug']\n return typeof collectionSlug === 'string' && collectionSlug\n ? buildRoute(Route.HOST_CONTENT_COLLECTION, { orgSlug, host, collectionSlug })\n : buildRoute(Route.HOST_CONTENT, { orgSlug, host })\n }\n if (output.resource === 'theme') {\n return buildRoute(Route.HOST_SETUP_THEME, { orgSlug, host })\n }\n if (output.resource === 'draft' && output.draftResource) {\n // A draft another plugin wrote — a dataset a site start or a build made\n // (AGL-3616) — opens where its owner lists it, or nowhere it is not loaded.\n return pluginRecordListHref(output.draftResource, { orgSlug, host })\n }\n if (output.resource === 'form') {\n // A new form has no version for the besigner to open: its own page — the\n // forms plugin's address for it — mints the first one, and holds the\n // routing and consent it declares.\n return pluginRecordHref('form', { orgSlug, host }, output.id)\n }\n if (output.resource === 'campaign') {\n // A campaign's page is the marketing plugin's, under the site: its\n // address for the campaign (AGL-3080), or no link where it is not loaded.\n return pluginRecordHref('campaign', { orgSlug, host }, output.id)\n }\n return null\n}\n\n/**\n * The ONE action a finished job's row, its notification and the dialog that\n * started it lead with (AGL-3593): what the job built. Several pages from one\n * job — a site's — open the site's Pages list, where they wait as drafts; one\n * output with a page of its own opens that page; anything else has no single\n * place to go, and the row's own links stand. `null` until the job is done.\n */\nexport function aiJobPrimaryLink(\n job: Pick<AiJobSummary, 'status' | 'outputs'>,\n orgSlug: string,\n): { href: string; label: string } | null {\n if (job.status !== 'done' || !orgSlug) return null\n const screens = job.outputs.filter(\n (output) => output.resource === 'screen' && output.hostSubdomain,\n )\n if (screens.length > 1) {\n return {\n href: buildRoute(Route.HOST_SCREENS, { orgSlug, host: String(screens[0].hostSubdomain) }),\n label: 'Open your draft pages',\n }\n }\n for (const output of job.outputs) {\n const href = aiJobOutputHref(output, orgSlug)\n if (href) {\n return {\n href,\n label: output.resource === 'screen' ? 'Open the draft page' : `Open ${output.label}`,\n }\n }\n }\n return null\n}\n\nexport { AI_SITE_BUILD_HREF }\n\n/**\n * The \"Building your site\" page for one site job (AGL-3594): linkable, so the\n * guided start lands on it, a reload keeps it and a notification opens it.\n * `null` without the org slug or the site's subdomain a console URL needs.\n */\nexport function aiSiteBuildHref(\n orgSlug: string | null | undefined,\n hostSubdomain: string | null | undefined,\n jobId: string,\n): string | null {\n if (!orgSlug || !hostSubdomain || !jobId) return null\n return `/${orgSlug}/hosts/${hostSubdomain}${AI_SITE_BUILD_HREF}/${encodeURIComponent(jobId)}`\n}\n\n/**\n * A finished site's two ways in (AGL-3594): its first page's draft, opened in\n * the console's preview of that version, and the Pages list every draft waits\n * in. Either is `null` where the job reported nothing to open.\n */\nexport function aiSiteBuildDoneLinks(\n job: Pick<AiJobSummary, 'outputs'>,\n orgSlug: string,\n): { view: string | null; pages: string | null } {\n const screens = job.outputs.filter((output) => output.resource === 'screen' && output.hostSubdomain)\n const first = screens[0]\n if (!first || !orgSlug) return { view: null, pages: null }\n const host = String(first.hostSubdomain)\n return {\n view: first.versionId\n ? `/${orgSlug}/hosts/${host}/screens/${first.id}/versions/${first.versionId}/view`\n : null,\n pages: buildRoute(Route.HOST_SCREENS, { orgSlug, host }),\n }\n}\n\n/**\n * Where a person gets more AI credits (AGL-3660): the workspace's Billing\n * page at its plans, where an upgrade and the AI add-on are both sold — the\n * door the AI credits meter's own upgrade link opens.\n */\nexport function aiCreditsBillingHref(orgSlug: string): string {\n return `${buildRoute(Route.MANAGE_BILLING, { orgSlug })}#plans`\n}\n"],"names":["buildRoute","Route","pluginRecordHref","pluginRecordListHref","AI_JOB_BESIGNER_SEGMENT","BESIGNER_SEGMENT","AI_SITE_BUILD_HREF","aiJobOutputHref","output","orgSlug","host","hostSubdomain","segment","resource","base","id","versionId","collectionSlug","proposal","HOST_CONTENT_COLLECTION","HOST_CONTENT","HOST_SETUP_THEME","draftResource","aiJobPrimaryLink","job","status","screens","outputs","filter","length","href","HOST_SCREENS","String","label","aiSiteBuildHref","jobId","encodeURIComponent","aiSiteBuildDoneLinks","first","view","pages","aiCreditsBillingHref","MANAGE_BILLING"],"mappings":"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"}
@@ -16,7 +16,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
16
  * See the License for the specific language governing permissions and
17
17
  * limitations under the License.
18
18
  */ import { AI_BUILD_PLAN_CREATION_NOUNS, AI_BUILD_PLAN_EMBED_HOST_NAMES, isAiPlanNewRef } from "../model/ai-build-plan.js";
19
- import { aiBuildSmaller, aiJobCreditRange } from "../model/ai-build-job.js";
19
+ import { aiBuildFreePageCapNote, aiBuildSmaller, aiJobCreditRange } from "../model/ai-build-job.js";
20
20
  import { aiCreditRangeText, aiCreditsPromptFor } from "../model/ai-credit-estimate.js";
21
21
  import { aiFreeCreditsResetLabel } from "../model/ai-site-job.js";
22
22
  import { AiCreditsPromptNotice } from "./ai-credits-prompt.component.js";
@@ -96,6 +96,8 @@ export function AiJobPlan({ job, onResume, busy = false, staff = false, user, or
96
96
  // the home page first, or upgrade — never a refusal with no way on.
97
97
  const freeCredits = (_ref = review == null ? void 0 : review.freeCredits) != null ? _ref : null;
98
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;
99
101
  const offersPublish = job.kind === 'build' && job.publishAsked === true && waiting && review.reason === 'plan';
100
102
  const publishing = offersPublish && publish ? {
101
103
  publish: true
@@ -229,6 +231,15 @@ export function AiJobPlan({ job, onResume, busy = false, staff = false, user, or
229
231
  freeCredits && !prompt ? ` You have ${freeCredits.left.toLocaleString('en-US')} of your free AI credits left this month, until ${aiFreeCreditsResetLabel(freeCredits.resetsOn)}.` : ''
230
232
  ]
231
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
242
+ }),
232
243
  waiting && review.retryRefusal && review.reason !== 'plan' && /*#__PURE__*/ _jsx(Typography, {
233
244
  variant: "caption",
234
245
  color: "text.secondary",