bit-ppt-generator 0.3.0 → 0.3.2

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,107 @@ 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
- 输出文件位于:
81
122
 
82
- ```text
83
- output/example.pptx
84
- output/body-layout-test.pptx
85
- output/chart-flow-test.pptx
86
- ```
123
+ ## 仓库开发
87
124
 
88
- 手动指定输入和输出:
125
+ 克隆仓库后安装依赖:
89
126
 
90
127
  ```powershell
91
- node bin/bit-ppt.mjs generate content/example.yaml output/example.pptx
128
+ npm install
92
129
  ```
93
130
 
94
- 先检查再生成:
131
+ 启动本地网页:
95
132
 
96
133
  ```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
134
+ npm run serve
99
135
  ```
100
136
 
101
- 严格模式会把 warning 也视为失败:
137
+ 生成示例 PPT:
102
138
 
103
139
  ```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
140
+ npm run build:ppt
141
+ npm run build:body-layouts
142
+ npm run build:charts
106
143
  ```
107
144
 
108
- ## Release Targets
145
+ 源码入口:
109
146
 
110
- 本仓库采用单主干、多入口策略。CLI、MCP 和 Node HTTP 服务都复用同一套核心生成逻辑。
147
+ ```powershell
148
+ node bin/bit-ppt.mjs --help
149
+ node bin/bit-ppt-http.mjs --help
150
+ node bin/bit-ppt-mcp.mjs --help
151
+ ```
111
152
 
112
- 当前状态:
153
+ 本仓库采用单主干、多入口策略。Web UI、CLI、MCP 和 Node HTTP API 都复用同一套核心生成逻辑。
154
+
155
+ ## npm 包入口
113
156
 
114
157
  - Web UI:默认入口为 `bit-ppt-generator`,适合 npm 用户本地打开网页
115
158
  - CLI:已支持,入口为 `bit-ppt` 或 `node bin/bit-ppt.mjs`
116
159
  - MCP:已支持,入口为 `bit-ppt-mcp` 或 `node bin/bit-ppt-mcp.mjs`
117
160
  - Node HTTP API:已支持,入口为 `bit-ppt-http` 或 `node bin/bit-ppt-http.mjs`
118
161
 
119
- 当前 npm 包名预定为 `bit-ppt-generator`。一个 npm release 同时包含 Web UI、CLI 和 MCP 三个入口,不拆成三个包。
162
+ 一个 npm release 同时包含 Web UI、CLI 和 MCP 三个入口,不拆成三个包。
120
163
 
121
164
  ## CLI 命令
122
165
 
@@ -134,10 +177,10 @@ bit-ppt-http
134
177
  常用命令:
135
178
 
136
179
  ```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
180
+ bit-ppt list-layouts
181
+ bit-ppt list-layouts --json
182
+ bit-ppt doctor
183
+ bit-ppt doctor --json
141
184
  ```
142
185
 
143
186
  `doctor` 会检查 Node 版本、依赖、关键素材、示例 deck、输出目录可写性。
@@ -150,24 +193,23 @@ MCP 只是 adapter,仍复用 CLI 背后的同一套生成和校验函数。
150
193
  本地运行:
151
194
 
152
195
  ```powershell
153
- node bin/bit-ppt-mcp.mjs
196
+ bit-ppt-mcp
154
197
  ```
155
198
 
156
- 全局链接后:
199
+ 从源码运行:
157
200
 
158
201
  ```powershell
159
- bit-ppt-mcp
202
+ node bin/bit-ppt-mcp.mjs
160
203
  ```
161
204
 
162
- MCP 客户端配置示例:
205
+ MCP 客户端配置示例,优先使用 npm 包命令:
163
206
 
164
207
  ```json
165
208
  {
166
209
  "mcpServers": {
167
210
  "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"
211
+ "command": "bit-ppt-mcp",
212
+ "args": []
171
213
  }
172
214
  }
173
215
  }
@@ -593,6 +635,32 @@ npm run check:charts
593
635
  npm pack --dry-run
594
636
  ```
595
637
 
638
+ ## 发布
639
+
640
+ npm 包名为 `bit-ppt-generator`。后续版本通过 GitHub Release 触发
641
+ `.github/workflows/publish.yml`,并使用 npm Trusted Publisher 发布,不需要在
642
+ GitHub Secrets 保存 npm token。
643
+
644
+ Trusted Publisher 配置:
645
+
646
+ ```text
647
+ Publisher: GitHub Actions
648
+ Organization or user: yang-kun-long
649
+ Repository: bit-ppt-template
650
+ Workflow filename: publish.yml
651
+ Environment name: 留空
652
+ ```
653
+
654
+ 发布新版本:
655
+
656
+ ```powershell
657
+ npm version patch
658
+ git push
659
+ git push --tags
660
+ ```
661
+
662
+ 随后在 GitHub 创建并发布对应 tag 的 Release。
663
+
596
664
  ## 项目结构
597
665
 
598
666
  ```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.2",
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
  }
@@ -40,11 +40,25 @@ const WEB_APP_HTML = String.raw`<!doctype html>
40
40
  button { border: 1px solid var(--red); background: var(--red); color: #fff; border-radius: 6px; padding: 9px 12px; font: inherit; cursor: pointer; }
41
41
  button.secondary { background: #fff; color: var(--red); }
42
42
  button:disabled { opacity: .55; cursor: not-allowed; }
43
+ a { color: var(--red); text-decoration: none; }
44
+ a:hover { text-decoration: underline; }
45
+ .intro { margin: 0 0 14px; color: var(--muted); font-size: 13px; line-height: 1.6; }
46
+ .repo-link { display: inline-flex; align-items: center; gap: 6px; margin-bottom: 12px; font-size: 13px; font-weight: 650; }
47
+ .repo-icon { width: 16px; height: 16px; fill: currentColor; flex: 0 0 auto; }
48
+ .guide { display: grid; gap: 10px; margin: 10px 0 14px; }
49
+ .guide-step { border-left: 3px solid #d9b6b8; padding: 2px 0 2px 10px; }
50
+ .guide-step strong { display: block; margin-bottom: 4px; font-size: 13px; }
51
+ .guide-step p { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.55; }
52
+ .yaml-section { min-width: 0; }
53
+ .yaml-workspace { display: grid; gap: 16px; grid-template-columns: minmax(0, 1fr) 290px; align-items: start; }
54
+ .yaml-workspace textarea { min-height: 640px; }
55
+ .side-guide { border-left: 1px solid var(--line); padding-left: 16px; }
43
56
  .status { min-height: 42px; white-space: pre-wrap; font-family: Consolas, "Cascadia Mono", monospace; font-size: 12px; color: var(--muted); }
44
57
  .ok { color: #137333; }
45
58
  .err { color: #b3261e; }
46
59
  .hidden { display: none; }
47
- @media (max-width: 860px) { main { grid-template-columns: 1fr; padding: 14px; } textarea { min-height: 420px; } }
60
+ @media (max-width: 1060px) { .yaml-workspace { grid-template-columns: 1fr; } .side-guide { border-left: 0; border-top: 1px solid var(--line); padding: 14px 0 0; } }
61
+ @media (max-width: 860px) { main { grid-template-columns: 1fr; padding: 14px; } textarea, .yaml-workspace textarea { min-height: 420px; } }
48
62
  </style>
49
63
  </head>
50
64
  <body>
@@ -63,6 +77,13 @@ const WEB_APP_HTML = String.raw`<!doctype html>
63
77
  </div>
64
78
  <div id="authStatus" class="status"></div>
65
79
  </div>
80
+ <a class="repo-link" href="https://github.com/yang-kun-long/bit-ppt-template" target="_blank" rel="noreferrer">
81
+ <svg class="repo-icon" viewBox="0 0 16 16" aria-hidden="true">
82
+ <path d="M8 0C3.58 0 0 3.64 0 8.13c0 3.59 2.29 6.63 5.47 7.7.4.07.55-.18.55-.39 0-.19-.01-.83-.01-1.51-2.01.38-2.53-.5-2.69-.96-.09-.23-.48-.96-.82-1.15-.28-.15-.68-.52-.01-.53.63-.01 1.08.59 1.23.83.72 1.23 1.87.88 2.33.67.07-.53.28-.88.51-1.09-1.78-.2-3.64-.9-3.64-4.01 0-.89.31-1.61.82-2.18-.08-.2-.36-1.03.08-2.15 0 0 .67-.22 2.2.83A7.5 7.5 0 0 1 8 3.92c.68 0 1.36.09 2 .27 1.53-1.05 2.2-.83 2.2-.83.44 1.12.16 1.95.08 2.15.51.57.82 1.29.82 2.18 0 3.12-1.87 3.81-3.65 4.01.29.25.54.74.54 1.5 0 1.09-.01 1.97-.01 2.24 0 .21.15.46.55.39A8.03 8.03 0 0 0 16 8.13C16 3.64 12.42 0 8 0Z"/>
83
+ </svg>
84
+ GitHub 仓库
85
+ </a>
86
+ <p class="intro">这是一个把 YAML 生成可编辑 PPTX 的网页入口。推荐先让 AI 产出 YAML,再在这里检查和生成;报错时把检查结果交给 AI 修改。</p>
66
87
  <h2>输出</h2>
67
88
  <label for="outputName">文件名</label>
68
89
  <input id="outputName" value="bit-ppt" />
@@ -75,6 +96,8 @@ const WEB_APP_HTML = String.raw`<!doctype html>
75
96
  <div class="row">
76
97
  <button id="copyPromptBtn" class="secondary">复制提示词</button>
77
98
  <button id="copyRulesBtn" class="secondary">复制语法规则</button>
99
+ <button id="copyWorkflowBtn" class="secondary">复制使用教程</button>
100
+ <button id="copyErrorHelpBtn" class="secondary">复制报错求助</button>
78
101
  <button id="insertExampleBtn" class="secondary">插入最小示例</button>
79
102
  <button id="insertFullExampleBtn" class="secondary">插入完整示例</button>
80
103
  </div>
@@ -343,6 +366,42 @@ YAML 与公式注意事项:
343
366
  - 表格中有公式时,建议用单引号包住单元格。
344
367
  - 图片没有真实路径时使用 placeholder,不要写不存在的 assets 路径。
345
368
  - 每页文字保持短;如果 check 返回 warnings,按 repairPrompt 压缩或拆页。</textarea>
369
+ <textarea id="workflowGuide" hidden>请按这个流程和 AI 协作生成 PPT:
370
+
371
+ 1. 先发“提示词”
372
+ - 告诉 AI:只输出 YAML,不要 Markdown 代码块,不要解释。
373
+ - 补充你的任务:主题、页数、受众、用途、是否需要演讲稿。
374
+
375
+ 2. 再发“语法规则”
376
+ - 让 AI 使用支持的 layout 和字段。
377
+ - 告诉 AI:没有真实图片路径时必须用 image.mode: placeholder。
378
+ - 告诉 AI:公式用 LaTeX,生成后要能通过 check。
379
+
380
+ 3. 最后发材料
381
+ - 可以贴论文摘要、章节结构、实验结果、表格数据、图片说明、参考文献。
382
+ - 要求 AI 生成完整 YAML。
383
+
384
+ 4. 在网页里检查
385
+ - 把 YAML 粘到网页的 YAML 输入框,先点“检查”。
386
+ - 没有 errors 再点“生成 PPTX”。
387
+
388
+ 5. 如果检查有问题
389
+ - 把 check 返回的 errors、warnings、repairPrompt 和当前 YAML 一起发给 AI。
390
+ - 要求 AI 只修改 YAML,不要解释,不要改变核心内容。</textarea>
391
+ <textarea id="errorHelpPrompt" hidden>下面是 BIT PPT Generator 的检查/生成报错。请你只输出修复后的完整 YAML,不要 Markdown 代码块,不要解释。
392
+
393
+ 修复要求:
394
+ 1. 保留原始内容意图和页面顺序。
395
+ 2. 修复未知 layout、字段结构错误、YAML 语法错误。
396
+ 3. 如果 warnings 说明文字过长,请压缩文字或拆成多页。
397
+ 4. 如果图片路径不存在,请改成 image.mode: placeholder,并补充具体 prompt。
398
+ 5. 如果公式导致 YAML 转义问题,请避免双引号,优先使用 plain scalar 或单引号。
399
+ 6. 输出必须能重新通过 check。
400
+
401
+ 网页报错 / check 结果:
402
+
403
+ 当前 YAML:
404
+ </textarea>
346
405
  <textarea id="exampleYaml" hidden>meta:
347
406
  title: 北理工风格汇报
348
407
  author: BIT PPT Generator
@@ -617,8 +676,9 @@ slides:
617
676
  收尾时提示听众:生成结果是可编辑 PPTX。
618
677
  后续可以在 PowerPoint 或 WPS 中继续调整。</textarea>
619
678
  </section>
620
- <section>
679
+ <section class="yaml-section">
621
680
  <h2>YAML</h2>
681
+ <div class="yaml-workspace">
622
682
  <textarea id="yaml" spellcheck="false">meta:
623
683
  title: 北理工风格 PPT
624
684
  author: BIT PPT Generator
@@ -633,6 +693,28 @@ slides:
633
693
  - 输出为可编辑 PPTX
634
694
  - 支持公式、图表和多种版式
635
695
  </textarea>
696
+ <aside class="side-guide">
697
+ <h3>使用教程</h3>
698
+ <div class="guide">
699
+ <div class="guide-step">
700
+ <strong>1. 先复制提示词</strong>
701
+ <p>发给 AI,说明它的角色、输出格式和页面限制。再补一句你的主题,例如“请做一份 10 页组会汇报”。</p>
702
+ </div>
703
+ <div class="guide-step">
704
+ <strong>2. 再复制语法规则</strong>
705
+ <p>继续发给 AI,让它按支持的 layout 和字段写 YAML。最后把论文摘要、实验结果、表格数据、图片说明等材料贴给 AI。</p>
706
+ </div>
707
+ <div class="guide-step">
708
+ <strong>3. 粘贴 YAML 后先检查</strong>
709
+ <p>把 AI 输出粘到左侧输入框,点“检查”。如果有 errors / warnings,把检查结果和当前 YAML 一起发回 AI,让它只修 YAML。</p>
710
+ </div>
711
+ <div class="guide-step">
712
+ <strong>4. 生成失败时这样沟通</strong>
713
+ <p>复制“报错求助”模板,把网页显示的错误、check 结果、当前 YAML 一起发给 AI;要求它保留内容意图,只修字段、长度、图片路径或公式写法。</p>
714
+ </div>
715
+ </div>
716
+ </aside>
717
+ </div>
636
718
  </section>
637
719
  </main>
638
720
  <script>
@@ -795,6 +877,8 @@ slides:
795
877
  $("generateBtn").addEventListener("click", generateDeck);
796
878
  $("copyPromptBtn").addEventListener("click", () => copyTextFrom("aiPrompt", "提示词"));
797
879
  $("copyRulesBtn").addEventListener("click", () => copyTextFrom("syntaxRules", "语法规则"));
880
+ $("copyWorkflowBtn").addEventListener("click", () => copyTextFrom("workflowGuide", "使用教程"));
881
+ $("copyErrorHelpBtn").addEventListener("click", () => copyTextFrom("errorHelpPrompt", "报错求助模板"));
798
882
  $("insertExampleBtn").addEventListener("click", insertExample);
799
883
  $("insertFullExampleBtn").addEventListener("click", insertFullExample);
800
884
  loadServerConfig();
@@ -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) {