mys's picture
Upload folder using huggingface_hub
1c75c98 verified
<!DOCTYPE html>
<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>