import gradio as gr import json from graphviz import Digraph import os from tempfile import NamedTemporaryFile def generate_concept_map(json_input: str) -> str: """ Generate concept map from JSON and return as image file Args: json_input (str): JSON describing the concept map structure. REQUIRED FORMAT EXAMPLE: { "central_node": "AI", "nodes": [ { "id": "ml", "label": "Machine Learning", "relationship": "subcategory", "subnodes": [ { "id": "dl", "label": "Deep Learning", "relationship": "type", "subnodes": [ { "id": "cnn", "label": "CNN", "relationship": "architecture" } ] } ] } ] } Returns: str: Path to generated PNG image file """ try: if not json_input.strip(): return "Error: Empty input" data = json.loads(json_input) if 'central_node' not in data or 'nodes' not in data: raise ValueError("Missing required fields: central_node or nodes") # Create graph dot = Digraph( name='ConceptMap', format='png', graph_attr={ 'rankdir': 'TB', 'splines': 'ortho', 'bgcolor': 'transparent' } ) # Central node (ellipse) dot.node( 'central', data['central_node'], shape='ellipse', style='filled', fillcolor='#2196F3', fontcolor='white', fontsize='14' ) # Process nodes (rectangles) for node in data['nodes']: node_id = node.get('id') label = node.get('label') relationship = node.get('relationship') # Validate node if not all([node_id, label, relationship]): raise ValueError(f"Invalid node: {node}") # Create main node (rectangle) dot.node( node_id, label, shape='box', style='filled', fillcolor='#4CAF50', fontcolor='white', fontsize='12' ) # Connect to central node dot.edge( 'central', node_id, label=relationship, color='#9C27B0', fontsize='10' ) # Process subnodes (rectangles with lighter fill) for subnode in node.get('subnodes', []): sub_id = subnode.get('id') sub_label = subnode.get('label') sub_rel = subnode.get('relationship') if not all([sub_id, sub_label, sub_rel]): raise ValueError(f"Invalid subnode: {subnode}") dot.node( sub_id, sub_label, shape='box', style='filled', fillcolor='#FFA726', fontcolor='white', fontsize='10' ) dot.edge( node_id, sub_id, label=sub_rel, color='#E91E63', fontsize='8' ) # Save to temporary file with NamedTemporaryFile(delete=False, suffix='.png') as tmp: dot.render(tmp.name, format='png', cleanup=True) return tmp.name + '.png' except json.JSONDecodeError: return "Error: Invalid JSON format" except Exception as e: return f"Error: {str(e)}" if __name__ == "__main__": # Complex sample JSON sample_json = """ { "central_node": "Artificial Intelligence (AI)", "nodes": [ { "id": "ml", "label": "Machine Learning", "relationship": "Core Component", "subnodes": [ { "id": "sl", "label": "Supervised Learning", "relationship": "Learning Type", "subnodes": [ { "id": "reg", "label": "Regression", "relationship": "Technique", "subnodes": [ {"id": "lr", "label": "Linear Regression", "relationship": "Algorithm"} ] }, { "id": "clf", "label": "Classification", "relationship": "Technique", "subnodes": [ {"id": "svm", "label": "SVM", "relationship": "Algorithm"}, {"id": "rf", "label": "Random Forest", "relationship": "Algorithm"} ] } ] }, { "id": "ul", "label": "Unsupervised Learning", "relationship": "Learning Type", "subnodes": [ { "id": "clus", "label": "Clustering", "relationship": "Technique", "subnodes": [ {"id": "kmeans", "label": "K-Means", "relationship": "Algorithm"} ] } ] } ] }, { "id": "nlp", "label": "NLP", "relationship": "Application Domain", "subnodes": [ { "id": "sa", "label": "Sentiment Analysis", "relationship": "Task", "subnodes": [ {"id": "tb", "label": "Transformer-Based", "relationship": "Approach"} ] } ] } ] } """ demo = gr.Interface( fn=generate_concept_map, inputs=gr.Textbox( value=sample_json, placeholder="Paste JSON following the documented format", label="Structured JSON Input", lines=25 ), outputs=gr.Image( label="Generated Concept Map", type="filepath", show_download_button=True ), title="Advanced Concept Map Generator", description="Create multi-level concept maps from properly formatted JSON" ) demo.launch( mcp_server=True, share=False, server_port=7860, server_name="0.0.0.0" )