diff --git a/servers/fastapi/api/v1/ppt/endpoints/images.py b/servers/fastapi/api/v1/ppt/endpoints/images.py index 0902201b..663ff807 100644 --- a/servers/fastapi/api/v1/ppt/endpoints/images.py +++ b/servers/fastapi/api/v1/ppt/endpoints/images.py @@ -1,5 +1,5 @@ from typing import List -from fastapi import APIRouter, Depends, File, UploadFile +from fastapi import APIRouter, Depends, File, UploadFile, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlmodel import select @@ -42,7 +42,7 @@ async def get_generated_images(sql_session: AsyncSession = Depends(get_async_ses ) return images except Exception as e: - return {"error": f"Failed to retrieve generated images: {str(e)}"} + raise HTTPException(status_code=500, detail=f"Failed to retrieve generated images: {str(e)}") @IMAGES_ROUTER.post("/upload-image") async def upload_image(file: UploadFile = File(...), sql_session: AsyncSession = Depends(get_async_session)): @@ -57,7 +57,7 @@ async def upload_image(file: UploadFile = File(...), sql_session: AsyncSession = "id": str(image_asset.id), } except Exception as e: - return {"error": f"Failed to upload image: {str(e)}"} + raise HTTPException(status_code=500, detail=f"Failed to upload image: {str(e)}") @IMAGES_ROUTER.get("/uploaded", response_model=List[ImageAsset]) async def get_uploaded_images(sql_session: AsyncSession = Depends(get_async_session)): @@ -67,7 +67,7 @@ async def get_uploaded_images(sql_session: AsyncSession = Depends(get_async_sess ) return images except Exception as e: - return {"error": f"Failed to retrieve uploaded images: {str(e)}"} + raise HTTPException(status_code=500, detail=f"Failed to retrieve uploaded images: {str(e)}") @IMAGES_ROUTER.delete("/uploaded-image/{image_id}") @@ -76,13 +76,13 @@ async def delete_image(image_id: uuid.UUID, sql_session: AsyncSession = Depends( # Fetch the asset to get its actual file path image = await sql_session.get(ImageAsset, image_id) if not image: - return {"error": "Image not found"} + raise HTTPException(status_code=404, detail="Image not found") service = ImageUploadService(get_uploads_directory()) await service.delete_image(image.path) await sql_session.delete(image) await sql_session.commit() - return {"success": True, "message": "Image deleted successfully"} + return {"message": "Image deleted successfully"} except Exception as e: - return {"error": f"Failed to delete image: {str(e)}"} + raise HTTPException(status_code=500, detail=f"Failed to delete image: {str(e)}") diff --git a/servers/fastapi/api/v1/ppt/endpoints/presentation.py b/servers/fastapi/api/v1/ppt/endpoints/presentation.py index d8e0eeb4..003894e4 100644 --- a/servers/fastapi/api/v1/ppt/endpoints/presentation.py +++ b/servers/fastapi/api/v1/ppt/endpoints/presentation.py @@ -79,7 +79,6 @@ async def delete_presentation( if not presentation: raise HTTPException(404, "Presentation not found") - await sql_session.execute(delete(SlideModel).where(SlideModel.presentation == id)) await sql_session.delete(presentation) await sql_session.commit() @@ -205,6 +204,8 @@ async def stream_presentation( status_code=400, detail="Outlines can not be empty", ) + await sql_session.execute(delete(SlideModel).where(SlideModel.presentation == presentation_id)) + await sql_session.commit() image_generation_service = ImageGenerationService(get_images_directory()) diff --git a/servers/nextjs/app/(presentation-generator)/components/ImageEditor.tsx b/servers/nextjs/app/(presentation-generator)/components/ImageEditor.tsx index 43fba715..f7a37277 100644 --- a/servers/nextjs/app/(presentation-generator)/components/ImageEditor.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/ImageEditor.tsx @@ -471,7 +471,7 @@ const ImageEditor = ({ ) : ( uploadedImages.map((image) => ( -
+
handleImageChange(image.path) diff --git a/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx b/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx index 5cf980d2..a27f7f40 100644 --- a/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/TiptapTextReplacer.tsx @@ -89,9 +89,11 @@ const TiptapTextReplacer: React.FC = ({ tiptapContainer.className = Array.from(allClasses).join(" "); // Replace the element - htmlElement.parentNode?.replaceChild(tiptapContainer, htmlElement); + if(htmlElement.parentNode) { + htmlElement.parentNode.replaceChild(tiptapContainer, htmlElement); // Mark as processed htmlElement.innerHTML = ""; + } setProcessedElements((prev) => new Set(prev).add(htmlElement)); // Render TiptapText const root = ReactDOM.createRoot(tiptapContainer); diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx index 54ba9ae4..591f5493 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/Header.tsx @@ -4,6 +4,9 @@ import { SquareArrowOutUpRight, Play, Loader2, + Redo2 , + Undo2, + RefreshCcw, } from "lucide-react"; import React, { useState } from "react"; import Wrapper from "@/components/Wrapper"; @@ -15,7 +18,7 @@ import { } from "@/components/ui/popover"; import { PresentationGenerationApi } from "../../services/api/presentation-generation"; import { OverlayLoader } from "@/components/ui/overlay-loader"; -import { useSelector } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import Link from "next/link"; @@ -30,6 +33,10 @@ import PDFIMAGE from "@/public/pdf.svg"; import PPTXIMAGE from "@/public/pptx.svg"; import Image from "next/image"; import { trackEvent, MixpanelEvent } from "@/utils/mixpanel"; +import { usePresentationUndoRedo } from "../hooks/PresentationUndoRedo"; +import ToolTip from "@/components/ToolTip"; +import { clearPresentationData } from "@/store/slices/presentationGeneration"; +import { clearHistory } from "@/store/slices/undoRedoSlice"; const Header = ({ presentation_id, @@ -42,12 +49,15 @@ const Header = ({ const [showLoader, setShowLoader] = useState(false); const router = useRouter(); const pathname = usePathname(); + const dispatch = useDispatch(); const { presentationData, isStreaming } = useSelector( (state: RootState) => state.presentationGeneration ); + const { onUndo, onRedo, canUndo, canRedo } = usePresentationUndoRedo(); + const get_presentation_pptx_model = async (id: string): Promise => { const response = await fetch(`/api/presentation_to_pptx_model?id=${id}`); const pptx_model = await response.json(); @@ -127,6 +137,12 @@ const Header = ({ setShowLoader(false); } }; + const handleReGenerate = () => { + dispatch(clearPresentationData()); + dispatch(clearHistory()) + trackEvent(MixpanelEvent.Header_ReGenerate_Button_Clicked, { pathname }); + router.push(`/presentation?id=${presentation_id}&stream=true`); + }; const downloadLink = (path: string) => { // if we have popup access give direct download if not redirect to the path if (window.opener) { @@ -170,6 +186,33 @@ const Header = ({ const MenuItems = ({ mobile }: { mobile: boolean }) => (
+ {/* undo redo */} + +
+ + + + + + + + +
+ {/* Present Button */}