""" Gradio UI layout and event wiring. Two-column layout: - Left (70%): ``gr.Chatbot`` with streaming responses. - Right (30%): Agent reasoning panel (collapsible accordions showing the Thought / Action / Observation chain, tool execution log, and cache status). """ from __future__ import annotations import asyncio import json import logging from typing import Any, AsyncIterator import gradio as gr from agent.events import AgentEvent, EventType from agent.orchestrator import AgentOrchestrator from model.engine import ModelEngine from tools import create_default_registry from ui.formatters import format_event_for_chatbot, format_event_for_log logger = logging.getLogger(__name__) # Pre-populated example prompts. _EXAMPLES = [ "Analyze AAPL's momentum and tell me if it's overbought", "Compare the correlation between NVDA and AMD over the last 6 months", "Run a Monte Carlo simulation for TSLA -- what's the 30-day risk?", "How might the current fed funds rate affect tech stocks?", ] def build_interface(demo_mode: bool = True) -> gr.Blocks: """Construct and return the Gradio Blocks interface. Parameters ---------- demo_mode: When True, the agent uses mock model responses (no GPU needed). """ engine = ModelEngine(demo_mode=demo_mode) registry = create_default_registry() with gr.Blocks( title="Agentic Market Analyzer", ) as app: gr.Markdown( "# Agentic Market Analyzer\n" "A custom ReAct agent for market microstructure analysis. " "Ask about any stock, ETF, or economic indicator." ) with gr.Row(): # ---------------------------------------------------------- # # Left panel: Chat # ---------------------------------------------------------- # with gr.Column(scale=7): chatbot = gr.Chatbot( label="Chat", height=550, ) with gr.Row(): msg_input = gr.Textbox( placeholder="Ask about any ticker, indicator, or market question...", show_label=False, scale=8, container=False, ) send_btn = gr.Button("Send", variant="primary", scale=1) clear_btn = gr.Button("Clear", scale=1) gr.Examples( examples=_EXAMPLES, inputs=msg_input, label="Example queries", ) # ---------------------------------------------------------- # # Right panel: Reasoning log # ---------------------------------------------------------- # with gr.Column(scale=3): with gr.Accordion("Agent Reasoning", open=True): reasoning_log = gr.Markdown( value="*Waiting for a query...*", label="Reasoning Chain", ) with gr.Accordion("Tool Execution Log", open=False): tool_log = gr.JSON(label="Tool Results", value={}) with gr.Accordion("Session Info", open=False): session_info = gr.Markdown( value=( f"- Mode: {'Demo (mock model)' if demo_mode else 'Live (GPU)'}\n" f"- Max iterations: 8\n" f"- Tools: {', '.join(registry.list_names())}" ) ) # -------------------------------------------------------------- # # State # -------------------------------------------------------------- # chat_history = gr.State([]) # -------------------------------------------------------------- # # Event handlers # -------------------------------------------------------------- # async def handle_message( user_message: str, history: list, ) -> tuple[list, list, str, str, dict]: """Process a user message through the agent loop.""" if not user_message.strip(): return history, history, "", "*No query provided.*", {} # Append user message to chat. history = history + [{"role": "user", "content": user_message}] orchestrator = AgentOrchestrator( model=engine, tool_registry=registry, max_iterations=8, tool_timeout=30.0, ) log_entries: list[str] = [] tool_results: dict[str, Any] = {} final_answer = "" async for event in orchestrator.run(user_message): log_entry = format_event_for_log(event) log_entries.append(log_entry) if event.type == EventType.TOOL_RESULT: tool_name = event.metadata.get("tool", "unknown") tool_results[tool_name] = { "success": event.metadata.get("success", False), "time_ms": event.metadata.get("execution_time_ms", 0), "cached": event.metadata.get("cached", False), "preview": event.content[:200], } chat_msg = format_event_for_chatbot(event) if chat_msg: final_answer = chat_msg if final_answer: history = history + [{"role": "assistant", "content": final_answer}] reasoning_md = "\n\n---\n\n".join(log_entries) if log_entries else "*No events.*" return ( history, # chatbot history, # chat_history state "", # clear input reasoning_md, # reasoning_log tool_results, # tool_log ) def clear_chat() -> tuple[list, list, str, str, dict]: return [], [], "", "*Waiting for a query...*", {} send_btn.click( fn=handle_message, inputs=[msg_input, chat_history], outputs=[chatbot, chat_history, msg_input, reasoning_log, tool_log], ) msg_input.submit( fn=handle_message, inputs=[msg_input, chat_history], outputs=[chatbot, chat_history, msg_input, reasoning_log, tool_log], ) clear_btn.click( fn=clear_chat, outputs=[chatbot, chat_history, msg_input, reasoning_log, tool_log], ) return app _CUSTOM_CSS = """ .gradio-container { max-width: 1400px !important; } """