fixes CF-932 # Pull Request Checklist ## Description - [ ] **Description of PR**: Clear and concise description of what this PR accomplishes - [ ] **Breaking Changes**: Document any breaking changes (if applicable) - [ ] **Related Issues**: Link to any related issues or tickets ## Testing - [ ] **Test cases Attached**: All relevant test cases have been added/updated - [ ] **Manual Testing**: Manual testing completed for the changes ## Monitoring & Debugging - [ ] **Logging in place**: Appropriate logging has been added for debugging user issues - [ ] **Sentry will be able to catch errors**: Error handling ensures Sentry can capture and report errors - [ ] **Avoid Dev based/Prisma logging**: No development-only or Prisma-specific logging in production code ## Configuration - [ ] **Env variables newly added**: Any new environment variables are documented in .env.example file or mentioned in description --- ## Additional Notes <!-- Add any additional context, screenshots, or notes for reviewers here --> --------- Co-authored-by: codeflash-ai[bot] <148906541+codeflash-ai[bot]@users.noreply.github.com> Co-authored-by: ali <mohammed18200118@gmail.com> Co-authored-by: Kevin Turcios <106575910+KRRT7@users.noreply.github.com>
475 lines
16 KiB
HTML
475 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>
|