diff --git a/app.py b/app.py index 5118bac..ff00b97 100644 --- a/app.py +++ b/app.py @@ -4,7 +4,8 @@ import pyautogui import base64 from io import BytesIO import socket -from threading import Thread +from threading import Thread, Event +import threading from PIL import Image import pyperclip from models import ModelFactory @@ -31,6 +32,9 @@ socketio = SocketIO( # 添加配置文件路径 CONFIG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config') +# 跟踪用户生成任务的字典 +generation_tasks = {} + # 初始化模型工厂 ModelFactory.initialize() @@ -310,6 +314,27 @@ def handle_text_extraction(data): 'error': error_msg }, room=request.sid) +@socketio.on('stop_generation') +def handle_stop_generation(): + """处理停止生成请求""" + sid = request.sid + print(f"接收到停止生成请求: {sid}") + + if sid in generation_tasks: + # 设置停止标志 + stop_event = generation_tasks[sid] + stop_event.set() + + # 发送已停止状态 + socketio.emit('claude_response', { + 'status': 'stopped', + 'content': '生成已停止' + }, room=sid) + + print(f"已停止用户 {sid} 的生成任务") + else: + print(f"未找到用户 {sid} 的生成任务") + @socketio.on('analyze_text') def handle_analyze_text(data): try: @@ -350,8 +375,23 @@ def handle_analyze_text(data): 'https': f"http://{settings.get('proxyHost')}:{settings.get('proxyPort')}" } - for response in model_instance.analyze_text(text, proxies=proxies): - socketio.emit('claude_response', response) + # 创建用于停止生成的事件 + sid = request.sid + stop_event = Event() + generation_tasks[sid] = stop_event + + try: + for response in model_instance.analyze_text(text, proxies=proxies): + # 检查是否收到停止信号 + if stop_event.is_set(): + print(f"分析文本生成被用户 {sid} 停止") + break + + socketio.emit('claude_response', response, room=sid) + finally: + # 清理任务 + if sid in generation_tasks: + del generation_tasks[sid] except Exception as e: print(f"Error in analyze_text: {str(e)}") @@ -398,8 +438,23 @@ def handle_analyze_image(data): 'https': f"http://{settings.get('proxyHost')}:{settings.get('proxyPort')}" } - for response in model_instance.analyze_image(image_data, proxies=proxies): - socketio.emit('claude_response', response) + # 创建用于停止生成的事件 + sid = request.sid + stop_event = Event() + generation_tasks[sid] = stop_event + + try: + for response in model_instance.analyze_image(image_data, proxies=proxies): + # 检查是否收到停止信号 + if stop_event.is_set(): + print(f"分析图像生成被用户 {sid} 停止") + break + + socketio.emit('claude_response', response, room=sid) + finally: + # 清理任务 + if sid in generation_tasks: + del generation_tasks[sid] except Exception as e: print(f"Error in analyze_image: {str(e)}") diff --git a/config/version.json b/config/version.json index a200850..407db5d 100644 --- a/config/version.json +++ b/config/version.json @@ -1,5 +1,5 @@ { - "version": "1.1.0", + "version": "1.1.1", "build_date": "2025-04-02", "github_repo": "Zippland/Snap-Solver" } \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js index 91ddd38..5061ecc 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -46,32 +46,40 @@ class SnapSolver { } initializeElements() { - // Main elements - this.screenshotImg = document.getElementById('screenshotImg'); - this.imagePreview = document.getElementById('imagePreview'); - this.emptyState = document.getElementById('emptyState'); - // 移除对裁剪按钮的引用 + // 查找主要UI元素 + this.connectionStatus = document.getElementById('connectionStatus'); this.captureBtn = document.getElementById('captureBtn'); + this.emptyState = document.getElementById('emptyState'); + this.imagePreview = document.getElementById('imagePreview'); + this.screenshotImg = document.getElementById('screenshotImg'); this.sendToClaudeBtn = document.getElementById('sendToClaude'); this.extractTextBtn = document.getElementById('extractText'); this.extractedText = document.getElementById('extractedText'); this.sendExtractedTextBtn = document.getElementById('sendExtractedText'); - this.cropContainer = document.getElementById('cropContainer'); - this.manualTextInput = document.getElementById('manualTextInput'); this.claudePanel = document.getElementById('claudePanel'); - this.responseContent = document.getElementById('responseContent'); + this.closeClaudePanel = document.getElementById('closeClaudePanel'); this.thinkingSection = document.getElementById('thinkingSection'); - this.thinkingContent = document.getElementById('thinkingContent'); this.thinkingToggle = document.getElementById('thinkingToggle'); - this.connectionStatus = document.getElementById('connectionStatus'); - this.statusLight = document.querySelector('.status-light'); - this.progressLine = document.querySelector('.progress-line'); - this.statusText = document.querySelector('.status-text'); - this.analysisIndicator = document.querySelector('.analysis-indicator'); - - // Crop elements + this.thinkingContent = document.getElementById('thinkingContent'); + this.responseContent = document.getElementById('responseContent'); + this.cropContainer = document.getElementById('cropContainer'); this.cropCancel = document.getElementById('cropCancel'); this.cropConfirm = document.getElementById('cropConfirm'); + this.stopGenerationBtn = document.getElementById('stopGenerationBtn'); + + // 处理按钮事件 + if (this.closeClaudePanel) { + this.closeClaudePanel.addEventListener('click', () => { + this.claudePanel.classList.add('hidden'); + }); + } + + // 处理停止生成按钮点击事件 + if (this.stopGenerationBtn) { + this.stopGenerationBtn.addEventListener('click', () => { + this.stopGeneration(); + }); + } } initializeState() { @@ -128,51 +136,52 @@ class SnapSolver { } updateStatusLight(status) { - // 更新状态指示器 - if (this.analysisIndicator) { - this.analysisIndicator.className = 'analysis-indicator'; - this.progressLine.className = 'progress-line'; - - switch (status) { - case 'started': - case 'streaming': - this.analysisIndicator.classList.add('processing'); - this.progressLine.classList.add('processing'); - this.statusText.textContent = '处理中...'; - break; - case 'completed': - this.analysisIndicator.classList.add('completed'); - this.progressLine.classList.add('completed'); - this.statusText.textContent = '已完成'; - break; - case 'error': - this.analysisIndicator.classList.add('error'); - this.progressLine.classList.add('error'); - this.statusText.textContent = '错误'; - break; - default: - // 重置为默认状态 - this.statusText.textContent = '准备中'; - break; - } - } else if (this.statusLight) { - // 兼容旧版状态灯 - 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: - // Reset to default state - break; - } + const statusLight = document.querySelector('.status-light'); + const progressLine = document.querySelector('.progress-line'); + const statusText = document.querySelector('.status-text'); + const analysisIndicator = document.querySelector('.analysis-indicator'); + + if (!statusLight || !progressLine || !statusText || !analysisIndicator) { + return; + } + + analysisIndicator.style.display = 'flex'; + statusLight.classList.remove('completed', 'error', 'working'); + + switch (status) { + case 'started': + case 'thinking': + case 'reasoning': + case 'streaming': + statusLight.classList.add('working'); + progressLine.style.animation = 'progress-animation 2s linear infinite'; + statusText.textContent = '生成中'; + break; + + case 'completed': + statusLight.classList.add('completed'); + progressLine.style.animation = 'none'; + progressLine.style.width = '100%'; + statusText.textContent = '完成'; + break; + + case 'error': + statusLight.classList.add('error'); + progressLine.style.animation = 'none'; + progressLine.style.width = '100%'; + statusText.textContent = '出错'; + break; + + case 'stopped': + statusLight.classList.add('error'); + progressLine.style.animation = 'none'; + progressLine.style.width = '100%'; + statusText.textContent = '已停止'; + break; + + default: + analysisIndicator.style.display = 'none'; + break; } } @@ -372,6 +381,9 @@ class SnapSolver { this.responseContent.innerHTML = '
'; this.responseContent.style.display = 'block'; } + + // 显示停止生成按钮 + this.showStopGenerationButton(); break; case 'thinking': @@ -406,6 +418,9 @@ class SnapSolver { 'fas fa-chevron-up' : 'fas fa-chevron-down'; } } + + // 确保停止按钮可见 + this.showStopGenerationButton(); break; case 'reasoning': @@ -440,6 +455,9 @@ class SnapSolver { 'fas fa-chevron-up' : 'fas fa-chevron-down'; } } + + // 确保停止按钮可见 + this.showStopGenerationButton(); break; case 'thinking_complete': @@ -529,6 +547,9 @@ class SnapSolver { // 平滑滚动到最新内容 this.scrollToBottom(); } + + // 确保停止按钮可见 + this.showStopGenerationButton(); break; case 'completed': @@ -587,6 +608,27 @@ class SnapSolver { // 滚动到结果内容底部 this.scrollToBottom(); } + + // 隐藏停止生成按钮 + this.hideStopGenerationButton(); + break; + + case 'stopped': + // 处理停止生成的响应 + console.log('Generation stopped'); + + // 重新启用按钮 + if (this.sendToClaudeBtn) this.sendToClaudeBtn.disabled = false; + if (this.sendExtractedTextBtn) this.sendExtractedTextBtn.disabled = false; + + // 恢复界面 + this.updateStatusLight('stopped'); + + // 隐藏停止生成按钮 + this.hideStopGenerationButton(); + + // 显示提示信息 + window.uiManager.showToast('已停止生成', 'info'); break; case 'error': @@ -604,6 +646,9 @@ class SnapSolver { if (this.sendExtractedTextBtn) this.sendExtractedTextBtn.disabled = false; window.uiManager.showToast('Analysis failed: ' + errorMessage, 'error'); + + // 隐藏停止生成按钮 + this.hideStopGenerationButton(); break; default: @@ -620,6 +665,9 @@ class SnapSolver { if (this.sendExtractedTextBtn) this.sendExtractedTextBtn.disabled = false; window.uiManager.showToast('Unknown error occurred', 'error'); + + // 隐藏停止生成按钮 + this.hideStopGenerationButton(); break; } }); @@ -1570,6 +1618,39 @@ class SnapSolver { } } } + + // 添加停止生成方法 + stopGeneration() { + console.log('停止生成请求'); + + // 向服务器发送停止生成信号 + if (this.socket && this.socket.connected) { + this.socket.emit('stop_generation'); + + // 显示提示 + window.uiManager.showToast('正在停止生成...', 'info'); + + // 隐藏停止按钮 + this.hideStopGenerationButton(); + } else { + console.error('无法停止生成: Socket未连接'); + window.uiManager.showToast('无法停止生成: 连接已断开', 'error'); + } + } + + // 显示停止生成按钮 + showStopGenerationButton() { + if (this.stopGenerationBtn) { + this.stopGenerationBtn.classList.add('visible'); + } + } + + // 隐藏停止生成按钮 + hideStopGenerationButton() { + if (this.stopGenerationBtn) { + this.stopGenerationBtn.classList.remove('visible'); + } + } } // Initialize the application when the DOM is loaded diff --git a/static/style.css b/static/style.css index d7fb90c..93781ca 100644 --- a/static/style.css +++ b/static/style.css @@ -10,7 +10,7 @@ --text-secondary: #666666; --border-color: #e0e0e0; --shadow-color: rgba(0, 0, 0, 0.1); - --error-color: #f44336; + --error-color: #e53935; --success-color: #4CAF50; --primary: #2196f3; --primary-rgb: 33, 150, 243; @@ -32,6 +32,7 @@ --accent: #4a6cf7; --placeholder: #a0a0a0; --disabled: #e6e6e6; + --error-hover-color: #c62828; } [data-theme="dark"] { @@ -680,8 +681,8 @@ body { .header-title { display: flex; - flex-direction: column; - gap: 0.5rem; + align-items: center; + gap: 8px; } .header-title h2 { @@ -2173,3 +2174,37 @@ button:disabled { padding-top: 0.6rem; } } + +/* 在适当位置添加停止生成按钮样式 */ +.btn-stop-generation { + display: none; + background-color: var(--error-color); + color: white; + border: none; + border-radius: 4px; + width: 32px; + height: 32px; + padding: 0; + margin-left: 10px; + cursor: pointer; + transition: all 0.2s ease; + align-items: center; + justify-content: center; +} + +.btn-stop-generation:hover { + background-color: var(--error-hover-color); + transform: scale(1.05); +} + +.btn-stop-generation:active { + transform: scale(0.95); +} + +.btn-stop-generation i { + font-size: 14px; +} + +.btn-stop-generation.visible { + display: flex; +} diff --git a/templates/index.html b/templates/index.html index a914d6b..b5fa690 100644 --- a/templates/index.html +++ b/templates/index.html @@ -100,6 +100,9 @@