Merge pull request #155 from presenton/fix/presentation-api
fix/presentation api
This commit is contained in:
commit
31aea68f42
7 changed files with 64 additions and 71 deletions
|
|
@ -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"
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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
|
||||
10
servers/fastapi/models/presentation_and_path.py
Normal file
10
servers/fastapi/models/presentation_and_path.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class PresentationAndPath(BaseModel):
|
||||
presentation_id: str
|
||||
path: str
|
||||
|
||||
|
||||
class PresentationPathAndEditPath(PresentationAndPath):
|
||||
edit_path: str
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue