From ca72a652eed4a9260a3039bb0a926fecbdbff7bd Mon Sep 17 00:00:00 2001 From: sauravniraula Date: Sun, 3 Aug 2025 20:14:53 +0545 Subject: [PATCH] feat(fastapi): changes presentation generation flow for documents to chunk it and extract as outlines --- .../fastapi/api/v1/ppt/endpoints/outlines.py | 66 +++++++++----- .../api/v1/ppt/endpoints/presentation.py | 82 ++++++++---------- servers/fastapi/chroma/chroma.sqlite3 | Bin 4329472 -> 4329472 bytes servers/fastapi/models/document_chunk.py | 11 +++ .../models/presentation_outline_model.py | 28 ++---- .../models/presentation_with_slides.py | 6 +- servers/fastapi/models/sql/presentation.py | 19 ++-- servers/fastapi/requirements.txt | 23 ----- .../fastapi/services/score_based_chunker.py | 18 ++-- servers/fastapi/utils/get_dynamic_models.py | 26 ++---- .../llm_calls/generate_document_summary.py | 44 ---------- .../generate_presentation_outlines.py | 40 ++------- .../utils/llm_calls/generate_slide_content.py | 23 ++--- .../components/MarkdownEditor.tsx | 3 +- .../outline/components/OutlineContent.tsx | 8 +- .../outline/components/OutlineItem.tsx | 26 ++---- .../outline/components/OutlinePage.tsx | 1 - .../outline/hooks/useOutlineStreaming.ts | 4 +- .../presentation/hooks/usePresentationData.ts | 7 +- .../hooks/usePresentationStreaming.ts | 3 + .../store/slices/presentationGeneration.ts | 11 +-- 21 files changed, 163 insertions(+), 286 deletions(-) create mode 100644 servers/fastapi/models/document_chunk.py delete mode 100644 servers/fastapi/utils/llm_calls/generate_document_summary.py diff --git a/servers/fastapi/api/v1/ppt/endpoints/outlines.py b/servers/fastapi/api/v1/ppt/endpoints/outlines.py index b5ff9f5a..8970e11d 100644 --- a/servers/fastapi/api/v1/ppt/endpoints/outlines.py +++ b/servers/fastapi/api/v1/ppt/endpoints/outlines.py @@ -7,7 +7,10 @@ from sqlalchemy.ext.asyncio import AsyncSession from models.presentation_outline_model import PresentationOutlineModel from models.sql.presentation import PresentationModel from models.sse_response import SSECompleteResponse, SSEResponse, SSEStatusResponse +from services import TEMP_FILE_SERVICE from services.database import get_async_session +from services.documents_loader import DocumentsLoader +from services.score_based_chunker import ScoreBasedChunker from utils.llm_calls.generate_presentation_outlines import generate_ppt_outline OUTLINES_ROUTER = APIRouter(prefix="/outlines", tags=["Outlines"]) @@ -22,38 +25,59 @@ async def stream_outlines( if not presentation: raise HTTPException(status_code=404, detail="Presentation not found") + temp_dir = TEMP_FILE_SERVICE.create_temp_dir() + async def inner(): yield SSEStatusResponse( status="Generating presentation outlines..." ).to_string() - presentation_content_text = "" - async for chunk in generate_ppt_outline( - presentation.prompt, - presentation.n_slides, - presentation.language, - presentation.summary, - ): - # Give control to the event loop - await asyncio.sleep(0) + presentation_outlines = None + additional_context = "" + if presentation.file_paths: + documents_loader = DocumentsLoader(file_paths=presentation.file_paths) + await documents_loader.load_documents(temp_dir) + documents = documents_loader.documents + if documents: + additional_context = documents[0] + chunker = ScoreBasedChunker() + try: + chunks = await chunker.get_n_chunks( + documents[0], presentation.n_slides + ) + presentation_outlines = PresentationOutlineModel( + slides=[chunk.to_slide_outline() for chunk in chunks] + ) + except Exception as e: + print(e) - yield SSEResponse( - event="response", - data=json.dumps({"type": "chunk", "chunk": chunk}), - ).to_string() - presentation_content_text += chunk + if not presentation_outlines: + presentation_outlines_text = "" + async for chunk in generate_ppt_outline( + presentation.prompt, + presentation.n_slides, + presentation.language, + additional_context, + ): + # Give control to the event loop + await asyncio.sleep(0) - presentation_content_json = json.loads(presentation_content_text) + yield SSEResponse( + event="response", + data=json.dumps({"type": "chunk", "chunk": chunk}), + ).to_string() + presentation_outlines_text += chunk - presentation_content = PresentationOutlineModel(**presentation_content_json) - presentation_content.slides = presentation_content.slides[ + presentation_outlines_json = json.loads(presentation_outlines_text) + presentation_outlines = PresentationOutlineModel( + **presentation_outlines_json + ) + + presentation_outlines.slides = presentation_outlines.slides[ : presentation.n_slides ] - presentation.title = presentation_content.title - presentation.outlines = [ - each.model_dump() for each in presentation_content.slides - ] + presentation.outlines = presentation_outlines.model_dump() sql_session.add(presentation) await sql_session.commit() diff --git a/servers/fastapi/api/v1/ppt/endpoints/presentation.py b/servers/fastapi/api/v1/ppt/endpoints/presentation.py index d06eecb4..215f4b4a 100644 --- a/servers/fastapi/api/v1/ppt/endpoints/presentation.py +++ b/servers/fastapi/api/v1/ppt/endpoints/presentation.py @@ -2,7 +2,6 @@ import asyncio import json import os import random -import importlib from typing import Annotated, List, Literal, Optional from fastapi import APIRouter, Body, Depends, File, HTTPException, UploadFile from fastapi.responses import StreamingResponse @@ -12,14 +11,12 @@ 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, - SlideOutlineModel, -) +from models.presentation_outline_model import PresentationOutlineModel 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 services.score_based_chunker import ScoreBasedChunker from utils.get_layout_by_name import get_layout_by_name from services.icon_finder_service import IconFinderService from services.image_generation_service import ImageGenerationService @@ -34,7 +31,6 @@ from services.documents_loader import DocumentsLoader from models.sql.presentation import PresentationModel from services.pptx_presentation_creator import PptxPresentationCreator from utils.asset_directory_utils import get_exports_directory, get_images_directory -from utils.llm_calls.generate_document_summary import generate_document_summary from utils.llm_calls.generate_presentation_structure import ( generate_presentation_structure, ) @@ -113,20 +109,12 @@ async def create_presentation( ): presentation_id = get_random_uuid() - summary = None - if file_paths: - temp_dir = TEMP_FILE_SERVICE.create_temp_dir(presentation_id) - documents_loader = DocumentsLoader(file_paths=file_paths) - await documents_loader.load_documents(temp_dir) - - summary = await generate_document_summary(documents_loader.documents) - presentation = PresentationModel( id=presentation_id, prompt=prompt, n_slides=n_slides, language=language, - summary=summary, + file_paths=file_paths, ) sql_session.add(presentation) @@ -138,7 +126,7 @@ async def create_presentation( @PRESENTATION_ROUTER.post("/prepare", response_model=PresentationModel) async def prepare_presentation( presentation_id: Annotated[str, Body()], - outlines: Annotated[List[SlideOutlineModel], Body()], + outlines: Annotated[List[str], Body()], layout: Annotated[PresentationLayoutModel, Body()], title: Annotated[Optional[str], Body()] = None, sql_session: AsyncSession = Depends(get_async_session), @@ -173,7 +161,7 @@ async def prepare_presentation( presentation_structure.slides[index] = random_slide_index sql_session.add(presentation) - presentation.outlines = [each.model_dump() for each in outlines] + presentation.outlines = PresentationOutlineModel(slides=outlines).model_dump() presentation.title = title or presentation.title presentation.set_layout(layout) presentation.set_structure(presentation_structure) @@ -328,37 +316,48 @@ async def generate_presentation_api( presentation_id = get_random_uuid() + temp_dir = TEMP_FILE_SERVICE.create_temp_dir() + # 1. Save uploaded files file_paths = [] if files: - temp_dir = TEMP_FILE_SERVICE.create_temp_dir() 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()) file_paths.append(file_path) - # 2. Create Presentation Summary (if documents are provided) - summary = None + # 3. Generate Outlines + presentation_outlines = None + additional_context = "" if file_paths: - temp_dir = TEMP_FILE_SERVICE.create_temp_dir(presentation_id) documents_loader = DocumentsLoader(file_paths=file_paths) await documents_loader.load_documents(temp_dir) - summary = await generate_document_summary(documents_loader.documents) + documents = documents_loader.documents + if documents: + additional_context = documents[0] + chunker = ScoreBasedChunker() + try: + chunks = await chunker.get_n_chunks(documents[0], n_slides) + presentation_outlines = PresentationOutlineModel( + slides=[chunk.to_slide_outline() for chunk in chunks] + ) + except Exception as e: + print(e) - # 3. Generate Outlines - presentation_content_text = "" - async for chunk in generate_ppt_outline( - prompt, - n_slides, - language, - summary, - ): - presentation_content_text += chunk + if not presentation_outlines: + presentation_outlines_text = "" + async for chunk in generate_ppt_outline( + prompt, + n_slides, + language, + additional_context, + ): + presentation_outlines_text += chunk - presentation_content_json = json.loads(presentation_content_text) - presentation_content = PresentationOutlineModel(**presentation_content_json) - outlines = presentation_content.slides[:n_slides] + presentation_outlines_json = json.loads(presentation_outlines_text) + presentation_outlines = PresentationOutlineModel(**presentation_outlines_json) + outlines = presentation_outlines.slides[:n_slides] total_outlines = len(outlines) print("-" * 40) @@ -374,11 +373,8 @@ async def generate_presentation_api( else: presentation_structure: PresentationStructureModel = ( await generate_presentation_structure( - presentation_outline=PresentationOutlineModel( - title=presentation_content.title, - slides=outlines, - ), - presentation_layout=layout_model, + presentation_outlines, + layout_model, ) ) @@ -397,9 +393,7 @@ async def generate_presentation_api( prompt=prompt, n_slides=n_slides, language=language, - title=presentation_content.title, - summary=summary, - outlines=[each.model_dump() for each in outlines], + outlines=presentation_outlines.model_dump(), layout=layout_model.model_dump(), structure=presentation_structure.model_dump(), ) @@ -445,7 +439,7 @@ async def generate_presentation_api( # 9. Export presentation_and_path = await export_presentation( - presentation_id, presentation_content.title, export_as + presentation_id, presentation.title or get_random_uuid(), export_as ) return PresentationPathAndEditPath( @@ -482,7 +476,7 @@ async def from_template( await sql_session.commit() presentation_and_path = await export_presentation( - new_presentation.id, new_presentation.title, data.export_as + new_presentation.id, new_presentation.title or get_random_uuid(), data.export_as ) return PresentationPathAndEditPath( diff --git a/servers/fastapi/chroma/chroma.sqlite3 b/servers/fastapi/chroma/chroma.sqlite3 index 838ef47853a5ba4d0517990baf7b0af183480a45..d7ddd11b0cc497bab197c752272c05778e2ba3ff 100644 GIT binary patch delta 313 zcmWm9HxdB>06qdb z7e4qAKoB8>5kV9&#F0P}DWs7>7CGc0qktkxD5HWZYN(@uCR%8tgD!gLV}Kz>7-NDd SW|(7vCH~(ls4rbvk3K&E?^Yy@ON(DC_cqIz&qT}`U5U8 zNxQ~3lJ?v^TC%DL-OZ8PzdYZqaun)Gs1o!rK!y<}m|=kxHrU~S6AD~#!vimT@FRdA zLI@**C}M~sfh1B$BZDk*$fJNFN+_d(Dr%^sfhJmLqk}Gb=wpB(Mi^s)DQ1{sf&Y7n K)my0R;pYc3?R+W# diff --git a/servers/fastapi/models/document_chunk.py b/servers/fastapi/models/document_chunk.py new file mode 100644 index 00000000..6861e4fb --- /dev/null +++ b/servers/fastapi/models/document_chunk.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel + + +class DocumentChunk(BaseModel): + heading: str + content: str + heading_index: int + score: float + + def to_slide_outline(self) -> str: + return f"{self.heading}\n{self.content}" diff --git a/servers/fastapi/models/presentation_outline_model.py b/servers/fastapi/models/presentation_outline_model.py index 39383390..ad55ae4b 100644 --- a/servers/fastapi/models/presentation_outline_model.py +++ b/servers/fastapi/models/presentation_outline_model.py @@ -1,31 +1,13 @@ -from typing import List, Optional -from pydantic import BaseModel, Field - - -class SlideOutlineModel(BaseModel): - title: str = Field( - description="Title of the slide in about 3 to 5 words", - ) - body: str = Field( - description="Content of the slide in markdown format", - ) +from typing import List +from pydantic import BaseModel class PresentationOutlineModel(BaseModel): - title: str = Field( - description="Title of the presentation in about 3 to 8 words", - ) - slides: List[SlideOutlineModel] = Field(description="List of slides") + slides: List[str] def to_string(self): - message = f"# Presentation Title: {self.title} \n\n" + message = "" for i, slide in enumerate(self.slides): message += f"## Slide {i+1}:\n" - message += f" - Title: {slide.title} \n" - message += f" - Body: {slide.body} \n" - - # if self.notes: - # message += f"# Notes: \n" - # for note in self.notes: - # message += f" - {note} \n" + message += f" - Content: {slide} \n" return message diff --git a/servers/fastapi/models/presentation_with_slides.py b/servers/fastapi/models/presentation_with_slides.py index d8b4e57e..08bc6fdb 100644 --- a/servers/fastapi/models/presentation_with_slides.py +++ b/servers/fastapi/models/presentation_with_slides.py @@ -4,7 +4,7 @@ from datetime import datetime from pydantic import BaseModel from models.presentation_layout import PresentationLayoutModel -from models.presentation_outline_model import SlideOutlineModel +from models.presentation_outline_model import PresentationOutlineModel from models.presentation_structure_model import PresentationStructureModel from models.sql.presentation import PresentationModel from models.sql.slide import SlideModel @@ -16,9 +16,7 @@ class PresentationWithSlides(BaseModel): n_slides: int language: str title: Optional[str] = None - notes: Optional[List[str]] - outlines: Optional[List[SlideOutlineModel]] - summary: Optional[str] + outlines: Optional[PresentationOutlineModel] created_at: datetime updated_at: datetime layout: Optional[PresentationLayoutModel] diff --git a/servers/fastapi/models/sql/presentation.py b/servers/fastapi/models/sql/presentation.py index a6f26604..0b2fcd1b 100644 --- a/servers/fastapi/models/sql/presentation.py +++ b/servers/fastapi/models/sql/presentation.py @@ -4,10 +4,7 @@ from sqlalchemy import JSON, Column, DateTime from sqlmodel import SQLModel, Field from models.presentation_layout import PresentationLayoutModel -from models.presentation_outline_model import ( - PresentationOutlineModel, - SlideOutlineModel, -) +from models.presentation_outline_model import PresentationOutlineModel from models.presentation_structure_model import PresentationStructureModel from utils.randomizers import get_random_uuid @@ -18,9 +15,8 @@ class PresentationModel(SQLModel, table=True): n_slides: int language: str title: Optional[str] = None - notes: Optional[List[str]] = Field(sa_column=Column(JSON), default=None) - outlines: Optional[List[dict]] = Field(sa_column=Column(JSON), default=None) - summary: 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)) layout: Optional[dict] = Field(sa_column=Column(JSON), default=None) @@ -33,9 +29,8 @@ class PresentationModel(SQLModel, table=True): n_slides=self.n_slides, language=self.language, title=self.title, - notes=self.notes, + file_paths=self.file_paths, outlines=self.outlines, - summary=self.summary, layout=self.layout, structure=self.structure, ) @@ -43,11 +38,7 @@ class PresentationModel(SQLModel, table=True): def get_presentation_outline(self): if not self.outlines: return None - return PresentationOutlineModel( - title=self.title, - slides=[SlideOutlineModel(**each) for each in self.outlines], - # notes=self.notes, - ) + return PresentationOutlineModel(**self.outlines) def get_layout(self): return PresentationLayoutModel(**self.layout) diff --git a/servers/fastapi/requirements.txt b/servers/fastapi/requirements.txt index 425f3800..6facab4e 100644 --- a/servers/fastapi/requirements.txt +++ b/servers/fastapi/requirements.txt @@ -55,37 +55,22 @@ huggingface-hub==0.34.3 humanfriendly==10.0 idna==3.10 imageio==2.37.0 -<<<<<<< HEAD -importlib_metadata==8.7.0 -importlib_resources==6.5.2 -Jinja2==3.1.6 -jiter==0.10.0 -======= importlib-metadata==8.7.0 importlib-resources==6.5.2 jinja2==3.1.6 jiter==0.10.0 joblib==1.5.1 ->>>>>>> main jsonlines==3.1.0 jsonref==1.1.0 jsonschema==4.25.0 jsonschema-specifications==2025.4.1 kubernetes==33.1.0 latex2mathml==3.78.0 -<<<<<<< HEAD -lazy_loader==0.4 -lxml==5.4.0 -markdown-it-py==3.0.0 -marko==2.1.4 -MarkupSafe==3.0.2 -======= lazy-loader==0.4 lxml==5.4.0 markdown-it-py==3.0.0 marko==2.1.4 markupsafe==3.0.1 ->>>>>>> main mdurl==0.1.2 mmh3==5.2.0 mpire==2.10.2 @@ -94,20 +79,12 @@ multidict==6.6.3 multiprocess==0.70.18 networkx==3.5 ninja==1.11.1.4 -<<<<<<< HEAD -numpy==2.2.6 -oauthlib==3.3.1 -onnxruntime==1.22.1 -openai==1.98.0 -opencv-python-headless==4.12.0.88 -======= nltk==3.9.1 numpy==2.3.2 oauthlib==3.3.1 onnxruntime==1.22.1 openai==1.98.0 opencv-python-headless==4.11.0.86 ->>>>>>> main openpyxl==3.1.5 opentelemetry-api==1.36.0 opentelemetry-exporter-otlp-proto-common==1.36.0 diff --git a/servers/fastapi/services/score_based_chunker.py b/servers/fastapi/services/score_based_chunker.py index ab0c974f..45a468f0 100644 --- a/servers/fastapi/services/score_based_chunker.py +++ b/servers/fastapi/services/score_based_chunker.py @@ -2,6 +2,8 @@ import asyncio from typing import List import nltk +from models.document_chunk import DocumentChunk + try: nltk.data.find("tokenizers/punkt") except LookupError: @@ -104,7 +106,7 @@ class ScoreBasedChunker: def get_chunks( self, sentences: List[str], sentences_scores: List[float], top_k: int = 10 - ) -> List[dict]: + ) -> List[DocumentChunk]: if not sentences_scores: sentences_scores = self.score_sentences_for_heading(sentences) @@ -175,16 +177,16 @@ class ScoreBasedChunker: content_sentences = sentences[heading_idx + 1 : content_end] content = " ".join(content_sentences).strip() - chunk = { - "heading": heading, - "content": content, - "heading_index": heading_idx, - "score": sentences_scores[heading_idx], - } + chunk = DocumentChunk( + heading=heading, + content=content, + heading_index=heading_idx, + score=sentences_scores[heading_idx], + ) chunks.append(chunk) return chunks - async def get_n_chunks(self, text: str, n: int) -> List[dict]: + async def get_n_chunks(self, text: str, n: int) -> List[DocumentChunk]: sentences = await asyncio.to_thread(self.extract_sentences, text, n) sentences_scores = await asyncio.to_thread( self.score_sentences_for_heading, sentences diff --git a/servers/fastapi/utils/get_dynamic_models.py b/servers/fastapi/utils/get_dynamic_models.py index 4dfded99..c2012217 100644 --- a/servers/fastapi/utils/get_dynamic_models.py +++ b/servers/fastapi/utils/get_dynamic_models.py @@ -1,29 +1,15 @@ -from typing import List, Optional +from typing import List from pydantic import Field -from models.presentation_outline_model import ( - PresentationOutlineModel, - SlideOutlineModel, -) +from models.presentation_outline_model import PresentationOutlineModel from models.presentation_structure_model import PresentationStructureModel -class SlideOutlineModelWithValidation(SlideOutlineModel): - title: str = Field( - description="Title of the slide in about 3 to 5 words", - min_length=10, - max_length=50, - ) - - def get_presentation_outline_model_with_n_slides(n_slides: int): class PresentationOutlineModelWithNSlides(PresentationOutlineModel): - title: str = Field( - description="Title of the presentation in about 3 to 8 words", - min_length=10, - max_length=50, - ) - slides: List[SlideOutlineModelWithValidation] = Field( - description="List of slides", min_items=n_slides, max_items=n_slides + slides: List[str] = Field( + description="Markdown content for each slide", + min_items=n_slides, + max_items=n_slides, ) return PresentationOutlineModelWithNSlides diff --git a/servers/fastapi/utils/llm_calls/generate_document_summary.py b/servers/fastapi/utils/llm_calls/generate_document_summary.py deleted file mode 100644 index 044d28e8..00000000 --- a/servers/fastapi/utils/llm_calls/generate_document_summary.py +++ /dev/null @@ -1,44 +0,0 @@ -import asyncio -from typing import List - -from models.llm_message import LLMMessage -from services.llm_client import LLMClient -from utils.llm_provider import get_model - - -sysmte_prompt = """ -Generate a blog-style summary of the provided document in **more than 2000 words**. -Maintain as much information as possible. - -### Output Format - -- Provide the summary in a **blog format** with an **engaging introduction** and a **clear structure**. -- Ensure the **logical flow** of the document is preserved. - -### Notes - -- **Retain the main ideas and essential details** from the document. -- **Show line-breaks** clearly. -- If **slides structure is mentioned** in document, structure the summary in the same way. -""" - - -async def generate_document_summary(documents: List[str]): - client = LLMClient() - model = get_model() - - coroutines = [] - for document in documents: - truncated_text = document[:200000] - coroutine = client.generate( - model=model, - messages=[ - LLMMessage(role="system", content=sysmte_prompt), - LLMMessage(role="user", content=truncated_text), - ], - ) - coroutines.append(coroutine) - - completions: List[str] = await asyncio.gather(*coroutines) - combined = "\n\n\n\n".join(completions) - return combined diff --git a/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py b/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py index 54e41d61..f875869d 100644 --- a/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py +++ b/servers/fastapi/utils/llm_calls/generate_presentation_outlines.py @@ -7,42 +7,12 @@ from utils.get_dynamic_models import get_presentation_outline_model_with_n_slide 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. + 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. -## Core Requirements - -### Input Processing -1. **Extract key information** from the user's prompt: - - Main topic/subject matter - - Required number of slides - - Target language for output - - Specific content requirements or focus areas - - Target audience (if specified) - - Presentation style or tone preferences - - -## Content Generation Guidelines - -### Presentation Title -- Create a **concise, descriptive title** that captures the essence of the topic -- Use **plain text format** (no markdown formatting) -- Make it **engaging and professional** -- Ensure it reflects the main theme and target audience - -### Slide Titles -- Generate **clear, specific titles** for each slide -- Use **plain text format** (no markdown, no "Slide 1", "Slide 2" prefixes) -- Make each title **descriptive and informative** -- Ensure titles create a **logical flow** through the presentation -- Keep titles **concise but meaningful** - - -## Special Considerations - -### Slide Count Compliance -- Generate **exactly** the number of slides requested -- Distribute content **evenly** across slides -- Create **balanced information flow** + - Provide content for each slide in markdown format. + - Make sure that flow of the presentation is logical and consistent. + - If Additional Information is provided, divide it into slides. + - Make sure that content follows language guidelines. """ diff --git a/servers/fastapi/utils/llm_calls/generate_slide_content.py b/servers/fastapi/utils/llm_calls/generate_slide_content.py index f75ed979..ecff518a 100644 --- a/servers/fastapi/utils/llm_calls/generate_slide_content.py +++ b/servers/fastapi/utils/llm_calls/generate_slide_content.py @@ -1,29 +1,28 @@ from models.llm_message import LLMMessage from models.presentation_layout import SlideLayoutModel -from models.presentation_outline_model import SlideOutlineModel from services.llm_client import LLMClient from utils.llm_provider import get_model from utils.schema_utils import remove_fields_from_schema system_prompt = """ - Generate structured slide based on provided title and outline, follow mentioned steps and notes and provide structured output. + Generate structured slide based on provided outline, follow mentioned steps and notes and provide structured output. # Steps - 1. Analyze the outline and title. - 2. Generate structured slide based on the outline and title. + 1. Analyze the outline. + 2. Generate structured slide based on the outline. # Notes - Slide body should not use words like "This slide", "This presentation". - Rephrase the slide body to make it flow naturally. - Provide prompt to generate image on "__image_prompt__" property. - Provide query to search icon on "__icon_query__" property. - - Do not use markdown formatting in slide body. + - Only use markdown to highlight important points. - Make sure to follow language guidelines. **Strictly follow the max and min character limit for every property in the slide.** """ -def get_user_prompt(title: str, outline: str, language: str): +def get_user_prompt(outline: str, language: str): return f""" ## Icon Query And Image Prompt Language English @@ -31,15 +30,12 @@ def get_user_prompt(title: str, outline: str, language: str): ## Slide Content Language {language} - ## Slide Title - {title} - ## Slide Outline {outline} """ -def get_messages(title: str, outline: str, language: str): +def get_messages(outline: str, language: str): return [ LLMMessage( @@ -48,13 +44,13 @@ def get_messages(title: str, outline: str, language: str): ), LLMMessage( role="user", - content=get_user_prompt(title, outline, language), + content=get_user_prompt(outline, language), ), ] async def get_slide_content_from_type_and_outline( - slide_layout: SlideLayoutModel, outline: SlideOutlineModel, language: str + slide_layout: SlideLayoutModel, outline: str, language: str ): client = LLMClient() model = get_model() @@ -66,8 +62,7 @@ async def get_slide_content_from_type_and_outline( response = await client.generate_structured( model=model, messages=get_messages( - outline.title, - outline.body, + outline, language, ), response_format=response_schema, diff --git a/servers/nextjs/app/(presentation-generator)/components/MarkdownEditor.tsx b/servers/nextjs/app/(presentation-generator)/components/MarkdownEditor.tsx index 3e645fcd..0d464476 100644 --- a/servers/nextjs/app/(presentation-generator)/components/MarkdownEditor.tsx +++ b/servers/nextjs/app/(presentation-generator)/components/MarkdownEditor.tsx @@ -1,10 +1,9 @@ import { useEditor, EditorContent } from "@tiptap/react" import StarterKit from "@tiptap/starter-kit" import { Markdown } from "tiptap-markdown" -import { useEffect } from "react" + export default function MarkdownEditor({ content, onChange }: { content: string; onChange: (content: string) => void }) { - const editor = useEditor({ extensions: [StarterKit, Markdown], content: content, diff --git a/servers/nextjs/app/(presentation-generator)/outline/components/OutlineContent.tsx b/servers/nextjs/app/(presentation-generator)/outline/components/OutlineContent.tsx index c2356940..0748a6a3 100644 --- a/servers/nextjs/app/(presentation-generator)/outline/components/OutlineContent.tsx +++ b/servers/nextjs/app/(presentation-generator)/outline/components/OutlineContent.tsx @@ -15,11 +15,10 @@ import { } from "@dnd-kit/sortable"; import { OutlineItem } from "./OutlineItem"; import { Button } from "@/components/ui/button"; -import { SlideOutline } from "@/store/slices/presentationGeneration"; import { FileText } from "lucide-react"; interface OutlineContentProps { - outlines: SlideOutline[] | null; + outlines: string[] | null; isLoading: boolean; isStreaming: boolean; onDragEnd: (event: any) => void; @@ -33,6 +32,7 @@ const OutlineContent: React.FC = ({ onDragEnd, onAddSlide }) => { + const sensors = useSensors( useSensor(PointerSensor), useSensor(KeyboardSensor, { @@ -84,12 +84,12 @@ const OutlineContent: React.FC = ({ onDragEnd={onDragEnd} > ({ id: item.title || `slide-${index}` })) || []} + items={outlines?.map((item, index) => ({ id: `slide-${index}` })) || []} strategy={verticalListSortingStrategy} > {outlines?.map((item, index) => ( { - if (isStreaming && slideOutline.body) { + if (isStreaming && slideOutline) { const outlineItem = document.getElementById(`outline-item-${index}`); if (outlineItem) { outlineItem.scrollIntoView({ @@ -38,7 +38,7 @@ export function OutlineItem({ } }, [outlines.length]); - const handleSlideChange = (newOutline: SlideOutline) => { + const handleSlideChange = (newOutline: string) => { if (isStreaming) return; const newData = outlines?.map((each, idx) => { if (idx === index - 1) { @@ -60,7 +60,7 @@ export function OutlineItem({ transform, transition, isDragging, - } = useSortable({ id: slideOutline.title || index }) + } = useSortable({ id: index }) const style = { transform: CSS.Transform.toString(transform), @@ -96,24 +96,16 @@ export function OutlineItem({ {/* Main Title Input - Add onFocus handler */}
- handleSlideChange({ ...slideOutline, title: e.target.value })} - className="text-lg mt-4 sm:text-xl flex-1 font-semibold bg-transparent outline-none" - placeholder="Title goes here" - /> - {/* Editable Markdown Content */} {isStreaming ?