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.
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
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>
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;
}
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);
});
}
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);
}
?>
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
]);
?>
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]);
?>
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
);
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);
});
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;
}
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");
});
});
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.
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.