strapi-plugin-mcp-chat 0.6.0 → 0.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.
package/dist/server/index.js
CHANGED
|
@@ -83,13 +83,13 @@ var audio_default = ({ strapi }) => ({
|
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
// server/src/controllers/frontend.ts
|
|
86
|
-
var
|
|
87
|
-
var
|
|
86
|
+
var import_node_fs8 = __toESM(require("node:fs"));
|
|
87
|
+
var import_node_path8 = __toESM(require("node:path"));
|
|
88
88
|
var import_jszip = __toESM(require("jszip"));
|
|
89
89
|
|
|
90
90
|
// server/src/provision/orchestrate.ts
|
|
91
|
-
var
|
|
92
|
-
var
|
|
91
|
+
var import_node_fs5 = __toESM(require("node:fs"));
|
|
92
|
+
var import_node_path5 = __toESM(require("node:path"));
|
|
93
93
|
|
|
94
94
|
// server/src/provision/manifest.ts
|
|
95
95
|
var import_utils = require("@strapi/utils");
|
|
@@ -267,8 +267,8 @@ function validateManifest(raw) {
|
|
|
267
267
|
const parsed = manifestSchema.safeParse(raw);
|
|
268
268
|
if (parsed.success) return { ok: true, data: parsed.data };
|
|
269
269
|
const errors = parsed.error.issues.map((i) => {
|
|
270
|
-
const
|
|
271
|
-
return `${
|
|
270
|
+
const path10 = i.path.length ? `${i.path.join(".")}: ` : "";
|
|
271
|
+
return `${path10}${i.message}`;
|
|
272
272
|
});
|
|
273
273
|
return { ok: false, errors };
|
|
274
274
|
}
|
|
@@ -980,8 +980,8 @@ function buildPreviewConfig(manifest, framework = manifest.framework) {
|
|
|
980
980
|
routes[apiUid(ct.singularName)] = ct.preview?.route ?? "/";
|
|
981
981
|
}
|
|
982
982
|
const routesJson = JSON.stringify(routes, null, 2);
|
|
983
|
-
const
|
|
984
|
-
const urlBranch =
|
|
983
|
+
const isNext2 = framework === "next";
|
|
984
|
+
const urlBranch = isNext2 ? ` // Next.js: rota de draft mode que seta o cookie e redireciona p/ \`path\`.
|
|
985
985
|
const qs = new URLSearchParams({ secret, status: status ?? 'draft', path: pathname });
|
|
986
986
|
return \`\${clientUrl}/api/preview?\${qs.toString()}\`;` : ` // SPA (Vite/TanStack): abre a p\xE1gina direta; o front l\xEA ?preview/status.
|
|
987
987
|
const qs = new URLSearchParams({ preview: '1', status: status ?? 'draft', secret });
|
|
@@ -1185,6 +1185,347 @@ function linkFrontend(manifest, opts) {
|
|
|
1185
1185
|
return result;
|
|
1186
1186
|
}
|
|
1187
1187
|
|
|
1188
|
+
// server/src/provision/wire.ts
|
|
1189
|
+
var import_node_fs4 = __toESM(require("node:fs"));
|
|
1190
|
+
var import_node_path4 = __toESM(require("node:path"));
|
|
1191
|
+
var OPENAI_URL = "https://api.openai.com/v1/chat/completions";
|
|
1192
|
+
var MODEL = process.env.OPENAI_CHAT_MODEL || "gpt-4o";
|
|
1193
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
1194
|
+
"node_modules",
|
|
1195
|
+
".git",
|
|
1196
|
+
"dist",
|
|
1197
|
+
".next",
|
|
1198
|
+
".output",
|
|
1199
|
+
".vinxi",
|
|
1200
|
+
".tanstack",
|
|
1201
|
+
"build",
|
|
1202
|
+
"coverage",
|
|
1203
|
+
".turbo",
|
|
1204
|
+
".cache",
|
|
1205
|
+
"public"
|
|
1206
|
+
]);
|
|
1207
|
+
function ensureInside2(base, target) {
|
|
1208
|
+
const b = import_node_path4.default.resolve(base);
|
|
1209
|
+
const t = import_node_path4.default.resolve(target);
|
|
1210
|
+
return t === b || t.startsWith(b + import_node_path4.default.sep);
|
|
1211
|
+
}
|
|
1212
|
+
function isNext(manifest) {
|
|
1213
|
+
return manifest.framework === "next";
|
|
1214
|
+
}
|
|
1215
|
+
function usesAtAlias(frontendDir) {
|
|
1216
|
+
for (const f of ["tsconfig.json", "tsconfig.app.json", "jsconfig.json"]) {
|
|
1217
|
+
try {
|
|
1218
|
+
const c = import_node_fs4.default.readFileSync(import_node_path4.default.join(frontendDir, f), "utf8");
|
|
1219
|
+
if (/"@\/\*"\s*:/.test(c)) return true;
|
|
1220
|
+
} catch {
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
return false;
|
|
1224
|
+
}
|
|
1225
|
+
function strapiClientSrc(manifest) {
|
|
1226
|
+
const envExpr = isNext(manifest) ? "(typeof process !== 'undefined' ? process.env.NEXT_PUBLIC_STRAPI_URL : undefined)" : "(import.meta as any).env?.VITE_STRAPI_URL";
|
|
1227
|
+
return `// Gerado pelo mcp-chat \u2014 camada de acesso \xE0 Strapi (REST, flat, sem nesting).
|
|
1228
|
+
// N\xE3o edite \xE0 m\xE3o: \xE9 regenerado ao religar o frontend.
|
|
1229
|
+
export const STRAPI_URL =
|
|
1230
|
+
(${envExpr} || "http://localhost:1337").replace(/\\/$/, "");
|
|
1231
|
+
|
|
1232
|
+
export type PreviewMode = { isPreview: boolean; status: "draft" | "published" };
|
|
1233
|
+
|
|
1234
|
+
/** L\xEA o modo de preview da URL (?preview=1 / ?status=draft). */
|
|
1235
|
+
export function getPreviewMode(): PreviewMode {
|
|
1236
|
+
if (typeof window === "undefined") return { isPreview: false, status: "published" };
|
|
1237
|
+
const p = new URLSearchParams(window.location.search);
|
|
1238
|
+
const status = p.get("status");
|
|
1239
|
+
const isPreview = p.get("preview") === "1" || p.has("preview") || status === "draft";
|
|
1240
|
+
return { isPreview, status: status === "draft" || isPreview ? "draft" : "published" };
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
/** Busca um singleType e devolve s\xF3 os atributos (objeto). null em erro. */
|
|
1244
|
+
export async function fetchSection<T = Record<string, any>>(
|
|
1245
|
+
name: string,
|
|
1246
|
+
status: "draft" | "published" = "published"
|
|
1247
|
+
): Promise<T | null> {
|
|
1248
|
+
const qs = status === "draft" ? "?status=draft" : "";
|
|
1249
|
+
try {
|
|
1250
|
+
const res = await fetch(\`\${STRAPI_URL}/api/\${name}\${qs}\`);
|
|
1251
|
+
if (!res.ok) return null;
|
|
1252
|
+
const json = await res.json();
|
|
1253
|
+
return (json?.data ?? null) as T | null;
|
|
1254
|
+
} catch {
|
|
1255
|
+
return null;
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
/** Busca uma collection (lista) pelo nome plural; ordena por sortOrder se existir. */
|
|
1260
|
+
export async function fetchCollection<T = Record<string, any>>(
|
|
1261
|
+
pluralName: string,
|
|
1262
|
+
status: "draft" | "published" = "published"
|
|
1263
|
+
): Promise<T[]> {
|
|
1264
|
+
const qs = new URLSearchParams({ "sort": "sortOrder:asc", "pagination[pageSize]": "100" });
|
|
1265
|
+
if (status === "draft") qs.set("status", "draft");
|
|
1266
|
+
try {
|
|
1267
|
+
const res = await fetch(\`\${STRAPI_URL}/api/\${pluralName}?\${qs.toString()}\`);
|
|
1268
|
+
if (!res.ok) return [];
|
|
1269
|
+
const json = await res.json();
|
|
1270
|
+
return (Array.isArray(json?.data) ? json.data : []) as T[];
|
|
1271
|
+
} catch {
|
|
1272
|
+
return [];
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
`;
|
|
1276
|
+
}
|
|
1277
|
+
function hooksSrc(manifest, libImport) {
|
|
1278
|
+
const clientDirective = isNext(manifest) ? `"use client";
|
|
1279
|
+
|
|
1280
|
+
` : "";
|
|
1281
|
+
return `${clientDirective}// Gerado pelo mcp-chat \u2014 hooks de leitura da Strapi (sem depend\xEAncias al\xE9m do React).
|
|
1282
|
+
// Em modo preview faz polling curto + refetch via postMessage (reflete edi\xE7\xF5es ao vivo).
|
|
1283
|
+
import { useEffect, useState } from "react";
|
|
1284
|
+
import { fetchSection, fetchCollection, getPreviewMode } from "${libImport}";
|
|
1285
|
+
|
|
1286
|
+
export function useSection<T = Record<string, any>>(name: string): Partial<T> {
|
|
1287
|
+
const [data, setData] = useState<Partial<T>>({});
|
|
1288
|
+
useEffect(() => {
|
|
1289
|
+
let alive = true;
|
|
1290
|
+
const { isPreview, status } = getPreviewMode();
|
|
1291
|
+
const load = () => fetchSection<T>(name, status).then((d) => { if (alive && d) setData(d as Partial<T>); });
|
|
1292
|
+
load();
|
|
1293
|
+
if (!isPreview) return () => { alive = false; };
|
|
1294
|
+
const id = window.setInterval(load, 2500);
|
|
1295
|
+
const onMsg = () => load();
|
|
1296
|
+
window.addEventListener("message", onMsg);
|
|
1297
|
+
return () => { alive = false; window.clearInterval(id); window.removeEventListener("message", onMsg); };
|
|
1298
|
+
}, [name]);
|
|
1299
|
+
return data;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
export function useCollection<T = Record<string, any>>(pluralName: string): T[] {
|
|
1303
|
+
const [data, setData] = useState<T[]>([]);
|
|
1304
|
+
useEffect(() => {
|
|
1305
|
+
let alive = true;
|
|
1306
|
+
const { isPreview, status } = getPreviewMode();
|
|
1307
|
+
const load = () => fetchCollection<T>(pluralName, status).then((d) => { if (alive) setData(d); });
|
|
1308
|
+
load();
|
|
1309
|
+
if (!isPreview) return () => { alive = false; };
|
|
1310
|
+
const id = window.setInterval(load, 2500);
|
|
1311
|
+
const onMsg = () => load();
|
|
1312
|
+
window.addEventListener("message", onMsg);
|
|
1313
|
+
return () => { alive = false; window.clearInterval(id); window.removeEventListener("message", onMsg); };
|
|
1314
|
+
}, [pluralName]);
|
|
1315
|
+
return data;
|
|
1316
|
+
}
|
|
1317
|
+
`;
|
|
1318
|
+
}
|
|
1319
|
+
function writeDataLayer(frontendDir, manifest, dryRun) {
|
|
1320
|
+
const written = [];
|
|
1321
|
+
const libDir = import_node_path4.default.join(frontendDir, "src", "lib");
|
|
1322
|
+
const hooksDir = import_node_path4.default.join(frontendDir, "src", "hooks");
|
|
1323
|
+
const libFile = import_node_path4.default.join(libDir, "strapi.ts");
|
|
1324
|
+
const hooksFile = import_node_path4.default.join(hooksDir, "useStrapi.ts");
|
|
1325
|
+
if (!ensureInside2(frontendDir, libFile) || !ensureInside2(frontendDir, hooksFile)) {
|
|
1326
|
+
throw new Error("camada de dados fora do frontendDir");
|
|
1327
|
+
}
|
|
1328
|
+
const libImport = usesAtAlias(frontendDir) ? "@/lib/strapi" : "../lib/strapi";
|
|
1329
|
+
if (!dryRun) {
|
|
1330
|
+
import_node_fs4.default.mkdirSync(libDir, { recursive: true });
|
|
1331
|
+
import_node_fs4.default.mkdirSync(hooksDir, { recursive: true });
|
|
1332
|
+
import_node_fs4.default.writeFileSync(libFile, strapiClientSrc(manifest), "utf8");
|
|
1333
|
+
import_node_fs4.default.writeFileSync(hooksFile, hooksSrc(manifest, libImport), "utf8");
|
|
1334
|
+
}
|
|
1335
|
+
written.push("src/lib/strapi.ts", "src/hooks/useStrapi.ts");
|
|
1336
|
+
return written;
|
|
1337
|
+
}
|
|
1338
|
+
var CODE_EXT = /* @__PURE__ */ new Set([".tsx", ".jsx"]);
|
|
1339
|
+
var MAX_COMPONENT_CHARS = 16e3;
|
|
1340
|
+
function walkComponents(dir, base, out) {
|
|
1341
|
+
let entries;
|
|
1342
|
+
try {
|
|
1343
|
+
entries = import_node_fs4.default.readdirSync(dir, { withFileTypes: true });
|
|
1344
|
+
} catch {
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
for (const e of entries) {
|
|
1348
|
+
if (e.name.startsWith(".")) continue;
|
|
1349
|
+
const full = import_node_path4.default.join(dir, e.name);
|
|
1350
|
+
if (e.isDirectory()) {
|
|
1351
|
+
if (SKIP_DIRS.has(e.name) || e.name === "ui") continue;
|
|
1352
|
+
walkComponents(full, base, out);
|
|
1353
|
+
} else if (CODE_EXT.has(import_node_path4.default.extname(e.name))) {
|
|
1354
|
+
out.push(import_node_path4.default.relative(base, full));
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
function resolvePlural(strapi, singularName) {
|
|
1359
|
+
const real = strapi?.contentTypes?.[apiUid(singularName)]?.info?.pluralName;
|
|
1360
|
+
return real || `${singularName}s`;
|
|
1361
|
+
}
|
|
1362
|
+
function contentModelSummary(strapi, manifest) {
|
|
1363
|
+
const lines = [];
|
|
1364
|
+
for (const ct of manifest.contentTypes) {
|
|
1365
|
+
const fields = Object.keys(ct.attributes || {}).join(", ");
|
|
1366
|
+
if (ct.kind === "singleType") {
|
|
1367
|
+
lines.push(`singleType "${ct.singularName}" (useSection("${ct.singularName}")) campos: ${fields}`);
|
|
1368
|
+
} else {
|
|
1369
|
+
const plural = resolvePlural(strapi, ct.singularName);
|
|
1370
|
+
lines.push(`collection "${ct.singularName}" (useCollection("${plural}")) campos: ${fields}`);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
return lines.join("\n");
|
|
1374
|
+
}
|
|
1375
|
+
function wirePrompt(rel, source, model, hooksImport, seedSnippet) {
|
|
1376
|
+
return `Voc\xEA religa um componente React para ler o conte\xFAdo da Strapi, SEM quebrar nada.
|
|
1377
|
+
|
|
1378
|
+
MODELO DE CONTE\xDADO (use EXATAMENTE estes nomes de hook/campo):
|
|
1379
|
+
${model}
|
|
1380
|
+
|
|
1381
|
+
DADOS SEMEADOS (para casar o texto hardcoded com o campo certo):
|
|
1382
|
+
${seedSnippet}
|
|
1383
|
+
|
|
1384
|
+
REGRAS (siga \xE0 risca):
|
|
1385
|
+
- Importe os hooks de "${hooksImport}" (ex.: import { useSection, useCollection } from "${hooksImport}";).
|
|
1386
|
+
- Dentro do componente, chame os hooks necess\xE1rios (ex.: const hero = useSection("hero-section-content");).
|
|
1387
|
+
- Troque CADA texto hardcoded que casa com um campo por { obj.campo ?? "TEXTO ORIGINAL" } \u2014 SEMPRE mantenha o texto original como fallback no ?? .
|
|
1388
|
+
- Para listas (arrays hardcoded de objetos), troque o array por useCollection(...) e itere sobre ele; mantenha \xEDcones/imagens/classes/animacoes/layout EXATAMENTE como est\xE3o (n\xE3o s\xE3o conte\xFAdo).
|
|
1389
|
+
- N\xC3O altere imports de \xEDcones/assets, JSX estrutural, classes Tailwind, hooks de anima\xE7\xE3o, nada que n\xE3o seja texto/dado.
|
|
1390
|
+
- N\xC3O invente campos nem textos. Se um trecho n\xE3o casa com nenhum campo, deixe como est\xE1.
|
|
1391
|
+
- Mantenha o arquivo V\xC1LIDO e COMPLETO (TypeScript/TSX que compila). Responda com JSON: {"code":"<arquivo .tsx completo>"} e NADA al\xE9m disso.
|
|
1392
|
+
|
|
1393
|
+
ARQUIVO: ${rel}
|
|
1394
|
+
\`\`\`tsx
|
|
1395
|
+
${source}
|
|
1396
|
+
\`\`\``;
|
|
1397
|
+
}
|
|
1398
|
+
function syntaxError(code) {
|
|
1399
|
+
let esbuild;
|
|
1400
|
+
try {
|
|
1401
|
+
esbuild = require("esbuild");
|
|
1402
|
+
} catch {
|
|
1403
|
+
return null;
|
|
1404
|
+
}
|
|
1405
|
+
try {
|
|
1406
|
+
esbuild.transformSync(code, { loader: "tsx", jsx: "automatic" });
|
|
1407
|
+
return "";
|
|
1408
|
+
} catch (e) {
|
|
1409
|
+
return (e?.message || "erro de sintaxe").split("\n")[0];
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
function looksSane(original, next, hooksImport) {
|
|
1413
|
+
if (!next || next.length < 40) return "sa\xEDda vazia/curta demais";
|
|
1414
|
+
if (next.length < original.length * 0.5) return "sa\xEDda muito menor que o original (poss\xEDvel truncamento)";
|
|
1415
|
+
if (!/export\s+default|export\s+function|export\s+const/.test(next)) return "sem export";
|
|
1416
|
+
if (!next.includes(hooksImport)) return "n\xE3o importou os hooks";
|
|
1417
|
+
const balanced = (s, a, b) => s.split(a).length - 1 === s.split(b).length - 1;
|
|
1418
|
+
if (!balanced(next, "{", "}")) return "chaves desbalanceadas";
|
|
1419
|
+
if (!balanced(next, "(", ")")) return "par\xEAnteses desbalanceados";
|
|
1420
|
+
if (!balanced(next, "[", "]")) return "colchetes desbalanceados";
|
|
1421
|
+
if (/```/.test(next)) return "markdown na sa\xEDda";
|
|
1422
|
+
return null;
|
|
1423
|
+
}
|
|
1424
|
+
async function callOpenAI(apiKey, prompt) {
|
|
1425
|
+
const res = await fetch(OPENAI_URL, {
|
|
1426
|
+
method: "POST",
|
|
1427
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1428
|
+
body: JSON.stringify({
|
|
1429
|
+
model: MODEL,
|
|
1430
|
+
temperature: 0,
|
|
1431
|
+
response_format: { type: "json_object" },
|
|
1432
|
+
messages: [
|
|
1433
|
+
{ role: "system", content: 'Voc\xEA religa componentes React para a Strapi e responde s\xF3 com JSON {"code": "..."} v\xE1lido.' },
|
|
1434
|
+
{ role: "user", content: prompt }
|
|
1435
|
+
]
|
|
1436
|
+
})
|
|
1437
|
+
});
|
|
1438
|
+
if (!res.ok) throw new Error(`OpenAI: ${await res.text()}`);
|
|
1439
|
+
const data = await res.json();
|
|
1440
|
+
const raw = JSON.parse(data.choices?.[0]?.message?.content ?? "{}");
|
|
1441
|
+
return typeof raw.code === "string" ? raw.code : "";
|
|
1442
|
+
}
|
|
1443
|
+
async function wireFrontend(_strapi, opts) {
|
|
1444
|
+
const { frontendDir, manifest } = opts;
|
|
1445
|
+
const result = {
|
|
1446
|
+
ok: false,
|
|
1447
|
+
dataLayer: [],
|
|
1448
|
+
componentsWired: [],
|
|
1449
|
+
componentsSkipped: [],
|
|
1450
|
+
warnings: [],
|
|
1451
|
+
errors: []
|
|
1452
|
+
};
|
|
1453
|
+
if (!import_node_path4.default.isAbsolute(frontendDir)) {
|
|
1454
|
+
result.errors.push("frontendDir deve ser absoluto");
|
|
1455
|
+
return result;
|
|
1456
|
+
}
|
|
1457
|
+
try {
|
|
1458
|
+
result.dataLayer = writeDataLayer(frontendDir, manifest, opts.dryRun);
|
|
1459
|
+
} catch (e) {
|
|
1460
|
+
result.errors.push(`camada de dados: ${e?.message ?? e}`);
|
|
1461
|
+
return result;
|
|
1462
|
+
}
|
|
1463
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
1464
|
+
if (!apiKey) {
|
|
1465
|
+
result.warnings.push("Sem OPENAI_API_KEY: camada de dados criada, mas os componentes N\xC3O foram religados (precisa da chave).");
|
|
1466
|
+
result.ok = true;
|
|
1467
|
+
return result;
|
|
1468
|
+
}
|
|
1469
|
+
const srcDir = import_node_path4.default.join(frontendDir, "src");
|
|
1470
|
+
const all = [];
|
|
1471
|
+
walkComponents(srcDir, frontendDir, all);
|
|
1472
|
+
const components = all.filter((rel) => /(\/|^)(components|pages|app|routes)(\/|$)/.test(rel.replace(/\\/g, "/")));
|
|
1473
|
+
const model = contentModelSummary(_strapi, manifest);
|
|
1474
|
+
const hooksImport = usesAtAlias(frontendDir) ? "@/hooks/useStrapi" : "../hooks/useStrapi";
|
|
1475
|
+
const seedSnippet = JSON.stringify(manifest.seed ?? []).slice(0, 6e3);
|
|
1476
|
+
for (const rel of components) {
|
|
1477
|
+
const abs = import_node_path4.default.join(frontendDir, rel);
|
|
1478
|
+
if (!ensureInside2(frontendDir, abs)) continue;
|
|
1479
|
+
let source;
|
|
1480
|
+
try {
|
|
1481
|
+
source = import_node_fs4.default.readFileSync(abs, "utf8");
|
|
1482
|
+
} catch {
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
if (source.length > MAX_COMPONENT_CHARS) {
|
|
1486
|
+
result.componentsSkipped.push({ rel, reason: "arquivo grande demais" });
|
|
1487
|
+
continue;
|
|
1488
|
+
}
|
|
1489
|
+
if (source.includes("useStrapi")) {
|
|
1490
|
+
result.componentsSkipped.push({ rel, reason: "j\xE1 religado" });
|
|
1491
|
+
continue;
|
|
1492
|
+
}
|
|
1493
|
+
const check = (code) => {
|
|
1494
|
+
const h = looksSane(source, code, hooksImport);
|
|
1495
|
+
if (h) return h;
|
|
1496
|
+
const s = syntaxError(code);
|
|
1497
|
+
return s ? `sintaxe: ${s}` : null;
|
|
1498
|
+
};
|
|
1499
|
+
try {
|
|
1500
|
+
const prompt = wirePrompt(rel, source, model, hooksImport, seedSnippet);
|
|
1501
|
+
let next = await callOpenAI(apiKey, prompt);
|
|
1502
|
+
let bad = check(next);
|
|
1503
|
+
if (bad) {
|
|
1504
|
+
const fix = `${prompt}
|
|
1505
|
+
|
|
1506
|
+
A sua tentativa anterior foi REJEITADA por: ${bad}. Corrija e responda s\xF3 com o JSON {"code":"..."} do arquivo completo e v\xE1lido.`;
|
|
1507
|
+
const next2 = await callOpenAI(apiKey, fix);
|
|
1508
|
+
const bad2 = check(next2);
|
|
1509
|
+
if (bad2) {
|
|
1510
|
+
result.componentsSkipped.push({ rel, reason: bad2 });
|
|
1511
|
+
continue;
|
|
1512
|
+
}
|
|
1513
|
+
next = next2;
|
|
1514
|
+
}
|
|
1515
|
+
if (!opts.dryRun) {
|
|
1516
|
+
const bak = abs + ".bak";
|
|
1517
|
+
if (!import_node_fs4.default.existsSync(bak)) import_node_fs4.default.writeFileSync(bak, source, "utf8");
|
|
1518
|
+
import_node_fs4.default.writeFileSync(abs, next, "utf8");
|
|
1519
|
+
}
|
|
1520
|
+
result.componentsWired.push(rel);
|
|
1521
|
+
} catch (e) {
|
|
1522
|
+
result.componentsSkipped.push({ rel, reason: `IA: ${e?.message ?? e}` });
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
result.ok = true;
|
|
1526
|
+
return result;
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1188
1529
|
// server/src/provision/permissions.ts
|
|
1189
1530
|
async function grantPublicRead(strapi, manifest) {
|
|
1190
1531
|
const result = { granted: [], errors: [] };
|
|
@@ -1223,17 +1564,17 @@ var MARKER_DIR = ".mcp-chat";
|
|
|
1223
1564
|
var MARKER_FILE = "pending-provision.json";
|
|
1224
1565
|
var DONE_FILE = "last-provision.json";
|
|
1225
1566
|
function markerPath(strapiAppDir) {
|
|
1226
|
-
return
|
|
1567
|
+
return import_node_path5.default.join(strapiAppDir, MARKER_DIR, MARKER_FILE);
|
|
1227
1568
|
}
|
|
1228
1569
|
function donePath(strapiAppDir) {
|
|
1229
|
-
return
|
|
1570
|
+
return import_node_path5.default.join(strapiAppDir, MARKER_DIR, DONE_FILE);
|
|
1230
1571
|
}
|
|
1231
1572
|
function getProvisionStatus(strapiAppDir) {
|
|
1232
|
-
const pending =
|
|
1573
|
+
const pending = import_node_fs5.default.existsSync(markerPath(strapiAppDir));
|
|
1233
1574
|
let done = null;
|
|
1234
1575
|
try {
|
|
1235
1576
|
const dp = donePath(strapiAppDir);
|
|
1236
|
-
if (
|
|
1577
|
+
if (import_node_fs5.default.existsSync(dp)) done = JSON.parse(import_node_fs5.default.readFileSync(dp, "utf8"));
|
|
1237
1578
|
} catch {
|
|
1238
1579
|
}
|
|
1239
1580
|
return { pending, done };
|
|
@@ -1269,10 +1610,10 @@ function stageProvision(strapi, input) {
|
|
|
1269
1610
|
if (!input.dryRun) {
|
|
1270
1611
|
try {
|
|
1271
1612
|
const mp = markerPath(input.strapiAppDir);
|
|
1272
|
-
|
|
1273
|
-
|
|
1613
|
+
import_node_fs5.default.mkdirSync(import_node_path5.default.dirname(mp), { recursive: true });
|
|
1614
|
+
import_node_fs5.default.writeFileSync(mp, JSON.stringify(marker, null, 2), "utf8");
|
|
1274
1615
|
try {
|
|
1275
|
-
|
|
1616
|
+
import_node_fs5.default.unlinkSync(donePath(input.strapiAppDir));
|
|
1276
1617
|
} catch {
|
|
1277
1618
|
}
|
|
1278
1619
|
result.staged = true;
|
|
@@ -1288,10 +1629,10 @@ function stageProvision(strapi, input) {
|
|
|
1288
1629
|
async function runPendingProvision(strapi, strapiAppDir) {
|
|
1289
1630
|
const result = { ran: false, errors: [] };
|
|
1290
1631
|
const mp = markerPath(strapiAppDir);
|
|
1291
|
-
if (!
|
|
1632
|
+
if (!import_node_fs5.default.existsSync(mp)) return result;
|
|
1292
1633
|
let marker;
|
|
1293
1634
|
try {
|
|
1294
|
-
marker = JSON.parse(
|
|
1635
|
+
marker = JSON.parse(import_node_fs5.default.readFileSync(mp, "utf8"));
|
|
1295
1636
|
} catch (e) {
|
|
1296
1637
|
result.errors.push(`marcador ileg\xEDvel: ${e?.message ?? e}`);
|
|
1297
1638
|
return result;
|
|
@@ -1319,6 +1660,15 @@ async function runPendingProvision(strapi, strapiAppDir) {
|
|
|
1319
1660
|
} catch (e) {
|
|
1320
1661
|
result.errors.push(`link: ${e?.message ?? e}`);
|
|
1321
1662
|
}
|
|
1663
|
+
try {
|
|
1664
|
+
result.wire = await wireFrontend(strapi, {
|
|
1665
|
+
frontendDir: marker.frontendDir,
|
|
1666
|
+
manifest: marker.manifest
|
|
1667
|
+
});
|
|
1668
|
+
if (result.wire.errors.length) result.errors.push(...result.wire.errors.map((e) => `wire: ${e}`));
|
|
1669
|
+
} catch (e) {
|
|
1670
|
+
result.errors.push(`wire: ${e?.message ?? e}`);
|
|
1671
|
+
}
|
|
1322
1672
|
try {
|
|
1323
1673
|
const previewUrl = marker.context.frontendUrl || `http://localhost:${FRONTEND_BASE_PORT}`;
|
|
1324
1674
|
const done = {
|
|
@@ -1332,24 +1682,24 @@ async function runPendingProvision(strapi, strapiAppDir) {
|
|
|
1332
1682
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1333
1683
|
};
|
|
1334
1684
|
const dp = donePath(strapiAppDir);
|
|
1335
|
-
|
|
1336
|
-
|
|
1685
|
+
import_node_fs5.default.mkdirSync(import_node_path5.default.dirname(dp), { recursive: true });
|
|
1686
|
+
import_node_fs5.default.writeFileSync(dp, JSON.stringify(done, null, 2), "utf8");
|
|
1337
1687
|
} catch (e) {
|
|
1338
1688
|
result.errors.push(`resumo: ${e?.message ?? e}`);
|
|
1339
1689
|
}
|
|
1340
1690
|
try {
|
|
1341
|
-
|
|
1691
|
+
import_node_fs5.default.unlinkSync(mp);
|
|
1342
1692
|
} catch {
|
|
1343
1693
|
}
|
|
1344
1694
|
return result;
|
|
1345
1695
|
}
|
|
1346
1696
|
|
|
1347
1697
|
// server/src/provision/infer.ts
|
|
1348
|
-
var
|
|
1349
|
-
var
|
|
1350
|
-
var
|
|
1351
|
-
var
|
|
1352
|
-
var
|
|
1698
|
+
var import_node_fs6 = __toESM(require("node:fs"));
|
|
1699
|
+
var import_node_path6 = __toESM(require("node:path"));
|
|
1700
|
+
var OPENAI_URL2 = "https://api.openai.com/v1/chat/completions";
|
|
1701
|
+
var MODEL2 = process.env.OPENAI_CHAT_MODEL || "gpt-4o";
|
|
1702
|
+
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
1353
1703
|
"node_modules",
|
|
1354
1704
|
".git",
|
|
1355
1705
|
"dist",
|
|
@@ -1363,7 +1713,7 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
1363
1713
|
".cache",
|
|
1364
1714
|
"public"
|
|
1365
1715
|
]);
|
|
1366
|
-
var
|
|
1716
|
+
var CODE_EXT2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs"]);
|
|
1367
1717
|
var MAX_FILES = 18;
|
|
1368
1718
|
var MAX_TOTAL_CHARS = 6e4;
|
|
1369
1719
|
var MAX_FILE_CHARS = 12e3;
|
|
@@ -1379,18 +1729,18 @@ function score(rel) {
|
|
|
1379
1729
|
function walk(dir, base, out) {
|
|
1380
1730
|
let entries;
|
|
1381
1731
|
try {
|
|
1382
|
-
entries =
|
|
1732
|
+
entries = import_node_fs6.default.readdirSync(dir, { withFileTypes: true });
|
|
1383
1733
|
} catch {
|
|
1384
1734
|
return;
|
|
1385
1735
|
}
|
|
1386
1736
|
for (const e of entries) {
|
|
1387
1737
|
if (e.name.startsWith(".") && e.name !== ".") continue;
|
|
1388
|
-
const full =
|
|
1738
|
+
const full = import_node_path6.default.join(dir, e.name);
|
|
1389
1739
|
if (e.isDirectory()) {
|
|
1390
|
-
if (
|
|
1740
|
+
if (SKIP_DIRS2.has(e.name)) continue;
|
|
1391
1741
|
walk(full, base, out);
|
|
1392
|
-
} else if (
|
|
1393
|
-
out.push(
|
|
1742
|
+
} else if (CODE_EXT2.has(import_node_path6.default.extname(e.name))) {
|
|
1743
|
+
out.push(import_node_path6.default.relative(base, full));
|
|
1394
1744
|
}
|
|
1395
1745
|
}
|
|
1396
1746
|
}
|
|
@@ -1405,7 +1755,7 @@ function collectFiles(frontendDir) {
|
|
|
1405
1755
|
for (const rel of all) {
|
|
1406
1756
|
let content;
|
|
1407
1757
|
try {
|
|
1408
|
-
content =
|
|
1758
|
+
content = import_node_fs6.default.readFileSync(import_node_path6.default.join(frontendDir, rel), "utf8");
|
|
1409
1759
|
} catch {
|
|
1410
1760
|
continue;
|
|
1411
1761
|
}
|
|
@@ -1460,7 +1810,7 @@ function collectPageTexts(frontendDir) {
|
|
|
1460
1810
|
for (const rel of ranked) {
|
|
1461
1811
|
if (out.length >= MAX_TEXT_FILES) break;
|
|
1462
1812
|
try {
|
|
1463
|
-
const texts = extractTexts(
|
|
1813
|
+
const texts = extractTexts(import_node_fs6.default.readFileSync(import_node_path6.default.join(frontendDir, rel), "utf8"));
|
|
1464
1814
|
if (texts.length) out.push({ rel, texts });
|
|
1465
1815
|
} catch {
|
|
1466
1816
|
}
|
|
@@ -1550,7 +1900,7 @@ function buildPageContentTypes(pageTexts, budget) {
|
|
|
1550
1900
|
}
|
|
1551
1901
|
function detectFramework2(frontendDir) {
|
|
1552
1902
|
try {
|
|
1553
|
-
const pkg = JSON.parse(
|
|
1903
|
+
const pkg = JSON.parse(import_node_fs6.default.readFileSync(import_node_path6.default.join(frontendDir, "package.json"), "utf8"));
|
|
1554
1904
|
const deps = { ...pkg.dependencies || {}, ...pkg.devDependencies || {} };
|
|
1555
1905
|
if (deps.next) return "next";
|
|
1556
1906
|
if (deps["@tanstack/react-start"]) return "tanstack";
|
|
@@ -1622,10 +1972,10 @@ async function inferManifest(strapi, frontendDir, opts) {
|
|
|
1622
1972
|
warnings: [],
|
|
1623
1973
|
errors: []
|
|
1624
1974
|
};
|
|
1625
|
-
const existing =
|
|
1626
|
-
if (
|
|
1975
|
+
const existing = import_node_path6.default.join(frontendDir, "strapi.manifest.json");
|
|
1976
|
+
if (import_node_fs6.default.existsSync(existing)) {
|
|
1627
1977
|
try {
|
|
1628
|
-
const raw = JSON.parse(
|
|
1978
|
+
const raw = JSON.parse(import_node_fs6.default.readFileSync(existing, "utf8"));
|
|
1629
1979
|
const v2 = validateManifest(raw);
|
|
1630
1980
|
result.inferred = false;
|
|
1631
1981
|
result.rawManifest = raw;
|
|
@@ -1662,11 +2012,11 @@ async function inferManifest(strapi, frontendDir, opts) {
|
|
|
1662
2012
|
let dataSeed = [];
|
|
1663
2013
|
const apiKey = process.env.OPENAI_API_KEY;
|
|
1664
2014
|
if (apiKey && collected.files.length) {
|
|
1665
|
-
const
|
|
1666
|
-
const res = await fetch(
|
|
2015
|
+
const callOpenAI2 = async (messages2) => {
|
|
2016
|
+
const res = await fetch(OPENAI_URL2, {
|
|
1667
2017
|
method: "POST",
|
|
1668
2018
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1669
|
-
body: JSON.stringify({ model:
|
|
2019
|
+
body: JSON.stringify({ model: MODEL2, temperature: 0, response_format: { type: "json_object" }, messages: messages2 })
|
|
1670
2020
|
});
|
|
1671
2021
|
if (!res.ok) throw new Error(`OpenAI: ${await res.text()}`);
|
|
1672
2022
|
return res.json();
|
|
@@ -1677,7 +2027,7 @@ async function inferManifest(strapi, frontendDir, opts) {
|
|
|
1677
2027
|
];
|
|
1678
2028
|
try {
|
|
1679
2029
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1680
|
-
const data = await
|
|
2030
|
+
const data = await callOpenAI2(messages);
|
|
1681
2031
|
const raw = JSON.parse(data.choices?.[0]?.message?.content ?? "{}");
|
|
1682
2032
|
const candidate = {
|
|
1683
2033
|
manifestVersion: 1,
|
|
@@ -1712,7 +2062,7 @@ async function inferManifest(strapi, frontendDir, opts) {
|
|
|
1712
2062
|
const parts = [];
|
|
1713
2063
|
for (const f of collected.files) {
|
|
1714
2064
|
try {
|
|
1715
|
-
parts.push(
|
|
2065
|
+
parts.push(import_node_fs6.default.readFileSync(import_node_path6.default.join(frontendDir, f.rel), "utf8"));
|
|
1716
2066
|
} catch {
|
|
1717
2067
|
parts.push(f.content);
|
|
1718
2068
|
}
|
|
@@ -1779,12 +2129,12 @@ async function inferManifest(strapi, frontendDir, opts) {
|
|
|
1779
2129
|
}
|
|
1780
2130
|
|
|
1781
2131
|
// server/src/provision/integrate.ts
|
|
1782
|
-
var
|
|
1783
|
-
var
|
|
2132
|
+
var import_node_fs7 = __toESM(require("node:fs"));
|
|
2133
|
+
var import_node_path7 = __toESM(require("node:path"));
|
|
1784
2134
|
var import_node_child_process2 = require("node:child_process");
|
|
1785
|
-
var
|
|
1786
|
-
var
|
|
1787
|
-
var
|
|
2135
|
+
var OPENAI_URL3 = "https://api.openai.com/v1/chat/completions";
|
|
2136
|
+
var MODEL3 = process.env.OPENAI_CHAT_MODEL || "gpt-4o";
|
|
2137
|
+
var SKIP_DIRS3 = /* @__PURE__ */ new Set([
|
|
1788
2138
|
"node_modules",
|
|
1789
2139
|
".git",
|
|
1790
2140
|
"dist",
|
|
@@ -1798,7 +2148,7 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
|
1798
2148
|
".cache",
|
|
1799
2149
|
"public"
|
|
1800
2150
|
]);
|
|
1801
|
-
var
|
|
2151
|
+
var CODE_EXT3 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs"]);
|
|
1802
2152
|
var MAX_DATA_FILES = 3;
|
|
1803
2153
|
var MAX_FILE_CHARS2 = 16e3;
|
|
1804
2154
|
function score2(rel) {
|
|
@@ -1813,18 +2163,18 @@ function score2(rel) {
|
|
|
1813
2163
|
function walk2(dir, base, out) {
|
|
1814
2164
|
let entries;
|
|
1815
2165
|
try {
|
|
1816
|
-
entries =
|
|
2166
|
+
entries = import_node_fs7.default.readdirSync(dir, { withFileTypes: true });
|
|
1817
2167
|
} catch {
|
|
1818
2168
|
return;
|
|
1819
2169
|
}
|
|
1820
2170
|
for (const e of entries) {
|
|
1821
2171
|
if (e.name.startsWith(".")) continue;
|
|
1822
|
-
const full =
|
|
2172
|
+
const full = import_node_path7.default.join(dir, e.name);
|
|
1823
2173
|
if (e.isDirectory()) {
|
|
1824
|
-
if (
|
|
2174
|
+
if (SKIP_DIRS3.has(e.name)) continue;
|
|
1825
2175
|
walk2(full, base, out);
|
|
1826
|
-
} else if (
|
|
1827
|
-
out.push(
|
|
2176
|
+
} else if (CODE_EXT3.has(import_node_path7.default.extname(e.name))) {
|
|
2177
|
+
out.push(import_node_path7.default.relative(base, full));
|
|
1828
2178
|
}
|
|
1829
2179
|
}
|
|
1830
2180
|
}
|
|
@@ -1833,7 +2183,7 @@ function findDataFiles(frontendDir) {
|
|
|
1833
2183
|
walk2(frontendDir, frontendDir, all);
|
|
1834
2184
|
return all.map((rel) => ({ rel, s: score2(rel) })).filter((x) => x.s > 0).filter(({ rel }) => {
|
|
1835
2185
|
try {
|
|
1836
|
-
const c =
|
|
2186
|
+
const c = import_node_fs7.default.readFileSync(import_node_path7.default.join(frontendDir, rel), "utf8");
|
|
1837
2187
|
return /export\s+const\s+\w+\s*[:=]\s*(\[|\{)/.test(c);
|
|
1838
2188
|
} catch {
|
|
1839
2189
|
return false;
|
|
@@ -1902,19 +2252,19 @@ export async function fetchSingle(singular: string, o: FetchOpts = {}): Promise<
|
|
|
1902
2252
|
}
|
|
1903
2253
|
`;
|
|
1904
2254
|
async function ensureClientDep(frontendDir, warnings) {
|
|
1905
|
-
if (
|
|
2255
|
+
if (import_node_fs7.default.existsSync(import_node_path7.default.join(frontendDir, "node_modules", "@strapi", "client"))) return;
|
|
1906
2256
|
try {
|
|
1907
|
-
const pkgPath =
|
|
1908
|
-
const pkg = JSON.parse(
|
|
2257
|
+
const pkgPath = import_node_path7.default.join(frontendDir, "package.json");
|
|
2258
|
+
const pkg = JSON.parse(import_node_fs7.default.readFileSync(pkgPath, "utf8"));
|
|
1909
2259
|
pkg.dependencies = pkg.dependencies || {};
|
|
1910
2260
|
if (!pkg.dependencies["@strapi/client"]) {
|
|
1911
2261
|
pkg.dependencies["@strapi/client"] = "^1.6.2";
|
|
1912
|
-
|
|
2262
|
+
import_node_fs7.default.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
1913
2263
|
}
|
|
1914
2264
|
} catch (e) {
|
|
1915
2265
|
warnings.push(`package.json do frontend: ${e?.message ?? e}`);
|
|
1916
2266
|
}
|
|
1917
|
-
const pm =
|
|
2267
|
+
const pm = import_node_fs7.default.existsSync(import_node_path7.default.join(frontendDir, "bun.lock")) || import_node_fs7.default.existsSync(import_node_path7.default.join(frontendDir, "bun.lockb")) ? "bun" : import_node_fs7.default.existsSync(import_node_path7.default.join(frontendDir, "pnpm-lock.yaml")) ? "pnpm" : import_node_fs7.default.existsSync(import_node_path7.default.join(frontendDir, "yarn.lock")) ? "yarn" : "npm";
|
|
1918
2268
|
const args = pm === "npm" ? ["install", "@strapi/client", "--no-audit", "--no-fund"] : ["add", "@strapi/client"];
|
|
1919
2269
|
await new Promise((resolve) => {
|
|
1920
2270
|
try {
|
|
@@ -1966,11 +2316,11 @@ ${baseSrc.length > MAX_FILE_CHARS2 ? baseSrc.slice(0, MAX_FILE_CHARS2) : baseSrc
|
|
|
1966
2316
|
|
|
1967
2317
|
AMOSTRA REAL DA RESPOSTA DO STRAPI (JSON, por singularName):
|
|
1968
2318
|
${JSON.stringify(sample, null, 1).slice(0, 18e3)}`;
|
|
1969
|
-
const res = await fetch(
|
|
2319
|
+
const res = await fetch(OPENAI_URL3, {
|
|
1970
2320
|
method: "POST",
|
|
1971
2321
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
1972
2322
|
body: JSON.stringify({
|
|
1973
|
-
model:
|
|
2323
|
+
model: MODEL3,
|
|
1974
2324
|
temperature: 0,
|
|
1975
2325
|
messages: [
|
|
1976
2326
|
{ role: "system", content: "Voc\xEA gera uma fun\xE7\xE3o pura de mapeamento Strapi\u2192shape do frontend. Responde s\xF3 com a fun\xE7\xE3o." },
|
|
@@ -2124,15 +2474,15 @@ export function LanguageSwitcher() {
|
|
|
2124
2474
|
export default LanguageSwitcher;
|
|
2125
2475
|
`;
|
|
2126
2476
|
function injectSwitcher(frontendDir, warnings, dataImport = "@/data/site") {
|
|
2127
|
-
const compDir =
|
|
2128
|
-
|
|
2129
|
-
|
|
2477
|
+
const compDir = import_node_path7.default.join(frontendDir, "src", "components");
|
|
2478
|
+
import_node_fs7.default.mkdirSync(compDir, { recursive: true });
|
|
2479
|
+
import_node_fs7.default.writeFileSync(import_node_path7.default.join(compDir, "LanguageSwitcher.tsx"), switcherTsx(dataImport), "utf8");
|
|
2130
2480
|
const rootRel = ["src/routes/__root.tsx", "src/routes/__root.jsx"].find(
|
|
2131
|
-
(r) =>
|
|
2481
|
+
(r) => import_node_fs7.default.existsSync(import_node_path7.default.join(frontendDir, r))
|
|
2132
2482
|
);
|
|
2133
2483
|
if (rootRel) {
|
|
2134
|
-
const abs =
|
|
2135
|
-
let src =
|
|
2484
|
+
const abs = import_node_path7.default.join(frontendDir, rootRel);
|
|
2485
|
+
let src = import_node_fs7.default.readFileSync(abs, "utf8");
|
|
2136
2486
|
if (!src.includes("loadAllData")) {
|
|
2137
2487
|
src = `import { loadAllData } from "${dataImport}";
|
|
2138
2488
|
` + src;
|
|
@@ -2146,17 +2496,17 @@ function injectSwitcher(frontendDir, warnings, dataImport = "@/data/site") {
|
|
|
2146
2496
|
} else {
|
|
2147
2497
|
warnings.push("n\xE3o consegui injetar o loader no __root (padr\xE3o n\xE3o encontrado).");
|
|
2148
2498
|
}
|
|
2149
|
-
|
|
2499
|
+
import_node_fs7.default.writeFileSync(abs, src, "utf8");
|
|
2150
2500
|
}
|
|
2151
2501
|
} else {
|
|
2152
2502
|
warnings.push("__root n\xE3o encontrado \u2014 dados ao vivo n\xE3o ligados ao SSR.");
|
|
2153
2503
|
}
|
|
2154
2504
|
const headerRel = ["src/components/Header.tsx", "src/components/Header.jsx"].find(
|
|
2155
|
-
(r) =>
|
|
2505
|
+
(r) => import_node_fs7.default.existsSync(import_node_path7.default.join(frontendDir, r))
|
|
2156
2506
|
);
|
|
2157
2507
|
if (headerRel) {
|
|
2158
|
-
const abs =
|
|
2159
|
-
let src =
|
|
2508
|
+
const abs = import_node_path7.default.join(frontendDir, headerRel);
|
|
2509
|
+
let src = import_node_fs7.default.readFileSync(abs, "utf8");
|
|
2160
2510
|
if (!src.includes("LanguageSwitcher")) {
|
|
2161
2511
|
src = `import { LanguageSwitcher } from "@/components/LanguageSwitcher";
|
|
2162
2512
|
` + src;
|
|
@@ -2166,7 +2516,7 @@ function injectSwitcher(frontendDir, warnings, dataImport = "@/data/site") {
|
|
|
2166
2516
|
src = src.replace(/<\/header>/, ` <LanguageSwitcher />
|
|
2167
2517
|
</header>`);
|
|
2168
2518
|
}
|
|
2169
|
-
|
|
2519
|
+
import_node_fs7.default.writeFileSync(abs, src, "utf8");
|
|
2170
2520
|
}
|
|
2171
2521
|
} else {
|
|
2172
2522
|
warnings.push("Header n\xE3o encontrado \u2014 adicione <LanguageSwitcher/> manualmente.");
|
|
@@ -2212,11 +2562,11 @@ REGRAS ESTRITAS:
|
|
|
2212
2562
|
|
|
2213
2563
|
ARQUIVO:
|
|
2214
2564
|
${src.length > MAX_FILE_CHARS2 ? src.slice(0, MAX_FILE_CHARS2) : src}`;
|
|
2215
|
-
const res = await fetch(
|
|
2565
|
+
const res = await fetch(OPENAI_URL3, {
|
|
2216
2566
|
method: "POST",
|
|
2217
2567
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
2218
2568
|
body: JSON.stringify({
|
|
2219
|
-
model:
|
|
2569
|
+
model: MODEL3,
|
|
2220
2570
|
temperature: 0,
|
|
2221
2571
|
messages: [
|
|
2222
2572
|
{ role: "system", content: "Voc\xEA religa componentes ao CMS trocando s\xF3 os textos do mapa por express\xF5es. Responde s\xF3 com o c\xF3digo." },
|
|
@@ -2290,10 +2640,10 @@ async function rewireComponents(strapi, opts, warnings) {
|
|
|
2290
2640
|
(rel) => /\.(tsx|jsx)$/.test(rel) && !/__root|routeTree\.gen|\/api\/|LanguageSwitcher|PreviewBridge|\/ui\//.test(rel)
|
|
2291
2641
|
);
|
|
2292
2642
|
for (const rel of targets) {
|
|
2293
|
-
const abs =
|
|
2643
|
+
const abs = import_node_path7.default.join(opts.frontendDir, rel);
|
|
2294
2644
|
let src;
|
|
2295
2645
|
try {
|
|
2296
|
-
src =
|
|
2646
|
+
src = import_node_fs7.default.readFileSync(abs, "utf8");
|
|
2297
2647
|
} catch {
|
|
2298
2648
|
continue;
|
|
2299
2649
|
}
|
|
@@ -2312,8 +2662,8 @@ async function rewireComponents(strapi, opts, warnings) {
|
|
|
2312
2662
|
continue;
|
|
2313
2663
|
}
|
|
2314
2664
|
const bak = abs + ".bak";
|
|
2315
|
-
if (!
|
|
2316
|
-
|
|
2665
|
+
if (!import_node_fs7.default.existsSync(bak)) import_node_fs7.default.writeFileSync(bak, src, "utf8");
|
|
2666
|
+
import_node_fs7.default.writeFileSync(abs, out, "utf8");
|
|
2317
2667
|
rewired.push(rel);
|
|
2318
2668
|
} catch (e) {
|
|
2319
2669
|
warnings.push(`${rel}: rewire falhou (${e?.message ?? e}).`);
|
|
@@ -2352,19 +2702,19 @@ async function integrateFrontend(strapi, opts) {
|
|
|
2352
2702
|
count: Array.isArray(sample[c.singularName]) ? sample[c.singularName].length : sample[c.singularName] ? 1 : 0
|
|
2353
2703
|
}));
|
|
2354
2704
|
for (const rel of dataFiles) {
|
|
2355
|
-
const abs =
|
|
2705
|
+
const abs = import_node_path7.default.join(opts.frontendDir, rel);
|
|
2356
2706
|
try {
|
|
2357
|
-
const original =
|
|
2707
|
+
const original = import_node_fs7.default.readFileSync(abs, "utf8");
|
|
2358
2708
|
const bak = abs + ".bak";
|
|
2359
|
-
if (!
|
|
2360
|
-
const baseSrc =
|
|
2709
|
+
if (!import_node_fs7.default.existsSync(bak)) import_node_fs7.default.writeFileSync(bak, original, "utf8");
|
|
2710
|
+
const baseSrc = import_node_fs7.default.existsSync(bak) ? import_node_fs7.default.readFileSync(bak, "utf8") : original;
|
|
2361
2711
|
const mapper = await generateMapper(apiKey, baseSrc, sample, assetImportIds(baseSrc));
|
|
2362
2712
|
if (!mapper || !/mapStrapiToData/.test(mapper)) {
|
|
2363
2713
|
result.warnings.push(`${rel}: mapeador inv\xE1lido, pulado.`);
|
|
2364
2714
|
continue;
|
|
2365
2715
|
}
|
|
2366
2716
|
const moduleSrc = buildLiveDataModule(baseSrc, mapper, ctMeta, locales, def);
|
|
2367
|
-
|
|
2717
|
+
import_node_fs7.default.writeFileSync(abs, moduleSrc, "utf8");
|
|
2368
2718
|
result.filesRewritten.push(rel);
|
|
2369
2719
|
} catch (e) {
|
|
2370
2720
|
result.errors.push(`${rel}: ${e?.message ?? e}`);
|
|
@@ -2373,8 +2723,8 @@ async function integrateFrontend(strapi, opts) {
|
|
|
2373
2723
|
if (result.filesRewritten.length > 0) {
|
|
2374
2724
|
try {
|
|
2375
2725
|
const rel0 = result.filesRewritten[0];
|
|
2376
|
-
const dataDir =
|
|
2377
|
-
|
|
2726
|
+
const dataDir = import_node_path7.default.dirname(import_node_path7.default.join(opts.frontendDir, rel0));
|
|
2727
|
+
import_node_fs7.default.writeFileSync(import_node_path7.default.join(dataDir, "strapi-client.ts"), STRAPI_CLIENT_TS, "utf8");
|
|
2378
2728
|
const dataImport = "@/" + rel0.replace(/^src\//, "").replace(/\.(tsx?|jsx?)$/, "");
|
|
2379
2729
|
injectSwitcher(opts.frontendDir, result.warnings, dataImport);
|
|
2380
2730
|
await ensureClientDep(opts.frontendDir, result.warnings);
|
|
@@ -2398,12 +2748,12 @@ async function integrateFrontend(strapi, opts) {
|
|
|
2398
2748
|
|
|
2399
2749
|
// server/src/controllers/frontend.ts
|
|
2400
2750
|
var MANIFEST_NAME = "strapi.manifest.json";
|
|
2401
|
-
function
|
|
2402
|
-
const b =
|
|
2403
|
-
const t =
|
|
2751
|
+
function ensureInside3(base, target) {
|
|
2752
|
+
const b = import_node_path8.default.resolve(base);
|
|
2753
|
+
const t = import_node_path8.default.resolve(target);
|
|
2404
2754
|
if (t === b) return true;
|
|
2405
|
-
const rel =
|
|
2406
|
-
return !!rel && !rel.startsWith("..") && !
|
|
2755
|
+
const rel = import_node_path8.default.relative(b, t);
|
|
2756
|
+
return !!rel && !rel.startsWith("..") && !import_node_path8.default.isAbsolute(rel);
|
|
2407
2757
|
}
|
|
2408
2758
|
function toKebab(input) {
|
|
2409
2759
|
const s = (input || "frontend").toLowerCase().replace(/\.zip$/, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/^[^a-z]+/, "");
|
|
@@ -2431,13 +2781,13 @@ var frontend_default = {
|
|
|
2431
2781
|
const originalName = file.originalFilename || file.name || "frontend";
|
|
2432
2782
|
let zip;
|
|
2433
2783
|
try {
|
|
2434
|
-
zip = await import_jszip.default.loadAsync(
|
|
2784
|
+
zip = await import_jszip.default.loadAsync(import_node_fs8.default.readFileSync(filepath));
|
|
2435
2785
|
} catch (e) {
|
|
2436
2786
|
return ctx.badRequest(`Zip inv\xE1lido: ${e?.message ?? e}`);
|
|
2437
2787
|
}
|
|
2438
2788
|
const entryNames = Object.keys(zip.files);
|
|
2439
2789
|
const manifestEntry = entryNames.find(
|
|
2440
|
-
(p) =>
|
|
2790
|
+
(p) => import_node_path8.default.basename(p) === MANIFEST_NAME && !zip.files[p].dir
|
|
2441
2791
|
);
|
|
2442
2792
|
let rootPrefix = "";
|
|
2443
2793
|
if (manifestEntry) {
|
|
@@ -2450,27 +2800,27 @@ var frontend_default = {
|
|
|
2450
2800
|
}
|
|
2451
2801
|
const name = toKebab(originalName);
|
|
2452
2802
|
const strapiAppDir = strapi.dirs.app.root;
|
|
2453
|
-
const frontendDir =
|
|
2454
|
-
if (
|
|
2803
|
+
const frontendDir = import_node_path8.default.resolve(strapiAppDir, "..", name);
|
|
2804
|
+
if (import_node_fs8.default.existsSync(frontendDir) && import_node_fs8.default.readdirSync(frontendDir).length > 0) {
|
|
2455
2805
|
return ctx.badRequest(
|
|
2456
2806
|
`A pasta de destino j\xE1 existe e n\xE3o est\xE1 vazia: ${frontendDir}. Renomeie o .zip ou remova a pasta.`
|
|
2457
2807
|
);
|
|
2458
2808
|
}
|
|
2459
2809
|
try {
|
|
2460
|
-
|
|
2810
|
+
import_node_fs8.default.mkdirSync(frontendDir, { recursive: true });
|
|
2461
2811
|
for (const entryName of entryNames) {
|
|
2462
2812
|
const entry = zip.files[entryName];
|
|
2463
2813
|
const rel = rootPrefix && entryName.startsWith(rootPrefix) ? entryName.slice(rootPrefix.length) : entryName;
|
|
2464
2814
|
if (!rel) continue;
|
|
2465
|
-
const dest =
|
|
2466
|
-
if (!
|
|
2815
|
+
const dest = import_node_path8.default.join(frontendDir, rel);
|
|
2816
|
+
if (!ensureInside3(frontendDir, dest)) {
|
|
2467
2817
|
throw new Error(`entrada perigosa no zip bloqueada: ${entryName}`);
|
|
2468
2818
|
}
|
|
2469
2819
|
if (entry.dir) {
|
|
2470
|
-
|
|
2820
|
+
import_node_fs8.default.mkdirSync(dest, { recursive: true });
|
|
2471
2821
|
} else {
|
|
2472
|
-
|
|
2473
|
-
|
|
2822
|
+
import_node_fs8.default.mkdirSync(import_node_path8.default.dirname(dest), { recursive: true });
|
|
2823
|
+
import_node_fs8.default.writeFileSync(dest, await entry.async("nodebuffer"));
|
|
2474
2824
|
}
|
|
2475
2825
|
}
|
|
2476
2826
|
} catch (e) {
|
|
@@ -2498,16 +2848,16 @@ var frontend_default = {
|
|
|
2498
2848
|
const rawManifest = body.manifest;
|
|
2499
2849
|
const frontendDir = body.frontendDir;
|
|
2500
2850
|
if (!rawManifest) return ctx.badRequest('Envie o "manifest".');
|
|
2501
|
-
if (!frontendDir || !
|
|
2851
|
+
if (!frontendDir || !import_node_path8.default.isAbsolute(frontendDir)) {
|
|
2502
2852
|
return ctx.badRequest("frontendDir ausente ou inv\xE1lido.");
|
|
2503
2853
|
}
|
|
2504
2854
|
const strapiAppDir = strapi.dirs.app.root;
|
|
2505
2855
|
const apiRoot = strapi.dirs.app.api;
|
|
2506
|
-
const parent =
|
|
2507
|
-
if (!
|
|
2856
|
+
const parent = import_node_path8.default.resolve(strapiAppDir, "..");
|
|
2857
|
+
if (!ensureInside3(parent, frontendDir) || frontendDir === parent) {
|
|
2508
2858
|
return ctx.badRequest("frontendDir fora da pasta permitida.");
|
|
2509
2859
|
}
|
|
2510
|
-
if (!
|
|
2860
|
+
if (!import_node_fs8.default.existsSync(frontendDir)) {
|
|
2511
2861
|
return ctx.badRequest("frontendDir n\xE3o existe (rode a an\xE1lise primeiro).");
|
|
2512
2862
|
}
|
|
2513
2863
|
const v = validateManifest(rawManifest);
|
|
@@ -2515,8 +2865,8 @@ var frontend_default = {
|
|
|
2515
2865
|
return ctx.badRequest({ message: "Manifest inv\xE1lido", errors: v.errors });
|
|
2516
2866
|
}
|
|
2517
2867
|
try {
|
|
2518
|
-
|
|
2519
|
-
|
|
2868
|
+
import_node_fs8.default.writeFileSync(
|
|
2869
|
+
import_node_path8.default.join(frontendDir, MANIFEST_NAME),
|
|
2520
2870
|
JSON.stringify(v.data, null, 2),
|
|
2521
2871
|
"utf8"
|
|
2522
2872
|
);
|
|
@@ -2578,8 +2928,8 @@ var frontend_default = {
|
|
|
2578
2928
|
if (!dir || !url) {
|
|
2579
2929
|
return ctx.badRequest("Nenhum frontend provisionado para rodar.");
|
|
2580
2930
|
}
|
|
2581
|
-
const parent =
|
|
2582
|
-
if (!
|
|
2931
|
+
const parent = import_node_path8.default.resolve(strapi.dirs.app.root, "..");
|
|
2932
|
+
if (!ensureInside3(parent, dir) || !import_node_fs8.default.existsSync(dir)) {
|
|
2583
2933
|
return ctx.badRequest("Pasta do frontend inv\xE1lida.");
|
|
2584
2934
|
}
|
|
2585
2935
|
ctx.body = await startFrontend(strapi, { dir, url });
|
|
@@ -2603,19 +2953,48 @@ var frontend_default = {
|
|
|
2603
2953
|
frontendDir = st.done?.frontendDir || "";
|
|
2604
2954
|
}
|
|
2605
2955
|
if (!frontendDir) return ctx.badRequest("Nenhum frontend provisionado.");
|
|
2606
|
-
const parent =
|
|
2607
|
-
if (!
|
|
2956
|
+
const parent = import_node_path8.default.resolve(strapi.dirs.app.root, "..");
|
|
2957
|
+
if (!ensureInside3(parent, frontendDir) || !import_node_fs8.default.existsSync(frontendDir)) {
|
|
2608
2958
|
return ctx.badRequest("Pasta do frontend inv\xE1lida.");
|
|
2609
2959
|
}
|
|
2610
2960
|
let manifest;
|
|
2611
2961
|
try {
|
|
2612
|
-
manifest = JSON.parse(
|
|
2962
|
+
manifest = JSON.parse(import_node_fs8.default.readFileSync(import_node_path8.default.join(frontendDir, MANIFEST_NAME), "utf8"));
|
|
2613
2963
|
} catch {
|
|
2614
2964
|
return ctx.badRequest("Manifest do projeto n\xE3o encontrado (rode a provis\xE3o primeiro).");
|
|
2615
2965
|
}
|
|
2616
2966
|
const v = validateManifest(manifest);
|
|
2617
2967
|
if (!v.ok) return ctx.badRequest({ message: "Manifest inv\xE1lido", errors: v.errors });
|
|
2618
2968
|
ctx.body = await integrateFrontend(strapi, { frontendDir, manifest: v.data });
|
|
2969
|
+
},
|
|
2970
|
+
/**
|
|
2971
|
+
* Religa o frontend à Strapi por FETCH AO VIVO: gera a camada de dados (REST,
|
|
2972
|
+
* flat) e religa os componentes (com .bak + fallback + sanidade). Só escreve no
|
|
2973
|
+
* frontend, nunca na Strapi. Usa o último provisionado por padrão.
|
|
2974
|
+
*/
|
|
2975
|
+
async wire(ctx) {
|
|
2976
|
+
const strapi = ctx.strapi ?? global.strapi;
|
|
2977
|
+
if (!devOnly(ctx)) return;
|
|
2978
|
+
const body = ctx.request.body || {};
|
|
2979
|
+
let frontendDir = body.frontendDir;
|
|
2980
|
+
if (!frontendDir) {
|
|
2981
|
+
const st = getProvisionStatus(strapi.dirs.app.root);
|
|
2982
|
+
frontendDir = st.done?.frontendDir || "";
|
|
2983
|
+
}
|
|
2984
|
+
if (!frontendDir) return ctx.badRequest("Nenhum frontend provisionado.");
|
|
2985
|
+
const parent = import_node_path8.default.resolve(strapi.dirs.app.root, "..");
|
|
2986
|
+
if (!ensureInside3(parent, frontendDir) || !import_node_fs8.default.existsSync(frontendDir)) {
|
|
2987
|
+
return ctx.badRequest("Pasta do frontend inv\xE1lida.");
|
|
2988
|
+
}
|
|
2989
|
+
let manifest;
|
|
2990
|
+
try {
|
|
2991
|
+
manifest = JSON.parse(import_node_fs8.default.readFileSync(import_node_path8.default.join(frontendDir, MANIFEST_NAME), "utf8"));
|
|
2992
|
+
} catch {
|
|
2993
|
+
return ctx.badRequest("Manifest do projeto n\xE3o encontrado (rode a provis\xE3o primeiro).");
|
|
2994
|
+
}
|
|
2995
|
+
const v = validateManifest(manifest);
|
|
2996
|
+
if (!v.ok) return ctx.badRequest({ message: "Manifest inv\xE1lido", errors: v.errors });
|
|
2997
|
+
ctx.body = await wireFrontend(strapi, { frontendDir, manifest: v.data });
|
|
2619
2998
|
}
|
|
2620
2999
|
};
|
|
2621
3000
|
|
|
@@ -2699,8 +3078,8 @@ var McpClient = class {
|
|
|
2699
3078
|
};
|
|
2700
3079
|
|
|
2701
3080
|
// server/src/provision/translate.ts
|
|
2702
|
-
var
|
|
2703
|
-
var
|
|
3081
|
+
var OPENAI_URL4 = "https://api.openai.com/v1/chat/completions";
|
|
3082
|
+
var MODEL4 = process.env.OPENAI_CHAT_MODEL || "gpt-4o";
|
|
2704
3083
|
var approxTokens = (s) => Math.ceil((s || "").length / 4);
|
|
2705
3084
|
var MAX_CHUNK_TOKENS = 1200;
|
|
2706
3085
|
var splitParagraphs = (text) => text.split(/\n{2,}/);
|
|
@@ -2745,7 +3124,7 @@ function splitForTranslation(value, _type) {
|
|
|
2745
3124
|
async function translateChunk(apiKey, text, sourceLang, targetLang) {
|
|
2746
3125
|
if (!text || !text.trim()) return text;
|
|
2747
3126
|
const body = {
|
|
2748
|
-
model:
|
|
3127
|
+
model: MODEL4,
|
|
2749
3128
|
temperature: 0,
|
|
2750
3129
|
max_tokens: Math.min(4e3, approxTokens(text) * 3 + 256),
|
|
2751
3130
|
messages: [
|
|
@@ -2756,7 +3135,7 @@ async function translateChunk(apiKey, text, sourceLang, targetLang) {
|
|
|
2756
3135
|
{ role: "user", content: text }
|
|
2757
3136
|
]
|
|
2758
3137
|
};
|
|
2759
|
-
const res = await fetch(
|
|
3138
|
+
const res = await fetch(OPENAI_URL4, {
|
|
2760
3139
|
method: "POST",
|
|
2761
3140
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
2762
3141
|
body: JSON.stringify(body)
|
|
@@ -2823,22 +3202,22 @@ function createContentTools(strapi) {
|
|
|
2823
3202
|
for (const [name, a] of Object.entries(attributes)) {
|
|
2824
3203
|
const v = node[name];
|
|
2825
3204
|
if (v == null) continue;
|
|
2826
|
-
const
|
|
3205
|
+
const path10 = [...basePath, name];
|
|
2827
3206
|
if (TEXTUAL.includes(a.type)) {
|
|
2828
3207
|
if (typeof v === "string" && v.toLowerCase().includes(needle)) {
|
|
2829
|
-
collect(
|
|
3208
|
+
collect(path10, name, v);
|
|
2830
3209
|
}
|
|
2831
3210
|
} else if (a.type === "component" && a.component) {
|
|
2832
3211
|
const sub = attrsOf(a.component);
|
|
2833
3212
|
if (a.repeatable && Array.isArray(v)) {
|
|
2834
|
-
v.forEach((item, i) => walkFind(item, sub, [...
|
|
3213
|
+
v.forEach((item, i) => walkFind(item, sub, [...path10, i], needle, collect));
|
|
2835
3214
|
} else {
|
|
2836
|
-
walkFind(v, sub,
|
|
3215
|
+
walkFind(v, sub, path10, needle, collect);
|
|
2837
3216
|
}
|
|
2838
3217
|
} else if (a.type === "dynamiczone" && Array.isArray(v)) {
|
|
2839
3218
|
v.forEach((item, i) => {
|
|
2840
3219
|
if (item?.__component) {
|
|
2841
|
-
walkFind(item, attrsOf(item.__component), [...
|
|
3220
|
+
walkFind(item, attrsOf(item.__component), [...path10, i], needle, collect);
|
|
2842
3221
|
}
|
|
2843
3222
|
});
|
|
2844
3223
|
}
|
|
@@ -2863,12 +3242,12 @@ function createContentTools(strapi) {
|
|
|
2863
3242
|
}
|
|
2864
3243
|
const dp = hasDraftAndPublish(ct.uid);
|
|
2865
3244
|
for (const e of entries) {
|
|
2866
|
-
walkFind(e, attributes, [], needle, (
|
|
3245
|
+
walkFind(e, attributes, [], needle, (path10, campo, valor) => {
|
|
2867
3246
|
matches.push({
|
|
2868
3247
|
uid: ct.uid,
|
|
2869
3248
|
tipo: ct.info?.displayName || ct.uid,
|
|
2870
3249
|
documentId: e.documentId,
|
|
2871
|
-
path:
|
|
3250
|
+
path: path10,
|
|
2872
3251
|
campo,
|
|
2873
3252
|
valor_atual: valor.length > 300 ? valor.slice(0, 300) + "\u2026" : valor,
|
|
2874
3253
|
// draftAndPublish=false → não há rascunho; a edição já é o conteúdo
|
|
@@ -2916,8 +3295,8 @@ function createContentTools(strapi) {
|
|
|
2916
3295
|
}
|
|
2917
3296
|
return value;
|
|
2918
3297
|
};
|
|
2919
|
-
const editarCampo = async ({ uid, documentId, path:
|
|
2920
|
-
const p = Array.isArray(
|
|
3298
|
+
const editarCampo = async ({ uid, documentId, path: path10, campo, novo_valor, locale }) => {
|
|
3299
|
+
const p = Array.isArray(path10) && path10.length ? path10 : campo ? [campo] : null;
|
|
2921
3300
|
if (!p) return { erro: 'informe "path" (array) ou "campo"' };
|
|
2922
3301
|
const attributes = strapi.contentTypes?.[uid]?.attributes || {};
|
|
2923
3302
|
const topAttr = p[0];
|
|
@@ -3219,29 +3598,29 @@ var openAiToolSpecs = [
|
|
|
3219
3598
|
];
|
|
3220
3599
|
|
|
3221
3600
|
// server/src/provision/enable-i18n.ts
|
|
3222
|
-
var
|
|
3223
|
-
var
|
|
3601
|
+
var import_node_fs9 = __toESM(require("node:fs"));
|
|
3602
|
+
var import_node_path9 = __toESM(require("node:path"));
|
|
3224
3603
|
var LOCALIZABLE = ["string", "text", "richtext", "component", "dynamiczone"];
|
|
3225
3604
|
var isDev2 = () => process.env.NODE_ENV === "development";
|
|
3226
3605
|
function schemaPathFor(apiRoot, uid) {
|
|
3227
3606
|
const m = /^api::([^.]+)\.([^.]+)$/.exec(uid);
|
|
3228
3607
|
if (!m) return null;
|
|
3229
3608
|
const [, api, ct] = m;
|
|
3230
|
-
return
|
|
3609
|
+
return import_node_path9.default.join(apiRoot, api, "content-types", ct, "schema.json");
|
|
3231
3610
|
}
|
|
3232
3611
|
function listAllUids(apiRoot) {
|
|
3233
3612
|
const out = [];
|
|
3234
3613
|
let apis = [];
|
|
3235
3614
|
try {
|
|
3236
|
-
apis =
|
|
3615
|
+
apis = import_node_fs9.default.readdirSync(apiRoot);
|
|
3237
3616
|
} catch {
|
|
3238
3617
|
return out;
|
|
3239
3618
|
}
|
|
3240
3619
|
for (const api of apis) {
|
|
3241
|
-
const ctDir =
|
|
3242
|
-
if (!
|
|
3243
|
-
for (const ct of
|
|
3244
|
-
if (
|
|
3620
|
+
const ctDir = import_node_path9.default.join(apiRoot, api, "content-types");
|
|
3621
|
+
if (!import_node_fs9.default.existsSync(ctDir)) continue;
|
|
3622
|
+
for (const ct of import_node_fs9.default.readdirSync(ctDir)) {
|
|
3623
|
+
if (import_node_fs9.default.existsSync(import_node_path9.default.join(ctDir, ct, "schema.json"))) out.push(`api::${api}.${ct}`);
|
|
3245
3624
|
}
|
|
3246
3625
|
}
|
|
3247
3626
|
return out;
|
|
@@ -3251,10 +3630,10 @@ var withLocalized = (obj) => ({
|
|
|
3251
3630
|
i18n: { ...(obj || {}).i18n || {}, localized: true }
|
|
3252
3631
|
});
|
|
3253
3632
|
function patchOne(file, campos) {
|
|
3254
|
-
if (!
|
|
3633
|
+
if (!import_node_fs9.default.existsSync(file)) return { erro: `schema.json n\xE3o encontrado (${file})` };
|
|
3255
3634
|
let schema;
|
|
3256
3635
|
try {
|
|
3257
|
-
schema = JSON.parse(
|
|
3636
|
+
schema = JSON.parse(import_node_fs9.default.readFileSync(file, "utf8"));
|
|
3258
3637
|
} catch (e) {
|
|
3259
3638
|
return { erro: `schema.json ileg\xEDvel: ${e?.message ?? e}` };
|
|
3260
3639
|
}
|
|
@@ -3268,7 +3647,7 @@ function patchOne(file, campos) {
|
|
|
3268
3647
|
changed.push(name);
|
|
3269
3648
|
}
|
|
3270
3649
|
try {
|
|
3271
|
-
|
|
3650
|
+
import_node_fs9.default.writeFileSync(file, JSON.stringify(schema, null, 2) + "\n", "utf8");
|
|
3272
3651
|
} catch (e) {
|
|
3273
3652
|
return { erro: `falha ao gravar schema.json: ${e?.message ?? e}` };
|
|
3274
3653
|
}
|
|
@@ -3279,8 +3658,8 @@ function enableI18n(opts) {
|
|
|
3279
3658
|
if (!allowOutsideDev && !isDev2()) {
|
|
3280
3659
|
return { erro: "habilitar i18n s\xF3 \xE9 permitido em desenvolvimento (NODE_ENV=development)." };
|
|
3281
3660
|
}
|
|
3282
|
-
const srcDir = strapi?.dirs?.app?.src ||
|
|
3283
|
-
const apiRoot =
|
|
3661
|
+
const srcDir = strapi?.dirs?.app?.src || import_node_path9.default.join(process.cwd(), "src");
|
|
3662
|
+
const apiRoot = import_node_path9.default.join(srcDir, "api");
|
|
3284
3663
|
if (!uid || uid === "*") {
|
|
3285
3664
|
const uids = listAllUids(apiRoot);
|
|
3286
3665
|
if (!uids.length) return { erro: `nenhuma content-type encontrada em ${apiRoot}` };
|
|
@@ -3303,9 +3682,9 @@ function enableI18n(opts) {
|
|
|
3303
3682
|
}
|
|
3304
3683
|
|
|
3305
3684
|
// server/src/services/chat.ts
|
|
3306
|
-
var
|
|
3685
|
+
var MODEL5 = process.env.OPENAI_CHAT_MODEL || "gpt-4o";
|
|
3307
3686
|
var MAX_TURNS = 10;
|
|
3308
|
-
var
|
|
3687
|
+
var OPENAI_URL5 = "https://api.openai.com/v1/chat/completions";
|
|
3309
3688
|
var SYSTEM = {
|
|
3310
3689
|
pt: `Voc\xEA \xE9 um assistente embutido no admin do Strapi 5 deste projeto. Voc\xEA N\xC3O \xE9 s\xF3 um guia: voc\xEA consegue EDITAR e PUBLICAR conte\xFAdo de verdade atrav\xE9s das ferramentas.
|
|
3311
3690
|
|
|
@@ -3469,12 +3848,12 @@ PUBLISH POLICY: DRAFT MODE (auto-publish OFF). Do NOT call publicar unless the u
|
|
|
3469
3848
|
convo.push({ role: m.role, content: m.content });
|
|
3470
3849
|
}
|
|
3471
3850
|
});
|
|
3472
|
-
const
|
|
3851
|
+
const callOpenAI2 = async (body) => {
|
|
3473
3852
|
const ctrl = new AbortController();
|
|
3474
3853
|
const timer = setTimeout(() => ctrl.abort(), 6e4);
|
|
3475
3854
|
let res;
|
|
3476
3855
|
try {
|
|
3477
|
-
res = await fetch(
|
|
3856
|
+
res = await fetch(OPENAI_URL5, {
|
|
3478
3857
|
method: "POST",
|
|
3479
3858
|
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
3480
3859
|
body: JSON.stringify(body),
|
|
@@ -3491,8 +3870,8 @@ PUBLISH POLICY: DRAFT MODE (auto-publish OFF). Do NOT call publicar unless the u
|
|
|
3491
3870
|
};
|
|
3492
3871
|
let didWrite = false;
|
|
3493
3872
|
for (let turn = 0; turn < MAX_TURNS; turn++) {
|
|
3494
|
-
const data = await
|
|
3495
|
-
model:
|
|
3873
|
+
const data = await callOpenAI2({
|
|
3874
|
+
model: MODEL5,
|
|
3496
3875
|
max_tokens: 2048,
|
|
3497
3876
|
messages: convo,
|
|
3498
3877
|
...tools2.length > 0 ? { tools: tools2, tool_choice: "auto" } : {}
|
|
@@ -3536,7 +3915,7 @@ PUBLISH POLICY: DRAFT MODE (auto-publish OFF). Do NOT call publicar unless the u
|
|
|
3536
3915
|
const text = (typeof msg.content === "string" ? msg.content : "").trim();
|
|
3537
3916
|
return {
|
|
3538
3917
|
reply: text || "(sem resposta)",
|
|
3539
|
-
model:
|
|
3918
|
+
model: MODEL5,
|
|
3540
3919
|
lang: language,
|
|
3541
3920
|
didWrite,
|
|
3542
3921
|
toolsAvailable: tools2.length
|
|
@@ -3544,7 +3923,7 @@ PUBLISH POLICY: DRAFT MODE (auto-publish OFF). Do NOT call publicar unless the u
|
|
|
3544
3923
|
}
|
|
3545
3924
|
return {
|
|
3546
3925
|
reply: language === "en" ? "(agent turn limit reached)" : "(limite de turnos do agente atingido)",
|
|
3547
|
-
model:
|
|
3926
|
+
model: MODEL5,
|
|
3548
3927
|
lang: language,
|
|
3549
3928
|
didWrite,
|
|
3550
3929
|
toolsAvailable: tools2.length
|
|
@@ -3645,6 +4024,12 @@ var routes_default = {
|
|
|
3645
4024
|
path: "/frontend/integrate",
|
|
3646
4025
|
handler: "frontend.integrate",
|
|
3647
4026
|
config: { policies: [] }
|
|
4027
|
+
},
|
|
4028
|
+
{
|
|
4029
|
+
method: "POST",
|
|
4030
|
+
path: "/frontend/wire",
|
|
4031
|
+
handler: "frontend.wire",
|
|
4032
|
+
config: { policies: [] }
|
|
3648
4033
|
}
|
|
3649
4034
|
]
|
|
3650
4035
|
}
|