Spaces:
Build error
Build error
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Tracklight Dashboard</title> | |
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script> | |
<style> | |
body { font-family: sans-serif; } | |
.container { max-width: 1200px; margin: 0 auto; padding: 20px; } | |
.header { display: flex; justify-content: space-between; align-items: center; } | |
.run { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; } | |
.metric-summary { display: flex; align-items: center; } | |
.play-button { margin-left: 10px; } | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<div class="header"> | |
<div> | |
<h1><a href="/dashboard">Tracklight Dashboard</a></h1> | |
<h2>Project: {{ project }}</h2> | |
<h3>User: {{ user }}</h3> | |
</div> | |
<button onclick="logout()">Logout</button> | |
</div> | |
<div id="runs"> | |
{% for run in runs %} | |
<div class="run" role="region" aria-labelledby="run-heading-{{ loop.index }}"> | |
<h4 id="run-heading-{{ loop.index }}">Run ID: {{ run[0] }}</h4> | |
<button onclick="loadMetrics('{{ run[0] }}')">Load Metrics</button> | |
<div id="config-{{ run[0] }}"></div> | |
<div id="plot-{{ run[0] }}" role="img" aria-label="A plot of metrics for this run."></div> | |
<div id="summary-{{ run[0] }}" aria-live="polite"></div> | |
</div> | |
{% endfor %} | |
</div> | |
</div> | |
<script> | |
const audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
const token = localStorage.getItem('tracklight-token'); | |
// If no token, redirect to login | |
if (!token) { | |
const urlParams = new URLSearchParams(window.location.search); | |
window.location.href = '/login?' + urlParams.toString(); | |
} | |
async function loadMetrics(runId) { | |
const response = await fetch(`/api/metrics?run_id=${runId}`, { | |
headers: { | |
'Authorization': `Bearer ${token}` | |
} | |
}); | |
if (response.status === 401) { | |
alert('Invalid or expired token. Please log in again.'); | |
logout(); | |
return; | |
} | |
const data = await response.json(); | |
// Render config table | |
const configDiv = document.getElementById(`config-${runId}`); | |
configDiv.innerHTML = generateConfigTable(data.config); | |
// Process and render metrics | |
const metrics = data.metrics; | |
const plotData = {}; | |
metrics.forEach(metric => { | |
const [name, value, timestamp] = metric; | |
if (!plotData[name]) { | |
plotData[name] = { | |
x: [], | |
y: [], | |
mode: 'lines+markers', | |
name: name | |
}; | |
} | |
plotData[name].x.push(new Date(timestamp)); | |
plotData[name].y.push(value); | |
}); | |
const plotDiv = document.getElementById(`plot-${runId}`); | |
Plotly.newPlot(plotDiv, Object.values(plotData), {title: `Metrics for run ${runId}`}); | |
// Generate and display summary | |
const summaryDiv = document.getElementById(`summary-${runId}`); | |
summaryDiv.innerHTML = generateSummary(plotData); | |
} | |
function generateConfigTable(config) { | |
if (!config || config.length === 0) { | |
return ''; | |
} | |
let table = '<h4>Configuration:</h4><table><thead><tr><th>Parameter</th><th>Value</th></tr></thead><tbody>'; | |
config.forEach(param => { | |
const name = param[0].replace('config/', ''); | |
const value = param[1]; | |
table += `<tr><td>${name}</td><td>${value}</td></tr>`; | |
}); | |
table += '</tbody></table>'; | |
return table; | |
} | |
function generateSummary(plotData) { | |
let summary = '<h4>Metrics Summary:</h4><ul>'; | |
for (const metricName in plotData) { | |
const metric = plotData[metricName]; | |
const values = metric.y; | |
const firstVal = values[0]; | |
const lastVal = values[values.length - 1]; | |
// Check for constant metric | |
const isConstant = values.every(v => v === firstVal); | |
if (isConstant) { | |
summary += `<li><b>${metricName}</b>: is constant at ${firstVal.toFixed(4)} throughout the run.</li>`; | |
continue; | |
} | |
// Descriptive stats for volatile metrics | |
const minVal = Math.min(...values); | |
const maxVal = Math.max(...values); | |
const trend = lastVal > firstVal ? 'increased' : 'decreased'; | |
// Momentum analysis (simple version) | |
const deltas = values.slice(1).map((v, i) => Math.abs(v - values[i])); | |
const avgDelta = deltas.reduce((a, b) => a + b, 0) / deltas.length; | |
const stdDev = Math.sqrt(deltas.map(d => Math.pow(d - avgDelta, 2)).reduce((a, b) => a + b, 0) / deltas.length); | |
let momentumDesc = ''; | |
if (stdDev / avgDelta > 0.5) { | |
momentumDesc = 'volatilely'; | |
} else if (avgDelta > (maxVal - minVal) / values.length * 2) { | |
momentumDesc = 'sharply'; | |
} else { | |
momentumDesc = 'steadily'; | |
} | |
summary += `<li class="metric-summary"> | |
<b>${metricName}</b>: ${momentumDesc} ${trend} from ${firstVal.toFixed(4)} to ${lastVal.toFixed(4)} over ${values.length} steps. | |
(Min: ${minVal.toFixed(4)}, Max: ${maxVal.toFixed(4)}) | |
<button class="play-button" onclick="playSlopeSound('${metricName}', [${values}])" aria-label="Play audio representation of the ${metricName} slope">Play Slope</button> | |
</li>`; | |
} | |
summary += '</ul>'; | |
return summary; | |
} | |
function playSlopeSound(metricName, values) { | |
const duration = 2; // seconds | |
const sampleRate = audioContext.sampleRate; | |
const buffer = audioContext.createBuffer(1, sampleRate * duration, sampleRate); | |
const data = buffer.getChannelData(0); | |
const minVal = Math.min(...values); | |
const maxVal = Math.max(...values); | |
for (let i = 0; i < data.length; i++) { | |
const t = i / sampleRate; | |
const progress = t / duration; | |
const index = Math.floor(progress * (values.length - 1)); | |
const value1 = values[index]; | |
const value2 = values[index + 1]; | |
const normalizedValue = (value1 + (value2 - value1) * (progress * (values.length - 1) - index) - minVal) / (maxVal - minVal); | |
const freq = 220 + (normalizedValue * 660); // Map value to frequency range (A3 to A5) | |
data[i] = Math.sin(2 * Math.PI * freq * t); | |
} | |
const source = audioContext.createBufferSource(); | |
source.buffer = buffer; | |
source.connect(audioContext.destination); | |
source.start(); | |
} | |
function logout() { | |
localStorage.removeItem('tracklight-token'); | |
window.location.href = '/login'; | |
} | |
</script> | |
</body> | |
</html> | |