Merge branch 'feat/custom_schema_and_layout' of github.com:presenton/presenton into feat/custom_schema_and_layout

This commit is contained in:
shiva raj badu 2025-07-17 00:38:22 +05:45
commit aa9ededb2b
13 changed files with 165 additions and 15 deletions

View file

View file

@ -4,6 +4,7 @@ from typing import Annotated, List, Optional
import uuid
from fastapi import APIRouter, Body, HTTPException
from fastapi.responses import StreamingResponse
from sqlmodel import select
from models.presentation_outline_model import SlideOutlineModel
from models.presentation_layout import PresentationLayoutModel
@ -144,6 +145,7 @@ async def stream_presentation(presentation_id: str):
slide = SlideModel(
presentation=presentation_id,
layout=slide_layout.id,
index=i,
content=slide_content,
)
slides.append(slide)
@ -176,3 +178,17 @@ async def stream_presentation(presentation_id: str):
).to_string()
return StreamingResponse(inner(), media_type="text/event-stream")
@PRESENTATION_ROUTER.get("/", response_model=PresentationWithSlides)
def get_presentation(id: str):
with get_sql_session() as sql_session:
presentation = sql_session.get(PresentationModel, id)
slides = sql_session.exec(
select(SlideModel).where(SlideModel.presentation == id)
)
slides = sorted(slides, key=lambda x: x.index)
return PresentationWithSlides(
**presentation.model_dump(),
slides=slides,
)

View file

View file

View file

View file

@ -7,4 +7,5 @@ class SlideModel(SQLModel, table=True):
id: str = Field(primary_key=True, default_factory=get_random_uuid)
presentation: str
layout: str
index: int
content: dict = Field(sa_column=Column(JSON))

View file

@ -26,6 +26,7 @@ httpcore==1.0.9
httptools==0.6.4
httpx==0.28.1
idna==3.10
iniconfig==2.1.0
Jinja2==3.1.6
jiter==0.10.0
lxml==6.0.0
@ -34,10 +35,12 @@ MarkupSafe==3.0.2
mdurl==0.1.2
multidict==6.6.3
openai==1.95.1
packaging==25.0
pathvalidate==3.3.1
pdfminer.six==20250506
pdfplumber==0.11.7
pillow==11.3.0
pluggy==1.6.0
propcache==0.3.2
pyasn1==0.6.1
pyasn1_modules==0.4.2
@ -46,6 +49,7 @@ pydantic==2.11.7
pydantic_core==2.33.2
Pygments==2.19.2
pypdfium2==4.30.1
pytest==8.4.1
python-docx==1.2.0
python-dotenv==1.1.1
python-multipart==0.0.20

View file

View file

@ -0,0 +1,70 @@
import json
from typing import Optional
from pydantic import BaseModel, Field
from google.genai.types import GenerateContentResponse, GenerateContentConfig
from utils.llm_provider import get_google_llm_client, get_large_model
class HeadingDescription(BaseModel):
heading: str = Field(
description="Heading of the slide", min_length=10, max_length=20
)
description: str = Field(
description="Description of the slide", min_length=40, max_length=200
)
class SlideContentTest(BaseModel):
title: str = Field(description="Title of the slide", min_length=10, max_length=20)
first_content: HeadingDescription = Field(description="First content of the slide")
second_content: HeadingDescription = Field(
description="Second content of the slide"
)
third_content: HeadingDescription = Field(description="Third content of the slide")
class ColumnContentModel(BaseModel):
title: str = Field(min_length=3, max_length=100, description="Column title")
content: str = Field(min_length=10, max_length=800, description="Column content")
class TwoColumnSlideModel(BaseModel):
title: str = Field(
min_length=3,
max_length=100,
description="Title of the slide",
)
subtitle: Optional[str] = Field(
min_length=3,
max_length=150,
description="Optional subtitle or description",
)
leftColumn: ColumnContentModel = Field(
description="Left column content",
)
rightColumn: ColumnContentModel = Field(
description="Right column content",
)
backgroundImage: Optional[str] = Field(
description="URL to background image for the slide"
)
def test_gemini_schema_support():
response: GenerateContentResponse = get_google_llm_client().models.generate_content(
model=get_large_model(),
contents=[
"Generate a slide for a presentation",
"The slide should have a title and two contents",
"The title should be a short title for the slide",
"The contents should be a heading and a description",
"The heading should be a short heading for the slide",
],
config=GenerateContentConfig(
response_mime_type="application/json",
response_schema=TwoColumnSlideModel.model_json_schema(),
),
)
print(response.parsed)

View file

@ -0,0 +1,66 @@
import asyncio
import json
from typing import Optional
from pydantic import BaseModel, Field
from utils.llm_provider import get_llm_client, get_large_model
class HeadingDescription(BaseModel):
heading: str = Field(
description="Heading of the slide", min_length=10, max_length=20
)
description: str = Field(
description="Description of the slide", min_length=40, max_length=200
)
class SlideContentTest(BaseModel):
title: str = Field(description="Title of the slide", min_length=10, max_length=20)
first_content: HeadingDescription = Field(description="First content of the slide")
second_content: HeadingDescription = Field(
description="Second content of the slide"
)
third_content: HeadingDescription = Field(description="Third content of the slide")
class ColumnContentModel(BaseModel):
title: str = Field(min_length=3, max_length=100, description="Column title")
content: str = Field(min_length=10, max_length=800, description="Column content")
class TwoColumnSlideModel(BaseModel):
title: str = Field(
min_length=3,
max_length=100,
description="Title of the slide",
)
subtitle: Optional[str] = Field(
min_length=3,
max_length=150,
description="Optional subtitle or description",
)
leftColumn: ColumnContentModel = Field(
description="Left column content",
)
rightColumn: ColumnContentModel = Field(
description="Right column content",
)
backgroundImage: Optional[str] = Field(
description="URL to background image for the slide"
)
def test_openai_schema_support():
response = asyncio.run(
get_llm_client().beta.chat.completions.parse(
model=get_large_model(),
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Generate a slide for a presentation"},
],
response_format=TwoColumnSlideModel,
)
)
print(response.choices[0].message.parsed)

View file

View file

@ -13,19 +13,6 @@ def get_prompt(presentation_layout: PresentationLayoutModel, n_slides: int, data
return [
{
"role": "system",
# "content": f"""
# You're a professional presentation designer with years of experience in designing clear and engaging presentations.
# {presentation_layout.to_string()}
# # Steps
# 1. Analyze provided Number of slides, Presentation title, Slides content and Presentation Layout.
# 2. Select appropriate slide layout **index** for each slide.
# # Notes
# - Slide layout should be selected based on provided content for slide and notes.
# - Don't fall into patterns like always using layout 2 and after layout 1.
# - Each presentation should have its own unique flow and rhythm.
# - Select type for {n_slides} slides.
# **Go through notes and steps and make sure they are all followed. Rule breaks are strictly not allowed.**
# """,
"content": f"""
You're a professional presentation designer.
{presentation_layout.to_string()}

View file

@ -1,5 +1,5 @@
from http.client import HTTPException
import os
from fastapi import HTTPException
from openai import AsyncOpenAI
from google import genai
@ -15,7 +15,13 @@ from utils.get_env import (
def get_llm_provider():
return LLMProvider(get_llm_provider_env())
try:
return LLMProvider(get_llm_provider_env())
except:
raise HTTPException(
status_code=500,
detail=f"Invalid LLM provider. Please select one of: openai, google, ollama, custom",
)
def get_ollama_url():