@aglyn/plugins-ai 1.0.0-beta.263 → 1.0.0-beta.266
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.
- package/package.json +17 -17
- package/src/lib/assets/site-styles/beauty.webp +0 -0
- package/src/lib/assets/site-styles/blog.webp +0 -0
- package/src/lib/assets/site-styles/business.webp +0 -0
- package/src/lib/assets/site-styles/coming-soon.webp +0 -0
- package/src/lib/assets/site-styles/education.webp +0 -0
- package/src/lib/assets/site-styles/events.webp +0 -0
- package/src/lib/assets/site-styles/fitness.webp +0 -0
- package/src/lib/assets/site-styles/landing.webp +0 -0
- package/src/lib/assets/site-styles/music.webp +0 -0
- package/src/lib/assets/site-styles/nonprofit.webp +0 -0
- package/src/lib/assets/site-styles/personal.webp +0 -0
- package/src/lib/assets/site-styles/photography.webp +0 -0
- package/src/lib/assets/site-styles/portfolio.webp +0 -0
- package/src/lib/assets/site-styles/professional.webp +0 -0
- package/src/lib/assets/site-styles/realestate.webp +0 -0
- package/src/lib/assets/site-styles/restaurant.webp +0 -0
- package/src/lib/assets/site-styles/store.webp +0 -0
- package/src/lib/assets/site-styles/studio.webp +0 -0
- package/src/lib/assets/site-styles/trades.webp +0 -0
- package/src/lib/assets/site-styles/wellness.webp +0 -0
- package/src/lib/assets/site-styles/yoga.webp +0 -0
- package/src/lib/components/ai-site-start-card.component.d.ts +12 -0
- package/src/lib/components/ai-site-start-card.component.js +256 -236
- package/src/lib/components/ai-site-start-card.component.js.map +1 -1
- package/src/lib/components/ai-site-start-steps.component.d.ts +108 -0
- package/src/lib/components/ai-site-start-steps.component.js +451 -0
- package/src/lib/components/ai-site-start-steps.component.js.map +1 -0
- package/src/lib/components/ai-site-start-tracking.d.ts +114 -0
- package/src/lib/components/ai-site-start-tracking.js +277 -0
- package/src/lib/components/ai-site-start-tracking.js.map +1 -0
- package/src/lib/components/ai-site-style-previews.d.ts +26 -0
- package/src/lib/components/ai-site-style-previews.js +115 -0
- package/src/lib/components/ai-site-style-previews.js.map +1 -0
- package/src/lib/insights/ai-insight-digest.d.ts +9 -0
- package/src/lib/insights/ai-insight-digest.js +35 -11
- package/src/lib/insights/ai-insight-digest.js.map +1 -1
- package/src/lib/jobs/ai-job-page-language.d.ts +9 -0
- package/src/lib/jobs/ai-job-page-language.js +20 -2
- package/src/lib/jobs/ai-job-page-language.js.map +1 -1
- package/src/lib/jobs/ai-job-page-sections.js +8 -2
- package/src/lib/jobs/ai-job-page-sections.js.map +1 -1
- package/src/lib/jobs/ai-job-plan-step.d.ts +43 -8
- package/src/lib/jobs/ai-job-plan-step.js +99 -18
- package/src/lib/jobs/ai-job-plan-step.js.map +1 -1
- package/src/lib/jobs/ai-job-site-datasets.d.ts +93 -2
- package/src/lib/jobs/ai-job-site-datasets.js +308 -21
- package/src/lib/jobs/ai-job-site-datasets.js.map +1 -1
- package/src/lib/jobs/ai-layout-stock-photos.d.ts +191 -13
- package/src/lib/jobs/ai-layout-stock-photos.js +1709 -69
- package/src/lib/jobs/ai-layout-stock-photos.js.map +1 -1
- package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.d.ts +58 -0
- package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.js +678 -0
- package/src/lib/jobs/fixtures/ai-site-foreign-datasets-recording.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.d.ts +33 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.js +270 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-beta265-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.d.ts +33 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.js +277 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-agl3856-people-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-juniper-records-recordings.d.ts +23 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-juniper-records-recordings.js +163 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-juniper-records-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-learning-center-recordings.d.ts +23 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-learning-center-recordings.js +53 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-learning-center-recordings.js.map +1 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-synchronicity-recordings.d.ts +21 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-synchronicity-recordings.js +119 -0
- package/src/lib/jobs/fixtures/ai-stock-photo-synchronicity-recordings.js.map +1 -0
- package/src/lib/layout-language/ai-layout-compiler.js +54 -15
- package/src/lib/layout-language/ai-layout-compiler.js.map +1 -1
- package/src/lib/layout-language/ai-layout-design.d.ts +6 -3
- package/src/lib/layout-language/ai-layout-design.js +14 -13
- package/src/lib/layout-language/ai-layout-design.js.map +1 -1
- package/src/lib/layout-language/ai-layout-listings.d.ts +55 -0
- package/src/lib/layout-language/ai-layout-listings.js +103 -5
- package/src/lib/layout-language/ai-layout-listings.js.map +1 -1
- package/src/lib/layout-language/ai-layout-pictures.d.ts +42 -8
- package/src/lib/layout-language/ai-layout-pictures.js +91 -6
- package/src/lib/layout-language/ai-layout-pictures.js.map +1 -1
- package/src/lib/model/ai-site-home-arcs.d.ts +75 -0
- package/src/lib/model/ai-site-home-arcs.js +741 -0
- package/src/lib/model/ai-site-home-arcs.js.map +1 -0
- package/src/lib/model/ai-site-inventory.d.ts +18 -0
- package/src/lib/model/ai-site-inventory.js.map +1 -1
- package/src/lib/model/ai-site-job.d.ts +9 -6
- package/src/lib/model/ai-site-job.js +8 -5
- package/src/lib/model/ai-site-job.js.map +1 -1
- package/src/lib/model/ai-site-kinds.js +24 -24
- package/src/lib/model/ai-site-kinds.js.map +1 -1
- package/src/lib/model/ai-site-start-steps.d.ts +67 -0
- package/src/lib/model/ai-site-start-steps.js +127 -0
- package/src/lib/model/ai-site-start-steps.js.map +1 -0
- package/src/lib/model/ai-site-start.js +4 -1
- package/src/lib/model/ai-site-start.js.map +1 -1
- package/src/lib/runtime/ai-doctrine-validators.d.ts +32 -0
- package/src/lib/runtime/ai-doctrine-validators.js +119 -2
- package/src/lib/runtime/ai-doctrine-validators.js.map +1 -1
- package/src/lib/runtime/ai-doctrine.d.ts +6 -1
- package/src/lib/runtime/ai-doctrine.js +9 -5
- package/src/lib/runtime/ai-doctrine.js.map +1 -1
- package/src/lib/runtime/ai-palette.generated.js +5 -2
- package/src/lib/runtime/ai-palette.generated.js.map +1 -1
- package/src/lib/runtime/site-inventory.js +6 -0
- package/src/lib/runtime/site-inventory.js.map +1 -1
- package/src/lib/server/ai-insight-digest-route.js +2 -2
- package/src/lib/server/ai-insight-digest-route.js.map +1 -1
- package/src/lib/usage/assist-free-taste.js +3 -0
- package/src/lib/usage/assist-free-taste.js.map +1 -1
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
|
+
import { pluginDocsHelp } from "@aglyn/aglyn/app-utils/docs-help";
|
|
20
|
+
import { mdiBookOpenPageVariant, mdiBriefcaseOutline, mdiCalendarHeart, mdiCreation, mdiHandHeart, mdiScaleBalance, mdiShopping, mdiSilverwareForkKnife, mdiViewGridOutline, mdiWrench } from "@aglyn/shared-data-mdi";
|
|
21
|
+
import { HelpTip } from "@aglyn/shared-ui-jsx";
|
|
22
|
+
import { OptionCardGrid } from "@aglyn/shared-ui-jsx/components/option-card-grid.component";
|
|
23
|
+
import { Box, Button, Divider, LinearProgress, MenuItem, Stack, Step, StepLabel, Stepper, TextField, Typography } from "@mui/material";
|
|
24
|
+
import { Fragment } from "react";
|
|
25
|
+
import { AI_SITE_FREE_PAGES_NOTE, AI_SITE_SUBMISSION_CHOICES } from "../model/ai-site-job.js";
|
|
26
|
+
import { AI_SITE_KINDS } from "../model/ai-site-kinds.js";
|
|
27
|
+
import { AI_SITE_START_TYPES, aiSiteStartKind } from "../model/ai-site-start.js";
|
|
28
|
+
import { AI_SITE_START_STEP_LABELS, AI_SITE_START_STEPS, aiSiteStartStepIndex } from "../model/ai-site-start-steps.js";
|
|
29
|
+
import { aiSiteStylePreview } from "./ai-site-style-previews.js";
|
|
30
|
+
/**
|
|
31
|
+
* The guided start's question steps (AGL-3898), one decision each. Each step
|
|
32
|
+
* draws its own answers and writes them through `answer`, exactly the patch
|
|
33
|
+
* the single form wrote, so the answers object is the same whichever way it
|
|
34
|
+
* was filled in. The dialog around them (`ai-site-start-card.component.tsx`)
|
|
35
|
+
* owns the step, Back and Next, validation and the job.
|
|
36
|
+
*/ /** Each part of the guided start links its own section of the Start with AI guide (AGL-3660). */ export const START_STEP_HELP = {
|
|
37
|
+
kind: pluginDocsHelp('aiStart', {
|
|
38
|
+
anchor: '#your-business',
|
|
39
|
+
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.'
|
|
40
|
+
}),
|
|
41
|
+
audience: pluginDocsHelp('aiStart', {
|
|
42
|
+
anchor: '#audience',
|
|
43
|
+
excerpt: 'Who the site is for is who every page, every search listing and the contact form are written for. Optional.'
|
|
44
|
+
}),
|
|
45
|
+
review: pluginDocsHelp('aiStart', {
|
|
46
|
+
anchor: '#review',
|
|
47
|
+
excerpt: 'Every answer, each with Edit to change it. Plan my site creates the site, plans it, writes the pages and publishes it.'
|
|
48
|
+
}),
|
|
49
|
+
style: pluginDocsHelp('aiSiteLooks', {
|
|
50
|
+
anchor: '#choose-a-style',
|
|
51
|
+
excerpt: 'The style decides the look — theme, colors, fonts, buttons — and which pages the site usually has. Every site gets its own variation.'
|
|
52
|
+
}),
|
|
53
|
+
details: pluginDocsHelp('aiStart', {
|
|
54
|
+
anchor: '#details',
|
|
55
|
+
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.'
|
|
56
|
+
})
|
|
57
|
+
};
|
|
58
|
+
/** The icon on each kind-of-site card: the icon of the style that answer picks. */ const START_TYPE_ICONS = {
|
|
59
|
+
'a local service business': mdiBriefcaseOutline.path,
|
|
60
|
+
'a restaurant or cafe': mdiSilverwareForkKnife.path,
|
|
61
|
+
'a professional practice': mdiScaleBalance.path,
|
|
62
|
+
'a trades business': mdiWrench.path,
|
|
63
|
+
'a shop': mdiShopping.path,
|
|
64
|
+
'a nonprofit': mdiHandHeart.path,
|
|
65
|
+
'a personal portfolio': mdiViewGridOutline.path,
|
|
66
|
+
'an event': mdiCalendarHeart.path
|
|
67
|
+
};
|
|
68
|
+
/** "a restaurant or cafe" as a card title: "Restaurant or cafe". The answer itself keeps its words. */ export function startTypeTitle(type) {
|
|
69
|
+
const words = type.replace(/^(a|an)\s+/i, '');
|
|
70
|
+
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
71
|
+
}
|
|
72
|
+
/** The id the style grid gives "Pick for me", which is no kind: the answers' `kind: null`. */ export const AI_SITE_STYLE_AUTO = 'auto';
|
|
73
|
+
/** The progress through the steps: labeled steps on a wide screen, a bar and "Step 3 of 6" on a phone. */ export function StartProgress({ step, compact }) {
|
|
74
|
+
const index = aiSiteStartStepIndex(step);
|
|
75
|
+
const count = AI_SITE_START_STEPS.length;
|
|
76
|
+
const name = `Step ${index + 1} of ${count}`;
|
|
77
|
+
if (compact) {
|
|
78
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
79
|
+
spacing: 1,
|
|
80
|
+
"data-start-area": "progress",
|
|
81
|
+
role: "group",
|
|
82
|
+
"aria-label": name,
|
|
83
|
+
children: [
|
|
84
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
85
|
+
variant: "caption",
|
|
86
|
+
color: "text.secondary",
|
|
87
|
+
children: `${name}: ${AI_SITE_START_STEP_LABELS[step]}`
|
|
88
|
+
}),
|
|
89
|
+
/*#__PURE__*/ _jsx(LinearProgress, {
|
|
90
|
+
variant: "determinate",
|
|
91
|
+
value: (index + 1) / count * 100,
|
|
92
|
+
"aria-hidden": true
|
|
93
|
+
})
|
|
94
|
+
]
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return /*#__PURE__*/ _jsx(Stepper, {
|
|
98
|
+
activeStep: index,
|
|
99
|
+
alternativeLabel: true,
|
|
100
|
+
"aria-label": name,
|
|
101
|
+
"data-start-area": "progress",
|
|
102
|
+
children: AI_SITE_START_STEPS.map((id)=>/*#__PURE__*/ _jsx(Step, {
|
|
103
|
+
children: /*#__PURE__*/ _jsx(StepLabel, {
|
|
104
|
+
children: AI_SITE_START_STEP_LABELS[id]
|
|
105
|
+
})
|
|
106
|
+
}, id))
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
/** A text answer's tracking: each change, and leaving the field, which ends one edit. */ const typed = (tracker, field)=>({
|
|
110
|
+
onBlur: ()=>tracker.fieldLeft(field)
|
|
111
|
+
});
|
|
112
|
+
/** Step: what kind of site it is — the one required answer — typed, or picked from cards. */ export function KindStep({ answers, answer, tracker, errors, fieldRef }) {
|
|
113
|
+
var _ref;
|
|
114
|
+
var _errors_siteType;
|
|
115
|
+
const picked = AI_SITE_START_TYPES.includes(answers.siteType) ? answers.siteType : null;
|
|
116
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
117
|
+
spacing: 3,
|
|
118
|
+
children: [
|
|
119
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
120
|
+
"data-start-area": "field-siteType",
|
|
121
|
+
children: /*#__PURE__*/ _jsx(TextField, _extends({
|
|
122
|
+
fullWidth: true,
|
|
123
|
+
required: true,
|
|
124
|
+
inputRef: fieldRef('siteType'),
|
|
125
|
+
label: "What kind of site are you creating?",
|
|
126
|
+
placeholder: "a neighborhood dog groomer that takes bookings",
|
|
127
|
+
value: answers.siteType,
|
|
128
|
+
onChange: (event)=>{
|
|
129
|
+
answer({
|
|
130
|
+
siteType: event.target.value
|
|
131
|
+
});
|
|
132
|
+
tracker.fieldChanged('siteType');
|
|
133
|
+
}
|
|
134
|
+
}, typed(tracker, 'siteType'), {
|
|
135
|
+
error: Boolean(errors.siteType),
|
|
136
|
+
helperText: (_ref = (_errors_siteType = errors.siteType) == null ? void 0 : _errors_siteType.message) != null ? _ref : 'A few words is enough, or pick one below.'
|
|
137
|
+
}))
|
|
138
|
+
}),
|
|
139
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
140
|
+
spacing: 1.5,
|
|
141
|
+
children: [
|
|
142
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
143
|
+
variant: "overline",
|
|
144
|
+
color: "text.secondary",
|
|
145
|
+
component: "h2",
|
|
146
|
+
children: 'Or pick the closest'
|
|
147
|
+
}),
|
|
148
|
+
/*#__PURE__*/ _jsx(OptionCardGrid, {
|
|
149
|
+
label: "Kind of site",
|
|
150
|
+
"data-start-area": "kind-cards",
|
|
151
|
+
columns: {
|
|
152
|
+
xs: 2,
|
|
153
|
+
sm: 2,
|
|
154
|
+
md: 4
|
|
155
|
+
},
|
|
156
|
+
options: AI_SITE_START_TYPES.map((type)=>{
|
|
157
|
+
var _START_TYPE_ICONS_type;
|
|
158
|
+
return {
|
|
159
|
+
id: type,
|
|
160
|
+
title: startTypeTitle(type),
|
|
161
|
+
icon: (_START_TYPE_ICONS_type = START_TYPE_ICONS[type]) != null ? _START_TYPE_ICONS_type : mdiBookOpenPageVariant.path
|
|
162
|
+
};
|
|
163
|
+
}),
|
|
164
|
+
value: picked,
|
|
165
|
+
onChange: (type)=>{
|
|
166
|
+
answer({
|
|
167
|
+
siteType: type
|
|
168
|
+
});
|
|
169
|
+
tracker.fieldPicked('siteType');
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
]
|
|
173
|
+
})
|
|
174
|
+
]
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/** Step: who the site is for. Optional. */ export function AudienceStep({ answers, answer, tracker, errors, fieldRef }) {
|
|
178
|
+
var _ref;
|
|
179
|
+
var _errors_audience;
|
|
180
|
+
return /*#__PURE__*/ _jsx(Box, {
|
|
181
|
+
"data-start-area": "field-audience",
|
|
182
|
+
children: /*#__PURE__*/ _jsx(TextField, _extends({
|
|
183
|
+
fullWidth: true,
|
|
184
|
+
inputRef: fieldRef('audience'),
|
|
185
|
+
label: "Who is it for?",
|
|
186
|
+
placeholder: "local dog owners who want a regular groom booked online",
|
|
187
|
+
value: answers.audience,
|
|
188
|
+
onChange: (event)=>{
|
|
189
|
+
answer({
|
|
190
|
+
audience: event.target.value
|
|
191
|
+
});
|
|
192
|
+
tracker.fieldChanged('audience');
|
|
193
|
+
}
|
|
194
|
+
}, typed(tracker, 'audience'), {
|
|
195
|
+
error: Boolean(errors.audience),
|
|
196
|
+
helperText: (_ref = (_errors_audience = errors.audience) == null ? void 0 : _errors_audience.message) != null ? _ref : 'Optional. It narrows who the pages are written for.'
|
|
197
|
+
}))
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Step: the style, from cards that show it. "Pick for me" leaves the kind
|
|
202
|
+
* unset, which is what the single form's unpicked state was: the kind the
|
|
203
|
+
* first answer suggests is the one used, and the card says which.
|
|
204
|
+
*/ export function StyleStep({ answers, answer, tracker }) {
|
|
205
|
+
var _answers_kind;
|
|
206
|
+
const suggested = aiSiteStartKind(_extends({}, answers, {
|
|
207
|
+
kind: null
|
|
208
|
+
}));
|
|
209
|
+
return /*#__PURE__*/ _jsx(OptionCardGrid, {
|
|
210
|
+
label: "Style of site",
|
|
211
|
+
"data-start-area": "style-cards",
|
|
212
|
+
columns: {
|
|
213
|
+
xs: 2,
|
|
214
|
+
sm: 2,
|
|
215
|
+
md: 3
|
|
216
|
+
},
|
|
217
|
+
options: [
|
|
218
|
+
{
|
|
219
|
+
id: AI_SITE_STYLE_AUTO,
|
|
220
|
+
title: 'Pick for me',
|
|
221
|
+
description: `From your answer: ${suggested.label}`,
|
|
222
|
+
icon: mdiCreation.path
|
|
223
|
+
},
|
|
224
|
+
...AI_SITE_KINDS.map((kind)=>{
|
|
225
|
+
const image = aiSiteStylePreview(kind.id);
|
|
226
|
+
return _extends({
|
|
227
|
+
id: kind.id,
|
|
228
|
+
title: kind.label,
|
|
229
|
+
description: kind.blurb
|
|
230
|
+
}, image ? {
|
|
231
|
+
image
|
|
232
|
+
} : {});
|
|
233
|
+
})
|
|
234
|
+
],
|
|
235
|
+
value: (_answers_kind = answers.kind) != null ? _answers_kind : AI_SITE_STYLE_AUTO,
|
|
236
|
+
onChange: (id)=>{
|
|
237
|
+
answer({
|
|
238
|
+
kind: id === AI_SITE_STYLE_AUTO ? null : id
|
|
239
|
+
});
|
|
240
|
+
tracker.fieldPicked('kind');
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
/** Step: where leads go, how many pages, and on a paid plan the welcome email. */ export function DetailsStep({ answers, answer, tracker, errors, fieldRef, freeTaste, band }) {
|
|
245
|
+
var _ref, _ref1;
|
|
246
|
+
var _errors_submissions, _errors_pages;
|
|
247
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
248
|
+
spacing: 3,
|
|
249
|
+
children: [
|
|
250
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
251
|
+
"data-start-area": "field-submissions",
|
|
252
|
+
children: /*#__PURE__*/ _jsx(TextField, {
|
|
253
|
+
select: true,
|
|
254
|
+
fullWidth: true,
|
|
255
|
+
inputRef: fieldRef('submissions'),
|
|
256
|
+
label: "Where do form submissions go?",
|
|
257
|
+
value: answers.submissions,
|
|
258
|
+
onChange: (event)=>{
|
|
259
|
+
answer({
|
|
260
|
+
submissions: event.target.value
|
|
261
|
+
});
|
|
262
|
+
tracker.fieldPicked('submissions');
|
|
263
|
+
},
|
|
264
|
+
error: Boolean(errors.submissions),
|
|
265
|
+
helperText: (_ref = (_errors_submissions = errors.submissions) == null ? void 0 : _errors_submissions.message) != null ? _ref : 'You can change this on the form itself afterwards.',
|
|
266
|
+
children: AI_SITE_SUBMISSION_CHOICES.map((option)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
267
|
+
value: option.id,
|
|
268
|
+
children: `${option.label} — ${option.blurb}`
|
|
269
|
+
}, option.id))
|
|
270
|
+
})
|
|
271
|
+
}),
|
|
272
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
273
|
+
direction: {
|
|
274
|
+
xs: 'column',
|
|
275
|
+
sm: 'row'
|
|
276
|
+
},
|
|
277
|
+
spacing: 2,
|
|
278
|
+
children: [
|
|
279
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
280
|
+
"data-start-area": "field-pages",
|
|
281
|
+
sx: {
|
|
282
|
+
flex: 1
|
|
283
|
+
},
|
|
284
|
+
children: /*#__PURE__*/ _jsx(TextField, {
|
|
285
|
+
select: true,
|
|
286
|
+
fullWidth: true,
|
|
287
|
+
inputRef: fieldRef('pages'),
|
|
288
|
+
label: "Pages",
|
|
289
|
+
value: answers.pages,
|
|
290
|
+
onChange: (event)=>{
|
|
291
|
+
answer({
|
|
292
|
+
pages: Number(event.target.value)
|
|
293
|
+
});
|
|
294
|
+
tracker.fieldPicked('pages');
|
|
295
|
+
},
|
|
296
|
+
error: Boolean(errors.pages),
|
|
297
|
+
helperText: (_ref1 = (_errors_pages = errors.pages) == null ? void 0 : _errors_pages.message) != null ? _ref1 : freeTaste ? AI_SITE_FREE_PAGES_NOTE : undefined,
|
|
298
|
+
children: Array.from({
|
|
299
|
+
length: band.max - band.min + 1
|
|
300
|
+
}, (_, index)=>band.min + index).map((count)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
301
|
+
value: count,
|
|
302
|
+
children: count
|
|
303
|
+
}, count))
|
|
304
|
+
})
|
|
305
|
+
}),
|
|
306
|
+
!freeTaste && /*#__PURE__*/ _jsx(Box, {
|
|
307
|
+
"data-start-area": "field-welcomeEmail",
|
|
308
|
+
sx: {
|
|
309
|
+
flex: 1
|
|
310
|
+
},
|
|
311
|
+
children: /*#__PURE__*/ _jsxs(TextField, {
|
|
312
|
+
select: true,
|
|
313
|
+
fullWidth: true,
|
|
314
|
+
label: "Welcome email",
|
|
315
|
+
value: answers.welcomeEmail ? 'yes' : 'no',
|
|
316
|
+
onChange: (event)=>{
|
|
317
|
+
answer({
|
|
318
|
+
welcomeEmail: event.target.value === 'yes'
|
|
319
|
+
});
|
|
320
|
+
tracker.fieldPicked('welcomeEmail');
|
|
321
|
+
},
|
|
322
|
+
children: [
|
|
323
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
324
|
+
value: "yes",
|
|
325
|
+
children: 'Draft one'
|
|
326
|
+
}),
|
|
327
|
+
/*#__PURE__*/ _jsx(MenuItem, {
|
|
328
|
+
value: "no",
|
|
329
|
+
children: 'No'
|
|
330
|
+
})
|
|
331
|
+
]
|
|
332
|
+
})
|
|
333
|
+
})
|
|
334
|
+
]
|
|
335
|
+
})
|
|
336
|
+
]
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
/** One answer on the review, with the way back to the step that asks it. */ function ReviewRow({ label, value, step, onEdit }) {
|
|
340
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
341
|
+
direction: "row",
|
|
342
|
+
spacing: 2,
|
|
343
|
+
sx: {
|
|
344
|
+
alignItems: 'flex-start',
|
|
345
|
+
py: 1.5
|
|
346
|
+
},
|
|
347
|
+
children: [
|
|
348
|
+
/*#__PURE__*/ _jsxs(Box, {
|
|
349
|
+
sx: {
|
|
350
|
+
flex: 1,
|
|
351
|
+
minWidth: 0
|
|
352
|
+
},
|
|
353
|
+
children: [
|
|
354
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
355
|
+
variant: "body2",
|
|
356
|
+
color: "text.secondary",
|
|
357
|
+
component: "dt",
|
|
358
|
+
children: label
|
|
359
|
+
}),
|
|
360
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
361
|
+
variant: "body1",
|
|
362
|
+
component: "dd",
|
|
363
|
+
sx: {
|
|
364
|
+
m: 0,
|
|
365
|
+
overflowWrap: 'anywhere'
|
|
366
|
+
},
|
|
367
|
+
children: value
|
|
368
|
+
})
|
|
369
|
+
]
|
|
370
|
+
}),
|
|
371
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
372
|
+
size: "small",
|
|
373
|
+
onClick: ()=>onEdit(step),
|
|
374
|
+
"aria-label": `Edit ${label.toLowerCase()}`,
|
|
375
|
+
children: 'Edit'
|
|
376
|
+
})
|
|
377
|
+
]
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
/** Step: every answer, each with Edit, before "Plan my site". */ export function ReviewStep({ answers, freeTaste, onEdit, children }) {
|
|
381
|
+
var _ref;
|
|
382
|
+
const kind = aiSiteStartKind(answers);
|
|
383
|
+
const submissions = AI_SITE_SUBMISSION_CHOICES.find((option)=>option.id === answers.submissions);
|
|
384
|
+
const rows = [
|
|
385
|
+
{
|
|
386
|
+
label: 'Kind of site',
|
|
387
|
+
value: answers.siteType.trim(),
|
|
388
|
+
step: 'kind'
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
label: 'Who it is for',
|
|
392
|
+
value: answers.audience.trim() || 'Anyone (not said)',
|
|
393
|
+
step: 'audience'
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
label: 'Style',
|
|
397
|
+
value: answers.kind ? kind.label : `${kind.label}, picked for you`,
|
|
398
|
+
step: 'style'
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
label: 'Form submissions',
|
|
402
|
+
value: (_ref = submissions == null ? void 0 : submissions.label) != null ? _ref : answers.submissions,
|
|
403
|
+
step: 'details'
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
label: 'Pages',
|
|
407
|
+
value: String(answers.pages),
|
|
408
|
+
step: 'details'
|
|
409
|
+
},
|
|
410
|
+
...freeTaste ? [] : [
|
|
411
|
+
{
|
|
412
|
+
label: 'Welcome email',
|
|
413
|
+
value: answers.welcomeEmail ? 'Draft one' : 'No',
|
|
414
|
+
step: 'details'
|
|
415
|
+
}
|
|
416
|
+
]
|
|
417
|
+
];
|
|
418
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
419
|
+
spacing: 3,
|
|
420
|
+
children: [
|
|
421
|
+
/*#__PURE__*/ _jsx(Box, {
|
|
422
|
+
component: "dl",
|
|
423
|
+
"data-start-area": "review",
|
|
424
|
+
sx: {
|
|
425
|
+
m: 0
|
|
426
|
+
},
|
|
427
|
+
children: rows.map((row, index)=>/*#__PURE__*/ _jsxs(Fragment, {
|
|
428
|
+
children: [
|
|
429
|
+
index > 0 && /*#__PURE__*/ _jsx(Divider, {
|
|
430
|
+
component: "div"
|
|
431
|
+
}),
|
|
432
|
+
/*#__PURE__*/ _jsx(ReviewRow, _extends({}, row, {
|
|
433
|
+
onEdit: onEdit
|
|
434
|
+
}))
|
|
435
|
+
]
|
|
436
|
+
}, row.label))
|
|
437
|
+
}),
|
|
438
|
+
children
|
|
439
|
+
]
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
/** A step's help beside its heading. */ export function StepHelp({ step }) {
|
|
443
|
+
const help = step === 'choose' ? null : START_STEP_HELP[step];
|
|
444
|
+
return help ? /*#__PURE__*/ _jsx(HelpTip, _extends({
|
|
445
|
+
sx: {
|
|
446
|
+
float: 'right'
|
|
447
|
+
}
|
|
448
|
+
}, help)) : null;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
//# sourceMappingURL=ai-site-start-steps.component.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/ai/src/lib/components/ai-site-start-steps.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 { pluginDocsHelp } from '@aglyn/aglyn/app-utils/docs-help'\nimport {\n mdiBookOpenPageVariant,\n mdiBriefcaseOutline,\n mdiCalendarHeart,\n mdiCreation,\n mdiHandHeart,\n mdiScaleBalance,\n mdiShopping,\n mdiSilverwareForkKnife,\n mdiViewGridOutline,\n mdiWrench,\n} from '@aglyn/shared-data-mdi'\nimport { HelpTip } from '@aglyn/shared-ui-jsx'\nimport { OptionCardGrid } from '@aglyn/shared-ui-jsx/components/option-card-grid.component'\nimport {\n Box,\n Button,\n Divider,\n LinearProgress,\n MenuItem,\n Stack,\n Step,\n StepLabel,\n Stepper,\n TextField,\n Typography,\n} from '@mui/material'\nimport { Fragment, type ReactNode, type Ref } from 'react'\nimport { AI_SITE_FREE_PAGES_NOTE, AI_SITE_SUBMISSION_CHOICES, type AiSiteSubmissions } from '../model/ai-site-job'\nimport { AI_SITE_KINDS } from '../model/ai-site-kinds'\nimport { AI_SITE_START_TYPES, aiSiteStartKind, type AiSiteStartAnswers } from '../model/ai-site-start'\nimport {\n AI_SITE_START_STEP_LABELS,\n AI_SITE_START_STEPS,\n aiSiteStartStepIndex,\n type AiSiteStartField,\n type AiSiteStartFieldError,\n type AiSiteStartStep,\n} from '../model/ai-site-start-steps'\nimport type { AiSiteStartTracker } from './ai-site-start-tracking'\nimport { aiSiteStylePreview } from './ai-site-style-previews'\n\n/**\n * The guided start's question steps (AGL-3898), one decision each. Each step\n * draws its own answers and writes them through `answer`, exactly the patch\n * the single form wrote, so the answers object is the same whichever way it\n * was filled in. The dialog around them (`ai-site-start-card.component.tsx`)\n * owns the step, Back and Next, validation and the job.\n */\n\n/** Each part of the guided start links its own section of the Start with AI guide (AGL-3660). */\nexport const START_STEP_HELP = {\n kind: pluginDocsHelp('aiStart', {\n anchor: '#your-business',\n excerpt:\n '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.',\n }),\n audience: pluginDocsHelp('aiStart', {\n anchor: '#audience',\n excerpt: 'Who the site is for is who every page, every search listing and the contact form are written for. Optional.',\n }),\n review: pluginDocsHelp('aiStart', {\n anchor: '#review',\n excerpt: 'Every answer, each with Edit to change it. Plan my site creates the site, plans it, writes the pages and publishes it.',\n }),\n style: pluginDocsHelp('aiSiteLooks', {\n anchor: '#choose-a-style',\n excerpt:\n 'The style decides the look — theme, colors, fonts, buttons — and which pages the site usually has. Every site gets its own variation.',\n }),\n details: pluginDocsHelp('aiStart', {\n anchor: '#details',\n excerpt:\n '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.',\n }),\n}\n\n/** The icon on each kind-of-site card: the icon of the style that answer picks. */\nconst START_TYPE_ICONS: Readonly<Record<string, string>> = {\n 'a local service business': mdiBriefcaseOutline.path,\n 'a restaurant or cafe': mdiSilverwareForkKnife.path,\n 'a professional practice': mdiScaleBalance.path,\n 'a trades business': mdiWrench.path,\n 'a shop': mdiShopping.path,\n 'a nonprofit': mdiHandHeart.path,\n 'a personal portfolio': mdiViewGridOutline.path,\n 'an event': mdiCalendarHeart.path,\n}\n\n/** \"a restaurant or cafe\" as a card title: \"Restaurant or cafe\". The answer itself keeps its words. */\nexport function startTypeTitle(type: string): string {\n const words = type.replace(/^(a|an)\\s+/i, '')\n return words.charAt(0).toUpperCase() + words.slice(1)\n}\n\n/** The id the style grid gives \"Pick for me\", which is no kind: the answers' `kind: null`. */\nexport const AI_SITE_STYLE_AUTO = 'auto'\n\n/** The progress through the steps: labeled steps on a wide screen, a bar and \"Step 3 of 6\" on a phone. */\nexport function StartProgress({ step, compact }: { step: AiSiteStartStep; compact: boolean }) {\n const index = aiSiteStartStepIndex(step)\n const count = AI_SITE_START_STEPS.length\n const name = `Step ${index + 1} of ${count}`\n if (compact) {\n return (\n <Stack spacing={1} data-start-area=\"progress\" role=\"group\" aria-label={name}>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${name}: ${AI_SITE_START_STEP_LABELS[step]}`}\n </Typography>\n <LinearProgress variant=\"determinate\" value={((index + 1) / count) * 100} aria-hidden />\n </Stack>\n )\n }\n return (\n <Stepper activeStep={index} alternativeLabel aria-label={name} data-start-area=\"progress\">\n {AI_SITE_START_STEPS.map((id) => (\n <Step key={id}>\n <StepLabel>{AI_SITE_START_STEP_LABELS[id]}</StepLabel>\n </Step>\n ))}\n </Stepper>\n )\n}\n\ninterface StepProps {\n answers: AiSiteStartAnswers\n answer: (patch: Partial<AiSiteStartAnswers>) => void\n tracker: AiSiteStartTracker\n /** Each field's reason not to go forward, once the person has tried to. */\n errors: Partial<Record<AiSiteStartField, AiSiteStartFieldError>>\n /** Where each field's input is, so a refused Next can put the cursor in it. */\n fieldRef: (field: AiSiteStartField) => Ref<HTMLInputElement>\n}\n\n/** A text answer's tracking: each change, and leaving the field, which ends one edit. */\nconst typed = (tracker: AiSiteStartTracker, field: AiSiteStartField) => ({\n onBlur: () => tracker.fieldLeft(field),\n})\n\n/** Step: what kind of site it is — the one required answer — typed, or picked from cards. */\nexport function KindStep({ answers, answer, tracker, errors, fieldRef }: StepProps) {\n const picked = AI_SITE_START_TYPES.includes(answers.siteType) ? answers.siteType : null\n return (\n <Stack spacing={3}>\n <Box data-start-area=\"field-siteType\">\n <TextField\n fullWidth\n required\n inputRef={fieldRef('siteType')}\n label=\"What kind of site are you creating?\"\n placeholder=\"a neighborhood dog groomer that takes bookings\"\n value={answers.siteType}\n onChange={(event) => {\n answer({ siteType: event.target.value })\n tracker.fieldChanged('siteType')\n }}\n {...typed(tracker, 'siteType')}\n error={Boolean(errors.siteType)}\n helperText={errors.siteType?.message ?? 'A few words is enough, or pick one below.'}\n />\n </Box>\n <Stack spacing={1.5}>\n <Typography variant=\"overline\" color=\"text.secondary\" component=\"h2\">\n {'Or pick the closest'}\n </Typography>\n <OptionCardGrid\n label=\"Kind of site\"\n data-start-area=\"kind-cards\"\n columns={{ xs: 2, sm: 2, md: 4 }}\n options={AI_SITE_START_TYPES.map((type) => ({\n id: type,\n title: startTypeTitle(type),\n icon: START_TYPE_ICONS[type] ?? mdiBookOpenPageVariant.path,\n }))}\n value={picked}\n onChange={(type) => {\n answer({ siteType: type })\n tracker.fieldPicked('siteType')\n }}\n />\n </Stack>\n </Stack>\n )\n}\n\n/** Step: who the site is for. Optional. */\nexport function AudienceStep({ answers, answer, tracker, errors, fieldRef }: StepProps) {\n return (\n <Box data-start-area=\"field-audience\">\n <TextField\n fullWidth\n inputRef={fieldRef('audience')}\n label=\"Who is it for?\"\n placeholder=\"local dog owners who want a regular groom booked online\"\n value={answers.audience}\n onChange={(event) => {\n answer({ audience: event.target.value })\n tracker.fieldChanged('audience')\n }}\n {...typed(tracker, 'audience')}\n error={Boolean(errors.audience)}\n helperText={errors.audience?.message ?? 'Optional. It narrows who the pages are written for.'}\n />\n </Box>\n )\n}\n\n/**\n * Step: the style, from cards that show it. \"Pick for me\" leaves the kind\n * unset, which is what the single form's unpicked state was: the kind the\n * first answer suggests is the one used, and the card says which.\n */\nexport function StyleStep({ answers, answer, tracker }: StepProps) {\n const suggested = aiSiteStartKind({ ...answers, kind: null })\n return (\n <OptionCardGrid\n label=\"Style of site\"\n data-start-area=\"style-cards\"\n columns={{ xs: 2, sm: 2, md: 3 }}\n options={[\n {\n id: AI_SITE_STYLE_AUTO,\n title: 'Pick for me',\n description: `From your answer: ${suggested.label}`,\n icon: mdiCreation.path,\n },\n ...AI_SITE_KINDS.map((kind) => {\n const image = aiSiteStylePreview(kind.id)\n return { id: kind.id, title: kind.label, description: kind.blurb, ...(image ? { image } : {}) }\n }),\n ]}\n value={answers.kind ?? AI_SITE_STYLE_AUTO}\n onChange={(id) => {\n answer({ kind: id === AI_SITE_STYLE_AUTO ? null : id })\n tracker.fieldPicked('kind')\n }}\n />\n )\n}\n\n/** Step: where leads go, how many pages, and on a paid plan the welcome email. */\nexport function DetailsStep({\n answers,\n answer,\n tracker,\n errors,\n fieldRef,\n freeTaste,\n band,\n}: StepProps & { freeTaste: boolean; band: { min: number; max: number } }) {\n return (\n <Stack spacing={3}>\n {/*\n The one setting a new site owner has to make, asked where they are\n already answering: a contact form nobody routed files its messages\n somewhere the person has not looked.\n */}\n <Box data-start-area=\"field-submissions\">\n <TextField\n select\n fullWidth\n inputRef={fieldRef('submissions')}\n label=\"Where do form submissions go?\"\n value={answers.submissions}\n onChange={(event) => {\n answer({ submissions: event.target.value as AiSiteSubmissions })\n tracker.fieldPicked('submissions')\n }}\n error={Boolean(errors.submissions)}\n helperText={errors.submissions?.message ?? 'You can change this on the form itself afterwards.'}\n >\n {AI_SITE_SUBMISSION_CHOICES.map((option) => (\n <MenuItem key={option.id} value={option.id}>\n {`${option.label} — ${option.blurb}`}\n </MenuItem>\n ))}\n </TextField>\n </Box>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>\n <Box data-start-area=\"field-pages\" sx={{ flex: 1 }}>\n <TextField\n select\n fullWidth\n inputRef={fieldRef('pages')}\n label=\"Pages\"\n value={answers.pages}\n onChange={(event) => {\n answer({ pages: Number(event.target.value) })\n tracker.fieldPicked('pages')\n }}\n error={Boolean(errors.pages)}\n helperText={errors.pages?.message ?? (freeTaste ? AI_SITE_FREE_PAGES_NOTE : undefined)}\n >\n {Array.from({ length: band.max - band.min + 1 }, (_, index) => band.min + index).map((count) => (\n <MenuItem key={count} value={count}>\n {count}\n </MenuItem>\n ))}\n </TextField>\n </Box>\n {!freeTaste && (\n <Box data-start-area=\"field-welcomeEmail\" sx={{ flex: 1 }}>\n <TextField\n select\n fullWidth\n label=\"Welcome email\"\n value={answers.welcomeEmail ? 'yes' : 'no'}\n onChange={(event) => {\n answer({ welcomeEmail: event.target.value === 'yes' })\n tracker.fieldPicked('welcomeEmail')\n }}\n >\n <MenuItem value=\"yes\">{'Draft one'}</MenuItem>\n <MenuItem value=\"no\">{'No'}</MenuItem>\n </TextField>\n </Box>\n )}\n </Stack>\n </Stack>\n )\n}\n\n/** One answer on the review, with the way back to the step that asks it. */\nfunction ReviewRow({ label, value, step, onEdit }: { label: string; value: ReactNode; step: AiSiteStartStep; onEdit: (step: AiSiteStartStep) => void }) {\n return (\n <Stack direction=\"row\" spacing={2} sx={{ alignItems: 'flex-start', py: 1.5 }}>\n <Box sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" color=\"text.secondary\" component=\"dt\">\n {label}\n </Typography>\n <Typography variant=\"body1\" component=\"dd\" sx={{ m: 0, overflowWrap: 'anywhere' }}>\n {value}\n </Typography>\n </Box>\n <Button size=\"small\" onClick={() => onEdit(step)} aria-label={`Edit ${label.toLowerCase()}`}>\n {'Edit'}\n </Button>\n </Stack>\n )\n}\n\n/** Step: every answer, each with Edit, before \"Plan my site\". */\nexport function ReviewStep({\n answers,\n freeTaste,\n onEdit,\n children,\n}: {\n answers: AiSiteStartAnswers\n freeTaste: boolean\n onEdit: (step: AiSiteStartStep) => void\n children?: ReactNode\n}) {\n const kind = aiSiteStartKind(answers)\n const submissions = AI_SITE_SUBMISSION_CHOICES.find((option) => option.id === answers.submissions)\n const rows: Array<{ label: string; value: ReactNode; step: AiSiteStartStep }> = [\n { label: 'Kind of site', value: answers.siteType.trim(), step: 'kind' },\n { label: 'Who it is for', value: answers.audience.trim() || 'Anyone (not said)', step: 'audience' },\n { label: 'Style', value: answers.kind ? kind.label : `${kind.label}, picked for you`, step: 'style' },\n { label: 'Form submissions', value: submissions?.label ?? answers.submissions, step: 'details' },\n { label: 'Pages', value: String(answers.pages), step: 'details' },\n ...(freeTaste ? [] : [{ label: 'Welcome email', value: answers.welcomeEmail ? 'Draft one' : 'No', step: 'details' as const }]),\n ]\n return (\n <Stack spacing={3}>\n <Box component=\"dl\" data-start-area=\"review\" sx={{ m: 0 }}>\n {rows.map((row, index) => (\n <Fragment key={row.label}>\n {index > 0 && <Divider component=\"div\" />}\n <ReviewRow {...row} onEdit={onEdit} />\n </Fragment>\n ))}\n </Box>\n {children}\n </Stack>\n )\n}\n\n/** A step's help beside its heading. */\nexport function StepHelp({ step }: { step: AiSiteStartStep }) {\n const help = step === 'choose' ? null : START_STEP_HELP[step]\n return help ? <HelpTip sx={{ float: 'right' }} {...help} /> : null\n}\n"],"names":["pluginDocsHelp","mdiBookOpenPageVariant","mdiBriefcaseOutline","mdiCalendarHeart","mdiCreation","mdiHandHeart","mdiScaleBalance","mdiShopping","mdiSilverwareForkKnife","mdiViewGridOutline","mdiWrench","HelpTip","OptionCardGrid","Box","Button","Divider","LinearProgress","MenuItem","Stack","Step","StepLabel","Stepper","TextField","Typography","Fragment","AI_SITE_FREE_PAGES_NOTE","AI_SITE_SUBMISSION_CHOICES","AI_SITE_KINDS","AI_SITE_START_TYPES","aiSiteStartKind","AI_SITE_START_STEP_LABELS","AI_SITE_START_STEPS","aiSiteStartStepIndex","aiSiteStylePreview","START_STEP_HELP","kind","anchor","excerpt","audience","review","style","details","START_TYPE_ICONS","path","startTypeTitle","type","words","replace","charAt","toUpperCase","slice","AI_SITE_STYLE_AUTO","StartProgress","step","compact","index","count","length","name","spacing","data-start-area","role","aria-label","variant","color","value","aria-hidden","activeStep","alternativeLabel","map","id","typed","tracker","field","onBlur","fieldLeft","KindStep","answers","answer","errors","fieldRef","picked","includes","siteType","fullWidth","required","inputRef","label","placeholder","onChange","event","target","fieldChanged","error","Boolean","helperText","message","component","columns","xs","sm","md","options","title","icon","fieldPicked","AudienceStep","StyleStep","suggested","description","image","blurb","DetailsStep","freeTaste","band","select","submissions","option","direction","sx","flex","pages","Number","undefined","Array","from","max","min","_","welcomeEmail","ReviewRow","onEdit","alignItems","py","minWidth","m","overflowWrap","size","onClick","toLowerCase","ReviewStep","children","find","rows","trim","String","row","StepHelp","help","float"],"mappings":"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"}
|
|
@@ -0,0 +1,114 @@
|
|
|
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 AnalyticsEventName, type AnalyticsEventParams } from '@aglyn/aglyn/app-utils/analytics-events';
|
|
18
|
+
import { type AiSiteStartErrorCode, type AiSiteStartField, type AiSiteStartStep } from '../model/ai-site-start-steps';
|
|
19
|
+
/**
|
|
20
|
+
* Where people get lost in the guided start (AGL-3898), as the console's own
|
|
21
|
+
* product analytics: each step drawn, left forward or back and how long it
|
|
22
|
+
* took, the step the start was abandoned on, the field a step refused on,
|
|
23
|
+
* and the signs of a struggle — one field changed again and again, a step
|
|
24
|
+
* left idle, fast repeated clicks on one spot (rage) and clicks on parts of
|
|
25
|
+
* the form that do nothing (dead).
|
|
26
|
+
*
|
|
27
|
+
* Every event goes through `trackEvent`, the one door the console's GA4
|
|
28
|
+
* taxonomy has, so it is consent-gated exactly as every other console event
|
|
29
|
+
* is (`firebase-app.layout.tsx` registers the transport, or the refusal that
|
|
30
|
+
* drops it) and its params are sanitized there too. What is sent is only ever
|
|
31
|
+
* a step id, a field id, a closed error code, a closed area id, a count or a
|
|
32
|
+
* duration: never anything typed, picked or named.
|
|
33
|
+
*
|
|
34
|
+
* The detector is a plain object over a clock and a timer so its rules are
|
|
35
|
+
* tested with fake timers; {@link useAiSiteStartTracking} binds it to the
|
|
36
|
+
* dialog.
|
|
37
|
+
*/
|
|
38
|
+
/** No input on a step for this long is a struggle. */
|
|
39
|
+
export declare const AI_SITE_START_IDLE_MS = 30000;
|
|
40
|
+
/** This many clicks on one spot… */
|
|
41
|
+
export declare const AI_SITE_START_RAGE_CLICKS = 3;
|
|
42
|
+
/** …within this long… */
|
|
43
|
+
export declare const AI_SITE_START_RAGE_WINDOW_MS = 700;
|
|
44
|
+
/** …and within this distance of the first, is rage. */
|
|
45
|
+
export declare const AI_SITE_START_RAGE_RADIUS_PX = 30;
|
|
46
|
+
/** One field changed this many times is a struggle. */
|
|
47
|
+
export declare const AI_SITE_START_STRUGGLE_EDITS = 3;
|
|
48
|
+
/** At most this many dead clicks are reported on one visit to a step. */
|
|
49
|
+
export declare const AI_SITE_START_DEAD_CLICKS_PER_STEP = 3;
|
|
50
|
+
/** Why the start was left without a site started. */
|
|
51
|
+
export type AiSiteStartAbandonReason = 'skip' | 'close' | 'unload' | 'route';
|
|
52
|
+
type Send = <K extends AnalyticsEventName>(name: K, params: AnalyticsEventParams[K]) => void;
|
|
53
|
+
export interface AiSiteStartTrackerOptions {
|
|
54
|
+
send?: Send;
|
|
55
|
+
now?: () => number;
|
|
56
|
+
/** Whether the page is out of sight, when an idle step is not a struggle. */
|
|
57
|
+
hidden?: () => boolean;
|
|
58
|
+
}
|
|
59
|
+
/** A click as the detector reads it: where, and what it landed on. */
|
|
60
|
+
export interface AiSiteStartClick {
|
|
61
|
+
x: number;
|
|
62
|
+
y: number;
|
|
63
|
+
/** Whether it landed on something that acts on a click. */
|
|
64
|
+
interactive: boolean;
|
|
65
|
+
/** The closed id of the part of the form it landed in. */
|
|
66
|
+
area: string;
|
|
67
|
+
}
|
|
68
|
+
export interface AiSiteStartTracker {
|
|
69
|
+
/** A step was drawn. */
|
|
70
|
+
view(step: AiSiteStartStep): void;
|
|
71
|
+
/** The person went forward from the step on screen. */
|
|
72
|
+
complete(): void;
|
|
73
|
+
/** The person went back from the step on screen. */
|
|
74
|
+
back(): void;
|
|
75
|
+
/** A step refused to go forward on a field. */
|
|
76
|
+
fieldError(field: AiSiteStartField, code: AiSiteStartErrorCode): void;
|
|
77
|
+
/** A text answer changed; counted as one edit when the field is left. */
|
|
78
|
+
fieldChanged(field: AiSiteStartField): void;
|
|
79
|
+
/** A text answer was left. */
|
|
80
|
+
fieldLeft(field: AiSiteStartField): void;
|
|
81
|
+
/** A choice was picked, which is one edit. */
|
|
82
|
+
fieldPicked(field: AiSiteStartField): void;
|
|
83
|
+
/** Any input on the step, which puts off the idle signal. */
|
|
84
|
+
activity(): void;
|
|
85
|
+
/** A click anywhere in the dialog. */
|
|
86
|
+
click(click: AiSiteStartClick): void;
|
|
87
|
+
/** The start was left without a site started; reported once. */
|
|
88
|
+
abandon(reason: AiSiteStartAbandonReason): void;
|
|
89
|
+
/** A site was started (or the starter chosen): nothing after it is an abandonment. */
|
|
90
|
+
finish(): void;
|
|
91
|
+
/** Stops the idle timer. */
|
|
92
|
+
dispose(): void;
|
|
93
|
+
}
|
|
94
|
+
export declare function createAiSiteStartTracker(options?: AiSiteStartTrackerOptions): AiSiteStartTracker;
|
|
95
|
+
/** The attribute naming the part of the form an element is in (a closed id, never content). */
|
|
96
|
+
export declare const AI_SITE_START_AREA_ATTRIBUTE = "data-start-area";
|
|
97
|
+
/** A DOM click, as the detector reads it. */
|
|
98
|
+
export declare function aiSiteStartClickOf(event: {
|
|
99
|
+
clientX: number;
|
|
100
|
+
clientY: number;
|
|
101
|
+
target: EventTarget | null;
|
|
102
|
+
}): AiSiteStartClick;
|
|
103
|
+
/**
|
|
104
|
+
* The tracker for one open guided start: views each step as it is drawn, and
|
|
105
|
+
* reports the start abandoned when the tab goes away or the page changes
|
|
106
|
+
* under it. A start that is finished ({@link AiSiteStartTracker.finish}) or
|
|
107
|
+
* already abandoned reports nothing more.
|
|
108
|
+
*
|
|
109
|
+
* The unmount report waits a tick, so a development double mount (React's
|
|
110
|
+
* StrictMode runs an effect's cleanup and then the effect again) is not read
|
|
111
|
+
* as the person leaving.
|
|
112
|
+
*/
|
|
113
|
+
export declare function useAiSiteStartTracking(step: AiSiteStartStep | null, active: boolean): AiSiteStartTracker;
|
|
114
|
+
export {};
|