strapi-plugin-mcp-chat 0.1.0 → 0.5.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.
@@ -2,6 +2,9 @@ import { useRef, useState, useEffect } from 'react';
2
2
  import { Box, Flex, Typography, Button, Loader, Textarea } from '@strapi/design-system';
3
3
  import { useFetchClient } from '@strapi/strapi/admin';
4
4
  import { Link } from 'react-router-dom';
5
+ import { useLang, makeT } from '../i18n';
6
+ import { LangSwitcher } from '../components/LangSwitcher';
7
+ import { StackLogos } from '../components/StackLogos';
5
8
 
6
9
  /**
7
10
  * Provisão de frontend em duas etapas (cenário Figma/Lovable, sem manifest):
@@ -41,6 +44,8 @@ const POLL_TIMEOUT_MS = 120000;
41
44
  const ProvisionPage = () => {
42
45
  const { post, get } = useFetchClient();
43
46
  const inputRef = useRef<HTMLInputElement | null>(null);
47
+ const [lang] = useLang();
48
+ const t = makeT(lang);
44
49
 
45
50
  const [file, setFile] = useState<File | null>(null);
46
51
  const [phase, setPhase] = useState<Phase>('idle');
@@ -96,11 +101,11 @@ const ProvisionPage = () => {
96
101
  setFilesAnalyzed(res.filesAnalyzed || []);
97
102
  setManifestText(res.manifest ? JSON.stringify(res.manifest, null, 2) : '');
98
103
  if (!res.ok && res.errors?.length) {
99
- setError(`Aviso da análise:\n• ${res.errors.join('\n• ')}`);
104
+ setError(`${t('prov.analyzeWarn')}\n• ${res.errors.join('\n• ')}`);
100
105
  }
101
106
  setPhase('review');
102
107
  } catch (e: any) {
103
- setError(errDetail(e, 'Falha ao analisar o projeto.'));
108
+ setError(errDetail(e, t('prov.analyzeFail')));
104
109
  setPhase('error');
105
110
  }
106
111
  };
@@ -111,7 +116,7 @@ const ProvisionPage = () => {
111
116
  try {
112
117
  manifest = JSON.parse(manifestText);
113
118
  } catch {
114
- setError('O manifest não é um JSON válido. Corrija a sintaxe.');
119
+ setError(t('prov.invalidJson'));
115
120
  return;
116
121
  }
117
122
  setError(null);
@@ -126,7 +131,7 @@ const ProvisionPage = () => {
126
131
  setPhase('done-noreload');
127
132
  }
128
133
  } catch (e: any) {
129
- setError(errDetail(e, 'Falha na provisão.'));
134
+ setError(errDetail(e, t('prov.provisionFail')));
130
135
  setPhase('error');
131
136
  }
132
137
  };
@@ -135,7 +140,7 @@ const ProvisionPage = () => {
135
140
  const startedAt = Date.now();
136
141
  while (!stopRef.current) {
137
142
  if (Date.now() - startedAt > POLL_TIMEOUT_MS) {
138
- setError('A provisão demorou mais que o esperado. Verifique o terminal da Strapi.');
143
+ setError(t('prov.provisionFail'));
139
144
  setPhase('error');
140
145
  return;
141
146
  }
@@ -160,17 +165,12 @@ const ProvisionPage = () => {
160
165
  try {
161
166
  const { data } = await post('/mcp-chat/frontend/integrate', {});
162
167
  if (data.ok) {
163
- setIntegrateMsg(
164
- `✅ Religado! Arquivos atualizados: ${data.filesRewritten.join(', ')}. ` +
165
- `Recarregue o preview para ver os dados do Strapi. (Original salvo como .bak.)`
166
- );
168
+ setIntegrateMsg(t('prov.relinkOk', { files: data.filesRewritten.join(', ') }));
167
169
  } else {
168
- setIntegrateMsg(
169
- `⚠️ Não consegui religar: ${(data.errors || []).join('; ') || 'sem arquivo de dados'}.`
170
- );
170
+ setIntegrateMsg(t('prov.relinkFail', { err: (data.errors || []).join('; ') || t('prov.noData') }));
171
171
  }
172
172
  } catch (e: any) {
173
- setIntegrateMsg(`⚠️ ${errDetail(e, 'Falha ao religar.')}`);
173
+ setIntegrateMsg(`⚠️ ${errDetail(e, t('prov.relinkErr'))}`);
174
174
  } finally {
175
175
  setIntegrating(false);
176
176
  }
@@ -194,15 +194,15 @@ const ProvisionPage = () => {
194
194
  <Box padding={6} background="neutral100" style={{ minHeight: '100vh' }}>
195
195
  <Flex justifyContent="space-between" alignItems="center" paddingBottom={4}>
196
196
  <Box>
197
- <Typography variant="alpha" tag="h1">Provisionar frontend</Typography>
198
- <Typography variant="pi" textColor="neutral600">
199
- Suba o .zip do seu frontend (Figma/Lovable, Next ou TanStack) — a IA infere o
200
- modelo de conteúdo, você revisa, e o plugin cria tudo no Strapi.
201
- </Typography>
197
+ <Typography variant="alpha" tag="h1">{t('prov.title')}</Typography>
198
+ <Typography variant="pi" textColor="neutral600">{t('prov.subtitle')}</Typography>
202
199
  </Box>
203
- <Link to="..">
204
- <Button variant="tertiary">← Voltar ao chat</Button>
205
- </Link>
200
+ <Flex gap={2} alignItems="center">
201
+ <LangSwitcher />
202
+ <Link to="..">
203
+ <Button variant="tertiary">{t('prov.back')}</Button>
204
+ </Link>
205
+ </Flex>
206
206
  </Flex>
207
207
 
208
208
  <Box
@@ -213,12 +213,17 @@ const ProvisionPage = () => {
213
213
  style={{ maxWidth: 820, margin: '0 auto' }}
214
214
  >
215
215
  <Flex direction="column" alignItems="stretch" gap={4}>
216
+ {/* Stacks suportados */}
217
+ <Box>
218
+ <Typography variant="sigma" textColor="neutral600" tag="div">{t('prov.supported')}</Typography>
219
+ <Box paddingTop={2}><StackLogos /></Box>
220
+ </Box>
221
+
222
+ <Box height="1px" background="neutral200" />
223
+
216
224
  {/* Seleção do arquivo */}
217
- <Typography variant="delta" tag="h2">1. Escolha o .zip do frontend</Typography>
218
- <Typography textColor="neutral600">
219
- Não precisa de <code>strapi.manifest.json</code>: se ele não existir, a IA cria um
220
- analisando os dados do código (ex.: <code>src/data/*.ts</code>).
221
- </Typography>
225
+ <Typography variant="delta" tag="h2">{t('prov.step1')}</Typography>
226
+ <Typography textColor="neutral600">{t('prov.step1desc')}</Typography>
222
227
 
223
228
  <input
224
229
  ref={inputRef}
@@ -236,27 +241,25 @@ const ProvisionPage = () => {
236
241
 
237
242
  <Flex gap={2} alignItems="center">
238
243
  <Button variant="secondary" onClick={() => inputRef.current?.click()} disabled={busy}>
239
- Selecionar arquivo…
244
+ {t('prov.selectFile')}
240
245
  </Button>
241
246
  <Typography textColor={file ? 'neutral800' : 'neutral500'}>
242
- {file ? file.name : 'Nenhum arquivo selecionado'}
247
+ {file ? file.name : t('prov.noFile')}
243
248
  </Typography>
244
249
  </Flex>
245
250
 
246
251
  {(phase === 'idle' || phase === 'analyzing') && (
247
252
  <Box paddingTop={2}>
248
253
  <Button onClick={analyze} loading={phase === 'analyzing'} disabled={!file || busy}>
249
- Analisar projeto
254
+ {t('prov.analyze')}
250
255
  </Button>
251
256
  </Box>
252
257
  )}
253
258
 
254
259
  {phase === 'analyzing' && (
255
260
  <Flex gap={3} alignItems="center" background="primary100" padding={4} hasRadius>
256
- <Loader small>Analisando…</Loader>
257
- <Typography textColor="primary700">
258
- Lendo o código e inferindo o modelo de conteúdo (content-types + seed)…
259
- </Typography>
261
+ <Loader small>{t('prov.analyzing')}</Loader>
262
+ <Typography textColor="primary700">{t('prov.analyzingDesc')}</Typography>
260
263
  </Flex>
261
264
  )}
262
265
 
@@ -264,23 +267,21 @@ const ProvisionPage = () => {
264
267
  {phase === 'review' && (
265
268
  <>
266
269
  <Box height="1px" background="neutral200" />
267
- <Typography variant="delta" tag="h2">2. Revise o modelo de conteúdo</Typography>
270
+ <Typography variant="delta" tag="h2">{t('prov.step2')}</Typography>
268
271
  <Flex gap={2} alignItems="center" wrap="wrap">
269
272
  <Box background={inferred ? 'warning100' : 'success100'} padding={2} hasRadius>
270
273
  <Typography variant="pi" textColor={inferred ? 'warning700' : 'success700'}>
271
- {inferred ? '🤖 Inferido pela IA' : '✓ Manifest do projeto'} • framework: {framework}
274
+ {inferred ? t('prov.inferred') : t('prov.fromManifest')} • {t('prov.framework')}: {framework}
272
275
  </Typography>
273
276
  </Box>
274
277
  {filesAnalyzed.length > 0 && (
275
278
  <Typography variant="pi" textColor="neutral600">
276
- Analisou: {filesAnalyzed.slice(0, 6).join(', ')}
279
+ {t('prov.analyzed')}: {filesAnalyzed.slice(0, 6).join(', ')}
277
280
  {filesAnalyzed.length > 6 ? ` +${filesAnalyzed.length - 6}` : ''}
278
281
  </Typography>
279
282
  )}
280
283
  </Flex>
281
- <Typography variant="pi" textColor="neutral600">
282
- Edite o JSON se quiser ajustar nomes, tipos ou o conteúdo semeado antes de criar.
283
- </Typography>
284
+ <Typography variant="pi" textColor="neutral600">{t('prov.editJson')}</Typography>
284
285
  <Textarea
285
286
  name="manifest"
286
287
  value={manifestText}
@@ -289,9 +290,9 @@ const ProvisionPage = () => {
289
290
  />
290
291
  <Flex gap={2}>
291
292
  <Button onClick={provision} disabled={!manifestText.trim()}>
292
- Provisionar
293
+ {t('prov.provision')}
293
294
  </Button>
294
- <Button variant="tertiary" onClick={reset}>Recomeçar</Button>
295
+ <Button variant="tertiary" onClick={reset}>{t('prov.restart')}</Button>
295
296
  </Flex>
296
297
  </>
297
298
  )}
@@ -300,15 +301,10 @@ const ProvisionPage = () => {
300
301
  {phase === 'provisioning' && (
301
302
  <Flex direction="column" gap={2} background="primary100" padding={4} hasRadius>
302
303
  <Flex gap={3} alignItems="center">
303
- <Loader small>Provisionando…</Loader>
304
- <Typography fontWeight="bold" textColor="primary700">
305
- Configurando tudo — isso leva alguns segundos
306
- </Typography>
304
+ <Loader small>{t('prov.provisioning')}</Loader>
305
+ <Typography fontWeight="bold" textColor="primary700">{t('prov.provisioningTitle')}</Typography>
307
306
  </Flex>
308
- <Typography variant="pi" textColor="neutral700">
309
- A Strapi está reiniciando para reconhecer as content-types, depois semeia o
310
- conteúdo, libera leitura pública e liga o preview. Não feche esta página.
311
- </Typography>
307
+ <Typography variant="pi" textColor="neutral700">{t('prov.provisioningDesc')}</Typography>
312
308
  </Flex>
313
309
  )}
314
310
 
@@ -322,24 +318,24 @@ const ProvisionPage = () => {
322
318
  {phase === 'ready' && done && (
323
319
  <Box background="success100" padding={4} hasRadius>
324
320
  <Typography variant="beta" textColor="success700" tag="div">
325
- ✅ Tudo pronto! Você já pode ver o preview.
321
+ {t('prov.doneTitle')}
326
322
  </Typography>
327
323
  <Box paddingTop={3}>
328
324
  <Typography variant="pi" textColor="neutral700" tag="div">
329
- Content-types criadas: {done.contentTypes.join(', ')}
325
+ {t('prov.typesCreated')} {done.contentTypes.join(', ')}
330
326
  </Typography>
331
327
  {done.seedCreated.length > 0 && (
332
328
  <Typography variant="pi" textColor="neutral700" tag="div">
333
- Conteúdo semeado: {done.seedCreated.map((s) => `${s.uid} (${s.count})`).join(', ')}
329
+ {t('prov.seeded')} {done.seedCreated.map((s) => `${s.uid} (${s.count})`).join(', ')}
334
330
  </Typography>
335
331
  )}
336
332
  <Typography variant="pi" textColor="neutral700" tag="div">
337
- Frontend em: <code>{done.frontendDir}</code>
333
+ {t('prov.frontendAt')} <code>{done.frontendDir}</code>
338
334
  </Typography>
339
335
  </Box>
340
336
  <Box paddingTop={3}>
341
337
  <Typography variant="pi" textColor="neutral700" tag="div">
342
- Para ver o preview, rode o frontend (uma vez):
338
+ {t('prov.runFrontend')}
343
339
  </Typography>
344
340
  <Box background="neutral0" padding={2} hasRadius marginTop={1}
345
341
  style={{ fontFamily: 'monospace', fontSize: 12 }}>
@@ -348,12 +344,11 @@ const ProvisionPage = () => {
348
344
  </Box>
349
345
  <Box paddingTop={3}>
350
346
  <Typography variant="pi" textColor="neutral700" tag="div">
351
- Religar o frontend ao Strapi (snapshot): troca os dados hardcoded pelos do
352
- Strapi, mantendo as imagens. Os componentes não mudam.
347
+ {t('prov.relinkDesc')}
353
348
  </Typography>
354
349
  <Box paddingTop={1}>
355
350
  <Button onClick={integrate} loading={integrating} variant="default">
356
- Religar dados ao Strapi
351
+ {t('prov.relink')}
357
352
  </Button>
358
353
  </Box>
359
354
  {integrateMsg && (
@@ -367,9 +362,9 @@ const ProvisionPage = () => {
367
362
 
368
363
  <Flex gap={2} paddingTop={3}>
369
364
  <a href={done.previewUrl} target="_blank" rel="noreferrer">
370
- <Button variant="success">Abrir {done.previewUrl} ↗</Button>
365
+ <Button variant="success">{t('prov.open')} {done.previewUrl} ↗</Button>
371
366
  </a>
372
- <Button variant="tertiary" onClick={reset}>Provisionar outro</Button>
367
+ <Button variant="tertiary" onClick={reset}>{t('prov.provisionAnother')}</Button>
373
368
  </Flex>
374
369
  </Box>
375
370
  )}