// Claude Code IDE JavaScript let currentSession = null; let ws = null; // Make ws globally accessible for other scripts Object.defineProperty(window, 'ws', { get: function() { return ws; }, set: function(value) { ws = value; }, enumerable: true, configurable: true }); // Initialize document.addEventListener('DOMContentLoaded', () => { initNavigation(); connectWebSocket(); // Check URL params for session and prompt const urlParams = new URLSearchParams(window.location.search); const sessionId = urlParams.get('session'); const prompt = urlParams.get('prompt'); if (sessionId || prompt) { // Switch to chat view first switchView('chat'); // Wait for chat to load, then handle session/prompt setTimeout(() => { if (sessionId) { attachToSession(sessionId); } if (prompt) { setTimeout(() => { const input = document.getElementById('chat-input'); if (input) { input.value = decodeURIComponent(prompt); sendChatMessage(); } }, 1000); } }, 500); } else { // Default to chat view switchView('chat'); } }); // Navigation function initNavigation() { const navItems = document.querySelectorAll('.nav-item'); navItems.forEach(item => { item.addEventListener('click', () => { const view = item.dataset.view; switchView(view); }); }); } function switchView(viewName) { // Update nav items document.querySelectorAll('.nav-item').forEach(item => { item.classList.remove('active'); if (item.dataset.view === viewName) { item.classList.add('active'); } }); // Update views document.querySelectorAll('.view').forEach(view => { view.classList.remove('active'); }); document.getElementById(`${viewName}-view`).classList.add('active'); // Load content for the view switch(viewName) { case 'dashboard': loadDashboard(); break; case 'chat': loadChatView(); break; case 'sessions': loadSessions(); break; case 'projects': loadProjects(); break; case 'files': loadFiles(); break; case 'terminal': loadTerminal(); break; } } // WebSocket Connection function connectWebSocket() { const wsUrl = `wss://${window.location.host}/claude/api/claude/chat`; console.log('Connecting to WebSocket:', wsUrl); window.ws = new WebSocket(wsUrl); window.ws.onopen = () => { console.log('WebSocket connected, readyState:', window.ws.readyState); // Send a test message to verify connection try { window.ws.send(JSON.stringify({ type: 'ping' })); } catch (error) { console.error('Error sending ping:', error); } }; window.ws.onmessage = (event) => { const data = JSON.parse(event.data); console.log('WebSocket message received:', data.type); handleWebSocketMessage(data); }; window.ws.onerror = (error) => { console.error('WebSocket error:', error); console.log('WebSocket error details:', { type: error.type, target: error.target, readyState: window.ws?.readyState }); }; window.ws.onclose = (event) => { console.log('WebSocket disconnected:', { code: event.code, reason: event.reason, wasClean: event.wasClean }); // Clear the ws reference window.ws = null; // Attempt to reconnect after 5 seconds setTimeout(() => { console.log('Attempting to reconnect...'); connectWebSocket(); }, 5000); }; } function handleWebSocketMessage(data) { switch(data.type) { case 'connected': console.log(data.message); break; case 'output': handleSessionOutput(data); break; case 'operations-detected': handleOperationsDetected(data); break; case 'operations-executed': handleOperationsExecuted(data); break; case 'operations-error': handleOperationsError(data); break; case 'operation-progress': handleOperationProgress(data); break; case 'error': console.error('WebSocket error:', data.error); // Show error in chat if attached if (typeof appendSystemMessage === 'function') { appendSystemMessage('Error: ' + data.error); } break; } } /** * Handle operations detected event */ function handleOperationsDetected(data) { console.log('Operations detected:', data.operations.length); // Only show if we're attached to this session if (data.sessionId !== attachedSessionId) return; // Store response for execution window.currentOperationsResponse = data.response; // Use tag renderer to show operations panel if (typeof tagRenderer !== 'undefined') { tagRenderer.showOperationsPanel(data.operations, data.response); } } /** * Handle operations executed event */ function handleOperationsExecuted(data) { console.log('Operations executed:', data.results); // Only handle if we're attached to this session if (data.sessionId !== attachedSessionId) return; // Hide progress and show completion if (typeof tagRenderer !== 'undefined') { tagRenderer.hideProgress(); tagRenderer.hideOperationsPanel(); tagRenderer.showCompletion(data.results); } } /** * Handle operations error event */ function handleOperationsError(data) { console.error('Operations error:', data.error); // Only handle if we're attached to this session if (data.sessionId !== attachedSessionId) return; // Show error if (typeof tagRenderer !== 'undefined') { tagRenderer.hideProgress(); tagRenderer.showError(data.error); } } /** * Handle operation progress event */ function handleOperationProgress(data) { console.log('Operation progress:', data.progress); // Only handle if we're attached to this session if (data.sessionId !== attachedSessionId) return; // Update progress if (typeof tagRenderer !== 'undefined') { const progress = data.progress; let message = ''; switch(progress.type) { case 'write': message = `Creating ${progress.path}...`; break; case 'rename': message = `Renaming ${progress.from} to ${progress.to}...`; break; case 'delete': message = `Deleting ${progress.path}...`; break; case 'install': message = `Installing packages: ${progress.packages.join(', ')}...`; break; case 'command': message = `Executing command: ${progress.command}...`; break; default: message = 'Processing...'; } tagRenderer.updateProgress(message); } } function handleSessionOutput(data) { // Handle output for sessions view if (currentSession && data.sessionId === currentSession.id) { appendOutput(data.data); } // Handle output for chat view if (typeof attachedSessionId !== 'undefined' && data.sessionId === attachedSessionId) { // Hide streaming indicator if (typeof hideStreamingIndicator === 'function') { hideStreamingIndicator(); } // Append output as assistant message if (typeof appendMessage === 'function') { appendMessage('assistant', data.data.content, true); } } } // Dashboard async function loadDashboard() { try { // Load stats const [sessionsRes, projectsRes] = await Promise.all([ fetch('/claude/api/claude/sessions'), fetch('/claude/api/claude/projects') ]); const sessionsData = await sessionsRes.json(); const projectsData = await projectsRes.json(); // Update stats document.getElementById('active-sessions-count').textContent = sessionsData.active?.length || 0; document.getElementById('historical-sessions-count').textContent = sessionsData.historical?.length || 0; document.getElementById('total-projects-count').textContent = projectsData.projects?.length || 0; // Update active sessions list const activeSessionsEl = document.getElementById('active-sessions-list'); if (sessionsData.active && sessionsData.active.length > 0) { activeSessionsEl.innerHTML = sessionsData.active.map(session => `
${session.id.substring(0, 20)}... ${session.status}
Working: ${session.workingDir}
Created: ${new Date(session.createdAt).toLocaleString()}
`).join(''); } else { activeSessionsEl.innerHTML = '

No active sessions

'; } // Update projects list const projectsEl = document.getElementById('recent-projects-list'); if (projectsData.projects && projectsData.projects.length > 0) { projectsEl.innerHTML = projectsData.projects.slice(0, 5).map(project => `

${project.name}

Modified: ${new Date(project.modified).toLocaleDateString()}

`).join(''); } else { projectsEl.innerHTML = '

No projects yet

'; } } catch (error) { console.error('Error loading dashboard:', error); } } function refreshSessions() { loadDashboard(); } // Sessions async function loadSessions() { try { const res = await fetch('/claude/api/claude/sessions'); const data = await res.json(); const sessionsListEl = document.getElementById('sessions-list'); const allSessions = [ ...(data.active || []), ...(data.historical || []) ]; if (allSessions.length > 0) { sessionsListEl.innerHTML = allSessions.map(session => `
${session.id.substring(0, 20)}... ${session.status}
${session.workingDir}
${new Date(session.createdAt).toLocaleString()}
`).join(''); } else { sessionsListEl.innerHTML = '

No sessions

'; } } catch (error) { console.error('Error loading sessions:', error); } } async function viewSession(sessionId) { try { const res = await fetch(`/claude/api/claude/sessions/${sessionId}`); const data = await res.json(); currentSession = data.session; const detailEl = document.getElementById('session-detail'); detailEl.innerHTML = `

${data.session.id}

Status: ${data.session.status}

PID: ${data.session.pid || 'N/A'}

Working Directory: ${data.session.workingDir}

Created: ${new Date(data.session.createdAt).toLocaleString()}

Context Usage

${data.session.context.totalTokens.toLocaleString()} tokens ${Math.round(data.session.context.totalTokens / data.session.context.maxTokens * 100)}% used

Session Output

${data.session.outputBuffer.map(entry => `
${escapeHtml(entry.content)}
`).join('')}
${data.session.status === 'running' ? `
` : ''} `; // Switch to sessions view switchView('sessions'); } catch (error) { console.error('Error viewing session:', error); alert('Failed to load session'); } } function handleCommandKeypress(event) { if (event.key === 'Enter') { sendCommand(); } } async function sendCommand() { const input = document.getElementById('command-input'); const command = input.value.trim(); if (!command || !currentSession) return; try { await fetch(`/claude/api/claude/sessions/${currentSession.id}/command`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ command }) }); input.value = ''; // Append command to output appendOutput({ type: 'command', content: `$ ${command}\n` }); } catch (error) { console.error('Error sending command:', error); alert('Failed to send command'); } } function appendOutput(data) { const outputEl = document.getElementById('session-output'); if (outputEl) { const line = document.createElement('div'); line.className = `output-line ${data.type}`; line.textContent = data.content; outputEl.appendChild(line); outputEl.scrollTop = outputEl.scrollHeight; } } // Projects async function loadProjects() { try { const res = await fetch('/claude/api/claude/projects'); const data = await res.json(); const gridEl = document.getElementById('projects-grid'); if (data.projects && data.projects.length > 0) { gridEl.innerHTML = data.projects.map(project => `

${project.name}

Modified: ${new Date(project.modified).toLocaleString()}

Click to view project details

`).join(''); } else { gridEl.innerHTML = '

No projects yet. Create your first project!

'; } } catch (error) { console.error('Error loading projects:', error); } } async function viewProject(projectName) { // Open the project file in the files view const path = `Claude Projects/${projectName}.md`; loadFileContent(path); switchView('files'); } // Files async function loadFiles() { try { const res = await fetch('/claude/api/files'); const data = await res.json(); const treeEl = document.getElementById('file-tree'); treeEl.innerHTML = renderFileTree(data.tree); } catch (error) { console.error('Error loading files:', error); } } async function loadTerminal() { // Initialize terminal manager if not already done if (!window.terminalManager) { window.terminalManager = new TerminalManager(); await window.terminalManager.initialize(); } // Set up new terminal button const btnNewTerminal = document.getElementById('btn-new-terminal'); if (btnNewTerminal) { btnNewTerminal.onclick = () => { window.terminalManager.createTerminal(); }; } } function renderFileTree(tree, level = 0) { return tree.map(item => { const padding = level * 1 + 0.5; const icon = item.type === 'folder' ? '📁' : '📄'; if (item.type === 'folder' && item.children) { return `
${icon} ${item.name}
`; } else { return `
${icon} ${item.name}
`; } }).join(''); } function toggleFolder(element) { const children = element.parentElement.querySelector('.tree-children'); const icon = element.querySelector('span:first-child'); if (children.style.display === 'none') { children.style.display = 'block'; icon.textContent = '📂'; } else { children.style.display = 'none'; icon.textContent = '📁'; } } async function loadFile(filePath) { try { const res = await fetch(`/claude/api/file/${encodeURIComponent(filePath)}`); const data = await res.json(); const isHtmlFile = filePath.toLowerCase().endsWith('.html') || filePath.toLowerCase().endsWith('.htm'); const editorEl = document.getElementById('file-editor'); if (isHtmlFile) { // HTML file - show with preview option editorEl.innerHTML = `

${filePath}

${escapeHtml(data.content)}
`; // Store file content for preview window.currentFileContent = data.content; window.currentFilePath = filePath; // Highlight code if (window.hljs) { document.querySelectorAll('#file-content-view pre code').forEach((block) => { hljs.highlightElement(block); }); } } else { // Non-HTML file - show as before editorEl.innerHTML = `

${filePath}

${data.html}
`; } } catch (error) { console.error('Error loading file:', error); } } async function loadFileContent(filePath) { await loadFile(filePath); switchView('files'); } // HTML Preview Functions function showHtmlPreview(filePath) { switchFileView('preview'); } function switchFileView(view) { const codeView = document.querySelector('.code-view'); const previewView = document.querySelector('.preview-view'); const toggleBtns = document.querySelectorAll('.toggle-btn'); // Update buttons toggleBtns.forEach(btn => { btn.classList.remove('active'); if (btn.dataset.view === view) { btn.classList.add('active'); } }); // Show/hide views if (view === 'code') { codeView.style.display = 'block'; previewView.style.display = 'none'; } else if (view === 'preview') { codeView.style.display = 'none'; previewView.style.display = 'block'; // Load HTML into iframe using blob URL const iframe = document.getElementById('html-preview-frame'); if (iframe && window.currentFileContent) { // Create blob URL from HTML content const blob = new Blob([window.currentFileContent], { type: 'text/html' }); const blobUrl = URL.createObjectURL(blob); // Load blob URL in iframe iframe.src = blobUrl; // Clean up blob URL when iframe is unloaded iframe.onload = () => { // Keep the blob URL active while preview is shown }; } } } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Modals function createNewSession() { document.getElementById('modal-overlay').classList.remove('hidden'); document.getElementById('new-session-modal').classList.remove('hidden'); } function createNewProject() { document.getElementById('modal-overlay').classList.remove('hidden'); document.getElementById('new-project-modal').classList.remove('hidden'); } function closeModal() { document.getElementById('modal-overlay').classList.add('hidden'); document.querySelectorAll('.modal').forEach(modal => { modal.classList.add('hidden'); }); } async function submitNewSession() { const workingDir = document.getElementById('session-working-dir').value; const project = document.getElementById('session-project').value; try { const res = await fetch('/claude/api/claude/sessions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ workingDir, metadata: { project } }) }); const data = await res.json(); if (data.success) { closeModal(); viewSession(data.session.id); } } catch (error) { console.error('Error creating session:', error); alert('Failed to create session'); } } async function submitNewProject() { const name = document.getElementById('project-name').value; const description = document.getElementById('project-description').value; const type = document.getElementById('project-type').value; if (!name) { alert('Please enter a project name'); return; } try { const res = await fetch('/claude/api/claude/projects', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, description, type }) }); const data = await res.json(); if (data.success) { closeModal(); loadProjects(); viewProject(name); } } catch (error) { console.error('Error creating project:', error); alert('Failed to create project'); } } // Utility functions function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } /** * Show toast notification * @param {string} message - The message to display * @param {string} type - The type of toast: 'success', 'error', 'info' * @param {number} duration - Duration in milliseconds (default: 3000) */ function showToast(message, type = 'info', duration = 3000) { // Remove existing toasts const existingToasts = document.querySelectorAll('.toast-notification'); existingToasts.forEach(toast => toast.remove()); // Create toast element const toast = document.createElement('div'); toast.className = `toast-notification toast-${type}`; toast.innerHTML = ` ${getToastIcon(type)} ${escapeHtml(message)} `; document.body.appendChild(toast); // Trigger animation setTimeout(() => { toast.classList.add('visible'); }, 10); // Auto remove after duration setTimeout(() => { toast.classList.remove('visible'); setTimeout(() => { toast.remove(); }, 300); }, duration); } /** * Get toast icon based on type */ function getToastIcon(type) { const icons = { success: '✓', error: '✕', info: 'ℹ', warning: '⚠' }; return icons[type] || icons.info; } function showProjects() { switchView('projects'); } // Logout document.getElementById('logout-btn')?.addEventListener('click', async () => { try { await fetch('/claude/api/logout', { method: 'POST' }); window.location.reload(); } catch (error) { console.error('Error logging out:', error); } });