mirror of https://github.com/kortix-ai/suna.git
validate ppt
This commit is contained in:
parent
98ca19b6c3
commit
cf6c6142c5
|
@ -5,7 +5,7 @@ You are a **Creative Presentation Virtuoso**, an elite visual storyteller and de
|
|||
## 🚨 **Core Directives**
|
||||
|
||||
1. **Theme Consistency is Paramount**: You MUST maintain a single, consistent visual theme throughout the entire presentation. This includes colors, fonts, and layout patterns. No exceptions.
|
||||
2. **Content Density is Strictly Controlled**: You MUST ensure that the content on each slide is concise and fits comfortably within the 1080px slide height. You will use a **Content Density Score** to validate this before creating each slide.
|
||||
2. **Content Density is Strictly Controlled**: You MUST ensure that the content on each slide is concise and fits comfortably within the 1080px slide height. You will use the `validate_slide` tool after creating each slide to ensure proper dimensions.
|
||||
|
||||
## 🎨 **Mandatory Workflow**
|
||||
|
||||
|
@ -48,20 +48,16 @@ Follow this simplified, four-step workflow for every presentation. **DO NOT SKIP
|
|||
|
||||
For each slide in your outline, you will perform the following steps:
|
||||
|
||||
1. **Calculate Content Density Score**: Before creating the slide, you MUST calculate a **Content Density Score**. This score is the sum of the following:
|
||||
* **Title**: Number of characters / 10
|
||||
* **Paragraphs**: Total number of characters in all paragraphs / 20
|
||||
* **Bullet Points**: Number of bullet points * 5
|
||||
* **Images**: Number of images * 15
|
||||
1. **Create the Slide**: Create the slide using the `create_slide` tool. All styling MUST be derived from the **Theme Object** defined in Phase 2. Use relative path like `../images/[name]` to link images.
|
||||
|
||||
2. **Validate Content Density**: The **Content Density Score MUST NOT exceed 100**. If it does, you must revise the content to be more concise.
|
||||
2. **Validate Slide Dimensions**: After creating each slide, you MUST use the `validate_slide` tool to verify that the slide height does not exceed 1080px. This tool will:
|
||||
* Check for explicit height values exceeding 1080px
|
||||
* Analyze content density and layout
|
||||
* Provide warnings and recommendations if issues are detected
|
||||
|
||||
3. **Declare and Create**: Once the score is validated, announce the score and then create the slide using the `create_slide` tool. All styling MUST be derived from the **Theme Object** defined in Phase 2. Use relative path like `../images/[name]` to link images.
|
||||
If validation fails or warnings are raised, revise the slide to reduce content or adjust spacing before proceeding to the next slide.
|
||||
|
||||
> **Example Slide Creation Announcement:**
|
||||
> "The Content Density Score for this slide is 85, which is within the acceptable limit. I will now create the slide."
|
||||
|
||||
4. **Enforce Theme Consistency**: Ensure that every slide uses the *exact same* colors and fonts from the **Theme Object**. Do not introduce new styles or deviate from the established theme.
|
||||
3. **Enforce Theme Consistency**: Ensure that every slide uses the *exact same* colors and fonts from the **Theme Object**. Do not introduce new styles or deviate from the established theme.
|
||||
|
||||
### **Phase 4: Final Presentation** 🎯
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import json
|
|||
import os
|
||||
from datetime import datetime
|
||||
import re
|
||||
import asyncio
|
||||
|
||||
class SandboxPresentationTool(SandboxToolsBase):
|
||||
"""
|
||||
|
@ -427,6 +428,183 @@ class SandboxPresentationTool(SandboxToolsBase):
|
|||
return self.fail_response(f"Failed to delete presentation: {str(e)}")
|
||||
|
||||
|
||||
@openapi_schema({
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "validate_slide",
|
||||
"description": "Validate a slide by reading its HTML code and checking if the content height exceeds 1080px. Use this tool to ensure slides fit within the standard presentation dimensions before finalizing them. This helps maintain proper slide formatting and prevents content overflow issues.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"presentation_name": {
|
||||
"type": "string",
|
||||
"description": "Name of the presentation containing the slide to validate"
|
||||
},
|
||||
"slide_number": {
|
||||
"type": "integer",
|
||||
"description": "Slide number to validate (1-based)"
|
||||
}
|
||||
},
|
||||
"required": ["presentation_name", "slide_number"]
|
||||
}
|
||||
}
|
||||
})
|
||||
async def validate_slide(self, presentation_name: str, slide_number: int) -> ToolResult:
|
||||
"""Validate a slide by rendering it in a browser and measuring actual content height"""
|
||||
try:
|
||||
await self._ensure_sandbox()
|
||||
|
||||
if not presentation_name:
|
||||
return self.fail_response("Presentation name is required.")
|
||||
|
||||
if slide_number < 1:
|
||||
return self.fail_response("Slide number must be 1 or greater.")
|
||||
|
||||
safe_name = self._sanitize_filename(presentation_name)
|
||||
presentation_path = f"{self.workspace_path}/{self.presentations_dir}/{safe_name}"
|
||||
|
||||
# Load metadata to verify slide exists
|
||||
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
|
||||
slide_info = metadata["slides"][str(slide_number)]
|
||||
slide_filename = slide_info["filename"]
|
||||
|
||||
# Create a Python script to measure the actual rendered height using Playwright
|
||||
measurement_script = f'''
|
||||
import asyncio
|
||||
import json
|
||||
from playwright.async_api import async_playwright
|
||||
|
||||
async def measure_slide_height():
|
||||
async with async_playwright() as p:
|
||||
browser = await p.chromium.launch(
|
||||
headless=True,
|
||||
args=['--no-sandbox', '--disable-setuid-sandbox']
|
||||
)
|
||||
page = await browser.new_page(viewport={{"width": 1920, "height": 1080}})
|
||||
|
||||
# Load the HTML file
|
||||
await page.goto('file:///workspace/{self.presentations_dir}/{safe_name}/{slide_filename}')
|
||||
|
||||
# Wait for page to load
|
||||
await page.wait_for_load_state('networkidle')
|
||||
|
||||
# Measure the actual content height
|
||||
dimensions = await page.evaluate("""
|
||||
() => {{
|
||||
const body = document.body;
|
||||
const html = document.documentElement;
|
||||
|
||||
// Get the actual scroll height (total content height)
|
||||
const scrollHeight = Math.max(
|
||||
body.scrollHeight, body.offsetHeight,
|
||||
html.clientHeight, html.scrollHeight, html.offsetHeight
|
||||
);
|
||||
|
||||
// Get viewport height
|
||||
const viewportHeight = window.innerHeight;
|
||||
|
||||
// Check if content overflows
|
||||
const overflows = scrollHeight > 1080;
|
||||
|
||||
return {{
|
||||
scrollHeight: scrollHeight,
|
||||
viewportHeight: viewportHeight,
|
||||
overflows: overflows,
|
||||
excessHeight: scrollHeight - 1080
|
||||
}};
|
||||
}}
|
||||
""")
|
||||
|
||||
await browser.close()
|
||||
return dimensions
|
||||
|
||||
result = asyncio.run(measure_slide_height())
|
||||
print(json.dumps(result))
|
||||
'''
|
||||
|
||||
# Write the script to a temporary file in the sandbox
|
||||
script_path = f"{self.workspace_path}/.validate_slide_temp.py"
|
||||
await self.sandbox.fs.upload_file(measurement_script.encode(), script_path)
|
||||
|
||||
# Execute the script
|
||||
try:
|
||||
result = await self.sandbox.process.exec(
|
||||
f"/bin/sh -c 'cd /workspace && python3 .validate_slide_temp.py'",
|
||||
timeout=30
|
||||
)
|
||||
|
||||
# Parse the result
|
||||
output = (getattr(result, "result", None) or getattr(result, "output", "") or "").strip()
|
||||
if not output:
|
||||
raise Exception("No output from validation script")
|
||||
|
||||
dimensions = json.loads(output)
|
||||
|
||||
# Clean up the temporary script
|
||||
try:
|
||||
await self.sandbox.fs.delete_file(script_path)
|
||||
except:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
# Clean up on error
|
||||
try:
|
||||
await self.sandbox.fs.delete_file(script_path)
|
||||
except:
|
||||
pass
|
||||
return self.fail_response(f"Failed to measure slide dimensions: {str(e)}")
|
||||
|
||||
# Analyze results
|
||||
validation_results = {
|
||||
"slide_number": slide_number,
|
||||
"slide_title": slide_info["title"],
|
||||
"actual_content_height": dimensions["scrollHeight"],
|
||||
"target_height": 1080,
|
||||
"excess_height": dimensions["excessHeight"],
|
||||
"validation_passed": not dimensions["overflows"],
|
||||
"warnings": [],
|
||||
"errors": [],
|
||||
"recommendations": []
|
||||
}
|
||||
|
||||
# Add errors or success messages based on actual measurements
|
||||
if dimensions["overflows"]:
|
||||
validation_results["validation_passed"] = False
|
||||
validation_results["errors"].append(
|
||||
f"Content height ({dimensions['scrollHeight']}px) exceeds the 1080px limit by {dimensions['excessHeight']}px"
|
||||
)
|
||||
validation_results["recommendations"].append(
|
||||
f"Reduce content or spacing by at least {dimensions['excessHeight']}px to fit within 1080px height"
|
||||
)
|
||||
|
||||
# Provide warnings for slides close to the limit
|
||||
elif dimensions["scrollHeight"] > 1000:
|
||||
margin = 1080 - dimensions["scrollHeight"]
|
||||
validation_results["warnings"].append(
|
||||
f"Content height ({dimensions['scrollHeight']}px) is close to the 1080px limit (only {margin}px margin remaining)"
|
||||
)
|
||||
validation_results["recommendations"].append(
|
||||
"Consider reducing content slightly to ensure comfortable fit across different browsers"
|
||||
)
|
||||
|
||||
# Summary message
|
||||
if validation_results["validation_passed"] and not validation_results["warnings"]:
|
||||
validation_results["message"] = f"✓ Slide {slide_number} '{slide_info['title']}' validation passed. Content height: {dimensions['scrollHeight']}px (within 1080px limit)"
|
||||
elif validation_results["validation_passed"] and validation_results["warnings"]:
|
||||
validation_results["message"] = f"⚠ Slide {slide_number} '{slide_info['title']}' validation passed with {len(validation_results['warnings'])} warning(s)"
|
||||
else:
|
||||
validation_results["message"] = f"✗ Slide {slide_number} '{slide_info['title']}' validation failed. Content height: {dimensions['scrollHeight']}px exceeds 1080px by {dimensions['excessHeight']}px"
|
||||
|
||||
return self.success_response(validation_results)
|
||||
|
||||
except Exception as e:
|
||||
return self.fail_response(f"Failed to validate slide: {str(e)}")
|
||||
|
||||
@openapi_schema({
|
||||
"type": "function",
|
||||
"function": {
|
||||
|
|
|
@ -218,6 +218,12 @@ TOOL_GROUPS: Dict[str, ToolGroup] = {
|
|||
description="Delete entire presentations",
|
||||
enabled=True
|
||||
),
|
||||
ToolMethod(
|
||||
name="validate_slide",
|
||||
display_name="Validate Slide",
|
||||
description="Validate slide dimensions and content height",
|
||||
enabled=True
|
||||
),
|
||||
ToolMethod(
|
||||
name="present_presentation",
|
||||
display_name="Present Presentation",
|
||||
|
|
Loading…
Reference in New Issue