""" Premium Gradio UI for the AI Executive Assistant Simulator. Features a glassmorphic dark theme with smooth animations, interactive agent comparison, and real-time schedule visualization. """ import sys import os import json sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) import gradio as gr import plotly.graph_objects as go from env.assistant_env import ExecutiveAssistantEnv from agents.random_agent import RandomAgent from agents.rule_based_agent import RuleBasedAgent from agents.rl_agent import RLAgent from ui.timeline import create_timeline, create_inbox_summary # ─── Global State ──────────────────────────────────────────────────────────── env = None agent = None episode_rewards = [] step_log = [] cumulative_reward = 0.0 # ─── Premium CSS ───────────────────────────────────────────────────────────── CUSTOM_CSS = """ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'); /* ── Base ── */ .gradio-container { background: linear-gradient(135deg, #0a0a1a 0%, #1a1040 30%, #0d1b3e 60%, #0a0a1a 100%) !important; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important; min-height: 100vh; --background-fill-primary: rgba(30, 30, 45, 0.4) !important; --background-fill-secondary: rgba(20, 20, 30, 0.6) !important; --border-color-primary: rgba(255, 255, 255, 0.1) !important; --block-background-fill: rgba(255, 255, 255, 0.03) !important; /* ── Glass Cards ── */ .glass-card { background: rgba(255, 255, 255, 0.03) !important; border: 1px solid rgba(255, 255, 255, 0.08) !important; border-radius: 16px !important; backdrop-filter: blur(20px) !important; -webkit-backdrop-filter: blur(20px) !important; padding: 20px !important; transition: all 0.3s ease !important; } .glass-card:hover { border-color: rgba(139, 92, 246, 0.3) !important; background: rgba(255, 255, 255, 0.05) !important; box-shadow: 0 8px 32px rgba(139, 92, 246, 0.1) !important; } /* ── Headings ── */ h1, .title-text { background: linear-gradient(135deg, #a78bfa, #818cf8, #6366f1, #8b5cf6) !important; -webkit-background-clip: text !important; -webkit-text-fill-color: transparent !important; background-clip: text !important; font-weight: 800 !important; letter-spacing: -0.5px !important; } /* ── Buttons ── */ .primary-btn { background: linear-gradient(135deg, #7c3aed, #6366f1, #8b5cf6) !important; border: none !important; color: white !important; font-weight: 600 !important; font-size: 15px !important; padding: 12px 28px !important; border-radius: 12px !important; transition: all 0.3s ease !important; box-shadow: 0 4px 15px rgba(124, 58, 237, 0.3) !important; letter-spacing: 0.3px !important; } .primary-btn:hover { transform: translateY(-2px) !important; box-shadow: 0 8px 25px rgba(124, 58, 237, 0.5) !important; } .success-btn { background: linear-gradient(135deg, #059669, #10b981, #34d399) !important; border: none !important; color: white !important; font-weight: 600 !important; font-size: 15px !important; padding: 12px 28px !important; border-radius: 12px !important; transition: all 0.3s ease !important; box-shadow: 0 4px 15px rgba(5, 150, 105, 0.3) !important; } .success-btn:hover { transform: translateY(-2px) !important; box-shadow: 0 8px 25px rgba(5, 150, 105, 0.5) !important; } .danger-btn { background: linear-gradient(135deg, #dc2626, #ef4444, #f87171) !important; border: none !important; color: white !important; font-weight: 600 !important; font-size: 15px !important; padding: 12px 28px !important; border-radius: 12px !important; transition: all 0.3s ease !important; box-shadow: 0 4px 15px rgba(220, 38, 38, 0.3) !important; } .danger-btn:hover { transform: translateY(-2px) !important; box-shadow: 0 8px 25px rgba(220, 38, 38, 0.5) !important; } /* ── Dropdowns ── */ /* Handled by global dark mode */ /* ── Labels ── */ label, span[data-testid="block-info"] { color: #a5b4fc !important; font-weight: 600 !important; font-size: 13px !important; text-transform: uppercase !important; letter-spacing: 0.8px !important; } /* ── Markdown ── */ .markdown-text, .prose { color: #c7d2fe !important; } .prose h1, .prose h2, .prose h3 { color: #e0e7ff !important; } .prose table { border-collapse: separate !important; border-spacing: 0 !important; border-radius: 12px !important; overflow: hidden !important; } .prose th { background: rgba(139, 92, 246, 0.15) !important; color: #c4b5fd !important; padding: 10px 16px !important; font-size: 12px !important; text-transform: uppercase !important; letter-spacing: 0.5px !important; border-bottom: 1px solid rgba(139, 92, 246, 0.2) !important; } .prose td { background: rgba(255, 255, 255, 0.02) !important; color: #e0e7ff !important; padding: 10px 16px !important; border-bottom: 1px solid rgba(255, 255, 255, 0.05) !important; } .prose code { background: rgba(139, 92, 246, 0.15) !important; color: #c4b5fd !important; padding: 2px 8px !important; border-radius: 6px !important; font-size: 13px !important; } /* ── Code Block ── */ .code-wrap, .cm-editor { background: rgba(0, 0, 0, 0.3) !important; border: 1px solid rgba(255, 255, 255, 0.06) !important; border-radius: 12px !important; } /* ── Plot containers ── */ .plot-container { background: transparent !important; border: 1px solid rgba(255, 255, 255, 0.06) !important; border-radius: 16px !important; overflow: hidden !important; } /* ── Hide footer ── */ footer { display: none !important; } /* ── Scrollbar ── */ ::-webkit-scrollbar { width: 6px; } ::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); } ::-webkit-scrollbar-thumb { background: rgba(139, 92, 246, 0.3); border-radius: 3px; } ::-webkit-scrollbar-thumb:hover { background: rgba(139, 92, 246, 0.5); } /* ── Tab styling ── */ button[role="tab"] { background: transparent !important; border: none !important; border-bottom: 2px solid transparent !important; color: #94a3b8 !important; border-radius: 0 !important; font-weight: 600 !important; padding: 12px 24px !important; font-size: 14px !important; transition: all 0.3s ease !important; } button[role="tab"].selected { background: rgba(139, 92, 246, 0.05) !important; border-bottom: 2px solid #a78bfa !important; color: #e0e7ff !important; } button[role="tab"]:hover:not(.selected) { background: rgba(255, 255, 255, 0.02) !important; color: #c7d2fe !important; } div[role="tabpanel"] { background: rgba(255, 255, 255, 0.02) !important; border: 1px solid rgba(255, 255, 255, 0.06) !important; border-radius: 12px !important; padding: 12px !important; margin-top: 8px !important; } """ # ─── Helper: Agent factory ─────────────────────────────────────────────────── def _get_agent(agent_type: str): agents = { "Random Agent": RandomAgent(), "Rule-Based Agent": RuleBasedAgent(), "Q-Learning Agent": RLAgent(epsilon=0.1), } return agents.get(agent_type, RandomAgent()) AGENT_DESCRIPTIONS = { "Random Agent": "Picks random valid actions. Performance lower-bound baseline.", "Rule-Based Agent": "Priority-first heuristic: high tasks > urgent emails > medium tasks.", "Q-Learning Agent": "Learns from experience via tabular Q-learning with exploration.", } DIFFICULTY_INFO = { "Easy": "3-5 tasks, few conflicts, no hidden items", "Medium": "5-8 tasks, some conflicts, hidden tasks appear mid-day", "Hard": "8-12 tasks, many conflicts, lots of surprises", } # ─── Core Functions ────────────────────────────────────────────────────────── def initialize(agent_type: str, difficulty: str): global env, agent, episode_rewards, step_log, cumulative_reward env = ExecutiveAssistantEnv(difficulty=difficulty.lower()) agent = _get_agent(agent_type) episode_rewards = [] step_log = [] cumulative_reward = 0.0 state = env.reset() state_json = json.dumps(state, indent=2, default=str) timeline_fig = create_timeline( state.get("tasks", []), current_time=state.get("time", "08:00"), ) inbox_fig = create_inbox_summary(state.get("inbox", [])) dashboard = _build_dashboard(state, 0, 0.0) tasks_html = _build_tasks_html(state) inbox_html = _build_inbox_html(state) log_msg = _format_log( "SYSTEM", "Episode Initialized", f"Agent: {agent_type} | Difficulty: {difficulty} | " f"Tasks: {len(state.get('tasks', []))} | Messages: {len(state.get('inbox', []))}", "info" ) return state_json, timeline_fig, inbox_fig, dashboard, tasks_html, inbox_html, log_msg def take_step(): global env, agent, episode_rewards, step_log, cumulative_reward if env is None: empty = _empty_outputs() return (*empty, _format_log("ERROR", "Not Initialized", "Click Initialize first.", "error")) state = env.get_state() action = agent.act(state) next_state, reward, done, info = env.step(action) cumulative_reward += reward episode_rewards.append(reward) step_log.append(info) state_json = json.dumps(next_state, indent=2, default=str) timeline_fig = create_timeline( next_state.get("tasks", []), current_time=next_state.get("time", "08:00"), ) inbox_fig = create_inbox_summary(next_state.get("inbox", [])) dashboard = _build_dashboard(next_state, len(step_log), cumulative_reward) tasks_html = _build_tasks_html(next_state) inbox_html = _build_inbox_html(next_state) if done: metrics = info.get("metrics", {}) log_msg = _format_log( "COMPLETE", f"Episode Finished in {len(step_log)} Steps", f"Total Reward: {cumulative_reward:+.1f} | " f"Completion: {metrics.get('task_completion_rate', 0):.0%} | " f"Efficiency: {metrics.get('efficiency_score', 0):.0f}/100", "success" ) else: icon = "+" if info.get("action_success") else "x" log_msg = _format_log( f"STEP {len(step_log)}", f"{info.get('action_type', '?')} -> target #{info.get('target_id', '?')}", f"[{icon}] {info.get('action_detail', '')} | Reward: {reward:+.2f} | Total: {cumulative_reward:+.1f}", "success" if info.get("action_success") else "warning" ) return state_json, timeline_fig, inbox_fig, dashboard, tasks_html, inbox_html, log_msg def run_full_episode(): global env, agent, episode_rewards, step_log, cumulative_reward if env is None: empty = _empty_outputs() return (*empty, _format_log("ERROR", "Not Initialized", "Click Initialize first.", "error")) done = False while not done: state = env.get_state() action = agent.act(state) next_state, reward, done, info = env.step(action) cumulative_reward += reward episode_rewards.append(reward) step_log.append(info) final_state = env.get_state() state_json = json.dumps(final_state, indent=2, default=str) timeline_fig = create_timeline( final_state.get("tasks", []), current_time=final_state.get("time", "08:00"), ) inbox_fig = create_inbox_summary(final_state.get("inbox", [])) dashboard = _build_dashboard(final_state, len(step_log), cumulative_reward) tasks_html = _build_tasks_html(final_state) inbox_html = _build_inbox_html(final_state) metrics = info.get("metrics", {}) log_msg = _format_log( "AUTO-RUN COMPLETE", f"Finished in {len(step_log)} Steps", f"Total Reward: {cumulative_reward:+.1f} | " f"Completion: {metrics.get('task_completion_rate', 0):.0%} | " f"Hi-Priority: {metrics.get('high_priority_completion', 0):.0%} | " f"Msg Response: {metrics.get('message_response_rate', 0):.0%} | " f"Efficiency: {metrics.get('efficiency_score', 0):.0f}/100 | " f"Conflicts: {metrics.get('conflict_count', 0)}", "success" ) return state_json, timeline_fig, inbox_fig, dashboard, tasks_html, inbox_html, log_msg def _empty_outputs(): return ( "{}", create_timeline([]), create_inbox_summary([]), _build_dashboard({}, 0, 0.0), "_No tasks yet._", "_No messages yet._", ) # ─── Formatters ────────────────────────────────────────────────────────────── def _format_log(tag: str, title: str, detail: str, level: str = "info") -> str: colors = { "info": "#818cf8", "success": "#34d399", "warning": "#fbbf24", "error": "#f87171", } color = colors.get(level, "#818cf8") return ( f"### [{tag}] {title}\n\n" f"{detail}" ) def _build_dashboard(state: dict, step: int, reward: float) -> str: tasks = state.get("tasks", []) inbox = state.get("inbox", []) total = len(tasks) pending = sum(1 for t in tasks if t.get("status") == "pending") completed = sum(1 for t in tasks if t.get("status") == "completed") missed = sum(1 for t in tasks if t.get("status") == "missed") total_msgs = len(inbox) unreplied = sum(1 for m in inbox if not m.get("replied", False)) replied = total_msgs - unreplied time_val = state.get("time", "--:--") # Build metrics cards return f"""
OpenEnv RL Environment — Watch AI agents learn to manage schedules, emails & priorities