#!/usr/bin/env node /** * Generate PowerPoint presentation from synthesis data * Uses pptxgenjs library to create professional presentations */ const fs = require('fs'); const path = require('path'); // Note: In production, install via npm install pptxgenjs // For this example, we'll show the structure let PptxGenJS; try { PptxGenJS = require('pptxgenjs'); } catch (e) { console.log('📦 Installing pptxgenjs... Run: npm install pptxgenjs'); process.exit(1); } class PresentationGenerator { constructor(synthesisData) { this.synthesis = synthesisData; this.pptx = new PptxGenJS(); this.setupPresentation(); this.defineLayouts(); } setupPresentation() { // Set presentation properties this.pptx.layout = 'LAYOUT_16x9'; this.pptx.author = this.synthesis.metadata.author || 'Research Team'; this.pptx.company = 'Generated by Research-to-Presentation'; this.pptx.title = this.synthesis.metadata.title || 'Research Presentation'; // Define theme colors this.colors = { primary: '#1a73e8', secondary: '#34a853', accent: '#ea4335', dark: '#202124', light: '#f8f9fa', text: '#3c4043', subtext: '#5f6368' }; // Define font styles this.fonts = { title: { face: 'Arial', size: 44, bold: true }, heading: { face: 'Arial', size: 32, bold: true }, subheading: { face: 'Arial', size: 24, bold: false }, body: { face: 'Arial', size: 18 }, small: { face: 'Arial', size: 14 } }; } defineLayouts() { // Define master slides/layouts this.pptx.defineSlideMaster({ title: 'CUSTOM_LAYOUT', background: { color: this.colors.light }, objects: [ // Footer with slide number { text: { text: '[[slideNumber]]', options: { x: 8.5, y: 5, w: 1, h: 0.4, fontSize: 12, color: this.colors.subtext } } } ] }); } generate() { console.log('🎨 Generating slides...'); this.synthesis.slide_plan.forEach(slide => { switch(slide.type) { case 'title': this.createTitleSlide(slide); break; case 'executive_summary': this.createExecutiveSummarySlide(slide); break; case 'agenda': this.createAgendaSlide(slide); break; case 'content': this.createContentSlide(slide); break; case 'data_visualization': this.createDataSlide(slide); break; case 'recommendations': this.createRecommendationsSlide(slide); break; case 'closing': this.createClosingSlide(slide); break; default: this.createContentSlide(slide); } }); return this.pptx; } createTitleSlide(slideData) { const slide = this.pptx.addSlide({ masterName: 'CUSTOM_LAYOUT' }); // Add background gradient slide.background = { fill: { type: 'gradient', colors: [ { color: this.colors.primary, position: 0 }, { color: this.colors.secondary, position: 100 } ] } }; // Title slide.addText(slideData.title, { x: 0.5, y: 2, w: 9, h: 1.5, fontSize: 44, bold: true, color: 'FFFFFF', align: 'center' }); // Subtitle if (slideData.subtitle) { slide.addText(slideData.subtitle, { x: 0.5, y: 3.5, w: 9, h: 0.75, fontSize: 24, color: 'FFFFFF', align: 'center' }); } // Speaker notes if (slideData.speaker_notes) { slide.addNotes(slideData.speaker_notes); } } createExecutiveSummarySlide(slideData) { const slide = this.pptx.addSlide({ masterName: 'CUSTOM_LAYOUT' }); // Title slide.addText(slideData.title, { x: 0.5, y: 0.5, w: 9, h: 0.75, ...this.fonts.heading, color: this.colors.primary }); // Summary content slide.addText(slideData.content, { x: 0.5, y: 1.5, w: 9, h: 1.5, ...this.fonts.body, color: this.colors.text }); // Key points if (slideData.key_points && slideData.key_points.length > 0) { const bulletPoints = slideData.key_points.map(point => ({ text: point, options: { bullet: true } })); slide.addText(bulletPoints, { x: 0.5, y: 3.25, w: 9, h: 2, ...this.fonts.body, color: this.colors.text, bullet: { type: 'circle' } }); } slide.addNotes(slideData.speaker_notes || ''); } createAgendaSlide(slideData) { const slide = this.pptx.addSlide({ masterName: 'CUSTOM_LAYOUT' }); // Title slide.addText(slideData.title, { x: 0.5, y: 0.5, w: 9, h: 0.75, ...this.fonts.heading, color: this.colors.primary }); // Agenda items const agendaItems = slideData.items.map((item, index) => ({ text: `${index + 1}. ${item}`, options: { fontSize: 20, bullet: false, indentLevel: 0 } })); slide.addText(agendaItems, { x: 1, y: 1.5, w: 8, h: 3.5, color: this.colors.text, lineSpacing: 32 }); // Duration footer if (slideData.total_duration) { slide.addText(`Total Duration: ${slideData.total_duration} minutes`, { x: 0.5, y: 5, w: 4, h: 0.4, ...this.fonts.small, color: this.colors.subtext }); } slide.addNotes(slideData.speaker_notes || ''); } createContentSlide(slideData) { const slide = this.pptx.addSlide({ masterName: 'CUSTOM_LAYOUT' }); // Title slide.addText(slideData.title, { x: 0.5, y: 0.5, w: 9, h: 0.75, ...this.fonts.heading, color: this.colors.primary }); // Determine layout based on content const hasData = slideData.data && slideData.data.length > 0; const contentWidth = hasData ? 5 : 9; // Bullet points if (slideData.bullets && slideData.bullets.length > 0) { const bulletPoints = slideData.bullets.map(point => ({ text: point, options: { bullet: true } })); slide.addText(bulletPoints, { x: 0.5, y: 1.5, w: contentWidth, h: 3.5, ...this.fonts.body, color: this.colors.text, bullet: { type: 'circle' }, lineSpacing: 24 }); } // Data table if present if (hasData) { const tableData = [ ['Metric', 'Value'], ...slideData.data.map(d => [d.metric, d.value]) ]; slide.addTable(tableData, { x: 6, y: 1.5, w: 3.5, h: 2, fontSize: 14, border: { pt: 1, color: this.colors.secondary }, fill: { color: this.colors.light } }); } slide.addNotes(slideData.speaker_notes || ''); } createDataSlide(slideData) { const slide = this.pptx.addSlide({ masterName: 'CUSTOM_LAYOUT' }); // Title slide.addText(slideData.title, { x: 0.5, y: 0.5, w: 9, h: 0.75, ...this.fonts.heading, color: this.colors.primary }); // Create data table if (slideData.data_points && slideData.data_points.length > 0) { const tableData = [ ['Source', 'Metric', 'Value'], ...slideData.data_points.map(dp => [ dp.source || '', dp.metric, dp.value ]) ]; slide.addTable(tableData, { x: 0.5, y: 1.5, w: 9, h: 3.5, fontSize: 16, border: { pt: 1, color: this.colors.secondary }, fill: { color: this.colors.light }, rowH: 0.5 }); } slide.addNotes(slideData.speaker_notes || ''); } createRecommendationsSlide(slideData) { const slide = this.pptx.addSlide({ masterName: 'CUSTOM_LAYOUT' }); // Title slide.addText(slideData.title, { x: 0.5, y: 0.5, w: 9, h: 0.75, ...this.fonts.heading, color: this.colors.primary }); // Recommendations as numbered list const recommendations = slideData.items.map((item, index) => ({ text: `${index + 1}. ${item}`, options: { fontSize: 20, bullet: false } })); slide.addText(recommendations, { x: 0.5, y: 1.5, w: 9, h: 3.5, color: this.colors.text, lineSpacing: 28 }); slide.addNotes(slideData.speaker_notes || ''); } createClosingSlide(slideData) { const slide = this.pptx.addSlide({ masterName: 'CUSTOM_LAYOUT' }); // Gradient background slide.background = { fill: { type: 'gradient', colors: [ { color: this.colors.secondary, position: 0 }, { color: this.colors.primary, position: 100 } ] } }; // Thank you text slide.addText(slideData.title, { x: 0.5, y: 2, w: 9, h: 1, fontSize: 48, bold: true, color: 'FFFFFF', align: 'center' }); // Subtitle if (slideData.subtitle) { slide.addText(slideData.subtitle, { x: 0.5, y: 3.5, w: 9, h: 0.75, fontSize: 28, color: 'FFFFFF', align: 'center' }); } slide.addNotes(slideData.speaker_notes || ''); } async save(outputPath) { try { await this.pptx.writeFile({ fileName: outputPath }); console.log(`✅ Presentation saved: ${outputPath}`); } catch (error) { console.error(`❌ Error saving presentation: ${error}`); throw error; } } } // Main execution async function main() { const args = process.argv.slice(2); if (args.length < 2) { console.log('Usage: node generate_pptx.js '); process.exit(1); } const synthesisFile = args[0]; const outputFile = args[1]; console.log(`📊 Loading synthesis from: ${synthesisFile}`); try { // Load synthesis data const synthesisData = JSON.parse( fs.readFileSync(synthesisFile, 'utf8') ); // Generate presentation const generator = new PresentationGenerator(synthesisData); const presentation = generator.generate(); // Save to file await generator.save(outputFile); console.log(`📈 Created ${synthesisData.slide_plan.length} slides`); } catch (error) { console.error(`❌ Error: ${error.message}`); process.exit(1); } } // Run if called directly if (require.main === module) { main(); } module.exports = { PresentationGenerator };