feat: AI auto-fix bug tracker with real-time error monitoring
- Real-time error monitoring system with WebSocket - Auto-fix agent that triggers on browser errors - Bug tracker dashboard with floating button (🐛) - Live activity stream showing AI thought process - Fixed 4 JavaScript errors (SyntaxError, TypeError) - Fixed SessionPicker API endpoint error - Enhanced chat input with Monaco editor - Session picker component for project management Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
663
public/claude-ide/bug-tracker.js
Normal file
663
public/claude-ide/bug-tracker.js
Normal file
@@ -0,0 +1,663 @@
|
||||
/**
|
||||
* Real-Time Bug Tracker Dashboard
|
||||
* Shows all auto-detected errors and fix progress
|
||||
*/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// Error state storage
|
||||
window.bugTracker = {
|
||||
errors: [],
|
||||
fixesInProgress: new Map(),
|
||||
fixesCompleted: new Map(),
|
||||
activityLog: [], // New: stores AI activity stream
|
||||
|
||||
addError(error) {
|
||||
const errorId = this.generateErrorId(error);
|
||||
const existingError = this.errors.find(e => e.id === errorId);
|
||||
|
||||
if (!existingError) {
|
||||
const errorWithMeta = {
|
||||
id: errorId,
|
||||
...error,
|
||||
detectedAt: new Date().toISOString(),
|
||||
status: 'detected',
|
||||
count: 1,
|
||||
activity: [] // Activity for this error
|
||||
};
|
||||
this.errors.push(errorWithMeta);
|
||||
this.updateDashboard();
|
||||
|
||||
// Trigger auto-fix notification
|
||||
if (typeof showErrorNotification === 'function') {
|
||||
showErrorNotification(errorWithMeta);
|
||||
}
|
||||
} else {
|
||||
existingError.count++;
|
||||
existingError.lastSeen = new Date().toISOString();
|
||||
}
|
||||
|
||||
return errorId;
|
||||
},
|
||||
|
||||
startFix(errorId) {
|
||||
const error = this.errors.find(e => e.id === errorId);
|
||||
if (error) {
|
||||
error.status = 'fixing';
|
||||
error.fixStartedAt = new Date().toISOString();
|
||||
this.fixesInProgress.set(errorId, true);
|
||||
this.addActivity(errorId, '🤖', 'AI agent started analyzing error...');
|
||||
this.updateDashboard();
|
||||
}
|
||||
},
|
||||
|
||||
// Add activity to error's activity log
|
||||
addActivity(errorId, icon, message, type = 'info') {
|
||||
// Add to global activity log
|
||||
this.activityLog.unshift({
|
||||
errorId,
|
||||
icon,
|
||||
message,
|
||||
type,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
// Keep only last 50 global activities
|
||||
if (this.activityLog.length > 50) {
|
||||
this.activityLog = this.activityLog.slice(0, 50);
|
||||
}
|
||||
|
||||
// Add to specific error's activity
|
||||
const error = this.errors.find(e => e.id === errorId);
|
||||
if (error) {
|
||||
if (!error.activity) error.activity = [];
|
||||
error.activity.unshift({
|
||||
icon,
|
||||
message,
|
||||
type,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
if (error.activity.length > 20) {
|
||||
error.activity = error.activity.slice(0, 20);
|
||||
}
|
||||
}
|
||||
|
||||
this.updateActivityStream();
|
||||
},
|
||||
|
||||
// Update the activity stream display
|
||||
updateActivityStream() {
|
||||
const stream = document.getElementById('activity-stream');
|
||||
if (!stream) return;
|
||||
|
||||
// Show last 10 activities globally
|
||||
const recentActivities = this.activityLog.slice(0, 10);
|
||||
|
||||
stream.innerHTML = recentActivities.map(activity => {
|
||||
const timeAgo = this.getTimeAgo(activity.timestamp);
|
||||
return `
|
||||
<div class="activity-item activity-${activity.type}">
|
||||
<span class="activity-icon">${activity.icon}</span>
|
||||
<span class="activity-message">${this.escapeHtml(activity.message)}</span>
|
||||
<span class="activity-time">${timeAgo}</span>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
},
|
||||
|
||||
completeFix(errorId, fixDetails) {
|
||||
const error = this.errors.find(e => e.id === errorId);
|
||||
if (error) {
|
||||
error.status = 'fixed';
|
||||
error.fixedAt = new Date().toISOString();
|
||||
error.fixDetails = fixDetails;
|
||||
this.fixesInProgress.delete(errorId);
|
||||
this.fixesCompleted.set(errorId, true);
|
||||
this.updateDashboard();
|
||||
}
|
||||
},
|
||||
|
||||
generateErrorId(error) {
|
||||
const parts = [
|
||||
error.type,
|
||||
error.message.substring(0, 50),
|
||||
error.filename || 'unknown'
|
||||
];
|
||||
return btoa(parts.join('::')).substring(0, 20);
|
||||
},
|
||||
|
||||
updateDashboard() {
|
||||
const dashboard = document.getElementById('bug-tracker-dashboard');
|
||||
if (!dashboard) return;
|
||||
|
||||
const content = dashboard.querySelector('#bug-tracker-content');
|
||||
const stats = dashboard.querySelector('#bug-tracker-stats');
|
||||
if (!content) return;
|
||||
|
||||
// Update stats
|
||||
if (stats) {
|
||||
const totalErrors = this.errors.length;
|
||||
const activeErrors = this.errors.filter(e => e.status === 'detected').length;
|
||||
const fixingErrors = this.errors.filter(e => e.status === 'fixing').length;
|
||||
const fixedErrors = this.errors.filter(e => e.status === 'fixed').length;
|
||||
|
||||
stats.innerHTML = `
|
||||
<div class="stat-item">
|
||||
<span>Total:</span>
|
||||
<span class="stat-value">${totalErrors}</span>
|
||||
</div>
|
||||
<div class="stat-item" style="color: #ff6b6b;">
|
||||
<span>🔴 Active:</span>
|
||||
<span class="stat-value">${activeErrors}</span>
|
||||
</div>
|
||||
<div class="stat-item" style="color: #ffa94d;">
|
||||
<span>🔧 Fixing:</span>
|
||||
<span class="stat-value">${fixingErrors}</span>
|
||||
</div>
|
||||
<div class="stat-item" style="color: #51cf66;">
|
||||
<span>✅ Fixed:</span>
|
||||
<span class="stat-value">${fixedErrors}</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Sort errors: fixing first, then detected, then fixed
|
||||
const sortedErrors = [...this.errors].sort((a, b) => {
|
||||
const statusOrder = { 'fixing': 0, 'detected': 1, 'fixed': 2 };
|
||||
return statusOrder[a.status] - statusOrder[b.status];
|
||||
});
|
||||
|
||||
content.innerHTML = this.renderErrors(sortedErrors);
|
||||
},
|
||||
|
||||
renderErrors(errors) {
|
||||
if (errors.length === 0) {
|
||||
return `
|
||||
<div class="bug-tracker-empty">
|
||||
<div class="empty-icon">✨</div>
|
||||
<div class="empty-title">No bugs detected!</div>
|
||||
<div class="empty-subtitle">The code is running smoothly</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return errors.map(error => this.renderError(error)).join('');
|
||||
},
|
||||
|
||||
renderError(error) {
|
||||
const statusIcons = {
|
||||
'detected': '🔴',
|
||||
'fixing': '🔧',
|
||||
'fixed': '✅'
|
||||
};
|
||||
|
||||
const statusClasses = {
|
||||
'detected': 'status-detected',
|
||||
'fixing': 'status-fixing',
|
||||
'fixed': 'status-fixed'
|
||||
};
|
||||
|
||||
const timeAgo = this.getTimeAgo(error.detectedAt || error.timestamp);
|
||||
|
||||
return `
|
||||
<div class="bug-item ${statusClasses[error.status]}" data-error-id="${error.id}">
|
||||
<div class="bug-header">
|
||||
<span class="bug-status">${statusIcons[error.status]} ${error.status}</span>
|
||||
<span class="bug-time">${timeAgo}</span>
|
||||
${error.count > 1 ? `<span class="bug-count">×${error.count}</span>` : ''}
|
||||
</div>
|
||||
<div class="bug-message">${this.escapeHtml(error.message.substring(0, 100))}${error.message.length > 100 ? '...' : ''}</div>
|
||||
${error.filename ? `<div class="bug-location">📄 ${error.filename.split('/').pop()}:${error.line || ''}</div>` : ''}
|
||||
${error.fixDetails ? `<div class="bug-fix-details">✨ ${error.fixDetails}</div>` : ''}
|
||||
${error.status === 'detected' ? `
|
||||
<button class="bug-fix-btn" onclick="window.bugTracker.triggerManualFix('${error.id}')">
|
||||
🤖 Fix Now
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
triggerManualFix(errorId) {
|
||||
const error = this.errors.find(e => e.id === errorId);
|
||||
if (error) {
|
||||
// Report to server to trigger fix
|
||||
fetch('/claude/api/log-error', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
...error,
|
||||
manualTrigger: true
|
||||
})
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getTimeAgo(timestamp) {
|
||||
const now = new Date();
|
||||
const then = new Date(timestamp);
|
||||
const diffMs = now - then;
|
||||
const diffSecs = Math.floor(diffMs / 1000);
|
||||
const diffMins = Math.floor(diffSecs / 60);
|
||||
|
||||
if (diffSecs < 60) return `${diffSecs}s ago`;
|
||||
if (diffMins < 60) return `${diffMins}m ago`;
|
||||
return `${Math.floor(diffMins / 60)}h ago`;
|
||||
},
|
||||
|
||||
escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
},
|
||||
|
||||
toggle() {
|
||||
const dashboard = document.getElementById('bug-tracker-dashboard');
|
||||
if (dashboard) {
|
||||
dashboard.classList.toggle('visible');
|
||||
dashboard.classList.toggle('hidden');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Create dashboard UI
|
||||
function createDashboard() {
|
||||
// Create toggle button
|
||||
const toggleBtn = document.createElement('button');
|
||||
toggleBtn.id = 'bug-tracker-toggle';
|
||||
toggleBtn.className = 'bug-tracker-toggle';
|
||||
toggleBtn.innerHTML = `
|
||||
<span class="toggle-icon">🐛</span>
|
||||
<span class="toggle-badge" id="bug-count-badge">0</span>
|
||||
`;
|
||||
toggleBtn.onclick = () => window.bugTracker.toggle();
|
||||
|
||||
// Create dashboard
|
||||
const dashboard = document.createElement('div');
|
||||
dashboard.id = 'bug-tracker-dashboard';
|
||||
dashboard.className = 'bug-tracker-dashboard hidden';
|
||||
dashboard.innerHTML = `
|
||||
<div class="bug-tracker-header">
|
||||
<div class="bug-tracker-title">
|
||||
<span class="title-icon">🤖</span>
|
||||
<span>AI Auto-Fix Tracker</span>
|
||||
</div>
|
||||
<button class="bug-tracker-close" onclick="window.bugTracker.toggle()">×</button>
|
||||
</div>
|
||||
<div class="activity-stream-header">
|
||||
<span class="activity-title">🔴 Live Activity Feed</span>
|
||||
</div>
|
||||
<div id="activity-stream" class="activity-stream"></div>
|
||||
<div class="bug-tracker-stats" id="bug-tracker-stats"></div>
|
||||
<div id="bug-tracker-content" class="bug-tracker-content"></div>
|
||||
`;
|
||||
|
||||
// Add styles
|
||||
const style = document.createElement('style');
|
||||
style.id = 'bug-tracker-styles';
|
||||
style.textContent = `
|
||||
.bug-tracker-toggle {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.4);
|
||||
cursor: pointer;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.bug-tracker-toggle:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 6px 30px rgba(102, 126, 234, 0.6);
|
||||
}
|
||||
|
||||
.toggle-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.toggle-badge {
|
||||
position: absolute;
|
||||
top: -5px;
|
||||
right: -5px;
|
||||
background: #ff6b6b;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.bug-tracker-dashboard {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
right: 20px;
|
||||
transform: translateY(-50%);
|
||||
width: 400px;
|
||||
max-height: 80vh;
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #333;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 60px rgba(0, 0, 0, 0.5);
|
||||
z-index: 9998;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.bug-tracker-dashboard.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.bug-tracker-dashboard.visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.bug-tracker-header {
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #333;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bug-tracker-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.bug-tracker-close {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #888;
|
||||
font-size: 24px;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bug-tracker-close:hover {
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.bug-tracker-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.bug-item {
|
||||
background: #252525;
|
||||
border: 1px solid #333;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
margin-bottom: 12px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.bug-item:hover {
|
||||
border-color: #4a9eff;
|
||||
}
|
||||
|
||||
.bug-item.status-fixing {
|
||||
border-color: #ffa94d;
|
||||
background: #2a2520;
|
||||
}
|
||||
|
||||
.bug-item.status-fixed {
|
||||
border-color: #51cf66;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.bug-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.bug-status {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.status-detected {
|
||||
background: rgba(255, 107, 107, 0.2);
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
.status-fixing {
|
||||
background: rgba(255, 169, 77, 0.2);
|
||||
color: #ffa94d;
|
||||
}
|
||||
|
||||
.status-fixed {
|
||||
background: rgba(81, 207, 102, 0.2);
|
||||
color: #51cf66;
|
||||
}
|
||||
|
||||
.bug-time {
|
||||
font-size: 11px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.bug-count {
|
||||
background: #ff6b6b;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
padding: 2px 6px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.bug-message {
|
||||
color: #e0e0e0;
|
||||
font-size: 13px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.bug-location {
|
||||
color: #888;
|
||||
font-size: 11px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.bug-fix-details {
|
||||
color: #51cf66;
|
||||
font-size: 12px;
|
||||
margin-top: 8px;
|
||||
padding: 8px;
|
||||
background: rgba(81, 207, 102, 0.1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.bug-fix-btn {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
background: linear-gradient(135deg, #4a9eff 0%, #a78bfa 100%);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.bug-fix-btn:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(74, 158, 255, 0.4);
|
||||
}
|
||||
|
||||
.bug-tracker-empty {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #e0e0e0;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.empty-subtitle {
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.activity-stream-header {
|
||||
padding: 12px 20px;
|
||||
border-bottom: 1px solid #333;
|
||||
background: rgba(255, 107, 107, 0.05);
|
||||
}
|
||||
|
||||
.activity-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #ff6b6b;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.activity-stream {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
background: #0d0d0d;
|
||||
border-bottom: 1px solid #333;
|
||||
}
|
||||
|
||||
.activity-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 12px;
|
||||
margin-bottom: 6px;
|
||||
background: #1a1a1a;
|
||||
border-radius: 6px;
|
||||
border-left: 3px solid #4a9eff;
|
||||
transition: all 0.2s ease;
|
||||
animation: slideIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
.activity-item:hover {
|
||||
background: #252525;
|
||||
border-left-color: #a78bfa;
|
||||
}
|
||||
|
||||
.activity-item.activity-error {
|
||||
border-left-color: #ff6b6b;
|
||||
background: rgba(255, 107, 107, 0.05);
|
||||
}
|
||||
|
||||
.activity-item.activity-success {
|
||||
border-left-color: #51cf66;
|
||||
background: rgba(81, 207, 102, 0.05);
|
||||
}
|
||||
|
||||
.activity-item.activity-warning {
|
||||
border-left-color: #ffa94d;
|
||||
background: rgba(255, 169, 77, 0.05);
|
||||
}
|
||||
|
||||
.activity-icon {
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.activity-message {
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
color: #e0e0e0;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.activity-time {
|
||||
font-size: 10px;
|
||||
color: #888;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.bug-tracker-stats {
|
||||
padding: 12px 20px;
|
||||
border-bottom: 1px solid #333;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
font-size: 12px;
|
||||
background: #151515;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-weight: 600;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
`;
|
||||
|
||||
document.head.appendChild(style);
|
||||
document.body.appendChild(toggleBtn);
|
||||
document.body.appendChild(dashboard);
|
||||
|
||||
// Auto-update error count badge
|
||||
setInterval(() => {
|
||||
const badge = document.getElementById('bug-count-badge');
|
||||
if (badge) {
|
||||
const activeErrors = window.bugTracker.errors.filter(e => e.status !== 'fixed').length;
|
||||
badge.textContent = activeErrors;
|
||||
badge.style.display = activeErrors > 0 ? 'block' : 'none';
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// Initialize on DOM ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', createDashboard);
|
||||
} else {
|
||||
createDashboard();
|
||||
}
|
||||
|
||||
console.log('[BugTracker] Real-time bug tracker initialized');
|
||||
})();
|
||||
Reference in New Issue
Block a user