import gradio as gr import os import sys import json import requests import re from typing import List, Tuple # 현재 디렉토리와 폰트 파일 경로 설정 CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) FONT_PATH = os.path.join(CURRENT_DIR, 'NanumGothic-Regular.ttf') FONTS_CONF_PATH = os.path.join(CURRENT_DIR, 'fonts.conf') # fonts.conf 파일 생성 함수 def create_fonts_conf(): """Graphviz가 로컬 폰트를 인식할 수 있도록 fonts.conf 파일 생성""" fonts_conf_content = f""" {CURRENT_DIR} NanumGothic NanumGothic-Regular NanumGothic-Regular NanumGothic-Regular NanumGothic NanumGothic-Regular """ with open(FONTS_CONF_PATH, 'w', encoding='utf-8') as f: f.write(fonts_conf_content) print(f"fonts.conf 파일 생성됨: {FONTS_CONF_PATH}") # 폰트 설정 if os.path.exists(FONT_PATH): print(f"한글 폰트 파일 발견: {FONT_PATH}") # fonts.conf 파일 생성 create_fonts_conf() # Graphviz가 폰트를 찾을 수 있도록 환경 변수 설정 os.environ['GDFONTPATH'] = CURRENT_DIR os.environ['FONTCONFIG_PATH'] = CURRENT_DIR os.environ['FONTCONFIG_FILE'] = FONTS_CONF_PATH print(f"GDFONTPATH 설정: {CURRENT_DIR}") print(f"FONTCONFIG_FILE 설정: {FONTS_CONF_PATH}") else: print(f"경고: 한글 폰트 파일을 찾을 수 없습니다: {FONT_PATH}") # 환경 변수로 폰트 경로 저장 (generator들이 사용할 수 있도록) os.environ['KOREAN_FONT_PATH'] = FONT_PATH from concept_map_generator import generate_concept_map from synoptic_chart_generator import generate_synoptic_chart from radial_diagram_generator import generate_radial_diagram from process_flow_generator import generate_process_flow_diagram from wbs_diagram_generator import generate_wbs_diagram from sample_data import CONCEPT_MAP_JSON, SYNOPTIC_CHART_JSON, RADIAL_DIAGRAM_JSON, PROCESS_FLOW_JSON, WBS_DIAGRAM_JSON # 브레이브 서치 API 함수 def brave_search(query: str) -> List[dict]: """브레이브 서치 API를 사용하여 검색 수행""" api_key = os.getenv("BSEARCH_API") if not api_key: print("Warning: BSEARCH_API environment variable not set") return [] url = "https://api.search.brave.com/res/v1/web/search" headers = { "Accept": "application/json", "X-Subscription-Token": api_key } params = { "q": query, "count": 5 # 상위 5개 결과만 가져오기 } try: response = requests.get(url, headers=headers, params=params, timeout=10) if response.status_code == 200: data = response.json() results = [] if "web" in data and "results" in data["web"]: for result in data["web"]["results"][:5]: results.append({ "title": result.get("title", ""), "description": result.get("description", ""), "url": result.get("url", "") }) return results else: print(f"Brave Search API error: {response.status_code}") return [] except Exception as e: print(f"Brave Search error: {str(e)}") return [] def extract_keywords(prompt: str, diagram_type: str) -> str: """프롬프트에서 핵심 키워드 추출""" # 다이어그램 타입 제거 prompt_clean = prompt.lower() # 일반적인 요청 표현 제거 remove_patterns = [ r"create\s+a?\s*", r"make\s+a?\s*", r"generate\s+a?\s*", r"build\s+a?\s*", r"design\s+a?\s*", r"develop\s+a?\s*", r"show\s+me\s*", r"i\s+need\s+a?\s*", r"diagram\s*", r"chart\s*", r"map\s*", r"flow\s*", r"를\s*만들어\s*", r"생성해\s*", r"그려\s*", r"보여\s*", r"설계\s*", r"구축\s*" ] for pattern in remove_patterns: prompt_clean = re.sub(pattern, "", prompt_clean) # 주요 명사와 핵심 용어만 추출 # 간단한 방법: 길이가 3자 이상인 단어들을 추출 words = prompt_clean.split() keywords = [] for word in words: # 불필요한 단어 제외 if len(word) > 2 and word not in ["the", "and", "for", "with", "that", "this", "from", "about"]: keywords.append(word) # 최대 5개 키워드만 사용 return " ".join(keywords[:5]) def enrich_prompt_with_search(prompt: str, diagram_type: str, search_results: List[dict]) -> str: """검색 결과를 바탕으로 프롬프트를 보강""" if not search_results: return prompt # 검색 결과에서 유용한 정보 추출 enrichment = "\n\nAdditional context from web search:\n" for i, result in enumerate(search_results[:3]): # 상위 3개만 사용 enrichment += f"- {result['title']}: {result['description']}\n" enriched_prompt = f"{prompt}{enrichment}" return enriched_prompt # LLM API 함수 수정 def call_llm_api(prompt, diagram_type, use_search=False, search_results=None): """Friendli AI API를 호출하여 JSON 생성""" token = os.environ.get("FRIENDLI_TOKEN") or "YOUR_FRIENDLI_TOKEN" url = "https://api.friendli.ai/dedicated/v1/chat/completions" headers = { "Authorization": "Bearer " + token, "Content-Type": "application/json" } # 다이어그램 타입별 JSON 구조 가이드 - 실제 생성기가 요구하는 정확한 형식 json_guides = { "Concept Map": """Generate a JSON for a concept map with the EXACT following structure: { "central_node": "Main Topic", "nodes": [ { "id": "node1", "label": "First Concept", "relationship": "is part of", "subnodes": [ { "id": "node1_1", "label": "Sub Concept 1", "relationship": "includes", "subnodes": [] } ] }, { "id": "node2", "label": "Second Concept", "relationship": "relates to", "subnodes": [] } ] }""", "Synoptic Chart": """Generate a JSON for a synoptic chart with the EXACT following structure: { "central_node": "Chart Title", "nodes": [ { "id": "phase1", "label": "Phase 1 Name", "relationship": "starts with", "subnodes": [ { "id": "sub1_1", "label": "Sub Item 1", "relationship": "includes", "subnodes": [] } ] } ] }""", "Radial Diagram": """Generate a JSON for a radial diagram with the EXACT following structure: { "central_node": "Central Concept", "nodes": [ { "id": "branch1", "label": "Branch 1", "relationship": "connected to", "subnodes": [ { "id": "item1", "label": "Item 1", "relationship": "example", "subnodes": [] } ] } ] }""", "Process Flow": """Generate a JSON for a process flow diagram with the EXACT following structure: { "start_node": "Start Process", "nodes": [ {"id": "step1", "label": "First Step", "type": "process"}, {"id": "step2", "label": "Decision Point", "type": "decision"}, {"id": "step3", "label": "Another Step", "type": "process"}, {"id": "end", "label": "End Process", "type": "end"} ], "connections": [ {"from": "start_node", "to": "step1", "label": "Begin"}, {"from": "step1", "to": "step2", "label": "Next"}, {"from": "step2", "to": "step3", "label": "Yes"}, {"from": "step3", "to": "end", "label": "Complete"} ] }""", "WBS Diagram": """Generate a JSON for a WBS diagram with the EXACT following structure: { "project_title": "Project Name", "phases": [ { "id": "phase1", "label": "Phase 1", "tasks": [ { "id": "task1_1", "label": "Task 1.1", "subtasks": [ { "id": "subtask1_1_1", "label": "Subtask 1.1.1", "sub_subtasks": [] } ] } ] } ] }""" } # 검색 결과가 있으면 시스템 프롬프트에 추가 search_context = "" if use_search and search_results: search_context = "\n\nUse the following search results to enrich the diagram content:\n" for result in search_results[:3]: search_context += f"- {result['title']}: {result['description']}\n" system_prompt = f"""You are a helpful assistant that generates JSON structures for diagrams. {json_guides.get(diagram_type, '')} {search_context} Important rules: 1. Generate ONLY valid JSON without any explanation or markdown formatting 2. The JSON must follow the EXACT structure shown above - do not change field names 3. Make the content relevant to the user's prompt 4. Use the user's language (Korean or English) for the content values 5. For IDs, use simple alphanumeric strings without spaces (e.g., "node1", "task1_1") 6. Ensure all connections reference existing node IDs 7. For Process Flow: 'type' can be: "process", "decision", "start", "end", "io" 8. For nested structures (Concept Map, Synoptic Chart, Radial, WBS), include empty 'subnodes' or 'subtasks' arrays when there are no children 9. Do not add any additional fields not shown in the example structure 10. If search results are provided, incorporate relevant information into the diagram content""" payload = { "model": "dep89a2fld32mcm", "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": f"Create a {diagram_type} JSON for: {prompt}"} ], "max_tokens": 16384, "top_p": 0.8, "stream": False # 간단하게 처리하기 위해 stream을 False로 설정 } try: response = requests.post(url, json=payload, headers=headers, timeout=30) # API 응답 상태 확인 if response.status_code != 200: return json.dumps({"error": f"API returned status code {response.status_code}: {response.text}"}) response_data = response.json() if 'choices' in response_data and len(response_data['choices']) > 0: content = response_data['choices'][0]['message']['content'] # JSON 부분만 추출 (마크다운 코드 블록 제거) content = content.strip() if content.startswith("```json"): content = content[7:] if content.startswith("```"): content = content[3:] if content.endswith("```"): content = content[:-3] # 추가적인 텍스트 제거 (JSON 외의 설명이 있을 경우) content = content.strip() # JSON 시작 위치 찾기 json_start = content.find('{') if json_start != -1: content = content[json_start:] # JSON 끝 위치 찾기 bracket_count = 0 json_end = -1 for i, char in enumerate(content): if char == '{': bracket_count += 1 elif char == '}': bracket_count -= 1 if bracket_count == 0: json_end = i break if json_end != -1: content = content[:json_end + 1] return content.strip() else: return json.dumps({"error": "No response from LLM"}) except requests.exceptions.Timeout: return json.dumps({"error": "Request timed out"}) except requests.exceptions.RequestException as e: print(f"LLM API Request Error: {str(e)}") return json.dumps({"error": f"Request failed: {str(e)}"}) except Exception as e: print(f"LLM API Error: {str(e)}") return json.dumps({"error": str(e)}) def generate_with_llm(prompt, diagram_type, output_format, use_search): """LLM으로 JSON을 생성하고 다이어그램을 생성""" if not prompt: return None, "Please enter a prompt", "" search_results = [] search_info = "" # 브레이브 서치 사용 시 if use_search: keywords = extract_keywords(prompt, diagram_type) if keywords: search_info = f"🔍 Searching for: {keywords}\n" search_results = brave_search(keywords) if search_results: search_info += f"✅ Found {len(search_results)} relevant results\n\n" for i, result in enumerate(search_results[:3]): search_info += f"{i+1}. {result['title']}\n {result['description'][:100]}...\n\n" else: search_info += "❌ No search results found\n\n" # LLM으로 JSON 생성 (검색 결과 포함) generated_json = call_llm_api(prompt, diagram_type, use_search, search_results) try: # JSON 유효성 검사 json_data = json.loads(generated_json) json_str = json.dumps(json_data, indent=2, ensure_ascii=False) # JSON 구조 검증 if diagram_type in ["Concept Map", "Synoptic Chart", "Radial Diagram"]: if "central_node" not in json_data or "nodes" not in json_data: return None, f"Invalid JSON structure for {diagram_type}. Missing 'central_node' or 'nodes'. Generated JSON:\n{json_str}", search_info elif diagram_type == "Process Flow": if "start_node" not in json_data or "nodes" not in json_data or "connections" not in json_data: return None, f"Invalid JSON structure for Process Flow. Missing 'start_node', 'nodes', or 'connections'. Generated JSON:\n{json_str}", search_info elif diagram_type == "WBS Diagram": if "project_title" not in json_data or "phases" not in json_data: return None, f"Invalid JSON structure for WBS. Missing 'project_title' or 'phases'. Generated JSON:\n{json_str}", search_info # 다이어그램 생성 try: if diagram_type == "Concept Map": diagram = generate_concept_map(json_str, output_format) elif diagram_type == "Synoptic Chart": diagram = generate_synoptic_chart(json_str, output_format) elif diagram_type == "Radial Diagram": diagram = generate_radial_diagram(json_str, output_format) elif diagram_type == "Process Flow": diagram = generate_process_flow_diagram(json_str, output_format) elif diagram_type == "WBS Diagram": diagram = generate_wbs_diagram(json_str, output_format) else: return None, "Invalid diagram type", search_info # 에러 메시지가 반환된 경우 if isinstance(diagram, str) and diagram.startswith("Error:"): return None, f"Diagram generation error: {diagram}\n\nGenerated JSON:\n{json_str}", search_info return diagram, json_str, search_info except Exception as e: return None, f"Error generating diagram: {str(e)}\n\nGenerated JSON:\n{json_str}", search_info except json.JSONDecodeError as e: return None, f"Invalid JSON generated: {str(e)}\n\nGenerated content:\n{generated_json}", search_info except Exception as e: return None, f"Unexpected error: {str(e)}\n\nGenerated content:\n{generated_json}", search_info if __name__ == "__main__": DEFAULT_BASE_COLOR = '#19191a' with gr.Blocks( title="Advanced Graph Generator", theme=gr.themes.Soft( primary_hue="violet", secondary_hue="purple", ), css=""" /* 그라디언트 배경 */ .gradio-container { font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; } /* 메인 컨테이너 스타일 */ .main-container { background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(10px); border-radius: 20px; padding: 30px; margin: 20px auto; max-width: 1400px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); } /* 헤더 스타일 */ .header-section { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 40px; border-radius: 15px; margin-bottom: 30px; text-align: center; box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3); } .header-section h1 { font-size: 2.5em; font-weight: 700; margin-bottom: 10px; } .header-section p { font-size: 1.2em; opacity: 0.9; } /* 탭 스타일 */ .gr-tab-item { padding: 15px 30px; font-size: 1.1em; font-weight: 600; background: white; border-radius: 10px 10px 0 0; transition: all 0.3s ease; margin-right: 5px; } .gr-tab-item:hover { background: linear-gradient(135deg, #f5f7fa 0%, #e9ecef 100%); } .gr-tab-item.selected { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white !important; } /* 버튼 스타일 */ .gr-button { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; padding: 12px 30px; font-size: 1.1em; font-weight: 600; border-radius: 10px; transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3); } .gr-button:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4); } .gr-button.primary { background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); } /* 입력 필드 스타일 */ .gr-textbox, .gr-dropdown { border: 2px solid #e9ecef; border-radius: 10px; padding: 12px; font-size: 1em; transition: all 0.3s ease; background: white; } .gr-textbox:focus, .gr-dropdown:focus { border-color: #667eea; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } /* 패널 스타일 */ .panel-section { background: white; border-radius: 15px; padding: 25px; margin-bottom: 20px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08); } /* 이미지 컨테이너 스타일 */ .gr-image { border-radius: 15px; overflow: hidden; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); } /* 예제 이미지 스타일 */ .example-images { gap: 20px; } .example-images .gr-image { transition: transform 0.3s ease; } .example-images .gr-image:hover { transform: scale(1.02); } /* 라디오 버튼 스타일 */ .gr-radio { background: white; padding: 15px; border-radius: 10px; border: 2px solid #e9ecef; } /* LLM 탭 특별 스타일 */ .llm-tab { background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%); padding: 30px; border-radius: 15px; } .llm-input-section { background: white; padding: 25px; border-radius: 15px; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05); margin-bottom: 20px; } /* 검색 정보 스타일 */ .search-info { background: #f8f9fa; padding: 15px; border-radius: 10px; margin-top: 10px; border-left: 4px solid #667eea; } /* 체크박스 스타일 */ .gr-checkbox { margin-top: 15px; padding: 10px; background: #f8f9fa; border-radius: 8px; } /* 반응형 디자인 */ @media (max-width: 768px) { .main-container { padding: 15px; margin: 10px; } .header-section h1 { font-size: 2em; } .gr-tab-item { padding: 10px 15px; font-size: 1em; } } """ ) as demo: with gr.Column(elem_classes=["main-container"]): with gr.Column(elem_classes=["header-section"]): gr.Markdown( """ # 🎨 ChartGPT : AI-Powered Multi Diagram Generator ### based LLM 'Gemma-3-R1984-27B' Model. Powered by VIDraft⚡ """ ) with gr.Row(variant="panel", elem_classes=["panel-section"]): output_format_radio = gr.Radio( choices=["png", "svg"], value="png", label="📁 Output Format", interactive=True ) with gr.Tabs(): # AI 어시스턴트 탭 (첫 번째) with gr.TabItem("🤖 AI Assistant", elem_classes=["llm-tab"]): gr.Markdown( """ ### 💡 Describe your diagram in Korean or English, and AI will create it for you! """ ) with gr.Row(): with gr.Column(scale=1, elem_classes=["llm-input-section"]): prompt_input = gr.Textbox( placeholder="예: '머신러닝 프로세스를 보여주는 플로우차트를 만들어줘' or 'Create a concept map about climate change'", label="📝 Enter your prompt", lines=3 ) diagram_type_select = gr.Dropdown( choices=["Concept Map", "Synoptic Chart", "Radial Diagram", "Process Flow", "WBS Diagram"], value="Concept Map", label="📊 Select Diagram Type", interactive=True ) use_search_checkbox = gr.Checkbox( label="🔍 Use Brave Search to enhance content", value=False, info="Search the web for relevant information to enrich your diagram" ) generate_btn = gr.Button("✨ Generate with AI", variant="primary", size="lg") search_info_output = gr.Textbox( label="🔍 Search Information", lines=8, interactive=False, visible=False, elem_classes=["search-info"] ) generated_json_output = gr.Textbox( label="📄 Generated JSON", lines=15, interactive=True, visible=True ) with gr.Column(scale=2): ai_output_image = gr.Image( label="🎨 Generated Diagram", type="filepath", show_download_button=True, height=600 ) # 검색 체크박스 변경 시 검색 정보 출력 표시/숨김 def toggle_search_info(use_search): return gr.update(visible=use_search) use_search_checkbox.change( fn=toggle_search_info, inputs=[use_search_checkbox], outputs=[search_info_output] ) generate_btn.click( fn=generate_with_llm, inputs=[prompt_input, diagram_type_select, output_format_radio, use_search_checkbox], outputs=[ai_output_image, generated_json_output, search_info_output] ) with gr.Row(elem_classes=["panel-section"]): gr.Examples( examples=[ ["Design a process flow diagram that maps every phase of the software development life cycle SDLC from requirements to maintenance and shows handoffs and feedback loops to reduce risk and speed delivery", "Process Flow"], ["Develop a concept map that classifies key types of artificial intelligence symbolic, statistical, neural, hybrid and links each to real world application areas such as healthcare diagnostics, autonomous vehicles, predictive maintenance and generative media to highlight unique capabilities", "Concept Map"], ["Build a work breakdown structure WBS diagram for an online shopping mall project that decomposes scope into clear deliverables and work packages including UX research, responsive front end, secure payment integration, logistics APIs and post launch analytics to align resources and schedules", "WBS Diagram"], ["Create a radial diagram with Renewable Energy at the center and spokes for solar, wind, hydro, geothermal and biomass each annotated with core technologies capacity trends and sustainability impact to underline the diversified portfolio needed for a net zero future", "Radial Diagram"], ["Present a synoptic chart of the machine learning pipeline showing data ingestion, cleansing, feature engineering, model training, validation and deployment with parallel swimlanes for tools compute resources and compliance checkpoints to optimize accuracy and governance", "Synoptic Chart"], ["DevSecOps 게이트, 실시간 KPI 대시보드, 자동 롤백 경로, AI 기반 의사 결정 노드를 교차 배치하여 지속적 딜리버리 우수성을 보여주는 엔터프라이즈급 SDLC 프로세스 플로우 시각화를 설계하라", "Process Flow"], ["뉴로심볼릭 추론, 파운데이션 모델, 엣지 AI, 자기 지도 학습과 같은 최첨단 패러다임을 한데 엮고 이를 바이오테크, 스마트 시티, 기후 모델링, 몰입형 XR 등 파괴적 활용 사례와 동적으로 연결하여 최전선 혁신을 조명하는 방대한 AI 분류 체계 컨셉맵을 구축하라", "Concept Map"], ["마이크로서비스, CI/CD 파이프라인, 헤드리스 CMS 통합, 초개인화 추천 엔진 튜닝, 멀티 클라우드 회복탄력성까지 세분화하여 작업량 추정과 크리티컬 패스 상호 연계를 주석으로 담은 차세대 옴니채널 e-커머스 생태계를 위한 초정밀 WBS를 설계하라", "WBS Diagram"], ["재생에너지를 중심으로 다층 방사형 다이어그램을 그리되, 발전·저장·송배전·정책 수단을 동심원으로 배치하고, 태양광 PV, CSP, 해상 및 육상풍력, 조력, 그린 수소, 차세대 배터리 화학, 스마트 그리드 AI를 스포크로 연결하며, LCOE 추세와 탄소 상쇄 시나리오를 주석으로 표기하라", "Radial Diagram"], ["데이터 출처, 자동화된 피처 스토어, 연합 학습 오케스트레이션, 모델 성능 드리프트 감지, 섀도 배포 캐너리, 윤리 준수 감사의 시간 축 레인을 병치하고, 게이트 기준과 롤백 트리거까지 갖춘 프로덕션급 머신러닝 파이프라인을 영화적 시야로 묘사한 시놉틱 차트를 제작하라", "Synoptic Chart"] ], inputs=[prompt_input, diagram_type_select], label="💭 Example Prompts" ) # 기존 탭들 with gr.TabItem("🗺️ Concept Map"): with gr.Row(): with gr.Column(scale=1, elem_classes=["panel-section"]): json_input_cm = gr.Textbox( value=CONCEPT_MAP_JSON, placeholder="Paste JSON following the documented format", label="JSON Input", lines=20 ) submit_btn_cm = gr.Button("Generate Concept Map", variant="primary") with gr.Column(scale=2): output_cm = gr.Image( label="Generated Diagram", type="filepath", show_download_button=True, height=500 ) submit_btn_cm.click( fn=generate_concept_map, inputs=[json_input_cm, output_format_radio], outputs=output_cm ) gr.Markdown("## 📸 Examples") with gr.Row(elem_classes=["example-images"]): gr.Image(value="./images/cm1.svg", label="Sample 1", show_label=True, interactive=False) gr.Image(value="./images/cm2.svg", label="Sample 2", show_label=True, interactive=False) with gr.TabItem("📊 Synoptic Chart"): with gr.Row(): with gr.Column(scale=1, elem_classes=["panel-section"]): json_input_sc = gr.Textbox( value=SYNOPTIC_CHART_JSON, placeholder="Paste JSON following the documented format", label="JSON Input", lines=20 ) submit_btn_sc = gr.Button("Generate Synoptic Chart", variant="primary") with gr.Column(scale=2): output_sc = gr.Image( label="Generated Diagram", type="filepath", show_download_button=True, height=500 ) submit_btn_sc.click( fn=generate_synoptic_chart, inputs=[json_input_sc, output_format_radio], outputs=output_sc ) gr.Markdown("## 📸 Examples") with gr.Row(elem_classes=["example-images"]): gr.Image(value="./images/sc1.svg", label="Sample 1", show_label=True, interactive=False) gr.Image(value="./images/sc2.svg", label="Sample 2", show_label=True, interactive=False) with gr.TabItem("☀️ Radial Diagram"): with gr.Row(): with gr.Column(scale=1, elem_classes=["panel-section"]): json_input_rd = gr.Textbox( value=RADIAL_DIAGRAM_JSON, placeholder="Paste JSON following the documented format", label="JSON Input", lines=20 ) submit_btn_rd = gr.Button("Generate Radial Diagram", variant="primary") with gr.Column(scale=2): output_rd = gr.Image( label="Generated Diagram", type="filepath", show_download_button=True, height=500 ) submit_btn_rd.click( fn=generate_radial_diagram, inputs=[json_input_rd, output_format_radio], outputs=output_rd ) gr.Markdown("## 📸 Examples") with gr.Row(elem_classes=["example-images"]): gr.Image(value="./images/rd1.svg", label="Sample 1", show_label=True, interactive=False) gr.Image(value="./images/rd2.svg", label="Sample 2", show_label=True, interactive=False) gr.Image(value="./images/rd3.svg", label="Sample 3", show_label=True, interactive=False) gr.Image(value="./images/rd4.svg", label="Sample 4", show_label=True, interactive=False) with gr.TabItem("🔄 Process Flow"): with gr.Row(): with gr.Column(scale=1, elem_classes=["panel-section"]): json_input_pf = gr.Textbox( value=PROCESS_FLOW_JSON, placeholder="Paste JSON following the documented format", label="JSON Input", lines=20 ) submit_btn_pf = gr.Button("Generate Process Flow", variant="primary") with gr.Column(scale=2): output_pf = gr.Image( label="Generated Diagram", type="filepath", show_download_button=True, height=500 ) submit_btn_pf.click( fn=generate_process_flow_diagram, inputs=[json_input_pf, output_format_radio], outputs=output_pf ) gr.Markdown("## 📸 Examples") with gr.Row(elem_classes=["example-images"]): gr.Image(value="./images/pf1.svg", label="Sample 1", show_label=True, interactive=False) gr.Image(value="./images/pf2.svg", label="Sample 2", show_label=True, interactive=False) with gr.TabItem("📋 WBS Diagram"): with gr.Row(): with gr.Column(scale=1, elem_classes=["panel-section"]): json_input_wbs = gr.Textbox( value=WBS_DIAGRAM_JSON, placeholder="Paste JSON following the documented format", label="JSON Input", lines=20 ) submit_btn_wbs = gr.Button("Generate WBS Diagram", variant="primary") with gr.Column(scale=2): output_wbs = gr.Image( label="Generated Diagram", type="filepath", show_download_button=True, height=500 ) submit_btn_wbs.click( fn=generate_wbs_diagram, inputs=[json_input_wbs, output_format_radio], outputs=output_wbs ) gr.Markdown("## 📸 Examples") with gr.Row(elem_classes=["example-images"]): gr.Image(value="./images/wd1.svg", label="Sample 1", show_label=True, interactive=False) gr.Image(value="./images/wd2.svg", label="Sample 2", show_label=True, interactive=False) demo.launch( mcp_server=True, share=False, server_port=7860, server_name="0.0.0.0" )