jvs-ppt-ui 0.0.10 → 0.0.12
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/README.md +68 -1
- package/dist/lib/jvs-ppt-ui.es.js +21367 -21291
- package/dist/lib/jvs-ppt-ui.umd.js +85 -85
- package/dist/lib/style.css +1 -1
- 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
- package/src/App.vue +1 -1
- package/src/components/CanvasArea.vue +38 -16
- package/src/components/Header.vue +1587 -1564
- package/src/components/PptEditor.vue +5 -0
- package/src/components/SidebarLeft.vue +14 -3
- package/src/components/canvas/elements/CanvasShapeElement.vue +3 -3
- package/src/components/canvas/elements/CanvasTextElement.vue +4 -4
- package/src/composables/canvas/useCanvasShortcuts.ts +50 -1
- package/src/composables/canvas/useCanvasTable.ts +8 -3
- package/src/composables/canvas/useCanvasText.ts +7 -3
- package/src/composables/useSlides.ts +14 -5
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
3
|
+
|
|
4
|
+
const transport = new StdioClientTransport({
|
|
5
|
+
command: "node",
|
|
6
|
+
args: ["mcp/server.js"],
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const client = new Client({
|
|
10
|
+
name: "jvs-ppt-ui-mcp-smoke",
|
|
11
|
+
version: "0.1.0",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
await client.connect(transport);
|
|
15
|
+
|
|
16
|
+
const presetLayouts = await client.callTool({
|
|
17
|
+
name: "list_preset_layouts",
|
|
18
|
+
arguments: {},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const dslLayouts = await client.callTool({
|
|
22
|
+
name: "list_semantic_dsl_layouts",
|
|
23
|
+
arguments: {},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const pptRegistry = await client.callTool({
|
|
27
|
+
name: "get_ppt_registry",
|
|
28
|
+
arguments: {},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const recommendedLayouts = await client.callTool({
|
|
32
|
+
name: "recommend_dsl_layouts",
|
|
33
|
+
arguments: {
|
|
34
|
+
topic: "MCP 生产级 PPT 工作流",
|
|
35
|
+
purpose: "需要说明方案对比、数据证明和落地路线图",
|
|
36
|
+
hasData: true,
|
|
37
|
+
hasProcess: true,
|
|
38
|
+
hasCompare: true,
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const dslExamples = await client.callTool({
|
|
43
|
+
name: "list_dsl_examples",
|
|
44
|
+
arguments: { category: "industry" },
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const dslExample = await client.callTool({
|
|
48
|
+
name: "get_dsl_example",
|
|
49
|
+
arguments: { exampleId: "ind-fin", topic: "金融智能投研风控底座" },
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const dslOutline = await client.callTool({
|
|
53
|
+
name: "create_dsl_deck_outline",
|
|
54
|
+
arguments: {
|
|
55
|
+
topic: "MCP 生产级 PPT 工作流",
|
|
56
|
+
pageCount: 7,
|
|
57
|
+
styleVariant: "tech",
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const registryValidation = await client.callTool({
|
|
62
|
+
name: "validate_dsl_against_registry",
|
|
63
|
+
arguments: {
|
|
64
|
+
dsl: dslOutline.structuredContent.dsl,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const dslPreview = await client.callTool({
|
|
69
|
+
name: "preview_dsl_compile_result",
|
|
70
|
+
arguments: {
|
|
71
|
+
dsl: dslOutline.structuredContent.dsl,
|
|
72
|
+
styleVariant: "tech",
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const compiledDslDeck = await client.callTool({
|
|
77
|
+
name: "compile_dsl_to_deck",
|
|
78
|
+
arguments: {
|
|
79
|
+
title: "Compiled DSL Smoke Deck",
|
|
80
|
+
dsl: dslOutline.structuredContent.dsl,
|
|
81
|
+
styleVariant: "tech",
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const aiRequest = await client.callTool({
|
|
86
|
+
name: "build_ai_generation_request",
|
|
87
|
+
arguments: {
|
|
88
|
+
topic: "MCP 生产级 PPT 工作流",
|
|
89
|
+
theme: "科技蓝",
|
|
90
|
+
pageCount: 8,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const motionStrategies = await client.callTool({
|
|
95
|
+
name: "list_motion_strategies",
|
|
96
|
+
arguments: {},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const aiWorkflowPlan = await client.callTool({
|
|
100
|
+
name: "build_ai_workflow_plan",
|
|
101
|
+
arguments: {
|
|
102
|
+
topic: "MCP 生产级 PPT 工作流",
|
|
103
|
+
pageCount: 8,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const mockAiSsePayload = `data: {"data": ${JSON.stringify(dslOutline.structuredContent.dsl)}}\n\ndata: [DONE]\n`;
|
|
108
|
+
|
|
109
|
+
const normalizedAiDsl = await client.callTool({
|
|
110
|
+
name: "normalize_ai_dsl_payload",
|
|
111
|
+
arguments: {
|
|
112
|
+
payload: mockAiSsePayload,
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const preparedAiDsl = await client.callTool({
|
|
117
|
+
name: "prepare_ai_dsl_for_compile",
|
|
118
|
+
arguments: {
|
|
119
|
+
payload: mockAiSsePayload,
|
|
120
|
+
styleVariant: "tech",
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
const storyboardResult = await client.callTool({
|
|
125
|
+
name: "create_storyboard",
|
|
126
|
+
arguments: {
|
|
127
|
+
topic: "MCP 生产级 PPT 工作流",
|
|
128
|
+
audience: "产品和研发负责人",
|
|
129
|
+
objective: "说明为什么要用 storyboard 和 blueprint 驱动生成",
|
|
130
|
+
pattern: "product_plan",
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const storyboardDeck = await client.callTool({
|
|
135
|
+
name: "expand_storyboard_to_deck",
|
|
136
|
+
arguments: {
|
|
137
|
+
title: "Storyboard Smoke Deck",
|
|
138
|
+
storyboard: storyboardResult.structuredContent.storyboard,
|
|
139
|
+
theme: {
|
|
140
|
+
fontFamily: "Microsoft YaHei",
|
|
141
|
+
fontColor: "#111827",
|
|
142
|
+
backgroundColor: "#ffffff",
|
|
143
|
+
themeColor: ["#2563eb", "#0f766e", "#f59e0b", "#dc2626", "#7c3aed"],
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
const storyboardValidation = await client.callTool({
|
|
149
|
+
name: "validate_deck",
|
|
150
|
+
arguments: { deckId: storyboardDeck.structuredContent.deckId },
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const created = await client.callTool({
|
|
154
|
+
name: "create_deck",
|
|
155
|
+
arguments: { title: "MCP 分步操作演示", withTitleSlide: false },
|
|
156
|
+
});
|
|
157
|
+
const deckId = created.structuredContent.deckId;
|
|
158
|
+
|
|
159
|
+
const presetSlide = await client.callTool({
|
|
160
|
+
name: "add_slide_from_preset_layout",
|
|
161
|
+
arguments: {
|
|
162
|
+
deckId,
|
|
163
|
+
layoutId: "compare",
|
|
164
|
+
title: "被恢复的比较版式入口",
|
|
165
|
+
leftText: "<b>一次性生成</b><br><br>• 难以控制质量<br>• 不便局部返工<br>• 结构不透明",
|
|
166
|
+
rightText: "<b>分步操作</b><br><br>• 可按规范迭代<br>• 可局部调整<br>• agent 可继续完善",
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const slide1 = await client.callTool({
|
|
171
|
+
name: "add_slide",
|
|
172
|
+
arguments: { deckId, title: "分步创建第一页", subtitle: "每个动作都是独立 MCP tool", notes: "Smoke test generated slide." },
|
|
173
|
+
});
|
|
174
|
+
const slideId = slide1.structuredContent.slideId;
|
|
175
|
+
|
|
176
|
+
const title = await client.callTool({
|
|
177
|
+
name: "add_text",
|
|
178
|
+
arguments: {
|
|
179
|
+
deckId,
|
|
180
|
+
slide: slideId,
|
|
181
|
+
content: "Codex 可以像编辑器一样逐步操作 PPT",
|
|
182
|
+
style: { left: "8%", top: "18%", width: "84%", height: "70px", fontSize: "40px", fontWeight: "bold", textAlign: "center" },
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await client.callTool({
|
|
187
|
+
name: "add_shape",
|
|
188
|
+
arguments: {
|
|
189
|
+
deckId,
|
|
190
|
+
slide: slideId,
|
|
191
|
+
shape: "roundRect",
|
|
192
|
+
text: "新增、修改、移动、导出都拆成独立入口",
|
|
193
|
+
style: { left: "18%", top: "42%", width: "64%", height: "110px", fill: "#2563eb", borderWidth: "0px", color: "#ffffff", fontSize: "22px" },
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const component = await client.callTool({
|
|
198
|
+
name: "add_component",
|
|
199
|
+
arguments: {
|
|
200
|
+
deckId,
|
|
201
|
+
slide: slideId,
|
|
202
|
+
type: "metric_cards",
|
|
203
|
+
props: {
|
|
204
|
+
items: [
|
|
205
|
+
{ label: "操作入口", value: "25+", note: "细粒度 tools" },
|
|
206
|
+
{ label: "组件", value: "7", note: "可编辑组合" },
|
|
207
|
+
{ label: "动效", value: "3类", note: "进入/退出/强调" },
|
|
208
|
+
],
|
|
209
|
+
accentColor: "#0f766e",
|
|
210
|
+
fillColor: "#ecfdf5",
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const snapshot = await client.callTool({
|
|
216
|
+
name: "create_snapshot",
|
|
217
|
+
arguments: { deckId, label: "before layout/theme operations" },
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
await client.callTool({
|
|
221
|
+
name: "replace_component_content",
|
|
222
|
+
arguments: {
|
|
223
|
+
deckId,
|
|
224
|
+
slide: slideId,
|
|
225
|
+
groupId: component.structuredContent.groupId,
|
|
226
|
+
replacements: {
|
|
227
|
+
"25+": "35+",
|
|
228
|
+
"7": "7类",
|
|
229
|
+
"3类": "3类动效",
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
await client.callTool({
|
|
235
|
+
name: "apply_theme",
|
|
236
|
+
arguments: {
|
|
237
|
+
deckId,
|
|
238
|
+
slide: slideId,
|
|
239
|
+
query: { groupId: component.structuredContent.groupId },
|
|
240
|
+
themeName: "calm_green",
|
|
241
|
+
scope: "selection",
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
await client.callTool({
|
|
246
|
+
name: "offset_elements",
|
|
247
|
+
arguments: {
|
|
248
|
+
deckId,
|
|
249
|
+
slide: slideId,
|
|
250
|
+
query: { groupId: component.structuredContent.groupId },
|
|
251
|
+
dy: 95,
|
|
252
|
+
},
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
await client.callTool({
|
|
256
|
+
name: "scale_group",
|
|
257
|
+
arguments: {
|
|
258
|
+
deckId,
|
|
259
|
+
slide: slideId,
|
|
260
|
+
query: { groupId: component.structuredContent.groupId },
|
|
261
|
+
scaleX: 0.92,
|
|
262
|
+
scaleY: 0.92,
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
await client.callTool({
|
|
267
|
+
name: "apply_animation_preset",
|
|
268
|
+
arguments: {
|
|
269
|
+
deckId,
|
|
270
|
+
slide: slideId,
|
|
271
|
+
groupId: component.structuredContent.groupId,
|
|
272
|
+
preset: "rise_sequence",
|
|
273
|
+
trigger: "click",
|
|
274
|
+
duration: 700,
|
|
275
|
+
staggerMs: 90,
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
await client.callTool({
|
|
280
|
+
name: "set_slide_transition",
|
|
281
|
+
arguments: { deckId, slide: slideId, transition: "fade" },
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const dslSlide = await client.callTool({
|
|
285
|
+
name: "add_slide_from_dsl",
|
|
286
|
+
arguments: {
|
|
287
|
+
deckId,
|
|
288
|
+
schema: {
|
|
289
|
+
id: "smoke-dsl-chart",
|
|
290
|
+
title: "DSL 编译落地入口",
|
|
291
|
+
subtitle: "语义 DSL 可以直接变成真实可编辑元素",
|
|
292
|
+
layout: "chart-list",
|
|
293
|
+
styleVariant: "tech",
|
|
294
|
+
chart: {
|
|
295
|
+
labels: ["规范", "编译", "微调", "校验"],
|
|
296
|
+
datasets: [{ label: "完成度", data: [86, 78, 72, 91] }],
|
|
297
|
+
listTitle: "能力闭环",
|
|
298
|
+
listItems: [
|
|
299
|
+
{ title: "语义规范", desc: "先用布局 DSL 约束内容结构。" },
|
|
300
|
+
{ title: "真实落页", desc: "编译后可继续元素级编辑。" },
|
|
301
|
+
{ title: "质量检查", desc: "再进入评分、修复和导出。" },
|
|
302
|
+
],
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
const motionPreview = await client.callTool({
|
|
309
|
+
name: "preview_motion_timeline",
|
|
310
|
+
arguments: {
|
|
311
|
+
deckId,
|
|
312
|
+
slide: dslSlide.structuredContent.slideId,
|
|
313
|
+
strategy: "data_story",
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const appliedMotion = await client.callTool({
|
|
318
|
+
name: "apply_motion_strategy",
|
|
319
|
+
arguments: {
|
|
320
|
+
deckId,
|
|
321
|
+
slide: dslSlide.structuredContent.slideId,
|
|
322
|
+
strategy: "data_story",
|
|
323
|
+
replaceExisting: true,
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const motionInspection = await client.callTool({
|
|
328
|
+
name: "inspect_motion_timeline",
|
|
329
|
+
arguments: {
|
|
330
|
+
deckId,
|
|
331
|
+
slide: dslSlide.structuredContent.slideId,
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
const motionValidation = await client.callTool({
|
|
336
|
+
name: "validate_motion_timeline",
|
|
337
|
+
arguments: {
|
|
338
|
+
deckId,
|
|
339
|
+
slide: dslSlide.structuredContent.slideId,
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
const appendedDsl = await client.callTool({
|
|
344
|
+
name: "append_dsl_slides",
|
|
345
|
+
arguments: {
|
|
346
|
+
deckId,
|
|
347
|
+
dsl: [
|
|
348
|
+
{
|
|
349
|
+
id: "smoke-dsl-timeline",
|
|
350
|
+
title: "后续优化顺序",
|
|
351
|
+
subtitle: "从编译落地到评分修复",
|
|
352
|
+
layout: "timeline",
|
|
353
|
+
styleVariant: "tech",
|
|
354
|
+
steps: [
|
|
355
|
+
{ num: "01", title: "编译落地", desc: "将 DSL 转为真实 slide。" },
|
|
356
|
+
{ num: "02", title: "设计评分", desc: "判断页面是否达标。" },
|
|
357
|
+
{ num: "03", title: "自动修复", desc: "按问题类型调整布局。" },
|
|
358
|
+
{ num: "04", title: "动效编排", desc: "按叙事节奏补充动画。" },
|
|
359
|
+
],
|
|
360
|
+
},
|
|
361
|
+
],
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const deckMotion = await client.callTool({
|
|
366
|
+
name: "apply_motion_strategy_to_deck",
|
|
367
|
+
arguments: {
|
|
368
|
+
deckId,
|
|
369
|
+
strategy: "auto",
|
|
370
|
+
replaceExisting: false,
|
|
371
|
+
maxAnimations: 8,
|
|
372
|
+
},
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
const stats = await client.callTool({
|
|
376
|
+
name: "get_deck_stats",
|
|
377
|
+
arguments: { deckId },
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
const layout = await client.callTool({
|
|
381
|
+
name: "inspect_slide_layout",
|
|
382
|
+
arguments: { deckId, slide: slideId, query: { groupId: component.structuredContent.groupId } },
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const slideDesignScore = await client.callTool({
|
|
386
|
+
name: "score_slide_design",
|
|
387
|
+
arguments: { deckId, slide: slideId },
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
const deckDesignScore = await client.callTool({
|
|
391
|
+
name: "score_deck_design",
|
|
392
|
+
arguments: { deckId },
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
const deckPagesDesignScore = await client.callTool({
|
|
396
|
+
name: "score_deck_pages_design",
|
|
397
|
+
arguments: { deckId },
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
const slideSuggestions = await client.callTool({
|
|
401
|
+
name: "suggest_slide_improvements",
|
|
402
|
+
arguments: { deckId, slide: slideId },
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const autoFixResult = await client.callTool({
|
|
406
|
+
name: "auto_fix_slide_layout",
|
|
407
|
+
arguments: { deckId, slide: slideId },
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
const validation = await client.callTool({
|
|
411
|
+
name: "validate_deck",
|
|
412
|
+
arguments: { deckId },
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
await client.callTool({
|
|
416
|
+
name: "move_element",
|
|
417
|
+
arguments: { deckId, elementId: title.structuredContent.elementId, top: "16%" },
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const exportedJson = await client.callTool({
|
|
421
|
+
name: "export_deck_json",
|
|
422
|
+
arguments: { deckId, fileName: "mcp-smoke-test.json" },
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const exportCapabilities = await client.callTool({
|
|
426
|
+
name: "get_pptx_export_capabilities",
|
|
427
|
+
arguments: {},
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const exportCompatibility = await client.callTool({
|
|
431
|
+
name: "inspect_pptx_export_compatibility",
|
|
432
|
+
arguments: { deckId },
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
const exportPlan = await client.callTool({
|
|
436
|
+
name: "create_pptx_export_plan",
|
|
437
|
+
arguments: { deckId, strategy: "balanced" },
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
const safePreview = await client.callTool({
|
|
441
|
+
name: "preview_pptx_safe_deck",
|
|
442
|
+
arguments: { deckId },
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
const safeDeck = await client.callTool({
|
|
446
|
+
name: "create_pptx_safe_deck",
|
|
447
|
+
arguments: {
|
|
448
|
+
deckId,
|
|
449
|
+
newDeckId: `${deckId}_safe_smoke`,
|
|
450
|
+
},
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
const exportedSafePptx = await client.callTool({
|
|
454
|
+
name: "export_pptx_safe_deck",
|
|
455
|
+
arguments: { deckId, fileName: "mcp-smoke-test-safe.pptx" },
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const renderedSafePptx = await client.callTool({
|
|
459
|
+
name: "render_pptx_to_images",
|
|
460
|
+
arguments: { pptxPath: exportedSafePptx.structuredContent.outputPath, dpi: 96 },
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
const renderedInspection = await client.callTool({
|
|
464
|
+
name: "inspect_rendered_images",
|
|
465
|
+
arguments: { imagePaths: renderedSafePptx.structuredContent.render.imagePaths },
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
const visualRegression = await client.callTool({
|
|
469
|
+
name: "run_pptx_visual_regression",
|
|
470
|
+
arguments: { pptxPath: exportedSafePptx.structuredContent.outputPath, dpi: 96 },
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
const exportedPptxWithReport = await client.callTool({
|
|
474
|
+
name: "export_deck_pptx_with_report",
|
|
475
|
+
arguments: { deckId, fileName: "mcp-smoke-test-with-report.pptx", strategy: "balanced", useSafeDeck: true },
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
const safeVisualExport = await client.callTool({
|
|
479
|
+
name: "export_pptx_safe_deck_with_visual_check",
|
|
480
|
+
arguments: { deckId, fileName: "mcp-smoke-test-safe-visual.pptx", dpi: 96 },
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const reviewReport = await client.callTool({
|
|
484
|
+
name: "generate_deck_review_report",
|
|
485
|
+
arguments: {
|
|
486
|
+
deckId,
|
|
487
|
+
fileName: "mcp-smoke-test-review",
|
|
488
|
+
format: "both",
|
|
489
|
+
imagePaths: renderedSafePptx.structuredContent.render.imagePaths,
|
|
490
|
+
artifacts: {
|
|
491
|
+
json: exportedJson.structuredContent.outputPath,
|
|
492
|
+
safePptx: exportedSafePptx.structuredContent.outputPath,
|
|
493
|
+
pptxWithReport: exportedPptxWithReport.structuredContent.outputPath,
|
|
494
|
+
safeVisualPptx: safeVisualExport.structuredContent.outputPath,
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
const exportedPptx = await client.callTool({
|
|
500
|
+
name: "export_deck_pptx",
|
|
501
|
+
arguments: { deckId, fileName: "mcp-smoke-test.pptx" },
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
console.log(JSON.stringify({
|
|
505
|
+
deckId,
|
|
506
|
+
storyboardDeckId: storyboardDeck.structuredContent.deckId,
|
|
507
|
+
presetLayoutCount: presetLayouts.structuredContent.layouts.length,
|
|
508
|
+
registryVersion: pptRegistry.structuredContent.registry.version,
|
|
509
|
+
dslLayoutCount: dslLayouts.structuredContent.layouts.length,
|
|
510
|
+
recommendedLayouts: recommendedLayouts.structuredContent.layouts.map((item) => item.layout),
|
|
511
|
+
industryExampleCount: dslExamples.structuredContent.examples.length,
|
|
512
|
+
exampleSlides: dslExample.structuredContent.example.dsl.length,
|
|
513
|
+
dslOutlineSlides: dslOutline.structuredContent.dsl.length,
|
|
514
|
+
registryDslValid: registryValidation.structuredContent.valid,
|
|
515
|
+
registryIssueCount: registryValidation.structuredContent.issues.length,
|
|
516
|
+
dslPreview: dslPreview.structuredContent.preview,
|
|
517
|
+
compiledDslDeckId: compiledDslDeck.structuredContent.deckId,
|
|
518
|
+
compiledDslSlideCount: compiledDslDeck.structuredContent.slideCount,
|
|
519
|
+
aiEndpoint: aiRequest.structuredContent.request.endpoint,
|
|
520
|
+
aiWorkflowSteps: aiWorkflowPlan.structuredContent.plan.steps.length,
|
|
521
|
+
normalizedAiSlides: normalizedAiDsl.structuredContent.result.slideCount,
|
|
522
|
+
preparedAiReady: preparedAiDsl.structuredContent.result.ready,
|
|
523
|
+
motionStrategyCount: motionStrategies.structuredContent.strategies.length,
|
|
524
|
+
motionPreviewCount: motionPreview.structuredContent.timeline.animations.length,
|
|
525
|
+
appliedMotionCount: appliedMotion.structuredContent.result.animationCount,
|
|
526
|
+
motionValid: motionValidation.structuredContent.validation.valid,
|
|
527
|
+
motionInspectionCount: motionInspection.structuredContent.report.animationCount,
|
|
528
|
+
deckMotionAnimations: deckMotion.structuredContent.result.animationCount,
|
|
529
|
+
presetSlideId: presetSlide.structuredContent.slideId,
|
|
530
|
+
dslSlideId: dslSlide.structuredContent.slideId,
|
|
531
|
+
appendedDsl: appendedDsl.structuredContent.inserted,
|
|
532
|
+
storyboardValid: storyboardValidation.structuredContent.valid,
|
|
533
|
+
snapshotId: snapshot.structuredContent.snapshotId,
|
|
534
|
+
stats: stats.structuredContent.stats,
|
|
535
|
+
layout: layout.structuredContent.report,
|
|
536
|
+
slideDesignScore: slideDesignScore.structuredContent.score.score,
|
|
537
|
+
deckDesignScore: deckDesignScore.structuredContent.score.score,
|
|
538
|
+
deckPageScoreCount: deckPagesDesignScore.structuredContent.report.slides.length,
|
|
539
|
+
deckMinPageScore: Math.min(...deckPagesDesignScore.structuredContent.report.slides.map((slide) => slide.score)),
|
|
540
|
+
suggestionCount: slideSuggestions.structuredContent.suggestions.length,
|
|
541
|
+
autoFixChanged: autoFixResult.structuredContent.result.changed,
|
|
542
|
+
valid: validation.structuredContent.valid,
|
|
543
|
+
exportSupportedCount: exportCapabilities.structuredContent.capabilities.supported.length,
|
|
544
|
+
exportRiskLevel: exportCompatibility.structuredContent.report.riskLevel,
|
|
545
|
+
exportPlanReady: exportPlan.structuredContent.plan.ready,
|
|
546
|
+
exportReportWarnings: exportCompatibility.structuredContent.report.stats.warningCount,
|
|
547
|
+
safePreviewChanges: safePreview.structuredContent.preview.changeCount,
|
|
548
|
+
safePreviewAfterWarnings: safePreview.structuredContent.preview.afterWarnings,
|
|
549
|
+
safeDeckId: safeDeck.structuredContent.deckId,
|
|
550
|
+
safeDeckWarnings: safeDeck.structuredContent.report.stats.warningCount,
|
|
551
|
+
json: exportedJson.structuredContent.outputPath,
|
|
552
|
+
safePptx: exportedSafePptx.structuredContent.outputPath,
|
|
553
|
+
safePptxChanges: exportedSafePptx.structuredContent.changeCount,
|
|
554
|
+
renderedPageCount: renderedSafePptx.structuredContent.render.pageCount,
|
|
555
|
+
renderedValid: renderedInspection.structuredContent.inspection.valid,
|
|
556
|
+
visualRegressionValid: visualRegression.structuredContent.result.valid,
|
|
557
|
+
visualRegressionIssues: visualRegression.structuredContent.result.inspection.issues.length,
|
|
558
|
+
pptxWithReport: exportedPptxWithReport.structuredContent.outputPath,
|
|
559
|
+
pptxWithReportUsedSafe: exportedPptxWithReport.structuredContent.safeDeck.used,
|
|
560
|
+
safeVisualPptx: safeVisualExport.structuredContent.outputPath,
|
|
561
|
+
safeVisualValid: safeVisualExport.structuredContent.visual.valid,
|
|
562
|
+
reviewMarkdown: reviewReport.structuredContent.report.markdownPath,
|
|
563
|
+
reviewHtml: reviewReport.structuredContent.report.htmlPath,
|
|
564
|
+
reviewDesignScore: reviewReport.structuredContent.report.summary.designScore,
|
|
565
|
+
pptx: exportedPptx.structuredContent.outputPath,
|
|
566
|
+
}, null, 2));
|
|
567
|
+
|
|
568
|
+
await client.close();
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function patchFile(relPath) {
|
|
5
|
+
const filePath = path.resolve(__dirname, '..', relPath);
|
|
6
|
+
if (!fs.existsSync(filePath)) return;
|
|
7
|
+
let code = fs.readFileSync(filePath, 'utf8');
|
|
8
|
+
let modified = false;
|
|
9
|
+
|
|
10
|
+
// 1. Patch getHorizontalAlign to inherit from lstStyle & defPPr
|
|
11
|
+
if (!code.includes('algn = getAlignFromTextNode(pNode, lvlStr);')) {
|
|
12
|
+
const target = `function getHorizontalAlign(node, pNode, type, slideLayoutSpNode, slideMasterSpNode, warpObj) {
|
|
13
|
+
var algn = getTextByPathList(node, ['a:pPr', 'attrs', 'algn']);
|
|
14
|
+
if (!algn) algn = getTextByPathList(pNode, ['p:txBody', 'a:p', 'a:pPr', 'attrs', 'algn']);
|
|
15
|
+
if (!algn) {
|
|
16
|
+
var lvlIdx = getParagraphLevel(node);
|
|
17
|
+
var lvlStr = 'a:lvl' + lvlIdx + 'pPr';
|
|
18
|
+
algn = getAlignFromTextNode(slideLayoutSpNode, lvlStr);`;
|
|
19
|
+
|
|
20
|
+
const replacement = `function getHorizontalAlign(node, pNode, type, slideLayoutSpNode, slideMasterSpNode, warpObj) {
|
|
21
|
+
var algn = getTextByPathList(node, ['a:pPr', 'attrs', 'algn']);
|
|
22
|
+
if (!algn) algn = getTextByPathList(pNode, ['p:txBody', 'a:p', 'a:pPr', 'attrs', 'algn']);
|
|
23
|
+
if (!algn) {
|
|
24
|
+
var lvlIdx = getParagraphLevel(node);
|
|
25
|
+
var lvlStr = 'a:lvl' + lvlIdx + 'pPr';
|
|
26
|
+
algn = getAlignFromTextNode(pNode, lvlStr);
|
|
27
|
+
if (!algn) algn = getTextByPathList(pNode, ['p:txBody', 'a:lstStyle', 'a:defPPr', 'attrs', 'algn']);
|
|
28
|
+
if (!algn) algn = getAlignFromTextNode(slideLayoutSpNode, lvlStr);`;
|
|
29
|
+
|
|
30
|
+
if (code.includes(target)) {
|
|
31
|
+
code = code.replace(target, replacement);
|
|
32
|
+
modified = true;
|
|
33
|
+
console.log(`[patch-pptxtojson] Patched getHorizontalAlign in ${relPath}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 2. Patch lineCap to read lineNode.attrs.cap (e.g. cap="rnd")
|
|
38
|
+
const capTarget = `var capNode = getTextByPathList(lineNode, ['a:cap', 'attrs']);
|
|
39
|
+
var lineCap = capNode ? capNode['val'] : 'flat';`;
|
|
40
|
+
|
|
41
|
+
const capReplacement = `var capNode = getTextByPathList(lineNode, ['a:cap', 'attrs']);
|
|
42
|
+
var capVal = (lineNode && lineNode['attrs'] && lineNode['attrs']['cap']) || (capNode ? capNode['val'] : 'flat');
|
|
43
|
+
var lineCap = 'flat';
|
|
44
|
+
if (capVal === 'rnd' || capVal === 'round') lineCap = 'round';
|
|
45
|
+
else if (capVal === 'sq' || capVal === 'square') lineCap = 'square';
|
|
46
|
+
else if (capVal === 'flat') lineCap = 'flat';`;
|
|
47
|
+
|
|
48
|
+
if (code.includes(capTarget)) {
|
|
49
|
+
code = code.replace(capTarget, capReplacement);
|
|
50
|
+
modified = true;
|
|
51
|
+
console.log(`[patch-pptxtojson] Patched lineCap (support cap="rnd") in ${relPath}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 3. Patch shapePie to use parseFloat(H) instead of parseInt(H) to avoid distorting circular arcs
|
|
55
|
+
const pieTarget = `function shapePie(H, w, adj1, adj2, isClose) {
|
|
56
|
+
var pieVal = parseFloat(adj2);
|
|
57
|
+
var piAngle = parseFloat(adj1);
|
|
58
|
+
var size = parseInt(H);`;
|
|
59
|
+
|
|
60
|
+
const pieReplacement = `function shapePie(H, w, adj1, adj2, isClose) {
|
|
61
|
+
var pieVal = parseFloat(adj2);
|
|
62
|
+
var piAngle = parseFloat(adj1);
|
|
63
|
+
var size = parseFloat(H);`;
|
|
64
|
+
|
|
65
|
+
if (code.includes(pieTarget)) {
|
|
66
|
+
code = code.replace(pieTarget, pieReplacement);
|
|
67
|
+
modified = true;
|
|
68
|
+
console.log(`[patch-pptxtojson] Patched shapePie (use parseFloat(H)) in ${relPath}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (modified) {
|
|
72
|
+
fs.writeFileSync(filePath, code, 'utf8');
|
|
73
|
+
console.log(`[patch-pptxtojson] Saved updates to ${relPath}`);
|
|
74
|
+
} else {
|
|
75
|
+
console.log(`[patch-pptxtojson] All patches already applied or targets not found in ${relPath}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
patchFile('node_modules/jvs-pptxtojson/dist/index.js');
|
|
80
|
+
patchFile('node_modules/jvs-pptxtojson/dist/index.cjs');
|
|
81
|
+
patchFile('node_modules/jvs-pptxtojson/dist/index.umd.js');
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const css = fs.readFileSync('public/jvs-ppt-ui/fonts/fonts.css', 'utf8')
|
|
4
|
+
const urls = [...css.matchAll(/url\('([^']+)'\)/g)].map(m => m[1])
|
|
5
|
+
let missing = 0
|
|
6
|
+
for (const u of urls) {
|
|
7
|
+
const p = path.join('public', decodeURIComponent(u.replace(/^\//, '')))
|
|
8
|
+
if (!fs.existsSync(p)) { missing++; console.log('MISSING', u) }
|
|
9
|
+
}
|
|
10
|
+
console.log('total refs:', urls.length, 'missing:', missing)
|
|
11
|
+
const woff2s = [];
|
|
12
|
+
(function walk(d) {
|
|
13
|
+
for (const n of fs.readdirSync(d)) {
|
|
14
|
+
const f = path.join(d, n)
|
|
15
|
+
fs.statSync(f).isDirectory() ? walk(f) : f.endsWith('.woff2') && woff2s.push(f)
|
|
16
|
+
}
|
|
17
|
+
})('public/jvs-ppt-ui/fonts')
|
|
18
|
+
let sum = 0
|
|
19
|
+
for (const f of woff2s) sum += fs.statSync(f).size
|
|
20
|
+
console.log('woff2 files:', woff2s.length, 'total:', (sum / 1024 / 1024).toFixed(2) + 'MB')
|
package/src/App.vue
CHANGED
|
@@ -46,7 +46,7 @@ async function handleCustomUpload(fileOrBlob: File | Blob, context: UploadResour
|
|
|
46
46
|
<template>
|
|
47
47
|
<!-- :custom-upload="handleCustomUpload" -->
|
|
48
48
|
<PptEditor ref="editorRef" :custom-upload="handleCustomUpload" :enable-mcp="true" :enable-ai="true"
|
|
49
|
-
ai-api-url="/jvs-dam/api/ppt/ai-replace" :enable-material="true"
|
|
49
|
+
ai-api-url="/jvs-dam/api/ppt/ai-replace" :enable-material="true" :readonly="true" :show-sidebar-right="false"
|
|
50
50
|
material-tree-api-url="/jvs-dam/api/dam/material/tree" material-page-api-url="/jvs-dam/api/dam/material/page"
|
|
51
51
|
material-api-url="/jvs-dam/api/dam/material/tree" material-save-api-url="/jvs-dam/api/dam/material/save"
|
|
52
52
|
@save="handleSave" @ready="handleReady" />
|