diff --git a/backend/api/v1/ppt/endpoints/presentation.py b/backend/api/v1/ppt/endpoints/presentation.py index c1f97b8..d125172 100644 --- a/backend/api/v1/ppt/endpoints/presentation.py +++ b/backend/api/v1/ppt/endpoints/presentation.py @@ -43,6 +43,7 @@ from utils.llm_calls.generate_presentation_outlines import generate_ppt_outline from utils.llm_calls.summarize_brief import summarize_brief from models.sql.slide import SlideModel from models.sse_response import SSECompleteResponse, SSEErrorResponse, SSEResponse +from pydantic import BaseModel, ConfigDict from services.database import get_async_session, async_session_maker from services.temp_file_service import TEMP_FILE_SERVICE @@ -73,6 +74,23 @@ from utils.process_slides import ( import uuid +class SlideInput(BaseModel): + """Plain Pydantic model for slide data in request body (avoids SQLModel table=True validation quirks).""" + model_config = ConfigDict(extra="ignore") + + id: Optional[uuid.UUID] = None + presentation: uuid.UUID + layout_group: str + layout: str + index: int + content: dict + html_content: Optional[str] = None + speaker_note: Optional[str] = None + properties: Optional[dict] = None + deleted_at: Optional[datetime] = None + + + PRESENTATION_ROUTER = APIRouter(prefix="/presentation", tags=["Presentation"]) @@ -425,7 +443,7 @@ async def update_presentation( id: Annotated[uuid.UUID, Body()], n_slides: Annotated[Optional[int], Body()] = None, title: Annotated[Optional[str], Body()] = None, - slides: Annotated[Optional[List[SlideModel]], Body()] = None, + slides: Annotated[Optional[List[SlideInput]], Body()] = None, _current_user: UserModel = Depends(get_current_user), sql_session: AsyncSession = Depends(get_async_session), ): @@ -442,22 +460,32 @@ async def update_presentation( if n_slides or title: presentation.sqlmodel_update(presentation_update_dict) + slide_models: List[SlideModel] = [] if slides: - # Just to make sure id is UUID - for slide in slides: - slide.presentation = uuid.UUID(slide.presentation) - slide.id = uuid.UUID(slide.id) + for s in slides: + slide_models.append(SlideModel( + id=s.id or uuid.uuid4(), + presentation=s.presentation, + layout_group=s.layout_group, + layout=s.layout, + index=s.index, + content=s.content, + html_content=s.html_content, + speaker_note=s.speaker_note, + properties=s.properties, + deleted_at=s.deleted_at, + )) await sql_session.execute( delete(SlideModel).where(SlideModel.presentation == presentation.id) ) - sql_session.add_all(slides) + sql_session.add_all(slide_models) await sql_session.commit() return PresentationWithSlides( **presentation.model_dump(), - slides=slides or [], + slides=slide_models, ) diff --git a/frontend/app/hooks/useCustomTemplates.ts b/frontend/app/hooks/useCustomTemplates.ts index 8a1fbc1..7b0c09b 100644 --- a/frontend/app/hooks/useCustomTemplates.ts +++ b/frontend/app/hooks/useCustomTemplates.ts @@ -8,6 +8,20 @@ import TemplateService from "../(presentation-generator)/services/api/template"; import { isJsonLayoutCode, parseLayoutSchema, ParsedLayout, LayoutSchema } from "./parseLayoutSchema"; import { SlideRenderer } from "../(presentation-generator)/components/SlideRenderer"; +// Build a JSON Schema from a LayoutSchema's elements so the backend LLM knows what content to generate. +function buildSchemaFromElements(layoutSchema: LayoutSchema): Record { + const properties: Record = {}; + for (const el of layoutSchema.elements) { + if (!el.placeholder) continue; + properties[el.placeholder] = { type: "string" }; + } + return { + type: "object", + title: layoutSchema.layoutName, + properties, + }; +} + // Adapter: convert ParsedLayout (JSON schema) into a CompiledLayout-compatible object // so existing code that uses CompiledLayout can work with both formats. function parsedLayoutToCompiled(parsed: ParsedLayout): CompiledLayout { @@ -26,7 +40,7 @@ function parsedLayoutToCompiled(parsed: ParsedLayout): CompiledLayout { layoutDescription: parsed.layoutDescription, schema: null, sampleData: parsed.sampleData, - schemaJSON: null, + schemaJSON: buildSchemaFromElements(schema), }; } diff --git a/frontend/store/slices/clientSlice.ts b/frontend/store/slices/clientSlice.ts index 1025004..ce1f0d0 100644 --- a/frontend/store/slices/clientSlice.ts +++ b/frontend/store/slices/clientSlice.ts @@ -67,7 +67,7 @@ export const fetchMasterDecks = createAsyncThunk( "client/fetchMasterDecks", async (clientId: string, { rejectWithValue }) => { try { - const response = await fetch( + const response = await apiFetch( `/api/v1/admin/master-decks?client_id=${clientId}`, { headers: getHeader() } ); @@ -84,7 +84,7 @@ export const fetchClientPresentations = createAsyncThunk( "client/fetchClientPresentations", async (clientId: string, { rejectWithValue }) => { try { - const response = await fetch( + const response = await apiFetch( `/api/v1/ppt/presentation/all?client_id=${clientId}`, { headers: getHeader() } );