bit-ppt-generator 0.3.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.
- package/AI_CONTENT_GUIDE.md +661 -0
- package/LICENSE +21 -0
- package/README.md +620 -0
- package/assets/bit-campus-line.png +0 -0
- package/assets/bit-campus-photo.png +0 -0
- package/assets/bit-emblem-gray.png +0 -0
- package/assets/bit-seal-small.png +0 -0
- package/assets/bit-wordmark-white.png +0 -0
- package/bin/bit-ppt-http.mjs +58 -0
- package/bin/bit-ppt-mcp.mjs +27 -0
- package/bin/bit-ppt.mjs +480 -0
- package/content/body-layout-test.yaml +112 -0
- package/content/chart-flow-test.yaml +82 -0
- package/content/example.yaml +193 -0
- package/content/extended-layout-test.yaml +120 -0
- package/content/formula-test.yaml +31 -0
- package/content/image-layout-demo.yaml +64 -0
- package/content/inline-formula-test.yaml +62 -0
- package/content/invalid-deck-test.yaml +25 -0
- package/content/overflow-test.yaml +77 -0
- package/content/placeholder-image-demo.yaml +59 -0
- package/content/speaker-notes-demo.yaml +30 -0
- package/content/table-formula-test.yaml +21 -0
- package/package.json +42 -0
- package/src/core/layouts.mjs +58 -0
- package/src/core/preflight.mjs +263 -0
- package/src/core/validation.mjs +372 -0
- package/src/core/yaml-parse.mjs +80 -0
- package/src/generate.mjs +1708 -0
- package/src/http-server.mjs +1201 -0
- package/src/layout-guides.mjs +315 -0
- package/src/mcp-server.mjs +197 -0
package/bin/bit-ppt.mjs
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
ROOT,
|
|
7
|
+
checkDeckFile,
|
|
8
|
+
defaultFont,
|
|
9
|
+
generateDeckFile,
|
|
10
|
+
listLayouts,
|
|
11
|
+
} from "../src/generate.mjs";
|
|
12
|
+
import {
|
|
13
|
+
getAllGuides,
|
|
14
|
+
getGuideOverview,
|
|
15
|
+
getGuideWorkflow,
|
|
16
|
+
getImagePlaceholderGuide,
|
|
17
|
+
getLayoutExample,
|
|
18
|
+
getLayoutGuide,
|
|
19
|
+
getLayoutSchema,
|
|
20
|
+
getSpeakerNotesGuide,
|
|
21
|
+
getWritingRules,
|
|
22
|
+
listGuideLayouts,
|
|
23
|
+
} from "../src/layout-guides.mjs";
|
|
24
|
+
|
|
25
|
+
function printHelp() {
|
|
26
|
+
console.log(`bit-ppt
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
bit-ppt generate <input.yaml> <output.pptx> [--json] [--strict] [font options]
|
|
30
|
+
bit-ppt check <input.yaml> [--json] [--strict]
|
|
31
|
+
bit-ppt list-layouts [--json]
|
|
32
|
+
bit-ppt guide [topic] [name] [--json]
|
|
33
|
+
bit-ppt doctor [--json]
|
|
34
|
+
|
|
35
|
+
Progressive guide:
|
|
36
|
+
bit-ppt guide
|
|
37
|
+
bit-ppt guide all --json
|
|
38
|
+
bit-ppt guide workflow --json
|
|
39
|
+
bit-ppt guide layouts
|
|
40
|
+
bit-ppt guide layout imageText
|
|
41
|
+
bit-ppt guide schema imageText --json
|
|
42
|
+
bit-ppt guide example imageText
|
|
43
|
+
bit-ppt guide speaker-notes
|
|
44
|
+
bit-ppt guide image-placeholder
|
|
45
|
+
bit-ppt guide writing-rules
|
|
46
|
+
|
|
47
|
+
Quality options:
|
|
48
|
+
--json Print machine-readable JSON where supported
|
|
49
|
+
--strict Treat validation warnings as failures
|
|
50
|
+
|
|
51
|
+
Font options:
|
|
52
|
+
--font-cn <name> Chinese/CJK font, default: ${defaultFont.cn}
|
|
53
|
+
--font-cn-light <name> Light Chinese/CJK font, default: ${defaultFont.cnLight}
|
|
54
|
+
--font-en <name> Latin font, default: ${defaultFont.en}
|
|
55
|
+
--font-serif <name> Serif font, default: ${defaultFont.serif}
|
|
56
|
+
--font-code <name> Code font, default: ${defaultFont.code}
|
|
57
|
+
`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function printJson(value) {
|
|
61
|
+
console.log(JSON.stringify(value, null, 2));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function printGuideOverview(overview) {
|
|
65
|
+
console.log(`${overview.name}
|
|
66
|
+
|
|
67
|
+
${overview.purpose}
|
|
68
|
+
|
|
69
|
+
Workflow:
|
|
70
|
+
${overview.workflow.map((item, idx) => ` ${idx + 1}. ${item}`).join("\n")}
|
|
71
|
+
|
|
72
|
+
Guide commands:
|
|
73
|
+
${overview.commands.map((item) => ` ${item}`).join("\n")}
|
|
74
|
+
|
|
75
|
+
Guided layouts:
|
|
76
|
+
${overview.guidedLayouts.join(", ")}
|
|
77
|
+
`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function printLayoutGuide(guide) {
|
|
81
|
+
console.log(`${guide.layout}
|
|
82
|
+
|
|
83
|
+
Purpose:
|
|
84
|
+
${guide.purpose}
|
|
85
|
+
|
|
86
|
+
Use when:
|
|
87
|
+
${guide.whenToUse}
|
|
88
|
+
|
|
89
|
+
Fields:
|
|
90
|
+
${Object.entries(guide.fields).map(([name, spec]) => {
|
|
91
|
+
const required = spec.required ? "required" : "optional";
|
|
92
|
+
const value = spec.value ? ` = ${spec.value}` : "";
|
|
93
|
+
return ` ${name}: ${spec.type}${value} (${required})`;
|
|
94
|
+
}).join("\n")}
|
|
95
|
+
|
|
96
|
+
Limits:
|
|
97
|
+
${Object.entries(guide.limits || {}).map(([name, spec]) => ` ${name}: ${Object.entries(spec).map(([key, value]) => `${key}=${value}`).join(", ")}`).join("\n") || " none"}
|
|
98
|
+
|
|
99
|
+
Notes:
|
|
100
|
+
${guide.notes.map((item) => ` - ${item}`).join("\n")}
|
|
101
|
+
`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function printExample(example) {
|
|
105
|
+
console.log(JSON.stringify(example, null, 2));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function printWritingRules(rules) {
|
|
109
|
+
console.log(`Writing rules:
|
|
110
|
+
${rules.map((item) => ` - ${item}`).join("\n")}
|
|
111
|
+
`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function printSpeakerNotesGuide(guide) {
|
|
115
|
+
console.log(`Speaker notes
|
|
116
|
+
|
|
117
|
+
Purpose:
|
|
118
|
+
${guide.purpose}
|
|
119
|
+
|
|
120
|
+
Fields:
|
|
121
|
+
${Object.entries(guide.fields).map(([name, spec]) => {
|
|
122
|
+
const required = spec.required ? "required" : "optional";
|
|
123
|
+
return ` ${name}: ${spec.type} (${required})`;
|
|
124
|
+
}).join("\n")}
|
|
125
|
+
|
|
126
|
+
Aliases:
|
|
127
|
+
${guide.aliases.join(", ")}
|
|
128
|
+
|
|
129
|
+
Limits:
|
|
130
|
+
${Object.entries(guide.limits || {}).map(([name, spec]) => ` ${name}: ${Object.entries(spec).map(([key, value]) => `${key}=${value}`).join(", ")}`).join("\n")}
|
|
131
|
+
|
|
132
|
+
Notes:
|
|
133
|
+
${guide.notes.map((item) => ` - ${item}`).join("\n")}
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
${JSON.stringify(guide.example, null, 2)}
|
|
137
|
+
`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function printImagePlaceholderGuide(guide) {
|
|
141
|
+
console.log(`Image placeholder
|
|
142
|
+
|
|
143
|
+
Purpose:
|
|
144
|
+
${guide.purpose}
|
|
145
|
+
|
|
146
|
+
Use when:
|
|
147
|
+
${guide.whenToUse}
|
|
148
|
+
|
|
149
|
+
Fields:
|
|
150
|
+
${Object.entries(guide.fields).map(([name, spec]) => {
|
|
151
|
+
const required = spec.required ? "required" : "optional";
|
|
152
|
+
return ` ${name}: ${spec.type} (${required})`;
|
|
153
|
+
}).join("\n")}
|
|
154
|
+
|
|
155
|
+
Limits:
|
|
156
|
+
${Object.entries(guide.limits || {}).map(([name, spec]) => ` ${name}: ${Object.entries(spec).map(([key, value]) => `${key}=${value}`).join(", ")}`).join("\n")}
|
|
157
|
+
|
|
158
|
+
Notes:
|
|
159
|
+
${guide.notes.map((item) => ` - ${item}`).join("\n")}
|
|
160
|
+
|
|
161
|
+
Example:
|
|
162
|
+
${JSON.stringify(guide.example, null, 2)}
|
|
163
|
+
`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function printAvailableGuideLayouts() {
|
|
167
|
+
console.log(listGuideLayouts().join("\n"));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function printWorkflow(workflow) {
|
|
171
|
+
console.log(`Workflow:
|
|
172
|
+
${workflow.map((item, idx) => ` ${idx + 1}. ${item}`).join("\n")}
|
|
173
|
+
`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function printGuideHelp() {
|
|
177
|
+
console.log(`bit-ppt guide
|
|
178
|
+
|
|
179
|
+
Usage:
|
|
180
|
+
bit-ppt guide
|
|
181
|
+
bit-ppt guide all --json
|
|
182
|
+
bit-ppt guide workflow [--json]
|
|
183
|
+
bit-ppt guide layouts
|
|
184
|
+
bit-ppt guide layout <name> [--json]
|
|
185
|
+
bit-ppt guide schema <name> --json
|
|
186
|
+
bit-ppt guide example <name> [--json]
|
|
187
|
+
bit-ppt guide speaker-notes [--json]
|
|
188
|
+
bit-ppt guide image-placeholder [--json]
|
|
189
|
+
bit-ppt guide writing-rules [--json]
|
|
190
|
+
`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function doctorCheck(name, ok, message, details = {}) {
|
|
194
|
+
return { name, ok, message, ...details };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function canImportPackage(packageName) {
|
|
198
|
+
try {
|
|
199
|
+
import.meta.resolve(packageName);
|
|
200
|
+
return true;
|
|
201
|
+
} catch {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function runDoctor() {
|
|
207
|
+
const checks = [];
|
|
208
|
+
const version = process.versions.node;
|
|
209
|
+
const major = Number(version.split(".")[0]);
|
|
210
|
+
checks.push(doctorCheck("node", major >= 18, `Node.js ${version}`, { version }));
|
|
211
|
+
|
|
212
|
+
["pptxgenjs", "yaml", "jszip", "latex-to-omml", "@modelcontextprotocol/sdk", "zod"].forEach((packageName) => {
|
|
213
|
+
const available = canImportPackage(packageName);
|
|
214
|
+
checks.push(doctorCheck(`dependency:${packageName}`, available, available ? `${packageName} is available` : `${packageName} is missing`));
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
["assets/bit-campus-photo.png", "assets/bit-campus-line.png", "content/example.yaml", "content/body-layout-test.yaml", "content/chart-flow-test.yaml"].forEach((relativePath) => {
|
|
218
|
+
const fileName = path.join(ROOT, relativePath);
|
|
219
|
+
checks.push(doctorCheck(`file:${relativePath}`, fs.existsSync(fileName), fs.existsSync(fileName) ? "found" : "missing", { path: fileName }));
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
[
|
|
223
|
+
["fixture:example", "content/example.yaml"],
|
|
224
|
+
["fixture:body-layouts", "content/body-layout-test.yaml"],
|
|
225
|
+
["fixture:charts", "content/chart-flow-test.yaml"],
|
|
226
|
+
].forEach(([name, relativePath]) => {
|
|
227
|
+
try {
|
|
228
|
+
const result = checkDeckFile(path.join(ROOT, relativePath));
|
|
229
|
+
checks.push(doctorCheck(name, result.validation.errors.length === 0, result.validation.errors.length ? "validation errors found" : "check passed", {
|
|
230
|
+
warnings: result.validation.warnings.length,
|
|
231
|
+
errors: result.validation.errors.length,
|
|
232
|
+
}));
|
|
233
|
+
} catch (error) {
|
|
234
|
+
checks.push(doctorCheck(name, false, error.message || String(error)));
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
const outputDir = path.join(ROOT, "output");
|
|
240
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
241
|
+
const probe = path.join(outputDir, `.bit-ppt-doctor-${process.pid}.tmp`);
|
|
242
|
+
fs.writeFileSync(probe, "ok");
|
|
243
|
+
fs.unlinkSync(probe);
|
|
244
|
+
checks.push(doctorCheck("output:writable", true, "output directory is writable", { path: outputDir }));
|
|
245
|
+
} catch (error) {
|
|
246
|
+
checks.push(doctorCheck("output:writable", false, error.message || String(error)));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const ok = checks.every((item) => item.ok);
|
|
250
|
+
return {
|
|
251
|
+
ok,
|
|
252
|
+
platform: process.platform,
|
|
253
|
+
arch: process.arch,
|
|
254
|
+
cwd: process.cwd(),
|
|
255
|
+
root: ROOT,
|
|
256
|
+
checks,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function printDoctor(result) {
|
|
261
|
+
console.log(`BIT PPT doctor: ${result.ok ? "ok" : "failed"}
|
|
262
|
+
|
|
263
|
+
Root:
|
|
264
|
+
${result.root}
|
|
265
|
+
|
|
266
|
+
Checks:
|
|
267
|
+
${result.checks.map((item) => ` ${item.ok ? "OK" : "FAIL"} ${item.name}: ${item.message}`).join("\n")}
|
|
268
|
+
`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function parseOptions(args) {
|
|
272
|
+
const options = {};
|
|
273
|
+
const positional = [];
|
|
274
|
+
for (let idx = 0; idx < args.length; idx += 1) {
|
|
275
|
+
const arg = args[idx];
|
|
276
|
+
if (arg === "--json") options.json = true;
|
|
277
|
+
else if (arg === "--strict") options.strict = true;
|
|
278
|
+
else if (arg === "--font-cn" || arg === "--font-cjk") options.fontCn = args[++idx];
|
|
279
|
+
else if (arg === "--font-cn-light") options.fontCnLight = args[++idx];
|
|
280
|
+
else if (arg === "--font-en" || arg === "--font-latin") options.fontEn = args[++idx];
|
|
281
|
+
else if (arg === "--font-serif") options.fontSerif = args[++idx];
|
|
282
|
+
else if (arg === "--font-code") options.fontCode = args[++idx];
|
|
283
|
+
else if (arg === "-h" || arg === "--help") options.help = true;
|
|
284
|
+
else positional.push(arg);
|
|
285
|
+
}
|
|
286
|
+
return { options, positional };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function printCheck(result, asJson) {
|
|
290
|
+
if (asJson) {
|
|
291
|
+
console.log(JSON.stringify(result, null, 2));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const errors = result.validation.errors.length;
|
|
295
|
+
const warnings = result.validation.warnings.length;
|
|
296
|
+
console.log(`slides: ${result.inputSlides} -> ${result.outputSlides}`);
|
|
297
|
+
console.log(`errors: ${errors}`);
|
|
298
|
+
console.log(`warnings: ${warnings}`);
|
|
299
|
+
if (result.actions.length) {
|
|
300
|
+
console.log(`actions: ${result.actions.map((item) => `${item.title}->${item.parts}`).join(", ")}`);
|
|
301
|
+
}
|
|
302
|
+
if (result.repairPrompt) {
|
|
303
|
+
console.log("\nrepair prompt:");
|
|
304
|
+
console.log(result.repairPrompt);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function hasStrictFailure(result, options) {
|
|
309
|
+
return Boolean(options.strict && result.validation.warnings.length);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function applyCheckExitCode(result, options) {
|
|
313
|
+
if (result.validation.errors.length || hasStrictFailure(result, options)) {
|
|
314
|
+
process.exitCode = 1;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function makeStrictWarningError(result) {
|
|
319
|
+
const error = new Error("Deck validation warning(s) failed strict mode.");
|
|
320
|
+
error.validation = result.validation;
|
|
321
|
+
error.repairPrompt = result.repairPrompt;
|
|
322
|
+
return error;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function printGuide(command, name, asJson) {
|
|
326
|
+
if (!command) {
|
|
327
|
+
const overview = getGuideOverview();
|
|
328
|
+
if (asJson) printJson(overview);
|
|
329
|
+
else printGuideOverview(overview);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (command === "help" || command === "-h" || command === "--help") {
|
|
334
|
+
printGuideHelp();
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (command === "all") {
|
|
339
|
+
const guides = getAllGuides();
|
|
340
|
+
if (asJson) printJson(guides);
|
|
341
|
+
else printGuideOverview(guides.overview);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (command === "workflow") {
|
|
346
|
+
const workflow = getGuideWorkflow();
|
|
347
|
+
if (asJson) printJson(workflow);
|
|
348
|
+
else printWorkflow(workflow);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
if (command === "speaker-notes") {
|
|
353
|
+
const guide = getSpeakerNotesGuide();
|
|
354
|
+
if (asJson) printJson(guide);
|
|
355
|
+
else printSpeakerNotesGuide(guide);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (command === "image-placeholder") {
|
|
360
|
+
const guide = getImagePlaceholderGuide();
|
|
361
|
+
if (asJson) printJson(guide);
|
|
362
|
+
else printImagePlaceholderGuide(guide);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (command === "layouts") {
|
|
367
|
+
const layouts = listGuideLayouts();
|
|
368
|
+
if (asJson) printJson(layouts);
|
|
369
|
+
else printAvailableGuideLayouts();
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (command === "layout") {
|
|
374
|
+
if (!name) throw new Error("Usage: bit-ppt guide layout <name>");
|
|
375
|
+
const guide = getLayoutGuide(name);
|
|
376
|
+
if (!guide) throw new Error(`No guide available for layout: ${name}`);
|
|
377
|
+
if (asJson) printJson(guide);
|
|
378
|
+
else printLayoutGuide(guide);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (command === "schema") {
|
|
383
|
+
if (!name) throw new Error("Usage: bit-ppt guide schema <name>");
|
|
384
|
+
const schema = getLayoutSchema(name);
|
|
385
|
+
if (!schema) throw new Error(`No schema available for layout: ${name}`);
|
|
386
|
+
printJson(schema);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (command === "example") {
|
|
391
|
+
if (!name) throw new Error("Usage: bit-ppt guide example <name>");
|
|
392
|
+
const example = getLayoutExample(name);
|
|
393
|
+
if (!example) throw new Error(`No example available for layout: ${name}`);
|
|
394
|
+
if (asJson) printJson(example);
|
|
395
|
+
else printExample(example);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (command === "writing-rules") {
|
|
400
|
+
const rules = getWritingRules();
|
|
401
|
+
if (asJson) printJson(rules);
|
|
402
|
+
else printWritingRules(rules);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
throw new Error(`Unknown guide topic: ${command}`);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async function main() {
|
|
410
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
411
|
+
const { options, positional } = parseOptions(rest);
|
|
412
|
+
if (!command || command === "help" || command === "-h" || command === "--help" || options.help) {
|
|
413
|
+
printHelp();
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (command === "list-layouts") {
|
|
418
|
+
const layouts = listLayouts();
|
|
419
|
+
if (options.json) console.log(JSON.stringify(layouts, null, 2));
|
|
420
|
+
else console.log(layouts.join("\n"));
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (command === "guide") {
|
|
425
|
+
printGuide(positional[0], positional[1], options.json);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (command === "doctor") {
|
|
430
|
+
const result = runDoctor();
|
|
431
|
+
if (options.json) printJson(result);
|
|
432
|
+
else printDoctor(result);
|
|
433
|
+
if (!result.ok) process.exitCode = 1;
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (command === "check") {
|
|
438
|
+
const input = positional[0];
|
|
439
|
+
if (!input) throw new Error("Missing input YAML path.");
|
|
440
|
+
const result = checkDeckFile(path.resolve(input));
|
|
441
|
+
printCheck(result, options.json);
|
|
442
|
+
applyCheckExitCode(result, options);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (command === "generate") {
|
|
447
|
+
const [input, output] = positional;
|
|
448
|
+
if (!input || !output) throw new Error("Usage: bit-ppt generate <input.yaml> <output.pptx>");
|
|
449
|
+
if (options.strict) {
|
|
450
|
+
const strictCheck = checkDeckFile(path.resolve(input));
|
|
451
|
+
if (hasStrictFailure(strictCheck, options)) throw makeStrictWarningError(strictCheck);
|
|
452
|
+
}
|
|
453
|
+
const result = await generateDeckFile(path.resolve(input), path.resolve(output), options);
|
|
454
|
+
if (options.json) {
|
|
455
|
+
printJson(result);
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (result.validation.warnings.length) {
|
|
459
|
+
console.warn(`Validation warning(s): ${result.validation.warnings.length}`);
|
|
460
|
+
console.warn(result.repairPrompt);
|
|
461
|
+
}
|
|
462
|
+
if (result.preflight.length) {
|
|
463
|
+
console.log(`Preflight adjusted ${result.preflight.length} slide(s): ${result.preflight.map((item) => `${item.title}->${item.parts}`).join(", ")}`);
|
|
464
|
+
}
|
|
465
|
+
console.log(`Generated ${result.output}`);
|
|
466
|
+
console.log(`Fonts: cn=${result.fonts.cn}, en=${result.fonts.en}, serif=${result.fonts.serif}, code=${result.fonts.code}`);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
throw new Error(`Unknown command: ${command}`);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
main().catch((error) => {
|
|
474
|
+
if (error.validation) {
|
|
475
|
+
console.error(JSON.stringify({ error: error.message, validation: error.validation, repairPrompt: error.repairPrompt }, null, 2));
|
|
476
|
+
} else {
|
|
477
|
+
console.error(error.message || error);
|
|
478
|
+
}
|
|
479
|
+
process.exitCode = 1;
|
|
480
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
meta:
|
|
2
|
+
title: 北理工正文页型扩展示例
|
|
3
|
+
subtitle: Architecture / ablation / case / image / code / appendix
|
|
4
|
+
author: BIT PPT Template Generator
|
|
5
|
+
advisor: 北京理工大学
|
|
6
|
+
date: 2026.05
|
|
7
|
+
|
|
8
|
+
slides:
|
|
9
|
+
- layout: title
|
|
10
|
+
|
|
11
|
+
- layout: architecture
|
|
12
|
+
title: 模型驱动的 PPT 生成架构
|
|
13
|
+
layers:
|
|
14
|
+
- title: 输入层
|
|
15
|
+
components: [论文草稿, 实验结果, 图片素材, 用户约束]
|
|
16
|
+
note: 将非结构化材料整理为可验证的内容块。
|
|
17
|
+
- title: 规划层
|
|
18
|
+
components: [章节规划, 页型选择, 字段填充, 修复提示]
|
|
19
|
+
note: AI 只选择 layout 并填写 YAML,不直接操作 PowerPoint。
|
|
20
|
+
- title: 生成层
|
|
21
|
+
components: [溢出检测, 公式 OMML, 表格拆页, PPTX 写入]
|
|
22
|
+
note: 模板侧保证字号、位置、颜色和 $p_{\theta}(x_i)$ 等公式写入。
|
|
23
|
+
- title: 验证层
|
|
24
|
+
components: [结构检查, 版面预检, 人工复核]
|
|
25
|
+
note: 发现异常后回传给模型压缩或改写内容。
|
|
26
|
+
|
|
27
|
+
- layout: ablation
|
|
28
|
+
title: 消融实验页
|
|
29
|
+
baseline: 基线模型使用完整模块组合,目标函数为 $L(\theta)$。
|
|
30
|
+
items:
|
|
31
|
+
- factor: 检索增强
|
|
32
|
+
setting: 移除检索上下文
|
|
33
|
+
delta: "-6.2%"
|
|
34
|
+
conclusion: 长文档问题下降明显
|
|
35
|
+
- factor: 结构化规划
|
|
36
|
+
setting: 直接生成正文页
|
|
37
|
+
delta: "+3.8 overflow"
|
|
38
|
+
conclusion: 页面溢出风险上升
|
|
39
|
+
- factor: 公式写入
|
|
40
|
+
setting: OMML 替换为图片
|
|
41
|
+
delta: 可编辑性下降
|
|
42
|
+
conclusion: 小公式不适合图片
|
|
43
|
+
- factor: 表格拆页
|
|
44
|
+
setting: 关闭 preflight
|
|
45
|
+
delta: 2 页重叠
|
|
46
|
+
conclusion: 长表格必须预检
|
|
47
|
+
|
|
48
|
+
- layout: caseStudy
|
|
49
|
+
title: 案例分析页
|
|
50
|
+
image: assets/bit-campus-photo.png
|
|
51
|
+
caption: 示例图片区域,可替换为实验样本、系统截图或结果图。
|
|
52
|
+
context:
|
|
53
|
+
- 输入是一段包含公式、表格和图片引用的论文草稿。
|
|
54
|
+
- 目标是生成北理工答辩风格的可编辑 PPTX。
|
|
55
|
+
method:
|
|
56
|
+
- 先让模型选择正文类型,再填充短字段。
|
|
57
|
+
- 生成器负责固定版式、OMML 公式和溢出检测。
|
|
58
|
+
result:
|
|
59
|
+
- 输出可在 WPS / PowerPoint 中继续编辑。
|
|
60
|
+
- 版式一致性优于直接让模型写 PPT 代码。
|
|
61
|
+
|
|
62
|
+
- layout: imageGrid
|
|
63
|
+
title: 多图结果展示
|
|
64
|
+
images:
|
|
65
|
+
- path: assets/bit-campus-photo.png
|
|
66
|
+
caption: 输入样本
|
|
67
|
+
- path: assets/bit-campus-photo.png
|
|
68
|
+
caption: 中间特征
|
|
69
|
+
- path: assets/bit-campus-photo.png
|
|
70
|
+
caption: 预测结果
|
|
71
|
+
- path: assets/bit-campus-photo.png
|
|
72
|
+
caption: 误差区域
|
|
73
|
+
- path: assets/bit-campus-photo.png
|
|
74
|
+
caption: 对照方法
|
|
75
|
+
- path: assets/bit-campus-photo.png
|
|
76
|
+
caption: 本文方法
|
|
77
|
+
|
|
78
|
+
- layout: code
|
|
79
|
+
title: 算法伪代码页
|
|
80
|
+
language: Algorithm
|
|
81
|
+
code: |
|
|
82
|
+
Input: draft D, template layouts T
|
|
83
|
+
1. parse D into claims, evidence and assets
|
|
84
|
+
2. select layout t in T for each content block
|
|
85
|
+
3. generate YAML fields with concise text
|
|
86
|
+
4. run preflight and formula conversion
|
|
87
|
+
5. repair overflow until deck is valid
|
|
88
|
+
noteTitle: 关键约束
|
|
89
|
+
notes:
|
|
90
|
+
- 模型只产出结构化 YAML。
|
|
91
|
+
- 公式使用 OMML 写入,避免图片变形。
|
|
92
|
+
- 长列表、长表格和参考文献优先自动拆页。
|
|
93
|
+
|
|
94
|
+
- layout: appendix
|
|
95
|
+
title: 附录索引页
|
|
96
|
+
items:
|
|
97
|
+
- key: A1
|
|
98
|
+
title: 数据集细节
|
|
99
|
+
text: 样本来源、筛选规则和统计分布。
|
|
100
|
+
- key: A2
|
|
101
|
+
title: 超参数设置
|
|
102
|
+
text: 学习率、batch size 和训练轮数。
|
|
103
|
+
- key: A3
|
|
104
|
+
title: 更多公式
|
|
105
|
+
text: 包括 $p_{\theta}(x_i)$、$L(\theta)$ 与推导步骤。
|
|
106
|
+
- key: A4
|
|
107
|
+
title: 额外可视化
|
|
108
|
+
text: 多组实验结果图和失败案例。
|
|
109
|
+
|
|
110
|
+
- layout: closing
|
|
111
|
+
title: 谢谢
|
|
112
|
+
subtitle: 新增正文页型测试完成
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
meta:
|
|
2
|
+
title: 图表与流程图能力测试
|
|
3
|
+
subtitle: Native PowerPoint flowchart and chart layouts
|
|
4
|
+
author: BIT PPT Template Generator
|
|
5
|
+
advisor: 北京理工大学
|
|
6
|
+
date: 2026.05
|
|
7
|
+
|
|
8
|
+
slides:
|
|
9
|
+
- layout: title
|
|
10
|
+
|
|
11
|
+
- layout: flowchart
|
|
12
|
+
title: 方法流程图
|
|
13
|
+
nodes:
|
|
14
|
+
- id: input
|
|
15
|
+
text: 输入数据
|
|
16
|
+
note: 文本 / 图像 / 表格
|
|
17
|
+
- id: parse
|
|
18
|
+
text: 结构解析
|
|
19
|
+
note: 提取 claim 与 evidence
|
|
20
|
+
- id: plan
|
|
21
|
+
text: 页型规划
|
|
22
|
+
note: 选择 layout
|
|
23
|
+
- id: render
|
|
24
|
+
text: PPTX 生成
|
|
25
|
+
note: 形状 / 表格 / OMML
|
|
26
|
+
- id: check
|
|
27
|
+
text: 预检修复
|
|
28
|
+
note: 检查溢出
|
|
29
|
+
emphasis: true
|
|
30
|
+
- id: output
|
|
31
|
+
text: 可编辑输出
|
|
32
|
+
note: WPS / PowerPoint
|
|
33
|
+
emphasis: true
|
|
34
|
+
edges:
|
|
35
|
+
- from: input
|
|
36
|
+
to: parse
|
|
37
|
+
- from: parse
|
|
38
|
+
to: plan
|
|
39
|
+
- from: plan
|
|
40
|
+
to: render
|
|
41
|
+
- from: render
|
|
42
|
+
to: check
|
|
43
|
+
- from: check
|
|
44
|
+
to: output
|
|
45
|
+
note: 流程图使用 PowerPoint 原生形状和箭头,可继续编辑。
|
|
46
|
+
|
|
47
|
+
- layout: chart
|
|
48
|
+
title: 柱状图:不同方案指标对比
|
|
49
|
+
type: bar
|
|
50
|
+
categories: [Dataset A, Dataset B, Dataset C, Dataset D]
|
|
51
|
+
valueAxisTitle: Accuracy
|
|
52
|
+
series:
|
|
53
|
+
- name: Baseline
|
|
54
|
+
values: [81.2, 83.5, 84.1, 82.4]
|
|
55
|
+
- name: Ours
|
|
56
|
+
values: [85.6, 87.2, 88.0, 86.5]
|
|
57
|
+
caption: 原生 PowerPoint 柱状图,可在 WPS / PowerPoint 中编辑数据。
|
|
58
|
+
|
|
59
|
+
- layout: chart
|
|
60
|
+
title: 折线图:训练过程变化
|
|
61
|
+
type: line
|
|
62
|
+
categories: [Epoch 1, Epoch 2, Epoch 3, Epoch 4, Epoch 5]
|
|
63
|
+
valueAxisTitle: Loss
|
|
64
|
+
series:
|
|
65
|
+
- name: Train
|
|
66
|
+
values: [0.92, 0.71, 0.55, 0.44, 0.39]
|
|
67
|
+
- name: Valid
|
|
68
|
+
values: [0.98, 0.78, 0.63, 0.57, 0.52]
|
|
69
|
+
caption: 折线图适合展示训练损失、指标趋势或时间序列。
|
|
70
|
+
|
|
71
|
+
- layout: chart
|
|
72
|
+
title: 饼图:错误类型分布
|
|
73
|
+
type: pie
|
|
74
|
+
categories: [识别错误, 定位错误, 格式错误, 其他]
|
|
75
|
+
series:
|
|
76
|
+
- name: Error Types
|
|
77
|
+
values: [42, 27, 18, 13]
|
|
78
|
+
caption: 饼图适合少量类别占比,不建议用于精细数值比较。
|
|
79
|
+
|
|
80
|
+
- layout: closing
|
|
81
|
+
title: 谢谢
|
|
82
|
+
subtitle: 图表与流程图测试完成
|