Merge pull request #155 from presenton/fix/presentation-api

fix/presentation api
This commit is contained in:
Saurav Niraula 2025-07-29 09:37:03 +05:45 committed by GitHub
commit 31aea68f42
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 64 additions and 71 deletions

View file

@ -155,12 +155,11 @@ Content-Type: `multipart/form-data`
#### Example Request
```bash
curl -X POST http://localhost:5000/api/v1/ppt/generate/presentation \
curl -X POST http://localhost:5000/api/v1/ppt/presentation/generate \
-F "prompt=Introduction to Machine Learning" \
-F "n_slides=5" \
-F "language=English" \
-F "layout=general" \
-F "theme=light" \
-F "export_as=pptx"
```

View file

@ -2,12 +2,15 @@ import asyncio
import json
import os
import random
from typing import Annotated, List, Optional
import uuid, aiohttp
from fastapi import APIRouter, Body, HTTPException
from typing import Annotated, List, Literal, Optional
import uuid
from annotated_types import Len
from fastapi import APIRouter, Body, File, HTTPException, UploadFile
from fastapi.responses import StreamingResponse
from sqlalchemy import delete
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,
@ -17,11 +20,6 @@ from models.pptx_models import PptxPresentationModel
from models.presentation_layout import PresentationLayoutModel
from models.presentation_structure_model import PresentationStructureModel
from models.presentation_with_slides import PresentationWithSlides
from models.generate_presentation_api import (
GeneratePresentationRequest,
PresentationAndPath,
PresentationPathAndEditPath,
)
from services.get_layout_by_name import get_layout_by_name
from services.icon_finder_service import IconFinderService
from services.image_generation_service import ImageGenerationService
@ -45,6 +43,7 @@ from utils.llm_calls.generate_slide_content import (
)
from utils.process_slides import process_slide_and_fetch_assets
from utils.randomizers import get_random_uuid
from utils.validators import validate_files
PRESENTATION_ROUTER = APIRouter(prefix="/presentation", tags=["Presentation"])
@ -322,18 +321,22 @@ async def create_pptx(pptx_model: Annotated[PptxPresentationModel, Body()]):
@PRESENTATION_ROUTER.post("/generate", response_model=PresentationPathAndEditPath)
async def generate_presentation_api(
data: Annotated[GeneratePresentationRequest, Body()],
prompt: Annotated[str, Body()],
n_slides: Annotated[int, Body()] = 8,
language: Annotated[str, Body()] = "English",
layout: Annotated[str, Body()] = "general",
files: Annotated[Optional[List[UploadFile]], File()] = None,
export_as: Annotated[Literal["pptx", "pdf"], Body()] = "pptx",
):
presentation_id = str(uuid.uuid4())
print("**" * 40)
print(f"Generating presentation with ID: {presentation_id}")
print(f"Received Body as JSON: {data.model_dump_json(indent=2)}")
validate_files(files, True, True, 50, UPLOAD_ACCEPTED_FILE_TYPES)
presentation_id = get_random_uuid()
# 1. Save uploaded files
file_paths = []
if data.documents:
if files:
temp_dir = TEMP_FILE_SERVICE.create_temp_dir()
for upload in data.documents:
for upload in files:
file_path = os.path.join(temp_dir, upload.filename)
with open(file_path, "wb") as f:
f.write(await upload.read())
@ -350,25 +353,23 @@ async def generate_presentation_api(
# 3. Generate Outlines
presentation_content_text = ""
async for chunk in generate_ppt_outline(
data.prompt,
data.n_slides,
data.language,
prompt,
n_slides,
language,
summary,
):
presentation_content_text += chunk
presentation_content_json = json.loads(presentation_content_text)
presentation_content = PresentationOutlineModel(**presentation_content_json)
outlines = presentation_content.slides[: data.n_slides]
outlines = presentation_content.slides[:n_slides]
total_outlines = len(outlines)
print("-" * 40)
print("Generated Presentation Content:", presentation_content_text)
print(f"Generated {total_outlines} outlines for the presentation")
print(f"Presentation Title: {presentation_content.title}")
# 4. Parse Layouts
layout = await get_layout_by_name(data.layout)
layout = await get_layout_by_name(layout)
total_slide_layouts = len(layout.slides)
# 5. Generate Structure
@ -395,12 +396,12 @@ async def generate_presentation_api(
if presentation_structure.slides[index] >= total_slide_layouts:
presentation_structure.slides[index] = random_slide_index
# 6. Create and Save PresentationModel
# 6. Create PresentationModel
presentation = PresentationModel(
id=presentation_id,
prompt=data.prompt,
n_slides=data.n_slides,
language=data.language,
prompt=prompt,
n_slides=n_slides,
language=language,
title=presentation_content.title,
summary=summary,
outlines=[each.model_dump() for each in outlines],
@ -408,10 +409,10 @@ async def generate_presentation_api(
layout=layout.model_dump(),
structure=presentation_structure.model_dump(),
)
with get_sql_session() as sql_session:
sql_session.add(presentation)
sql_session.commit()
sql_session.refresh(presentation)
image_generation_service = ImageGenerationService(get_images_directory())
icon_finder_service = IconFinderService()
async_asset_generation_tasks = []
# 7. Generate slide content and save slides
slides: List[SlideModel] = []
@ -422,7 +423,6 @@ async def generate_presentation_api(
slide_content = await get_slide_content_from_type_and_outline(
slide_layout, outlines[i]
)
print(f"Generated content for slide {i}: {json.dumps(slide_content, indent=2)}")
slide = SlideModel(
presentation=presentation_id,
layout_group=layout.name,
@ -430,29 +430,29 @@ async def generate_presentation_api(
index=i,
content=slide_content,
)
async_asset_generation_tasks.append(
process_slide_and_fetch_assets(
image_generation_service, icon_finder_service, slide
)
)
slides.append(slide)
slide_contents.append(slide_content)
# Process slides to fetch assets (images, icons, etc.)
print("Processing slides to fetch assets")
image_generation_service = ImageGenerationService(get_images_directory())
icon_finder_service = IconFinderService()
for slide in slides:
try:
await process_slide_and_fetch_assets(
image_generation_service, icon_finder_service, slide
)
print(f"Processed slide {slide.index} successfully")
except Exception as e:
print(f"Error processing slide {slide.index}: {e}")
generated_assets_lists = await asyncio.gather(*async_asset_generation_tasks)
generated_assets = []
for assets_list in generated_assets_lists:
generated_assets.extend(assets_list)
# 8. Save PresentationModel and Slides
with get_sql_session() as sql_session:
sql_session.add(presentation)
sql_session.add_all(slides)
sql_session.add_all(generated_assets)
sql_session.commit()
# 8. Export
# 9. Export
presentation_and_path = await export_presentation(
presentation_id, presentation_content.title, data.export_as
presentation_id, presentation_content.title, export_as
)
return PresentationPathAndEditPath(

View file

@ -1,19 +0,0 @@
from typing import List, Optional, Literal
from pydantic import BaseModel, Field
from fastapi import UploadFile
class GeneratePresentationRequest(BaseModel):
prompt: str
n_slides: int = Field(default=8, ge=5, le=20)
language: str = Field(default="English")
layout: str = Field(default="general")
documents: Optional[List[UploadFile]] = None
export_as: Literal["pptx", "pdf"] = Field(default="pptx")
class PresentationAndPath(BaseModel):
presentation_id: str
path: str
class PresentationPathAndEditPath(PresentationAndPath):
edit_path: str

View file

@ -0,0 +1,10 @@
from pydantic import BaseModel
class PresentationAndPath(BaseModel):
presentation_id: str
path: str
class PresentationPathAndEditPath(PresentationAndPath):
edit_path: str

View file

@ -6,7 +6,7 @@ from fastapi import HTTPException
from pathvalidate import sanitize_filename
from models.pptx_models import PptxPresentationModel
from models.generate_presentation_api import PresentationAndPath
from models.presentation_and_path import PresentationAndPath
from services.pptx_presentation_creator import PptxPresentationCreator
from services import TEMP_FILE_SERVICE
from utils.asset_directory_utils import get_exports_directory

View file

@ -1,5 +1,5 @@
from http.client import HTTPException
from typing import List
from fastapi import HTTPException
from fastapi import UploadFile
@ -18,10 +18,13 @@ def validate_files(
if (max_size * 1024 * 1024) < each_file.size:
raise HTTPException(
400,
f"File '{each_file.filename}' exceeded max upload size of {max_size} MB",
detail=f"File '{each_file.filename}' exceeded max upload size of {max_size} MB",
)
elif each_file.content_type not in accepted_types:
raise HTTPException(400, f"File '{each_file.filename}' not accepted.")
raise HTTPException(
400,
detail=f"File '{each_file.filename}' not accepted. Accepted types: {accepted_types}",
)
elif not (field or nullable):
raise HTTPException(400, "File must be provided.")
raise HTTPException(400, detail="File must be provided.")