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;