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

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 (98) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/ai-credits-prompt.component.d.ts +37 -0
  3. package/src/lib/components/ai-credits-prompt.component.js +67 -0
  4. package/src/lib/components/ai-credits-prompt.component.js.map +1 -0
  5. package/src/lib/components/ai-job-plan.component.d.ts +5 -4
  6. package/src/lib/components/ai-job-plan.component.js +37 -12
  7. package/src/lib/components/ai-job-plan.component.js.map +1 -1
  8. package/src/lib/components/ai-job-requests.d.ts +10 -0
  9. package/src/lib/components/ai-job-requests.js +12 -4
  10. package/src/lib/components/ai-job-requests.js.map +1 -1
  11. package/src/lib/components/ai-site-build-page.component.js +26 -7
  12. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  13. package/src/lib/components/ai-site-start-card.component.js +68 -22
  14. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  15. package/src/lib/components/assist-build-card.component.d.ts +3 -1
  16. package/src/lib/components/assist-build-card.component.js +58 -7
  17. package/src/lib/components/assist-build-card.component.js.map +1 -1
  18. package/src/lib/components/assist-jobs-drawer.component.js +54 -6
  19. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  20. package/src/lib/components/assist-panel.component.js +2 -1
  21. package/src/lib/components/assist-panel.component.js.map +1 -1
  22. package/src/lib/jobs/ai-job-admission.d.ts +22 -2
  23. package/src/lib/jobs/ai-job-admission.js.map +1 -1
  24. package/src/lib/jobs/ai-job-build-step.d.ts +24 -11
  25. package/src/lib/jobs/ai-job-build-step.js +50 -34
  26. package/src/lib/jobs/ai-job-build-step.js.map +1 -1
  27. package/src/lib/jobs/ai-job-layout-language.js +1 -1
  28. package/src/lib/jobs/ai-job-layout-language.js.map +1 -1
  29. package/src/lib/jobs/ai-job-page-language.js +4 -0
  30. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  31. package/src/lib/jobs/ai-job-page-step.d.ts +13 -0
  32. package/src/lib/jobs/ai-job-page-step.js +34 -3
  33. package/src/lib/jobs/ai-job-page-step.js.map +1 -1
  34. package/src/lib/jobs/ai-job-plan-step.d.ts +23 -0
  35. package/src/lib/jobs/ai-job-plan-step.js +75 -15
  36. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  37. package/src/lib/jobs/ai-job-site-content.d.ts +34 -3
  38. package/src/lib/jobs/ai-job-site-content.js +132 -32
  39. package/src/lib/jobs/ai-job-site-content.js.map +1 -1
  40. package/src/lib/jobs/ai-job-site-step.js +45 -15
  41. package/src/lib/jobs/ai-job-site-step.js.map +1 -1
  42. package/src/lib/jobs/ai-job-text-step.d.ts +8 -0
  43. package/src/lib/jobs/ai-job-text-step.js.map +1 -1
  44. package/src/lib/jobs/ai-jobs.d.ts +18 -1
  45. package/src/lib/jobs/ai-jobs.js +29 -13
  46. package/src/lib/jobs/ai-jobs.js.map +1 -1
  47. package/src/lib/jobs/ai-layout-stock-photos.d.ts +105 -18
  48. package/src/lib/jobs/ai-layout-stock-photos.js +356 -114
  49. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  50. package/src/lib/layout-language/ai-layout-compiler.d.ts +27 -0
  51. package/src/lib/layout-language/ai-layout-compiler.js +78 -11
  52. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  53. package/src/lib/layout-language/ai-layout-gaps.js +3 -1
  54. package/src/lib/layout-language/ai-layout-gaps.js.map +1 -1
  55. package/src/lib/layout-language/ai-layout-language.js +1 -1
  56. package/src/lib/layout-language/ai-layout-language.js.map +1 -1
  57. package/src/lib/layout-language/ai-layout-listings.d.ts +37 -1
  58. package/src/lib/layout-language/ai-layout-listings.js +66 -11
  59. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  60. package/src/lib/layout-language/ai-layout-pictures.d.ts +2 -1
  61. package/src/lib/layout-language/ai-layout-pictures.js +5 -2
  62. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  63. package/src/lib/model/ai-build-job.d.ts +31 -0
  64. package/src/lib/model/ai-build-job.js +86 -1
  65. package/src/lib/model/ai-build-job.js.map +1 -1
  66. package/src/lib/model/ai-build-progress.d.ts +8 -0
  67. package/src/lib/model/ai-build-progress.js +33 -0
  68. package/src/lib/model/ai-build-progress.js.map +1 -1
  69. package/src/lib/model/ai-credit-estimate.d.ts +182 -0
  70. package/src/lib/model/ai-credit-estimate.js +190 -0
  71. package/src/lib/model/ai-credit-estimate.js.map +1 -0
  72. package/src/lib/model/ai-jobs.types.d.ts +15 -0
  73. package/src/lib/model/ai-jobs.types.js.map +1 -1
  74. package/src/lib/model/ai-site-job.d.ts +59 -22
  75. package/src/lib/model/ai-site-job.js +104 -23
  76. package/src/lib/model/ai-site-job.js.map +1 -1
  77. package/src/lib/model/ai-site-kinds.js +3 -3
  78. package/src/lib/model/ai-site-kinds.js.map +1 -1
  79. package/src/lib/runtime/ai-doctrine-validators.js +10 -1
  80. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  81. package/src/lib/runtime/ai-node-tree.d.ts +11 -0
  82. package/src/lib/runtime/ai-node-tree.js +26 -0
  83. package/src/lib/runtime/ai-node-tree.js.map +1 -1
  84. package/src/lib/runtime/ai-palette.generated.js +539 -7
  85. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  86. package/src/lib/server/ai-jobs-resume.js +93 -35
  87. package/src/lib/server/ai-jobs-resume.js.map +1 -1
  88. package/src/lib/server/ai-jobs-route.js +45 -4
  89. package/src/lib/server/ai-jobs-route.js.map +1 -1
  90. package/src/lib/server/assist-chat.js +4 -1
  91. package/src/lib/server/assist-chat.js.map +1 -1
  92. package/src/lib/server/assist-edit.js +3 -1
  93. package/src/lib/server/assist-edit.js.map +1 -1
  94. package/src/lib/server/assist-music.d.ts +36 -0
  95. package/src/lib/server/assist-music.js +36 -0
  96. package/src/lib/server/assist-music.js.map +1 -0
  97. package/src/lib/server.js +6 -0
  98. package/src/lib/server.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.237",
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.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",
44
44
  "@swc/helpers": "0.5.23"
45
45
  },
46
46
  "peerDependencies": {
@@ -0,0 +1,37 @@
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 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.
24
+ */
25
+ export interface AiCreditsPromptProps {
26
+ prompt: AiCreditsPrompt;
27
+ noun: 'build' | 'site';
28
+ /** The workspace's path slug, for Upgrade; Upgrade is left out without it. */
29
+ orgSlug?: string | null;
30
+ busy?: boolean;
31
+ /** "Build what fits": start it, to pause where the credits run out. */
32
+ onBuildWhatFits: () => void;
33
+ /** The smaller first build; offered only when the prompt has one and this is given. */
34
+ onSmaller?: () => void;
35
+ }
36
+ export declare function AiCreditsPromptNotice({ prompt, noun, orgSlug, busy, onBuildWhatFits, onSmaller, }: AiCreditsPromptProps): JSX.Element;
37
+ export default AiCreditsPromptNotice;
@@ -0,0 +1,67 @@
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 } 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";
20
+ 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",
24
+ sx: {
25
+ mt: 1
26
+ },
27
+ role: "alert",
28
+ "aria-label": "More than your credits left",
29
+ children: [
30
+ aiCreditsPromptText(prompt, noun),
31
+ /*#__PURE__*/ _jsxs(Stack, {
32
+ direction: "row",
33
+ spacing: 1,
34
+ useFlexGap: true,
35
+ sx: {
36
+ mt: 1,
37
+ flexWrap: 'wrap'
38
+ },
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
+ ]
61
+ })
62
+ ]
63
+ });
64
+ }
65
+ export default AiCreditsPromptNotice;
66
+
67
+ //# 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 { 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"}
@@ -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 { 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,18 @@ 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;
91
99
  const offersPublish = job.kind === 'build' && job.publishAsked === true && waiting && review.reason === 'plan';
100
+ const publishing = offersPublish && publish ? {
101
+ publish: true
102
+ } : {};
92
103
  return /*#__PURE__*/ _jsxs(Box, {
93
104
  sx: {
94
105
  mt: 1
@@ -206,14 +217,17 @@ export function AiJobPlan({ job, onResume, busy = false, staff = false, user })
206
217
  })
207
218
  ]
208
219
  }),
209
- estimate > 0 && /*#__PURE__*/ _jsx(Typography, {
220
+ range && range.ceiling > 0 && /*#__PURE__*/ _jsxs(Typography, {
210
221
  variant: "caption",
211
222
  color: "text.secondary",
212
223
  sx: {
213
224
  display: 'block',
214
225
  mt: 1
215
226
  },
216
- children: `Estimated cost: about ${estimate.toLocaleString('en-US')} credits. What it costs is what its steps spend.`
227
+ children: [
228
+ `Estimated cost: ${aiCreditRangeText(range)}. What it costs is what its steps spend.`,
229
+ freeCredits && !prompt ? ` You have ${freeCredits.left.toLocaleString('en-US')} of your free AI credits left this month, until ${aiFreeCreditsResetLabel(freeCredits.resetsOn)}.` : ''
230
+ ]
217
231
  }),
218
232
  waiting && review.retryRefusal && review.reason !== 'plan' && /*#__PURE__*/ _jsx(Typography, {
219
233
  variant: "caption",
@@ -239,15 +253,26 @@ export function AiJobPlan({ job, onResume, busy = false, staff = false, user })
239
253
  children: 'Publish the new pages when they are built'
240
254
  })
241
255
  }),
242
- waiting && /*#__PURE__*/ _jsx(Button, {
256
+ waiting && prompt && /*#__PURE__*/ _jsx(AiCreditsPromptNotice, {
257
+ prompt: prompt,
258
+ noun: "build",
259
+ orgSlug: orgSlug,
260
+ busy: busy,
261
+ onBuildWhatFits: ()=>onResume(job, _extends({}, publishing, {
262
+ creditsConfirmed: true
263
+ })),
264
+ onSmaller: ()=>onResume(job, _extends({}, publishing, {
265
+ creditsConfirmed: true,
266
+ reduce: 'first-page'
267
+ }))
268
+ }),
269
+ waiting && !prompt && /*#__PURE__*/ _jsx(Button, {
243
270
  size: "small",
244
271
  variant: "contained",
245
272
  // Disabled with its reason above when the Free allowance left
246
273
  // cannot pay for another try (AGL-3594).
247
274
  disabled: busy || review.reason !== 'plan' && Boolean(review.retryRefusal),
248
- onClick: ()=>onResume(job, offersPublish && publish ? {
249
- publish: true
250
- } : undefined),
275
+ onClick: ()=>onResume(job, offersPublish && publish ? publishing : undefined),
251
276
  sx: {
252
277
  mt: 1
253
278
  },
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-plan.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\nimport {\n AI_BUILD_PLAN_CREATION_NOUNS,\n AI_BUILD_PLAN_EMBED_HOST_NAMES,\n isAiPlanNewRef,\n type AiBuildPlanEmbed,\n} from '../model/ai-build-plan'\nimport type { AiJobReview, AiJobSummary } from '../model/ai-jobs.types'\nimport { aiJobCreditEstimate } from '../model/ai-build-job'\nimport { aiBuildOpNoun } from '../model/ai-build-progress'\nimport { aiJobConfirmingOwnPlan } from '../model/ai-job-activity'\nimport { aiSiteStarterFallbackOffered } from '../model/ai-job-failure-copy'\nimport { AiSiteStarterFallback } from './ai-site-starter-fallback.component'\nimport { Box, Button, Checkbox, Collapse, FormControlLabel, Stack, Typography } from '@mui/material'\nimport { useState } from 'react'\n\n/**\n * A job's plan and what it waits for (AGL-2935), inside the AI jobs drawer\n * and the dialog that started the job (AGL-3593): what the job reuses from\n * the site, what it creates and why, the screens it builds — and, while the\n * job waits for a person, the one button that confirms the plan or tries a\n * refused step again. Both send the same request (`resumeAiJobRequest`);\n * this renders.\n */\nexport interface AiJobPlanProps {\n job: AiJobSummary\n /**\n * Confirms the plan, or tries the refused step again. A build whose request\n * asked to publish confirms with `publish` when its box is ticked (AGL-3616).\n */\n onResume: (job: AiJobSummary, options?: { publish?: boolean }) => void\n /** A resume for this job is in flight. */\n busy?: boolean\n /**\n * The viewer is staff (AGL-3078), who can open where a refused answer broke\n * its rules and the outline of those parts, to tell a shape the rules should\n * allow from an answer that ignored its re-ask. A member reads the findings.\n */\n staff?: boolean\n /**\n * The signed-in user, for \"Use the starter site instead\" on a guided start\n * that did not work out (AGL-3594); the action is drawn only when given.\n */\n user?: Parameters<typeof AiSiteStarterFallback>[0]['user']\n}\n\n/**\n * Where a refused answer broke its rules, and the outline of those parts, as\n * lines staff read (AGL-3078): each finding with the nodes or plan entries it\n * names, then each outlined node indented by its depth, with its element, a\n * Grid's layout as written, and the names of what it sets and holds.\n */\nexport function aiJobReviewDetails(review: AiJobReview | null): string[] {\n if (review?.reason !== 'doctrine') return []\n const lines = review.findings.flatMap((finding) => {\n const where = finding.nodeIds?.length\n ? `nodes ${finding.nodeIds.join(', ')}`\n : finding.paths?.length\n ? `at ${finding.paths.join(', ')}`\n : null\n return where ? [`${finding.rule === null ? '' : `Rule ${finding.rule} `}${finding.code}: ${where}`] : []\n })\n for (const node of review.outline ?? []) {\n lines.push(\n [\n `${' '.repeat(node.depth)}${node.id} ${node.componentId}`,\n ...Object.entries(node.grid ?? {}).map(([name, value]) => `${name}=${JSON.stringify(value)}`),\n ...(node.props.length ? [`props ${node.props.join(', ')}`] : []),\n ...(node.sx?.length ? [`sx ${node.sx.join(', ')}`] : []),\n ...(node.children.length ? [`holds ${node.children.join(', ')}`] : []),\n ].join(' · '),\n )\n }\n return lines\n}\n\n/**\n * A third-party player the plan lists, with what it costs the page, read\n * before the member confirms it (rule 16, AGL-3433). The cost is the\n * platform's sentence, never the model's: the player is framed lazily, so its\n * host's code loads when a visitor reaches it, whether or not they press play.\n */\nexport function aiPlanEmbedLine(\n embed: AiBuildPlanEmbed,\n named: (ref: string) => string,\n): string {\n const host = AI_BUILD_PLAN_EMBED_HOST_NAMES[embed.host]\n const where = isAiPlanNewRef(embed.where) ? `the component ${named(embed.where)}` : embed.where\n const link = embed.url ? ` playing ${embed.url}` : ', its link left for you to paste'\n 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.`\n}\n\nexport function AiJobPlan({\n job,\n onResume,\n busy = false,\n staff = false,\n user,\n}: AiJobPlanProps): JSX.Element | null {\n const [detailsOpen, setDetailsOpen] = useState(false)\n // Unticked until the person ticks it: a build publishes only when its\n // request asked AND they confirm it here (AGL-3616).\n const [publish, setPublish] = useState(false)\n const { plan, review } = job\n if (!plan && !review) return null\n const details = staff ? aiJobReviewDetails(review) : []\n /** A reference as a person reads it: the record's name, or what the plan creates. */\n const named = (ref: string | null): string =>\n !ref\n ? ''\n : isAiPlanNewRef(ref)\n ? ref.slice('new:'.length)\n : (plan?.labels[ref] ?? ref)\n // A plan the machine is confirming itself is not one to confirm (AGL-3596).\n const waiting = job.status === 'needs_review' && review !== null && !aiJobConfirmingOwnPlan(job)\n // The guard rail (AGL-2911): what the plan is estimated to cost is read\n // before it is confirmed, not after it has been spent. An estimate, and\n // said to be one — the plan's own passes at the nominal credits a step\n // holds, where what a step really costs is its model's tokens. A page job\n // counts the creations it builds before its page (AGL-3031).\n const estimate =\n plan && waiting && review.reason === 'plan' ? aiJobCreditEstimate(job.kind, plan) : 0\n const offersPublish = job.kind === 'build' && job.publishAsked === true && waiting && review.reason === 'plan'\n return (\n <Box sx={{ mt: 1 }}>\n {plan && (\n <Stack spacing={0.5} role=\"list\" aria-label=\"Plan\">\n <Typography variant=\"caption\" color=\"text.secondary\">\n {plan.status === 'confirmed' ? 'Confirmed plan' : 'Proposed plan'}\n </Typography>\n {plan.reuse.map((entry, index) => (\n <Typography key={`reuse-${index}`} variant=\"body2\" role=\"listitem\">\n Reuses the {entry.kind === 'screen' ? 'page' : entry.kind} {named(entry.id)} — {entry.purpose}\n </Typography>\n ))}\n {plan.create.map((entry, index) => (\n <Typography key={`create-${index}`} variant=\"body2\" role=\"listitem\">\n Creates the {AI_BUILD_PLAN_CREATION_NOUNS[entry.kind].noun} {entry.name}\n {entry.duplicateOf\n ? `, from a copy of ${named(entry.duplicateOf)}`\n : ''}{' '}\n — {entry.why}\n </Typography>\n ))}\n {plan.screens.map((screen, index) => (\n <Typography key={`screen-${index}`} variant=\"body2\" role=\"listitem\">\n {screen.record\n ? `Builds the record template ${screen.title}, a page per record of ${named(screen.record.dataset)} at /${screen.record.base}/…`\n : `Builds the page ${screen.title} at ${screen.slug}`}\n {screen.duplicateOf\n ? `, from a copy of ${named(screen.duplicateOf)}`\n : ''}\n {screen.layout ? ` in ${named(screen.layout)}` : ''}\n {screen.sections.length\n ? `: ${screen.sections.map((section) => section.name).join(', ')}`\n : ''}\n </Typography>\n ))}\n {(plan.items ?? []).map((item) => (\n <Typography key={`item-${item.slot}`} variant=\"body2\" role=\"listitem\">\n {`Makes the ${aiBuildOpNoun(item.op).toLowerCase()} ${item.name}`}\n {item.why ? ` — ${item.why}` : ''}\n </Typography>\n ))}\n {(plan.embeds ?? []).map((embed, index) => (\n <Typography key={`embed-${index}`} variant=\"body2\" role=\"listitem\">\n {aiPlanEmbedLine(embed, named)}\n </Typography>\n ))}\n </Stack>\n )}\n {/*\n A review written for the member (AGL-3594) carries the checks' own\n sentence as `detail`: its findings are then staff reading too, and the\n member reads the job's one sentence alone.\n */}\n {staff && review?.detail && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\" sx={{ mt: 0.5 }}>\n {review.detail}\n </Typography>\n )}\n {review?.reason === 'doctrine' && review.findings.length > 0 && (staff || !review.detail) && (\n <Box\n component=\"ul\"\n sx={{ my: 0.5, pl: 2.5 }}\n aria-label=\"Building rules broken\"\n >\n {review.findings.map((finding, index) => (\n <Typography\n key={`finding-${index}`}\n component=\"li\"\n variant=\"caption\"\n >\n {finding.message}\n </Typography>\n ))}\n </Box>\n )}\n {details.length > 0 && (\n <Box sx={{ mt: 0.5 }}>\n <Button\n size=\"small\"\n aria-expanded={detailsOpen}\n onClick={() => setDetailsOpen((prior) => !prior)}\n >\n {detailsOpen ? 'Hide what was refused' : 'Show what was refused'}\n </Button>\n <Collapse in={detailsOpen} unmountOnExit>\n <Typography\n variant=\"caption\"\n component=\"pre\"\n aria-label=\"What was refused\"\n sx={{ m: 0, fontFamily: 'monospace', whiteSpace: 'pre', overflowX: 'auto' }}\n >\n {details.join('\\n')}\n </Typography>\n </Collapse>\n </Box>\n )}\n {estimate > 0 && (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mt: 1 }}\n >\n {`Estimated cost: about ${estimate.toLocaleString('en-US')} credits. What it costs is what its steps spend.`}\n </Typography>\n )}\n {waiting && review.retryRefusal && review.reason !== 'plan' && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\" sx={{ mt: 1 }}>\n {review.retryRefusal}\n </Typography>\n )}\n {offersPublish && (\n <FormControlLabel\n sx={{ display: 'flex', mt: 1 }}\n control={<Checkbox size=\"small\" checked={publish} onChange={(event) => setPublish(event.target.checked)} />}\n label={<Typography variant=\"body2\">{'Publish the new pages when they are built'}</Typography>}\n />\n )}\n {waiting && (\n <Button\n size=\"small\"\n variant=\"contained\"\n // Disabled with its reason above when the Free allowance left\n // cannot pay for another try (AGL-3594).\n disabled={busy || (review.reason !== 'plan' && Boolean(review.retryRefusal))}\n onClick={() => onResume(job, offersPublish && publish ? { publish: true } : undefined)}\n sx={{ mt: 1 }}\n >\n {review.reason === 'plan' ? 'Confirm plan' : 'Try again'}\n </Button>\n )}\n {/* A guided start that did not work out can take the starter instead (AGL-3594). */}\n {waiting && user && aiSiteStarterFallbackOffered(job) && <AiSiteStarterFallback job={job} user={user} />}\n </Box>\n )\n}\n\nexport default AiJobPlan\n"],"names":["AI_BUILD_PLAN_CREATION_NOUNS","AI_BUILD_PLAN_EMBED_HOST_NAMES","isAiPlanNewRef","aiJobCreditEstimate","aiBuildOpNoun","aiJobConfirmingOwnPlan","aiSiteStarterFallbackOffered","AiSiteStarterFallback","Box","Button","Checkbox","Collapse","FormControlLabel","Stack","Typography","useState","aiJobReviewDetails","review","reason","lines","findings","flatMap","finding","where","nodeIds","length","join","paths","rule","code","node","outline","push","repeat","depth","id","componentId","Object","entries","grid","map","name","value","JSON","stringify","props","sx","children","aiPlanEmbedLine","embed","named","host","link","url","asked","AiJobPlan","job","onResume","busy","staff","user","plan","detailsOpen","setDetailsOpen","publish","setPublish","details","ref","slice","labels","waiting","status","estimate","kind","offersPublish","publishAsked","mt","spacing","role","aria-label","variant","color","reuse","entry","index","purpose","create","noun","duplicateOf","why","screens","screen","record","title","dataset","base","slug","layout","sections","section","items","item","op","toLowerCase","slot","embeds","detail","component","my","pl","message","size","aria-expanded","onClick","prior","in","unmountOnExit","m","fontFamily","whiteSpace","overflowX","display","toLocaleString","retryRefusal","control","checked","onChange","event","target","label","disabled","Boolean","undefined"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-plan.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\nimport {\n AI_BUILD_PLAN_CREATION_NOUNS,\n AI_BUILD_PLAN_EMBED_HOST_NAMES,\n isAiPlanNewRef,\n type AiBuildPlanEmbed,\n} from '../model/ai-build-plan'\nimport type { AiJobReview, AiJobSummary } from '../model/ai-jobs.types'\nimport { aiBuildSmaller, aiJobCreditRange } from '../model/ai-build-job'\nimport { aiCreditRangeText, aiCreditsPromptFor } from '../model/ai-credit-estimate'\nimport { aiFreeCreditsResetLabel } from '../model/ai-site-job'\nimport { AiCreditsPromptNotice } from './ai-credits-prompt.component'\nimport type { AiJobResumeOptions } from './ai-job-requests'\nimport { aiBuildOpNoun } from '../model/ai-build-progress'\nimport { aiJobConfirmingOwnPlan } from '../model/ai-job-activity'\nimport { aiSiteStarterFallbackOffered } from '../model/ai-job-failure-copy'\nimport { AiSiteStarterFallback } from './ai-site-starter-fallback.component'\nimport { Box, Button, Checkbox, Collapse, FormControlLabel, Stack, Typography } from '@mui/material'\nimport { useState } from 'react'\n\n/**\n * A job's plan and what it waits for (AGL-2935), inside the AI jobs drawer\n * and the dialog that started the job (AGL-3593): what the job reuses from\n * the site, what it creates and why, the screens it builds — and, while the\n * job waits for a person, the one button that confirms the plan or tries a\n * refused step again. Both send the same request (`resumeAiJobRequest`);\n * this renders.\n */\nexport interface AiJobPlanProps {\n job: AiJobSummary\n /**\n * Confirms the plan, or tries the refused step again. A build whose request\n * asked to publish confirms with `publish` when its box is ticked (AGL-3616).\n */\n onResume: (job: AiJobSummary, options?: AiJobResumeOptions) => void\n /** A resume for this job is in flight. */\n busy?: boolean\n /**\n * The viewer is staff (AGL-3078), who can open where a refused answer broke\n * its rules and the outline of those parts, to tell a shape the rules should\n * allow from an answer that ignored its re-ask. A member reads the findings.\n */\n staff?: boolean\n /**\n * The signed-in user, for \"Use the starter site instead\" on a guided start\n * that did not work out (AGL-3594); the action is drawn only when given.\n */\n user?: Parameters<typeof AiSiteStarterFallback>[0]['user']\n /** The workspace's path slug, for the Upgrade a Free build past what is left offers (AGL-3722). */\n orgSlug?: string | null\n}\n\n/**\n * Where a refused answer broke its rules, and the outline of those parts, as\n * lines staff read (AGL-3078): each finding with the nodes or plan entries it\n * names, then each outlined node indented by its depth, with its element, a\n * Grid's layout as written, and the names of what it sets and holds.\n */\nexport function aiJobReviewDetails(review: AiJobReview | null): string[] {\n if (review?.reason !== 'doctrine') return []\n const lines = review.findings.flatMap((finding) => {\n const where = finding.nodeIds?.length\n ? `nodes ${finding.nodeIds.join(', ')}`\n : finding.paths?.length\n ? `at ${finding.paths.join(', ')}`\n : null\n return where ? [`${finding.rule === null ? '' : `Rule ${finding.rule} `}${finding.code}: ${where}`] : []\n })\n for (const node of review.outline ?? []) {\n lines.push(\n [\n `${' '.repeat(node.depth)}${node.id} ${node.componentId}`,\n ...Object.entries(node.grid ?? {}).map(([name, value]) => `${name}=${JSON.stringify(value)}`),\n ...(node.props.length ? [`props ${node.props.join(', ')}`] : []),\n ...(node.sx?.length ? [`sx ${node.sx.join(', ')}`] : []),\n ...(node.children.length ? [`holds ${node.children.join(', ')}`] : []),\n ].join(' · '),\n )\n }\n return lines\n}\n\n/**\n * A third-party player the plan lists, with what it costs the page, read\n * before the member confirms it (rule 16, AGL-3433). The cost is the\n * platform's sentence, never the model's: the player is framed lazily, so its\n * host's code loads when a visitor reaches it, whether or not they press play.\n */\nexport function aiPlanEmbedLine(\n embed: AiBuildPlanEmbed,\n named: (ref: string) => string,\n): string {\n const host = AI_BUILD_PLAN_EMBED_HOST_NAMES[embed.host]\n const where = isAiPlanNewRef(embed.where) ? `the component ${named(embed.where)}` : embed.where\n const link = embed.url ? ` playing ${embed.url}` : ', its link left for you to paste'\n 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.`\n}\n\nexport function AiJobPlan({\n job,\n onResume,\n busy = false,\n staff = false,\n user,\n orgSlug,\n}: AiJobPlanProps): JSX.Element | null {\n const [detailsOpen, setDetailsOpen] = useState(false)\n // Unticked until the person ticks it: a build publishes only when its\n // request asked AND they confirm it here (AGL-3616).\n const [publish, setPublish] = useState(false)\n const { plan, review } = job\n if (!plan && !review) return null\n const details = staff ? aiJobReviewDetails(review) : []\n /** A reference as a person reads it: the record's name, or what the plan creates. */\n const named = (ref: string | null): string =>\n !ref\n ? ''\n : isAiPlanNewRef(ref)\n ? ref.slice('new:'.length)\n : (plan?.labels[ref] ?? ref)\n // A plan the machine is confirming itself is not one to confirm (AGL-3596).\n const waiting = job.status === 'needs_review' && review !== null && !aiJobConfirmingOwnPlan(job)\n // The guard rail (AGL-2911): what the plan is estimated to cost is read\n // before it is confirmed, not after it has been spent. An estimate, and\n // said to be one: what it is likely to cost — what builds like it measured\n // — and its ceiling, every pass at its reserve (AGL-3722). A page job\n // counts the creations it builds before its page (AGL-3031).\n const range = plan && waiting && review.reason === 'plan' ? aiJobCreditRange(job.kind, plan) : null\n // A Free build past what is left asks first (AGL-3722): build what fits,\n // the home page first, or upgrade — never a refusal with no way on.\n const freeCredits = review?.freeCredits ?? null\n const prompt =\n plan && range && job.kind === 'build' ? aiCreditsPromptFor(range, freeCredits, aiBuildSmaller(plan)) : null\n const offersPublish = job.kind === 'build' && job.publishAsked === true && waiting && review.reason === 'plan'\n const publishing = offersPublish && publish ? { publish: true } : {}\n return (\n <Box sx={{ mt: 1 }}>\n {plan && (\n <Stack spacing={0.5} role=\"list\" aria-label=\"Plan\">\n <Typography variant=\"caption\" color=\"text.secondary\">\n {plan.status === 'confirmed' ? 'Confirmed plan' : 'Proposed plan'}\n </Typography>\n {plan.reuse.map((entry, index) => (\n <Typography key={`reuse-${index}`} variant=\"body2\" role=\"listitem\">\n Reuses the {entry.kind === 'screen' ? 'page' : entry.kind} {named(entry.id)} — {entry.purpose}\n </Typography>\n ))}\n {plan.create.map((entry, index) => (\n <Typography key={`create-${index}`} variant=\"body2\" role=\"listitem\">\n Creates the {AI_BUILD_PLAN_CREATION_NOUNS[entry.kind].noun} {entry.name}\n {entry.duplicateOf\n ? `, from a copy of ${named(entry.duplicateOf)}`\n : ''}{' '}\n — {entry.why}\n </Typography>\n ))}\n {plan.screens.map((screen, index) => (\n <Typography key={`screen-${index}`} variant=\"body2\" role=\"listitem\">\n {screen.record\n ? `Builds the record template ${screen.title}, a page per record of ${named(screen.record.dataset)} at /${screen.record.base}/…`\n : `Builds the page ${screen.title} at ${screen.slug}`}\n {screen.duplicateOf\n ? `, from a copy of ${named(screen.duplicateOf)}`\n : ''}\n {screen.layout ? ` in ${named(screen.layout)}` : ''}\n {screen.sections.length\n ? `: ${screen.sections.map((section) => section.name).join(', ')}`\n : ''}\n </Typography>\n ))}\n {(plan.items ?? []).map((item) => (\n <Typography key={`item-${item.slot}`} variant=\"body2\" role=\"listitem\">\n {`Makes the ${aiBuildOpNoun(item.op).toLowerCase()} ${item.name}`}\n {item.why ? ` — ${item.why}` : ''}\n </Typography>\n ))}\n {(plan.embeds ?? []).map((embed, index) => (\n <Typography key={`embed-${index}`} variant=\"body2\" role=\"listitem\">\n {aiPlanEmbedLine(embed, named)}\n </Typography>\n ))}\n </Stack>\n )}\n {/*\n A review written for the member (AGL-3594) carries the checks' own\n sentence as `detail`: its findings are then staff reading too, and the\n member reads the job's one sentence alone.\n */}\n {staff && review?.detail && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\" sx={{ mt: 0.5 }}>\n {review.detail}\n </Typography>\n )}\n {review?.reason === 'doctrine' && review.findings.length > 0 && (staff || !review.detail) && (\n <Box\n component=\"ul\"\n sx={{ my: 0.5, pl: 2.5 }}\n aria-label=\"Building rules broken\"\n >\n {review.findings.map((finding, index) => (\n <Typography\n key={`finding-${index}`}\n component=\"li\"\n variant=\"caption\"\n >\n {finding.message}\n </Typography>\n ))}\n </Box>\n )}\n {details.length > 0 && (\n <Box sx={{ mt: 0.5 }}>\n <Button\n size=\"small\"\n aria-expanded={detailsOpen}\n onClick={() => setDetailsOpen((prior) => !prior)}\n >\n {detailsOpen ? 'Hide what was refused' : 'Show what was refused'}\n </Button>\n <Collapse in={detailsOpen} unmountOnExit>\n <Typography\n variant=\"caption\"\n component=\"pre\"\n aria-label=\"What was refused\"\n sx={{ m: 0, fontFamily: 'monospace', whiteSpace: 'pre', overflowX: 'auto' }}\n >\n {details.join('\\n')}\n </Typography>\n </Collapse>\n </Box>\n )}\n {range && range.ceiling > 0 && (\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n sx={{ display: 'block', mt: 1 }}\n >\n {`Estimated cost: ${aiCreditRangeText(range)}. What it costs is what its steps spend.`}\n {freeCredits && !prompt\n ? ` You have ${freeCredits.left.toLocaleString('en-US')} of your free AI credits left this month, until ${aiFreeCreditsResetLabel(freeCredits.resetsOn)}.`\n : ''}\n </Typography>\n )}\n {waiting && review.retryRefusal && review.reason !== 'plan' && (\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"div\" sx={{ mt: 1 }}>\n {review.retryRefusal}\n </Typography>\n )}\n {offersPublish && (\n <FormControlLabel\n sx={{ display: 'flex', mt: 1 }}\n control={<Checkbox size=\"small\" checked={publish} onChange={(event) => setPublish(event.target.checked)} />}\n label={<Typography variant=\"body2\">{'Publish the new pages when they are built'}</Typography>}\n />\n )}\n {waiting && prompt && (\n <AiCreditsPromptNotice\n prompt={prompt}\n noun=\"build\"\n orgSlug={orgSlug}\n busy={busy}\n onBuildWhatFits={() => onResume(job, { ...publishing, creditsConfirmed: true })}\n onSmaller={() => onResume(job, { ...publishing, creditsConfirmed: true, reduce: 'first-page' })}\n />\n )}\n {waiting && !prompt && (\n <Button\n size=\"small\"\n variant=\"contained\"\n // Disabled with its reason above when the Free allowance left\n // cannot pay for another try (AGL-3594).\n disabled={busy || (review.reason !== 'plan' && Boolean(review.retryRefusal))}\n onClick={() => onResume(job, offersPublish && publish ? publishing : undefined)}\n sx={{ mt: 1 }}\n >\n {review.reason === 'plan' ? 'Confirm plan' : 'Try again'}\n </Button>\n )}\n {/* A guided start that did not work out can take the starter instead (AGL-3594). */}\n {waiting && user && aiSiteStarterFallbackOffered(job) && <AiSiteStarterFallback job={job} user={user} />}\n </Box>\n )\n}\n\nexport default AiJobPlan\n"],"names":["AI_BUILD_PLAN_CREATION_NOUNS","AI_BUILD_PLAN_EMBED_HOST_NAMES","isAiPlanNewRef","aiBuildSmaller","aiJobCreditRange","aiCreditRangeText","aiCreditsPromptFor","aiFreeCreditsResetLabel","AiCreditsPromptNotice","aiBuildOpNoun","aiJobConfirmingOwnPlan","aiSiteStarterFallbackOffered","AiSiteStarterFallback","Box","Button","Checkbox","Collapse","FormControlLabel","Stack","Typography","useState","aiJobReviewDetails","review","reason","lines","findings","flatMap","finding","where","nodeIds","length","join","paths","rule","code","node","outline","push","repeat","depth","id","componentId","Object","entries","grid","map","name","value","JSON","stringify","props","sx","children","aiPlanEmbedLine","embed","named","host","link","url","asked","AiJobPlan","job","onResume","busy","staff","user","orgSlug","plan","detailsOpen","setDetailsOpen","publish","setPublish","details","ref","slice","labels","waiting","status","range","kind","freeCredits","prompt","offersPublish","publishAsked","publishing","mt","spacing","role","aria-label","variant","color","reuse","entry","index","purpose","create","noun","duplicateOf","why","screens","screen","record","title","dataset","base","slug","layout","sections","section","items","item","op","toLowerCase","slot","embeds","detail","component","my","pl","message","size","aria-expanded","onClick","prior","in","unmountOnExit","m","fontFamily","whiteSpace","overflowX","ceiling","display","left","toLocaleString","resetsOn","retryRefusal","control","checked","onChange","event","target","label","onBuildWhatFits","creditsConfirmed","onSmaller","reduce","disabled","Boolean","undefined"],"mappings":";;AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,4BAA4B,EAC5BC,8BAA8B,EAC9BC,cAAc,QAET,4BAAwB;AAE/B,SAASC,cAAc,EAAEC,gBAAgB,QAAQ,2BAAuB;AACxE,SAASC,iBAAiB,EAAEC,kBAAkB,QAAQ,iCAA6B;AACnF,SAASC,uBAAuB,QAAQ,0BAAsB;AAC9D,SAASC,qBAAqB,QAAQ,mCAA+B;AAErE,SAASC,aAAa,QAAQ,gCAA4B;AAC1D,SAASC,sBAAsB,QAAQ,8BAA0B;AACjE,SAASC,4BAA4B,QAAQ,kCAA8B;AAC3E,SAASC,qBAAqB,QAAQ,0CAAsC;AAC5E,SAASC,GAAG,EAAEC,MAAM,EAAEC,QAAQ,EAAEC,QAAQ,EAAEC,gBAAgB,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AACpG,SAASC,QAAQ,QAAQ,QAAO;AAkChC;;;;;CAKC,GACD,OAAO,SAASC,mBAAmBC,MAA0B;QAUxCA;IATnB,IAAIA,CAAAA,0BAAAA,OAAQC,MAAM,MAAK,YAAY,OAAO,EAAE;IAC5C,MAAMC,QAAQF,OAAOG,QAAQ,CAACC,OAAO,CAAC,CAACC;YACvBA,kBAEVA;QAFJ,MAAMC,QAAQD,EAAAA,mBAAAA,QAAQE,OAAO,qBAAfF,iBAAiBG,MAAM,IACjC,CAAC,MAAM,EAAEH,QAAQE,OAAO,CAACE,IAAI,CAAC,OAAO,GACrCJ,EAAAA,iBAAAA,QAAQK,KAAK,qBAAbL,eAAeG,MAAM,IACnB,CAAC,GAAG,EAAEH,QAAQK,KAAK,CAACD,IAAI,CAAC,OAAO,GAChC;QACN,OAAOH,QAAQ;YAAC,GAAGD,QAAQM,IAAI,KAAK,OAAO,KAAK,CAAC,KAAK,EAAEN,QAAQM,IAAI,CAAC,CAAC,CAAC,GAAGN,QAAQO,IAAI,CAAC,EAAE,EAAEN,OAAO;SAAC,GAAG,EAAE;IAC1G;IACA,KAAK,MAAMO,SAAQb,kBAAAA,OAAOc,OAAO,YAAdd,kBAAkB,EAAE,CAAE;YAIjBa;YAEdA;QALRX,MAAMa,IAAI,CACR;YACE,GAAG,KAAKC,MAAM,CAACH,KAAKI,KAAK,IAAIJ,KAAKK,EAAE,CAAC,CAAC,EAAEL,KAAKM,WAAW,EAAE;eACvDC,OAAOC,OAAO,EAACR,aAAAA,KAAKS,IAAI,YAATT,aAAa,CAAC,GAAGU,GAAG,CAAC,CAAC,CAACC,MAAMC,MAAM,GAAK,GAAGD,KAAK,CAAC,EAAEE,KAAKC,SAAS,CAACF,QAAQ;eACxFZ,KAAKe,KAAK,CAACpB,MAAM,GAAG;gBAAC,CAAC,MAAM,EAAEK,KAAKe,KAAK,CAACnB,IAAI,CAAC,OAAO;aAAC,GAAG,EAAE;eAC3DI,EAAAA,WAAAA,KAAKgB,EAAE,qBAAPhB,SAASL,MAAM,IAAG;gBAAC,CAAC,GAAG,EAAEK,KAAKgB,EAAE,CAACpB,IAAI,CAAC,OAAO;aAAC,GAAG,EAAE;eACnDI,KAAKiB,QAAQ,CAACtB,MAAM,GAAG;gBAAC,CAAC,MAAM,EAAEK,KAAKiB,QAAQ,CAACrB,IAAI,CAAC,OAAO;aAAC,GAAG,EAAE;SACtE,CAACA,IAAI,CAAC;IAEX;IACA,OAAOP;AACT;AAEA;;;;;CAKC,GACD,OAAO,SAAS6B,gBACdC,KAAuB,EACvBC,KAA8B;IAE9B,MAAMC,OAAOvD,8BAA8B,CAACqD,MAAME,IAAI,CAAC;IACvD,MAAM5B,QAAQ1B,eAAeoD,MAAM1B,KAAK,IAAI,CAAC,cAAc,EAAE2B,MAAMD,MAAM1B,KAAK,GAAG,GAAG0B,MAAM1B,KAAK;IAC/F,MAAM6B,OAAOH,MAAMI,GAAG,GAAG,CAAC,SAAS,EAAEJ,MAAMI,GAAG,EAAE,GAAG;IACnD,OAAO,CAAC,SAAS,EAAEF,KAAK,WAAW,EAAE5B,QAAQ6B,KAAK,iBAAiB,EAAEH,MAAMK,KAAK,CAAC,aAAa,EAAEH,KAAK,sEAAsE,CAAC;AAC9K;AAEA,OAAO,SAASI,UAAU,EACxBC,GAAG,EACHC,QAAQ,EACRC,OAAO,KAAK,EACZC,QAAQ,KAAK,EACbC,IAAI,EACJC,OAAO,EACQ;cAiELC,aAMAA;IAtEV,MAAM,CAACC,aAAaC,eAAe,GAAGjD,SAAS;IAC/C,sEAAsE;IACtE,qDAAqD;IACrD,MAAM,CAACkD,SAASC,WAAW,GAAGnD,SAAS;IACvC,MAAM,EAAE+C,IAAI,EAAE7C,MAAM,EAAE,GAAGuC;IACzB,IAAI,CAACM,QAAQ,CAAC7C,QAAQ,OAAO;IAC7B,MAAMkD,UAAUR,QAAQ3C,mBAAmBC,UAAU,EAAE;IACvD,mFAAmF,GACnF,MAAMiC,QAAQ,CAACkB;;eACb,CAACA,MACG,KACAvE,eAAeuE,OACbA,IAAIC,KAAK,CAAC,OAAO5C,MAAM,YACtBqC,wBAAAA,KAAMQ,MAAM,CAACF,IAAI,mBAAIA;;IAC9B,4EAA4E;IAC5E,MAAMG,UAAUf,IAAIgB,MAAM,KAAK,kBAAkBvD,WAAW,QAAQ,CAACZ,uBAAuBmD;IAC5F,wEAAwE;IACxE,wEAAwE;IACxE,2EAA2E;IAC3E,sEAAsE;IACtE,6DAA6D;IAC7D,MAAMiB,QAAQX,QAAQS,WAAWtD,OAAOC,MAAM,KAAK,SAASnB,iBAAiByD,IAAIkB,IAAI,EAAEZ,QAAQ;IAC/F,yEAAyE;IACzE,oEAAoE;IACpE,MAAMa,sBAAc1D,0BAAAA,OAAQ0D,WAAW,mBAAI;IAC3C,MAAMC,SACJd,QAAQW,SAASjB,IAAIkB,IAAI,KAAK,UAAUzE,mBAAmBwE,OAAOE,aAAa7E,eAAegE,SAAS;IACzG,MAAMe,gBAAgBrB,IAAIkB,IAAI,KAAK,WAAWlB,IAAIsB,YAAY,KAAK,QAAQP,WAAWtD,OAAOC,MAAM,KAAK;IACxG,MAAM6D,aAAaF,iBAAiBZ,UAAU;QAAEA,SAAS;IAAK,IAAI,CAAC;IACnE,qBACE,MAACzD;QAAIsC,IAAI;YAAEkC,IAAI;QAAE;;YACdlB,sBACC,MAACjD;gBAAMoE,SAAS;gBAAKC,MAAK;gBAAOC,cAAW;;kCAC1C,KAACrE;wBAAWsE,SAAQ;wBAAUC,OAAM;kCACjCvB,KAAKU,MAAM,KAAK,cAAc,mBAAmB;;oBAEnDV,KAAKwB,KAAK,CAAC9C,GAAG,CAAC,CAAC+C,OAAOC,sBACtB,MAAC1E;4BAAkCsE,SAAQ;4BAAQF,MAAK;;gCAAW;gCACrDK,MAAMb,IAAI,KAAK,WAAW,SAASa,MAAMb,IAAI;gCAAC;gCAAExB,MAAMqC,MAAMpD,EAAE;gCAAE;gCAAIoD,MAAME,OAAO;;2BAD9E,CAAC,MAAM,EAAED,OAAO;oBAIlC1B,KAAK4B,MAAM,CAAClD,GAAG,CAAC,CAAC+C,OAAOC,sBACvB,MAAC1E;4BAAmCsE,SAAQ;4BAAQF,MAAK;;gCAAW;gCACrDvF,4BAA4B,CAAC4F,MAAMb,IAAI,CAAC,CAACiB,IAAI;gCAAC;gCAAEJ,MAAM9C,IAAI;gCACtE8C,MAAMK,WAAW,GACd,CAAC,iBAAiB,EAAE1C,MAAMqC,MAAMK,WAAW,GAAG,GAC9C;gCAAI;gCAAI;gCACTL,MAAMM,GAAG;;2BALG,CAAC,OAAO,EAAEL,OAAO;oBAQnC1B,KAAKgC,OAAO,CAACtD,GAAG,CAAC,CAACuD,QAAQP,sBACzB,MAAC1E;4BAAmCsE,SAAQ;4BAAQF,MAAK;;gCACtDa,OAAOC,MAAM,GACV,CAAC,2BAA2B,EAAED,OAAOE,KAAK,CAAC,uBAAuB,EAAE/C,MAAM6C,OAAOC,MAAM,CAACE,OAAO,EAAE,KAAK,EAAEH,OAAOC,MAAM,CAACG,IAAI,CAAC,EAAE,CAAC,GAC9H,CAAC,gBAAgB,EAAEJ,OAAOE,KAAK,CAAC,IAAI,EAAEF,OAAOK,IAAI,EAAE;gCACtDL,OAAOH,WAAW,GACf,CAAC,iBAAiB,EAAE1C,MAAM6C,OAAOH,WAAW,GAAG,GAC/C;gCACHG,OAAOM,MAAM,GAAG,CAAC,IAAI,EAAEnD,MAAM6C,OAAOM,MAAM,GAAG,GAAG;gCAChDN,OAAOO,QAAQ,CAAC7E,MAAM,GACnB,CAAC,EAAE,EAAEsE,OAAOO,QAAQ,CAAC9D,GAAG,CAAC,CAAC+D,UAAYA,QAAQ9D,IAAI,EAAEf,IAAI,CAAC,OAAO,GAChE;;2BAVW,CAAC,OAAO,EAAE8D,OAAO;oBAanC,EAAC1B,cAAAA,KAAK0C,KAAK,YAAV1C,cAAc,EAAE,EAAEtB,GAAG,CAAC,CAACiE,qBACvB,MAAC3F;4BAAqCsE,SAAQ;4BAAQF,MAAK;;gCACxD,CAAC,UAAU,EAAE9E,cAAcqG,KAAKC,EAAE,EAAEC,WAAW,GAAG,CAAC,EAAEF,KAAKhE,IAAI,EAAE;gCAChEgE,KAAKZ,GAAG,GAAG,CAAC,GAAG,EAAEY,KAAKZ,GAAG,EAAE,GAAG;;2BAFhB,CAAC,KAAK,EAAEY,KAAKG,IAAI,EAAE;oBAKrC,EAAC9C,eAAAA,KAAK+C,MAAM,YAAX/C,eAAe,EAAE,EAAEtB,GAAG,CAAC,CAACS,OAAOuC,sBAC/B,KAAC1E;4BAAkCsE,SAAQ;4BAAQF,MAAK;sCACrDlC,gBAAgBC,OAAOC;2BADT,CAAC,MAAM,EAAEsC,OAAO;;;YAWtC7B,UAAS1C,0BAAAA,OAAQ6F,MAAM,mBACtB,KAAChG;gBAAWsE,SAAQ;gBAAUC,OAAM;gBAAiB0B,WAAU;gBAAMjE,IAAI;oBAAEkC,IAAI;gBAAI;0BAChF/D,OAAO6F,MAAM;;YAGjB7F,CAAAA,0BAAAA,OAAQC,MAAM,MAAK,cAAcD,OAAOG,QAAQ,CAACK,MAAM,GAAG,KAAMkC,CAAAA,SAAS,CAAC1C,OAAO6F,MAAM,AAAD,mBACrF,KAACtG;gBACCuG,WAAU;gBACVjE,IAAI;oBAAEkE,IAAI;oBAAKC,IAAI;gBAAI;gBACvB9B,cAAW;0BAEVlE,OAAOG,QAAQ,CAACoB,GAAG,CAAC,CAAClB,SAASkE,sBAC7B,KAAC1E;wBAECiG,WAAU;wBACV3B,SAAQ;kCAEP9D,QAAQ4F,OAAO;uBAJX,CAAC,QAAQ,EAAE1B,OAAO;;YAS9BrB,QAAQ1C,MAAM,GAAG,mBAChB,MAACjB;gBAAIsC,IAAI;oBAAEkC,IAAI;gBAAI;;kCACjB,KAACvE;wBACC0G,MAAK;wBACLC,iBAAerD;wBACfsD,SAAS,IAAMrD,eAAe,CAACsD,QAAU,CAACA;kCAEzCvD,cAAc,0BAA0B;;kCAE3C,KAACpD;wBAAS4G,IAAIxD;wBAAayD,aAAa;kCACtC,cAAA,KAAC1G;4BACCsE,SAAQ;4BACR2B,WAAU;4BACV5B,cAAW;4BACXrC,IAAI;gCAAE2E,GAAG;gCAAGC,YAAY;gCAAaC,YAAY;gCAAOC,WAAW;4BAAO;sCAEzEzD,QAAQzC,IAAI,CAAC;;;;;YAKrB+C,SAASA,MAAMoD,OAAO,GAAG,mBACxB,MAAC/G;gBACCsE,SAAQ;gBACRC,OAAM;gBACNvC,IAAI;oBAAEgF,SAAS;oBAAS9C,IAAI;gBAAE;;oBAE7B,CAAC,gBAAgB,EAAEhF,kBAAkByE,OAAO,wCAAwC,CAAC;oBACrFE,eAAe,CAACC,SACb,CAAC,UAAU,EAAED,YAAYoD,IAAI,CAACC,cAAc,CAAC,SAAS,gDAAgD,EAAE9H,wBAAwByE,YAAYsD,QAAQ,EAAE,CAAC,CAAC,GACxJ;;;YAGP1D,WAAWtD,OAAOiH,YAAY,IAAIjH,OAAOC,MAAM,KAAK,wBACnD,KAACJ;gBAAWsE,SAAQ;gBAAUC,OAAM;gBAAiB0B,WAAU;gBAAMjE,IAAI;oBAAEkC,IAAI;gBAAE;0BAC9E/D,OAAOiH,YAAY;;YAGvBrD,+BACC,KAACjE;gBACCkC,IAAI;oBAAEgF,SAAS;oBAAQ9C,IAAI;gBAAE;gBAC7BmD,uBAAS,KAACzH;oBAASyG,MAAK;oBAAQiB,SAASnE;oBAASoE,UAAU,CAACC,QAAUpE,WAAWoE,MAAMC,MAAM,CAACH,OAAO;;gBACtGI,qBAAO,KAAC1H;oBAAWsE,SAAQ;8BAAS;;;YAGvCb,WAAWK,wBACV,KAACzE;gBACCyE,QAAQA;gBACRe,MAAK;gBACL9B,SAASA;gBACTH,MAAMA;gBACN+E,iBAAiB,IAAMhF,SAASD,KAAK,aAAKuB;wBAAY2D,kBAAkB;;gBACxEC,WAAW,IAAMlF,SAASD,KAAK,aAAKuB;wBAAY2D,kBAAkB;wBAAME,QAAQ;;;YAGnFrE,WAAW,CAACK,wBACX,KAACnE;gBACC0G,MAAK;gBACL/B,SAAQ;gBACR,8DAA8D;gBAC9D,yCAAyC;gBACzCyD,UAAUnF,QAASzC,OAAOC,MAAM,KAAK,UAAU4H,QAAQ7H,OAAOiH,YAAY;gBAC1Eb,SAAS,IAAM5D,SAASD,KAAKqB,iBAAiBZ,UAAUc,aAAagE;gBACrEjG,IAAI;oBAAEkC,IAAI;gBAAE;0BAEX/D,OAAOC,MAAM,KAAK,SAAS,iBAAiB;;YAIhDqD,WAAWX,QAAQtD,6BAA6BkD,sBAAQ,KAACjD;gBAAsBiD,KAAKA;gBAAKI,MAAMA;;;;AAGtG;AAEA,eAAeL,UAAS"}
@@ -1,4 +1,5 @@
1
1
  import { type MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token';
2
+ import { type AiCreditsPrompt } from '../model/ai-credit-estimate';
2
3
  import type { AiJobSummary } from '../model/ai-jobs.types';
3
4
  import { type AssistBuildProposal } from '../model/assist-build';
4
5
  /**
@@ -15,6 +16,11 @@ import { type AssistBuildProposal } from '../model/assist-build';
15
16
  export interface AiJobDecision {
16
17
  job: AiJobSummary | null;
17
18
  error: string | null;
19
+ /**
20
+ * A Free job past what is left (AGL-3722): the door's 409 asks for the
21
+ * person's go-ahead with these figures and choices instead of starting it.
22
+ */
23
+ credits?: AiCreditsPrompt | null;
18
24
  }
19
25
  /**
20
26
  * What a resume asks beyond confirming (AGL-3616): a build's plan confirmed
@@ -23,6 +29,10 @@ export interface AiJobDecision {
23
29
  export interface AiJobResumeOptions {
24
30
  publish?: boolean;
25
31
  retry?: 'failed-items';
32
+ /** "Build what fits" (AGL-3722): a Free job past what is left, started on the person's go-ahead. */
33
+ creditsConfirmed?: boolean;
34
+ /** "Build the home page first" (AGL-3722): the plan narrowed to its home page and what it needs. */
35
+ reduce?: 'first-page';
26
36
  }
27
37
  /**
28
38
  * Confirms a plan, or tries again a step whose answer broke a building rule
@@ -16,6 +16,7 @@
16
16
  */ 'use client';
17
17
  import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
19
+ import { AI_CREDITS_CONFIRM_CODE } from "../model/ai-credit-estimate.js";
19
20
  import { assistBuildJobRequest } from "../model/assist-build.js";
20
21
  import { publishAiJob } from "./ai-jobs-store.js";
21
22
  /**
@@ -23,7 +24,7 @@ import { publishAiJob } from "./ai-jobs-store.js";
23
24
  * (AGL-2935). The door runs the next step inline and answers with the job.
24
25
  */ export async function resumeAiJobRequest(user, orgId, job, options = {}) {
25
26
  try {
26
- var _ref, _ref1;
27
+ var _ref, _ref1, _ref2;
27
28
  const response = await authorizedFetch(user, `/api/ai/jobs/${encodeURIComponent(job.id)}/resume`, {
28
29
  method: 'POST',
29
30
  headers: {
@@ -36,15 +37,22 @@ import { publishAiJob } from "./ai-jobs-store.js";
36
37
  publish: true
37
38
  } : {}, options.retry ? {
38
39
  retry: options.retry
40
+ } : {}, options.creditsConfirmed ? {
41
+ creditsConfirmed: true
42
+ } : {}, options.reduce ? {
43
+ reduce: options.reduce
39
44
  } : {}))
40
45
  });
41
46
  const payload = await response.json().catch(()=>null);
42
47
  const next = (_ref = payload == null ? void 0 : payload.job) != null ? _ref : null;
43
48
  publishAiJob(next);
44
- return {
49
+ const credits = response.status === 409 && (payload == null ? void 0 : payload.code) === AI_CREDITS_CONFIRM_CODE ? (_ref1 = payload == null ? void 0 : payload.credits) != null ? _ref1 : null : null;
50
+ return _extends({
45
51
  job: next,
46
- error: response.ok ? null : String((_ref1 = payload == null ? void 0 : payload.error) != null ? _ref1 : 'The job could not be resumed — try again.')
47
- };
52
+ error: response.ok ? null : String((_ref2 = payload == null ? void 0 : payload.error) != null ? _ref2 : 'The job could not be resumed — try again.')
53
+ }, credits ? {
54
+ credits
55
+ } : {});
48
56
  } catch (unused) {
49
57
  return {
50
58
  job: null,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-requests.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { authorizedFetch, type MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { assistBuildJobRequest, type AssistBuildProposal } from '../model/assist-build'\nimport { publishAiJob } from './ai-jobs-store'\n\n/**\n * The two decisions a person makes about a job — confirm its plan (or try a\n * refused step again), and cancel it — as ONE request each, whichever\n * surface they press it on (AGL-3593). The AI jobs drawer and the dialog that\n * started the job both call these, so confirming in the dialog is the same\n * door, the same body and the same answer as confirming in the drawer.\n *\n * Each hands the job it gets back to every counting surface, so the top-bar\n * indicator stops saying \"plan ready\" the moment the plan is confirmed.\n */\n\n/** A door's answer: the job as it now stands, and the refusal when there is one. */\nexport interface AiJobDecision {\n job: AiJobSummary | null\n error: string | null\n}\n\n/**\n * What a resume asks beyond confirming (AGL-3616): a build's plan confirmed\n * with its publish box ticked, or a finished build's failed items tried again.\n */\nexport interface AiJobResumeOptions {\n publish?: boolean\n retry?: 'failed-items'\n}\n\n/**\n * Confirms a plan, or tries again a step whose answer broke a building rule\n * (AGL-2935). The door runs the next step inline and answers with the job.\n */\nexport async function resumeAiJobRequest(\n user: MaybeTokenSource,\n orgId: string,\n job: Pick<AiJobSummary, 'id' | 'hostId'>,\n options: AiJobResumeOptions = {},\n): Promise<AiJobDecision> {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs/${encodeURIComponent(job.id)}/resume`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId: job.hostId,\n ...(options.publish ? { publish: true } : {}),\n ...(options.retry ? { retry: options.retry } : {}),\n }),\n },\n )\n const payload = await response.json().catch(() => null)\n const next = (payload?.job as AiJobSummary | undefined) ?? null\n publishAiJob(next)\n return {\n job: next,\n error: response.ok\n ? null\n : String(payload?.error ?? 'The job could not be resumed — try again.'),\n }\n } catch {\n return { job: null, error: 'The job could not be resumed — try again.' }\n }\n}\n\n/**\n * Starts the build a chat turn proposed (AGL-3616): one `build` job on the\n * proposal's site, through the same door every job is created by, which\n * climbs its own gates and plans inline. What comes back stops at its plan\n * card; nothing is built until the person confirms it.\n */\nexport async function startAssistBuildRequest(\n user: MaybeTokenSource,\n orgId: string,\n proposal: AssistBuildProposal,\n): Promise<AiJobDecision> {\n const failed = 'The build could not be planned — try asking again.'\n try {\n const response = await authorizedFetch(user, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(assistBuildJobRequest(orgId, proposal)),\n })\n const payload = await response.json().catch(() => null)\n const next = (payload?.job as AiJobSummary | undefined) ?? null\n if (response.ok && next) publishAiJob(next)\n return {\n job: response.ok ? next : null,\n error: response.ok && next ? null : String(payload?.error ?? failed),\n }\n } catch {\n return { job: null, error: failed }\n }\n}\n\n/** Cancels a job that has not settled. */\nexport async function cancelAiJobRequest(\n user: MaybeTokenSource,\n orgId: string,\n jobId: string,\n): Promise<AiJobDecision> {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs/${encodeURIComponent(jobId)}/cancel`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId }),\n },\n )\n const payload = await response.json().catch(() => null)\n const next = (payload?.job as AiJobSummary | undefined) ?? null\n publishAiJob(next)\n return {\n job: next,\n error: response.ok\n ? null\n : String(payload?.error ?? 'The job could not be canceled — try again.'),\n }\n } catch {\n return { job: null, error: 'The job could not be canceled — try again.' }\n }\n}\n"],"names":["authorizedFetch","assistBuildJobRequest","publishAiJob","resumeAiJobRequest","user","orgId","job","options","response","encodeURIComponent","id","method","headers","body","JSON","stringify","hostId","publish","retry","payload","json","catch","next","error","ok","String","startAssistBuildRequest","proposal","failed","cancelAiJobRequest","jobId"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,eAAe,QAA+B,2CAA0C;AAEjG,SAASC,qBAAqB,QAAkC,2BAAuB;AACvF,SAASC,YAAY,QAAQ,qBAAiB;AA4B9C;;;CAGC,GACD,OAAO,eAAeC,mBACpBC,IAAsB,EACtBC,KAAa,EACbC,GAAwC,EACxCC,UAA8B,CAAC,CAAC;IAEhC,IAAI;;QACF,MAAMC,WAAW,MAAMR,gBACrBI,MACA,CAAC,aAAa,EAAEK,mBAAmBH,IAAII,EAAE,EAAE,OAAO,CAAC,EACnD;YACEC,QAAQ;YACRC,SAAS;gBAAE,gBAAgB;YAAmB;YAC9CC,MAAMC,KAAKC,SAAS,CAAC;gBACnBV;gBACAW,QAAQV,IAAIU,MAAM;eACdT,QAAQU,OAAO,GAAG;gBAAEA,SAAS;YAAK,IAAI,CAAC,GACvCV,QAAQW,KAAK,GAAG;gBAAEA,OAAOX,QAAQW,KAAK;YAAC,IAAI,CAAC;QAEpD;QAEF,MAAMC,UAAU,MAAMX,SAASY,IAAI,GAAGC,KAAK,CAAC,IAAM;QAClD,MAAMC,eAAQH,2BAAAA,QAASb,GAAG,mBAAiC;QAC3DJ,aAAaoB;QACb,OAAO;YACLhB,KAAKgB;YACLC,OAAOf,SAASgB,EAAE,GACd,OACAC,gBAAON,2BAAAA,QAASI,KAAK,oBAAI;QAC/B;IACF,EAAE,eAAM;QACN,OAAO;YAAEjB,KAAK;YAAMiB,OAAO;QAA4C;IACzE;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAeG,wBACpBtB,IAAsB,EACtBC,KAAa,EACbsB,QAA6B;IAE7B,MAAMC,SAAS;IACf,IAAI;;QACF,MAAMpB,WAAW,MAAMR,gBAAgBI,MAAM,gBAAgB;YAC3DO,QAAQ;YACRC,SAAS;gBAAE,gBAAgB;YAAmB;YAC9CC,MAAMC,KAAKC,SAAS,CAACd,sBAAsBI,OAAOsB;QACpD;QACA,MAAMR,UAAU,MAAMX,SAASY,IAAI,GAAGC,KAAK,CAAC,IAAM;QAClD,MAAMC,eAAQH,2BAAAA,QAASb,GAAG,mBAAiC;QAC3D,IAAIE,SAASgB,EAAE,IAAIF,MAAMpB,aAAaoB;QACtC,OAAO;YACLhB,KAAKE,SAASgB,EAAE,GAAGF,OAAO;YAC1BC,OAAOf,SAASgB,EAAE,IAAIF,OAAO,OAAOG,gBAAON,2BAAAA,QAASI,KAAK,oBAAIK;QAC/D;IACF,EAAE,eAAM;QACN,OAAO;YAAEtB,KAAK;YAAMiB,OAAOK;QAAO;IACpC;AACF;AAEA,wCAAwC,GACxC,OAAO,eAAeC,mBACpBzB,IAAsB,EACtBC,KAAa,EACbyB,KAAa;IAEb,IAAI;;QACF,MAAMtB,WAAW,MAAMR,gBACrBI,MACA,CAAC,aAAa,EAAEK,mBAAmBqB,OAAO,OAAO,CAAC,EAClD;YACEnB,QAAQ;YACRC,SAAS;gBAAE,gBAAgB;YAAmB;YAC9CC,MAAMC,KAAKC,SAAS,CAAC;gBAAEV;YAAM;QAC/B;QAEF,MAAMc,UAAU,MAAMX,SAASY,IAAI,GAAGC,KAAK,CAAC,IAAM;QAClD,MAAMC,eAAQH,2BAAAA,QAASb,GAAG,mBAAiC;QAC3DJ,aAAaoB;QACb,OAAO;YACLhB,KAAKgB;YACLC,OAAOf,SAASgB,EAAE,GACd,OACAC,gBAAON,2BAAAA,QAASI,KAAK,oBAAI;QAC/B;IACF,EAAE,eAAM;QACN,OAAO;YAAEjB,KAAK;YAAMiB,OAAO;QAA6C;IAC1E;AACF"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-job-requests.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { authorizedFetch, type MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport { AI_CREDITS_CONFIRM_CODE, type AiCreditsPrompt } from '../model/ai-credit-estimate'\nimport type { AiJobSummary } from '../model/ai-jobs.types'\nimport { assistBuildJobRequest, type AssistBuildProposal } from '../model/assist-build'\nimport { publishAiJob } from './ai-jobs-store'\n\n/**\n * The two decisions a person makes about a job — confirm its plan (or try a\n * refused step again), and cancel it — as ONE request each, whichever\n * surface they press it on (AGL-3593). The AI jobs drawer and the dialog that\n * started the job both call these, so confirming in the dialog is the same\n * door, the same body and the same answer as confirming in the drawer.\n *\n * Each hands the job it gets back to every counting surface, so the top-bar\n * indicator stops saying \"plan ready\" the moment the plan is confirmed.\n */\n\n/** A door's answer: the job as it now stands, and the refusal when there is one. */\nexport interface AiJobDecision {\n job: AiJobSummary | null\n error: string | null\n /**\n * A Free job past what is left (AGL-3722): the door's 409 asks for the\n * person's go-ahead with these figures and choices instead of starting it.\n */\n credits?: AiCreditsPrompt | null\n}\n\n/**\n * What a resume asks beyond confirming (AGL-3616): a build's plan confirmed\n * with its publish box ticked, or a finished build's failed items tried again.\n */\nexport interface AiJobResumeOptions {\n publish?: boolean\n retry?: 'failed-items'\n /** \"Build what fits\" (AGL-3722): a Free job past what is left, started on the person's go-ahead. */\n creditsConfirmed?: boolean\n /** \"Build the home page first\" (AGL-3722): the plan narrowed to its home page and what it needs. */\n reduce?: 'first-page'\n}\n\n/**\n * Confirms a plan, or tries again a step whose answer broke a building rule\n * (AGL-2935). The door runs the next step inline and answers with the job.\n */\nexport async function resumeAiJobRequest(\n user: MaybeTokenSource,\n orgId: string,\n job: Pick<AiJobSummary, 'id' | 'hostId'>,\n options: AiJobResumeOptions = {},\n): Promise<AiJobDecision> {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs/${encodeURIComponent(job.id)}/resume`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n orgId,\n hostId: job.hostId,\n ...(options.publish ? { publish: true } : {}),\n ...(options.retry ? { retry: options.retry } : {}),\n ...(options.creditsConfirmed ? { creditsConfirmed: true } : {}),\n ...(options.reduce ? { reduce: options.reduce } : {}),\n }),\n },\n )\n const payload = await response.json().catch(() => null)\n const next = (payload?.job as AiJobSummary | undefined) ?? null\n publishAiJob(next)\n const credits =\n response.status === 409 && payload?.code === AI_CREDITS_CONFIRM_CODE\n ? ((payload?.credits as AiCreditsPrompt | undefined) ?? null)\n : null\n return {\n job: next,\n error: response.ok\n ? null\n : String(payload?.error ?? 'The job could not be resumed — try again.'),\n ...(credits ? { credits } : {}),\n }\n } catch {\n return { job: null, error: 'The job could not be resumed — try again.' }\n }\n}\n\n/**\n * Starts the build a chat turn proposed (AGL-3616): one `build` job on the\n * proposal's site, through the same door every job is created by, which\n * climbs its own gates and plans inline. What comes back stops at its plan\n * card; nothing is built until the person confirms it.\n */\nexport async function startAssistBuildRequest(\n user: MaybeTokenSource,\n orgId: string,\n proposal: AssistBuildProposal,\n): Promise<AiJobDecision> {\n const failed = 'The build could not be planned — try asking again.'\n try {\n const response = await authorizedFetch(user, '/api/ai/jobs', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(assistBuildJobRequest(orgId, proposal)),\n })\n const payload = await response.json().catch(() => null)\n const next = (payload?.job as AiJobSummary | undefined) ?? null\n if (response.ok && next) publishAiJob(next)\n return {\n job: response.ok ? next : null,\n error: response.ok && next ? null : String(payload?.error ?? failed),\n }\n } catch {\n return { job: null, error: failed }\n }\n}\n\n/** Cancels a job that has not settled. */\nexport async function cancelAiJobRequest(\n user: MaybeTokenSource,\n orgId: string,\n jobId: string,\n): Promise<AiJobDecision> {\n try {\n const response = await authorizedFetch(\n user,\n `/api/ai/jobs/${encodeURIComponent(jobId)}/cancel`,\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId }),\n },\n )\n const payload = await response.json().catch(() => null)\n const next = (payload?.job as AiJobSummary | undefined) ?? null\n publishAiJob(next)\n return {\n job: next,\n error: response.ok\n ? null\n : String(payload?.error ?? 'The job could not be canceled — try again.'),\n }\n } catch {\n return { job: null, error: 'The job could not be canceled — try again.' }\n }\n}\n"],"names":["authorizedFetch","AI_CREDITS_CONFIRM_CODE","assistBuildJobRequest","publishAiJob","resumeAiJobRequest","user","orgId","job","options","response","encodeURIComponent","id","method","headers","body","JSON","stringify","hostId","publish","retry","creditsConfirmed","reduce","payload","json","catch","next","credits","status","code","error","ok","String","startAssistBuildRequest","proposal","failed","cancelAiJobRequest","jobId"],"mappings":"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"}
@@ -33,8 +33,10 @@ import { AI_JOB_TERMINAL_STATUSES } from "../model/ai-jobs.types.js";
33
33
  import { followAiJobEvents } from "./ai-job-events.js";
34
34
  import { aiCreditsBillingHref, aiSiteBuildDoneLinks } from "./ai-job-links.js";
35
35
  import { AiJobPlan } from "./ai-job-plan.component.js";
36
- import { aiBuildCanRetry } from "../model/ai-build-progress.js";
36
+ import { aiBuildCanRetry, aiBuildRetryCreditRange } from "../model/ai-build-progress.js";
37
37
  import { resumeAiJobRequest } from "./ai-job-requests.js";
38
+ import { AiCreditsPromptNotice } from "./ai-credits-prompt.component.js";
39
+ import { aiCreditRangeText } from "../model/ai-credit-estimate.js";
38
40
  import { useAiJobSite } from "./ai-job-site.js";
39
41
  import { AiSiteStarterFallback } from "./ai-site-starter-fallback.component.js";
40
42
  /**
@@ -235,16 +237,21 @@ export function AiSiteBuildPage({ hostId, segments, basePath }) {
235
237
  // A failed guided start's Try again reopens its questions, filled in (AGL-3596);
236
238
  // `?retry=1` — what the AI jobs drawer links — opens them on arrival.
237
239
  const [retrying, setRetrying] = useState(()=>typeof window !== 'undefined' && new URLSearchParams(window.location.search).get('retry') === '1');
238
- const tryAgain = useCallback(async ()=>{
240
+ // A Free Try again past what is left (AGL-3722): its prompt, until chosen.
241
+ const [retryPrompt, setRetryPrompt] = useState(null);
242
+ const tryAgain = useCallback(async (options = {})=>{
239
243
  if (!orgId || !job || typeof job !== 'object') return;
240
244
  setBusy(true);
241
245
  setNotice(null);
246
+ setRetryPrompt(null);
242
247
  // A finished build tries again only what failed (AGL-3616).
243
- const decision = await resumeAiJobRequest(user, orgId, job, aiBuildCanRetry(job) ? {
248
+ const retry = aiBuildCanRetry(job);
249
+ const decision = await resumeAiJobRequest(user, orgId, job, retry ? _extends({
244
250
  retry: 'failed-items'
245
- } : {});
251
+ }, options) : {});
246
252
  if (decision.job) setJob(decision.job);
247
- if (decision.error) setNotice(decision.error);
253
+ if (decision.credits && retry) setRetryPrompt(decision.credits);
254
+ else if (decision.error) setNotice(decision.error);
248
255
  setBusy(false);
249
256
  }, [
250
257
  orgId,
@@ -258,7 +265,8 @@ export function AiSiteBuildPage({ hostId, segments, basePath }) {
258
265
  setNotice(null);
259
266
  const decision = await resumeAiJobRequest(user, orgId, target, options);
260
267
  if (decision.job) setJob(decision.job);
261
- if (decision.error) setNotice(decision.error);
268
+ // A plan past what is left draws its own prompt from the job (AGL-3722).
269
+ if (decision.error && !(decision.credits && target.status === 'needs_review')) setNotice(decision.error);
262
270
  setBusy(false);
263
271
  }, [
264
272
  orgId,
@@ -350,6 +358,7 @@ export function AiSiteBuildPage({ hostId, segments, basePath }) {
350
358
  const paused = ready.status === 'needs_input';
351
359
  const retryRefusal = (_ready_review = ready.review) == null ? void 0 : _ready_review.retryRefusal;
352
360
  const buildRetry = aiBuildCanRetry(ready);
361
+ const retryRange = buildRetry ? aiBuildRetryCreditRange(ready) : null;
353
362
  const canRetry = phase === 'stopped' && ((_ready_review1 = ready.review) == null ? void 0 : _ready_review1.reason) === 'doctrine' || buildRetry;
354
363
  const buildPlanWaiting = ready.kind === 'build' && phase === 'stopped' && ((_ready_review2 = ready.review) == null ? void 0 : _ready_review2.reason) === 'plan';
355
364
  // A guided start that ended without building its site starts over from its
@@ -503,6 +512,7 @@ export function AiSiteBuildPage({ hostId, segments, basePath }) {
503
512
  /*#__PURE__*/ _jsx(AiJobPlan, {
504
513
  job: ready,
505
514
  busy: busy,
515
+ orgSlug: orgSlug,
506
516
  onResume: (target, options)=>void confirmPlan(target, options)
507
517
  }) : null,
508
518
  sitePublish && sitePublish.drafts.length > 0 && // The pages the publish left as drafts, each with its plain reason;
@@ -544,9 +554,18 @@ export function AiSiteBuildPage({ hostId, segments, basePath }) {
544
554
  variant: "outlined",
545
555
  disabled: busy,
546
556
  onClick: ()=>void tryAgain(),
547
- children: 'Try again what failed'
557
+ children: retryRange ? `Try again what failed · ${aiCreditRangeText(retryRange)}` : 'Try again what failed'
548
558
  })
549
559
  }),
560
+ retryPrompt && /*#__PURE__*/ _jsx(AiCreditsPromptNotice, {
561
+ prompt: retryPrompt,
562
+ noun: ready.kind === 'site' ? 'site' : 'build',
563
+ orgSlug: orgSlug,
564
+ busy: busy,
565
+ onBuildWhatFits: ()=>void tryAgain({
566
+ creditsConfirmed: true
567
+ })
568
+ }),
550
569
  phase === 'done' && !liveNotice && /*#__PURE__*/ _jsxs(Stack, {
551
570
  direction: {
552
571
  xs: 'column',