mirror of
https://github.com/Zippland/Snap-Solver.git
synced 2026-01-19 01:21:13 +08:00
gemini pro
This commit is contained in:
15
app.py
15
app.py
@@ -98,6 +98,8 @@ def create_model_instance(model_id, settings, is_reasoning=False):
|
||||
api_key_id = "DeepseekApiKey"
|
||||
elif "qvq" in model_id.lower() or "alibaba" in model_id.lower() or "qwen" in model_id.lower():
|
||||
api_key_id = "AlibabaApiKey"
|
||||
elif "gemini" in model_id.lower() or "google" in model_id.lower():
|
||||
api_key_id = "GoogleApiKey"
|
||||
|
||||
# 首先尝试从本地配置获取API密钥
|
||||
api_key = get_api_key(api_key_id)
|
||||
@@ -758,12 +760,13 @@ def load_api_keys():
|
||||
else:
|
||||
# 如果文件不存在,创建默认配置
|
||||
default_keys = {
|
||||
"AnthropicApiKey": "",
|
||||
"OpenaiApiKey": "",
|
||||
"DeepseekApiKey": "",
|
||||
"AlibabaApiKey": "",
|
||||
"MathpixAppId": "",
|
||||
"MathpixAppKey": ""
|
||||
"AnthropicApiKey": "",
|
||||
"OpenaiApiKey": "",
|
||||
"DeepseekApiKey": "",
|
||||
"AlibabaApiKey": "",
|
||||
"MathpixAppId": "",
|
||||
"MathpixAppKey": "",
|
||||
"GoogleApiKey": ""
|
||||
}
|
||||
save_api_keys(default_keys)
|
||||
return default_keys
|
||||
|
||||
@@ -19,6 +19,11 @@
|
||||
"name": "Alibaba",
|
||||
"api_key_id": "AlibabaApiKey",
|
||||
"class_name": "AlibabaModel"
|
||||
},
|
||||
"google": {
|
||||
"name": "Google",
|
||||
"api_key_id": "GoogleApiKey",
|
||||
"class_name": "GoogleModel"
|
||||
}
|
||||
},
|
||||
"models": {
|
||||
@@ -77,6 +82,22 @@
|
||||
"isReasoning": false,
|
||||
"version": "latest",
|
||||
"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模型,支持图像理解,响应更迅速"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"default": {
|
||||
"a_default": {
|
||||
"name": "默认提示词",
|
||||
"content": "您是一位专业的问题解决专家。请逐步分析问题,找出问题所在,并提供详细的解决方案。始终使用用户偏好的语言回答。",
|
||||
"description": "通用问题解决提示词"
|
||||
@@ -14,7 +14,7 @@
|
||||
"content": "您是一位专业的多选题解析专家。当看到一个多选题时,请:\n1. 仔细阅读题目要求和所有选项\n2. 逐一分析每个选项的正确性\n3. 明确列出所有正确选项\n4. 详细解释每个正确选项的理由\n5. 说明错误选项的问题所在\n6. 归纳总结相关知识点",
|
||||
"description": "专为多选题分析设计的提示词"
|
||||
},
|
||||
"acm_programming": {
|
||||
"programming": {
|
||||
"name": "ACM编程题提示词",
|
||||
"content": "您是一位专业的ACM编程竞赛解题专家。当看到一个编程题时,请:\n1. 分析题目要求、输入输出格式和约束条件\n2. 确定解题思路和算法策略\n3. 分析算法复杂度\n4. 提供完整、可运行的代码实现\n5. 解释代码中的关键部分\n6. 提供一些测试用例及其输出\n7. 讨论可能的优化方向",
|
||||
"description": "专为ACM编程竞赛题设计的提示词"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.2.0",
|
||||
"build_date": "2025-04-02",
|
||||
"version": "1.3.0",
|
||||
"build_date": "2025-04-11",
|
||||
"github_repo": "Zippland/Snap-Solver"
|
||||
}
|
||||
@@ -3,6 +3,7 @@ from .anthropic import AnthropicModel
|
||||
from .openai import OpenAIModel
|
||||
from .deepseek import DeepSeekModel
|
||||
from .alibaba import AlibabaModel
|
||||
from .google import GoogleModel
|
||||
from .factory import ModelFactory
|
||||
|
||||
__all__ = [
|
||||
@@ -11,5 +12,6 @@ __all__ = [
|
||||
'OpenAIModel',
|
||||
'DeepSeekModel',
|
||||
'AlibabaModel',
|
||||
'GoogleModel',
|
||||
'ModelFactory'
|
||||
]
|
||||
|
||||
@@ -57,35 +57,18 @@ class ModelFactory:
|
||||
@classmethod
|
||||
def _initialize_defaults(cls):
|
||||
"""初始化默认模型(当配置加载失败时)"""
|
||||
print("使用默认模型配置")
|
||||
# 导入所有模型类作为备份
|
||||
from .anthropic import AnthropicModel
|
||||
from .openai import OpenAIModel
|
||||
from .deepseek import DeepSeekModel
|
||||
print("配置加载失败,使用空模型列表")
|
||||
|
||||
cls._models = {
|
||||
'claude-3-7-sonnet-20250219': {
|
||||
'class': AnthropicModel,
|
||||
'is_multimodal': True,
|
||||
'is_reasoning': True,
|
||||
'display_name': 'Claude 3.7 Sonnet',
|
||||
'description': '强大的Claude 3.7 Sonnet模型,支持图像理解和思考过程'
|
||||
},
|
||||
'gpt-4o-2024-11-20': {
|
||||
'class': OpenAIModel,
|
||||
'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': {
|
||||
# 不再硬编码模型定义,而是使用空字典
|
||||
cls._models = {}
|
||||
|
||||
# 只保留Mathpix作为基础工具
|
||||
try:
|
||||
# 导入MathpixModel类
|
||||
from .mathpix import MathpixModel
|
||||
|
||||
# 添加Mathpix作为基础工具
|
||||
cls._models['mathpix'] = {
|
||||
'class': MathpixModel,
|
||||
'is_multimodal': True,
|
||||
'is_reasoning': False,
|
||||
@@ -93,7 +76,8 @@ class ModelFactory:
|
||||
'description': '文本提取工具,适用于数学公式和文本',
|
||||
'is_ocr_only': True
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"无法加载基础Mathpix工具: {str(e)}")
|
||||
|
||||
@classmethod
|
||||
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
236
models/google.py
Normal 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)}"
|
||||
}
|
||||
@@ -5,4 +5,5 @@ flask-socketio==5.5.1
|
||||
python-engineio==4.11.2
|
||||
python-socketio==5.12.1
|
||||
requests==2.32.3
|
||||
openai==1.61.0
|
||||
openai==1.61.0
|
||||
google-generativeai==0.7.0
|
||||
@@ -697,28 +697,29 @@ class SettingsManager {
|
||||
* @param {string} modelType 模型类型
|
||||
*/
|
||||
updateVisibleApiKey(modelType) {
|
||||
// 获取所有API密钥状态元素
|
||||
// 高亮显示对应的API密钥
|
||||
const allApiKeys = document.querySelectorAll('.api-key-status');
|
||||
|
||||
// 首先隐藏所有API密钥
|
||||
// 先清除所有高亮
|
||||
allApiKeys.forEach(key => {
|
||||
key.classList.remove('highlight');
|
||||
});
|
||||
|
||||
// 根据当前选择的模型类型,突出显示对应的API密钥
|
||||
// 根据模型类型高亮相应的API密钥
|
||||
let apiKeyToHighlight = null;
|
||||
|
||||
if (modelType.startsWith('claude')) {
|
||||
if (modelType && modelType.toLowerCase().includes('claude')) {
|
||||
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
|
||||
} else if (modelType.startsWith('deepseek')) {
|
||||
} else if (modelType && modelType.toLowerCase().includes('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
|
||||
} else if (modelType && (modelType.toLowerCase().includes('gemini') || modelType.toLowerCase().includes('google'))) {
|
||||
apiKeyToHighlight = document.querySelector('.api-key-status:nth-child(5)'); // Google
|
||||
}
|
||||
|
||||
// 高亮显示对应的API密钥
|
||||
if (apiKeyToHighlight) {
|
||||
apiKeyToHighlight.classList.add('highlight');
|
||||
}
|
||||
@@ -1541,61 +1542,43 @@ class SettingsManager {
|
||||
*/
|
||||
async loadPrompts() {
|
||||
try {
|
||||
// 从服务器获取提示词列表
|
||||
// 从服务器获取提示词配置
|
||||
const response = await fetch('/api/prompts');
|
||||
|
||||
if (response.ok) {
|
||||
// 解析提示词列表
|
||||
// 解析提示词数据
|
||||
const prompts = await response.json();
|
||||
|
||||
// 保存到本地
|
||||
// 存储提示词数据(保持原始顺序)
|
||||
this.prompts = prompts;
|
||||
// 添加提示词顺序属性,记录从服务器获取的原始顺序
|
||||
this.promptsOrder = Object.keys(prompts);
|
||||
|
||||
// 更新提示词选择下拉框
|
||||
if (this.promptSelect) {
|
||||
this.updatePromptSelect();
|
||||
this.updatePromptSelect();
|
||||
}
|
||||
|
||||
// 确定要加载的提示词ID
|
||||
let promptIdToLoad = null;
|
||||
|
||||
// 优先使用之前保存的currentPromptId
|
||||
// 如果当前已有选中的提示词,尝试加载它
|
||||
if (this.currentPromptId && this.prompts[this.currentPromptId]) {
|
||||
promptIdToLoad = this.currentPromptId;
|
||||
}
|
||||
// 其次使用default提示词
|
||||
else if (this.prompts.default) {
|
||||
promptIdToLoad = 'default';
|
||||
}
|
||||
// 最后使用第一个可用的提示词
|
||||
else if (Object.keys(this.prompts).length > 0) {
|
||||
promptIdToLoad = Object.keys(this.prompts)[0];
|
||||
this.loadPrompt(this.currentPromptId);
|
||||
}
|
||||
|
||||
// 如果找到了要加载的提示词,加载它
|
||||
if (promptIdToLoad) {
|
||||
// 否则尝试加载默认提示词
|
||||
else if (this.prompts.default) {
|
||||
this.loadPrompt('default');
|
||||
}
|
||||
// 否则加载第一个提示词
|
||||
else if (Object.keys(this.prompts).length > 0) {
|
||||
const promptIdToLoad = Object.keys(this.prompts)[0];
|
||||
this.loadPrompt(promptIdToLoad);
|
||||
console.log('加载提示词:', promptIdToLoad);
|
||||
} else {
|
||||
// 如果没有提示词,显示默认描述
|
||||
if (this.promptDescriptionElement) {
|
||||
this.promptDescriptionElement.innerHTML = '<p>暂无提示词,请点击"+"创建新提示词</p>';
|
||||
}
|
||||
}
|
||||
|
||||
console.log('提示词加载成功:', this.prompts);
|
||||
} else {
|
||||
console.error('加载提示词失败:', response.status, response.statusText);
|
||||
window.uiManager?.showToast('加载提示词失败', 'error');
|
||||
|
||||
// 显示默认描述
|
||||
if (this.promptDescriptionElement) {
|
||||
this.promptDescriptionElement.innerHTML = '<p>加载提示词失败,请检查网络连接</p>';
|
||||
}
|
||||
console.error('加载提示词失败:', response.statusText);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载提示词错误:', error);
|
||||
window.uiManager?.showToast('加载提示词错误: ' + error.message, 'error');
|
||||
|
||||
// 显示错误描述
|
||||
if (this.promptDescriptionElement) {
|
||||
@@ -1616,20 +1599,39 @@ class SettingsManager {
|
||||
// 清空下拉框
|
||||
this.promptSelect.innerHTML = '';
|
||||
|
||||
// 添加所有提示词选项
|
||||
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);
|
||||
// 按prompts.json中的原始顺序添加提示词选项
|
||||
// 使用this.promptsOrder来保持原始顺序
|
||||
if (this.promptsOrder && this.promptsOrder.length > 0) {
|
||||
for (const promptId of this.promptsOrder) {
|
||||
if (this.prompts[promptId]) {
|
||||
const prompt = this.prompts[promptId];
|
||||
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]) {
|
||||
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) {
|
||||
// 如果之前选中的提示词不存在,选择第一个
|
||||
// 如果没有原始顺序,选择第一个提示词
|
||||
this.promptSelect.value = Object.keys(this.prompts)[0];
|
||||
// 更新当前提示词ID和描述显示
|
||||
this.loadPrompt(this.promptSelect.value);
|
||||
@@ -1731,6 +1733,11 @@ class SettingsManager {
|
||||
description: promptDescription
|
||||
};
|
||||
|
||||
// 如果是新增提示词,将其添加到顺序数组末尾
|
||||
if (isNew && this.promptsOrder && !this.promptsOrder.includes(promptId)) {
|
||||
this.promptsOrder.push(promptId);
|
||||
}
|
||||
|
||||
// 更新提示词选择下拉框
|
||||
this.updatePromptSelect();
|
||||
|
||||
@@ -1779,6 +1786,11 @@ class SettingsManager {
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
// 从顺序数组中移除该提示词
|
||||
if (this.promptsOrder && this.promptsOrder.includes(promptId)) {
|
||||
this.promptsOrder = this.promptsOrder.filter(id => id !== promptId);
|
||||
}
|
||||
|
||||
// 删除本地提示词
|
||||
delete this.prompts[promptId];
|
||||
|
||||
@@ -1786,9 +1798,10 @@ class SettingsManager {
|
||||
this.updatePromptSelect();
|
||||
|
||||
// 如果还有其他提示词,加载第一个
|
||||
const promptIds = Object.keys(this.prompts);
|
||||
if (promptIds.length > 0) {
|
||||
this.loadPrompt(promptIds[0]);
|
||||
if (this.promptsOrder && this.promptsOrder.length > 0) {
|
||||
this.loadPrompt(this.promptsOrder[0]);
|
||||
} else if (Object.keys(this.prompts).length > 0) {
|
||||
this.loadPrompt(Object.keys(this.prompts)[0]);
|
||||
} else {
|
||||
// 如果没有提示词了,清空输入框和描述显示
|
||||
this.systemPromptInput.value = '';
|
||||
@@ -2093,6 +2106,7 @@ class SettingsManager {
|
||||
'OpenaiApiKey': document.getElementById('OpenaiApiKey'),
|
||||
'DeepseekApiKey': document.getElementById('DeepseekApiKey'),
|
||||
'AlibabaApiKey': document.getElementById('AlibabaApiKey'),
|
||||
'GoogleApiKey': document.getElementById('GoogleApiKey'),
|
||||
'mathpixAppId': this.mathpixAppIdInput,
|
||||
'mathpixAppKey': this.mathpixAppKeyInput
|
||||
};
|
||||
@@ -2133,6 +2147,7 @@ class SettingsManager {
|
||||
'OpenaiApiKey': '',
|
||||
'DeepseekApiKey': '',
|
||||
'AlibabaApiKey': '',
|
||||
'GoogleApiKey': '',
|
||||
'MathpixAppId': '',
|
||||
'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
|
||||
|
||||
@@ -5284,3 +5284,42 @@ textarea,
|
||||
.model-dropdown-panel::-webkit-scrollbar {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@
|
||||
</div>
|
||||
<div class="prompt-preview-overlay">
|
||||
<div class="prompt-edit-hint">
|
||||
<i class="fas fa-edit"></i>
|
||||
<!-- 移除重复的编辑图标 -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -403,6 +403,28 @@
|
||||
</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">
|
||||
<span class="key-name">Mathpix App ID:</span>
|
||||
<div class="key-status-wrapper">
|
||||
|
||||
Reference in New Issue
Block a user