diff --git a/config/models.json b/config/models.json index fe35fd8..2958112 100644 --- a/config/models.json +++ b/config/models.json @@ -110,7 +110,15 @@ "supportsMultimodal": true, "isReasoning": false, "version": "latest", - "description": "Google更快速的Gemini 2.5 Flash模型,支持图像理解,有免费配额" + "description": "Google最新的Gemini 2.5 Flash模型,支持图像理解,速度更快,性能更好" + }, + "gemini-2.0-flash": { + "name": "Gemini 2.0 Flash", + "provider": "google", + "supportsMultimodal": true, + "isReasoning": false, + "version": "latest", + "description": "Google更快速的Gemini 2.0 Flash模型,支持图像理解,有免费配额" }, "doubao-seed-1-6-250615": { "name": "Doubao-Seed-1.6", @@ -121,4 +129,4 @@ "description": "支持auto/thinking/non-thinking三种思考模式、支持多模态、256K长上下文" } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/config/version.json b/config/version.json index b4eba3f..be03306 100644 --- a/config/version.json +++ b/config/version.json @@ -1,5 +1,5 @@ { - "version": "1.3.0", + "version": "1.4.0", "build_date": "2025-04-11", "github_repo": "Zippland/Snap-Solver" } \ No newline at end of file diff --git a/models/doubao.py b/models/doubao.py index 68d7925..bbce3c4 100644 --- a/models/doubao.py +++ b/models/doubao.py @@ -74,9 +74,16 @@ class DoubaoModel(BaseModel): "Content-Type": "application/json" } - # 构建消息 - 根据官方API文档,暂时不使用系统提示词 + # 构建消息 - 添加系统提示词 messages = [] + # 添加系统提示词 + if self.system_prompt: + messages.append({ + "role": "system", + "content": self.system_prompt + }) + # 添加用户查询 user_content = text if self.language and self.language != 'auto': @@ -221,6 +228,16 @@ class DoubaoModel(BaseModel): elif image_data.startswith('iVBORw0KGgo'): # PNG magic number in base64 image_format = "png" + # 构建消息 + messages = [] + + # 添加系统提示词 + if self.system_prompt: + messages.append({ + "role": "system", + "content": self.system_prompt + }) + user_content = [ { "type": "text", @@ -234,12 +251,10 @@ class DoubaoModel(BaseModel): } ] - messages = [ - { - "role": "user", - "content": user_content - } - ] + messages.append({ + "role": "user", + "content": user_content + }) # 处理推理配置 thinking = { diff --git a/models/google.py b/models/google.py index fe9f210..e135070 100644 --- a/models/google.py +++ b/models/google.py @@ -53,7 +53,7 @@ class GoogleModel(BaseModel): def get_model_identifier(self) -> str: """返回默认的模型标识符""" - return "gemini-2.5-flash" # 使用有免费配额的模型作为默认值 + return "gemini-2.0-flash" # 使用有免费配额的模型作为默认值 def analyze_text(self, text: str, proxies: dict = None) -> Generator[dict, None, None]: """流式生成文本响应""" diff --git a/static/js/main.js b/static/js/main.js index 13b2686..4e2fbe1 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -40,6 +40,7 @@ class SnapSolver { // Crop elements this.cropCancel = document.getElementById('cropCancel'); this.cropConfirm = document.getElementById('cropConfirm'); + this.cropSendToAI = document.getElementById('cropSendToAI'); // 初始化应用 this.initialize(); @@ -65,6 +66,7 @@ class SnapSolver { this.cropContainer = document.getElementById('cropContainer'); this.cropCancel = document.getElementById('cropCancel'); this.cropConfirm = document.getElementById('cropConfirm'); + this.cropSendToAI = document.getElementById('cropSendToAI'); this.stopGenerationBtn = document.getElementById('stopGenerationBtn'); // 处理按钮事件 @@ -657,12 +659,24 @@ class SnapSolver { case 'error': console.error('Analysis error:', data.error); - const errorMessage = data.error || 'Unknown error occurred'; + + // 安全处理错误消息,确保它是字符串 + let errorMessage = 'Unknown error occurred'; + if (data.error) { + if (typeof data.error === 'string') { + errorMessage = data.error; + } else if (typeof data.error === 'object') { + // 如果是对象,尝试获取消息字段或转换为JSON + errorMessage = data.error.message || data.error.error || JSON.stringify(data.error); + } else { + errorMessage = String(data.error); + } + } // 显示错误信息 - if (errorMessage && this.responseContent) { - const currentText = this.responseContent.textContent || ''; - this.setElementContent(this.responseContent, currentText + '\nError: ' + errorMessage); + if (this.responseContent) { + // 不要尝试获取现有内容,直接显示错误信息 + this.setElementContent(this.responseContent, 'Error: ' + errorMessage); } // 重新启用按钮 @@ -803,11 +817,35 @@ class SnapSolver { setElementContent(element, content) { if (!element) return; + // 首先确保content是字符串 + if (typeof content !== 'string') { + if (content === null || content === undefined) { + content = ''; + } else if (typeof content === 'object') { + // 对于对象,尝试获取有意义的字符串表示 + if (content.error || content.message) { + content = content.error || content.message; + } else if (content.toString && typeof content.toString === 'function' && content.toString() !== '[object Object]') { + content = content.toString(); + } else { + // 作为最后手段,使用JSON.stringify + try { + content = JSON.stringify(content, null, 2); + } catch (e) { + content = '[Complex Object]'; + } + } + } else { + content = String(content); + } + } + try { // 检查marked是否已配置 if (typeof marked === 'undefined') { console.warn('Marked库未加载,回退到纯文本显示'); - element.textContent = content; + // 即使回退到纯文本,也要保留换行和基本格式 + element.innerHTML = content.replace(/\n/g, '
'); return; } @@ -825,8 +863,8 @@ class SnapSolver { } } catch (error) { console.error('Markdown解析错误:', error); - // 发生错误时回退到纯文本显示 - element.textContent = content; + // 发生错误时也保留换行格式 + element.innerHTML = content.replace(/\n/g, '
'); } // 自动滚动到底部 @@ -868,7 +906,14 @@ class SnapSolver { dragMode: 'move', aspectRatio: NaN, modal: true, - background: true + background: true, + ready: function() { + // 如果有上次保存的裁剪框数据,应用它 + if (self.lastCropBoxData) { + self.cropper.setCropBoxData(self.lastCropBoxData); + console.log('Applied saved crop box data'); + } + } }); } catch (error) { console.error('Failed to initialize cropper', error); @@ -938,10 +983,38 @@ class SnapSolver { } setupCropEvents() { - // 移除裁剪按钮的点击事件监听 + // 防止重复绑定事件监听器 + if (this.cropConfirm) { + const newCropConfirm = this.cropConfirm.cloneNode(true); + this.cropConfirm.parentNode.replaceChild(newCropConfirm, this.cropConfirm); + this.cropConfirm = newCropConfirm; + } + + if (this.cropCancel) { + const newCropCancel = this.cropCancel.cloneNode(true); + this.cropCancel.parentNode.replaceChild(newCropCancel, this.cropCancel); + this.cropCancel = newCropCancel; + } + + const cropResetElement = document.getElementById('cropReset'); + if (cropResetElement) { + const newCropReset = cropResetElement.cloneNode(true); + cropResetElement.parentNode.replaceChild(newCropReset, cropResetElement); + } + + if (this.cropSendToAI) { + const newCropSendToAI = this.cropSendToAI.cloneNode(true); + this.cropSendToAI.parentNode.replaceChild(newCropSendToAI, this.cropSendToAI); + this.cropSendToAI = newCropSendToAI; + } + + console.log('DEBUG: 已清除裁剪按钮上的事件监听器,防止重复绑定'); + + // 存储裁剪框数据 + this.lastCropBoxData = null; // Crop confirm button - document.getElementById('cropConfirm').addEventListener('click', () => { + this.cropConfirm.addEventListener('click', () => { if (!this.checkConnectionBeforeAction()) return; if (this.cropper) { @@ -957,6 +1030,9 @@ class SnapSolver { const cropBoxData = this.cropper.getCropBoxData(); console.log('Crop box data:', cropBoxData); + // 保存裁剪框数据以便下次使用 + this.lastCropBoxData = cropBoxData; + if (!cropBoxData || typeof cropBoxData.width !== 'number' || typeof cropBoxData.height !== 'number') { throw new Error('Invalid crop box data'); } @@ -1008,6 +1084,8 @@ class SnapSolver { this.updateImageActionButtons(); window.uiManager.showToast('裁剪成功'); + + // 不再自动发送至AI,由用户手动选择 } catch (error) { console.error('Cropping error details:', { message: error.message, @@ -1026,7 +1104,7 @@ class SnapSolver { }); // Crop cancel button - document.getElementById('cropCancel').addEventListener('click', () => { + this.cropCancel.addEventListener('click', () => { if (this.cropper) { this.cropper.destroy(); this.cropper = null; @@ -1036,15 +1114,168 @@ class SnapSolver { this.imagePreview.classList.add('hidden'); document.querySelector('.crop-area').innerHTML = ''; }); + + // Crop reset button + const cropResetBtn = document.getElementById('cropReset'); + if (cropResetBtn) { + cropResetBtn.addEventListener('click', () => { + if (this.cropper) { + // 重置裁剪区域到默认状态 + this.cropper.reset(); + window.uiManager.showToast('已重置裁剪区域'); + } + }); + } + + // Crop send to AI button + this.cropSendToAI.addEventListener('click', () => { + if (!this.checkConnectionBeforeAction()) return; + + // 如果有裁剪器,尝试获取裁剪结果;否则使用原始图片 + if (this.cropper) { + try { + console.log('Starting crop and send operation...'); + + // Validate cropper instance + if (!this.cropper) { + throw new Error('Cropper not initialized'); + } + + // Get and validate crop box data + const cropBoxData = this.cropper.getCropBoxData(); + console.log('Crop box data:', cropBoxData); + + // 保存裁剪框数据以便下次使用 + this.lastCropBoxData = 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).'); + } + + // Get cropped canvas + 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'); + + // Convert to data URL + 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.'); + } + + // Clean up cropper and update UI + this.cropper.destroy(); + this.cropper = null; + this.cropContainer.classList.add('hidden'); + document.querySelector('.crop-area').innerHTML = ''; + + // Update the screenshot image with the cropped version + this.screenshotImg.src = this.croppedImage; + this.imagePreview.classList.remove('hidden'); + + // 根据当前选择的模型类型决定显示哪些按钮 + this.updateImageActionButtons(); + + // 显示Claude分析面板 + this.claudePanel.classList.remove('hidden'); + this.emptyState.classList.add('hidden'); + + // 发送图像到Claude进行分析 + this.sendImageToClaude(this.croppedImage); + + window.uiManager.showToast('正在发送至AI分析...'); + + } catch (error) { + console.error('Crop and send error details:', { + message: error.message, + stack: error.stack, + cropperState: this.cropper ? 'initialized' : 'not initialized' + }); + window.uiManager.showToast(error.message || '处理图像时出错', 'error'); + + // Clean up on error + if (this.cropper) { + this.cropper.destroy(); + this.cropper = null; + } + this.cropContainer.classList.add('hidden'); + document.querySelector('.crop-area').innerHTML = ''; + } + } else if (this.originalImage) { + // 如果没有裁剪器但有原始图片,直接发送原始图片 + try { + // 隐藏裁剪容器 + this.cropContainer.classList.add('hidden'); + + // 显示Claude分析面板 + this.claudePanel.classList.remove('hidden'); + this.emptyState.classList.add('hidden'); + + // 发送原始图像到Claude进行分析 + this.sendImageToClaude(this.originalImage); + + window.uiManager.showToast('正在发送至AI分析...'); + + } catch (error) { + console.error('Send original image error:', error); + window.uiManager.showToast('发送图片失败: ' + error.message, 'error'); + } + } else { + window.uiManager.showToast('请先截图', 'error'); + } + }); } setupAnalysisEvents() { + // 防止重复绑定事件监听器 + if (this.extractTextBtn) { + const newExtractBtn = this.extractTextBtn.cloneNode(true); + this.extractTextBtn.parentNode.replaceChild(newExtractBtn, this.extractTextBtn); + this.extractTextBtn = newExtractBtn; + } + + if (this.sendExtractedTextBtn) { + const newSendBtn = this.sendExtractedTextBtn.cloneNode(true); + this.sendExtractedTextBtn.parentNode.replaceChild(newSendBtn, this.sendExtractedTextBtn); + this.sendExtractedTextBtn = newSendBtn; + } + + if (this.sendToClaudeBtn) { + const newClaudeBtn = this.sendToClaudeBtn.cloneNode(true); + this.sendToClaudeBtn.parentNode.replaceChild(newClaudeBtn, this.sendToClaudeBtn); + this.sendToClaudeBtn = newClaudeBtn; + } + + console.log('DEBUG: 已清除分析按钮上的事件监听器,防止重复绑定'); + // Extract Text button this.extractTextBtn.addEventListener('click', () => { if (!this.checkConnectionBeforeAction()) return; - if (!this.croppedImage) { - window.uiManager.showToast('请先裁剪图片', 'error'); + // 优先使用裁剪后的图片,如果没有则使用原始截图 + const imageToExtract = this.croppedImage || this.originalImage; + + if (!imageToExtract) { + window.uiManager.showToast('请先截图', 'error'); return; } @@ -1097,7 +1328,7 @@ class SnapSolver { try { this.socket.emit('extract_text', { - image: this.croppedImage.split(',')[1], + image: imageToExtract.split(',')[1], settings: { ocrSource: settings.ocrSource || 'auto' } @@ -1142,13 +1373,6 @@ class SnapSolver { console.log("Debug - 发送文本分析API密钥:", apiKeys); - // 清空之前的结果 - this.responseContent.innerHTML = ''; - this.thinkingContent.innerHTML = ''; - - // 显示Claude分析面板 - this.claudePanel.classList.remove('hidden'); - // 禁用按钮防止重复点击 this.sendExtractedTextBtn.disabled = true; @@ -1167,7 +1391,7 @@ class SnapSolver { } }); } catch (error) { - this.responseContent.textContent = 'Error: Failed to send text for analysis - ' + error.message; + this.setElementContent(this.responseContent, 'Error: Failed to send text for analysis - ' + error.message); this.sendExtractedTextBtn.disabled = false; window.uiManager.showToast('发送文本进行分析失败', 'error'); } @@ -1192,6 +1416,7 @@ class SnapSolver { return; } + // 只发送裁剪后的图片,如果没有裁剪过则提示用户先裁剪 if (this.croppedImage) { try { // 清空之前的结果 @@ -1346,7 +1571,7 @@ class SnapSolver { // 注意:Claude面板的显示已经在点击事件中处理,这里不再重复 } catch (error) { - this.responseContent.textContent = 'Error: ' + error.message; + this.setElementContent(this.responseContent, 'Error: ' + error.message); window.uiManager.showToast('发送图片分析失败', 'error'); this.sendToClaudeBtn.disabled = false; } @@ -1393,7 +1618,6 @@ class SnapSolver { // 初始化UI元素和事件处理 this.initializeElements(); - this.setupSocketEventHandlers(); // 设置所有事件监听器(注意:setupEventListeners内部已经调用了setupCaptureEvents,不需要重复调用) this.setupEventListeners(); @@ -1453,6 +1677,8 @@ class SnapSolver { smartLists: true, // 使用比原生markdown更智能的列表行为 smartypants: false, // 不要使用更智能的标点符号 xhtml: false, // 不使用自闭合标签 + mangle: false, // 不混淆邮箱地址 + headerIds: false, // 不生成header ID highlight: function(code, lang) { // 如果highlight.js不可用,直接返回代码 if (typeof hljs === 'undefined') { @@ -1478,7 +1704,7 @@ class SnapSolver { return code; // 使用默认编码效果 } }); - + // 配置hljs以支持自动语言检测 try { hljs.configure({ @@ -1572,10 +1798,13 @@ class SnapSolver { this.updateConnectionStatus('重连失败', false); window.uiManager.showToast('连接服务器失败,请刷新页面重试', 'error'); }); + + // 设置socket事件处理器 + this.setupSocketEventHandlers(); } isConnected() { - return this.connectionStatus && this.connectionStatus.classList.contains('connected'); + return this.socket && this.socket.connected; } checkConnectionBeforeAction(action) { diff --git a/static/style.css b/static/style.css index 2df0e26..8e6fa7f 100644 --- a/static/style.css +++ b/static/style.css @@ -22,7 +22,8 @@ --highlight-bg-color-dark: #2d333b; --button-hover: #f3f4f6; --button-active: #ebecf0; - --header-height: 60px; + --header-height: 40px; + --header-height: 40px; --transition-speed: 0.3s; --screen-md: 768px; --screen-sm: 480px; @@ -131,12 +132,19 @@ body { /* Header Styles */ .app-header { background-color: var(--surface); - padding: 0.75rem 1rem; + padding: 0.3rem 1rem; + padding: 0.3rem 1rem; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); z-index: 100; position: sticky; top: 0; border-bottom: 1px solid var(--border-color); + height: var(--header-height); + display: flex; + align-items: center; + height: var(--header-height); + display: flex; + align-items: center; } .header-content { @@ -149,6 +157,12 @@ body { width: 100%; } +.header-middle { + display: flex; + align-items: center; + gap: 0.5rem; +} + .app-header h1 { font-size: 1.3rem; color: var(--primary); @@ -163,7 +177,7 @@ body { font-size: 0.8rem; padding: 0.2rem 0.5rem; border-radius: 1rem; - margin: 0 0.5rem; + margin: 0; white-space: nowrap; font-weight: 600; display: inline-flex; @@ -206,11 +220,14 @@ body { .header-buttons { display: flex; gap: 0.5rem; + align-items: center; /* 确保按钮垂直居中 */ + align-items: center; /* 确保按钮垂直居中 */ } .header-buttons .btn-icon { width: 36px; - height: 36px; + height: 32px; + height: 32px; border-radius: 50%; display: flex; align-items: center; @@ -221,6 +238,16 @@ body { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); } +.capture-btn-highlight { + background-color: var(--primary) !important; + color: white !important; + box-shadow: 0 4px 12px rgba(var(--primary-rgb), 0.3) !important; + width: auto !important; + min-width: 100px !important; + border-radius: 18px !important; + padding: 0 8px !important; +} + .header-buttons .btn-icon:hover { transform: translateY(-2px); background-color: var(--hover-color); @@ -236,8 +263,10 @@ body { .app-main { flex: 1; display: flex; - padding: 1.5rem; - gap: 1.5rem; + padding: 0.5rem 1rem; /* 减小垂直内边距和水平内边距 */ + gap: 1rem; + padding: 0.5rem 1rem; /* 减小垂直内边距和水平内边距 */ + gap: 1rem; position: relative; overflow: hidden; background-color: var(--background); @@ -375,20 +404,27 @@ body { .btn-action { background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%); color: white; - padding: 0.6rem 1rem; + padding: 0.8rem 1.5rem; + padding: 0.8rem 1.5rem; border: none; - border-radius: 0.4rem; - font-size: 0.85rem; + border-radius: 2rem; + font-size: 1rem; + border-radius: 2rem; + font-size: 1rem; font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; - gap: 0.4rem; + gap: 0.5rem; + gap: 0.5rem; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); position: relative; overflow: hidden; + min-width: 180px; + min-width: 180px; } .btn-action i { @@ -515,6 +551,10 @@ body { font-size: 1.1rem; } + .header-middle { + gap: 0.3rem; + } + #connectionStatus { font-size: 0.7rem; padding: 0.15rem 0.4rem; @@ -525,6 +565,12 @@ body { height: 32px; } + .capture-btn-highlight { + min-width: 90px !important; + padding: 0 6px !important; + font-size: 0.85rem !important; + } + .toolbar-buttons { flex-direction: row; gap: 0.5rem; @@ -548,9 +594,40 @@ body { } .claude-panel { - border-radius: 0.75rem; - min-height: 200px; - } + border-radius: 0.75rem; + min-height: 200px; +} + +.claude-panel .response-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.25rem 0.75rem; /* 调整垂直内边距 */ + background-color: var(--surface-alt); + border-bottom: 1px solid var(--border-color); + border-top-left-radius: 0.75rem; + border-top-right-radius: 0.75rem; + font-weight: 600; + color: var(--text-primary); + font-size: 1rem; +} + border-radius: 0.75rem; + min-height: 200px; +} + +.claude-panel .response-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.25rem 0.75rem; /* 调整垂直内边距 */ + background-color: var(--surface-alt); + border-bottom: 1px solid var(--border-color); + border-top-left-radius: 0.75rem; + border-top-right-radius: 0.75rem; + font-weight: 600; + color: var(--text-primary); + font-size: 1rem; +} .response-content { padding: 1rem; @@ -590,10 +667,20 @@ body { font-size: 1rem; } + .header-middle { + gap: 0.2rem; + } + + .header-middle .btn-icon { + width: 28px; + height: 28px; + } + #connectionStatus { max-width: 70px; text-overflow: ellipsis; overflow: hidden; + font-size: 0.65rem; } .header-buttons .btn-icon { @@ -602,6 +689,12 @@ body { font-size: 0.85rem; } + .capture-btn-highlight { + min-width: 80px !important; + padding: 0 4px !important; + font-size: 0.8rem !important; + } + .toolbar-buttons { justify-content: center; } @@ -684,6 +777,10 @@ body { transition: all 0.3s ease; overflow: visible; width: 100%; + position: relative; + margin-bottom: 1.5rem; + position: relative; + margin-bottom: 1.5rem; } .claude-panel:not(.hidden) { @@ -705,7 +802,8 @@ body { display: flex; justify-content: space-between; align-items: center; - padding: 1rem 1.25rem; + padding: 0.25rem 1.25rem; /* 进一步减小垂直内边距 */ + padding: 0.25rem 1.25rem; /* 进一步减小垂直内边距 */ border-bottom: 1px solid var(--border-color); background-color: var(--surface); position: sticky; @@ -1603,6 +1701,58 @@ button:disabled { background-color: var(--surface); } +.crop-actions-top { + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 10; + background-color: rgba(0, 0, 0, 0.7); + border-bottom: 1px solid rgba(255, 255, 255, 0.2); + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + gap: 1rem; +} + +.crop-actions-top button { + min-height: 44px; + white-space: nowrap; +} + +.crop-bottom-buttons { + display: flex; + gap: 0; + min-width: 120px; + max-width: 150px; +} + +.crop-half-btn { + flex: 1; + border-radius: 0; + margin: 0; + white-space: nowrap; + min-height: 44px; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.85rem; +} + +.crop-half-btn:first-child { + border-top-left-radius: 8px; + border-bottom-left-radius: 8px; + border-right: 1px solid rgba(255, 255, 255, 0.2); +} + +.crop-half-btn:last-child { + border-top-right-radius: 8px; + border-bottom-right-radius: 8px; +} + + + /* Animations */ @keyframes toast-in { from { @@ -2948,10 +3098,17 @@ button:disabled { .capture-btn-highlight { background-color: #ff5a5f !important; color: white !important; - transform: scale(1.15); - box-shadow: 0 0 8px rgba(255, 90, 95, 0.5); - border-radius: 50%; + /* transform: scale(1.3); */ /* 移除整体缩放 */ + box-shadow: 0 0 12px rgba(255, 90, 95, 0.6); + border-radius: 0.5rem; /* 调整为长条状的圆角 */ + /* transform: scale(1.3); */ /* 移除整体缩放 */ + box-shadow: 0 0 12px rgba(255, 90, 95, 0.6); + border-radius: 0.5rem; /* 调整为长条状的圆角 */ position: relative; + width: 100px !important; /* 增加宽度 */ + height: 36px !important; /* 保持与普通按钮相同的高度 */ + width: 100px !important; /* 增加宽度 */ + height: 36px !important; /* 保持与普通按钮相同的高度 */ } .capture-btn-highlight i { @@ -2960,12 +3117,14 @@ button:disabled { .capture-btn-highlight:hover { background-color: #ff7e82 !important; - transform: scale(1.2); + /* transform: scale(1.2); */ + /* transform: scale(1.2); */ box-shadow: 0 0 12px rgba(255, 90, 95, 0.7); } .capture-btn-highlight:active { - transform: scale(1.1); + /* transform: scale(1.1); */ + /* transform: scale(1.1); */ box-shadow: 0 0 5px rgba(255, 90, 95, 0.4); } @@ -5565,3 +5724,38 @@ textarea, font-weight: normal; margin-left: 5px; } + +/* 移动设备横屏模式优化 */ +@media screen and (max-height: 500px) and (orientation: landscape) { + .crop-actions-top { + padding: 0.5rem; + gap: 0.5rem; + } + + .crop-actions-top button { + padding: 0.5rem 0.75rem; + font-size: 0.85rem; + } + + .crop-actions-top button span { + display: none; + } + + .crop-actions-top button i { + margin: 0; + } +} + +/* 针对极小高度的横屏设备 */ +@media screen and (max-height: 400px) and (orientation: landscape) { + .crop-actions-top { + padding: 0.25rem; + gap: 0.25rem; + } + + .crop-actions-top button { + padding: 0.25rem 0.5rem; + font-size: 0.8rem; + min-height: auto; + } +} diff --git a/templates/index.html b/templates/index.html index 0a2f075..633df0e 100644 --- a/templates/index.html +++ b/templates/index.html @@ -32,17 +32,20 @@

Snap Solver v{{ update_info.current_version }}

-
未连接
-
- +
+
未连接
+
+
+
@@ -61,6 +64,68 @@
+ + + +
@@ -100,37 +165,6 @@
- -