bit-ppt-generator 0.3.0 → 0.3.1

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 CHANGED
@@ -6,6 +6,25 @@
6
6
  项目目标不是把幻灯片截图塞进 PPT,而是尽量生成原生 Office 对象:
7
7
  文本框、形状、表格、图表、图片和 Office Math 公式都应保持可编辑。
8
8
 
9
+ 在线测试地址:[http://47.109.207.16:8080/](http://47.109.207.16:8080/)
10
+
11
+ 欢迎试用并在 [GitHub Issues](https://github.com/yang-kun-long/bit-ppt-template/issues)
12
+ 反馈问题、语法建议、版式需求或生成失败案例。
13
+
14
+ ## 项目来源与相关路线
15
+
16
+ 本项目的视觉风格和最初需求来自 TeXPage 上的北京理工大学 LaTeX 幻灯片模板:
17
+ [BIThesis Beamer Slide Template](https://www.texpage.com/zh/template/18f4a5d2-2167-47b8-b193-85e2475e7a06)。
18
+ 原模板适合熟悉 LaTeX / Beamer 的用户,但 LaTeX 工具链较重,输出也通常是 PDF。
19
+ PDF 用 WPS 播放比较方便;如果没有 WPS,常见的 PDF 转 PPT 流程又容易造成格式错乱。
20
+
21
+ 因此这里提供另一条路线:用 YAML 描述内容,直接生成可编辑 PPTX,尽量保留文本、
22
+ 表格、图表、形状和 Office Math 公式的可编辑性。
23
+
24
+ 如果你的需求是“已有 PDF 幻灯片,只想在线展示或播放”,可以使用另一个项目
25
+ [pptView](https://github.com/yang-kun-long/pptView)。在线演示:
26
+ [https://ppt.yangkunlong.top](https://ppt.yangkunlong.top)。
27
+
9
28
  代码使用 MIT License。北京理工大学名称、标识和视觉参考归其各自权利人所有。
10
29
 
11
30
  ## 特性
@@ -19,9 +38,14 @@
19
38
  - 支持 `--json` 和 `--strict`,便于脚本、CI 和 AI agent 自动调用
20
39
  - 支持图片尺寸读取和 `imageText` 自动排版
21
40
 
22
- ## 安装
41
+ ## 三种使用方式
42
+
43
+ 同一个 npm 包同时提供 Web UI、CLI 和 MCP 三个入口。普通用户优先使用 Web UI;
44
+ 脚本和 CI 使用 CLI;Codex、Claude Code 等本地 agent 使用 MCP。
45
+
46
+ ### 1. 普通用户:打开本地网页
23
47
 
24
- 发布到 npm 后,普通用户可直接启动本地网页:
48
+ 无需安装,直接启动:
25
49
 
26
50
  ```powershell
27
51
  npx bit-ppt-generator
@@ -35,88 +59,110 @@ http://127.0.0.1:3000/
35
59
 
36
60
  本地网页默认不需要登录;只有部署者配置了鉴权环境变量时才会显示登录区。
37
61
 
38
- 全局安装:
62
+ 也可以全局安装后启动:
39
63
 
40
64
  ```powershell
41
65
  npm install -g bit-ppt-generator
42
66
  bit-ppt-generator
43
67
  ```
44
68
 
45
- 仓库开发:
69
+ ### 2. 命令行用户:检查和生成 PPTX
46
70
 
47
71
  ```powershell
48
- npm install
72
+ npm install -g bit-ppt-generator
73
+ bit-ppt check content/example.yaml --json
74
+ bit-ppt generate content/example.yaml output/example.pptx
49
75
  ```
50
76
 
51
- 本地开发可以直接使用 Node 入口:
77
+ 常用 CLI:
52
78
 
53
- ```powershell
54
- node bin/bit-ppt.mjs --help
79
+ ```text
80
+ bit-ppt generate <input.yaml> <output.pptx> [--json] [--strict] [font options]
81
+ bit-ppt check <input.yaml> [--json] [--strict]
82
+ bit-ppt list-layouts [--json]
83
+ bit-ppt guide [topic] [name] [--json]
84
+ bit-ppt doctor [--json]
55
85
  ```
56
86
 
57
- 也可以链接为全局命令:
87
+ ### 3. Agent 用户:配置 MCP
88
+
89
+ 全局安装后,MCP 客户端可以直接启动 npm 包提供的 `bit-ppt-mcp` 命令:
58
90
 
59
91
  ```powershell
60
- npm link
61
- bit-ppt --help
92
+ npm install -g bit-ppt-generator
93
+ bit-ppt-mcp --help
62
94
  ```
63
95
 
64
- ## 快速开始
65
-
66
- 启动本地网页:
96
+ MCP 客户端配置示例:
67
97
 
68
- ```powershell
69
- npm run serve
98
+ ```json
99
+ {
100
+ "mcpServers": {
101
+ "bit-ppt": {
102
+ "command": "bit-ppt-mcp",
103
+ "args": []
104
+ }
105
+ }
106
+ }
70
107
  ```
71
108
 
72
- 生成示例 PPT:
109
+ 如果 MCP 客户端不继承系统 PATH,改用 `npx` 启动:
73
110
 
74
- ```powershell
75
- npm run build:ppt
76
- npm run build:body-layouts
77
- npm run build:charts
111
+ ```json
112
+ {
113
+ "mcpServers": {
114
+ "bit-ppt": {
115
+ "command": "npx",
116
+ "args": ["-y", "--package", "bit-ppt-generator", "bit-ppt-mcp"]
117
+ }
118
+ }
119
+ }
78
120
  ```
79
121
 
80
- 输出文件位于:
122
+ 不再需要写类似 `D:/atuodl/presentation-slide/bit-ppt-template/bin/bit-ppt-mcp.mjs`
123
+ 这样的本机源码路径。那种写法只适合仓库开发阶段;发布到 npm 后应使用包里的
124
+ `bit-ppt-mcp` 命令。
81
125
 
82
- ```text
83
- output/example.pptx
84
- output/body-layout-test.pptx
85
- output/chart-flow-test.pptx
86
- ```
126
+ ## 仓库开发
87
127
 
88
- 手动指定输入和输出:
128
+ 克隆仓库后安装依赖:
89
129
 
90
130
  ```powershell
91
- node bin/bit-ppt.mjs generate content/example.yaml output/example.pptx
131
+ npm install
92
132
  ```
93
133
 
94
- 先检查再生成:
134
+ 启动本地网页:
95
135
 
96
136
  ```powershell
97
- node bin/bit-ppt.mjs check content/example.yaml --json
98
- node bin/bit-ppt.mjs generate content/example.yaml output/example.pptx --json
137
+ npm run serve
99
138
  ```
100
139
 
101
- 严格模式会把 warning 也视为失败:
140
+ 生成示例 PPT:
102
141
 
103
142
  ```powershell
104
- node bin/bit-ppt.mjs check content/formula-test.yaml --json --strict
105
- node bin/bit-ppt.mjs generate content/example.yaml output/example.pptx --json --strict
143
+ npm run build:ppt
144
+ npm run build:body-layouts
145
+ npm run build:charts
106
146
  ```
107
147
 
108
- ## Release Targets
148
+ 源码入口:
109
149
 
110
- 本仓库采用单主干、多入口策略。CLI、MCP 和 Node HTTP 服务都复用同一套核心生成逻辑。
150
+ ```powershell
151
+ node bin/bit-ppt.mjs --help
152
+ node bin/bit-ppt-http.mjs --help
153
+ node bin/bit-ppt-mcp.mjs --help
154
+ ```
111
155
 
112
- 当前状态:
156
+ 本仓库采用单主干、多入口策略。Web UI、CLI、MCP 和 Node HTTP API 都复用同一套核心生成逻辑。
157
+
158
+ ## npm 包入口
113
159
 
114
160
  - Web UI:默认入口为 `bit-ppt-generator`,适合 npm 用户本地打开网页
115
161
  - CLI:已支持,入口为 `bit-ppt` 或 `node bin/bit-ppt.mjs`
116
162
  - MCP:已支持,入口为 `bit-ppt-mcp` 或 `node bin/bit-ppt-mcp.mjs`
117
163
  - Node HTTP API:已支持,入口为 `bit-ppt-http` 或 `node bin/bit-ppt-http.mjs`
118
164
 
119
- 当前 npm 包名预定为 `bit-ppt-generator`。一个 npm release 同时包含 Web UI、CLI 和 MCP 三个入口,不拆成三个包。
165
+ 一个 npm release 同时包含 Web UI、CLI 和 MCP 三个入口,不拆成三个包。
120
166
 
121
167
  ## CLI 命令
122
168
 
@@ -134,10 +180,10 @@ bit-ppt-http
134
180
  常用命令:
135
181
 
136
182
  ```powershell
137
- node bin/bit-ppt.mjs list-layouts
138
- node bin/bit-ppt.mjs list-layouts --json
139
- node bin/bit-ppt.mjs doctor
140
- node bin/bit-ppt.mjs doctor --json
183
+ bit-ppt list-layouts
184
+ bit-ppt list-layouts --json
185
+ bit-ppt doctor
186
+ bit-ppt doctor --json
141
187
  ```
142
188
 
143
189
  `doctor` 会检查 Node 版本、依赖、关键素材、示例 deck、输出目录可写性。
@@ -150,24 +196,23 @@ MCP 只是 adapter,仍复用 CLI 背后的同一套生成和校验函数。
150
196
  本地运行:
151
197
 
152
198
  ```powershell
153
- node bin/bit-ppt-mcp.mjs
199
+ bit-ppt-mcp
154
200
  ```
155
201
 
156
- 全局链接后:
202
+ 从源码运行:
157
203
 
158
204
  ```powershell
159
- bit-ppt-mcp
205
+ node bin/bit-ppt-mcp.mjs
160
206
  ```
161
207
 
162
- MCP 客户端配置示例:
208
+ MCP 客户端配置示例,优先使用 npm 包命令:
163
209
 
164
210
  ```json
165
211
  {
166
212
  "mcpServers": {
167
213
  "bit-ppt": {
168
- "command": "node",
169
- "args": ["D:/atuodl/presentation-slide/bit-ppt-template/bin/bit-ppt-mcp.mjs"],
170
- "cwd": "D:/atuodl/presentation-slide/bit-ppt-template"
214
+ "command": "bit-ppt-mcp",
215
+ "args": []
171
216
  }
172
217
  }
173
218
  }
@@ -593,6 +638,32 @@ npm run check:charts
593
638
  npm pack --dry-run
594
639
  ```
595
640
 
641
+ ## 发布
642
+
643
+ npm 包名为 `bit-ppt-generator`。后续版本通过 GitHub Release 触发
644
+ `.github/workflows/publish.yml`,并使用 npm Trusted Publisher 发布,不需要在
645
+ GitHub Secrets 保存 npm token。
646
+
647
+ Trusted Publisher 配置:
648
+
649
+ ```text
650
+ Publisher: GitHub Actions
651
+ Organization or user: yang-kun-long
652
+ Repository: bit-ppt-template
653
+ Workflow filename: publish.yml
654
+ Environment name: 留空
655
+ ```
656
+
657
+ 发布新版本:
658
+
659
+ ```powershell
660
+ npm version patch
661
+ git push
662
+ git push --tags
663
+ ```
664
+
665
+ 随后在 GitHub 创建并发布对应 tag 的 Release。
666
+
596
667
  ## 项目结构
597
668
 
598
669
  ```text
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bit-ppt-generator",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "LaTeX-free Beijing Institute of Technology style PPTX generator with Web UI, CLI, and MCP entrypoints.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -38,5 +38,13 @@
38
38
  "pptxgenjs": "^4.0.1",
39
39
  "yaml": "^2.8.4",
40
40
  "zod": "^4.4.3"
41
- }
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/yang-kun-long/bit-ppt-template.git"
45
+ },
46
+ "bugs": {
47
+ "url": "https://github.com/yang-kun-long/bit-ppt-template/issues"
48
+ },
49
+ "homepage": "https://github.com/yang-kun-long/bit-ppt-template#readme"
42
50
  }
@@ -1,3 +1,5 @@
1
+ import { listLayouts as listSupportedLayouts } from "./core/layouts.mjs";
2
+
1
3
  const writingRules = [
2
4
  "Keep slide text short and concrete; use validation warnings as rewrite hints.",
3
5
  "Prefer editable PPTX objects: text boxes, shapes, tables, charts, OMML formulas, and native images.",
@@ -226,8 +228,414 @@ const layoutGuides = {
226
228
  },
227
229
  };
228
230
 
231
+ const additionalLayoutGuides = {
232
+ title: {
233
+ layout: "title",
234
+ purpose: "Create the deck cover slide from top-level metadata.",
235
+ whenToUse: "Use as the first slide; title content usually comes from the deck-level `meta` object.",
236
+ fields: {
237
+ layout: { type: "literal", required: true, value: "title" },
238
+ "meta.title": { type: "string", required: false },
239
+ "meta.subtitle": { type: "string", required: false },
240
+ "meta.author": { type: "string", required: false },
241
+ "meta.advisor": { type: "string", required: false },
242
+ "meta.date": { type: "string", required: false },
243
+ },
244
+ limits: {
245
+ "meta.title": { recommendedChars: 28 },
246
+ "meta.subtitle": { recommendedChars: 42 },
247
+ },
248
+ notes: [
249
+ "The slide object normally only needs `layout: title`.",
250
+ "Cover text is read from deck metadata.",
251
+ ],
252
+ example: { layout: "title" },
253
+ },
254
+ agenda: {
255
+ layout: "agenda",
256
+ purpose: "Show the main agenda or chapter list.",
257
+ whenToUse: "Use near the beginning of a deck to preview sections.",
258
+ fields: {
259
+ layout: { type: "literal", required: true, value: "agenda" },
260
+ title: { type: "string", required: true },
261
+ items: { type: "string[]", required: true },
262
+ },
263
+ limits: { items: { maxItems: 7, recommendedChars: 24 } },
264
+ notes: ["Keep agenda labels short and parallel.", "Use `section` slides for major chapter breaks."],
265
+ example: { layout: "agenda", title: "目录", items: ["研究背景", "方法设计", "实验结果", "总结展望"] },
266
+ },
267
+ section: {
268
+ layout: "section",
269
+ purpose: "Create a visual chapter divider.",
270
+ whenToUse: "Use before a major topic shift or report section.",
271
+ fields: {
272
+ layout: { type: "literal", required: true, value: "section" },
273
+ sectionNo: { type: "string", required: false },
274
+ title: { type: "string", required: true },
275
+ subtitle: { type: "string", required: false },
276
+ },
277
+ limits: { title: { recommendedChars: 24 }, subtitle: { recommendedChars: 42 } },
278
+ notes: ["`sectionNo` can be supplied explicitly, for example `01`.", "Keep the subtitle as context, not body content."],
279
+ example: { layout: "section", sectionNo: "01", title: "研究背景", subtitle: "从问题定义与现有瓶颈切入" },
280
+ },
281
+ bullets: {
282
+ layout: "bullets",
283
+ purpose: "Show a lead sentence and a concise bullet list.",
284
+ whenToUse: "Use for findings, observations, requirements, and short argument chains.",
285
+ fields: {
286
+ layout: { type: "literal", required: true, value: "bullets" },
287
+ title: { type: "string", required: true },
288
+ lead: { type: "string", required: false },
289
+ bullets: { type: "string[]", required: true },
290
+ fontSize: { type: "number", required: false },
291
+ },
292
+ limits: { lead: { recommendedChars: 58 }, bullets: { maxItems: 8, recommendedChars: 38 } },
293
+ notes: ["Inline `$...$` and `\\(...\\)` formulas are supported.", "Long bullet lists may be split during preflight."],
294
+ example: { layout: "bullets", title: "核心发现", lead: "结构化输入能降低排版漂移。", bullets: ["模型只填写短字段。", "模板负责视觉规范。", "预检负责溢出修复。"] },
295
+ },
296
+ claim: {
297
+ layout: "claim",
298
+ purpose: "Emphasize one central claim with supporting evidence.",
299
+ whenToUse: "Use when a slide needs one clear takeaway rather than several equal points.",
300
+ fields: {
301
+ layout: { type: "literal", required: true, value: "claim" },
302
+ title: { type: "string", required: true },
303
+ claim: { type: "string", required: true },
304
+ evidence: { type: "string[]", required: false },
305
+ },
306
+ limits: { claim: { recommendedChars: 54 }, evidence: { maxItems: 5, recommendedChars: 38 } },
307
+ notes: ["Make `claim` a complete sentence.", "Use evidence bullets for proof, not extra claims."],
308
+ example: { layout: "claim", title: "核心结论", claim: "AI 生成 PPT 的关键是稳定内容结构。", evidence: ["YAML 限定字段。", "PPTX 对象保持可编辑。"] },
309
+ },
310
+ twoColumn: {
311
+ layout: "twoColumn",
312
+ purpose: "Compare or separate two parallel content blocks.",
313
+ whenToUse: "Use for before/after, baseline/ours, or two-topic explanation.",
314
+ fields: {
315
+ layout: { type: "literal", required: true, value: "twoColumn" },
316
+ title: { type: "string", required: true },
317
+ left: { type: "{ title, text? | bullets? }", required: true },
318
+ right: { type: "{ title, text? | bullets? }", required: true },
319
+ },
320
+ limits: { columnTitle: { recommendedChars: 16 }, columnText: { recommendedChars: 105 }, columnBullets: { maxItems: 5, recommendedChars: 34 } },
321
+ notes: ["Each column can use either `text` or `bullets`.", "Use `comparison` when the point is explicitly adversarial or directional."],
322
+ example: { layout: "twoColumn", title: "两种路线", left: { title: "传统做法", bullets: ["手工排版", "一致性依赖人工"] }, right: { title: "模板化做法", bullets: ["结构化输入", "生成器固定样式"] } },
323
+ },
324
+ cards: {
325
+ layout: "cards",
326
+ purpose: "Show several peer points as compact cards.",
327
+ whenToUse: "Use for features, modules, contributions, or categories with equal weight.",
328
+ fields: {
329
+ layout: { type: "literal", required: true, value: "cards" },
330
+ title: { type: "string", required: true },
331
+ cards: { type: "{ title, text }[]", required: true },
332
+ },
333
+ limits: { cards: { maxItems: 6 }, cardTitle: { recommendedChars: 14 }, cardText: { recommendedChars: 52 } },
334
+ notes: ["Cards work best when each item has similar importance.", "Use no more than six cards on one slide."],
335
+ example: { layout: "cards", title: "能力模块", cards: [{ title: "校验", text: "检查字段与长度风险。" }, { title: "生成", text: "写入可编辑 PPTX 对象。" }] },
336
+ },
337
+ comparison: {
338
+ layout: "comparison",
339
+ purpose: "Show a direct comparison between two options.",
340
+ whenToUse: "Use when one side is preferred, rejected, or contrasted against another.",
341
+ fields: {
342
+ layout: { type: "literal", required: true, value: "comparison" },
343
+ title: { type: "string", required: true },
344
+ left: { type: "{ label?, title, bullets }", required: true },
345
+ right: { type: "{ label?, title, bullets }", required: true },
346
+ },
347
+ limits: { comparisonTitle: { recommendedChars: 18 }, bullets: { maxItems: 5, recommendedChars: 32 } },
348
+ notes: ["`left.label` and `right.label` are short tags.", "Keep the two sides structurally parallel."],
349
+ example: { layout: "comparison", title: "技术路线取舍", left: { label: "不优先", title: "端到端 Agent", bullets: ["流程封闭", "复核成本高"] }, right: { label: "优先", title: "MCP 工具链", bullets: ["跨客户端", "人类可介入"] } },
350
+ },
351
+ timeline: {
352
+ layout: "timeline",
353
+ purpose: "Show milestones along a horizontal timeline.",
354
+ whenToUse: "Use for roadmaps, phases, history, or scheduled work.",
355
+ fields: {
356
+ layout: { type: "literal", required: true, value: "timeline" },
357
+ title: { type: "string", required: true },
358
+ items: { type: "{ date? | phase?, title, text }[]", required: true },
359
+ },
360
+ limits: { items: { maxItems: 6 }, itemTitle: { recommendedChars: 10 }, itemText: { recommendedChars: 24 } },
361
+ notes: ["Use `date` for calendar time and `phase` for abstract stages.", "Avoid paragraph text in timeline nodes."],
362
+ example: { layout: "timeline", title: "路线图", items: [{ date: "阶段 1", title: "模板", text: "固化视觉规范。" }, { date: "阶段 2", title: "校验", text: "加入预检修复。" }] },
363
+ },
364
+ process: {
365
+ layout: "process",
366
+ purpose: "Show a linear process as connected steps.",
367
+ whenToUse: "Use for workflows, generation pipelines, or execution procedures.",
368
+ fields: {
369
+ layout: { type: "literal", required: true, value: "process" },
370
+ title: { type: "string", required: true },
371
+ steps: { type: "{ title, text }[]", required: true },
372
+ },
373
+ limits: { steps: { maxItems: 5 }, stepTitle: { recommendedChars: 8 }, stepText: { recommendedChars: 24 } },
374
+ notes: ["Steps are rendered left-to-right.", "Use `flowchart` for branching or non-linear processes."],
375
+ example: { layout: "process", title: "生成流程", steps: [{ title: "解析", text: "读取 YAML。" }, { title: "校验", text: "检查字段。" }, { title: "导出", text: "生成 PPTX。" }] },
376
+ },
377
+ problemSolution: {
378
+ layout: "problemSolution",
379
+ purpose: "Present problem, solution, and impact in three panels.",
380
+ whenToUse: "Use for proposal framing or product/research motivation.",
381
+ fields: {
382
+ layout: { type: "literal", required: true, value: "problemSolution" },
383
+ title: { type: "string", required: true },
384
+ problem: { type: "{ label?, title, bullets }", required: true },
385
+ solution: { type: "{ label?, title, bullets }", required: true },
386
+ impact: { type: "{ label?, title, bullets }", required: true },
387
+ },
388
+ limits: { panelTitle: { recommendedChars: 12 }, bullets: { maxItems: 4, recommendedChars: 26 } },
389
+ notes: ["Use this when the three blocks form one argument.", "Each panel supports a custom short `label`."],
390
+ example: { layout: "problemSolution", title: "问题与方案", problem: { title: "排版不稳", bullets: ["模型自由发挥导致溢出。"] }, solution: { title: "结构约束", bullets: ["YAML 固定字段。"] }, impact: { title: "可复核", bullets: ["输出可编辑 PPTX。"] } },
391
+ },
392
+ painOpportunity: {
393
+ layout: "painOpportunity",
394
+ purpose: "Frame current status, pain points, and opportunity.",
395
+ whenToUse: "Use for background analysis before introducing a solution.",
396
+ fields: {
397
+ layout: { type: "literal", required: true, value: "painOpportunity" },
398
+ title: { type: "string", required: true },
399
+ status: { type: "{ title, text? | bullets? }", required: true },
400
+ pain: { type: "{ title, text? | bullets? }", required: true },
401
+ opportunity: { type: "{ title, text? | bullets? }", required: true },
402
+ },
403
+ limits: { panelTitle: { recommendedChars: 12 }, bullets: { maxItems: 4, recommendedChars: 30 } },
404
+ notes: ["Panels use the same structure as `twoColumn` column blocks.", "Best for moving from observation to opportunity."],
405
+ example: { layout: "painOpportunity", title: "现状痛点与机会", status: { title: "现状", bullets: ["PPT 生成需求高。"] }, pain: { title: "痛点", bullets: ["直接生成不稳定。"] }, opportunity: { title: "机会", bullets: ["模板化生成可控。"] } },
406
+ },
407
+ experimentDesign: {
408
+ layout: "experimentDesign",
409
+ purpose: "Summarize an experiment setup.",
410
+ whenToUse: "Use before result slides to define data, variables, metrics, and baselines.",
411
+ fields: {
412
+ layout: { type: "literal", required: true, value: "experimentDesign" },
413
+ title: { type: "string", required: true },
414
+ dataset: { type: "string | string[]", required: false },
415
+ variables: { type: "string | string[]", required: false },
416
+ metrics: { type: "string | string[]", required: false },
417
+ baselines: { type: "string | string[]", required: false },
418
+ procedure: { type: "string[]", required: false },
419
+ },
420
+ limits: { eachBlock: { maxItems: 4, recommendedChars: 26 }, procedure: { maxItems: 5 } },
421
+ notes: ["Scalar strings are accepted and rendered as one bullet.", "Keep procedure items short because they render on one line."],
422
+ example: { layout: "experimentDesign", title: "实验设计", dataset: ["公开数据集 A"], variables: ["是否启用预检"], metrics: ["溢出页数"], baselines: ["直接生成 PPT"], procedure: ["准备输入", "生成", "人工复核"] },
423
+ },
424
+ resultAnalysis: {
425
+ layout: "resultAnalysis",
426
+ purpose: "State one finding, key metrics, and analysis bullets.",
427
+ whenToUse: "Use after experiments to connect numbers to interpretation.",
428
+ fields: {
429
+ layout: { type: "literal", required: true, value: "resultAnalysis" },
430
+ title: { type: "string", required: true },
431
+ finding: { type: "string", required: true },
432
+ metrics: { type: "{ value, label, note? }[]", required: false },
433
+ analysis: { type: "string[]", required: false },
434
+ },
435
+ limits: { finding: { recommendedChars: 52 }, metrics: { maxItems: 3 }, analysis: { maxItems: 4, recommendedChars: 38 } },
436
+ notes: ["Put the strongest result in `finding`.", "Metrics are compact; use `chart` for richer data."],
437
+ example: { layout: "resultAnalysis", title: "结果分析", finding: "预检显著减少长列表造成的页面溢出。", metrics: [{ value: "-80%", label: "溢出页", note: "相对基线" }], analysis: ["主要收益来自 bullets 和 references 拆页。"] },
438
+ },
439
+ riskMitigation: {
440
+ layout: "riskMitigation",
441
+ purpose: "Show risks, impacts, and mitigations in a table.",
442
+ whenToUse: "Use for project planning, deployment risk, or limitation handling.",
443
+ fields: {
444
+ layout: { type: "literal", required: true, value: "riskMitigation" },
445
+ title: { type: "string", required: true },
446
+ items: { type: "{ risk, impact, mitigation }[]", required: true },
447
+ },
448
+ limits: { items: { maxItems: 5 }, cell: { recommendedChars: 28 } },
449
+ notes: ["Keep mitigation actionable.", "Use this for risks; use `table` for arbitrary structured data."],
450
+ example: { layout: "riskMitigation", title: "风险与对策", items: [{ risk: "图片缺失", impact: "生成失败", mitigation: "使用 placeholder 并补 prompt" }] },
451
+ },
452
+ contribution: {
453
+ layout: "contribution",
454
+ purpose: "List the main contributions with numbered emphasis.",
455
+ whenToUse: "Use near the end of a research or project deck.",
456
+ fields: {
457
+ layout: { type: "literal", required: true, value: "contribution" },
458
+ title: { type: "string", required: true },
459
+ items: { type: "{ title, text }[]", required: true },
460
+ },
461
+ limits: { items: { maxItems: 4 }, itemTitle: { recommendedChars: 14 }, itemText: { recommendedChars: 44 } },
462
+ notes: ["Use one contribution per item.", "Avoid turning this into a general summary slide."],
463
+ example: { layout: "contribution", title: "主要贡献", items: [{ title: "可编辑输出", text: "文本、表格、图表均保留为 PPTX 对象。" }, { title: "公式支持", text: "LaTeX 公式转换为原生 OMML。" }] },
464
+ },
465
+ summary: {
466
+ layout: "summary",
467
+ purpose: "Close a section with one takeaway and supporting points.",
468
+ whenToUse: "Use at section endings or before the closing slide.",
469
+ fields: {
470
+ layout: { type: "literal", required: true, value: "summary" },
471
+ title: { type: "string", required: true },
472
+ takeaway: { type: "string", required: true },
473
+ points: { type: "string[]", required: false },
474
+ },
475
+ limits: { takeaway: { recommendedChars: 52 }, points: { maxItems: 5, recommendedChars: 36 } },
476
+ notes: ["`takeaway` should be a sentence, not a heading.", "Use `closing` only for the final thank-you page."],
477
+ example: { layout: "summary", title: "章节小结", takeaway: "结构化内容让 PPT 生成更稳定也更容易修复。", points: ["页面类型明确。", "校验信息可回传模型。"] },
478
+ },
479
+ architecture: {
480
+ layout: "architecture",
481
+ purpose: "Show a layered architecture with components and notes.",
482
+ whenToUse: "Use for systems, model stacks, or pipeline architecture.",
483
+ fields: {
484
+ layout: { type: "literal", required: true, value: "architecture" },
485
+ title: { type: "string", required: true },
486
+ layers: { type: "{ title, components, note? }[]", required: true },
487
+ },
488
+ limits: { layers: { maxItems: 4 }, layerTitle: { recommendedChars: 8 }, components: { maxItems: 5, recommendedChars: 10 }, note: { recommendedChars: 44 } },
489
+ notes: ["Layer order is top-to-bottom.", "Use short component labels so chips stay readable."],
490
+ example: { layout: "architecture", title: "系统架构", layers: [{ title: "输入层", components: ["YAML", "图片"], note: "收集结构化材料。" }, { title: "生成层", components: ["校验", "PPTX", "OMML"], note: "写入可编辑对象。" }] },
491
+ },
492
+ ablation: {
493
+ layout: "ablation",
494
+ purpose: "Summarize ablation factors, settings, deltas, and conclusions.",
495
+ whenToUse: "Use for research experiments that remove or vary components.",
496
+ fields: {
497
+ layout: { type: "literal", required: true, value: "ablation" },
498
+ title: { type: "string", required: true },
499
+ baseline: { type: "string", required: false },
500
+ items: { type: "{ factor, setting, delta, conclusion }[]", required: true },
501
+ },
502
+ limits: { baseline: { recommendedChars: 58 }, items: { maxItems: 6 }, factor: { recommendedChars: 18 }, setting: { recommendedChars: 18 }, delta: { recommendedChars: 18 }, conclusion: { recommendedChars: 28 } },
503
+ notes: ["Use `delta` for numeric or qualitative change.", "Keep conclusions short enough for table-like rendering."],
504
+ example: { layout: "ablation", title: "消融实验", baseline: "基线启用所有模块。", items: [{ factor: "预检", setting: "关闭", delta: "+3 overflow", conclusion: "长列表风险上升" }] },
505
+ },
506
+ caseStudy: {
507
+ layout: "caseStudy",
508
+ purpose: "Show one visual case with context, method, and result.",
509
+ whenToUse: "Use for qualitative examples, screenshots, or representative samples.",
510
+ fields: {
511
+ layout: { type: "literal", required: true, value: "caseStudy" },
512
+ title: { type: "string", required: true },
513
+ image: { type: "string | { path, fit? } | { mode: placeholder, prompt, aspectRatio? }", required: true },
514
+ caption: { type: "string", required: false },
515
+ context: { type: "string[]", required: false },
516
+ method: { type: "string[]", required: false },
517
+ result: { type: "string[]", required: false },
518
+ },
519
+ limits: { caption: { recommendedChars: 40 }, context: { maxItems: 2, recommendedChars: 34 }, method: { maxItems: 2, recommendedChars: 34 }, result: { maxItems: 2, recommendedChars: 34 } },
520
+ notes: ["Image placeholders are supported.", "Use `imageGrid` when comparing multiple images."],
521
+ example: { layout: "caseStudy", title: "案例分析", image: "assets/bit-campus-photo.png", caption: "示例图片,可替换为实验结果图。", context: ["输入是一段论文草稿。"], method: ["生成器按 layout 写入 PPTX。"], result: ["输出可继续编辑。"] },
522
+ },
523
+ imageGrid: {
524
+ layout: "imageGrid",
525
+ purpose: "Show multiple images in a regular grid.",
526
+ whenToUse: "Use for visual result sets, comparisons, or qualitative examples.",
527
+ fields: {
528
+ layout: { type: "literal", required: true, value: "imageGrid" },
529
+ title: { type: "string", required: true },
530
+ images: { type: "(string | { path, caption? } | { mode: placeholder, prompt, caption?, aspectRatio? })[]", required: true },
531
+ },
532
+ limits: { images: { maxItems: 6 }, caption: { recommendedChars: 16 } },
533
+ notes: ["Each image can be a local path or an image object.", "Use short captions to avoid crowding the grid."],
534
+ example: { layout: "imageGrid", title: "多图结果", images: [{ path: "assets/bit-campus-photo.png", caption: "输入" }, { path: "assets/bit-campus-photo.png", caption: "输出" }] },
535
+ },
536
+ code: {
537
+ layout: "code",
538
+ purpose: "Show pseudocode or a compact code block with notes.",
539
+ whenToUse: "Use for algorithms, command snippets, or implementation sketches.",
540
+ fields: {
541
+ layout: { type: "literal", required: true, value: "code" },
542
+ title: { type: "string", required: true },
543
+ language: { type: "string", required: false },
544
+ code: { type: "string", required: false },
545
+ algorithm: { type: "string", required: false },
546
+ noteTitle: { type: "string", required: false },
547
+ notes: { type: "string[]", required: false },
548
+ },
549
+ limits: { code: { recommendedLines: 12, recommendedCharsPerLine: 72 }, notes: { maxItems: 5, recommendedChars: 32 } },
550
+ notes: ["Use either `code` or `algorithm`; `code` takes precedence.", "A YAML block scalar is recommended for multi-line code."],
551
+ example: { layout: "code", title: "算法伪代码", language: "Algorithm", code: "Input: deck D\n1. validate D\n2. generate PPTX", noteTitle: "关键约束", notes: ["代码块保持短行。"] },
552
+ },
553
+ appendix: {
554
+ layout: "appendix",
555
+ purpose: "Create an appendix index or supplemental topic list.",
556
+ whenToUse: "Use for backup slides, extra details, or appendix navigation.",
557
+ fields: {
558
+ layout: { type: "literal", required: true, value: "appendix" },
559
+ title: { type: "string", required: true },
560
+ items: { type: "{ key?, title, text }[]", required: true },
561
+ },
562
+ limits: { items: { maxItems: 8 }, itemTitle: { recommendedChars: 14 }, itemText: { recommendedChars: 42 } },
563
+ notes: ["`key` is optional and defaults to a numbered label.", "Use this as an index, not as dense body text."],
564
+ example: { layout: "appendix", title: "附录索引", items: [{ key: "A1", title: "数据细节", text: "样本来源与筛选规则。" }] },
565
+ },
566
+ metrics: {
567
+ layout: "metrics",
568
+ purpose: "Highlight up to four key metrics.",
569
+ whenToUse: "Use when numeric or compact KPI-style facts should dominate the slide.",
570
+ fields: {
571
+ layout: { type: "literal", required: true, value: "metrics" },
572
+ title: { type: "string", required: true },
573
+ metrics: { type: "{ value, label, note? }[]", required: true },
574
+ },
575
+ limits: { metrics: { maxItems: 4 }, value: { recommendedChars: 8 }, label: { recommendedChars: 12 }, note: { recommendedChars: 30 } },
576
+ notes: ["Use compact values such as `12+`, `-8%`, or `0`.", "Use `chart` when the reader needs to inspect a trend or distribution."],
577
+ example: { layout: "metrics", title: "关键指标", metrics: [{ value: "12+", label: "正文页型", note: "覆盖常见汇报页面" }, { value: "100%", label: "可编辑", note: "原生 PPTX 对象" }] },
578
+ },
579
+ matrix: {
580
+ layout: "matrix",
581
+ purpose: "Show four quadrants or a compact 2x2 decision matrix.",
582
+ whenToUse: "Use for strategy tradeoffs, option classification, or two-axis judgment.",
583
+ fields: {
584
+ layout: { type: "literal", required: true, value: "matrix" },
585
+ title: { type: "string", required: true },
586
+ cells: { type: "{ title, text }[]", required: true },
587
+ },
588
+ limits: { cells: { maxItems: 4 }, cellTitle: { recommendedChars: 18 }, cellText: { recommendedChars: 52 } },
589
+ notes: ["Cells render row-major in a 2x2 grid.", "Provide exactly four cells for a complete matrix."],
590
+ example: { layout: "matrix", title: "方案判断矩阵", cells: [{ title: "低成本 / 高控制", text: "YAML 内容加模板生成。" }, { title: "高成本 / 高控制", text: "完整设计系统与截图校验。" }] },
591
+ },
592
+ quote: {
593
+ layout: "quote",
594
+ purpose: "Show a short quote or memorable statement.",
595
+ whenToUse: "Use for thesis statements, external quotes, or section openers.",
596
+ fields: {
597
+ layout: { type: "literal", required: true, value: "quote" },
598
+ title: { type: "string", required: false },
599
+ quote: { type: "string", required: true },
600
+ source: { type: "string", required: false },
601
+ },
602
+ limits: { quote: { recommendedChars: 70 } },
603
+ notes: ["Keep the quote short enough to remain visually dominant.", "`source` is optional and rendered below the quote."],
604
+ example: { layout: "quote", title: "引用页", quote: "让模型生成结构化内容,让模板承担视觉和排版责任。", source: "BIT PPT Template Generator" },
605
+ },
606
+ references: {
607
+ layout: "references",
608
+ purpose: "List references or citations.",
609
+ whenToUse: "Use near the end of academic or research decks.",
610
+ fields: {
611
+ layout: { type: "literal", required: true, value: "references" },
612
+ title: { type: "string", required: false },
613
+ items: { type: "string[]", required: true },
614
+ fontSize: { type: "number", required: false },
615
+ },
616
+ limits: { items: { recommendedChars: 140 } },
617
+ notes: ["Long reference lists may be split during preflight.", "Keep each reference as one string item."],
618
+ example: { layout: "references", title: "参考文献", items: ["PptxGenJS project documentation. Generate editable PowerPoint presentations with JavaScript."] },
619
+ },
620
+ closing: {
621
+ layout: "closing",
622
+ purpose: "Create the final thank-you slide.",
623
+ whenToUse: "Use as the final slide of a deck.",
624
+ fields: {
625
+ layout: { type: "literal", required: true, value: "closing" },
626
+ title: { type: "string", required: false },
627
+ subtitle: { type: "string", required: false },
628
+ },
629
+ limits: { title: { recommendedChars: 12 }, subtitle: { recommendedChars: 28 } },
630
+ notes: ["Defaults to a BIT green closing slide.", "Use `subtitle` for defense/Q&A wording."],
631
+ example: { layout: "closing", title: "谢谢", subtitle: "敬请各位老师批评指正" },
632
+ },
633
+ };
634
+
635
+ Object.assign(layoutGuides, additionalLayoutGuides);
636
+
229
637
  function listGuideLayouts() {
230
- return Object.keys(layoutGuides);
638
+ return listSupportedLayouts();
231
639
  }
232
640
 
233
641
  function getLayoutGuide(layout) {