gemini pro

This commit is contained in:
Zylan
2025-04-11 17:10:18 +08:00
parent 65d1123eb2
commit e662ff24b5
11 changed files with 467 additions and 94 deletions

15
app.py
View File

@@ -98,6 +98,8 @@ def create_model_instance(model_id, settings, is_reasoning=False):
api_key_id = "DeepseekApiKey" api_key_id = "DeepseekApiKey"
elif "qvq" in model_id.lower() or "alibaba" in model_id.lower() or "qwen" in model_id.lower(): elif "qvq" in model_id.lower() or "alibaba" in model_id.lower() or "qwen" in model_id.lower():
api_key_id = "AlibabaApiKey" api_key_id = "AlibabaApiKey"
elif "gemini" in model_id.lower() or "google" in model_id.lower():
api_key_id = "GoogleApiKey"
# 首先尝试从本地配置获取API密钥 # 首先尝试从本地配置获取API密钥
api_key = get_api_key(api_key_id) api_key = get_api_key(api_key_id)
@@ -758,12 +760,13 @@ def load_api_keys():
else: else:
# 如果文件不存在,创建默认配置 # 如果文件不存在,创建默认配置
default_keys = { default_keys = {
"AnthropicApiKey": "", "AnthropicApiKey": "",
"OpenaiApiKey": "", "OpenaiApiKey": "",
"DeepseekApiKey": "", "DeepseekApiKey": "",
"AlibabaApiKey": "", "AlibabaApiKey": "",
"MathpixAppId": "", "MathpixAppId": "",
"MathpixAppKey": "" "MathpixAppKey": "",
"GoogleApiKey": ""
} }
save_api_keys(default_keys) save_api_keys(default_keys)
return default_keys return default_keys

View File

@@ -19,6 +19,11 @@
"name": "Alibaba", "name": "Alibaba",
"api_key_id": "AlibabaApiKey", "api_key_id": "AlibabaApiKey",
"class_name": "AlibabaModel" "class_name": "AlibabaModel"
},
"google": {
"name": "Google",
"api_key_id": "GoogleApiKey",
"class_name": "GoogleModel"
} }
}, },
"models": { "models": {
@@ -77,6 +82,22 @@
"isReasoning": false, "isReasoning": false,
"version": "latest", "version": "latest",
"description": "阿里通义千问VL-MAX模型视觉理解能力最强支持图像理解和复杂任务" "description": "阿里通义千问VL-MAX模型视觉理解能力最强支持图像理解和复杂任务"
},
"gemini-2.5-pro-preview-03-25": {
"name": "Gemini 2.5 Pro",
"provider": "google",
"supportsMultimodal": true,
"isReasoning": true,
"version": "preview-03-25",
"description": "Google最强大的Gemini 2.5 Pro模型支持图像理解"
},
"gemini-2.0-flash": {
"name": "Gemini 2.0 Flash",
"provider": "google",
"supportsMultimodal": true,
"isReasoning": false,
"version": "latest",
"description": "Google更快速的Gemini 2.0 Flash模型支持图像理解响应更迅速"
} }
} }
} }

View File

@@ -1,5 +1,5 @@
{ {
"default": { "a_default": {
"name": "默认提示词", "name": "默认提示词",
"content": "您是一位专业的问题解决专家。请逐步分析问题,找出问题所在,并提供详细的解决方案。始终使用用户偏好的语言回答。", "content": "您是一位专业的问题解决专家。请逐步分析问题,找出问题所在,并提供详细的解决方案。始终使用用户偏好的语言回答。",
"description": "通用问题解决提示词" "description": "通用问题解决提示词"
@@ -14,7 +14,7 @@
"content": "您是一位专业的多选题解析专家。当看到一个多选题时,请:\n1. 仔细阅读题目要求和所有选项\n2. 逐一分析每个选项的正确性\n3. 明确列出所有正确选项\n4. 详细解释每个正确选项的理由\n5. 说明错误选项的问题所在\n6. 归纳总结相关知识点", "content": "您是一位专业的多选题解析专家。当看到一个多选题时,请:\n1. 仔细阅读题目要求和所有选项\n2. 逐一分析每个选项的正确性\n3. 明确列出所有正确选项\n4. 详细解释每个正确选项的理由\n5. 说明错误选项的问题所在\n6. 归纳总结相关知识点",
"description": "专为多选题分析设计的提示词" "description": "专为多选题分析设计的提示词"
}, },
"acm_programming": { "programming": {
"name": "ACM编程题提示词", "name": "ACM编程题提示词",
"content": "您是一位专业的ACM编程竞赛解题专家。当看到一个编程题时\n1. 分析题目要求、输入输出格式和约束条件\n2. 确定解题思路和算法策略\n3. 分析算法复杂度\n4. 提供完整、可运行的代码实现\n5. 解释代码中的关键部分\n6. 提供一些测试用例及其输出\n7. 讨论可能的优化方向", "content": "您是一位专业的ACM编程竞赛解题专家。当看到一个编程题时\n1. 分析题目要求、输入输出格式和约束条件\n2. 确定解题思路和算法策略\n3. 分析算法复杂度\n4. 提供完整、可运行的代码实现\n5. 解释代码中的关键部分\n6. 提供一些测试用例及其输出\n7. 讨论可能的优化方向",
"description": "专为ACM编程竞赛题设计的提示词" "description": "专为ACM编程竞赛题设计的提示词"

View File

@@ -1,5 +1,5 @@
{ {
"version": "1.2.0", "version": "1.3.0",
"build_date": "2025-04-02", "build_date": "2025-04-11",
"github_repo": "Zippland/Snap-Solver" "github_repo": "Zippland/Snap-Solver"
} }

View File

@@ -3,6 +3,7 @@ from .anthropic import AnthropicModel
from .openai import OpenAIModel from .openai import OpenAIModel
from .deepseek import DeepSeekModel from .deepseek import DeepSeekModel
from .alibaba import AlibabaModel from .alibaba import AlibabaModel
from .google import GoogleModel
from .factory import ModelFactory from .factory import ModelFactory
__all__ = [ __all__ = [
@@ -11,5 +12,6 @@ __all__ = [
'OpenAIModel', 'OpenAIModel',
'DeepSeekModel', 'DeepSeekModel',
'AlibabaModel', 'AlibabaModel',
'GoogleModel',
'ModelFactory' 'ModelFactory'
] ]

View File

@@ -57,35 +57,18 @@ class ModelFactory:
@classmethod @classmethod
def _initialize_defaults(cls): def _initialize_defaults(cls):
"""初始化默认模型(当配置加载失败时)""" """初始化默认模型(当配置加载失败时)"""
print("使用默认模型配置") print("配置加载失败,使用空模型列表")
# 导入所有模型类作为备份
from .anthropic import AnthropicModel
from .openai import OpenAIModel
from .deepseek import DeepSeekModel
cls._models = { # 不再硬编码模型定义,而是使用空字典
'claude-3-7-sonnet-20250219': { cls._models = {}
'class': AnthropicModel,
'is_multimodal': True, # 只保留Mathpix作为基础工具
'is_reasoning': True, try:
'display_name': 'Claude 3.7 Sonnet', # 导入MathpixModel类
'description': '强大的Claude 3.7 Sonnet模型支持图像理解和思考过程' from .mathpix import MathpixModel
},
'gpt-4o-2024-11-20': { # 添加Mathpix作为基础工具
'class': OpenAIModel, cls._models['mathpix'] = {
'is_multimodal': True,
'is_reasoning': False,
'display_name': 'GPT-4o',
'description': 'OpenAI的GPT-4o模型支持图像理解'
},
'deepseek-reasoner': {
'class': DeepSeekModel,
'is_multimodal': False,
'is_reasoning': True,
'display_name': 'DeepSeek Reasoner',
'description': 'DeepSeek推理模型提供详细思考过程仅支持文本'
},
'mathpix': {
'class': MathpixModel, 'class': MathpixModel,
'is_multimodal': True, 'is_multimodal': True,
'is_reasoning': False, 'is_reasoning': False,
@@ -93,7 +76,8 @@ class ModelFactory:
'description': '文本提取工具,适用于数学公式和文本', 'description': '文本提取工具,适用于数学公式和文本',
'is_ocr_only': True 'is_ocr_only': True
} }
} except Exception as e:
print(f"无法加载基础Mathpix工具: {str(e)}")
@classmethod @classmethod
def create_model(cls, model_name: str, api_key: str, temperature: float = 0.7, system_prompt: str = None, language: str = None) -> BaseModel: def create_model(cls, model_name: str, api_key: str, temperature: float = 0.7, system_prompt: str = None, language: str = None) -> BaseModel:

236
models/google.py Normal file
View File

@@ -0,0 +1,236 @@
import json
import os
import base64
from typing import Generator, Dict, Any, Optional, List
import google.generativeai as genai
from .base import BaseModel
class GoogleModel(BaseModel):
"""
Google Gemini API模型实现类
支持Gemini 2.5 Pro等模型可处理文本和图像输入
"""
def __init__(self, api_key: str, temperature: float = 0.7, system_prompt: str = None, language: str = None, model_name: str = None):
"""
初始化Google模型
Args:
api_key: Google API密钥
temperature: 生成温度
system_prompt: 系统提示词
language: 首选语言
model_name: 指定具体模型名称,如不指定则使用默认值
"""
super().__init__(api_key, temperature, system_prompt, language)
self.model_name = model_name or self.get_model_identifier()
self.max_tokens = 8192 # 默认最大输出token数
# 配置Google API
genai.configure(api_key=api_key)
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:
1. First read and understand the question carefully
2. Break down the key components of the question
3. Provide a clear, step-by-step solution
4. If relevant, explain any concepts or theories involved
5. If there are multiple approaches, explain the most efficient one first"""
def get_model_identifier(self) -> str:
"""返回默认的模型标识符"""
return "gemini-2.5-pro-preview-03-25"
def analyze_text(self, text: str, proxies: dict = None) -> Generator[dict, None, None]:
"""流式生成文本响应"""
try:
yield {"status": "started"}
# 设置环境变量代理(如果提供)
original_proxies = None
if proxies:
original_proxies = {
'http_proxy': os.environ.get('http_proxy'),
'https_proxy': os.environ.get('https_proxy')
}
if 'http' in proxies:
os.environ['http_proxy'] = proxies['http']
if 'https' in proxies:
os.environ['https_proxy'] = proxies['https']
try:
# 初始化模型
model = genai.GenerativeModel(self.model_name)
# 获取最大输出Token设置
max_tokens = self.max_tokens if hasattr(self, 'max_tokens') else 8192
# 创建配置参数
generation_config = {
'temperature': self.temperature,
'max_output_tokens': max_tokens,
'top_p': 0.95,
'top_k': 64,
}
# 构建提示
prompt_parts = []
# 添加系统提示词
if self.system_prompt:
prompt_parts.append(self.system_prompt)
# 添加用户查询
if self.language and self.language != 'auto':
prompt_parts.append(f"请使用{self.language}回答以下问题: {text}")
else:
prompt_parts.append(text)
# 初始化响应缓冲区
response_buffer = ""
# 流式生成响应
response = model.generate_content(
prompt_parts,
generation_config=generation_config,
stream=True
)
for chunk in response:
if not chunk.text:
continue
# 累积响应文本
response_buffer += chunk.text
# 发送响应进度
if len(chunk.text) >= 10 or chunk.text.endswith(('.', '!', '?', '', '', '', '\n')):
yield {
"status": "streaming",
"content": response_buffer
}
# 确保发送完整的最终内容
yield {
"status": "completed",
"content": response_buffer
}
finally:
# 恢复原始代理设置
if original_proxies:
for key, value in original_proxies.items():
if value is None:
if key in os.environ:
del os.environ[key]
else:
os.environ[key] = value
except Exception as e:
yield {
"status": "error",
"error": f"Gemini API错误: {str(e)}"
}
def analyze_image(self, image_data: str, proxies: dict = None) -> Generator[dict, None, None]:
"""分析图像并流式生成响应"""
try:
yield {"status": "started"}
# 设置环境变量代理(如果提供)
original_proxies = None
if proxies:
original_proxies = {
'http_proxy': os.environ.get('http_proxy'),
'https_proxy': os.environ.get('https_proxy')
}
if 'http' in proxies:
os.environ['http_proxy'] = proxies['http']
if 'https' in proxies:
os.environ['https_proxy'] = proxies['https']
try:
# 初始化模型
model = genai.GenerativeModel(self.model_name)
# 获取最大输出Token设置
max_tokens = self.max_tokens if hasattr(self, 'max_tokens') else 8192
# 创建配置参数
generation_config = {
'temperature': self.temperature,
'max_output_tokens': max_tokens,
'top_p': 0.95,
'top_k': 64,
}
# 构建提示词
prompt_parts = []
# 添加系统提示词
if self.system_prompt:
prompt_parts.append(self.system_prompt)
# 添加默认图像分析指令
if self.language and self.language != 'auto':
prompt_parts.append(f"请使用{self.language}分析这张图片并提供详细解答。")
else:
prompt_parts.append("请分析这张图片并提供详细解答。")
# 处理图像数据
if image_data.startswith('data:image'):
# 如果是data URI提取base64部分
image_data = image_data.split(',', 1)[1]
# 使用genai的特定方法处理图像
image_part = {
"mime_type": "image/jpeg",
"data": base64.b64decode(image_data)
}
prompt_parts.append(image_part)
# 初始化响应缓冲区
response_buffer = ""
# 流式生成响应
response = model.generate_content(
prompt_parts,
generation_config=generation_config,
stream=True
)
for chunk in response:
if not chunk.text:
continue
# 累积响应文本
response_buffer += chunk.text
# 发送响应进度
if len(chunk.text) >= 10 or chunk.text.endswith(('.', '!', '?', '', '', '', '\n')):
yield {
"status": "streaming",
"content": response_buffer
}
# 确保发送完整的最终内容
yield {
"status": "completed",
"content": response_buffer
}
finally:
# 恢复原始代理设置
if original_proxies:
for key, value in original_proxies.items():
if value is None:
if key in os.environ:
del os.environ[key]
else:
os.environ[key] = value
except Exception as e:
yield {
"status": "error",
"error": f"Gemini图像分析错误: {str(e)}"
}

View File

@@ -5,4 +5,5 @@ flask-socketio==5.5.1
python-engineio==4.11.2 python-engineio==4.11.2
python-socketio==5.12.1 python-socketio==5.12.1
requests==2.32.3 requests==2.32.3
openai==1.61.0 openai==1.61.0
google-generativeai==0.7.0

View File

@@ -697,28 +697,29 @@ class SettingsManager {
* @param {string} modelType 模型类型 * @param {string} modelType 模型类型
*/ */
updateVisibleApiKey(modelType) { updateVisibleApiKey(modelType) {
// 获取所有API密钥状态元素 // 高亮显示对应的API密钥
const allApiKeys = document.querySelectorAll('.api-key-status'); const allApiKeys = document.querySelectorAll('.api-key-status');
// 首先隐藏所有API密钥 // 先清除所有高亮
allApiKeys.forEach(key => { allApiKeys.forEach(key => {
key.classList.remove('highlight'); key.classList.remove('highlight');
}); });
// 根据当前选择的模型类型,突出显示对应的API密钥 // 根据模型类型高亮相应的API密钥
let apiKeyToHighlight = null; let apiKeyToHighlight = null;
if (modelType.startsWith('claude')) { if (modelType && modelType.toLowerCase().includes('claude')) {
apiKeyToHighlight = document.querySelector('.api-key-status:nth-child(1)'); // Anthropic apiKeyToHighlight = document.querySelector('.api-key-status:nth-child(1)'); // Anthropic
} else if (modelType.startsWith('gpt')) { } else if (modelType && (modelType.toLowerCase().includes('gpt') || modelType.toLowerCase().includes('openai'))) {
apiKeyToHighlight = document.querySelector('.api-key-status:nth-child(2)'); // OpenAI apiKeyToHighlight = document.querySelector('.api-key-status:nth-child(2)'); // OpenAI
} else if (modelType.startsWith('deepseek')) { } else if (modelType && modelType.toLowerCase().includes('deepseek')) {
apiKeyToHighlight = document.querySelector('.api-key-status:nth-child(3)'); // DeepSeek apiKeyToHighlight = document.querySelector('.api-key-status:nth-child(3)'); // DeepSeek
} else if (modelType.startsWith('qwen')) { } else if (modelType && (modelType.toLowerCase().includes('qwen') || modelType.toLowerCase().includes('qvq') || modelType.toLowerCase().includes('alibaba'))) {
apiKeyToHighlight = document.querySelector('.api-key-status:nth-child(4)'); // Alibaba apiKeyToHighlight = document.querySelector('.api-key-status:nth-child(4)'); // Alibaba
} else if (modelType && (modelType.toLowerCase().includes('gemini') || modelType.toLowerCase().includes('google'))) {
apiKeyToHighlight = document.querySelector('.api-key-status:nth-child(5)'); // Google
} }
// 高亮显示对应的API密钥
if (apiKeyToHighlight) { if (apiKeyToHighlight) {
apiKeyToHighlight.classList.add('highlight'); apiKeyToHighlight.classList.add('highlight');
} }
@@ -1541,61 +1542,43 @@ class SettingsManager {
*/ */
async loadPrompts() { async loadPrompts() {
try { try {
// 从服务器获取提示词列表 // 从服务器获取提示词配置
const response = await fetch('/api/prompts'); const response = await fetch('/api/prompts');
if (response.ok) { if (response.ok) {
// 解析提示词列表 // 解析提示词数据
const prompts = await response.json(); const prompts = await response.json();
// 保存到本地 // 存储提示词数据(保持原始顺序)
this.prompts = prompts; this.prompts = prompts;
// 添加提示词顺序属性,记录从服务器获取的原始顺序
this.promptsOrder = Object.keys(prompts);
// 更新提示词选择下拉框 // 更新提示词选择下拉框
if (this.promptSelect) { if (this.promptSelect) {
this.updatePromptSelect(); this.updatePromptSelect();
} }
// 确定要加载的提示词ID // 如果当前已有选中的提示词,尝试加载它
let promptIdToLoad = null;
// 优先使用之前保存的currentPromptId
if (this.currentPromptId && this.prompts[this.currentPromptId]) { if (this.currentPromptId && this.prompts[this.currentPromptId]) {
promptIdToLoad = this.currentPromptId; this.loadPrompt(this.currentPromptId);
}
// 其次使用default提示词
else if (this.prompts.default) {
promptIdToLoad = 'default';
}
// 最后使用第一个可用的提示词
else if (Object.keys(this.prompts).length > 0) {
promptIdToLoad = Object.keys(this.prompts)[0];
} }
// 否则尝试加载默认提示词
// 如果找到了要加载的提示词,加载它 else if (this.prompts.default) {
if (promptIdToLoad) { this.loadPrompt('default');
}
// 否则加载第一个提示词
else if (Object.keys(this.prompts).length > 0) {
const promptIdToLoad = Object.keys(this.prompts)[0];
this.loadPrompt(promptIdToLoad); this.loadPrompt(promptIdToLoad);
console.log('加载提示词:', promptIdToLoad);
} else {
// 如果没有提示词,显示默认描述
if (this.promptDescriptionElement) {
this.promptDescriptionElement.innerHTML = '<p>暂无提示词,请点击"+"创建新提示词</p>';
}
} }
console.log('提示词加载成功:', this.prompts); console.log('提示词加载成功:', this.prompts);
} else { } else {
console.error('加载提示词失败:', response.status, response.statusText); console.error('加载提示词失败:', response.statusText);
window.uiManager?.showToast('加载提示词失败', 'error');
// 显示默认描述
if (this.promptDescriptionElement) {
this.promptDescriptionElement.innerHTML = '<p>加载提示词失败,请检查网络连接</p>';
}
} }
} catch (error) { } catch (error) {
console.error('加载提示词错误:', error); console.error('加载提示词错误:', error);
window.uiManager?.showToast('加载提示词错误: ' + error.message, 'error');
// 显示错误描述 // 显示错误描述
if (this.promptDescriptionElement) { if (this.promptDescriptionElement) {
@@ -1616,20 +1599,39 @@ class SettingsManager {
// 清空下拉框 // 清空下拉框
this.promptSelect.innerHTML = ''; this.promptSelect.innerHTML = '';
// 添加所有提示词选项 // 按prompts.json中的原始顺序添加提示词选项
for (const promptId in this.prompts) { // 使用this.promptsOrder来保持原始顺序
const prompt = this.prompts[promptId]; if (this.promptsOrder && this.promptsOrder.length > 0) {
const option = document.createElement('option'); for (const promptId of this.promptsOrder) {
option.value = promptId; if (this.prompts[promptId]) {
option.textContent = prompt.name; const prompt = this.prompts[promptId];
this.promptSelect.appendChild(option); const option = document.createElement('option');
option.value = promptId;
option.textContent = prompt.name;
this.promptSelect.appendChild(option);
}
}
} else {
// 如果没有保存顺序,则使用对象键的顺序(不推荐,但作为后备方案)
for (const promptId in this.prompts) {
const prompt = this.prompts[promptId];
const option = document.createElement('option');
option.value = promptId;
option.textContent = prompt.name;
this.promptSelect.appendChild(option);
}
} }
// 恢复之前选中的提示词或选择第一个提示词 // 恢复之前选中的提示词或选择第一个提示词
if (currentPromptId && this.prompts[currentPromptId]) { if (currentPromptId && this.prompts[currentPromptId]) {
this.promptSelect.value = currentPromptId; this.promptSelect.value = currentPromptId;
} else if (this.promptsOrder && this.promptsOrder.length > 0) {
// 选择原始顺序的第一个提示词
this.promptSelect.value = this.promptsOrder[0];
// 更新当前提示词ID和描述显示
this.loadPrompt(this.promptSelect.value);
} else if (Object.keys(this.prompts).length > 0) { } else if (Object.keys(this.prompts).length > 0) {
// 如果之前选中的提示词不存在,选择第一个 // 如果没有原始顺序,选择第一个提示词
this.promptSelect.value = Object.keys(this.prompts)[0]; this.promptSelect.value = Object.keys(this.prompts)[0];
// 更新当前提示词ID和描述显示 // 更新当前提示词ID和描述显示
this.loadPrompt(this.promptSelect.value); this.loadPrompt(this.promptSelect.value);
@@ -1731,6 +1733,11 @@ class SettingsManager {
description: promptDescription description: promptDescription
}; };
// 如果是新增提示词,将其添加到顺序数组末尾
if (isNew && this.promptsOrder && !this.promptsOrder.includes(promptId)) {
this.promptsOrder.push(promptId);
}
// 更新提示词选择下拉框 // 更新提示词选择下拉框
this.updatePromptSelect(); this.updatePromptSelect();
@@ -1779,6 +1786,11 @@ class SettingsManager {
if (response.ok) { if (response.ok) {
const result = await response.json(); const result = await response.json();
if (result.success) { if (result.success) {
// 从顺序数组中移除该提示词
if (this.promptsOrder && this.promptsOrder.includes(promptId)) {
this.promptsOrder = this.promptsOrder.filter(id => id !== promptId);
}
// 删除本地提示词 // 删除本地提示词
delete this.prompts[promptId]; delete this.prompts[promptId];
@@ -1786,9 +1798,10 @@ class SettingsManager {
this.updatePromptSelect(); this.updatePromptSelect();
// 如果还有其他提示词,加载第一个 // 如果还有其他提示词,加载第一个
const promptIds = Object.keys(this.prompts); if (this.promptsOrder && this.promptsOrder.length > 0) {
if (promptIds.length > 0) { this.loadPrompt(this.promptsOrder[0]);
this.loadPrompt(promptIds[0]); } else if (Object.keys(this.prompts).length > 0) {
this.loadPrompt(Object.keys(this.prompts)[0]);
} else { } else {
// 如果没有提示词了,清空输入框和描述显示 // 如果没有提示词了,清空输入框和描述显示
this.systemPromptInput.value = ''; this.systemPromptInput.value = '';
@@ -2093,6 +2106,7 @@ class SettingsManager {
'OpenaiApiKey': document.getElementById('OpenaiApiKey'), 'OpenaiApiKey': document.getElementById('OpenaiApiKey'),
'DeepseekApiKey': document.getElementById('DeepseekApiKey'), 'DeepseekApiKey': document.getElementById('DeepseekApiKey'),
'AlibabaApiKey': document.getElementById('AlibabaApiKey'), 'AlibabaApiKey': document.getElementById('AlibabaApiKey'),
'GoogleApiKey': document.getElementById('GoogleApiKey'),
'mathpixAppId': this.mathpixAppIdInput, 'mathpixAppId': this.mathpixAppIdInput,
'mathpixAppKey': this.mathpixAppKeyInput 'mathpixAppKey': this.mathpixAppKeyInput
}; };
@@ -2133,6 +2147,7 @@ class SettingsManager {
'OpenaiApiKey': '', 'OpenaiApiKey': '',
'DeepseekApiKey': '', 'DeepseekApiKey': '',
'AlibabaApiKey': '', 'AlibabaApiKey': '',
'GoogleApiKey': '',
'MathpixAppId': '', 'MathpixAppId': '',
'MathpixAppKey': '' 'MathpixAppKey': ''
}; };
@@ -2154,6 +2169,56 @@ class SettingsManager {
}); });
} }
} }
/**
* 打开提示词编辑对话框
* @param {string|null} promptId 要编辑的提示词ID为空则表示新建
*/
openPromptDialog(promptId = null) {
// 判断是否为新建提示词
const isNew = !promptId || !this.prompts[promptId];
if (isNew) {
// 新建提示词 - 清空输入框
this.promptIdInput.value = '';
this.promptNameInput.value = '';
this.promptContentInput.value = '';
this.promptDescriptionInput.value = '';
// 设置对话框标题
if (this.promptDialogTitle) {
this.promptDialogTitle.textContent = '新建提示词';
}
// 启用ID输入框
this.promptIdInput.disabled = false;
// 设置保存按钮动作
this.promptSaveBtn.onclick = () => this.savePrompt(true); // 明确传递isNew=true
} else {
// 编辑现有提示词 - 填充现有内容
this.promptIdInput.value = promptId;
this.promptNameInput.value = this.prompts[promptId].name;
this.promptContentInput.value = this.prompts[promptId].content;
this.promptDescriptionInput.value = this.prompts[promptId].description || '';
// 设置对话框标题
if (this.promptDialogTitle) {
this.promptDialogTitle.textContent = '编辑提示词';
}
// 禁用ID输入框不允许修改ID
this.promptIdInput.disabled = true;
// 设置保存按钮动作
this.promptSaveBtn.onclick = () => this.savePrompt(false); // 明确传递isNew=false
}
// 显示对话框
if (this.promptDialogMask) {
this.promptDialogMask.classList.remove('hidden');
}
}
} }
// Export for use in other modules // Export for use in other modules

View File

@@ -5284,3 +5284,42 @@ textarea,
.model-dropdown-panel::-webkit-scrollbar { .model-dropdown-panel::-webkit-scrollbar {
width: 6px; width: 6px;
} }
/* 提示词预览样式 */
.prompt-preview {
position: relative;
cursor: pointer;
}
.prompt-preview-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 8px;
background-color: rgba(0, 0, 0, 0.03);
opacity: 0;
transition: opacity 0.2s ease;
}
.prompt-preview:hover .prompt-preview-overlay {
opacity: 1;
}
.prompt-edit-hint {
position: absolute;
right: 10px;
bottom: 10px;
/* 不显示图标,因为上方已有编辑按钮 */
}
[data-theme="dark"] .prompt-preview-overlay {
background-color: rgba(255, 255, 255, 0.05);
}
@media (max-width: 768px) {
.prompt-preview-overlay {
display: none;
}
}

View File

@@ -300,7 +300,7 @@
</div> </div>
<div class="prompt-preview-overlay"> <div class="prompt-preview-overlay">
<div class="prompt-edit-hint"> <div class="prompt-edit-hint">
<i class="fas fa-edit"></i> <!-- 移除重复的编辑图标 -->
</div> </div>
</div> </div>
</div> </div>
@@ -403,6 +403,28 @@
</div> </div>
</div> </div>
</div> </div>
<div class="api-key-status">
<span class="key-name">Google API:</span>
<div class="key-status-wrapper">
<!-- 显示状态 -->
<div class="key-display">
<span id="GoogleApiKeyStatus" class="key-status" data-key="GoogleApiKey">未设置</span>
<button class="btn-icon edit-api-key" data-key-type="GoogleApiKey" title="编辑此密钥">
<i class="fas fa-edit"></i>
</button>
</div>
<!-- 编辑状态 -->
<div class="key-edit hidden">
<input type="password" class="key-input" data-key-type="GoogleApiKey" placeholder="输入 Google API key">
<button class="btn-icon toggle-visibility">
<i class="fas fa-eye"></i>
</button>
<button class="btn-icon save-api-key" data-key-type="GoogleApiKey" title="保存密钥">
<i class="fas fa-save"></i>
</button>
</div>
</div>
</div>
<div class="api-key-status"> <div class="api-key-status">
<span class="key-name">Mathpix App ID:</span> <span class="key-name">Mathpix App ID:</span>
<div class="key-status-wrapper"> <div class="key-status-wrapper">