@slicemachine/manager 0.24.4-alpha.xru-slice-generation-ai-poc.1 → 0.24.4-alpha.xru-slice-generation-ai-poc.2
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/_node_modules/@amplitude/experiment-node-server/dist/src/local/client.cjs +1 -1
- package/dist/_node_modules/@amplitude/experiment-node-server/dist/src/local/client.js +1 -1
- package/dist/_node_modules/cross-spawn/index.cjs +1 -1
- package/dist/_node_modules/cross-spawn/index.js +1 -1
- package/dist/_node_modules/execa/lib/pipe.cjs +2 -2
- package/dist/_node_modules/execa/lib/pipe.cjs.map +1 -1
- package/dist/_node_modules/execa/lib/stream.cjs +3 -3
- package/dist/_node_modules/execa/lib/stream.cjs.map +1 -1
- package/dist/_virtual/index2.cjs +4 -3
- package/dist/_virtual/index2.cjs.map +1 -1
- package/dist/_virtual/index2.js +4 -2
- package/dist/_virtual/index2.js.map +1 -1
- package/dist/_virtual/index3.cjs +3 -4
- package/dist/_virtual/index3.cjs.map +1 -1
- package/dist/_virtual/index3.js +2 -4
- package/dist/_virtual/index3.js.map +1 -1
- package/dist/managers/project/ProjectManager.cjs +8 -8
- package/dist/managers/project/ProjectManager.cjs.map +1 -1
- package/dist/managers/slices/SlicesManager.cjs +370 -353
- package/dist/managers/slices/SlicesManager.cjs.map +1 -1
- package/dist/managers/slices/SlicesManager.d.ts +1 -1
- package/dist/managers/slices/SlicesManager.js +370 -353
- package/dist/managers/slices/SlicesManager.js.map +1 -1
- package/package.json +2 -2
- package/src/managers/slices/SlicesManager.ts +460 -438
@@ -1,13 +1,11 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
3
|
-
const fs = require("node:fs");
|
4
3
|
const t = require("io-ts");
|
5
4
|
const OpenAI = require("openai");
|
6
5
|
const prismicCustomTypesClient = require("@prismicio/custom-types-client");
|
7
6
|
const content = require("@prismicio/types-internal/lib/content");
|
8
7
|
const diff = require("@prismicio/types-internal/lib/customtypes/diff");
|
9
8
|
const customtypes = require("@prismicio/types-internal/lib/customtypes");
|
10
|
-
const path = require("node:path");
|
11
9
|
const assertPluginsInitialized = require("../../lib/assertPluginsInitialized.cjs");
|
12
10
|
const bufferCodec = require("../../lib/bufferCodec.cjs");
|
13
11
|
const decodeHookResult = require("../../lib/decodeHookResult.cjs");
|
@@ -1257,38 +1255,117 @@ type GroupField = {
|
|
1257
1255
|
}
|
1258
1256
|
async generateSlicesFromUrl(args) {
|
1259
1257
|
assertPluginsInitialized.assertPluginsInitialized(this.sliceMachinePluginRunner);
|
1260
|
-
const { OPENAI_API_KEY } = process.env;
|
1258
|
+
const { OPENAI_API_KEY, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY } = process.env;
|
1259
|
+
if (!AWS_ACCESS_KEY_ID || !AWS_SECRET_ACCESS_KEY) {
|
1260
|
+
throw new Error("AWS credentials are not set.");
|
1261
|
+
}
|
1262
|
+
const AWS_REGION = "us-east-1";
|
1263
|
+
const bedrockClient = new clientBedrockRuntime.BedrockRuntimeClient({
|
1264
|
+
region: AWS_REGION,
|
1265
|
+
credentials: {
|
1266
|
+
accessKeyId: AWS_ACCESS_KEY_ID,
|
1267
|
+
secretAccessKey: AWS_SECRET_ACCESS_KEY
|
1268
|
+
}
|
1269
|
+
});
|
1261
1270
|
if (!OPENAI_API_KEY) {
|
1262
1271
|
throw new Error("OPENAI_API_KEY is not set.");
|
1263
1272
|
}
|
1264
|
-
const openai = new OpenAI({ apiKey:
|
1273
|
+
const openai = new OpenAI({ apiKey: OPENAI_API_KEY });
|
1265
1274
|
const sliceMachineConfig = await this.project.getSliceMachineConfig();
|
1266
1275
|
const libraryIDs = sliceMachineConfig.libraries || [];
|
1267
1276
|
const DEFAULT_LIBRARY_ID = libraryIDs[0];
|
1268
|
-
|
1269
|
-
|
1270
|
-
|
1271
|
-
|
1272
|
-
|
1273
|
-
|
1274
|
-
|
1275
|
-
|
1276
|
-
|
1277
|
-
|
1278
|
-
|
1279
|
-
|
1280
|
-
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1284
|
-
|
1285
|
-
|
1286
|
-
|
1287
|
-
|
1288
|
-
|
1289
|
-
|
1290
|
-
|
1291
|
-
|
1277
|
+
let retry = args.sliceImages.map((_) => ({
|
1278
|
+
MODEL: 0,
|
1279
|
+
MOCKS: 0,
|
1280
|
+
CODE: 0,
|
1281
|
+
APPEARANCE: 0
|
1282
|
+
}));
|
1283
|
+
async function callAI({ ai, sliceIndex, stepName, systemPrompt, imageFile, textContent }) {
|
1284
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
1285
|
+
let resultText;
|
1286
|
+
if (ai === "OPENAI") {
|
1287
|
+
const messages = [
|
1288
|
+
{ role: "system", content: systemPrompt }
|
1289
|
+
];
|
1290
|
+
const userContent = [];
|
1291
|
+
if (imageFile) {
|
1292
|
+
userContent.push({
|
1293
|
+
type: "image_url",
|
1294
|
+
image_url: {
|
1295
|
+
url: "data:image/png;base64," + Buffer.from(imageFile).toString("base64")
|
1296
|
+
}
|
1297
|
+
});
|
1298
|
+
}
|
1299
|
+
if (textContent) {
|
1300
|
+
userContent.push({ type: "text", text: textContent });
|
1301
|
+
}
|
1302
|
+
if (userContent.length > 0) {
|
1303
|
+
messages.push({
|
1304
|
+
role: "user",
|
1305
|
+
content: userContent
|
1306
|
+
});
|
1307
|
+
}
|
1308
|
+
const response = await openai.chat.completions.create({
|
1309
|
+
model: "gpt-4o",
|
1310
|
+
messages,
|
1311
|
+
response_format: { type: "json_object" }
|
1312
|
+
});
|
1313
|
+
console.log(`Generated response for ${stepName} - ${sliceIndex}:`, JSON.stringify(response));
|
1314
|
+
resultText = (_c = (_b = (_a = response.choices[0]) == null ? void 0 : _a.message) == null ? void 0 : _b.content) == null ? void 0 : _c.trim();
|
1315
|
+
} else if (ai === "AWS") {
|
1316
|
+
const messages = [];
|
1317
|
+
if (imageFile) {
|
1318
|
+
messages.push({
|
1319
|
+
role: "user",
|
1320
|
+
content: [
|
1321
|
+
{
|
1322
|
+
image: { format: "png", source: { bytes: imageFile } }
|
1323
|
+
}
|
1324
|
+
]
|
1325
|
+
});
|
1326
|
+
}
|
1327
|
+
if (textContent) {
|
1328
|
+
messages.push({
|
1329
|
+
role: "user",
|
1330
|
+
content: [
|
1331
|
+
{
|
1332
|
+
text: textContent
|
1333
|
+
}
|
1334
|
+
]
|
1335
|
+
});
|
1336
|
+
}
|
1337
|
+
const command = new clientBedrockRuntime.ConverseCommand({
|
1338
|
+
modelId: "us.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
1339
|
+
system: [{ text: systemPrompt }],
|
1340
|
+
messages
|
1341
|
+
});
|
1342
|
+
const response = await bedrockClient.send(command);
|
1343
|
+
console.log(`Generated response for ${stepName} - ${sliceIndex}:`, JSON.stringify(response));
|
1344
|
+
resultText = (_h = (_g = (_f = (_e = (_d = response.output) == null ? void 0 : _d.message) == null ? void 0 : _e.content) == null ? void 0 : _f[0]) == null ? void 0 : _g.text) == null ? void 0 : _h.trim();
|
1345
|
+
}
|
1346
|
+
async function retryCall(error) {
|
1347
|
+
if (retry[sliceIndex][stepName] < 3) {
|
1348
|
+
retry[sliceIndex][stepName]++;
|
1349
|
+
console.log(`Retrying ${retry[sliceIndex][stepName]} ${stepName} for slice ${sliceIndex}.`, error);
|
1350
|
+
return await callAI({
|
1351
|
+
ai,
|
1352
|
+
sliceIndex,
|
1353
|
+
stepName,
|
1354
|
+
systemPrompt,
|
1355
|
+
imageFile,
|
1356
|
+
textContent
|
1357
|
+
});
|
1358
|
+
}
|
1359
|
+
throw new Error(error);
|
1360
|
+
}
|
1361
|
+
if (!resultText) {
|
1362
|
+
return await retryCall(`No valid response was generated for ${stepName}.`);
|
1363
|
+
}
|
1364
|
+
try {
|
1365
|
+
return JSON.parse(resultText);
|
1366
|
+
} catch (error) {
|
1367
|
+
return await retryCall(`Failed to parse AI response for ${stepName}: ` + error);
|
1368
|
+
}
|
1292
1369
|
}
|
1293
1370
|
const SHARED_SLICE_SCHEMA = `
|
1294
1371
|
/**
|
@@ -1606,355 +1683,289 @@ type GroupField = {
|
|
1606
1683
|
}
|
1607
1684
|
]
|
1608
1685
|
};
|
1609
|
-
async function generateSliceModel(
|
1610
|
-
var _a, _b, _c;
|
1686
|
+
async function generateSliceModel(sliceIndex, imageFile) {
|
1611
1687
|
const systemPrompt = `
|
1612
|
-
You are an expert in Prismic content modeling
|
1613
|
-
- Use the TypeScript schema provided as your reference.
|
1614
|
-
- Place all main content fields under the "primary" object.
|
1615
|
-
- Do not create any collections or groups for single-image content (background images should be a single image field).
|
1616
|
-
- Ensure that each field has appropriate placeholders, labels, and configurations.
|
1617
|
-
- Never generate a Link / Button text field, only the Link / Button field itself is enough. Just enable "allowText" when doing that.
|
1618
|
-
- Do not forget any field visible from the image provide in the user prompt.
|
1619
|
-
- Ensure to differentiate Prismic fields from just an image with visual inside the image. When that's the case, just add a Prismic image field.
|
1620
|
-
- Use the code to know exactly what is a real field and not an image. If in the code it's an image, then the field should also be an image, do a 1-1 mapping thanks to the code.
|
1621
|
-
- Do not include any decorative fields. When an element is purely visual, decorative, don't include it att all in the slice model.
|
1622
|
-
- Do not include any extra commentary or formatting.
|
1623
|
-
- When you see a repetition of an image, a text, a link, etc, NEVER create one field per repeated item, you HAVE to use a group for that.
|
1624
|
-
- When you see multiple fields repeated, you MUST use a group for that.
|
1625
|
-
- NEVER put a group inside another group field, this is not allowed. In the final JSON a group CANNOT be within another group field. YOU CANNOT NEST GROUP FIELDS! Not for any reason you are allowed to do that! Even for navigation, in header or footer you cannot nest group fields.
|
1626
|
-
- The "items" field must not be used under any circumstances. All repeatable fields should be defined using a Group field inside the primary object. If a field represents a collection of items, it must be part of a Group field, and items must never appear in the JSON output.
|
1627
|
-
- Don't forget to replace the temporary text in the "Existing Slice to update", like <ID_TO_CHANGE>, <NAME_TO_CHANGE>, <DESCRIPTION_TO_CHANGE>, <VARIATION_ID_TO_CHANGE>, etc.
|
1628
|
-
- Field placeholder should be super short, do not put the content from the image inside the placeholder.
|
1629
|
-
- Field label and id should define the field's purpose, not its content.
|
1630
|
-
- Slice name and id should define the slice's purpose, not its content.
|
1631
|
-
- Slice description should be a brief explanation of the slice's purpose not its content.
|
1632
|
-
|
1633
|
-
!IMPORTANT!:
|
1634
|
-
- Only return a valid JSON object representing the full slice model, nothing else before. JSON.parse on your response should not throw an error.
|
1635
|
-
- All your response should fit in a single return response.
|
1636
|
-
- Never stop the response until you totally finish the full JSON response you wanted.
|
1688
|
+
You are an **expert in Prismic content modeling**. Using the **image and code provided**, generate a **valid Prismic JSON model** for the slice described below.
|
1637
1689
|
|
1638
|
-
|
1690
|
+
**STRICT MODELING RULES (NO EXCEPTIONS):**
|
1691
|
+
- **Use the TypeScript schema provided as your reference**.
|
1692
|
+
- **Absolutely all fields must be placed under the "primary" object**.
|
1693
|
+
- **Do not create groups or collections for single-image content** (background images must be a single image field).
|
1694
|
+
- **Ensure each field has appropriate placeholders, labels, and configurations**.
|
1695
|
+
- **Never generate a Link/Button text field—only the Link/Button field itself** with \`"allowText": true\`.
|
1696
|
+
- **Include all fields visible in the provided image**, do not forget any field and everything should be covered.
|
1697
|
+
- **Repeated fields must always be grouped**:
|
1698
|
+
- **Identify when field are part of a group**, when there is a repetition of a field or multiple fields together use a Group field.
|
1699
|
+
- **DO NOT** create individually numbered fields like \`feature1\`, \`feature2\`, \`feature3\`. Instead, define a single **Group field** (e.g., \`features\`) and move all repeated items inside it.
|
1700
|
+
- **Differentiate Prismic fields from decorative elements:**
|
1701
|
+
- If an element in the image is purely visual/decorative, **do not include it in the model**.
|
1702
|
+
- Use the **code as the source of truth** to determine what should be a field.
|
1703
|
+
- If an element is an image in the code, it **must also be an image field in Prismic** (strict 1:1 mapping).
|
1704
|
+
- **Handle repeated content correctly:**
|
1705
|
+
- **If an image, text, or link is repeated, always use a Group field**—do not create individual fields.
|
1706
|
+
- **If multiple fields are repeated together, they must be inside a single Group field**.
|
1707
|
+
- **Strictly forbid nesting of groups:**
|
1708
|
+
- **NEVER put a Group inside another Group field**.
|
1709
|
+
- **Group fields CANNOT be nested for any reason**—this is **strictly prohibited** even for navigation structures like headers or footers.
|
1710
|
+
- **Do not use the "items" field**:
|
1711
|
+
- **All repeatable fields must be defined as Group fields under "primary"**.
|
1712
|
+
- **"items" must never appear in the final JSON output**.
|
1713
|
+
- **Do not create more than one SliceVariation**, only one variation is enough to create the model.
|
1714
|
+
|
1715
|
+
**STRICT FIELD NAMING & CONTENT RULES:**
|
1716
|
+
- **Replace placeholders in the existing slice template** (\`<ID_TO_CHANGE>\`, \`<NAME_TO_CHANGE>\`, etc.).
|
1717
|
+
- **Field placeholders must be very short**—do **not** put actual image content inside placeholders.
|
1718
|
+
- **Field labels and IDs must define the field's purpose, not its content**.
|
1719
|
+
- **Slice name, ID, and description must describe the slice's function, not its content**.
|
1720
|
+
- The slice name and ID must be **generic and reusable**, defining what the slice **does**, not what it is used for.
|
1721
|
+
- **DO NOT name the slice after a specific topic, content type, or industry. Instead, name it based on its structure and function.
|
1722
|
+
|
1723
|
+
**STRICT JSON OUTPUT FORMAT (NO MARKDOWN OR EXTRA TEXT):**
|
1724
|
+
- **Return ONLY a valid JSON object**—no extra text, comments, or formatting.
|
1725
|
+
- **The response must be directly parseable** using \`JSON.parse(output)\`.
|
1726
|
+
- **Do not wrap the output in markdown (\`\`\`\`json\`) or any other formatting.**
|
1727
|
+
|
1728
|
+
**VALIDATION REQUIREMENT:**
|
1729
|
+
- Before returning, **validate that \`JSON.parse(output)\` runs without errors**.
|
1730
|
+
- If there is **any extra text, markdown, or incorrect structure**, **rewrite the response before returning**.
|
1731
|
+
|
1732
|
+
**REFERENCE SCHEMA (Follow this exactly):**
|
1639
1733
|
${SHARED_SLICE_SCHEMA}
|
1640
1734
|
|
1641
|
-
|
1735
|
+
**EXISTING SLICE TO UPDATE (Modify strictly according to the rules above):**
|
1642
1736
|
${JSON.stringify(DEFAULT_SLICE_MODEL)}
|
1643
1737
|
`.trim();
|
1644
|
-
const
|
1645
|
-
|
1646
|
-
|
1647
|
-
|
1648
|
-
|
1649
|
-
|
1650
|
-
type: "image_url",
|
1651
|
-
image_url: {
|
1652
|
-
url: "data:image/png;base64," + Buffer.from(imageFile).toString("base64")
|
1653
|
-
}
|
1654
|
-
},
|
1655
|
-
{ type: "text", text: codeFile }
|
1656
|
-
]
|
1657
|
-
}
|
1658
|
-
];
|
1659
|
-
const response = await openai.chat.completions.create({
|
1660
|
-
model: "gpt-4o",
|
1661
|
-
messages,
|
1662
|
-
response_format: {
|
1663
|
-
type: "json_object"
|
1664
|
-
}
|
1738
|
+
const generatedModel = await callAI({
|
1739
|
+
ai: "OPENAI",
|
1740
|
+
sliceIndex,
|
1741
|
+
stepName: "MODEL",
|
1742
|
+
systemPrompt,
|
1743
|
+
imageFile
|
1665
1744
|
});
|
1666
|
-
|
1667
|
-
const resultText = (_c = (_b = (_a = response.choices[0]) == null ? void 0 : _a.message) == null ? void 0 : _b.content) == null ? void 0 : _c.trim();
|
1668
|
-
if (!resultText) {
|
1669
|
-
throw new Error("No valid slice model was generated.");
|
1670
|
-
}
|
1671
|
-
try {
|
1672
|
-
const generatedModel = JSON.parse(resultText);
|
1673
|
-
return generatedModel;
|
1674
|
-
} catch (error) {
|
1675
|
-
throw new Error("Failed to parse AI response for model: " + error);
|
1676
|
-
}
|
1745
|
+
return generatedModel;
|
1677
1746
|
}
|
1678
|
-
async function generateSliceMocks(imageFile, existingMocks) {
|
1679
|
-
var _a, _b, _c;
|
1747
|
+
async function generateSliceMocks(sliceIndex, imageFile, existingMocks) {
|
1680
1748
|
const systemPrompt = `
|
1681
|
-
You are a seasoned frontend engineer with deep expertise in Prismic slices
|
1682
|
-
Your task is to update the provided mocks template based
|
1683
|
-
Follow these guidelines strictly:
|
1684
|
-
- Do not modify the overall structure of the mocks template.
|
1685
|
-
- Strictly only update text content.
|
1686
|
-
- Do not touch images.
|
1687
|
-
- If you see a repetition with a group, you must create the same number of group items that are visible on the image.
|
1688
|
-
- Absolutely do not touch what is not necessary to be changed, like link "key" property, or the StructureText "direction", spans", etc or the structure, this is really important that you just do a replace of the text.
|
1689
|
-
- For structure text content you must alway keep the same structure and properties, even if empty, ONLY replace text content.
|
1690
|
-
- Only and strictly update the text content of the fields, nothing else. You should only and strictly update the text that is visible in the image.
|
1691
|
-
- Never touch the image fields, nothing should be changed for image fields.
|
1749
|
+
You are a **seasoned frontend engineer** with **deep expertise in Prismic slices**.
|
1750
|
+
Your task is to **update the provided mocks template** based **only** on the visible text content in the provided image.
|
1692
1751
|
|
1693
|
-
|
1694
|
-
-
|
1695
|
-
-
|
1696
|
-
-
|
1752
|
+
**STRICT UPDATE GUIDELINES:**
|
1753
|
+
- **Do no create content, only take visible text from the image.**
|
1754
|
+
- **Do not modify the overall structure of the mocks template.**
|
1755
|
+
- **Strictly update text content only.**
|
1756
|
+
- **Do not touch images or image-related fields.**
|
1757
|
+
- **If a repeated item appears in a group, match the exact number of group items seen in the image.**
|
1758
|
+
- **Do not modify metadata, field properties, or structure.** This includes:
|
1759
|
+
- Do **not** change the \`"key"\` property of links.
|
1760
|
+
- Do **not** modify \`StructuredText\` properties such as \`"direction"\`, \`"spans"\`, or \`"type"\`.
|
1761
|
+
- Do **not** alter field nesting or object structure.
|
1762
|
+
- **For StructuredText fields, maintain all existing structure and properties**—**only replace text content**.
|
1763
|
+
- **Ensure that only visible text in the image is updated**—do not generate or assume content.
|
1764
|
+
- **Never modify image fields**—image references and properties must remain unchanged.
|
1697
1765
|
|
1698
|
-
|
1766
|
+
**STRICT JSON OUTPUT FORMAT:**
|
1767
|
+
- **Return ONLY a valid JSON object**—no extra text, explanations, or formatting.
|
1768
|
+
- **The response must be directly parseable** using \`JSON.parse(output)\`.
|
1769
|
+
- **Do not wrap the output in markdown (\`\`\`\`json\`) or any other formatting.**
|
1770
|
+
|
1771
|
+
**VALIDATION REQUIREMENT:**
|
1772
|
+
- Before returning, **validate that \`JSON.parse(output)\` runs without errors**.
|
1773
|
+
- If there is **any extra text, markdown, or incorrect structure**, **rewrite the response before returning**.
|
1774
|
+
|
1775
|
+
**EXISTING MOCKS TEMPLATE (To be updated with the visible text from the image only):**
|
1699
1776
|
${JSON.stringify(existingMocks)}
|
1700
1777
|
`.trim();
|
1701
|
-
const
|
1702
|
-
|
1703
|
-
|
1704
|
-
|
1705
|
-
|
1706
|
-
|
1707
|
-
type: "image_url",
|
1708
|
-
image_url: {
|
1709
|
-
url: "data:image/png;base64," + Buffer.from(imageFile).toString("base64")
|
1710
|
-
}
|
1711
|
-
}
|
1712
|
-
]
|
1713
|
-
}
|
1714
|
-
];
|
1715
|
-
const response = await openai.chat.completions.create({
|
1716
|
-
model: "gpt-4o",
|
1717
|
-
messages,
|
1718
|
-
response_format: {
|
1719
|
-
type: "json_object"
|
1720
|
-
}
|
1778
|
+
const updatedMock = await callAI({
|
1779
|
+
ai: "OPENAI",
|
1780
|
+
sliceIndex,
|
1781
|
+
stepName: "MOCKS",
|
1782
|
+
systemPrompt,
|
1783
|
+
imageFile
|
1721
1784
|
});
|
1722
|
-
|
1723
|
-
const resultText = (_c = (_b = (_a = response.choices[0]) == null ? void 0 : _a.message) == null ? void 0 : _b.content) == null ? void 0 : _c.trim();
|
1724
|
-
if (!resultText) {
|
1725
|
-
throw new Error("No valid mocks were generated.");
|
1726
|
-
}
|
1727
|
-
try {
|
1728
|
-
const updatedMock = JSON.parse(resultText);
|
1729
|
-
return [updatedMock];
|
1730
|
-
} catch (error) {
|
1731
|
-
throw new Error("Failed to parse AI response for mocks: " + error);
|
1732
|
-
}
|
1785
|
+
return [updatedMock];
|
1733
1786
|
}
|
1734
|
-
|
1735
|
-
|
1736
|
-
|
1737
|
-
|
1738
|
-
|
1739
|
-
|
1740
|
-
|
1741
|
-
Follow these guidelines strictly:
|
1742
|
-
- Be self-contained.
|
1743
|
-
- For links, you must use PrismicNextLink and you must just pass the field, PrismicNextLink will handle the display of the link text, don't do it manually.
|
1744
|
-
- PrismicNextLink should never be open, just passing the field is enough like in the code example below. You can use className or inline style directly on the PrismicNextLink component.
|
1745
|
-
- Ensure to strictly respect what is defined on the model for each fields ID, do not invent or use something not in the model.
|
1746
|
-
- Ensure to use all fields provided in the model.
|
1747
|
-
- Follow the structure provided in the code example below.
|
1748
|
-
- Use the provided code to help yourself to create the structure.
|
1749
|
-
- As you can see in the example of the code you MUST never access the data with "<field>.value".
|
1750
|
-
- You need to really inspire yourself from the code example bellow in order to understand how to access field, write field etc. Do not try to invent something that you didn't see.
|
1751
|
-
- You cannot add a style prop to "PrismicRichText" component, it's not allowed.
|
1752
|
-
- It's important to respect the same imports as done in the code example bellow, import exactly from the same package.
|
1753
|
-
- Never do wrong W3C HTML structure, always respect a correct HTML structure, for example you cannot put a PrismicRichText component inside a <h1>, or a <p>, etc.
|
1754
|
-
- Ensure to map the field type to the correct Prismic component, for example, a StructuredText field should be mapped to PrismicRichText, an image field should be mapped to PrismicNextImage, a Text field should just be map to a classic <p> component
|
1755
|
-
|
1756
|
-
!IMPORTANT!:
|
1757
|
-
- Return a valid JSON object containing only one key: "componentCode". No additional keys, text, or formatting are allowed before, after, or within the JSON object.
|
1758
|
-
- Return a valid JSON, meaning you should NEVER start with a sentence, directly the JSON so that I can JSON.parse your response.
|
1759
|
-
- All strings must be enclosed in double quotes ("). Do not use single quotes or template literals.
|
1760
|
-
- Within the string value for "componentCode", every embedded double quote must be escaped as ". Similarly, every backslash must be escaped as \\.
|
1761
|
-
- Ensure that the string value does not contain any raw control characters (such as literal newline, tab, or carriage return characters). Instead, use their escape sequences.
|
1762
|
-
- Before finalizing the output, validate that JSON.parse(output) works without throwing an error. No unescaped characters should cause the parser to crash.
|
1763
|
-
- The output must not include any markdown formatting, code block fences, or extra text. It should be a single, clean JSON object.
|
1764
|
-
|
1765
|
-
## Example of a Fully Isolated Slice Component:
|
1766
|
-
-----------------------------------------------------------
|
1767
|
-
import { FC } from "react";
|
1768
|
-
import { Content } from "@prismicio/client";
|
1769
|
-
import { SliceComponentProps, PrismicRichText } from "@prismicio/react";
|
1770
|
-
import { PrismicNextImage, PrismicNextLink } from "@prismicio/next";
|
1787
|
+
const SLICE_CODE_EXAMPLE = `
|
1788
|
+
-----------------------------------------------------------
|
1789
|
+
import { FC } from "react";
|
1790
|
+
import { Content } from "@prismicio/client";
|
1791
|
+
import { SliceComponentProps, PrismicRichText } from "@prismicio/react";
|
1792
|
+
import { PrismicNextImage, PrismicNextLink } from "@prismicio/next";
|
1771
1793
|
|
1772
|
-
|
1773
|
-
|
1794
|
+
export type PascalNameToReplaceProps =
|
1795
|
+
SliceComponentProps<Content.PascalNameToReplaceSlice>;
|
1774
1796
|
|
1775
|
-
|
1776
|
-
|
1777
|
-
|
1778
|
-
|
1779
|
-
|
1780
|
-
|
1781
|
-
|
1782
|
-
|
1783
|
-
|
1784
|
-
|
1785
|
-
|
1786
|
-
|
1787
|
-
|
1788
|
-
|
1789
|
-
|
1790
|
-
|
1791
|
-
|
1792
|
-
|
1793
|
-
|
1794
|
-
|
1795
|
-
|
1796
|
-
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1800
|
-
|
1801
|
-
|
1802
|
-
|
1797
|
+
const PascalNameToReplace: FC<PascalNameToReplaceProps> = ({ slice }) => {
|
1798
|
+
return (
|
1799
|
+
<section
|
1800
|
+
data-slice-type={slice.slice_type}
|
1801
|
+
data-slice-variation={slice.variation}
|
1802
|
+
className="es-bounded es-alternate-grid"
|
1803
|
+
>
|
1804
|
+
<PrismicNextLink
|
1805
|
+
className="es-alternate-grid__button"
|
1806
|
+
field={slice.primary.buttonLink}
|
1807
|
+
/>
|
1808
|
+
<div className="es-alternate-grid__content">
|
1809
|
+
<PrismicNextImage
|
1810
|
+
field={slice.primary.image}
|
1811
|
+
className="es-alternate-grid__image"
|
1812
|
+
/>
|
1813
|
+
<div className="es-alternate-grid__primary-content">
|
1814
|
+
<div className="es-alternate-grid__primary-content__intro">
|
1815
|
+
<p className="es-alternate-grid__primary-content__intro__eyebrow">
|
1816
|
+
{slice.primary.eyebrowHeadline}
|
1817
|
+
</p>
|
1818
|
+
<div className="es-alternate-grid__primary-content__intro__headline">
|
1819
|
+
<PrismicRichText field={slice.primary.title} />
|
1820
|
+
</div>
|
1821
|
+
<div className="es-alternate-grid__primary-content__intro__description">
|
1822
|
+
<PrismicRichText field={slice.primary.description} />
|
1823
|
+
</div>
|
1824
|
+
</div>
|
1803
1825
|
|
1804
|
-
|
1805
|
-
|
1806
|
-
|
1807
|
-
|
1808
|
-
|
1809
|
-
|
1810
|
-
|
1811
|
-
|
1812
|
-
</div>
|
1813
|
-
</div>
|
1814
|
-
))}
|
1815
|
-
</div>
|
1826
|
+
<div className="es-alternate-grid__primary-content__stats">
|
1827
|
+
{slice.primary.stats.map((stat, i) => (
|
1828
|
+
<div key={\`stat-\${i + 1}\`} className="es-alternate-grid__stat">
|
1829
|
+
<div className="es-alternate-grid__stat__heading">
|
1830
|
+
<PrismicRichText field={stat.title} />
|
1831
|
+
</div>
|
1832
|
+
<div className="es-alternate-grid__stat__description">
|
1833
|
+
<PrismicRichText field={stat.description} />
|
1816
1834
|
</div>
|
1817
1835
|
</div>
|
1818
|
-
|
1819
|
-
|
1820
|
-
|
1836
|
+
))}
|
1837
|
+
</div>
|
1838
|
+
</div>
|
1839
|
+
</div>
|
1840
|
+
</section>
|
1841
|
+
);
|
1842
|
+
};
|
1821
1843
|
|
1822
|
-
|
1823
|
-
|
1844
|
+
export default PascalNameToReplace;
|
1845
|
+
-----------------------------------------------------------
|
1846
|
+
`.trim();
|
1847
|
+
async function generateSliceComponentCode(sliceIndex, imageFile, updatedSlice) {
|
1848
|
+
const systemPrompt = `
|
1849
|
+
You are a **seasoned frontend engineer** with **deep expertise in Prismic slices**.
|
1850
|
+
Your task is to generate a **fully isolated React component** for a Prismic slice, **focusing ONLY on structure (HTML) without styling**.
|
1851
|
+
|
1852
|
+
**STRICT STRUCTURAL GUIDELINES:**
|
1853
|
+
- **Do not include styling.** Focus **100% on correct structure**.
|
1854
|
+
- **Be self-contained.** The component must work in isolation.
|
1855
|
+
- **Follow the structure provided in the example.** Do not introduce **any variations**.
|
1856
|
+
- **Use all fields provided in the model**—do not omit or invent fields.
|
1857
|
+
- **Never access a field using** \`<field>.value\`. Always follow the example pattern with just \`<field>\`.
|
1858
|
+
- **Ensure correct mapping of field types:**
|
1859
|
+
- **StructuredText** → \`PrismicRichText\`
|
1860
|
+
- **Image field** → \`PrismicNextImage\`
|
1861
|
+
- **Text field** → Standard \`<p>\` element
|
1862
|
+
- **Link field** → \`PrismicNextLink\`
|
1863
|
+
- **Group field** → Map to the correct structure based on the example.
|
1864
|
+
- **Maintain W3C-compliant HTML.** Do not place \`PrismicRichText\` inside \`<h1>\`, \`<p>\`, or other invalid elements.
|
1865
|
+
|
1866
|
+
**PRISMIC COMPONENT USAGE RULES:**
|
1867
|
+
- **Links must use \`PrismicNextLink\`**, passing only the \`field\` (no manual text extraction).
|
1868
|
+
- **\`PrismicNextLink\` must never be opened manually**—pass the field directly as in the example.
|
1869
|
+
- **\`PrismicRichText\` cannot have a \`style\` prop**.
|
1870
|
+
- **Imports must be identical to the provided example**.
|
1871
|
+
|
1872
|
+
**STRICT JSON OUTPUT FORMAT**
|
1873
|
+
- **Return ONLY a valid JSON object** with **one key**: \`"componentCode"\`.
|
1874
|
+
- **No markdown (\`\`\`\`json\`), no comments, no text before or after—ONLY pure JSON**.
|
1875
|
+
- **The response MUST start with \`{\` and end with \`}\` exactly, do not start with a sentence explaining what you will do.**
|
1876
|
+
- **Ensure the output is directly parseable** with \`JSON.parse(output)\`.
|
1877
|
+
- **All strings must use double quotes (\`"\`).** Do not use single quotes or template literals.
|
1878
|
+
- **Escape all embedded double quotes (\`"\`) and backslashes (\`\\\`).**
|
1879
|
+
- **The output must not contain raw control characters** (newline, tab, etc.); use escape sequences instead.
|
1880
|
+
|
1881
|
+
**Before returning, VALIDATE that \`JSON.parse(output)\` runs without errors.**
|
1824
1882
|
|
1825
|
-
|
1883
|
+
**EXAMPLE OF A FULLY ISOLATED SLICE COMPONENT (Follow this strictly):**
|
1884
|
+
${SLICE_CODE_EXAMPLE}
|
1885
|
+
|
1886
|
+
**SLICE MODEL (Use this as the exact reference):**
|
1826
1887
|
${JSON.stringify(updatedSlice)}
|
1827
1888
|
`.trim();
|
1828
|
-
const
|
1829
|
-
|
1830
|
-
|
1831
|
-
|
1832
|
-
|
1833
|
-
|
1834
|
-
type: "image_url",
|
1835
|
-
image_url: {
|
1836
|
-
url: "data:image/png;base64," + Buffer.from(imageFile).toString("base64")
|
1837
|
-
}
|
1838
|
-
},
|
1839
|
-
{ type: "text", text: codeFile }
|
1840
|
-
]
|
1841
|
-
}
|
1842
|
-
];
|
1843
|
-
const response = await openai.chat.completions.create({
|
1844
|
-
model: "gpt-4o",
|
1845
|
-
messages,
|
1846
|
-
response_format: {
|
1847
|
-
type: "json_object"
|
1848
|
-
}
|
1889
|
+
const parsed = await callAI({
|
1890
|
+
ai: "AWS",
|
1891
|
+
sliceIndex,
|
1892
|
+
stepName: "CODE",
|
1893
|
+
systemPrompt,
|
1894
|
+
imageFile
|
1849
1895
|
});
|
1850
|
-
|
1851
|
-
|
1852
|
-
if (!resultText) {
|
1853
|
-
throw new Error("No valid slice component code was generated.");
|
1854
|
-
}
|
1855
|
-
try {
|
1856
|
-
const parsed = JSON.parse(resultText);
|
1857
|
-
if (!parsed.componentCode) {
|
1858
|
-
throw new Error("Missing key 'componentCode' in AI response.");
|
1859
|
-
}
|
1860
|
-
return parsed.componentCode;
|
1861
|
-
} catch (error) {
|
1862
|
-
throw new Error("Failed to parse AI response for component code: " + error);
|
1896
|
+
if (!parsed.componentCode) {
|
1897
|
+
throw new Error("Missing key 'componentCode' in AI response.");
|
1863
1898
|
}
|
1899
|
+
return parsed.componentCode;
|
1864
1900
|
}
|
1865
|
-
async function generateSliceComponentCodeAppearance(
|
1866
|
-
var _a, _b, _c;
|
1901
|
+
async function generateSliceComponentCodeAppearance(sliceIndex, imageFile, componentCode) {
|
1867
1902
|
const systemPrompt = `
|
1868
|
-
You are a seasoned frontend engineer with deep expertise in Prismic slices
|
1869
|
-
Your task is to apply
|
1870
|
-
The branding is
|
1871
|
-
|
1872
|
-
|
1873
|
-
-
|
1874
|
-
-
|
1875
|
-
-
|
1876
|
-
|
1877
|
-
|
1878
|
-
-
|
1879
|
-
-
|
1880
|
-
-
|
1881
|
-
-
|
1882
|
-
-
|
1883
|
-
-
|
1884
|
-
-
|
1885
|
-
-
|
1886
|
-
-
|
1887
|
-
|
1888
|
-
|
1889
|
-
|
1890
|
-
|
1891
|
-
|
1892
|
-
|
1893
|
-
|
1894
|
-
|
1895
|
-
|
1896
|
-
|
1897
|
-
|
1898
|
-
|
1903
|
+
You are a **seasoned frontend engineer** with **deep expertise in Prismic slices**.
|
1904
|
+
Your task is to **apply branding (appearance) strictly based on the provided image and code input**.
|
1905
|
+
The **branding is CRITICAL**—the slice you create **must perfectly match the visual appearance** of the provided slice image.
|
1906
|
+
|
1907
|
+
**STRICT GUIDELINES TO FOLLOW (NO EXCEPTIONS):**
|
1908
|
+
- **DO NOT** modify the structure of the code—**ONLY apply styling**. Your role is **purely styling-related**.
|
1909
|
+
- **NO external dependencies**—use **only inline** styling.
|
1910
|
+
- **VISUAL ACCURACY IS MANDATORY**—your goal is to make the output **visually identical** to the provided image.
|
1911
|
+
|
1912
|
+
**MUST strictly respect the following:**
|
1913
|
+
- **Background color** → Must **exactly match** the image. If unsure, **do not apply** any background color.
|
1914
|
+
- **Padding & margin** → Must be **pixel-perfect** per the provided image.
|
1915
|
+
- **Font size, color, and type** → Must match exactly. If the exact font is unavailable, choose the **closest possible match**.
|
1916
|
+
- **Typography precision** → If the font-family does not match, **the output is incorrect**.
|
1917
|
+
- **Color accuracy** → Use **ONLY the colors visible** in the provided image.
|
1918
|
+
- **Element positioning** → Elements **must be placed exactly** as seen in the provided image.
|
1919
|
+
- **Element sizes** → Every element **must match** the provided image in width, height, and proportions.
|
1920
|
+
- **Overall proportions** → The slice must maintain **identical proportions** to the provided image.
|
1921
|
+
- **Image constraints** → Images **must** maintain their **original aspect ratio**. Use explicit \`width\` and \`height\` constraints with an explicit pixels value. Avoid \`width: auto\`, \`height: auto\`, \`width: 100%\` or \`height: 100%\`.
|
1922
|
+
- **Repetitions & layout** → Ensure **consistent styling** across repeated items. The **layout direction (horizontal/vertical)** must match the image.
|
1923
|
+
- **Animations** → Handle animations as seen in the image, but **keep them fast and subtle** (avoid long animations).
|
1924
|
+
|
1925
|
+
**IMPORTANT RULES:**
|
1926
|
+
1. **DO NOT modify any non-styling code**.
|
1927
|
+
- **Everything from the first import to the last export must remain unchanged**.
|
1928
|
+
- **Only add styling** on top of the existing structure.
|
1929
|
+
|
1930
|
+
2. **STRICT JSON OUTPUT FORMAT**
|
1931
|
+
- Return a **valid JSON object** with **one key only**: \`"componentCode"\`.
|
1932
|
+
- **NO markdown, NO code blocks, NO text before or after**—only **pure JSON**.
|
1933
|
+
- The response **must start and end directly with \`{ "componentCode": ... }\`**.
|
1934
|
+
- Ensure the output is **directly parseable** using \`JSON.parse(output)\`.
|
1935
|
+
|
1936
|
+
3. **INLINE \`<style>\` RULES**
|
1937
|
+
- Use **only inline** \`<style>\` tags (not \`<style jsx>\`).
|
1938
|
+
- Ensure **all CSS is valid** and matches the image precisely.
|
1939
|
+
- **Use backtick inside the \`<style>\` tag like this: <style>{\`...\`}</style>**.
|
1940
|
+
- **Do NOT escape the backtick (\`\`\`) inside the \`<style>\` tag**.
|
1941
|
+
|
1942
|
+
**Before returning, VALIDATE that \`JSON.parse(output)\` runs without errors.**
|
1943
|
+
|
1944
|
+
**EXISTING CODE (to apply branding on):**
|
1899
1945
|
${componentCode}
|
1900
|
-
|
1901
|
-
const
|
1902
|
-
|
1903
|
-
|
1904
|
-
|
1905
|
-
|
1906
|
-
|
1907
|
-
type: "image_url",
|
1908
|
-
image_url: {
|
1909
|
-
url: "data:image/png;base64," + Buffer.from(imageFile).toString("base64")
|
1910
|
-
}
|
1911
|
-
},
|
1912
|
-
{ type: "text", text: codeFile }
|
1913
|
-
]
|
1914
|
-
}
|
1915
|
-
];
|
1916
|
-
const response = await openai.chat.completions.create({
|
1917
|
-
model: "gpt-4o",
|
1918
|
-
messages,
|
1919
|
-
response_format: {
|
1920
|
-
type: "json_object"
|
1921
|
-
}
|
1946
|
+
`.trim();
|
1947
|
+
const parsed = await callAI({
|
1948
|
+
ai: "AWS",
|
1949
|
+
sliceIndex,
|
1950
|
+
stepName: "APPEARANCE",
|
1951
|
+
systemPrompt,
|
1952
|
+
imageFile
|
1922
1953
|
});
|
1923
|
-
|
1924
|
-
|
1925
|
-
if (!resultText) {
|
1926
|
-
throw new Error("No valid slice component code was generated.");
|
1927
|
-
}
|
1928
|
-
try {
|
1929
|
-
const parsed = JSON.parse(resultText);
|
1930
|
-
if (!parsed.componentCode) {
|
1931
|
-
throw new Error("Missing key 'componentCode' in AI response.");
|
1932
|
-
}
|
1933
|
-
return parsed.componentCode;
|
1934
|
-
} catch (error) {
|
1935
|
-
throw new Error("Failed to parse AI response for component code appearance: " + error);
|
1954
|
+
if (!parsed.componentCode) {
|
1955
|
+
throw new Error("Missing key 'componentCode' in AI response.");
|
1936
1956
|
}
|
1957
|
+
return parsed.componentCode;
|
1937
1958
|
}
|
1938
1959
|
try {
|
1939
|
-
|
1940
|
-
|
1941
|
-
|
1942
|
-
|
1943
|
-
console.log("STEP 2: Get the slices codes from the folder.");
|
1944
|
-
const sliceCodes = await readCodeFromFolder(`${folderPath}/code`);
|
1945
|
-
slices = sliceImages.map((sliceImage, index) => ({
|
1946
|
-
sliceImage,
|
1947
|
-
codeFile: sliceCodes[index]
|
1948
|
-
}));
|
1949
|
-
const updatedSlices = await Promise.all(slices.map(async ({ sliceImage, codeFile }, index) => {
|
1950
|
-
console.log("STEP 3: Generate the slice model using the image for slice:", index);
|
1951
|
-
const updatedSlice = await generateSliceModel(sliceImage, codeFile);
|
1952
|
-
console.log("STEP 4: Persist the updated slice model for:", `${index} - ${updatedSlice.name}`);
|
1960
|
+
const updatedSlices = await Promise.all(args.sliceImages.map(async (sliceImage, index) => {
|
1961
|
+
console.log("STEP 1: Generate the slice model using the image for slice:", index);
|
1962
|
+
const updatedSlice = await generateSliceModel(index, sliceImage);
|
1963
|
+
console.log("STEP 2: Persist the updated slice model for:", `${index} - ${updatedSlice.name}`);
|
1953
1964
|
await this.updateSlice({
|
1954
1965
|
libraryID: DEFAULT_LIBRARY_ID,
|
1955
1966
|
model: updatedSlice
|
1956
1967
|
});
|
1957
|
-
console.log("STEP
|
1968
|
+
console.log("STEP 3: Update the slice screenshot for:", `${index} - ${updatedSlice.name}`);
|
1958
1969
|
await this.updateSliceScreenshot({
|
1959
1970
|
libraryID: DEFAULT_LIBRARY_ID,
|
1960
1971
|
sliceID: updatedSlice.id,
|
@@ -1963,47 +1974,53 @@ type GroupField = {
|
|
1963
1974
|
});
|
1964
1975
|
let updatedMock;
|
1965
1976
|
try {
|
1966
|
-
console.log("STEP
|
1977
|
+
console.log("STEP 4: Generate updated mocks for:", `${index} - ${updatedSlice.name}`);
|
1967
1978
|
const existingMocks = mockSlice.mockSlice({ model: updatedSlice });
|
1968
|
-
updatedMock = await generateSliceMocks(sliceImage, existingMocks);
|
1979
|
+
updatedMock = await generateSliceMocks(index, sliceImage, existingMocks);
|
1969
1980
|
} catch (error) {
|
1970
1981
|
console.error(`Failed to generate mocks for ${index} - ${updatedSlice.name}:`, error);
|
1971
1982
|
updatedMock = mockSlice.mockSlice({ model: updatedSlice });
|
1972
1983
|
}
|
1973
1984
|
let componentCode;
|
1974
1985
|
try {
|
1975
|
-
console.log("STEP
|
1976
|
-
const
|
1977
|
-
|
1978
|
-
|
1979
|
-
componentCode = await generateSliceComponentCodeAppearance(sliceImage, codeFile, globalStyle, initialCode);
|
1986
|
+
console.log("STEP 5: Generate the isolated slice component code for:", `${index} - ${updatedSlice.name}`);
|
1987
|
+
const initialCode = await generateSliceComponentCode(index, sliceImage, updatedSlice);
|
1988
|
+
console.log("STEP 6: Generate the branding on the code:", `${index} - ${updatedSlice.name}`);
|
1989
|
+
componentCode = await generateSliceComponentCodeAppearance(index, sliceImage, initialCode);
|
1980
1990
|
} catch (error) {
|
1981
1991
|
console.error(`Failed to generate code for ${index} - ${updatedSlice.name}:`, error);
|
1982
1992
|
}
|
1983
1993
|
return { updatedSlice, componentCode, updatedMock };
|
1984
1994
|
}));
|
1985
|
-
await
|
1986
|
-
console.log("STEP
|
1995
|
+
await updatedSlices.forEach(async ({ updatedSlice, componentCode, updatedMock }, index) => {
|
1996
|
+
console.log("STEP 7: Update the slice code for:", `${index} - ${updatedSlice.name}`);
|
1987
1997
|
if (componentCode) {
|
1988
|
-
await this.createSlice({
|
1998
|
+
const { errors: errors2 } = await this.createSlice({
|
1989
1999
|
libraryID: DEFAULT_LIBRARY_ID,
|
1990
2000
|
model: updatedSlice,
|
1991
2001
|
componentContents: componentCode
|
1992
2002
|
});
|
2003
|
+
if (errors2.length > 0) {
|
2004
|
+
console.log(`Errors while updating the slice code for ${index} - ${updatedSlice.name}:`, errors2);
|
2005
|
+
await this.createSlice({
|
2006
|
+
libraryID: DEFAULT_LIBRARY_ID,
|
2007
|
+
model: updatedSlice
|
2008
|
+
});
|
2009
|
+
}
|
1993
2010
|
} else {
|
1994
2011
|
await this.createSlice({
|
1995
2012
|
libraryID: DEFAULT_LIBRARY_ID,
|
1996
2013
|
model: updatedSlice
|
1997
2014
|
});
|
1998
2015
|
}
|
1999
|
-
console.log("STEP
|
2016
|
+
console.log("STEP 8: Persist the generated mocks for:", `${index} - ${updatedSlice.name}`);
|
2000
2017
|
await this.updateSliceMocks({
|
2001
2018
|
libraryID: DEFAULT_LIBRARY_ID,
|
2002
2019
|
sliceID: updatedSlice.id,
|
2003
2020
|
mocks: updatedMock
|
2004
2021
|
});
|
2005
|
-
})
|
2006
|
-
console.log("STEP
|
2022
|
+
});
|
2023
|
+
console.log("STEP 9: THE END");
|
2007
2024
|
return {
|
2008
2025
|
slices: updatedSlices.map(({ updatedSlice }) => updatedSlice)
|
2009
2026
|
};
|