refactor: streamline MCP workflow by integrating file processing into outline generation and removing redundant states
This commit is contained in:
parent
18aee8fc78
commit
75abd8085d
14 changed files with 178 additions and 225 deletions
69
README.md
69
README.md
|
|
@ -203,10 +203,77 @@ For detailed info checkout [API documentation](https://docs.presenton.ai/using-p
|
|||
- [Create Presentations from CSV using AI](https://docs.presenton.ai/tutorial/generate-presentation-from-csv)
|
||||
- [Create Data Reports Using AI](https://docs.presenton.ai/tutorial/create-data-reports-using-ai)
|
||||
|
||||
## 🏗️ MCP Architecture Overview
|
||||
|
||||
Presenton is built on a modular architecture featuring a FastAPI backend and a Next.js frontend. At its core is the **MCP (Model Context Protocol) server**, which orchestrates the entire presentation generation workflow using a robust state machine. This architecture ensures flexibility, reliability, and extensibility.
|
||||
|
||||
### MCP Workflow Highlights
|
||||
|
||||
- **Session Management:** Each presentation runs in its own session for isolation and tracking.
|
||||
- **Outline Generation:** Automatically creates outlines, with or without input files.
|
||||
- **Layout Selection:** Choose from built-in or custom layouts.
|
||||
- **Content & Asset Generation:** Generates slide text, images, and icons using your selected AI models.
|
||||
- **Export Options:** Seamlessly export presentations as PDF or PPTX files.
|
||||
|
||||
All workflow logic and tool APIs are organized in the `app_mcp` package. The orchestrator handles state transitions and error management, making it easy to extend or customize.
|
||||
|
||||
#### Key Files & Directories
|
||||
|
||||
- `.vscode/mcp.json`: VS Code integration and MCP server configuration.
|
||||
- `servers/fastapi/app_mcp/`: Backend workflow logic and tool registration.
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Quick Start: VS Code Integration
|
||||
|
||||
1. **Configure MCP:** Make sure `.vscode/mcp.json` points to your running MCP server (see example below).
|
||||
2. **Start a Presentation:** Use the VS Code command palette or chat to run `start_presentation` with your topic.
|
||||
3. **Advance Workflow:** Use `continue_workflow` to progress through outline, layout, and slide generation steps.
|
||||
4. **Export:** Use `export_presentation` to download your presentation as PDF or PPTX.
|
||||
5. **Check Progress:** Use `get_status` at any time to view your workflow status.
|
||||
|
||||
#### Example `.vscode/mcp.json`
|
||||
```jsonc
|
||||
{
|
||||
"servers": {
|
||||
"my-mcp-server-5f58fb2c": {
|
||||
"url": "http://localhost:5000/mcp/",
|
||||
"type": "http"
|
||||
}
|
||||
},
|
||||
"inputs": []
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🗣️ Using Chat Commands in VS Code
|
||||
|
||||
You can interact with Presenton directly from the VS Code chat window:
|
||||
|
||||
- **Step-by-step Workflow:**
|
||||
Type a prompt like:
|
||||
```plaintext
|
||||
I want to create a presentation on "Artificial Intelligence in Healthcare". Can you please show me the step by step and verify things to me so that I can be sure that the presentation is good?
|
||||
```
|
||||
|
||||
- **Direct Commands:**
|
||||
For a faster workflow, use direct commands such as:
|
||||
```plaintext
|
||||
Start a presentation on "Artificial Intelligence in Healthcare" with general layout and 10 slides.
|
||||
```
|
||||
|
||||
This integration gives you full control—whether you want a guided, step-by-step experience or prefer to automate the entire process with a single command.
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
## Roadmap
|
||||
- [x] Support for custom HTML templates by developers
|
||||
- [x] Support for accessing custom templates over API
|
||||
- [ ] Implement MCP server
|
||||
- [x] Implement MCP server
|
||||
- [ ] Ability for users to change system prompt
|
||||
- [X] Support external SQL database
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from fastapi import APIRouter, HTTPException
|
||||
import aiohttp
|
||||
from typing import List, Any
|
||||
from services.get_layout_by_name import get_layout_by_name
|
||||
from utils.get_layout_by_name import get_layout_by_name
|
||||
from models.presentation_layout import PresentationLayoutModel
|
||||
|
||||
LAYOUTS_ROUTER = APIRouter(prefix="/layouts", tags=["Layouts"])
|
||||
|
|
|
|||
|
|
@ -2,21 +2,10 @@ from app_mcp.services.state_machine.states import PresentationState
|
|||
|
||||
TRANSITIONS = {
|
||||
PresentationState.INIT: {
|
||||
PresentationState.FILES_UPLOADED,
|
||||
PresentationState.OUTLINE_REQUESTED
|
||||
},
|
||||
|
||||
# Upload and summary flow
|
||||
PresentationState.FILES_UPLOADED: {
|
||||
PresentationState.SUMMARY_GENERATED,
|
||||
PresentationState.UPLOAD_FAILED
|
||||
},
|
||||
PresentationState.SUMMARY_GENERATED: {
|
||||
PresentationState.OUTLINE_REQUESTED,
|
||||
PresentationState.SUMMARY_FAILED
|
||||
},
|
||||
|
||||
# Outline generation flow
|
||||
# Outline generation flow (now includes file processing)
|
||||
PresentationState.OUTLINE_REQUESTED: {
|
||||
PresentationState.OUTLINE_GENERATED,
|
||||
PresentationState.OUTLINE_FAILED
|
||||
|
|
@ -74,14 +63,6 @@ TRANSITIONS = {
|
|||
},
|
||||
|
||||
# Error recovery transitions
|
||||
PresentationState.UPLOAD_FAILED: {
|
||||
PresentationState.INIT,
|
||||
PresentationState.FILES_UPLOADED
|
||||
},
|
||||
PresentationState.SUMMARY_FAILED: {
|
||||
PresentationState.FILES_UPLOADED,
|
||||
PresentationState.OUTLINE_REQUESTED
|
||||
},
|
||||
PresentationState.OUTLINE_FAILED: {
|
||||
PresentationState.OUTLINE_REQUESTED,
|
||||
PresentationState.INIT
|
||||
|
|
@ -102,10 +83,9 @@ TRANSITIONS = {
|
|||
|
||||
|
||||
SUGGESTIONS = {
|
||||
PresentationState.INIT: "Upload files or start with outline generation",
|
||||
PresentationState.FILES_UPLOADED: "Generate summary from uploaded files",
|
||||
PresentationState.SUMMARY_GENERATED: "Generate presentation outline",
|
||||
PresentationState.OUTLINE_GENERATED: "Review and approve outline, or regenerate",
|
||||
PresentationState.INIT: "Start with outline generation (files will be processed automatically if provided)",
|
||||
PresentationState.OUTLINE_REQUESTED: "Generating presentation outline with file analysis if applicable",
|
||||
PresentationState.OUTLINE_GENERATED: "Review and approve outline",
|
||||
PresentationState.OUTLINE_APPROVED: "Select presentation layout",
|
||||
PresentationState.LAYOUT_SELECTED: "Generate presentation",
|
||||
PresentationState.PRESENTATION_READY: "Export presentation or request edits",
|
||||
|
|
@ -117,9 +97,7 @@ SUGGESTIONS = {
|
|||
|
||||
PROGRESS_WEIGHTS = {
|
||||
PresentationState.INIT: 0,
|
||||
PresentationState.FILES_UPLOADED: 10,
|
||||
PresentationState.SUMMARY_GENERATED: 20,
|
||||
PresentationState.OUTLINE_REQUESTED: 25,
|
||||
PresentationState.OUTLINE_REQUESTED: 20,
|
||||
PresentationState.OUTLINE_GENERATED: 35,
|
||||
PresentationState.OUTLINE_APPROVED: 40,
|
||||
PresentationState.LAYOUT_REQUESTED: 45,
|
||||
|
|
@ -134,8 +112,6 @@ PROGRESS_WEIGHTS = {
|
|||
|
||||
|
||||
ERROR_STATES = {
|
||||
PresentationState.UPLOAD_FAILED,
|
||||
PresentationState.SUMMARY_FAILED,
|
||||
PresentationState.OUTLINE_FAILED,
|
||||
PresentationState.GENERATION_FAILED,
|
||||
PresentationState.EXPORT_FAILED,
|
||||
|
|
|
|||
|
|
@ -5,11 +5,8 @@ class PresentationState(Enum):
|
|||
Represents the various states in the presentation workflow.
|
||||
"""
|
||||
INIT = auto()
|
||||
# Upload and summary phase
|
||||
FILES_UPLOADED = auto()
|
||||
SUMMARY_GENERATED = auto()
|
||||
|
||||
# Outline generation phase
|
||||
|
||||
# Outline generation phase (now includes file processing)
|
||||
OUTLINE_REQUESTED = auto()
|
||||
OUTLINE_GENERATED = auto()
|
||||
OUTLINE_APPROVED = auto()
|
||||
|
|
@ -32,8 +29,6 @@ class PresentationState(Enum):
|
|||
TEMPLATE_EDITING = auto()
|
||||
|
||||
# Error states
|
||||
UPLOAD_FAILED = auto()
|
||||
SUMMARY_FAILED = auto()
|
||||
OUTLINE_FAILED = auto()
|
||||
GENERATION_FAILED = auto()
|
||||
EXPORT_FAILED = auto()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ from dataclasses import asdict
|
|||
from app_mcp.services.state_machine.machine import PresentationStateMachine
|
||||
from app_mcp.services.state_machine.states import PresentationState
|
||||
from utils.user_config import update_env_with_user_config
|
||||
from app_mcp.wrapper.upload_and_generate_summary import upload_and_summarize_files
|
||||
from app_mcp.wrapper.generate_outline import generate_outline
|
||||
from app_mcp.wrapper.presentation_generation import process_post_outline_workflow
|
||||
from app_mcp.wrapper.presentation_export import export_presentation_and_get_path
|
||||
|
|
@ -73,50 +72,6 @@ class WorkflowOrchestrator:
|
|||
"""Remove workflow session"""
|
||||
return self._active_sessions.pop(session_id, None) is not None
|
||||
|
||||
async def execute_upload_and_summarize(self, session_id: str, files: List[Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Execute file upload and summary generation workflow step.
|
||||
Args:
|
||||
session_id (str): Unique identifier for the session.
|
||||
files (List[Any]): List of files to be uploaded and summarized.
|
||||
Returns:
|
||||
Dict[str, Any]: Result containing status, state, progress, next action, and any
|
||||
|
||||
"""
|
||||
fsm = self.get_session(session_id)
|
||||
if not fsm:
|
||||
raise ValueError(f"Session {session_id} not found")
|
||||
|
||||
try:
|
||||
fsm.transition(PresentationState.FILES_UPLOADED)
|
||||
|
||||
result = await upload_and_summarize_files(files)
|
||||
|
||||
# Update context and transition to summary generated
|
||||
context_updates = {
|
||||
"summary": result["summary"],
|
||||
"file_paths": result["file_paths"]
|
||||
}
|
||||
fsm.transition(PresentationState.SUMMARY_GENERATED, context_updates)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"state": fsm.state.name,
|
||||
"progress": fsm.get_workflow_progress(),
|
||||
"next_action": fsm.get_next_suggested_action(),
|
||||
"result": result
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
fsm.transition(PresentationState.UPLOAD_FAILED, {"error_message": str(e)})
|
||||
print(f"There was an error uploading and summarizing files: {e}")
|
||||
return {
|
||||
"status": "error",
|
||||
"state": fsm.state.name,
|
||||
"error": str(e),
|
||||
"next_action": fsm.get_next_suggested_action()
|
||||
}
|
||||
|
||||
async def execute_generate_outline(self, session_id: str, prompt: str, **kwargs) -> Dict[str, Any]:
|
||||
"""
|
||||
Execute outline generation workflow step
|
||||
|
|
@ -136,7 +91,7 @@ class WorkflowOrchestrator:
|
|||
fsm.transition(PresentationState.OUTLINE_REQUESTED)
|
||||
|
||||
|
||||
result = await generate_outline(prompt, summary=fsm.context.summary, **kwargs)
|
||||
result = await generate_outline(prompt, **kwargs)
|
||||
|
||||
# Update the Context and transition to outline generated
|
||||
context_updates = {
|
||||
|
|
@ -149,10 +104,9 @@ class WorkflowOrchestrator:
|
|||
"status": "success",
|
||||
"state": fsm.state.name,
|
||||
"progress": fsm.get_workflow_progress(),
|
||||
"next_action": "Review outline and approve, or request regeneration",
|
||||
"next_action": "Review outline and approve",
|
||||
"result": result,
|
||||
"can_approve": True,
|
||||
"can_regenerate": True
|
||||
"can_approve": True
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
from app_mcp.tools.choose_layout import register_choose_layout
|
||||
from app_mcp.tools.export_presentation import register_export_presentation
|
||||
from app_mcp.tools.regenerate_outline import register_regenerate_outline
|
||||
from app_mcp.tools.get_status import register_get_status
|
||||
from app_mcp.tools.show_layouts import register_show_layouts
|
||||
from app_mcp.tools.start_presentation import register_start_presentation
|
||||
|
|
@ -13,7 +12,6 @@ from app_mcp.tools.continue_workflow import register_continue_workflow
|
|||
__all__ = [
|
||||
'register_choose_layout',
|
||||
'register_export_presentation',
|
||||
'register_regenerate_outline',
|
||||
'register_get_status',
|
||||
'register_show_layouts',
|
||||
'register_start_presentation',
|
||||
|
|
@ -27,7 +25,6 @@ def register_tools(mcp, orchestrator):
|
|||
tools = [
|
||||
register_choose_layout,
|
||||
register_export_presentation,
|
||||
register_regenerate_outline,
|
||||
register_get_status,
|
||||
register_show_layouts,
|
||||
register_start_presentation,
|
||||
|
|
|
|||
|
|
@ -44,11 +44,12 @@ def register_continue_workflow(mcp, orchestrator):
|
|||
|
||||
current_state = fsm.state.name
|
||||
|
||||
if current_state in ["FILES_UPLOADED", "SUMMARY_GENERATED", "INIT"]:
|
||||
# Generate outline
|
||||
if current_state in ["INIT"]:
|
||||
# Generate outline (this now handles file processing internally)
|
||||
prompt = fsm.context.metadata.get("original_prompt", "")
|
||||
n_slides = fsm.context.metadata.get("n_slides", 8)
|
||||
language = fsm.context.metadata.get("language", "English")
|
||||
files = fsm.context.metadata.get("files", None)
|
||||
|
||||
if not prompt:
|
||||
return {
|
||||
|
|
@ -57,8 +58,13 @@ def register_continue_workflow(mcp, orchestrator):
|
|||
"suggestion": "Call start_presentation with a valid prompt"
|
||||
}
|
||||
|
||||
# Pass files to outline generation if they exist
|
||||
kwargs = {"n_slides": n_slides, "language": language}
|
||||
if files:
|
||||
kwargs["files"] = files
|
||||
|
||||
result = await orchestrator.execute_generate_outline(
|
||||
session_id, prompt, n_slides=n_slides, language=language
|
||||
session_id, prompt, **kwargs
|
||||
)
|
||||
|
||||
if result["status"] == "success":
|
||||
|
|
@ -68,8 +74,9 @@ def register_continue_workflow(mcp, orchestrator):
|
|||
"message": "Here's your presentation outline:",
|
||||
"title": result["result"]["title"],
|
||||
"outlines": result["result"]["outlines"],
|
||||
"files_processed": bool(files),
|
||||
"suggestion": "Take a look at the outline. If it looks good, use 'continue_workflow' again to proceed to layout selection.",
|
||||
"next_step": "Call continue_workflow again to choose layouts, or use regenerate_outline to try different approach"
|
||||
"next_step": "Call continue_workflow again to choose layouts"
|
||||
}
|
||||
return result
|
||||
|
||||
|
|
|
|||
|
|
@ -39,12 +39,7 @@ def register_export_presentation(mcp, orchestrator):
|
|||
"session_id": session_id,
|
||||
"message": f"🎉 Your presentation has been exported as {format.upper()}!",
|
||||
"path": result["result"]["path"],
|
||||
"suggestion": "You can download it now, or start creating another presentation.",
|
||||
"available_actions": {
|
||||
"download": "Download the presentation",
|
||||
"new_presentation": "Create a new presentation",
|
||||
"edit": "Make edits to this presentation"
|
||||
}
|
||||
"suggestion": "You can download it now, or start creating another presentation."
|
||||
}
|
||||
return result
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ def register_get_status(mcp, orchestrator):
|
|||
# Provide user-friendly status messages
|
||||
friendly_messages = {
|
||||
"INIT": "Ready to start! Use 'start_presentation' to begin.",
|
||||
"SUMMARY_GENERATED": "Files processed. Use 'continue_workflow' to generate outline.",
|
||||
"OUTLINE_REQUESTED": "Generating outline with file analysis if applicable.",
|
||||
"OUTLINE_GENERATED": "Outline created. Use 'continue_workflow' to proceed to layouts.",
|
||||
"OUTLINE_APPROVED": "Outline approved. Use 'choose_layout' to select a theme.",
|
||||
"LAYOUT_SELECTED": "Layout chosen. Use 'continue_workflow' to generate presentation.",
|
||||
|
|
@ -52,8 +52,8 @@ def register_get_status(mcp, orchestrator):
|
|||
|
||||
next_actions = {
|
||||
"INIT": "start_presentation",
|
||||
"SUMMARY_GENERATED": "continue_workflow",
|
||||
"OUTLINE_GENERATED": "continue_workflow (or regenerate_outline)",
|
||||
"OUTLINE_REQUESTED": "Wait for outline generation to complete",
|
||||
"OUTLINE_GENERATED": "continue_workflow",
|
||||
"OUTLINE_APPROVED": "choose_layout",
|
||||
"LAYOUT_SELECTED": "continue_workflow",
|
||||
"PRESENTATION_READY": "export_presentation",
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ def register_help_me(mcp, orchestrator):
|
|||
"helpful_commands": {
|
||||
"get_status": "📊 Check your current progress anytime",
|
||||
"show_layouts": "👀 Browse available themes and styles",
|
||||
"regenerate_outline": "🔄 Try a different outline approach",
|
||||
"help": "❓ Show this helpful guide"
|
||||
},
|
||||
"quick_start": {
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
from typing import Dict, Any, Optional, List
|
||||
from app_mcp.tools.continue_workflow import register_continue_workflow
|
||||
from app_mcp.services.state_machine.states import PresentationState
|
||||
|
||||
|
||||
def register_regenerate_outline(mcp, orchestrator):
|
||||
"""Register all workflow-related tools for chat-based interaction"""
|
||||
|
||||
@mcp.tool("regenerate_outline")
|
||||
async def regenerate_outline(
|
||||
session_id: str,
|
||||
new_prompt: Optional[str] = None,
|
||||
n_slides: Optional[int] = None,
|
||||
language: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
🔄 Create a new outline with different requirements.
|
||||
|
||||
Not happy with the generated outline? Use this to:
|
||||
- Try a different angle or focus for your topic
|
||||
- Change the number of slides
|
||||
- Adjust the language or tone
|
||||
- Incorporate new requirements
|
||||
|
||||
Args:
|
||||
session_id: Your presentation session ID
|
||||
new_prompt: New description of what you want (optional)
|
||||
n_slides: Different number of slides (optional)
|
||||
language: Different language (optional)
|
||||
"""
|
||||
try:
|
||||
fsm = orchestrator.get_session(session_id)
|
||||
if not fsm:
|
||||
return {"status": "error", "error": "Session not found"}
|
||||
|
||||
# Update parameters if provided
|
||||
if new_prompt:
|
||||
fsm.context.metadata["original_prompt"] = new_prompt
|
||||
if n_slides:
|
||||
fsm.context.metadata["n_slides"] = n_slides
|
||||
if language:
|
||||
fsm.context.metadata["language"] = language
|
||||
|
||||
# Reset to outline generation
|
||||
if fsm.can_transition_to(PresentationState.OUTLINE_REQUESTED):
|
||||
fsm.transition(PresentationState.OUTLINE_REQUESTED)
|
||||
|
||||
# Generate new outline
|
||||
continue_workflow = register_continue_workflow(mcp, orchestrator)
|
||||
result = await continue_workflow(session_id=session_id, action="continue")
|
||||
|
||||
if result["status"] == "success":
|
||||
result["message"] = "I've created a new outline for you:"
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e),
|
||||
"session_id": session_id
|
||||
}
|
||||
|
||||
return regenerate_outline
|
||||
|
|
@ -71,20 +71,21 @@ def register_start_presentation(mcp, orchestrator):
|
|||
# Debug log to verify metadata update
|
||||
print("DEBUG: Metadata after update:", fsm.context.metadata)
|
||||
|
||||
# Handle files if provided
|
||||
# Handle files if provided - store them in context for later use
|
||||
if files and len(files) > 0:
|
||||
result = await orchestrator.execute_upload_and_summarize(session_id, files)
|
||||
if result["status"] == "error":
|
||||
return result
|
||||
|
||||
# Store files in context for integrated processing during outline generation
|
||||
fsm.context.metadata.update({
|
||||
"files": files
|
||||
})
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"session_id": session_id,
|
||||
"message": "Great! I've uploaded and analyzed your files. Here's a summary:",
|
||||
"summary": result["result"]["summary"],
|
||||
"message": "Great! I've received your files and will analyze them during presentation creation.",
|
||||
"prompt": prompt,
|
||||
"suggestion": f"Now I can create a presentation outline based on your prompt '{prompt}' and the file content. Use 'continue_workflow' to proceed.",
|
||||
"next_step": "Call continue_workflow to generate the outline"
|
||||
"files_count": len(files),
|
||||
"suggestion": f"Now I'll create a presentation outline based on your prompt '{prompt}' and analyze the uploaded files. Use 'continue_workflow' to proceed.",
|
||||
"next_step": "Call continue_workflow to generate the outline with file analysis"
|
||||
}
|
||||
else:
|
||||
# Direct outline generation without files
|
||||
|
|
|
|||
|
|
@ -1,35 +1,91 @@
|
|||
import json
|
||||
from typing import Dict, Any, Optional
|
||||
import os
|
||||
from typing import Dict, Any, Optional, List, Annotated
|
||||
from models.presentation_outline_model import PresentationOutlineModel
|
||||
from utils.llm_calls.generate_presentation_outlines import generate_ppt_outline
|
||||
from services import TEMP_FILE_SERVICE
|
||||
from services.documents_loader import DocumentsLoader
|
||||
from services.score_based_chunker import ScoreBasedChunker
|
||||
from utils.validators import validate_files
|
||||
from fastapi import UploadFile, File
|
||||
from models.sse_response import SSEResponse
|
||||
from constants.documents import UPLOAD_ACCEPTED_FILE_TYPES
|
||||
import asyncio
|
||||
|
||||
|
||||
|
||||
async def generate_outline(
|
||||
prompt: str,
|
||||
n_slides: int = 8,
|
||||
language: str = "English",
|
||||
summary: Optional[str] = None,
|
||||
files: Annotated[Optional[List[UploadFile]], File()] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate presentation outlines given a prompt, number of slides, language, and optional summary.
|
||||
Generate presentation outlines given a prompt, number of slides, language, optional summary, and files.
|
||||
Files are now processed directly within this function instead of a separate step.
|
||||
Returns the parsed outline data.
|
||||
"""
|
||||
presentation_content_text = ""
|
||||
async for chunk in generate_ppt_outline(
|
||||
prompt,
|
||||
n_slides,
|
||||
language,
|
||||
summary,
|
||||
):
|
||||
presentation_content_text += chunk
|
||||
validate_files(files, True, True, 50, UPLOAD_ACCEPTED_FILE_TYPES)
|
||||
|
||||
temp_dir = TEMP_FILE_SERVICE.create_temp_dir()
|
||||
file_paths = []
|
||||
if files:
|
||||
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)
|
||||
|
||||
presentation_outlines = None
|
||||
additional_context = ""
|
||||
if file_paths:
|
||||
documents_loader = DocumentsLoader(file_paths=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], n_slides)
|
||||
presentation_outlines = PresentationOutlineModel(
|
||||
slides=[chunk.to_slide_outline() for chunk in chunks]
|
||||
)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
if not presentation_outlines:
|
||||
presentation_outlines_text = ""
|
||||
async for chunk in generate_ppt_outline(
|
||||
prompt,
|
||||
n_slides,
|
||||
language,
|
||||
additional_context,
|
||||
):
|
||||
# Give control to the event loop
|
||||
await asyncio.sleep(0)
|
||||
presentation_outlines_text += chunk
|
||||
|
||||
presentation_outlines_json = json.loads(presentation_outlines_text)
|
||||
presentation_outlines = PresentationOutlineModel(
|
||||
**presentation_outlines_json
|
||||
)
|
||||
|
||||
# Truncate slides to n_slides
|
||||
presentation_outlines.slides = presentation_outlines.slides[:n_slides]
|
||||
|
||||
# Compose title from first slide (if available)
|
||||
title = ""
|
||||
if presentation_outlines.slides and hasattr(presentation_outlines.slides[0], '__str__'):
|
||||
title = str(presentation_outlines.slides[0])[:50]
|
||||
title = title.replace("#", "").replace("/", "").replace("\\", "").replace("\n", "")
|
||||
elif presentation_outlines.slides:
|
||||
title = str(presentation_outlines.slides[0])[:50]
|
||||
|
||||
# Prepare outlines list
|
||||
outlines = [slide.model_dump() for slide in presentation_outlines.slides]
|
||||
|
||||
presentation_content_json = json.loads(presentation_content_text)
|
||||
presentation_content = PresentationOutlineModel(
|
||||
**presentation_content_json)
|
||||
outlines = [slide.model_dump()
|
||||
for slide in presentation_content.slides[:n_slides]]
|
||||
return {
|
||||
"title": presentation_content.title,
|
||||
"title": getattr(presentation_outlines, 'title', title),
|
||||
"outlines": outlines,
|
||||
"notes": presentation_content.notes
|
||||
"notes": getattr(presentation_outlines, 'notes', []),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +0,0 @@
|
|||
from typing import List, Dict, Any
|
||||
from fastapi import UploadFile
|
||||
from services import TEMP_FILE_SERVICE
|
||||
from services.documents_loader import DocumentsLoader
|
||||
from utils.randomizers import get_random_uuid
|
||||
from utils.llm_calls.generate_document_summary import generate_document_summary
|
||||
|
||||
|
||||
# Standalone function for workflow orchestrator
|
||||
async def upload_and_summarize_files(
|
||||
files: List[UploadFile]
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Upload files, generate a document summary, and return both summary and file paths.
|
||||
"""
|
||||
if not files:
|
||||
raise ValueError("No files provided")
|
||||
temp_dir = TEMP_FILE_SERVICE.create_temp_dir(get_random_uuid())
|
||||
file_paths = []
|
||||
for upload in files:
|
||||
temp_path = TEMP_FILE_SERVICE.create_temp_file_path(upload.filename, temp_dir)
|
||||
with open(temp_path, "wb") as f:
|
||||
f.write(await upload.read())
|
||||
file_paths.append(temp_path)
|
||||
documents_loader = DocumentsLoader(file_paths=file_paths)
|
||||
await documents_loader.load_documents(temp_dir)
|
||||
summary = await generate_document_summary(documents_loader.documents)
|
||||
return {
|
||||
"summary": summary,
|
||||
"file_paths": file_paths,
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue