/** * Live Preview Manager * Manages application preview in an iframe */ class PreviewManager { constructor() { this.previewUrl = null; this.previewServer = null; this.previewPort = null; this.isPreviewRunning = false; } /** * Start preview server */ async startPreview(sessionId, workingDir) { console.log('Starting preview for session:', sessionId); try { const res = await fetch(`/claude/api/claude/sessions/${sessionId}/preview/start`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ workingDir }) }); const data = await res.json(); if (!res.ok) { throw new Error(data.error || 'Failed to start preview'); } this.previewUrl = data.url; this.previewPort = data.port; this.previewServer = data.processId; this.isPreviewRunning = true; console.log('Preview started:', this.previewUrl); // Show preview panel this.showPreviewPanel(this.previewUrl); return data; } catch (error) { console.error('Error starting preview:', error); this.showError(error.message); throw error; } } /** * Stop preview server */ async stopPreview(sessionId) { console.log('Stopping preview for session:', sessionId); try { const res = await fetch(`/claude/api/claude/sessions/${sessionId}/preview/stop`, { method: 'POST' }); const data = await res.json(); if (!res.ok) { throw new Error(data.error || 'Failed to stop preview'); } this.isPreviewRunning = false; this.previewUrl = null; this.previewServer = null; // Hide preview panel this.hidePreviewPanel(); console.log('Preview stopped'); return data; } catch (error) { console.error('Error stopping preview:', error); throw error; } } /** * Refresh preview */ refreshPreview() { if (!this.previewUrl || !this.isPreviewRunning) { console.warn('No preview to refresh'); return; } const iframe = document.getElementById('preview-iframe'); if (iframe) { iframe.src = iframe.src; // Reload iframe console.log('Preview refreshed'); } } /** * Show preview panel */ showPreviewPanel(url) { // Remove existing panel const existing = document.querySelector('.preview-panel'); if (existing) { existing.remove(); } // Create preview panel const previewPanel = document.createElement('div'); previewPanel.className = 'preview-panel'; previewPanel.innerHTML = `

Live Preview

${url}
Preview running on ${this.previewPort || 'local port'}
`; // Add to page - find best place to insert const chatView = document.getElementById('chat-view'); if (chatView) { chatView.appendChild(previewPanel); } previewPanel.style.display = 'block'; } /** * Hide preview panel */ hidePreviewPanel() { const panel = document.querySelector('.preview-panel'); if (panel) { panel.remove(); } } /** * Open preview in new tab */ openInNewTab() { if (this.previewUrl) { window.open(this.previewUrl, '_blank'); } } /** * Show error message */ showError(message) { // Remove existing error const existing = document.querySelector('.preview-error'); if (existing) { existing.remove(); } const error = document.createElement('div'); error.className = 'preview-error'; error.innerHTML = `
⚠️

Preview Error

${message}
`; const chatView = document.getElementById('chat-view'); if (chatView) { chatView.appendChild(error); } error.style.display = 'block'; // Auto-hide after 5 seconds setTimeout(() => { error.remove(); }, 5000); } /** * Update preview status */ updateStatus(status, message) { const statusElement = document.querySelector('.preview-status'); if (statusElement) { const dotClass = status === 'running' ? 'status-running' : 'status-stopped'; statusElement.innerHTML = ` ${message} `; } } } // Global instance const previewManager = new PreviewManager(); // Global function to stop preview window.stopCurrentPreview = async function() { if (window.chatSessionId) { await previewManager.stopPreview(window.chatSessionId); } }; // Export for use in other files if (typeof module !== 'undefined' && module.exports) { module.exports = PreviewManager; }