1Jayanth/devops-autoheal
0
1"""2DevOps AI Agent Simulator โ Streamlit UI (v4)3===============================================4Interactive web interface for the DevOps Incident Response Environment.5Communicates with the FastAPI backend via HTTP.6v4 fixes:7 - Passes task_id to /auto-run (fixes always-easy bug)8 - AI suggestion button in manual mode via /suggest9 - Reasoning display in manual step log10"""11 12import requests13import json14import time15import subprocess16import signal17try:18 import plotly.graph_objects as go19 HAS_PLOTLY = True20except ImportError:21 HAS_PLOTLY = False22from datetime import datetime23import streamlit as st24from dotenv import load_dotenv25import os26from typing import Any, Dict, List, Optional27 28 29# ---------------------------------------------------------------------------30# Configuration31# ---------------------------------------------------------------------------32 33 34load_dotenv()35 36API_URL = os.getenv("API_URL") or os.getenv("BACKEND_URL")37if not API_URL:38 try:39 API_URL = st.secrets.get("API_URL") or st.secrets.get("BACKEND_URL")40 except Exception: # Catch StreamlitSecretNotFoundError and others41 API_URL = None42 43if not API_URL:44 API_URL = "http://localhost:7860"45 46st.set_page_config(47 page_title="DevOps AI Agent Simulator",48 page_icon="๐ค",49 layout="wide",50 initial_sidebar_state="expanded",51)52try:53 import gymnasium as gym54 import numpy as np55 import stable_baselines356 HAS_TRAINING = True57except ImportError:58 HAS_TRAINING = False59 60# ---------------------------------------------------------------------------61# Custom CSS62# ---------------------------------------------------------------------------63 64st.markdown("""65<style>66 @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');67 68 html, body, [class*="css"] { font-family: 'Inter', sans-serif; }69 70 .hero-section {71 background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%);72 padding: 2rem 2.5rem;73 border-radius: 16px;74 margin-bottom: 1.5rem;75 color: white;76 border: 1px solid rgba(255,255,255,0.08);77 }78 .hero-section h1 { margin: 0 0 0.3rem 0; font-size: 2rem; }79 .hero-section p { margin: 0.2rem 0; opacity: 0.85; font-size: 0.95rem; }80 81 .metric-card {82 background: linear-gradient(145deg, #1a1a2e, #16213e);83 border: 1px solid rgba(255,255,255,0.08);84 border-radius: 12px;85 padding: 1.2rem;86 text-align: center;87 color: white;88 }89 .metric-card .label { font-size: 0.75rem; opacity: 0.6; text-transform: uppercase; letter-spacing: 1px; }90 .metric-card .value { font-size: 1.8rem; font-weight: 700; margin: 0.3rem 0; }91 92 .status-healthy { color: #00e676; }93 .status-degraded { color: #ffab00; }94 .status-down { color: #ff1744; }95 .status-critical { color: #d50000; font-weight: 800; animation: blink 1.5s linear infinite; }96 @keyframes blink { 50% { opacity: 0.3; } }97 98 .log-box {99 background: #0d1117;100 border: 1px solid #30363d;101 border-radius: 8px;102 padding: 1rem;103 font-family: 'JetBrains Mono', 'Fira Code', monospace;104 font-size: 0.82rem;105 color: #c9d1d9;106 white-space: pre-wrap;107 max-height: 200px;108 overflow-y: auto;109 }110 111 .chat-msg {112 background: #161b22;113 border: 1px solid #30363d;114 border-radius: 10px;115 padding: 1rem;116 margin: 0.5rem 0;117 color: #e6edf3;118 }119 120 .action-btn { margin: 0.2rem; }121 122 .step-log {123 background: #0d1117;124 border-left: 3px solid #58a6ff;125 padding: 0.6rem 1rem;126 margin: 0.3rem 0;127 border-radius: 0 6px 6px 0;128 font-size: 0.85rem;129 color: #c9d1d9;130 }131 .step-log.positive { border-left-color: #00e676; }132 .step-log.negative { border-left-color: #ff1744; }133 134 .auto-step {135 background: #0d1117;136 border-left: 3px solid #bb86fc;137 padding: 0.6rem 1rem;138 margin: 0.3rem 0;139 border-radius: 0 6px 6px 0;140 font-size: 0.85rem;141 color: #c9d1d9;142 }143 .auto-step.positive { border-left-color: #00e676; }144 .auto-step.negative { border-left-color: #ff1744; }145 146 .result-banner {147 background: linear-gradient(135deg, #1b5e20, #2e7d32);148 border: 1px solid #43a047;149 border-radius: 12px;150 padding: 1.2rem 1.5rem;151 color: white;152 text-align: center;153 margin: 1rem 0;154 }155 .result-banner.failed {156 background: linear-gradient(135deg, #b71c1c, #c62828);157 border-color: #e53935;158 }159 .result-banner h3 { margin: 0; font-size: 1.4rem; }160 .result-banner p { margin: 0.3rem 0 0 0; opacity: 0.9; }161 162 .mode-card {163 background: linear-gradient(145deg, #1a1a2e, #16213e);164 border: 1px solid rgba(255,255,255,0.08);165 border-radius: 12px;166 padding: 1.2rem;167 color: white;168 }169 .mode-card h4 { margin: 0 0 0.5rem 0; }170 .mode-card p { margin: 0; opacity: 0.8; font-size: 0.9rem; }171 172 .usecase-grid {173 display: grid;174 grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));175 gap: 0.8rem;176 margin: 1rem 0;177 }178 .usecase-item {179 background: linear-gradient(145deg, #1a1a2e, #16213e);180 border: 1px solid rgba(255,255,255,0.08);181 border-radius: 10px;182 padding: 1rem;183 color: white;184 text-align: center;185 }186 .usecase-item .icon { font-size: 1.8rem; margin-bottom: 0.4rem; }187 .usecase-item .title { font-weight: 600; font-size: 0.9rem; }188 .usecase-item .desc { font-size: 0.78rem; opacity: 0.7; margin-top: 0.3rem; }189 190 .reasoning-box {191 background: #161b22;192 border: 1px solid #bb86fc;193 border-radius: 8px;194 padding: 0.6rem 1rem;195 margin: 0.2rem 0 0.5rem 0;196 color: #e6edf3;197 font-size: 0.85rem;198 }199 .reasoning-box .label { color: #bb86fc; font-weight: 600; font-size: 0.78rem; }200</style>201""", unsafe_allow_html=True)202 203 204# ---------------------------------------------------------------------------205# Helper functions206# ---------------------------------------------------------------------------207 208def api_call(method: str, endpoint: str, data: dict = None) -> dict | list | None:209 """Make an API call to the FastAPI backend."""210 url = f"{API_URL}{endpoint}"211 try:212 if method == "GET":213 r = requests.get(url, timeout=10)214 else:215 # POST requests can involve long-running LLM loops (auto-run)216 r = requests.post(url, json=data or {}, timeout=300)217 r.raise_for_status()218 return r.json()219 except requests.exceptions.ConnectionError:220 st.error(f"โ ๏ธ Cannot connect to backend at {API_URL}. Please ensure it is running.")221 return None222 except Exception as e:223 st.error(f"API Error: {e}")224 return None225 226 227def get_status_class(status: str) -> str:228 return f"status-{status}" if status in ("healthy", "degraded", "down", "critical") else ""229 230 231def get_cpu_color(cpu: int) -> str:232 if cpu <= 40: return "#00e676"233 if cpu <= 70: return "#ffab00"234 return "#ff1744"235 236 237def get_mem_color(mem: int) -> str:238 if mem <= 50: return "#00e676"239 if mem <= 75: return "#ffab00"240 return "#ff1744"241 242 243def get_latency_color(lat: str) -> str:244 return {"low": "#00e676", "medium": "#ffab00", "high": "#ff1744"}.get(lat, "#999")245 246def get_delta_html(curr: Any, prev: Any, invert: bool = False) -> str:247 if prev is None or curr == prev: return ""248 try:249 diff = float(curr) - float(prev)250 if diff > 0:251 color = "#ff1744" if not invert else "#00e676"252 return f"<div style='font-size:0.75rem; color:{color}; margin-top:2px;'>โฒ +{diff:.1f}</div>"253 else:254 color = "#00e676" if not invert else "#ff1744"255 return f"<div style='font-size:0.75rem; color:{color}; margin-top:2px;'>โผ {diff:.1f}</div>"256 except (ValueError, TypeError):257 return f"<div style='font-size:0.75rem; color:#ffab00; margin-top:2px;'>โค from {str(prev).upper()}</div>"258 259 260# ---------------------------------------------------------------------------261# Session state initialisation262# ---------------------------------------------------------------------------263 264if "state" not in st.session_state:265 st.session_state.state = None266if "prev_state" not in st.session_state:267 st.session_state.prev_state = None268if "step_history" not in st.session_state:269 st.session_state.step_history = []270if "chat_history" not in st.session_state:271 st.session_state.chat_history = []272if "actions_taken" not in st.session_state:273 st.session_state.actions_taken = []274if "total_reward" not in st.session_state:275 st.session_state.total_reward = 0.0276if "episode_done" not in st.session_state:277 st.session_state.episode_done = False278if "auto_run_result" not in st.session_state:279 st.session_state.auto_run_result = None280if "training_process" not in st.session_state:281 st.session_state.training_process = None282if "training_log" not in st.session_state:283 st.session_state.training_log = []284 285 286# ---------------------------------------------------------------------------287# Training Dashboard288# ---------------------------------------------------------------------------289 290def show_training_dashboard():291 st.markdown("## ๐ค Reinforcement Learning Training")292 293 if not HAS_TRAINING:294 st.warning("โ ๏ธ **Platform Not Supported for Local Training**")295 st.markdown(f"""296 RL Training requires **PyTorch** and **Stable Baselines 3**, which are currently not supported on your platform 297 (**{st.session_state.get('platform', 'Intel Mac')}** + **Python 3.13**).298 299 **How to Train?**300 1. **Push to Cloud**: Click the 'Push' button or run `openenv push`.301 2. **Run in Hugging Face**: All features will be fully functional on the Linux-based cloud environment.302 """)303 return304 305 st.info("Train a **PPO (Proximal Policy Optimization)** model using Stable Baselines 3 to resolve incidents automatically.")306 # ... rest of training logic ...307 308 col1, col2 = st.columns([1, 2])309 310 with col1:311 st.markdown("### โ๏ธ Training Settings")312 timesteps = st.number_input("Total Timesteps", min_value=1000, max_value=100000, value=10000, step=1000)313 314 if st.session_state.training_process is None:315 if st.button("๐ Start Training", type="primary", use_container_width=True):316 try:317 proc = subprocess.Popen(["python", "training.py", "--timesteps", str(timesteps)])318 st.session_state.training_process = proc.pid319 st.success(f"Training started (PID: {proc.pid})")320 st.rerun()321 except Exception as e:322 st.error(f"Failed to start training: {e}")323 else:324 if st.button("๐ Stop Training", type="secondary", use_container_width=True):325 try:326 os.kill(st.session_state.training_process, signal.SIGTERM)327 st.session_state.training_process = None328 st.warning("Training stopped.")329 st.rerun()330 except Exception as e:331 st.error(f"Failed to stop process: {e}")332 333 st.markdown("---")334 st.markdown("**Status:** " + ("๐ข Running" if st.session_state.training_process else "โช Idle"))335 336 with col2:337 st.markdown("### ๐ Performance Monitor")338 339 if os.path.exists("training_log.json"):340 try:341 with open("training_log.json", "r") as f:342 log_data = json.load(f)343 344 if log_data:345 steps = [entry["step"] for entry in log_data]346 rewards = [entry["reward"] for entry in log_data]347 348 fig = go.Figure()349 fig.add_trace(go.Scatter(x=steps, y=rewards, mode='lines+markers', name='Mean Reward'))350 fig.update_layout(351 title="Mean Episode Reward vs. Timesteps",352 xaxis_title="Timesteps",353 yaxis_title="Mean Reward",354 template="plotly_dark",355 height=400356 )357 st.plotly_chart(fig, use_container_width=True)358 359 st.table(log_data[-5:][::-1])360 else:361 st.info("Waiting for first training metrics...")362 except Exception as e:363 st.error(f"Error reading log: {e}")364 else:365 st.info("No training log found. Start training to see metrics.")366 367 368# ---------------------------------------------------------------------------369# Sidebar โ Controls370# ---------------------------------------------------------------------------371 372with st.sidebar:373 st.markdown("## โ๏ธ Controls")374 view_mode = st.radio("View Mode", ["Incident Dashboard", "AI Training Hub"])375 st.divider()376 377 if view_mode == "Incident Dashboard":378 def load_selected_task():379 task_id = st.session_state.task_choice_sb380 st.session_state.active_task_id = task_id # Save active task381 result = api_call("POST", "/reset", {"task_id": task_id})382 if result:383 st.session_state.state = result.get("observation", result)384 st.session_state.prev_state = None385 st.session_state.step_history = []386 st.session_state.actions_taken = []387 st.session_state.chat_history = []388 st.session_state.total_reward = 0.0389 st.session_state.episode_done = False390 st.session_state.auto_run_result = None391 392 available_tasks = ["easy", "medium", "hard", "expert"]393 st.markdown("### ๐ Select Task")394 task_choice = st.selectbox("Task", available_tasks, label_visibility="collapsed", key="task_choice_sb", on_change=load_selected_task)395 396 col_reset, col_gen = st.columns(2)397 with col_reset:398 if st.button("๐ Reset", use_container_width=True):399 load_selected_task()400 st.rerun()401 with col_gen:402 if st.button("๐ฒ Random", use_container_width=True):403 tid = f"random_scenario_{int(time.time()) % 1000}"404 gen_result = api_call("POST", "/generate", {"task_id": tid, "difficulty": "random"})405 if gen_result:406 result = api_call("POST", "/reset", {"task_id": tid})407 if result:408 st.session_state.active_task_id = tid # Ensure Auto-Run uses this random task409 st.session_state.state = result.get("observation", result)410 st.session_state.prev_state = None411 st.session_state.step_history = []412 st.session_state.actions_taken = []413 st.session_state.chat_history = []414 st.session_state.total_reward = 0.0415 st.session_state.episode_done = False416 st.session_state.auto_run_result = None417 st.toast(f"๐ฒ Generated new task: {tid}")418 st.rerun()419 420 st.divider()421 st.markdown("### ๐ Action History")422 if st.session_state.actions_taken:423 for i, a in enumerate(st.session_state.actions_taken, 1):424 st.markdown(f"`{i}.` {a}")425 st.metric("Total Reward", f"{st.session_state.total_reward:+.2f}")426 else:427 st.caption("No actions taken yet. Reset a task to begin.")428 429 st.divider()430 st.markdown("### ๐ง How AI Works")431 st.markdown("""432 1. **Analyzes** system metrics and logs433 2. **Infers** hidden root causes (Leaks/Locks)434 3. **Executes** multi-step corrective chains435 """)436 437 with st.expander("๐ ๏ธ Technical Deep Dive"):438 st.markdown("""439 **Tech Stack:**440 - **Backend:** FastAPI + OpenEnv Core441 - **Logic:** Pydantic v2 + Dataclasses442 - **Agent:** OpenAI-compatible Reasoning Agent443 - **UI:** Streamlit + Custom CSS444 445 **Deep Logic:**446 - **Dual-State Engine:** Metrics are driven by *hidden* variables (Leaks, Locks).447 - **Failure Propagation:** High CPU increases DB Latency chance; High Memory triggers API crashes.448 - **Deterministic Grader:** Scientific 5-component scoring (0.0 - 1.0).449 """)450 451 452# ---------------------------------------------------------------------------453# Main content454# ---------------------------------------------------------------------------455 456if view_mode == "AI Training Hub":457 show_training_dashboard()458else:459 # โโ Hero Section โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ460 st.markdown("""461<div class="hero-section">462 <h1>๐ค DevOps AI Agent Simulator</h1>463 <p style="font-size: 1.1rem; margin-bottom: 0.8rem;">Interactive simulation of real-world DevOps system failures with AI-powered diagnosis</p>464</div>465""", unsafe_allow_html=True)466 467 if st.session_state.state is None:468 st.info("๐ Select a task and click **๐ Reset** to start a simulation.")469 st.stop()470 471 state = st.session_state.state472 prev_state = st.session_state.prev_state473 474 # โโ System State Panel โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ475 st.markdown("## ๐ System State")476 cols = st.columns(5)477 478 with cols[0]:479 cpu = state["cpu_usage"]480 pcpu = prev_state.get("cpu_usage") if prev_state else None481 st.markdown(f"""482 <div class="metric-card">483 <div class="label">CPU Usage</div>484 <div class="value" style="color: {get_cpu_color(cpu)}">{cpu}%</div>485 {get_delta_html(cpu, pcpu)}486 </div>487 """, unsafe_allow_html=True)488 489 with cols[1]:490 mem = state["memory_usage"]491 pmem = prev_state.get("memory_usage") if prev_state else None492 st.markdown(f"""493 <div class="metric-card">494 <div class="label">Memory</div>495 <div class="value" style="color: {get_mem_color(mem)}">{mem}%</div>496 {get_delta_html(mem, pmem)}497 </div>498 """, unsafe_allow_html=True)499 500 with cols[2]:501 lat = state["db_latency"]502 plat = prev_state.get("db_latency") if prev_state else None503 st.markdown(f"""504 <div class="metric-card">505 <div class="label">DB Latency</div>506 <div class="value" style="color: {get_latency_color(lat)}">{lat.upper()}</div>507 {get_delta_html(lat, plat)}508 </div>509 """, unsafe_allow_html=True)510 511 with cols[3]:512 status = state["status"]513 if status == "down" and st.session_state.get("task_choice_sb") == "expert":514 status = "critical"515 516 st.markdown(f"""517 <div class="metric-card">518 <div class="label">Status</div>519 <div class="value {get_status_class(status)}">{status.upper()}</div>520 </div>521 """, unsafe_allow_html=True)522 523 with cols[4]:524 st.markdown(f"""525 <div class="metric-card">526 <div class="label">Step</div>527 <div class="value" style="color: #64b5f6">{state['step_count']}</div>528 </div>529 """, unsafe_allow_html=True)530 531 # Services532 svc_icons = {"api": "๐", "database": "๐๏ธ", "cache": "โก"}533 svc_text = " ".join(534 f"{svc_icons.get(s, 'โข')} **{s}** โ
" if s in state["services"]535 else f"{svc_icons.get(s, 'โข')} **{s}** โ"536 for s in ["api", "database", "cache"]537 )538 st.markdown(f"**Services:** {svc_text}")539 540 # Logs541 st.markdown("**๐ System Logs:**")542 st.markdown(f'<div class="log-box">{state["logs"]}</div>', unsafe_allow_html=True)543 544 st.markdown("---")545 546 # Reveal Reward Progression Chart if available547 if HAS_PLOTLY and getattr(st.session_state, "step_history", []):548 cumulative = []549 cur = 0550 for e in st.session_state.step_history:551 cur += e.get("reward", 0)552 cumulative.append(cur)553 if cumulative:554 st.markdown("### ๐ Reward Progression")555 fig = go.Figure()556 fig.add_trace(go.Scatter(557 x=[e["step"] for e in st.session_state.step_history] , 558 y=cumulative, 559 mode='lines+markers', 560 name='Total Reward', 561 line=dict(color='#00e676', width=2),562 marker=dict(size=8)563 ))564 fig.update_layout(565 xaxis_title="Step", 566 yaxis_title="Total Reward", 567 template="plotly_dark", 568 height=250, 569 margin=dict(t=10, b=10, l=10, r=10),570 paper_bgcolor='rgba(0,0,0,0)',571 plot_bgcolor='rgba(0,0,0,0.2)'572 )573 st.plotly_chart(fig, use_container_width=True)574 st.markdown("---")575 576 # โโ Action Panel โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ577 578 action_col, chat_col = st.columns([1, 1])579 580 with action_col:581 st.markdown("## ๐ฎ Actions")582 583 if st.session_state.episode_done:584 if state["status"] == "healthy":585 st.markdown(586 '<div class="result-banner">'587 '<h3>โ
System Fully Resolved!</h3>'588 '<p>All root causes have been addressed. The system is healthy.</p>'589 '</div>',590 unsafe_allow_html=True,591 )592 else:593 st.markdown(594 '<div class="result-banner failed">'595 f'<h3>โ ๏ธ Episode Ended โ Status: {state["status"].upper()}</h3>'596 '<p>Max steps reached. The system was not fully restored.</p>'597 '</div>',598 unsafe_allow_html=True,599 )600 st.info("Click **๐ Reset** in the sidebar to start a new episode.")601 else:602 actions = [603 ("๐ restart_service:api", "restart_service:api"),604 ("๐๏ธ restart_service:database", "restart_service:database"),605 ("๐ scale_up:cpu", "scale_up:cpu"),606 ("โก optimize_database", "optimize_database"),607 ("๐งน clear_cache", "clear_cache"),608 ("๐ check_logs", "check_logs"),609 ("โธ๏ธ no_action", "no_action"),610 ]611 612 # AI Suggestion Button613 suggest_col1, suggest_col2 = st.columns([3, 1])614 with suggest_col1:615 if st.button("๐ง Ask AI for Next Action", use_container_width=True, type="secondary"):616 suggestion = api_call("POST", "/suggest")617 if suggestion:618 st.session_state["ai_suggestion"] = suggestion619 st.rerun()620 with suggest_col2:621 pass622 623 if st.session_state.get("ai_suggestion"):624 sug = st.session_state["ai_suggestion"]625 st.markdown(626 f'<div class="reasoning-box">'627 f'<span class="label">๐ง AI Suggests:</span> <strong>{sug.get("action", "?")}</strong><br>'628 f'{sug.get("reasoning", "")}'629 f'</div>',630 unsafe_allow_html=True,631 )632 633 action_cols = st.columns(3)634 for i, (label, action_id) in enumerate(actions):635 with action_cols[i % 3]:636 disabled = action_id in st.session_state.actions_taken and action_id not in ("check_logs", "no_action")637 if st.button(label, key=f"act_{action_id}", use_container_width=True, disabled=disabled):638 # Get AI reasoning for this action639 diag = {}640 try:641 suggest_result = api_call("POST", "/suggest")642 if suggest_result:643 diag = suggest_result644 except Exception:645 pass646 647 result = api_call("POST", "/step", {"action": {"action_str": action_id}})648 if result:649 obs = result.get("observation", result)650 reward = result.get("reward", 0.0)651 done = result.get("done", False)652 step_diag = result.get("diagnosis", diag)653 654 st.session_state.prev_state = dict(st.session_state.state)655 st.session_state.state = obs656 st.session_state.actions_taken.append(action_id)657 st.session_state.total_reward += reward658 st.session_state.episode_done = done659 660 metrics = obs.get("info", {})661 entry = {662 "step": len(st.session_state.actions_taken),663 "action": action_id,664 "reward": reward,665 "status": obs["status"],666 "message": obs.get("message", ""),667 "info": metrics,668 "reasoning": step_diag.get("reasoning", "") if isinstance(step_diag, dict) else ""669 }670 st.session_state.step_history.append(entry)671 672 # Update UI immediately for a "live" feel673 st.toast(f"AI Step {entry['step']}: {action_id}", icon="๐ค")674 675 if action_id == "no_action" and obs["status"] == "healthy":676 st.success("๐ฏ AI Agent successfully resolved the incident!")677 break678 679 # Small pause for visual effect680 import time681 time.sleep(1.2)682 st.rerun()683 684 # Step log685 if st.session_state.step_history:686 st.markdown("### ๐ Step Log & Timeline")687 688 # Basic stats summary if info exists689 latest_info = st.session_state.step_history[-1].get("info", {})690 if latest_info:691 ok_actions = latest_info.get("total_correct_actions", 0)692 bad_actions = latest_info.get("total_wrong_actions", 0)693 st.markdown(f"**Metrics:** โ
Correct Actions: `{ok_actions}` | โ Wrong Actions: `{bad_actions}`")694 695 for entry in reversed(st.session_state.step_history):696 reward = entry["reward"]697 cls = "positive" if reward > 0 else "negative" if reward < 0 else ""698 699 with st.expander(f"Step {entry['step']}: **{entry['action']}**", expanded=(entry['step'] == len(st.session_state.step_history))):700 st.markdown(f"**Reward:** <span class='reward-{cls}'>{reward:+.4f}</span>", unsafe_allow_html=True)701 st.markdown(f"**Status:** `{entry['status'].upper()}`")702 if entry.get("reasoning"):703 st.info(f"**AI Reasoning:** {entry['reasoning']}")704 st.write(entry["message"])705 706 707 # โโ Chat Panel โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ708 709 with chat_col:710 st.markdown("## ๐ฌ AI Diagnosis Chat")711 st.caption("Ask the AI about the current incident for diagnosis and advice.")712 713 # Chat input714 user_msg = st.text_input("Ask about the incident...", key="chat_input",715 placeholder="What's wrong with the system?")716 if st.button("๐ Diagnose", use_container_width=True) and user_msg:717 result = api_call("POST", "/chat", {"message": user_msg})718 if result:719 st.session_state.chat_history.append({720 "role": "user",721 "content": user_msg,722 })723 st.session_state.chat_history.append({724 "role": "assistant",725 "diagnosis": result.get("diagnosis", ""),726 "reasoning": result.get("reasoning", ""),727 "suggested_action": result.get("suggested_action", ""),728 })729 st.rerun()730 731 # Chat history732 if st.session_state.chat_history:733 for msg in reversed(st.session_state.chat_history):734 if msg["role"] == "user":735 st.markdown(736 f'<div class="chat-msg"><strong>๐ง You:</strong> {msg["content"]}</div>',737 unsafe_allow_html=True,738 )739 else:740 st.markdown(741 f'<div class="chat-msg">'742 f'<strong>๐ค AI Diagnosis:</strong><br>'743 f'<strong>Issue:</strong> {msg.get("diagnosis", "")}<br>'744 f'<strong>Reasoning:</strong> {msg.get("reasoning", "")}<br>'745 f'<strong>Suggested Action:</strong> <code>{msg.get("suggested_action", "")}</code>'746 f'</div>',747 unsafe_allow_html=True,748 )749 else:750 st.caption("No chat messages yet. Ask a question above!")751 752 753 # ---------------------------------------------------------------------------754 # AI Agent Mode (add-on โ appended below existing UI)755 # ---------------------------------------------------------------------------756 757 st.markdown("---")758 759 st.markdown("## ๐ค AI Agent Mode")760 st.caption("Let the AI agent automatically diagnose and resolve the incident.")761 762 agent_col1, agent_col2 = st.columns([2, 1])763 764 with agent_col1:765 if st.session_state.episode_done:766 st.info("Episode already finished. **๐ Reset** to run the agent on a new task.")767 elif st.session_state.state is None:768 st.info("Select a task and **๐ Reset** first, then run the AI agent.")769 else:770 if st.button("โถ๏ธ Run AI Agent Automatically", use_container_width=True, type="primary"):771 # Step-by-step animated execution772 progress_placeholder = st.empty()773 step_container = st.container()774 775 # Get the currently selected active task ID (works for both dropdown and random button)776 current_task = st.session_state.get("active_task_id", st.session_state.get("task_choice_sb", "easy"))777 778 with st.spinner(f"๐ค AI Agent is analyzing {current_task.upper()}..."):779 # Clear UI history for a fresh auto-run visualization780 st.session_state.actions_taken = []781 st.session_state.step_history = []782 result = api_call("POST", "/auto-run", {"task_id": current_task})783 784 if result:785 # Animate step-by-step reveal786 with step_container:787 for i, step_entry in enumerate(result.get("steps", [])):788 progress_placeholder.progress(789 (i + 1) / max(len(result["steps"]), 1),790 text=f"โณ AI is executing step {i+1}/{len(result['steps'])}..."791 )792 time.sleep(0.6) # Realistic delay between steps793 794 progress_placeholder.progress(1.0, text="โ
AI Agent finished!")795 time.sleep(0.5)796 progress_placeholder.empty()797 798 st.session_state.auto_run_result = result799 # Final observation is derived from the last step in result800 if result.get("steps"):801 last = result["steps"][-1]802 st.session_state.state = {803 "status": last["status"],804 "cpu_usage": last["cpu_usage"],805 "memory_usage": last["memory_usage"],806 "db_latency": last["db_latency"],807 "services": last.get("services", []), 808 "logs": last.get("logs", ""), 809 "step_count": last["step"],810 }811 st.session_state.episode_done = True812 for step_entry in result.get("steps", []):813 st.session_state.actions_taken.append(step_entry["action"])814 st.session_state.total_reward += step_entry["reward"]815 st.session_state.step_history.append({816 "step": step_entry["step"],817 "action": step_entry["action"],818 "reward": step_entry["reward"],819 "status": step_entry["status"],820 "message": step_entry.get("message", ""),821 })822 st.rerun()823 824 with agent_col2:825 if st.session_state.auto_run_result:826 r = st.session_state.auto_run_result827 st.metric("Final Status", r["final_status"].upper())828 st.metric("Steps Taken", r["total_steps"])829 st.metric("Total Reward", f"{r['total_reward']:+.2f}")830 831 # Display auto-run execution log832 if st.session_state.auto_run_result:833 ar = st.session_state.auto_run_result834 st.markdown("---")835 st.markdown("### ๐ AI Execution Log")836 for step_entry in ar.get("steps", []):837 reward = step_entry["reward"]838 reasoning = step_entry.get("reasoning", "")839 cls = "positive" if reward > 0 else "negative" if reward < 0 else ""840 st.markdown(841 f'<div class="auto-step {cls}">'842 f'<strong>Step {step_entry["step"]}</strong>: '843 f'{step_entry["action"]} โ '844 f'reward={reward:+.4f} | status={step_entry["status"]} | '845 f'cpu={step_entry["cpu_usage"]}% mem={step_entry["memory_usage"]}% db={step_entry["db_latency"]}'846 f'<br><small>{step_entry["message"]}</small></div>',847 unsafe_allow_html=True,848 )849 if reasoning:850 st.markdown(851 f'<div class="reasoning-box">'852 f'<span class="label">๐ง Reasoning:</span> {reasoning}'853 f'</div>',854 unsafe_allow_html=True,855 )856 857 858 859# ---------------------------------------------------------------------------860# Mode Explanation (add-on โ appended at bottom)861# ---------------------------------------------------------------------------862 863st.markdown("---")864 865st.markdown("## ๐ Execution Modes")866 867mode_col1, mode_col2 = st.columns(2)868 869with mode_col1:870 st.markdown(871 '<div class="mode-card">'872 '<h4>๐ฎ Manual Mode</h4>'873 '<p>You control actions step-by-step. Choose which action to execute, '874 'observe the results, and decide the next move. Great for learning '875 'how the system works and understanding action dependencies.</p>'876 '</div>',877 unsafe_allow_html=True,878 )879 880with mode_col2:881 st.markdown(882 '<div class="mode-card">'883 '<h4>๐ค AI Agent Mode</h4>'884 '<p>The AI agent automatically diagnoses and resolves the system. '885 'It uses structured reasoning (Intent โ Dependencies โ Failure Avoidance '886 'โ Orchestration) to find the optimal fix sequence. Watch the execution '887 'log to see how the agent reasons.</p>'888 '</div>',889 unsafe_allow_html=True,890 )891 892 