From c840b8fce921decbd584530659d12e02fa309a11 Mon Sep 17 00:00:00 2001 From: sauravniraula Date: Wed, 6 Aug 2025 19:34:53 +0545 Subject: [PATCH 01/16] issue: outlines still flickering --- servers/fastapi/services/llm_client.py | 44 +++++++++---------- .../outline/components/GenerateButton.tsx | 4 +- .../outline/components/OutlineContent.tsx | 2 +- .../outline/components/PageHeader.tsx | 14 ------ .../outline/hooks/useOutlineStreaming.ts | 41 +++++++++-------- .../outline/types/index.ts | 5 +-- .../upload/components/UploadPage.tsx | 2 - 7 files changed, 46 insertions(+), 66 deletions(-) delete mode 100644 servers/nextjs/app/(presentation-generator)/outline/components/PageHeader.tsx diff --git a/servers/fastapi/services/llm_client.py b/servers/fastapi/services/llm_client.py index f016e763..eaf61770 100644 --- a/servers/fastapi/services/llm_client.py +++ b/servers/fastapi/services/llm_client.py @@ -39,8 +39,6 @@ class LLMClient: # ? Disable thinking def disable_thinking(self) -> bool: - if self.llm_provider != LLMProvider.CUSTOM: - return False return parse_bool_or_none(get_disable_thinking_env()) or False # ? Clients @@ -122,15 +120,14 @@ class LLMClient: model: str, messages: List[LLMMessage], max_tokens: Optional[int] = None, + extra_body: Optional[dict] = None, ): client: AsyncOpenAI = self._client response = await client.chat.completions.create( model=model, messages=[message.model_dump() for message in messages], max_completion_tokens=max_tokens, - extra_body={ - "enable_thinking": not self.disable_thinking(), - }, + extra_body=extra_body, ) return response.choices[0].message.content @@ -185,7 +182,10 @@ class LLMClient: async def _generate_custom( self, model: str, messages: List[LLMMessage], max_tokens: Optional[int] = None ): - return await self._generate_openai(model, messages, max_tokens) + extra_body = {"enable_thinking": not self.disable_thinking()} + return await self._generate_openai( + model, messages, max_tokens, extra_body=extra_body + ) async def generate( self, @@ -220,6 +220,7 @@ class LLMClient: response_format: dict, strict: bool = False, max_tokens: Optional[int] = None, + extra_body: Optional[dict] = None, ): client: AsyncOpenAI = self._client use_tool_calls = self.use_tool_calls() @@ -245,9 +246,7 @@ class LLMClient: ), }, max_completion_tokens=max_tokens, - extra_body={ - "enable_thinking": not self.disable_thinking(), - }, + extra_body=extra_body, ) content = response.choices[0].message.content else: @@ -267,9 +266,7 @@ class LLMClient: ], tool_choice="required", max_completion_tokens=max_tokens, - extra_body={ - "enable_thinking": not self.disable_thinking(), - }, + extra_body=extra_body, ) tool_calls = response.choices[0].message.tool_calls if tool_calls: @@ -359,8 +356,9 @@ class LLMClient: strict: bool = False, max_tokens: Optional[int] = None, ): + extra_body = {"enable_thinking": not self.disable_thinking()} return await self._generate_openai_structured( - model, messages, response_format, strict, max_tokens + model, messages, response_format, strict, max_tokens, extra_body ) async def generate_structured( @@ -406,15 +404,14 @@ class LLMClient: model: str, messages: List[LLMMessage], max_tokens: Optional[int] = None, + extra_body: Optional[dict] = None, ): client: AsyncOpenAI = self._client async with client.chat.completions.stream( model=model, messages=[message.model_dump() for message in messages], max_completion_tokens=max_tokens, - extra_body={ - "enable_thinking": not self.disable_thinking(), - }, + extra_body=extra_body, ) as stream: async for event in stream: if event.type == "content.delta": @@ -474,7 +471,8 @@ class LLMClient: messages: List[LLMMessage], max_tokens: Optional[int] = None, ): - return self._stream_openai(model, messages, max_tokens) + extra_body = {"enable_thinking": not self.disable_thinking()} + return self._stream_openai(model, messages, max_tokens, extra_body) def stream( self, model: str, messages: List[LLMMessage], max_tokens: Optional[int] = None @@ -499,6 +497,7 @@ class LLMClient: response_format: dict, strict: bool = False, max_tokens: Optional[int] = None, + extra_body: Optional[dict] = None, ): client: AsyncOpenAI = self._client use_tool_calls = self.use_tool_calls() @@ -524,9 +523,7 @@ class LLMClient: }, } ), - extra_body={ - "enable_thinking": not self.disable_thinking(), - }, + extra_body=extra_body, ) as stream: async for event in stream: if event.type == "content.delta": @@ -548,9 +545,7 @@ class LLMClient: } ], tool_choice="required", - extra_body={ - "enable_thinking": not self.disable_thinking(), - }, + extra_body=extra_body, ) as stream: async for event in stream: if event.type == "tool_calls.function.arguments.delta": @@ -630,8 +625,9 @@ class LLMClient: strict: bool = False, max_tokens: Optional[int] = None, ): + extra_body = {"enable_thinking": not self.disable_thinking()} return self._stream_openai_structured( - model, messages, response_format, strict, max_tokens + model, messages, response_format, strict, max_tokens, extra_body ) def stream_structured( diff --git a/servers/nextjs/app/(presentation-generator)/outline/components/GenerateButton.tsx b/servers/nextjs/app/(presentation-generator)/outline/components/GenerateButton.tsx index bc5ee297..e5f37757 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/components/GenerateButton.tsx +++ b/servers/nextjs/app/(presentation-generator)/outline/components/GenerateButton.tsx @@ -1,10 +1,10 @@ import React from "react"; import { Button } from "@/components/ui/button"; -import { LoadingState, StreamState, LayoutGroup } from "../types/index"; +import { LoadingState, LayoutGroup } from "../types/index"; interface GenerateButtonProps { loadingState: LoadingState; - streamState: StreamState; + streamState: { isStreaming: boolean, isLoading: boolean }; selectedLayoutGroup: LayoutGroup | null; onSubmit: () => void; } diff --git a/servers/nextjs/app/(presentation-generator)/outline/components/OutlineContent.tsx b/servers/nextjs/app/(presentation-generator)/outline/components/OutlineContent.tsx index 0748a6a3..21852f5e 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/components/OutlineContent.tsx +++ b/servers/nextjs/app/(presentation-generator)/outline/components/OutlineContent.tsx @@ -32,7 +32,7 @@ const OutlineContent: React.FC = ({ onDragEnd, onAddSlide }) => { - + console.log('isLoading', isLoading) const sensors = useSensors( useSensor(PointerSensor), useSensor(KeyboardSensor, { diff --git a/servers/nextjs/app/(presentation-generator)/outline/components/PageHeader.tsx b/servers/nextjs/app/(presentation-generator)/outline/components/PageHeader.tsx deleted file mode 100644 index 8a20c78f..00000000 --- a/servers/nextjs/app/(presentation-generator)/outline/components/PageHeader.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from "react"; - -const PageHeader: React.FC = () => ( -
- {/*

- Customize Your Presentation -

*/} - {/*

- Review your outline and select a layout style for your presentation. -

*/} -
-); - -export default PageHeader; \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/outline/hooks/useOutlineStreaming.ts b/servers/nextjs/app/(presentation-generator)/outline/hooks/useOutlineStreaming.ts index f5db594e..c09a0981 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/hooks/useOutlineStreaming.ts +++ b/servers/nextjs/app/(presentation-generator)/outline/hooks/useOutlineStreaming.ts @@ -3,18 +3,15 @@ import { useDispatch, useSelector } from "react-redux"; import { toast } from "sonner"; import { setOutlines } from "@/store/slices/presentationGeneration"; import { jsonrepair } from "jsonrepair"; -import { StreamState } from "../types/index"; import { RootState } from "@/store/store"; -const DEFAULT_STREAM_STATE: StreamState = { - isStreaming: false, - isLoading: true, -}; + export const useOutlineStreaming = (presentationId: string | null) => { const dispatch = useDispatch(); const { outlines } = useSelector((state: RootState) => state.presentationGeneration); - const [streamState, setStreamState] = useState(DEFAULT_STREAM_STATE); + const [isStreaming, setIsStreaming] = useState(true); + const [isLoading, setIsLoading] = useState(true); useEffect(() => { if (!presentationId || outlines.length > 0) return; @@ -23,8 +20,8 @@ export const useOutlineStreaming = (presentationId: string | null) => { let accumulatedChunks = ""; const initializeStream = async () => { - setStreamState({ isStreaming: true, isLoading: true }); - + setIsStreaming(true) + setIsLoading(true) try { eventSource = new EventSource( `/api/v1/ppt/outlines/stream?presentation_id=${presentationId}` @@ -40,7 +37,7 @@ export const useOutlineStreaming = (presentationId: string | null) => { const partialData = JSON.parse(repairedJson); if (partialData.slides) { dispatch(setOutlines(partialData.slides)); - setStreamState(prev => ({ ...prev, isLoading: false })); + setIsLoading(false) } } catch (error) { // JSON isn't complete yet, continue accumulating @@ -51,8 +48,9 @@ export const useOutlineStreaming = (presentationId: string | null) => { try { const outlinesData: string[] = data.presentation.outlines.slides; dispatch(setOutlines(outlinesData)); - setStreamState({ isStreaming: false, isLoading: false }); - eventSource.close(); + setIsStreaming(false) + setIsLoading(false) + eventSource.close(); } catch (error) { console.error("Error parsing accumulated chunks:", error); toast.error("Failed to parse presentation data"); @@ -62,11 +60,13 @@ export const useOutlineStreaming = (presentationId: string | null) => { break; case "closing": - setStreamState({ isStreaming: false, isLoading: false }); + setIsStreaming(false) + setIsLoading(false) eventSource.close(); break; case "error": - setStreamState({ isStreaming: false, isLoading: false }); + setIsStreaming(false) + setIsLoading(false) eventSource.close(); toast.error('Error in outline streaming', { @@ -78,18 +78,21 @@ export const useOutlineStreaming = (presentationId: string | null) => { }); eventSource.onerror = () => { - setStreamState({ isStreaming: false, isLoading: false }); + setIsStreaming(false) + setIsLoading(false) eventSource.close(); toast.error("Failed to connect to the server. Please try again."); }; } catch (error) { - setStreamState({ isStreaming: false, isLoading: false }); + setIsStreaming(false) + setIsLoading(false) toast.error("Failed to initialize connection"); - }finally{ - setStreamState({ isStreaming: false, isLoading: false }); + } finally { + setIsStreaming(false) + setIsLoading(false) } }; - initializeStream(); + initializeStream(); return () => { if (eventSource) { eventSource.close(); @@ -97,5 +100,5 @@ export const useOutlineStreaming = (presentationId: string | null) => { }; }, [presentationId, dispatch]); - return streamState; + return { isStreaming, isLoading }; }; \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/outline/types/index.ts b/servers/nextjs/app/(presentation-generator)/outline/types/index.ts index d3067f3e..c916c3a5 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/types/index.ts +++ b/servers/nextjs/app/(presentation-generator)/outline/types/index.ts @@ -14,10 +14,7 @@ export interface LoadingState { duration: number; } -export interface StreamState { - isStreaming: boolean; - isLoading: boolean; -} + export const TABS = { OUTLINE: 'outline', diff --git a/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx b/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx index f8799d51..771160d9 100644 --- a/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx +++ b/servers/nextjs/app/(presentation-generator)/upload/components/UploadPage.tsx @@ -131,7 +131,6 @@ const UploadPage = () => { config, files: responses, })); - dispatch(clearOutlines()); router.push("/documents-preview"); }; @@ -155,7 +154,6 @@ const UploadPage = () => { }); dispatch(setPresentationId(createResponse.id)); - dispatch(clearOutlines()); router.push("/outline"); }; From 09f648dc5b8c7b0dd0b8250c4f9d293f555a5283 Mon Sep 17 00:00:00 2001 From: sauravniraula Date: Wed, 6 Aug 2025 21:53:40 +0545 Subject: [PATCH 02/16] fix(fastapi): uses SlideOutlineModel to wrap slide outline, to fix json repair issue --- servers/fastapi/api/v1/ppt/endpoints/outlines.py | 3 ++- servers/fastapi/models/document_chunk.py | 6 ++++-- .../fastapi/models/presentation_outline_model.py | 6 +++++- servers/fastapi/utils/get_dynamic_models.py | 16 +++++++++++++--- .../llm_calls/generate_presentation_outlines.py | 1 - .../utils/llm_calls/generate_slide_content.py | 5 +++-- 6 files changed, 27 insertions(+), 10 deletions(-) diff --git a/servers/fastapi/api/v1/ppt/endpoints/outlines.py b/servers/fastapi/api/v1/ppt/endpoints/outlines.py index b0ec47af..f1eff7ba 100644 --- a/servers/fastapi/api/v1/ppt/endpoints/outlines.py +++ b/servers/fastapi/api/v1/ppt/endpoints/outlines.py @@ -87,7 +87,8 @@ async def stream_outlines( presentation.outlines = presentation_outlines.model_dump() presentation.title = ( - presentation_outlines.slides[0][:50] + presentation_outlines.slides[0] + .content[:50] .replace("#", "") .replace("/", "") .replace("\\", "") diff --git a/servers/fastapi/models/document_chunk.py b/servers/fastapi/models/document_chunk.py index 6861e4fb..a7500be9 100644 --- a/servers/fastapi/models/document_chunk.py +++ b/servers/fastapi/models/document_chunk.py @@ -1,5 +1,7 @@ from pydantic import BaseModel +from models.presentation_outline_model import SlideOutlineModel + class DocumentChunk(BaseModel): heading: str @@ -7,5 +9,5 @@ class DocumentChunk(BaseModel): heading_index: int score: float - def to_slide_outline(self) -> str: - return f"{self.heading}\n{self.content}" + def to_slide_outline(self) -> SlideOutlineModel: + return SlideOutlineModel(content=f"{self.heading}\n{self.content}") diff --git a/servers/fastapi/models/presentation_outline_model.py b/servers/fastapi/models/presentation_outline_model.py index ad55ae4b..01a3b2b7 100644 --- a/servers/fastapi/models/presentation_outline_model.py +++ b/servers/fastapi/models/presentation_outline_model.py @@ -2,8 +2,12 @@ from typing import List from pydantic import BaseModel +class SlideOutlineModel(BaseModel): + content: str + + class PresentationOutlineModel(BaseModel): - slides: List[str] + slides: List[SlideOutlineModel] def to_string(self): message = "" diff --git a/servers/fastapi/utils/get_dynamic_models.py b/servers/fastapi/utils/get_dynamic_models.py index 744a6a5a..fd4b2bda 100644 --- a/servers/fastapi/utils/get_dynamic_models.py +++ b/servers/fastapi/utils/get_dynamic_models.py @@ -1,13 +1,23 @@ from typing import List from pydantic import Field -from models.presentation_outline_model import PresentationOutlineModel +from models.presentation_outline_model import ( + PresentationOutlineModel, + SlideOutlineModel, +) from models.presentation_structure_model import PresentationStructureModel def get_presentation_outline_model_with_n_slides(n_slides: int): + class SlideOutlineModelWithNSlides(SlideOutlineModel): + content: str = Field( + description="Markdown content for each slide", + min_length=100, + max_length=300, + ) + class PresentationOutlineModelWithNSlides(PresentationOutlineModel): - slides: List[str] = Field( - description="Markdown content for each slide in about 100 to 200 words", + slides: List[SlideOutlineModelWithNSlides] = Field( + description="List of slide outlines", min_items=n_slides, max_items=n_slides, ) diff --git a/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py b/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py index 507bb6eb..2a3c9e95 100644 --- a/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py +++ b/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py @@ -1,4 +1,3 @@ -import asyncio from typing import Optional from models.llm_message import LLMMessage diff --git a/servers/fastapi/utils/llm_calls/generate_slide_content.py b/servers/fastapi/utils/llm_calls/generate_slide_content.py index ecff518a..62b87e2b 100644 --- a/servers/fastapi/utils/llm_calls/generate_slide_content.py +++ b/servers/fastapi/utils/llm_calls/generate_slide_content.py @@ -1,5 +1,6 @@ from models.llm_message import LLMMessage from models.presentation_layout import SlideLayoutModel +from models.presentation_outline_model import SlideOutlineModel from services.llm_client import LLMClient from utils.llm_provider import get_model from utils.schema_utils import remove_fields_from_schema @@ -50,7 +51,7 @@ def get_messages(outline: str, language: str): async def get_slide_content_from_type_and_outline( - slide_layout: SlideLayoutModel, outline: str, language: str + slide_layout: SlideLayoutModel, outline: SlideOutlineModel, language: str ): client = LLMClient() model = get_model() @@ -62,7 +63,7 @@ async def get_slide_content_from_type_and_outline( response = await client.generate_structured( model=model, messages=get_messages( - outline, + outline.content, language, ), response_format=response_schema, From f6550b92a2fe6651fdaef93562759fecb2003fba Mon Sep 17 00:00:00 2001 From: sauravniraula Date: Wed, 6 Aug 2025 22:02:59 +0545 Subject: [PATCH 03/16] fix(fastapi): outline type on /prepare --- servers/fastapi/api/v1/ppt/endpoints/presentation.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/servers/fastapi/api/v1/ppt/endpoints/presentation.py b/servers/fastapi/api/v1/ppt/endpoints/presentation.py index 5b4589a2..2650782d 100644 --- a/servers/fastapi/api/v1/ppt/endpoints/presentation.py +++ b/servers/fastapi/api/v1/ppt/endpoints/presentation.py @@ -11,7 +11,10 @@ from sqlmodel import select from constants.documents import UPLOAD_ACCEPTED_FILE_TYPES from models.presentation_and_path import PresentationPathAndEditPath from models.presentation_from_template import GetPresentationUsingTemplateRequest -from models.presentation_outline_model import PresentationOutlineModel +from models.presentation_outline_model import ( + PresentationOutlineModel, + SlideOutlineModel, +) from models.pptx_models import PptxPresentationModel from models.presentation_layout import PresentationLayoutModel from models.presentation_structure_model import PresentationStructureModel @@ -126,7 +129,7 @@ async def create_presentation( @PRESENTATION_ROUTER.post("/prepare", response_model=PresentationModel) async def prepare_presentation( presentation_id: Annotated[str, Body()], - outlines: Annotated[List[str], Body()], + outlines: Annotated[List[SlideOutlineModel], Body()], layout: Annotated[PresentationLayoutModel, Body()], title: Annotated[Optional[str], Body()] = None, sql_session: AsyncSession = Depends(get_async_session), @@ -161,7 +164,9 @@ async def prepare_presentation( presentation_structure.slides[index] = random_slide_index sql_session.add(presentation) - presentation.outlines = PresentationOutlineModel(slides=outlines).model_dump() + presentation.outlines = PresentationOutlineModel(slides=outlines).model_dump( + mode="json" + ) presentation.title = title or presentation.title presentation.set_layout(layout) presentation.set_structure(presentation_structure) From 1d4898cbcee66b62f94a347d6dd66fae2d6ff390 Mon Sep 17 00:00:00 2001 From: shiva raj badu Date: Wed, 6 Aug 2025 22:31:56 +0545 Subject: [PATCH 04/16] fix(Nextjs): Outline streaming issue solved --- .../outline/components/OutlineContent.tsx | 17 ++++++++++++++--- .../outline/components/OutlineItem.tsx | 10 ++++++---- .../outline/hooks/useOutlineManagement.ts | 8 ++++---- .../outline/hooks/useOutlineStreaming.ts | 13 +++++++++---- .../outline/hooks/usePresentationGeneration.ts | 2 +- .../upload/components/UploadPage.tsx | 2 ++ .../store/slices/presentationGeneration.ts | 4 ++-- 7 files changed, 38 insertions(+), 18 deletions(-) diff --git a/servers/nextjs/app/(presentation-generator)/outline/components/OutlineContent.tsx b/servers/nextjs/app/(presentation-generator)/outline/components/OutlineContent.tsx index 21852f5e..31bce158 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/components/OutlineContent.tsx +++ b/servers/nextjs/app/(presentation-generator)/outline/components/OutlineContent.tsx @@ -18,7 +18,7 @@ import { Button } from "@/components/ui/button"; import { FileText } from "lucide-react"; interface OutlineContentProps { - outlines: string[] | null; + outlines: { content: string }[] | null; isLoading: boolean; isStreaming: boolean; onDragEnd: (event: any) => void; @@ -83,7 +83,18 @@ const OutlineContent: React.FC = ({ collisionDetection={closestCenter} onDragEnd={onDragEnd} > - ( + + )) + ) : + ({ id: `slide-${index}` })) || []} strategy={verticalListSortingStrategy} > @@ -95,7 +106,7 @@ const OutlineContent: React.FC = ({ isStreaming={isStreaming} /> ))} - + } diff --git a/servers/nextjs/app/(presentation-generator)/custom-layout/components/SaveLayoutModal.tsx b/servers/nextjs/app/(presentation-generator)/custom-template/components/SaveLayoutModal.tsx similarity index 91% rename from servers/nextjs/app/(presentation-generator)/custom-layout/components/SaveLayoutModal.tsx rename to servers/nextjs/app/(presentation-generator)/custom-template/components/SaveLayoutModal.tsx index 57924e7a..40fa3a89 100644 --- a/servers/nextjs/app/(presentation-generator)/custom-layout/components/SaveLayoutModal.tsx +++ b/servers/nextjs/app/(presentation-generator)/custom-template/components/SaveLayoutModal.tsx @@ -53,22 +53,22 @@ export const SaveLayoutModal: React.FC = ({ - Save Layout + Save Template - Enter a name and description for your layout. This will help you identify it later. + Enter a name and description for your template. This will help you identify it later.
setLayoutName(e.target.value)} - placeholder="Enter layout name..." + placeholder="Enter template name..." disabled={isSaving} className="w-full" /> @@ -81,7 +81,7 @@ export const SaveLayoutModal: React.FC = ({ id="description" value={description} onChange={(e) => setDescription(e.target.value)} - placeholder="Enter a description for your layout..." + placeholder="Enter a description for your template..." disabled={isSaving} className="w-full resize-none" rows={3} @@ -109,7 +109,7 @@ export const SaveLayoutModal: React.FC = ({ ) : ( <> - Save Layout + Save Template )} diff --git a/servers/nextjs/app/(presentation-generator)/custom-layout/components/SlideContent.tsx b/servers/nextjs/app/(presentation-generator)/custom-template/components/SlideContent.tsx similarity index 100% rename from servers/nextjs/app/(presentation-generator)/custom-layout/components/SlideContent.tsx rename to servers/nextjs/app/(presentation-generator)/custom-template/components/SlideContent.tsx diff --git a/servers/nextjs/app/(presentation-generator)/custom-layout/hooks/useCustomLayout.ts b/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useCustomLayout.ts similarity index 100% rename from servers/nextjs/app/(presentation-generator)/custom-layout/hooks/useCustomLayout.ts rename to servers/nextjs/app/(presentation-generator)/custom-template/hooks/useCustomLayout.ts diff --git a/servers/nextjs/app/(presentation-generator)/custom-layout/hooks/useDrawingCanvas.ts b/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useDrawingCanvas.ts similarity index 100% rename from servers/nextjs/app/(presentation-generator)/custom-layout/hooks/useDrawingCanvas.ts rename to servers/nextjs/app/(presentation-generator)/custom-template/hooks/useDrawingCanvas.ts diff --git a/servers/nextjs/app/(presentation-generator)/custom-layout/hooks/useFileUpload.ts b/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useFileUpload.ts similarity index 100% rename from servers/nextjs/app/(presentation-generator)/custom-layout/hooks/useFileUpload.ts rename to servers/nextjs/app/(presentation-generator)/custom-template/hooks/useFileUpload.ts diff --git a/servers/nextjs/app/(presentation-generator)/custom-layout/hooks/useFontManagement.ts b/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useFontManagement.ts similarity index 100% rename from servers/nextjs/app/(presentation-generator)/custom-layout/hooks/useFontManagement.ts rename to servers/nextjs/app/(presentation-generator)/custom-template/hooks/useFontManagement.ts diff --git a/servers/nextjs/app/(presentation-generator)/custom-layout/hooks/useHtmlEdit.ts b/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useHtmlEdit.ts similarity index 100% rename from servers/nextjs/app/(presentation-generator)/custom-layout/hooks/useHtmlEdit.ts rename to servers/nextjs/app/(presentation-generator)/custom-template/hooks/useHtmlEdit.ts diff --git a/servers/nextjs/app/(presentation-generator)/custom-layout/hooks/useLayoutSaving.ts b/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useLayoutSaving.ts similarity index 100% rename from servers/nextjs/app/(presentation-generator)/custom-layout/hooks/useLayoutSaving.ts rename to servers/nextjs/app/(presentation-generator)/custom-template/hooks/useLayoutSaving.ts diff --git a/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useOpenAIKeyCheck.ts b/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useOpenAIKeyCheck.ts new file mode 100644 index 00000000..8263ddd9 --- /dev/null +++ b/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useOpenAIKeyCheck.ts @@ -0,0 +1,18 @@ +import { useState, useEffect } from "react"; + +export const useOpenAIKeyCheck = () => { + const [hasOpenAIKey, setHasOpenAIKey] = useState(false); + const [isOpenAIKeyLoading, setIsOpenAIKeyLoading] = useState(true); + + useEffect(() => { + fetch("/api/has-openai-key") + .then((res) => res.json()) + .then((data) => { + setHasOpenAIKey(Boolean(data.hasKey)); + setIsOpenAIKeyLoading(false); + }) + .catch(() => setIsOpenAIKeyLoading(false)); + }, []); + + return { hasOpenAIKey, isOpenAIKeyLoading }; +}; \ No newline at end of file diff --git a/servers/nextjs/app/(presentation-generator)/custom-layout/hooks/useSlideEdit.ts b/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useSlideEdit.ts similarity index 100% rename from servers/nextjs/app/(presentation-generator)/custom-layout/hooks/useSlideEdit.ts rename to servers/nextjs/app/(presentation-generator)/custom-template/hooks/useSlideEdit.ts diff --git a/servers/nextjs/app/(presentation-generator)/custom-layout/hooks/useSlideProcessing.ts b/servers/nextjs/app/(presentation-generator)/custom-template/hooks/useSlideProcessing.ts similarity index 100% rename from servers/nextjs/app/(presentation-generator)/custom-layout/hooks/useSlideProcessing.ts rename to servers/nextjs/app/(presentation-generator)/custom-template/hooks/useSlideProcessing.ts diff --git a/servers/nextjs/app/(presentation-generator)/custom-layout/page.tsx b/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx similarity index 98% rename from servers/nextjs/app/(presentation-generator)/custom-layout/page.tsx rename to servers/nextjs/app/(presentation-generator)/custom-template/page.tsx index eabe6e06..63dc95f1 100644 --- a/servers/nextjs/app/(presentation-generator)/custom-layout/page.tsx +++ b/servers/nextjs/app/(presentation-generator)/custom-template/page.tsx @@ -125,7 +125,7 @@ const CustomLayoutPage = () => {
)} - {/* Floating Save Layout Button */} + {/* Floating Save Template Button */} {slides.length > 0 && slides.some((s) => s.processed) && ( { /> )} - {/* Save Layout Modal */} + {/* Save Template Modal */} {
- Layouts + Templates
diff --git a/servers/nextjs/app/(presentation-generator)/outline/components/GenerateButton.tsx b/servers/nextjs/app/(presentation-generator)/outline/components/GenerateButton.tsx index e5f37757..329a5ef1 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/components/GenerateButton.tsx +++ b/servers/nextjs/app/(presentation-generator)/outline/components/GenerateButton.tsx @@ -23,7 +23,7 @@ const GenerateButton: React.FC = ({ const getButtonText = () => { if (loadingState.isLoading) return loadingState.message; if (streamState.isLoading || streamState.isStreaming) return "Loading..."; - if (!selectedLayoutGroup) return "Select a Layout Style"; + if (!selectedLayoutGroup) return "Select a Templae"; return "Generate Presentation"; }; diff --git a/servers/nextjs/app/(presentation-generator)/outline/components/LayoutSelection.tsx b/servers/nextjs/app/(presentation-generator)/outline/components/LayoutSelection.tsx index 0824fa1c..e5538cff 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/components/LayoutSelection.tsx +++ b/servers/nextjs/app/(presentation-generator)/outline/components/LayoutSelection.tsx @@ -82,10 +82,10 @@ const LayoutSelection: React.FC = ({
- No Layout Styles Available + No Templates Available

- No presentation layout styles could be loaded. Please try refreshing the page. + No presentation templates could be loaded. Please try refreshing the page.

diff --git a/servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx b/servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx index eceb950b..c11a5a59 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx +++ b/servers/nextjs/app/(presentation-generator)/outline/components/OutlinePage.tsx @@ -51,7 +51,7 @@ const OutlinePage: React.FC = () => { Outline & Content - Layout Style + Select Template
diff --git a/servers/nextjs/app/(presentation-generator)/layout-preview/[slug]/page.tsx b/servers/nextjs/app/(presentation-generator)/template-preview/[slug]/page.tsx similarity index 98% rename from servers/nextjs/app/(presentation-generator)/layout-preview/[slug]/page.tsx rename to servers/nextjs/app/(presentation-generator)/template-preview/[slug]/page.tsx index 22570da2..f54e7abd 100644 --- a/servers/nextjs/app/(presentation-generator)/layout-preview/[slug]/page.tsx +++ b/servers/nextjs/app/(presentation-generator)/template-preview/[slug]/page.tsx @@ -44,7 +44,7 @@ const GroupLayoutPreview = () => { method: "DELETE", }); if (response.ok) { - router.push("/layout-preview"); + router.push("/template-preview"); } } return ( @@ -66,7 +66,7 @@ const GroupLayoutPreview = () => {