优化WebSocket连接和图像处理的稳定性

This commit is contained in:
Zylan
2025-03-05 18:23:51 +08:00
parent 810c977f88
commit 80829f09d2
2 changed files with 104 additions and 12 deletions

26
app.py
View File

@@ -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,

View File

@@ -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 = '<i class="fas fa-font"></i><span>Extract Text</span>';
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`);
// 如果图片大小超过8MBWebSocket默认限制则显示错误
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;