Creating a Presentation (PPT) with React, Express, and OpenAI APIs

Creating a Presentation (PPT) with React, Express, and OpenAI APIs

Last weekend, I talked to one of my friends, and we started discussing AI tools built with the OpenAI API. We are both developers, and after some conversation, he asked me if I could make an app that takes text as input and generates a presentation of 5 to 10 slides with images, titles, and descriptions.

I found it interesting, so on Saturday, I sat down for an hour and did some research on OpenAI's documentation about how to use these APIs.

Getting a response from the input text was easy, and writing prompts to get the desired responses wasn't hard either. Then the difficult part came: generating images for each PowerPoint slide based on the slide content. I had to fetch all these API responses with async/await to avoid server loading issues.

It took me two hours to write the code. Then it was time to create the PowerPoint and align the content within it. I installed the necessary libraries in Express, and after a lot of trial and error, I was able to create the PowerPoint with the correct format.

In total, it took me 5 to 6 hours, though I spent some time on Sunday as well.

It wasn't a very easy task, but not too hard either. Most importantly, I finally did something I had been avoiding for a long time—working with OpenAI's APIs.

Now I feel like I can create any kind of tool I see nowadays. It feels much easier to do now.

So, if you've been avoiding something for a few days, give it a shot. You'll feel satisfaction and strength.

Here is the React.js and Express.js code. If you want to use it, feel free!

Express JS Code

const express = require('express');
const dotenv = require('dotenv');
const cors = require('cors');
const axios = require('axios');
const PptxGenJS = require('pptxgenjs');
const fs = require('fs');
const path = require('path'); 
const app = express();

app.use(express.json());
app.use(cors());
dotenv.config();

const { Configuration, OpenAIApi } = require('openai');

const configuration = new Configuration({
    apiKey: process.env.OPENAI_API_KEY
});

const openai = new OpenAIApi(configuration);

// Create a directory for saving images and presentations
const saveDirectory = path.join(__dirname, 'generated_files');
if (!fs.existsSync(saveDirectory)) {
    fs.mkdirSync(saveDirectory);
}

// Function to generate slide text using OpenAI GPT (Chat Completion)
async function runCompletion(prompt) {
    if (prompt.length > 1000) {
        console.warn(`Prompt is too long. Truncating to 1000 characters.`);
        prompt = prompt.slice(0, 1000); // Truncate to 1000 characters
    }

    console.log(`Running completion with prompt: "${prompt}" (length: ${prompt.length})`);

    const response = await openai.createChatCompletion({
        model: "gpt-4",
        messages: [
            { role: "system", content: "WRITE A DETAILED PROMPT" },
            { role: "user", content: prompt }
        ],
        max_tokens: 1000, // Adjust based on content length
    });

    return response.data.choices[0].message.content.trim();
}

// Function to generate an image using OpenAI with additional prompts
async function generateImage(segmentText) {
    // Customize the prompt for image generation
    const customPrompt = `Create an image representing ${segmentText}. WRITE A DETAILED PROMPT`;

    console.log(`Generating image with prompt: "${customPrompt}"`); // Log the prompt

    const response = await openai.createImage({
        prompt: customPrompt,
        n: 1,
        size: "256x256",
        response_format: "url" // Ensure no text in the generated image
    });
    return response.data.data[0].url;
}

// Function to download an image from a URL and save it to local disk
async function downloadImage(imageUrl, outputPath) {
    const response = await axios({
        url: imageUrl,
        responseType: 'arraybuffer',
    });
    fs.writeFileSync(outputPath, response.data);
}

// Function to create PowerPoint presentation
// Function to create PowerPoint presentation
async function createPPT(slideSegments) {
    let pres = new PptxGenJS();

    for (const segment of slideSegments) {
        const slide = pres.addSlide();

        slide.addText(segment.text, {
            x: 0.5,
            y: 0.5,
            fontSize: 14,
            color: "#363636",
            w: "40%",
            h: "80%",
            align: 'left',
            margin: 10,
        });

        slide.addImage({
            path: segment.imagePath,
            x: "25%",
            y: 0,
            w: "50%",
            h: "100%",
            sizing: {
                type: 'contain',
                w: '100%',
                h: '100%'
            }
        });
    }

    const filePath = path.join(saveDirectory, "openai_generated_presentation.pptx");
    let attempts = 3;
    let success = false;

    while (attempts > 0 && !success) {
        try {
            await pres.writeFile({ fileName: filePath });
            success = true; // Exit loop if successful
        } catch (error) {
            console.error('Error writing file:', error.message);
            attempts--;
            if (attempts > 0) {
                console.log(`Retrying... (${3 - attempts} of 3)`);
                await new Promise(res => setTimeout(res, 2000)); // Wait for 2 seconds before retrying
            } else {
                throw new Error('Failed to write PowerPoint file after multiple attempts.');
            }
        }
    }
}


// Endpoint to handle text generation requests
app.post('/v1/chat/completions', async (req, res) => {
    try {
        const { text } = req.body;

        // Validate the input
        if (!text || typeof text !== 'string') {
            return res.status(400).json({
                error: {
                    message: 'Invalid input: Prompt is required and must be a string.'
                }
            });
        }

        console.log(`Received prompt: "${text}" (length: ${text.length})`);

        // Generate slide text
        const slideText = await runCompletion(text);
        console.log(`Generated slide text: "${slideText}"`);

        // Split slide text into segments
        const slideSegments = slideText.split('\n\n').map((segment, index) => ({
            text: segment,
            imagePath: path.join(saveDirectory, `generated_image_${index}.png`) // Updated path for saving the image
        }));

        // Generate images for each segment
        for (const segment of slideSegments) {
            const imageUrl = await generateImage(text);
            console.log(`Generated image URL for segment: "${segment.text}" -> "${imageUrl}"`);
            await downloadImage(imageUrl, segment.imagePath);
            console.log(`Downloaded image for segment to path: "${segment.imagePath}"`);
        }

        // Create PowerPoint presentation
        await createPPT(slideSegments);
        console.log(`Created PowerPoint presentation successfully.`);

        // Send response
        res.json({
            message: "Presentation created successfully!"
        });
    } catch (error) {
        handleApiError(res, error);
    }
});

// Helper function to handle API errors
function handleApiError(res, error) {
    if (error.response) {
        console.error(error.response.status, error.response.data);
        res.status(error.response.status).json(error.response.data);
    } else {
        console.error('Error with OPENAI API request:', error.message);
        res.status(500).json({
            error: {
                message: 'An error occurred during your request.'
            }
        });
    }
}

const PORT = process.env.PORT || 5000;

app.listen(PORT, () => console.log(`Server started on port ${PORT}`));        

React JS Code

import React, { useState } from 'react';
import './style/style.css';

function App() {
    const [inputValue, setInputValue] = useState('');
    const [error, setError] = useState('');
    const [result, setResult] = useState('');
    const [prompt, setPrompt] = useState('');
    const [imageUrl, setImageUrl] = useState(''); // New state for the image URL
    const [jresult, setJresult] = useState('');

    const handleSubmit = async (event) => {
        event.preventDefault();

        if (!inputValue) {
            setError('Please enter a prompt!');
            setPrompt('');
            setResult('');
            setImageUrl(''); // Clear image URL on error
            setJresult('');
            return;
        }

        try {
            // Corrected URL to match the backend endpoint
            const response = await fetch('https://localhost:5000/v1/chat/completions', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ text: inputValue })
            });

            if (response.ok) {
                const data = await response.json();
                console.log(data);
                setPrompt(inputValue);
                setResult(data.data.slideText); // Updated to get slide text
                setImageUrl(data.data.imageUrl); // New image URL from the response
                setJresult(JSON.stringify(data.data, null, 2));
                setInputValue('');
                setError('');
            } else {
                throw new Error('An error occurred while fetching data.');
            }

        } catch (error) {
            console.error(error);
            setResult('');
            setImageUrl(''); // Clear image URL on error
            setError('An error occurred while submitting the form: ' + error.message);
        }
    }

    return (
        <div className='container'>
            <form className='form-horizontal' onSubmit={handleSubmit}>
                <div className='row form-group mt-2'>
                    <div className='col-sm-10'>
                        <div className='form-floating'>
                            <textarea
                                className='form-control custom-input'
                                id='floatingInput'
                                placeholder='Enter a prompt'
                                value={inputValue}
                                onChange={e => setInputValue(e.target.value)}
                            />
                            <label htmlFor='floatingInput'>Enter Your Query</label>
                        </div>
                    </div>
                    <div className='col-sm-2'>
                        <button type="submit" className='btn btn-primary custom-button'>Submit</button>
                    </div>
                </div>
            </form>
            {error && <div className='alert alert-danger mt-3'>{error}</div>}
            {prompt && <div className='mt-3 capitalize'>Presentation on: {prompt}</div>}
            {result && (
                <>
                    <div className='mt-3'>
                        <h5>Generated Slide Text:</h5>
                        <div className='alert alert-info'>{result}</div>
                    </div>
                </>
            )}
            {imageUrl && (
                <div className='mt-3'>
                    <h5>Generated Image:</h5>
                    <img src={imageUrl} alt="Generated" className='img-fluid' />
                </div>
            )}
            {/* Optional: Show raw JSON response */}
            {jresult && (<pre className='alert alert-secondary mt-3'><code>{jresult}</code></pre>)}
        </div>
    );
}

export default App;        

Try it, and if you face any problems, just message me. I'll send you the complete code.

It was a simple test project, so I didn’t refine it. Right now, the prompt I used wasn’t accurate or perfect for generating high-quality images, and the text prompt wasn’t perfect either.

However, if you have time, spend it on crafting better prompts to generate better output.

Here is the Sample output of the generated PPT.


Generate PPT with Open AI, React and Express


Thank you for taking the time to read the article! I truly appreciate your interest and hope you found it insightful. If you have any thoughts, questions, or feedback, feel free to reach out!

Read more articles at https://frontbackgeek.com/








要查看或添加评论,请登录

Sandeep Kumar的更多文章

社区洞察

其他会员也浏览了