wassemgtk's picture
Add slide renderer source for one-click deployment
0eaa85f verified
Raw
History Blame Contribute Delete
2.49 kB
import * as PptxModule from "pptxgenjs";
import { cleanHex, normalizeTheme } from "./themes.js";
export type SlideSpec = {
layout: "title" | "section" | "bullets" | "chart_and_insight" | "image_reference";
title: string;
subtitle?: string;
bullets?: string[];
insights?: string[];
notes?: string;
};
export type PresentationSpec = {
title: string;
theme: Record<string, unknown>;
slides: SlideSpec[];
provenance?: Record<string, unknown>[];
};
export async function renderPresentation(spec: PresentationSpec): Promise<Buffer> {
const PptxGenJS = ((PptxModule as unknown as { default?: unknown }).default ?? PptxModule) as new () => any;
const pptx = new PptxGenJS();
pptx.layout = "LAYOUT_WIDE";
pptx.author = "GLM Visual Variable Runtime";
pptx.subject = "Generated from visual evidence";
pptx.title = spec.title;
const theme = normalizeTheme(spec.theme);
pptx.defineSlideMaster({
title: "VVR_MASTER",
background: { color: cleanHex(theme.background) },
objects: [
{ rect: { x: 0, y: 0, w: 13.333, h: 0.12, fill: { color: cleanHex(theme.accent) }, line: { color: cleanHex(theme.accent) } } }
],
slideNumber: { x: 12.3, y: 7.15, color: cleanHex(theme.secondary) }
});
const slides = spec.slides.length ? spec.slides : [{ layout: "title" as const, title: spec.title }];
for (const item of slides) {
const slide = pptx.addSlide("VVR_MASTER");
slide.addText(item.title, {
x: 0.7,
y: 0.65,
w: 11.9,
h: 0.6,
fontFace: "Aptos Display",
fontSize: item.layout === "title" ? 34 : 26,
bold: true,
color: cleanHex(theme.primary),
margin: 0
});
if (item.subtitle) {
slide.addText(item.subtitle, { x: 0.75, y: 1.35, w: 11.7, h: 0.4, fontSize: 16, color: cleanHex(theme.secondary) });
}
const bullets = [...(item.bullets ?? []), ...(item.insights ?? [])];
if (bullets.length) {
slide.addText(bullets.map((text) => ({ text, options: { bullet: { indent: 14 }, hanging: 4 } })), {
x: 0.9,
y: 2.0,
w: 11.4,
h: 4.6,
fontSize: 18,
color: cleanHex(theme.text_dark),
breakLine: false,
fit: "shrink"
});
}
if (item.notes || spec.provenance?.length) {
slide.addNotes(`Provenance: ${JSON.stringify(spec.provenance ?? [])}\n${item.notes ?? ""}`);
}
}
const data = await pptx.write({ outputType: "nodebuffer" });
return Buffer.from(data as ArrayBuffer);
}