2025-08-06 05:12:46 +08:00
from agentpress . tool import ToolResult , openapi_schema , usage_example
from sandbox . tool_base import SandboxToolsBase
from agentpress . thread_manager import ThreadManager
2025-08-21 14:06:01 +08:00
from typing import List , Dict , Optional
2025-08-06 05:12:46 +08:00
import json
import os
from datetime import datetime
2025-08-06 19:48:37 +08:00
import re
2025-08-21 17:42:54 +08:00
from . presentation_styles_config import get_style_config , get_all_styles
2025-08-06 05:12:46 +08:00
class SandboxPresentationTool ( SandboxToolsBase ) :
2025-08-21 14:06:01 +08:00
"""
Per - slide HTML presentation tool for creating professional presentations .
Each slide is managed individually with 1920 x1080 dimensions .
Supports iterative slide creation , editing , and presentation assembly .
"""
2025-08-06 05:12:46 +08:00
def __init__ ( self , project_id : str , thread_manager : ThreadManager ) :
super ( ) . __init__ ( project_id , thread_manager )
self . workspace_path = " /workspace "
self . presentations_dir = " presentations "
async def _ensure_presentations_dir ( self ) :
2025-08-21 14:06:01 +08:00
""" Ensure the presentations directory exists """
2025-08-06 05:12:46 +08:00
full_path = f " { self . workspace_path } / { self . presentations_dir } "
try :
await self . sandbox . fs . create_folder ( full_path , " 755 " )
except :
pass
2025-08-21 14:06:01 +08:00
async def _ensure_presentation_dir ( self , presentation_name : str ) :
""" Ensure a specific presentation directory exists """
safe_name = self . _sanitize_filename ( presentation_name )
presentation_path = f " { self . workspace_path } / { self . presentations_dir } / { safe_name } "
try :
await self . sandbox . fs . create_folder ( presentation_path , " 755 " )
except :
pass
return safe_name , presentation_path
def _sanitize_filename ( self , name : str ) - > str :
""" Convert presentation name to safe filename """
return " " . join ( c for c in name if c . isalnum ( ) or c in " -_ " ) . lower ( )
2025-08-06 05:12:46 +08:00
2025-08-21 17:42:54 +08:00
def _get_style_config ( self , style_name : str ) - > Dict :
""" Get style configuration for a given style name """
return get_style_config ( style_name )
def _create_slide_html ( self , slide_content : str , slide_number : int , total_slides : int , presentation_title : str , style : str = " default " ) - > str :
2025-08-21 14:06:01 +08:00
""" Create a complete HTML document for a single slide with proper 1920x1080 dimensions """
2025-08-06 05:12:46 +08:00
2025-08-21 17:42:54 +08:00
# Get style configuration
style_config = self . _get_style_config ( style )
2025-08-21 14:06:01 +08:00
html_template = f """ <!DOCTYPE html>
2025-08-06 05:12:46 +08:00
< html lang = " en " >
< head >
< meta charset = " UTF-8 " >
< meta name = " viewport " content = " width=device-width, initial-scale=1.0 " >
2025-08-21 14:06:01 +08:00
< title > { presentation_title } - Slide { slide_number } < / title >
< link href = " https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css " rel = " stylesheet " >
2025-08-21 17:42:54 +08:00
< link href = " {style_config['font_import']} " rel = " stylesheet " >
2025-08-21 14:06:01 +08:00
< script src = " https://d3js.org/d3.v7.min.js " > < / script >
< link href = " https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css " rel = " stylesheet " >
< script src = " https://cdn.jsdelivr.net/npm/chart.js@3.9.1 " > < / script >
2025-08-06 05:12:46 +08:00
< style >
2025-08-21 14:06:01 +08:00
/ * Base styling and 1920 x1080 slide container * /
2025-08-14 13:17:33 +08:00
* { {
margin : 0 ;
padding : 0 ;
box - sizing : border - box ;
} }
2025-08-21 14:06:01 +08:00
2025-08-06 05:12:46 +08:00
body { {
2025-08-21 17:42:54 +08:00
font - family : { style_config [ ' font_family ' ] } ;
2025-08-21 14:06:01 +08:00
background : #000000;
2025-08-21 17:42:54 +08:00
color : { style_config [ ' text_color ' ] } ;
2025-08-14 13:17:33 +08:00
display : flex ;
2025-08-21 14:06:01 +08:00
align - items : center ;
justify - content : center ;
min - height : 100 vh ;
overflow : hidden ;
2025-08-14 13:17:33 +08:00
} }
2025-08-21 14:06:01 +08:00
. slide - container { {
/ * CRITICAL : Standard presentation dimensions * /
width : 1920 px ;
height : 1080 px ;
max - width : 100 vw ;
max - height : 100 vh ;
position : relative ;
2025-08-21 17:42:54 +08:00
background : { style_config [ ' background ' ] } ;
color : { style_config [ ' text_color ' ] } ;
2025-08-21 14:06:01 +08:00
overflow : hidden ;
/ * Auto - scale to fit viewport while maintaining aspect ratio * /
transform - origin : center center ;
transform : scale ( min ( 100 vw / 1920 px , 100 vh / 1080 px ) ) ;
2025-08-06 05:12:46 +08:00
} }
2025-08-21 14:06:01 +08:00
/ * Slide number indicator * /
. slide - number { {
position : absolute ;
bottom : 30 px ;
right : 30 px ;
font - size : 18 px ;
2025-08-21 17:42:54 +08:00
color : { style_config [ ' text_color ' ] } ;
opacity : 0.7 ;
2025-08-21 14:06:01 +08:00
font - weight : 500 ;
z - index : 1000 ;
2025-08-06 05:12:46 +08:00
} }
2025-08-21 14:06:01 +08:00
2025-08-21 17:42:54 +08:00
/ * Common presentation elements with style theming * /
2025-08-21 14:06:01 +08:00
. slide - title { {
font - size : 48 px ;
font - weight : bold ;
margin - bottom : 30 px ;
2025-08-21 17:42:54 +08:00
color : { style_config [ ' primary_color ' ] } ;
2025-08-06 05:12:46 +08:00
} }
2025-08-21 14:06:01 +08:00
. slide - subtitle { {
font - size : 32 px ;
margin - bottom : 40 px ;
2025-08-21 17:42:54 +08:00
color : { style_config [ ' text_color ' ] } ;
2025-08-14 13:17:33 +08:00
} }
2025-08-21 14:06:01 +08:00
. slide - content { {
font - size : 24 px ;
line - height : 1.6 ;
2025-08-21 17:42:54 +08:00
color : { style_config [ ' text_color ' ] } ;
2025-08-14 13:17:33 +08:00
} }
2025-08-21 14:06:01 +08:00
. accent - bar { {
width : 100 px ;
height : 4 px ;
2025-08-21 17:42:54 +08:00
background - color : { style_config [ ' accent_color ' ] } ;
2025-08-21 14:06:01 +08:00
margin : 20 px 0 ;
2025-08-14 13:17:33 +08:00
} }
2025-08-21 14:06:01 +08:00
2025-08-21 17:42:54 +08:00
/ * Primary color elements * /
. primary - color { {
color : { style_config [ ' primary_color ' ] } ;
} }
. primary - bg { {
background - color : { style_config [ ' primary_color ' ] } ;
} }
/ * Accent color elements * /
. accent - color { {
color : { style_config [ ' accent_color ' ] } ;
} }
. accent - bg { {
background - color : { style_config [ ' accent_color ' ] } ;
} }
/ * Style - aware text color * /
. text - color { {
color : { style_config [ ' text_color ' ] } ;
} }
2025-08-21 14:06:01 +08:00
/ * Responsive images * /
img { {
max - width : 100 % ;
height : auto ;
border - radius : 8 px ;
2025-08-06 05:12:46 +08:00
} }
2025-08-21 14:06:01 +08:00
/ * List styling * /
ul , ol { {
margin : 20 px 0 ;
padding - left : 30 px ;
2025-08-14 13:17:33 +08:00
} }
2025-08-21 14:06:01 +08:00
li { {
margin : 10 px 0 ;
font - size : 20 px ;
line - height : 1.5 ;
2025-08-21 17:42:54 +08:00
color : { style_config [ ' text_color ' ] } ;
} }
/ * Style - specific enhancements * /
. card { {
background : { ' rgba(255, 255, 255, 0.1) ' if ' gradient ' in style_config [ ' background ' ] or style_config [ ' background ' ] . startswith ( ' #1 ' ) or style_config [ ' background ' ] . startswith ( ' #0 ' ) else ' rgba(0, 0, 0, 0.05) ' } ;
border - radius : 12 px ;
padding : 30 px ;
backdrop - filter : blur ( 10 px ) ;
} }
. highlight { {
background : { style_config [ ' accent_color ' ] } ;
color : { ' #FFFFFF ' if style_config [ ' accent_color ' ] . startswith ( ' # ' ) else style_config [ ' text_color ' ] } ;
padding : 4 px 12 px ;
border - radius : 6 px ;
font - weight : 600 ;
2025-08-06 05:12:46 +08:00
} }
< / style >
< / head >
< body >
2025-08-21 14:06:01 +08:00
< div class = " slide-container " >
{ slide_content }
< div class = " slide-number " > { slide_number } { f " / { total_slides } " if total_slides > 0 else " " } < / div >
2025-08-14 13:17:33 +08:00
< / div >
2025-08-06 05:12:46 +08:00
< / body >
< / html > """
2025-08-21 14:06:01 +08:00
return html_template
async def _load_presentation_metadata ( self , presentation_path : str ) :
""" Load presentation metadata, create if doesn ' t exist """
metadata_path = f " { presentation_path } /metadata.json "
try :
metadata_content = await self . sandbox . fs . download_file ( metadata_path )
return json . loads ( metadata_content . decode ( ) )
except :
# Create default metadata
return {
" presentation_name " : " " ,
" title " : " Presentation " ,
" description " : " " ,
" slides " : { } ,
" created_at " : datetime . now ( ) . isoformat ( ) ,
" updated_at " : datetime . now ( ) . isoformat ( )
}
async def _save_presentation_metadata ( self , presentation_path : str , metadata : Dict ) :
""" Save presentation metadata """
metadata [ " updated_at " ] = datetime . now ( ) . isoformat ( )
metadata_path = f " { presentation_path } /metadata.json "
await self . sandbox . fs . upload_file ( json . dumps ( metadata , indent = 2 ) . encode ( ) , metadata_path )
2025-08-06 05:12:46 +08:00
@openapi_schema ( {
" type " : " function " ,
" function " : {
2025-08-21 14:06:01 +08:00
" name " : " create_slide " ,
2025-08-21 17:42:54 +08:00
" description " : " Create or update a single slide in a presentation. Each slide is saved as a standalone HTML file with 1920x1080 dimensions (16:9 aspect ratio). Perfect for iterative slide creation and editing. Use ' presentation_styles ' tool first to see available styles. " ,
2025-08-06 05:12:46 +08:00
" parameters " : {
" type " : " object " ,
" properties " : {
" presentation_name " : {
" type " : " string " ,
2025-08-21 14:06:01 +08:00
" description " : " Name of the presentation (creates folder if doesn ' t exist) "
} ,
" slide_number " : {
" type " : " integer " ,
" description " : " Slide number (1-based). If slide exists, it will be updated. "
2025-08-06 05:12:46 +08:00
} ,
2025-08-21 14:06:01 +08:00
" slide_title " : {
2025-08-06 05:12:46 +08:00
" type " : " string " ,
2025-08-21 14:06:01 +08:00
" description " : " Title of this specific slide (for reference and navigation) "
2025-08-06 05:12:46 +08:00
} ,
2025-08-21 14:06:01 +08:00
" content " : {
2025-08-14 13:17:33 +08:00
" type " : " string " ,
2025-08-21 17:42:54 +08:00
" description " : " HTML content for the slide body. Should include all styling within the content. The content will be placed inside a 1920x1080 slide container with CSS frameworks (Tailwind, FontAwesome, D3, Chart.js) available. Use professional styling with good typography, spacing, and visual hierarchy. You can use style-aware CSS classes: .primary-color, .primary-bg, .accent-color, .accent-bg, .text-color, .card, .highlight "
2025-08-06 05:12:46 +08:00
} ,
2025-08-21 14:06:01 +08:00
" presentation_title " : {
2025-08-06 05:12:46 +08:00
" type " : " string " ,
2025-08-21 14:06:01 +08:00
" description " : " Main title of the presentation (used in HTML title and navigation) " ,
" default " : " Presentation "
2025-08-21 17:42:54 +08:00
} ,
" style " : {
" type " : " string " ,
" description " : " Visual style theme for the slide. Use ' presentation_styles ' tool to see all available options. Examples: ' velvet ' , ' glacier ' , ' ember ' , ' sage ' , ' obsidian ' , ' coral ' , ' platinum ' , ' aurora ' , ' midnight ' , ' citrus ' , or ' default ' " ,
" default " : " default "
2025-08-06 05:12:46 +08:00
}
} ,
2025-08-21 14:06:01 +08:00
" required " : [ " presentation_name " , " slide_number " , " slide_title " , " content " ]
2025-08-06 05:12:46 +08:00
}
}
} )
@usage_example ( '''
2025-08-21 14:06:01 +08:00
Create individual slides for a presentation about " Modern Web Development " :
< function_calls >
< invoke name = " create_slide " >
< parameter name = " presentation_name " > modern_web_development < / parameter >
< parameter name = " slide_number " > 1 < / parameter >
< parameter name = " slide_title " > Title Slide < / parameter >
< parameter name = " presentation_title " > Modern Web Development Trends 2024 < / parameter >
< parameter name = " content " > < div style = ' background: linear-gradient(135deg, #005A9C 0 % , #FF6B00 100 % ); height: 100 % ; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; color: white; padding: 80px; ' > < h1 style = ' font-size: 72px; font-weight: bold; margin-bottom: 30px; ' > Modern Web Development < / h1 > < div style = ' width: 150px; height: 6px; background: white; margin: 30px auto; ' > < / div > < h2 style = ' font-size: 36px; margin-bottom: 40px; opacity: 0.9; ' > Trends & Technologies 2024 < / h2 > < p style = ' font-size: 24px; opacity: 0.8; ' > Building Tomorrow ' s Web Today</p></div></parameter>
< / invoke >
< / function_calls >
Then create the next slide :
2025-08-06 05:12:46 +08:00
< function_calls >
2025-08-21 14:06:01 +08:00
< invoke name = " create_slide " >
< parameter name = " presentation_name " > modern_web_development < / parameter >
< parameter name = " slide_number " > 2 < / parameter >
< parameter name = " slide_title " > Frontend Frameworks < / parameter >
< parameter name = " presentation_title " > Modern Web Development Trends 2024 < / parameter >
< parameter name = " content " > < div style = ' display: flex; height: 100 % ; padding: 0; ' > < div style = ' width: 60 % ; padding: 80px; display: flex; flex-direction: column; justify-content: center; ' > < h1 style = ' font-size: 48px; font-weight: bold; color: #005A9C; margin-bottom: 20px; ' > Frontend Frameworks < / h1 > < div style = ' width: 100px; height: 4px; background: #FF6B00; margin-bottom: 40px; ' > < / div > < div style = ' font-size: 22px; line-height: 1.8; ' > < div style = ' margin-bottom: 25px; display: flex; align-items: center; ' > < i class = ' fab fa-react ' style = ' color: #61DAFB; font-size: 28px; margin-right: 15px; ' > < / i > < div > < strong > React < / strong > - Component - based UI library < / div > < / div > < div style = ' margin-bottom: 25px; display: flex; align-items: center; ' > < i class = ' fab fa-vuejs ' style = ' color: #4FC08D; font-size: 28px; margin-right: 15px; ' > < / i > < div > < strong > Vue . js < / strong > - Progressive framework < / div > < / div > < / div > < / div > < div style = ' width: 40 % ; background: #f8f9fa; display: flex; align-items: center; justify-content: center; padding: 40px; ' > < div style = ' text-align: center; ' > < div style = ' font-size: 64px; margin-bottom: 30px; ' > 📱 < / div > < h3 style = ' font-size: 28px; color: #005A9C; ' > Modern Tools < / h3 > < / div > < / div > < / div > < / parameter >
2025-08-06 05:12:46 +08:00
< / invoke >
< / function_calls >
2025-08-21 14:06:01 +08:00
This approach allows you to :
- Create slides one at a time
- Edit existing slides by using the same slide number
- Build presentations iteratively
- Mix and match different slide designs
- Each slide is a standalone HTML file with full styling
2025-08-06 05:12:46 +08:00
''' )
2025-08-21 14:06:01 +08:00
async def create_slide (
2025-08-06 05:12:46 +08:00
self ,
presentation_name : str ,
2025-08-21 14:06:01 +08:00
slide_number : int ,
slide_title : str ,
content : str ,
2025-08-21 17:42:54 +08:00
presentation_title : str = " Presentation " ,
style : str = " default "
2025-08-06 05:12:46 +08:00
) - > ToolResult :
2025-08-21 14:06:01 +08:00
""" Create or update a single slide in a presentation """
2025-08-06 05:12:46 +08:00
try :
await self . _ensure_sandbox ( )
await self . _ensure_presentations_dir ( )
2025-08-21 14:06:01 +08:00
# Validation
2025-08-06 05:12:46 +08:00
if not presentation_name :
return self . fail_response ( " Presentation name is required. " )
2025-08-21 14:06:01 +08:00
if slide_number < 1 :
return self . fail_response ( " Slide number must be 1 or greater. " )
if not slide_title :
return self . fail_response ( " Slide title is required. " )
if not content :
return self . fail_response ( " Slide content is required. " )
# Ensure presentation directory exists
safe_name , presentation_path = await self . _ensure_presentation_dir ( presentation_name )
# Load or create metadata
metadata = await self . _load_presentation_metadata ( presentation_path )
metadata [ " presentation_name " ] = presentation_name
if presentation_title != " Presentation " : # Only update if explicitly provided
metadata [ " title " ] = presentation_title
# Create slide HTML
slide_html = self . _create_slide_html (
slide_content = content ,
slide_number = slide_number ,
total_slides = 0 , # Will be updated when regenerating navigation
2025-08-21 17:42:54 +08:00
presentation_title = presentation_title ,
style = style
2025-08-21 14:06:01 +08:00
)
# Save slide file
slide_filename = f " slide_ { slide_number : 02d } .html "
slide_path = f " { presentation_path } / { slide_filename } "
await self . sandbox . fs . upload_file ( slide_html . encode ( ) , slide_path )
# Update metadata
if " slides " not in metadata :
metadata [ " slides " ] = { }
metadata [ " slides " ] [ str ( slide_number ) ] = {
" title " : slide_title ,
" filename " : slide_filename ,
" file_path " : f " { self . presentations_dir } / { safe_name } / { slide_filename } " ,
" preview_url " : f " /workspace/ { self . presentations_dir } / { safe_name } / { slide_filename } " ,
2025-08-21 17:42:54 +08:00
" style " : style ,
2025-08-21 14:06:01 +08:00
" created_at " : datetime . now ( ) . isoformat ( )
}
2025-08-06 05:12:46 +08:00
2025-08-21 14:06:01 +08:00
# Save updated metadata
await self . _save_presentation_metadata ( presentation_path , metadata )
2025-08-06 05:12:46 +08:00
2025-08-21 14:06:01 +08:00
return self . success_response ( {
2025-08-21 17:42:54 +08:00
" message " : f " Slide { slide_number } ' { slide_title } ' created/updated successfully with ' { style } ' style " ,
2025-08-21 14:06:01 +08:00
" presentation_name " : presentation_name ,
" presentation_path " : f " { self . presentations_dir } / { safe_name } " ,
" slide_number " : slide_number ,
" slide_title " : slide_title ,
" slide_file " : f " { self . presentations_dir } / { safe_name } / { slide_filename } " ,
" preview_url " : f " /workspace/ { self . presentations_dir } / { safe_name } / { slide_filename } " ,
2025-08-21 17:42:54 +08:00
" style " : style ,
2025-08-21 14:06:01 +08:00
" total_slides " : len ( metadata [ " slides " ] ) ,
" note " : " Slide saved as standalone HTML file with 1920x1080 dimensions "
} )
2025-08-06 05:12:46 +08:00
2025-08-21 14:06:01 +08:00
except Exception as e :
return self . fail_response ( f " Failed to create slide: { str ( e ) } " )
@openapi_schema ( {
" type " : " function " ,
" function " : {
" name " : " list_slides " ,
" description " : " List all slides in a presentation, showing their titles and order " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" presentation_name " : {
" type " : " string " ,
" description " : " Name of the presentation to list slides for "
}
} ,
" required " : [ " presentation_name " ]
}
}
} )
async def list_slides ( self , presentation_name : str ) - > ToolResult :
""" List all slides in a presentation """
try :
await self . _ensure_sandbox ( )
2025-08-06 05:12:46 +08:00
2025-08-21 14:06:01 +08:00
if not presentation_name :
return self . fail_response ( " Presentation name is required. " )
2025-08-06 05:12:46 +08:00
2025-08-21 14:06:01 +08:00
safe_name = self . _sanitize_filename ( presentation_name )
presentation_path = f " { self . workspace_path } / { self . presentations_dir } / { safe_name } "
2025-08-06 05:12:46 +08:00
2025-08-21 14:06:01 +08:00
# Load metadata
metadata = await self . _load_presentation_metadata ( presentation_path )
2025-08-06 05:12:46 +08:00
2025-08-21 14:06:01 +08:00
if not metadata . get ( " slides " ) :
return self . success_response ( {
" message " : f " No slides found in presentation ' { presentation_name } ' " ,
" presentation_name " : presentation_name ,
" slides " : [ ] ,
" total_slides " : 0
} )
# Sort slides by number
slides_info = [ ]
for slide_num_str , slide_data in metadata [ " slides " ] . items ( ) :
slides_info . append ( {
" slide_number " : int ( slide_num_str ) ,
" title " : slide_data [ " title " ] ,
" filename " : slide_data [ " filename " ] ,
" preview_url " : slide_data [ " preview_url " ] ,
" created_at " : slide_data . get ( " created_at " , " Unknown " )
} )
2025-08-06 05:12:46 +08:00
2025-08-21 14:06:01 +08:00
slides_info . sort ( key = lambda x : x [ " slide_number " ] )
2025-08-06 05:12:46 +08:00
return self . success_response ( {
2025-08-21 14:06:01 +08:00
" message " : f " Found { len ( slides_info ) } slides in presentation ' { presentation_name } ' " ,
2025-08-06 05:12:46 +08:00
" presentation_name " : presentation_name ,
2025-08-21 14:06:01 +08:00
" presentation_title " : metadata . get ( " title " , " Presentation " ) ,
" slides " : slides_info ,
" total_slides " : len ( slides_info ) ,
" presentation_path " : f " { self . presentations_dir } / { safe_name } "
2025-08-06 05:12:46 +08:00
} )
except Exception as e :
2025-08-21 14:06:01 +08:00
return self . fail_response ( f " Failed to list slides: { str ( e ) } " )
2025-08-06 05:12:46 +08:00
@openapi_schema ( {
" type " : " function " ,
" function " : {
2025-08-21 14:06:01 +08:00
" name " : " delete_slide " ,
" description " : " Delete a specific slide from a presentation " ,
2025-08-06 05:12:46 +08:00
" parameters " : {
" type " : " object " ,
" properties " : {
" presentation_name " : {
" type " : " string " ,
2025-08-21 14:06:01 +08:00
" description " : " Name of the presentation "
2025-08-06 05:12:46 +08:00
} ,
2025-08-21 14:06:01 +08:00
" slide_number " : {
" type " : " integer " ,
" description " : " Slide number to delete (1-based) "
2025-08-06 05:12:46 +08:00
}
} ,
2025-08-21 14:06:01 +08:00
" required " : [ " presentation_name " , " slide_number " ]
2025-08-06 05:12:46 +08:00
}
}
} )
2025-08-21 14:06:01 +08:00
async def delete_slide ( self , presentation_name : str , slide_number : int ) - > ToolResult :
""" Delete a specific slide from a presentation """
2025-08-06 19:48:37 +08:00
try :
2025-08-21 14:06:01 +08:00
await self . _ensure_sandbox ( )
2025-08-14 13:17:33 +08:00
2025-08-21 14:06:01 +08:00
if not presentation_name :
return self . fail_response ( " Presentation name is required. " )
2025-08-14 13:17:33 +08:00
2025-08-21 14:06:01 +08:00
if slide_number < 1 :
return self . fail_response ( " Slide number must be 1 or greater. " )
2025-08-14 13:17:33 +08:00
2025-08-21 14:06:01 +08:00
safe_name = self . _sanitize_filename ( presentation_name )
presentation_path = f " { self . workspace_path } / { self . presentations_dir } / { safe_name } "
# Load metadata
metadata = await self . _load_presentation_metadata ( presentation_path )
if not metadata . get ( " slides " ) or str ( slide_number ) not in metadata [ " slides " ] :
return self . fail_response ( f " Slide { slide_number } not found in presentation ' { presentation_name } ' " )
# Get slide info before deletion
slide_info = metadata [ " slides " ] [ str ( slide_number ) ]
slide_filename = slide_info [ " filename " ]
# Delete slide file
slide_path = f " { presentation_path } / { slide_filename } "
try :
await self . sandbox . fs . delete_file ( slide_path )
except :
pass # File might not exist
# Remove from metadata
del metadata [ " slides " ] [ str ( slide_number ) ]
# Save updated metadata
await self . _save_presentation_metadata ( presentation_path , metadata )
return self . success_response ( {
" message " : f " Slide { slide_number } ' { slide_info [ ' title ' ] } ' deleted successfully " ,
" presentation_name " : presentation_name ,
" deleted_slide " : slide_number ,
" deleted_title " : slide_info [ ' title ' ] ,
" remaining_slides " : len ( metadata [ " slides " ] )
} )
except Exception as e :
return self . fail_response ( f " Failed to delete slide: { str ( e ) } " )
2025-08-06 19:48:37 +08:00
2025-08-21 14:06:01 +08:00
2025-08-21 17:42:54 +08:00
@openapi_schema ( {
" type " : " function " ,
" function " : {
" name " : " presentation_styles " ,
" description " : " Get available presentation styles with their descriptions and visual characteristics. Use this to show users different style options before creating slides. " ,
" parameters " : {
" type " : " object " ,
" properties " : { } ,
" required " : [ ]
}
}
} )
async def presentation_styles ( self ) - > ToolResult :
""" Get available presentation styles with descriptions and examples """
try :
styles = get_all_styles ( )
return self . success_response ( {
" message " : f " Found { len ( styles ) } presentation styles available " ,
" styles " : styles ,
" usage_tip " : " Choose a style and use it with the ' style ' parameter in create_slide "
} )
except Exception as e :
return self . fail_response ( f " Failed to get presentation styles: { str ( e ) } " )
2025-08-21 14:06:01 +08:00
@openapi_schema ( {
" type " : " function " ,
" function " : {
" name " : " list_presentations " ,
" description " : " List all available presentations in the workspace " ,
" parameters " : {
" type " : " object " ,
" properties " : { } ,
" required " : [ ]
}
}
} )
async def list_presentations ( self ) - > ToolResult :
""" List all presentations in the workspace """
2025-08-06 05:12:46 +08:00
try :
2025-08-06 19:48:37 +08:00
await self . _ensure_sandbox ( )
2025-08-21 14:06:01 +08:00
presentations_path = f " { self . workspace_path } / { self . presentations_dir } "
2025-08-06 05:12:46 +08:00
try :
2025-08-21 14:06:01 +08:00
files = await self . sandbox . fs . list_files ( presentations_path )
presentations = [ ]
2025-08-06 19:48:37 +08:00
2025-08-21 14:06:01 +08:00
for file_info in files :
if file_info . is_directory :
metadata = await self . _load_presentation_metadata ( f " { presentations_path } / { file_info . name } " )
presentations . append ( {
" folder " : file_info . name ,
" title " : metadata . get ( " title " , " Unknown Title " ) ,
" description " : metadata . get ( " description " , " " ) ,
" total_slides " : len ( metadata . get ( " slides " , { } ) ) ,
" created_at " : metadata . get ( " created_at " , " Unknown " ) ,
" updated_at " : metadata . get ( " updated_at " , " Unknown " )
2025-08-06 19:48:37 +08:00
} )
2025-08-21 14:06:01 +08:00
return self . success_response ( {
" message " : f " Found { len ( presentations ) } presentations " ,
" presentations " : presentations ,
" presentations_directory " : f " /workspace/ { self . presentations_dir } "
} )
2025-08-06 19:48:37 +08:00
2025-08-21 14:06:01 +08:00
except Exception as e :
2025-08-06 19:48:37 +08:00
return self . success_response ( {
2025-08-21 14:06:01 +08:00
" message " : " No presentations found " ,
" presentations " : [ ] ,
" presentations_directory " : f " /workspace/ { self . presentations_dir } " ,
" note " : " Create your first slide using create_slide "
2025-08-06 19:48:37 +08:00
} )
2025-08-21 14:06:01 +08:00
except Exception as e :
return self . fail_response ( f " Failed to list presentations: { str ( e ) } " )
@openapi_schema ( {
" type " : " function " ,
" function " : {
" name " : " delete_presentation " ,
" description " : " Delete an entire presentation and all its files " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" presentation_name " : {
" type " : " string " ,
" description " : " Name of the presentation to delete "
}
} ,
" required " : [ " presentation_name " ]
}
}
} )
async def delete_presentation ( self , presentation_name : str ) - > ToolResult :
""" Delete a presentation and all its files """
try :
await self . _ensure_sandbox ( )
if not presentation_name :
return self . fail_response ( " Presentation name is required. " )
2025-08-06 05:12:46 +08:00
2025-08-21 14:06:01 +08:00
safe_name = self . _sanitize_filename ( presentation_name )
presentation_path = f " { self . workspace_path } / { self . presentations_dir } / { safe_name } "
try :
await self . sandbox . fs . delete_folder ( presentation_path )
return self . success_response ( {
" message " : f " Presentation ' { presentation_name } ' deleted successfully " ,
" deleted_path " : f " { self . presentations_dir } / { safe_name } "
} )
except Exception as e :
return self . fail_response ( f " Presentation ' { presentation_name } ' not found or could not be deleted: { str ( e ) } " )
2025-08-06 19:48:37 +08:00
except Exception as e :
2025-08-21 14:06:01 +08:00
return self . fail_response ( f " Failed to delete presentation: { str ( e ) } " )