Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 78 additions & 2 deletions app/api/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,92 @@
from app.services.logger import setup_logger
from app.api.error_utilities import InputValidationError, ErrorResponse
from app.tools.utils.tool_utilities import load_tool_metadata, execute_tool, finalize_inputs
from fastapi.responses import FileResponse
from starlette.background import BackgroundTask
from app.tools.presentation_generator.tools.slides_generator import SlidesGenerator
import uuid
from fastapi import FastAPI

logger = setup_logger(__name__)
router = APIRouter()
app = FastAPI()

# Initialize presentation contexts in app state if not exists
if not hasattr(app.state, "presentation_contexts"):
app.state.presentation_contexts = {}

@router.get("/")
def read_root():
return {"Hello": "World"}

# Handles two-step presentation generation:
# 1. Generate outline with initial inputs
# 2. Generate slides using stored outline and inputs
@router.post("/generate-outline", response_model=Union[ToolResponse, ErrorResponse])
async def generate_outline(data: ToolRequest, _ = Depends(key_check)):
try:
# Execute outline generation and store context for slides
request_data = data.tool_data
requested_tool = load_tool_metadata(request_data.tool_id)
request_inputs_dict = finalize_inputs(request_data.inputs, requested_tool['inputs'])
result = execute_tool(request_data.tool_id, request_inputs_dict)

# Store in app state, to use as context for slides generation
presentation_id = str(uuid.uuid4())
app.state.presentation_contexts[presentation_id] = {
"outline": result,
"inputs": request_inputs_dict
}

return ToolResponse(data={
"outline": result,
"presentation_id": presentation_id
})

except InputValidationError as e:
logger.error(f"InputValidationError: {e}")
return JSONResponse(
status_code=400,
content=jsonable_encoder(ErrorResponse(status=400, message=e.message))
)

except HTTPException as e:
logger.error(f"HTTPException: {e}")
return JSONResponse(
status_code=e.status_code,
content=jsonable_encoder(ErrorResponse(status=e.status_code, message=e.detail))
)

@router.post("/generate-slides/{presentation_id}", response_model=Union[ToolResponse, ErrorResponse])
async def generate_slides(presentation_id: str, _ = Depends(key_check)):
try:
# Retrieve stored context and generate slides
context = app.state.presentation_contexts.get(presentation_id)
if not context:
raise HTTPException(
status_code=404,
detail="Presentation context not found"
)

slides = SlidesGenerator(
outline=context["outline"],
inputs=context["inputs"]
).compile()

return ToolResponse(data=slides)

except InputValidationError as e:
logger.error(f"InputValidationError: {e}")
return JSONResponse(
status_code=400,
content=jsonable_encoder(ErrorResponse(status=400, message=e.message))
)

except HTTPException as e:
logger.error(f"HTTPException: {e}")
return JSONResponse(
status_code=e.status_code,
content=jsonable_encoder(ErrorResponse(status=e.status_code, message=e.detail))
)

@router.post("/submit-tool", response_model=Union[ToolResponse, ErrorResponse])
async def submit_tool( data: ToolRequest, _ = Depends(key_check)):
try:
Expand Down
50 changes: 0 additions & 50 deletions app/main.py

This file was deleted.

59 changes: 47 additions & 12 deletions app/services/schemas.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pydantic import BaseModel, Field
from typing import Optional, List, Any, Literal, Union
from typing import Optional, List, Any, Literal, Union, Dict
from enum import Enum
from app.services.assistant_registry import AssistantInputs
from app.services.tool_registry import BaseTool
Expand Down Expand Up @@ -92,6 +92,9 @@ class SyllabusGeneratorArgsModel(BaseModel):
file_url: str
file_type: str
lang: Optional[str] = "en"
unit_time: Optional[str] = "Week" # New
unit_time_value: Optional[int] = 10 # New
start_date: Optional[str] = "2025-03-01" # New

class AIResistantArgs(BaseModel):
assignment: str = Field(..., max_length=255, description="The given assignment")
Expand All @@ -111,16 +114,15 @@ class ConnectWithThemArgs(BaseModel):
lang: str = Field(..., description="The language in which the subject is being taught.")

class PresentationGeneratorInput(BaseModel):
grade_level: str
n_slides: int
topic: str
objectives: str
additional_comments: str
objectives_file_url: str
objectives_file_type: str
additional_comments_file_url: str
additional_comments_file_type: str
lang: Optional[str] = "en"
instructionalLevel: str # Renamed from grade_level
slideCount: int # Renamed from n_slides
text: str # Renamed from topic
objectives: str = "" # Optional, kept as-is
additional_comments: str = "" # Optional, kept as-is
objectives_file_url: str = ""
objectives_file_type: str = ""
additional_comments_file_url: str = ""
additional_comments_file_type: str = ""

class RubricGeneratorArgs(BaseModel):
grade_level: Literal["pre-k", "kindergarten", "elementary", "middle", "high", "university", "professional"]
Expand Down Expand Up @@ -153,4 +155,37 @@ class WritingFeedbackGeneratorArgs(BaseModel):
criteria_file_type: str
writing_to_review_file_url: str
writing_to_review_file_type: str
lang: Optional[str] = "en"
lang: Optional[str] = "en"

# New output schemas for Presentation Generator
class SlideContent(BaseModel):
topic: str = Field(description="The topic or title of the slide")
content: Dict[str, Any] = Field( # Dict is now defined
description="Structured content of the slide",
example={
"main_points": ["point 1", "point 2"],
"examples": ["example 1", "example 2"],
"details": "Additional explanation",
"visual_notes": "Suggested visuals"
}
)
speaker_notes: str = Field(
description="Detailed notes for the presenter to assist in delivering the slide content",
example="Begin by introducing the topic, elaborate on the main points with examples, and transition smoothly to the next slide."
)
class Config:
json_schema_extra = {
"example": {
"topic": "Introduction to World War II",
"content": {
"main_points": ["Causes of the war", "Key events"],
"examples": ["Treaty of Versailles", "Pearl Harbor"],
"details": "The war began in 1939...",
"visual_notes": "Timeline of events"
},
"speaker_notes": "Start with a brief hook about global tensions, explain the causes concisely, and preview the key events we’ll cover next."
}
}

class PresentationOutput(BaseModel):
slides: List[SlideContent] = Field(description="List of slides with their content and speaker notes")
99 changes: 51 additions & 48 deletions app/tools/presentation_generator/core.py
Original file line number Diff line number Diff line change
@@ -1,68 +1,71 @@
from app.utils.document_loaders import get_docs
from app.tools.presentation_generator.tools import PresentationGenerator
from app.services.schemas import PresentationGeneratorInput
from app.tools.outline_generator import executor as outline_executor
from app.tools.slides_generator import executor as slides_executor
from app.services.schemas import PresentationGeneratorArgs
from app.services.logger import setup_logger
from app.api.error_utilities import LoaderError, ToolExecutorError

logger = setup_logger()

def executor(grade_level: str,
n_slides: int,
topic: str,
objectives: str,
additional_comments: str,
objectives_file_url: str,
objectives_file_type: str,
additional_comments_file_url: str,
additional_comments_file_type: str,
lang: str,
verbose=False):
def executor(instructionalLevel: str,
slideCount: int,
text: str,
objectives: str = "",
additional_comments: str = "",
objectives_file_url: str = "",
objectives_file_type: str = "",
additional_comments_file_url: str = "",
additional_comments_file_type: str = "",
verbose: bool = False):
"""
Execute the presentation generation process (outline only for this context).

try:
if(objectives_file_type):
logger.info(f"Generating docs. from {objectives_file_type}")
if(additional_comments_file_type):
logger.info(f"Generating docs. from {additional_comments_file_type}")

docs = None
Args:
instructionalLevel (str): The educational level (e.g., Elementary, High School, University).
slideCount (int): Number of slides to generate (5-20 per PRD).
text (str): The topic or context for the presentation.
objectives (str, optional): Learning objectives.
additional_comments (str, optional): Extra notes.
objectives_file_url (str, optional): URL to a file with objectives.
objectives_file_type (str, optional): Type of the objectives file (e.g., pdf, gdoc).
additional_comments_file_url (str, optional): URL to a file with comments.
additional_comments_file_type (str, optional): Type of the comments file.
verbose (bool): Enable detailed logging for debugging.

def fetch_docs(file_url, file_type):
return get_docs(file_url, file_type, True) if file_url and file_type else None
Returns:
dict: The generated outline in JSON format.

objectives_docs = fetch_docs(objectives_file_url, objectives_file_type)
additional_comments_docs = fetch_docs(additional_comments_file_url, additional_comments_file_type)

docs = (
objectives_docs + additional_comments_docs
if objectives_docs and additional_comments_docs
else objectives_docs or additional_comments_docs
)
Raises:
ToolExecutorError: If generation fails.
"""
try:
# Optional document loading (for context, though not used in outline_generator yet)
docs = None
if objectives_file_url and objectives_file_type:
logger.info(f"Generating docs from {objectives_file_type}")
docs = get_docs(objectives_file_url, objectives_file_type, verbose)
if additional_comments_file_url and additional_comments_file_type:
logger.info(f"Generating docs from {additional_comments_file_type}")
additional_docs = get_docs(additional_comments_file_url, additional_comments_file_type, verbose)
docs = docs + additional_docs if docs and additional_docs else additional_docs or docs

presentation_generator_args = PresentationGeneratorInput(
grade_level=grade_level,
n_slides=n_slides,
topic=topic,
# Generate outline (this core.py is only for outline in your friend's setup)
output = outline_executor(
instructionalLevel=instructionalLevel,
slideCount=slideCount,
text=text,
objectives=objectives,
additional_comments=additional_comments,
objectives_file_url=objectives_file_url,
objectives_file_type=objectives_file_type,
additional_comments_file_url=additional_comments_file_url,
additional_comments_file_type=additional_comments_file_type,
lang=lang
verbose=verbose
)

output = PresentationGenerator(args=presentation_generator_args, verbose=verbose).generate_presentation(docs)

logger.info(f"Presentation generated successfully")
logger.info("Outline generated successfully")
return output

except LoaderError as e:
error_message = e
error_message = str(e)
logger.error(f"Error in Presentation Generator Pipeline -> {error_message}")
raise ToolExecutorError(error_message)

except Exception as e:
error_message = f"Error in executor: {e}"
logger.error(error_message)
raise ValueError(error_message)

return output
raise ToolExecutorError(error_message)
Loading