import gradio as gr import json from graphviz import Digraph import os from typing import Dict, Any def generate_concept_map(json_input: str) -> str: """ Generates a concept map from structured JSON Args: json_input (str): JSON describing the concept map structure. Required format: { "central_node": "Main concept", "nodes": [ { "id": "unique_identifier", "label": "Node label", "relationship": "Relationship to parent", "subnodes": [...] # Optional } ] } Returns: str: Image URL (compatible with Hugging Face Spaces) Raises: ValueError: If JSON doesn't meet required format """ try: # Validate input if not json_input.strip(): return "Error: Empty input" data = json.loads(json_input) # Validate basic structure 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 dot.node( 'central', data['central_node'], shape='ellipse', style='filled', fillcolor='#2196F3', fontcolor='white', fontsize='14' ) # Process nodes 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 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 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='diamond', style='filled', fillcolor='#FF5722', fontcolor='white', fontsize='10' ) dot.edge( node_id, sub_id, label=sub_rel, color='#E91E63', fontsize='8' ) # Save image filename = f"/tmp/concept_map_{hash(json_input)}.gv" dot.render(filename, format='png', cleanup=True) # Return Hugging Face compatible URL return f"/file={filename}.png" except json.JSONDecodeError: return "Error: Invalid JSON" except Exception as e: return f"Error: {str(e)}" if __name__ == "__main__": demo = gr.Interface( fn=generate_concept_map, inputs=gr.Textbox( placeholder="Paste structured JSON here...", label="JSON Input", lines=15 ), outputs=gr.Textbox( label="Image URL", placeholder="Concept map URL will be generated here" ), title="Concept Map Generator", description="Create concept maps from JSON (Claude compatible)" ) # Hugging Face Spaces configuration demo.launch( mcp_server=True, share=False, server_port=int(os.getenv('PORT', 7860)), server_name="0.0.0.0" )