fix: frontend left panel updates and functionality
This commit is contained in:
parent
cea3411911
commit
105e93faf2
|
|
@ -78,10 +78,14 @@ The frontend will be built using HTMX attributes directly in the HTML templates.
|
|||
* Contains the initial configuration form. The form will have an `hx-post="/start"` attribute to trigger the process.
|
||||
|
||||
* **Left Panel (`_partials/left_panel.html`)**:
|
||||
* This partial will be the target of the status polling. The main container will have `hx-get="/status"` and `hx-trigger="load, every 2s"`.
|
||||
* This partial will be the target of the status polling. The main container will have `hx-get="/status"` and `hx-trigger="load, every 5s"`.
|
||||
* It will use a template loop (Jinja2) to render the hierarchical tree from the state object provided by the backend.
|
||||
* Each item in the tree will be a clickable element with an `hx-get="/content/{item_id}"` attribute and an `hx-target="#right-panel"` attribute to load its content on the right side.
|
||||
* The status of each item (pending, in-progress, completed, error) will be reflected using different CSS classes.
|
||||
* The status of each item (pending, in-progress, completed, error) will be reflected using different CSS classes and icons:
|
||||
- **Pending**: ⏸️ (paused icon, gray color)
|
||||
- **In Progress**: ⏳ (hourglass icon, blue color)
|
||||
- **Completed**: ✅ (check mark, green color)
|
||||
- **Error**: ❌ (X mark, red color)
|
||||
|
||||
* **Right Panel (`_partials/right_panel.html`)**:
|
||||
* A simple container (`<div id="right-panel">`) that gets its content replaced by HTMX when a user clicks an item on the left.
|
||||
|
|
@ -91,6 +95,65 @@ The frontend will be built using HTMX attributes directly in the HTML templates.
|
|||
* The response from the initial `POST /start` call will replace the configuration form with a global progress bar.
|
||||
* This progress bar's value will be updated as part of the `/status` polling response, by targeting its element ID with an `hx-swap-oob="true"` (Out of Band swap).
|
||||
|
||||
### Execution Tree Structure
|
||||
|
||||
The left panel should display a hierarchical tree structure as follows:
|
||||
|
||||
```
|
||||
📈 Trading Analysis for [SYMBOL]
|
||||
├── 📊 Data Collection Phase
|
||||
│ ├── 📈 Market Analyst
|
||||
│ │ ├── 📄 Market Analysis Report
|
||||
│ │ └── 💬 Agent Messages
|
||||
│ ├── 📱 Social Media Analyst
|
||||
│ │ ├── 📄 Sentiment Analysis Report
|
||||
│ │ └── 💬 Agent Messages
|
||||
│ ├── 📰 News Analyst
|
||||
│ │ ├── 📄 News Analysis Report
|
||||
│ │ └── 💬 Agent Messages
|
||||
│ └── 📊 Fundamentals Analyst
|
||||
│ ├── 📄 Fundamentals Report
|
||||
│ └── 💬 Agent Messages
|
||||
├── 🔍 Research Phase
|
||||
│ ├── 🐂 Bull Researcher
|
||||
│ │ ├── 📄 Bull Case Analysis
|
||||
│ │ └── 💬 Agent Messages
|
||||
│ ├── 🐻 Bear Researcher
|
||||
│ │ ├── 📄 Bear Case Analysis
|
||||
│ │ └── 💬 Agent Messages
|
||||
│ └── 🔍 Research Manager
|
||||
│ ├── 📄 Research Synthesis
|
||||
│ └── 💬 Agent Messages
|
||||
├── 📋 Planning Phase
|
||||
│ └── 📋 Trade Planner
|
||||
│ ├── 📄 Trading Plan
|
||||
│ └── 💬 Agent Messages
|
||||
├── ⚡ Execution Phase
|
||||
│ └── ⚡ Trader
|
||||
│ ├── 📄 Execution Report
|
||||
│ └── 💬 Agent Messages
|
||||
└── ⚠️ Risk Management Phase
|
||||
├── 🚨 Aggressive Risk Analyst
|
||||
│ ├── 📄 Risk Assessment (Aggressive)
|
||||
│ └── 💬 Agent Messages
|
||||
├── ⚖️ Neutral Risk Analyst
|
||||
│ ├── 📄 Risk Assessment (Neutral)
|
||||
│ └── 💬 Agent Messages
|
||||
├── 🛡️ Conservative Risk Analyst
|
||||
│ ├── 📄 Risk Assessment (Conservative)
|
||||
│ └── 💬 Agent Messages
|
||||
└── ⚠️ Risk Judge
|
||||
├── 📄 Final Risk Decision
|
||||
└── 💬 Agent Messages
|
||||
```
|
||||
|
||||
Each agent should have:
|
||||
1. **Status Icon**: Shows current execution state (pending, in-progress, completed, error)
|
||||
2. **Report Sub-item**: Shows the specific report generated by that agent
|
||||
3. **Messages Sub-item**: Shows messages to/from that agent during execution
|
||||
|
||||
The tree structure should be initialized at the start showing all agents in "pending" state, then update their status as execution progresses.
|
||||
|
||||
## 5. Detailed Implementation Steps
|
||||
|
||||
1. **Setup Environment**:
|
||||
|
|
@ -127,3 +190,46 @@ The frontend will be built using HTMX attributes directly in the HTML templates.
|
|||
* Add a loading indicator for HTMX requests.
|
||||
* Refine the CSS to ensure the application is visually appealing and user-friendly.
|
||||
* Ensure the background process is managed correctly, especially in case of errors or server shutdown.
|
||||
|
||||
## 6. Current Implementation Issues & Solutions
|
||||
|
||||
### Issues Identified:
|
||||
|
||||
1. **Incomplete Agent Tree Structure**: The current implementation only shows a single top-level item "Trading Analysis for [SYMBOL]" with limited sub-items, instead of the full agent hierarchy.
|
||||
|
||||
2. **Improper Status Tracking**: Agents don't show proper execution status (pending, in-progress, completed, error) with appropriate icons.
|
||||
|
||||
3. **Missing Reports and Messages**: Sub-items for individual agent reports and messages are not being created or displayed.
|
||||
|
||||
4. **Callback State Detection**: The `update_execution_state` callback in `webapp/main.py` is not properly detecting and organizing the execution flow of all agents.
|
||||
|
||||
### Solutions Implemented:
|
||||
|
||||
#### Backend Changes (`webapp/main.py`):
|
||||
|
||||
1. **Initialize Complete Tree Structure**: Pre-populate the execution tree with all agents in "pending" state at the start of execution.
|
||||
|
||||
2. **Improved State Detection**: Enhanced the callback function to:
|
||||
- Detect agent execution start/completion more reliably
|
||||
- Track both agent status and their generated reports/messages
|
||||
- Maintain proper phase organization (Data Collection, Research, Planning, Execution, Risk Management)
|
||||
|
||||
3. **Agent Sub-items**: Each agent now has sub-items for:
|
||||
- **Report**: The specific analysis/report generated by the agent
|
||||
- **Messages**: Communication to/from the agent during execution
|
||||
|
||||
#### Frontend Changes (`_partials/left_panel.html`):
|
||||
|
||||
1. **Enhanced Status Icons**: Clear visual indicators for each execution state
|
||||
2. **Hierarchical Display**: Proper nesting of phases, agents, and their sub-items
|
||||
3. **Clickable Content**: All items are clickable to show detailed content in the right panel
|
||||
|
||||
#### State Management:
|
||||
|
||||
The execution tree now properly reflects:
|
||||
- **Phases**: Logical grouping of related agents (Data Collection, Research, etc.)
|
||||
- **Agents**: Individual agents with their execution status
|
||||
- **Sub-items**: Reports and messages for each agent
|
||||
- **Real-time Updates**: Status changes as execution progresses
|
||||
|
||||
This provides users with complete visibility into the trading analysis process, allowing them to track which agents are running, completed, or encountering issues, and access detailed reports and communications from each agent.
|
||||
|
|
|
|||
Binary file not shown.
394
webapp/main.py
394
webapp/main.py
|
|
@ -52,126 +52,300 @@ def update_execution_state(state: Dict[str, Any]):
|
|||
print(f"📡 Callback received state keys: {list(state.keys())}")
|
||||
|
||||
with app_state_lock:
|
||||
# Initialize the root node if needed
|
||||
# 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"
|
||||
):
|
||||
app_state["execution_tree"] = [{
|
||||
"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",
|
||||
app_state["execution_tree"] = initialize_complete_execution_tree()
|
||||
|
||||
# Map LangGraph node names to our tracking system
|
||||
agent_state_mapping = {
|
||||
"Market Analyst": {
|
||||
"phase": "data_collection",
|
||||
"agent_id": "market_analyst",
|
||||
"report_key": "market_report",
|
||||
"report_name": "Market Analysis Report"
|
||||
},
|
||||
"Social Analyst": {
|
||||
"phase": "data_collection",
|
||||
"agent_id": "social_analyst",
|
||||
"report_key": "sentiment_report",
|
||||
"report_name": "Sentiment Analysis Report"
|
||||
},
|
||||
"News Analyst": {
|
||||
"phase": "data_collection",
|
||||
"agent_id": "news_analyst",
|
||||
"report_key": "news_report",
|
||||
"report_name": "News Analysis Report"
|
||||
},
|
||||
"Fundamentals Analyst": {
|
||||
"phase": "data_collection",
|
||||
"agent_id": "fundamentals_analyst",
|
||||
"report_key": "fundamentals_report",
|
||||
"report_name": "Fundamentals Report"
|
||||
},
|
||||
"Bull Researcher": {
|
||||
"phase": "research",
|
||||
"agent_id": "bull_researcher",
|
||||
"report_key": "investment_debate_state.bull_history",
|
||||
"report_name": "Bull Case Analysis"
|
||||
},
|
||||
"Bear Researcher": {
|
||||
"phase": "research",
|
||||
"agent_id": "bear_researcher",
|
||||
"report_key": "investment_debate_state.bear_history",
|
||||
"report_name": "Bear Case Analysis"
|
||||
},
|
||||
"Research Manager": {
|
||||
"phase": "research",
|
||||
"agent_id": "research_manager",
|
||||
"report_key": "investment_debate_state.judge_decision",
|
||||
"report_name": "Research Synthesis"
|
||||
},
|
||||
"Trade Planner": {
|
||||
"phase": "planning",
|
||||
"agent_id": "trade_planner",
|
||||
"report_key": "trader_investment_plan",
|
||||
"report_name": "Trading Plan"
|
||||
},
|
||||
"Trader": {
|
||||
"phase": "execution",
|
||||
"agent_id": "trader",
|
||||
"report_key": "investment_plan",
|
||||
"report_name": "Execution Report"
|
||||
},
|
||||
"Risky Analyst": {
|
||||
"phase": "risk_analysis",
|
||||
"agent_id": "risky_analyst",
|
||||
"report_key": "risk_debate_state.risky_history",
|
||||
"report_name": "Risk Assessment (Aggressive)"
|
||||
},
|
||||
"Neutral Analyst": {
|
||||
"phase": "risk_analysis",
|
||||
"agent_id": "neutral_analyst",
|
||||
"report_key": "risk_debate_state.neutral_history",
|
||||
"report_name": "Risk Assessment (Neutral)"
|
||||
},
|
||||
"Safe Analyst": {
|
||||
"phase": "risk_analysis",
|
||||
"agent_id": "safe_analyst",
|
||||
"report_key": "risk_debate_state.safe_history",
|
||||
"report_name": "Risk Assessment (Conservative)"
|
||||
},
|
||||
"Risk Judge": {
|
||||
"phase": "risk_analysis",
|
||||
"agent_id": "risk_judge",
|
||||
"report_key": "final_trade_decision",
|
||||
"report_name": "Final Risk Decision"
|
||||
}
|
||||
}
|
||||
|
||||
# Update agent statuses based on available reports
|
||||
for agent_name, agent_info in agent_state_mapping.items():
|
||||
# Check if this agent has completed (has report data)
|
||||
report_data = get_nested_value(state, agent_info["report_key"])
|
||||
if report_data:
|
||||
update_agent_status(agent_info, "completed", report_data, state)
|
||||
|
||||
# Update overall progress
|
||||
root_node = app_state["execution_tree"][0]
|
||||
total_agents = len(agent_state_mapping)
|
||||
completed_agents = count_completed_agents(root_node)
|
||||
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()
|
||||
}]
|
||||
|
||||
def create_agent_node(agent_id: str, agent_name: str):
|
||||
"""Create a standardized agent node with report and messages sub-items."""
|
||||
return {
|
||||
"id": agent_id,
|
||||
"name": agent_name,
|
||||
"status": "pending",
|
||||
"content": f"Agent: {agent_name} - Awaiting execution",
|
||||
"children": [
|
||||
{
|
||||
"id": f"{agent_id}_report",
|
||||
"name": "📄 Report",
|
||||
"status": "pending",
|
||||
"content": "Report not yet generated",
|
||||
"children": [],
|
||||
"timestamp": time.time()
|
||||
}]
|
||||
|
||||
root_node = app_state["execution_tree"][0]
|
||||
|
||||
# Map LangGraph node names to user-friendly display info
|
||||
node_mapping = {
|
||||
"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"}
|
||||
}
|
||||
|
||||
phase_names = {
|
||||
"data_collection": "📊 Data Collection",
|
||||
"research": "🔍 Research & Analysis",
|
||||
"planning": "📋 Trade Planning",
|
||||
"execution": "⚡ Trade Execution",
|
||||
"risk_analysis": "⚠️ Risk Management"
|
||||
}
|
||||
|
||||
# The state dict contains the current state of all nodes
|
||||
# We need to determine what has actually been executed
|
||||
current_step = None
|
||||
|
||||
# LangGraph streams the full state each time, so we need to detect what's new
|
||||
# Look for populated report fields to determine what has been completed
|
||||
if state.get("market_report") and not any(child.get("id") == "data_collection_market" for phase in root_node["children"] for child in phase.get("children", [])):
|
||||
current_step = "Market Analyst"
|
||||
elif state.get("sentiment_report") and not any(child.get("id") == "data_collection_social" for phase in root_node["children"] for child in phase.get("children", [])):
|
||||
current_step = "Social Analyst"
|
||||
elif state.get("news_report") and not any(child.get("id") == "data_collection_news" for phase in root_node["children"] for child in phase.get("children", [])):
|
||||
current_step = "News Analyst"
|
||||
elif state.get("fundamentals_report") and not any(child.get("id") == "data_collection_fundamentals" for phase in root_node["children"] for child in phase.get("children", [])):
|
||||
current_step = "Fundamentals Analyst"
|
||||
elif state.get("investment_debate_state", {}).get("bull_history") and not any(child.get("id") == "research_bull" for phase in root_node["children"] for child in phase.get("children", [])):
|
||||
current_step = "Bull Researcher"
|
||||
elif state.get("investment_debate_state", {}).get("bear_history") and not any(child.get("id") == "research_bear" for phase in root_node["children"] for child in phase.get("children", [])):
|
||||
current_step = "Bear Researcher"
|
||||
elif state.get("investment_debate_state", {}).get("judge_decision") and not any(child.get("id") == "research_manager" for phase in root_node["children"] for child in phase.get("children", [])):
|
||||
current_step = "Research Manager"
|
||||
elif state.get("trader_investment_plan") and not any(child.get("id") == "planning_trade_planner" for phase in root_node["children"] for child in phase.get("children", [])):
|
||||
current_step = "Trade Planner"
|
||||
elif state.get("investment_plan") and not any(child.get("id") == "execution_trader" for phase in root_node["children"] for child in phase.get("children", [])):
|
||||
current_step = "Trader"
|
||||
elif state.get("risk_debate_state", {}).get("risky_history") and not any(child.get("id") == "risk_analysis_risky" for phase in root_node["children"] for child in phase.get("children", [])):
|
||||
current_step = "Risky Analyst"
|
||||
elif state.get("risk_debate_state", {}).get("neutral_history") and not any(child.get("id") == "risk_analysis_neutral" for phase in root_node["children"] for child in phase.get("children", [])):
|
||||
current_step = "Neutral Analyst"
|
||||
elif state.get("risk_debate_state", {}).get("safe_history") and not any(child.get("id") == "risk_analysis_safe" for phase in root_node["children"] for child in phase.get("children", [])):
|
||||
current_step = "Safe Analyst"
|
||||
elif state.get("final_trade_decision") and not any(child.get("id") == "risk_analysis_risk_judge" for phase in root_node["children"] for child in phase.get("children", [])):
|
||||
current_step = "Risk Judge"
|
||||
|
||||
if current_step and current_step in node_mapping:
|
||||
print(f"🎯 Processing step: {current_step}")
|
||||
node_info = node_mapping[current_step]
|
||||
phase_id = node_info["phase"]
|
||||
|
||||
# Find or create phase category
|
||||
phase_category = None
|
||||
for child in root_node["children"]:
|
||||
if child["id"] == phase_id:
|
||||
phase_category = child
|
||||
break
|
||||
|
||||
if not phase_category:
|
||||
phase_category = {
|
||||
"id": phase_id,
|
||||
"name": phase_names.get(phase_id, phase_id),
|
||||
"status": "in_progress",
|
||||
"content": f"Phase: {phase_names.get(phase_id, phase_id)}",
|
||||
"children": [],
|
||||
"timestamp": time.time()
|
||||
}
|
||||
root_node["children"].append(phase_category)
|
||||
|
||||
# Add new step
|
||||
step_id = f"{phase_id}_{current_step.lower().replace(' ', '_')}"
|
||||
new_step = {
|
||||
"id": step_id,
|
||||
"name": node_info["name"],
|
||||
"status": "completed",
|
||||
"content": f"✅ {node_info['name']} completed successfully",
|
||||
},
|
||||
{
|
||||
"id": f"{agent_id}_messages",
|
||||
"name": "💬 Messages",
|
||||
"status": "pending",
|
||||
"content": "No messages yet",
|
||||
"children": [],
|
||||
"timestamp": time.time()
|
||||
}
|
||||
phase_category["children"].append(new_step)
|
||||
|
||||
# Mark phase as completed if it has steps
|
||||
phase_category["status"] = "completed"
|
||||
|
||||
# Update overall progress
|
||||
total_steps = len(node_mapping)
|
||||
completed_steps = sum(len(child["children"]) for child in root_node["children"])
|
||||
app_state["overall_progress"] = min(100, int((completed_steps / max(total_steps, 1)) * 100))
|
||||
|
||||
print(f"📊 Progress updated: {app_state['overall_progress']}% ({completed_steps}/{total_steps} steps)")
|
||||
],
|
||||
"timestamp": time.time()
|
||||
}
|
||||
|
||||
def get_nested_value(data: dict, key_path: str):
|
||||
"""Get value from nested dict using dot notation (e.g., 'investment_debate_state.bull_history')."""
|
||||
keys = key_path.split('.')
|
||||
value = data
|
||||
for key in keys:
|
||||
if isinstance(value, dict) and key in value:
|
||||
value = value[key]
|
||||
else:
|
||||
print(f"⏳ No new step detected or step already processed")
|
||||
return None
|
||||
return value
|
||||
|
||||
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]
|
||||
|
||||
# Find the agent in the tree
|
||||
agent_node = find_agent_in_tree(agent_info["agent_id"], root_node)
|
||||
if not agent_node:
|
||||
return
|
||||
|
||||
# Update agent status
|
||||
if agent_node["status"] != "completed":
|
||||
agent_node["status"] = status
|
||||
agent_node["content"] = f"✅ {agent_node['name']} - Analysis completed"
|
||||
|
||||
# Update report sub-item
|
||||
report_node = find_item_by_id(f"{agent_info['agent_id']}_report", agent_node["children"])
|
||||
if report_node:
|
||||
report_node["status"] = "completed"
|
||||
report_node["content"] = format_report_content(agent_info["report_name"], report_data)
|
||||
|
||||
# Update messages sub-item (extract from state if available)
|
||||
messages_node = find_item_by_id(f"{agent_info['agent_id']}_messages", agent_node["children"])
|
||||
if messages_node:
|
||||
messages_node["status"] = "completed"
|
||||
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)
|
||||
|
||||
def find_agent_in_tree(agent_id: str, root_node: dict):
|
||||
"""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
|
||||
return None
|
||||
|
||||
def find_item_by_id(item_id: str, items: list):
|
||||
"""Find an item by ID in a list of items."""
|
||||
for item in items:
|
||||
if item["id"] == item_id:
|
||||
return item
|
||||
return None
|
||||
|
||||
def format_report_content(report_name: str, report_data: any) -> str:
|
||||
"""Format report data for display."""
|
||||
if isinstance(report_data, str):
|
||||
return f"📄 {report_name}\n\n{report_data}"
|
||||
elif isinstance(report_data, dict):
|
||||
return f"📄 {report_name}\n\n{str(report_data)}"
|
||||
elif isinstance(report_data, list) and report_data:
|
||||
# For debate histories, show the latest message
|
||||
latest = report_data[-1] if report_data else "No data"
|
||||
return f"📄 {report_name}\n\n{str(latest)}"
|
||||
else:
|
||||
return f"📄 {report_name}\n\nReport generated successfully"
|
||||
|
||||
def extract_agent_messages(state: dict, agent_id: str) -> str:
|
||||
"""Extract relevant messages for an agent from the state."""
|
||||
# This is a simplified version - could be enhanced to extract actual messages
|
||||
messages = state.get("messages", [])
|
||||
if messages:
|
||||
return f"💬 Agent Messages\n\n{len(messages)} messages exchanged during execution"
|
||||
else:
|
||||
return "💬 Agent Messages\n\nExecution completed without specific message logs"
|
||||
|
||||
def update_phase_status_if_complete(phase_id: str, root_node: dict):
|
||||
"""Update phase status to completed if all its agents are completed."""
|
||||
phase_node = find_item_by_id(f"{phase_id}_phase", root_node["children"])
|
||||
if not phase_node:
|
||||
return
|
||||
|
||||
# Check if all agents in this phase are completed
|
||||
all_completed = all(agent["status"] == "completed" for agent in phase_node["children"])
|
||||
if all_completed and phase_node["status"] != "completed":
|
||||
phase_node["status"] = "completed"
|
||||
phase_node["content"] = f"✅ {phase_node['name']} - All agents completed successfully"
|
||||
|
||||
def count_completed_agents(root_node: dict) -> 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
|
||||
return count
|
||||
|
||||
def run_trading_process(company_symbol: str, config: Dict[str, Any]):
|
||||
"""Runs the TradingAgentsGraph in a separate thread."""
|
||||
|
|
|
|||
|
|
@ -238,6 +238,13 @@ body {
|
|||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.item-children {
|
||||
margin-left: 20px;
|
||||
border-left: 2px solid var(--border-color);
|
||||
padding-left: 15px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.process-item {
|
||||
position: relative;
|
||||
margin-bottom: 5px;
|
||||
|
|
@ -267,6 +274,31 @@ body {
|
|||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* Specific styling for different item types */
|
||||
.process-item[class*="_phase"] .item-name {
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.process-item[class*="analyst"] .item-name,
|
||||
.process-item[class*="researcher"] .item-name,
|
||||
.process-item[class*="planner"] .item-name,
|
||||
.process-item[class*="trader"] .item-name,
|
||||
.process-item[class*="judge"] .item-name {
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.process-item[class*="_report"] .item-name,
|
||||
.process-item[class*="_messages"] .item-name {
|
||||
font-size: 0.9em;
|
||||
color: var(--text-secondary);
|
||||
font-style: italic;
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
/* Loading indicator */
|
||||
.htmx-indicator {
|
||||
display: none;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<li class="process-item status-{{ item.status }}">
|
||||
<span hx-get="/content/{{ item.id }}" hx-target="#right-panel" hx-swap="innerHTML" class="item-name clickable">
|
||||
<span class="status-icon">
|
||||
{% if item.status == 'completed' %}✓
|
||||
{% if item.status == 'completed' %}✅
|
||||
{% elif item.status == 'in_progress' %}⏳
|
||||
{% elif item.status == 'error' %}❌
|
||||
{% else %}⏸️
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
{{ item.name }}
|
||||
</span>
|
||||
{% if item.children %}
|
||||
<ul>
|
||||
<ul class="item-children">
|
||||
{% for child in item.children %}
|
||||
{{ render_item(child) }}
|
||||
{% endfor %}
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
<div id="overall-progress-bar" hx-swap-oob="true" style="width:{{ app_state.overall_progress }}%;"></div>
|
||||
<span id="overall-progress-text" hx-swap-oob="true">{{ app_state.overall_progress }}% ({{ app_state.overall_status }})</span>
|
||||
|
||||
<div id="left-panel-content" hx-get="/status" hx-trigger="every 2s" hx-swap="innerHTML">
|
||||
<div id="left-panel-content" hx-get="/status" hx-trigger="every 5s" hx-swap="innerHTML">
|
||||
<h2>Execution Status</h2>
|
||||
{% if tree %}
|
||||
<ul class="execution-tree">
|
||||
|
|
|
|||
Loading…
Reference in New Issue