| import React, { useState, useEffect } from 'react';
|
| import { X, Projector, Download, Loader2, FileText, Image, Activity, GitCommit } from 'lucide-react';
|
| import { generateSlideDeck } from '../api';
|
|
|
| const SlideDeckModal = ({ isOpen, onClose, notebookId }) => {
|
| const [generating, setGenerating] = useState(false);
|
| const [result, setResult] = useState(null);
|
| const [error, setError] = useState(null);
|
| const BACKEND_URL = import.meta.env.VITE_BACKEND_URL || 'http://localhost:8000';
|
|
|
| useEffect(() => {
|
| if (isOpen && !result && !generating) {
|
| handleGenerate();
|
| }
|
| }, [isOpen]);
|
|
|
| const handleGenerate = async () => {
|
| setGenerating(true);
|
| setError(null);
|
| try {
|
| const data = await generateSlideDeck(notebookId);
|
| setResult(data);
|
| } catch (err) {
|
| console.error(err);
|
| setError("Failed to generate slide deck. Please try again.");
|
| } finally {
|
| setGenerating(false);
|
| }
|
| };
|
|
|
| if (!isOpen) return null;
|
|
|
|
|
| const getVisualIcon = (type) => {
|
| switch (type) {
|
| case 'diagram': return <GitCommit size={14} className="text-blue-500" />;
|
| case 'chart': return <Activity size={14} className="text-green-500" />;
|
| case 'illustration': return <Image size={14} className="text-purple-500" />;
|
| default: return null;
|
| }
|
| };
|
|
|
| return (
|
| <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 backdrop-blur-sm p-4">
|
| <div className="bg-white rounded-2xl shadow-2xl w-full max-w-2xl flex flex-col max-h-[90vh] animate-in fade-in zoom-in duration-200">
|
| {/* Header */}
|
| <div className="p-4 border-b border-slate-100 flex items-center justify-between">
|
| <div className="flex items-center gap-3">
|
| <div className="w-10 h-10 rounded-full bg-orange-100 flex items-center justify-center text-orange-600">
|
| <Projector size={20} fill="currentColor" />
|
| </div>
|
| <div>
|
| <h2 className="text-lg font-bold text-slate-900">Slide Deck Generator</h2>
|
| <p className="text-sm text-slate-500">AI-Planned Presentation</p>
|
| </div>
|
| </div>
|
| <button onClick={onClose} className="p-2 hover:bg-slate-100 rounded-full text-slate-400 transition-colors">
|
| <X size={20} />
|
| </button>
|
| </div>
|
|
|
| {/* Content */}
|
| <div className="p-6 overflow-y-auto flex-1">
|
| {generating && (
|
| <div className="flex flex-col items-center justify-center py-12 text-slate-500">
|
| <Loader2 size={48} className="animate-spin text-orange-500 mb-4" />
|
| <p className="font-medium text-lg">Planning your presentation...</p>
|
| <p className="text-sm mt-2">Analyzing documents, structuring narrative, and designing slides.</p>
|
| </div>
|
| )}
|
|
|
| {error && (
|
| <div className="p-4 bg-red-50 text-red-600 rounded-xl text-sm mb-4">
|
| {error}
|
| <button onClick={handleGenerate} className="ml-2 underline font-bold">Retry</button>
|
| </div>
|
| )}
|
|
|
| {result && !generating && (
|
| <div className="space-y-6">
|
| <div className="bg-orange-50 rounded-xl p-6 border border-orange-100 flex items-center justify-between">
|
| <div>
|
| <h3 className="font-bold text-orange-900 text-lg">Presentation Ready</h3>
|
| <p className="text-orange-700 text-sm">{result.outline.slides.length} Slides Generated</p>
|
| </div>
|
| <a
|
| href={`${BACKEND_URL}${result.download_url}`}
|
| download
|
| className="px-6 py-3 bg-orange-600 hover:bg-orange-700 text-white font-bold rounded-lg shadow-lg shadow-orange-200 flex items-center gap-2 transition-transform hover:scale-105"
|
| >
|
| <Download size={20} /> Download .pptx
|
| </a>
|
| </div>
|
|
|
| <div>
|
| <h3 className="text-sm font-bold text-slate-500 uppercase tracking-wider mb-4">Slide Plan Preview</h3>
|
| <div className="space-y-3">
|
| {result.outline.slides.map((slide, idx) => (
|
| <div key={idx} className="flex gap-4 p-4 bg-slate-50 border border-slate-100 rounded-lg hover:border-slate-300 transition-colors">
|
| <div className="w-8 h-8 rounded-full bg-white border border-slate-200 flex items-center justify-center font-bold text-slate-400 text-sm shrink-0">
|
| {slide.slide_id}
|
| </div>
|
| <div className="flex-1">
|
| <div className="flex items-center justify-between mb-1">
|
| <h4 className="font-semibold text-slate-800">{slide.slide_title}</h4>
|
| <span className="text-xs px-2 py-1 bg-white border border-slate-200 rounded text-slate-500">{slide.slide_type}</span>
|
| </div>
|
| <p className="text-sm text-slate-600 line-clamp-2">{slide.speaker_notes}</p>
|
|
|
| {slide.visual && slide.visual.type !== 'none' && (
|
| <div className="mt-2 flex items-center gap-2 text-xs font-medium text-slate-500 bg-white inline-flex px-2 py-1 rounded border border-slate-100">
|
| {getVisualIcon(slide.visual.type)}
|
| <span>{slide.visual.type === 'diagram' ? 'Diagram' : slide.visual.type === 'chart' ? 'Chart' : 'Illustration'}: {slide.visual.description}</span>
|
| </div>
|
| )}
|
| </div>
|
| </div>
|
| ))}
|
| </div>
|
| </div>
|
| </div>
|
| )}
|
| </div>
|
| </div>
|
| </div>
|
| );
|
| };
|
|
|
| export default SlideDeckModal;
|
|
|