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 */}