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:
@@ -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; }
|
||||
.task-actions { margin-top: 20px; }
|
||||
.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 {
|
||||
color: #8492a6;
|
||||
@@ -425,16 +398,12 @@ h2 { color: #0056b3; margin-top: 32px; font-size: 1.6em; }
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* 为列表设置淡入动画 */
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(20px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.fab { right: 18px; width: 52px; height: 52px; font-size: 1.3em; }
|
||||
.fab-refresh { bottom: 18px; }
|
||||
h1 { font-size: 1.8em; }
|
||||
.system-status { font-size: 0.4em; display: block; margin: 10px auto; width: fit-content; }
|
||||
.task-status { padding: 4px 10px; font-size: 0.85em; }
|
||||
|
||||
@@ -16,6 +16,41 @@ let availableConfigs = [];
|
||||
let currentConfig = null;
|
||||
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() {
|
||||
// Save expanded state before refresh
|
||||
const expandedTaskTypes = [];
|
||||
@@ -29,128 +64,12 @@ function refreshPage() {
|
||||
// Store in sessionStorage
|
||||
sessionStorage.setItem('expandedTaskTypes', JSON.stringify(expandedTaskTypes));
|
||||
|
||||
// Only fetch brief data for update to improve refresh speed
|
||||
fetchTasksForRefresh();
|
||||
}
|
||||
|
||||
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}`;
|
||||
}
|
||||
});
|
||||
});
|
||||
// Full page refresh
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function fetchTasks() {
|
||||
fetch('/api/tasks/brief')
|
||||
fetch(buildAPIURL('/api/tasks/brief'))
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
allTaskData = data;
|
||||
@@ -428,7 +347,16 @@ function renderTasks(data) {
|
||||
if (task.status.status !== 'Not Started') {
|
||||
taskCard.style.cursor = 'pointer';
|
||||
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);
|
||||
@@ -511,40 +439,51 @@ function changeConfiguration() {
|
||||
|
||||
const selectedConfig = availableConfigs[selectedIndex];
|
||||
|
||||
// Send configuration change request
|
||||
fetch('/api/set-config', {
|
||||
method: 'POST',
|
||||
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();
|
||||
});
|
||||
// Update URL parameters and do full page refresh
|
||||
updateURLWithConfig(selectedConfig);
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
function fetchConfig() {
|
||||
return fetch('/api/current-config')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
currentConfig = data;
|
||||
displayConfig(data);
|
||||
updateConfigSelect();
|
||||
return data;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching config:', error);
|
||||
displayConfigError();
|
||||
});
|
||||
// Check URL parameters first
|
||||
const urlConfig = getConfigFromURL();
|
||||
|
||||
if (urlConfig.action_space && urlConfig.observation_type && urlConfig.model_name) {
|
||||
// Use config from URL and fetch detailed info
|
||||
const params = new URLSearchParams();
|
||||
params.set('action_space', urlConfig.action_space);
|
||||
params.set('observation_type', urlConfig.observation_type);
|
||||
params.set('model_name', urlConfig.model_name);
|
||||
|
||||
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() {
|
||||
@@ -574,21 +513,39 @@ function displayConfig(config) {
|
||||
document.getElementById('model-name').textContent = config.model_name || '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');
|
||||
if (config.model_args && Object.keys(config.model_args).length > 0) {
|
||||
let argsHtml = '';
|
||||
Object.entries(config.model_args).forEach(([key, value]) => {
|
||||
// Skip max_steps as it's already displayed above
|
||||
if (key !== 'max_steps') {
|
||||
argsHtml += `<div class="config-item">
|
||||
<span class="config-label">${key}:</span>
|
||||
<span class="config-value">${JSON.stringify(value)}</span>
|
||||
// Skip the main config parameters that are already displayed
|
||||
const skipKeys = ['action_space', 'observation_type', 'model_name', 'max_steps'];
|
||||
const additionalArgs = Object.entries(config.model_args).filter(([key]) => !skipKeys.includes(key));
|
||||
|
||||
if (additionalArgs.length > 0) {
|
||||
let argsHtml = `
|
||||
<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>`;
|
||||
}
|
||||
});
|
||||
modelArgsElement.innerHTML = argsHtml;
|
||||
modelArgsElement.style.display = 'block';
|
||||
|
||||
modelArgsElement.innerHTML = argsHtml;
|
||||
modelArgsElement.style.display = 'block';
|
||||
} else {
|
||||
modelArgsElement.style.display = 'none';
|
||||
}
|
||||
} else {
|
||||
modelArgsElement.style.display = 'none';
|
||||
}
|
||||
@@ -665,3 +622,55 @@ function calculateCategoryStats(data) {
|
||||
|
||||
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; }
|
||||
.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-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; }
|
||||
.timestamp { font-size: 0.8em; color: #666; }
|
||||
|
||||
Reference in New Issue
Block a user