Spaces:
Running
Running
| /* | |
| * GenerationProgress.tsx | |
| * Purpose: A polished full-screen progress overlay shown while the AI generates a deck. | |
| * The backend call is a single async request with no streaming progress, so this eases a | |
| * determinate-looking bar toward ~92% and cycles through human-readable stage labels, then | |
| * snaps to 100% when it unmounts (i.e. generation finished). | |
| * Used by: GoogleSlidesEditor (rendered while isGenerating is true). | |
| * Depends on: React only. | |
| */ | |
| 'use client'; | |
| import React, { useEffect, useRef, useState } from 'react'; | |
| const STAGES = [ | |
| 'Understanding your topic', | |
| 'Outlining the structure', | |
| 'Writing slide content', | |
| 'Designing the layout', | |
| 'Applying your theme', | |
| 'Finishing touches', | |
| ]; | |
| export default function GenerationProgress() { | |
| const [progress, setProgress] = useState(8); | |
| const progressRef = useRef(8); | |
| useEffect(() => { | |
| // Ease toward a ceiling so the bar always feels alive but never "completes" early. | |
| // The real completion is the component unmounting, which removes the overlay entirely. | |
| const id = window.setInterval(() => { | |
| const current = progressRef.current; | |
| const ceiling = 92; | |
| const next = Math.min(ceiling, current + Math.max(0.4, (ceiling - current) * 0.06)); | |
| progressRef.current = next; | |
| setProgress(next); | |
| }, 220); | |
| return () => window.clearInterval(id); | |
| }, []); | |
| const pct = Math.round(progress); | |
| const stageIndex = Math.min(STAGES.length - 1, Math.floor((progress / 100) * STAGES.length)); | |
| return ( | |
| <div className="absolute inset-0 z-50 flex items-center justify-center bg-gray-900/60 backdrop-blur-sm"> | |
| <div className="w-[min(440px,90vw)] rounded-2xl bg-white p-8 shadow-2xl" style={{ fontFamily: 'Inter, sans-serif' }}> | |
| {/* Title */} | |
| <div className="mb-6"> | |
| <span className="text-sm font-semibold tracking-tight text-gray-900">Creating your presentation</span> | |
| </div> | |
| {/* Progress bar */} | |
| <div className="mb-3 h-2.5 w-full overflow-hidden rounded-full bg-gray-100"> | |
| <div | |
| className="h-full rounded-full transition-[width] duration-300 ease-out" | |
| style={{ | |
| width: `${pct}%`, | |
| backgroundImage: | |
| 'linear-gradient(90deg, #ef4444 0%, #f97316 55%, #fb923c 100%)', | |
| }} | |
| /> | |
| </div> | |
| {/* Stage label + percentage */} | |
| <div className="flex items-center justify-between"> | |
| <div className="flex items-center gap-2 text-sm text-gray-600"> | |
| <span>{STAGES[stageIndex]}…</span> | |
| </div> | |
| <span className="tabular-nums text-sm font-semibold text-gray-900">{pct}%</span> | |
| </div> | |
| <p className="mt-5 text-xs text-gray-400">This usually takes a few moments. Please don’t refresh the page.</p> | |
| </div> | |
| </div> | |
| ); | |
| } | |