Chat
Ask me anything
Ithy Logo

Comprehensive Guide to Building an AI-Powered Story Generator App

Transforming Text into Engaging Stories with Visuals, Voice, and Interactive Quizzes

storytelling app interface

Key Takeaways

  • Integrated Technologies: Utilize HTML5, CSS, JavaScript, PHP, and XAMPP to create a seamless application.
  • AI Integration: Leverage OpenAI's API for story generation and image creation, enhancing user experience with advanced AI capabilities.
  • Interactive Features: Incorporate voice narration, video creation, and interactive quizzes to engage users dynamically.

Project Overview

This guide provides a step-by-step approach to building a web application that transforms user-inputted text into a rich, multimedia story. The application will generate related images, narrate the story using voice synthesis, create a video with background music, and generate interactive multiple-choice questions. The technologies employed include HTML5, CSS, JavaScript, PHP, XAMPP for database management, OpenAI's AI capabilities, and the Web Speech Synthesis API.

System Architecture

Components and Technologies

  • Frontend: HTML5, CSS, JavaScript
  • Backend: PHP
  • Database: MySQL (managed via XAMPP)
  • AI Services: OpenAI API for story and image generation
  • APIs: Web Speech Synthesis API for voice narration

Setup Environment

Prerequisites

  • XAMPP Installation: Download and install XAMPP to set up your local server environment. XAMPP includes Apache, MySQL, PHP, and Perl, which are essential for backend processing.
  • OpenAI Account: Register for an OpenAI account to obtain API keys for accessing GPT models and image generation services.
  • Development Tools: Install a code editor like Visual Studio Code or Sublime Text for coding purposes.

Directory Structure

Create the following directory structure within your XAMPP's htdocs folder:

/storygenerator
├── index.html
├── style.css
├── main.js
├── backend
│   ├── generate.php
│   ├── submit_answers.php
│   └── db_connection.php
├── uploads
│   ├── images
│   └── videos
└── assets
    └── bgmusic.mp3
  

Frontend Development

HTML Structure

Create an index.html file with the following structure:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>AI Story Generator</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <header>
        <h1>AI Story Generator</h1>
    </header>
    <main>
        <section id="inputSection">
            <h2>Enter Your Text</h2>
            <textarea id="userInput" rows="6" placeholder="Type your idea or concept here..."></textarea>
            <button id="generateBtn">Generate Story</button>
        </section>

        <section id="storySection" style="display:none">
            <h2>Generated Story</h2>
            <div id="storyText"></div>
            <canvas id="storyCanvas" width="600" height="400"></canvas>
            <button id="playStoryBtn">Play Story</button>
        </section>

        <section id="questionsSection" style="display:none">
            <h2>Story Questions</h2>
            <form id="questionForm">
                <div id="questionContainer"></div>
                <button type="submit">Submit Answers</button>
            </form>
            <div id="results"></div>
        </section>
    </main>
    <script src="main.js"></script>
</body>
</html>
  

CSS Styling

Create a style.css file to style the application:

body {
    font-family: Arial, sans-serif;
    padding: 20px;
    background-color: #f9f9f9;
}

header, main {
    max-width: 800px;
    margin: auto;
}

textarea {
    width: 100%;
    padding: 10px;
    resize: vertical;
}

button {
    padding: 10px 20px;
    margin-top: 10px;
    cursor: pointer;
    background-color: #388278;
    color: #fff;
    border: none;
    border-radius: 5px;
}

button:hover {
    background-color: #2e6e63;
}

section {
    background-color: #ffffff;
    padding: 20px;
    margin-bottom: 20px;
    border-radius: 8px;
    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}

#storyCanvas {
    border: 1px solid #ccc;
    margin-top: 15px;
}

.question-block {
    margin-bottom: 20px;
}
  

JavaScript Functionality

Create a main.js file to handle user interactions and API communications:

document.getElementById("generateBtn").addEventListener("click", function () {
    const userInput = document.getElementById("userInput").value.trim();
    if (!userInput) {
        alert("Please enter some text!");
        return;
    }
    // Disable the button while generating
    this.disabled = true;
    fetch("backend/generate.php", {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({ input_text: userInput })
    })
    .then(response => response.json())
    .then(data => {
        // Display the story text
        document.getElementById("storyText").innerText = data.story;
        // Render image on canvas
        loadImageToCanvas(data.image_url);
        // Generate questions
        generateQuestions(data.questions);
        // Show the story and questions sections
        document.getElementById("storySection").style.display = "block";
        document.getElementById("questionsSection").style.display = "block";
    })
    .catch(err => {
        console.error(err);
        alert("Error generating story");
    })
    .finally(() => {
        document.getElementById("generateBtn").disabled = false;
    });
});

document.getElementById("playStoryBtn").addEventListener("click", function () {
    const story = document.getElementById("storyText").innerText;
    if (!speechSynthesis) {
        alert("Speech Synthesis API not supported in your browser.");
        return;
    }
    // Setup background music
    const bgMusic = new Audio("assets/bgmusic.mp3");
    bgMusic.loop = true;
    bgMusic.volume = 0.2;
    bgMusic.play();

    // Use SpeechSynthesis to read out the story.
    const utterance = new SpeechSynthesisUtterance(story);
    utterance.rate = 1;
    utterance.onend = () => {
        bgMusic.pause();
    };
    speechSynthesis.speak(utterance);
});

// Handle submission of multiple choice questions
document.getElementById("questionForm").addEventListener("submit", function (e) {
    e.preventDefault();
    const formData = new FormData(this);
    const answers = {};
    for (let pair of formData.entries()) {
        answers[pair[0]] = pair[1];
    }
    fetch("backend/submit_answers.php", {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({ answers: answers })
    })
    .then(response => response.json())
    .then(data => {
        // Show result from backend
        document.getElementById("results").innerText = data.message;
    })
    .catch(err => {
        console.error(err);
        alert("Error submitting answers");
    });
});

// Helper function: loads an image URL onto the canvas.
function loadImageToCanvas(imageUrl) {
    const canvas = document.getElementById("storyCanvas");
    const ctx = canvas.getContext("2d");
    const img = new Image();
    img.crossOrigin = "Anonymous"; // if needed for cross origin images
    img.onload = function () {
        // clear canvas and draw new image
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        let scale = Math.min(canvas.width / img.width, canvas.height / img.height);
        let x = (canvas.width / 2) - (img.width / 2) * scale;
        let y = (canvas.height / 2) - (img.height / 2) * scale;
        ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
    };
    img.onerror = function(){
        console.error("Could not load image");
    }
    img.src = imageUrl;
}

// Function to generate and display questions
function generateQuestions(questions) {
    const container = document.getElementById("questionContainer");
    container.innerHTML = '';
    questions.forEach((question, idx) => {
        const div = document.createElement("div");
        div.classList.add("question-block");
        let html = `<p><strong>Q${idx + 1}:</strong> ${question.question}</p>`;
        question.options.forEach((option, oIdx) => {
            html += `
                <label>
                    <input type="radio" name="q${idx}" value="${option}" required> ${option}
                </label>
                <br>`;
        });
        div.innerHTML = html;
        container.appendChild(div);
    });
}
  

Backend Development

Database Connection

Create a db_connection.php file within the backend folder to handle database connections:

<?php
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "story_app";

// Create connection
$conn = new mysqli($servername, $username, $password, $dbname);

// Check connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}
?>
  

Story Generation Script

Create a generate.php file to handle the generation of stories, images, and questions:

<?php
header("Content-Type: application/json");

// Retrieve POST data
$data = json_decode(file_get_contents("php://input"), true);
if (!isset($data['input_text'])) {
    echo json_encode(["error" => "No input text provided"]);
    exit();
}
$inputText = $data['input_text'];

// Include database connection
require_once 'db_connection.php';

// Function to call OpenAI API
function callOpenAI($prompt, $max_tokens = 300) {
    $apiKey = 'YOUR_OPENAI_API_KEY'; // Replace with your OpenAI API key
    $ch = curl_init();

    curl_setopt($ch, CURLOPT_URL, "https://api.openai.com/v1/completions");
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_POST, 1);
    $postData = json_encode([
        "model" => "text-davinci-003",
        "prompt" => $prompt,
        "max_tokens" => $max_tokens,
        "temperature" => 0.7
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        "Content-Type: application/json",
        "Authorization: Bearer " . $apiKey
    ]);

    $result = curl_exec($ch);
    if (curl_errno($ch)) {
        error_log('Error:' . curl_error($ch));
        return null;
    }
    curl_close($ch);
    return json_decode($result, true);
}

// 1. Generate Story
$storyPrompt = "Create a captivating story based on the following idea:\n" . $inputText;
$storyResponse = callOpenAI($storyPrompt, 500);
$storyText = trim($storyResponse['choices'][0]['text']) ?? "Unable to generate story.";

// 2. Generate Image
$imagePrompt = "An illustration for the following story: " . substr($storyText, 0, 100);
$imageResponse = callOpenAI($imagePrompt, 50); // Adjust tokens as needed for image descriptions
$imageDescription = trim($imageResponse['choices'][0]['text']) ?? "nature";
$imageURL = "https://source.unsplash.com/600x400/?" . urlencode($imageDescription);

// 3. Generate Questions
$questionPrompt = "Generate 2 multiple-choice questions based on the following story:\n" . $storyText;
$questionResponse = callOpenAI($questionPrompt, 200);
$questionsRaw = trim($questionResponse['choices'][0]['text']) ?? "";
// Simple parsing assuming questions are separated by new lines
$questionsArray = explode("\n", $questionsRaw);
$questions = [];
foreach ($questionsArray as $q) {
    if (trim($q) != "") {
        // Example format: Q1: Question text? A) Option1 B) Option2 C) Option3 D) Option4
        preg_match('/Q\d+:\s*(.*?)\s*A\)\s*(.*?)\s*B\)\s*(.*?)\s*C\)\s*(.*?)\s*D\)\s*(.*)/', $q, $matches);
        if (count($matches) === 7) {
            $questions[] = [
                "question" => $matches[1],
                "options" => [$matches[2], $matches[3], $matches[4], $matches[5]]
            ];
        }
    }
}

// 4. Save to Database
$stmt = $conn->prepare("INSERT INTO stories (story_text, image_url, created_at) VALUES (?, ?, NOW())");
$stmt->bind_param("ss", $storyText, $imageURL);
$stmt->execute();
$story_id = $stmt->insert_id;
$stmt->close();

foreach ($questions as $q) {
    $questionText = $q['question'];
    $option1 = $q['options'][0];
    $option2 = $q['options'][1];
    $option3 = $q['options'][2];
    $option4 = $q['options'][3];
    // For simplicity, not storing correct answers in this example
    $stmt = $conn->prepare("INSERT INTO story_questions (story_id, question_text, option1, option2, option3, option4) VALUES (?, ?, ?, ?, ?, ?)");
    $stmt->bind_param("isssss", $story_id, $questionText, $option1, $option2, $option3, $option4);
    $stmt->execute();
    $stmt->close();
}

$conn->close();

// Return response
echo json_encode([
    "story" => $storyText,
    "image_url" => $imageURL,
    "questions" => $questions
]);
?>
  

Answer Submission and Scoring

Create a submit_answers.php file to handle quiz submissions and provide results:

<?php
header("Content-Type: application/json");

// Retrieve POST data
$data = json_decode(file_get_contents("php://input"), true);
if (!isset($data['answers'])) {
    echo json_encode(["message" => "No answers provided."]);
    exit();
}
$userAnswers = $data['answers'];

// For demonstration, assuming correct answers are known
$correctAnswers = [
    "q0" => "Option1",
    "q1" => "Option2"
];

$score = 0;
$total = count($correctAnswers);
foreach ($correctAnswers as $key => $correct) {
    if (isset($userAnswers[$key]) && $userAnswers[$key] === $correct) {
        $score++;
    }
}

$message = "You scored $score out of $total.";

echo json_encode(["message" => $message]);
?>
  

Database Schema

Creating Necessary Tables

Table Name Columns Description
stories id (INT, AUTO_INCREMENT, PRIMARY KEY)
story_text (TEXT)
image_url (VARCHAR(255))
created_at (DATETIME)
Stores generated stories along with associated images and timestamps.
story_questions id (INT, AUTO_INCREMENT, PRIMARY KEY)
story_id (INT, FOREIGN KEY)
question_text (TEXT)
option1 (VARCHAR(255))
option2 (VARCHAR(255))
option3 (VARCHAR(255))
option4 (VARCHAR(255))
Stores multiple-choice questions related to each story.

Execute the following SQL statements in phpMyAdmin to create the necessary tables:

CREATE DATABASE story_app;

USE story_app;

CREATE TABLE stories (
    id INT AUTO_INCREMENT PRIMARY KEY,
    story_text TEXT NOT NULL,
    image_url VARCHAR(255),
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE story_questions (
    id INT AUTO_INCREMENT PRIMARY KEY,
    story_id INT NOT NULL,
    question_text TEXT NOT NULL,
    option1 VARCHAR(255) NOT NULL,
    option2 VARCHAR(255) NOT NULL,
    option3 VARCHAR(255) NOT NULL,
    option4 VARCHAR(255) NOT NULL,
    FOREIGN KEY (story_id) REFERENCES stories(id) ON DELETE CASCADE
);
  

Enhancing User Experience

Voice Narration with SpeechSynthesis API

The JavaScript code utilizes the Web Speech Synthesis API to narrate the generated story:

document.getElementById("playStoryBtn").addEventListener("click", function () {
    const story = document.getElementById("storyText").innerText;
    if (!speechSynthesis) {
        alert("Speech Synthesis API not supported in your browser.");
        return;
    }
    // Setup background music
    const bgMusic = new Audio("assets/bgmusic.mp3");
    bgMusic.loop = true;
    bgMusic.volume = 0.2;
    bgMusic.play();

    // Use SpeechSynthesis to read out the story.
    const utterance = new SpeechSynthesisUtterance(story);
    utterance.rate = 1;
    utterance.onend = () => {
        bgMusic.pause();
    };
    speechSynthesis.speak(utterance);
});
  

Image Rendering on Canvas

The generated image is displayed on an HTML5 canvas element:

function loadImageToCanvas(imageUrl) {
    const canvas = document.getElementById("storyCanvas");
    const ctx = canvas.getContext("2d");
    const img = new Image();
    img.crossOrigin = "Anonymous"; // if needed for cross origin images
    img.onload = function () {
        // clear canvas and draw new image
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        let scale = Math.min(canvas.width / img.width, canvas.height / img.height);
        let x = (canvas.width / 2) - (img.width / 2) * scale;
        let y = (canvas.height / 2) - (img.height / 2) * scale;
        ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
    };
    img.onerror = function(){
        console.error("Could not load image");
    }
    img.src = imageUrl;
}
  

Generating Interactive Quizzes

After the story is generated, multiple-choice questions are dynamically created and presented to the user:

function generateQuestions(questions) {
    const container = document.getElementById("questionContainer");
    container.innerHTML = '';
    questions.forEach((question, idx) => {
        const div = document.createElement("div");
        div.classList.add("question-block");
        let html = `<p><strong>Q${idx + 1}:</strong> ${question.question}</p>`;
        question.options.forEach((option, oIdx) => {
            html += `
                <label>
                    <input type="radio" name="q${idx}" value="${option}" required> ${option}
                </label>
                <br>`;
        });
        div.innerHTML = html;
        container.appendChild(div);
    });
}
  

Upon submission, answers are sent to the backend for evaluation:

document.getElementById("questionForm").addEventListener("submit", function (e) {
    e.preventDefault();
    const formData = new FormData(this);
    const answers = {};
    for (let pair of formData.entries()) {
        answers[pair[0]] = pair[1];
    }
    fetch("backend/submit_answers.php", {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({ answers: answers })
    })
    .then(response => response.json())
    .then(data => {
        // Show result from backend
        document.getElementById("results").innerText = data.message;
    })
    .catch(err => {
        console.error(err);
        alert("Error submitting answers");
    });
});
  

Video Creation (Advanced Feature)

Integrating Video Generation

Creating a video that combines the generated images, voice narration, and background music involves more advanced processing. While client-side video creation is limited, server-side solutions or third-party services can be utilized.

One approach is to use FFmpeg on the server to programmatically combine images and audio into a video file. This process requires server access and appropriate configurations.

Alternatively, leveraging third-party APIs or services like ClipChamp can simplify video creation by providing user-friendly interfaces and integration capabilities.

Due to the complexity, this section provides a conceptual overview rather than detailed code implementations.


Conclusion

Building an AI-powered story generator application involves integrating multiple technologies to create a dynamic and engaging user experience. By combining HTML5, CSS, JavaScript, PHP, and XAMPP for backend processing with the prowess of OpenAI's APIs, developers can craft a tool that not only generates creative stories but also enriches them with visual and auditory elements. Interactive quizzes further enhance user engagement, making the application both educational and entertaining. While certain advanced features like video creation require more sophisticated solutions, the foundational components outlined in this guide provide a robust starting point for developing a comprehensive storytelling platform.


References


Last updated February 11, 2025
Ask Ithy AI
Download Article
Delete Article