@12-apps/mcp 3.5.1 → 3.7.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.
@@ -1,11 +1,13 @@
1
1
  "use client";
2
2
 
3
+ import { useMemo } from "react";
4
+ import type { AiStatusBoardCopy } from "./copy";
3
5
  import AddLinkOutlinedIcon from "@mui/icons-material/AddLinkOutlined";
4
6
  import CheckCircleIcon from "@mui/icons-material/CheckCircle";
5
7
  import LinkOffOutlinedIcon from "@mui/icons-material/LinkOffOutlined";
6
8
  import MenuBookOutlinedIcon from "@mui/icons-material/MenuBookOutlined";
7
9
 
8
- import { ConfirmButton } from "@12-apps/ui/feedback/ConfirmAction";
10
+ import { createConfirmButton } from "@12-apps/ui/feedback/ConfirmAction";
9
11
  import { Button } from "@12-apps/ui/form/Button";
10
12
  import { Box } from "@12-apps/ui/mui/Box";
11
13
  import { Stack } from "@12-apps/ui/mui/Stack";
@@ -33,7 +35,7 @@ export interface HostStatus {
33
35
  * The right-hand side of a CONNECTED box: the green pill, then the two things
34
36
  * the pill alone left the owner unable to do.
35
37
  *
36
- * "Instruções" exists because the setup steps were reachable ONLY from a RED
38
+ * "{copy.instructions}" exists because the setup steps were reachable ONLY from a RED
37
39
  * card's "Conectar" — so on an all-green board there was no way to re-read how
38
40
  * any of them was connected, which is exactly when someone re-doing the setup on
39
41
  * a second machine needs them. It re-enters the same flow that button does.
@@ -45,11 +47,20 @@ function ConnectedControls({
45
47
  host,
46
48
  onConnect,
47
49
  onDisconnect,
50
+ copy,
48
51
  }: {
49
52
  host: AiHostGuide;
50
53
  onConnect: (hostId: string) => void;
51
54
  onDisconnect?: DisconnectHandler;
55
+ copy: AiStatusBoardCopy;
52
56
  }): React.JSX.Element {
57
+ // Built from the copy rather than at module scope: `createConfirmButton`
58
+ // takes the words, and they only arrive as a prop. Memoised so the wrapped
59
+ // component keeps its identity across renders and React does not remount it.
60
+ const DisconnectButton = useMemo(
61
+ () => createConfirmButton(copy.confirmAction, copy.disconnectError),
62
+ [copy.confirmAction, copy.disconnectError],
63
+ );
53
64
  return (
54
65
  <Stack
55
66
  direction="row"
@@ -70,7 +81,7 @@ function ConnectedControls({
70
81
  }}
71
82
  >
72
83
  <Text variant="caption" weight="bold" as="span">
73
- Conectado
84
+ {copy.connected}
74
85
  </Text>
75
86
  </Box>
76
87
  <Button
@@ -80,27 +91,27 @@ function ConnectedControls({
80
91
  data-testid={`ai-status-instructions-${host.id}`}
81
92
  >
82
93
  <MenuBookOutlinedIcon sx={{ fontSize: 16, mr: 0.5 }} />
83
- Instruções
94
+ {copy.instructions}
84
95
  </Button>
85
96
  {onDisconnect ? (
86
- <ConfirmButton
97
+ <DisconnectButton
87
98
  variant="outline"
88
99
  color="danger"
89
100
  size="sm"
90
101
  onClick={() => onDisconnect(host.id)}
91
102
  confirm={{
92
- title: "Desconectar o assistente?",
103
+ title: copy.disconnectTitle,
93
104
  entityName: host.label,
94
105
  description:
95
- "Ele perde o acesso à sua loja na hora. Para voltar a usar, será preciso conectar de novo.",
96
- confirmText: "Desconectar",
106
+ copy.disconnectBody,
107
+ confirmText: copy.disconnectConfirm,
97
108
  dataTestId: `ai-status-disconnect-confirm-${host.id}`,
98
109
  }}
99
110
  data-testid={`ai-status-disconnect-${host.id}`}
100
111
  >
101
112
  <LinkOffOutlinedIcon sx={{ fontSize: 16, mr: 0.5 }} />
102
- Desconectar
103
- </ConfirmButton>
113
+ {copy.disconnect}
114
+ </DisconnectButton>
104
115
  ) : null}
105
116
  </Stack>
106
117
  );
@@ -111,10 +122,12 @@ function StatusBox({
111
122
  status,
112
123
  onConnect,
113
124
  onDisconnect,
125
+ copy,
114
126
  }: {
115
127
  status: HostStatus;
116
128
  onConnect: (hostId: string) => void;
117
129
  onDisconnect?: DisconnectHandler;
130
+ copy: AiStatusBoardCopy;
118
131
  }): React.JSX.Element {
119
132
  const { host, connected, detail } = status;
120
133
  return (
@@ -141,11 +154,11 @@ function StatusBox({
141
154
  {connected ? <CheckCircleIcon sx={{ fontSize: 18, color: "success.main" }} /> : null}
142
155
  </Stack>
143
156
  <Text variant="caption" as="p" color="secondary">
144
- {connected ? (detail ?? "Conectado") : "Ainda não conectado"}
157
+ {connected ? (detail ?? copy.connected) : copy.notConnected}
145
158
  </Text>
146
159
  </Box>
147
160
  {connected ? (
148
- <ConnectedControls host={host} onConnect={onConnect} onDisconnect={onDisconnect} />
161
+ <ConnectedControls copy={copy} host={host} onConnect={onConnect} onDisconnect={onDisconnect} />
149
162
  ) : (
150
163
  <Button
151
164
  variant="outline"
@@ -169,7 +182,7 @@ function StatusBox({
169
182
  * `announceAiConnection`), so several assistants can show connected at once.
170
183
  *
171
184
  * A connected box carries two more controls, because the pill it used to show
172
- * alone was a dead end in both directions: "Instruções" re-enters the host's
185
+ * alone was a dead end in both directions: "{copy.instructions}" re-enters the host's
173
186
  * setup steps (previously reachable only from a RED card, so an all-green board
174
187
  * hid them entirely), and "Desconectar" — when the app passes `onDisconnect` —
175
188
  * revokes access, which nothing in the UI could do at all.
@@ -178,20 +191,22 @@ export function AiStatusBoard({
178
191
  statuses,
179
192
  onConnect,
180
193
  onDisconnect,
194
+ copy,
181
195
  }: {
182
196
  statuses: readonly HostStatus[];
183
197
  onConnect: (hostId: string) => void;
184
198
  /** Revoke this host's access. Omit to render a read-only board. */
185
199
  onDisconnect?: DisconnectHandler;
200
+ copy: AiStatusBoardCopy;
186
201
  }): React.JSX.Element {
187
202
  return (
188
203
  <Stack spacing={2} data-testid="ai-status-board">
189
204
  <Box>
190
205
  <Text variant="heading" size="sm" as="h2">
191
- Assistentes conectados
206
+ {copy.boardTitle}
192
207
  </Text>
193
208
  <Text variant="caption" as="p" color="secondary">
194
- Em verde os que já operam a sua loja; em vermelho os que faltam conectar.
209
+ {copy.boardCaption}
195
210
  </Text>
196
211
  </Box>
197
212
  <Box
@@ -202,7 +217,7 @@ export function AiStatusBoard({
202
217
  }}
203
218
  >
204
219
  {statuses.map((status) => (
205
- <StatusBox
220
+ <StatusBox copy={copy}
206
221
  key={status.host.id}
207
222
  status={status}
208
223
  onConnect={onConnect}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import type { McpAiCopy } from "./copy";
3
4
  import { type GuidedNav, type GuidedStep } from "@12-apps/onboarding";
4
5
 
5
6
  import { providerForHostId, type AiHostGuide } from "../guide";
@@ -24,11 +25,13 @@ export function StatusBoard({
24
25
  connections,
25
26
  hosts,
26
27
  onDisconnect,
28
+ copy,
27
29
  }: {
28
30
  nav: GuidedNav;
29
31
  connections: readonly AiConnection[];
30
32
  hosts: readonly AiHostGuide[];
31
33
  onDisconnect?: DisconnectHandler;
34
+ copy: McpAiCopy;
32
35
  }): React.JSX.Element {
33
36
  const legacyHostId = (nav.data.connectedHost ?? nav.data.selectedHost) as string | undefined;
34
37
 
@@ -43,7 +46,7 @@ export function StatusBoard({
43
46
  return {
44
47
  host,
45
48
  connected: connection !== null,
46
- detail: connection ? activeAgo(connection.lastActiveAt) : undefined,
49
+ detail: connection ? activeAgo(connection.lastActiveAt, copy.summary) : undefined,
47
50
  };
48
51
  });
49
52
 
@@ -78,19 +81,20 @@ export function StatusBoard({
78
81
  : undefined;
79
82
 
80
83
  return (
81
- <AiStatusBoard statuses={statuses} onConnect={goToConnect} onDisconnect={disconnect} />
84
+ <AiStatusBoard copy={copy.statusBoard} statuses={statuses} onConnect={goToConnect} onDisconnect={disconnect} />
82
85
  );
83
86
  }
84
87
 
85
88
  /** The first step (host picker) — picking a card advances to the right path. */
86
- function selectStep(hosts: readonly AiHostGuide[]): GuidedStep {
89
+ function selectStep(hosts: readonly AiHostGuide[], copy: McpAiCopy): GuidedStep {
87
90
  return {
88
91
  id: "select",
89
- label: "Escolher",
92
+ label: copy.flow.steps.select,
90
93
  render: (nav: GuidedNav) => (
91
94
  <HostSelectStep
92
95
  hosts={hosts}
93
96
  selectedId={(nav.data.selectedHost as string | undefined) ?? null}
97
+ copy={copy.hostSelect}
94
98
  onSelect={(hostId) =>
95
99
  nav.goTo(hosts.find((h) => h.id === hostId)?.pluginUrl ? "install" : "copy", {
96
100
  selectedHost: hostId,
@@ -102,11 +106,11 @@ function selectStep(hosts: readonly AiHostGuide[]): GuidedStep {
102
106
  }
103
107
 
104
108
  /** The Copiar-URL step, shared by every manual flow. */
105
- function copyStep(endpointUrl: string): GuidedStep {
109
+ function copyStep(endpointUrl: string, copy: McpAiCopy): GuidedStep {
106
110
  return {
107
111
  id: "copy",
108
- label: "Copiar URL",
109
- render: (nav: GuidedNav) => <CopyUrlStep nav={nav} endpointUrl={endpointUrl} />,
112
+ label: copy.flow.steps.copyUrl,
113
+ render: (nav: GuidedNav) => <CopyUrlStep copy={copy} nav={nav} endpointUrl={endpointUrl} />,
110
114
  };
111
115
  }
112
116
 
@@ -120,44 +124,70 @@ function manualMiddle(
120
124
  hosts: readonly AiHostGuide[],
121
125
  endpointUrl: string,
122
126
  connectPrompt: string,
127
+ copy: McpAiCopy,
123
128
  ): GuidedStep[] {
124
129
  if (host.configureStages && host.configureStages.length > 0) {
125
130
  return [
126
- copyStep(endpointUrl),
131
+ copyStep(endpointUrl, copy),
127
132
  ...host.configureStages.map((stage) => ({
128
133
  id: stage.id,
129
134
  label: stage.label,
130
135
  render: (nav: GuidedNav) => (
131
- <ConfigureStageStep nav={nav} host={resolveHost(hosts, nav.data.selectedHost)} stage={stage} />
136
+ <ConfigureStageStep
137
+ nav={nav}
138
+ host={resolveHost(hosts, nav.data.selectedHost)}
139
+ stage={stage}
140
+ copy={copy}
141
+ />
132
142
  ),
133
143
  })),
134
144
  ];
135
145
  }
136
146
  return [
137
- copyStep(endpointUrl),
147
+ copyStep(endpointUrl, copy),
138
148
  {
139
149
  id: "configure",
140
- label: "Configurar",
141
- render: (nav: GuidedNav) => <ConfigureStep nav={nav} host={resolveHost(hosts, nav.data.selectedHost)} />,
150
+ label: copy.flow.steps.configure,
151
+ render: (nav: GuidedNav) => (
152
+ <ConfigureStep
153
+ nav={nav}
154
+ host={resolveHost(hosts, nav.data.selectedHost)}
155
+ copy={copy}
156
+ />
157
+ ),
142
158
  },
143
159
  {
144
160
  id: "connect",
145
- label: "Conectar",
161
+ label: copy.flow.steps.connect,
146
162
  render: (nav: GuidedNav) => (
147
- <ConnectStep nav={nav} host={resolveHost(hosts, nav.data.selectedHost)} connectPrompt={connectPrompt} />
163
+ <ConnectStep
164
+ nav={nav}
165
+ host={resolveHost(hosts, nav.data.selectedHost)}
166
+ connectPrompt={connectPrompt}
167
+ copy={copy}
168
+ />
148
169
  ),
149
170
  },
150
171
  ];
151
172
  }
152
173
 
153
174
  /** The single middle step for the simplified plugin flow: install. */
154
- function simpleMiddle(hosts: readonly AiHostGuide[], connectPrompt: string): GuidedStep[] {
175
+ function simpleMiddle(
176
+ hosts: readonly AiHostGuide[],
177
+ connectPrompt: string,
178
+ copy: McpAiCopy,
179
+ ): GuidedStep[] {
155
180
  return [
156
181
  {
157
182
  id: "install",
158
- label: "Instalar",
183
+ label: copy.flow.steps.install,
159
184
  render: (nav: GuidedNav) => (
160
- <InstallStep nav={nav} host={resolveHost(hosts, nav.data.selectedHost)} connectPrompt={connectPrompt} />
185
+ <InstallStep
186
+ nav={nav}
187
+ host={resolveHost(hosts, nav.data.selectedHost)}
188
+ connectPrompt={connectPrompt}
189
+ copy={copy}
190
+ />
161
191
  ),
162
192
  },
163
193
  ];
@@ -176,17 +206,24 @@ export function buildFlowSteps(opts: {
176
206
  connectPrompt: string;
177
207
  connections: readonly AiConnection[];
178
208
  onRetest: () => void;
209
+ copy: McpAiCopy;
179
210
  }): GuidedStep[] {
180
- const { host, hosts, endpointUrl, connectPrompt, connections, onRetest } = opts;
211
+ const { host, hosts, endpointUrl, connectPrompt, connections, onRetest, copy } = opts;
181
212
  const middle = host.pluginUrl
182
- ? simpleMiddle(hosts, connectPrompt)
183
- : manualMiddle(host, hosts, endpointUrl, connectPrompt);
213
+ ? simpleMiddle(hosts, connectPrompt, copy)
214
+ : manualMiddle(host, hosts, endpointUrl, connectPrompt, copy);
184
215
  const confirm: GuidedStep = {
185
216
  id: "confirm",
186
- label: "Confirmar",
217
+ label: copy.flow.steps.confirm,
187
218
  render: (nav: GuidedNav) => (
188
- <ConfirmStep nav={nav} connections={connections} hosts={hosts} onRetest={onRetest} />
219
+ <ConfirmStep
220
+ nav={nav}
221
+ connections={connections}
222
+ hosts={hosts}
223
+ onRetest={onRetest}
224
+ copy={copy}
225
+ />
189
226
  ),
190
227
  };
191
- return [selectStep(hosts), ...middle, confirm];
228
+ return [selectStep(hosts, copy), ...middle, confirm];
192
229
  }
@@ -0,0 +1,165 @@
1
+ import type { ConfirmActionCopy } from "@12-apps/ui/copy";
2
+ /**
3
+ * Every word the AI-integration screens render, as REQUIRED host config
4
+ * (FUT-760).
5
+ *
6
+ * These components used to take `capabilities` / `permissionModel` / `hosts` as
7
+ * OPTIONAL props defaulting to this package's own pt-BR, and to compile the rest
8
+ * of their sentences in outright. `guide.ts` even said so — "these are the
9
+ * shared defaults" — which is the anti-pattern named exactly: a host that
10
+ * configured nothing shipped one product's Portuguese, and nothing failed to
11
+ * say so.
12
+ *
13
+ * Split per screen rather than one flat map, because three of them
14
+ * (`AiLanding`, `AiCapabilities`, `AiStatusBoard`, `HostSelectStep`) are
15
+ * exported standalone and a host mounting only one should not have to supply
16
+ * words for screens it never renders.
17
+ */
18
+
19
+ /** The marketing block's headline and its supporting line. */
20
+ export interface AiCapabilitiesCopy {
21
+ heading: string;
22
+ subheading: string;
23
+ }
24
+
25
+ /** One reassurance chip on the landing: an icon's label and its caption. */
26
+ export interface AiTrustPoint {
27
+ /** Stable id — maps to the icon in the component. */
28
+ id: string;
29
+ label: string;
30
+ caption: string;
31
+ }
32
+
33
+ export interface AiLandingCopy {
34
+ /** Small line above the headline. */
35
+ eyebrow: string;
36
+ /** The headline's second half, after the assistant names. */
37
+ titleTail: string;
38
+ /** The paragraph under the headline. */
39
+ lede: string;
40
+ /** The reassurance strip. Order is the order rendered. */
41
+ trust: readonly AiTrustPoint[];
42
+ /** The call to action that starts the guided flow. */
43
+ start: string;
44
+ }
45
+
46
+ /** The wizard's step labels, in the order the stepper shows them. */
47
+ export interface AiFlowStepLabels {
48
+ select: string;
49
+ copyUrl: string;
50
+ configure: string;
51
+ connect: string;
52
+ install: string;
53
+ confirm: string;
54
+ }
55
+
56
+ /** The wizard's navigation words and each step's own sentences. */
57
+ export interface AiFlowCopy {
58
+ /** The stepper's labels. Not sentences, but still the host's words. */
59
+ steps: AiFlowStepLabels;
60
+ back: string;
61
+ next: string;
62
+ advance: string;
63
+ finish: string;
64
+ copyUrl: string;
65
+ copied: string;
66
+ copyMessage: string;
67
+ /** The copy-the-URL step's heading. Names the host's own noun for a tenant. */
68
+ copyUrlTitle: string;
69
+ /** Under the MCP URL: what this value is and what happens on copy. */
70
+ urlCaption: string;
71
+ /** Under the paste-in prompt: what the assistant will do with it. */
72
+ promptCaption: string;
73
+ /** The "ask the assistant to connect" step heading. */
74
+ askTitle: string;
75
+ /** The one-click install step's body, for a host with a published plugin. */
76
+ installBody: string;
77
+ /** The install button on that step. */
78
+ installAction: string;
79
+ /** Heading over the paste-in message on the manual path. */
80
+ pasteTitle: string;
81
+ /** Caption under the paste-in message on the install path. */
82
+ pasteInstallCaption: string;
83
+ /** Confirmation once a host reports in. */
84
+ connectedTo(hostLabel: string): string;
85
+ waitingTitle: string;
86
+ waitingBody(hostLabel: string): string;
87
+ testNow: string;
88
+ }
89
+
90
+ export interface AiStatusBoardCopy {
91
+ instructions: string;
92
+ connected: string;
93
+ notConnected: string;
94
+ connect: string;
95
+ disconnect: string;
96
+ disconnectTitle: string;
97
+ disconnectBody: string;
98
+ disconnectConfirm: string;
99
+ /** Shown when the disconnect write fails without a reason of its own. */
100
+ disconnectError: string;
101
+ boardTitle: string;
102
+ boardCaption: string;
103
+ /**
104
+ * The confirmation popup's own furniture — its cancel button and fallbacks.
105
+ * `@12-apps/ui` stopped shipping defaults for these (FUT-760), so the words
106
+ * arrive here rather than from the design system's Portuguese.
107
+ */
108
+ confirmAction: ConfirmActionCopy;
109
+ }
110
+
111
+ /**
112
+ * The one-line summary of a connection's recency.
113
+ *
114
+ * Functions rather than strings because the number is interpolated and the
115
+ * plural rule is the host language's, not this package's.
116
+ */
117
+ export interface AiConnectionSummaryCopy {
118
+ /** Active within the last minute — no number to interpolate. */
119
+ activeNow: string;
120
+ activeMinutes(minutes: number): string;
121
+ activeHours(hours: number): string;
122
+ activeDays(days: number): string;
123
+ /** Connected, but too long ago to state a recency for. */
124
+ configured: string;
125
+ /** Appended after a host's name: `Claude Conectado`. */
126
+ connectedSuffix: string;
127
+ /** Several assistants at once, from an already-joined list of names. */
128
+ connectedSeveral(names: string): string;
129
+ /** Connected, but the provider was never attributed to a known assistant. */
130
+ connectedGeneric: string;
131
+ }
132
+
133
+ export interface AiHostSelectCopy {
134
+ /** Why the owner is picking, under the host tiles. */
135
+ caption: string;
136
+ }
137
+
138
+ export interface AiConnectGuideCopy {
139
+ /** The label on the MCP URL field. Names the host's own noun for a tenant. */
140
+ urlLabel: string;
141
+ /** Above the MCP URL field. */
142
+ urlHint: string;
143
+ /** Lead-in to the vendor's official connector docs link. */
144
+ moreInfo: string;
145
+ connectOn(hostLabel: string): string;
146
+ }
147
+
148
+ export interface AiOnboardingCopy {
149
+ title: string;
150
+ /** The reveal toggle in the configured state, and its collapsed twin. */
151
+ editLabel: string;
152
+ collapseLabel: string;
153
+ }
154
+
155
+ /** Every screen's words, for a host mounting the whole flow. */
156
+ export interface McpAiCopy {
157
+ capabilities: AiCapabilitiesCopy;
158
+ landing: AiLandingCopy;
159
+ flow: AiFlowCopy;
160
+ statusBoard: AiStatusBoardCopy;
161
+ summary: AiConnectionSummaryCopy;
162
+ hostSelect: AiHostSelectCopy;
163
+ connectGuide: AiConnectGuideCopy;
164
+ onboarding: AiOnboardingCopy;
165
+ }
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import type { AiConnectGuideCopy, AiFlowCopy } from "./copy";
3
4
  import CheckIcon from "@mui/icons-material/Check";
4
5
  import ContentCopyOutlinedIcon from "@mui/icons-material/ContentCopyOutlined";
5
6
  import OpenInNewIcon from "@mui/icons-material/OpenInNew";
@@ -24,10 +25,14 @@ export function EndpointCopyBlock({
24
25
  endpointUrl,
25
26
  copied,
26
27
  onCopy,
28
+ flowCopy,
29
+ copy,
27
30
  }: {
28
31
  endpointUrl: string;
29
32
  copied: boolean;
30
33
  onCopy: () => void;
34
+ flowCopy: AiFlowCopy;
35
+ copy: AiConnectGuideCopy;
31
36
  }): React.JSX.Element {
32
37
  const handleClick = (): void => {
33
38
  if (typeof navigator !== "undefined" && navigator.clipboard) {
@@ -47,10 +52,10 @@ export function EndpointCopyBlock({
47
52
  }}
48
53
  >
49
54
  <Text variant="caption" as="p" weight="bold">
50
- URL do servidor da sua loja
55
+ {copy.urlLabel}
51
56
  </Text>
52
57
  <Text variant="caption" as="p" color="secondary">
53
- Copie esta URL e cole no seu assistente para conectá-lo à loja.
58
+ {copy.urlHint}
54
59
  </Text>
55
60
  <Box sx={{ mt: 1 }}>
56
61
  <McpEndpointUrl url={endpointUrl} copyable={false} />
@@ -62,7 +67,7 @@ export function EndpointCopyBlock({
62
67
  ) : (
63
68
  <ContentCopyOutlinedIcon sx={{ fontSize: 18, mr: 0.5 }} />
64
69
  )}
65
- {copied ? "Copiado" : "Copiar URL"}
70
+ {copied ? flowCopy.copied : flowCopy.copyUrl}
66
71
  </Button>
67
72
  </Box>
68
73
  </Box>
@@ -78,10 +83,12 @@ export function PromptCopyBlock({
78
83
  title,
79
84
  caption,
80
85
  message,
86
+ flowCopy,
81
87
  }: {
82
88
  title: string;
83
89
  caption: string;
84
90
  message: string;
91
+ flowCopy: AiFlowCopy;
85
92
  }): React.JSX.Element {
86
93
  const [copied, setCopied] = useState(false);
87
94
 
@@ -130,7 +137,7 @@ export function PromptCopyBlock({
130
137
  ) : (
131
138
  <ContentCopyOutlinedIcon sx={{ fontSize: 18, mr: 0.5 }} />
132
139
  )}
133
- {copied ? "Copiado" : "Copiar mensagem"}
140
+ {copied ? flowCopy.copied : flowCopy.copyMessage}
134
141
  </Button>
135
142
  </Box>
136
143
  </Box>
@@ -138,12 +145,18 @@ export function PromptCopyBlock({
138
145
  }
139
146
 
140
147
  /** The brand mark + "Conectar no <host>" header shared by the connect sub-steps. */
141
- export function HostConnectHeader({ host }: { host: AiHostGuide }): React.JSX.Element {
148
+ export function HostConnectHeader({
149
+ host,
150
+ copy,
151
+ }: {
152
+ host: AiHostGuide;
153
+ copy: AiConnectGuideCopy;
154
+ }): React.JSX.Element {
142
155
  return (
143
156
  <Stack direction="row" spacing={1.5} alignItems="center">
144
157
  <HostBrandAvatar brand={host.brand} size={36} />
145
158
  <Text variant="heading" size="sm" as="h3">
146
- Conectar no {host.label}
159
+ {copy.connectOn(host.label)}
147
160
  </Text>
148
161
  </Stack>
149
162
  );
@@ -179,11 +192,14 @@ export function HostOpenButton({
179
192
  }
180
193
 
181
194
  /** A "Para mais informações" reference to the host's official connector docs. */
182
- export function HostDocsLink({ host }: { host: AiHostGuide }): React.JSX.Element | null {
195
+ export function HostDocsLink({ host,
196
+ copy,
197
+ }: { host: AiHostGuide;
198
+ copy: AiConnectGuideCopy;}): React.JSX.Element | null {
183
199
  if (!host.docs) return null;
184
200
  return (
185
201
  <Text variant="caption" as="p" color="secondary">
186
- Para mais informações, consulte a{" "}
202
+ {copy.moreInfo}{" "}
187
203
  <Box
188
204
  component="a"
189
205
  href={host.docs.url}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
 
3
+ import type { AiHostSelectCopy } from "./copy";
3
4
  import CheckCircleIcon from "@mui/icons-material/CheckCircle";
4
5
 
5
6
  import { Box } from "@12-apps/ui/mui/Box";
@@ -72,10 +73,12 @@ export function HostSelectStep({
72
73
  hosts,
73
74
  selectedId,
74
75
  onSelect,
76
+ copy,
75
77
  }: {
76
78
  hosts: readonly AiHostGuide[];
77
79
  selectedId: string | null;
78
80
  onSelect: (hostId: string) => void;
81
+ copy: AiHostSelectCopy;
79
82
  }): React.JSX.Element {
80
83
  return (
81
84
  <Stack spacing={3} data-testid="ai-host-select">
@@ -84,7 +87,7 @@ export function HostSelectStep({
84
87
  Escolha o seu assistente
85
88
  </Text>
86
89
  <Text variant="caption" as="p" color="secondary">
87
- Selecione onde você usa a IA — o passo a passo é feito sob medida para ele.
90
+ {copy.caption}
88
91
  </Text>
89
92
  </Box>
90
93
 
@@ -29,9 +29,6 @@ export { McpEndpointUrl } from "./mcp-endpoint-url";
29
29
  export { HostBrandAvatar, CapabilityIcon } from "./ai-icons";
30
30
  export { FeatureBadge, type FeatureBadgeItem } from "./feature-badge";
31
31
  export {
32
- aiHostGuides,
33
- AI_CAPABILITIES,
34
- AI_PERMISSION_MODEL,
35
32
  aiConnectPrompt,
36
33
  type AiConnectPromptSpec,
37
34
  providerForHostId,
@@ -42,3 +39,24 @@ export {
42
39
  type AiProvider,
43
40
  type AiCapability,
44
41
  } from "../guide";
42
+
43
+ export {
44
+ PT_BR_AI_CAPABILITIES,
45
+ PT_BR_AI_CONNECT_PROMPT,
46
+ PT_BR_AI_HOST_GUIDES,
47
+ PT_BR_AI_PERMISSION_MODEL,
48
+ } from "../pt-BR";
49
+
50
+ export type {
51
+ AiCapabilitiesCopy,
52
+ AiConnectGuideCopy,
53
+ AiConnectionSummaryCopy,
54
+ AiFlowCopy,
55
+ AiHostSelectCopy,
56
+ AiLandingCopy,
57
+ AiOnboardingCopy,
58
+ AiStatusBoardCopy,
59
+ AiTrustPoint,
60
+ McpAiCopy,
61
+ } from "./copy";
62
+ export { PT_BR_MCP_AI_COPY } from "./pt-BR";