diff love/pdf-mcp/main.py @ 38:cf9caa4abc3e

[Love] FE and BE. Can chat and render images. Also created MCP for powerpoint generations.
author MrJuneJune <me@mrjunejune.com>
date Mon, 01 Dec 2025 20:35:56 -0800
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/love/pdf-mcp/main.py	Mon Dec 01 20:35:56 2025 -0800
@@ -0,0 +1,240 @@
+import os
+import uuid
+import json
+import re
+from typing import List, Dict
+import markdown2
+import requests
+
+from fastmcp import FastMCP
+
+from xai_sdk import Client
+from xai_sdk.chat import system, user
+from xai_sdk.tools import web_search, code_execution
+
+# Configuration
+XAI_API_KEY = os.getenv("XAI_API_KEY", "no_api")
+OUTPUT_DIR = os.getenv("DECK_OUTPUT_DIR", "generated_decks")
+
+# Initialize xAI client
+xai_client = Client(api_key=XAI_API_KEY)
+
+# Create FastMCP server
+mcp = FastMCP("presentation-generator", port=7776, host="0.0.0.0")
+
+def generate_summary_and_images(topic: str) -> Dict[str, any]:
+    """Use Grok API with live_search to summarize + extract image URLs."""
+    chat = xai_client.chat.create(
+        model="grok-4-fast",
+        tools=[
+            web_search(),
+            code_execution(),
+        ],  
+    )
+    
+    SYSTEM_PROMPT = """
+    You are an expert presentation creator. For the given topic, use live_search to fetch the latest, reliable web sources.
+    Summarize into concise, impactful bullet points perfect for slides (max 6-8 lines per slide, start with strong verbs/key facts).
+    Use markdown bullets. Separate logical sections with --- for slide breaks.
+    Also, search for 10-15 high-quality, royalty-free images (e.g., from Unsplash/Pexels via web search) suitable for slides—focus on visuals like diagrams/infographics.
+    Respond ONLY in valid JSON: {"summary": "markdown summary here", "images": ["url1", "url2", ...]}.
+    Ensure images are direct .jpg/.png links, diverse, and relevant.
+    """
+   
+    chat.append(system(SYSTEM_PROMPT))
+    response = chat.append(user(f"Topic: {topic}"))
+    
+    try:
+        content = response.sample().content
+        data = json.loads(content)
+        return data
+    except json.JSONDecodeError:
+        # Fallback: Parse markdown and extract URLs via regex
+        # Shouldn't really get here...
+        summary = content.split("images:")[0].strip() if "images:" in content else content
+        img_pattern = r'https?://[^\s<>"]+\.(?:jpg|jpeg|png|gif)(?:\?[^\s<>"]*)?'
+        images = re.findall(img_pattern, content)
+        return {"summary": summary, "images": images[:15]}
+
+def download_image(url: str, path: str) -> bool:
+    """Download image to local path."""
+    try:
+        img_data = requests.get(url, timeout=10).content
+        os.makedirs(os.path.dirname(path), exist_ok=True)
+        with open(path, 'wb') as f:
+            f.write(img_data)
+        return True
+    except:
+        return False
+
+def create_presentation_deck(topic: str) -> Dict[str, str | int]:
+    """Generate a complete presentation deck."""
+    # Create unique deck directory
+    deck_id = str(uuid.uuid4())[:8]
+    deck_dir = os.path.join(OUTPUT_DIR, deck_id)
+    os.makedirs(deck_dir, exist_ok=True)
+    os.makedirs(os.path.join(deck_dir, "img"), exist_ok=True)
+
+    # Generate summary and images using Grok
+    data = generate_summary_and_images(topic)
+    markdown_summary = data["summary"]
+    image_urls = data.get("images", [])
+
+    # Download images
+    local_images = []
+    for i, url in enumerate(image_urls[:15]):
+        ext = url.split(".")[-1].split("?")[0] or "jpg"
+        path = os.path.join(deck_dir, "img", f"{i}.{ext}")
+        if download_image(url, path):
+            local_images.append(f"img/{i}.{ext}")
+        else:
+            local_images.append(url)
+
+    # Split summary into slides
+    raw_slides = markdown_summary.split("---")
+    slides = []
+    for i, raw in enumerate(raw_slides):
+        html_content = markdown2.markdown(raw.strip())
+        img = local_images[i] if i < len(local_images) else (local_images[-1] if local_images else "")
+        slides.append({"content": html_content, "image": img})
+
+    # Generate Reveal.js HTML
+    html_content = generate_reveal_html(topic, slides)
+    
+    deck_path = os.path.join(deck_dir, "index.html")
+    with open(deck_path, "w", encoding="utf-8") as f:
+        f.write(html_content)
+
+    # Get absolute path
+    abs_path = os.path.abspath(deck_path)
+    
+    return {
+        "deck_id": deck_id,
+        "deck_path": abs_path,
+        "markdown_summary": markdown_summary,
+        "num_slides": len(slides),
+        "images_count": len(local_images)
+    }
+
+def generate_reveal_html(topic: str, slides: List[Dict]) -> str:
+    """Generate Reveal.js HTML presentation."""
+    slides_html = ""
+    for slide in slides:
+        img_html = f'<img src="{slide["image"]}" style="max-width:50%;float:right;margin-left:20px;" />' if slide["image"] else ""
+        slides_html += f"""
+        <section>
+            {img_html}
+            <div style="text-align:left;">
+                {slide["content"]}
+            </div>
+        </section>
+        """
+    
+    return f"""<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <title>{topic} - Presentation</title>
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.3.1/reset.min.css">
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.3.1/reveal.min.css">
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.3.1/theme/black.min.css">
+</head>
+<body>
+    <div class="reveal">
+        <div class="slides">
+            <section>
+                <h1>{topic}</h1>
+                <p>Generated with Grok AI</p>
+            </section>
+            {slides_html}
+        </div>
+    </div>
+    <script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.3.1/reveal.min.js"></script>
+    <script>
+        Reveal.initialize({{
+            hash: true,
+            transition: 'slide'
+        }});
+    </script>
+</body>
+</html>"""
+
+# FastMCP Tool Definitions
[email protected]()
+def generate_presentation(topic: str) -> str:
+     """Generate a complete PowerPoint-style presentation deck on any topic using Grok AI.
+
+     This tool researches the topic using live web search, creates concise slide content,
+     finds relevant images, and generates a Reveal.js HTML presentation that can be viewed in browser.
+
+     Args:
+         topic: The topic for the presentation (e.g., 'Dogs in Korea', 'Quantum Computing Basics', 'Climate Change
+
+     Returns:
+         A summary of the generated presentation with file location
+     """
+     try:
+         result = create_presentation_deck(topic)
+
+         # Construct the public URL
+         public_url = f"https://slide.babocoder.com/{result['deck_id']}/index.html"
+
+         # Return JSON response as Grok expects with instruction
+         json_response = json.dumps({
+             "file_path": public_url,
+             "type": "html",
+             "metadata": {
+                 "topic": topic,
+                 "deck_id": result['deck_id'],
+                 "num_slides": result['num_slides'],
+                 "images_count": result['images_count']
+             }
+         })
+
+         response = f"""Presentation generated successfully!
+
+ IMPORTANT: Return this exact JSON response to the client:
+
+ {json_response}
+
+ DO NOT modify or explain this JSON. Send it directly to the client as-is."""
+
+         return response
+     except Exception as e:
+         return f"❌ Error generating presentation: {str(e)}\n\nPlease try again or check the server logs."
+ 
[email protected]()
+def get_presentation_preview(topic: str) -> str:
+    """Generate just the markdown summary for a presentation topic without creating the full deck.
+    
+    Useful for previewing content or getting a quick outline before generating slides.
+    
+    Args:
+        topic: The topic to preview
+    
+    Returns:
+        A markdown preview of the presentation content
+    """
+    try:
+        data = generate_summary_and_images(topic)
+        markdown_summary = data["summary"]
+        
+        response = f"""📝 Presentation Preview for: {topic}
+
+{markdown_summary}
+
+---
+**Images Found:** {len(data.get('images', []))}
+
+To generate the full presentation deck with slides, use the 'generate_presentation' tool.
+"""
+        return response
+        
+    except Exception as e:
+        return f"❌ Error generating preview: {str(e)}"
+
+if __name__ == "__main__":
+    os.makedirs(OUTPUT_DIR, exist_ok=True)
+    mcp.run(transport="streamable-http")
+
+