overgrowth / app_old.py
Graham Paasch
Redesign UI with modern dark theme
4a3cf30
Raw
History Blame
10.2 kB
import gradio as gr
from agent.pipeline import (
analyze_change,
oob_troubleshoot,
perform_recovery,
load_synapse_log,
reset_state,
)
from agent import topology
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: #0f4d3f;
--og-mint: #38c49d;
--og-fern: #1c6b53;
--og-cream: #f5f3ec;
--og-shadow: 0 18px 40px rgba(10,48,38,0.18);
}
html, body { overflow-x: hidden; }
body, .gradio-container {
font-family: 'Inter', system-ui, -apple-system, sans-serif;
background:
url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyNDAnIGhlaWdodD0nMjQwJyB2aWV3Qm94PScwIDAgMjQwIDI0MCc+CjxwYXRoIGQ9J00xMCAyMDAgQyA2MCAxNTAsIDEyMCAxMjAsIDE0MCA3MCBDIDE1NSA0MCwgMTg1IDMwLCAyMjAgMjAnIHN0cm9rZT0nIzFjNmI1Mycgc3Ryb2tlLXdpZHRoPSc0JyBmaWxsPSdub25lJyBzdHJva2UtbGluZWNhcD0ncm91bmQnIHN0cm9rZS1vcGFjaXR5PScuMTYnLz4KPHBhdGggZD0nTTUwIDIyMCBDIDkwIDE5MCwgMTMwIDE0MCwgMTcwIDEwMCcgc3Ryb2tlPScjM2NiZjlhJyBzdHJva2Utd2lkdGg9JzMnIGZpbGw9J25vbmUnIHN0cm9rZS1saW5lY2FwPSdyb3VuZCcgc3Ryb2tlLW9wYWNpdHk9Jy4yMCcvPgo8Y2lyY2xlIGN4PSc5MCcgY3k9JzE1MCcgcj0nMTAnIGZpbGw9JyMzY2JmOWEnIGZpbGwtb3BhY2l0eT0nLjI1Jy8+CjxjaXJjbGUgY3g9JzE1MCcgY3k9JzkwJyByPSc5JyBmaWxsPScjMGY0ZDNmJyBmaWxsLW9wYWNpdHk9Jy4xOCcvPgo8Y2lyY2xlIGN4PScyMDAnIGN5PSc1MCcgcj0nNycgZmlsbD0nIzhiZDdiMScgZmlsbC1vcGFjaXR5PScuMjInLz4KPC9zdmc+") repeat,
radial-gradient(circle at 12% 20%, rgba(60,191,154,0.10), transparent 32%),
radial-gradient(circle at 82% 16%, rgba(15,77,63,0.10), transparent 30%),
linear-gradient(135deg, #f8f7f2 0%, #ecf2e9 100%);
color: #0f1b1a;
position: relative;
}
.gradio-container > * { position: relative; z-index: 1; }
.og-hero {
background:
linear-gradient(120deg, rgba(15,77,63,0.32), rgba(56,196,157,0.18)),
linear-gradient(160deg, rgba(140,215,177,0.20), rgba(15,77,63,0.14)),
url("assets/trellis.jpg?v=1");
background-size: cover;
background-position: center;
background-repeat: no-repeat;
border: 1px solid rgba(15,77,63,0.16);
box-shadow: var(--og-shadow);
border-radius: 18px;
padding: 22px;
position: relative;
overflow: hidden;
}
.og-hero::before,
.og-hero::after {
content: "";
position: absolute;
inset: -80px -20px auto auto;
width: 240px;
height: 240px;
background: radial-gradient(circle at 40% 40%, rgba(60,191,154,0.4), transparent 60%),
radial-gradient(circle at 70% 70%, rgba(15,77,63,0.45), transparent 62%);
filter: blur(6px);
opacity: 0.35;
}
.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 {
border: 1px solid rgba(15,77,63,0.16);
border-radius: 16px;
background: rgba(255,255,255,0.9);
box-shadow: 0 12px 28px rgba(0,0,0,0.07);
padding: 16px;
position: relative;
overflow: hidden;
}
.og-panel::before {
content: "";
position: absolute;
inset: -30px auto auto -20px;
width: 160px;
height: 160px;
background: radial-gradient(circle at 60% 40%, rgba(60,191,154,0.22), transparent 60%);
opacity: 0.55;
pointer-events: none;
}
.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;">
Autonomous network reliability agent — parses changes, simulates risk, diagnoses drift, and self-heals with MCP.
</p>
</div>
""",
)
device_choices = topology.list_devices()
with gr.Row():
with gr.Column(scale=1, elem_classes=["og-panel"]):
change_request = gr.Textbox(
label="Describe your network change",
placeholder=(
"Example: Add VLAN 120 for tenant Blue on leaf-01 and "
"uplink it to tor-03 (staging only)."
),
lines=6,
)
run_btn = gr.Button("Run Analysis", variant="primary")
with gr.Column(scale=2, elem_classes=["og-panel", "og-tabs"]):
with gr.Tab("Plan"):
plan_md = gr.Markdown(label="Planned Steps (Hybrid NL + JSON)")
with gr.Tab("Risk & Topology"):
risk_md = gr.Markdown(label="Risk & Topology Summary")
with gr.Tab("Diffs"):
diffs_md = gr.Markdown(label="Config Diff Summary")
with gr.Tab("Rollback"):
rollback_md = gr.Markdown(label="Rollback Plan")
with gr.Tab("Tool Calls"):
tool_log_md = gr.Markdown(label="MCP Tool Call Log")
with gr.Tab("OOB Troubleshooting"):
troubleshoot_device = gr.Dropdown(
label="Device",
choices=device_choices or ["leaf-01", "leaf-02"],
value=device_choices[0] if device_choices else "leaf-01",
)
troubleshoot_text = gr.Textbox(
label="Describe the issue",
placeholder="Example: Link flaps on leaf-01, seeing packet loss.",
lines=3,
)
troubleshoot_btn = gr.Button("Run Troubleshooting")
troubleshoot_md = gr.Markdown(label="Troubleshooting Summary")
troubleshoot_log = gr.Markdown(label="Troubleshooting Log")
troubleshoot_actions = gr.Markdown(label="Recommended Actions")
troubleshoot_conf = gr.Markdown(label="Root Cause Confidence")
troubleshoot_state = gr.Markdown(label="Device State Snapshot")
execute_btn = gr.Button("Execute Recovery Plan")
execute_summary = gr.Markdown(label="Recovery Execution Summary")
execute_log = gr.Markdown(label="Recovery Execution Log")
reset_btn = gr.Button("Reset Overgrowth State (dangerous)", variant="secondary")
reset_md = gr.Markdown(label="State Reset Log")
with gr.Tab("Synapse Log"):
synapse_btn = gr.Button("Refresh Log")
synapse_md = gr.Markdown(label="Overgrowth Synapse Log")
def on_run(change_text: str):
"""
Wraps the agent pipeline for Gradio.
Returns:
plan_markdown, risk_markdown, diffs_markdown,
rollback_markdown, tool_log_markdown
"""
return analyze_change(change_text)
def run_troubleshoot(dev, text):
return oob_troubleshoot(dev, text)
def execute_recovery_plan_wrapper(dev, text):
return perform_recovery(dev, text)
run_btn.click(
fn=on_run,
inputs=[change_request],
outputs=[plan_md, risk_md, diffs_md, rollback_md, tool_log_md],
)
troubleshoot_btn.click(
fn=run_troubleshoot,
inputs=[troubleshoot_device, troubleshoot_text],
outputs=[
troubleshoot_md,
troubleshoot_log,
troubleshoot_actions,
troubleshoot_conf,
troubleshoot_state,
],
)
execute_btn.click(
fn=execute_recovery_plan_wrapper,
inputs=[troubleshoot_device, troubleshoot_text],
outputs=[execute_summary, execute_log],
)
reset_btn.click(
fn=reset_state,
inputs=[],
outputs=[reset_md],
)
synapse_btn.click(fn=load_synapse_log, inputs=None, outputs=synapse_md)
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)