mirror of
https://github.com/Zippland/Snap-Solver.git
synced 2026-01-20 07:00:57 +08:00
@@ -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长上下文"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"build_date": "2025-04-11",
|
||||
"github_repo": "Zippland/Snap-Solver"
|
||||
}
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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]:
|
||||
"""流式生成文本响应"""
|
||||
|
||||
@@ -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, '<br>');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -825,8 +863,8 @@ class SnapSolver {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Markdown解析错误:', error);
|
||||
// 发生错误时回退到纯文本显示
|
||||
element.textContent = content;
|
||||
// 发生错误时也保留换行格式
|
||||
element.innerHTML = content.replace(/\n/g, '<br>');
|
||||
}
|
||||
|
||||
// 自动滚动到底部
|
||||
@@ -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) {
|
||||
|
||||
234
static/style.css
234
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,17 +32,20 @@
|
||||
<header class="app-header">
|
||||
<div class="header-content">
|
||||
<h1>Snap Solver <span class="version-badge">v<span id="currentVersion">{{ update_info.current_version }}</span></span></h1>
|
||||
<div id="connectionStatus" class="status disconnected">未连接</div>
|
||||
<div class="header-buttons">
|
||||
<button id="captureBtn" class="btn-icon capture-btn-highlight" title="截图" disabled>
|
||||
<i class="fas fa-camera"></i>
|
||||
</button>
|
||||
<div class="header-middle">
|
||||
<button id="themeToggle" class="btn-icon" title="切换主题">
|
||||
<i class="fas fa-moon"></i>
|
||||
</button>
|
||||
<button id="settingsToggle" class="btn-icon" title="设置">
|
||||
<i class="fas fa-cog"></i>
|
||||
</button>
|
||||
<div id="connectionStatus" class="status disconnected">未连接</div>
|
||||
</div>
|
||||
<div class="header-buttons">
|
||||
<button id="captureBtn" class="btn-icon capture-btn-highlight" title="截图" disabled>
|
||||
<i class="fas fa-camera"></i>
|
||||
<span>开始截图</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -61,6 +64,68 @@
|
||||
|
||||
<main class="app-main">
|
||||
<div class="content-panel">
|
||||
<div id="claudePanel" class="claude-panel hidden">
|
||||
<div class="panel-header">
|
||||
<div class="header-title">
|
||||
<h2><i class="fas fa-chart-bar"></i> 分析结果</h2>
|
||||
<div class="analysis-indicator">
|
||||
<div class="progress-line"></div>
|
||||
<div class="status-text">准备中</div>
|
||||
</div>
|
||||
<button id="stopGenerationBtn" class="btn-stop-generation" title="停止生成">
|
||||
<i class="fas fa-stop"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn-icon" id="closeClaudePanel" title="关闭分析结果">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="thinkingSection" class="thinking-section hidden">
|
||||
<div class="thinking-header" id="thinkingToggle" title="点击查看AI思考过程">
|
||||
<div class="thinking-title">
|
||||
<i class="fas fa-brain"></i>
|
||||
<h3>思考过程<span class="dots-animation"></span></h3>
|
||||
</div>
|
||||
<button class="toggle-btn">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="thinkingContent" class="thinking-content collapsed"></div>
|
||||
</div>
|
||||
<div id="responseContent" class="response-content"></div>
|
||||
</div>
|
||||
|
||||
<div id="claudePanel" class="claude-panel hidden">
|
||||
<div class="panel-header">
|
||||
<div class="header-title">
|
||||
<h2><i class="fas fa-chart-bar"></i> 分析结果</h2>
|
||||
<div class="analysis-indicator">
|
||||
<div class="progress-line"></div>
|
||||
<div class="status-text">准备中</div>
|
||||
</div>
|
||||
<button id="stopGenerationBtn" class="btn-stop-generation" title="停止生成">
|
||||
<i class="fas fa-stop"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn-icon" id="closeClaudePanel" title="关闭分析结果">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="thinkingSection" class="thinking-section hidden">
|
||||
<div class="thinking-header" id="thinkingToggle" title="点击查看AI思考过程">
|
||||
<div class="thinking-title">
|
||||
<i class="fas fa-brain"></i>
|
||||
<h3>思考过程<span class="dots-animation"></span></h3>
|
||||
</div>
|
||||
<button class="toggle-btn">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="thinkingContent" class="thinking-content collapsed"></div>
|
||||
</div>
|
||||
<div id="responseContent" class="response-content"></div>
|
||||
</div>
|
||||
|
||||
<div class="capture-section">
|
||||
<div id="emptyState" class="empty-state">
|
||||
<i class="fas fa-camera-retro"></i>
|
||||
@@ -100,37 +165,6 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="claudePanel" class="claude-panel hidden">
|
||||
<div class="panel-header">
|
||||
<div class="header-title">
|
||||
<h2><i class="fas fa-chart-bar"></i> 分析结果</h2>
|
||||
<div class="analysis-indicator">
|
||||
<div class="progress-line"></div>
|
||||
<div class="status-text">准备中</div>
|
||||
</div>
|
||||
<button id="stopGenerationBtn" class="btn-stop-generation" title="停止生成">
|
||||
<i class="fas fa-stop"></i>
|
||||
</button>
|
||||
</div>
|
||||
<button class="btn-icon" id="closeClaudePanel" title="关闭分析结果">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="thinkingSection" class="thinking-section hidden">
|
||||
<div class="thinking-header" id="thinkingToggle" title="点击查看AI思考过程">
|
||||
<div class="thinking-title">
|
||||
<i class="fas fa-brain"></i>
|
||||
<h3>思考过程<span class="dots-animation"></span></h3>
|
||||
</div>
|
||||
<button class="toggle-btn">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="thinkingContent" class="thinking-content collapsed"></div>
|
||||
</div>
|
||||
<div id="responseContent" class="response-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside id="settingsPanel" class="settings-panel">
|
||||
@@ -774,19 +808,29 @@
|
||||
</main>
|
||||
|
||||
<div id="cropContainer" class="crop-container hidden">
|
||||
<div class="crop-wrapper">
|
||||
<div class="crop-area"></div>
|
||||
</div>
|
||||
<div class="crop-actions">
|
||||
<div class="crop-actions crop-actions-top">
|
||||
<button id="cropCancel" class="btn-secondary">
|
||||
<i class="fas fa-times"></i>
|
||||
<span>取消</span>
|
||||
</button>
|
||||
<button id="cropConfirm" class="btn-primary">
|
||||
<i class="fas fa-check"></i>
|
||||
<span>确认</span>
|
||||
<div class="crop-bottom-buttons">
|
||||
<button id="cropReset" class="btn-secondary crop-half-btn">
|
||||
<i class="fas fa-undo"></i>
|
||||
<span>重置</span>
|
||||
</button>
|
||||
<button id="cropConfirm" class="btn-secondary crop-half-btn">
|
||||
<i class="fas fa-check"></i>
|
||||
<span>确认</span>
|
||||
</button>
|
||||
</div>
|
||||
<button id="cropSendToAI" class="btn-primary">
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
<span>发送</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="crop-wrapper">
|
||||
<div class="crop-area"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="toastContainer" class="toast-container"></div>
|
||||
|
||||
Reference in New Issue
Block a user