From 67e6a1ef7a2f73cf175d522b90bd9c906e1c57e9 Mon Sep 17 00:00:00 2001 From: Zylan Date: Wed, 5 Feb 2025 00:51:45 +0800 Subject: [PATCH] m --- static/js/core.js | 603 ------------------------------------------- static/js/events.js | 385 --------------------------- static/js/history.js | 138 ---------- 3 files changed, 1126 deletions(-) delete mode 100644 static/js/core.js delete mode 100644 static/js/events.js delete mode 100644 static/js/history.js diff --git a/static/js/core.js b/static/js/core.js deleted file mode 100644 index 9fdbf84..0000000 --- a/static/js/core.js +++ /dev/null @@ -1,603 +0,0 @@ -class SnapSolver { - constructor() { - // Initialize managers first - window.uiManager = new UIManager(); - window.settingsManager = new SettingsManager(); - - this.initializeElements(); - this.initializeState(); - this.initializeConnection(); - this.setupAutoScroll(); - this.setupEventListeners(); - } - - initializeElements() { - // Capture elements - this.captureBtn = document.getElementById('captureBtn'); - this.cropBtn = document.getElementById('cropBtn'); - this.connectionStatus = document.getElementById('connectionStatus'); - this.screenshotImg = document.getElementById('screenshotImg'); - this.cropContainer = document.getElementById('cropContainer'); - this.imagePreview = document.getElementById('imagePreview'); - this.sendToClaudeBtn = document.getElementById('sendToClaude'); - this.extractTextBtn = document.getElementById('extractText'); - this.textEditor = document.getElementById('textEditor'); - this.extractedText = document.getElementById('extractedText'); - this.sendExtractedTextBtn = document.getElementById('sendExtractedText'); - this.responseContent = document.getElementById('responseContent'); - this.claudePanel = document.getElementById('claudePanel'); - this.statusLight = document.querySelector('.status-light'); - - // Verify all elements are found - const elements = [ - this.captureBtn, this.cropBtn, this.connectionStatus, this.screenshotImg, - this.cropContainer, this.imagePreview, this.sendToClaudeBtn, this.extractTextBtn, - this.textEditor, this.extractedText, this.sendExtractedTextBtn, this.responseContent, - this.claudePanel, this.statusLight - ]; - - elements.forEach((element, index) => { - if (!element) { - console.error(`Failed to initialize element at index ${index}`); - } - }); - } - - initializeState() { - this.socket = null; - this.cropper = null; - this.croppedImage = null; - this.history = JSON.parse(localStorage.getItem('snapHistory') || '[]'); - this.heartbeatInterval = null; - this.connectionCheckInterval = null; - this.isReconnecting = false; - this.lastConnectionAttempt = 0; - } - - resetConnection() { - const now = Date.now(); - const timeSinceLastAttempt = now - this.lastConnectionAttempt; - - // Prevent multiple reset attempts within 2 seconds - if (timeSinceLastAttempt < 2000) { - console.log('Skipping reset - too soon since last attempt'); - return; - } - - console.log('Resetting connection...'); - this.isReconnecting = true; - this.lastConnectionAttempt = now; - - // Clear existing intervals - if (this.heartbeatInterval) { - clearInterval(this.heartbeatInterval); - this.heartbeatInterval = null; - } - - if (this.connectionCheckInterval) { - clearInterval(this.connectionCheckInterval); - this.connectionCheckInterval = null; - } - - // Clean up existing socket - if (this.socket) { - this.socket.removeAllListeners(); - this.socket.disconnect(); - this.socket = null; - } - - // Small delay before reconnecting - setTimeout(() => { - this.initializeConnection(); - this.isReconnecting = false; - }, 100); - } - - startConnectionCheck() { - // Clear any existing interval - if (this.connectionCheckInterval) { - clearInterval(this.connectionCheckInterval); - } - - // Check connection status every 5 seconds - this.connectionCheckInterval = setInterval(() => { - if (!this.isReconnecting && (!this.socket || !this.socket.connected)) { - console.log('Connection check failed, attempting reset...'); - this.resetConnection(); - } - }, 5000); - } - - initializeCropper() { - try { - // Clean up existing cropper if any - if (this.cropper) { - this.cropper.destroy(); - this.cropper = null; - } - - // Show crop container and prepare crop area - this.cropContainer.classList.remove('hidden'); - const cropArea = document.querySelector('.crop-area'); - cropArea.innerHTML = ''; - - // Create a new image element for cropping - const cropImage = document.createElement('img'); - cropImage.src = this.screenshotImg.src; - cropArea.appendChild(cropImage); - - // Initialize Cropper.js - this.cropper = new Cropper(cropImage, { - aspectRatio: NaN, - viewMode: 1, - dragMode: 'move', - autoCropArea: 0.8, - restore: false, - modal: true, - guides: true, - highlight: true, - cropBoxMovable: true, - cropBoxResizable: true, - toggleDragModeOnDblclick: false, - ready: () => { - console.log('Cropper initialized successfully'); - }, - error: (error) => { - console.error('Cropper initialization error:', error); - window.showToast('Failed to initialize image cropper', 'error'); - } - }); - } catch (error) { - console.error('Error initializing cropper:', error); - window.showToast('Failed to initialize image cropper', 'error'); - } - } - - setupAutoScroll() { - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - if (mutation.type === 'characterData' || mutation.type === 'childList') { - this.responseContent.scrollTo({ - top: this.responseContent.scrollHeight, - behavior: 'smooth' - }); - } - }); - }); - - observer.observe(this.responseContent, { - childList: true, - characterData: true, - subtree: true - }); - } - - updateConnectionStatus(connected) { - if (!this.connectionStatus || !this.captureBtn) { - console.error('Required elements not initialized'); - return; - } - - this.connectionStatus.textContent = connected ? 'Connected' : 'Disconnected'; - this.connectionStatus.className = `status ${connected ? 'connected' : 'disconnected'}`; - - // Enable/disable capture button - if (this.captureBtn) { - this.captureBtn.disabled = !connected; - } - - if (!connected) { - // Hide UI elements when disconnected - const elements = [ - this.imagePreview, - this.cropBtn, - this.sendToClaudeBtn, - this.extractTextBtn, - this.textEditor - ]; - - elements.forEach(element => { - if (element) { - element.classList.add('hidden'); - } - }); - } - } - - updateStatusLight(status) { - if (!this.statusLight) return; - - this.statusLight.className = 'status-light'; - switch (status) { - case 'started': - case 'streaming': - this.statusLight.classList.add('processing'); - break; - case 'completed': - this.statusLight.classList.add('completed'); - break; - case 'error': - this.statusLight.classList.add('error'); - break; - default: - break; - } - } - - initializeConnection() { - // Clear any existing heartbeat interval - if (this.heartbeatInterval) { - clearInterval(this.heartbeatInterval); - this.heartbeatInterval = null; - } - - try { - // Clean up existing socket if any - if (this.socket) { - this.socket.disconnect(); - this.socket = null; - } - - console.log('Initializing socket connection...'); - this.socket = io(window.location.origin, { - reconnection: true, - reconnectionAttempts: Infinity, - reconnectionDelay: 100, // Very fast initial reconnection - reconnectionDelayMax: 1000, // Shorter max delay - timeout: 120000, - autoConnect: true, // Enable auto-connect - transports: ['websocket'], - forceNew: true, // Force a new connection on refresh - closeOnBeforeunload: false, // Prevent auto-close on page refresh - reconnectionAttempts: Infinity, // Never stop trying to reconnect - extraHeaders: { - 'X-Client-Version': '1.0' - } - }); - - // Setup heartbeat with monitoring - this.heartbeatInterval = setInterval(() => { - if (this.socket && this.socket.connected) { - const heartbeatTimeout = setTimeout(() => { - console.log('Heartbeat timeout, resetting connection...'); - if (!this.isReconnecting) { - this.resetConnection(); - } - }, 5000); // Wait 5 seconds for heartbeat response - - this.socket.emit('heartbeat'); - - // Clear timeout when heartbeat is acknowledged - this.socket.once('heartbeat_response', () => { - clearTimeout(heartbeatTimeout); - }); - } - }, 10000); - - this.socket.on('connect', () => { - console.log('Connected to server'); - this.updateConnectionStatus(true); - // Re-enable capture button on reconnection - if (this.captureBtn) { - this.captureBtn.disabled = false; - } - // Start connection check after successful connection - this.startConnectionCheck(); - }); - - this.socket.on('disconnect', (reason) => { - console.log('Disconnected from server:', reason); - this.updateConnectionStatus(false); - - // Always attempt to reconnect regardless of reason - console.log('Attempting reconnection...'); - if (!this.socket.connected && !this.isReconnecting) { - this.resetConnection(); - } - - // Clean up resources but maintain reconnection ability - if (this.heartbeatInterval) { - clearInterval(this.heartbeatInterval); - this.heartbeatInterval = null; - } - }); - - // Add reconnecting event handler - this.socket.on('reconnecting', (attemptNumber) => { - console.log(`Reconnection attempt ${attemptNumber}...`); - if (!this.isReconnecting) { - this.resetConnection(); - } - }); - - // Add reconnect_failed event handler - this.socket.on('reconnect_failed', () => { - console.log('Reconnection failed, trying again...'); - if (!this.isReconnecting) { - this.resetConnection(); - } - }); - - this.socket.on('connect_error', (error) => { - console.error('Connection error:', error); - this.updateConnectionStatus(false); - window.showToast('Connection error: ' + error.message, 'error'); - - // Enhanced exponential backoff with jitter - const attempts = this.socket.io.backoff?.attempts || 0; - const baseDelay = 1000; - const maxDelay = 10000; - const jitter = Math.random() * 1000; - const delay = Math.min(baseDelay * Math.pow(1.5, attempts) + jitter, maxDelay); - - console.log(`Scheduling reconnection attempt in ${Math.round(delay)}ms...`); - setTimeout(() => { - if (!this.socket.connected) { - console.log(`Attempting to reconnect (attempt ${attempts + 1})...`); - this.socket.connect(); - } - }, delay); - }); - - this.socket.on('heartbeat_response', () => { - console.debug('Heartbeat acknowledged'); - // Reset connection if we were in a disconnected state - if (this.connectionStatus && this.connectionStatus.textContent === 'Disconnected' && !this.isReconnecting) { - this.resetConnection(); - } - }); - - this.socket.on('error', (error) => { - console.error('Socket error:', error); - window.showToast('Socket error occurred', 'error'); - }); - - this.setupSocketEventHandlers(); - - } catch (error) { - console.error('Connection initialization error:', error); - this.updateConnectionStatus(false); - } - } - - setupSocketEventHandlers() { - if (!this.socket) { - console.error('Socket not initialized'); - return; - } - - // Screenshot response handler - this.socket.on('screenshot_response', (data) => { - if (data.success) { - this.screenshotImg.src = `data:image/png;base64,${data.image}`; - this.imagePreview.classList.remove('hidden'); - this.cropBtn.classList.remove('hidden'); - this.captureBtn.disabled = false; - this.captureBtn.innerHTML = 'Capture'; - this.sendToClaudeBtn.classList.add('hidden'); - this.extractTextBtn.classList.add('hidden'); - this.textEditor.classList.add('hidden'); - window.showToast('Screenshot captured successfully'); - } else { - window.showToast('Failed to capture screenshot: ' + data.error, 'error'); - this.captureBtn.disabled = false; - this.captureBtn.innerHTML = 'Capture'; - } - }); - - // Mathpix text extraction response handler - this.socket.on('mathpix_response', (data) => { - console.log('Received mathpix_response:', data); - this.updateStatusLight(data.status); - - switch (data.status) { - case 'started': - console.log('Text extraction started'); - this.extractedText.value = ''; - this.extractTextBtn.disabled = true; - break; - - case 'completed': - if (data.content) { - console.log('Received extracted text:', data.content); - const confidenceMatch = data.content.match(/Confidence: (\d+\.\d+)%/); - if (confidenceMatch) { - const confidence = confidenceMatch[1]; - document.getElementById('confidenceDisplay').textContent = confidence + '%'; - this.extractedText.value = data.content.replace(/Confidence: \d+\.\d+%\n\n/, ''); - } else { - this.extractedText.value = data.content; - document.getElementById('confidenceDisplay').textContent = ''; - } - this.textEditor.classList.remove('hidden'); - } - this.extractTextBtn.disabled = false; - this.extractTextBtn.innerHTML = 'Extract Text'; - window.showToast('Text extracted successfully'); - break; - - case 'error': - console.error('Text extraction error:', data.error); - const errorMessage = data.error || 'Unknown error occurred'; - window.showToast('Failed to extract text: ' + errorMessage, 'error'); - this.extractTextBtn.disabled = false; - this.extractTextBtn.innerHTML = 'Extract Text'; - break; - - default: - console.warn('Unknown mathpix response status:', data.status); - if (data.error) { - window.showToast('Text extraction failed: ' + data.error, 'error'); - this.extractTextBtn.disabled = false; - this.extractTextBtn.innerHTML = 'Extract Text'; - } - } - }); - - // AI analysis response handler - this.socket.on('claude_response', (data) => { - console.log('Received claude_response:', data); - this.updateStatusLight(data.status); - - switch (data.status) { - case 'started': - console.log('AI analysis started'); - this.responseContent.textContent = ''; - this.sendToClaudeBtn.disabled = true; - this.sendExtractedTextBtn.disabled = true; - break; - - case 'streaming': - if (data.content) { - console.log('Received AI content:', data.content); - this.responseContent.textContent += data.content; - } - break; - - case 'completed': - if (data.content) { - console.log('Received final AI content:', data.content); - this.responseContent.textContent += data.content; - } - this.sendToClaudeBtn.disabled = false; - this.sendExtractedTextBtn.disabled = false; - this.addToHistory(this.croppedImage, this.responseContent.textContent); - window.showToast('Analysis completed successfully'); - break; - - case 'error': - console.error('AI analysis error:', data.error); - const errorMessage = data.error || 'Unknown error occurred'; - this.responseContent.textContent += '\nError: ' + errorMessage; - this.sendToClaudeBtn.disabled = false; - this.sendExtractedTextBtn.disabled = false; - window.showToast('Analysis failed: ' + errorMessage, 'error'); - break; - - default: - console.warn('Unknown claude response status:', data.status); - if (data.error) { - this.responseContent.textContent += '\nError: ' + data.error; - this.sendToClaudeBtn.disabled = false; - this.sendExtractedTextBtn.disabled = false; - window.showToast('Unknown error occurred', 'error'); - } - } - }); - } - - setupEventListeners() { - // Add click handler for app title - const appTitle = document.getElementById('appTitle'); - if (appTitle) { - appTitle.addEventListener('click', () => { - this.resetInterface(); - }); - } - - // Handle page visibility changes - document.addEventListener('visibilitychange', () => { - if (document.visibilityState === 'visible') { - console.log('Page became visible, checking connection...'); - // Check connection status and reset if needed - if (this.socket && !this.socket.connected && !this.isReconnecting) { - console.log('Connection lost while page was hidden, resetting...'); - this.resetConnection(); - } - } - }); - - // Handle before unload to clean up properly - window.addEventListener('beforeunload', () => { - if (this.socket) { - console.log('Page unloading, cleaning up socket...'); - // Store connection state in sessionStorage - sessionStorage.setItem('wasConnected', 'true'); - this.socket.disconnect(); - } - }); - - // Check if we need to reconnect after a page reload - if (sessionStorage.getItem('wasConnected') === 'true') { - console.log('Page reloaded, initiating immediate reconnection...'); - sessionStorage.removeItem('wasConnected'); - // Force an immediate connection attempt - setTimeout(() => { - if (!this.socket?.connected && !this.isReconnecting) { - this.resetConnection(); - } - }, 500); - } - - this.setupCaptureEvents(); - this.setupCropEvents(); - this.setupAnalysisEvents(); - this.setupKeyboardShortcuts(); - } - - resetInterface() { - if (!this.captureBtn) return; - - // Clear all intervals - if (this.heartbeatInterval) { - clearInterval(this.heartbeatInterval); - this.heartbeatInterval = null; - } - - if (this.connectionCheckInterval) { - clearInterval(this.connectionCheckInterval); - this.connectionCheckInterval = null; - } - - // Clean up cropper if it exists - if (this.cropper) { - this.cropper.destroy(); - this.cropper = null; - } - - // Clean up socket if it exists - if (this.socket) { - this.socket.removeAllListeners(); // Remove all event listeners - this.socket.disconnect(); - this.socket = null; - } - - // Show capture button - this.captureBtn.classList.remove('hidden'); - - // Hide all panels - const panels = ['historyPanel', 'settingsPanel']; - panels.forEach(panelId => { - const panel = document.getElementById(panelId); - if (panel) panel.classList.add('hidden'); - }); - - // Reset image preview and related buttons - const elements = [ - this.imagePreview, - this.cropBtn, - this.sendToClaudeBtn, - this.extractTextBtn, - this.textEditor - ]; - - elements.forEach(element => { - if (element) element.classList.add('hidden'); - }); - - // Clear text areas - if (this.extractedText) this.extractedText.value = ''; - if (this.responseContent) this.responseContent.textContent = ''; - - const confidenceDisplay = document.getElementById('confidenceDisplay'); - if (confidenceDisplay) confidenceDisplay.textContent = ''; - - // Hide Claude panel - if (this.claudePanel) this.claudePanel.classList.add('hidden'); - } -} - -// Initialize the application -window.addEventListener('DOMContentLoaded', () => { - window.app = new SnapSolver(); -}); diff --git a/static/js/events.js b/static/js/events.js deleted file mode 100644 index 6d9e644..0000000 --- a/static/js/events.js +++ /dev/null @@ -1,385 +0,0 @@ -// Events handling extension for SnapSolver class -Object.assign(SnapSolver.prototype, { - setupCaptureEvents() { - if (!this.captureBtn) { - console.error('Capture button not initialized'); - return; - } - - // Capture button - this.captureBtn.addEventListener('click', async () => { - if (!this.socket) { - console.error('Socket not initialized'); - window.showToast('Connection not initialized. Please refresh the page.', 'error'); - return; - } - - if (!this.socket.connected) { - console.error('Socket not connected'); - window.showToast('Server connection lost. Attempting to reconnect...', 'error'); - this.socket.connect(); - return; - } - - try { - this.captureBtn.disabled = true; - this.captureBtn.innerHTML = 'Capturing...'; - - // Set a timeout to re-enable the button if no response is received - const timeout = setTimeout(() => { - if (this.captureBtn.disabled) { - this.captureBtn.disabled = false; - this.captureBtn.innerHTML = 'Capture'; - window.showToast('Screenshot capture timed out. Please try again.', 'error'); - } - }, 10000); - - this.socket.emit('request_screenshot', null, (error) => { - if (error) { - clearTimeout(timeout); - console.error('Screenshot error:', error); - window.showToast('Error capturing screenshot: ' + error, 'error'); - this.captureBtn.disabled = false; - this.captureBtn.innerHTML = 'Capture'; - } - }); - } catch (error) { - console.error('Capture error:', error); - window.showToast('Error requesting screenshot: ' + error.message, 'error'); - this.captureBtn.disabled = false; - this.captureBtn.innerHTML = 'Capture'; - } - }); - }, - - setupCropEvents() { - if (!this.cropBtn || !this.screenshotImg) { - console.error('Required elements not initialized'); - return; - } - - // Crop button - this.cropBtn.addEventListener('click', () => { - if (this.screenshotImg.src) { - this.initializeCropper(); - } - }); - - // Crop confirm button - const cropConfirm = document.getElementById('cropConfirm'); - if (cropConfirm) { - cropConfirm.addEventListener('click', () => { - if (this.cropper) { - try { - console.log('Starting crop operation...'); - - if (!this.cropper) { - throw new Error('Cropper not initialized'); - } - - const cropBoxData = this.cropper.getCropBoxData(); - console.log('Crop box data:', cropBoxData); - - if (!cropBoxData || typeof cropBoxData.width !== 'number' || typeof cropBoxData.height !== 'number') { - throw new Error('Invalid crop box data'); - } - - if (cropBoxData.width < 10 || cropBoxData.height < 10) { - throw new Error('Crop area is too small. Please select a larger area (minimum 10x10 pixels).'); - } - - console.log('Getting cropped canvas...'); - const canvas = this.cropper.getCroppedCanvas({ - maxWidth: 2560, - maxHeight: 1440, - fillColor: '#fff', - imageSmoothingEnabled: true, - imageSmoothingQuality: 'high', - }); - - if (!canvas) { - throw new Error('Failed to create cropped canvas'); - } - - console.log('Canvas created successfully'); - - console.log('Converting to data URL...'); - try { - this.croppedImage = canvas.toDataURL('image/png'); - console.log('Data URL conversion successful'); - } catch (dataUrlError) { - console.error('Data URL conversion error:', dataUrlError); - throw new Error('Failed to process cropped image. The image might be too large or memory insufficient.'); - } - - if (this.cropper) { - this.cropper.destroy(); - this.cropper = null; - } - - this.cropContainer.classList.add('hidden'); - const cropArea = document.querySelector('.crop-area'); - if (cropArea) cropArea.innerHTML = ''; - - this.screenshotImg.src = this.croppedImage; - this.imagePreview.classList.remove('hidden'); - this.cropBtn.classList.remove('hidden'); - this.sendToClaudeBtn.classList.remove('hidden'); - this.extractTextBtn.classList.remove('hidden'); - window.showToast('Image cropped successfully'); - } catch (error) { - console.error('Cropping error details:', { - message: error.message, - stack: error.stack, - cropperState: this.cropper ? 'initialized' : 'not initialized' - }); - window.showToast(error.message || 'Error while cropping image', 'error'); - } finally { - if (this.cropper) { - this.cropper.destroy(); - this.cropper = null; - } - } - } - }); - } - - // Crop cancel button - const cropCancel = document.getElementById('cropCancel'); - if (cropCancel) { - cropCancel.addEventListener('click', () => { - if (this.cropper) { - this.cropper.destroy(); - this.cropper = null; - } - this.cropContainer.classList.add('hidden'); - this.sendToClaudeBtn.classList.add('hidden'); - this.extractTextBtn.classList.add('hidden'); - const cropArea = document.querySelector('.crop-area'); - if (cropArea) cropArea.innerHTML = ''; - }); - } - }, - - setupAnalysisEvents() { - // Set up text extraction socket event listener once - this.socket.on('text_extracted', (data) => { - if (data.error) { - console.error('Text extraction error:', data.error); - window.showToast('Failed to extract text: ' + data.error, 'error'); - if (this.extractedText) { - this.extractedText.value = ''; - this.extractedText.disabled = false; - } - } else if (data.content) { - if (this.extractedText) { - this.extractedText.value = data.content; - this.extractedText.disabled = false; - // Scroll to make text editor visible - this.extractedText.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); - } - window.showToast('Text extracted successfully'); - } - - if (this.extractTextBtn) { - this.extractTextBtn.disabled = false; - this.extractTextBtn.innerHTML = 'Extract Text'; - } - }); - - // Extract Text button - if (this.extractTextBtn) { - this.extractTextBtn.addEventListener('click', () => { - if (!this.croppedImage) { - window.showToast('Please crop the image first', 'error'); - return; - } - - const settings = window.settingsManager.getSettings(); - const mathpixAppId = settings.mathpixAppId; - const mathpixAppKey = settings.mathpixAppKey; - - if (!mathpixAppId || !mathpixAppKey) { - window.showToast('Please enter Mathpix credentials in settings', 'error'); - const settingsPanel = document.getElementById('settingsPanel'); - if (settingsPanel) settingsPanel.classList.remove('hidden'); - return; - } - - this.extractTextBtn.disabled = true; - this.extractTextBtn.innerHTML = 'Extracting...'; - - try { - // Show text editor and prepare UI - const textEditor = document.getElementById('textEditor'); - if (textEditor) { - textEditor.classList.remove('hidden'); - // Scroll to make text editor visible - textEditor.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); - } - - // Clear any previous text and show loading indicator - if (this.extractedText) { - this.extractedText.value = 'Extracting text...'; - this.extractedText.disabled = true; - } - - // Set up timeout to re-enable button if no response - const timeout = setTimeout(() => { - if (this.extractTextBtn && this.extractTextBtn.disabled) { - this.extractTextBtn.disabled = false; - this.extractTextBtn.innerHTML = 'Extract Text'; - if (this.extractedText) { - this.extractedText.value = ''; - this.extractedText.disabled = false; - } - window.showToast('Text extraction timed out. Please try again.', 'error'); - } - }, 30000); // 30 second timeout - - this.socket.emit('extract_text', { - image: this.croppedImage.split(',')[1], - settings: { - mathpixApiKey: `${mathpixAppId}:${mathpixAppKey}` - } - }, (error) => { - // Clear timeout on acknowledgement - clearTimeout(timeout); - if (error) { - console.error('Text extraction error:', error); - window.showToast('Failed to start text extraction: ' + error, 'error'); - this.extractTextBtn.disabled = false; - this.extractTextBtn.innerHTML = 'Extract Text'; - if (this.extractedText) { - this.extractedText.value = ''; - this.extractedText.disabled = false; - } - } - }); - } catch (error) { - console.error('Text extraction error:', error); - window.showToast('Failed to extract text: ' + error.message, 'error'); - this.extractTextBtn.disabled = false; - this.extractTextBtn.innerHTML = 'Extract Text'; - if (this.extractedText) { - this.extractedText.value = ''; - this.extractedText.disabled = false; - } - } - }); - } - - // Send Extracted Text button - if (this.sendExtractedTextBtn && this.extractedText) { - this.sendExtractedTextBtn.addEventListener('click', () => { - const text = this.extractedText.value.trim(); - if (!text) { - window.showToast('Please enter some text', 'error'); - return; - } - - const settings = window.settingsManager.getSettings(); - const apiKey = window.settingsManager.getApiKey(); - - if (!apiKey) { - const settingsPanel = document.getElementById('settingsPanel'); - if (settingsPanel) settingsPanel.classList.remove('hidden'); - window.showToast('Please configure API key in settings', 'error'); - return; - } - - if (this.claudePanel) this.claudePanel.classList.remove('hidden'); - if (this.responseContent) this.responseContent.textContent = ''; - this.sendExtractedTextBtn.disabled = true; - - try { - this.socket.emit('analyze_text', { - text: text, - settings: { - apiKey: apiKey, - model: settings.model || 'claude-3-5-sonnet-20241022', - temperature: parseFloat(settings.temperature) || 0.7, - systemPrompt: settings.systemPrompt || 'You are an expert at analyzing questions and providing detailed solutions.', - proxyEnabled: settings.proxyEnabled || false, - proxyHost: settings.proxyHost || '127.0.0.1', - proxyPort: settings.proxyPort || '4780' - } - }); - } catch (error) { - console.error('Text analysis error:', error); - if (this.responseContent) { - this.responseContent.textContent = 'Error: Failed to send text for analysis - ' + error.message; - } - this.sendExtractedTextBtn.disabled = false; - window.showToast('Failed to send text for analysis', 'error'); - } - }); - } - - // Send to Claude button - if (this.sendToClaudeBtn) { - this.sendToClaudeBtn.addEventListener('click', () => { - if (!this.croppedImage) { - window.showToast('Please crop the image first', 'error'); - return; - } - - const settings = window.settingsManager.getSettings(); - const apiKey = window.settingsManager.getApiKey(); - - if (!apiKey) { - const settingsPanel = document.getElementById('settingsPanel'); - if (settingsPanel) settingsPanel.classList.remove('hidden'); - window.showToast('Please configure API key in settings', 'error'); - return; - } - - if (this.claudePanel) this.claudePanel.classList.remove('hidden'); - if (this.responseContent) this.responseContent.textContent = ''; - this.sendToClaudeBtn.disabled = true; - - try { - this.socket.emit('analyze_image', { - image: this.croppedImage.split(',')[1], - settings: { - apiKey: apiKey, - model: settings.model || 'claude-3-5-sonnet-20241022', - temperature: parseFloat(settings.temperature) || 0.7, - systemPrompt: settings.systemPrompt || 'You are an expert at analyzing questions and providing detailed solutions.', - proxyEnabled: settings.proxyEnabled || false, - proxyHost: settings.proxyHost || '127.0.0.1', - proxyPort: settings.proxyPort || '4780' - } - }); - } catch (error) { - console.error('Image analysis error:', error); - if (this.responseContent) { - this.responseContent.textContent = 'Error: Failed to send image for analysis - ' + error.message; - } - this.sendToClaudeBtn.disabled = false; - window.showToast('Failed to send image for analysis', 'error'); - } - }); - } - }, - - setupKeyboardShortcuts() { - // Keyboard shortcuts for capture and crop - document.addEventListener('keydown', (e) => { - if (e.ctrlKey || e.metaKey) { - switch(e.key) { - case 'c': - if (this.captureBtn && !this.captureBtn.disabled) { - this.captureBtn.click(); - } - break; - case 'x': - if (this.cropBtn && !this.cropBtn.disabled) { - this.cropBtn.click(); - } - break; - } - } - }); - } -}); diff --git a/static/js/history.js b/static/js/history.js deleted file mode 100644 index c1ae63c..0000000 --- a/static/js/history.js +++ /dev/null @@ -1,138 +0,0 @@ -// History management extension for SnapSolver class -Object.assign(SnapSolver.prototype, { - addToHistory(imageData, response) { - const historyItem = { - id: Date.now(), - timestamp: new Date().toISOString(), - image: imageData, - extractedText: this.extractedText.value || null, - response: response - }; - this.history.unshift(historyItem); - if (this.history.length > 10) this.history.pop(); - localStorage.setItem('snapHistory', JSON.stringify(this.history)); - window.renderHistory(); - } -}); - -// Global function for history rendering -window.renderHistory = function() { - const content = document.querySelector('.history-content'); - const history = JSON.parse(localStorage.getItem('snapHistory') || '[]'); - - if (history.length === 0) { - content.innerHTML = ` -
- -

No history yet

-
- `; - return; - } - - content.innerHTML = history.map(item => ` -
-
- ${new Date(item.timestamp).toLocaleString()} - -
- Historical screenshot -
- `).join(''); - - // Add click handlers for history items - content.querySelectorAll('.delete-history').forEach(btn => { - btn.addEventListener('click', (e) => { - e.stopPropagation(); - const id = parseInt(btn.dataset.id); - const updatedHistory = history.filter(item => item.id !== id); - localStorage.setItem('snapHistory', JSON.stringify(updatedHistory)); - window.renderHistory(); - window.showToast('History item deleted'); - }); - }); - - content.querySelectorAll('.history-item').forEach(item => { - item.addEventListener('click', () => { - const historyItem = history.find(h => h.id === parseInt(item.dataset.id)); - if (historyItem) { - // Store current state before entering history view - const previousState = { - textEditorVisible: !window.app.textEditor.classList.contains('hidden'), - textFormatControlsVisible: !document.querySelector('.text-format-controls').classList.contains('hidden'), - extractedTextValue: window.app.extractedText.value - }; - - // Add history view class and display the image - document.body.classList.add('history-view'); - window.app.screenshotImg.src = historyItem.image; - window.app.imagePreview.classList.remove('hidden'); - document.getElementById('historyPanel').classList.add('hidden'); - - // Force hide all unwanted elements - const elementsToHide = [ - window.app.cropBtn, - window.app.captureBtn, - window.app.sendToClaudeBtn, - window.app.extractTextBtn, - window.app.sendExtractedTextBtn, - window.app.textEditor, - document.querySelector('.text-format-controls'), - document.getElementById('confidenceIndicator'), - document.querySelector('.analysis-button .button-group') - ]; - - elementsToHide.forEach(element => { - if (element) { - element.classList.add('hidden'); - element.style.display = 'none'; - } - }); - - // Clear text and confidence display - window.app.extractedText.value = ''; - document.getElementById('confidenceDisplay').textContent = ''; - - // Show response if it exists - if (historyItem.response) { - window.app.claudePanel.classList.remove('hidden'); - window.app.responseContent.textContent = historyItem.response; - } - - // Add click handler to close history view and restore previous state - const closeHandler = () => { - // Remove history view class - document.body.classList.remove('history-view'); - - // Hide preview panels - window.app.imagePreview.classList.add('hidden'); - window.app.claudePanel.classList.add('hidden'); - - // Restore previous state - if (previousState.textEditorVisible) { - window.app.textEditor.classList.remove('hidden'); - window.app.textEditor.style.display = ''; - } - if (previousState.textFormatControlsVisible) { - document.querySelector('.text-format-controls').classList.remove('hidden'); - document.querySelector('.text-format-controls').style.display = ''; - } - window.app.extractedText.value = previousState.extractedTextValue; - - // Remove event listener - document.removeEventListener('click', closeHandler); - }; - - // Close history view when clicking outside the image or response - document.addEventListener('click', (e) => { - if (!window.app.imagePreview.contains(e.target) && - !window.app.claudePanel.contains(e.target)) { - closeHandler(); - } - }); - } - }); - }); -};