更新DeepSeek模型配置,新增DeepSeek-V3和DeepSeek推理模型,优化API调用逻辑,支持多模态模型的图像分析,改进前端按钮显示逻辑,提升用户体验

This commit is contained in:
Zylan
2025-03-22 22:52:15 +08:00
parent 5a1ca6761c
commit 5cac831156
5 changed files with 398 additions and 109 deletions

View File

@@ -38,15 +38,23 @@
"provider": "openai", "provider": "openai",
"supportsMultimodal": false, "supportsMultimodal": false,
"isReasoning": true, "isReasoning": true,
"version": "不可用", "version": "2025-01-31",
"description": "OpenAI的o3-mini模型支持图像理解和思考过程" "description": "OpenAI的o3-mini模型支持图像理解和思考过程"
}, },
"deepseek-r1": { "deepseek-chat": {
"name": "DeepSeek-V3",
"provider": "deepseek",
"supportsMultimodal": false,
"isReasoning": false,
"version": "2025-01",
"description": "DeepSeek最新大模型671B MoE模型支持60 tokens/秒的高速生成"
},
"deepseek-reasoner": {
"name": "DeepSeek-R1", "name": "DeepSeek-R1",
"provider": "deepseek", "provider": "deepseek",
"supportsMultimodal": false, "supportsMultimodal": false,
"isReasoning": true, "isReasoning": true,
"version": "不可用", "version": "latest",
"description": "DeepSeek推理模型提供详细思考过程仅支持文本" "description": "DeepSeek推理模型提供详细思考过程仅支持文本"
} }
} }

View File

@@ -1,10 +1,15 @@
import json import json
import requests import requests
import os
from typing import Generator from typing import Generator
from openai import OpenAI from openai import OpenAI
from .base import BaseModel from .base import BaseModel
class DeepSeekModel(BaseModel): class DeepSeekModel(BaseModel):
def __init__(self, api_key: str, temperature: float = 0.7, system_prompt: str = None, language: str = None, model_name: str = "deepseek-reasoner"):
super().__init__(api_key, temperature, system_prompt, language)
self.model_name = model_name
def get_default_system_prompt(self) -> str: def get_default_system_prompt(self) -> str:
return """You are an expert at analyzing questions and providing detailed solutions. When presented with an image of a question: return """You are an expert at analyzing questions and providing detailed solutions. When presented with an image of a question:
1. First read and understand the question carefully 1. First read and understand the question carefully
@@ -14,6 +19,11 @@ class DeepSeekModel(BaseModel):
5. If there are multiple approaches, explain the most efficient one first""" 5. If there are multiple approaches, explain the most efficient one first"""
def get_model_identifier(self) -> str: def get_model_identifier(self) -> str:
"""根据模型名称返回正确的API标识符"""
# 通过模型名称来确定实际的API调用标识符
if self.model_name == "deepseek-chat":
return "deepseek-chat"
# deepseek-reasoner是默认的推理模型名称
return "deepseek-reasoner" return "deepseek-reasoner"
def analyze_text(self, text: str, proxies: dict = None) -> Generator[dict, None, None]: def analyze_text(self, text: str, proxies: dict = None) -> Generator[dict, None, None]:
@@ -22,142 +32,365 @@ class DeepSeekModel(BaseModel):
# Initial status # Initial status
yield {"status": "started", "content": ""} yield {"status": "started", "content": ""}
# Configure client with proxy if needed # 保存原始环境变量
client_args = { original_env = {
"api_key": self.api_key, 'http_proxy': os.environ.get('http_proxy'),
"base_url": "https://api.deepseek.com" 'https_proxy': os.environ.get('https_proxy')
} }
if proxies:
session = requests.Session()
session.proxies = proxies
client_args["http_client"] = session
client = OpenAI(**client_args) try:
# 如果提供了代理设置,通过环境变量设置
if proxies:
if 'http' in proxies:
os.environ['http_proxy'] = proxies['http']
if 'https' in proxies:
os.environ['https_proxy'] = proxies['https']
response = client.chat.completions.create( # 初始化DeepSeek客户端不再使用session对象
model=self.get_model_identifier(), client = OpenAI(
messages=[{ api_key=self.api_key,
'role': 'system', base_url="https://api.deepseek.com"
'content': self.system_prompt )
}, {
'role': 'user',
'content': text
}],
stream=True
)
for chunk in response: # 添加系统语言指令
try: system_prompt = self.system_prompt
if hasattr(chunk.choices[0].delta, 'reasoning_content'): language = self.language or '中文'
content = chunk.choices[0].delta.reasoning_content if not any(phrase in system_prompt for phrase in ['Please respond in', '请用', '使用', '回答']):
if content: system_prompt = f"{system_prompt}\n\n请务必使用{language}回答。"
yield {
"status": "streaming",
"content": content
}
elif hasattr(chunk.choices[0].delta, 'content'):
content = chunk.choices[0].delta.content
if content:
yield {
"status": "streaming",
"content": content
}
except Exception as e: # 构建请求参数
print(f"Chunk processing error: {str(e)}") params = {
continue "model": self.get_model_identifier(),
"messages": [
{
'role': 'system',
'content': system_prompt
},
{
'role': 'user',
'content': text
}
],
"stream": True
}
# 只有非推理模型才设置temperature参数
if not self.model_name.endswith('reasoner') and self.temperature is not None:
params["temperature"] = self.temperature
print(f"调用DeepSeek API: {self.get_model_identifier()}, 是否设置温度: {not self.model_name.endswith('reasoner')}")
# Send completion status response = client.chat.completions.create(**params)
yield {
"status": "completed", # 使用两个缓冲区,分别用于常规内容和思考内容
"content": "" response_buffer = ""
} thinking_buffer = ""
for chunk in response:
# 打印chunk以调试
try:
print(f"DeepSeek API返回chunk: {chunk}")
except:
print("无法打印chunk")
try:
# 处理推理模型的思考内容
if hasattr(chunk.choices[0].delta, 'reasoning_content'):
content = chunk.choices[0].delta.reasoning_content
if content:
# 累积思考内容
thinking_buffer += content
# 只在积累一定数量的字符或遇到句子结束标记时才发送
if len(content) >= 20 or content.endswith(('.', '!', '?', '', '', '', '\n')):
yield {
"status": "thinking",
"content": thinking_buffer
}
# 处理常规内容
elif hasattr(chunk.choices[0].delta, 'content'):
content = chunk.choices[0].delta.content
if content:
# 累积响应内容
response_buffer += content
print(f"累积响应内容: '{content}', 当前buffer: '{response_buffer}'")
# 只在积累一定数量的字符或遇到句子结束标记时才发送
if len(content) >= 10 or content.endswith(('.', '!', '?', '', '', '', '\n')):
yield {
"status": "streaming",
"content": response_buffer
}
# 尝试直接从message内容获取
elif hasattr(chunk.choices[0], 'message') and hasattr(chunk.choices[0].message, 'content'):
content = chunk.choices[0].message.content
if content:
response_buffer += content
print(f"从message获取内容: '{content}'")
yield {
"status": "streaming",
"content": response_buffer
}
# 检查是否有finish_reason表示生成结束
elif hasattr(chunk.choices[0], 'finish_reason') and chunk.choices[0].finish_reason:
print(f"生成结束,原因: {chunk.choices[0].finish_reason}")
# 如果没有内容但有思考内容,把思考内容作为正文显示
if not response_buffer and thinking_buffer:
response_buffer = thinking_buffer
yield {
"status": "streaming",
"content": response_buffer
}
except Exception as e:
print(f"解析响应chunk时出错: {str(e)}")
continue
# 确保发送最终的缓冲内容
if thinking_buffer:
yield {
"status": "thinking_complete",
"content": thinking_buffer
}
# 如果推理完成后没有正文内容,则使用思考内容作为最终响应
if not response_buffer and thinking_buffer:
response_buffer = thinking_buffer
# 发送最终响应内容
if response_buffer:
yield {
"status": "streaming",
"content": response_buffer
}
# 发送完成状态
yield {
"status": "completed",
"content": response_buffer
}
except Exception as e:
error_msg = str(e)
print(f"DeepSeek API调用出错: {error_msg}")
# 提供具体的错误信息
if "invalid_api_key" in error_msg.lower():
error_msg = "DeepSeek API密钥无效请检查您的API密钥"
elif "rate_limit" in error_msg.lower():
error_msg = "DeepSeek API请求频率超限请稍后再试"
elif "quota_exceeded" in error_msg.lower():
error_msg = "DeepSeek API配额已用完请续费或等待下个计费周期"
yield {
"status": "error",
"error": f"DeepSeek API错误: {error_msg}"
}
finally:
# 恢复原始环境变量
for key, value in original_env.items():
if value is None:
if key in os.environ:
del os.environ[key]
else:
os.environ[key] = value
except Exception as e: except Exception as e:
error_msg = str(e) error_msg = str(e)
print(f"调用DeepSeek模型时发生错误: {error_msg}")
if "invalid_api_key" in error_msg.lower(): if "invalid_api_key" in error_msg.lower():
error_msg = "Invalid API key provided" error_msg = "API密钥无效请检查设置"
elif "rate_limit" in error_msg.lower(): elif "rate_limit" in error_msg.lower():
error_msg = "Rate limit exceeded. Please try again later." error_msg = "API请求频率超限请稍后再试"
yield { yield {
"status": "error", "status": "error",
"error": f"DeepSeek API error: {error_msg}" "error": f"DeepSeek API错误: {error_msg}"
} }
def analyze_image(self, image_data: str, proxies: dict = None) -> Generator[dict, None, None]: def analyze_image(self, image_data: str, proxies: dict = None) -> Generator[dict, None, None]:
"""Stream DeepSeek's response for image analysis""" """Stream DeepSeek's response for image analysis"""
try: try:
# 检查我们是否有支持图像的模型
if self.model_name == "deepseek-chat" or self.model_name == "deepseek-reasoner":
yield {
"status": "error",
"error": "当前DeepSeek模型不支持图像分析请使用Anthropic或OpenAI的多模态模型"
}
return
# Initial status # Initial status
yield {"status": "started", "content": ""} yield {"status": "started", "content": ""}
# Configure client with proxy if needed # 保存原始环境变量
client_args = { original_env = {
"api_key": self.api_key, 'http_proxy': os.environ.get('http_proxy'),
"base_url": "https://api.deepseek.com" 'https_proxy': os.environ.get('https_proxy')
} }
if proxies:
session = requests.Session()
session.proxies = proxies
client_args["http_client"] = session
client = OpenAI(**client_args) try:
# 如果提供了代理设置,通过环境变量设置
if proxies:
if 'http' in proxies:
os.environ['http_proxy'] = proxies['http']
if 'https' in proxies:
os.environ['https_proxy'] = proxies['https']
# 检查系统提示词是否已包含语言设置指令 # 初始化DeepSeek客户端不再使用session对象
system_prompt = self.system_prompt client = OpenAI(
language = self.language or '中文' api_key=self.api_key,
if not any(phrase in system_prompt for phrase in ['Please respond in', '请用', '使用', '回答']): base_url="https://api.deepseek.com"
system_prompt = f"{system_prompt}\n\n请务必使用{language}回答,无论问题是什么语言。即使在分析图像时也请使用{language}回答。" )
response = client.chat.completions.create( # 检查系统提示词是否已包含语言设置指令
model=self.get_model_identifier(), system_prompt = self.system_prompt
messages=[{ language = self.language or '中文'
'role': 'system', if not any(phrase in system_prompt for phrase in ['Please respond in', '请用', '使用', '回答']):
'content': system_prompt system_prompt = f"{system_prompt}\n\n请务必使用{language}回答,无论问题是什么语言。即使在分析图像时也请使用{language}回答。"
}, {
'role': 'user',
'content': f"Here's an image of a question to analyze: data:image/png;base64,{image_data}"
}],
stream=True
)
for chunk in response: # 构建请求参数
try: params = {
if hasattr(chunk.choices[0].delta, 'reasoning_content'): "model": self.get_model_identifier(),
content = chunk.choices[0].delta.reasoning_content "messages": [
if content: {
yield { 'role': 'system',
"status": "streaming", 'content': system_prompt
"content": content },
} {
elif hasattr(chunk.choices[0].delta, 'content'): 'role': 'user',
content = chunk.choices[0].delta.content 'content': f"Here's an image of a question to analyze: data:image/png;base64,{image_data}"
if content: }
yield { ],
"status": "streaming", "stream": True
"content": content }
}
# 只有非推理模型才设置temperature参数
if not self.model_name.endswith('reasoner') and self.temperature is not None:
params["temperature"] = self.temperature
except Exception as e: response = client.chat.completions.create(**params)
print(f"Chunk processing error: {str(e)}")
continue # 使用两个缓冲区,分别用于常规内容和思考内容
response_buffer = ""
thinking_buffer = ""
for chunk in response:
# 打印chunk以调试
try:
print(f"DeepSeek图像API返回chunk: {chunk}")
except:
print("无法打印chunk")
try:
# 处理推理模型的思考内容
if hasattr(chunk.choices[0].delta, 'reasoning_content'):
content = chunk.choices[0].delta.reasoning_content
if content:
# 累积思考内容
thinking_buffer += content
# 只在积累一定数量的字符或遇到句子结束标记时才发送
if len(content) >= 20 or content.endswith(('.', '!', '?', '', '', '', '\n')):
yield {
"status": "thinking",
"content": thinking_buffer
}
# 处理常规内容
elif hasattr(chunk.choices[0].delta, 'content'):
content = chunk.choices[0].delta.content
if content:
# 累积响应内容
response_buffer += content
print(f"累积图像响应内容: '{content}', 当前buffer: '{response_buffer}'")
# 只在积累一定数量的字符或遇到句子结束标记时才发送
if len(content) >= 10 or content.endswith(('.', '!', '?', '', '', '', '\n')):
yield {
"status": "streaming",
"content": response_buffer
}
# 尝试直接从message内容获取
elif hasattr(chunk.choices[0], 'message') and hasattr(chunk.choices[0].message, 'content'):
content = chunk.choices[0].message.content
if content:
response_buffer += content
print(f"从message获取图像内容: '{content}'")
yield {
"status": "streaming",
"content": response_buffer
}
# 检查是否有finish_reason表示生成结束
elif hasattr(chunk.choices[0], 'finish_reason') and chunk.choices[0].finish_reason:
print(f"图像生成结束,原因: {chunk.choices[0].finish_reason}")
# 如果没有内容但有思考内容,把思考内容作为正文显示
if not response_buffer and thinking_buffer:
response_buffer = thinking_buffer
yield {
"status": "streaming",
"content": response_buffer
}
except Exception as e:
print(f"解析图像响应chunk时出错: {str(e)}")
continue
# Send completion status # 确保发送最终的缓冲内容
yield { if thinking_buffer:
"status": "completed", yield {
"content": "" "status": "thinking_complete",
} "content": thinking_buffer
}
# 如果推理完成后没有正文内容,则使用思考内容作为最终响应
if not response_buffer and thinking_buffer:
response_buffer = thinking_buffer
# 发送最终响应内容
if response_buffer:
yield {
"status": "streaming",
"content": response_buffer
}
# 发送完成状态
yield {
"status": "completed",
"content": response_buffer
}
except Exception as e:
error_msg = str(e)
print(f"DeepSeek API调用出错: {error_msg}")
# 提供具体的错误信息
if "invalid_api_key" in error_msg.lower():
error_msg = "DeepSeek API密钥无效请检查您的API密钥"
elif "rate_limit" in error_msg.lower():
error_msg = "DeepSeek API请求频率超限请稍后再试"
yield {
"status": "error",
"error": f"DeepSeek API错误: {error_msg}"
}
finally:
# 恢复原始环境变量
for key, value in original_env.items():
if value is None:
if key in os.environ:
del os.environ[key]
else:
os.environ[key] = value
except Exception as e: except Exception as e:
error_msg = str(e) error_msg = str(e)
if "invalid_api_key" in error_msg.lower(): if "invalid_api_key" in error_msg.lower():
error_msg = "Invalid API key provided" error_msg = "API密钥无效请检查设置"
elif "rate_limit" in error_msg.lower(): elif "rate_limit" in error_msg.lower():
error_msg = "Rate limit exceeded. Please try again later." error_msg = "API请求频率超限请稍后再试"
yield { yield {
"status": "error", "status": "error",
"error": f"DeepSeek API error: {error_msg}" "error": f"DeepSeek API错误: {error_msg}"
} }

View File

@@ -126,6 +126,15 @@ class ModelFactory:
temperature=temperature, temperature=temperature,
system_prompt=system_prompt system_prompt=system_prompt
) )
# 对于DeepSeek模型传递model_name参数
elif "deepseek" in model_name.lower():
return model_class(
api_key=api_key,
temperature=temperature,
system_prompt=system_prompt,
language=language,
model_name=model_name
)
else: else:
# 对于其他模型,传递所有参数 # 对于其他模型,传递所有参数
return model_class( return model_class(

View File

@@ -698,8 +698,10 @@ class SnapSolver {
this.screenshotImg.src = this.croppedImage; this.screenshotImg.src = this.croppedImage;
this.imagePreview.classList.remove('hidden'); this.imagePreview.classList.remove('hidden');
this.cropBtn.classList.remove('hidden'); this.cropBtn.classList.remove('hidden');
this.sendToClaudeBtn.classList.remove('hidden');
this.extractTextBtn.classList.remove('hidden'); // 根据当前选择的模型类型决定显示哪些按钮
this.updateImageActionButtons();
window.uiManager.showToast('Cropping successful'); window.uiManager.showToast('Cropping successful');
} catch (error) { } catch (error) {
console.error('Cropping error details:', { console.error('Cropping error details:', {
@@ -725,8 +727,8 @@ class SnapSolver {
this.cropper = null; this.cropper = null;
} }
this.cropContainer.classList.add('hidden'); this.cropContainer.classList.add('hidden');
this.sendToClaudeBtn.classList.add('hidden'); // 取消裁剪时隐藏图像预览和相关按钮
this.extractTextBtn.classList.add('hidden'); this.imagePreview.classList.add('hidden');
document.querySelector('.crop-area').innerHTML = ''; document.querySelector('.crop-area').innerHTML = '';
}); });
} }
@@ -858,6 +860,17 @@ class SnapSolver {
if (this.sendToClaudeBtn.disabled) return; if (this.sendToClaudeBtn.disabled) return;
this.sendToClaudeBtn.disabled = true; this.sendToClaudeBtn.disabled = true;
// 获取当前模型设置
const settings = window.settingsManager.getSettings();
const isMultimodalModel = settings.modelInfo?.supportsMultimodal || false;
const modelName = settings.model || '未知';
if (!isMultimodalModel) {
window.uiManager.showToast(`当前选择的模型 ${modelName} 不支持图像分析。请先提取文本或切换到支持多模态的模型。`, 'error');
this.sendToClaudeBtn.disabled = false;
return;
}
if (this.croppedImage) { if (this.croppedImage) {
try { try {
// 清空之前的结果 // 清空之前的结果
@@ -1068,6 +1081,12 @@ class SnapSolver {
// 更新图像操作按钮 // 更新图像操作按钮
this.updateImageActionButtons(); this.updateImageActionButtons();
// 确保DOM完全加载后再次更新按钮状态以防设置未完全加载
setTimeout(() => {
this.updateImageActionButtons();
console.log('延时更新图像操作按钮完成');
}, 1000);
console.log('SnapSolver initialization complete'); console.log('SnapSolver initialization complete');
} }
@@ -1187,25 +1206,40 @@ class SnapSolver {
// 新增方法:根据所选模型更新图像操作按钮 // 新增方法:根据所选模型更新图像操作按钮
updateImageActionButtons() { updateImageActionButtons() {
if (!window.settingsManager) return; if (!window.settingsManager) {
console.error('Settings manager not available');
return;
}
const settings = window.settingsManager.getSettings(); const settings = window.settingsManager.getSettings();
const isMultimodalModel = settings.modelInfo?.supportsMultimodal || false; const isMultimodalModel = settings.modelInfo?.supportsMultimodal || false;
const modelName = settings.model || '未知';
console.log(`更新图像操作按钮 - 当前模型: ${modelName}, 是否支持多模态: ${isMultimodalModel}`);
// 对于截图后的操作按钮显示逻辑 // 对于截图后的操作按钮显示逻辑
if (this.sendToClaudeBtn && this.extractTextBtn) { if (this.sendToClaudeBtn && this.extractTextBtn) {
if (!isMultimodalModel) { if (!isMultimodalModel) {
// 非多模态模型只显示提取文本按钮隐藏发送到AI按钮 // 非多模态模型只显示提取文本按钮隐藏发送到AI按钮
console.log('非多模态模型:隐藏"发送图片至AI"按钮');
this.sendToClaudeBtn.classList.add('hidden'); this.sendToClaudeBtn.classList.add('hidden');
this.extractTextBtn.classList.remove('hidden'); this.extractTextBtn.classList.remove('hidden');
} else { } else {
// 多模态模型:显示两个按钮 // 多模态模型:显示两个按钮
if (!this.imagePreview.classList.contains('hidden')) { if (!this.imagePreview.classList.contains('hidden')) {
// 只有在有图像时才显示按钮 // 只有在有图像时才显示按钮
console.log('多模态模型:显示全部按钮');
this.sendToClaudeBtn.classList.remove('hidden'); this.sendToClaudeBtn.classList.remove('hidden');
this.extractTextBtn.classList.remove('hidden'); this.extractTextBtn.classList.remove('hidden');
} else {
// 无图像时隐藏所有按钮
console.log('无图像:隐藏所有按钮');
this.sendToClaudeBtn.classList.add('hidden');
this.extractTextBtn.classList.add('hidden');
} }
} }
} else {
console.warn('按钮元素不可用');
} }
} }
} }

View File

@@ -434,6 +434,11 @@ class SettingsManager {
this.updateUIBasedOnModelType(); this.updateUIBasedOnModelType();
this.updateModelVersionDisplay(e.target.value); this.updateModelVersionDisplay(e.target.value);
this.saveSettings(); this.saveSettings();
// 通知应用更新图像操作按钮
if (window.app && typeof window.app.updateImageActionButtons === 'function') {
window.app.updateImageActionButtons();
}
}); });
this.temperatureInput.addEventListener('input', (e) => { this.temperatureInput.addEventListener('input', (e) => {