From 9201f77c9f3d0fc04bdb4fd9c889023e44d03a92 Mon Sep 17 00:00:00 2001 From: Kevin Bruton Date: Mon, 29 Sep 2025 19:09:20 +0200 Subject: [PATCH] feat: menu updates --- memory_store/chroma.sqlite3 | Bin 163840 -> 163840 bytes webapp/main.py | 223 +++++++++++-------- webapp/static/styles.css | 2 + webapp/templates/_partials/left_panel.html | 31 +-- webapp/templates/_partials/tree_content.html | 34 +++ webapp/templates/index.html | 144 ++++++++++++ 6 files changed, 328 insertions(+), 106 deletions(-) create mode 100644 webapp/templates/_partials/tree_content.html diff --git a/memory_store/chroma.sqlite3 b/memory_store/chroma.sqlite3 index cbe7969ec73832bcc636f075a641d317b5e7b558..33891af576834b968374df207b06a1d90fd7d8ce 100644 GIT binary patch delta 110 zcmZo@;A&{#njp<+G*QNx(P(4B5`AV(e&fmP2984f(fncjf&4!F?)*;tw)__S#+wZl wZu03eYcex%GHZYcbr7KjB2+92l?gO?FUlX-=?jPq1g)o?y?k GzySbyg%2qJ diff --git a/webapp/main.py b/webapp/main.py index 068ac62f..1d3f67d1 100644 --- a/webapp/main.py +++ b/webapp/main.py @@ -52,11 +52,8 @@ def update_execution_state(state: Dict[str, Any]): print(f"πŸ“‘ Callback received state keys: {list(state.keys())}") with app_state_lock: - # Initialize the complete execution tree structure if not exists - if not app_state["execution_tree"] or ( - len(app_state["execution_tree"]) == 1 and - app_state["execution_tree"][0]["id"] == "initialization" - ): + # Ensure execution tree is initialized + if not app_state["execution_tree"]: app_state["execution_tree"] = initialize_complete_execution_tree() # Map LangGraph node names to our tracking system @@ -149,77 +146,70 @@ def update_execution_state(state: Dict[str, Any]): update_agent_status(agent_info, "completed", report_data, state) # Update overall progress - root_node = app_state["execution_tree"][0] + execution_tree = app_state["execution_tree"] total_agents = len(agent_state_mapping) - completed_agents = count_completed_agents(root_node) + completed_agents = count_completed_agents(execution_tree) app_state["overall_progress"] = min(100, int((completed_agents / max(total_agents, 1)) * 100)) print(f"πŸ“Š Progress updated: {app_state['overall_progress']}% ({completed_agents}/{total_agents} agents)") def initialize_complete_execution_tree(): """Initialize the complete execution tree with all agents in pending state.""" - return [{ - "id": "root", - "name": f"πŸ“ˆ Trading Analysis for {app_state['company_symbol']}", - "status": "in_progress", - "content": f"Comprehensive trading analysis for {app_state['company_symbol']}", - "children": [ - { - "id": "data_collection_phase", - "name": "πŸ“Š Data Collection Phase", - "status": "pending", - "content": "Collecting market data and analysis from various sources", - "children": [ - create_agent_node("market_analyst", "πŸ“ˆ Market Analyst"), - create_agent_node("social_analyst", "πŸ“± Social Media Analyst"), - create_agent_node("news_analyst", "πŸ“° News Analyst"), - create_agent_node("fundamentals_analyst", "πŸ“Š Fundamentals Analyst") - ] - }, - { - "id": "research_phase", - "name": "πŸ” Research Phase", - "status": "pending", - "content": "Research and debate investment perspectives", - "children": [ - create_agent_node("bull_researcher", "πŸ‚ Bull Researcher"), - create_agent_node("bear_researcher", "🐻 Bear Researcher"), - create_agent_node("research_manager", "πŸ” Research Manager") - ] - }, - { - "id": "planning_phase", - "name": "πŸ“‹ Planning Phase", - "status": "pending", - "content": "Develop trading strategy and execution plan", - "children": [ - create_agent_node("trade_planner", "πŸ“‹ Trade Planner") - ] - }, - { - "id": "execution_phase", - "name": "⚑ Execution Phase", - "status": "pending", - "content": "Execute trades based on analysis and planning", - "children": [ - create_agent_node("trader", "⚑ Trader") - ] - }, - { - "id": "risk_analysis_phase", - "name": "⚠️ Risk Management Phase", - "status": "pending", - "content": "Assess and manage investment risks", - "children": [ - create_agent_node("risky_analyst", "🚨 Aggressive Risk Analyst"), - create_agent_node("neutral_analyst", "βš–οΈ Neutral Risk Analyst"), - create_agent_node("safe_analyst", "πŸ›‘οΈ Conservative Risk Analyst"), - create_agent_node("risk_judge", "⚠️ Risk Judge") - ] - } - ], - "timestamp": time.time() - }] + return [ + { + "id": "data_collection_phase", + "name": "πŸ“Š Data Collection Phase", + "status": "pending", + "content": "Collecting market data and analysis from various sources", + "children": [ + create_agent_node("market_analyst", "πŸ“ˆ Market Analyst"), + create_agent_node("social_analyst", "πŸ“± Social Media Analyst"), + create_agent_node("news_analyst", "πŸ“° News Analyst"), + create_agent_node("fundamentals_analyst", "πŸ“Š Fundamentals Analyst") + ] + }, + { + "id": "research_phase", + "name": "πŸ” Research Phase", + "status": "pending", + "content": "Research and debate investment perspectives", + "children": [ + create_agent_node("bull_researcher", "πŸ‚ Bull Researcher"), + create_agent_node("bear_researcher", "🐻 Bear Researcher"), + create_agent_node("research_manager", "πŸ” Research Manager") + ] + }, + { + "id": "planning_phase", + "name": "πŸ“‹ Planning Phase", + "status": "pending", + "content": "Develop trading strategy and execution plan", + "children": [ + create_agent_node("trade_planner", "πŸ“‹ Trade Planner") + ] + }, + { + "id": "execution_phase", + "name": "⚑ Execution Phase", + "status": "pending", + "content": "Execute trades based on analysis and planning", + "children": [ + create_agent_node("trader", "⚑ Trader") + ] + }, + { + "id": "risk_analysis_phase", + "name": "⚠️ Risk Management Phase", + "status": "pending", + "content": "Assess and manage investment risks", + "children": [ + create_agent_node("risky_analyst", "🚨 Aggressive Risk Analyst"), + create_agent_node("neutral_analyst", "βš–οΈ Neutral Risk Analyst"), + create_agent_node("safe_analyst", "πŸ›‘οΈ Conservative Risk Analyst"), + create_agent_node("risk_judge", "⚠️ Risk Judge") + ] + } + ] def create_agent_node(agent_id: str, agent_name: str): """Create a standardized agent node with report and messages sub-items.""" @@ -262,10 +252,10 @@ def get_nested_value(data: dict, key_path: str): def update_agent_status(agent_info: dict, status: str, report_data: any, full_state: dict): """Update an agent's status and content in the execution tree.""" - root_node = app_state["execution_tree"][0] + execution_tree = app_state["execution_tree"] # Find the agent in the tree - agent_node = find_agent_in_tree(agent_info["agent_id"], root_node) + agent_node = find_agent_in_tree(agent_info["agent_id"], execution_tree) if not agent_node: return @@ -287,14 +277,15 @@ def update_agent_status(agent_info: dict, status: str, report_data: any, full_st messages_node["content"] = extract_agent_messages(full_state, agent_info["agent_id"]) # Update phase status if all agents in phase are completed - update_phase_status_if_complete(agent_info["phase"], root_node) + update_phase_status_if_complete(agent_info["phase"], execution_tree) -def find_agent_in_tree(agent_id: str, root_node: dict): +def find_agent_in_tree(agent_id: str, tree: list): """Find an agent node in the execution tree.""" - for phase in root_node["children"]: - for agent in phase["children"]: - if agent["id"] == agent_id: - return agent + for phase in tree: + if phase.get("children"): + for agent in phase["children"]: + if agent["id"] == agent_id: + return agent return None def find_item_by_id(item_id: str, items: list): @@ -326,9 +317,9 @@ def extract_agent_messages(state: dict, agent_id: str) -> str: else: return "πŸ’¬ Agent Messages\n\nExecution completed without specific message logs" -def update_phase_status_if_complete(phase_id: str, root_node: dict): +def update_phase_status_if_complete(phase_id: str, execution_tree: list): """Update phase status to completed if all its agents are completed.""" - phase_node = find_item_by_id(f"{phase_id}_phase", root_node["children"]) + phase_node = find_item_by_id(f"{phase_id}_phase", execution_tree) if not phase_node: return @@ -338,13 +329,14 @@ def update_phase_status_if_complete(phase_id: str, root_node: dict): phase_node["status"] = "completed" phase_node["content"] = f"βœ… {phase_node['name']} - All agents completed successfully" -def count_completed_agents(root_node: dict) -> int: +def count_completed_agents(execution_tree: list) -> int: """Count the number of completed agents across all phases.""" count = 0 - for phase in root_node["children"]: - for agent in phase["children"]: - if agent["status"] == "completed": - count += 1 + for phase in execution_tree: + if phase.get("children"): + for agent in phase["children"]: + if agent["status"] == "completed": + count += 1 return count def run_trading_process(company_symbol: str, config: Dict[str, Any]): @@ -475,15 +467,8 @@ async def start_process( "analysis_date": analysis_date } - # Initialize execution tree with startup message - app_state["execution_tree"] = [{ - "id": "initialization", - "name": f"πŸš€ Initializing Trading Analysis for {company_symbol}", - "status": "in_progress", - "content": f"Starting comprehensive trading analysis for {company_symbol}...\n\nConfiguration:\nβ€’ LLM Provider: {llm_provider}\nβ€’ Quick Think Model: {quick_think_llm}\nβ€’ Deep Think Model: {deep_think_llm}\nβ€’ Max Debate Rounds: {max_debate_rounds}\nβ€’ Cost Per Trade: ${cost_per_trade}\nβ€’ Analysis Date: {analysis_date}\n\nInitializing trading agents and preparing analysis pipeline...", - "children": [], - "timestamp": time.time() - }] + # Initialize execution tree with complete structure + app_state["execution_tree"] = initialize_complete_execution_tree() background_tasks.add_task(run_trading_process, company_symbol, app_state["config"]) @@ -496,6 +481,58 @@ async def get_status(): template = jinja_env.get_template("_partials/left_panel.html") return template.render(tree=app_state["execution_tree"], app_state=app_state) +@app.get("/status-content", response_class=HTMLResponse) +async def get_status_content(): + with app_state_lock: + if not app_state["execution_tree"]: + return HTMLResponse(content="

No process running. Start a new one from the configuration.

") + + template = jinja_env.get_template("_partials/tree_content.html") + tree_content = template.render(tree=app_state["execution_tree"]) + + # Add out-of-band updates for progress bar + progress_updates = f''' +
+ {app_state["overall_progress"]}% ({app_state["overall_status"]}) + ''' + + return HTMLResponse(content=tree_content + progress_updates) + +@app.get("/status-updates") +async def get_status_updates(): + """Return only the status updates as JSON for targeted updates.""" + with app_state_lock: + status_updates = {} + + def extract_status_info(items, prefix=""): + for item in items: + item_id = item["id"] + status_updates[item_id] = { + "status": item["status"], + "status_icon": get_status_icon(item["status"]) + } + if item.get("children"): + extract_status_info(item["children"]) + + extract_status_info(app_state["execution_tree"]) + + return { + "status_updates": status_updates, + "overall_progress": app_state["overall_progress"], + "overall_status": app_state["overall_status"] + } + +def get_status_icon(status: str) -> str: + """Get the status icon for a given status.""" + if status == 'completed': + return 'βœ…' + elif status == 'in_progress': + return '⏳' + elif status == 'error': + return '❌' + else: + return '⏸️' + def find_item_in_tree(item_id: str, tree: list) -> Dict[str, Any] | None: """Recursively searches the execution tree for an item by its ID.""" for item in tree: diff --git a/webapp/static/styles.css b/webapp/static/styles.css index 9f79c5af..aae32e50 100644 --- a/webapp/static/styles.css +++ b/webapp/static/styles.css @@ -341,6 +341,8 @@ body { .status-icon { margin-right: 8px; font-size: 0.9em; + transition: all 0.3s ease; + display: inline-block; } /* Specific styling for different item types */ diff --git a/webapp/templates/_partials/left_panel.html b/webapp/templates/_partials/left_panel.html index 5ca29167..e495b9a5 100644 --- a/webapp/templates/_partials/left_panel.html +++ b/webapp/templates/_partials/left_panel.html @@ -1,5 +1,5 @@ {% macro render_item(item) %} -
  • +
  • {% if item.children %}
    {% if item.children %} -