x
This commit is contained in:
1 parent
f8da2cceaf
commit
63bc3bfb1a
6 files changed
+658
-2
No files matched your search
@@ -270,4 +270,130 @@ describe("Page Builder block contracts", () => {
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
test("get_page_section_translation requests one language with source fallback", async () => {
|
||||
const calls: string[] = [];
|
||||
const api = {
|
||||
get: async (url: string) => {
|
||||
calls.push(url);
|
||||
return {
|
||||
data: {
|
||||
id: 41,
|
||||
type: "ARTICLE_REFERENCES",
|
||||
payload: {
|
||||
title: "参考资料",
|
||||
items: [{ title: "精密冲裁技术", note: null, url: "https://example.com" }],
|
||||
},
|
||||
locale: {
|
||||
lang: "zh",
|
||||
fallback: true,
|
||||
missingPaths: [],
|
||||
fallbackPaths: ["items[0].note"],
|
||||
},
|
||||
updatedAt: "2026-07-24T10:00:00.000Z",
|
||||
},
|
||||
};
|
||||
},
|
||||
} as any;
|
||||
|
||||
const result = await handleSectionTool(api, "get_page_section_translation", {
|
||||
sectionId: 41,
|
||||
lang: "zh",
|
||||
});
|
||||
const response = JSON.parse(result?.content[0]?.text ?? "{}");
|
||||
|
||||
expect(calls).toEqual([
|
||||
"/pages/sections/41?lang=zh&view=resolved&fallback=true",
|
||||
]);
|
||||
expect(response).toEqual({
|
||||
sectionId: 41,
|
||||
sectionType: "ARTICLE_REFERENCES",
|
||||
lang: "zh",
|
||||
payload: {
|
||||
title: "参考资料",
|
||||
items: [{ title: "精密冲裁技术", note: null, url: "https://example.com" }],
|
||||
},
|
||||
missingPaths: [],
|
||||
fallbackPaths: ["items[0].note"],
|
||||
updatedAt: "2026-07-24T10:00:00.000Z",
|
||||
});
|
||||
});
|
||||
|
||||
test("update_section_translation sends only translation fields and version", async () => {
|
||||
const calls: Array<{ url: string; payload: unknown }> = [];
|
||||
const api = {
|
||||
patch: async (url: string, payload: unknown) => {
|
||||
calls.push({ url, payload });
|
||||
return {
|
||||
data: {
|
||||
success: true,
|
||||
sectionId: 41,
|
||||
sectionType: "ARTICLE_REFERENCES",
|
||||
lang: "zh",
|
||||
translatedPaths: ["title", "items[0].title"],
|
||||
preservedOtherLanguages: true,
|
||||
updatedAt: "2026-07-24T10:01:00.000Z",
|
||||
},
|
||||
};
|
||||
},
|
||||
} as any;
|
||||
const translations = {
|
||||
title: "参考资料",
|
||||
items: [{ title: "精密冲裁技术" }],
|
||||
};
|
||||
|
||||
const result = await handleSectionTool(api, "update_section_translation", {
|
||||
sectionId: 41,
|
||||
lang: "zh",
|
||||
translations,
|
||||
expectedUpdatedAt: "2026-07-24T10:00:00.000Z",
|
||||
});
|
||||
const response = JSON.parse(result?.content[0]?.text ?? "{}");
|
||||
|
||||
expect(calls).toEqual([{
|
||||
url: "/pages/sections/41/translations/zh",
|
||||
payload: {
|
||||
translations,
|
||||
expectedUpdatedAt: "2026-07-24T10:00:00.000Z",
|
||||
},
|
||||
}]);
|
||||
expect(response.preservedOtherLanguages).toBe(true);
|
||||
expect(response.translatedPaths).toEqual(["title", "items[0].title"]);
|
||||
});
|
||||
|
||||
test("get_page_translation_status requests a compact language audit", async () => {
|
||||
const calls: string[] = [];
|
||||
const api = {
|
||||
get: async (url: string) => {
|
||||
calls.push(url);
|
||||
return {
|
||||
data: {
|
||||
success: true,
|
||||
pageId: 15,
|
||||
lang: "zh",
|
||||
complete: false,
|
||||
translatedSections: 8,
|
||||
totalSections: 9,
|
||||
missingSections: [{
|
||||
sectionId: 41,
|
||||
type: "ARTICLE_REFERENCES",
|
||||
order: 9,
|
||||
complete: false,
|
||||
missingPaths: ["items[0].note"],
|
||||
}],
|
||||
},
|
||||
};
|
||||
},
|
||||
} as any;
|
||||
|
||||
const result = await handleSectionTool(api, "get_page_translation_status", {
|
||||
pageId: 15,
|
||||
lang: "zh",
|
||||
});
|
||||
const response = JSON.parse(result?.content[0]?.text ?? "{}");
|
||||
|
||||
expect(calls).toEqual(["/pages/translation-status/15?lang=zh"]);
|
||||
expect(response.totalSections).toBe(9);
|
||||
expect(response.missingSections[0].sectionId).toBe(41);
|
||||
});
|
||||
});
|
||||
@@ -1264,7 +1264,7 @@ export const SECTION_TOOL_DEFINITIONS = [
|
||||
},
|
||||
{
|
||||
name: "get_page_section",
|
||||
description: "Get a specific page section by ID, returning its hydrated payload resolving linked content like categories or products. Optionally pass lang to control translation.",
|
||||
description: "Get a specific page section by ID. A specific lang returns localized fields as compact strings with fallback enabled; lang=all returns every stored language map.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -1274,6 +1274,45 @@ export const SECTION_TOOL_DEFINITIONS = [
|
||||
required: ["sectionId"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "get_page_section_translation",
|
||||
description: "Read one Page Builder block in exactly one language for translation work. Localized fields become strings; fallback defaults to true so missing translations still include source text and are listed in fallbackPaths.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
sectionId: { type: "number", description: "The numeric ID of the section to fetch" },
|
||||
lang: { type: "string", enum: ["th", "en", "ja", "zh", "vi", "ko", "lo", "it"], description: "Language to return" },
|
||||
fallback: { type: "boolean", description: "Use another stored language when the requested language is missing. Default true so the LLM has source text to translate." }
|
||||
},
|
||||
required: ["sectionId", "lang"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "update_section_translation",
|
||||
description: "Patch only one language of registered localized fields in a Page Builder block. Other languages and non-translatable configuration are preserved. Pass expectedUpdatedAt from get_page_section_translation to prevent overwriting concurrent edits.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
sectionId: { type: "number", description: "The numeric ID of the section to translate" },
|
||||
lang: { type: "string", enum: ["th", "en", "ja", "zh", "vi", "ko", "lo", "it"], description: "Language being written" },
|
||||
translations: { type: "object", description: "Only translated fields, using the same object and array shape returned by the read tool" },
|
||||
expectedUpdatedAt: { type: "string", description: "Optional section updatedAt value from the read tool for optimistic concurrency" }
|
||||
},
|
||||
required: ["sectionId", "lang", "translations"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "get_page_translation_status",
|
||||
description: "Return compact translation completeness for every Page Builder block on one page, including section IDs and missing localized paths.",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
pageId: { type: "number", description: "The numeric ID of the page" },
|
||||
lang: { type: "string", enum: ["th", "en", "ja", "zh", "vi", "ko", "lo", "it"], description: "Language to audit" }
|
||||
},
|
||||
required: ["pageId", "lang"]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "add_page_section",
|
||||
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.",
|
||||
@@ -1533,6 +1572,7 @@ export async function handleSectionTool(api: AxiosInstance, name: string, params
|
||||
"validate_page_section_payload",
|
||||
"create_page_with_section",
|
||||
"add_page_section",
|
||||
"get_page_translation_status",
|
||||
"reorder_page_sections",
|
||||
"delete_dynamic_page",
|
||||
"get_product_detail_sections",
|
||||
@@ -1617,6 +1657,45 @@ export async function handleSectionTool(api: AxiosInstance, name: string, params
|
||||
const res = await api.get(`/pages/sections/${params.sectionId}${langParam}`);
|
||||
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
||||
}
|
||||
case "get_page_section_translation": {
|
||||
const fallback = params.fallback !== false;
|
||||
const res = await api.get(
|
||||
`/pages/sections/${params.sectionId}?lang=${encodeURIComponent(params.lang)}&view=resolved&fallback=${fallback}`,
|
||||
);
|
||||
return {
|
||||
content: [{
|
||||
type: "text",
|
||||
text: JSON.stringify({
|
||||
sectionId: res.data?.id ?? params.sectionId,
|
||||
sectionType: res.data?.type,
|
||||
lang: params.lang,
|
||||
payload: res.data?.payload,
|
||||
missingPaths: res.data?.locale?.missingPaths ?? [],
|
||||
fallbackPaths: res.data?.locale?.fallbackPaths ?? [],
|
||||
updatedAt: res.data?.updatedAt,
|
||||
}, null, 2),
|
||||
}],
|
||||
};
|
||||
}
|
||||
case "update_section_translation": {
|
||||
const res = await api.patch(
|
||||
`/pages/sections/${params.sectionId}/translations/${encodeURIComponent(params.lang)}`,
|
||||
{
|
||||
translations: params.translations,
|
||||
...(params.expectedUpdatedAt ? { expectedUpdatedAt: params.expectedUpdatedAt } : {}),
|
||||
},
|
||||
);
|
||||
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
||||
}
|
||||
case "get_page_translation_status": {
|
||||
if (params.pageId === undefined) {
|
||||
throw new Error(`Validation Error: 'pageId' is required for tool '${name}'`);
|
||||
}
|
||||
const res = await api.get(
|
||||
`/pages/translation-status/${params.pageId}?lang=${encodeURIComponent(params.lang)}`,
|
||||
);
|
||||
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
||||
}
|
||||
case "add_page_section": {
|
||||
if (params.pageId === undefined) {
|
||||
throw new Error(`Validation Error: 'pageId' is required for tool '${name}'`);
|
||||
|
||||
Reference in new issue
Block a user