UNPKG

hatch-slidev-builder-mcp

Version:

A comprehensive MCP server for creating Slidev presentations with component library, interactive elements, and team collaboration features

262 lines (248 loc) 9.97 kB
import * as fs from 'fs-extra'; import * as path from 'path'; import { spawn } from 'child_process'; export async function generateChart(args) { const { chartType, data, pythonScript, outputPath, styling = {} } = args; try { // Ensure output directory exists const chartDir = path.join(outputPath, 'public', 'charts'); await fs.ensureDir(chartDir); // Hatch brand colors const hatchColors = { primary: '#00A651', secondary: '#004225', accent: '#FFB800', gray: '#6B7280' }; let chartConfig = { type: chartType, data: data, styling: { colors: [hatchColors.primary, hatchColors.secondary, hatchColors.accent], width: styling.width || 800, height: styling.height || 400, title: styling.title || 'Chart', theme: styling.theme || 'hatch', ...styling } }; if (pythonScript) { // Use custom Python script return await executeCustomPythonScript(pythonScript, chartConfig, outputPath); } else { // Use built-in chart generation return await generateBuiltInChart(chartType, chartConfig, outputPath); } } catch (error) { throw new Error(`Failed to generate chart: ${error instanceof Error ? error.message : String(error)}`); } } async function generateBuiltInChart(chartType, config, outputPath) { const chartId = `chart_${Date.now()}`; const chartPath = path.join(outputPath, 'public', 'charts', `${chartId}.json`); // Save chart configuration await fs.writeFile(chartPath, JSON.stringify(config, null, 2)); // Generate Python script for chart creation const pythonScript = generatePythonChartScript(chartType, config, chartId); const scriptPath = path.join(outputPath, 'python', `generate_${chartId}.py`); await fs.writeFile(scriptPath, pythonScript); // Execute Python script if Python is available try { await executePythonScript(scriptPath); return { content: [ { type: 'text', text: `📊 Successfully generated ${chartType} chart\n\n` + `📁 Chart ID: ${chartId}\n` + `📐 Size: ${config.styling.width}x${config.styling.height}\n` + `🎨 Theme: ${config.styling.theme}\n` + `📄 Config saved: /public/charts/${chartId}.json\n` + `🐍 Python script: /python/generate_${chartId}.py\n\n` + `To use in slides, add:\n\`\`\`html\n<img src="/charts/${chartId}.png" alt="${config.styling.title}" />\n\`\`\`` } ] }; } catch (pythonError) { // Python execution failed, return configuration for manual generation return { content: [ { type: 'text', text: `📊 Chart configuration generated (Python execution failed)\n\n` + `📁 Chart ID: ${chartId}\n` + `📄 Config saved: /public/charts/${chartId}.json\n` + `🐍 Python script generated: /python/generate_${chartId}.py\n\n` + `To generate chart manually:\n` + `1. Install requirements: pip install -r python/requirements.txt\n` + `2. Run: python python/generate_${chartId}.py\n\n` + `Chart Configuration:\n\`\`\`json\n${JSON.stringify(config, null, 2)}\n\`\`\`` } ] }; } } async function executeCustomPythonScript(script, config, outputPath) { const scriptId = `custom_${Date.now()}`; const scriptPath = path.join(outputPath, 'python', `${scriptId}.py`); // Write custom script await fs.writeFile(scriptPath, script); // Write config file for script to use const configPath = path.join(outputPath, 'python', `${scriptId}_config.json`); await fs.writeFile(configPath, JSON.stringify(config, null, 2)); try { await executePythonScript(scriptPath); return { content: [ { type: 'text', text: `🐍 Successfully executed custom Python script\n\n` + `📁 Script ID: ${scriptId}\n` + `📄 Script: /python/${scriptId}.py\n` + `⚙️ Config: /python/${scriptId}_config.json\n\n` + `Check the /public/charts/ directory for generated files.` } ] }; } catch (error) { return { content: [ { type: 'text', text: `❌ Custom Python script execution failed\n\n` + `📁 Script ID: ${scriptId}\n` + `📄 Script saved: /python/${scriptId}.py\n` + `⚙️ Config saved: /python/${scriptId}_config.json\n` + `❗ Error: ${error}\n\n` + `To run manually: python python/${scriptId}.py` } ] }; } } function generatePythonChartScript(chartType, config, chartId) { return `#!/usr/bin/env python3 """ Auto-generated chart script for Slidev presentation Chart ID: ${chartId} Chart Type: ${chartType} """ import matplotlib.pyplot as plt import numpy as np import pandas as pd import json from pathlib import Path # Hatch brand colors HATCH_COLORS = { 'primary': '#00A651', 'secondary': '#004225', 'accent': '#FFB800', 'gray': '#6B7280' } def generate_${chartType}_chart(): """Generate ${chartType} chart with Hatch styling""" # Set up the plot plt.style.use('seaborn-v0_8') fig, ax = plt.subplots(figsize=(${config.styling.width / 100}, ${config.styling.height / 100})) # Chart data ${generateChartDataCode(chartType, config.data)} # Styling ax.set_title('${config.styling.title}', fontsize=16, fontweight='bold', color=HATCH_COLORS['secondary']) # Apply Hatch color scheme ${generateChartStylingCode(chartType)} # Save chart output_dir = Path("./public/charts") output_dir.mkdir(exist_ok=True) plt.tight_layout() plt.savefig(output_dir / "${chartId}.png", dpi=300, bbox_inches='tight', facecolor='white', edgecolor='none') plt.savefig(output_dir / "${chartId}.svg", format='svg', bbox_inches='tight', facecolor='white', edgecolor='none') print(f"Chart saved: {chartId}.png and {chartId}.svg") plt.close() if __name__ == "__main__": generate_${chartType}_chart() `; } function generateChartDataCode(chartType, data) { switch (chartType) { case 'bar': return ` labels = ${JSON.stringify(data.labels || ['A', 'B', 'C', 'D'])} values = ${JSON.stringify(data.values || [10, 20, 15, 25])} bars = ax.bar(labels, values, color=HATCH_COLORS['primary']) # Add value labels on bars for bar in bars: height = bar.get_height() ax.text(bar.get_x() + bar.get_width()/2., height, f'{height:.1f}', ha='center', va='bottom')`; case 'line': return ` x_data = ${JSON.stringify(data.x || [1, 2, 3, 4, 5])} y_data = ${JSON.stringify(data.y || [10, 15, 13, 17, 20])} ax.plot(x_data, y_data, color=HATCH_COLORS['primary'], linewidth=3, marker='o', markersize=8) ax.grid(True, alpha=0.3)`; case 'scatter': return ` x_data = ${JSON.stringify(data.x || [1, 2, 3, 4, 5])} y_data = ${JSON.stringify(data.y || [10, 15, 13, 17, 20])} ax.scatter(x_data, y_data, color=HATCH_COLORS['primary'], s=100, alpha=0.7)`; case 'pie': return ` labels = ${JSON.stringify(data.labels || ['A', 'B', 'C', 'D'])} values = ${JSON.stringify(data.values || [30, 25, 20, 25])} colors = [HATCH_COLORS['primary'], HATCH_COLORS['secondary'], HATCH_COLORS['accent'], HATCH_COLORS['gray']] ax.pie(values, labels=labels, colors=colors, autopct='%1.1f%%', startangle=90)`; default: return ` # Default chart data labels = ['Category 1', 'Category 2', 'Category 3'] values = [10, 20, 15] ax.bar(labels, values, color=HATCH_COLORS['primary'])`; } } function generateChartStylingCode(chartType) { if (chartType === 'pie') { return `ax.axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle`; } return ` ax.set_xlabel('X Axis', fontweight='bold') ax.set_ylabel('Y Axis', fontweight='bold') ax.tick_params(colors=HATCH_COLORS['gray']) # Style the spines for spine in ax.spines.values(): spine.set_color(HATCH_COLORS['gray']) spine.set_linewidth(0.5)`; } async function executePythonScript(scriptPath) { return new Promise((resolve, reject) => { const python = spawn('python', [scriptPath], { stdio: ['pipe', 'pipe', 'pipe'] }); let stdout = ''; let stderr = ''; python.stdout.on('data', (data) => { stdout += data.toString(); }); python.stderr.on('data', (data) => { stderr += data.toString(); }); python.on('close', (code) => { if (code === 0) { resolve(); } else { reject(`Python script failed with code ${code}: ${stderr}`); } }); python.on('error', (error) => { reject(`Failed to execute Python script: ${error.message}`); }); }); }