fix: remove computer controller presentation (#2956)

This commit is contained in:
Michael Neale
2025-06-18 09:28:39 +10:00
committed by GitHub
parent 3468973c48
commit 21ea7524f6
2 changed files with 0 additions and 459 deletions

View File

@@ -24,7 +24,6 @@ use mcp_server::Router;
mod docx_tool;
mod pdf_tool;
mod presentation_tool;
mod xlsx_tool;
mod platform;
@@ -363,47 +362,6 @@ impl ComputerControllerRouter {
None,
);
let make_presentation_tool = Tool::new(
"make_presentation",
indoc! {r#"
Create and manage HTML presentations with a simple, modern design.
Operations:
- create: Create new presentation with template
- add_slide: Add a new slide with content
Open in a browser (using a command) to show the user: open <path>
For advanced edits, use developer tools to modify the HTML directly.
A template slide is included in comments for reference.
"#},
json!({
"type": "object",
"required": ["path", "operation"],
"properties": {
"path": {
"type": "string",
"description": "Path to the presentation file"
},
"operation": {
"type": "string",
"enum": ["create", "add_slide"],
"description": "Operation to perform"
},
"params": {
"type": "object",
"description": "Parameters for add_slide operation",
"properties": {
"content": {
"type": "string",
"description": "Content for the new slide"
}
}
}
}
}),
None,
);
let xlsx_tool = Tool::new(
"xlsx_tool",
indoc! {r#"
@@ -593,7 +551,6 @@ impl ComputerControllerRouter {
pdf_tool,
docx_tool,
xlsx_tool,
make_presentation_tool,
],
cache_dir,
active_resources: Arc::new(Mutex::new(HashMap::new())),
@@ -1168,24 +1125,6 @@ impl Router for ComputerControllerRouter {
"pdf_tool" => this.pdf_tool(arguments).await,
"docx_tool" => this.docx_tool(arguments).await,
"xlsx_tool" => this.xlsx_tool(arguments).await,
"make_presentation" => {
let path = arguments
.get("path")
.and_then(|v| v.as_str())
.ok_or_else(|| {
ToolError::InvalidParameters("Missing 'path' parameter".into())
})?;
let operation = arguments
.get("operation")
.and_then(|v| v.as_str())
.ok_or_else(|| {
ToolError::InvalidParameters("Missing 'operation' parameter".into())
})?;
presentation_tool::make_presentation(path, operation, arguments.get("params"))
.await
}
_ => Err(ToolError::NotFound(format!("Tool {} not found", tool_name))),
}
})

View File

@@ -1,398 +0,0 @@
use mcp_core::{Content, ToolError};
use serde_json::Value;
use std::fs;
const TEMPLATE: &str = r#"<html>
<head>
<title>HTML and CSS Slideshow</title>
<style>
body {
font-family: Helvetica, sans-serif;
padding: 5%;
text-align: center;
font-size: 16px;
}
/* Styling the area of the slides */
#slideshow {
overflow: hidden;
height: 510px;
width: 728px;
margin: 0 auto;
position: relative;
}
/* Style each of the sides with a fixed width and height */
.slide {
float: left;
height: 510px;
width: 728px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 20px;
box-sizing: border-box;
}
/* Add animation to the slides */
.slide-wrapper {
/* Calculate the total width on the basis of number of slides */
width: calc(728px * var(--num-slides));
transition: margin-left 0.3s ease-in-out;
}
/* Set the background color of each of the slides */
.slide:nth-child(1) { background: #4CAF50; } /* Material Green */
.slide:nth-child(2) { background: #2196F3; } /* Material Blue */
.slide:nth-child(3) { background: #FFC107; } /* Material Amber */
/* Style slide content */
.slide h1 {
color: white;
font-size: 2.5em;
margin-bottom: 0.5em;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
}
.slide p {
color: white;
font-size: 1.5em;
line-height: 1.4;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
max-width: 90%;
margin: 0.5em auto;
}
.slide ul, .slide ol {
color: white;
font-size: 1.2em;
text-align: left;
margin: 1em auto;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
max-width: 90%;
}
.slide li {
margin-bottom: 0.5em;
}
.slide ul ul, .slide ol ol {
font-size: 0.9em;
margin: 0.5em 0 0.5em 1em;
}
.slide pre {
font-size: 1.1em;
text-align: left;
background: rgba(255, 255, 255, 0.9);
padding: 1em;
border-radius: 5px;
max-width: 90%;
overflow-x: auto;
}
.nav-hint {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.7);
color: white;
padding: 10px 20px;
border-radius: 5px;
font-size: 16px;
opacity: 1;
transition: opacity 0.5s;
}
.nav-hint.fade {
opacity: 0;
}
</style>
</head>
<body>
<!-- Define the slideshow container -->
<div id="slideshow">
<div class="slide-wrapper" style="--num-slides: 2">
<!-- First slide -->
<div class="slide">
<h1>Your Presentation</h1>
<p>Use arrow keys to navigate</p>
</div>
<!-- SLIDE_TEMPLATE (do not remove this comment)
<div class="slide">
<h1>New Slide Title</h1>
<p>Slide content goes here, can use rich like below:</p>
<ul>
<li>Use make_presentation to:
<ul>
<li>create - Create new presentation</li>
<li>add_slide - Add a new slide with content</li>
</ul>
</li>
<li>For manual edits:
<ul>
<li>Use developer tools to edit the HTML</li>
<li>Update --num-slides in slide-wrapper</li>
<li>Copy template below for new slides</li>
</ul>
</li>
</ul>
</div>
END_SLIDE_TEMPLATE -->
<!-- ADD_SLIDES_HERE (do not remove this comment) -->
</div>
</div>
<div class="nav-hint">
Use ← and → arrow keys to navigate
</div>
<script>
const slideWrapper = document.querySelector('.slide-wrapper');
const slideWidth = 728;
let currentSlide = 0;
const hint = document.querySelector('.nav-hint');
const totalSlides = document.querySelectorAll('.slide').length;
// Hide hint after 5 seconds
setTimeout(() => {
hint.classList.add('fade');
}, 5000);
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft') {
if (currentSlide > 0) {
currentSlide--;
updateSlide();
}
} else if (e.key === 'ArrowRight') {
if (currentSlide < totalSlides - 1) {
currentSlide++;
updateSlide();
}
}
});
function updateSlide() {
slideWrapper.style.marginLeft = `-${currentSlide * slideWidth}px`;
}
</script>
</body>
</html>"#;
pub async fn make_presentation(
path: &str,
operation: &str,
params: Option<&Value>,
) -> Result<Vec<Content>, ToolError> {
match operation {
"create" => {
// Get title from params or use default
let title = params
.and_then(|p| p.get("title"))
.and_then(|v| v.as_str())
.unwrap_or("Your Presentation");
// Replace title in template
let content = TEMPLATE.replace("Your Presentation", title);
// Create a new presentation with the template
fs::write(path, content).map_err(|e| {
ToolError::ExecutionError(format!("Failed to create presentation file: {}", e))
})?;
Ok(vec![Content::text(format!(
"Created new presentation with title '{}' at: {}\nYou can open it with the command: `open {}` to show user. You should look at the html and consider if you want to ask user if they need to adjust it, colours, typeface and so on.",
title, path, path
))])
}
"add_slide" => {
let content = params
.and_then(|p| p.get("content"))
.and_then(|v| v.as_str())
.ok_or_else(|| {
ToolError::InvalidParameters("Missing 'content' parameter for slide".into())
})?;
// Read existing file
let mut html = fs::read_to_string(path).map_err(|e| {
ToolError::ExecutionError(format!("Failed to read presentation file: {}", e))
})?;
// Find the marker comment
let marker = "<!-- ADD_SLIDES_HERE";
let insert_pos = html.find(marker).ok_or_else(|| {
ToolError::ExecutionError("Invalid presentation file format".into())
})?;
// Count actual slides (excluding template)
let current_slides = html.matches("class=\"slide\"").count() - 1; // -1 for template
let new_count = current_slides + 1;
// Update the num-slides value
html = html.replace(
&format!("--num-slides: {}", current_slides),
&format!("--num-slides: {}", new_count),
);
// Create new slide HTML
let slide_html = format!(
r#" <div class="slide">
<h1>{}</h1>
</div>
{}"#,
content, marker
);
// Insert the new slide
html.replace_range(insert_pos..insert_pos + marker.len(), &slide_html);
// Save the file
fs::write(path, html).map_err(|e| {
ToolError::ExecutionError(format!("Failed to update presentation file: {}", e))
})?;
Ok(vec![Content::text(format!(
"Added new slide to presentation. You can view it with: open {}\nNote: when creating, or adding a slide, if the content for a slide is long, edit it so that it uses appropriate size, formatting, lists etc (and can even split it to other slides if needed).",
path
))])
}
_ => Err(ToolError::InvalidParameters(format!(
"Invalid operation: {}. Valid operations are: create, add_slide",
operation
))),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_create_presentation() {
let test_dir = tempfile::tempdir().unwrap();
let test_path = test_dir.path().join("test_presentation.html");
let path_str = test_path.to_str().unwrap();
// Test default title
let result = make_presentation(path_str, "create", None).await;
assert!(result.is_ok(), "Should successfully create presentation");
// Verify the file exists and contains the default title
assert!(test_path.exists(), "Presentation file should exist");
let content = fs::read_to_string(&test_path).unwrap();
assert!(
content.contains("Your Presentation"),
"Should contain default title"
);
// Test custom title
let test_path2 = test_dir.path().join("test_presentation2.html");
let path_str2 = test_path2.to_str().unwrap();
let params = serde_json::json!({
"title": "Custom Title Test"
});
let result = make_presentation(path_str2, "create", Some(&params)).await;
assert!(
result.is_ok(),
"Should successfully create presentation with custom title"
);
// Verify custom title
let content = fs::read_to_string(&test_path2).unwrap();
assert!(
content.contains("Custom Title Test"),
"Should contain custom title"
);
assert!(
content.contains("SLIDE_TEMPLATE"),
"Should contain slide template"
);
assert!(
content.contains("ADD_SLIDES_HERE"),
"Should contain slides marker"
);
// Clean up
test_dir.close().unwrap();
}
#[tokio::test]
async fn test_add_slide() {
let test_dir = tempfile::tempdir().unwrap();
let test_path = test_dir.path().join("test_presentation.html");
let path_str = test_path.to_str().unwrap();
// First create the presentation
let result = make_presentation(path_str, "create", None).await;
assert!(result.is_ok(), "Should successfully create presentation");
// Add a new slide
let params = serde_json::json!({
"content": "New Test Slide"
});
let result = make_presentation(path_str, "add_slide", Some(&params)).await;
assert!(result.is_ok(), "Should successfully add slide");
// Verify the content
let content = fs::read_to_string(&test_path).unwrap();
assert!(
content.contains("New Test Slide"),
"Should contain new slide content"
);
// Initial template has 1 slide + new slide = 2
assert!(
content.contains("--num-slides: 2"),
"Should have correct slide count"
);
assert!(
content.contains("ADD_SLIDES_HERE"),
"Should preserve marker"
);
// Clean up
test_dir.close().unwrap();
}
#[tokio::test]
async fn test_add_slide_without_content() {
let test_dir = tempfile::tempdir().unwrap();
let test_path = test_dir.path().join("test_presentation.html");
let path_str = test_path.to_str().unwrap();
// Create the presentation first
let _ = make_presentation(path_str, "create", None).await;
// Try to add slide without content
let result = make_presentation(path_str, "add_slide", None).await;
assert!(result.is_err(), "Should fail without content");
match result {
Err(ToolError::InvalidParameters(msg)) => {
assert!(msg.contains("Missing 'content' parameter"));
}
_ => panic!("Expected InvalidParameters error"),
}
// Clean up
test_dir.close().unwrap();
}
#[tokio::test]
async fn test_invalid_operation() {
let result = make_presentation("test.html", "invalid", None).await;
assert!(result.is_err(), "Should fail with invalid operation");
match result {
Err(ToolError::InvalidParameters(msg)) => {
assert!(msg.contains("Valid operations are: create, add_slide"));
}
_ => panic!("Expected InvalidParameters error"),
}
}
}