feat: start frontend development
This commit is contained in:
parent
9b7b12d14f
commit
e9b0ad42f0
|
|
@ -0,0 +1,17 @@
|
|||
# Trading Agents Environment Variables
|
||||
# Copy this file to .env and fill in your values
|
||||
|
||||
# Finnhub API Key (required for market data)
|
||||
FINNHUB_API_KEY=your_finnhub_api_key_here
|
||||
|
||||
# OpenAI API Key (required for AI processing)
|
||||
OPENAI_API_KEY=your_openai_api_key_here
|
||||
|
||||
# Reddit API Credentials (required for social media analysis)
|
||||
REDDIT_CLIENT_ID=your_reddit_client_id_here
|
||||
REDDIT_CLIENT_SECRET=your_reddit_client_secret_here
|
||||
REDDIT_USER_AGENT=TradingAgents/1.0
|
||||
|
||||
# Optional Configuration
|
||||
# DEBUG=True
|
||||
# LOG_LEVEL=INFO
|
||||
15
README.md
15
README.md
|
|
@ -155,6 +155,21 @@ An interface will appear showing results as they load, letting you track the age
|
|||
<img src="assets/cli/cli_transaction.png" width="100%" style="display: inline-block; margin: 0 2%;">
|
||||
</p>
|
||||
|
||||
## Web Frontend (HTMX/FastAPI)
|
||||
|
||||
In addition to the CLI, a new web-based frontend is available to visualize the agent communication process in real-time. It allows you to set configuration parameters, start the trading analysis, and observe the step-by-step execution of agents and tools, including their outputs and any errors.
|
||||
|
||||
### Running the Web Frontend
|
||||
|
||||
1. Ensure you have installed all dependencies using `uv sync`.
|
||||
2. Navigate to the project root directory in your terminal.
|
||||
3. Start the FastAPI server:
|
||||
```bash
|
||||
uvicorn webapp.main:app --reload
|
||||
```
|
||||
4. Open your web browser and go to `http://127.0.0.1:8000`.
|
||||
5. Enter a company symbol (e.g., `AAPL`) in the configuration form and click "Start Process" to begin the analysis.
|
||||
|
||||
## TradingAgents Package
|
||||
|
||||
### Implementation Details
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
# HTMX Frontend Implementation Plan
|
||||
|
||||
This document outlines the architecture and step-by-step plan for building a new HTMX-based frontend for the TradingAgents project.
|
||||
|
||||
## 1. General Architecture
|
||||
|
||||
The frontend will be a single-page web application served by a lightweight Python backend (FastAPI). This backend will be responsible for serving the HTML, handling user requests to start the agent process, and providing real-time status updates. The frontend and backend code will be housed in a new top-level `webapp` directory to keep it separate from the core agent logic.
|
||||
|
||||
### Core Components:
|
||||
|
||||
* **FastAPI Backend:** A Python web server that will:
|
||||
* Serve the main `index.html` file.
|
||||
* Provide API endpoints for the frontend to interact with.
|
||||
* Run the `TradingAgentsGraph` in a background thread.
|
||||
* Maintain and serve the state of the execution process.
|
||||
* **HTMX Frontend:** The user interface, which will:
|
||||
* Display the configuration form and start button.
|
||||
* Show a hierarchical view of the agent execution process.
|
||||
* Poll the backend for status updates.
|
||||
* Display the content of selected process steps (reports, messages, errors) on the right side of the screen.
|
||||
* **Communication:** The frontend will communicate with the backend using a simple polling mechanism. The HTMX frontend will periodically request a status update from a `/status` endpoint. The backend will return a JSON object representing the current state of the execution tree. For displaying detailed content, the frontend will make specific requests to a `/content/{item_id}` endpoint.
|
||||
|
||||
## 2. Proposed Project Structure
|
||||
|
||||
To maintain separation of concerns, the new frontend code will live in a `webapp` directory.
|
||||
|
||||
```
|
||||
C:\Users\kevin\repo\TradingAgents\
|
||||
├───... (existing project files)
|
||||
└───webapp/
|
||||
├───main.py # FastAPI application
|
||||
├───static/
|
||||
│ └───styles.css # CSS for styling
|
||||
└───templates/
|
||||
├───index.html # Main HTML file
|
||||
└───_partials/
|
||||
├───left_panel.html # HTMX partial for the execution tree
|
||||
└───right_panel.html # HTMX partial for the content view
|
||||
```
|
||||
|
||||
## 3. Backend Implementation (FastAPI)
|
||||
|
||||
The `webapp/main.py` file will define the FastAPI application and its endpoints.
|
||||
|
||||
### API Endpoints:
|
||||
|
||||
* **`GET /`**: Serves the main `templates/index.html` page.
|
||||
* **`POST /start`**:
|
||||
* Accepts a JSON payload with the run configuration (`company_symbol`, etc.).
|
||||
* Initializes the `TradingAgentsGraph`.
|
||||
* Starts the `graph.propagate()` method in a background thread.
|
||||
* Returns an initial response that replaces the config form with the main progress bar.
|
||||
* **`GET /status`**:
|
||||
* This is the main polling endpoint for HTMX.
|
||||
* It will return an HTML partial (`_partials/left_panel.html`) rendered with the current state of the execution tree. The state will be stored in memory.
|
||||
* **`GET /content/{item_id}`**:
|
||||
* When a user clicks an item in the left panel, HTMX will call this endpoint.
|
||||
* It will retrieve the specific content for that `item_id` from the in-memory state.
|
||||
* It will return an HTML partial (`_partials/right_panel.html`) with the formatted content (e.g., a formatted report, a code block for a message, or a stack trace for an error).
|
||||
|
||||
### State Management & Integration:
|
||||
|
||||
To get real-time updates from the `TradingAgentsGraph`, we will need to instrument its execution. The plan is to modify the `TradingAgentsGraph` class slightly to accept a callback function.
|
||||
|
||||
1. **Modify `TradingAgentsGraph.__init__`**: Add an optional `on_step_end` callback parameter.
|
||||
2. **Callback Execution**: Inside the graph's execution logic (after each agent or tool runs), this callback will be invoked with the details of the completed step (e.g., node name, output, status).
|
||||
3. **Update Global State**: The callback function, defined in `webapp/main.py`, will update a global in-memory dictionary that represents the hierarchical execution tree. This tree will store the status, content, and relationships of all steps.
|
||||
|
||||
This approach avoids tight coupling and allows the web application to listen to the progress of the core agent logic.
|
||||
|
||||
## 4. Frontend Implementation (HTMX)
|
||||
|
||||
The frontend will be built using HTMX attributes directly in the HTML templates.
|
||||
|
||||
* **`templates/index.html`**:
|
||||
* Contains the basic page structure: a top bar for the overall progress, a left panel for the execution tree, and a right panel for content.
|
||||
* Includes the HTMX library.
|
||||
* 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"`.
|
||||
* 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.
|
||||
|
||||
* **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.
|
||||
* Content will be pre-formatted by the backend (e.g., using Markdown-to-HTML conversion or syntax highlighting for code/errors).
|
||||
|
||||
* **Progress Bar**:
|
||||
* 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).
|
||||
|
||||
## 5. Detailed Implementation Steps
|
||||
|
||||
1. **Setup Environment**:
|
||||
* Create the `webapp` directory and the file structure outlined above.
|
||||
* Add `fastapi`, `uvicorn`, and `python-multipart` to the `requirements.txt` file and install them.
|
||||
|
||||
2. **Backend - Basic Server**:
|
||||
* Create the initial FastAPI app in `webapp/main.py`.
|
||||
* Implement the `GET /` endpoint to serve `templates/index.html`.
|
||||
* Create a basic `index.html` with the two-panel layout.
|
||||
|
||||
3. **Backend - State & Integration**:
|
||||
* Define the Python data classes for the execution state (e.g., `ProcessStep`, `RunState`).
|
||||
* Modify `tradingagents/graph/trading_graph.py` to include the `on_step_end` callback mechanism.
|
||||
* In `webapp/main.py`, implement the callback function that builds the hierarchical state tree in memory.
|
||||
|
||||
4. **Backend - Endpoints**:
|
||||
* Implement the `/start` endpoint to receive configuration and launch the `propagate` method in a background thread, passing the callback function.
|
||||
* Implement the `/status` endpoint to render and return the `_partials/left_panel.html` partial.
|
||||
* Implement the `/content/{item_id}` endpoint to render and return the `_partials/right_panel.html` partial.
|
||||
|
||||
5. **Frontend - HTMX**:
|
||||
* Develop the configuration form in `index.html` with `hx-post` to start the process.
|
||||
* Create the `_partials/left_panel.html` template with the Jinja2 loop and the `hx-get` attributes for clicking on items.
|
||||
* Add the polling mechanism to the main container in `index.html`.
|
||||
* Style the different states (pending, completed, error) using CSS in `static/styles.css`.
|
||||
|
||||
6. **Error Handling**:
|
||||
* When the callback receives an error, it will update the corresponding item's status to "error" and store the stack trace.
|
||||
* The frontend will visually flag the item as an error.
|
||||
* When clicked, the `/content/{item_id}` endpoint will return the formatted stack trace to be displayed in the right panel.
|
||||
|
||||
7. **Refinement**:
|
||||
* 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.
|
||||
Binary file not shown.
|
|
@ -33,4 +33,8 @@ dependencies = [
|
|||
"tushare>=1.4.21",
|
||||
"typing-extensions>=4.14.0",
|
||||
"yfinance>=0.2.63",
|
||||
"fastapi",
|
||||
"uvicorn",
|
||||
"python-multipart",
|
||||
"jinja2",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -24,3 +24,7 @@ rich
|
|||
questionary
|
||||
langchain_anthropic
|
||||
langchain-google-genai
|
||||
fastapi
|
||||
uvicorn
|
||||
python-multipart
|
||||
jinja2
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ class TradingAgentsGraph:
|
|||
),
|
||||
}
|
||||
|
||||
def propagate(self, company_name, trade_date, user_position="none", cost_per_trade=0.0):
|
||||
def propagate(self, company_name, trade_date, user_position="none", cost_per_trade=0.0, on_step_callback=None):
|
||||
"""Run the trading agents graph for a company on a specific date."""
|
||||
|
||||
self.ticker = company_name
|
||||
|
|
@ -206,17 +206,17 @@ class TradingAgentsGraph:
|
|||
if self.debug:
|
||||
# Debug mode with tracing
|
||||
trace = []
|
||||
for chunk in self.graph.stream(init_agent_state, **args):
|
||||
if len(chunk["messages"]) == 0:
|
||||
pass
|
||||
else:
|
||||
chunk["messages"][-1].pretty_print()
|
||||
trace.append(chunk)
|
||||
|
||||
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]
|
||||
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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,202 @@
|
|||
from fastapi import FastAPI, Request, Form, BackgroundTasks, HTTPException
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
import jinja2
|
||||
import os
|
||||
from typing import Dict, Any
|
||||
import threading
|
||||
import time
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Load environment variables from .env file
|
||||
load_dotenv()
|
||||
|
||||
# Check required environment variables
|
||||
required_env_vars = [
|
||||
'FINNHUB_API_KEY',
|
||||
'OPENAI_API_KEY',
|
||||
#'REDDIT_CLIENT_ID',
|
||||
#'REDDIT_CLIENT_SECRET',
|
||||
#'REDDIT_USER_AGENT'
|
||||
]
|
||||
|
||||
missing_vars = [var for var in required_env_vars if not os.getenv(var)]
|
||||
if missing_vars:
|
||||
print(f"Error: Missing required environment variables: {', '.join(missing_vars)}")
|
||||
print("Please create a .env file with these variables or set them in your environment.")
|
||||
|
||||
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
# In-memory storage for the process state
|
||||
# Using a lock for thread-safe access to app_state
|
||||
app_state_lock = threading.Lock()
|
||||
app_state: Dict[str, Any] = {
|
||||
"process_running": False,
|
||||
"company_symbol": None,
|
||||
"execution_tree": [],
|
||||
"overall_status": "idle", # idle, in_progress, completed, error
|
||||
"overall_progress": 0 # 0-100
|
||||
}
|
||||
|
||||
# Mount the static directory to serve CSS, JS, etc.
|
||||
app.mount("/static", StaticFiles(directory="webapp/static"), name="static")
|
||||
|
||||
# Setup Jinja2 for templating
|
||||
template_dir = os.path.join(os.path.dirname(__file__), "templates")
|
||||
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
|
||||
"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)
|
||||
|
||||
def run_trading_process(company_symbol: str):
|
||||
"""Runs the TradingAgentsGraph in a separate thread."""
|
||||
with app_state_lock:
|
||||
app_state["overall_status"] = "in_progress"
|
||||
app_state["overall_progress"] = 0
|
||||
|
||||
try:
|
||||
graph = TradingAgentsGraph()
|
||||
current_date = time.strftime("%Y-%m-%d") # Use current date for analysis
|
||||
# The propagate method now accepts the callback and trade_date
|
||||
final_state = graph.propagate(company_symbol, trade_date=current_date, on_step_callback=update_execution_state)
|
||||
|
||||
with app_state_lock:
|
||||
app_state["overall_status"] = "completed"
|
||||
app_state["overall_progress"] = 100
|
||||
# Update the root node status to completed
|
||||
if app_state["execution_tree"]:
|
||||
app_state["execution_tree"][0]["status"] = "completed"
|
||||
app_state["execution_tree"][0]["content"] = str(final_state)
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
error_detail = traceback.format_exc()
|
||||
with app_state_lock:
|
||||
app_state["overall_status"] = "error"
|
||||
app_state["overall_progress"] = 100
|
||||
if app_state["execution_tree"]:
|
||||
app_state["execution_tree"][0]["status"] = "error"
|
||||
app_state["execution_tree"][0]["content"] = f"Error during execution: {str(e)}\n\n{error_detail}"
|
||||
# Add a specific error item to the tree
|
||||
app_state["execution_tree"].append({
|
||||
"id": "error",
|
||||
"name": "Process Error",
|
||||
"status": "error",
|
||||
"content": f"Error during execution: {str(e)}\n\n{error_detail}",
|
||||
"children": [],
|
||||
"timestamp": time.time()
|
||||
})
|
||||
finally:
|
||||
with app_state_lock:
|
||||
app_state["process_running"] = False
|
||||
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def read_root():
|
||||
template = jinja_env.get_template("index.html")
|
||||
return template.render(app_state=app_state)
|
||||
|
||||
@app.post("/start", response_class=HTMLResponse)
|
||||
async def start_process(background_tasks: BackgroundTasks, company_symbol: str = Form(...)):
|
||||
# Check if all required environment variables are set
|
||||
missing_vars = [var for var in required_env_vars if not os.getenv(var)]
|
||||
if missing_vars:
|
||||
app_state["overall_status"] = "error"
|
||||
app_state["execution_tree"] = [{
|
||||
"id": "error",
|
||||
"name": "Configuration Error",
|
||||
"status": "error",
|
||||
"content": f"Missing required environment variables: {', '.join(missing_vars)}. Please check .env.example file.",
|
||||
"children": [],
|
||||
"timestamp": time.time()
|
||||
}]
|
||||
template = jinja_env.get_template("_partials/left_panel.html")
|
||||
return template.render(tree=app_state["execution_tree"], app_state=app_state)
|
||||
|
||||
with app_state_lock:
|
||||
if app_state["process_running"]:
|
||||
# Optionally, return an error or a message that a process is already running
|
||||
template = jinja_env.get_template("_partials/left_panel.html")
|
||||
return template.render(tree=app_state["execution_tree"], app_state=app_state)
|
||||
|
||||
app_state["process_running"] = True
|
||||
app_state["company_symbol"] = company_symbol
|
||||
app_state["execution_tree"] = [] # Clear for new run
|
||||
app_state["overall_status"] = "in_progress"
|
||||
app_state["overall_progress"] = 0
|
||||
|
||||
background_tasks.add_task(run_trading_process, company_symbol)
|
||||
|
||||
template = jinja_env.get_template("_partials/left_panel.html")
|
||||
return template.render(tree=app_state["execution_tree"], app_state=app_state)
|
||||
|
||||
@app.get("/status", response_class=HTMLResponse)
|
||||
async def get_status():
|
||||
with app_state_lock:
|
||||
template = jinja_env.get_template("_partials/left_panel.html")
|
||||
return template.render(tree=app_state["execution_tree"], app_state=app_state)
|
||||
|
||||
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:
|
||||
if item["id"] == item_id:
|
||||
return item
|
||||
if item["children"]:
|
||||
found_child = find_item_in_tree(item_id, item["children"])
|
||||
if found_child:
|
||||
return found_child
|
||||
return None
|
||||
|
||||
@app.get("/content/{item_id}", response_class=HTMLResponse)
|
||||
async def get_item_content(item_id: str):
|
||||
with app_state_lock:
|
||||
item = find_item_in_tree(item_id, app_state["execution_tree"])
|
||||
if item:
|
||||
template = jinja_env.get_template("_partials/right_panel.html")
|
||||
return template.render(content=item.get("content", "No content available."))
|
||||
else:
|
||||
return HTMLResponse(content="<p>Item not found.</p>", status_code=404)
|
||||
|
||||
# To run this app:
|
||||
# uvicorn webapp.main:app --reload
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
/* Dark theme variables */
|
||||
:root {
|
||||
--bg-primary: #1a1a1a;
|
||||
--bg-secondary: #242424;
|
||||
--text-primary: #e0e0e0;
|
||||
--text-secondary: #a0a0a0;
|
||||
--accent-color: #4CAF50;
|
||||
--border-color: #333;
|
||||
--input-bg: #2a2a2a;
|
||||
--hover-color: #333;
|
||||
}
|
||||
|
||||
/* Basic styles for the webapp */
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
#left-panel {
|
||||
width: 30%;
|
||||
border-right: 1px solid var(--border-color);
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
#right-panel {
|
||||
width: 70%;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
background-color: var(--bg-primary);
|
||||
}
|
||||
|
||||
/* Overall Progress Bar */
|
||||
#overall-progress-container {
|
||||
width: 100%;
|
||||
background-color: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#overall-progress-bar {
|
||||
height: 6px;
|
||||
background-color: var(--accent-color);
|
||||
width: 0%; /* Initial width */
|
||||
transition: width 0.5s ease-in-out, background-color 0.3s ease;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 10px rgba(76, 175, 80, 0.3);
|
||||
}
|
||||
|
||||
#overall-progress-text {
|
||||
margin-left: 15px;
|
||||
font-size: 0.9em;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Main Content Layout */
|
||||
#main-content {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
height: calc(100vh - 46px); /* Adjust for progress bar height */
|
||||
background-color: var(--bg-primary);
|
||||
}
|
||||
|
||||
/* Left Panel - Configuration and Controls */
|
||||
#left-panel {
|
||||
width: 30%;
|
||||
border-right: 1px solid var(--border-color);
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
#left-panel h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 20px;
|
||||
color: var(--text-primary);
|
||||
font-size: 1.5em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#config-form {
|
||||
background-color: var(--bg-primary);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
#config-form label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
#config-form input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
margin-bottom: 15px;
|
||||
background-color: var(--input-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
color: var(--text-primary);
|
||||
font-size: 1em;
|
||||
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
#config-form input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-color);
|
||||
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);
|
||||
}
|
||||
|
||||
#config-form button {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background-color: var(--accent-color);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease, transform 0.1s ease;
|
||||
}
|
||||
|
||||
#config-form button:hover {
|
||||
background-color: #45a049;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
#config-form button:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
#left-panel ul {
|
||||
list-style: none;
|
||||
padding-left: 15px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#left-panel li {
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
#left-panel li .item-name {
|
||||
padding: 8px 12px;
|
||||
border-radius: 4px;
|
||||
display: inline-block;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
#left-panel li .item-name:hover {
|
||||
background-color: var(--hover-color);
|
||||
}
|
||||
|
||||
/* Status Indicators */
|
||||
.status-pending .item-name {
|
||||
color: var(--text-secondary);
|
||||
border-left: 3px solid var(--text-secondary);
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.status-in_progress .item-name {
|
||||
color: #3b82f6;
|
||||
font-weight: 600;
|
||||
border-left: 3px solid #3b82f6;
|
||||
padding-left: 10px;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.status-completed .item-name {
|
||||
color: var(--accent-color);
|
||||
border-left: 3px solid var(--accent-color);
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.status-error .item-name {
|
||||
color: #ef4444;
|
||||
font-weight: 600;
|
||||
border-left: 3px solid #ef4444;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
/* Right Panel Styles */
|
||||
#right-panel {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
#right-panel p {
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{% macro render_item(item) %}
|
||||
<li class="process-item status-{{ item.status }}">
|
||||
<span hx-get="/content/{{ item.id }}" hx-target="#right-panel" hx-swap="innerHTML" class="item-name">{{ item.name }}</span>
|
||||
{% if item.children %}
|
||||
<ul>
|
||||
{% for child in item.children %}
|
||||
{{ render_item(child) }}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endmacro %}
|
||||
|
||||
<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">
|
||||
<h2>Execution Status</h2>
|
||||
{% if tree %}
|
||||
<ul>
|
||||
{% for item in tree %}
|
||||
{{ render_item(item) }}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>No process running. Start a new one from the configuration.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<div>
|
||||
<h3>Content Details</h3>
|
||||
<pre>{{ content }}</pre>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TradingAgents</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/styles.css">
|
||||
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="overall-progress-container">
|
||||
<div id="overall-progress-bar" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
<span id="overall-progress-text">0%</span>
|
||||
</div>
|
||||
<div id="main-content">
|
||||
<div id="left-panel">
|
||||
<h2>Configuration</h2>
|
||||
<div id="config-form">
|
||||
<form hx-post="/start" hx-target="#left-panel" hx-swap="innerHTML">
|
||||
<label for="company_symbol">Company Symbol:</label>
|
||||
<input type="text" id="company_symbol" name="company_symbol" value="AAPL" required>
|
||||
<button type="submit">Start Process</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div id="right-panel">
|
||||
<p>Welcome! Please set your configuration and start the process.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in New Issue