agentic-market-analyzer / ui /components.py
WolfDavid's picture
Upload ui/components.py with huggingface_hub
151210a verified
"""
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;
}
"""