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..c25ed1f 100644
--- a/static/js/main.js
+++ b/static/js/main.js
@@ -657,12 +657,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 +815,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 +861,8 @@ class SnapSolver {
}
} catch (error) {
console.error('Markdown解析错误:', error);
- // 发生错误时回退到纯文本显示
- element.textContent = content;
+ // 发生错误时也保留换行格式
+ element.innerHTML = content.replace(/\n/g, '
');
}
// 自动滚动到底部
@@ -868,7 +904,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);
@@ -940,6 +983,9 @@ class SnapSolver {
setupCropEvents() {
// 移除裁剪按钮的点击事件监听
+ // 存储裁剪框数据
+ this.lastCropBoxData = null;
+
// Crop confirm button
document.getElementById('cropConfirm').addEventListener('click', () => {
if (!this.checkConnectionBeforeAction()) return;
@@ -957,6 +1003,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 +1057,22 @@ class SnapSolver {
this.updateImageActionButtons();
window.uiManager.showToast('裁剪成功');
+
+ // 获取当前模型信息
+ const settings = window.settingsManager.getSettings();
+ const supportsMultimodal = settings.modelInfo?.supportsMultimodal || false;
+
+ // 如果模型支持多模态,自动发送至AI
+ if (supportsMultimodal) {
+ setTimeout(() => {
+ // 显示Claude分析面板
+ this.claudePanel.classList.remove('hidden');
+ this.emptyState.classList.add('hidden');
+
+ // 发送图像到Claude进行分析
+ this.sendImageToClaude(this.croppedImage);
+ }, 500); // 短暂延迟以确保UI更新
+ }
} catch (error) {
console.error('Cropping error details:', {
message: error.message,
@@ -1036,6 +1101,15 @@ class SnapSolver {
this.imagePreview.classList.add('hidden');
document.querySelector('.crop-area').innerHTML = '';
});
+
+ // Crop reset button
+ document.getElementById('cropReset').addEventListener('click', () => {
+ if (this.cropper) {
+ // 重置裁剪区域到默认状态
+ this.cropper.reset();
+ window.uiManager.showToast('已重置裁剪区域');
+ }
+ });
}
setupAnalysisEvents() {
@@ -1167,7 +1241,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');
}
@@ -1346,7 +1420,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;
}
@@ -1453,6 +1527,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 +1554,7 @@ class SnapSolver {
return code; // 使用默认编码效果
}
});
-
+
// 配置hljs以支持自动语言检测
try {
hljs.configure({
diff --git a/static/style.css b/static/style.css
index 2df0e26..2641e51 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,26 @@ 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);
+}
+
+.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);
+}
+
/* Animations */
@keyframes toast-in {
from {
@@ -2948,10 +3066,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 +3085,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);
}
diff --git a/templates/index.html b/templates/index.html
index 0a2f075..269970d 100644
--- a/templates/index.html
+++ b/templates/index.html
@@ -32,17 +32,20 @@