From 77629bc89e427a8464c36369ba01a3f075dc904d Mon Sep 17 00:00:00 2001 From: Kevin Bruton Date: Mon, 29 Sep 2025 08:41:10 +0200 Subject: [PATCH] fix: frontend - backend issues and styles --- memory_store/chroma.sqlite3 | Bin 163840 -> 163840 bytes tradingagents/graph/trading_graph.py | 9 +- webapp/main.py | 131 +++++++++++++++------ webapp/static/styles.css | 75 ++++++++++++ webapp/templates/_partials/left_panel.html | 13 +- webapp/templates/index.html | 4 +- 6 files changed, 187 insertions(+), 45 deletions(-) diff --git a/memory_store/chroma.sqlite3 b/memory_store/chroma.sqlite3 index aad62f7e01c2fcaa07abce8c2b4ab0a5ec4714c4..bd325de5dbe8d5bde664a66f37d43bc4879ee4c2 100644 GIT binary patch delta 54 zcmZo@;A&{#njp=nI#I@%QFUX&5`AVF{*uY;2IfrsC7T@$O!!%uWtbT_n=9?xEA1J# KSK2eRI{*NWcMehj delta 45 zcmZo@;A&{#njp=nGEv5vQDtMo5`AWA{>sVh2IiXu9jy7AEA87W?HRXM+B3B~001$Q B4Tb;! diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 29300689..4d86d74b 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -203,20 +203,17 @@ class TradingAgentsGraph: ) args = self.propagator.get_graph_args() - if self.debug: - # Debug mode with tracing + if on_step_callback or self.debug: + # Stream mode for callbacks or debug mode trace = [] for s in self.graph.stream(init_agent_state, **args): trace.append(s) if on_step_callback: on_step_callback(s) - final_state = trace[-1] + final_state = trace[-1] if trace else {} else: # Standard mode without tracing final_state = self.graph.invoke(init_agent_state, **args) - # If not in debug mode, we still want to call the callback for the final state - if on_step_callback: - on_step_callback(final_state) # Store current state for reflection self.curr_state = final_state diff --git a/webapp/main.py b/webapp/main.py index c902780d..6e037461 100644 --- a/webapp/main.py +++ b/webapp/main.py @@ -50,44 +50,103 @@ jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader(template_dir)) def update_execution_state(state: Dict[str, Any]): """Callback function to update the app_state based on LangGraph's state.""" with app_state_lock: - current_step_name = None - # LangGraph state typically has a single key for the current node's output - # We need to find which agent just ran - for key, value in state.items(): - if key != "__end__": # Ignore the special __end__ key - current_step_name = key - break - - if current_step_name: - # Find the root node or create it if it doesn't exist - if not app_state["execution_tree"]: - app_state["execution_tree"].append({ - "id": "root", - "name": f"Trading Analysis for {app_state['company_symbol']}", - "status": "in_progress", - "content": "", - "children": [], - "timestamp": time.time() - }) - - root_node = app_state["execution_tree"][0] - - # Check if this step already exists (e.g., if an agent runs multiple times) - # For simplicity, we'll just append for now. A more robust solution would update existing. - new_item = { - "id": f"{current_step_name}-{len(root_node['children'])}", # Simple unique ID - "name": current_step_name, - "status": "completed", # Assume completed for now - "content": str(state.get(current_step_name, "No specific output")), # Store the agent's output + # Initialize the root node if it doesn't exist + if not app_state["execution_tree"]: + app_state["execution_tree"].append({ + "id": "root", + "name": f"Trading Analysis for {app_state['company_symbol']}", + "status": "in_progress", + "content": f"Analyzing {app_state['company_symbol']} using multiple trading agents", "children": [], "timestamp": time.time() - } - root_node["children"].append(new_item) - root_node["status"] = "in_progress" # Keep root in progress until final - - # Update overall progress (very basic, just increments) - # In a real scenario, you'd have a predefined number of steps - app_state["overall_progress"] = min(100, app_state["overall_progress"] + 5) + }) + + root_node = app_state["execution_tree"][0] + + # Define the expected phases and their order + phase_map = { + "market_analyst": {"name": "Market Analysis", "phase": "data_collection"}, + "social_analyst": {"name": "Social Media Analysis", "phase": "data_collection"}, + "news_analyst": {"name": "News Analysis", "phase": "data_collection"}, + "fundamentals_analyst": {"name": "Fundamental Analysis", "phase": "data_collection"}, + "bull_researcher": {"name": "Bull Case Research", "phase": "research"}, + "bear_researcher": {"name": "Bear Case Research", "phase": "research"}, + "research_manager": {"name": "Research Synthesis", "phase": "research"}, + "trade_planner": {"name": "Trade Planning", "phase": "planning"}, + "trader": {"name": "Trade Execution", "phase": "execution"}, + "risky_analyst": {"name": "Risk Assessment (Aggressive)", "phase": "risk_analysis"}, + "neutral_analyst": {"name": "Risk Assessment (Neutral)", "phase": "risk_analysis"}, + "safe_analyst": {"name": "Risk Assessment (Conservative)", "phase": "risk_analysis"}, + "risk_judge": {"name": "Final Risk Evaluation", "phase": "risk_analysis"} + } + + # Find which agent just completed by examining the state + for key, value in state.items(): + if key in ["__end__", "messages"]: + continue + + # Map the key to a more user-friendly name + agent_key = key.lower().replace(" ", "_").replace("_agent", "").replace("_node", "") + if agent_key in phase_map: + phase_info = phase_map[agent_key] + + # Find or create phase category + phase_category = None + for child in root_node["children"]: + if child["id"] == phase_info["phase"]: + phase_category = child + break + + if not phase_category: + phase_names = { + "data_collection": "πŸ“Š Data Collection", + "research": "πŸ” Research & Analysis", + "planning": "πŸ“‹ Trade Planning", + "execution": "⚑ Trade Execution", + "risk_analysis": "⚠️ Risk Management" + } + + phase_category = { + "id": phase_info["phase"], + "name": phase_names.get(phase_info["phase"], phase_info["phase"]), + "status": "in_progress", + "content": f"Phase: {phase_names.get(phase_info['phase'], phase_info['phase'])}", + "children": [], + "timestamp": time.time() + } + root_node["children"].append(phase_category) + + # Check if this specific step already exists + step_exists = False + for step in phase_category["children"]: + if step["name"] == phase_info["name"]: + step["status"] = "completed" + step["content"] = str(value) if value else "Completed successfully" + step_exists = True + break + + if not step_exists: + # Add new step + new_step = { + "id": f"{phase_info['phase']}_{agent_key}_{len(phase_category['children'])}", + "name": phase_info["name"], + "status": "completed", + "content": str(value) if value else "Completed successfully", + "children": [], + "timestamp": time.time() + } + phase_category["children"].append(new_step) + + # Check if phase is complete (simple heuristic) + completed_steps = sum(1 for step in phase_category["children"] if step["status"] == "completed") + if completed_steps >= len(phase_category["children"]): + phase_category["status"] = "completed" + + # Update overall progress based on completed phases + total_phases = len([p for p in phase_map.values()]) + completed_agents = sum(len(child["children"]) for child in root_node["children"] + if child.get("children")) + app_state["overall_progress"] = min(100, int((completed_agents / max(total_phases, 1)) * 100)) def run_trading_process(company_symbol: str): """Runs the TradingAgentsGraph in a separate thread.""" diff --git a/webapp/static/styles.css b/webapp/static/styles.css index 13fa1840..e9b3da6e 100644 --- a/webapp/static/styles.css +++ b/webapp/static/styles.css @@ -205,6 +205,81 @@ body { font-size: 1.1em; } +/* Execution Tree Styles */ +.execution-tree { + margin-top: 20px; +} + +.execution-tree ul { + margin-left: 20px; + border-left: 2px solid var(--border-color); + padding-left: 15px; +} + +.process-item { + position: relative; + margin-bottom: 5px; +} + +.process-item::before { + content: ''; + position: absolute; + left: -18px; + top: 12px; + width: 8px; + height: 2px; + background-color: var(--border-color); +} + +.clickable { + cursor: pointer; + transition: all 0.2s ease; +} + +.clickable:hover { + transform: translateX(3px); +} + +.status-icon { + margin-right: 8px; + font-size: 0.9em; +} + +/* Loading indicator */ +.htmx-indicator { + display: none; + color: var(--accent-color); + font-size: 0.9em; + margin-top: 10px; + animation: pulse 2s infinite; +} + +.htmx-request .htmx-indicator { + display: block; +} + +/* Content display improvements */ +#right-panel pre { + background-color: var(--bg-secondary); + padding: 20px; + border-radius: 8px; + border: 1px solid var(--border-color); + color: var(--text-primary); + font-family: 'Monaco', 'Menlo', 'Consolas', monospace; + font-size: 0.9em; + line-height: 1.4; + overflow-x: auto; + white-space: pre-wrap; + word-wrap: break-word; +} + +#right-panel h3 { + color: var(--text-primary); + margin-bottom: 15px; + font-size: 1.3em; + font-weight: 600; +} + /* Animations */ @keyframes pulse { 0% { diff --git a/webapp/templates/_partials/left_panel.html b/webapp/templates/_partials/left_panel.html index ad4361f0..fa81f7a1 100644 --- a/webapp/templates/_partials/left_panel.html +++ b/webapp/templates/_partials/left_panel.html @@ -1,6 +1,15 @@ {% macro render_item(item) %}
  • - {{ item.name }} + + + {% if item.status == 'completed' %}βœ“ + {% elif item.status == 'in_progress' %}⏳ + {% elif item.status == 'error' %}❌ + {% else %}⏸️ + {% endif %} + + {{ item.name }} + {% if item.children %}