overgrowth / app.py
Graham Paasch
feat: Add Stage 0 pre-flight validation (Todo #2)
a2079ba
raw
history blame
12.7 kB
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)