Merge pull request #1798 from KrishavRajSingh/main

feat: validate slide after creating
This commit is contained in:
Krishav 2025-10-09 16:27:49 +05:30 committed by GitHub
commit 8d11bc4bb1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 175 additions and 12 deletions

View File

@ -5,7 +5,7 @@ You are a **Creative Presentation Virtuoso**, an elite visual storyteller and de
## 🚨 **Core Directives** ## 🚨 **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. 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** ## 🎨 **Mandatory Workflow**
@ -16,7 +16,7 @@ Follow this simplified, four-step workflow for every presentation. **DO NOT SKIP
1. **Understand the Users Needs**: Ask the user about the presentations **audience, context, and goals**. 1. **Understand the Users Needs**: Ask the user about the presentations **audience, context, and goals**.
2. **Gather Information**: Use `web_search` and `web_scape` to research the topic thoroughly. 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. 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. 5. **Download Images**: Use `wget` command to batch download all images in `presentations/images` folder.
### **Phase 2: Theme Definition** 🎨 ### **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: 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: 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.
* **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
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
If validation fails, you must edit the slide to reduce content or adjust spacing before proceeding to the next slide.
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. 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.
> **Example Slide Creation Announcement:** * **`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.
> "The Content Density Score for this slide is 85, which is within the acceptable limit. I will now create the slide." * **`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. 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.

View File

@ -6,6 +6,7 @@ import json
import os import os
from datetime import datetime from datetime import datetime
import re import re
import asyncio
class SandboxPresentationTool(SandboxToolsBase): class SandboxPresentationTool(SandboxToolsBase):
""" """
@ -427,6 +428,154 @@ class SandboxPresentationTool(SandboxToolsBase):
return self.fail_response(f"Failed to delete presentation: {str(e)}") 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({ @openapi_schema({
"type": "function", "type": "function",
"function": { "function": {

View File

@ -218,6 +218,12 @@ TOOL_GROUPS: Dict[str, ToolGroup] = {
description="Delete entire presentations", description="Delete entire presentations",
enabled=True enabled=True
), ),
ToolMethod(
name="validate_slide",
display_name="Validate Slide",
description="Validate slide dimensions and content height",
enabled=True
),
ToolMethod( ToolMethod(
name="present_presentation", name="present_presentation",
display_name="Present Presentation", display_name="Present Presentation",

View File

@ -484,6 +484,12 @@ export const TOOL_GROUPS: Record<string, ToolGroup> = {
description: 'Delete entire presentations', description: 'Delete entire presentations',
enabled: true, enabled: true,
}, },
{
name: 'validate_slide',
displayName: 'Validate Slide',
description: 'Validate slide dimensions and content height',
enabled: true,
}
], ],
}, },

View File

@ -136,6 +136,7 @@ const defaultRegistry: ToolViewRegistryType = {
'list-presentations': ListPresentationsToolView, 'list-presentations': ListPresentationsToolView,
'delete-slide': DeleteSlideToolView, 'delete-slide': DeleteSlideToolView,
'delete-presentation': DeletePresentationToolView, 'delete-presentation': DeletePresentationToolView,
'validate-slide': PresentationViewer,
// 'presentation-styles': PresentationStylesToolView, // 'presentation-styles': PresentationStylesToolView,
'present-presentation': PresentPresentationToolView, 'present-presentation': PresentPresentationToolView,
@ -263,6 +264,7 @@ export function ToolView({ name = 'default', assistantContent, toolContent, ...p
'list-slides', 'list-slides',
'delete-slide', 'delete-slide',
'delete-presentation', 'delete-presentation',
'validate-slide',
// 'presentation-styles', // 'presentation-styles',
'present-presentation', 'present-presentation',
] ]