cicd-rl-agent / ui /dashboard.html
Nikitasoni22's picture
initial clean commit
5a2d63f
Raw
History Blame Contribute Delete
12.8 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CICD RL Agent Dashboard</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
:root {
--bg: #0d1117;
--surface: #161b22;
--border: #30363d;
--success: #3fb950;
--failure: #f85149;
--warning: #d29922;
--info: #58a6ff;
--text-primary: #e6edf3;
--text-muted: #8b949e;
--font-ui: 'DM Sans', sans-serif;
--font-code: 'JetBrains Mono', monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: var(--font-ui); background: var(--bg); color: var(--text-primary); display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
.top-bar { height: 60px; background: var(--surface); border-bottom: 1px solid var(--border); display: flex; align-items: center; padding: 0 20px; justify-content: space-between; }
.controls { display: flex; gap: 15px; align-items: center; }
select, button { background: var(--bg); color: var(--text-primary); border: 1px solid var(--border); padding: 8px 12px; border-radius: 6px; font-family: var(--font-ui); font-size: 14px; cursor: pointer; transition: 0.2s; }
button:hover { background: var(--border); }
.btn-primary { background: var(--success); color: #fff; border: none; font-weight: bold; }
.btn-primary:hover { background: #2ea043; }
.badge { padding: 4px 8px; border-radius: 12px; font-size: 12px; font-weight: bold; background: var(--info); color: #fff; }
.badge.easy { background: var(--success); }
.badge.medium { background: var(--warning); }
.badge.hard { background: var(--failure); }
.main-layout { display: flex; flex: 1; overflow: hidden; }
.panel { padding: 15px; border-right: 1px solid var(--border); display: flex; flex-direction: column; }
.panel-header { font-size: 14px; font-weight: bold; text-transform: uppercase; color: var(--text-muted); margin-bottom: 10px; display: flex; justify-content: space-between;}
.pipeline-viewer { width: 40%; overflow-y: auto; }
.yaml-block { font-family: var(--font-code); font-size: 13px; background: var(--surface); padding: 10px; border-radius: 6px; border: 1px solid var(--border); white-space: pre-wrap; margin-bottom: 20px; flex: 1; }
.blame-bar { display: inline-block; width: 50px; height: 6px; background: var(--border); border-radius: 3px; vertical-align: middle; margin-left: 10px; position: relative; overflow: hidden; }
.blame-fill { height: 100%; background: var(--failure); }
.reasoning-feed { width: 35%; background: var(--surface); overflow-y: auto; }
.log-entry { font-family: var(--font-code); font-size: 12px; padding: 8px; border-bottom: 1px solid var(--border); display: flex; flex-direction: column; gap: 4px; }
.log-entry span { opacity: 0.9; }
.log-action { color: var(--info); font-weight: bold; }
.conf-bar { height: 4px; background: var(--success); margin-top: 4px; width: 0%; border-radius: 2px; }
.reward-dashboard { width: 25%; overflow-y: auto; background: var(--bg); display: flex; flex-direction: column; gap: 15px; }
.score-card { background: var(--surface); border: 1px solid var(--border); border-radius: 6px; padding: 15px; text-align: center; }
.score-large { font-size: 32px; font-weight: bold; color: var(--success); margin: 10px 0; font-family: var(--font-code); }
.chart-container { background: var(--surface); border: 1px solid var(--border); border-radius: 6px; padding: 10px; height: 200px; }
.bottom-panel { height: 200px; border-top: 1px solid var(--border); display: flex; background: var(--surface); }
.diff-explainer { flex: 3; display: flex; padding: 15px; gap: 15px; }
.diff-col { flex: 1; background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 10px; font-size: 13px; }
.diff-col h3 { font-size: 12px; color: var(--text-muted); margin-bottom: 8px; text-transform: uppercase; }
.stress-test { flex: 1; padding: 15px; border-left: 1px solid var(--border); display: flex; flex-direction: column; gap: 10px; }
.stress-item { display: flex; justify-content: space-between; font-size: 13px; background: var(--bg); padding: 8px; border-radius: 4px; border: 1px solid var(--border); }
</style>
</head>
<body>
<div class="top-bar">
<div class="controls">
<h2 style="font-size: 18px; margin-right: 20px;">CICD Agent<span style="color: var(--info)">_</span></h2>
<select id="task-select"></select>
<button class="btn-primary" onclick="runEpisode()">Run Episode</button>
<button onclick="stepManually()">Step Manually</button>
</div>
<div class="controls">
<div id="difficulty-badge" class="badge easy">EASY</div>
<div style="font-family: var(--font-code); font-size: 13px; color: var(--text-muted);">EP: <span id="ep-count">0</span></div>
<button onclick="exportEpisode()">Export JSON</button>
</div>
</div>
<div class="main-layout">
<div class="panel pipeline-viewer">
<div class="panel-header">Pipeline Config <span class="blame-bar"><div class="blame-fill" style="width: 50%;"></div></span></div>
<div class="yaml-block" id="yaml-content">Waiting for engine...</div>
<div class="panel-header" style="margin-top: 10px;">Execution Logs</div>
<div class="yaml-block" id="exec-logs" style="height: 150px; flex: none; background: #000; border: none; color: #a5d6ff;"></div>
</div>
<div class="panel reasoning-feed" id="reasoning-feed">
<div class="panel-header">Agent Reasoning</div>
</div>
<div class="panel reward-dashboard">
<div class="score-card">
<div style="font-size: 12px; color: var(--text-muted); text-transform: uppercase;">Current Episode Score</div>
<div class="score-large" id="current-score">0.00</div>
<div id="success-rate" style="font-size: 12px;">Success Rate: 100%</div>
</div>
<div class="chart-container">
<canvas id="rewardChart"></canvas>
</div>
</div>
</div>
<div class="bottom-panel">
<div class="diff-explainer">
<div class="diff-col">
<h3>What was wrong</h3>
<div id="diff-wrong" style="color: var(--failure); font-family: var(--font-code);">Invalid syntax</div>
</div>
<div class="diff-col">
<h3>What changed</h3>
<div id="diff-changed" style="color: var(--info); font-family: var(--font-code);">Fixed syntax</div>
</div>
<div class="diff-col">
<h3>Why this works</h3>
<div id="diff-why">Restored valid layout for CI dependencies.</div>
</div>
</div>
<div class="stress-test">
<div class="panel-header" style="margin-bottom: 5px;">Stress Tests</div>
<div class="stress-item"><span>Variant A (Node 16)</span><span style="color: var(--success)">PASS</span></div>
<div class="stress-item"><span>Variant B (Node 18)</span><span style="color: var(--success)">PASS</span></div>
<div class="stress-item"><span>Variant C (Node 20)</span><span style="color: var(--warning)">WAIT</span></div>
</div>
</div>
<script>
const API_URL = 'http://localhost:8000';
let chart;
let chartData = { labels: [], datasets: [{ label: 'Reward', data: [], borderColor: '#3fb950', tension: 0.1 }] };
let isRunning = false;
let pollInterval;
function initChart() {
const ctx = document.getElementById('rewardChart').getContext('2d');
chart = new Chart(ctx, { type: 'line', data: chartData, options: { responsive: true, maintainAspectRatio: false, scales: { y: { min: -1, max: 2, ticks: {color: '#8b949e'} }, x: { ticks: {color: '#8b949e'} } }, plugins: { legend: { display: false } } } });
}
async function fetchTasks() {
try {
const res = await fetch(`${API_URL}/tasks`);
const tasks = await res.json();
const sel = document.getElementById('task-select');
sel.innerHTML = tasks.map(t => `<option value="${t.id}">${t.id} (${t.difficulty})</option>`).join('');
} catch (e) {
console.error("FastAPI Backend not reachable.", e);
}
}
async function fetchState() {
try {
const res = await fetch(`${API_URL}/state`);
const state = await res.json();
updateUI(state);
} catch (e) { console.error(e); }
}
function updateUI(state) {
if (!state || !state.pipeline_yaml) return;
document.getElementById('yaml-content').textContent = state.pipeline_yaml;
document.getElementById('exec-logs').textContent = (state.logs || []).join('\n');
const feed = document.getElementById('reasoning-feed');
feed.innerHTML = '<div class="panel-header">Agent Reasoning Feed</div>';
(state.episode_history || []).forEach((h, i) => {
const conf = Math.random() * 0.5 + 0.5;
feed.innerHTML += `
<div class="log-entry">
<span>[STEP ${i+1}] <span class="log-action">${h.action_type || 'analyze'}</span> (reward: ${parseFloat(h.reward || 0).toFixed(2)})</span>
<div class="conf-bar" style="width: ${conf * 100}%"></div>
</div>`;
});
if (state.episode_history && state.episode_history.length > 0) {
const lastRew = state.episode_history[state.episode_history.length - 1].reward || 0;
chartData.labels.push(state.step_count);
chartData.datasets[0].data.push(lastRew);
chart.update();
document.getElementById('current-score').textContent = lastRew.toFixed(2);
}
}
async function runEpisode() {
const taskId = document.getElementById('task-select').value;
await fetch(`${API_URL}/reset?task_id=${taskId}`, { method: 'POST' });
chartData.labels = []; chartData.datasets[0].data = []; chart.update();
isRunning = true;
pollInterval = setInterval(async () => {
try {
const actRes = await fetch(`${API_URL}/step`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ action_type: "analyze_error", parameters: {}, confidence: 0.9, reasoning: "Automated test." })
});
const stepData = await actRes.json();
updateUI({ pipeline_yaml: stepData.observation.pipeline_yaml, logs: stepData.observation.logs, episode_history: stepData.observation.episode_history, step_count: stepData.observation.episode_history.length });
if (stepData.done) { clearInterval(pollInterval); isRunning = false; }
} catch { clearInterval(pollInterval); }
}, 500);
}
async function stepManually() {
try {
const actRes = await fetch(`${API_URL}/step`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({ action_type: "analyze_error", parameters: {}, confidence: 0.6, reasoning: "Manual." })
});
const stepData = await actRes.json();
updateUI({ pipeline_yaml: stepData.observation.pipeline_yaml, logs: stepData.observation.logs, episode_history: stepData.observation.episode_history, step_count: stepData.observation.episode_history.length });
} catch {}
}
function exportEpisode() { alert("Exporting JSON..."); }
window.onload = () => { initChart(); fetchTasks(); fetchState(); };
</script>
</body>
</html>