diff --git a/servers/nextjs/app/(presentation-generator)/outline/components/OutlineItem.tsx b/servers/nextjs/app/(presentation-generator)/outline/components/OutlineItem.tsx index 6c62d2da..1c18a134 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/components/OutlineItem.tsx +++ b/servers/nextjs/app/(presentation-generator)/outline/components/OutlineItem.tsx @@ -11,20 +11,21 @@ import MarkdownEditor from "../../components/MarkdownEditor" interface OutlineItemProps { slideOutline: SlideOutline, index: number + isStreaming: boolean } export function OutlineItem({ index, slideOutline, + isStreaming, }: OutlineItemProps) { const { - presentation_id, outlines, } = useSelector((state: RootState) => state.presentationGeneration); const dispatch = useDispatch() const handleSlideChange = (newOutline: SlideOutline) => { - + if (isStreaming) return; const newData = outlines?.map((each, idx) => { if (idx === index - 1) { return newOutline @@ -45,7 +46,7 @@ export function OutlineItem({ transform, transition, isDragging, - } = useSortable({ id: slideOutline.title }) + } = useSortable({ id: slideOutline.title || index }) const style = { transform: CSS.Transform.toString(transform), @@ -54,6 +55,7 @@ export function OutlineItem({ const handleSlideDelete = () => { + if (isStreaming) return; dispatch(deleteSlideOutline({ index: index - 1 })) } @@ -83,19 +85,17 @@ export function OutlineItem({ {/* Main Title Input - Add onFocus handler */}
- handleSlideChange({ ...slideOutline, title: e.target.value })} - className="text-md sm:text-lg flex-1 font-semibold bg-transparent outline-none" placeholder="Title goes here" /> {/* Editable Markdown Content */} handleSlideChange({ ...slideOutline, body: content })} />
diff --git a/servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx b/servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx index b343f858..e90f530f 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx +++ b/servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx @@ -24,6 +24,7 @@ import { toast } from "@/hooks/use-toast"; import { setPresentationData, setOutlines, + SlideOutline, } from "@/store/slices/presentationGeneration"; import { OverlayLoader } from "@/components/ui/overlay-loader"; import Wrapper from "@/components/Wrapper"; @@ -39,6 +40,7 @@ const OutlinePage = () => { coordinateGetter: sortableKeyboardCoordinates, }) ); + const { presentation_id, outlines } = useSelector( (state: RootState) => state.presentationGeneration ); @@ -47,7 +49,6 @@ const OutlinePage = () => { (state: RootState) => state.theme ); - const [loadingState, setLoadingState] = useState({ message: "", isLoading: false, @@ -58,77 +59,100 @@ const OutlinePage = () => { const [isStreaming, setStreaming] = useState(false); const [isLoading, setLoading] = useState(true); - - useEffect(() => { let evtSource: EventSource; let accumulatedChunks = ""; + const fetchSlides = async () => { setStreaming(true); - evtSource = new EventSource( - `/api/v1/ppt/outlines/stream?presentation_id=${presentation_id}` - ); - evtSource.onopen = () => { - console.log('connection open'); - }; + setLoading(true); - evtSource.addEventListener("response", (event) => { - const data = JSON.parse(event.data); - console.log(data) - if (data.type === "chunk") { - accumulatedChunks += data.chunk; + try { + evtSource = new EventSource( + `/api/v1/ppt/outlines/stream?presentation_id=${presentation_id}` + ); - // try { - // const repairedJson = jsonrepair(accumulatedChunks); - // const partialData = JSON.parse(repairedJson); - // if (partialData.slides) { - // // Check if the length of slides has changed - // if ( - // partialData.slides.length !== previousSlidesLength.current && - // partialData.slides.length > 1 - // ) { - // partialData.slides.splice(-1); + evtSource.onopen = () => { + console.log('connection open'); + }; - // previousSlidesLength.current = partialData.slides.length + 1; // Update the previous length - // setLoading(false); - // } - // } - // } catch (error) { - // // console.error('error while repairing json', error) - // // It's okay if this fails, it just means the JSON isn't complete yet - // } - } else if (data.type === "complete") { - try { + evtSource.addEventListener("response", (event) => { + const data = JSON.parse(event.data); + + if (data.type === "chunk") { + accumulatedChunks += data.chunk; + + try { + const repairedJson = jsonrepair(accumulatedChunks); + const partialData = JSON.parse(repairedJson); + + if (partialData.slides) { + dispatch(setOutlines(partialData.slides)); + setLoading(false); + } + } catch (error) { + // It's okay if this fails, it just means the JSON isn't complete yet + } + } else if (data.type === "complete") { + try { + setLoading(false); + setStreaming(false); + const outlinesData: SlideOutline[] = JSON.parse(data.presentation).outlines; + dispatch(setOutlines(outlinesData)); + evtSource.close(); + } catch (error) { + evtSource.close(); + console.error("Error parsing accumulated chunks:", error); + toast({ + title: "Error", + description: "Failed to parse presentation data", + variant: "destructive", + }); + } + accumulatedChunks = ""; + } else if (data.type === "closing") { setLoading(false); setStreaming(false); - evtSource.close(); - - } catch (error) { - evtSource.close(); - console.error("Error parsing accumulated chunks:", error); } - accumulatedChunks = ""; - } else if (data.type === "closing") { + }); + + evtSource.onerror = (error) => { + setLoading(false); setStreaming(false); evtSource.close(); - } - }); - evtSource.onerror = (error) => { - console.error("EventSource failed:", error); + + toast({ + title: "Connection Error", + description: "Failed to connect to the server. Please try again.", + variant: "destructive", + }); + }; + } catch (error) { setLoading(false); setStreaming(false); - evtSource.close(); - }; + + toast({ + title: "Error", + description: "Failed to initialize connection", + variant: "destructive", + }); + } }; - fetchSlides(); - - }, []) - + if (presentation_id) { + fetchSlides(); + } + // Cleanup function + return () => { + if (evtSource) { + evtSource.close(); + } + }; + }, [presentation_id, dispatch]); const handleDragEnd = (event: any) => { const { active, over } = event; @@ -140,50 +164,55 @@ const OutlinePage = () => { const oldIndex = outlines.findIndex((item) => item.title === active.id); const newIndex = outlines.findIndex((item) => item.title === over.id); - // Create new array with reordered items and updated indices - // Reorder the array const reorderedArray = arrayMove(outlines, oldIndex, newIndex); + // Update local state + setOutlines(reorderedArray); // Update the store with new order dispatch(setOutlines(reorderedArray)); } }; const handleSubmit = async () => { + if (!outlines || outlines.length === 0) { + toast({ + title: "No Outlines", + description: "Please wait for outlines to load before generating presentation", + variant: "destructive", + }); + return; + } // Generate data setLoadingState({ - message: "Generating data...", + message: "Generating presentation data...", isLoading: true, - showProgress: false, - duration: 10, + showProgress: true, + duration: 30, }); + try { - - - const response = await PresentationGenerationApi.generateData({ + const response = await PresentationGenerationApi.presentationPrepare({ presentation_id: presentation_id, - theme: { - name: currentTheme.toLocaleLowerCase(), - colors: currentColors, - }, - outlines: outlines, - }); if (response) { dispatch(setPresentationData(response)); - router.push( - `/presentation?id=${presentation_id}&stream=true` - ); + toast({ + title: "Success", + description: "Presentation generated successfully!", + variant: "default", + }); + + router.push(`/presentation?id=${presentation_id}&stream=true`); } } catch (error) { console.error("error in data generation", error); toast({ - title: "Error Adding Charts", - description: "Something went wrong, Try again", + title: "Generation Error", + description: "Failed to generate presentation. Please try again.", variant: "destructive", }); } finally { @@ -197,17 +226,33 @@ const OutlinePage = () => { }; const handleAddSlide = () => { + if (!outlines) return; + const newSlide: SlideOutline = { + title: "New Slide", + body: "", + // Add any other required properties based on your SlideOutline type + }; - - - // const newTitleWithCharts = [...outlines, { title: "New Slide", body: "" }]; - - // dispatch(setOutlines(newTitleWithCharts)); + const updatedOutlines = [...outlines, newSlide]; + setOutlines(updatedOutlines); + dispatch(setOutlines(updatedOutlines)); }; + if (!presentation_id) { - return null; + return ( + +
+
+

+ No Presentation ID Found +

+

Please start a new presentation.

+
+
+
+ ); } return ( @@ -219,41 +264,102 @@ const OutlinePage = () => { duration={loadingState.duration} /> -
+
-

- Outline -

- {/*
- - ({ id: item.title })) || []} - strategy={verticalListSortingStrategy} +
+

+ Outline +

+ {isStreaming && ( +
+
+ Generating outlines... +
+ )} +
+ + {/* Skeleton loading state */} + {isLoading && ( +
+ {[...Array(5)].map((_, index) => ( +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))} +
+
+ )} + + {/* Outlines content */} + {outlines && outlines.length > 0 && ( +
+ - {outlines?.map((item, index) => ( - - ))} - - - -
*/} + ({ id: item.title || `slide-${index}` })) || []} + strategy={verticalListSortingStrategy} + > + {outlines?.map((item, index) => ( + + ))} + +
+ + +
+ )} + + {/* Empty state */} + {!isLoading && outlines && outlines.length === 0 && ( +
+

No outlines available

+ +
+ )}
- + : isLoading || isStreaming + ? "Loading..." + : "Generate Presentation"} + }
); }; -export default OutlinePage; +export default OutlinePage; \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx b/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx index 4493e33b..34e52124 100644 --- a/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx +++ b/servers/nextjs/app/(presentation-generator)/presentation/components/PresentationPage.tsx @@ -131,7 +131,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { dispatch(setStreaming(true)); evtSource = new EventSource( - `/api/v1/ppt/generate/stream?presentation_id=${presentation_id}` + `/api/v1/ppt/presentation/stream?presentation_id=${presentation_id}` ); evtSource.onopen = () => { @@ -147,6 +147,7 @@ const PresentationPage = ({ presentation_id }: { presentation_id: string }) => { try { const repairedJson = jsonrepair(accumulatedChunks); const partialData = JSON.parse(repairedJson); + console.log(partialData); if (partialData.slides) { // Check if the length of slides has changed if ( diff --git a/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts b/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts index d4eb0710..73fef649 100644 --- a/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts +++ b/servers/nextjs/app/(presentation-generator)/services/api/presentation-generation.ts @@ -195,10 +195,10 @@ export class PresentationGenerationApi { } } - static async generateData(presentationData: any) { + static async presentationPrepare(presentationData: any) { try { const response = await fetch( - `/api/v1/ppt/generate/data`, + `/api/v1/ppt/presentation/prepare`, { method: "POST", headers: getHeader(), diff --git a/servers/nextjs/components/layouts/BulletPointSlideLayout.tsx b/servers/nextjs/components/layouts/BulletPointSlideLayout.tsx index b7c1824a..5aac05e6 100644 --- a/servers/nextjs/components/layouts/BulletPointSlideLayout.tsx +++ b/servers/nextjs/components/layouts/BulletPointSlideLayout.tsx @@ -22,6 +22,10 @@ const bulletPointSlideSchema = z.object({ export const Schema = bulletPointSlideSchema +console.log(zodToJsonSchema(Schema, { + removeAdditionalStrategy: 'strict', +})) + export type BulletPointSlideData = z.infer interface BulletPointSlideLayoutProps { diff --git a/servers/nextjs/store/slices/presentationGeneration.ts b/servers/nextjs/store/slices/presentationGeneration.ts index 33a6bde6..04f0b683 100644 --- a/servers/nextjs/store/slices/presentationGeneration.ts +++ b/servers/nextjs/store/slices/presentationGeneration.ts @@ -18,8 +18,8 @@ export interface ChartSettings { } export interface SlideOutline { - title: string; - body: string; + title?: string; + body?: string; } export interface Chart { @@ -48,7 +48,7 @@ export interface PresentationData { theme: string | null; title: string; titles: string[]; - vector_store: string | null; + thumbnail: string | null; language: string; } | null;