""" 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']