AI Slides Generator – Complete Build and Integration Guide
Part 1: Building the Standalone AI Slides Generator
What You're Building
The AI Slides Generator is a web application that transforms text prompts into professional presentations. Users enter a topic or description, and the app generates structured slide decks with titles, content, and layouts. The app includes an interactive editor for reordering slides, editing content, and applying themes, plus export functionality for PDF and PowerPoint formats.
Core Features
- Prompt-to-Slides Generation: Convert natural language descriptions into structured presentations
- Interactive Editor: Drag-and-drop slide reordering, inline content editing, theme customization
- Multiple Export Formats: PDF, PPTX, and shareable web links
- Template System: Pre-built layouts for different presentation types
- Real-time Preview: Live updates as users edit content
Tech Stack
Frontend:
- Next.js 14 with App Router
- TypeScript for type safety
- Tailwind CSS for styling
- React DnD for drag-and-drop functionality
- React Hook Form for form management
Backend:
- Next.js API routes
- OpenAI GPT-4 for content generation
- jsPDF for PDF export
- PptxGenJS for PowerPoint export
- MongoDB for data persistence
Development Tools:
- ESLint and Prettier for code quality
- Jest for testing
- Docker for containerization
Project Structure
slides-generator/
├── app/
│ ├── (auth)/
│ │ └── login/
│ ├── (dashboard)/
│ │ ├── slides/
│ │ │ ├── page.tsx
│ │ │ ├── new/
│ │ │ │ └── page.tsx
│ │ │ └── [id]/
│ │ │ └── page.tsx
│ │ └── layout.tsx
│ ├── api/
│ │ ├── slides/
│ │ │ ├── route.ts
│ │ │ ├── [id]/
│ │ │ │ └── route.ts
│ │ │ └── generate/
│ │ │ └── route.ts
│ │ └── export/
│ │ └── route.ts
│ ├── globals.css
│ └── layout.tsx
├── components/
│ ├── slides/
│ │ ├── SlideCanvas.tsx
│ │ ├── SlideOutline.tsx
│ │ ├── ContentEditor.tsx
│ │ ├── ThemeSelector.tsx
│ │ └── ExportModal.tsx
│ ├── ui/
│ │ ├── Button.tsx
│ │ ├── Input.tsx
│ │ ├── Textarea.tsx
│ │ └── Modal.tsx
│ └── layout/
│ ├── Header.tsx
│ └── Sidebar.tsx
├── lib/
│ ├── ai/
│ │ └── openai.ts
│ ├── export/
│ │ ├── pdf.ts
│ │ └── pptx.ts
│ ├── database/
│ │ └── mongodb.ts
│ └── utils.ts
├── types/
│ └── slides.ts
├── public/
│ └── icons/
├── package.json
├── next.config.js
├── tailwind.config.js
├── tsconfig.json
└── .env.local
Data Models
// types/slides.ts
export interface Slide {
id: string;
title: string;
content: string[];
layout: 'title' | 'content' | 'image' | 'chart' | 'comparison';
order: number;
notes?: string;
}
export interface Presentation {
id: string;
title: string;
description?: string;
slides: Slide[];
theme: {
primaryColor: string;
secondaryColor: string;
fontFamily: string;
fontSize: 'small' | 'medium' | 'large';
};
createdAt: Date;
updatedAt: Date;
userId: string;
}
export interface GenerationRequest {
prompt: string;
slideCount?: number;
style?: 'business' | 'academic' | 'creative' | 'minimal';
includeImages?: boolean;
}
API Endpoints
// app/api/slides/route.ts
POST /api/slides // Create new presentation
GET /api/slides // List user's presentations
// app/api/slides/[id]/route.ts
GET /api/slides/[id] // Get specific presentation
PUT /api/slides/[id] // Update presentation
DELETE /api/slides/[id] // Delete presentation
// app/api/slides/generate/route.ts
POST /api/slides/generate // Generate slides from prompt
// app/api/export/route.ts
POST /api/export // Export presentation to PDF/PPTX
Core Components
1. Prompt Input Component
// components/slides/PromptInput.tsx
'use client';
import { useState } from 'react';
import { Button } from '@/components/ui/Button';
import { Textarea } from '@/components/ui/Textarea';
interface PromptInputProps {
onSubmit: (prompt: string, options: GenerationOptions) => void;
loading?: boolean;
}
export default function PromptInput({ onSubmit, loading }: PromptInputProps) {
const [prompt, setPrompt] = useState('');
const [slideCount, setSlideCount] = useState(8);
const [style, setStyle] = useState<'business' | 'academic' | 'creative' | 'minimal'>('business');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!prompt.trim()) return;
onSubmit(prompt, {
slideCount,
style,
includeImages: false
});
};
return (
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label className="block text-sm font-medium mb-2">
What would you like to present?
</label>
<Textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="Describe your presentation topic... (e.g., 'Quarterly business review for Q3 2024')"
rows={4}
className="w-full"
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium mb-2">Number of slides</label>
<select
value={slideCount}
onChange={(e) => setSlideCount(Number(e.target.value))}
className="w-full p-2 border rounded-md"
>
<option value={6}>6 slides</option>
<option value={8}>8 slides</option>
<option value={10}>10 slides</option>
<option value={12}>12 slides</option>
</select>
</div>
<div>
<label className="block text-sm font-medium mb-2">Style</label>
<select
value={style}
onChange={(e) => setStyle(e.target.value as any)}
className="w-full p-2 border rounded-md"
>
<option value="business">Business</option>
<option value="academic">Academic</option>
<option value="creative">Creative</option>
<option value="minimal">Minimal</option>
</select>
</div>
</div>
<Button type="submit" disabled={loading || !prompt.trim()}>
{loading ? 'Generating...' : 'Generate Slides'}
</Button>
</form>
);
}
2. Slide Canvas Component
// components/slides/SlideCanvas.tsx
'use client';
import { useState } from 'react';
import { Slide } from '@/types/slides';
import { ContentEditor } from './ContentEditor';
interface SlideCanvasProps {
slides: Slide[];
onUpdateSlide: (slideId: string, updates: Partial<Slide>) => void;
onReorderSlides: (fromIndex: number, toIndex: number) => void;
}
export default function SlideCanvas({ slides, onUpdateSlide, onReorderSlides }: SlideCanvasProps) {
const [selectedSlide, setSelectedSlide] = useState<string | null>(null);
return (
<div className="flex-1 p-6 bg-gray-50">
<div className="max-w-4xl mx-auto space-y-6">
{slides.map((slide, index) => (
<div
key={slide.id}
className={`bg-white rounded-lg shadow-md p-6 cursor-pointer transition-all ${
selectedSlide === slide.id ? 'ring-2 ring-blue-500' : 'hover:shadow-lg'
}`}
onClick={() => setSelectedSlide(slide.id)}
>
<div className="flex items-center justify-between mb-4">
<span className="text-sm text-gray-500">Slide {index + 1}</span>
<span className="text-xs bg-gray-100 px-2 py-1 rounded">
{slide.layout}
</span>
</div>
<ContentEditor
slide={slide}
onUpdate={(updates) => onUpdateSlide(slide.id, updates)}
isSelected={selectedSlide === slide.id}
/>
</div>
))}
</div>
</div>
);
}
3. AI Generation Service
// lib/ai/openai.ts
import OpenAI from 'openai';
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
export async function generateSlides(prompt: string, options: GenerationOptions): Promise<Slide[]> {
const systemPrompt = `You are a presentation expert. Generate ${options.slideCount} slides based on the user's prompt.
Return a JSON array of slides with this exact structure:
[
{
"id": "unique-id",
"title": "Slide Title",
"content": ["Bullet point 1", "Bullet point 2"],
"layout": "title|content|image|chart|comparison",
"order": 1
}
]
Guidelines:
- Use the ${options.style} style
- Make titles concise and engaging
- Keep bullet points short and impactful
- Choose appropriate layouts for each slide
- Ensure logical flow between slides`;
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: prompt }
],
temperature: 0.7,
});
const content = response.choices[0].message.content;
return JSON.parse(content || '[]');
}
4. Export Services
// lib/export/pdf.ts
import jsPDF from 'jspdf';
export async function exportToPDF(presentation: Presentation): Promise<Buffer> {
const pdf = new jsPDF();
presentation.slides.forEach((slide, index) => {
if (index > 0) pdf.addPage();
// Add title
pdf.setFontSize(24);
pdf.text(slide.title, 20, 30);
// Add content
pdf.setFontSize(12);
slide.content.forEach((item, i) => {
pdf.text(`• ${item}`, 20, 50 + (i * 10));
});
});
return Buffer.from(pdf.output('arraybuffer'));
}
// lib/export/pptx.ts
import PptxGenJS from 'pptxgenjs';
export async function exportToPPTX(presentation: Presentation): Promise<Buffer> {
const pptx = new PptxGenJS();
presentation.slides.forEach(slide => {
const slideObj = pptx.addSlide();
// Add title
slideObj.addText(slide.title, {
x: 1, y: 1, w: 8, h: 1,
fontSize: 24,
bold: true
});
// Add content
slide.content.forEach((item, i) => {
slideObj.addText(`• ${item}`, {
x: 1, y: 2 + (i * 0.5), w: 8, h: 0.5,
fontSize: 14
});
});
});
return pptx.write('nodebuffer');
}
Database Schema
// lib/database/mongodb.ts
import { MongoClient } from 'mongodb';
const client = new MongoClient(process.env.MONGODB_URI!);
export const db = client.db('slides_generator');
export const presentations = db.collection('presentations');
export const users = db.collection('users');
// Indexes
await presentations.createIndex({ userId: 1, createdAt: -1 });
await presentations.createIndex({ id: 1 }, { unique: true });
Main Application Page
// app/(dashboard)/slides/page.tsx
'use client';
import { useState, useEffect } from 'react';
import { Presentation } from '@/types/slides';
import PromptInput from '@/components/slides/PromptInput';
import SlideCanvas from '@/components/slides/SlideCanvas';
import SlideOutline from '@/components/slides/SlideOutline';
import ExportModal from '@/components/slides/ExportModal';
export default function SlidesPage() {
const [presentations, setPresentations] = useState<Presentation[]>([]);
const [currentPresentation, setCurrentPresentation] = useState<Presentation | null>(null);
const [loading, setLoading] = useState(false);
const [showExport, setShowExport] = useState(false);
const handleGenerateSlides = async (prompt: string, options: GenerationOptions) => {
setLoading(true);
try {
const response = await fetch('/api/slides/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt, ...options }),
});
const slides = await response.json();
const newPresentation: Presentation = {
id: crypto.randomUUID(),
title: 'New Presentation',
slides,
theme: {
primaryColor: '#3B82F6',
secondaryColor: '#1E40AF',
fontFamily: 'Inter',
fontSize: 'medium'
},
createdAt: new Date(),
updatedAt: new Date(),
userId: 'current-user-id'
};
setCurrentPresentation(newPresentation);
} catch (error) {
console.error('Failed to generate slides:', error);
} finally {
setLoading(false);
}
};
const handleUpdateSlide = (slideId: string, updates: Partial<Slide>) => {
if (!currentPresentation) return;
const updatedSlides = currentPresentation.slides.map(slide =>
slide.id === slideId ? { ...slide, ...updates } : slide
);
setCurrentPresentation({
...currentPresentation,
slides: updatedSlides,
updatedAt: new Date()
});
};
const handleSavePresentation = async () => {
if (!currentPresentation) return;
try {
await fetch('/api/slides', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(currentPresentation),
});
} catch (error) {
console.error('Failed to save presentation:', error);
}
};
return (
<div className="min-h-screen bg-gray-50">
<div className="flex">
{/* Sidebar */}
<div className="w-64 bg-white shadow-sm">
<SlideOutline
slides={currentPresentation?.slides || []}
onReorder={(from, to) => {
// Handle reordering logic
}}
/>
</div>
{/* Main Content */}
<div className="flex-1">
{!currentPresentation ? (
<div className="max-w-2xl mx-auto p-8">
<h1 className="text-3xl font-bold mb-8">AI Slides Generator</h1>
<PromptInput onSubmit={handleGenerateSlides} loading={loading} />
</div>
) : (
<SlideCanvas
slides={currentPresentation.slides}
onUpdateSlide={handleUpdateSlide}
onReorderSlides={() => {}}
/>
)}
</div>
</div>
{/* Export Modal */}
{showExport && currentPresentation && (
<ExportModal
presentation={currentPresentation}
onClose={() => setShowExport(false)}
/>
)}
</div>
);
}
Environment Configuration
# .env.local
MONGODB_URI=mongodb://localhost:27017/slides_generator
OPENAI_API_KEY=your_openai_api_key_here
NEXTAUTH_SECRET=your_nextauth_secret_here
NEXTAUTH_URL=http://localhost:3000
Package Dependencies
{
"dependencies": {
"next": "14.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"typescript": "5.0.0",
"tailwindcss": "3.3.0",
"openai": "4.0.0",
"jspdf": "2.5.0",
"pptxgenjs": "3.12.0",
"mongodb": "6.0.0",
"react-dnd": "16.0.0",
"react-dnd-html5-backend": "16.0.0",
"react-hook-form": "7.45.0",
"@hookform/resolvers": "3.3.0",
"zod": "3.22.0"
},
"devDependencies": {
"@types/node": "20.0.0",
"@types/react": "18.2.0",
"@types/react-dom": "18.2.0",
"eslint": "8.50.0",
"eslint-config-next": "14.0.0",
"prettier": "3.0.0",
"jest": "29.7.0",
"@testing-library/react": "13.4.0"
}
}
Part 2: Now that your app is working fine, let's integrate it with Weam
Integration Overview
Once your AI Slides Generator is fully functional as a standalone application, you can integrate it into the Weam ecosystem. This integration involves:
- Authentication: Using Weam's session management
- Database: Migrating to Weam's MongoDB with proper naming conventions
- UI Theming: Matching Weam's design system
- Routing: Setting up base path configuration
- Deployment: Configuring NGINX and Docker
Weam Integration Steps
Step 1: Authentication Integration
Replace your standalone authentication with Weam's iron-session:
// lib/session.ts
import { getIronSession } from 'iron-session';
import { cookies } from 'next/headers';
export interface SessionData {
user?: {
id: string;
email: string;
name: string;
};
}
export const sessionOptions = {
cookieName: 'weam_session',
password: process.env.IRON_SESSION_PASSWORD!,
cookieOptions: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
sameSite: 'lax' as const,
path: '/',
domain: process.env.COOKIE_DOMAIN,
maxAge: 60 * 60 * 24 * 7, // 7 days
},
};
export async function getSession() {
const cookieStore = await cookies();
return getIronSession<SessionData>(cookieStore, sessionOptions);
}
Step 2: Database Migration
Update your database collections to follow Weam's naming convention:
// lib/database/mongodb.ts
import { MongoClient } from 'mongodb';
const client = new MongoClient(process.env.MONGODB_URI!);
export const db = client.db('weam');
// Weam naming convention: solution_{solutionName}_{tableName}
export const presentations = db.collection('solution_slides_presentations');
export const exports = db.collection('solution_slides_exports');
export const templates = db.collection('solution_slides_templates');
Step 3: Base Path Configuration
Update your Next.js configuration for Weam routing:
// next.config.js
const nextConfig = {
basePath: '/slides',
assetPrefix: '/slides',
async rewrites() {
return [
{
source: '/slides/api/:path*',
destination: '/api/:path*',
},
];
},
};
module.exports = nextConfig;
Step 4: Environment Variables
Update your environment configuration:
# .env.local
NEXT_PUBLIC_API_BASE_PATH=/slides
MONGODB_URI=mongodb://localhost:27017/weam
IRON_SESSION_PASSWORD=your-secure-session-password
COOKIE_DOMAIN=.weam.ai
OPENAI_API_KEY=your-openai-key
Step 5: Sidebar Integration
Add your solution to Weam's sidebar:
// Update src/seeders/superSolution.json
{
"name": "AI Slides Generator",
"path": "/slides",
"icon": "PresentationChart",
"description": "Create AI-powered presentations from text or prompts"
}
Step 6: NGINX Configuration
Add routing configuration to Weam's NGINX:
# Add to /etc/nginx/sites-available/weam
location /slides/ {
proxy_pass http://localhost:3010/slides/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Step 7: Docker Configuration
Create Docker setup for deployment:
# Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
FROM node:18-alpine AS production
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3010
CMD ["npm", "start"]
# docker-compose.yml
version: '3.8'
services:
slides-generator:
build: .
ports:
- "3010:3010"
environment:
- NODE_ENV=production
- NEXT_PUBLIC_API_BASE_PATH=/slides
- MONGODB_URI=${MONGODB_URI}
- IRON_SESSION_PASSWORD=${IRON_SESSION_PASSWORD}
- COOKIE_DOMAIN=.weam.ai
- OPENAI_API_KEY=${OPENAI_API_KEY}
restart: unless-stopped
Final Integration Checklist
✅ Standalone app fully functional
✅ Weam authentication integrated
✅ Database collections follow naming convention
✅ Base path configuration set
✅ Sidebar entry added
✅ NGINX routing configured
✅ Docker deployment ready
✅ Environment variables updated
Testing Integration
- Authentication Test: Verify users can access the app through Weam login
- Database Test: Confirm data is stored with proper user/company scoping
- Routing Test: Check that all routes work under
/slides base path
- Export Test: Verify PDF/PPTX generation works in production
- UI Test: Ensure theming matches Weam's design system
Your AI Slides Generator is now fully integrated into the Weam ecosystem while maintaining all its standalone functionality!
AI Slides Generator – Complete Build and Integration Guide
Part 1: Building the Standalone AI Slides Generator
What You're Building
The AI Slides Generator is a web application that transforms text prompts into professional presentations. Users enter a topic or description, and the app generates structured slide decks with titles, content, and layouts. The app includes an interactive editor for reordering slides, editing content, and applying themes, plus export functionality for PDF and PowerPoint formats.
Core Features
Tech Stack
Frontend:
Backend:
Development Tools:
Project Structure
Data Models
API Endpoints
Core Components
1. Prompt Input Component
2. Slide Canvas Component
3. AI Generation Service
4. Export Services
Database Schema
Main Application Page
Environment Configuration
# .env.local MONGODB_URI=mongodb://localhost:27017/slides_generator OPENAI_API_KEY=your_openai_api_key_here NEXTAUTH_SECRET=your_nextauth_secret_here NEXTAUTH_URL=http://localhost:3000Package Dependencies
{ "dependencies": { "next": "14.0.0", "react": "18.2.0", "react-dom": "18.2.0", "typescript": "5.0.0", "tailwindcss": "3.3.0", "openai": "4.0.0", "jspdf": "2.5.0", "pptxgenjs": "3.12.0", "mongodb": "6.0.0", "react-dnd": "16.0.0", "react-dnd-html5-backend": "16.0.0", "react-hook-form": "7.45.0", "@hookform/resolvers": "3.3.0", "zod": "3.22.0" }, "devDependencies": { "@types/node": "20.0.0", "@types/react": "18.2.0", "@types/react-dom": "18.2.0", "eslint": "8.50.0", "eslint-config-next": "14.0.0", "prettier": "3.0.0", "jest": "29.7.0", "@testing-library/react": "13.4.0" } }Part 2: Now that your app is working fine, let's integrate it with Weam
Integration Overview
Once your AI Slides Generator is fully functional as a standalone application, you can integrate it into the Weam ecosystem. This integration involves:
Weam Integration Steps
Step 1: Authentication Integration
Replace your standalone authentication with Weam's iron-session:
Step 2: Database Migration
Update your database collections to follow Weam's naming convention:
Step 3: Base Path Configuration
Update your Next.js configuration for Weam routing:
Step 4: Environment Variables
Update your environment configuration:
# .env.local NEXT_PUBLIC_API_BASE_PATH=/slides MONGODB_URI=mongodb://localhost:27017/weam IRON_SESSION_PASSWORD=your-secure-session-password COOKIE_DOMAIN=.weam.ai OPENAI_API_KEY=your-openai-keyStep 5: Sidebar Integration
Add your solution to Weam's sidebar:
Step 6: NGINX Configuration
Add routing configuration to Weam's NGINX:
Step 7: Docker Configuration
Create Docker setup for deployment:
Final Integration Checklist
✅ Standalone app fully functional
✅ Weam authentication integrated
✅ Database collections follow naming convention
✅ Base path configuration set
✅ Sidebar entry added
✅ NGINX routing configured
✅ Docker deployment ready
✅ Environment variables updated
Testing Integration
/slidesbase pathYour AI Slides Generator is now fully integrated into the Weam ecosystem while maintaining all its standalone functionality!