Files
SuperCharged-Claude-Code-Up…/public/claude-ide/components/session-picker.js
uroma efb3ecfb19 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>
2026-01-21 10:53:11 +00:00

436 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Session Picker Component
* Show modal on startup to select existing session or create new
*
* Features:
* - Session picker modal on startup
* - Recent sessions list
* - Sessions grouped by project
* - Create new session
* - Session forking support
*/
class SessionPicker {
constructor() {
this.modal = null;
this.sessions = [];
this.initialized = false;
}
async initialize() {
if (this.initialized) return;
// Check URL params first
const urlParams = new URLSearchParams(window.location.search);
const sessionId = urlParams.get('session');
const project = urlParams.get('project');
if (sessionId) {
// Load specific session
console.log('[SessionPicker] Loading session from URL:', sessionId);
await this.loadSession(sessionId);
this.initialized = true;
return;
}
if (project) {
// Create or load session for project
console.log('[SessionPicker] Project context:', project);
await this.ensureSessionForProject(project);
this.initialized = true;
return;
}
// No session or project - show picker
await this.showPicker();
this.initialized = true;
}
async showPicker() {
// Create modal
this.modal = document.createElement('div');
this.modal.className = 'session-picker-modal';
this.modal.innerHTML = `
<div class="session-picker-content">
<div class="picker-header">
<h2>Select a Session</h2>
<button class="btn-close" onclick="window.sessionPicker.close()">×</button>
</div>
<div class="picker-tabs">
<button class="picker-tab active" data-tab="recent" onclick="window.sessionPicker.switchTab('recent')">
<span class="tab-icon">🕐</span>
<span class="tab-label">Recent</span>
</button>
<button class="picker-tab" data-tab="projects" onclick="window.sessionPicker.switchTab('projects')">
<span class="tab-icon">📁</span>
<span class="tab-label">Projects</span>
</button>
<button class="picker-tab" data-tab="new" onclick="window.sessionPicker.switchTab('new')">
<span class="tab-icon"></span>
<span class="tab-label">New Session</span>
</button>
</div>
<div class="picker-body">
<div id="picker-recent" class="picker-tab-content active">
<div class="loading">Loading recent sessions...</div>
</div>
<div id="picker-projects" class="picker-tab-content">
<div class="loading">Loading projects...</div>
</div>
<div id="picker-new" class="picker-tab-content">
<div class="new-session-form">
<div class="form-group">
<label>Session Name</label>
<input type="text" id="new-session-name" placeholder="My Session" />
</div>
<div class="form-group">
<label>Project (optional)</label>
<input type="text" id="new-session-project" placeholder="my-project" />
</div>
<button class="btn-primary btn-block" onclick="window.sessionPicker.createNewSession()">
Create Session
</button>
</div>
</div>
</div>
</div>
`;
document.body.appendChild(this.modal);
document.body.style.overflow = 'hidden'; // Prevent scrolling
// Load recent sessions
await this.loadRecentSessions();
await this.loadProjects();
}
async loadRecentSessions() {
const container = document.getElementById('picker-recent');
try {
const response = await fetch('/claude/api/claude/sessions');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
this.sessions = data.sessions || [];
if (this.sessions.length === 0) {
container.innerHTML = `
<div class="empty-state">
<div class="empty-icon">💬</div>
<h3>No sessions yet</h3>
<p>Create a new session to get started</p>
<button class="btn-primary" onclick="window.sessionPicker.switchTab('new')">
Create Session
</button>
</div>
`;
return;
}
// Sort by last modified
this.sessions.sort((a, b) => {
const dateA = new Date(a.modified || a.created);
const dateB = new Date(b.modified || b.created);
return dateB - dateA;
});
// Show last 10 sessions
const recentSessions = this.sessions.slice(0, 10);
container.innerHTML = recentSessions.map(session => {
const date = new Date(session.modified || session.created);
const timeAgo = this.formatTimeAgo(date);
const title = session.title || session.id;
const project = session.project || 'General';
return `
<div class="session-item" onclick="window.sessionPicker.selectSession('${session.id}')">
<div class="session-icon">💬</div>
<div class="session-info">
<div class="session-title">${this.escapeHtml(title)}</div>
<div class="session-meta">
<span class="session-project">${this.escapeHtml(project)}</span>
<span class="session-time">${timeAgo}</span>
</div>
</div>
<div class="session-arrow">→</div>
</div>
`;
}).join('');
} catch (error) {
console.error('[SessionPicker] Failed to load sessions:', error);
container.innerHTML = `
<div class="error-state">
<h3>Failed to load sessions</h3>
<p>${error.message}</p>
<button class="btn-secondary" onclick="window.sessionPicker.loadRecentSessions()">
Try Again
</button>
</div>
`;
}
}
async loadProjects() {
const container = document.getElementById('picker-projects');
try {
// Use the sessions endpoint to get projects
const response = await fetch('/claude/api/claude/sessions');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
// Group sessions by project
const projectMap = new Map();
const allSessions = [
...(data.active || []),
...(data.historical || [])
];
allSessions.forEach(session => {
const projectName = session.metadata?.project || session.workingDir?.split('/').pop() || 'Untitled';
if (!projectMap.has(projectName)) {
projectMap.set(projectName, {
name: projectName,
sessionCount: 0,
lastSession: session
});
}
const project = projectMap.get(projectName);
project.sessionCount++;
});
const projects = Array.from(projectMap.values());
if (projects.length === 0) {
container.innerHTML = `
<div class="empty-state">
<div class="empty-icon">📁</div>
<h3>No projects yet</h3>
<p>Create a new project to organize your sessions</p>
<button class="btn-primary" onclick="window.sessionPicker.switchTab('new')">
New Session
</button>
</div>
`;
return;
}
// Sort by session count (most used first)
projects.sort((a, b) => b.sessionCount - a.sessionCount);
container.innerHTML = projects.map(project => {
const sessionCount = project.sessionCount || 0;
return `
<div class="project-item" onclick="window.sessionPicker.selectProject('${this.escapeHtml(project.name)}')">
<div class="project-icon">📁</div>
<div class="project-info">
<div class="project-name">${this.escapeHtml(project.name)}</div>
<div class="project-meta">${sessionCount} session${sessionCount !== 1 ? 's' : ''}</div>
</div>
<div class="project-arrow">→</div>
</div>
`;
}).join('');
} catch (error) {
console.error('[SessionPicker] Failed to load projects:', error);
container.innerHTML = `
<div class="error-state">
<h3>Failed to load projects</h3>
<p>${error.message}</p>
</div>
`;
}
}
async selectSession(sessionId) {
await this.loadSession(sessionId);
this.close();
}
async selectProject(projectName) {
await this.ensureSessionForProject(projectName);
this.close();
}
async loadSession(sessionId) {
try {
const response = await fetch(`/claude/api/claude/sessions/${sessionId}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const session = await response.json();
// Attach to session
if (typeof attachToSession === 'function') {
attachToSession(sessionId);
}
console.log('[SessionPicker] Loaded session:', sessionId);
return session;
} catch (error) {
console.error('[SessionPicker] Failed to load session:', error);
if (typeof showToast === 'function') {
showToast(`Failed to load session: ${error.message}`, 'error', 3000);
}
}
}
async ensureSessionForProject(projectName) {
try {
// Check if session exists for this project
const response = await fetch('/claude/api/claude/sessions');
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const data = await response.json();
const sessions = data.sessions || [];
const projectSession = sessions.find(s => s.project === projectName);
if (projectSession) {
return await this.loadSession(projectSession.id);
}
// Create new session for project
return await this.createNewSession(projectName);
} catch (error) {
console.error('[SessionPicker] Failed to ensure session:', error);
}
}
async createNewSession(projectName = null) {
const nameInput = document.getElementById('new-session-name');
const projectInput = document.getElementById('new-session-project');
const name = nameInput?.value || projectName || 'Untitled Session';
const project = projectInput?.value || projectName || '';
try {
const response = await fetch('/claude/api/claude/sessions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: name,
project: project,
source: 'web-ide'
})
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
const session = await response.json();
// Attach to new session
if (typeof attachToSession === 'function') {
attachToSession(session.id);
}
console.log('[SessionPicker] Created session:', session.id);
this.close();
return session;
} catch (error) {
console.error('[SessionPicker] Failed to create session:', error);
if (typeof showToast === 'function') {
showToast(`Failed to create session: ${error.message}`, 'error', 3000);
}
}
}
switchTab(tabName) {
// Update tab buttons
this.modal.querySelectorAll('.picker-tab').forEach(tab => {
tab.classList.remove('active');
if (tab.dataset.tab === tabName) {
tab.classList.add('active');
}
});
// Update tab content
this.modal.querySelectorAll('.picker-tab-content').forEach(content => {
content.classList.remove('active');
});
const activeContent = document.getElementById(`picker-${tabName}`);
if (activeContent) {
activeContent.classList.add('active');
}
}
close() {
if (this.modal) {
this.modal.remove();
this.modal = null;
}
document.body.style.overflow = ''; // Restore scrolling
}
formatTimeAgo(date) {
const seconds = Math.floor((new Date() - date) / 1000);
if (seconds < 60) {
return 'Just now';
} else if (seconds < 3600) {
const minutes = Math.floor(seconds / 60);
return `${minutes}m ago`;
} else if (seconds < 86400) {
const hours = Math.floor(seconds / 3600);
return `${hours}h ago`;
} else if (seconds < 604800) {
const days = Math.floor(seconds / 86400);
return `${days}d ago`;
} else {
return date.toLocaleDateString();
}
}
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
}
// Global instance
let sessionPicker = null;
// Auto-initialize
if (typeof window !== 'undefined') {
window.SessionPicker = SessionPicker;
// Create instance
sessionPicker = new SessionPicker();
window.sessionPicker = sessionPicker;
// Initialize on DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
sessionPicker.initialize();
});
} else {
sessionPicker.initialize();
}
}
// Export for use in other scripts
if (typeof module !== 'undefined' && module.exports) {
module.exports = { SessionPicker };
}