x
This commit is contained in:
1 parent
e509e3f0a2
commit
0d4df55d93
7 files changed
+526
-3
No files matched your search
@@ -17,7 +17,12 @@ const websiteBlockTypes = [
|
||||
"BANNER",
|
||||
"HERO_ARTICLE",
|
||||
"RICH_TEXT",
|
||||
"ARTICLE_HEADER",
|
||||
"ARTICLE_MEDIA",
|
||||
"ARTICLE_GALLERY",
|
||||
"ARTICLE_FACTS",
|
||||
"ARTICLE_VIDEO",
|
||||
"ARTICLE_REFERENCES",
|
||||
"SCENT_NOTES_BREAKDOWN",
|
||||
"QUOTE_CALLOUT",
|
||||
"PRODUCT_CAROUSEL",
|
||||
@@ -82,6 +87,24 @@ describe("Page Builder block contracts", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("ARTICLE_GALLERY rejects fewer than two images", () => {
|
||||
const result = validatePayloadAgainstMetadata("ARTICLE_GALLERY", {
|
||||
images: [{ imageUrl: "/uploads/one.jpg", alt: { th: "ภาพเดียว" } }],
|
||||
});
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.validationIssues.some((issue) => issue.path === "images")).toBe(true);
|
||||
});
|
||||
|
||||
test("ARTICLE_VIDEO rejects non-HTTPS URLs", () => {
|
||||
const result = validatePayloadAgainstMetadata("ARTICLE_VIDEO", {
|
||||
videoUrl: "http://example.com/video.mp4",
|
||||
});
|
||||
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.validationIssues.some((issue) => issue.path === "videoUrl")).toBe(true);
|
||||
});
|
||||
|
||||
test("create_page_with_section rejects invalid payloads without calling backend", async () => {
|
||||
let postCalls = 0;
|
||||
const api = {
|
||||
|
||||
@@ -18,7 +18,12 @@ const WEBSITE_PAGE_SECTION_TYPE_LIST = [
|
||||
"BANNER",
|
||||
"HERO_ARTICLE",
|
||||
"RICH_TEXT",
|
||||
"ARTICLE_HEADER",
|
||||
"ARTICLE_MEDIA",
|
||||
"ARTICLE_GALLERY",
|
||||
"ARTICLE_FACTS",
|
||||
"ARTICLE_VIDEO",
|
||||
"ARTICLE_REFERENCES",
|
||||
"SCENT_NOTES_BREAKDOWN",
|
||||
"QUOTE_CALLOUT",
|
||||
"PRODUCT_CAROUSEL",
|
||||
@@ -326,6 +331,93 @@ const PAGE_BLOCK_METADATA: Record<string, BlockMetadata> = {
|
||||
background: "default",
|
||||
},
|
||||
},
|
||||
ARTICLE_HEADER: {
|
||||
type: "ARTICLE_HEADER",
|
||||
requiredFields: ["title"],
|
||||
optionalFields: ["subtitle", "category", "author", "publishDate", "imageUrl", "imageAlt"],
|
||||
renderedFields: ["title", "subtitle", "category", "author", "publishDate", "imageUrl", "imageAlt"],
|
||||
storedOnlyFields: [],
|
||||
discoveryToolsNeeded: [],
|
||||
warnings: ["imageAlt is required whenever imageUrl is provided."],
|
||||
minimalPayload: { title: localizedObjectExample },
|
||||
recommendedPayload: {
|
||||
title: { th: "เทคโนโลยีที่อยู่เบื้องหลังทุกการขับเคลื่อน", en: "Technology behind every ride" },
|
||||
subtitle: { th: "ทำความรู้จักกระบวนการผลิตที่ให้ความสำคัญกับความแม่นยำ", en: "A closer look at precision manufacturing." },
|
||||
category: { th: "เทคโนโลยี", en: "Technology" },
|
||||
author: { th: "ทีมงาน DDC", en: "DDC Team" },
|
||||
publishDate: "2026-07-24",
|
||||
},
|
||||
},
|
||||
ARTICLE_GALLERY: {
|
||||
type: "ARTICLE_GALLERY",
|
||||
requiredFields: ["images", "images[].imageUrl", "images[].alt"],
|
||||
optionalFields: ["title", "layout", "images[].caption"],
|
||||
renderedFields: ["title", "layout", "images"],
|
||||
storedOnlyFields: [],
|
||||
discoveryToolsNeeded: ["Use real CMS media URLs; do not invent image URLs."],
|
||||
warnings: ["Provide 2 to 8 images. Every image requires localized alt text."],
|
||||
minimalPayload: {
|
||||
images: [
|
||||
{ imageUrl: imageUrlExample, alt: localizedObjectExample },
|
||||
{ imageUrl: imageUrlExample, alt: localizedObjectExample },
|
||||
],
|
||||
},
|
||||
recommendedPayload: {
|
||||
title: { th: "ภาพจากกระบวนการผลิต", en: "Inside the production process" },
|
||||
layout: "grid",
|
||||
images: [
|
||||
{ imageUrl: imageUrlExample, alt: { th: "ขั้นตอนการผลิตชิ้นงาน", en: "Component production" } },
|
||||
{ imageUrl: imageUrlExample, alt: { th: "การตรวจสอบคุณภาพ", en: "Quality inspection" } },
|
||||
],
|
||||
},
|
||||
},
|
||||
ARTICLE_FACTS: {
|
||||
type: "ARTICLE_FACTS",
|
||||
requiredFields: ["items", "items[].title"],
|
||||
optionalFields: ["title", "items[].description"],
|
||||
renderedFields: ["title", "items"],
|
||||
storedOnlyFields: [],
|
||||
discoveryToolsNeeded: [],
|
||||
warnings: ["Provide 1 to 8 concise facts."],
|
||||
minimalPayload: { items: [{ title: localizedObjectExample }] },
|
||||
recommendedPayload: {
|
||||
title: { th: "ข้อมูลสำคัญ", en: "Key facts" },
|
||||
items: [
|
||||
{ title: { th: "ผิวตัดสม่ำเสมอ", en: "Consistent cut surface" }, description: { th: "ช่วยลดขั้นตอนการแต่งผิว", en: "Reduces secondary finishing." } },
|
||||
{ title: { th: "รูปทรงแม่นยำ", en: "Precise geometry" }, description: { th: "ช่วยให้การขบกับโซ่ต่อเนื่อง", en: "Supports consistent chain engagement." } },
|
||||
],
|
||||
},
|
||||
},
|
||||
ARTICLE_VIDEO: {
|
||||
type: "ARTICLE_VIDEO",
|
||||
requiredFields: ["videoUrl"],
|
||||
optionalFields: ["title", "caption", "posterUrl", "aspectRatio"],
|
||||
renderedFields: ["title", "videoUrl", "caption", "posterUrl", "aspectRatio"],
|
||||
storedOnlyFields: [],
|
||||
discoveryToolsNeeded: [],
|
||||
warnings: ["Use a full HTTPS YouTube, Vimeo, or direct video URL. Do not send embed HTML."],
|
||||
minimalPayload: { videoUrl: "https://www.youtube.com/watch?v=VIDEO_ID" },
|
||||
recommendedPayload: {
|
||||
title: { th: "ชมกระบวนการผลิต", en: "Watch the production process" },
|
||||
videoUrl: "https://www.youtube.com/watch?v=VIDEO_ID",
|
||||
caption: { th: "เบื้องหลังการผลิตที่แม่นยำ", en: "Behind precision manufacturing" },
|
||||
aspectRatio: "16:9",
|
||||
},
|
||||
},
|
||||
ARTICLE_REFERENCES: {
|
||||
type: "ARTICLE_REFERENCES",
|
||||
requiredFields: ["items", "items[].title"],
|
||||
optionalFields: ["title", "items[].url", "items[].note"],
|
||||
renderedFields: ["title", "items"],
|
||||
storedOnlyFields: [],
|
||||
discoveryToolsNeeded: [],
|
||||
warnings: ["Use verified source URLs only. A reference may omit url for offline sources."],
|
||||
minimalPayload: { items: [{ title: localizedObjectExample }] },
|
||||
recommendedPayload: {
|
||||
title: { th: "แหล่งข้อมูล", en: "References" },
|
||||
items: [{ title: { th: "เอกสารข้อมูลทางเทคนิค", en: "Technical documentation" }, note: { th: "ข้อมูลจากฝ่ายวิศวกรรม", en: "Engineering source material" } }],
|
||||
},
|
||||
},
|
||||
SCENT_NOTES_BREAKDOWN: {
|
||||
type: "SCENT_NOTES_BREAKDOWN",
|
||||
requiredFields: [],
|
||||
@@ -555,6 +647,26 @@ const ADD_PAGE_SECTION_GUIDANCE: Record<string, { required: string[]; example: s
|
||||
required: ["body"],
|
||||
example: `{ "heading": ${localizedStringExample}, "body": ${localizedStringExample}, "imageUrl": "https://example.com/article.jpg", "imageAlt": ${localizedStringExample}, "imagePosition": "right" }`
|
||||
},
|
||||
ARTICLE_HEADER: {
|
||||
required: ["title"],
|
||||
example: `{ "title": ${localizedStringExample}, "subtitle": ${localizedStringExample}, "author": ${localizedStringExample} }`
|
||||
},
|
||||
ARTICLE_GALLERY: {
|
||||
required: ["images", "images[].imageUrl", "images[].alt"],
|
||||
example: `{ "images": [{ "imageUrl": "https://example.com/one.jpg", "alt": ${localizedStringExample} }, { "imageUrl": "https://example.com/two.jpg", "alt": ${localizedStringExample} }] }`
|
||||
},
|
||||
ARTICLE_FACTS: {
|
||||
required: ["items", "items[].title"],
|
||||
example: `{ "title": ${localizedStringExample}, "items": [{ "title": ${localizedStringExample}, "description": ${localizedStringExample} }] }`
|
||||
},
|
||||
ARTICLE_VIDEO: {
|
||||
required: ["videoUrl"],
|
||||
example: `{ "videoUrl": "https://www.youtube.com/watch?v=VIDEO_ID", "title": ${localizedStringExample}, "aspectRatio": "16:9" }`
|
||||
},
|
||||
ARTICLE_REFERENCES: {
|
||||
required: ["items", "items[].title"],
|
||||
example: `{ "items": [{ "title": ${localizedStringExample}, "url": "https://example.com/source" }] }`
|
||||
},
|
||||
SCENT_NOTES_BREAKDOWN: {
|
||||
required: [],
|
||||
example: `{ "title": ${localizedStringExample}, "topNotes": ${localizedStringListExample} }`
|
||||
@@ -1100,7 +1212,7 @@ export const SECTION_TOOL_DEFINITIONS = [
|
||||
},
|
||||
{
|
||||
name: "add_page_section",
|
||||
description: `Add a new website-renderable block to an existing dynamic CMS page. Use this instead of create_page_with_section when the page or slug already exists. Call get_dynamic_page_detail first to obtain pageId, and get_page_block_schema before composing payload.\n\n${buildAddPageSectionDescription()}\n\nRender target guard: generic CMS pages are rendered by apps/website DynamicPageRenderer. PRODUCT_MAIN, PRODUCT_DETAILS, PRODUCT_SPECS, PRODUCT_REVIEWS, and RELATED_PRODUCTS are product detail native blocks and are rejected by default here. Use product detail CMS tools for those blocks.`,
|
||||
description: "Add one website-renderable block to an existing dynamic CMS page. Call get_dynamic_page_detail to obtain pageId, then get_page_block_schema for the selected type before composing payload. Use product detail CMS tools for product-native blocks.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
|
||||
Reference in new issue
Block a user