auxoppttest / gradio_interface.py
BruceWayne1's picture
hhh
79e8250
"""
Gradio web interface for PowerPoint MCP Server - COMPLETE VERSION
Provides testing interface and MCP configuration details
CORRECT filename - works with complete utils/ and tools/ integration
"""
import gradio as gr
import json
import tempfile
import uuid
import logging
from typing import Optional, Tuple
# Import our complete modules - CORRECT names
from presentation_manager import presentations, set_current_presentation_id, get_current_presentation_id
from config import get_mcp_endpoint_url, get_server_config, is_huggingface_spaces
logger = logging.getLogger(__name__)
def create_test_presentation():
"""Create a test presentation using existing PowerPoint functionality"""
try:
# Use existing PowerPoint libraries
from pptx import Presentation
# Create presentation directly
prs = Presentation()
presentation_id = str(uuid.uuid4())
presentations[presentation_id] = prs
set_current_presentation_id(presentation_id)
return presentation_id, "✅ Test presentation created successfully"
except ImportError:
return None, "❌ python-pptx not available - install requirements"
except Exception as e:
return None, f"❌ Error creating presentation: {str(e)}"
def add_test_slide(presentation_id, title="Test Slide", content="Test content"):
"""Add a test slide using existing functionality"""
try:
if presentation_id not in presentations:
return False, "Presentation not found"
prs = presentations[presentation_id]
# Add slide with title and content layout
slide_layout = prs.slide_layouts[1] # Title and content layout
slide = prs.slides.add_slide(slide_layout)
# Set title
if slide.shapes.title:
slide.shapes.title.text = title
# Set content
if len(slide.placeholders) > 1:
slide.placeholders[1].text = content
return True, f"Slide added: {title}"
except Exception as e:
return False, f"Error adding slide: {str(e)}"
def save_test_presentation(presentation_id):
"""Save test presentation to file"""
try:
if presentation_id not in presentations:
return None, "Presentation not found"
prs = presentations[presentation_id]
temp_file = tempfile.NamedTemporaryFile(suffix='.pptx', delete=False)
prs.save(temp_file.name)
return temp_file.name, "✅ Presentation saved successfully"
except Exception as e:
return None, f"❌ Error saving presentation: {str(e)}"
def create_presentation_from_json(slides_data):
"""Create PowerPoint from JSON data using complete functionality"""
try:
if isinstance(slides_data, str):
data = json.loads(slides_data)
else:
data = slides_data
# Create presentation
presentation_id, create_message = create_test_presentation()
if not presentation_id:
return None, create_message
# Add slides
slide_count = 0
for slide_info in data.get("slides", []):
success, message = add_test_slide(
presentation_id,
slide_info.get("title", f"Slide {slide_count + 1}"),
slide_info.get("content", "Sample content")
)
if success:
slide_count += 1
if slide_count == 0:
return None, "❌ No slides were created successfully"
# Save presentation
file_path, save_message = save_test_presentation(presentation_id)
if file_path:
return file_path, f"✅ Presentation created with {slide_count} slides"
else:
return None, save_message
except Exception as e:
logger.error(f"Error creating presentation: {str(e)}")
return None, f"❌ Error: {str(e)}"
def _render_slide_to_image(slide, width=1280, height=720):
"""Lightweight slide renderer: draws title and main text onto a canvas.
This is a best-effort preview (python-pptx can't export to images natively).
"""
try:
from PIL import Image, ImageDraw, ImageFont
except Exception:
return None
image = Image.new("RGB", (width, height), color=(245, 246, 250))
draw = ImageDraw.Draw(image)
# Try loading a default font; fallback to a basic one if unavailable
try:
title_font = ImageFont.truetype("arial.ttf", 48)
body_font = ImageFont.truetype("arial.ttf", 28)
except Exception:
title_font = ImageFont.load_default()
body_font = ImageFont.load_default()
# Extract probable title/content text
title_text = None
body_text_lines = []
try:
if hasattr(slide, "shapes"):
# Title
if getattr(slide.shapes, "title", None) and getattr(slide.shapes.title, "text", None):
title_text = slide.shapes.title.text
# Other placeholders / text frames
for shape in slide.shapes:
try:
if hasattr(shape, "has_text_frame") and shape.has_text_frame:
text = shape.text or ""
if text and (not title_text or text != title_text):
# Split into lines and extend
for line in text.splitlines():
if line.strip():
body_text_lines.append(line.strip())
except Exception:
continue
except Exception:
pass
# Draw title
margin = 60
y = margin
if title_text:
draw.text((margin, y), title_text, font=title_font, fill=(28, 28, 28))
y += 90
# Draw body (limit lines for preview)
max_lines = 12
for i, line in enumerate(body_text_lines[:max_lines]):
draw.text((margin, y + i * 38), f"• {line}", font=body_font, fill=(60, 60, 60))
return image
def render_presentation_to_images(presentation_id):
"""Render a presentation's slides to PIL images for preview."""
try:
if presentation_id not in presentations:
return [], "Presentation not found"
prs = presentations[presentation_id]
images = []
for slide in getattr(prs, "slides", []):
img = _render_slide_to_image(slide)
if img is not None:
images.append(img)
if not images:
return [], "No slides to render yet"
return images, f"Rendered {len(images)} slide preview(s)"
except Exception as e:
logger.error(f"Error rendering presentation: {str(e)}")
return [], f"❌ Error rendering: {str(e)}"
def list_presentations_for_ui():
try:
items = []
for pid, pres in presentations.items():
try:
slide_count = len(getattr(pres, "slides", []))
except Exception:
slide_count = 0
items.append((f"{pid} ({slide_count} slides)", pid))
return items
except Exception:
return []
def save_current_presentation():
try:
pid = get_current_presentation_id()
if not pid or pid not in presentations:
return None, "No current presentation"
prs = presentations[pid]
import tempfile
tmp = tempfile.NamedTemporaryFile(suffix=".pptx", delete=False)
prs.save(tmp.name)
return tmp.name, f"Saved presentation {pid}"
except Exception as e:
return None, f"❌ Error saving: {str(e)}"
def get_server_status():
"""Get current server status for display"""
try:
# Try to get server info from MCP server
from mcp_server import get_mcp_server_info
server_info = get_mcp_server_info()
config = get_server_config()
status = f"""
🟢 **Server Status**: Running
🔧 **Transport**: {server_info.get('transport', 'streamable-http')}
🌐 **Deployment**: {config.get('deployment_platform', 'unknown')}
📦 **Tools Available**: {server_info.get('total_tools', 0)}+ tools
🎯 **MCP Version**: 2024-11-05
⚡ **Server Version**: {server_info.get('version', '2.1.0')}
📊 **Presentations Loaded**: {server_info.get('loaded_presentations', 0)}
🎮 **Current Presentation**: {server_info.get('current_presentation') or 'None'}
🔗 **MCP Endpoint**: {get_mcp_endpoint_url()}
📁 **Architecture**: {server_info.get('architecture', 'Complete modular')}
🔧 **Utils Modules**: {len(server_info.get('modules', {}).get('utils', []))} modules
🛠️ **Tools Modules**: {len(server_info.get('modules', {}).get('tools', []))} modules
"""
if is_huggingface_spaces():
status += f"""
🤗 **Hugging Face Space**: Active
📍 **Base URL**: {config.get('base_url', 'Unknown')}
"""
return status
except Exception as e:
return f"""
⚠️ **Server Status**: Starting up...
🔗 **MCP Endpoint**: {get_mcp_endpoint_url()}
📝 **Note**: Server may still be initializing. Refresh in a few seconds.
If error persists: {str(e)}
"""
def create_gradio_interface():
"""Create the main Gradio interface for complete PowerPoint MCP Server"""
with gr.Blocks(title="PowerPoint MCP Server - Complete Edition", theme=gr.themes.Soft()) as demo:
gr.Markdown("# 🎯 PowerPoint MCP Server - Complete Edition")
gr.Markdown("**Transport**: Streamable HTTP | **Tools**: 32+ available | **Complete Utils & Tools Integration**")
with gr.Tab("🚀 Test Interface"):
with gr.Row():
with gr.Column():
gr.Markdown("### Create Presentation from JSON")
gr.Markdown("Test the complete PowerPoint functionality:")
slides_input = gr.Textbox(
label="Slides Data (JSON)",
value='{"slides": [{"title": "Welcome to Complete MCP Server", "content": "This uses all existing utils and tools modules"}, {"title": "Professional Features", "content": "• 32+ PowerPoint tools\\n• Complete utils integration\\n• Professional templates\\n• Advanced formatting"}]}',
lines=12,
placeholder='{"slides": [{"title": "Slide Title", "content": "Slide content"}]}'
)
create_btn = gr.Button("🚀 Create Presentation", variant="primary", size="lg")
with gr.Column():
status_output = gr.Textbox(
label="Creation Status",
interactive=False,
lines=3
)
download_btn = gr.DownloadButton(
label="📥 Download PowerPoint",
visible=False,
size="lg"
)
with gr.Tab("🔧 MCP Configuration"):
gr.Markdown("## MCP Server Settings for Your AI Application")
endpoint_url = get_mcp_endpoint_url()
config_info = f"""
### MCP Server Configuration for AI Applications
**Name**: PowerPoint Creator
**Description**: AI-powered PowerPoint presentation generator with 32+ professional tools and complete utils/tools integration
**URL**: {endpoint_url}
**Transport**: Stream
### Copy-Paste Configuration:
{{
"name": "PowerPoint Creator",
"description": "AI-powered PowerPoint presentation generator with 32+ professional tools",
"url": "{endpoint_url}",
"transport": "Stream"
}}
text
### Features Available:
- ✅ **Complete Integration**: All existing utils/ and tools/ modules loaded
- ✅ **Presentation Management** (7 tools) - Create, open, save presentations
- ✅ **Content Management** (8 tools) - Add slides, manage text, extract content
- ✅ **Template Operations** (7 tools) - Professional templates and layouts
- ✅ **Structural Elements** (4 tools) - Tables, shapes, charts
- ✅ **Professional Design** (3 tools) - Themes, effects, fonts
- ✅ **Specialized Features** (5+ tools) - Hyperlinks, transitions, connectors
### Complete Tool Suite:
- **Utils Modules**: core_utils, presentation_utils, content_utils, design_utils, template_utils, validation_utils
- **Tools Modules**: presentation_tools, content_tools, template_tools, structural_tools, professional_tools, hyperlink_tools, chart_tools, connector_tools, master_tools, transition_tools
"""
gr.Markdown(config_info)
with gr.Row():
with gr.Column():
gr.Markdown("### Quick Copy Fields")
gr.Textbox(
value=get_mcp_endpoint_url(),
label="MCP Server URL",
interactive=True,
info="Copy this URL for your AI application"
)
gr.Textbox(
value="Stream",
label="Transport Type",
interactive=False,
info="Select 'Stream' in your AI application"
)
with gr.Column():
gr.Markdown("### Integration Instructions")
gr.Markdown("""
1. **Copy the MCP Server URL** from the field on the left
2. **Open your AI application** (Claude, etc.)
3. **Find the MCP/Tools settings** section
4. **Add a new MCP server** with these settings:
- **Name**: PowerPoint Creator
- **URL**: [paste the URL from left]
- **Transport**: Stream
5. **Save and connect** to start using all PowerPoint tools
""")
with gr.Tab("📊 Server Status"):
status_text = gr.Textbox(
value=get_server_status(),
label="Complete Server Information",
lines=20,
interactive=False
)
refresh_btn = gr.Button("🔄 Refresh Status", variant="secondary")
refresh_btn.click(
fn=get_server_status,
outputs=[status_text]
)
with gr.Tab("🖼️ Preview & Download"):
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### Current Presentation")
pres_dropdown = gr.Dropdown(
choices=[v for (l, v) in list_presentations_for_ui()],
value=get_current_presentation_id(),
label="Select Presentation",
interactive=True
)
reload_list_btn = gr.Button("🔄 Reload Presentations")
render_btn = gr.Button("🖼️ Render Previews", variant="primary")
download_btn2 = gr.DownloadButton(
label="📥 Download .pptx",
visible=True,
size="lg"
)
with gr.Column(scale=3):
previews = gr.Gallery(
label="Slide Previews",
show_label=True,
columns=[2],
height=540,
preview=True
)
render_status = gr.Markdown(visible=True)
# Wire up
def _reload_choices():
items = list_presentations_for_ui()
choices = [v for (l, v) in items]
current = get_current_presentation_id()
if current and current in choices:
value = current
else:
value = choices[0] if choices else None
return gr.update(choices=choices, value=value)
reload_list_btn.click(fn=_reload_choices, outputs=[pres_dropdown])
def _render_for_dropdown(pid):
if not pid:
return [], "No presentation selected"
# Also set current for consistency
try:
set_current_presentation_id(pid)
except Exception:
pass
imgs, msg = render_presentation_to_images(pid)
return imgs, msg
render_btn.click(
fn=_render_for_dropdown,
inputs=[pres_dropdown],
outputs=[previews, render_status]
)
def _download_current():
path, msg = save_current_presentation()
# Return path to populate the DownloadButton
return path
download_btn2.click(fn=_download_current, outputs=[download_btn2])
with gr.Tab("📖 Complete Tools Reference"):
gr.Markdown("## All Available MCP Tools (32+ Tools)")
tools_info = """
### 🎯 Presentation Management (7 tools)
• `create_presentation` • `create_presentation_from_template` • `open_presentation`
• `save_presentation` • `get_presentation_info` • `get_template_file_info` • `set_core_properties`
### 📝 Content Management (8 tools)
• `add_slide` • `get_slide_info` • `extract_slide_text` • `extract_presentation_text`
• `populate_placeholder` • `add_bullet_points` • `manage_text` • `manage_image`
### 🎨 Template Operations (7 tools)
• `list_slide_templates` • `apply_slide_template` • `create_slide_from_template`
• `create_presentation_from_templates` • `get_template_info` • `auto_generate_presentation` • `optimize_slide_text`
### 🏗️ Structural Elements (4 tools)
• `add_table` • `format_table_cell` • `add_shape` • `add_chart`
### ✨ Professional Design (3 tools)
• `apply_professional_design` • `apply_picture_effects` • `manage_fonts`
### 🔧 Specialized Features (5+ tools)
• `manage_hyperlinks` • `manage_slide_masters` • `add_connector` • `update_chart_data` • `manage_slide_transitions`
### 🔧 Utility Tools (3 tools)
• `list_presentations` • `switch_presentation` • `get_server_info`
---
**Total**: 32+ professional PowerPoint manipulation tools with complete utils integration
"""
gr.Markdown(tools_info)
# Event handlers
create_btn.click(
fn=create_presentation_from_json,
inputs=[slides_input],
outputs=[download_btn, status_output]
).then(
fn=lambda x: gr.update(visible=bool(x)),
inputs=[download_btn],
outputs=[download_btn]
)
# Attach middleware to handle root pings like '/?logs=container&__theme=system' with 200 OK
try:
app = demo.app
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import PlainTextResponse
class RootQueryOkMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
try:
if request.url.path == "/":
qp = request.query_params
if "logs" in qp or "__theme" in qp:
return PlainTextResponse("ok")
except Exception:
pass
return await call_next(request)
app.add_middleware(RootQueryOkMiddleware)
except Exception:
# Non-fatal; UI still works even if middleware can't be added
pass
return demo
# Export the interface creation function
__all__ = ['create_gradio_interface']