mirror of
https://github.com/codeflash-ai/codeflash-internal.git
synced 2026-05-04 18:25:18 +00:00
476 lines
16 KiB
HTML
476 lines
16 KiB
HTML
|
|
<!DOCTYPE html>
|
|||
|
|
<html lang="en">
|
|||
|
|
<head>
|
|||
|
|
<meta charset="UTF-8">
|
|||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|||
|
|
<title>Code Repair Logs Dashboard</title>
|
|||
|
|
<style>
|
|||
|
|
* {
|
|||
|
|
box-sizing: border-box;
|
|||
|
|
margin: 0;
|
|||
|
|
padding: 0;
|
|||
|
|
}
|
|||
|
|
body {
|
|||
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|||
|
|
background: #1a1a2e;
|
|||
|
|
color: #eee;
|
|||
|
|
min-height: 100vh;
|
|||
|
|
padding: 20px;
|
|||
|
|
}
|
|||
|
|
h1 {
|
|||
|
|
text-align: center;
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
color: #00d9ff;
|
|||
|
|
}
|
|||
|
|
.stats {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 20px;
|
|||
|
|
justify-content: center;
|
|||
|
|
margin-bottom: 30px;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
}
|
|||
|
|
.stat-card {
|
|||
|
|
background: #16213e;
|
|||
|
|
padding: 20px 30px;
|
|||
|
|
border-radius: 10px;
|
|||
|
|
text-align: center;
|
|||
|
|
min-width: 150px;
|
|||
|
|
}
|
|||
|
|
.stat-card .number {
|
|||
|
|
font-size: 2em;
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: #00d9ff;
|
|||
|
|
}
|
|||
|
|
.stat-card .label {
|
|||
|
|
color: #888;
|
|||
|
|
font-size: 0.9em;
|
|||
|
|
}
|
|||
|
|
.search-bar {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 10px;
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
justify-content: center;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
}
|
|||
|
|
.search-bar input {
|
|||
|
|
padding: 10px 15px;
|
|||
|
|
border: none;
|
|||
|
|
border-radius: 5px;
|
|||
|
|
background: #16213e;
|
|||
|
|
color: #eee;
|
|||
|
|
width: 300px;
|
|||
|
|
font-size: 1em;
|
|||
|
|
}
|
|||
|
|
.search-bar input:focus {
|
|||
|
|
outline: 2px solid #00d9ff;
|
|||
|
|
}
|
|||
|
|
.trace-group {
|
|||
|
|
background: #16213e;
|
|||
|
|
border-radius: 10px;
|
|||
|
|
margin-bottom: 20px;
|
|||
|
|
overflow: hidden;
|
|||
|
|
}
|
|||
|
|
.trace-header {
|
|||
|
|
background: #0f3460;
|
|||
|
|
padding: 15px 20px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: center;
|
|||
|
|
transition: background 0.2s;
|
|||
|
|
}
|
|||
|
|
.trace-header:hover {
|
|||
|
|
background: #1a4a80;
|
|||
|
|
}
|
|||
|
|
.trace-header .trace-id {
|
|||
|
|
font-family: monospace;
|
|||
|
|
color: #00d9ff;
|
|||
|
|
font-size: 0.95em;
|
|||
|
|
}
|
|||
|
|
.trace-header .count-badge {
|
|||
|
|
background: #e94560;
|
|||
|
|
color: white;
|
|||
|
|
padding: 3px 10px;
|
|||
|
|
border-radius: 15px;
|
|||
|
|
font-size: 0.85em;
|
|||
|
|
}
|
|||
|
|
.trace-header .arrow {
|
|||
|
|
transition: transform 0.2s;
|
|||
|
|
}
|
|||
|
|
.trace-header.expanded .arrow {
|
|||
|
|
transform: rotate(180deg);
|
|||
|
|
}
|
|||
|
|
.trace-content {
|
|||
|
|
display: none;
|
|||
|
|
padding: 0;
|
|||
|
|
}
|
|||
|
|
.trace-content.show {
|
|||
|
|
display: block;
|
|||
|
|
}
|
|||
|
|
.log-entry {
|
|||
|
|
border-top: 1px solid #0f3460;
|
|||
|
|
padding: 20px;
|
|||
|
|
}
|
|||
|
|
.log-entry:first-child {
|
|||
|
|
border-top: none;
|
|||
|
|
}
|
|||
|
|
.log-entry-header {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: center;
|
|||
|
|
margin-bottom: 15px;
|
|||
|
|
flex-wrap: wrap;
|
|||
|
|
gap: 10px;
|
|||
|
|
}
|
|||
|
|
.optimization-id {
|
|||
|
|
font-family: monospace;
|
|||
|
|
font-size: 0.8em;
|
|||
|
|
color: #888;
|
|||
|
|
}
|
|||
|
|
.timestamp {
|
|||
|
|
font-size: 0.8em;
|
|||
|
|
color: #888;
|
|||
|
|
}
|
|||
|
|
.section {
|
|||
|
|
margin-bottom: 15px;
|
|||
|
|
}
|
|||
|
|
.section-title {
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: #00d9ff;
|
|||
|
|
margin-bottom: 8px;
|
|||
|
|
font-size: 0.9em;
|
|||
|
|
text-transform: uppercase;
|
|||
|
|
}
|
|||
|
|
.code-block {
|
|||
|
|
background: #0d1117;
|
|||
|
|
border-radius: 5px;
|
|||
|
|
padding: 15px;
|
|||
|
|
overflow-x: auto;
|
|||
|
|
font-family: 'Fira Code', 'Consolas', monospace;
|
|||
|
|
font-size: 0.85em;
|
|||
|
|
line-height: 1.5;
|
|||
|
|
white-space: pre-wrap;
|
|||
|
|
word-wrap: break-word;
|
|||
|
|
max-height: 400px;
|
|||
|
|
overflow-y: auto;
|
|||
|
|
}
|
|||
|
|
.explanation-block {
|
|||
|
|
background: #1e3a5f;
|
|||
|
|
border-radius: 5px;
|
|||
|
|
padding: 15px;
|
|||
|
|
line-height: 1.6;
|
|||
|
|
font-size: 0.9em;
|
|||
|
|
max-height: 300px;
|
|||
|
|
overflow-y: auto;
|
|||
|
|
}
|
|||
|
|
.refined-code {
|
|||
|
|
background: #0d2818;
|
|||
|
|
border: 1px solid #2ea043;
|
|||
|
|
}
|
|||
|
|
.expand-all-btn {
|
|||
|
|
background: #0f3460;
|
|||
|
|
color: #eee;
|
|||
|
|
border: none;
|
|||
|
|
padding: 10px 20px;
|
|||
|
|
border-radius: 5px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
font-size: 0.9em;
|
|||
|
|
transition: background 0.2s;
|
|||
|
|
}
|
|||
|
|
.expand-all-btn:hover {
|
|||
|
|
background: #1a4a80;
|
|||
|
|
}
|
|||
|
|
.no-results {
|
|||
|
|
text-align: center;
|
|||
|
|
padding: 40px;
|
|||
|
|
color: #888;
|
|||
|
|
}
|
|||
|
|
.copy-btn {
|
|||
|
|
background: #333;
|
|||
|
|
color: #888;
|
|||
|
|
border: none;
|
|||
|
|
padding: 5px 10px;
|
|||
|
|
border-radius: 3px;
|
|||
|
|
cursor: pointer;
|
|||
|
|
font-size: 0.75em;
|
|||
|
|
float: right;
|
|||
|
|
margin-bottom: 5px;
|
|||
|
|
}
|
|||
|
|
.copy-btn:hover {
|
|||
|
|
background: #444;
|
|||
|
|
color: #eee;
|
|||
|
|
}
|
|||
|
|
.status-badges {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 8px;
|
|||
|
|
margin-top: 10px;
|
|||
|
|
}
|
|||
|
|
.status-badge {
|
|||
|
|
padding: 4px 12px;
|
|||
|
|
border-radius: 4px;
|
|||
|
|
font-size: 0.8em;
|
|||
|
|
font-weight: 600;
|
|||
|
|
text-transform: uppercase;
|
|||
|
|
}
|
|||
|
|
.status-badge.passed-true {
|
|||
|
|
background: #238636;
|
|||
|
|
color: #fff;
|
|||
|
|
}
|
|||
|
|
.status-badge.passed-false {
|
|||
|
|
background: #da3633;
|
|||
|
|
color: #fff;
|
|||
|
|
}
|
|||
|
|
.status-badge.faster-true {
|
|||
|
|
background: #1f6feb;
|
|||
|
|
color: #fff;
|
|||
|
|
}
|
|||
|
|
.status-badge.faster-false {
|
|||
|
|
background: #6e7681;
|
|||
|
|
color: #fff;
|
|||
|
|
}
|
|||
|
|
.status-badge.pending {
|
|||
|
|
background: #484f58;
|
|||
|
|
color: #8b949e;
|
|||
|
|
}
|
|||
|
|
.trace-status-summary {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 8px;
|
|||
|
|
align-items: center;
|
|||
|
|
}
|
|||
|
|
.trace-status-icon {
|
|||
|
|
font-size: 1.1em;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
</head>
|
|||
|
|
<body>
|
|||
|
|
<h1>Code Repair Logs Dashboard</h1>
|
|||
|
|
|
|||
|
|
<div class="stats">
|
|||
|
|
<div class="stat-card">
|
|||
|
|
<div class="number" id="total-traces">0</div>
|
|||
|
|
<div class="label">Trace Groups</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="stat-card">
|
|||
|
|
<div class="number" id="total-logs">0</div>
|
|||
|
|
<div class="label">Total Logs</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="stat-card">
|
|||
|
|
<div class="number" id="avg-per-trace">0</div>
|
|||
|
|
<div class="label">Avg per Trace</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="stat-card">
|
|||
|
|
<div class="number" id="passed-count" style="color: #238636;">0</div>
|
|||
|
|
<div class="label">Passed</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="stat-card">
|
|||
|
|
<div class="number" id="faster-count" style="color: #1f6feb;">0</div>
|
|||
|
|
<div class="label">Faster</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="search-bar">
|
|||
|
|
<input type="text" id="search" placeholder="Search by trace ID, optimization ID, or content...">
|
|||
|
|
<button class="expand-all-btn" onclick="toggleAll()">Expand All</button>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div id="dashboard"></div>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
// Data will be injected here
|
|||
|
|
const data = DATA_PLACEHOLDER;
|
|||
|
|
|
|||
|
|
let allExpanded = false;
|
|||
|
|
|
|||
|
|
function escapeHtml(text) {
|
|||
|
|
if (!text) return '';
|
|||
|
|
const div = document.createElement('div');
|
|||
|
|
div.textContent = text;
|
|||
|
|
return div.innerHTML;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function formatDate(dateStr) {
|
|||
|
|
if (!dateStr) return '';
|
|||
|
|
const date = new Date(dateStr);
|
|||
|
|
return date.toLocaleString();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function copyToClipboard(text, btn) {
|
|||
|
|
navigator.clipboard.writeText(text).then(() => {
|
|||
|
|
const originalText = btn.textContent;
|
|||
|
|
btn.textContent = 'Copied!';
|
|||
|
|
setTimeout(() => btn.textContent = originalText, 1500);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function getStatusBadge(value, label, type) {
|
|||
|
|
if (value === null || value === undefined) {
|
|||
|
|
return `<span class="status-badge pending">${label}: Pending</span>`;
|
|||
|
|
}
|
|||
|
|
const isTrue = value === 'True' || value === 'true' || value === true || value === 'yes';
|
|||
|
|
const className = `${type}-${isTrue}`;
|
|||
|
|
const displayValue = isTrue ? 'Yes' : 'No';
|
|||
|
|
return `<span class="status-badge ${className}">${label}: ${displayValue}</span>`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function getTraceStatusSummary(logs) {
|
|||
|
|
const withStatus = logs.filter(l => l.passed !== null && l.passed !== undefined);
|
|||
|
|
if (withStatus.length === 0) return '';
|
|||
|
|
|
|||
|
|
const passedCount = withStatus.filter(l => l.passed === 'True' || l.passed === 'true' || l.passed === true).length;
|
|||
|
|
const fasterCount = withStatus.filter(l => l.faster === 'True' || l.faster === 'true' || l.faster === true).length;
|
|||
|
|
|
|||
|
|
const passedIcon = passedCount === withStatus.length ? '✓' : passedCount > 0 ? '◐' : '✗';
|
|||
|
|
const fasterIcon = fasterCount === withStatus.length ? '⚡' : fasterCount > 0 ? '◐' : '−';
|
|||
|
|
|
|||
|
|
return `<span class="trace-status-summary">
|
|||
|
|
<span title="Passed: ${passedCount}/${withStatus.length}">${passedIcon}</span>
|
|||
|
|
<span title="Faster: ${fasterCount}/${withStatus.length}">${fasterIcon}</span>
|
|||
|
|
</span>`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function renderDashboard(filteredData = data) {
|
|||
|
|
const dashboard = document.getElementById('dashboard');
|
|||
|
|
|
|||
|
|
// Group by trace_id
|
|||
|
|
const grouped = {};
|
|||
|
|
filteredData.forEach(log => {
|
|||
|
|
if (!grouped[log.trace_id]) {
|
|||
|
|
grouped[log.trace_id] = [];
|
|||
|
|
}
|
|||
|
|
grouped[log.trace_id].push(log);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Sort groups by most recent
|
|||
|
|
const sortedTraces = Object.entries(grouped).sort((a, b) => {
|
|||
|
|
const aDate = new Date(a[1][0].created_at);
|
|||
|
|
const bDate = new Date(b[1][0].created_at);
|
|||
|
|
return bDate - aDate;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Update stats
|
|||
|
|
document.getElementById('total-traces').textContent = sortedTraces.length;
|
|||
|
|
document.getElementById('total-logs').textContent = filteredData.length;
|
|||
|
|
document.getElementById('avg-per-trace').textContent = sortedTraces.length > 0
|
|||
|
|
? (filteredData.length / sortedTraces.length).toFixed(1)
|
|||
|
|
: '0';
|
|||
|
|
|
|||
|
|
// Calculate passed/faster stats
|
|||
|
|
const withStatus = filteredData.filter(l => l.passed !== null && l.passed !== undefined);
|
|||
|
|
const passedCount = withStatus.filter(l => l.passed === 'True' || l.passed === 'true' || l.passed === true).length;
|
|||
|
|
const fasterCount = withStatus.filter(l => l.faster === 'True' || l.faster === 'true' || l.faster === true).length;
|
|||
|
|
document.getElementById('passed-count').textContent = withStatus.length > 0 ? `${passedCount}/${withStatus.length}` : '−';
|
|||
|
|
document.getElementById('faster-count').textContent = withStatus.length > 0 ? `${fasterCount}/${withStatus.length}` : '−';
|
|||
|
|
|
|||
|
|
if (sortedTraces.length === 0) {
|
|||
|
|
dashboard.innerHTML = '<div class="no-results">No results found</div>';
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let html = '';
|
|||
|
|
sortedTraces.forEach(([traceId, logs], idx) => {
|
|||
|
|
// Sort logs within group by created_at
|
|||
|
|
logs.sort((a, b) => new Date(a.created_at) - new Date(b.created_at));
|
|||
|
|
|
|||
|
|
html += `
|
|||
|
|
<div class="trace-group">
|
|||
|
|
<div class="trace-header" onclick="toggleTrace(${idx})">
|
|||
|
|
<span class="trace-id">Trace: ${escapeHtml(traceId)}</span>
|
|||
|
|
<div style="display: flex; align-items: center; gap: 10px;">
|
|||
|
|
${getTraceStatusSummary(logs)}
|
|||
|
|
<span class="count-badge">${logs.length} entries</span>
|
|||
|
|
<span class="arrow">▼</span>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
<div class="trace-content" id="trace-${idx}">
|
|||
|
|
${logs.map((log, logIdx) => `
|
|||
|
|
<div class="log-entry">
|
|||
|
|
<div class="log-entry-header">
|
|||
|
|
<span class="optimization-id">Optimization: ${escapeHtml(log.optimization_id)}</span>
|
|||
|
|
<span class="timestamp">${formatDate(log.created_at)}</span>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="status-badges">
|
|||
|
|
${getStatusBadge(log.passed, 'Passed', 'passed')}
|
|||
|
|
${getStatusBadge(log.faster, 'Faster', 'faster')}
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="section" style="margin-top: 15px;">
|
|||
|
|
<div class="section-title">User Prompt</div>
|
|||
|
|
<button class="copy-btn" onclick="copyToClipboard(data.find(d => d.optimization_id === '${log.optimization_id}').user_prompt, this)">Copy</button>
|
|||
|
|
<div class="code-block">${escapeHtml(log.user_prompt)}</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title">Explanation</div>
|
|||
|
|
<div class="explanation-block">${escapeHtml(log.explanation)}</div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<div class="section">
|
|||
|
|
<div class="section-title">Refined Optimization</div>
|
|||
|
|
<button class="copy-btn" onclick="copyToClipboard(data.find(d => d.optimization_id === '${log.optimization_id}').refined_optimization, this)">Copy</button>
|
|||
|
|
<div class="code-block refined-code">${escapeHtml(log.refined_optimization)}</div>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
`).join('')}
|
|||
|
|
</div>
|
|||
|
|
</div>`;
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
dashboard.innerHTML = html;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function toggleTrace(idx) {
|
|||
|
|
const content = document.getElementById(`trace-${idx}`);
|
|||
|
|
const header = content.previousElementSibling;
|
|||
|
|
content.classList.toggle('show');
|
|||
|
|
header.classList.toggle('expanded');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function toggleAll() {
|
|||
|
|
const contents = document.querySelectorAll('.trace-content');
|
|||
|
|
const headers = document.querySelectorAll('.trace-header');
|
|||
|
|
allExpanded = !allExpanded;
|
|||
|
|
|
|||
|
|
contents.forEach(c => {
|
|||
|
|
if (allExpanded) {
|
|||
|
|
c.classList.add('show');
|
|||
|
|
} else {
|
|||
|
|
c.classList.remove('show');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
headers.forEach(h => {
|
|||
|
|
if (allExpanded) {
|
|||
|
|
h.classList.add('expanded');
|
|||
|
|
} else {
|
|||
|
|
h.classList.remove('expanded');
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
document.querySelector('.expand-all-btn').textContent = allExpanded ? 'Collapse All' : 'Expand All';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
document.getElementById('search').addEventListener('input', function(e) {
|
|||
|
|
const query = e.target.value.toLowerCase();
|
|||
|
|
if (!query) {
|
|||
|
|
renderDashboard(data);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const filtered = data.filter(log =>
|
|||
|
|
(log.trace_id && log.trace_id.toLowerCase().includes(query)) ||
|
|||
|
|
(log.optimization_id && log.optimization_id.toLowerCase().includes(query)) ||
|
|||
|
|
(log.user_prompt && log.user_prompt.toLowerCase().includes(query)) ||
|
|||
|
|
(log.explanation && log.explanation.toLowerCase().includes(query)) ||
|
|||
|
|
(log.refined_optimization && log.refined_optimization.toLowerCase().includes(query))
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
renderDashboard(filtered);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// Initial render
|
|||
|
|
renderDashboard();
|
|||
|
|
</script>
|
|||
|
|
</body>
|
|||
|
|
</html>
|