Spaces:
Sleeping
Sleeping
| """ | |
| 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'] |