diff --git a/app.py b/app.py index 559dbe1..8d989df 100644 --- a/app.py +++ b/app.py @@ -10,7 +10,7 @@ from PIL import Image, ImageDraw import pyperclip from models import ModelFactory app = Flask(__name__) -socketio = SocketIO(app, cors_allowed_origins="*") +socketio = SocketIO(app, cors_allowed_origins="*", ping_timeout=30, ping_interval=5, max_http_buffer_size=50 * 1024 * 1024) # Commented out due to model file issues # from pix2text import Pix2Text @@ -188,6 +188,11 @@ def handle_text_extraction(data): image_data = data['image'] if not isinstance(image_data, str): raise ValueError("Invalid image data format") + + # 检查图像大小,避免处理过大的图像导致断开连接 + image_size_bytes = len(image_data) * 3 / 4 # 估算base64的实际大小 + if image_size_bytes > 10 * 1024 * 1024: # 10MB + raise ValueError("Image too large, please crop to a smaller area") settings = data.get('settings', {}) if not isinstance(settings, dict): @@ -197,6 +202,13 @@ def handle_text_extraction(data): if not mathpix_key: raise ValueError("Mathpix API key is required") + # 先回复客户端,确认已收到请求,防止超时断开 + # 注意:这里不能使用return,否则后续代码不会执行 + socketio.emit('request_acknowledged', { + 'status': 'received', + 'message': 'Image received, text extraction in progress' + }, room=request.sid) + try: app_id, app_key = mathpix_key.split(':') if not app_id.strip() or not app_key.strip(): @@ -298,6 +310,11 @@ def handle_analyze_image(data): if not image_data: raise ValueError("No image data provided") + # 检查图像大小,避免处理过大的图像导致断开连接 + image_size_bytes = len(image_data) * 3 / 4 # 估算base64的实际大小 + if image_size_bytes > 10 * 1024 * 1024: # 10MB + raise ValueError("Image too large, please crop to a smaller area or use text extraction") + settings = data.get('settings', {}) # 不需要分割了,因为前端已经做了分割 @@ -327,6 +344,13 @@ def handle_analyze_image(data): } try: + # 先回复客户端,确认已收到请求,防止超时断开 + # 注意:这里不能使用return,否则后续代码不会执行 + socketio.emit('request_acknowledged', { + 'status': 'received', + 'message': 'Image received, analysis in progress' + }, room=request.sid) + # Create model instance using factory model = ModelFactory.create_model( model_name=model_name, diff --git a/static/js/main.js b/static/js/main.js index 1898813..5ed5651 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -61,6 +61,7 @@ class SnapSolver { this.croppedImage = null; this.history = JSON.parse(localStorage.getItem('snapHistory') || '[]'); this.currentFormat = 'text'; + this.emitTimeout = null; this.extractedFormats = { text: '', latex: '' @@ -134,7 +135,14 @@ class SnapSolver { initializeConnection() { try { - this.socket = io(window.location.origin); + this.socket = io(window.location.origin, { + reconnection: true, + reconnectionAttempts: 5, + reconnectionDelay: 1000, + reconnectionDelayMax: 5000, + timeout: 20000, + autoConnect: true + }); this.socket.on('connect', () => { console.log('Connected to server'); @@ -144,8 +152,29 @@ class SnapSolver { this.socket.on('disconnect', () => { console.log('Disconnected from server'); this.updateConnectionStatus(false); - this.socket = null; - setTimeout(() => this.initializeConnection(), 5000); + }); + + this.socket.on('connect_error', (error) => { + console.error('Connection error:', error); + this.updateConnectionStatus(false); + }); + + this.socket.on('reconnect', (attemptNumber) => { + console.log(`Reconnected after ${attemptNumber} attempts`); + this.updateConnectionStatus(true); + }); + + this.socket.on('reconnect_attempt', (attemptNumber) => { + console.log(`Reconnection attempt: ${attemptNumber}`); + }); + + this.socket.on('reconnect_error', (error) => { + console.error('Reconnection error:', error); + }); + + this.socket.on('reconnect_failed', () => { + console.error('Failed to reconnect'); + window.showToast('连接服务器失败,请刷新页面重试', 'error'); }); this.setupSocketEventHandlers(); @@ -177,6 +206,17 @@ class SnapSolver { } }); + // 请求确认响应处理器 + this.socket.on('request_acknowledged', (data) => { + console.log('服务器确认收到请求:', data); + window.showToast(data.message || '请求已接收,正在处理...', 'info'); + // 清除可能存在的超时计时器 + if (this.emitTimeout) { + clearTimeout(this.emitTimeout); + this.emitTimeout = null; + } + }); + // Text extraction response handler this.socket.on('text_extracted', (data) => { if (data.error) { @@ -319,13 +359,6 @@ class SnapSolver { } } }); - - this.socket.on('connect_error', (error) => { - console.error('Connection error:', error); - this.updateConnectionStatus(false); - this.socket = null; - setTimeout(() => this.initializeConnection(), 5000); - }); } // 新方法:安全设置DOM内容的方法(替代updateElementContent) @@ -751,11 +784,23 @@ class SnapSolver { } try { + // 设置超时时间(10秒)以避免长时间无响应 + this.emitTimeout = setTimeout(() => { + window.showToast('文本提取超时,请重试或手动输入文本', 'error'); + this.extractTextBtn.disabled = false; + this.extractTextBtn.innerHTML = 'Extract Text'; + this.extractedText.disabled = false; + }, 10000); + this.socket.emit('extract_text', { image: this.croppedImage.split(',')[1], settings: { mathpixApiKey: `${mathpixAppId}:${mathpixAppKey}` } + }, (ackResponse) => { + // 如果服务器确认收到请求,清除超时 + clearTimeout(this.emitTimeout); + console.log('服务器确认收到文本提取请求', ackResponse); }); } catch (error) { window.showToast('Failed to extract text: ' + error.message, 'error'); @@ -821,13 +866,36 @@ class SnapSolver { this.sendToClaudeBtn.disabled = true; try { + // 添加图片大小检查和压缩 + const base64Data = this.croppedImage.split(',')[1]; + // 计算图片大小(以字节为单位) + const imageSize = Math.ceil((base64Data.length * 3) / 4); + console.log(`图片大小: ${imageSize / 1024} KB`); + + // 如果图片大小超过8MB(WebSocket默认限制),则显示错误 + if (imageSize > 8 * 1024 * 1024) { + window.showToast('图片太大,请裁剪更小的区域或使用文本提取功能', 'error'); + this.sendToClaudeBtn.disabled = false; + return; + } + + // 设置超时时间(10秒)以避免长时间无响应 + this.emitTimeout = setTimeout(() => { + window.showToast('发送图片超时,请重试或使用文本提取功能', 'error'); + this.sendToClaudeBtn.disabled = false; + }, 10000); + this.socket.emit('analyze_image', { - image: this.croppedImage.split(',')[1], + image: base64Data, settings: { ...settings, api_keys: apiKeys, model: settings.model || 'claude-3-7-sonnet-20250219', } + }, (ackResponse) => { + // 如果服务器确认收到请求,清除超时 + clearTimeout(this.emitTimeout); + console.log('服务器确认收到图片分析请求', ackResponse); }); } catch (error) { this.responseContent.textContent = 'Error: Failed to send image for analysis - ' + error.message;