feat: menu updates
This commit is contained in:
parent
e0848e960c
commit
9201f77c9f
Binary file not shown.
223
webapp/main.py
223
webapp/main.py
|
|
@ -52,11 +52,8 @@ def update_execution_state(state: Dict[str, Any]):
|
||||||
print(f"📡 Callback received state keys: {list(state.keys())}")
|
print(f"📡 Callback received state keys: {list(state.keys())}")
|
||||||
|
|
||||||
with app_state_lock:
|
with app_state_lock:
|
||||||
# Initialize the complete execution tree structure if not exists
|
# Ensure execution tree is initialized
|
||||||
if not app_state["execution_tree"] or (
|
if not app_state["execution_tree"]:
|
||||||
len(app_state["execution_tree"]) == 1 and
|
|
||||||
app_state["execution_tree"][0]["id"] == "initialization"
|
|
||||||
):
|
|
||||||
app_state["execution_tree"] = initialize_complete_execution_tree()
|
app_state["execution_tree"] = initialize_complete_execution_tree()
|
||||||
|
|
||||||
# Map LangGraph node names to our tracking system
|
# 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_agent_status(agent_info, "completed", report_data, state)
|
||||||
|
|
||||||
# Update overall progress
|
# Update overall progress
|
||||||
root_node = app_state["execution_tree"][0]
|
execution_tree = app_state["execution_tree"]
|
||||||
total_agents = len(agent_state_mapping)
|
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))
|
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)")
|
print(f"📊 Progress updated: {app_state['overall_progress']}% ({completed_agents}/{total_agents} agents)")
|
||||||
|
|
||||||
def initialize_complete_execution_tree():
|
def initialize_complete_execution_tree():
|
||||||
"""Initialize the complete execution tree with all agents in pending state."""
|
"""Initialize the complete execution tree with all agents in pending state."""
|
||||||
return [{
|
return [
|
||||||
"id": "root",
|
{
|
||||||
"name": f"📈 Trading Analysis for {app_state['company_symbol']}",
|
"id": "data_collection_phase",
|
||||||
"status": "in_progress",
|
"name": "📊 Data Collection Phase",
|
||||||
"content": f"Comprehensive trading analysis for {app_state['company_symbol']}",
|
"status": "pending",
|
||||||
"children": [
|
"content": "Collecting market data and analysis from various sources",
|
||||||
{
|
"children": [
|
||||||
"id": "data_collection_phase",
|
create_agent_node("market_analyst", "📈 Market Analyst"),
|
||||||
"name": "📊 Data Collection Phase",
|
create_agent_node("social_analyst", "📱 Social Media Analyst"),
|
||||||
"status": "pending",
|
create_agent_node("news_analyst", "📰 News Analyst"),
|
||||||
"content": "Collecting market data and analysis from various sources",
|
create_agent_node("fundamentals_analyst", "📊 Fundamentals Analyst")
|
||||||
"children": [
|
]
|
||||||
create_agent_node("market_analyst", "📈 Market Analyst"),
|
},
|
||||||
create_agent_node("social_analyst", "📱 Social Media Analyst"),
|
{
|
||||||
create_agent_node("news_analyst", "📰 News Analyst"),
|
"id": "research_phase",
|
||||||
create_agent_node("fundamentals_analyst", "📊 Fundamentals Analyst")
|
"name": "🔍 Research Phase",
|
||||||
]
|
"status": "pending",
|
||||||
},
|
"content": "Research and debate investment perspectives",
|
||||||
{
|
"children": [
|
||||||
"id": "research_phase",
|
create_agent_node("bull_researcher", "🐂 Bull Researcher"),
|
||||||
"name": "🔍 Research Phase",
|
create_agent_node("bear_researcher", "🐻 Bear Researcher"),
|
||||||
"status": "pending",
|
create_agent_node("research_manager", "🔍 Research Manager")
|
||||||
"content": "Research and debate investment perspectives",
|
]
|
||||||
"children": [
|
},
|
||||||
create_agent_node("bull_researcher", "🐂 Bull Researcher"),
|
{
|
||||||
create_agent_node("bear_researcher", "🐻 Bear Researcher"),
|
"id": "planning_phase",
|
||||||
create_agent_node("research_manager", "🔍 Research Manager")
|
"name": "📋 Planning Phase",
|
||||||
]
|
"status": "pending",
|
||||||
},
|
"content": "Develop trading strategy and execution plan",
|
||||||
{
|
"children": [
|
||||||
"id": "planning_phase",
|
create_agent_node("trade_planner", "📋 Trade Planner")
|
||||||
"name": "📋 Planning Phase",
|
]
|
||||||
"status": "pending",
|
},
|
||||||
"content": "Develop trading strategy and execution plan",
|
{
|
||||||
"children": [
|
"id": "execution_phase",
|
||||||
create_agent_node("trade_planner", "📋 Trade Planner")
|
"name": "⚡ Execution Phase",
|
||||||
]
|
"status": "pending",
|
||||||
},
|
"content": "Execute trades based on analysis and planning",
|
||||||
{
|
"children": [
|
||||||
"id": "execution_phase",
|
create_agent_node("trader", "⚡ Trader")
|
||||||
"name": "⚡ Execution Phase",
|
]
|
||||||
"status": "pending",
|
},
|
||||||
"content": "Execute trades based on analysis and planning",
|
{
|
||||||
"children": [
|
"id": "risk_analysis_phase",
|
||||||
create_agent_node("trader", "⚡ Trader")
|
"name": "⚠️ Risk Management Phase",
|
||||||
]
|
"status": "pending",
|
||||||
},
|
"content": "Assess and manage investment risks",
|
||||||
{
|
"children": [
|
||||||
"id": "risk_analysis_phase",
|
create_agent_node("risky_analyst", "🚨 Aggressive Risk Analyst"),
|
||||||
"name": "⚠️ Risk Management Phase",
|
create_agent_node("neutral_analyst", "⚖️ Neutral Risk Analyst"),
|
||||||
"status": "pending",
|
create_agent_node("safe_analyst", "🛡️ Conservative Risk Analyst"),
|
||||||
"content": "Assess and manage investment risks",
|
create_agent_node("risk_judge", "⚠️ Risk Judge")
|
||||||
"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):
|
def create_agent_node(agent_id: str, agent_name: str):
|
||||||
"""Create a standardized agent node with report and messages sub-items."""
|
"""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):
|
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."""
|
"""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
|
# 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:
|
if not agent_node:
|
||||||
return
|
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"])
|
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 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."""
|
"""Find an agent node in the execution tree."""
|
||||||
for phase in root_node["children"]:
|
for phase in tree:
|
||||||
for agent in phase["children"]:
|
if phase.get("children"):
|
||||||
if agent["id"] == agent_id:
|
for agent in phase["children"]:
|
||||||
return agent
|
if agent["id"] == agent_id:
|
||||||
|
return agent
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def find_item_by_id(item_id: str, items: list):
|
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:
|
else:
|
||||||
return "💬 Agent Messages\n\nExecution completed without specific message logs"
|
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."""
|
"""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:
|
if not phase_node:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -338,13 +329,14 @@ def update_phase_status_if_complete(phase_id: str, root_node: dict):
|
||||||
phase_node["status"] = "completed"
|
phase_node["status"] = "completed"
|
||||||
phase_node["content"] = f"✅ {phase_node['name']} - All agents completed successfully"
|
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 the number of completed agents across all phases."""
|
||||||
count = 0
|
count = 0
|
||||||
for phase in root_node["children"]:
|
for phase in execution_tree:
|
||||||
for agent in phase["children"]:
|
if phase.get("children"):
|
||||||
if agent["status"] == "completed":
|
for agent in phase["children"]:
|
||||||
count += 1
|
if agent["status"] == "completed":
|
||||||
|
count += 1
|
||||||
return count
|
return count
|
||||||
|
|
||||||
def run_trading_process(company_symbol: str, config: Dict[str, Any]):
|
def run_trading_process(company_symbol: str, config: Dict[str, Any]):
|
||||||
|
|
@ -475,15 +467,8 @@ async def start_process(
|
||||||
"analysis_date": analysis_date
|
"analysis_date": analysis_date
|
||||||
}
|
}
|
||||||
|
|
||||||
# Initialize execution tree with startup message
|
# Initialize execution tree with complete structure
|
||||||
app_state["execution_tree"] = [{
|
app_state["execution_tree"] = initialize_complete_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()
|
|
||||||
}]
|
|
||||||
|
|
||||||
background_tasks.add_task(run_trading_process, company_symbol, app_state["config"])
|
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")
|
template = jinja_env.get_template("_partials/left_panel.html")
|
||||||
return template.render(tree=app_state["execution_tree"], app_state=app_state)
|
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="<p>No process running. Start a new one from the configuration.</p>")
|
||||||
|
|
||||||
|
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'''
|
||||||
|
<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>
|
||||||
|
'''
|
||||||
|
|
||||||
|
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:
|
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."""
|
"""Recursively searches the execution tree for an item by its ID."""
|
||||||
for item in tree:
|
for item in tree:
|
||||||
|
|
|
||||||
|
|
@ -341,6 +341,8 @@ body {
|
||||||
.status-icon {
|
.status-icon {
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Specific styling for different item types */
|
/* Specific styling for different item types */
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{% macro render_item(item) %}
|
{% macro render_item(item) %}
|
||||||
<li class="process-item status-{{ item.status }}">
|
<li class="process-item status-{{ item.status }}" id="item-{{ item.id }}">
|
||||||
<div class="item-header">
|
<div class="item-header">
|
||||||
{% if item.children %}
|
{% if item.children %}
|
||||||
<button class="toggle-btn" onclick="toggleNode(this)" aria-label="Toggle children">
|
<button class="toggle-btn" onclick="toggleNode(this)" aria-label="Toggle children">
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
<span class="toggle-spacer"></span>
|
<span class="toggle-spacer"></span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span hx-get="/content/{{ item.id }}" hx-target="#right-panel" hx-swap="innerHTML" class="item-name clickable">
|
<span hx-get="/content/{{ item.id }}" hx-target="#right-panel" hx-swap="innerHTML" class="item-name clickable">
|
||||||
<span class="status-icon">
|
<span class="status-icon" id="status-icon-{{ item.id }}">
|
||||||
{% if item.status == 'completed' %}✅
|
{% if item.status == 'completed' %}✅
|
||||||
{% elif item.status == 'in_progress' %}⏳
|
{% elif item.status == 'in_progress' %}⏳
|
||||||
{% elif item.status == 'error' %}❌
|
{% elif item.status == 'error' %}❌
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{% if item.children %}
|
{% if item.children %}
|
||||||
<ul class="item-children collapsed">
|
<ul class="item-children collapsed" id="children-{{ item.id }}">
|
||||||
{% for child in item.children %}
|
{% for child in item.children %}
|
||||||
{{ render_item(child) }}
|
{{ render_item(child) }}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
@ -32,15 +32,20 @@
|
||||||
<div id="overall-progress-bar" hx-swap-oob="true" style="width:{{ app_state.overall_progress }}%;"></div>
|
<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>
|
<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 5s" hx-swap="innerHTML">
|
<h2>
|
||||||
<h2>Execution Status</h2>
|
{% if app_state.company_symbol %}
|
||||||
{% if tree %}
|
Trading Analysis for {{ app_state.company_symbol }}
|
||||||
<ul class="execution-tree">
|
|
||||||
{% for item in tree %}
|
|
||||||
{{ render_item(item) }}
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>No process running. Start a new one from the configuration.</p>
|
Execution Status
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</h2>
|
||||||
|
|
||||||
|
{% if tree %}
|
||||||
|
<ul class="execution-tree" id="execution-tree" hx-get="/status-content" hx-trigger="every 5s" hx-swap="innerHTML">
|
||||||
|
{% for item in tree %}
|
||||||
|
{{ render_item(item) }}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<p id="execution-tree">No process running. Start a new one from the configuration.</p>
|
||||||
|
{% endif %}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
{% macro render_item(item) %}
|
||||||
|
<li class="process-item status-{{ item.status }}" id="item-{{ item.id }}">
|
||||||
|
<div class="item-header">
|
||||||
|
{% if item.children %}
|
||||||
|
<button class="toggle-btn" onclick="toggleNode(this)" aria-label="Toggle children">
|
||||||
|
<span class="toggle-icon">▶</span>
|
||||||
|
</button>
|
||||||
|
{% else %}
|
||||||
|
<span class="toggle-spacer"></span>
|
||||||
|
{% endif %}
|
||||||
|
<span hx-get="/content/{{ item.id }}" hx-target="#right-panel" hx-swap="innerHTML" class="item-name clickable">
|
||||||
|
<span class="status-icon" id="status-icon-{{ item.id }}">
|
||||||
|
{% if item.status == 'completed' %}✅
|
||||||
|
{% elif item.status == 'in_progress' %}⏳
|
||||||
|
{% elif item.status == 'error' %}❌
|
||||||
|
{% else %}⏸️
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
{{ item.name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% if item.children %}
|
||||||
|
<ul class="item-children collapsed" id="children-{{ item.id }}">
|
||||||
|
{% for child in item.children %}
|
||||||
|
{{ render_item(child) }}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% for item in tree %}
|
||||||
|
{{ render_item(item) }}
|
||||||
|
{% endfor %}
|
||||||
|
|
@ -203,10 +203,154 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save the current expansion state of all toggleable items
|
||||||
|
function saveExpansionState() {
|
||||||
|
const state = {};
|
||||||
|
const executionTree = document.querySelector('.execution-tree');
|
||||||
|
if (executionTree) {
|
||||||
|
const items = executionTree.querySelectorAll('.process-item');
|
||||||
|
items.forEach(item => {
|
||||||
|
const children = item.querySelector('.item-children');
|
||||||
|
const button = item.querySelector('.toggle-btn');
|
||||||
|
if (children && button) {
|
||||||
|
const itemId = getItemId(item);
|
||||||
|
if (itemId) {
|
||||||
|
state[itemId] = children.classList.contains('expanded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore the expansion state after content update
|
||||||
|
function restoreExpansionState(savedState) {
|
||||||
|
if (!savedState) return;
|
||||||
|
|
||||||
|
const executionTree = document.querySelector('.execution-tree');
|
||||||
|
if (executionTree) {
|
||||||
|
const items = executionTree.querySelectorAll('.process-item');
|
||||||
|
items.forEach(item => {
|
||||||
|
const children = item.querySelector('.item-children');
|
||||||
|
const button = item.querySelector('.toggle-btn');
|
||||||
|
if (children && button) {
|
||||||
|
const itemId = getItemId(item);
|
||||||
|
if (itemId && savedState.hasOwnProperty(itemId)) {
|
||||||
|
if (savedState[itemId]) {
|
||||||
|
// Expand
|
||||||
|
children.classList.remove('collapsed');
|
||||||
|
children.classList.add('expanded');
|
||||||
|
button.classList.add('expanded');
|
||||||
|
} else {
|
||||||
|
// Collapse
|
||||||
|
children.classList.remove('expanded');
|
||||||
|
children.classList.add('collapsed');
|
||||||
|
button.classList.remove('expanded');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get item ID from the clickable span
|
||||||
|
function getItemId(processItem) {
|
||||||
|
const clickableSpan = processItem.querySelector('.item-name.clickable');
|
||||||
|
if (clickableSpan) {
|
||||||
|
const hxGet = clickableSpan.getAttribute('hx-get');
|
||||||
|
if (hxGet) {
|
||||||
|
const match = hxGet.match(/\/content\/(.+)$/);
|
||||||
|
return match ? match[1] : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle HTMX before request to save state
|
||||||
|
document.addEventListener('htmx:beforeSwap', function(event) {
|
||||||
|
if (event.target.id === 'left-panel-content') {
|
||||||
|
window.savedExpansionState = saveExpansionState();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle HTMX after settle to restore state
|
||||||
|
document.addEventListener('htmx:afterSettle', function(event) {
|
||||||
|
if (event.target.id === 'left-panel-content' && window.savedExpansionState) {
|
||||||
|
restoreExpansionState(window.savedExpansionState);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Targeted status updates to prevent flickering
|
||||||
|
function updateStatusIndicators() {
|
||||||
|
fetch('/status-updates')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
// Update overall progress
|
||||||
|
const progressBar = document.getElementById('overall-progress-bar');
|
||||||
|
const progressText = document.getElementById('overall-progress-text');
|
||||||
|
if (progressBar) {
|
||||||
|
progressBar.style.width = data.overall_progress + '%';
|
||||||
|
}
|
||||||
|
if (progressText) {
|
||||||
|
progressText.textContent = data.overall_progress + '% (' + data.overall_status + ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update individual status icons
|
||||||
|
for (const [itemId, statusInfo] of Object.entries(data.status_updates)) {
|
||||||
|
const statusIcon = document.querySelector(`[hx-get="/content/${itemId}"] .status-icon`);
|
||||||
|
if (statusIcon && statusIcon.textContent !== statusInfo.status_icon) {
|
||||||
|
statusIcon.textContent = statusInfo.status_icon;
|
||||||
|
|
||||||
|
// Update the parent item's CSS class
|
||||||
|
const processItem = statusIcon.closest('.process-item');
|
||||||
|
if (processItem) {
|
||||||
|
// Remove old status classes
|
||||||
|
processItem.classList.remove('status-pending', 'status-in_progress', 'status-completed', 'status-error');
|
||||||
|
// Add new status class
|
||||||
|
processItem.classList.add('status-' + statusInfo.status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log('Status update failed:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start targeted updates when execution tree is present
|
||||||
|
function startTargetedUpdates() {
|
||||||
|
if (document.querySelector('.execution-tree')) {
|
||||||
|
window.statusUpdateInterval = setInterval(updateStatusIndicators, 2000); // Update every 2 seconds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop targeted updates
|
||||||
|
function stopTargetedUpdates() {
|
||||||
|
if (window.statusUpdateInterval) {
|
||||||
|
clearInterval(window.statusUpdateInterval);
|
||||||
|
window.statusUpdateInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize the page
|
// Initialize the page
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
setCurrentDate();
|
setCurrentDate();
|
||||||
updateModelOptions(); // Set initial model options for OpenRouter
|
updateModelOptions(); // Set initial model options for OpenRouter
|
||||||
|
|
||||||
|
// Start targeted updates if execution tree is already present
|
||||||
|
startTargetedUpdates();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle when new content is loaded (like when starting a process)
|
||||||
|
document.addEventListener('htmx:afterSettle', function(event) {
|
||||||
|
if (event.target.id === 'left-panel-content') {
|
||||||
|
if (window.savedExpansionState) {
|
||||||
|
restoreExpansionState(window.savedExpansionState);
|
||||||
|
}
|
||||||
|
// Start or restart targeted updates when new content is loaded
|
||||||
|
stopTargetedUpdates();
|
||||||
|
startTargetedUpdates();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue