presenton/electron/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py
sudipnext cc8f0bb862 feat: Enhance presentation generation features and improve asset handling
- Added MAX_NUMBER_OF_SLIDES constant to limit slide generation.
- Updated GeneratePresentationRequest model to allow optional slide count and language detection.
- Implemented language resolution in edit_slide and generate_presentation_outlines utilities.
- Enhanced user prompts to include optional parameters for slide count and table of contents.
- Introduced outline utilities for managing table of contents and slide outlines.
- Improved image and icon processing in slides to utilize outline image URLs.
- Updated frontend components to resolve backend asset URLs for icons and images.
- Refactored upload components to handle optional slide count and language selection.
- Enhanced API utility functions to resolve backend asset URLs correctly.
2026-04-06 18:28:43 +05:45

204 lines
6.8 KiB
Python

from datetime import datetime
from typing import Optional
from models.llm_message import LLMSystemMessage, LLMUserMessage
from models.presentation_outline_model import PresentationOutlineModel
from models.llm_tools import SearchWebTool
from services.llm_client import LLMClient
from utils.get_dynamic_models import get_presentation_outline_model_with_n_slides
from utils.llm_client_error_handler import handle_llm_client_exceptions
from utils.llm_provider import get_model
"""
Previously there was a dedicated search-query generation prompt constant.
Removed in favor of embedding short, actionable web-search steps into the
system prompt when web grounding is requested.
"""
def get_system_prompt(
tone: Optional[str] = None,
verbosity: Optional[str] = None,
instructions: Optional[str] = None,
include_title_slide: bool = True,
include_table_of_contents: bool = False,
):
verbosity_instruction = (
"Slide content should be abound 20 words but detailed enough to generate a good slide."
if verbosity == "concise"
else (
"Slide content should be abound 60 words but detailed enough to generate a good slide."
if verbosity == "text-heavy"
else "Slide content should be abound 40 words but detailed enough to generate a good slide."
)
)
title_slide_instruction = (
"Include presenter name in first slide."
if include_title_slide
else "Do not include presenter name in any slides."
)
toc_instruction = (
"Include a table of contents slide in the outline sequence."
if include_table_of_contents
else ""
)
toc_block = f"{toc_instruction}\n" if toc_instruction else ""
slide_outline_structure = (
"Each slide content:\n"
" - Must have a ## title.\n"
" - Must have content either in multiple bullet points or table or both.\n"
" - Must be in Markdown format.\n"
" - Don't use **bold** and __italic__ text."
" - First slide title must be the same as the presentation title."
)
system = (
"Generate presentation title and content for slides.\n"
"Generate flow based on user **content** and use **context** just for reference.\n"
"Presentation title should be plain text, not markdown. It should be a concise title for the presentation.\n"
"Each slide content should contain the content for that slide.\n"
f"{verbosity_instruction}\n"
"Minimize repetitive content and make sure to use different words and phrases for different slides.\n"
"Include numerical data or tables if required or asked by the user.\n"
"If 'auto-detect' is used, figure it out from the content/context.\n"
f"{title_slide_instruction}\n"
f"{toc_block}"
f"{slide_outline_structure}\n"
"Slide content must not contain any presentation branding/styling information.\n"
"Title slide must only contain title, presenter name, date and overview.\n"
"Only include URLs if they appear in the provided content/context.\n"
"Make sure data used is strictly from the provided content/context.\n"
"Make sure data is consistent across all slides."
)
return system
def _resolve_prompt_language(language: Optional[str]) -> str:
if language is None:
return "auto-detect"
s = str(language).strip()
if not s:
return "auto-detect"
if s.lower() in {"auto", "auto-detect"}:
return "auto-detect"
return s
def _resolve_prompt_n_slides(n_slides: Optional[int]) -> str:
if n_slides is None:
return "auto-detect"
return str(n_slides)
def get_user_prompt(
content: str,
n_slides: Optional[int],
language: Optional[str],
additional_context: Optional[str] = None,
tone: Optional[str] = None,
instructions: Optional[str] = None,
include_title_slide: bool = True,
include_table_of_contents: bool = False,
):
display_language = _resolve_prompt_language(language)
display_slides = _resolve_prompt_n_slides(n_slides)
toc_text = f"Include Table Of Contents: {str(include_table_of_contents).lower()}\n"
return (
f"Content: {content or ''}\n"
f"Number of Slides: {display_slides}\n"
f"Language: {display_language}\n"
f"Tone: {tone or ''}\n"
f"Today's Date: {datetime.now().strftime('%Y-%m-%d')}\n"
f"Include Title Slide: {include_title_slide}\n"
f"{toc_text if include_table_of_contents else ''}"
f"Instructions: {instructions or ''}\n"
f"Context: {additional_context or ''}"
)
def get_messages(
content: str,
n_slides: Optional[int],
language: Optional[str],
additional_context: Optional[str] = None,
tone: Optional[str] = None,
verbosity: Optional[str] = None,
instructions: Optional[str] = None,
include_title_slide: bool = True,
include_table_of_contents: bool = False,
):
return [
LLMSystemMessage(
content=get_system_prompt(
tone,
verbosity,
instructions,
include_title_slide,
include_table_of_contents,
),
),
LLMUserMessage(
content=get_user_prompt(
content,
n_slides,
language,
additional_context,
tone,
instructions,
include_title_slide,
include_table_of_contents,
),
),
]
async def generate_ppt_outline(
content: str,
n_slides: Optional[int],
language: Optional[str] = None,
additional_context: Optional[str] = None,
tone: Optional[str] = None,
verbosity: Optional[str] = None,
instructions: Optional[str] = None,
include_title_slide: bool = True,
web_search: bool = False,
include_table_of_contents: bool = False,
):
model = get_model()
response_model = (
get_presentation_outline_model_with_n_slides(n_slides)
if n_slides is not None
else PresentationOutlineModel
)
client = LLMClient()
try:
async for chunk in client.stream_structured(
model,
get_messages(
content,
n_slides,
language,
additional_context,
tone,
verbosity,
instructions,
include_title_slide,
include_table_of_contents,
),
response_model.model_json_schema(),
strict=True,
tools=(
[SearchWebTool]
if (client.enable_web_grounding() and web_search)
else None
),
):
yield chunk
except Exception as e:
yield handle_llm_client_exceptions(e)