Feat/monitor cache (#267)
* feat&style: add task status configuration and clear cache functionality; enhance UI styles * feat&refactor: enhance current configuration API and improve cache clearing logic * refactor&style: simplify task status update logic and improve page refresh mechanism * refactor&feat: streamline default configuration retrieval and enhance cache initialization logic * feat&refactor: add caching to default configuration retrieval and streamline task status logic * feat&style: add collapsible section for additional model parameters and enhance styling for config items * refactor&style: remove floating action button and clean up related styles
This commit is contained in:
328
monitor/main.py
328
monitor/main.py
@@ -5,10 +5,8 @@ from functools import cache
|
|||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import subprocess
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from flask import Flask, jsonify, send_file, request, render_template
|
||||||
from flask import Flask, render_template_string, jsonify, send_file, request, render_template
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
|
||||||
@@ -36,15 +34,11 @@ else:
|
|||||||
EXAMPLES_BASE_PATH = os.getenv("EXAMPLES_BASE_PATH", "../evaluation_examples/examples")
|
EXAMPLES_BASE_PATH = os.getenv("EXAMPLES_BASE_PATH", "../evaluation_examples/examples")
|
||||||
RESULTS_BASE_PATH = os.getenv("RESULTS_BASE_PATH", "../results")
|
RESULTS_BASE_PATH = os.getenv("RESULTS_BASE_PATH", "../results")
|
||||||
|
|
||||||
ACTION_SPACE=os.getenv("ACTION_SPACE", "pyautogui")
|
|
||||||
OBSERVATION_TYPE=os.getenv("OBSERVATION_TYPE", "screenshot")
|
|
||||||
MODEL_NAME=os.getenv("MODEL_NAME", "computer-use-preview")
|
|
||||||
MAX_STEPS = int(os.getenv("MAX_STEPS", "150"))
|
MAX_STEPS = int(os.getenv("MAX_STEPS", "150"))
|
||||||
|
|
||||||
def initialize_default_config():
|
@cache
|
||||||
"""Initialize default configuration from the first available config in results directory"""
|
def get_default_config():
|
||||||
global ACTION_SPACE, OBSERVATION_TYPE, MODEL_NAME, RESULTS_PATH, MAX_STEPS
|
"""Get the first available configuration from results directory"""
|
||||||
|
|
||||||
if os.path.exists(RESULTS_BASE_PATH):
|
if os.path.exists(RESULTS_BASE_PATH):
|
||||||
try:
|
try:
|
||||||
# Scan for the first available configuration
|
# Scan for the first available configuration
|
||||||
@@ -57,34 +51,38 @@ def initialize_default_config():
|
|||||||
for model_name in os.listdir(obs_path):
|
for model_name in os.listdir(obs_path):
|
||||||
model_path = os.path.join(obs_path, model_name)
|
model_path = os.path.join(obs_path, model_name)
|
||||||
if os.path.isdir(model_path):
|
if os.path.isdir(model_path):
|
||||||
# Use the first available configuration as default
|
# Get max_steps from args.json if available
|
||||||
ACTION_SPACE = action_space
|
|
||||||
OBSERVATION_TYPE = obs_type
|
|
||||||
MODEL_NAME = model_name
|
|
||||||
RESULTS_PATH = model_path
|
|
||||||
|
|
||||||
# Read max_steps from args.json if available
|
|
||||||
model_args = get_model_args(action_space, obs_type, model_name)
|
model_args = get_model_args(action_space, obs_type, model_name)
|
||||||
|
max_steps = MAX_STEPS
|
||||||
if model_args and 'max_steps' in model_args:
|
if model_args and 'max_steps' in model_args:
|
||||||
MAX_STEPS = model_args['max_steps']
|
max_steps = model_args['max_steps']
|
||||||
|
|
||||||
print(f"Initialized default config: {ACTION_SPACE}/{OBSERVATION_TYPE}/{MODEL_NAME} (max_steps: {MAX_STEPS})")
|
print(f"Found default config: {action_space}/{obs_type}/{model_name} (max_steps: {max_steps})")
|
||||||
return
|
return {
|
||||||
|
'action_space': action_space,
|
||||||
|
'observation_type': obs_type,
|
||||||
|
'model_name': model_name,
|
||||||
|
'max_steps': max_steps
|
||||||
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error scanning results directory for default config: {e}")
|
print(f"Error scanning results directory for default config: {e}")
|
||||||
|
|
||||||
# Fallback to original environment-based path if no configs found
|
# Fallback to environment-based config if no configs found
|
||||||
RESULTS_PATH = os.path.join(RESULTS_BASE_PATH, ACTION_SPACE, OBSERVATION_TYPE, MODEL_NAME)
|
fallback_config = {
|
||||||
print(f"Using fallback config from environment: {ACTION_SPACE}/{OBSERVATION_TYPE}/{MODEL_NAME} (max_steps: {MAX_STEPS})")
|
'action_space': os.getenv("ACTION_SPACE", "pyautogui"),
|
||||||
|
'observation_type': os.getenv("OBSERVATION_TYPE", "screenshot"),
|
||||||
|
'model_name': os.getenv("MODEL_NAME", "computer-use-preview"),
|
||||||
|
'max_steps': MAX_STEPS
|
||||||
|
}
|
||||||
|
print(f"Using fallback config from environment: {fallback_config['action_space']}/{fallback_config['observation_type']}/{fallback_config['model_name']} (max_steps: {fallback_config['max_steps']})")
|
||||||
|
return fallback_config
|
||||||
|
|
||||||
# Initialize default configuration
|
def ensure_cache_initialized(action_space, observation_type, model_name):
|
||||||
initialize_default_config()
|
"""Ensure cache is initialized for the given configuration"""
|
||||||
|
results_path = os.path.join(RESULTS_BASE_PATH, action_space, observation_type, model_name)
|
||||||
RESULTS_PATH = os.path.join(RESULTS_BASE_PATH, ACTION_SPACE, OBSERVATION_TYPE, MODEL_NAME)
|
if results_path not in TASK_STATUS_CACHE:
|
||||||
|
TASK_STATUS_CACHE[results_path] = {}
|
||||||
if RESULTS_PATH not in TASK_STATUS_CACHE:
|
return results_path
|
||||||
# Initialize cache for this results path
|
|
||||||
TASK_STATUS_CACHE[RESULTS_PATH] = {}
|
|
||||||
|
|
||||||
@cache
|
@cache
|
||||||
def load_task_list():
|
def load_task_list():
|
||||||
@@ -99,8 +97,16 @@ def get_task_info(task_type, task_id):
|
|||||||
return json.load(f)
|
return json.load(f)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_task_status(task_type, task_id):
|
def get_task_status_with_config(task_type, task_id, action_space, observation_type, model_name):
|
||||||
result_dir = os.path.join(RESULTS_PATH, task_type, task_id)
|
results_path = os.path.join(RESULTS_BASE_PATH, action_space, observation_type, model_name)
|
||||||
|
max_steps = MAX_STEPS
|
||||||
|
|
||||||
|
# Get max_steps from args.json if available
|
||||||
|
model_args = get_model_args(action_space, observation_type, model_name)
|
||||||
|
if model_args and 'max_steps' in model_args:
|
||||||
|
max_steps = model_args['max_steps']
|
||||||
|
|
||||||
|
result_dir = os.path.join(results_path, task_type, task_id)
|
||||||
|
|
||||||
if not os.path.exists(result_dir):
|
if not os.path.exists(result_dir):
|
||||||
return {
|
return {
|
||||||
@@ -152,7 +158,7 @@ def get_task_status(task_type, task_id):
|
|||||||
log_content = f.readlines()
|
log_content = f.readlines()
|
||||||
last_response = None
|
last_response = None
|
||||||
|
|
||||||
for i, line in enumerate(log_content):
|
for line in log_content:
|
||||||
# Extract agent responses for each step
|
# Extract agent responses for each step
|
||||||
if "Responses: [" in line:
|
if "Responses: [" in line:
|
||||||
response_text = line.split("Responses: [")[1].strip()
|
response_text = line.split("Responses: [")[1].strip()
|
||||||
@@ -192,7 +198,7 @@ def get_task_status(task_type, task_id):
|
|||||||
status = "Done (Message Exit)"
|
status = "Done (Message Exit)"
|
||||||
elif log_data.get("exit_condition") and "thought_exit: True" in log_data.get("exit_condition", ""):
|
elif log_data.get("exit_condition") and "thought_exit: True" in log_data.get("exit_condition", ""):
|
||||||
status = "Done (Thought Exit)"
|
status = "Done (Thought Exit)"
|
||||||
elif len(steps) >= MAX_STEPS:
|
elif len(steps) >= max_steps:
|
||||||
status = "Done (Max Steps)"
|
status = "Done (Max Steps)"
|
||||||
else:
|
else:
|
||||||
status = "Running"
|
status = "Running"
|
||||||
@@ -214,25 +220,41 @@ def get_task_status(task_type, task_id):
|
|||||||
return {
|
return {
|
||||||
"status": status,
|
"status": status,
|
||||||
"progress": len(steps),
|
"progress": len(steps),
|
||||||
"max_steps": MAX_STEPS,
|
"max_steps": max_steps,
|
||||||
"last_update": last_update,
|
"last_update": last_update,
|
||||||
"steps": steps,
|
"steps": steps,
|
||||||
"log_data": log_data,
|
"log_data": log_data,
|
||||||
"result": result_content
|
"result": result_content
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_task_status_brief(task_type, task_id):
|
def get_task_status(task_type, task_id):
|
||||||
|
# This function should not be used anymore - use get_task_status_with_config instead
|
||||||
|
default_config = get_default_config()
|
||||||
|
return get_task_status_with_config(task_type, task_id,
|
||||||
|
default_config['action_space'],
|
||||||
|
default_config['observation_type'],
|
||||||
|
default_config['model_name'])
|
||||||
|
|
||||||
|
def get_task_status_brief_with_config(task_type, task_id, action_space, observation_type, model_name):
|
||||||
"""
|
"""
|
||||||
Get brief status info for a task, without detailed step data, for fast homepage loading.
|
Get brief status info for a task, without detailed step data, for fast homepage loading.
|
||||||
"""
|
"""
|
||||||
# Generate cache key based on task type and ID
|
results_path = os.path.join(RESULTS_BASE_PATH, action_space, observation_type, model_name)
|
||||||
cache_key = f"{task_type}_{task_id}"
|
max_steps = MAX_STEPS
|
||||||
|
|
||||||
|
# Get max_steps from args.json if available
|
||||||
|
model_args = get_model_args(action_space, observation_type, model_name)
|
||||||
|
if model_args and 'max_steps' in model_args:
|
||||||
|
max_steps = model_args['max_steps']
|
||||||
|
|
||||||
|
# Generate cache key based on task type, ID, and config
|
||||||
|
cache_key = f"{task_type}_{task_id}_{action_space}_{observation_type}_{model_name}"
|
||||||
|
|
||||||
# Check if the status is already cached
|
# Check if the status is already cached
|
||||||
current_time = time.time()
|
current_time = time.time()
|
||||||
last_cache_time = None
|
last_cache_time = None
|
||||||
if cache_key in TASK_STATUS_CACHE[RESULTS_PATH]:
|
if results_path in TASK_STATUS_CACHE and cache_key in TASK_STATUS_CACHE[results_path]:
|
||||||
cached_status, cached_time = TASK_STATUS_CACHE[RESULTS_PATH][cache_key]
|
cached_status, cached_time = TASK_STATUS_CACHE[results_path][cache_key]
|
||||||
last_cache_time = cached_time
|
last_cache_time = cached_time
|
||||||
# If cached status is "Done", check if it's within the stability period
|
# If cached status is "Done", check if it's within the stability period
|
||||||
if cached_status["status"].startswith("Done"):
|
if cached_status["status"].startswith("Done"):
|
||||||
@@ -247,13 +269,13 @@ def get_task_status_brief(task_type, task_id):
|
|||||||
# For non-Done status (like Error), just return from cache
|
# For non-Done status (like Error), just return from cache
|
||||||
return cached_status
|
return cached_status
|
||||||
|
|
||||||
result_dir = os.path.join(RESULTS_PATH, task_type, task_id)
|
result_dir = os.path.join(results_path, task_type, task_id)
|
||||||
|
|
||||||
if not os.path.exists(result_dir):
|
if not os.path.exists(result_dir):
|
||||||
return {
|
return {
|
||||||
"status": "Not Started",
|
"status": "Not Started",
|
||||||
"progress": 0,
|
"progress": 0,
|
||||||
"max_steps": MAX_STEPS,
|
"max_steps": max_steps,
|
||||||
"last_update": None
|
"last_update": None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +287,7 @@ def get_task_status_brief(task_type, task_id):
|
|||||||
return {
|
return {
|
||||||
"status": "Preparing",
|
"status": "Preparing",
|
||||||
"progress": 0,
|
"progress": 0,
|
||||||
"max_steps": MAX_STEPS,
|
"max_steps": max_steps,
|
||||||
"last_update": datetime.fromtimestamp(os.path.getmtime(result_dir)).strftime("%Y-%m-%d %H:%M:%S")
|
"last_update": datetime.fromtimestamp(os.path.getmtime(result_dir)).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +318,7 @@ def get_task_status_brief(task_type, task_id):
|
|||||||
return {
|
return {
|
||||||
"status": "Initializing",
|
"status": "Initializing",
|
||||||
"progress": 0,
|
"progress": 0,
|
||||||
"max_steps": MAX_STEPS,
|
"max_steps": max_steps,
|
||||||
"last_update": datetime.fromtimestamp(os.path.getmtime(traj_file)).strftime("%Y-%m-%d %H:%M:%S")
|
"last_update": datetime.fromtimestamp(os.path.getmtime(traj_file)).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,7 +333,7 @@ def get_task_status_brief(task_type, task_id):
|
|||||||
status = "Error"
|
status = "Error"
|
||||||
|
|
||||||
# If step count reaches max, consider as done
|
# If step count reaches max, consider as done
|
||||||
if step_count >= MAX_STEPS:
|
if step_count >= max_steps:
|
||||||
status = "Done (Max Steps)"
|
status = "Done (Max Steps)"
|
||||||
|
|
||||||
# Quickly check exit condition in log file (only last few lines)
|
# Quickly check exit condition in log file (only last few lines)
|
||||||
@@ -329,7 +351,7 @@ def get_task_status_brief(task_type, task_id):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
# If step count reaches max again (double check)
|
# If step count reaches max again (double check)
|
||||||
if step_count >= MAX_STEPS:
|
if step_count >= max_steps:
|
||||||
status = "Done (Max Steps)"
|
status = "Done (Max Steps)"
|
||||||
|
|
||||||
# Get last update time
|
# Get last update time
|
||||||
@@ -352,18 +374,34 @@ def get_task_status_brief(task_type, task_id):
|
|||||||
status_dict = {
|
status_dict = {
|
||||||
"status": status,
|
"status": status,
|
||||||
"progress": step_count,
|
"progress": step_count,
|
||||||
"max_steps": MAX_STEPS,
|
"max_steps": max_steps,
|
||||||
"last_update": last_update,
|
"last_update": last_update,
|
||||||
"result": result_content
|
"result": result_content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Initialize cache for this results path if it doesn't exist
|
||||||
|
if results_path not in TASK_STATUS_CACHE:
|
||||||
|
TASK_STATUS_CACHE[results_path] = {}
|
||||||
|
|
||||||
# Cache the status if it is done or error
|
# Cache the status if it is done or error
|
||||||
if status.startswith("Done") or status == "Error":
|
if status.startswith("Done") or status == "Error":
|
||||||
current_time = last_cache_time if last_cache_time else current_time
|
current_time = last_cache_time if last_cache_time else current_time
|
||||||
TASK_STATUS_CACHE[RESULTS_PATH][cache_key] = (status_dict, current_time)
|
TASK_STATUS_CACHE[results_path][cache_key] = (status_dict, current_time)
|
||||||
|
|
||||||
return status_dict
|
return status_dict
|
||||||
|
|
||||||
|
def get_task_status_brief(task_type, task_id):
|
||||||
|
"""
|
||||||
|
Get brief status info for a task, without detailed step data, for fast homepage loading.
|
||||||
|
"""
|
||||||
|
# This function should not be used anymore - use get_task_status_brief_with_config instead
|
||||||
|
default_config = get_default_config()
|
||||||
|
return get_task_status_brief_with_config(task_type, task_id,
|
||||||
|
default_config['action_space'],
|
||||||
|
default_config['observation_type'],
|
||||||
|
default_config['model_name'])
|
||||||
|
|
||||||
|
|
||||||
def get_all_tasks_status():
|
def get_all_tasks_status():
|
||||||
task_list = load_task_list()
|
task_list = load_task_list()
|
||||||
result = {}
|
result = {}
|
||||||
@@ -389,6 +427,59 @@ def get_all_tasks_status():
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def get_all_tasks_status_with_config(action_space, observation_type, model_name):
|
||||||
|
task_list = load_task_list()
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
for task_type, task_ids in task_list.items():
|
||||||
|
result[task_type] = []
|
||||||
|
for task_id in task_ids:
|
||||||
|
task_info = get_task_info(task_type, task_id)
|
||||||
|
task_status = get_task_status_with_config(task_type, task_id, action_space, observation_type, model_name)
|
||||||
|
|
||||||
|
if task_info:
|
||||||
|
result[task_type].append({
|
||||||
|
"id": task_id,
|
||||||
|
"instruction": task_info.get("instruction", "No instruction provided"),
|
||||||
|
"status": task_status
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
result[task_type].append({
|
||||||
|
"id": task_id,
|
||||||
|
"instruction": "No task info available",
|
||||||
|
"status": task_status
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
def get_all_tasks_status_brief_with_config(action_space, observation_type, model_name):
|
||||||
|
"""
|
||||||
|
Get brief status info for all tasks, without detailed step data, for fast homepage loading.
|
||||||
|
"""
|
||||||
|
task_list = load_task_list()
|
||||||
|
result = {}
|
||||||
|
|
||||||
|
for task_type, task_ids in task_list.items():
|
||||||
|
result[task_type] = []
|
||||||
|
for task_id in task_ids:
|
||||||
|
task_info = get_task_info(task_type, task_id)
|
||||||
|
task_status = get_task_status_brief_with_config(task_type, task_id, action_space, observation_type, model_name)
|
||||||
|
|
||||||
|
if task_info:
|
||||||
|
result[task_type].append({
|
||||||
|
"id": task_id,
|
||||||
|
"instruction": task_info.get("instruction", "No instruction provided"),
|
||||||
|
"status": task_status
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
result[task_type].append({
|
||||||
|
"id": task_id,
|
||||||
|
"instruction": "No task info available",
|
||||||
|
"status": task_status
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
def get_all_tasks_status_brief():
|
def get_all_tasks_status_brief():
|
||||||
"""
|
"""
|
||||||
Get brief status info for all tasks, without detailed step data, for fast homepage loading.
|
Get brief status info for all tasks, without detailed step data, for fast homepage loading.
|
||||||
@@ -423,8 +514,14 @@ def index():
|
|||||||
|
|
||||||
@app.route('/task/<task_type>/<task_id>')
|
@app.route('/task/<task_type>/<task_id>')
|
||||||
def task_detail(task_type, task_id):
|
def task_detail(task_type, task_id):
|
||||||
|
# Get config from URL parameters
|
||||||
|
default_config = get_default_config()
|
||||||
|
action_space = request.args.get('action_space', default_config['action_space'])
|
||||||
|
observation_type = request.args.get('observation_type', default_config['observation_type'])
|
||||||
|
model_name = request.args.get('model_name', default_config['model_name'])
|
||||||
|
|
||||||
task_info = get_task_info(task_type, task_id)
|
task_info = get_task_info(task_type, task_id)
|
||||||
task_status = get_task_status(task_type, task_id)
|
task_status = get_task_status_with_config(task_type, task_id, action_space, observation_type, model_name)
|
||||||
|
|
||||||
if not task_info:
|
if not task_info:
|
||||||
return "Task not found", 404
|
return "Task not found", 404
|
||||||
@@ -433,22 +530,44 @@ def task_detail(task_type, task_id):
|
|||||||
task_id=task_id,
|
task_id=task_id,
|
||||||
task_type=task_type,
|
task_type=task_type,
|
||||||
task_info=task_info,
|
task_info=task_info,
|
||||||
task_status=task_status)
|
task_status=task_status,
|
||||||
|
action_space=action_space,
|
||||||
|
observation_type=observation_type,
|
||||||
|
model_name=model_name)
|
||||||
|
|
||||||
@app.route('/api/tasks')
|
@app.route('/api/tasks')
|
||||||
def api_tasks():
|
def api_tasks():
|
||||||
"""Task status API"""
|
"""Task status API"""
|
||||||
return jsonify(get_all_tasks_status())
|
# Get config from URL parameters
|
||||||
|
default_config = get_default_config()
|
||||||
|
action_space = request.args.get('action_space', default_config['action_space'])
|
||||||
|
observation_type = request.args.get('observation_type', default_config['observation_type'])
|
||||||
|
model_name = request.args.get('model_name', default_config['model_name'])
|
||||||
|
|
||||||
|
return jsonify(get_all_tasks_status_with_config(action_space, observation_type, model_name))
|
||||||
|
|
||||||
@app.route('/api/tasks/brief')
|
@app.route('/api/tasks/brief')
|
||||||
def api_tasks_brief():
|
def api_tasks_brief():
|
||||||
"""Return brief status info for all tasks, without detailed step data, for fast homepage loading."""
|
"""Return brief status info for all tasks, without detailed step data, for fast homepage loading."""
|
||||||
return jsonify(get_all_tasks_status_brief())
|
# Get config from URL parameters
|
||||||
|
default_config = get_default_config()
|
||||||
|
action_space = request.args.get('action_space', default_config['action_space'])
|
||||||
|
observation_type = request.args.get('observation_type', default_config['observation_type'])
|
||||||
|
model_name = request.args.get('model_name', default_config['model_name'])
|
||||||
|
|
||||||
|
return jsonify(get_all_tasks_status_brief_with_config(action_space, observation_type, model_name))
|
||||||
|
|
||||||
@app.route('/task/<task_type>/<task_id>/screenshot/<path:filename>')
|
@app.route('/task/<task_type>/<task_id>/screenshot/<path:filename>')
|
||||||
def task_screenshot(task_type, task_id, filename):
|
def task_screenshot(task_type, task_id, filename):
|
||||||
"""Get task screenshot"""
|
"""Get task screenshot"""
|
||||||
screenshot_path = os.path.join(RESULTS_PATH, task_type, task_id, filename)
|
# Get config from URL parameters
|
||||||
|
default_config = get_default_config()
|
||||||
|
action_space = request.args.get('action_space', default_config['action_space'])
|
||||||
|
observation_type = request.args.get('observation_type', default_config['observation_type'])
|
||||||
|
model_name = request.args.get('model_name', default_config['model_name'])
|
||||||
|
|
||||||
|
results_path = os.path.join(RESULTS_BASE_PATH, action_space, observation_type, model_name)
|
||||||
|
screenshot_path = os.path.join(results_path, task_type, task_id, filename)
|
||||||
if os.path.exists(screenshot_path):
|
if os.path.exists(screenshot_path):
|
||||||
return send_file(screenshot_path, mimetype='image/png')
|
return send_file(screenshot_path, mimetype='image/png')
|
||||||
else:
|
else:
|
||||||
@@ -457,7 +576,14 @@ def task_screenshot(task_type, task_id, filename):
|
|||||||
@app.route('/task/<task_type>/<task_id>/recording')
|
@app.route('/task/<task_type>/<task_id>/recording')
|
||||||
def task_recording(task_type, task_id):
|
def task_recording(task_type, task_id):
|
||||||
"""Get task recording video"""
|
"""Get task recording video"""
|
||||||
recording_path = os.path.join(RESULTS_PATH, task_type, task_id, "recording.mp4")
|
# Get config from URL parameters
|
||||||
|
default_config = get_default_config()
|
||||||
|
action_space = request.args.get('action_space', default_config['action_space'])
|
||||||
|
observation_type = request.args.get('observation_type', default_config['observation_type'])
|
||||||
|
model_name = request.args.get('model_name', default_config['model_name'])
|
||||||
|
|
||||||
|
results_path = os.path.join(RESULTS_BASE_PATH, action_space, observation_type, model_name)
|
||||||
|
recording_path = os.path.join(results_path, task_type, task_id, "recording.mp4")
|
||||||
if os.path.exists(recording_path):
|
if os.path.exists(recording_path):
|
||||||
response = send_file(recording_path, mimetype='video/mp4')
|
response = send_file(recording_path, mimetype='video/mp4')
|
||||||
# Add headers to improve mobile compatibility
|
# Add headers to improve mobile compatibility
|
||||||
@@ -471,8 +597,14 @@ def task_recording(task_type, task_id):
|
|||||||
@app.route('/api/task/<task_type>/<task_id>')
|
@app.route('/api/task/<task_type>/<task_id>')
|
||||||
def api_task_detail(task_type, task_id):
|
def api_task_detail(task_type, task_id):
|
||||||
"""Task detail API"""
|
"""Task detail API"""
|
||||||
|
# Get config from URL parameters
|
||||||
|
default_config = get_default_config()
|
||||||
|
action_space = request.args.get('action_space', default_config['action_space'])
|
||||||
|
observation_type = request.args.get('observation_type', default_config['observation_type'])
|
||||||
|
model_name = request.args.get('model_name', default_config['model_name'])
|
||||||
|
|
||||||
task_info = get_task_info(task_type, task_id)
|
task_info = get_task_info(task_type, task_id)
|
||||||
task_status = get_task_status(task_type, task_id)
|
task_status = get_task_status_with_config(task_type, task_id, action_space, observation_type, model_name)
|
||||||
|
|
||||||
if not task_info:
|
if not task_info:
|
||||||
return jsonify({"error": "Task does not exist"}), 404
|
return jsonify({"error": "Task does not exist"}), 404
|
||||||
@@ -488,9 +620,9 @@ def api_config():
|
|||||||
config_info = {
|
config_info = {
|
||||||
"task_config_path": TASK_CONFIG_PATH,
|
"task_config_path": TASK_CONFIG_PATH,
|
||||||
"results_base_path": RESULTS_BASE_PATH,
|
"results_base_path": RESULTS_BASE_PATH,
|
||||||
"action_space": ACTION_SPACE,
|
"action_space": get_default_config()['action_space'],
|
||||||
"observation_type": OBSERVATION_TYPE,
|
"observation_type": get_default_config()['observation_type'],
|
||||||
"model_name": MODEL_NAME,
|
"model_name": get_default_config()['model_name'],
|
||||||
"max_steps": MAX_STEPS,
|
"max_steps": MAX_STEPS,
|
||||||
"examples_base_path": EXAMPLES_BASE_PATH
|
"examples_base_path": EXAMPLES_BASE_PATH
|
||||||
}
|
}
|
||||||
@@ -529,16 +661,27 @@ def api_available_configs():
|
|||||||
@app.route('/api/current-config')
|
@app.route('/api/current-config')
|
||||||
def api_current_config():
|
def api_current_config():
|
||||||
"""Get current configuration including args.json data"""
|
"""Get current configuration including args.json data"""
|
||||||
|
# Get config from URL parameters or use defaults
|
||||||
|
default_config = get_default_config()
|
||||||
|
action_space = request.args.get('action_space', default_config['action_space'])
|
||||||
|
observation_type = request.args.get('observation_type', default_config['observation_type'])
|
||||||
|
model_name = request.args.get('model_name', default_config['model_name'])
|
||||||
|
|
||||||
|
# Get max_steps from args.json if available
|
||||||
|
model_args = get_model_args(action_space, observation_type, model_name)
|
||||||
|
max_steps = MAX_STEPS
|
||||||
|
if model_args and 'max_steps' in model_args:
|
||||||
|
max_steps = model_args['max_steps']
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
"action_space": ACTION_SPACE,
|
"action_space": action_space,
|
||||||
"observation_type": OBSERVATION_TYPE,
|
"observation_type": observation_type,
|
||||||
"model_name": MODEL_NAME,
|
"model_name": model_name,
|
||||||
"max_steps": MAX_STEPS,
|
"max_steps": max_steps,
|
||||||
"results_path": RESULTS_PATH
|
"results_path": os.path.join(RESULTS_BASE_PATH, action_space, observation_type, model_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
# Add model args from args.json
|
# Add model args from args.json
|
||||||
model_args = get_model_args(ACTION_SPACE, OBSERVATION_TYPE, MODEL_NAME)
|
|
||||||
if model_args:
|
if model_args:
|
||||||
config["model_args"] = model_args
|
config["model_args"] = model_args
|
||||||
else:
|
else:
|
||||||
@@ -546,39 +689,6 @@ def api_current_config():
|
|||||||
|
|
||||||
return jsonify(config)
|
return jsonify(config)
|
||||||
|
|
||||||
@app.route('/api/set-config', methods=['POST'])
|
|
||||||
def api_set_config():
|
|
||||||
"""Set current configuration"""
|
|
||||||
global ACTION_SPACE, OBSERVATION_TYPE, MODEL_NAME, RESULTS_PATH, MAX_STEPS
|
|
||||||
|
|
||||||
data = request.get_json()
|
|
||||||
if not data:
|
|
||||||
return jsonify({"error": "No data provided"}), 400
|
|
||||||
|
|
||||||
# Update global variables
|
|
||||||
ACTION_SPACE = data.get('action_space', ACTION_SPACE)
|
|
||||||
OBSERVATION_TYPE = data.get('observation_type', OBSERVATION_TYPE)
|
|
||||||
MODEL_NAME = data.get('model_name', MODEL_NAME)
|
|
||||||
|
|
||||||
# Update results path
|
|
||||||
RESULTS_PATH = os.path.join(RESULTS_BASE_PATH, ACTION_SPACE, OBSERVATION_TYPE, MODEL_NAME)
|
|
||||||
|
|
||||||
# Update max_steps from args.json if available
|
|
||||||
model_args = get_model_args(ACTION_SPACE, OBSERVATION_TYPE, MODEL_NAME)
|
|
||||||
if model_args and 'max_steps' in model_args:
|
|
||||||
MAX_STEPS = model_args['max_steps']
|
|
||||||
|
|
||||||
if RESULTS_PATH not in TASK_STATUS_CACHE:
|
|
||||||
# Initialize cache for this results path
|
|
||||||
TASK_STATUS_CACHE[RESULTS_PATH] = {}
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
"action_space": ACTION_SPACE,
|
|
||||||
"observation_type": OBSERVATION_TYPE,
|
|
||||||
"model_name": MODEL_NAME,
|
|
||||||
"max_steps": MAX_STEPS,
|
|
||||||
"results_path": RESULTS_PATH
|
|
||||||
})
|
|
||||||
|
|
||||||
def get_model_args(action_space, observation_type, model_name):
|
def get_model_args(action_space, observation_type, model_name):
|
||||||
"""Get model arguments from args.json file"""
|
"""Get model arguments from args.json file"""
|
||||||
@@ -591,6 +701,28 @@ def get_model_args(action_space, observation_type, model_name):
|
|||||||
print(f"Error reading args.json: {e}")
|
print(f"Error reading args.json: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@app.route('/api/clear-cache', methods=['POST'])
|
||||||
|
def api_clear_cache():
|
||||||
|
"""Clear task status cache for current configuration"""
|
||||||
|
global TASK_STATUS_CACHE
|
||||||
|
|
||||||
|
# Get config from URL parameters or use defaults
|
||||||
|
default_config = get_default_config()
|
||||||
|
action_space = request.args.get('action_space', default_config['action_space'])
|
||||||
|
observation_type = request.args.get('observation_type', default_config['observation_type'])
|
||||||
|
model_name = request.args.get('model_name', default_config['model_name'])
|
||||||
|
|
||||||
|
results_path = os.path.join(RESULTS_BASE_PATH, action_space, observation_type, model_name)
|
||||||
|
|
||||||
|
# Clear cache only for the current configuration
|
||||||
|
if results_path in TASK_STATUS_CACHE:
|
||||||
|
TASK_STATUS_CACHE[results_path].clear()
|
||||||
|
message = f"Cache cleared for configuration: {action_space}/{observation_type}/{model_name}"
|
||||||
|
else:
|
||||||
|
message = f"No cache found for configuration: {action_space}/{observation_type}/{model_name}"
|
||||||
|
|
||||||
|
return jsonify({"message": message})
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
# Check if necessary directories exist
|
# Check if necessary directories exist
|
||||||
if not os.path.exists(TASK_CONFIG_PATH):
|
if not os.path.exists(TASK_CONFIG_PATH):
|
||||||
|
|||||||
@@ -364,33 +364,6 @@ h2 { color: #0056b3; margin-top: 32px; font-size: 1.6em; }
|
|||||||
.progress-fill { height: 100%; background: linear-gradient(90deg, #007bff, #00c6ff); width: 0%; transition: width 0.6s ease; }
|
.progress-fill { height: 100%; background: linear-gradient(90deg, #007bff, #00c6ff); width: 0%; transition: width 0.6s ease; }
|
||||||
.task-actions { margin-top: 20px; }
|
.task-actions { margin-top: 20px; }
|
||||||
.timestamp { font-size: 0.9em; color: #6c757d; margin-top: 6px; }
|
.timestamp { font-size: 0.9em; color: #6c757d; margin-top: 6px; }
|
||||||
.fab {
|
|
||||||
position: fixed;
|
|
||||||
z-index: 1000;
|
|
||||||
right: 36px;
|
|
||||||
width: 60px;
|
|
||||||
height: 60px;
|
|
||||||
border-radius: 50%;
|
|
||||||
box-shadow: 0 6px 24px rgba(0,0,0,0.18);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 1.6em;
|
|
||||||
font-weight: bold;
|
|
||||||
background: linear-gradient(135deg, #007bff, #0056b3);
|
|
||||||
color: #fff;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
|
||||||
.fab:hover {
|
|
||||||
background: linear-gradient(135deg, #0056b3, #007bff);
|
|
||||||
box-shadow: 0 8px 32px rgba(0,123,255,0.28);
|
|
||||||
transform: translateY(-3px) rotate(360deg);
|
|
||||||
}
|
|
||||||
.fab-refresh {
|
|
||||||
bottom: 36px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-tasks {
|
.no-tasks {
|
||||||
color: #8492a6;
|
color: #8492a6;
|
||||||
@@ -425,16 +398,12 @@ h2 { color: #0056b3; margin-top: 32px; font-size: 1.6em; }
|
|||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 为列表设置淡入动画 */
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
from { opacity: 0; transform: translateY(20px); }
|
from { opacity: 0; transform: translateY(20px); }
|
||||||
to { opacity: 1; transform: translateY(0); }
|
to { opacity: 1; transform: translateY(0); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.fab { right: 18px; width: 52px; height: 52px; font-size: 1.3em; }
|
|
||||||
.fab-refresh { bottom: 18px; }
|
|
||||||
h1 { font-size: 1.8em; }
|
h1 { font-size: 1.8em; }
|
||||||
.system-status { font-size: 0.4em; display: block; margin: 10px auto; width: fit-content; }
|
.system-status { font-size: 0.4em; display: block; margin: 10px auto; width: fit-content; }
|
||||||
.task-status { padding: 4px 10px; font-size: 0.85em; }
|
.task-status { padding: 4px 10px; font-size: 0.85em; }
|
||||||
|
|||||||
@@ -16,6 +16,41 @@ let availableConfigs = [];
|
|||||||
let currentConfig = null;
|
let currentConfig = null;
|
||||||
let categoryStats = {};
|
let categoryStats = {};
|
||||||
|
|
||||||
|
// Get configuration from URL parameters
|
||||||
|
function getConfigFromURL() {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
return {
|
||||||
|
action_space: urlParams.get('action_space'),
|
||||||
|
observation_type: urlParams.get('observation_type'),
|
||||||
|
model_name: urlParams.get('model_name')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update URL parameters with current configuration
|
||||||
|
function updateURLWithConfig(config) {
|
||||||
|
const url = new URL(window.location);
|
||||||
|
if (config.action_space) url.searchParams.set('action_space', config.action_space);
|
||||||
|
else url.searchParams.delete('action_space');
|
||||||
|
if (config.observation_type) url.searchParams.set('observation_type', config.observation_type);
|
||||||
|
else url.searchParams.delete('observation_type');
|
||||||
|
if (config.model_name) url.searchParams.set('model_name', config.model_name);
|
||||||
|
else url.searchParams.delete('model_name');
|
||||||
|
|
||||||
|
window.history.replaceState({}, '', url);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build API URL with config parameters
|
||||||
|
function buildAPIURL(endpoint, config = null) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
const configToUse = config || getConfigFromURL();
|
||||||
|
|
||||||
|
if (configToUse.action_space) params.set('action_space', configToUse.action_space);
|
||||||
|
if (configToUse.observation_type) params.set('observation_type', configToUse.observation_type);
|
||||||
|
if (configToUse.model_name) params.set('model_name', configToUse.model_name);
|
||||||
|
|
||||||
|
return params.toString() ? `${endpoint}?${params.toString()}` : endpoint;
|
||||||
|
}
|
||||||
|
|
||||||
function refreshPage() {
|
function refreshPage() {
|
||||||
// Save expanded state before refresh
|
// Save expanded state before refresh
|
||||||
const expandedTaskTypes = [];
|
const expandedTaskTypes = [];
|
||||||
@@ -29,128 +64,12 @@ function refreshPage() {
|
|||||||
// Store in sessionStorage
|
// Store in sessionStorage
|
||||||
sessionStorage.setItem('expandedTaskTypes', JSON.stringify(expandedTaskTypes));
|
sessionStorage.setItem('expandedTaskTypes', JSON.stringify(expandedTaskTypes));
|
||||||
|
|
||||||
// Only fetch brief data for update to improve refresh speed
|
// Full page refresh
|
||||||
fetchTasksForRefresh();
|
window.location.reload();
|
||||||
}
|
|
||||||
|
|
||||||
function fetchTasksForRefresh() {
|
|
||||||
fetch('/api/tasks/brief')
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
allTaskData = data;
|
|
||||||
categoryStats = calculateCategoryStats(data);
|
|
||||||
// Only update statistics and task status, do not fully re-render
|
|
||||||
updateStatistics(data);
|
|
||||||
updateTaskStatus(data);
|
|
||||||
})
|
|
||||||
.catch(error => console.error('Error refreshing tasks:', error));
|
|
||||||
}
|
|
||||||
|
|
||||||
// New function: only update task status, do not re-render the entire list
|
|
||||||
function updateTaskStatus(data) {
|
|
||||||
// Add pulse animation to score banner when refreshing
|
|
||||||
const scoreBanner = document.querySelector('.score-banner');
|
|
||||||
if (scoreBanner) {
|
|
||||||
scoreBanner.classList.add('refreshing');
|
|
||||||
setTimeout(() => {
|
|
||||||
scoreBanner.classList.remove('refreshing');
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the status display of each task
|
|
||||||
Object.entries(data).forEach(([taskType, tasks]) => {
|
|
||||||
tasks.forEach(task => {
|
|
||||||
// Find the corresponding task card
|
|
||||||
const taskCard = document.querySelector(`.task-card[data-task-id="${task.id}"][data-task-type="${taskType}"]`);
|
|
||||||
if (!taskCard) return;
|
|
||||||
|
|
||||||
// Update status display
|
|
||||||
const statusElement = taskCard.querySelector('.task-status');
|
|
||||||
if (statusElement) {
|
|
||||||
// Remove all status classes
|
|
||||||
statusElement.classList.remove('status-not-started', 'status-preparing', 'status-running', 'status-completed', 'status-error', 'status-unknown');
|
|
||||||
|
|
||||||
// Set new status class and icon
|
|
||||||
let statusClass = '';
|
|
||||||
let statusIcon = '';
|
|
||||||
|
|
||||||
switch(task.status.status) {
|
|
||||||
case 'Not Started':
|
|
||||||
statusClass = 'status-not-started';
|
|
||||||
statusIcon = 'fa-hourglass-start';
|
|
||||||
break;
|
|
||||||
case 'Preparing':
|
|
||||||
case 'Initializing':
|
|
||||||
statusClass = 'status-preparing';
|
|
||||||
statusIcon = 'fa-spinner fa-pulse';
|
|
||||||
break;
|
|
||||||
case 'Running':
|
|
||||||
statusClass = 'status-running';
|
|
||||||
statusIcon = 'fa-running';
|
|
||||||
break;
|
|
||||||
case 'Done':
|
|
||||||
case 'Done (Message Exit)':
|
|
||||||
case 'Done (Max Steps)':
|
|
||||||
case 'Done (Thought Exit)':
|
|
||||||
statusClass = 'status-completed';
|
|
||||||
statusIcon = 'fa-check-circle';
|
|
||||||
break;
|
|
||||||
case 'Error':
|
|
||||||
statusClass = 'status-error';
|
|
||||||
statusIcon = 'fa-exclamation-circle';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
statusClass = 'status-unknown';
|
|
||||||
statusIcon = 'fa-question-circle';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
statusElement.classList.add(statusClass);
|
|
||||||
statusElement.innerHTML = `<i class="fas ${statusIcon}"></i> ${task.status.status}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update progress bar
|
|
||||||
if (task.status.progress > 0) {
|
|
||||||
const progressText = taskCard.querySelector('.task-details div:first-child');
|
|
||||||
if (progressText) {
|
|
||||||
progressText.innerHTML = `<i class="fas fa-chart-line"></i> Progress: ${task.status.progress}/${task.status.max_steps} step(s)`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const progressFill = taskCard.querySelector('.progress-fill');
|
|
||||||
if (progressFill) {
|
|
||||||
const percentage = (task.status.progress / task.status.max_steps) * 100;
|
|
||||||
progressFill.style.width = `${percentage}%`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const progressPercentage = taskCard.querySelector('.progress-percentage');
|
|
||||||
if (progressPercentage) {
|
|
||||||
const percentage = (task.status.progress / task.status.max_steps) * 100;
|
|
||||||
progressPercentage.textContent = `${Math.round(percentage)}%`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update last update time
|
|
||||||
const timestamp = taskCard.querySelector('.timestamp');
|
|
||||||
if (timestamp && task.status.last_update) {
|
|
||||||
timestamp.innerHTML = `<i class="far fa-clock"></i> Last Update: ${task.status.last_update}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update result info
|
|
||||||
if (task.status.result) {
|
|
||||||
let resultDiv = taskCard.querySelector('.task-result');
|
|
||||||
if (!resultDiv) {
|
|
||||||
resultDiv = document.createElement('div');
|
|
||||||
resultDiv.className = 'task-result';
|
|
||||||
taskCard.querySelector('.task-details').appendChild(resultDiv);
|
|
||||||
}
|
|
||||||
resultDiv.innerHTML = `<strong><i class="fas fa-flag-checkered"></i> Result:</strong> ${task.status.result}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchTasks() {
|
function fetchTasks() {
|
||||||
fetch('/api/tasks/brief')
|
fetch(buildAPIURL('/api/tasks/brief'))
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
allTaskData = data;
|
allTaskData = data;
|
||||||
@@ -428,7 +347,16 @@ function renderTasks(data) {
|
|||||||
if (task.status.status !== 'Not Started') {
|
if (task.status.status !== 'Not Started') {
|
||||||
taskCard.style.cursor = 'pointer';
|
taskCard.style.cursor = 'pointer';
|
||||||
taskCard.addEventListener('click', () => {
|
taskCard.addEventListener('click', () => {
|
||||||
window.location.href = `/task/${taskType}/${task.id}`;
|
const config = getConfigFromURL();
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
if (config.action_space) params.set('action_space', config.action_space);
|
||||||
|
if (config.observation_type) params.set('observation_type', config.observation_type);
|
||||||
|
if (config.model_name) params.set('model_name', config.model_name);
|
||||||
|
|
||||||
|
const url = params.toString() ?
|
||||||
|
`/task/${taskType}/${task.id}?${params.toString()}` :
|
||||||
|
`/task/${taskType}/${task.id}`;
|
||||||
|
window.location.href = url;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
tasksContainer.appendChild(taskCard);
|
tasksContainer.appendChild(taskCard);
|
||||||
@@ -511,40 +439,51 @@ function changeConfiguration() {
|
|||||||
|
|
||||||
const selectedConfig = availableConfigs[selectedIndex];
|
const selectedConfig = availableConfigs[selectedIndex];
|
||||||
|
|
||||||
// Send configuration change request
|
// Update URL parameters and do full page refresh
|
||||||
fetch('/api/set-config', {
|
updateURLWithConfig(selectedConfig);
|
||||||
method: 'POST',
|
window.location.reload();
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify(selectedConfig)
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
currentConfig = data;
|
|
||||||
displayConfig(data);
|
|
||||||
// Refresh tasks with new configuration
|
|
||||||
fetchTasks();
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error setting config:', error);
|
|
||||||
displayConfigError();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchConfig() {
|
function fetchConfig() {
|
||||||
return fetch('/api/current-config')
|
// Check URL parameters first
|
||||||
.then(response => response.json())
|
const urlConfig = getConfigFromURL();
|
||||||
.then(data => {
|
|
||||||
currentConfig = data;
|
if (urlConfig.action_space && urlConfig.observation_type && urlConfig.model_name) {
|
||||||
displayConfig(data);
|
// Use config from URL and fetch detailed info
|
||||||
updateConfigSelect();
|
const params = new URLSearchParams();
|
||||||
return data;
|
params.set('action_space', urlConfig.action_space);
|
||||||
})
|
params.set('observation_type', urlConfig.observation_type);
|
||||||
.catch(error => {
|
params.set('model_name', urlConfig.model_name);
|
||||||
console.error('Error fetching config:', error);
|
|
||||||
displayConfigError();
|
return fetch(`/api/current-config?${params.toString()}`)
|
||||||
});
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
currentConfig = data;
|
||||||
|
displayConfig(data);
|
||||||
|
updateConfigSelect();
|
||||||
|
return data;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching config:', error);
|
||||||
|
displayConfigError();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Fallback to default config from server
|
||||||
|
return fetch('/api/current-config')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
currentConfig = data;
|
||||||
|
displayConfig(data);
|
||||||
|
updateConfigSelect();
|
||||||
|
// Update URL with current config
|
||||||
|
updateURLWithConfig(data);
|
||||||
|
return data;
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error fetching config:', error);
|
||||||
|
displayConfigError();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateConfigSelect() {
|
function updateConfigSelect() {
|
||||||
@@ -574,21 +513,39 @@ function displayConfig(config) {
|
|||||||
document.getElementById('model-name').textContent = config.model_name || 'N/A';
|
document.getElementById('model-name').textContent = config.model_name || 'N/A';
|
||||||
document.getElementById('max-steps').textContent = config.max_steps || 'N/A';
|
document.getElementById('max-steps').textContent = config.max_steps || 'N/A';
|
||||||
|
|
||||||
// Display model args from args.json
|
// Display additional model args from args.json (excluding main config params)
|
||||||
const modelArgsElement = document.getElementById('model-args');
|
const modelArgsElement = document.getElementById('model-args');
|
||||||
if (config.model_args && Object.keys(config.model_args).length > 0) {
|
if (config.model_args && Object.keys(config.model_args).length > 0) {
|
||||||
let argsHtml = '';
|
// Skip the main config parameters that are already displayed
|
||||||
Object.entries(config.model_args).forEach(([key, value]) => {
|
const skipKeys = ['action_space', 'observation_type', 'model_name', 'max_steps'];
|
||||||
// Skip max_steps as it's already displayed above
|
const additionalArgs = Object.entries(config.model_args).filter(([key]) => !skipKeys.includes(key));
|
||||||
if (key !== 'max_steps') {
|
|
||||||
argsHtml += `<div class="config-item">
|
if (additionalArgs.length > 0) {
|
||||||
<span class="config-label">${key}:</span>
|
let argsHtml = `
|
||||||
<span class="config-value">${JSON.stringify(value)}</span>
|
<div class="config-collapsible">
|
||||||
|
<div class="config-collapsible-header" onclick="toggleConfigArgs()">
|
||||||
|
<i class="fas fa-chevron-right" id="config-args-chevron"></i>
|
||||||
|
<span>Additional Parameters (${additionalArgs.length})</span>
|
||||||
|
</div>
|
||||||
|
<div class="config-collapsible-content" id="config-args-content">`;
|
||||||
|
|
||||||
|
additionalArgs.forEach(([key, value]) => {
|
||||||
|
argsHtml += `
|
||||||
|
<div class="config-item">
|
||||||
|
<span class="config-label">${key}:</span>
|
||||||
|
<span class="config-value">${JSON.stringify(value)}</span>
|
||||||
|
</div>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
argsHtml += `
|
||||||
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
|
||||||
});
|
modelArgsElement.innerHTML = argsHtml;
|
||||||
modelArgsElement.innerHTML = argsHtml;
|
modelArgsElement.style.display = 'block';
|
||||||
modelArgsElement.style.display = 'block';
|
} else {
|
||||||
|
modelArgsElement.style.display = 'none';
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
modelArgsElement.style.display = 'none';
|
modelArgsElement.style.display = 'none';
|
||||||
}
|
}
|
||||||
@@ -665,3 +622,55 @@ function calculateCategoryStats(data) {
|
|||||||
|
|
||||||
return stats;
|
return stats;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleConfigArgs() {
|
||||||
|
const content = document.getElementById('config-args-content');
|
||||||
|
const chevron = document.getElementById('config-args-chevron');
|
||||||
|
|
||||||
|
if (content.style.display === 'none' || !content.style.display) {
|
||||||
|
content.style.display = 'block';
|
||||||
|
chevron.classList.remove('fa-chevron-right');
|
||||||
|
chevron.classList.add('fa-chevron-down');
|
||||||
|
} else {
|
||||||
|
content.style.display = 'none';
|
||||||
|
chevron.classList.remove('fa-chevron-down');
|
||||||
|
chevron.classList.add('fa-chevron-right');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearCacheAndRefresh() {
|
||||||
|
if (!confirm('Clearing the cache will cause slower loading temporarily as data needs to be reloaded. Continue?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const button = document.getElementById('clear-cache-btn');
|
||||||
|
const originalText = button.innerHTML;
|
||||||
|
|
||||||
|
// Show loading state
|
||||||
|
button.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Clearing...';
|
||||||
|
button.disabled = true;
|
||||||
|
|
||||||
|
// Build URL with current configuration parameters
|
||||||
|
const clearCacheUrl = buildAPIURL('/api/clear-cache');
|
||||||
|
|
||||||
|
fetch(clearCacheUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
console.log('Cache cleared:', data.message);
|
||||||
|
// Refresh the page after clearing cache
|
||||||
|
window.location.reload();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error clearing cache:', error);
|
||||||
|
alert('Failed to clear cache. Please try again.');
|
||||||
|
|
||||||
|
// Restore button state
|
||||||
|
button.innerHTML = originalText;
|
||||||
|
button.disabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,5 +16,48 @@ h1, h2, h3 { margin-top: 20px; }
|
|||||||
.task-actions { margin-top: 15px; }
|
.task-actions { margin-top: 15px; }
|
||||||
.btn { padding: 5px 10px; background-color: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer; text-decoration: none; display: inline-block; }
|
.btn { padding: 5px 10px; background-color: #007bff; color: white; border: none; border-radius: 3px; cursor: pointer; text-decoration: none; display: inline-block; }
|
||||||
.btn:hover { background-color: #0069d9; }
|
.btn:hover { background-color: #0069d9; }
|
||||||
|
.btn-warning { background-color: #ffc107; color: #212529; }
|
||||||
|
.btn-warning:hover { background-color: #e0a800; }
|
||||||
|
.btn:disabled { background-color: #6c757d; cursor: not-allowed; }
|
||||||
|
.config-actions { margin-top: 15px; padding-top: 10px; border-top: 1px solid #eee; }
|
||||||
|
.config-actions .btn { width: 100%; }
|
||||||
|
|
||||||
|
/* Collapsible config args styling */
|
||||||
|
.config-collapsible { margin-top: 10px; }
|
||||||
|
.config-collapsible-header {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.config-collapsible-header:hover { color: #333; }
|
||||||
|
.config-collapsible-header i {
|
||||||
|
margin-right: 8px;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.config-collapsible-content {
|
||||||
|
display: none;
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fix config item spacing */
|
||||||
|
.config-item {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
.config-item:last-child { margin-bottom: 0; }
|
||||||
|
.config-label {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #555;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.config-value {
|
||||||
|
color: #333;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
.refresh-btn { float: right; margin-top: 10px; }
|
.refresh-btn { float: right; margin-top: 10px; }
|
||||||
.timestamp { font-size: 0.8em; color: #666; }
|
.timestamp { font-size: 0.8em; color: #666; }
|
||||||
|
|||||||
@@ -53,6 +53,11 @@
|
|||||||
<!-- Model args from args.json will be populated here -->
|
<!-- Model args from args.json will be populated here -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="config-actions">
|
||||||
|
<button id="clear-cache-btn" class="btn btn-warning" onclick="clearCacheAndRefresh()">
|
||||||
|
<i class="fas fa-broom"></i> Clear Cache
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,7 +105,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="fab fab-refresh" onclick="refreshPage()" title="Refresh"><i class="fas fa-sync-alt"></i></button>
|
|
||||||
<script src="/static/index.js"></script>
|
<script src="/static/index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user