@12-apps/mcp 3.5.1 → 3.6.0
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/ADOPTING.md +69 -1
- package/dist/chunk-FBUSSQSK.js +22 -0
- package/dist/chunk-FBUSSQSK.js.map +1 -0
- package/dist/{chunk-FYEVBTDU.js → chunk-HRAQKMDC.js} +12 -20
- package/dist/chunk-HRAQKMDC.js.map +1 -0
- package/dist/{guide-DV5MQbCg.d.ts → guide-KQNcXlMG.d.ts} +32 -17
- package/dist/index.d.ts +2 -1
- package/dist/index.js +11 -7
- package/dist/index.js.map +1 -1
- package/dist/oauth/index.d.ts +1 -1
- package/dist/oauth/index.js +1 -1
- package/dist/pt-BR-e1RnV2v8.d.ts +25 -0
- package/dist/react/index.d.ts +197 -30
- package/dist/react/index.js +327 -147
- package/dist/react/index.js.map +1 -1
- package/package.json +4 -4
- package/src/guide.ts +33 -165
- package/src/index.ts +8 -3
- package/src/pt-BR.ts +195 -0
- package/src/react/ai-capabilities.tsx +10 -6
- package/src/react/ai-connection-utils.ts +18 -13
- package/src/react/ai-flow-steps.tsx +63 -43
- package/src/react/ai-landing.tsx +47 -21
- package/src/react/ai-onboarding.tsx +30 -25
- package/src/react/ai-status-board.tsx +18 -11
- package/src/react/ai-steps.tsx +60 -23
- package/src/react/copy.ts +154 -0
- package/src/react/host-connect-guide.tsx +24 -8
- package/src/react/host-select-step.tsx +4 -1
- package/src/react/index.ts +21 -3
- package/src/react/pt-BR.ts +109 -0
- package/dist/chunk-FYEVBTDU.js.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import type { McpAiCopy } from "./copy";
|
|
3
4
|
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
|
|
4
5
|
import OpenInNewIcon from "@mui/icons-material/OpenInNew";
|
|
5
6
|
import { useEffect, useState } from "react";
|
|
@@ -32,21 +33,21 @@ const POLL_INTERVAL_MS = 3000;
|
|
|
32
33
|
function StepNav({
|
|
33
34
|
nav,
|
|
34
35
|
onNext,
|
|
35
|
-
nextLabel
|
|
36
|
+
nextLabel,
|
|
36
37
|
nextDisabled = false,
|
|
37
38
|
nextTestId,
|
|
39
|
+
copy,
|
|
38
40
|
}: {
|
|
39
41
|
nav: GuidedNav;
|
|
40
42
|
onNext: () => void;
|
|
41
43
|
nextLabel?: string;
|
|
42
44
|
nextDisabled?: boolean;
|
|
43
45
|
nextTestId?: string;
|
|
46
|
+
copy: McpAiCopy;
|
|
44
47
|
}): React.JSX.Element {
|
|
45
48
|
return (
|
|
46
49
|
<Stack direction="row" spacing={1} alignItems="center" justifyContent="space-between">
|
|
47
|
-
<Button variant="ghost" onClick={() => nav.back()} data-testid="ai-step-back">
|
|
48
|
-
Voltar
|
|
49
|
-
</Button>
|
|
50
|
+
<Button variant="ghost" onClick={() => nav.back()} data-testid="ai-step-back">{copy.flow.back}</Button>
|
|
50
51
|
<Button onClick={onNext} disabled={nextDisabled} data-testid={nextTestId}>
|
|
51
52
|
{nextLabel}
|
|
52
53
|
</Button>
|
|
@@ -55,39 +56,53 @@ function StepNav({
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
/** Step 2 — copy the store URL; copying is the action that advances the wizard. */
|
|
58
|
-
export function CopyUrlStep({
|
|
59
|
+
export function CopyUrlStep({
|
|
60
|
+
nav,
|
|
61
|
+
endpointUrl,
|
|
62
|
+
copy,
|
|
63
|
+
}: {
|
|
64
|
+
nav: GuidedNav;
|
|
65
|
+
endpointUrl: string;
|
|
66
|
+
copy: McpAiCopy;
|
|
67
|
+
}): React.JSX.Element {
|
|
59
68
|
return (
|
|
60
69
|
<Stack spacing={3} data-testid="ai-copy-step">
|
|
61
70
|
<Box>
|
|
62
71
|
<Text variant="heading" size="sm" as="h2">
|
|
63
|
-
|
|
72
|
+
{copy.flow.copyUrlTitle}
|
|
64
73
|
</Text>
|
|
65
74
|
<Text variant="caption" as="p" color="secondary">
|
|
66
|
-
|
|
75
|
+
{copy.flow.urlCaption}
|
|
67
76
|
</Text>
|
|
68
77
|
</Box>
|
|
69
|
-
<EndpointCopyBlock endpointUrl={endpointUrl} copied={false} onCopy={() => nav.next()} />
|
|
78
|
+
<EndpointCopyBlock copy={copy.connectGuide} flowCopy={copy.flow} endpointUrl={endpointUrl} copied={false} onCopy={() => nav.next()} />
|
|
70
79
|
<Box>
|
|
71
|
-
<Button variant="ghost" onClick={() => nav.back()} data-testid="ai-step-back">
|
|
72
|
-
Voltar
|
|
73
|
-
</Button>
|
|
80
|
+
<Button variant="ghost" onClick={() => nav.back()} data-testid="ai-step-back">{copy.flow.back}</Button>
|
|
74
81
|
</Box>
|
|
75
82
|
</Stack>
|
|
76
83
|
);
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
/** Step 3 — open the host's connectors and add + configure the custom connector. */
|
|
80
|
-
export function ConfigureStep({
|
|
87
|
+
export function ConfigureStep({
|
|
88
|
+
nav,
|
|
89
|
+
host,
|
|
90
|
+
copy,
|
|
91
|
+
}: {
|
|
92
|
+
nav: GuidedNav;
|
|
93
|
+
host: AiHostGuide;
|
|
94
|
+
copy: McpAiCopy;
|
|
95
|
+
}): React.JSX.Element {
|
|
81
96
|
// Hosts with no direct link (e.g. Claude Desktop) have nothing to open, so
|
|
82
97
|
// "Próximo" is available immediately; otherwise it unlocks on the open click.
|
|
83
98
|
const [opened, setOpened] = useState(!host.link);
|
|
84
99
|
return (
|
|
85
100
|
<Stack spacing={2.5} data-testid="ai-configure-step">
|
|
86
|
-
<HostConnectHeader host={host} />
|
|
101
|
+
<HostConnectHeader copy={copy.connectGuide} host={host} />
|
|
87
102
|
<HostOpenButton host={host} onOpen={() => setOpened(true)} />
|
|
88
103
|
<HostStepList steps={host.steps.slice(0, CONFIGURE_STEP_COUNT)} start={1} />
|
|
89
|
-
<HostDocsLink host={host} />
|
|
90
|
-
<StepNav nav={nav} onNext={() => nav.next()} nextDisabled={!opened} nextTestId="ai-configure-next" />
|
|
104
|
+
<HostDocsLink copy={copy.connectGuide} host={host} />
|
|
105
|
+
<StepNav copy={copy} nav={nav} onNext={() => nav.next()} nextDisabled={!opened} nextTestId="ai-configure-next" />
|
|
91
106
|
</Stack>
|
|
92
107
|
);
|
|
93
108
|
}
|
|
@@ -101,15 +116,17 @@ export function ConfigureStageStep({
|
|
|
101
116
|
nav,
|
|
102
117
|
host,
|
|
103
118
|
stage,
|
|
119
|
+
copy,
|
|
104
120
|
}: {
|
|
105
121
|
nav: GuidedNav;
|
|
106
122
|
host: AiHostGuide;
|
|
107
123
|
stage: AiHostConfigureStage;
|
|
124
|
+
copy: McpAiCopy;
|
|
108
125
|
}): React.JSX.Element {
|
|
109
126
|
const [opened, setOpened] = useState(!stage.link);
|
|
110
127
|
return (
|
|
111
128
|
<Stack spacing={2.5} data-testid={`ai-stage-${stage.id}`}>
|
|
112
|
-
<HostConnectHeader host={host} />
|
|
129
|
+
<HostConnectHeader copy={copy.connectGuide} host={host} />
|
|
113
130
|
{stage.link && (
|
|
114
131
|
<Box>
|
|
115
132
|
<Button
|
|
@@ -125,8 +142,8 @@ export function ConfigureStageStep({
|
|
|
125
142
|
</Box>
|
|
126
143
|
)}
|
|
127
144
|
<HostStepList steps={stage.steps} start={1} />
|
|
128
|
-
<HostDocsLink host={host} />
|
|
129
|
-
<StepNav
|
|
145
|
+
<HostDocsLink copy={copy.connectGuide} host={host} />
|
|
146
|
+
<StepNav copy={copy}
|
|
130
147
|
nav={nav}
|
|
131
148
|
onNext={() => nav.next()}
|
|
132
149
|
nextDisabled={!opened}
|
|
@@ -141,22 +158,24 @@ export function ConnectStep({
|
|
|
141
158
|
nav,
|
|
142
159
|
host,
|
|
143
160
|
connectPrompt,
|
|
161
|
+
copy,
|
|
144
162
|
}: {
|
|
145
163
|
nav: GuidedNav;
|
|
146
164
|
host: AiHostGuide;
|
|
147
165
|
connectPrompt: string;
|
|
166
|
+
copy: McpAiCopy;
|
|
148
167
|
}): React.JSX.Element {
|
|
149
168
|
return (
|
|
150
169
|
<Stack spacing={2.5} data-testid="ai-connect-step">
|
|
151
|
-
<HostConnectHeader host={host} />
|
|
170
|
+
<HostConnectHeader copy={copy.connectGuide} host={host} />
|
|
152
171
|
<HostStepList steps={host.steps.slice(CONFIGURE_STEP_COUNT)} start={CONFIGURE_STEP_COUNT + 1} />
|
|
153
|
-
<PromptCopyBlock
|
|
154
|
-
title=
|
|
155
|
-
caption=
|
|
172
|
+
<PromptCopyBlock flowCopy={copy.flow}
|
|
173
|
+
title={copy.flow.pasteTitle}
|
|
174
|
+
caption={copy.flow.promptCaption}
|
|
156
175
|
message={connectPrompt}
|
|
157
176
|
/>
|
|
158
|
-
<HostDocsLink host={host} />
|
|
159
|
-
<StepNav nav={nav} onNext={() => nav.next()} nextLabel=
|
|
177
|
+
<HostDocsLink copy={copy.connectGuide} host={host} />
|
|
178
|
+
<StepNav copy={copy} nav={nav} onNext={() => nav.next()} nextLabel={copy.flow.advance} nextTestId="ai-connect-done" />
|
|
160
179
|
</Stack>
|
|
161
180
|
);
|
|
162
181
|
}
|
|
@@ -170,18 +189,19 @@ export function InstallStep({
|
|
|
170
189
|
nav,
|
|
171
190
|
host,
|
|
172
191
|
connectPrompt,
|
|
192
|
+
copy,
|
|
173
193
|
}: {
|
|
174
194
|
nav: GuidedNav;
|
|
175
195
|
host: AiHostGuide;
|
|
176
196
|
connectPrompt: string;
|
|
197
|
+
copy: McpAiCopy;
|
|
177
198
|
}): React.JSX.Element {
|
|
178
199
|
const [opened, setOpened] = useState(false);
|
|
179
200
|
return (
|
|
180
201
|
<Stack spacing={2.5} data-testid="ai-install-step">
|
|
181
|
-
<HostConnectHeader host={host} />
|
|
202
|
+
<HostConnectHeader copy={copy.connectGuide} host={host} />
|
|
182
203
|
<Text variant="body" as="p" color="secondary">
|
|
183
|
-
|
|
184
|
-
credenciais.
|
|
204
|
+
{copy.flow.installBody}
|
|
185
205
|
</Text>
|
|
186
206
|
{host.pluginUrl && (
|
|
187
207
|
<Box>
|
|
@@ -192,21 +212,21 @@ export function InstallStep({
|
|
|
192
212
|
}}
|
|
193
213
|
data-testid="ai-install-open"
|
|
194
214
|
>
|
|
195
|
-
|
|
215
|
+
{copy.flow.installAction}
|
|
196
216
|
<OpenInNewIcon sx={{ fontSize: 16, ml: 0.5 }} />
|
|
197
217
|
</Button>
|
|
198
218
|
</Box>
|
|
199
219
|
)}
|
|
200
|
-
<PromptCopyBlock
|
|
201
|
-
title=
|
|
202
|
-
caption=
|
|
220
|
+
<PromptCopyBlock flowCopy={copy.flow}
|
|
221
|
+
title={copy.flow.askTitle}
|
|
222
|
+
caption={copy.flow.pasteInstallCaption}
|
|
203
223
|
message={connectPrompt}
|
|
204
224
|
/>
|
|
205
|
-
<HostDocsLink host={host} />
|
|
206
|
-
<StepNav
|
|
225
|
+
<HostDocsLink copy={copy.connectGuide} host={host} />
|
|
226
|
+
<StepNav copy={copy}
|
|
207
227
|
nav={nav}
|
|
208
228
|
onNext={() => nav.next()}
|
|
209
|
-
nextLabel=
|
|
229
|
+
nextLabel={copy.flow.advance}
|
|
210
230
|
nextDisabled={!opened}
|
|
211
231
|
nextTestId="ai-install-done"
|
|
212
232
|
/>
|
|
@@ -225,11 +245,13 @@ export function ConfirmStep({
|
|
|
225
245
|
connections,
|
|
226
246
|
hosts,
|
|
227
247
|
onRetest,
|
|
248
|
+
copy,
|
|
228
249
|
}: {
|
|
229
250
|
nav: GuidedNav;
|
|
230
251
|
connections: readonly AiConnection[];
|
|
231
252
|
hosts: readonly AiHostGuide[];
|
|
232
253
|
onRetest: () => void;
|
|
254
|
+
copy: McpAiCopy;
|
|
233
255
|
}): React.JSX.Element {
|
|
234
256
|
const selectedHostId = nav.data.selectedHost as string | undefined;
|
|
235
257
|
const connection = connectionForHost(connections, selectedHostId);
|
|
@@ -250,15 +272,15 @@ export function ConfirmStep({
|
|
|
250
272
|
<CheckCircleIcon sx={{ color: "success.main" }} />
|
|
251
273
|
<Box>
|
|
252
274
|
<Text variant="body" weight="bold" as="p">
|
|
253
|
-
{label}
|
|
275
|
+
{copy.flow.connectedTo(label)}
|
|
254
276
|
</Text>
|
|
255
277
|
<Text variant="caption" as="p" color="secondary">
|
|
256
|
-
{activeAgo(connection.lastActiveAt)}
|
|
278
|
+
{activeAgo(connection.lastActiveAt, copy.summary)}
|
|
257
279
|
</Text>
|
|
258
280
|
</Box>
|
|
259
281
|
</Stack>
|
|
260
282
|
<Box>
|
|
261
|
-
<Button onClick={() => nav.complete({ connectedHost: selectedHostId })}>
|
|
283
|
+
<Button onClick={() => nav.complete({ connectedHost: selectedHostId })}>{copy.flow.finish}</Button>
|
|
262
284
|
</Box>
|
|
263
285
|
</Stack>
|
|
264
286
|
);
|
|
@@ -270,20 +292,18 @@ export function ConfirmStep({
|
|
|
270
292
|
<Progress variant="circular" circularSize={22} thickness={4} dataTestId="ai-confirm-spinner" />
|
|
271
293
|
<Box>
|
|
272
294
|
<Text variant="body" weight="bold" as="p">
|
|
273
|
-
|
|
295
|
+
{copy.flow.waitingTitle}
|
|
274
296
|
</Text>
|
|
275
297
|
<Text variant="caption" as="p" color="secondary">
|
|
276
|
-
|
|
298
|
+
{copy.flow.waitingBody(label)}
|
|
277
299
|
</Text>
|
|
278
300
|
</Box>
|
|
279
301
|
</Stack>
|
|
280
302
|
<Stack direction="row" spacing={1}>
|
|
281
303
|
<Button variant="ghost" size="sm" onClick={onRetest} data-testid="ai-confirm-retest">
|
|
282
|
-
|
|
283
|
-
</Button>
|
|
284
|
-
<Button variant="ghost" size="sm" onClick={() => nav.back()}>
|
|
285
|
-
Voltar
|
|
304
|
+
{copy.flow.testNow}
|
|
286
305
|
</Button>
|
|
306
|
+
<Button variant="ghost" size="sm" onClick={() => nav.back()}>{copy.flow.back}</Button>
|
|
287
307
|
</Stack>
|
|
288
308
|
</Stack>
|
|
289
309
|
);
|
package/src/react/ai-landing.tsx
CHANGED
|
@@ -10,19 +10,42 @@ import { Box } from "@12-apps/ui/mui/Box";
|
|
|
10
10
|
import { Stack } from "@12-apps/ui/mui/Stack";
|
|
11
11
|
import { Text } from "@12-apps/ui/typography/Text";
|
|
12
12
|
|
|
13
|
-
import {
|
|
13
|
+
import type { AiCapability } from "../guide";
|
|
14
|
+
import type { AiCapabilitiesCopy, AiLandingCopy } from "./copy";
|
|
14
15
|
import { AiCapabilities } from "./ai-capabilities";
|
|
15
16
|
import { FeatureBadge, type FeatureBadgeItem } from "./feature-badge";
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
/**
|
|
19
|
+
* The ICONS for the landing's reassurance strip, keyed by the copy's own ids.
|
|
20
|
+
*
|
|
21
|
+
* The package keeps the icons and the host keeps the words: an icon is not
|
|
22
|
+
* copy, and a pack that had to ship React elements could not be a plain data
|
|
23
|
+
* file. An id the map does not know renders without an icon rather than
|
|
24
|
+
* throwing — a host adding a fifth point should get its words on screen.
|
|
25
|
+
*/
|
|
26
|
+
const TRUST_ICONS: Readonly<Record<string, React.JSX.Element>> = {
|
|
27
|
+
login: <LockOpenOutlinedIcon />,
|
|
28
|
+
install: <BoltOutlinedIcon />,
|
|
29
|
+
permissions: <VerifiedUserOutlinedIcon />,
|
|
30
|
+
surface: <LanguageOutlinedIcon />,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function trustBadges(trust: AiLandingCopy["trust"]): FeatureBadgeItem[] {
|
|
34
|
+
return trust.map((point) => ({
|
|
35
|
+
icon: TRUST_ICONS[point.id],
|
|
36
|
+
label: point.label,
|
|
37
|
+
caption: point.caption,
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
23
40
|
|
|
24
41
|
/** The single-column marketing hero: eyebrow + headline + description + CTA. */
|
|
25
|
-
function Hero({
|
|
42
|
+
function Hero({
|
|
43
|
+
onStart,
|
|
44
|
+
copy,
|
|
45
|
+
}: {
|
|
46
|
+
onStart: () => void;
|
|
47
|
+
copy: AiLandingCopy;
|
|
48
|
+
}): React.JSX.Element {
|
|
26
49
|
return (
|
|
27
50
|
<Stack spacing={2.5} sx={{ maxWidth: 680 }}>
|
|
28
51
|
<Box
|
|
@@ -34,19 +57,17 @@ function Hero({ onStart }: { onStart: () => void }): React.JSX.Element {
|
|
|
34
57
|
textTransform: "uppercase",
|
|
35
58
|
}}
|
|
36
59
|
>
|
|
37
|
-
|
|
60
|
+
{copy.eyebrow}
|
|
38
61
|
</Box>
|
|
39
62
|
<Text variant="heading" size="xl" weight="bold" as="h1">
|
|
40
63
|
Conecte{" "}
|
|
41
64
|
<Box component="span" sx={{ color: "primary.main" }}>
|
|
42
65
|
assistentes de IA
|
|
43
66
|
</Box>{" "}
|
|
44
|
-
|
|
67
|
+
{copy.titleTail}
|
|
45
68
|
</Text>
|
|
46
69
|
<Text variant="body" color="secondary" as="p">
|
|
47
|
-
|
|
48
|
-
pedidos — e executarem ações por você, direto na conversa. Com segurança e sem instalar
|
|
49
|
-
nada.
|
|
70
|
+
{copy.lede}
|
|
50
71
|
</Text>
|
|
51
72
|
<Box sx={{ pt: 1 }}>
|
|
52
73
|
<Button onClick={onStart} data-testid="ai-landing-start">
|
|
@@ -61,20 +82,25 @@ function Hero({ onStart }: { onStart: () => void }): React.JSX.Element {
|
|
|
61
82
|
* Homepage-style marketing landing for the AI integration (shown before the
|
|
62
83
|
* owner starts): a hero, the permission reassurance, a trust/feature strip, and
|
|
63
84
|
* the capability highlights. `onStart` begins the guided flow. `permissionModel`
|
|
64
|
-
* and `capabilities`
|
|
85
|
+
* and `capabilities` are REQUIRED host copy — the package ships no default
|
|
86
|
+
* sentence (FUT-760).
|
|
65
87
|
*/
|
|
66
88
|
export function AiLanding({
|
|
67
89
|
onStart,
|
|
68
|
-
permissionModel
|
|
69
|
-
capabilities
|
|
90
|
+
permissionModel,
|
|
91
|
+
capabilities,
|
|
92
|
+
copy,
|
|
93
|
+
capabilitiesCopy,
|
|
70
94
|
}: {
|
|
71
95
|
onStart: () => void;
|
|
72
|
-
permissionModel
|
|
73
|
-
capabilities
|
|
96
|
+
permissionModel: string;
|
|
97
|
+
capabilities: readonly AiCapability[];
|
|
98
|
+
copy: AiLandingCopy;
|
|
99
|
+
capabilitiesCopy: AiCapabilitiesCopy;
|
|
74
100
|
}): React.JSX.Element {
|
|
75
101
|
return (
|
|
76
102
|
<Stack spacing={6} data-testid="ai-landing">
|
|
77
|
-
<Hero onStart={onStart} />
|
|
103
|
+
<Hero onStart={onStart} copy={copy} />
|
|
78
104
|
|
|
79
105
|
<Stack
|
|
80
106
|
direction="row"
|
|
@@ -111,14 +137,14 @@ export function AiLanding({
|
|
|
111
137
|
gridTemplateColumns: { xs: "1fr", sm: "1fr 1fr", md: "repeat(4, 1fr)" },
|
|
112
138
|
}}
|
|
113
139
|
>
|
|
114
|
-
{
|
|
140
|
+
{trustBadges(copy.trust).map((f) => (
|
|
115
141
|
<FeatureBadge key={f.label} icon={f.icon} label={f.label} caption={f.caption} />
|
|
116
142
|
))}
|
|
117
143
|
</Box>
|
|
118
144
|
</Box>
|
|
119
145
|
|
|
120
146
|
<Box>
|
|
121
|
-
<AiCapabilities capabilities={capabilities} />
|
|
147
|
+
<AiCapabilities capabilities={capabilities} copy={capabilitiesCopy} />
|
|
122
148
|
</Box>
|
|
123
149
|
</Stack>
|
|
124
150
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import type { McpAiCopy } from "./copy";
|
|
3
4
|
import {
|
|
4
5
|
GuidedSection,
|
|
5
6
|
OnboardingProvider,
|
|
@@ -9,9 +10,6 @@ import {
|
|
|
9
10
|
} from "@12-apps/onboarding";
|
|
10
11
|
|
|
11
12
|
import {
|
|
12
|
-
AI_CAPABILITIES,
|
|
13
|
-
aiHostGuides,
|
|
14
|
-
AI_PERMISSION_MODEL,
|
|
15
13
|
type AiCapability,
|
|
16
14
|
type AiHostGuide,
|
|
17
15
|
} from "../guide";
|
|
@@ -44,20 +42,20 @@ export interface AiIntegrationOnboardingProps {
|
|
|
44
42
|
/** Show the dev-only "reset onboarding" button. @default false */
|
|
45
43
|
devReset?: boolean;
|
|
46
44
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
45
|
+
* Assistants offered in the flow, with their step-by-step instructions.
|
|
46
|
+
*
|
|
47
|
+
* REQUIRED since FUT-760. These used to default to this package's own pt-BR
|
|
48
|
+
* walkthrough, so a host that said nothing shipped one product's Portuguese
|
|
49
|
+
* and had no field to decline it. `PT_BR_AI_HOST_GUIDES(platformName)` is that
|
|
50
|
+
* exact content, now chosen by name.
|
|
53
51
|
*/
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
|
|
52
|
+
hosts: readonly AiHostGuide[];
|
|
53
|
+
/** Capability cards on the landing. REQUIRED — see `hosts`. */
|
|
54
|
+
capabilities: readonly AiCapability[];
|
|
55
|
+
/** Permission reassurance copy on the landing. REQUIRED — see `hosts`. */
|
|
56
|
+
permissionModel: string;
|
|
57
|
+
/** Every sentence the screens render. REQUIRED — see `hosts`. */
|
|
58
|
+
copy: McpAiCopy;
|
|
61
59
|
/**
|
|
62
60
|
* Message the owner pastes into the assistant on the Conectar step.
|
|
63
61
|
*
|
|
@@ -97,6 +95,7 @@ interface FlowProps {
|
|
|
97
95
|
onRetest: () => void;
|
|
98
96
|
onDisconnect: DisconnectHandler | undefined;
|
|
99
97
|
devReset: boolean;
|
|
98
|
+
copy: McpAiCopy;
|
|
100
99
|
}
|
|
101
100
|
|
|
102
101
|
/**
|
|
@@ -116,6 +115,7 @@ function AiOnboardingFlow(props: FlowProps): React.JSX.Element {
|
|
|
116
115
|
onRetest,
|
|
117
116
|
onDisconnect,
|
|
118
117
|
devReset,
|
|
118
|
+
copy,
|
|
119
119
|
} = props;
|
|
120
120
|
const { state } = useOnboarding();
|
|
121
121
|
const selectedHost =
|
|
@@ -128,6 +128,7 @@ function AiOnboardingFlow(props: FlowProps): React.JSX.Element {
|
|
|
128
128
|
connectPrompt,
|
|
129
129
|
connections,
|
|
130
130
|
onRetest,
|
|
131
|
+
copy,
|
|
131
132
|
});
|
|
132
133
|
const connectedHostId = (state.data.connectedHost ??
|
|
133
134
|
state.data.selectedHost) as string | undefined;
|
|
@@ -135,24 +136,27 @@ function AiOnboardingFlow(props: FlowProps): React.JSX.Element {
|
|
|
135
136
|
return (
|
|
136
137
|
<GuidedSection
|
|
137
138
|
steps={steps}
|
|
138
|
-
title=
|
|
139
|
-
startLabel=
|
|
139
|
+
title={copy.onboarding.title}
|
|
140
|
+
startLabel={copy.landing.start}
|
|
140
141
|
renderLanding={(start) => (
|
|
141
142
|
<AiLanding
|
|
142
143
|
onStart={start}
|
|
143
144
|
permissionModel={permissionModel}
|
|
144
145
|
capabilities={capabilities}
|
|
146
|
+
copy={copy.landing}
|
|
147
|
+
capabilitiesCopy={copy.capabilities}
|
|
145
148
|
/>
|
|
146
149
|
)}
|
|
147
|
-
configuredTitle={connectedTitle(connections, hosts, connectedHostId)}
|
|
148
|
-
configuredSummary={connectedSummary(connections)}
|
|
149
|
-
editLabel=
|
|
150
|
+
configuredTitle={connectedTitle(connections, hosts, connectedHostId, copy.summary)}
|
|
151
|
+
configuredSummary={connectedSummary(connections, copy.summary)}
|
|
152
|
+
editLabel={copy.onboarding.editLabel}
|
|
150
153
|
completedContent={(nav) => (
|
|
151
154
|
<StatusBoard
|
|
152
155
|
nav={nav}
|
|
153
156
|
connections={connections}
|
|
154
157
|
hosts={hosts}
|
|
155
158
|
onDisconnect={onDisconnect}
|
|
159
|
+
copy={copy}
|
|
156
160
|
/>
|
|
157
161
|
)}
|
|
158
162
|
devReset={devReset}
|
|
@@ -179,15 +183,15 @@ export function AiIntegrationOnboarding({
|
|
|
179
183
|
connections,
|
|
180
184
|
featureKey = DEFAULT_FEATURE_KEY,
|
|
181
185
|
devReset = false,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
permissionModel = AI_PERMISSION_MODEL,
|
|
186
|
+
hosts,
|
|
187
|
+
capabilities,
|
|
188
|
+
permissionModel,
|
|
186
189
|
connectPrompt,
|
|
187
190
|
onRetest = () => {
|
|
188
191
|
if (typeof window !== "undefined") window.location.reload();
|
|
189
192
|
},
|
|
190
193
|
onDisconnect,
|
|
194
|
+
copy,
|
|
191
195
|
}: AiIntegrationOnboardingProps): React.JSX.Element {
|
|
192
196
|
return (
|
|
193
197
|
<OnboardingProvider
|
|
@@ -205,6 +209,7 @@ export function AiIntegrationOnboarding({
|
|
|
205
209
|
onRetest={onRetest}
|
|
206
210
|
onDisconnect={onDisconnect}
|
|
207
211
|
devReset={devReset}
|
|
212
|
+
copy={copy}
|
|
208
213
|
/>
|
|
209
214
|
</OnboardingProvider>
|
|
210
215
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
+
import type { AiStatusBoardCopy } from "./copy";
|
|
3
4
|
import AddLinkOutlinedIcon from "@mui/icons-material/AddLinkOutlined";
|
|
4
5
|
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
|
|
5
6
|
import LinkOffOutlinedIcon from "@mui/icons-material/LinkOffOutlined";
|
|
@@ -33,7 +34,7 @@ export interface HostStatus {
|
|
|
33
34
|
* The right-hand side of a CONNECTED box: the green pill, then the two things
|
|
34
35
|
* the pill alone left the owner unable to do.
|
|
35
36
|
*
|
|
36
|
-
* "
|
|
37
|
+
* "{copy.instructions}" exists because the setup steps were reachable ONLY from a RED
|
|
37
38
|
* card's "Conectar" — so on an all-green board there was no way to re-read how
|
|
38
39
|
* any of them was connected, which is exactly when someone re-doing the setup on
|
|
39
40
|
* a second machine needs them. It re-enters the same flow that button does.
|
|
@@ -45,10 +46,12 @@ function ConnectedControls({
|
|
|
45
46
|
host,
|
|
46
47
|
onConnect,
|
|
47
48
|
onDisconnect,
|
|
49
|
+
copy,
|
|
48
50
|
}: {
|
|
49
51
|
host: AiHostGuide;
|
|
50
52
|
onConnect: (hostId: string) => void;
|
|
51
53
|
onDisconnect?: DisconnectHandler;
|
|
54
|
+
copy: AiStatusBoardCopy;
|
|
52
55
|
}): React.JSX.Element {
|
|
53
56
|
return (
|
|
54
57
|
<Stack
|
|
@@ -80,7 +83,7 @@ function ConnectedControls({
|
|
|
80
83
|
data-testid={`ai-status-instructions-${host.id}`}
|
|
81
84
|
>
|
|
82
85
|
<MenuBookOutlinedIcon sx={{ fontSize: 16, mr: 0.5 }} />
|
|
83
|
-
|
|
86
|
+
{copy.instructions}
|
|
84
87
|
</Button>
|
|
85
88
|
{onDisconnect ? (
|
|
86
89
|
<ConfirmButton
|
|
@@ -89,11 +92,11 @@ function ConnectedControls({
|
|
|
89
92
|
size="sm"
|
|
90
93
|
onClick={() => onDisconnect(host.id)}
|
|
91
94
|
confirm={{
|
|
92
|
-
title:
|
|
95
|
+
title: copy.disconnectTitle,
|
|
93
96
|
entityName: host.label,
|
|
94
97
|
description:
|
|
95
|
-
|
|
96
|
-
confirmText:
|
|
98
|
+
copy.disconnectBody,
|
|
99
|
+
confirmText: copy.disconnectConfirm,
|
|
97
100
|
dataTestId: `ai-status-disconnect-confirm-${host.id}`,
|
|
98
101
|
}}
|
|
99
102
|
data-testid={`ai-status-disconnect-${host.id}`}
|
|
@@ -111,10 +114,12 @@ function StatusBox({
|
|
|
111
114
|
status,
|
|
112
115
|
onConnect,
|
|
113
116
|
onDisconnect,
|
|
117
|
+
copy,
|
|
114
118
|
}: {
|
|
115
119
|
status: HostStatus;
|
|
116
120
|
onConnect: (hostId: string) => void;
|
|
117
121
|
onDisconnect?: DisconnectHandler;
|
|
122
|
+
copy: AiStatusBoardCopy;
|
|
118
123
|
}): React.JSX.Element {
|
|
119
124
|
const { host, connected, detail } = status;
|
|
120
125
|
return (
|
|
@@ -141,11 +146,11 @@ function StatusBox({
|
|
|
141
146
|
{connected ? <CheckCircleIcon sx={{ fontSize: 18, color: "success.main" }} /> : null}
|
|
142
147
|
</Stack>
|
|
143
148
|
<Text variant="caption" as="p" color="secondary">
|
|
144
|
-
{connected ? (detail ??
|
|
149
|
+
{connected ? (detail ?? copy.connected) : copy.notConnected}
|
|
145
150
|
</Text>
|
|
146
151
|
</Box>
|
|
147
152
|
{connected ? (
|
|
148
|
-
<ConnectedControls host={host} onConnect={onConnect} onDisconnect={onDisconnect} />
|
|
153
|
+
<ConnectedControls copy={copy} host={host} onConnect={onConnect} onDisconnect={onDisconnect} />
|
|
149
154
|
) : (
|
|
150
155
|
<Button
|
|
151
156
|
variant="outline"
|
|
@@ -169,7 +174,7 @@ function StatusBox({
|
|
|
169
174
|
* `announceAiConnection`), so several assistants can show connected at once.
|
|
170
175
|
*
|
|
171
176
|
* A connected box carries two more controls, because the pill it used to show
|
|
172
|
-
* alone was a dead end in both directions: "
|
|
177
|
+
* alone was a dead end in both directions: "{copy.instructions}" re-enters the host's
|
|
173
178
|
* setup steps (previously reachable only from a RED card, so an all-green board
|
|
174
179
|
* hid them entirely), and "Desconectar" — when the app passes `onDisconnect` —
|
|
175
180
|
* revokes access, which nothing in the UI could do at all.
|
|
@@ -178,20 +183,22 @@ export function AiStatusBoard({
|
|
|
178
183
|
statuses,
|
|
179
184
|
onConnect,
|
|
180
185
|
onDisconnect,
|
|
186
|
+
copy,
|
|
181
187
|
}: {
|
|
182
188
|
statuses: readonly HostStatus[];
|
|
183
189
|
onConnect: (hostId: string) => void;
|
|
184
190
|
/** Revoke this host's access. Omit to render a read-only board. */
|
|
185
191
|
onDisconnect?: DisconnectHandler;
|
|
192
|
+
copy: AiStatusBoardCopy;
|
|
186
193
|
}): React.JSX.Element {
|
|
187
194
|
return (
|
|
188
195
|
<Stack spacing={2} data-testid="ai-status-board">
|
|
189
196
|
<Box>
|
|
190
197
|
<Text variant="heading" size="sm" as="h2">
|
|
191
|
-
|
|
198
|
+
{copy.boardTitle}
|
|
192
199
|
</Text>
|
|
193
200
|
<Text variant="caption" as="p" color="secondary">
|
|
194
|
-
|
|
201
|
+
{copy.boardCaption}
|
|
195
202
|
</Text>
|
|
196
203
|
</Box>
|
|
197
204
|
<Box
|
|
@@ -202,7 +209,7 @@ export function AiStatusBoard({
|
|
|
202
209
|
}}
|
|
203
210
|
>
|
|
204
211
|
{statuses.map((status) => (
|
|
205
|
-
<StatusBox
|
|
212
|
+
<StatusBox copy={copy}
|
|
206
213
|
key={status.host.id}
|
|
207
214
|
status={status}
|
|
208
215
|
onConnect={onConnect}
|