jvs-ppt-ui 0.0.10 → 0.0.11
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/package.json +7 -7
- package/scripts/check_connectors.cjs +25 -0
- package/scripts/check_slide1.cjs +11 -0
- package/scripts/check_slide123.cjs +25 -0
- package/scripts/convert-fonts-to-woff2.cjs +97 -0
- package/scripts/extract-slides.ts +149 -0
- package/scripts/find_candidate_pptx.cjs +34 -0
- package/scripts/find_orange_slide.cjs +29 -0
- package/scripts/generate-complete-ppt-preview.js +385 -0
- package/scripts/inspect_315_327.cjs +19 -0
- package/scripts/mcp-smoke-test.js +568 -0
- package/scripts/patch-pptxtojson.cjs +81 -0
- package/scripts/verify-fonts.cjs +20 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { defaultDeck, defaultElement, defaultSlide, MCP_EXPORT_DIR, saveDeck } from "../mcp/deckStore.js";
|
|
4
|
+
import { createPptxSafeDeck } from "../mcp/exportQualityTools.js";
|
|
5
|
+
import { applyMotionToDeck } from "../mcp/motionTools.js";
|
|
6
|
+
import { exportDeckToPptx } from "../mcp/pptxExport.js";
|
|
7
|
+
import { generateDeckReviewReport } from "../mcp/reportTools.js";
|
|
8
|
+
import { runPptxVisualRegression } from "../mcp/visualRegressionTools.js";
|
|
9
|
+
|
|
10
|
+
const W = 1000;
|
|
11
|
+
const H = 562.5;
|
|
12
|
+
|
|
13
|
+
const theme = {
|
|
14
|
+
fontFamily: "Heiti SC",
|
|
15
|
+
fontColor: "#172033",
|
|
16
|
+
backgroundColor: "#f7fafc",
|
|
17
|
+
themeColor: ["#155eef", "#12a594", "#ffb020", "#e5484d", "#7258e8"],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function px(value) {
|
|
21
|
+
return `${value}px`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function gid(seed) {
|
|
25
|
+
return `g_${seed}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function text(content, x, y, w, h, style = {}) {
|
|
29
|
+
return defaultElement("text", {
|
|
30
|
+
content,
|
|
31
|
+
style: {
|
|
32
|
+
left: px(x),
|
|
33
|
+
top: px(y),
|
|
34
|
+
width: px(w),
|
|
35
|
+
height: px(h),
|
|
36
|
+
fontFamily: theme.fontFamily,
|
|
37
|
+
color: style.color || "#172033",
|
|
38
|
+
fontSize: px(style.fontSize || 24),
|
|
39
|
+
fontWeight: style.bold ? "bold" : style.fontWeight,
|
|
40
|
+
textAlign: style.align || "left",
|
|
41
|
+
verticalAlign: style.valign || "top",
|
|
42
|
+
...style,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function shape(kind, x, y, w, h, style = {}, label = "") {
|
|
48
|
+
return defaultElement("shape", {
|
|
49
|
+
shape: kind,
|
|
50
|
+
text: label,
|
|
51
|
+
style: {
|
|
52
|
+
left: px(x),
|
|
53
|
+
top: px(y),
|
|
54
|
+
width: px(w),
|
|
55
|
+
height: px(h),
|
|
56
|
+
fill: style.fill || "#ffffff",
|
|
57
|
+
borderColor: style.borderColor || "#d9e2f2",
|
|
58
|
+
borderWidth: style.borderWidth ?? "1px",
|
|
59
|
+
color: style.color || "#172033",
|
|
60
|
+
fontSize: px(style.fontSize || 18),
|
|
61
|
+
fontWeight: style.bold ? "bold" : style.fontWeight,
|
|
62
|
+
textAlign: style.align || "center",
|
|
63
|
+
verticalAlign: style.valign || "middle",
|
|
64
|
+
...style,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function line(x1, y1, x2, y2, style = {}) {
|
|
70
|
+
return defaultElement("line", {
|
|
71
|
+
start: [x1, y1],
|
|
72
|
+
end: [x2, y2],
|
|
73
|
+
lineStyle: style.lineStyle || "solid",
|
|
74
|
+
style: {
|
|
75
|
+
left: px(Math.min(x1, x2)),
|
|
76
|
+
top: px(Math.min(y1, y2)),
|
|
77
|
+
width: px(Math.abs(x2 - x1) || 1),
|
|
78
|
+
height: px(Math.abs(y2 - y1) || 1),
|
|
79
|
+
borderColor: style.borderColor || "#cbd5e1",
|
|
80
|
+
borderWidth: style.borderWidth || "2px",
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function chart(chartType, x, y, w, h, labels, datasets) {
|
|
86
|
+
return defaultElement("chart", {
|
|
87
|
+
chartType,
|
|
88
|
+
data: { labels, datasets },
|
|
89
|
+
style: { left: px(x), top: px(y), width: px(w), height: px(h) },
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function slide(id, title, subtitle = "") {
|
|
94
|
+
return defaultSlide({
|
|
95
|
+
slideId: id,
|
|
96
|
+
title,
|
|
97
|
+
subtitle,
|
|
98
|
+
background: "#f7fafc",
|
|
99
|
+
notes: `本页用于说明:${title}`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function addHeader(s, index, section) {
|
|
104
|
+
s.elements.push(text(section, 62, 28, 360, 24, { fontSize: 15, bold: true, color: "#5b6b82" }));
|
|
105
|
+
s.elements.push(text(String(index).padStart(2, "0"), 890, 28, 48, 24, { fontSize: 15, bold: true, color: "#5b6b82", align: "right" }));
|
|
106
|
+
s.elements.push(line(62, 58, 938, 58, { borderColor: "#d8e1ee", borderWidth: "1px" }));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function addTitle(s, titleValue, subtitleValue) {
|
|
110
|
+
s.elements.push(text(titleValue, 62, 78, 650, 54, { fontSize: 38, bold: true }));
|
|
111
|
+
if (subtitleValue) s.elements.push(text(subtitleValue, 64, 136, 760, 38, { fontSize: 18, color: "#5b6b82" }));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function coverSlide() {
|
|
115
|
+
const s = slide("complete_ppt_cover", "MCP 生产级 PPT 生成系统", "给 Codex 和其它 agent 的分步操作入口");
|
|
116
|
+
s.background = "#071426";
|
|
117
|
+
s.elements.push(shape("rect", 0, 0, W, H, { fill: "#071426", borderWidth: "0px" }));
|
|
118
|
+
s.elements.push(shape("rect", 632, 0, 368, H, { fill: "#0f766e", borderWidth: "0px" }));
|
|
119
|
+
s.elements.push(shape("roundRect", 68, 68, 150, 34, { fill: "#13243a", borderColor: "#284461", color: "#b7c6d8", fontSize: 14, bold: true }, "JVS PPT UI / MCP"));
|
|
120
|
+
s.elements.push(text("把 PPT 生成拆成 agent 可持续操作的生产流程", 68, 132, 548, 164, { fontSize: 44, bold: true, color: "#ffffff" }));
|
|
121
|
+
s.elements.push(text("从语义 DSL、组件布局、动效策略、质量评分到 PPTX 安全导出,每一步都可以被 Codex 独立调用、检查和返工。", 72, 318, 520, 76, { fontSize: 20, color: "#cbd7e6" }));
|
|
122
|
+
[["35+", "细粒度工具"], ["10", "完整页面"], ["QA", "渲染检查"]].forEach(([value, label], i) => {
|
|
123
|
+
s.elements.push(shape("roundRect", 72 + i * 166, 438, 138, 72, { fill: i === 0 ? "#155eef" : "#13243a", borderColor: "#2e4967", color: "#ffffff", fontSize: 20, bold: true }, `${value}\n${label}`));
|
|
124
|
+
});
|
|
125
|
+
s.elements.push(shape("roundRect", 684, 92, 234, 352, { fill: "#effaf7", borderWidth: "0px", color: "#0f2d2a", fontSize: 18, bold: true }, "PLAN\n\nDSL\n\nLAYOUT\n\nMOTION\n\nEXPORT"));
|
|
126
|
+
s.elements.push(text("完整效果预览", 710, 466, 180, 34, { fontSize: 28, bold: true, color: "#ffffff", align: "center" }));
|
|
127
|
+
return s;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function problemSlide() {
|
|
131
|
+
const s = slide("complete_ppt_problem", "一次性生成很快,但很难稳定到生产级", "问题不是模型不会写内容,而是缺少可检查、可返工、可复用的操作层。");
|
|
132
|
+
addHeader(s, 2, "01 / 为什么要拆");
|
|
133
|
+
addTitle(s, s.title, s.subtitle);
|
|
134
|
+
const items = [
|
|
135
|
+
["结构不可控", "模型一次输出所有页面,页面之间的叙事、层级和版式约束很难被持续验证。"],
|
|
136
|
+
["局部返工成本高", "只想调一个图表、一组卡片或一段动效时,重新生成会破坏其它已通过的页面。"],
|
|
137
|
+
["导出风险隐藏", "复杂样式、动画、图片裁剪在 PPTX 导出时可能降级,需要显式预检。"],
|
|
138
|
+
];
|
|
139
|
+
items.forEach(([k, v], i) => {
|
|
140
|
+
const y = 220 + i * 92;
|
|
141
|
+
s.elements.push(shape("roundRect", 66, y, 260, 62, { fill: "#fff4f4", borderColor: "#ffd0d3", color: "#a61b2b", fontSize: 23, bold: true }, k));
|
|
142
|
+
s.elements.push(text(v, 356, y + 6, 548, 54, { fontSize: 20, color: "#334155" }));
|
|
143
|
+
});
|
|
144
|
+
s.elements.push(shape("roundRect", 738, 94, 184, 76, { fill: "#172033", borderWidth: "0px", color: "#ffffff", fontSize: 22, bold: true }, "核心判断\n要生成,也要能编辑"));
|
|
145
|
+
return s;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function architectureSlide() {
|
|
149
|
+
const s = slide("complete_ppt_architecture", "MCP 应该变成 PPT 生产流水线,而不是一个生成按钮", "每个阶段都有输入、产物、检查点和下一步操作入口。");
|
|
150
|
+
addHeader(s, 3, "02 / 系统架构");
|
|
151
|
+
addTitle(s, s.title, s.subtitle);
|
|
152
|
+
const stages = [
|
|
153
|
+
["需求", "topic / audience / objective"],
|
|
154
|
+
["DSL", "semantic outline / registry"],
|
|
155
|
+
["组件", "cards / charts / timeline"],
|
|
156
|
+
["质量", "score / suggest / fix"],
|
|
157
|
+
["动效", "strategy / timeline"],
|
|
158
|
+
["导出", "safe pptx / visual QA"],
|
|
159
|
+
];
|
|
160
|
+
stages.forEach(([name, desc], i) => {
|
|
161
|
+
const x = 70 + i * 146;
|
|
162
|
+
s.elements.push(shape("roundRect", x, 238, 112, 92, { fill: i % 2 ? "#eefbf7" : "#eef4ff", borderColor: i % 2 ? "#9ee5d4" : "#b8ccff", color: i % 2 ? "#0f766e" : "#155eef", fontSize: 23, bold: true }, name));
|
|
163
|
+
s.elements.push(text(desc, x - 8, 352, 128, 42, { fontSize: 14, color: "#64748b", align: "center" }));
|
|
164
|
+
if (i < stages.length - 1) s.elements.push(line(x + 118, 284, x + 140, 284, { borderColor: "#94a3b8", borderWidth: "2px" }));
|
|
165
|
+
});
|
|
166
|
+
s.elements.push(shape("roundRect", 150, 434, 700, 52, { fill: "#172033", borderWidth: "0px", color: "#ffffff", fontSize: 21, bold: true }, "agent 可以在任意节点暂停、检查、替换、继续,而不是只能整包重来"));
|
|
167
|
+
return s;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function entrySlide() {
|
|
171
|
+
const s = slide("complete_ppt_entries", "不同操作入口对应不同的返工粒度", "把“大生成”拆成可组合工具,才能让 Codex 在上下文中持续完善。");
|
|
172
|
+
addHeader(s, 4, "03 / 操作入口");
|
|
173
|
+
addTitle(s, s.title, s.subtitle);
|
|
174
|
+
const rows = [
|
|
175
|
+
["Deck", "create / duplicate / snapshot / restore", "控制版本和整体结构"],
|
|
176
|
+
["Slide", "add / delete / preset / DSL append", "控制页面顺序和叙事节奏"],
|
|
177
|
+
["Element", "text / shape / image / chart / line", "控制单个对象的可编辑性"],
|
|
178
|
+
["Component", "metric cards / roadmap / comparison", "控制可复用视觉模块"],
|
|
179
|
+
["QA", "validate / score / suggest / visual regression", "控制交付前质量门禁"],
|
|
180
|
+
];
|
|
181
|
+
rows.forEach(([name, ops, why], i) => {
|
|
182
|
+
const y = 190 + i * 58;
|
|
183
|
+
s.elements.push(shape("roundRect", 66, y, 126, 38, { fill: "#eaf2ff", borderColor: "#b8ccff", color: "#155eef", fontSize: 20, bold: true }, name));
|
|
184
|
+
s.elements.push(text(ops, 222, y + 4, 390, 30, { fontSize: 18, color: "#172033", bold: true }));
|
|
185
|
+
s.elements.push(text(why, 636, y + 5, 280, 30, { fontSize: 17, color: "#64748b" }));
|
|
186
|
+
});
|
|
187
|
+
return s;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function workflowSlide() {
|
|
191
|
+
const s = slide("complete_ppt_workflow", "推荐流程是先成稿,再用工具循环提质", "一次产出初稿,多轮进入局部优化,最终导出安全版本并留下报告。");
|
|
192
|
+
addHeader(s, 5, "04 / 生成流程");
|
|
193
|
+
addTitle(s, s.title, s.subtitle);
|
|
194
|
+
const steps = [
|
|
195
|
+
["1", "生成语义大纲", "锁定叙事与页面角色"],
|
|
196
|
+
["2", "编译为可编辑元素", "所有对象都有 id 和样式"],
|
|
197
|
+
["3", "评分与建议", "发现稀疏、拥挤、重叠"],
|
|
198
|
+
["4", "局部修复", "只改失败页面或组件"],
|
|
199
|
+
["5", "导出与渲染", "PPTX + PNG + report"],
|
|
200
|
+
];
|
|
201
|
+
steps.forEach(([num, titleValue, desc], i) => {
|
|
202
|
+
const x = 84 + i * 172;
|
|
203
|
+
s.elements.push(shape("circle", x + 32, 214, 74, 74, { fill: "#155eef", borderWidth: "0px", color: "#ffffff", fontSize: 28, bold: true }, num));
|
|
204
|
+
s.elements.push(text(titleValue, x, 318, 140, 34, { fontSize: 20, bold: true, align: "center" }));
|
|
205
|
+
s.elements.push(text(desc, x - 6, 358, 152, 48, { fontSize: 15, color: "#64748b", align: "center" }));
|
|
206
|
+
if (i < steps.length - 1) s.elements.push(line(x + 120, 251, x + 158, 251, { borderColor: "#94a3b8", borderWidth: "2px" }));
|
|
207
|
+
});
|
|
208
|
+
s.elements.push(shape("roundRect", 110, 454, 780, 48, { fill: "#fff7e8", borderColor: "#ffdf9c", color: "#8a5200", fontSize: 20, bold: true }, "关键:每一轮都留下 JSON 源、PPTX 版本、渲染图和质量报告"));
|
|
209
|
+
return s;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function qualitySlide() {
|
|
213
|
+
const s = slide("complete_ppt_quality", "生产级效果要靠质量维度驱动,而不是只靠视觉直觉", "检查项覆盖结构、版式、信息密度、动效和导出兼容性。");
|
|
214
|
+
addHeader(s, 6, "05 / 质量体系");
|
|
215
|
+
addTitle(s, s.title, s.subtitle);
|
|
216
|
+
s.elements.push(chart("bar", 70, 206, 430, 250, ["结构", "层级", "密度", "平衡", "动效"], [{ label: "目标评分", data: [92, 88, 84, 86, 78] }]));
|
|
217
|
+
const checks = [
|
|
218
|
+
["结构完整", "标题、页面角色、备注、叙事顺序"],
|
|
219
|
+
["版式安全", "边界、对齐、重叠、可读字号"],
|
|
220
|
+
["视觉丰富", "图表、卡片、流程、指标合理组合"],
|
|
221
|
+
["导出可控", "降级点、静态安全版、渲染回归"],
|
|
222
|
+
];
|
|
223
|
+
checks.forEach(([k, v], i) => {
|
|
224
|
+
s.elements.push(shape("roundRect", 548, 202 + i * 62, 330, 44, { fill: i % 2 ? "#f4fbf9" : "#f4f7ff", borderColor: "#d8e1ee", color: "#172033", fontSize: 17, bold: true }, `${k} - ${v}`));
|
|
225
|
+
});
|
|
226
|
+
return s;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function layoutSlide() {
|
|
230
|
+
const s = slide("complete_ppt_layouts", "布局规范需要恢复成可选策略,而不是藏在代码里", "agent 需要知道什么时候用对比、指标、时间线、图表列表或封底。");
|
|
231
|
+
addHeader(s, 7, "06 / 布局规范");
|
|
232
|
+
addTitle(s, s.title, s.subtitle);
|
|
233
|
+
const cards = [
|
|
234
|
+
["compare", "判断方案取舍"],
|
|
235
|
+
["metric", "表达业务收益"],
|
|
236
|
+
["timeline", "表达推进路径"],
|
|
237
|
+
["chart-list", "用数据支撑结论"],
|
|
238
|
+
["closing", "收束行动项"],
|
|
239
|
+
["title-only", "制造章节停顿"],
|
|
240
|
+
];
|
|
241
|
+
cards.forEach(([k, v], i) => {
|
|
242
|
+
const col = i % 3;
|
|
243
|
+
const row = Math.floor(i / 3);
|
|
244
|
+
const x = 82 + col * 288;
|
|
245
|
+
const y = 210 + row * 120;
|
|
246
|
+
s.elements.push(shape("roundRect", x, y, 238, 78, { fill: "#ffffff", borderColor: "#d8e1ee", color: "#155eef", fontSize: 22, bold: true }, k));
|
|
247
|
+
s.elements.push(text(v, x + 18, y + 86, 202, 28, { fontSize: 16, color: "#64748b", align: "center" }));
|
|
248
|
+
});
|
|
249
|
+
return s;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function motionSlide() {
|
|
253
|
+
const s = slide("complete_ppt_motion", "动效也要拆成策略和时间线,才能服务叙事", "PPTX 静态导出保留页面,JSON 源保留动画编排,二者各司其职。");
|
|
254
|
+
addHeader(s, 8, "07 / 动效拆分");
|
|
255
|
+
addTitle(s, s.title, s.subtitle);
|
|
256
|
+
const lanes = [
|
|
257
|
+
["Executive", "标题先入场,主体分组递进,适合正式汇报"],
|
|
258
|
+
["Data story", "先框架、再数据、最后强调结论"],
|
|
259
|
+
["Roadmap", "沿阅读方向逐步展开里程碑"],
|
|
260
|
+
["Minimal", "少量淡入,避免干扰高管阅读"],
|
|
261
|
+
];
|
|
262
|
+
lanes.forEach(([name, desc], i) => {
|
|
263
|
+
const y = 202 + i * 66;
|
|
264
|
+
s.elements.push(shape("roundRect", 82, y, 190, 44, { fill: "#172033", borderWidth: "0px", color: "#ffffff", fontSize: 20, bold: true }, name));
|
|
265
|
+
s.elements.push(line(292, y + 22, 408, y + 22, { borderColor: "#12a594", borderWidth: "3px" }));
|
|
266
|
+
s.elements.push(text(desc, 430, y + 6, 450, 32, { fontSize: 18, color: "#334155" }));
|
|
267
|
+
});
|
|
268
|
+
s.elements.push(shape("roundRect", 126, 486, 740, 40, { fill: "#eefbf7", borderColor: "#9ee5d4", color: "#0f766e", fontSize: 18, bold: true }, "后续继续拆:动画触发、分组节奏、强调规则、PPTX 降级说明"));
|
|
269
|
+
return s;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function roadmapSlide() {
|
|
273
|
+
const s = slide("complete_ppt_roadmap", "落地路线按风险从低到高推进", "先把可控工具链跑通,再逐步提升模板、图像和原生动画能力。");
|
|
274
|
+
addHeader(s, 9, "08 / 落地路线");
|
|
275
|
+
addTitle(s, s.title, s.subtitle);
|
|
276
|
+
const quarters = [
|
|
277
|
+
["Now", "完整 MCP 工具链", "生成、编辑、导出、渲染、报告"],
|
|
278
|
+
["Next", "生产模板库", "行业模板、组件资产、版式规范"],
|
|
279
|
+
["Later", "智能优化闭环", "自动拆页、自动重排、风格迁移"],
|
|
280
|
+
["Future", "高保真多端输出", "PPTX 动画、网页演示、视频录制"],
|
|
281
|
+
];
|
|
282
|
+
quarters.forEach(([q, titleValue, desc], i) => {
|
|
283
|
+
const x = 88 + i * 218;
|
|
284
|
+
s.elements.push(shape("roundRect", x, 226, 164, 142, { fill: i === 0 ? "#155eef" : "#ffffff", borderColor: i === 0 ? "#155eef" : "#d8e1ee", color: i === 0 ? "#ffffff" : "#172033", fontSize: 21, bold: true }, `${q}\n${titleValue}`));
|
|
285
|
+
s.elements.push(text(desc, x + 8, 386, 148, 52, { fontSize: 15, color: "#64748b", align: "center" }));
|
|
286
|
+
if (i < quarters.length - 1) s.elements.push(line(x + 170, 296, x + 208, 296, { borderColor: "#94a3b8", borderWidth: "2px" }));
|
|
287
|
+
});
|
|
288
|
+
return s;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function closeSlide() {
|
|
292
|
+
const s = slide("complete_ppt_close", "先用完整 PPT 验证效果,再继续拆工具入口", "下一轮建议重点优化模板库、组件丰富度和评分后的自动重排。");
|
|
293
|
+
s.background = "#071426";
|
|
294
|
+
s.elements.push(shape("rect", 0, 0, W, H, { fill: "#071426", borderWidth: "0px" }));
|
|
295
|
+
s.elements.push(text("结论", 74, 72, 160, 34, { fontSize: 24, bold: true, color: "#12a594" }));
|
|
296
|
+
s.elements.push(text("这个 MCP 的价值不是一次性生成 PPT,而是让 agent 拥有持续打磨 PPT 的手。", 74, 140, 760, 118, { fontSize: 45, bold: true, color: "#ffffff" }));
|
|
297
|
+
const items = [
|
|
298
|
+
"保留完整 JSON 源,后续可继续局部编辑",
|
|
299
|
+
"PPTX 安全版用于分发,渲染图用于视觉验收",
|
|
300
|
+
"质量报告指导下一轮拆分和优化",
|
|
301
|
+
];
|
|
302
|
+
items.forEach((item, i) => {
|
|
303
|
+
s.elements.push(shape("roundRect", 78, 306 + i * 58, 640, 38, { fill: "#13243a", borderColor: "#284461", color: "#dce8f7", fontSize: 18, bold: true }, item));
|
|
304
|
+
});
|
|
305
|
+
s.elements.push(shape("roundRect", 764, 342, 128, 128, { fill: "#12a594", borderWidth: "0px", color: "#ffffff", fontSize: 28, bold: true }, "NEXT\nITERATION"));
|
|
306
|
+
return s;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function main() {
|
|
310
|
+
const deck = defaultDeck({
|
|
311
|
+
deckId: "mcp_complete_ppt_preview",
|
|
312
|
+
title: "MCP 生产级 PPT 生成系统完整预览",
|
|
313
|
+
width: W,
|
|
314
|
+
height: H,
|
|
315
|
+
theme,
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
deck.slides.push(
|
|
319
|
+
coverSlide(),
|
|
320
|
+
problemSlide(),
|
|
321
|
+
architectureSlide(),
|
|
322
|
+
entrySlide(),
|
|
323
|
+
workflowSlide(),
|
|
324
|
+
qualitySlide(),
|
|
325
|
+
layoutSlide(),
|
|
326
|
+
motionSlide(),
|
|
327
|
+
roadmapSlide(),
|
|
328
|
+
closeSlide(),
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
deck.slides.forEach((s, index) => {
|
|
332
|
+
s.elements.forEach((element, elementIndex) => {
|
|
333
|
+
if (!element.groupId && element.type === "shape" && element.text) {
|
|
334
|
+
element.groupId = gid(`${index}_${elementIndex}`);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
applyMotionToDeck(deck, { strategy: "auto", maxAnimations: 10, replaceExisting: true });
|
|
340
|
+
await saveDeck(deck);
|
|
341
|
+
|
|
342
|
+
const jsonPath = path.join(MCP_EXPORT_DIR, "mcp-complete-ppt-preview.json");
|
|
343
|
+
await writeFile(jsonPath, JSON.stringify(deck, null, 2), "utf8");
|
|
344
|
+
|
|
345
|
+
const safe = createPptxSafeDeck(deck, {
|
|
346
|
+
deckId: "mcp_complete_ppt_preview_safe",
|
|
347
|
+
title: "MCP 生产级 PPT 生成系统完整预览",
|
|
348
|
+
});
|
|
349
|
+
const safeJsonPath = path.join(MCP_EXPORT_DIR, "mcp-complete-ppt-preview-safe.json");
|
|
350
|
+
await writeFile(safeJsonPath, JSON.stringify(safe.deck, null, 2), "utf8");
|
|
351
|
+
|
|
352
|
+
const pptxPath = await exportDeckToPptx(safe.deck, {
|
|
353
|
+
fileName: "mcp-complete-ppt-preview-safe.pptx",
|
|
354
|
+
subject: "Complete PPT preview generated through JVS PPT UI MCP flow",
|
|
355
|
+
});
|
|
356
|
+
const visual = await runPptxVisualRegression({ pptxPath, dpi: 96 });
|
|
357
|
+
const report = await generateDeckReviewReport(safe.deck, {
|
|
358
|
+
fileName: "mcp-complete-ppt-preview-review",
|
|
359
|
+
format: "both",
|
|
360
|
+
visualResult: visual,
|
|
361
|
+
artifacts: {
|
|
362
|
+
json: jsonPath,
|
|
363
|
+
safeJson: safeJsonPath,
|
|
364
|
+
pptx: pptxPath,
|
|
365
|
+
renderDir: visual.render.outputDir,
|
|
366
|
+
},
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
console.log(JSON.stringify({
|
|
370
|
+
deckId: deck.id,
|
|
371
|
+
slideCount: deck.slides.length,
|
|
372
|
+
jsonPath,
|
|
373
|
+
safeJsonPath,
|
|
374
|
+
pptxPath,
|
|
375
|
+
renderDir: visual.render.outputDir,
|
|
376
|
+
imagePaths: visual.render.imagePaths,
|
|
377
|
+
visualValid: visual.valid,
|
|
378
|
+
report,
|
|
379
|
+
}, null, 2));
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
main().catch((error) => {
|
|
383
|
+
console.error(error);
|
|
384
|
+
process.exitCode = 1;
|
|
385
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const JSZip = require('jszip');
|
|
3
|
+
|
|
4
|
+
async function main() {
|
|
5
|
+
const p = 'C:/Users/57087/Desktop/组合的元素7.pptx';
|
|
6
|
+
const zip = await JSZip.loadAsync(fs.readFileSync(p));
|
|
7
|
+
const xml = await zip.file('ppt/slides/slide1.xml')?.async('text');
|
|
8
|
+
|
|
9
|
+
// Find shape with id="315" and id="327"
|
|
10
|
+
for (const id of ['315', '327']) {
|
|
11
|
+
const regex = new RegExp('<p:(?:sp|cxnSp|grpSp)[\\s\\S]*?id="' + id + '"[\\s\\S]*?<\\/p:(?:sp|cxnSp|grpSp)>');
|
|
12
|
+
const m = xml.match(regex);
|
|
13
|
+
if (m) {
|
|
14
|
+
console.log(`=== Shape ${id} ===`);
|
|
15
|
+
console.log(m[0]);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
main();
|