@aglyn/plugins-ai 1.0.0-beta.264 → 1.0.0-beta.268

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 (124) hide show
  1. package/package.json +18 -18
  2. package/src/lib/assets/site-styles/beauty.webp +0 -0
  3. package/src/lib/assets/site-styles/blog.webp +0 -0
  4. package/src/lib/assets/site-styles/business.webp +0 -0
  5. package/src/lib/assets/site-styles/coming-soon.webp +0 -0
  6. package/src/lib/assets/site-styles/education.webp +0 -0
  7. package/src/lib/assets/site-styles/events.webp +0 -0
  8. package/src/lib/assets/site-styles/fitness.webp +0 -0
  9. package/src/lib/assets/site-styles/landing.webp +0 -0
  10. package/src/lib/assets/site-styles/music.webp +0 -0
  11. package/src/lib/assets/site-styles/nonprofit.webp +0 -0
  12. package/src/lib/assets/site-styles/personal.webp +0 -0
  13. package/src/lib/assets/site-styles/photography.webp +0 -0
  14. package/src/lib/assets/site-styles/portfolio.webp +0 -0
  15. package/src/lib/assets/site-styles/professional.webp +0 -0
  16. package/src/lib/assets/site-styles/realestate.webp +0 -0
  17. package/src/lib/assets/site-styles/restaurant.webp +0 -0
  18. package/src/lib/assets/site-styles/store.webp +0 -0
  19. package/src/lib/assets/site-styles/studio.webp +0 -0
  20. package/src/lib/assets/site-styles/trades.webp +0 -0
  21. package/src/lib/assets/site-styles/wellness.webp +0 -0
  22. package/src/lib/assets/site-styles/yoga.webp +0 -0
  23. package/src/lib/components/ai-job-site.d.ts +8 -1
  24. package/src/lib/components/ai-job-site.js +7 -0
  25. package/src/lib/components/ai-job-site.js.map +1 -1
  26. package/src/lib/components/ai-site-build-page.component.js +37 -0
  27. package/src/lib/components/ai-site-build-page.component.js.map +1 -1
  28. package/src/lib/components/ai-site-start-card.component.d.ts +12 -0
  29. package/src/lib/components/ai-site-start-card.component.js +279 -240
  30. package/src/lib/components/ai-site-start-card.component.js.map +1 -1
  31. package/src/lib/components/ai-site-start-steps.component.d.ts +114 -0
  32. package/src/lib/components/ai-site-start-steps.component.js +480 -0
  33. package/src/lib/components/ai-site-start-steps.component.js.map +1 -0
  34. package/src/lib/components/ai-site-start-tracking.d.ts +122 -0
  35. package/src/lib/components/ai-site-start-tracking.js +296 -0
  36. package/src/lib/components/ai-site-start-tracking.js.map +1 -0
  37. package/src/lib/components/ai-site-style-previews.d.ts +26 -0
  38. package/src/lib/components/ai-site-style-previews.js +115 -0
  39. package/src/lib/components/ai-site-style-previews.js.map +1 -0
  40. package/src/lib/components/assist-jobs-drawer.component.js +24 -2
  41. package/src/lib/components/assist-jobs-drawer.component.js.map +1 -1
  42. package/src/lib/insights/ai-insight-digest.d.ts +9 -0
  43. package/src/lib/insights/ai-insight-digest.js +35 -11
  44. package/src/lib/insights/ai-insight-digest.js.map +1 -1
  45. package/src/lib/jobs/ai-empty-site-sweep.d.ts +154 -0
  46. package/src/lib/jobs/ai-empty-site-sweep.js +346 -0
  47. package/src/lib/jobs/ai-empty-site-sweep.js.map +1 -0
  48. package/src/lib/jobs/ai-job-page-language.d.ts +9 -0
  49. package/src/lib/jobs/ai-job-page-language.js +20 -2
  50. package/src/lib/jobs/ai-job-page-language.js.map +1 -1
  51. package/src/lib/jobs/ai-job-plan-step.d.ts +43 -8
  52. package/src/lib/jobs/ai-job-plan-step.js +99 -18
  53. package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
  54. package/src/lib/jobs/ai-job-site-datasets.d.ts +82 -1
  55. package/src/lib/jobs/ai-job-site-datasets.js +276 -14
  56. package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
  57. package/src/lib/jobs/ai-jobs-beat.d.ts +11 -3
  58. package/src/lib/jobs/ai-jobs-beat.js +23 -6
  59. package/src/lib/jobs/ai-jobs-beat.js.map +1 -1
  60. package/src/lib/jobs/ai-jobs.js +46 -7
  61. package/src/lib/jobs/ai-jobs.js.map +1 -1
  62. package/src/lib/jobs/ai-layout-stock-photos.d.ts +143 -10
  63. package/src/lib/jobs/ai-layout-stock-photos.js +1046 -102
  64. package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
  65. package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.d.ts +58 -0
  66. package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.js +678 -0
  67. package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.js.map +1 -0
  68. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.d.ts +33 -0
  69. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.js +270 -0
  70. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.js.map +1 -0
  71. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.d.ts +33 -0
  72. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.js +277 -0
  73. package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.js.map +1 -0
  74. package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.d.ts +35 -0
  75. package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.js +379 -0
  76. package/src/lib/jobs/fixtures/ai-stock-photo-agl3923-recordings.js.map +1 -0
  77. package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.d.ts +31 -0
  78. package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.js +208 -0
  79. package/src/lib/jobs/fixtures/ai-stock-photo-brightvale-recordings.js.map +1 -0
  80. package/src/lib/layout-language/ai-layout-compiler.js +49 -12
  81. package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
  82. package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
  83. package/src/lib/layout-language/ai-layout-design.js +19 -13
  84. package/src/lib/layout-language/ai-layout-design.js.map +1 -1
  85. package/src/lib/layout-language/ai-layout-listings.d.ts +48 -0
  86. package/src/lib/layout-language/ai-layout-listings.js +91 -5
  87. package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
  88. package/src/lib/layout-language/ai-layout-pictures.d.ts +15 -0
  89. package/src/lib/layout-language/ai-layout-pictures.js +6 -2
  90. package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
  91. package/src/lib/model/ai-jobs.types.d.ts +20 -0
  92. package/src/lib/model/ai-jobs.types.js.map +1 -1
  93. package/src/lib/model/ai-site-home-arcs.d.ts +75 -0
  94. package/src/lib/model/ai-site-home-arcs.js +741 -0
  95. package/src/lib/model/ai-site-home-arcs.js.map +1 -0
  96. package/src/lib/model/ai-site-inventory.d.ts +18 -0
  97. package/src/lib/model/ai-site-inventory.js.map +1 -1
  98. package/src/lib/model/ai-site-job.d.ts +9 -6
  99. package/src/lib/model/ai-site-job.js +8 -5
  100. package/src/lib/model/ai-site-job.js.map +1 -1
  101. package/src/lib/model/ai-site-kinds.js +24 -24
  102. package/src/lib/model/ai-site-kinds.js.map +1 -1
  103. package/src/lib/model/ai-site-start-handoff.d.ts +49 -0
  104. package/src/lib/model/ai-site-start-handoff.js +88 -0
  105. package/src/lib/model/ai-site-start-handoff.js.map +1 -0
  106. package/src/lib/model/ai-site-start-steps.d.ts +67 -0
  107. package/src/lib/model/ai-site-start-steps.js +127 -0
  108. package/src/lib/model/ai-site-start-steps.js.map +1 -0
  109. package/src/lib/model/ai-site-start.js +4 -1
  110. package/src/lib/model/ai-site-start.js.map +1 -1
  111. package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -0
  112. package/src/lib/runtime/ai-doctrine-validators.js +119 -2
  113. package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
  114. package/src/lib/runtime/ai-doctrine.d.ts +6 -1
  115. package/src/lib/runtime/ai-doctrine.js +9 -5
  116. package/src/lib/runtime/ai-doctrine.js.map +1 -1
  117. package/src/lib/runtime/ai-palette.generated.js +5 -2
  118. package/src/lib/runtime/ai-palette.generated.js.map +1 -1
  119. package/src/lib/runtime/site-inventory.js +6 -0
  120. package/src/lib/runtime/site-inventory.js.map +1 -1
  121. package/src/lib/server/ai-insight-digest-route.js +2 -2
  122. package/src/lib/server/ai-insight-digest-route.js.map +1 -1
  123. package/src/lib/usage/assist-free-taste.js +3 -0
  124. package/src/lib/usage/assist-free-taste.js.map +1 -1
@@ -15,54 +15,34 @@
15
15
  * limitations under the License.
16
16
  */ 'use client';
17
17
  import { _ as _extends } from "@swc/helpers/_/_extends";
18
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
18
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
19
  import { lockdownRefusalText, parseLockdownRefusal, PLATFORM_BRAND_NAME } from "@aglyn/aglyn";
20
20
  import { trackEvent } from "@aglyn/aglyn/app-utils/analytics-events";
21
21
  import { ICON_VARIANT_CLOSE } from "@aglyn/shared-data-enums";
22
- import { mdiAccount, mdiBookOpenPageVariant, mdiBriefcaseOutline, mdiCalendarHeart, mdiCamera, mdiClockOutline, mdiContentCut, mdiCreation, mdiDumbbell, mdiHandHeart, mdiHeartPulse, mdiHomeCity, mdiMusic, mdiPaletteOutline, mdiRocketLaunch, mdiScaleBalance, mdiSchool, mdiShopping, mdiSilverwareForkKnife, mdiViewGridOutline, mdiWrench, mdiYoga, mdiPageLayoutHeaderFooter } from "@aglyn/shared-data-mdi";
22
+ import { mdiCreation, mdiPageLayoutHeaderFooter } from "@aglyn/shared-data-mdi";
23
23
  import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
24
24
  import { HelpTip, MdiIcon } from "@aglyn/shared-ui-jsx";
25
- import { OptionCardGrid } from "@aglyn/shared-ui-jsx/components/option-card-grid.component";
26
25
  import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
27
26
  import { useUser } from "@aglyn/tenant-feature-instance";
28
- import { Alert, AppBar, Box, Button, Card, CardActionArea, Chip, CircularProgress, Container, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, IconButton, MenuItem, Stack, Step, StepLabel, Stepper, TextField, Toolbar, Typography } from "@mui/material";
29
- import { alpha } from "@mui/material/styles";
27
+ import { Alert, AppBar, Box, Button, Card, CardActionArea, Chip, CircularProgress, Container, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, IconButton, Stack, Toolbar, Typography, useMediaQuery } from "@mui/material";
28
+ import { alpha, useTheme } from "@mui/material/styles";
30
29
  import { useRouter } from "next/navigation";
31
30
  import { useCallback, useEffect, useId, useRef, useState } from "react";
32
- import { AI_JOB_AUTO_CONFIRM_INPUT, AI_SITE_FREE_PAGES, AI_SITE_FREE_PAGES_NOTE, AI_SITE_SUBMISSION_CHOICES, aiFreeCreditsNoneLeftText, aiFreeCreditsResetLabel, aiFreeSiteCreditRange, aiFreeSitePrompt, aiSiteCreditRange, aiSitePagesBand } from "../model/ai-site-job.js";
31
+ import { AI_JOB_AUTO_CONFIRM_INPUT, AI_SITE_FREE_PAGES, aiFreeCreditsNoneLeftText, aiFreeCreditsResetLabel, aiFreeSiteCreditRange, aiFreeSitePrompt, aiSiteCreditRange, aiSitePagesBand } from "../model/ai-site-job.js";
33
32
  import { FREE_AI_TASTE_CREDITS_PER_MONTH } from "../plan-entitlements.js";
34
33
  import { AI_CREDITS_CONFIRM_CODE, aiCreditRangeText } from "../model/ai-credit-estimate.js";
35
34
  import { AiCreditsPromptNotice } from "./ai-credits-prompt.component.js";
36
- import { AI_SITE_START_ANSWERS, AI_SITE_START_TYPES, aiSiteStartBrief, aiSiteStartKind, aiSiteStartInputs, aiSiteStartRefusal } from "../model/ai-site-start.js";
37
- import { AI_SITE_KINDS, aiSiteKindOfInputs } from "../model/ai-site-kinds.js";
38
- /** The icon each kind of site's card shows. */ const AI_SITE_KIND_ICONS = {
39
- business: mdiBriefcaseOutline.path,
40
- trades: mdiWrench.path,
41
- professional: mdiScaleBalance.path,
42
- wellness: mdiHeartPulse.path,
43
- restaurant: mdiSilverwareForkKnife.path,
44
- store: mdiShopping.path,
45
- portfolio: mdiViewGridOutline.path,
46
- studio: mdiPaletteOutline.path,
47
- photography: mdiCamera.path,
48
- blog: mdiBookOpenPageVariant.path,
49
- events: mdiCalendarHeart.path,
50
- fitness: mdiDumbbell.path,
51
- yoga: mdiYoga.path,
52
- beauty: mdiContentCut.path,
53
- realestate: mdiHomeCity.path,
54
- education: mdiSchool.path,
55
- nonprofit: mdiHandHeart.path,
56
- music: mdiMusic.path,
57
- personal: mdiAccount.path,
58
- landing: mdiRocketLaunch.path,
59
- 'coming-soon': mdiClockOutline.path
60
- };
35
+ import { AI_SITE_START_ANSWERS, aiSiteStartBrief, aiSiteStartInputs, aiSiteStartRefusal } from "../model/ai-site-start.js";
36
+ import { aiSiteStartFieldErrors, aiSiteStartFirstInvalidStep, aiSiteStartNextStep, aiSiteStartPreviousStep, aiSiteStartStepErrors, aiSiteStartStepIndex, AI_SITE_START_STEP_FIELDS } from "../model/ai-site-start-steps.js";
37
+ import { aiSiteKindOfInputs } from "../model/ai-site-kinds.js";
38
+ import { aiSiteStartClickOf, useAiSiteStartTracking } from "./ai-site-start-tracking.js";
39
+ import { AudienceStep, DetailsStep, KindStep, ReviewStep, StartProgress, StepHelp, StyleStep } from "./ai-site-start-steps.component.js";
61
40
  import { AiJobFollow } from "./ai-job-follow.component.js";
62
41
  import { AiModelSelector } from "./ai-model-selector.component.js";
63
42
  import { useAiModelChoice } from "./use-ai-model-choice.js";
64
43
  import { aiCreditsBillingHref, aiSiteBuildHref } from "./ai-job-links.js";
65
44
  import { publishAiJob } from "./ai-jobs-store.js";
45
+ import { clearAiSiteStartAnswers, readAiSiteStartAnswers } from "../model/ai-site-start-handoff.js";
66
46
  /** Labels the dialog for a reader, from its own heading. */ const TITLE_ID = 'ai-site-start-title';
67
47
  /**
68
48
  * The takeover's surface, for its paper, its bar and its footer alike: the
@@ -71,10 +51,6 @@ import { publishAiJob } from "./ai-jobs-store.js";
71
51
  */ export const AI_SITE_START_SURFACE_SX = {
72
52
  bgcolor: 'background.default'
73
53
  };
74
- /** The two steps, as the indicator under the header names them. */ const STEP_LABELS = [
75
- 'Choose',
76
- 'Describe'
77
- ];
78
54
  /** The soft round badge an icon sits in, in its accent's tint. */ function IconBadge({ accent, children }) {
79
55
  return /*#__PURE__*/ _jsx(Box, {
80
56
  "aria-hidden": true,
@@ -179,18 +155,6 @@ import { publishAiJob } from "./ai-jobs-store.js";
179
155
  anchor: '#how-do-you-want-to-start',
180
156
  excerpt: 'The starter site gives you a ready-made home page to edit. Start with AI asks a few questions, then plans, writes and publishes your pages.'
181
157
  }),
182
- business: pluginDocsHelp('aiStart', {
183
- anchor: '#your-business',
184
- excerpt: 'What kind of site it is picks its style and pages; who it is for is who every page and the contact form are written for.'
185
- }),
186
- style: pluginDocsHelp('aiSiteLooks', {
187
- anchor: '#choose-a-style',
188
- excerpt: 'The style decides the look — theme, colors, fonts, buttons — and which pages the site usually has. Every site gets its own variation.'
189
- }),
190
- details: pluginDocsHelp('aiStart', {
191
- anchor: '#details',
192
- excerpt: 'Where contact form messages go, how many pages to write, and on paid plans whether to draft a welcome email. All can be changed later.'
193
- }),
194
158
  estimate: pluginDocsHelp('aiCredits', {
195
159
  anchor: '#before-a-job-starts',
196
160
  excerpt: 'About what a start like this costs, measured on real builds, and the most it can cost. What it really costs is what each step spends, and you can watch that add up while it runs.'
@@ -217,8 +181,19 @@ function SectionLabel({ children, help }) {
217
181
  ]
218
182
  });
219
183
  }
220
- export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leave, pendingSite, initialAnswers = null }) {
221
- var _ref, _ref1, _aiFreeSitePrompt;
184
+ /**
185
+ * The questions are one decision a step (AGL-3898): what kind of site it is
186
+ * (typed, or a card), who it is for, the style (cards that show each one,
187
+ * "Pick for me" first and picked), the details, and a review that plans the
188
+ * site (`ai-site-start-steps.component.tsx`). Back and Next keep every
189
+ * answer; Enter in a text answer is Next; each step's heading takes the focus
190
+ * when it is drawn; a step that cannot go forward says why beside the field
191
+ * and puts the cursor in it. The answers are the same object the single form
192
+ * held, and only the review turns them into the job, through the same
193
+ * `aiSiteStartBrief` and `aiSiteStartInputs`. Each step is reported as a
194
+ * funnel, with where people struggle (`ai-site-start-tracking.ts`).
195
+ */ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leave, pendingSite, initialAnswers = null }) {
196
+ var _ref, _ref1, _ref2, _aiFreeSitePrompt;
222
197
  var _aiSiteKindOfInputs, _modelChoice_options_options_find, _modelChoice_options;
223
198
  const { data: user } = useUser();
224
199
  const router = useRouter();
@@ -228,10 +203,28 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
228
203
  userRef.current = user;
229
204
  const uid = (_ref = user == null ? void 0 : user.uid) != null ? _ref : null;
230
205
  const [verdict, setVerdict] = useState('checking');
206
+ // "Start again" on a start whose empty site was removed (AGL-3936) opens
207
+ // Create site with that start's answers: read once, and only here, where
208
+ // the site does not exist yet. It opens on the first question, where the
209
+ // site's name and address are asked, with every answer after it filled in.
210
+ const [handedOff] = useState(()=>pendingSite && !initialAnswers ? readAiSiteStartAnswers() : null);
231
211
  // Read once: a start reopened on its answers stays one while it is open.
232
- const reopened = useRef(initialAnswers !== null);
233
- const [step, setStep] = useState(initialAnswers ? 'describe' : 'choose');
234
- const [answers, setAnswers] = useState(initialAnswers != null ? initialAnswers : AI_SITE_START_ANSWERS);
212
+ const reopened = useRef(initialAnswers !== null || handedOff !== null);
213
+ // A start reopened on its answers opens on the review, every answer there to change.
214
+ const [step, setStep] = useState(initialAnswers ? 'review' : 'choose');
215
+ const [answers, setAnswers] = useState((_ref1 = initialAnswers != null ? initialAnswers : handedOff) != null ? _ref1 : AI_SITE_START_ANSWERS);
216
+ // The steps whose reasons not to go forward are shown: the ones Next was refused on.
217
+ const [checked, setChecked] = useState(()=>new Set());
218
+ // A step opened from the review's Edit goes back to the review on Next.
219
+ const [returnTo, setReturnTo] = useState(null);
220
+ // Each step's heading takes the focus when the step changes, never on the first draw.
221
+ const headingRef = useRef(null);
222
+ const drawnStep = useRef(null);
223
+ const fieldRefs = useRef({});
224
+ const theme = useTheme();
225
+ const compact = useMediaQuery(theme.breakpoints.down('sm'), {
226
+ noSsr: true
227
+ });
235
228
  const [busy, setBusy] = useState(false);
236
229
  // The starter card's own busy state while `startBlank` runs (AGL-3594).
237
230
  const [startingStarter, setStartingStarter] = useState(false);
@@ -254,6 +247,8 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
254
247
  const pendingRef = useRef(pendingSite);
255
248
  pendingRef.current = pendingSite;
256
249
  const creating = Boolean(pendingSite) && !(pendingSite == null ? void 0 : pendingSite.created);
250
+ // The funnel (AGL-3898): each step drawn, left and abandoned, and where people struggle.
251
+ const tracker = useAiSiteStartTracking(step, verdict === 'ready');
257
252
  useEffect(()=>{
258
253
  if (!orgId || !uid) return;
259
254
  let active = true;
@@ -306,7 +301,7 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
306
301
  answers.pages
307
302
  ]);
308
303
  // A store's first products and a blog's first posts are part of the start (AGL-3676, AGL-3798).
309
- const siteKind = (_ref1 = (_aiSiteKindOfInputs = aiSiteKindOfInputs(aiSiteStartInputs(answers))) == null ? void 0 : _aiSiteKindOfInputs.id) != null ? _ref1 : null;
304
+ const siteKind = (_ref2 = (_aiSiteKindOfInputs = aiSiteKindOfInputs(aiSiteStartInputs(answers))) == null ? void 0 : _aiSiteKindOfInputs.id) != null ? _ref2 : null;
310
305
  const prompt = freeTaste ? (_aiFreeSitePrompt = aiFreeSitePrompt(freeCredits, answers.pages, {
311
306
  siteKind
312
307
  })) != null ? _aiFreeSitePrompt : serverPrompt : null;
@@ -328,7 +323,7 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
328
323
  // and the list is otherwise read when the picker opens.
329
324
  const { load: loadModels, model: rememberedModel } = modelChoice;
330
325
  useEffect(()=>{
331
- if (!freeTaste && step === 'describe' && rememberedModel) loadModels();
326
+ if (!freeTaste && step === 'review' && rememberedModel) loadModels();
332
327
  }, [
333
328
  freeTaste,
334
329
  step,
@@ -425,6 +420,11 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
425
420
  }
426
421
  // The indicator and the launcher count it from the moment it exists.
427
422
  publishAiJob(created);
423
+ // A removed start's answers have been used (AGL-3936).
424
+ clearAiSiteStartAnswers();
425
+ // The funnel's last step: the site is started, and nothing after is leaving it.
426
+ tracker.complete();
427
+ tracker.finish();
428
428
  // Where the next step is (AGL-3594): the site's "Building your site"
429
429
  // page for this job. The zone closes without the starter first, so the
430
430
  // setup page does not ask again behind it.
@@ -455,7 +455,8 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
455
455
  router,
456
456
  pickedModel,
457
457
  pendingSite,
458
- siteKind
458
+ siteKind,
459
+ tracker
459
460
  ]);
460
461
  // The zone's `startBlank`. Before the site exists it creates it with the
461
462
  // starter, and answers `false` when it could not — most often a name or
@@ -478,24 +479,106 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
478
479
  trackEvent('site_start_choice', {
479
480
  choice: 'starter'
480
481
  });
482
+ // The starter is a way to start, not a way to leave: no abandonment after it.
483
+ tracker.finish();
481
484
  runStartBlank();
482
485
  }, [
483
486
  pendingSite,
484
- runStartBlank
487
+ runStartBlank,
488
+ tracker
485
489
  ]);
486
490
  const chooseAi = useCallback(()=>{
487
491
  if (pendingSite && !pendingSite.created && !pendingSite.check()) return;
488
492
  trackEvent('site_start_choice', {
489
493
  choice: 'ai'
490
494
  });
491
- setStep('describe');
495
+ tracker.complete();
496
+ setStep('kind');
492
497
  }, [
493
- pendingSite
498
+ pendingSite,
499
+ tracker
494
500
  ]);
501
+ // The heading of each step takes the focus as the step is drawn, so a
502
+ // keyboard and a screen reader start where the new question does.
503
+ useEffect(()=>{
504
+ var _headingRef_current;
505
+ if (verdict !== 'ready') return;
506
+ if (drawnStep.current !== null && drawnStep.current !== step) (_headingRef_current = headingRef.current) == null ? void 0 : _headingRef_current.focus();
507
+ drawnStep.current = step;
508
+ }, [
509
+ step,
510
+ verdict
511
+ ]);
512
+ /** Forward from a question step, when its answers may go forward. */ const next = useCallback(()=>{
513
+ if (step === 'choose' || step === 'review') return;
514
+ const errors = aiSiteStartStepErrors(step, answers, {
515
+ freeTaste
516
+ });
517
+ if (errors.length) {
518
+ var _fieldRefs_current_errors__field;
519
+ setChecked((current)=>new Set(current).add(step));
520
+ for (const error of errors)tracker.fieldError(error.field, error.code);
521
+ (_fieldRefs_current_errors__field = fieldRefs.current[errors[0].field]) == null ? void 0 : _fieldRefs_current_errors__field.focus();
522
+ return;
523
+ }
524
+ tracker.complete();
525
+ const target = returnTo != null ? returnTo : aiSiteStartNextStep(step);
526
+ setReturnTo(null);
527
+ // A review reached with an answer that cannot go forward opens on that answer instead.
528
+ if (target === 'review') {
529
+ const invalid = aiSiteStartFirstInvalidStep(answers, {
530
+ freeTaste
531
+ });
532
+ if (invalid && invalid !== 'review') {
533
+ setChecked((current)=>new Set(current).add(invalid));
534
+ setStep(invalid);
535
+ return;
536
+ }
537
+ }
538
+ if (target) setStep(target);
539
+ }, [
540
+ step,
541
+ answers,
542
+ freeTaste,
543
+ returnTo,
544
+ tracker
545
+ ]);
546
+ /** Back one step, keeping every answer. */ const back = useCallback(()=>{
547
+ const target = aiSiteStartPreviousStep(step);
548
+ if (!target) return;
549
+ tracker.back();
550
+ setReturnTo(null);
551
+ setStep(target);
552
+ }, [
553
+ step,
554
+ tracker
555
+ ]);
556
+ /** From the review, to the step that asks an answer, and back to the review on Next. */ const edit = useCallback((target)=>{
557
+ tracker.back();
558
+ setReturnTo('review');
559
+ setStep(target);
560
+ }, [
561
+ tracker
562
+ ]);
563
+ /** From the progress, back to a step already answered: from the review it is Edit. */ const goBackTo = (target)=>{
564
+ if (aiSiteStartStepIndex(target) >= aiSiteStartStepIndex(step)) return;
565
+ if (step === 'review') {
566
+ edit(target);
567
+ return;
568
+ }
569
+ tracker.back();
570
+ setReturnTo(null);
571
+ setStep(target);
572
+ };
495
573
  if (verdict !== 'ready') return null;
496
574
  // Before a job is started Skip is the blank site, which writes the starter
497
575
  // (AGL-3594); after, it only closes — the job builds the site.
498
576
  const exit = started ? leave != null ? leave : runStartBlank : runStartBlank;
577
+ // Skip, as the header draws it: before a site is started it is leaving the questions.
578
+ const skip = ()=>{
579
+ if (!started) tracker.abandon('skip');
580
+ exit();
581
+ };
499
582
  // Escape and the close control. Before "Create site" has made the site
500
583
  // (AGL-3761) there is nothing to give a starter to, so they only close,
501
584
  // and nothing is created; otherwise they are the same way out as Skip.
@@ -513,11 +596,16 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
513
596
  if (root && target instanceof Node && !root.contains(target)) return;
514
597
  }
515
598
  if (drafted) setConfirmingLeave(true);
516
- else dismissTo();
599
+ else closeTo();
600
+ };
601
+ // The dismissal itself, once nothing typed is in the way of it.
602
+ const closeTo = ()=>{
603
+ if (!started) tracker.abandon('close');
604
+ dismissTo();
517
605
  };
518
606
  const leaveDraft = ()=>{
519
607
  setConfirmingLeave(false);
520
- dismissTo();
608
+ closeTo();
521
609
  };
522
610
  // The chosen model's cost against Auto's, as the model list states it.
523
611
  const modelMultiplier = pickedModel && ((_modelChoice_options = modelChoice.options) == null ? void 0 : (_modelChoice_options_options_find = _modelChoice_options.options.find((option)=>option.id === pickedModel)) == null ? void 0 : _modelChoice_options_options_find.multiplier) || 1;
@@ -538,12 +626,71 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
538
626
  // Free workspaces — never the month's whole allowance as if none were spent.
539
627
  const estimateText = freeTaste ? freeCredits ? `${aiCreditRangeText(estimate)}. You have ${freeCredits.left.toLocaleString('en-US')} of your ${freeCredits.total.toLocaleString('en-US')} free AI credits left this month, until ${aiFreeCreditsResetLabel(freeCredits.resetsOn)}` : `${aiCreditRangeText(estimate)}, of the ${FREE_AI_TASTE_CREDITS_PER_MONTH} AI credits you get free each month` : `${aiCreditRangeText(estimate)}, estimated`;
540
628
  const choosing = step === 'choose' && !started;
629
+ const reviewing = step === 'review' && !started;
630
+ const asking = step !== 'choose' && !started;
631
+ // Each answer's reason not to go forward, shown on a step once Next was refused there.
632
+ const fieldErrors = aiSiteStartFieldErrors(answers, {
633
+ freeTaste
634
+ });
635
+ const shownErrors = Object.fromEntries(AI_SITE_START_STEP_FIELDS[step].filter((field)=>checked.has(step) && fieldErrors[field]).map((field)=>[
636
+ field,
637
+ fieldErrors[field]
638
+ ]));
639
+ const stepProps = {
640
+ answers,
641
+ answer,
642
+ tracker,
643
+ errors: shownErrors,
644
+ fieldRef: (field)=>(node)=>{
645
+ fieldRefs.current[field] = node;
646
+ }
647
+ };
648
+ const HEADINGS = {
649
+ choose: {
650
+ title: 'How do you want to start?',
651
+ lede: creating ? 'Name your site, then choose how it starts. You can change everything later.' : 'You can change everything later.'
652
+ },
653
+ kind: {
654
+ title: 'What are you building?',
655
+ lede: `Say it in a few words, or pick the closest. ${PLATFORM_BRAND_NAME} AI plans and writes your pages from it.`
656
+ },
657
+ audience: {
658
+ title: 'Who is it for?',
659
+ lede: 'The pages and the contact form are written for them.'
660
+ },
661
+ style: {
662
+ title: 'Choose a style',
663
+ lede: 'Each picture is an example home page in that style. Yours is written for your business, with its own colors and fonts within the style you pick.'
664
+ },
665
+ details: {
666
+ title: 'A few details',
667
+ lede: 'Where messages go, how many pages, and what else to draft. All can be changed later.'
668
+ },
669
+ review: {
670
+ title: 'Review your site',
671
+ lede: started ? `${PLATFORM_BRAND_NAME} AI is on it.` : `${PLATFORM_BRAND_NAME} AI plans and writes your pages from these answers, then publishes your site.`
672
+ }
673
+ };
674
+ const heading = HEADINGS[step];
675
+ // Enter in a text answer is Next. A select, a card or a button keeps its own Enter.
676
+ const onStepKeyDown = (event)=>{
677
+ if (event.key !== 'Enter' || event.nativeEvent.isComposing || !asking || reviewing) return;
678
+ const target = event.target;
679
+ if (target.tagName !== 'INPUT' || target.type === 'hidden') return;
680
+ event.preventDefault();
681
+ next();
682
+ };
541
683
  return /*#__PURE__*/ _jsxs(Dialog, {
542
684
  open: true,
543
685
  fullScreen: true,
544
686
  ref: dialogRef,
545
687
  onClose: dismiss,
546
688
  "aria-labelledby": TITLE_ID,
689
+ // Where people get lost (AGL-3898): any input puts off the idle signal,
690
+ // and every click is read for rage and dead clicks.
691
+ onClickCapture: (event)=>tracker.click(aiSiteStartClickOf(event)),
692
+ onKeyDownCapture: ()=>tracker.activity(),
693
+ onInputCapture: ()=>tracker.activity(),
547
694
  // The console's own page surface, in both modes. A Dialog's paper sits
548
695
  // at elevation 24 by default, and in dark mode MUI lightens an elevated
549
696
  // paper with a white overlay, which turned the whole takeover a
@@ -561,6 +708,7 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
561
708
  color: "inherit",
562
709
  elevation: 0,
563
710
  enableColorOnDark: true,
711
+ "data-start-area": "header",
564
712
  sx: _extends({}, AI_SITE_START_SURFACE_SX, {
565
713
  borderBottom: 1,
566
714
  borderColor: 'divider'
@@ -593,7 +741,7 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
593
741
  }),
594
742
  /*#__PURE__*/ _jsx(Button, {
595
743
  color: "inherit",
596
- onClick: exit,
744
+ onClick: skip,
597
745
  "aria-label": started ? 'Close' : 'Skip and start blank',
598
746
  children: started ? 'Close' : 'Skip'
599
747
  })
@@ -607,8 +755,9 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
607
755
  sm: 3
608
756
  }
609
757
  },
758
+ onKeyDown: onStepKeyDown,
610
759
  children: /*#__PURE__*/ _jsx(Container, {
611
- maxWidth: choosing ? 'md' : 'sm',
760
+ maxWidth: choosing || step === 'kind' || step === 'style' ? 'md' : 'sm',
612
761
  disableGutters: true,
613
762
  sx: {
614
763
  py: {
@@ -619,30 +768,39 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
619
768
  children: /*#__PURE__*/ _jsxs(Stack, {
620
769
  spacing: 4,
621
770
  children: [
622
- /*#__PURE__*/ _jsx(Stepper, {
623
- activeStep: choosing ? 0 : 1,
624
- "aria-label": choosing ? 'Step 1 of 2' : 'Step 2 of 2',
771
+ /*#__PURE__*/ _jsx(Box, {
625
772
  sx: {
626
- maxWidth: (theme)=>theme.spacing(40),
773
+ maxWidth: (t)=>t.spacing(80),
627
774
  width: '100%',
628
775
  mx: 'auto'
629
776
  },
630
- children: STEP_LABELS.map((label)=>/*#__PURE__*/ _jsx(Step, {
631
- children: /*#__PURE__*/ _jsx(StepLabel, {
632
- children: label
633
- })
634
- }, label))
777
+ children: /*#__PURE__*/ _jsx(StartProgress, {
778
+ step: step,
779
+ compact: compact,
780
+ onStep: started ? undefined : goBackTo
781
+ })
635
782
  }),
636
783
  /*#__PURE__*/ _jsxs(Stack, {
637
784
  spacing: 1,
638
785
  sx: {
639
786
  textAlign: choosing ? 'center' : 'left'
640
787
  },
788
+ "data-start-area": "heading",
641
789
  children: [
642
790
  /*#__PURE__*/ _jsx(Typography, {
791
+ ref: headingRef,
792
+ tabIndex: -1,
643
793
  variant: "h4",
644
794
  component: "h1",
645
- children: choosing ? 'How do you want to start?' : 'Tell us about your site'
795
+ sx: {
796
+ '&:focus': {
797
+ outline: 'none'
798
+ },
799
+ '&:focus-visible': {
800
+ outline: 'none'
801
+ }
802
+ },
803
+ children: heading.title
646
804
  }),
647
805
  /*#__PURE__*/ _jsxs(Typography, {
648
806
  variant: "body1",
@@ -652,18 +810,22 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
652
810
  sx: {
653
811
  float: 'right'
654
812
  }
655
- }, START_HELP.choose)) : null,
656
- choosing ? creating ? 'Name your site, then choose how it starts. You can change everything later.' : 'You can change everything later.' : `A few answers and ${PLATFORM_BRAND_NAME} AI plans and writes your pages, then publishes your site.`
813
+ }, START_HELP.choose)) : /*#__PURE__*/ _jsx(StepHelp, {
814
+ step: step
815
+ }),
816
+ heading.lede
657
817
  ]
658
818
  })
659
819
  ]
660
820
  }),
661
821
  notice && /*#__PURE__*/ _jsx(Alert, {
662
822
  severity: "info",
823
+ "data-start-area": "notice",
663
824
  children: notice
664
825
  }),
665
- step === 'describe' && !started && noneLeft && freeCredits && /*#__PURE__*/ _jsx(Alert, {
826
+ asking && noneLeft && freeCredits && /*#__PURE__*/ _jsx(Alert, {
666
827
  severity: "warning",
828
+ "data-start-area": "notice",
667
829
  action: orgSlug ? /*#__PURE__*/ _jsx(Button, {
668
830
  color: "inherit",
669
831
  size: "small",
@@ -672,19 +834,6 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
672
834
  }) : undefined,
673
835
  children: aiFreeCreditsNoneLeftText(freeCredits.resetsOn)
674
836
  }),
675
- step === 'describe' && !started && !noneLeft && prompt && /*#__PURE__*/ _jsx(AiCreditsPromptNotice, {
676
- prompt: prompt,
677
- noun: "site",
678
- orgSlug: orgSlug,
679
- busy: busy,
680
- onBuildWhatFits: ()=>void plan({
681
- creditsConfirmed: true
682
- }),
683
- onSmaller: ()=>void plan({
684
- creditsConfirmed: true,
685
- pages: 1
686
- })
687
- }),
688
837
  choosing && creating && pendingSite ? /*#__PURE__*/ _jsxs(Stack, {
689
838
  spacing: 2,
690
839
  component: "section",
@@ -697,6 +846,7 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
697
846
  ]
698
847
  }) : null,
699
848
  choosing ? /*#__PURE__*/ _jsxs(Box, {
849
+ "data-start-area": "choose-cards",
700
850
  sx: {
701
851
  display: 'grid',
702
852
  gridTemplateColumns: {
@@ -754,153 +904,33 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
754
904
  // Full screen: AI jobs opens in the panel this dialog covers.
755
905
  onOpenJobs: exit
756
906
  })
757
- }) : /*#__PURE__*/ _jsxs(_Fragment, {
907
+ }) : /*#__PURE__*/ _jsxs(Box, {
908
+ component: "section",
758
909
  children: [
759
- /*#__PURE__*/ _jsxs(Stack, {
760
- spacing: 2,
761
- component: "section",
762
- "aria-label": "Your business",
763
- children: [
764
- /*#__PURE__*/ _jsx(SectionLabel, {
765
- help: START_HELP.business,
766
- children: 'Your business'
767
- }),
768
- /*#__PURE__*/ _jsx(TextField, {
769
- fullWidth: true,
770
- required: true,
771
- label: "What kind of site are you creating?",
772
- placeholder: "a neighborhood dog groomer that takes bookings",
773
- value: answers.siteType,
774
- onChange: (event)=>answer({
775
- siteType: event.target.value
776
- }),
777
- helperText: "A few words is enough, or pick one below."
778
- }),
779
- /*#__PURE__*/ _jsx(Box, {
780
- sx: {
781
- display: 'flex',
782
- flexWrap: 'wrap',
783
- gap: 1
784
- },
785
- children: AI_SITE_START_TYPES.map((type)=>/*#__PURE__*/ _jsx(Chip, {
786
- label: type,
787
- size: "small",
788
- color: answers.siteType === type ? 'primary' : 'default',
789
- variant: answers.siteType === type ? 'filled' : 'outlined',
790
- onClick: ()=>answer({
791
- siteType: type
792
- })
793
- }, type))
794
- }),
795
- /*#__PURE__*/ _jsx(TextField, {
796
- fullWidth: true,
797
- label: "Who is it for?",
798
- placeholder: "local dog owners who want a regular groom booked online",
799
- value: answers.audience,
800
- onChange: (event)=>answer({
801
- audience: event.target.value
802
- }),
803
- helperText: "Optional. It narrows who the pages are written for."
804
- })
805
- ]
806
- }),
807
- /*#__PURE__*/ _jsxs(Stack, {
808
- spacing: 2,
809
- component: "section",
810
- "aria-label": "Style",
811
- children: [
812
- /*#__PURE__*/ _jsx(SectionLabel, {
813
- help: START_HELP.style,
814
- children: 'Style'
815
- }),
816
- /*#__PURE__*/ _jsx(Typography, {
817
- variant: "body2",
818
- color: "text.secondary",
819
- children: answers.kind ? 'This decides how your site looks and which pages it usually has.' : 'Picked from your answer above. Choose another to change how your site looks and which pages it usually has.'
820
- }),
821
- /*#__PURE__*/ _jsx(OptionCardGrid, {
822
- label: "Style of site",
823
- options: AI_SITE_KINDS.map((kind)=>({
824
- id: kind.id,
825
- title: kind.label,
826
- description: kind.blurb,
827
- icon: AI_SITE_KIND_ICONS[kind.id]
828
- })),
829
- value: aiSiteStartKind(answers).id,
830
- onChange: (kind)=>answer({
831
- kind
832
- })
833
- })
834
- ]
835
- }),
836
- /*#__PURE__*/ _jsxs(Stack, {
837
- spacing: 2,
838
- component: "section",
839
- "aria-label": "Details",
840
- children: [
841
- /*#__PURE__*/ _jsx(SectionLabel, {
842
- help: START_HELP.details,
843
- children: 'Details'
844
- }),
845
- /*#__PURE__*/ _jsx(TextField, {
846
- select: true,
847
- fullWidth: true,
848
- label: "Where do form submissions go?",
849
- value: answers.submissions,
850
- onChange: (event)=>answer({
851
- submissions: event.target.value
852
- }),
853
- helperText: "You can change this on the form itself afterwards.",
854
- children: AI_SITE_SUBMISSION_CHOICES.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
855
- value: option.id,
856
- children: `${option.label} — ${option.blurb}`
857
- }, option.id))
858
- }),
859
- /*#__PURE__*/ _jsxs(Stack, {
860
- direction: {
861
- xs: 'column',
862
- sm: 'row'
863
- },
864
- spacing: 2,
865
- children: [
866
- /*#__PURE__*/ _jsx(TextField, {
867
- select: true,
868
- fullWidth: true,
869
- label: "Pages",
870
- value: answers.pages,
871
- onChange: (event)=>answer({
872
- pages: Number(event.target.value)
873
- }),
874
- helperText: freeTaste ? AI_SITE_FREE_PAGES_NOTE : undefined,
875
- children: Array.from({
876
- length: band.max - band.min + 1
877
- }, (_, index)=>band.min + index).map((count)=>/*#__PURE__*/ _jsx(MenuItem, {
878
- value: count,
879
- children: count
880
- }, count))
881
- }),
882
- !freeTaste && /*#__PURE__*/ _jsxs(TextField, {
883
- select: true,
884
- fullWidth: true,
885
- label: "Welcome email",
886
- value: answers.welcomeEmail ? 'yes' : 'no',
887
- onChange: (event)=>answer({
888
- welcomeEmail: event.target.value === 'yes'
889
- }),
890
- children: [
891
- /*#__PURE__*/ _jsx(MenuItem, {
892
- value: "yes",
893
- children: 'Draft one'
894
- }),
895
- /*#__PURE__*/ _jsx(MenuItem, {
896
- value: "no",
897
- children: 'No'
898
- })
899
- ]
900
- })
901
- ]
902
- })
903
- ]
910
+ step === 'kind' && /*#__PURE__*/ _jsx(KindStep, _extends({}, stepProps)),
911
+ step === 'audience' && /*#__PURE__*/ _jsx(AudienceStep, _extends({}, stepProps)),
912
+ step === 'style' && /*#__PURE__*/ _jsx(StyleStep, _extends({}, stepProps)),
913
+ step === 'details' && /*#__PURE__*/ _jsx(DetailsStep, _extends({}, stepProps, {
914
+ freeTaste: freeTaste,
915
+ band: band
916
+ })),
917
+ step === 'review' && /*#__PURE__*/ _jsx(ReviewStep, {
918
+ answers: answers,
919
+ freeTaste: freeTaste,
920
+ onEdit: edit,
921
+ children: !noneLeft && prompt && /*#__PURE__*/ _jsx(AiCreditsPromptNotice, {
922
+ prompt: prompt,
923
+ noun: "site",
924
+ orgSlug: orgSlug,
925
+ busy: busy,
926
+ onBuildWhatFits: ()=>void plan({
927
+ creditsConfirmed: true
928
+ }),
929
+ onSmaller: ()=>void plan({
930
+ creditsConfirmed: true,
931
+ pages: 1
932
+ })
933
+ })
904
934
  })
905
935
  ]
906
936
  })
@@ -908,8 +938,9 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
908
938
  })
909
939
  })
910
940
  }),
911
- step === 'describe' && !started && /* The footer stays put while the questions scroll. It wraps, because
941
+ asking && /* The footer stays put while the questions scroll. It wraps, because
912
942
  the estimate, the reason and the buttons are wider than a phone. */ /*#__PURE__*/ _jsxs(DialogActions, {
943
+ "data-start-area": "footer",
913
944
  sx: _extends({
914
945
  px: {
915
946
  xs: 2,
@@ -923,7 +954,7 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
923
954
  borderColor: 'divider'
924
955
  }, AI_SITE_START_SURFACE_SX),
925
956
  children: [
926
- /*#__PURE__*/ _jsxs(Stack, {
957
+ reviewing && /*#__PURE__*/ _jsxs(Stack, {
927
958
  direction: "row",
928
959
  spacing: 0.5,
929
960
  sx: {
@@ -942,20 +973,23 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
942
973
  }))
943
974
  ]
944
975
  }),
945
- refusal && /*#__PURE__*/ _jsx(Typography, {
976
+ reviewing && refusal && /*#__PURE__*/ _jsx(Typography, {
946
977
  variant: "body2",
947
978
  color: "text.secondary",
948
979
  children: refusal
949
980
  }),
950
- !freeTaste && /*#__PURE__*/ _jsx(AiModelSelector, {
981
+ reviewing && !freeTaste && /*#__PURE__*/ _jsx(AiModelSelector, {
951
982
  choice: modelChoice,
952
983
  disabled: busy
953
984
  }),
954
985
  /*#__PURE__*/ _jsx(Button, {
955
- onClick: ()=>setStep('choose'),
986
+ onClick: back,
987
+ sx: reviewing ? undefined : {
988
+ mr: 'auto'
989
+ },
956
990
  children: 'Back'
957
991
  }),
958
- /*#__PURE__*/ _jsx(Button, {
992
+ reviewing ? /*#__PURE__*/ _jsx(Button, {
959
993
  variant: "contained",
960
994
  size: "large",
961
995
  disabled: busy || Boolean(refusal) || Boolean(prompt) || noneLeft,
@@ -964,6 +998,11 @@ export function AiSiteStartCard({ hostId, orgId, orgSlug, host, startBlank, leav
964
998
  path: mdiCreation.path
965
999
  }),
966
1000
  children: busy ? 'Starting…' : 'Plan my site'
1001
+ }) : /*#__PURE__*/ _jsx(Button, {
1002
+ variant: "contained",
1003
+ size: "large",
1004
+ onClick: next,
1005
+ children: returnTo === 'review' ? 'Back to review' : 'Next'
967
1006
  })
968
1007
  ]
969
1008
  }),