Spaces:
Running
Running
| import gradio as gr | |
| import os | |
| from dotenv import load_dotenv | |
| # Load environment variables from .env file | |
| load_dotenv() | |
| from agent.pipeline import ( | |
| analyze_change, | |
| oob_troubleshoot, | |
| perform_recovery, | |
| load_synapse_log, | |
| reset_state, | |
| ) | |
| from agent import topology | |
| from agent.network_ops import ( | |
| get_lab_topology, | |
| get_lab_projects, | |
| manage_device, | |
| get_device_configuration, | |
| configure_device, | |
| backup_device_config, | |
| build_network_from_description, | |
| ) | |
| import json | |
| VINE_CSS = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@600;700&family=Inter:wght@400;500;600&display=swap'); | |
| :root { | |
| --og-green: #2d5f4f; | |
| --og-mint: #5fc9a0; | |
| --og-fern: #3d8169; | |
| --og-cream: #fafaf8; | |
| --og-shadow: 0 8px 24px rgba(45,95,79,0.12); | |
| } | |
| html, body { overflow-x: hidden; } | |
| body, .gradio-container { | |
| font-family: 'Inter', system-ui, -apple-system, sans-serif; | |
| background: | |
| repeating-linear-gradient(45deg, transparent, transparent 35px, rgba(95,201,160,0.03) 35px, rgba(95,201,160,0.03) 70px), | |
| repeating-linear-gradient(-45deg, transparent, transparent 35px, rgba(61,129,105,0.03) 35px, rgba(61,129,105,0.03) 70px), | |
| linear-gradient(to bottom, #fafaf8, #f5f7f5); | |
| color: #1a2f26; | |
| position: relative; | |
| min-height: 100vh; | |
| } | |
| .gradio-container > * { position: relative; z-index: 1; } | |
| .og-hero { | |
| background: | |
| linear-gradient(135deg, rgba(95,201,160,0.08), rgba(61,129,105,0.12)), | |
| repeating-linear-gradient(45deg, transparent, transparent 20px, rgba(95,201,160,0.04) 20px, rgba(95,201,160,0.04) 40px), | |
| repeating-linear-gradient(-45deg, transparent, transparent 20px, rgba(61,129,105,0.04) 20px, rgba(61,129,105,0.04) 40px), | |
| linear-gradient(to bottom right, #f8faf9, #f0f5f2); | |
| border: 1px solid rgba(95,201,160,0.2); | |
| box-shadow: var(--og-shadow); | |
| border-radius: 16px; | |
| padding: 28px; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .og-hero::before { | |
| content: ""; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| height: 3px; | |
| background: linear-gradient(90deg, transparent, var(--og-mint), transparent); | |
| opacity: 0.6; | |
| } | |
| .og-hero::after { | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| background: | |
| radial-gradient(circle at 20% 30%, rgba(95,201,160,0.08), transparent 40%), | |
| radial-gradient(circle at 80% 70%, rgba(61,129,105,0.08), transparent 40%); | |
| pointer-events: none; | |
| } | |
| .og-hero::after { | |
| inset: auto auto -120px -20px; | |
| transform: rotate(8deg); | |
| } | |
| .og-hero h2 { | |
| font-family: 'Playfair Display', serif; | |
| font-size: 28px; | |
| color: var(--og-green); | |
| letter-spacing: 0.02em; | |
| } | |
| .og-hero p { | |
| margin: 0; | |
| font-size: 15px; | |
| color: #0f4d3f; | |
| max-width: 720px; | |
| } | |
| .og-pill { | |
| display: inline-block; | |
| padding: 7px 12px; | |
| border-radius: 999px; | |
| background: rgba(60,191,154,0.22); | |
| color: var(--og-green); | |
| font-weight: 700; | |
| margin: 0 10px 8px 0; | |
| letter-spacing: 0.01em; | |
| box-shadow: 0 8px 18px rgba(0,0,0,0.06); | |
| } | |
| .og-panel { | |
| background: rgba(255,255,255,0.92); | |
| backdrop-filter: blur(16px); | |
| border: 1px solid rgba(95,201,160,0.15); | |
| border-radius: 12px; | |
| padding: 22px; | |
| box-shadow: 0 4px 16px rgba(45,95,79,0.08); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| /* Hide transparent boxes on empty rows/columns */ | |
| .og-main-row > div:empty, | |
| .og-main-row > div > div:empty { | |
| display: none !important; | |
| } | |
| /* Ensure main row doesn't create unwanted backgrounds */ | |
| .og-main-row { | |
| background: transparent !important; | |
| gap: 16px; | |
| } | |
| .og-main-row > div { | |
| background: transparent !important; | |
| } | |
| .og-panel::before { | |
| .og-panel::after { | |
| content: ""; | |
| position: absolute; | |
| inset: auto -20px -40px auto; | |
| width: 150px; | |
| height: 150px; | |
| background: radial-gradient(circle at 30% 30%, rgba(15,77,63,0.14), transparent 65%); | |
| opacity: 0.55; | |
| pointer-events: none; | |
| } | |
| .og-tab-title { | |
| font-family: 'Playfair Display', serif; | |
| color: var(--og-green); | |
| } | |
| .og-tabs .tab-nav button { | |
| font-family: 'Playfair Display', serif; | |
| color: var(--og-fern); | |
| } | |
| .og-tabs .tab-nav button.selected { | |
| background: rgba(60,191,154,0.16); | |
| border-color: rgba(60,191,154,0.4); | |
| color: var(--og-green); | |
| } | |
| .og-divider { border-bottom: 1px solid rgba(15,77,63,0.12); margin: 10px 0 14px 0; } | |
| .og-label { color: var(--og-green); font-weight: 700; letter-spacing: 0.02em; } | |
| textarea, input, select { | |
| border-radius: 12px !important; | |
| border: 1px solid rgba(15,77,63,0.16) !important; | |
| background: rgba(255,255,255,0.95) !important; | |
| } | |
| .gr-button { | |
| border-radius: 12px !important; | |
| box-shadow: 0 12px 22px rgba(15,77,63,0.18) !important; | |
| background: linear-gradient(120deg, var(--og-green), #128162) !important; | |
| color: #f5f3ec !important; | |
| border: none !important; | |
| } | |
| .gr-button.secondary { | |
| background: rgba(15,77,63,0.08) !important; | |
| color: var(--og-green) !important; | |
| box-shadow: none !important; | |
| } | |
| """ | |
| def build_ui(): | |
| with gr.Blocks( | |
| title="Overgrowth β a living digital environment", | |
| css=VINE_CSS, | |
| theme=gr.themes.Soft(primary_hue="green", neutral_hue="slate"), | |
| ) as demo: | |
| gr.HTML( | |
| """ | |
| <div class="og-hero"> | |
| <div class="og-pill">Living OOB Mesh</div> | |
| <div class="og-pill">MCP + Topology</div> | |
| <h2 style="font-family:'Playfair Display',serif; margin:6px 0 4px 0;">πΏ Overgrowth</h2> | |
| <p style="margin:0; font-size:15px; color:#0f4d3f;"> | |
| A living digital environment | |
| </p> | |
| </div> | |
| """, | |
| ) | |
| device_choices = topology.list_devices() | |
| # Main Pipeline UI | |
| gr.Markdown("""### πΏ Network Automation Pipeline | |
| **From Consultation β Production** | |
| Describe your network in plain English. Overgrowth handles the rest: | |
| 1. π¬ **Consultation** - Capture requirements | |
| 2. π **Source of Truth** - Generate network data model | |
| 3. π **Diagram** - Visualize topology | |
| 4. π **Bill of Materials** - Hardware shopping list | |
| 5. π§ **Setup Guide** - Human deployment steps + OOB network | |
| 6. π€ **Autonomous Deploy** - AI configures devices (Ansible/Netmiko) | |
| 7. ποΈ **Observability** - Monitoring, SNMP, topology discovery | |
| 8. β **Validation** - pytest-based network testing & compliance | |
| """) | |
| with gr.Row(elem_classes=["og-main-row"]): | |
| with gr.Column(scale=1, elem_classes=["og-panel"]): | |
| pipeline_input = gr.Textbox( | |
| label="Describe Your Network", | |
| placeholder=( | |
| "Example: We're a coffee shop chain with 3 locations. " | |
| "We need WiFi for customers, POS systems with payment processing, " | |
| "security cameras, and secure VPN to HQ for centralized management. " | |
| "Each location has ~50 customers at peak time." | |
| ), | |
| lines=10, | |
| ) | |
| run_pipeline_btn = gr.Button("π Run Full Pipeline", variant="primary", size="lg") | |
| gr.Markdown("#### π Tool Calls & Logs") | |
| tool_log_md = gr.Markdown(label="MCP Activity") | |
| with gr.Column(scale=2, elem_classes=["og-panel", "og-tabs"]): | |
| gr.Markdown("#### π¦ Pipeline Outputs") | |
| pipeline_status = gr.Markdown(label="Status") | |
| with gr.Tabs(): | |
| with gr.Tab("π Source of Truth"): | |
| sot_output = gr.Code(label="Network Model (YAML)", language="yaml", lines=20) | |
| with gr.Tab("π Bill of Materials"): | |
| bom_output = gr.Markdown(label="Shopping List") | |
| with gr.Tab("π§ Setup Guide"): | |
| setup_output = gr.Markdown(label="Deployment Guide") | |
| with gr.Tab("π Diagram"): | |
| diagram_output = gr.Textbox(label="Network Diagram", lines=25, max_lines=50) | |
| # Pipeline handler | |
| def run_pipeline(user_input): | |
| """Execute the full automation pipeline""" | |
| from agent.pipeline_engine import OvergrowthPipeline | |
| pipeline = OvergrowthPipeline() | |
| try: | |
| # Run the pipeline | |
| results = pipeline.run_full_pipeline(user_input) | |
| # Check pre-flight validation | |
| preflight = results.get('preflight', {}) | |
| ready_to_deploy = preflight.get('ready_to_deploy', False) | |
| # Format status | |
| status = "## π Pipeline Execution Complete!\n\n" | |
| # Pre-flight validation section | |
| if ready_to_deploy: | |
| status += "### β Pre-flight Validation PASSED\n" | |
| status += f"- Schema validation: β Passed\n" | |
| status += f"- Policy checks: β Passed\n" | |
| if preflight.get('warnings'): | |
| status += f"- Warnings: {len(preflight['warnings'])}\n" | |
| if preflight.get('info'): | |
| status += f"- Info: {len(preflight['info'])}\n" | |
| status += "\n" | |
| else: | |
| status += "### β Pre-flight Validation FAILED\n" | |
| status += "**Deployment blocked until errors are fixed:**\n\n" | |
| for error in preflight.get('errors', []): | |
| status += f"- β {error}\n" | |
| status += "\n" | |
| if preflight.get('warnings'): | |
| status += "**Warnings:**\n" | |
| for warning in preflight.get('warnings', []): | |
| status += f"- β οΈ {warning}\n" | |
| status += "\n" | |
| # Completed stages | |
| status += "### Completed Stages:\n" | |
| status += "0. " + ("β " if ready_to_deploy else "β") + " Pre-flight Validation\n" | |
| status += "1. β Consultation - Intent captured\n" | |
| status += "2. β Source of Truth - Network model generated\n" | |
| status += "3. β Diagrams - Visualizations created\n" | |
| status += "4. β Bill of Materials - Shopping list ready\n" | |
| status += "5. β Setup Guide - Deployment instructions generated\n" | |
| if ready_to_deploy: | |
| status += "6. β³ Autonomous Deploy - Ready for execution\n" | |
| status += "7. β³ Observability - Ready for setup\n" | |
| status += "8. β³ Validation - Ready for verification\n\n" | |
| else: | |
| status += "6. π« Autonomous Deploy - BLOCKED (fix validation errors)\n" | |
| status += "7. π« Observability - BLOCKED\n" | |
| status += "8. π« Validation - BLOCKED\n\n" | |
| status += "### π Files Created:\n" | |
| status += "- `infra/network_model.yaml` - Source of Truth\n" | |
| status += "- `infra/bill_of_materials.json` - BOM data\n" | |
| status += "- `infra/setup_guide.md` - Deployment guide\n" | |
| # Extract outputs | |
| sot_yaml = results.get('model', {}) | |
| import yaml | |
| sot_str = yaml.dump(sot_yaml, default_flow_style=False) | |
| bom_md = results.get('shopping_list', 'No BOM generated') | |
| setup_md = results.get('setup_guide', 'No setup guide generated') | |
| diagram = results.get('diagrams', {}).get('ascii', 'No diagram available') | |
| return status, sot_str, bom_md, setup_md, diagram | |
| except Exception as e: | |
| error = f"## β Pipeline Error\n\n{str(e)}" | |
| import traceback | |
| error += f"\n\n```\n{traceback.format_exc()}\n```" | |
| return error, "", "", "", "" | |
| # Event Handlers | |
| run_pipeline_btn.click( | |
| fn=run_pipeline, | |
| inputs=[pipeline_input], | |
| outputs=[pipeline_status, sot_output, bom_output, setup_output, diagram_output], | |
| ) | |
| return demo | |
| demo = build_ui().queue() # enable queue by default for Spaces | |
| app = demo # Hugging Face picks this up automatically | |
| if __name__ == "__main__": | |
| demo.launch(server_name="0.0.0.0", server_port=7860, share=False, show_api=False) | |