dsh-ppt-studio 1.1.0

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.
Files changed (179) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +480 -0
  3. package/agent-presets/ppt/agent.cordis.yml +308 -0
  4. package/agent-presets/ppt/preset.yml +3 -0
  5. package/cordis.patch.yml +29 -0
  6. package/docs/00-/350/256/276/350/256/241/346/272/220.md +537 -0
  7. package/docs/01-/351/234/200/346/261/202/344/270/216/347/233/256/346/240/207.md +136 -0
  8. package/docs/02-/346/212/200/346/234/257/346/212/245/345/221/212.md +483 -0
  9. package/docs/03-/346/233/264/346/226/260/346/227/245/345/277/227.md +2898 -0
  10. package/docs/04-/350/267/257/347/272/277/345/233/276/344/270/216/351/207/214/347/250/213/347/242/221.md +113 -0
  11. package/docs/05-/350/277/255/344/273/243/346/265/201/347/250/213.md +210 -0
  12. package/docs/06-/350/257/204/345/256/241/344/270/216/346/265/213/350/257/225.md +296 -0
  13. package/docs/fidelity-report.md +225 -0
  14. package/examples/fx/fx-mini/deck.yaml +39 -0
  15. package/examples/fx/fx-mini/pages/01.yaml +6 -0
  16. package/examples/fx/fx-mini/pages/02.yaml +2 -0
  17. package/examples/fx/fx-mini/pages/03.yaml +6 -0
  18. package/examples/fx/fx-pro/deck.yaml +48 -0
  19. package/examples/fx/fx-pro/pages/01_cover.yaml +40 -0
  20. package/examples/fx/fx-pro/pages/02_arch.yaml +55 -0
  21. package/examples/fx/fx-pro/pages/03_table.yaml +20 -0
  22. package/examples/fx/fx-pro/pages/04_chart.yaml +40 -0
  23. package/examples/fx/fx-pro/pages/05_timeline.yaml +71 -0
  24. package/examples/fx/fx-pro/pages/06_longtext.yaml +13 -0
  25. package/examples/fx/fx-pro/pages/07_safearea.yaml +33 -0
  26. package/examples/fx/fx-pro/pages/08_contrast.yaml +22 -0
  27. package/examples/fx/fx-pro/pages/09_custgeom.yaml +32 -0
  28. package/examples/fx/fx-pro/pages/10_numbers.yaml +25 -0
  29. package/examples/fx/fx-pro/pages/11_section.yaml +20 -0
  30. package/examples/fx/fx-pro/pages/12_summary.yaml +57 -0
  31. package/examples/fx/fx-pro/preview/01_pages_01_cover.html +1 -0
  32. package/examples/fx/fx-pro/preview/02_pages_02_arch.html +3 -0
  33. package/examples/fx/fx-pro/preview/03_pages_03_table.html +1 -0
  34. package/examples/fx/fx-pro/preview/04_pages_04_chart.html +3 -0
  35. package/examples/fx/fx-pro/preview/05_pages_05_timeline.html +2 -0
  36. package/examples/fx/fx-pro/preview/06_pages_06_longtext.html +4 -0
  37. package/examples/fx/fx-pro/preview/07_pages_07_safearea.html +2 -0
  38. package/examples/fx/fx-pro/preview/08_pages_08_contrast.html +1 -0
  39. package/examples/fx/fx-pro/preview/09_pages_09_custgeom.html +1 -0
  40. package/examples/fx/fx-pro/preview/10_pages_10_numbers.html +1 -0
  41. package/examples/fx/fx-pro/preview/11_pages_11_section.html +1 -0
  42. package/examples/fx/fx-pro/preview/12_pages_12_summary.html +1 -0
  43. package/examples/fx/fx-pro/preview/deck.html +10 -0
  44. package/examples/fx/fx-pro/preview/layout.json +2325 -0
  45. package/examples/fx/fx-pro/preview/measured.json +814 -0
  46. package/examples/fx/fx-pro.pptx +0 -0
  47. package/examples/fx/ren-single/01.png +0 -0
  48. package/examples/fx/ren-spliced/01.png +0 -0
  49. package/examples/fx/ren-spliced/03.png +0 -0
  50. package/examples/fx/seed.pptx +0 -0
  51. package/examples/fx/single.pptx +0 -0
  52. package/examples/fx/spliced.pptx +0 -0
  53. package/examples/smoke/deck.yaml +26 -0
  54. package/examples/smoke/imported/deck.yaml +30 -0
  55. package/examples/smoke/imported/import-styles.json +126 -0
  56. package/examples/smoke/imported/pages/slide_01.yaml +31 -0
  57. package/examples/smoke/imported/pages/slide_02.yaml +38 -0
  58. package/examples/smoke/imported/pages/slide_03.yaml +55 -0
  59. package/examples/smoke/imported/preview-rt/01_pages_slide_01.html +1 -0
  60. package/examples/smoke/imported/preview-rt/02_pages_slide_02.html +1 -0
  61. package/examples/smoke/imported/preview-rt/03_pages_slide_03.html +1 -0
  62. package/examples/smoke/imported/preview-rt/deck.html +1 -0
  63. package/examples/smoke/imported/preview-rt/layout.json +434 -0
  64. package/examples/smoke/imported/reference/previews/01.png +0 -0
  65. package/examples/smoke/imported/reference/previews/02.png +0 -0
  66. package/examples/smoke/imported/reference/previews/03.png +0 -0
  67. package/examples/smoke/imported/source.pptx +0 -0
  68. package/examples/smoke/media-test.png +0 -0
  69. package/examples/smoke/out-smoke.pptx +0 -0
  70. package/examples/smoke/pages/01_cover.yaml +27 -0
  71. package/examples/smoke/pages/02_content.yaml +42 -0
  72. package/examples/smoke/pages/03_chart.yaml +35 -0
  73. package/examples/smoke/preview/01_pages_01_cover.html +1 -0
  74. package/examples/smoke/preview/02_pages_02_content.html +1 -0
  75. package/examples/smoke/preview/03_pages_03_chart.html +1 -0
  76. package/examples/smoke/preview/deck.html +1 -0
  77. package/examples/smoke/preview/layout.json +378 -0
  78. package/lib/autodeclare.js +40 -0
  79. package/lib/commands.js +161 -0
  80. package/lib/crosscheck.js +300 -0
  81. package/lib/imgmeta.js +55 -0
  82. package/lib/index.js +239 -0
  83. package/lib/measurement.js +209 -0
  84. package/lib/msrender.js +123 -0
  85. package/lib/pptd/export-pptx.js +686 -0
  86. package/lib/pptd/import-pptx.js +774 -0
  87. package/lib/pptd/layout.js +209 -0
  88. package/lib/pptd/render-html.js +254 -0
  89. package/lib/pptd/schema.js +518 -0
  90. package/lib/pptd/svgCharts.js +147 -0
  91. package/lib/pptxPy.js +205 -0
  92. package/lib/preset-delivery.js +195 -0
  93. package/lib/preview-server.js +164 -0
  94. package/lib/router.js +165 -0
  95. package/lib/scaffold.js +315 -0
  96. package/lib/skill.js +174 -0
  97. package/lib/splice.js +219 -0
  98. package/lib/state.js +88 -0
  99. package/lib/surgical.js +369 -0
  100. package/lib/templates.js +400 -0
  101. package/lib/tools.js +955 -0
  102. package/lib/verify.js +828 -0
  103. package/lib/xmljs.js +112 -0
  104. package/lib/zips.js +117 -0
  105. package/package.json +81 -0
  106. package/scripts/audit-manual-facts.mjs +294 -0
  107. package/scripts/audit-template-fidelity.mjs +162 -0
  108. package/scripts/bench-m2.mjs +48 -0
  109. package/scripts/build-templates.mjs +63 -0
  110. package/scripts/build.mjs +14 -0
  111. package/scripts/build.sh +7 -0
  112. package/scripts/check-lib-freshness.mjs +70 -0
  113. package/scripts/check-preset.mjs +189 -0
  114. package/scripts/check-tracked-lf.mjs +84 -0
  115. package/scripts/dbg-import.mjs +13 -0
  116. package/scripts/dbg-media.mjs +10 -0
  117. package/scripts/dbg-tpl-inventory.mjs +23 -0
  118. package/scripts/dump-workflow-baseline.mjs +38 -0
  119. package/scripts/e2e-1.0.mjs +129 -0
  120. package/scripts/eval-skills-blind.mjs +91 -0
  121. package/scripts/eval-skills-score.mjs +129 -0
  122. package/scripts/export-orig-templates.ps1 +17 -0
  123. package/scripts/fixtures/workflow-baseline.json +300 -0
  124. package/scripts/fixtures.mjs +501 -0
  125. package/scripts/import-user-templates.mjs +60 -0
  126. package/scripts/install-desktop.mjs +312 -0
  127. package/scripts/install.mjs +209 -0
  128. package/scripts/migrate-templates-dualtrack.mjs +71 -0
  129. package/scripts/preflight-1.0.mjs +198 -0
  130. package/scripts/probe-asset-freshness.mjs +57 -0
  131. package/scripts/probe-effect-semantics.mjs +69 -0
  132. package/scripts/probe-reparse-safety.mjs +32 -0
  133. package/scripts/rebuild-templates-c11.mjs +69 -0
  134. package/scripts/regression-real.mjs +84 -0
  135. package/scripts/release-sync.mjs +425 -0
  136. package/scripts/shot-templates.mjs +49 -0
  137. package/scripts/smoke.mjs +2512 -0
  138. package/scripts/verify-bundle-install.mjs +281 -0
  139. package/scripts/verify-fresh-install.mjs +260 -0
  140. package/skills/ppt-studio-copy/SKILL.md +139 -0
  141. package/skills/ppt-studio-craft/SKILL.md +171 -0
  142. package/skills/ppt-studio-data/SKILL.md +173 -0
  143. package/skills/ppt-studio-manual/SKILL.md +104 -0
  144. package/templates/academic-white/deck.yaml +24 -0
  145. package/templates/academic-white/pages/_chart.yaml +43 -0
  146. package/templates/academic-white/pages/_compare.yaml +37 -0
  147. package/templates/academic-white/pages/_content.yaml +35 -0
  148. package/templates/academic-white/pages/_cover.yaml +28 -0
  149. package/templates/academic-white/pages/_ending.yaml +28 -0
  150. package/templates/academic-white/pages/_flow.yaml +61 -0
  151. package/templates/academic-white/preview.png +0 -0
  152. package/templates/academic-white/template.yaml +14 -0
  153. package/templates/business-blue/deck.yaml +28 -0
  154. package/templates/business-blue/pages/_chart.yaml +36 -0
  155. package/templates/business-blue/pages/_compare.yaml +45 -0
  156. package/templates/business-blue/pages/_content.yaml +80 -0
  157. package/templates/business-blue/pages/_cover.yaml +39 -0
  158. package/templates/business-blue/pages/_ending.yaml +32 -0
  159. package/templates/business-blue/pages/_flow.yaml +87 -0
  160. package/templates/business-blue/preview.png +0 -0
  161. package/templates/business-blue/template.yaml +14 -0
  162. package/templates/pitch-bold/deck.yaml +23 -0
  163. package/templates/pitch-bold/pages/_chart.yaml +32 -0
  164. package/templates/pitch-bold/pages/_compare.yaml +36 -0
  165. package/templates/pitch-bold/pages/_content.yaml +58 -0
  166. package/templates/pitch-bold/pages/_cover.yaml +31 -0
  167. package/templates/pitch-bold/pages/_ending.yaml +27 -0
  168. package/templates/pitch-bold/pages/_flow.yaml +46 -0
  169. package/templates/pitch-bold/preview.png +0 -0
  170. package/templates/pitch-bold/template.yaml +14 -0
  171. package/templates/tech-dark/deck.yaml +24 -0
  172. package/templates/tech-dark/pages/_chart.yaml +27 -0
  173. package/templates/tech-dark/pages/_compare.yaml +38 -0
  174. package/templates/tech-dark/pages/_content.yaml +51 -0
  175. package/templates/tech-dark/pages/_cover.yaml +30 -0
  176. package/templates/tech-dark/pages/_ending.yaml +25 -0
  177. package/templates/tech-dark/pages/_flow.yaml +80 -0
  178. package/templates/tech-dark/preview.png +0 -0
  179. package/templates/tech-dark/template.yaml +14 -0
@@ -0,0 +1,378 @@
1
+ {
2
+ "size": {
3
+ "width": 960,
4
+ "height": 540
5
+ },
6
+ "theme": {
7
+ "colors": {
8
+ "primary": "#2563EB",
9
+ "accent": "#F59E0B",
10
+ "text": "#1F2937",
11
+ "bg": "#F8FAFC"
12
+ },
13
+ "textStyles": {
14
+ "title": {
15
+ "fontSize": 40,
16
+ "color": "$text",
17
+ "bold": true
18
+ },
19
+ "body": {
20
+ "fontSize": 18,
21
+ "color": "$text",
22
+ "lineHeight": 1.5
23
+ },
24
+ "caption": {
25
+ "fontSize": 12,
26
+ "color": "#64748B"
27
+ }
28
+ },
29
+ "grid": {
30
+ "base": 8
31
+ },
32
+ "safeArea": null,
33
+ "minFontSize": null,
34
+ "themeConformance": "strict"
35
+ },
36
+ "pages": [
37
+ {
38
+ "index": 0,
39
+ "pageNo": 1,
40
+ "file": "pages/01_cover.yaml",
41
+ "name": "pages/01_cover",
42
+ "background": {
43
+ "type": "solid",
44
+ "color": "#F8FAFC"
45
+ },
46
+ "safeArea": {
47
+ "top": 0,
48
+ "bottom": 0,
49
+ "left": 0,
50
+ "right": 0
51
+ },
52
+ "expectedOverlaps": [],
53
+ "expectedOutOfSafeArea": [],
54
+ "contrastExempt": [],
55
+ "overlapMode": "declared",
56
+ "elements": [
57
+ {
58
+ "id": "hero-rect",
59
+ "kind": "shape",
60
+ "bounds": {
61
+ "x": 0,
62
+ "y": 0,
63
+ "w": 960,
64
+ "h": 120
65
+ },
66
+ "shape": "rect",
67
+ "fill": "#2563EB",
68
+ "rotation": 0
69
+ },
70
+ {
71
+ "id": "title",
72
+ "kind": "text",
73
+ "bounds": {
74
+ "x": 80,
75
+ "y": 180,
76
+ "w": 700,
77
+ "h": 90
78
+ },
79
+ "style": {
80
+ "fontSize": 40,
81
+ "color": "#1F2937",
82
+ "bold": true,
83
+ "lineHeight": 1.2,
84
+ "wrap": true
85
+ },
86
+ "metrics": {
87
+ "textW": 508.79999999999995,
88
+ "textH": 48,
89
+ "lines": 1,
90
+ "lineWidths": [
91
+ 508.79999999999995
92
+ ],
93
+ "overflowY": 0,
94
+ "overflowX": 0
95
+ },
96
+ "text": "季度复盘:增长引擎与风险"
97
+ },
98
+ {
99
+ "id": "subtitle",
100
+ "kind": "text",
101
+ "bounds": {
102
+ "x": 80,
103
+ "y": 290,
104
+ "w": 600,
105
+ "h": 60
106
+ },
107
+ "style": {
108
+ "fontSize": 18,
109
+ "color": "#F59E0B",
110
+ "lineHeight": 1.5,
111
+ "wrap": true
112
+ },
113
+ "metrics": {
114
+ "textW": 170.10000000000002,
115
+ "textH": 27,
116
+ "lines": 1,
117
+ "lineWidths": [
118
+ 148.5
119
+ ],
120
+ "overflowY": 0,
121
+ "overflowX": 0
122
+ },
123
+ "text": "2026 Q3 · 内部汇报"
124
+ },
125
+ {
126
+ "id": "deco",
127
+ "kind": "shape",
128
+ "bounds": {
129
+ "x": 780,
130
+ "y": 360,
131
+ "w": 120,
132
+ "h": 120
133
+ },
134
+ "shape": "ellipse",
135
+ "fill": "#F59E0B",
136
+ "rotation": 0
137
+ }
138
+ ]
139
+ },
140
+ {
141
+ "index": 1,
142
+ "pageNo": 2,
143
+ "file": "pages/02_content.yaml",
144
+ "name": "pages/02_content",
145
+ "background": {
146
+ "type": "solid",
147
+ "color": "#F8FAFC"
148
+ },
149
+ "safeArea": {
150
+ "top": 0,
151
+ "bottom": 0,
152
+ "left": 0,
153
+ "right": 0
154
+ },
155
+ "expectedOverlaps": [
156
+ {
157
+ "pair": [
158
+ "metric-rect",
159
+ "metric"
160
+ ]
161
+ }
162
+ ],
163
+ "expectedOutOfSafeArea": [],
164
+ "contrastExempt": [],
165
+ "overlapMode": "declared",
166
+ "elements": [
167
+ {
168
+ "id": "page-title",
169
+ "kind": "text",
170
+ "bounds": {
171
+ "x": 60,
172
+ "y": 40,
173
+ "w": 840,
174
+ "h": 50
175
+ },
176
+ "style": {
177
+ "fontSize": 28,
178
+ "color": "#1F2937",
179
+ "bold": true,
180
+ "lineHeight": 1.2,
181
+ "wrap": true
182
+ },
183
+ "metrics": {
184
+ "textW": 118.72,
185
+ "textH": 33.6,
186
+ "lines": 1,
187
+ "lineWidths": [
188
+ 118.72
189
+ ],
190
+ "overflowY": 0,
191
+ "overflowX": 0
192
+ },
193
+ "text": "关键进展"
194
+ },
195
+ {
196
+ "id": "body-a",
197
+ "kind": "text",
198
+ "bounds": {
199
+ "x": 60,
200
+ "y": 130,
201
+ "w": 420,
202
+ "h": 200
203
+ },
204
+ "style": {
205
+ "fontSize": 18,
206
+ "color": "#1F2937",
207
+ "lineHeight": 1.5,
208
+ "wrap": true
209
+ },
210
+ "metrics": {
211
+ "textW": 816.3000000000001,
212
+ "textH": 54,
213
+ "lines": 2,
214
+ "lineWidths": [
215
+ 429.3,
216
+ 343.8
217
+ ],
218
+ "overflowY": 0,
219
+ "overflowX": 396
220
+ },
221
+ "text": "线上增长 34%,主要来自新渠道投放;NPS 提升 12 分;复购率升至 41%。供应链成本环比下降 8%。"
222
+ },
223
+ {
224
+ "id": "metric-rect",
225
+ "kind": "shape",
226
+ "bounds": {
227
+ "x": 540,
228
+ "y": 130,
229
+ "w": 360,
230
+ "h": 80
231
+ },
232
+ "shape": "rect",
233
+ "fill": "#2563EB",
234
+ "rotation": 0
235
+ },
236
+ {
237
+ "id": "metric",
238
+ "kind": "text",
239
+ "bounds": {
240
+ "x": 540,
241
+ "y": 150,
242
+ "w": 360,
243
+ "h": 40
244
+ },
245
+ "style": {
246
+ "fontSize": 22,
247
+ "color": "#FFFFFF",
248
+ "bold": true,
249
+ "align": "center",
250
+ "lineHeight": 1.2,
251
+ "wrap": true
252
+ },
253
+ "metrics": {
254
+ "textW": 112.19999999999999,
255
+ "textH": 26.4,
256
+ "lines": 1,
257
+ "lineWidths": [
258
+ 103.4
259
+ ],
260
+ "overflowY": 0,
261
+ "overflowX": 0
262
+ },
263
+ "text": "+34% 增长"
264
+ },
265
+ {
266
+ "id": "table-b",
267
+ "kind": "table",
268
+ "bounds": {
269
+ "x": 60,
270
+ "y": 380,
271
+ "w": 840,
272
+ "h": 120
273
+ },
274
+ "cols": 4,
275
+ "rows": 3,
276
+ "fontPt": 11,
277
+ "metrics": {
278
+ "overflowY": -52.8,
279
+ "lines": 4,
280
+ "textH": 67
281
+ }
282
+ }
283
+ ]
284
+ },
285
+ {
286
+ "index": 2,
287
+ "pageNo": 3,
288
+ "file": "pages/03_chart.yaml",
289
+ "name": "pages/03_chart",
290
+ "background": {
291
+ "type": "solid",
292
+ "color": "#F8FAFC"
293
+ },
294
+ "safeArea": {
295
+ "top": 0,
296
+ "bottom": 0,
297
+ "left": 0,
298
+ "right": 0
299
+ },
300
+ "expectedOverlaps": [],
301
+ "expectedOutOfSafeArea": [],
302
+ "contrastExempt": [],
303
+ "overlapMode": "declared",
304
+ "elements": [
305
+ {
306
+ "id": "page-title",
307
+ "kind": "text",
308
+ "bounds": {
309
+ "x": 60,
310
+ "y": 40,
311
+ "w": 840,
312
+ "h": 50
313
+ },
314
+ "style": {
315
+ "fontSize": 28,
316
+ "color": "#1F2937",
317
+ "bold": true,
318
+ "lineHeight": 1.2,
319
+ "wrap": true
320
+ },
321
+ "metrics": {
322
+ "textW": 194.04000000000002,
323
+ "textH": 33.6,
324
+ "lines": 1,
325
+ "lineWidths": [
326
+ 171.64000000000001
327
+ ],
328
+ "overflowY": 0,
329
+ "overflowX": 0
330
+ },
331
+ "text": "渠道 GMV 走势"
332
+ },
333
+ {
334
+ "id": "chart-c",
335
+ "kind": "chart",
336
+ "bounds": {
337
+ "x": 60,
338
+ "y": 130,
339
+ "w": 840,
340
+ "h": 330
341
+ },
342
+ "chartType": "bar",
343
+ "colors": [
344
+ "#2563EB",
345
+ "#F59E0B"
346
+ ]
347
+ },
348
+ {
349
+ "id": "caption",
350
+ "kind": "text",
351
+ "bounds": {
352
+ "x": 60,
353
+ "y": 480,
354
+ "w": 840,
355
+ "h": 30
356
+ },
357
+ "style": {
358
+ "fontSize": 12,
359
+ "color": "#64748B",
360
+ "lineHeight": 1.2,
361
+ "wrap": true
362
+ },
363
+ "metrics": {
364
+ "textW": 235.79999999999995,
365
+ "textH": 14.399999999999999,
366
+ "lines": 1,
367
+ "lineWidths": [
368
+ 231
369
+ ],
370
+ "overflowY": 0,
371
+ "overflowX": 0
372
+ },
373
+ "text": "数据来源:内部经营报表(2026-09 月报)"
374
+ }
375
+ ]
376
+ }
377
+ ]
378
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * 一键声明(反馈 D2 ★★):把警告级未声明重叠对写入页面 expectedOverlaps。
3
+ * - 保留源文件注释(parseDocument + setIn,不重排全文件)。
4
+ * - 内容互压(content-collision)不会出现在 collectDeclarable 中,必须手工修复。
5
+ */
6
+ import { readFile, writeFile } from 'node:fs/promises'
7
+ import YAML from 'yaml'
8
+ import { collectDeclarable } from './verify.js'
9
+
10
+ /**
11
+ * @param ctx resolveDeck 结果(pages[].file 为页面 yaml 绝对路径)
12
+ * @param layout renderDeck 的 layout(pages[] 含 elements/expectedOverlaps/size)
13
+ * @returns [{ page, added }]
14
+ */
15
+ export async function applyAutoDeclare(ctx, layout) {
16
+ const added = []
17
+ for (const pageL of layout.pages) {
18
+ const pairs = collectDeclarable(pageL, layout.size)
19
+ if (!pairs.length) continue
20
+ const file = ctx.pages[pageL.index].file
21
+ const text = await readFile(file, 'utf8')
22
+ const doc = YAML.parseDocument(text)
23
+ // yaml@2 的 doc.get() 返回 AST 节点(YAMLSeq,无 .map);其 toJS() 裸调用还会抛
24
+ // "A document argument is required"(需要 doc 上下文)→ 统一走 doc.toJS()(Document 级,纯 JS 值)
25
+ const data = doc.toJS() ?? {}
26
+ const list = Array.isArray(data.expectedOverlaps) ? data.expectedOverlaps : []
27
+ const existing = list.map((p) => ({
28
+ pair: [String(p?.pair?.[0]), String(p?.pair?.[1])],
29
+ }))
30
+ const exSet = new Set(existing.map((p) => [p.pair[0], p.pair[1]].sort().join('×')))
31
+ const add = pairs
32
+ .filter(([a, b]) => !exSet.has([a, b].sort().join('×')))
33
+ .map(([a, b]) => ({ pair: [a, b] }))
34
+ if (!add.length) continue
35
+ doc.setIn(['expectedOverlaps'], doc.createNode([...existing, ...add], { keepScalarTypes: true }))
36
+ await writeFile(file, String(doc))
37
+ added.push({ page: pageL.index + 1, added: add.length, file })
38
+ }
39
+ return added
40
+ }
@@ -0,0 +1,161 @@
1
+ /**
2
+ * /ppt 斜杠命令面(命令 > 语义;命令写入会话/项目状态)。
3
+ * /ppt → 状态查询
4
+ * /ppt on|off → 强制进入/退出工作流
5
+ * /ppt free|mid|strict → 强制协作模式
6
+ * /ppt fidelity strict|auto|free [--page N / --dir D]
7
+ * /ppt review none|points|every
8
+ * /ppt engine pptd|python-pptx|pptxgenjs
9
+ * /ppt quality quick|standard|audit
10
+ * /ppt pause-after outline|layout|pages|overall [--dir D]
11
+ * /ppt help
12
+ */
13
+ import { loadSession, saveSession, loadProject, saveProject } from './state.js'
14
+
15
+ export function registerCommands(ctx) {
16
+ ctx.commands.register({
17
+ name: 'ppt',
18
+ description: 'PPT 工作流控制:进入/退出、快速模式、协作模式、引擎、质量、暂停点',
19
+ input: { hint: '[on|off|quick|normal|free|mid|strict|fidelity <v>|review <v>|engine <v>|quality <v>|pause-after <v>|help]' },
20
+ handler: (invocation) => handle(invocation),
21
+ })
22
+ }
23
+
24
+ async function handle(invocation) {
25
+ const sessionId = invocation.agent.session?.id ?? 'default'
26
+ const input = invocation.rawInput.trim()
27
+ const state = await loadSession(sessionId)
28
+ const [cmdRaw, ...rest] = input.split(/\s+/)
29
+ const cmd = (cmdRaw ?? '').toLowerCase()
30
+ const args = rest.join(' ').trim()
31
+ const dir = argValue(args, '--dir')
32
+
33
+ try {
34
+ if (cmd === '' || cmd === 'status') return textStatus(state, dir, await projectSummary(dir))
35
+ if (cmd === 'help') {
36
+ return ok('PPT 工作流命令:/ppt [on|off|free|mid|strict|fidelity <v>|review <v>|engine <v>|quality <v>|pause-after <v>|help]。语义检测自动进入;命令强制优先。')
37
+ }
38
+ if (cmd === 'on') { state.routing = 'on'; state.workflowActive = true; await save(); return ok('✓ PPT 工作流已强制进入(语义退出已屏蔽)') }
39
+ if (cmd === 'off') {
40
+ state.routing = 'off'; state.workflowActive = false
41
+ await save()
42
+ return ok('✓ PPT 工作流已退出(PPT 任务将按标准模式能力处理)')
43
+ }
44
+ if (cmd === 'free' || cmd === 'mid' || cmd === 'strict') {
45
+ state.mode = cmd
46
+ await save()
47
+ return ok(`✓ 协作模式=${cmd}(下次任务生效;free 可跳过询问环节)`)
48
+ }
49
+ if (cmd === 'quick') {
50
+ state.quick = true
51
+ await save()
52
+ return ok('✓ 快速模式已开启:低 token 快交付——跳过视觉定调/素材搜索/视觉审阅迭代;数字门禁照跑;回完整模式用 /ppt normal')
53
+ }
54
+ if (cmd === 'normal') {
55
+ state.quick = false
56
+ await save()
57
+ return ok('✓ 已回到完整模式(视觉定调/素材/审阅迭代恢复)')
58
+ }
59
+ if (cmd === 'fidelity') {
60
+ const v = (args.split(' ')[0] ?? '').toLowerCase()
61
+ if (!['strict', 'auto', 'free'].includes(v)) return err('fidelity 取值 strict|auto|free')
62
+ state.fidelity = v
63
+ await save()
64
+ const pageN = argValue(args, '--page')
65
+ if (pageN && dir) {
66
+ const proj = await loadProject(dir)
67
+ proj.spec = proj.spec ?? {}
68
+ const idx = Number(pageN) - 1
69
+ for (const p of (proj.spec.pages ?? [])) { void p }
70
+ await saveProject(dir, proj)
71
+ return ok(`✓ 会话忠实度=${v}${pageN ? `(--page ${pageN} 已记录,页面级 fidelity 由模型写入 spec.pages)` : ''}`)
72
+ }
73
+ return ok(`✓ 内容忠实度=${v}`)
74
+ }
75
+ if (cmd === 'review') {
76
+ const v = (args.split(' ')[0] ?? '').toLowerCase()
77
+ if (!['none', 'points', 'every'].includes(v)) return err('review 取值 none|points|every')
78
+ state.review = v
79
+ await save()
80
+ return ok(`✓ 审阅频率=${v}`)
81
+ }
82
+ if (cmd === 'engine') {
83
+ const v = (args.split(' ')[0] ?? '').toLowerCase()
84
+ // pptxgenjs **未实现**(v1.0 前按计划不动)。旧实现把它收下、回执"已记录(待接入)"并写进会话状态,
85
+ // 而导出侧根本不认它 ⇒ 用户以为选了引擎,实际静默按 pptd 跑。未实现就明确拒绝,不装作成功。
86
+ if (v === 'pptxgenjs') return err('pptxgenjs 第三引擎**未实现**(v1.0 前按计划不动,见 docs/04)——不会静默切到 pptd。可用:auto|pptd|python-pptx')
87
+ if (!['auto', 'pptd', 'python-pptx'].includes(v)) return err('engine 取值 auto|pptd|python-pptx')
88
+ state.engine = v
89
+ await save()
90
+ return ok(`✓ 引擎=${v}(auto = pptd 主引擎 + 硬失败时兜底 python-pptx;此设置会被 ppt_export 采纳)`)
91
+ }
92
+ if (cmd === 'quality') {
93
+ const v = (args.split(' ')[0] ?? '').toLowerCase()
94
+ if (!['quick', 'standard', 'audit'].includes(v)) return err('quality 取值 quick|standard|audit')
95
+ state.quality = v
96
+ await save()
97
+ if (dir) { const proj = await loadProject(dir); proj.quality = v; await saveProject(dir, proj) }
98
+ return ok(`✓ 质量模式=${v}`)
99
+ }
100
+ if (cmd === 'template') {
101
+ const v = (args.split(' ')[0] ?? '').trim()
102
+ if (!v) {
103
+ const { listTemplates } = await import('./templates.js')
104
+ const list = await listTemplates()
105
+ if (!list.length) return err('模板库为空(templates/ 缺失)')
106
+ state.template = undefined
107
+ await save()
108
+ return ok(`可用模板(${list.length} 套):${list.map((t) => `${t.id}=${t.name}`).join('、')}\n用法:/ppt template <id>(记录本会话默认模板;用 ppt_templates 看完整清单与预览图)`)
109
+ }
110
+ const { templateWorkspace } = await import('./templates.js')
111
+ try {
112
+ const t = await templateWorkspace(v)
113
+ state.template = v
114
+ await save()
115
+ return ok(`✓ 本会话默认模板=${t.meta.name ?? v}(${v})——S0/S2 优先使用;新建工作区:ppt_new dir=<目录> template=${v}`)
116
+ } catch (e) {
117
+ return err(String(e?.message ?? e))
118
+ }
119
+ }
120
+ if (cmd === 'pause-after') {
121
+ const v = (args.split(' ')[0] ?? '').toLowerCase()
122
+ if (!['outline', 'layout', 'pages', 'overall', 'none'].includes(v)) return err('pause-after 取值 outline|layout|pages|overall|none')
123
+ if (v === 'none') state.pauseAfter = []
124
+ else state.pauseAfter = [...new Set([...state.pauseAfter, v])]
125
+ await save()
126
+ if (dir) { const proj = await loadProject(dir); proj.pauseAfter = state.pauseAfter; await saveProject(dir, proj) }
127
+ return ok(`✓ 暂停点=${state.pauseAfter.join('、') || '无'}`)
128
+ }
129
+ return err(`未知子命令 "${cmd}";/ppt help 查看用法`)
130
+ } catch (error) {
131
+ return { kind: 'error', text: `ppt 命令失败:${error?.message ?? error}` }
132
+ }
133
+
134
+ async function save() { await saveSession(sessionId, state) }
135
+ }
136
+
137
+ async function projectSummary(dir) {
138
+ if (!dir) return []
139
+ const proj = await loadProject(dir)
140
+ return [`项目(${dir}): 任务 ${proj.taskType ?? '—'}|阶段 ${proj.stage ?? '—'}|质量 ${proj.quality}`]
141
+ }
142
+
143
+ function textStatus(state, dir, extra) {
144
+ const lines = [
145
+ `PPT 工作流:${state.workflowActive ? '✓ 激活中' : '未激活'}(routing=${state.routing},语义自动/命令强制)`,
146
+ `快速模式=${state.quick ? 'on(跳过定调/素材/视觉审阅迭代)' : 'off'}|协作模式=${state.mode}|忠实度=${state.fidelity}|审阅=${state.review}|质量=${state.quality}|引擎=${state.engine}(auto=pptd)`,
147
+ `暂停点=${state.pauseAfter.length ? state.pauseAfter.join('、') : '无'}${state.template ? `|模板=${state.template}` : ''}`,
148
+ ...extra,
149
+ '',
150
+ '/ppt [on|off|quick|normal|free|mid|strict|fidelity <v>|review <v>|engine <v>|quality <v>|template <id>|pause-after <v>|help]',
151
+ ]
152
+ return ok(lines.join('\n'))
153
+ }
154
+
155
+ function argValue(input, name) {
156
+ const m = input.match(new RegExp(`${name}\\s+(\"[^\"]+\"|'[^']+'|\\S+)`))
157
+ return m ? m[1].replace(/^["']|["']$/g, '') : null
158
+ }
159
+
160
+ function ok(text) { return { kind: 'success', text } }
161
+ function err(text) { return { kind: 'error', text } }