import textwrap class CodeGenerator: @staticmethod def generate_gradio_app(function_code: str, inputs: dict, output_desc: str) -> str: """ Generates the complete code for a Gradio application from a function snippet. Args: function_code: The source code of the main function (e.g. def count_r(word): ...) inputs: Dict describing inputs (e.g. {"word": "text"}) output_desc: Description of the output Returns: The complete source code for app.py """ # Simple analysis to find the function name (very naive for now) # We assume the code contains "def function_name(" import re match = re.search(r"def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(", function_code) func_name = match.group(1) if match else "main_function" # Mapping MCP/JSON types to Gradio types # For simplicity, we map everything to Text for now or use direct strings # TODO: Improve type mapping # Code construction template = f""" import gradio as gr import json # --- User Defined Logic --- {function_code} # --- Gradio Interface --- # Wrapper to handle types if necessary def wrapper(*args): result = {func_name}(*args) return str(result) # Force string output for simplicity # Gradio inputs configuration # Note: This part is generic for the MVP. # Ideally, iterate over 'inputs' to create corresponding Gradio components. iface = gr.Interface( fn=wrapper, inputs=[gr.Textbox(label=k) for k in {list(inputs.keys())}], outputs=gr.Textbox(label="{output_desc}"), title="Meta-MCP Generated Tool", description="Auto-generated by Meta-MCP Fractal" ) if __name__ == "__main__": iface.launch(mcp_server=True, show_error=True) """ return textwrap.dedent(template).strip() @staticmethod def generate_tool_module(function_code: str, inputs: dict, output_desc: str, tool_name: str, output_component: str = "text") -> str: """ Generates a Python module containing the tool logic and an interface factory. """ import re match = re.search(r"def\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(", function_code) func_name = match.group(1) if match else "main_function" inputs_keys_str = str(list(inputs.keys())) # Output component mapping if output_component == "image": gradio_output = 'gr.Image(type="filepath", label="__OUTPUT_DESC__")' elif output_component == "audio": gradio_output = 'gr.Audio(type="filepath", label="__OUTPUT_DESC__")' elif output_component == "video": gradio_output = 'gr.Video(label="__OUTPUT_DESC__")' elif output_component == "html": gradio_output = 'gr.HTML(label="__OUTPUT_DESC__")' elif output_component == "json": gradio_output = 'gr.JSON(label="__OUTPUT_DESC__")' elif output_component == "file": gradio_output = 'gr.File(label="__OUTPUT_DESC__")' else: # text gradio_output = 'gr.Textbox(label="__OUTPUT_DESC__")' template = """ import gradio as gr import json from PIL import Image import numpy as np # --- User Defined Logic --- __FUNCTION_CODE__ # --- Interface Factory --- def create_interface(): return gr.Interface( fn=__FUNC_NAME__, inputs=[gr.Textbox(label=k) for k in __INPUTS_KEYS__], outputs=__GRADIO_OUTPUT__, title="__TOOL_NAME__", description="Auto-generated tool: __TOOL_NAME__" ) """ code = textwrap.dedent(template).strip() code = code.replace("__FUNCTION_CODE__", function_code) code = code.replace("__FUNC_NAME__", func_name) code = code.replace("__INPUTS_KEYS__", inputs_keys_str) code = code.replace("__GRADIO_OUTPUT__", gradio_output) # Dynamic component injection code = code.replace("__OUTPUT_DESC__", output_desc) code = code.replace("__TOOL_NAME__", tool_name) return code @staticmethod def generate_master_app() -> str: """ Generates the main app.py file. Uses a standard approach with simple dynamic import. """ template = """ import gradio as gr import os import sys import importlib # Configuration TOOLS_DIR = "tools" # Ensure tools directory exists and is a package if not os.path.exists(TOOLS_DIR): os.makedirs(TOOLS_DIR, exist_ok=True) with open(os.path.join(TOOLS_DIR, "__init__.py"), "w") as f: pass # Add current directory to path so 'import tools.xxx' works sys.path.append(os.path.dirname(os.path.abspath(__file__))) interfaces = [] names = [] print(f"🚀 Starting Meta-MCP Toolbox...") print(f"📂 Scanning '{TOOLS_DIR}' directory...") # Scan and import tools try: for filename in sorted(os.listdir(TOOLS_DIR)): if filename.endswith(".py") and not filename.startswith("_"): module_name = filename[:-3] full_module_name = f"{TOOLS_DIR}.{module_name}" try: print(f" 👉 Importing {full_module_name}...") # Standard dynamic import # Use reload to ensure latest version is taken if restarted module = importlib.import_module(full_module_name) importlib.reload(module) if hasattr(module, "create_interface"): # Create Gradio interface for this tool tool_interface = module.create_interface() interfaces.append(tool_interface) names.append(module_name) print(f" ✅ Loaded {module_name}") else: print(f" ⚠️ Module {module_name} has no create_interface()") except Exception as e: print(f" ❌ Error loading {module_name}: {e}") import traceback traceback.print_exc() except Exception as e: print(f"Error scanning tools directory: {e}") # Final interface construction if not interfaces: demo = gr.Interface( fn=lambda x: "No tools loaded yet. Add a tool via Meta-MCP!", inputs="text", outputs="text", title="Empty Toolbox", description="This Space is ready to receive tools." ) else: demo = gr.TabbedInterface(interfaces, names) if __name__ == "__main__": demo.launch(mcp_server=True, show_error=True) """ return textwrap.dedent(template).strip() @staticmethod def generate_mcp_server_code(function_code: str) -> str: """ Generates an MCP server (FastMCP) instead of Gradio (Future feature). """ pass