Merge pull request #255 from presenton/feat/tone-instructions-support
feat/tone instructions support
This commit is contained in:
commit
69cfddc2bf
33 changed files with 426 additions and 180 deletions
|
|
@ -7,7 +7,7 @@ from constants.documents import UPLOAD_ACCEPTED_FILE_TYPES
|
|||
from models.decomposed_file_info import DecomposedFileInfo
|
||||
from services.temp_file_service import TEMP_FILE_SERVICE
|
||||
from services.documents_loader import DocumentsLoader
|
||||
from utils.randomizers import get_random_uuid
|
||||
import uuid
|
||||
from utils.validators import validate_files
|
||||
|
||||
FILES_ROUTER = APIRouter(prefix="/files", tags=["Files"])
|
||||
|
|
@ -18,7 +18,7 @@ async def upload_files(files: Optional[List[UploadFile]]):
|
|||
if not files:
|
||||
raise HTTPException(400, "Documents are required")
|
||||
|
||||
temp_dir = TEMP_FILE_SERVICE.create_temp_dir(get_random_uuid())
|
||||
temp_dir = TEMP_FILE_SERVICE.create_temp_dir(str(uuid.uuid4()))
|
||||
|
||||
validate_files(files, True, True, 100, UPLOAD_ACCEPTED_FILE_TYPES)
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ async def upload_files(files: Optional[List[UploadFile]]):
|
|||
|
||||
@FILES_ROUTER.post("/decompose", response_model=List[DecomposedFileInfo])
|
||||
async def decompose_files(file_paths: Annotated[List[str], Body(embed=True)]):
|
||||
temp_dir = TEMP_FILE_SERVICE.create_temp_dir(get_random_uuid())
|
||||
temp_dir = TEMP_FILE_SERVICE.create_temp_dir(str(uuid.uuid4()))
|
||||
|
||||
txt_files = []
|
||||
other_files = []
|
||||
|
|
@ -56,7 +56,7 @@ async def decompose_files(file_paths: Annotated[List[str], Body(embed=True)]):
|
|||
response = []
|
||||
for index, parsed_doc in enumerate(parsed_documents):
|
||||
file_path = TEMP_FILE_SERVICE.create_temp_file_path(
|
||||
f"{get_random_uuid()}.txt", temp_dir
|
||||
f"{uuid.uuid4()}.txt", temp_dir
|
||||
)
|
||||
parsed_doc = parsed_doc.replace("<br>", "\n")
|
||||
with open(file_path, "w") as text_file:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ from typing import List, Dict, Any, Optional
|
|||
from fastapi import APIRouter, HTTPException, File, UploadFile
|
||||
from pydantic import BaseModel
|
||||
from utils.asset_directory_utils import get_app_data_directory_env
|
||||
from utils.randomizers import get_random_uuid
|
||||
import uuid
|
||||
|
||||
try:
|
||||
from fontTools.ttLib import TTFont
|
||||
|
|
@ -157,7 +157,7 @@ async def upload_font(
|
|||
# Generate unique filename to avoid conflicts
|
||||
file_ext = os.path.splitext(font_file.filename)[1].lower()
|
||||
base_name = os.path.splitext(font_file.filename)[0]
|
||||
unique_filename = f"{base_name}_{get_random_uuid()[:8]}{file_ext}"
|
||||
unique_filename = f"{base_name}_{str(uuid.uuid4())[:8]}{file_ext}"
|
||||
|
||||
# Get fonts directory
|
||||
fonts_dir = get_fonts_directory()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import asyncio
|
||||
import json
|
||||
import uuid
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.responses import StreamingResponse
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
|
@ -18,7 +19,7 @@ OUTLINES_ROUTER = APIRouter(prefix="/outlines", tags=["Outlines"])
|
|||
|
||||
@OUTLINES_ROUTER.get("/stream")
|
||||
async def stream_outlines(
|
||||
presentation_id: str, sql_session: AsyncSession = Depends(get_async_session)
|
||||
presentation_id: uuid.UUID, sql_session: AsyncSession = Depends(get_async_session)
|
||||
):
|
||||
presentation = await sql_session.get(PresentationModel, presentation_id)
|
||||
|
||||
|
|
@ -49,17 +50,23 @@ async def stream_outlines(
|
|||
slides=[chunk.to_slide_outline() for chunk in chunks]
|
||||
)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Failed to generate presentation outlines. Please try again.",
|
||||
)
|
||||
else:
|
||||
additional_context = "\n\n".join(documents)
|
||||
|
||||
if not presentation_outlines:
|
||||
presentation_outlines_text = ""
|
||||
async for chunk in generate_ppt_outline(
|
||||
presentation.prompt,
|
||||
presentation.content,
|
||||
presentation.n_slides,
|
||||
presentation.language,
|
||||
additional_context,
|
||||
presentation.tone,
|
||||
presentation.verbosity,
|
||||
presentation.instructions,
|
||||
):
|
||||
# Give control to the event loop
|
||||
await asyncio.sleep(0)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from fastapi import APIRouter, UploadFile, File, HTTPException
|
|||
from pydantic import BaseModel
|
||||
|
||||
from utils.asset_directory_utils import get_images_directory
|
||||
from utils.randomizers import get_random_uuid
|
||||
import uuid
|
||||
from constants.documents import PDF_MIME_TYPES
|
||||
|
||||
|
||||
|
|
@ -68,8 +68,8 @@ async def process_pdf_slides(
|
|||
|
||||
# Move screenshots to images directory and generate URLs
|
||||
images_dir = get_images_directory()
|
||||
presentation_id = get_random_uuid()
|
||||
presentation_images_dir = os.path.join(images_dir, presentation_id)
|
||||
presentation_id = uuid.uuid4()
|
||||
presentation_images_dir = os.path.join(images_dir, str(presentation_id))
|
||||
os.makedirs(presentation_images_dir, exist_ok=True)
|
||||
|
||||
slides_data = []
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import xml.etree.ElementTree as ET
|
|||
import re
|
||||
|
||||
from utils.asset_directory_utils import get_images_directory
|
||||
from utils.randomizers import get_random_uuid
|
||||
import uuid
|
||||
from constants.documents import POWERPOINT_TYPES
|
||||
|
||||
|
||||
|
|
@ -308,8 +308,8 @@ async def process_pptx_slides(
|
|||
|
||||
# Move screenshots to images directory and generate URLs
|
||||
images_dir = get_images_directory()
|
||||
presentation_id = get_random_uuid()
|
||||
presentation_images_dir = os.path.join(images_dir, presentation_id)
|
||||
presentation_id = uuid.uuid4()
|
||||
presentation_images_dir = os.path.join(images_dir, str(presentation_id))
|
||||
os.makedirs(presentation_images_dir, exist_ok=True)
|
||||
|
||||
slides_data = []
|
||||
|
|
|
|||
|
|
@ -18,7 +18,9 @@ from models.presentation_outline_model import (
|
|||
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.presentation_with_slides import (
|
||||
PresentationWithSlides,
|
||||
)
|
||||
|
||||
from services.documents_loader import DocumentsLoader
|
||||
from services.score_based_chunker import ScoreBasedChunker
|
||||
|
|
@ -45,7 +47,7 @@ from utils.process_slides import (
|
|||
process_slide_add_placeholder_assets,
|
||||
process_slide_and_fetch_assets,
|
||||
)
|
||||
from utils.randomizers import get_random_uuid
|
||||
import uuid
|
||||
|
||||
|
||||
PRESENTATION_ROUTER = APIRouter(prefix="/presentation", tags=["Presentation"])
|
||||
|
|
@ -53,7 +55,7 @@ PRESENTATION_ROUTER = APIRouter(prefix="/presentation", tags=["Presentation"])
|
|||
|
||||
@PRESENTATION_ROUTER.get("", response_model=PresentationWithSlides)
|
||||
async def get_presentation(
|
||||
id: str, sql_session: AsyncSession = Depends(get_async_session)
|
||||
id: uuid.UUID, sql_session: AsyncSession = Depends(get_async_session)
|
||||
):
|
||||
presentation = await sql_session.get(PresentationModel, id)
|
||||
if not presentation:
|
||||
|
|
@ -71,7 +73,7 @@ async def get_presentation(
|
|||
|
||||
@PRESENTATION_ROUTER.delete("", status_code=204)
|
||||
async def delete_presentation(
|
||||
id: str, sql_session: AsyncSession = Depends(get_async_session)
|
||||
id: uuid.UUID, sql_session: AsyncSession = Depends(get_async_session)
|
||||
):
|
||||
presentation = await sql_session.get(PresentationModel, id)
|
||||
if not presentation:
|
||||
|
|
@ -108,20 +110,26 @@ async def get_all_presentations(sql_session: AsyncSession = Depends(get_async_se
|
|||
|
||||
@PRESENTATION_ROUTER.post("/create", response_model=PresentationModel)
|
||||
async def create_presentation(
|
||||
prompt: Annotated[str, Body()],
|
||||
content: Annotated[str, Body()],
|
||||
n_slides: Annotated[int, Body()],
|
||||
language: Annotated[str, Body()],
|
||||
file_paths: Annotated[Optional[List[str]], Body()] = None,
|
||||
tone: Annotated[Optional[str], Body()] = None,
|
||||
verbosity: Annotated[Optional[str], Body()] = None,
|
||||
instructions: Annotated[Optional[str], Body()] = None,
|
||||
sql_session: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
presentation_id = get_random_uuid()
|
||||
presentation_id = uuid.uuid4()
|
||||
|
||||
presentation = PresentationModel(
|
||||
id=presentation_id,
|
||||
prompt=prompt,
|
||||
content=content,
|
||||
n_slides=n_slides,
|
||||
language=language,
|
||||
file_paths=file_paths,
|
||||
tone=tone,
|
||||
verbosity=verbosity,
|
||||
instructions=instructions,
|
||||
)
|
||||
|
||||
sql_session.add(presentation)
|
||||
|
|
@ -132,7 +140,7 @@ async def create_presentation(
|
|||
|
||||
@PRESENTATION_ROUTER.post("/prepare", response_model=PresentationModel)
|
||||
async def prepare_presentation(
|
||||
presentation_id: Annotated[str, Body()],
|
||||
presentation_id: Annotated[uuid.UUID, Body()],
|
||||
outlines: Annotated[List[SlideOutlineModel], Body()],
|
||||
layout: Annotated[PresentationLayoutModel, Body()],
|
||||
title: Annotated[Optional[str], Body()] = None,
|
||||
|
|
@ -157,6 +165,7 @@ async def prepare_presentation(
|
|||
await generate_presentation_structure(
|
||||
presentation_outline=presentation_outline_model,
|
||||
presentation_layout=layout,
|
||||
instructions=presentation.instructions,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -181,7 +190,7 @@ async def prepare_presentation(
|
|||
|
||||
@PRESENTATION_ROUTER.get("/stream", response_model=PresentationWithSlides)
|
||||
async def stream_presentation(
|
||||
presentation_id: str, sql_session: AsyncSession = Depends(get_async_session)
|
||||
presentation_id: uuid.UUID, sql_session: AsyncSession = Depends(get_async_session)
|
||||
):
|
||||
presentation = await sql_session.get(PresentationModel, presentation_id)
|
||||
if not presentation:
|
||||
|
|
@ -216,7 +225,12 @@ async def stream_presentation(
|
|||
slide_layout = layout.slides[slide_layout_index]
|
||||
|
||||
slide_content = await get_slide_content_from_type_and_outline(
|
||||
slide_layout, outline.slides[i], presentation.language
|
||||
slide_layout,
|
||||
outline.slides[i],
|
||||
presentation.language,
|
||||
presentation.tone,
|
||||
presentation.verbosity,
|
||||
presentation.instructions,
|
||||
)
|
||||
|
||||
slide = SlideModel(
|
||||
|
|
@ -277,14 +291,22 @@ async def update_presentation(
|
|||
):
|
||||
updated_presentation = presentation_with_slides.to_presentation_model()
|
||||
updated_slides = presentation_with_slides.slides
|
||||
|
||||
presentation = await sql_session.get(PresentationModel, updated_presentation.id)
|
||||
if not presentation:
|
||||
raise HTTPException(status_code=404, detail="Presentation not found")
|
||||
|
||||
presentation.sqlmodel_update(updated_presentation)
|
||||
|
||||
await sql_session.execute(
|
||||
delete(SlideModel).where(SlideModel.presentation == updated_presentation.id)
|
||||
)
|
||||
|
||||
# Just to make sure id is UUID
|
||||
for slide in updated_slides:
|
||||
slide.presentation = uuid.UUID(slide.presentation)
|
||||
slide.id = uuid.UUID(slide.id)
|
||||
|
||||
sql_session.add_all(updated_slides)
|
||||
await sql_session.commit()
|
||||
|
||||
|
|
@ -305,7 +327,7 @@ async def create_pptx(
|
|||
|
||||
export_directory = get_exports_directory()
|
||||
pptx_path = os.path.join(
|
||||
export_directory, f"{pptx_model.name or get_random_uuid()}.pptx"
|
||||
export_directory, f"{pptx_model.name or uuid.uuid4()}.pptx"
|
||||
)
|
||||
pptx_creator.save(pptx_path)
|
||||
|
||||
|
|
@ -317,7 +339,7 @@ async def generate_presentation_api(
|
|||
request: GeneratePresentationRequest,
|
||||
sql_session: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
presentation_id = get_random_uuid()
|
||||
presentation_id = uuid.uuid4()
|
||||
|
||||
# 3. Generate Outlines
|
||||
presentation_outlines = None
|
||||
|
|
@ -341,10 +363,14 @@ async def generate_presentation_api(
|
|||
if not presentation_outlines:
|
||||
presentation_outlines_text = ""
|
||||
async for chunk in generate_ppt_outline(
|
||||
request.prompt,
|
||||
request.content,
|
||||
request.n_slides,
|
||||
request.language,
|
||||
additional_context,
|
||||
request.tone,
|
||||
request.verbosity,
|
||||
request.instructions,
|
||||
request.web_search,
|
||||
):
|
||||
presentation_outlines_text += chunk
|
||||
|
||||
|
|
@ -376,6 +402,7 @@ async def generate_presentation_api(
|
|||
await generate_presentation_structure(
|
||||
presentation_outlines,
|
||||
layout_model,
|
||||
request.instructions,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
@ -391,12 +418,15 @@ async def generate_presentation_api(
|
|||
# 6. Create PresentationModel
|
||||
presentation = PresentationModel(
|
||||
id=presentation_id,
|
||||
prompt=request.prompt,
|
||||
content=request.content,
|
||||
n_slides=request.n_slides,
|
||||
language=request.language,
|
||||
outlines=presentation_outlines.model_dump(),
|
||||
layout=layout_model.model_dump(),
|
||||
structure=presentation_structure.model_dump(),
|
||||
tone=request.tone,
|
||||
verbosity=request.verbosity,
|
||||
instructions=request.instructions,
|
||||
)
|
||||
|
||||
image_generation_service = ImageGenerationService(get_images_directory())
|
||||
|
|
@ -409,7 +439,12 @@ async def generate_presentation_api(
|
|||
slide_layout = layout_model.slides[slide_layout_index]
|
||||
print(f"Generating content for slide {i} with layout {slide_layout.id}")
|
||||
slide_content = await get_slide_content_from_type_and_outline(
|
||||
slide_layout, outlines[i], request.language
|
||||
slide_layout,
|
||||
outlines[i],
|
||||
request.language,
|
||||
request.tone,
|
||||
request.verbosity,
|
||||
request.instructions,
|
||||
)
|
||||
slide = SlideModel(
|
||||
presentation=presentation_id,
|
||||
|
|
@ -438,7 +473,7 @@ async def generate_presentation_api(
|
|||
|
||||
# 9. Export
|
||||
presentation_and_path = await export_presentation(
|
||||
presentation_id, presentation.title or get_random_uuid(), request.export_as
|
||||
presentation_id, presentation.title or str(uuid.uuid4()), request.export_as
|
||||
)
|
||||
|
||||
return PresentationPathAndEditPath(
|
||||
|
|
@ -475,7 +510,7 @@ async def from_template(
|
|||
await sql_session.commit()
|
||||
|
||||
presentation_and_path = await export_presentation(
|
||||
new_presentation.id, new_presentation.title or get_random_uuid(), data.export_as
|
||||
new_presentation.id, new_presentation.title or str(uuid.uuid4()), data.export_as
|
||||
)
|
||||
|
||||
return PresentationPathAndEditPath(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from typing import Annotated, Optional
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
import uuid
|
||||
|
||||
from models.sql.presentation import PresentationModel
|
||||
from models.sql.slide import SlideModel
|
||||
|
|
@ -11,7 +12,7 @@ from utils.llm_calls.edit_slide import get_edited_slide_content
|
|||
from utils.llm_calls.edit_slide_html import get_edited_slide_html
|
||||
from utils.llm_calls.select_slide_type_on_edit import get_slide_layout_from_prompt
|
||||
from utils.process_slides import process_old_and_new_slides_and_fetch_assets
|
||||
from utils.randomizers import get_random_uuid
|
||||
import uuid
|
||||
|
||||
|
||||
SLIDE_ROUTER = APIRouter(prefix="/slide", tags=["Slide"])
|
||||
|
|
@ -19,7 +20,7 @@ SLIDE_ROUTER = APIRouter(prefix="/slide", tags=["Slide"])
|
|||
|
||||
@SLIDE_ROUTER.post("/edit")
|
||||
async def edit_slide(
|
||||
id: Annotated[str, Body()],
|
||||
id: Annotated[uuid.UUID, Body()],
|
||||
prompt: Annotated[str, Body()],
|
||||
sql_session: AsyncSession = Depends(get_async_session),
|
||||
):
|
||||
|
|
@ -49,7 +50,7 @@ async def edit_slide(
|
|||
)
|
||||
|
||||
# Always assign a new unique id to the slide
|
||||
slide.id = get_random_uuid()
|
||||
slide.id = uuid.uuid4()
|
||||
|
||||
sql_session.add(slide)
|
||||
slide.content = edited_slide_content
|
||||
|
|
@ -63,7 +64,7 @@ async def edit_slide(
|
|||
|
||||
@SLIDE_ROUTER.post("/edit-html", response_model=SlideModel)
|
||||
async def edit_slide_html(
|
||||
id: Annotated[str, Body()],
|
||||
id: Annotated[uuid.UUID, Body()],
|
||||
prompt: Annotated[str, Body()],
|
||||
html: Annotated[Optional[str], Body()] = None,
|
||||
sql_session: AsyncSession = Depends(get_async_session),
|
||||
|
|
@ -80,7 +81,7 @@ async def edit_slide_html(
|
|||
|
||||
# Always assign a new unique id to the slide
|
||||
# This is to ensure that the nextjs can track slide updates
|
||||
slide.id = get_random_uuid()
|
||||
slide.id = uuid.uuid4()
|
||||
|
||||
sql_session.add(slide)
|
||||
slide.html_content = edited_slide_html
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import os
|
|||
import base64
|
||||
from datetime import datetime
|
||||
from typing import Optional, List, Dict
|
||||
from uuid import UUID
|
||||
from fastapi import APIRouter, HTTPException, File, UploadFile, Form, Depends
|
||||
from pydantic import BaseModel
|
||||
from openai import OpenAI
|
||||
|
|
@ -54,7 +55,7 @@ class HtmlToReactResponse(BaseModel):
|
|||
|
||||
# Request/Response models for layout management endpoints
|
||||
class LayoutData(BaseModel):
|
||||
presentation_id: str # UUID of the presentation
|
||||
presentation: UUID # UUID of the presentation
|
||||
layout_id: str # Unique identifier for the layout
|
||||
layout_name: str # Display name of the layout
|
||||
layout_code: str # TSX/React component code for the layout
|
||||
|
|
@ -80,7 +81,7 @@ class GetLayoutsResponse(BaseModel):
|
|||
|
||||
|
||||
class PresentationSummary(BaseModel):
|
||||
presentation_id: str
|
||||
presentation_id: UUID
|
||||
layout_count: int
|
||||
last_updated_at: Optional[datetime] = None
|
||||
template: Optional[dict] = None
|
||||
|
|
@ -101,7 +102,7 @@ class ErrorResponse(BaseModel):
|
|||
|
||||
|
||||
class TemplateCreateRequest(BaseModel):
|
||||
id: str
|
||||
id: UUID
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
|
||||
|
|
@ -113,7 +114,7 @@ class TemplateCreateResponse(BaseModel):
|
|||
|
||||
|
||||
class TemplateInfo(BaseModel):
|
||||
id: str
|
||||
id: UUID
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
created_at: Optional[datetime] = None
|
||||
|
|
@ -688,7 +689,7 @@ async def save_layouts(
|
|||
|
||||
for i, layout_data in enumerate(request.layouts):
|
||||
# Validate individual layout data
|
||||
if not layout_data.presentation_id or not layout_data.presentation_id.strip():
|
||||
if not layout_data.presentation or not str(layout_data.presentation).strip():
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Layout {i+1}: presentation_id cannot be empty"
|
||||
|
|
@ -714,7 +715,7 @@ async def save_layouts(
|
|||
|
||||
# Check if layout already exists for this presentation and layout_id
|
||||
stmt = select(PresentationLayoutCodeModel).where(
|
||||
PresentationLayoutCodeModel.presentation_id == layout_data.presentation_id,
|
||||
PresentationLayoutCodeModel.presentation == layout_data.presentation,
|
||||
PresentationLayoutCodeModel.layout_id == layout_data.layout_id
|
||||
)
|
||||
result = await session.execute(stmt)
|
||||
|
|
@ -729,7 +730,7 @@ async def save_layouts(
|
|||
else:
|
||||
# Create new layout
|
||||
new_layout = PresentationLayoutCodeModel(
|
||||
presentation_id=layout_data.presentation_id,
|
||||
presentation=layout_data.presentation,
|
||||
layout_id=layout_data.layout_id,
|
||||
layout_name=layout_data.layout_name,
|
||||
layout_code=layout_data.layout_code,
|
||||
|
|
@ -762,7 +763,7 @@ async def save_layouts(
|
|||
|
||||
# ENDPOINT 5: Get layouts for a presentation
|
||||
@LAYOUT_MANAGEMENT_ROUTER.get(
|
||||
"/get-templates/{presentation_id}",
|
||||
"/get-templates/{presentation}",
|
||||
response_model=GetLayoutsResponse,
|
||||
responses={
|
||||
400: {"model": ErrorResponse, "description": "Invalid presentation ID"},
|
||||
|
|
@ -771,14 +772,14 @@ async def save_layouts(
|
|||
}
|
||||
)
|
||||
async def get_layouts(
|
||||
presentation_id: str,
|
||||
presentation: UUID,
|
||||
session: AsyncSession = Depends(get_async_session)
|
||||
):
|
||||
"""
|
||||
Retrieve all layouts for a specific presentation.
|
||||
|
||||
Args:
|
||||
presentation_id: UUID of the presentation
|
||||
presentation: UUID of the presentation
|
||||
session: Database session
|
||||
|
||||
Returns:
|
||||
|
|
@ -789,7 +790,7 @@ async def get_layouts(
|
|||
"""
|
||||
try:
|
||||
# Validate presentation_id format (basic UUID check)
|
||||
if not presentation_id or len(presentation_id.strip()) == 0:
|
||||
if not presentation or len(str(presentation).strip()) == 0:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Presentation ID cannot be empty"
|
||||
|
|
@ -797,7 +798,7 @@ async def get_layouts(
|
|||
|
||||
# Query layouts for the given presentation_id
|
||||
stmt = select(PresentationLayoutCodeModel).where(
|
||||
PresentationLayoutCodeModel.presentation_id == presentation_id
|
||||
PresentationLayoutCodeModel.presentation == presentation
|
||||
)
|
||||
result = await session.execute(stmt)
|
||||
layouts_db = result.scalars().all()
|
||||
|
|
@ -806,13 +807,13 @@ async def get_layouts(
|
|||
if not layouts_db:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"No layouts found for presentation ID: {presentation_id}"
|
||||
detail=f"No layouts found for presentation ID: {presentation}"
|
||||
)
|
||||
|
||||
# Convert to response format
|
||||
layouts = [
|
||||
LayoutData(
|
||||
presentation_id=layout.presentation_id,
|
||||
presentation=layout.presentation,
|
||||
layout_id=layout.layout_id,
|
||||
layout_name=layout.layout_name,
|
||||
layout_code=layout.layout_code,
|
||||
|
|
@ -829,7 +830,7 @@ async def get_layouts(
|
|||
fonts_list = sorted(list(aggregated_fonts)) if aggregated_fonts else None
|
||||
|
||||
# Fetch template meta
|
||||
template_meta = await session.get(TemplateModel, presentation_id)
|
||||
template_meta = await session.get(TemplateModel, presentation)
|
||||
template = None
|
||||
if template_meta:
|
||||
template = {
|
||||
|
|
@ -842,7 +843,7 @@ async def get_layouts(
|
|||
return GetLayoutsResponse(
|
||||
success=True,
|
||||
layouts=layouts,
|
||||
message=f"Retrieved {len(layouts)} layout(s) for presentation {presentation_id}",
|
||||
message=f"Retrieved {len(layouts)} layout(s) for presentation {presentation}",
|
||||
template=template,
|
||||
fonts=fonts_list,
|
||||
)
|
||||
|
|
@ -851,7 +852,7 @@ async def get_layouts(
|
|||
# Re-raise HTTP exceptions as-is
|
||||
raise
|
||||
except Exception as e:
|
||||
print(f"Error retrieving layouts for presentation {presentation_id}: {str(e)}")
|
||||
print(f"Error retrieving layouts for presentation {presentation}: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Internal server error while retrieving layouts: {str(e)}"
|
||||
|
|
@ -878,10 +879,10 @@ async def get_presentations_summary(
|
|||
try:
|
||||
# Query to get presentation_id, count of layouts, and MAX(updated_at)
|
||||
stmt = select(
|
||||
PresentationLayoutCodeModel.presentation_id,
|
||||
PresentationLayoutCodeModel.presentation,
|
||||
func.count(PresentationLayoutCodeModel.id).label('layout_count'),
|
||||
func.max(PresentationLayoutCodeModel.updated_at).label('last_updated_at')
|
||||
).group_by(PresentationLayoutCodeModel.presentation_id)
|
||||
).group_by(PresentationLayoutCodeModel.presentation)
|
||||
|
||||
result = await session.execute(stmt)
|
||||
presentation_data = result.all()
|
||||
|
|
@ -889,7 +890,7 @@ async def get_presentations_summary(
|
|||
# Convert to response format with template info if available
|
||||
presentations = []
|
||||
for row in presentation_data:
|
||||
template_meta = await session.get(TemplateModel, row.presentation_id)
|
||||
template_meta = await session.get(TemplateModel, row.presentation)
|
||||
template = None
|
||||
if template_meta:
|
||||
template = {
|
||||
|
|
@ -900,7 +901,7 @@ async def get_presentations_summary(
|
|||
}
|
||||
presentations.append(
|
||||
PresentationSummary(
|
||||
presentation_id=row.presentation_id,
|
||||
presentation=row.presentation,
|
||||
layout_count=row.layout_count,
|
||||
last_updated_at=row.last_updated_at,
|
||||
template=template,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,17 @@ from pydantic import BaseModel, Field
|
|||
|
||||
|
||||
class GeneratePresentationRequest(BaseModel):
|
||||
prompt: str = Field(..., description="The prompt for generating the presentation")
|
||||
content: str = Field(..., description="The content for generating the presentation")
|
||||
instructions: Optional[str] = Field(
|
||||
default=None, description="The instruction for generating the presentation"
|
||||
)
|
||||
tone: Optional[str] = Field(
|
||||
default=None, description="The tone for the presentation"
|
||||
)
|
||||
verbosity: Optional[str] = Field(
|
||||
default=None, description="The verbosity for the presentation"
|
||||
)
|
||||
web_search: bool = Field(default=False, description="Whether to enable web search")
|
||||
n_slides: int = Field(default=8, description="Number of slides to generate")
|
||||
language: str = Field(
|
||||
default="English", description="Language for the presentation"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
from pydantic import BaseModel
|
||||
import uuid
|
||||
|
||||
|
||||
class PresentationAndPath(BaseModel):
|
||||
presentation_id: str
|
||||
presentation_id: uuid.UUID
|
||||
path: str
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from typing import List, Literal
|
||||
from pydantic import BaseModel
|
||||
import uuid
|
||||
|
||||
|
||||
class SlideContentUpdate(BaseModel):
|
||||
|
|
@ -8,6 +9,6 @@ class SlideContentUpdate(BaseModel):
|
|||
|
||||
|
||||
class GetPresentationUsingTemplateRequest(BaseModel):
|
||||
presentation_id: str
|
||||
presentation_id: uuid.UUID
|
||||
data: List[SlideContentUpdate]
|
||||
export_as: Literal["pptx", "pdf"] = "pptx"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
from typing import List, Optional
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
|
@ -11,17 +12,38 @@ from models.sql.slide import SlideModel
|
|||
|
||||
|
||||
class PresentationWithSlides(BaseModel):
|
||||
id: str
|
||||
prompt: str
|
||||
id: uuid.UUID
|
||||
content: str
|
||||
n_slides: int
|
||||
language: str
|
||||
file_paths: Optional[List[str]]
|
||||
title: Optional[str] = None
|
||||
outlines: Optional[PresentationOutlineModel]
|
||||
outlines: Optional[PresentationOutlineModel] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
layout: Optional[PresentationLayoutModel]
|
||||
structure: Optional[PresentationStructureModel]
|
||||
structure: Optional[PresentationStructureModel] = None
|
||||
instructions: Optional[str] = None
|
||||
tone: Optional[str] = None
|
||||
verbosity: Optional[str] = None
|
||||
slides: List[SlideModel]
|
||||
|
||||
def to_presentation_model(self) -> PresentationModel:
|
||||
return PresentationModel(**self.model_dump())
|
||||
return PresentationModel(
|
||||
id=self.id,
|
||||
content=self.content,
|
||||
n_slides=self.n_slides,
|
||||
language=self.language,
|
||||
file_paths=self.file_paths,
|
||||
title=self.title,
|
||||
outlines=self.outlines.model_dump(mode="json") if self.outlines else None,
|
||||
created_at=self.created_at,
|
||||
updated_at=self.updated_at,
|
||||
layout=self.layout.model_dump(mode="json") if self.layout else None,
|
||||
structure=(
|
||||
self.structure.model_dump(mode="json") if self.structure else None
|
||||
),
|
||||
instructions=self.instructions,
|
||||
tone=self.tone,
|
||||
verbosity=self.verbosity,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,19 @@
|
|||
from datetime import datetime
|
||||
from typing import Optional
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import JSON, Column, DateTime
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
from utils.randomizers import get_random_uuid
|
||||
from utils.datetime_utils import get_current_utc_datetime
|
||||
|
||||
|
||||
class ImageAsset(SQLModel, table=True):
|
||||
id: str = Field(default_factory=get_random_uuid, primary_key=True)
|
||||
created_at: datetime = Field(sa_column=Column(DateTime, default=datetime.now))
|
||||
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
||||
created_at: datetime = Field(
|
||||
sa_column=Column(
|
||||
DateTime(timezone=True), nullable=False, default=get_current_utc_datetime
|
||||
),
|
||||
)
|
||||
path: str
|
||||
extras: Optional[dict] = Field(sa_column=Column(JSON), default=None)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import uuid
|
||||
from sqlmodel import Field, Column, JSON, SQLModel
|
||||
|
||||
from utils.randomizers import get_random_uuid
|
||||
|
||||
|
||||
class KeyValueSqlModel(SQLModel, table=True):
|
||||
id: str = Field(default_factory=get_random_uuid, primary_key=True)
|
||||
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
|
||||
key: str = Field(index=True)
|
||||
value: dict = Field(sa_column=Column(JSON))
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
from datetime import datetime
|
||||
import uuid
|
||||
from sqlmodel import Field, Column, JSON, SQLModel, DateTime
|
||||
|
||||
|
||||
class OllamaPullStatus(SQLModel, table=True):
|
||||
id: str = Field(primary_key=True)
|
||||
id: uuid.UUID = Field(primary_key=True, default_factory=uuid.uuid4)
|
||||
last_updated: datetime = Field(sa_column=Column(DateTime, default=datetime.now))
|
||||
status: dict = Field(sa_column=Column(JSON))
|
||||
|
|
|
|||
|
|
@ -1,31 +1,48 @@
|
|||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
from sqlalchemy import JSON, Column, DateTime
|
||||
import uuid
|
||||
from sqlalchemy import JSON, Column, DateTime, String
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
from models.presentation_layout import PresentationLayoutModel
|
||||
from models.presentation_outline_model import PresentationOutlineModel
|
||||
from models.presentation_structure_model import PresentationStructureModel
|
||||
from utils.randomizers import get_random_uuid
|
||||
from utils.datetime_utils import get_current_utc_datetime
|
||||
|
||||
|
||||
class PresentationModel(SQLModel, table=True):
|
||||
id: str = Field(primary_key=True)
|
||||
prompt: str
|
||||
__tablename__ = "presentations"
|
||||
|
||||
id: uuid.UUID = Field(primary_key=True, default_factory=uuid.uuid4)
|
||||
content: str
|
||||
n_slides: int
|
||||
language: str
|
||||
title: Optional[str] = None
|
||||
file_paths: Optional[List[str]] = Field(sa_column=Column(JSON), default=None)
|
||||
outlines: Optional[dict] = Field(sa_column=Column(JSON), default=None)
|
||||
created_at: datetime = Field(sa_column=Column(DateTime, default=datetime.now))
|
||||
updated_at: datetime = Field(sa_column=Column(DateTime, default=datetime.now))
|
||||
created_at: datetime = Field(
|
||||
sa_column=Column(
|
||||
DateTime(timezone=True), nullable=False, default=get_current_utc_datetime
|
||||
),
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
sa_column=Column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=get_current_utc_datetime,
|
||||
onupdate=get_current_utc_datetime,
|
||||
),
|
||||
)
|
||||
layout: Optional[dict] = Field(sa_column=Column(JSON), default=None)
|
||||
structure: Optional[dict] = Field(sa_column=Column(JSON), default=None)
|
||||
instructions: Optional[str] = Field(sa_column=Column(String), default=None)
|
||||
tone: Optional[str] = Field(sa_column=Column(String), default=None)
|
||||
verbosity: Optional[str] = Field(sa_column=Column(String), default=None)
|
||||
|
||||
def get_new_presentation(self):
|
||||
return PresentationModel(
|
||||
id=get_random_uuid(),
|
||||
prompt=self.prompt,
|
||||
id=uuid.uuid4(),
|
||||
content=self.content,
|
||||
n_slides=self.n_slides,
|
||||
language=self.language,
|
||||
title=self.title,
|
||||
|
|
@ -33,6 +50,7 @@ class PresentationModel(SQLModel, table=True):
|
|||
outlines=self.outlines,
|
||||
layout=self.layout,
|
||||
structure=self.structure,
|
||||
instructions=self.instructions,
|
||||
)
|
||||
|
||||
def get_presentation_outline(self):
|
||||
|
|
|
|||
|
|
@ -1,19 +1,37 @@
|
|||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
import uuid
|
||||
from sqlalchemy import Column, DateTime, Text, JSON
|
||||
from sqlmodel import SQLModel, Field
|
||||
|
||||
from utils.datetime_utils import get_current_utc_datetime
|
||||
|
||||
|
||||
class PresentationLayoutCodeModel(SQLModel, table=True):
|
||||
"""Model for storing presentation layout codes"""
|
||||
|
||||
|
||||
__tablename__ = "presentation_layout_codes"
|
||||
|
||||
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
presentation_id: str = Field(index=True, description="UUID of the presentation")
|
||||
presentation: uuid.UUID = Field(index=True, description="UUID of the presentation")
|
||||
layout_id: str = Field(description="Unique identifier for the layout")
|
||||
layout_name: str = Field(description="Display name of the layout")
|
||||
layout_code: str = Field(sa_column=Column(Text), description="TSX/React component code for the layout")
|
||||
fonts: Optional[List[str]] = Field(sa_column=Column(JSON), default=None, description="Optional list of font links")
|
||||
created_at: datetime = Field(sa_column=Column(DateTime, default=datetime.now))
|
||||
updated_at: datetime = Field(sa_column=Column(DateTime, default=datetime.now, onupdate=datetime.now))
|
||||
layout_code: str = Field(
|
||||
sa_column=Column(Text), description="TSX/React component code for the layout"
|
||||
)
|
||||
fonts: Optional[List[str]] = Field(
|
||||
sa_column=Column(JSON), default=None, description="Optional list of font links"
|
||||
)
|
||||
created_at: datetime = Field(
|
||||
sa_column=Column(
|
||||
DateTime(timezone=True), nullable=False, default=get_current_utc_datetime
|
||||
)
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
sa_column=Column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
default=get_current_utc_datetime,
|
||||
onupdate=get_current_utc_datetime,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,24 +1,28 @@
|
|||
from typing import Optional
|
||||
import uuid
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlmodel import Field, Column, JSON, SQLModel
|
||||
|
||||
from utils.randomizers import get_random_uuid
|
||||
|
||||
|
||||
class SlideModel(SQLModel, table=True):
|
||||
id: str = Field(primary_key=True, default_factory=get_random_uuid)
|
||||
presentation: str
|
||||
__tablename__ = "slides"
|
||||
|
||||
id: uuid.UUID = Field(primary_key=True, default_factory=uuid.uuid4)
|
||||
presentation: uuid.UUID = Field(
|
||||
sa_column=Column(ForeignKey("presentations.id", ondelete="CASCADE"), index=True)
|
||||
)
|
||||
layout_group: str
|
||||
layout: str
|
||||
index: int
|
||||
content: dict = Field(sa_column=Column(JSON))
|
||||
html_content: Optional[str]
|
||||
speaker_note: str
|
||||
speaker_note: Optional[str] = None
|
||||
properties: Optional[dict] = Field(sa_column=Column(JSON))
|
||||
|
||||
def get_new_slide(self, presentation_id: str, content: Optional[dict] = None):
|
||||
def get_new_slide(self, presentation: uuid.UUID, content: Optional[dict] = None):
|
||||
return SlideModel(
|
||||
id=get_random_uuid(),
|
||||
presentation=presentation_id,
|
||||
id=uuid.uuid4(),
|
||||
presentation=presentation,
|
||||
layout_group=self.layout_group,
|
||||
layout=self.layout,
|
||||
index=self.index,
|
||||
|
|
|
|||
|
|
@ -1,13 +1,26 @@
|
|||
from datetime import datetime
|
||||
from typing import Optional
|
||||
import uuid
|
||||
from sqlalchemy import Column, DateTime
|
||||
from sqlmodel import SQLModel, Field
|
||||
|
||||
from utils.datetime_utils import get_current_utc_datetime
|
||||
|
||||
|
||||
class TemplateModel(SQLModel, table=True):
|
||||
__tablename__ = "templates"
|
||||
|
||||
id: str = Field(primary_key=True, description="UUID for the template (matches presentation_id)")
|
||||
id: uuid.UUID = Field(
|
||||
default_factory=uuid.uuid4,
|
||||
primary_key=True,
|
||||
description="UUID for the template (matches presentation_id)",
|
||||
)
|
||||
name: str = Field(description="Human friendly template name")
|
||||
description: Optional[str] = Field(default=None, description="Optional template description")
|
||||
created_at: datetime = Field(sa_column=Column(DateTime, default=datetime.now))
|
||||
description: Optional[str] = Field(
|
||||
default=None, description="Optional template description"
|
||||
)
|
||||
created_at: datetime = Field(
|
||||
sa_column=Column(
|
||||
DateTime(timezone=True), nullable=False, default=get_current_utc_datetime
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from utils.image_provider import (
|
|||
is_gemini_flash_selected,
|
||||
is_dalle3_selected,
|
||||
)
|
||||
from utils.randomizers import get_random_uuid
|
||||
import uuid
|
||||
|
||||
|
||||
class ImageGenerationService:
|
||||
|
|
@ -104,7 +104,7 @@ class ImageGenerationService:
|
|||
if part.text is not None:
|
||||
print(part.text)
|
||||
elif part.inline_data is not None:
|
||||
image_path = os.path.join(output_directory, f"{get_random_uuid()}.jpg")
|
||||
image_path = os.path.join(output_directory, f"{uuid.uuid4()}.jpg")
|
||||
with open(image_path, "wb") as f:
|
||||
f.write(part.inline_data.data)
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ from utils.image_utils import (
|
|||
round_image_corners,
|
||||
set_image_opacity,
|
||||
)
|
||||
from utils.randomizers import get_random_uuid
|
||||
import uuid
|
||||
|
||||
BLANK_SLIDE_LAYOUT = 6
|
||||
|
||||
|
|
@ -216,7 +216,7 @@ class PptxPresentationCreator:
|
|||
image = invert_image(image)
|
||||
if picture_model.opacity:
|
||||
image = set_image_opacity(image, picture_model.opacity)
|
||||
image_path = os.path.join(self._temp_dir, f"{get_random_uuid()}.png")
|
||||
image_path = os.path.join(self._temp_dir, f"{uuid.uuid4()}.png")
|
||||
image.save(image_path)
|
||||
|
||||
margined_position = self.get_margined_position(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import os
|
|||
from typing import Optional, Union
|
||||
|
||||
from utils.get_env import get_temp_directory_env
|
||||
from utils.randomizers import get_random_uuid
|
||||
import uuid
|
||||
|
||||
|
||||
class TempFileService:
|
||||
|
|
@ -13,7 +13,7 @@ class TempFileService:
|
|||
os.makedirs(self.base_dir, exist_ok=True)
|
||||
|
||||
def create_dir_in_dir(self, base_dir: str, dir_name: Optional[str] = None) -> str:
|
||||
temp_dir = os.path.join(base_dir, dir_name if dir_name else get_random_uuid())
|
||||
temp_dir = os.path.join(base_dir, dir_name if dir_name else str(uuid.uuid4()))
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
return temp_dir
|
||||
|
||||
|
|
|
|||
5
servers/fastapi/utils/datetime_utils.py
Normal file
5
servers/fastapi/utils/datetime_utils.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
def get_current_utc_datetime():
|
||||
return datetime.now(timezone.utc)
|
||||
|
|
@ -6,7 +6,7 @@ from urllib.parse import urlparse
|
|||
|
||||
import aiohttp
|
||||
|
||||
from utils.randomizers import get_random_uuid
|
||||
import uuid
|
||||
|
||||
|
||||
async def download_file(
|
||||
|
|
@ -36,9 +36,9 @@ async def download_file(
|
|||
content_type.split(";")[0]
|
||||
)
|
||||
if extension:
|
||||
filename = f"{get_random_uuid()}{extension}"
|
||||
filename = f"{uuid.uuid4()}{extension}"
|
||||
|
||||
filename = filename or get_random_uuid()
|
||||
filename = filename or str(uuid.uuid4())
|
||||
save_path = os.path.join(save_directory, filename)
|
||||
|
||||
async with aiohttp.ClientSession(trust_env=True) as session:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import json
|
|||
import os
|
||||
import aiohttp
|
||||
from typing import Literal
|
||||
import uuid
|
||||
from fastapi import HTTPException
|
||||
from pathvalidate import sanitize_filename
|
||||
|
||||
|
|
@ -10,11 +11,11 @@ from models.presentation_and_path import PresentationAndPath
|
|||
from services.pptx_presentation_creator import PptxPresentationCreator
|
||||
from services.temp_file_service import TEMP_FILE_SERVICE
|
||||
from utils.asset_directory_utils import get_exports_directory
|
||||
from utils.randomizers import get_random_uuid
|
||||
import uuid
|
||||
|
||||
|
||||
async def export_presentation(
|
||||
presentation_id: str, title: str, export_as: Literal["pptx", "pdf"]
|
||||
presentation_id: uuid.UUID, title: str, export_as: Literal["pptx", "pdf"]
|
||||
) -> PresentationAndPath:
|
||||
if export_as == "pptx":
|
||||
|
||||
|
|
@ -41,7 +42,7 @@ async def export_presentation(
|
|||
export_directory = get_exports_directory()
|
||||
pptx_path = os.path.join(
|
||||
export_directory,
|
||||
f"{sanitize_filename(title or get_random_uuid())}.pptx",
|
||||
f"{sanitize_filename(title or str(uuid.uuid4()))}.pptx",
|
||||
)
|
||||
pptx_creator.save(pptx_path)
|
||||
|
||||
|
|
@ -55,7 +56,7 @@ async def export_presentation(
|
|||
"http://localhost/api/export-as-pdf",
|
||||
json={
|
||||
"id": presentation_id,
|
||||
"title": sanitize_filename(title or get_random_uuid()),
|
||||
"title": sanitize_filename(title or str(uuid.uuid4())),
|
||||
},
|
||||
) as response:
|
||||
response_json = await response.json()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from models.llm_message import LLMSystemMessage, LLMUserMessage
|
||||
from models.presentation_layout import SlideLayoutModel
|
||||
from models.sql.slide import SlideModel
|
||||
|
|
@ -5,9 +7,23 @@ from services.llm_client import LLMClient
|
|||
from utils.llm_provider import get_model
|
||||
from utils.schema_utils import add_field_in_schema, remove_fields_from_schema
|
||||
|
||||
system_prompt = """
|
||||
|
||||
def get_system_prompt(
|
||||
tone: Optional[str] = None,
|
||||
verbosity: Optional[str] = None,
|
||||
instructions: Optional[str] = None,
|
||||
):
|
||||
return f"""
|
||||
Edit Slide data and speaker note based on provided prompt, follow mentioned steps and notes and provide structured output.
|
||||
|
||||
{"# User Instruction:" if instructions else ""}
|
||||
{instructions or ""}
|
||||
|
||||
{"# Tone:" if tone else ""}
|
||||
{tone or ""}
|
||||
|
||||
{"# Verbosity:" if verbosity else ""}
|
||||
{verbosity or ""}
|
||||
|
||||
# Notes
|
||||
- Provide output in language mentioned in **Input**.
|
||||
|
|
@ -19,7 +35,7 @@ system_prompt = """
|
|||
- Speaker note should be simple, clear, concise and to the point.
|
||||
|
||||
**Go through all notes and steps and make sure they are followed, including mentioned constraints**
|
||||
"""
|
||||
"""
|
||||
|
||||
|
||||
def get_user_prompt(prompt: str, slide_data: dict, language: str):
|
||||
|
|
@ -27,6 +43,9 @@ def get_user_prompt(prompt: str, slide_data: dict, language: str):
|
|||
## Icon Query And Image Prompt Language
|
||||
English
|
||||
|
||||
## Current Date and Time
|
||||
{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
|
||||
|
||||
## Slide Content Language
|
||||
{language}
|
||||
|
||||
|
|
@ -42,10 +61,13 @@ def get_messages(
|
|||
prompt: str,
|
||||
slide_data: dict,
|
||||
language: str,
|
||||
tone: Optional[str] = None,
|
||||
verbosity: Optional[str] = None,
|
||||
instructions: Optional[str] = None,
|
||||
):
|
||||
return [
|
||||
LLMSystemMessage(
|
||||
content=system_prompt,
|
||||
content=get_system_prompt(tone, verbosity, instructions),
|
||||
),
|
||||
LLMUserMessage(
|
||||
content=get_user_prompt(prompt, slide_data, language),
|
||||
|
|
@ -58,6 +80,9 @@ async def get_edited_slide_content(
|
|||
slide: SlideModel,
|
||||
language: str,
|
||||
slide_layout: SlideLayoutModel,
|
||||
tone: Optional[str] = None,
|
||||
verbosity: Optional[str] = None,
|
||||
instructions: Optional[str] = None,
|
||||
):
|
||||
model = get_model()
|
||||
|
||||
|
|
@ -80,7 +105,9 @@ async def get_edited_slide_content(
|
|||
client = LLMClient()
|
||||
response = await client.generate_structured(
|
||||
model=model,
|
||||
messages=get_messages(prompt, slide.content, language),
|
||||
messages=get_messages(
|
||||
prompt, slide.content, language, tone, verbosity, instructions
|
||||
),
|
||||
response_format=response_schema,
|
||||
strict=False,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,47 +7,79 @@ from services.llm_client import LLMClient
|
|||
from utils.get_dynamic_models import get_presentation_outline_model_with_n_slides
|
||||
from utils.llm_provider import get_model
|
||||
|
||||
system_prompt = """
|
||||
You are an expert presentation creator. Generate structured presentations based on user requirements and format them according to the specified JSON schema with markdown content.
|
||||
|
||||
Try to use available tools for better results.
|
||||
|
||||
- Provide content for each slide in markdown format.
|
||||
- Make sure that flow of the presentation is logical and consistent.
|
||||
- Place greater emphasis on numerical data.
|
||||
- If Additional Information is provided, divide it into slides.
|
||||
- Make sure no images are provided in the content.
|
||||
- Make sure that content follows language guidelines.
|
||||
"""
|
||||
|
||||
|
||||
def get_user_prompt(prompt: str, n_slides: int, language: str, content: str):
|
||||
def get_system_prompt(
|
||||
tone: Optional[str] = None,
|
||||
verbosity: Optional[str] = None,
|
||||
instructions: Optional[str] = None,
|
||||
):
|
||||
return f"""
|
||||
**Input:**
|
||||
- Prompt: {prompt}
|
||||
- Output Language: {language}
|
||||
- Number of Slides: {n_slides}
|
||||
- Current Date and Time: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
|
||||
- Additional Information: {content}
|
||||
You are an expert presentation creator. Generate structured presentations based on user requirements and format them according to the specified JSON schema with markdown content.
|
||||
|
||||
Try to use available tools for better results.
|
||||
|
||||
{"# User Instruction:" if instructions else ""}
|
||||
{instructions or ""}
|
||||
|
||||
{"# Tone:" if tone else ""}
|
||||
{tone or ""}
|
||||
|
||||
{"# Verbosity:" if verbosity else ""}
|
||||
{verbosity or ""}
|
||||
|
||||
- Provide content for each slide in markdown format.
|
||||
- Make sure that flow of the presentation is logical and consistent.
|
||||
- Place greater emphasis on numerical data.
|
||||
- If Additional Information is provided, divide it into slides.
|
||||
- Make sure no images are provided in the content.
|
||||
- Make sure that content follows language guidelines.
|
||||
"""
|
||||
|
||||
|
||||
def get_messages(prompt: str, n_slides: int, language: str, content: str):
|
||||
def get_user_prompt(
|
||||
content: str,
|
||||
n_slides: int,
|
||||
language: str,
|
||||
additional_context: Optional[str] = None,
|
||||
):
|
||||
return f"""
|
||||
**Input:**
|
||||
- User provided content: {content}
|
||||
- Output Language: {language}
|
||||
- Number of Slides: {n_slides}
|
||||
- Current Date and Time: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
|
||||
- Additional Information: {additional_context or ""}
|
||||
"""
|
||||
|
||||
|
||||
def get_messages(
|
||||
content: str,
|
||||
n_slides: int,
|
||||
language: str,
|
||||
additional_context: Optional[str] = None,
|
||||
tone: Optional[str] = None,
|
||||
verbosity: Optional[str] = None,
|
||||
instructions: Optional[str] = None,
|
||||
):
|
||||
return [
|
||||
LLMSystemMessage(
|
||||
content=system_prompt,
|
||||
content=get_system_prompt(tone, verbosity, instructions),
|
||||
),
|
||||
LLMUserMessage(
|
||||
content=get_user_prompt(prompt, n_slides, language, content),
|
||||
content=get_user_prompt(content, n_slides, language, additional_context),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
async def generate_ppt_outline(
|
||||
prompt: Optional[str],
|
||||
content: str,
|
||||
n_slides: int,
|
||||
language: Optional[str] = None,
|
||||
content: Optional[str] = None,
|
||||
additional_context: Optional[str] = None,
|
||||
tone: Optional[str] = None,
|
||||
verbosity: Optional[str] = None,
|
||||
instructions: Optional[str] = None,
|
||||
web_search: bool = False,
|
||||
):
|
||||
model = get_model()
|
||||
response_model = get_presentation_outline_model_with_n_slides(n_slides)
|
||||
|
|
@ -56,9 +88,19 @@ async def generate_ppt_outline(
|
|||
|
||||
async for chunk in client.stream_structured(
|
||||
model,
|
||||
get_messages(prompt, n_slides, language, content),
|
||||
get_messages(
|
||||
content,
|
||||
n_slides,
|
||||
language,
|
||||
additional_context,
|
||||
tone,
|
||||
verbosity,
|
||||
instructions,
|
||||
),
|
||||
response_model.model_json_schema(),
|
||||
strict=True,
|
||||
tools=[SearchWebTool] if client.enable_web_grounding() else None,
|
||||
tools=(
|
||||
[SearchWebTool] if (client.enable_web_grounding() and web_search) else None
|
||||
),
|
||||
):
|
||||
yield chunk
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
from typing import Optional
|
||||
from models.llm_message import LLMSystemMessage, LLMUserMessage
|
||||
from models.presentation_layout import PresentationLayoutModel
|
||||
from models.presentation_outline_model import PresentationOutlineModel
|
||||
|
|
@ -8,7 +9,10 @@ from models.presentation_structure_model import PresentationStructureModel
|
|||
|
||||
|
||||
def get_messages(
|
||||
presentation_layout: PresentationLayoutModel, n_slides: int, data: str
|
||||
presentation_layout: PresentationLayoutModel,
|
||||
n_slides: int,
|
||||
data: str,
|
||||
instructions: Optional[str] = None,
|
||||
):
|
||||
return [
|
||||
LLMSystemMessage(
|
||||
|
|
@ -43,6 +47,9 @@ def get_messages(
|
|||
|
||||
**Trust your design instincts. Focus on creating the most effective presentation for the content and audience.**
|
||||
|
||||
{"# User Instruction:" if instructions else ""}
|
||||
{instructions or ""}
|
||||
|
||||
Select layout index for each of the {n_slides} slides based on what will best serve the presentation's goals.
|
||||
""",
|
||||
),
|
||||
|
|
@ -57,6 +64,7 @@ def get_messages(
|
|||
async def generate_presentation_structure(
|
||||
presentation_outline: PresentationOutlineModel,
|
||||
presentation_layout: PresentationLayoutModel,
|
||||
instructions: Optional[str] = None,
|
||||
) -> PresentationStructureModel:
|
||||
|
||||
client = LLMClient()
|
||||
|
|
@ -71,6 +79,7 @@ async def generate_presentation_structure(
|
|||
presentation_layout,
|
||||
len(presentation_outline.slides),
|
||||
presentation_outline.to_string(),
|
||||
instructions,
|
||||
),
|
||||
response_format=response_model.model_json_schema(),
|
||||
strict=True,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from models.llm_message import LLMSystemMessage, LLMUserMessage
|
||||
from models.presentation_layout import SlideLayoutModel
|
||||
from models.presentation_outline_model import SlideOutlineModel
|
||||
|
|
@ -6,32 +7,48 @@ from services.llm_client import LLMClient
|
|||
from utils.llm_provider import get_model
|
||||
from utils.schema_utils import add_field_in_schema, remove_fields_from_schema
|
||||
|
||||
system_prompt = """
|
||||
Generate structured slide based on provided outline, follow mentioned steps and notes and provide structured output.
|
||||
|
||||
# Steps
|
||||
1. Analyze the outline.
|
||||
2. Generate structured slide based on the outline.
|
||||
3. Generate speaker note that is simple, clear, concise and to the point.
|
||||
def get_system_prompt(
|
||||
tone: Optional[str] = None,
|
||||
verbosity: Optional[str] = None,
|
||||
instructions: Optional[str] = None,
|
||||
):
|
||||
return f"""
|
||||
Generate structured slide based on provided outline, follow mentioned steps and notes and provide structured output.
|
||||
|
||||
# Notes
|
||||
- Slide body should not use words like "This slide", "This presentation".
|
||||
- Rephrase the slide body to make it flow naturally.
|
||||
- Only use markdown to highlight important points.
|
||||
- Make sure to follow language guidelines.
|
||||
- Speaker note should be normal text, not markdown.
|
||||
- Strictly follow the max and min character limit for every property in the slide.
|
||||
- Never ever go over the max character limit. Limit your narration to make sure you never go over the max character limit.
|
||||
- Number of items should not be more than max number of items specified in slide schema. If you have to put multiple points then merge them to obey max numebr of items.
|
||||
{"# User Instructions:" if instructions else ""}
|
||||
{instructions or ""}
|
||||
|
||||
# Image and Icon Output Format
|
||||
image: {
|
||||
__image_prompt__: string,
|
||||
}
|
||||
icon: {
|
||||
__icon_query__: string,
|
||||
}
|
||||
"""
|
||||
{"# Tone:" if tone else ""}
|
||||
{tone or ""}
|
||||
|
||||
{"# Verbosity:" if verbosity else ""}
|
||||
{verbosity or ""}
|
||||
|
||||
# Steps
|
||||
1. Analyze the outline.
|
||||
2. Generate structured slide based on the outline.
|
||||
3. Generate speaker note that is simple, clear, concise and to the point.
|
||||
|
||||
# Notes
|
||||
- Slide body should not use words like "This slide", "This presentation".
|
||||
- Rephrase the slide body to make it flow naturally.
|
||||
- Only use markdown to highlight important points.
|
||||
- Make sure to follow language guidelines.
|
||||
- Speaker note should be normal text, not markdown.
|
||||
- Strictly follow the max and min character limit for every property in the slide.
|
||||
- Never ever go over the max character limit. Limit your narration to make sure you never go over the max character limit.
|
||||
- Number of items should not be more than max number of items specified in slide schema. If you have to put multiple points then merge them to obey max numebr of items.
|
||||
|
||||
# Image and Icon Output Format
|
||||
image: {{
|
||||
__image_prompt__: string,
|
||||
}}
|
||||
icon: {{
|
||||
__icon_query__: string,
|
||||
}}
|
||||
|
||||
"""
|
||||
|
||||
|
||||
def get_user_prompt(outline: str, language: str):
|
||||
|
|
@ -50,11 +67,17 @@ def get_user_prompt(outline: str, language: str):
|
|||
"""
|
||||
|
||||
|
||||
def get_messages(outline: str, language: str):
|
||||
def get_messages(
|
||||
outline: str,
|
||||
language: str,
|
||||
tone: Optional[str] = None,
|
||||
verbosity: Optional[str] = None,
|
||||
instructions: Optional[str] = None,
|
||||
):
|
||||
|
||||
return [
|
||||
LLMSystemMessage(
|
||||
content=system_prompt,
|
||||
content=get_system_prompt(tone, verbosity, instructions),
|
||||
),
|
||||
LLMUserMessage(
|
||||
content=get_user_prompt(outline, language),
|
||||
|
|
@ -63,7 +86,12 @@ def get_messages(outline: str, language: str):
|
|||
|
||||
|
||||
async def get_slide_content_from_type_and_outline(
|
||||
slide_layout: SlideLayoutModel, outline: SlideOutlineModel, language: str
|
||||
slide_layout: SlideLayoutModel,
|
||||
outline: SlideOutlineModel,
|
||||
language: str,
|
||||
tone: Optional[str] = None,
|
||||
verbosity: Optional[str] = None,
|
||||
instructions: Optional[str] = None,
|
||||
):
|
||||
client = LLMClient()
|
||||
model = get_model()
|
||||
|
|
@ -89,6 +117,9 @@ async def get_slide_content_from_type_and_outline(
|
|||
messages=get_messages(
|
||||
outline.content,
|
||||
language,
|
||||
tone,
|
||||
verbosity,
|
||||
instructions,
|
||||
),
|
||||
response_format=response_schema,
|
||||
strict=False,
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
import uuid
|
||||
|
||||
|
||||
def get_random_uuid():
|
||||
return str(uuid.uuid4())
|
||||
|
|
@ -50,12 +50,12 @@ export class PresentationGenerationApi {
|
|||
}
|
||||
|
||||
static async createPresentation({
|
||||
prompt,
|
||||
content,
|
||||
n_slides,
|
||||
file_paths,
|
||||
language,
|
||||
}: {
|
||||
prompt: string;
|
||||
content: string;
|
||||
n_slides: number | null;
|
||||
file_paths?: string[];
|
||||
language: string | null;
|
||||
|
|
@ -67,7 +67,7 @@ export class PresentationGenerationApi {
|
|||
method: "POST",
|
||||
headers: getHeader(),
|
||||
body: JSON.stringify({
|
||||
prompt,
|
||||
content,
|
||||
n_slides,
|
||||
file_paths,
|
||||
language,
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ const GroupLayoutPreview = () => {
|
|||
const payload = {
|
||||
layouts: [
|
||||
{
|
||||
presentation_id: presentationId,
|
||||
presentation: presentationId,
|
||||
layout_id: currentLayoutId,
|
||||
layout_name: currentLayoutName,
|
||||
layout_code: currentCode,
|
||||
|
|
|
|||
|
|
@ -152,7 +152,7 @@ const UploadPage = () => {
|
|||
// Use the first available layout group for direct generation
|
||||
trackEvent(MixpanelEvent.Upload_Create_Presentation_API_Call);
|
||||
const createResponse = await PresentationGenerationApi.createPresentation({
|
||||
prompt: config?.prompt ?? "",
|
||||
content: config?.prompt ?? "",
|
||||
n_slides: config?.slides ? parseInt(config.slides) : null,
|
||||
file_paths: [],
|
||||
language: config?.language ?? "",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue