mirror of https://github.com/kortix-ai/suna.git
Merge pull request #1798 from KrishavRajSingh/main
feat: validate slide after creating
This commit is contained in:
commit
8d11bc4bb1
|
@ -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**
|
||||
|
||||
|
@ -16,7 +16,7 @@ Follow this simplified, four-step workflow for every presentation. **DO NOT SKIP
|
|||
1. **Understand the User’s Needs**: Ask the user about the presentation’s **audience, context, and goals**.
|
||||
2. **Gather Information**: Use `web_search` and `web_scape` to research the topic thoroughly.
|
||||
3. **Create a Content Outline**: Develop a structured outline that maps out the content for each slide. Focus on one main idea per slide. Also decide if a slide need any images or not, if yes what all. images will it need based on content.
|
||||
4. **Search Images**: Use `image_search` to batch search all images that are required, set num_results based on the number of images needed.
|
||||
4. **Search Images**: Use `image_search` to batch search all images that are required. **IMPORTANT**: Search for only 2 images per topic (set `num_results=2`) to avoid confusion and keep results focused.
|
||||
5. **Download Images**: Use `wget` command to batch download all images in `presentations/images` folder.
|
||||
|
||||
### **Phase 2: Theme Definition** 🎨
|
||||
|
@ -48,18 +48,18 @@ 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. The validation is simple pass/fail:
|
||||
* **Pass**: Content height ≤ 1080px
|
||||
* **Fail**: Content height > 1080px
|
||||
|
||||
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, you must edit 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."
|
||||
3. **Edit Slides When Needed**: When you need to modify existing slides (e.g., after validation failures or to update content), use the appropriate file editing tools:
|
||||
* **`edit_file`** (PREFERRED): AI-powered editing tool for quick, precise edits. Specify only the lines you want to change using `// ... existing code ...` for unchanged sections. This is the fastest and most efficient option.
|
||||
* **`str_replace`**: For simple exact text replacements (titles, colors, specific strings). Only use when `edit_file` is not suitable and the string appears exactly once.
|
||||
* **`full_file_rewrite`**: Complete file rewrite. Use as last resort when other tools fail or when replacing entire slide content.
|
||||
|
||||
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.
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import json
|
|||
import os
|
||||
from datetime import datetime
|
||||
import re
|
||||
import asyncio
|
||||
|
||||
class SandboxPresentationTool(SandboxToolsBase):
|
||||
"""
|
||||
|
@ -427,6 +428,154 @@ 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 rendered height of the content
|
||||
dimensions = await page.evaluate("""
|
||||
() => {{
|
||||
// Get the actual bounding box height of the body content
|
||||
const bodyRect = document.body.getBoundingClientRect();
|
||||
const actualHeight = Math.ceil(bodyRect.height);
|
||||
|
||||
const viewportHeight = window.innerHeight;
|
||||
const overflows = actualHeight > 1080;
|
||||
|
||||
return {{
|
||||
scrollHeight: actualHeight,
|
||||
viewportHeight: viewportHeight,
|
||||
overflows: overflows,
|
||||
excessHeight: Math.max(0, actualHeight - 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 - simple pass/fail
|
||||
validation_passed = not dimensions["overflows"]
|
||||
|
||||
validation_results = {
|
||||
"presentation_name": presentation_name,
|
||||
"presentation_path": presentation_path,
|
||||
"slide_number": slide_number,
|
||||
"slide_title": slide_info["title"],
|
||||
"actual_content_height": dimensions["scrollHeight"],
|
||||
"target_height": 1080,
|
||||
"validation_passed": validation_passed
|
||||
}
|
||||
|
||||
# Add pass/fail message
|
||||
if validation_passed:
|
||||
validation_results["message"] = f"✓ Slide {slide_number} '{slide_info['title']}' validation passed. Content height: {dimensions['scrollHeight']}px"
|
||||
else:
|
||||
validation_results["message"] = f"✗ Slide {slide_number} '{slide_info['title']}' validation failed. Content height: {dimensions['scrollHeight']}px exceeds 1080px limit by {dimensions['excessHeight']}px"
|
||||
validation_results["excess_height"] = dimensions["excessHeight"]
|
||||
|
||||
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",
|
||||
|
|
|
@ -484,6 +484,12 @@ export const TOOL_GROUPS: Record<string, ToolGroup> = {
|
|||
description: 'Delete entire presentations',
|
||||
enabled: true,
|
||||
},
|
||||
{
|
||||
name: 'validate_slide',
|
||||
displayName: 'Validate Slide',
|
||||
description: 'Validate slide dimensions and content height',
|
||||
enabled: true,
|
||||
}
|
||||
],
|
||||
},
|
||||
|
||||
|
|
|
@ -136,6 +136,7 @@ const defaultRegistry: ToolViewRegistryType = {
|
|||
'list-presentations': ListPresentationsToolView,
|
||||
'delete-slide': DeleteSlideToolView,
|
||||
'delete-presentation': DeletePresentationToolView,
|
||||
'validate-slide': PresentationViewer,
|
||||
// 'presentation-styles': PresentationStylesToolView,
|
||||
'present-presentation': PresentPresentationToolView,
|
||||
|
||||
|
@ -263,6 +264,7 @@ export function ToolView({ name = 'default', assistantContent, toolContent, ...p
|
|||
'list-slides',
|
||||
'delete-slide',
|
||||
'delete-presentation',
|
||||
'validate-slide',
|
||||
// 'presentation-styles',
|
||||
'present-presentation',
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue