mirror of
https://github.com/Zippland/Snap-Solver.git
synced 2026-02-02 09:38:54 +08:00
实现提示词管理功能,包括加载、保存、删除提示词的API接口和前端交互;更新样式以支持提示词管理界面,优化用户体验。
This commit is contained in:
174
app.py
174
app.py
@@ -29,11 +29,18 @@ socketio = SocketIO(
|
||||
logger=True # 启用Socket.IO日志
|
||||
)
|
||||
|
||||
# 添加配置文件路径
|
||||
CONFIG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config')
|
||||
# 常量定义
|
||||
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
CONFIG_DIR = os.path.join(CURRENT_DIR, 'config')
|
||||
STATIC_DIR = os.path.join(CURRENT_DIR, 'static')
|
||||
# 确保配置目录存在
|
||||
os.makedirs(CONFIG_DIR, exist_ok=True)
|
||||
|
||||
# API密钥配置文件路径
|
||||
# 密钥和其他配置文件路径
|
||||
API_KEYS_FILE = os.path.join(CONFIG_DIR, 'api_keys.json')
|
||||
VERSION_FILE = os.path.join(CONFIG_DIR, 'version.json')
|
||||
UPDATE_INFO_FILE = os.path.join(CONFIG_DIR, 'update_info.json')
|
||||
PROMPT_FILE = os.path.join(CONFIG_DIR, 'prompts.json') # 新增提示词配置文件路径
|
||||
|
||||
# 跟踪用户生成任务的字典
|
||||
generation_tasks = {}
|
||||
@@ -132,7 +139,7 @@ def stream_model_response(response_generator, sid, model_name=None):
|
||||
print(f"使用推理模型 {model_name},将显示思考过程")
|
||||
|
||||
# 初始化:发送开始状态
|
||||
socketio.emit('claude_response', {
|
||||
socketio.emit('ai_response', {
|
||||
'status': 'started',
|
||||
'content': '',
|
||||
'is_reasoning': is_reasoning
|
||||
@@ -169,7 +176,7 @@ def stream_model_response(response_generator, sid, model_name=None):
|
||||
# 控制发送频率,至少间隔0.3秒
|
||||
current_time = time.time()
|
||||
if current_time - last_emit_time >= 0.3:
|
||||
socketio.emit('claude_response', {
|
||||
socketio.emit('ai_response', {
|
||||
'status': 'thinking',
|
||||
'content': thinking_buffer,
|
||||
'is_reasoning': True
|
||||
@@ -183,7 +190,7 @@ def stream_model_response(response_generator, sid, model_name=None):
|
||||
thinking_buffer = content
|
||||
|
||||
print(f"Thinking complete, total length: {len(thinking_buffer)} chars")
|
||||
socketio.emit('claude_response', {
|
||||
socketio.emit('ai_response', {
|
||||
'status': 'thinking_complete',
|
||||
'content': thinking_buffer,
|
||||
'is_reasoning': True
|
||||
@@ -196,7 +203,7 @@ def stream_model_response(response_generator, sid, model_name=None):
|
||||
# 控制发送频率,至少间隔0.3秒
|
||||
current_time = time.time()
|
||||
if current_time - last_emit_time >= 0.3:
|
||||
socketio.emit('claude_response', {
|
||||
socketio.emit('ai_response', {
|
||||
'status': 'streaming',
|
||||
'content': response_buffer,
|
||||
'is_reasoning': is_reasoning
|
||||
@@ -205,7 +212,7 @@ def stream_model_response(response_generator, sid, model_name=None):
|
||||
|
||||
elif status == 'completed':
|
||||
# 确保发送最终完整内容
|
||||
socketio.emit('claude_response', {
|
||||
socketio.emit('ai_response', {
|
||||
'status': 'completed',
|
||||
'content': content or response_buffer,
|
||||
'is_reasoning': is_reasoning
|
||||
@@ -215,18 +222,18 @@ def stream_model_response(response_generator, sid, model_name=None):
|
||||
elif status == 'error':
|
||||
# 错误状态直接转发
|
||||
response['is_reasoning'] = is_reasoning
|
||||
socketio.emit('claude_response', response, room=sid)
|
||||
socketio.emit('ai_response', response, room=sid)
|
||||
print(f"Error: {response.get('error', 'Unknown error')}")
|
||||
|
||||
# 其他状态直接转发
|
||||
else:
|
||||
response['is_reasoning'] = is_reasoning
|
||||
socketio.emit('claude_response', response, room=sid)
|
||||
socketio.emit('ai_response', response, room=sid)
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Streaming error: {str(e)}"
|
||||
print(error_msg)
|
||||
socketio.emit('claude_response', {
|
||||
socketio.emit('ai_response', {
|
||||
'status': 'error',
|
||||
'error': error_msg,
|
||||
'is_reasoning': model_name and ModelFactory.is_reasoning(model_name)
|
||||
@@ -349,7 +356,7 @@ def handle_stop_generation():
|
||||
stop_event.set()
|
||||
|
||||
# 发送已停止状态
|
||||
socketio.emit('claude_response', {
|
||||
socketio.emit('ai_response', {
|
||||
'status': 'stopped',
|
||||
'content': '生成已停止'
|
||||
}, room=sid)
|
||||
@@ -410,7 +417,7 @@ def handle_analyze_text(data):
|
||||
print(f"分析文本生成被用户 {sid} 停止")
|
||||
break
|
||||
|
||||
socketio.emit('claude_response', response, room=sid)
|
||||
socketio.emit('ai_response', response, room=sid)
|
||||
finally:
|
||||
# 清理任务
|
||||
if sid in generation_tasks:
|
||||
@@ -473,7 +480,7 @@ def handle_analyze_image(data):
|
||||
print(f"分析图像生成被用户 {sid} 停止")
|
||||
break
|
||||
|
||||
socketio.emit('claude_response', response, room=sid)
|
||||
socketio.emit('ai_response', response, room=sid)
|
||||
finally:
|
||||
# 清理任务
|
||||
if sid in generation_tasks:
|
||||
@@ -522,6 +529,60 @@ def load_model_config():
|
||||
"models": {}
|
||||
}
|
||||
|
||||
def load_prompts():
|
||||
"""加载系统提示词配置"""
|
||||
try:
|
||||
if os.path.exists(PROMPT_FILE):
|
||||
with open(PROMPT_FILE, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
else:
|
||||
# 如果文件不存在,创建默认提示词配置
|
||||
default_prompts = {
|
||||
"default": {
|
||||
"name": "默认提示词",
|
||||
"content": "您是一位专业的问题解决专家。请逐步分析问题,找出问题所在,并提供详细的解决方案。始终使用用户偏好的语言回答。",
|
||||
"description": "通用问题解决提示词"
|
||||
}
|
||||
}
|
||||
with open(PROMPT_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(default_prompts, f, ensure_ascii=False, indent=4)
|
||||
return default_prompts
|
||||
except Exception as e:
|
||||
print(f"加载提示词配置失败: {e}")
|
||||
return {
|
||||
"default": {
|
||||
"name": "默认提示词",
|
||||
"content": "您是一位专业的问题解决专家。请逐步分析问题,找出问题所在,并提供详细的解决方案。始终使用用户偏好的语言回答。",
|
||||
"description": "通用问题解决提示词"
|
||||
}
|
||||
}
|
||||
|
||||
def save_prompt(prompt_id, prompt_data):
|
||||
"""保存单个提示词到配置文件"""
|
||||
try:
|
||||
prompts = load_prompts()
|
||||
prompts[prompt_id] = prompt_data
|
||||
with open(PROMPT_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(prompts, f, ensure_ascii=False, indent=4)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"保存提示词配置失败: {e}")
|
||||
return False
|
||||
|
||||
def delete_prompt(prompt_id):
|
||||
"""从配置文件中删除一个提示词"""
|
||||
try:
|
||||
prompts = load_prompts()
|
||||
if prompt_id in prompts:
|
||||
del prompts[prompt_id]
|
||||
with open(PROMPT_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(prompts, f, ensure_ascii=False, indent=4)
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"删除提示词配置失败: {e}")
|
||||
return False
|
||||
|
||||
# 替换 before_first_request 装饰器
|
||||
def init_model_config():
|
||||
"""初始化模型配置"""
|
||||
@@ -722,6 +783,91 @@ def get_api_key(key_name):
|
||||
api_keys = load_api_keys()
|
||||
return api_keys.get(key_name, "")
|
||||
|
||||
@app.route('/api/models')
|
||||
def api_models():
|
||||
"""API端点:获取可用模型列表"""
|
||||
try:
|
||||
# 加载模型配置
|
||||
config = load_model_config()
|
||||
|
||||
# 转换为前端需要的格式
|
||||
models = []
|
||||
for model_id, model_info in config['models'].items():
|
||||
models.append({
|
||||
'id': model_id,
|
||||
'display_name': model_info.get('name', model_id),
|
||||
'is_multimodal': model_info.get('supportsMultimodal', False),
|
||||
'is_reasoning': model_info.get('isReasoning', False),
|
||||
'description': model_info.get('description', ''),
|
||||
'version': model_info.get('version', 'latest')
|
||||
})
|
||||
|
||||
# 返回模型列表
|
||||
return jsonify(models)
|
||||
except Exception as e:
|
||||
print(f"获取模型列表时出错: {e}")
|
||||
return jsonify([]), 500
|
||||
|
||||
@app.route('/api/prompts', methods=['GET'])
|
||||
def get_prompts():
|
||||
"""API端点:获取所有系统提示词"""
|
||||
try:
|
||||
prompts = load_prompts()
|
||||
return jsonify(prompts)
|
||||
except Exception as e:
|
||||
print(f"获取提示词列表时出错: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route('/api/prompts/<prompt_id>', methods=['GET'])
|
||||
def get_prompt(prompt_id):
|
||||
"""API端点:获取单个系统提示词"""
|
||||
try:
|
||||
prompts = load_prompts()
|
||||
if prompt_id in prompts:
|
||||
return jsonify(prompts[prompt_id])
|
||||
else:
|
||||
return jsonify({"error": "提示词不存在"}), 404
|
||||
except Exception as e:
|
||||
print(f"获取提示词时出错: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route('/api/prompts', methods=['POST'])
|
||||
def add_prompt():
|
||||
"""API端点:添加或更新系统提示词"""
|
||||
try:
|
||||
data = request.json
|
||||
if not data or not isinstance(data, dict):
|
||||
return jsonify({"error": "无效的请求数据"}), 400
|
||||
|
||||
prompt_id = data.get('id')
|
||||
if not prompt_id:
|
||||
return jsonify({"error": "提示词ID不能为空"}), 400
|
||||
|
||||
prompt_data = {
|
||||
"name": data.get('name', f"提示词{prompt_id}"),
|
||||
"content": data.get('content', ""),
|
||||
"description": data.get('description', "")
|
||||
}
|
||||
|
||||
save_prompt(prompt_id, prompt_data)
|
||||
return jsonify({"success": True, "id": prompt_id})
|
||||
except Exception as e:
|
||||
print(f"保存提示词时出错: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route('/api/prompts/<prompt_id>', methods=['DELETE'])
|
||||
def remove_prompt(prompt_id):
|
||||
"""API端点:删除系统提示词"""
|
||||
try:
|
||||
success = delete_prompt(prompt_id)
|
||||
if success:
|
||||
return jsonify({"success": True})
|
||||
else:
|
||||
return jsonify({"error": "提示词不存在或删除失败"}), 404
|
||||
except Exception as e:
|
||||
print(f"删除提示词时出错: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
local_ip = get_local_ip()
|
||||
print(f"Local IP Address: {local_ip}")
|
||||
|
||||
27
config/prompts.json
Normal file
27
config/prompts.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"default": {
|
||||
"name": "默认提示词",
|
||||
"content": "您是一位专业的问题解决专家。请逐步分析问题,找出问题所在,并提供详细的解决方案。始终使用用户偏好的语言回答。",
|
||||
"description": "通用问题解决提示词"
|
||||
},
|
||||
"math": {
|
||||
"name": "数学问题提示词",
|
||||
"content": "您是一位专业的数学问题解决专家。当看到一个数学问题时,请:\n1. 仔细阅读并理解问题\n2. 分析问题的关键组成部分\n3. 提供清晰的、逐步的解决方案\n4. 解释涉及的数学概念或定理\n5. 如果有多种解决方法,先解释最高效的方法",
|
||||
"description": "专为数学问题设计的提示词"
|
||||
},
|
||||
"physics": {
|
||||
"name": "物理问题提示词",
|
||||
"content": "您是一位专业的物理问题解决专家。当看到一个物理问题时,请:\n1. 仔细阅读并理解问题\n2. 确定涉及的物理概念和定律\n3. 列出已知条件和未知量\n4. 提供清晰的、逐步的解决方案\n5. 解释涉及的物理原理\n6. 检查单位是否一致,结果是否合理",
|
||||
"description": "专为物理问题设计的提示词"
|
||||
},
|
||||
"programming": {
|
||||
"name": "编程问题提示词",
|
||||
"content": "您是一位专业的编程问题解决专家。当看到一个编程问题时,请:\n1. 仔细阅读并理解问题需求\n2. 分析问题的算法复杂度和可能的解决方案\n3. 提供清晰的、逐步的解决方案,并解释代码\n4. 如有必要,讨论时间复杂度和空间复杂度\n5. 如果有多种解决方法,比较它们的优缺点",
|
||||
"description": "专为编程问题设计的提示词"
|
||||
},
|
||||
"chemistry": {
|
||||
"name": "化学问题提示词",
|
||||
"content": "您是一位专业的化学问题解决专家。当看到一个化学问题时,请:\n1. 仔细阅读并理解问题\n2. 确定涉及的化学原理和反应\n3. 列出已知条件和需要求解的量\n4. 提供清晰的、逐步的解决方案\n5. 解释化学平衡、动力学或热力学的相关原理\n6. 必要时平衡化学方程式",
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
@@ -36,10 +36,9 @@ class BaseModel(ABC):
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_default_system_prompt(self) -> str:
|
||||
"""Return the default system prompt for this model"""
|
||||
pass
|
||||
"""返回默认的系统提示词,子类可覆盖但不再是必须实现的方法"""
|
||||
return "您是一位专业的问题解决专家。请逐步分析问题,找出问题所在,并提供详细的解决方案。始终使用用户偏好的语言回答。"
|
||||
|
||||
@abstractmethod
|
||||
def get_model_identifier(self) -> str:
|
||||
|
||||
@@ -269,7 +269,7 @@ class SnapSolver {
|
||||
this.socket.off('screenshot_complete');
|
||||
this.socket.off('request_acknowledged');
|
||||
this.socket.off('text_extracted');
|
||||
this.socket.off('claude_response');
|
||||
this.socket.off('ai_response');
|
||||
}
|
||||
|
||||
// 标记事件处理器已设置
|
||||
@@ -366,8 +366,8 @@ class SnapSolver {
|
||||
}
|
||||
});
|
||||
|
||||
this.socket.on('claude_response', (data) => {
|
||||
console.log('Received claude_response:', data);
|
||||
this.socket.on('ai_response', (data) => {
|
||||
console.log('Received ai_response:', data);
|
||||
this.updateStatusLight(data.status);
|
||||
|
||||
// 确保Claude面板可见
|
||||
@@ -1617,7 +1617,7 @@ class SnapSolver {
|
||||
this.socket.off('screenshot_response');
|
||||
this.socket.off('screenshot_complete');
|
||||
this.socket.off('request_acknowledged');
|
||||
this.socket.off('claude_response');
|
||||
this.socket.off('ai_response');
|
||||
this.socket.off('thinking');
|
||||
this.socket.off('thinking_complete');
|
||||
this.socket.off('analysis_complete');
|
||||
|
||||
@@ -7,6 +7,10 @@ class SettingsManager {
|
||||
// 初始化界面元素
|
||||
this.initializeElements();
|
||||
|
||||
// 提示词配置
|
||||
this.prompts = {};
|
||||
this.currentPromptId = 'default';
|
||||
|
||||
// 加载模型配置
|
||||
this.isInitialized = false;
|
||||
this.initialize();
|
||||
@@ -20,6 +24,7 @@ class SettingsManager {
|
||||
// 成功加载配置后,执行后续初始化
|
||||
this.updateModelOptions();
|
||||
await this.loadSettings();
|
||||
await this.loadPrompts(); // 加载提示词配置
|
||||
this.setupEventListeners();
|
||||
this.updateUIBasedOnModelType();
|
||||
|
||||
@@ -36,6 +41,7 @@ class SettingsManager {
|
||||
this.setupDefaultModels();
|
||||
this.updateModelOptions();
|
||||
await this.loadSettings();
|
||||
await this.loadPrompts(); // 加载提示词配置
|
||||
this.setupEventListeners();
|
||||
this.updateUIBasedOnModelType();
|
||||
|
||||
@@ -176,6 +182,22 @@ class SettingsManager {
|
||||
this.proxyPortInput = document.getElementById('proxyPort');
|
||||
this.proxySettings = document.getElementById('proxySettings');
|
||||
|
||||
// 提示词管理相关元素
|
||||
this.promptSelect = document.getElementById('promptSelect');
|
||||
this.savePromptBtn = document.getElementById('savePromptBtn');
|
||||
this.newPromptBtn = document.getElementById('newPromptBtn');
|
||||
this.deletePromptBtn = document.getElementById('deletePromptBtn');
|
||||
|
||||
// 提示词对话框元素
|
||||
this.promptDialog = document.getElementById('promptDialog');
|
||||
this.promptDialogOverlay = document.getElementById('promptDialogOverlay');
|
||||
this.promptIdInput = document.getElementById('promptId');
|
||||
this.promptNameInput = document.getElementById('promptName');
|
||||
this.promptContentInput = document.getElementById('promptContent');
|
||||
this.promptDescriptionInput = document.getElementById('promptDescription');
|
||||
this.cancelPromptBtn = document.getElementById('cancelPromptBtn');
|
||||
this.confirmPromptBtn = document.getElementById('confirmPromptBtn');
|
||||
|
||||
// 最大Token设置元素 - 现在是输入框而不是滑块
|
||||
this.maxTokensInput = document.getElementById('maxTokens');
|
||||
|
||||
@@ -353,6 +375,11 @@ class SettingsManager {
|
||||
this.proxyPortInput.value = settings.proxyPort;
|
||||
}
|
||||
|
||||
// 加载当前选中的提示词ID
|
||||
if (settings.currentPromptId) {
|
||||
this.currentPromptId = settings.currentPromptId;
|
||||
}
|
||||
|
||||
// Update UI based on model type
|
||||
this.updateUIBasedOnModelType();
|
||||
|
||||
@@ -463,10 +490,6 @@ class SettingsManager {
|
||||
// 高亮显示对应的API密钥
|
||||
if (apiKeyToHighlight) {
|
||||
apiKeyToHighlight.classList.add('highlight');
|
||||
// 延迟一秒后滚动到该元素
|
||||
setTimeout(() => {
|
||||
apiKeyToHighlight.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,6 +505,7 @@ class SettingsManager {
|
||||
temperature: this.temperatureInput.value,
|
||||
language: this.languageInput.value,
|
||||
systemPrompt: this.systemPromptInput.value,
|
||||
currentPromptId: this.currentPromptId,
|
||||
proxyEnabled: this.proxyEnabledInput.checked,
|
||||
proxyHost: this.proxyHostInput.value,
|
||||
proxyPort: this.proxyPortInput.value
|
||||
@@ -594,6 +618,85 @@ class SettingsManager {
|
||||
}
|
||||
});
|
||||
|
||||
// 提示词相关事件监听
|
||||
if (this.promptSelect) {
|
||||
this.promptSelect.addEventListener('change', (e) => {
|
||||
// 阻止事件冒泡
|
||||
e.stopPropagation();
|
||||
|
||||
// 加载选中的提示词
|
||||
this.loadPrompt(e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
// 保存提示词按钮
|
||||
if (this.savePromptBtn) {
|
||||
this.savePromptBtn.addEventListener('click', (e) => {
|
||||
// 阻止事件冒泡
|
||||
e.stopPropagation();
|
||||
|
||||
// 打开编辑对话框
|
||||
this.openEditPromptDialog();
|
||||
});
|
||||
}
|
||||
|
||||
// 新建提示词按钮
|
||||
if (this.newPromptBtn) {
|
||||
this.newPromptBtn.addEventListener('click', (e) => {
|
||||
// 阻止事件冒泡
|
||||
e.stopPropagation();
|
||||
|
||||
// 打开新建对话框
|
||||
this.openNewPromptDialog();
|
||||
});
|
||||
}
|
||||
|
||||
// 删除提示词按钮
|
||||
if (this.deletePromptBtn) {
|
||||
this.deletePromptBtn.addEventListener('click', (e) => {
|
||||
// 阻止事件冒泡
|
||||
e.stopPropagation();
|
||||
|
||||
// 删除当前提示词
|
||||
this.deletePrompt();
|
||||
});
|
||||
}
|
||||
|
||||
// 提示词对话框相关事件
|
||||
if (this.cancelPromptBtn) {
|
||||
this.cancelPromptBtn.addEventListener('click', (e) => {
|
||||
// 阻止事件冒泡
|
||||
e.stopPropagation();
|
||||
|
||||
// 关闭对话框
|
||||
this.closePromptDialog();
|
||||
});
|
||||
}
|
||||
|
||||
if (this.confirmPromptBtn) {
|
||||
this.confirmPromptBtn.addEventListener('click', (e) => {
|
||||
// 阻止事件冒泡
|
||||
e.stopPropagation();
|
||||
|
||||
// 保存提示词
|
||||
this.savePrompt();
|
||||
});
|
||||
}
|
||||
|
||||
if (this.promptDialogOverlay) {
|
||||
this.promptDialogOverlay.addEventListener('click', (e) => {
|
||||
// 点击遮罩关闭对话框
|
||||
this.closePromptDialog();
|
||||
});
|
||||
}
|
||||
|
||||
// 系统提示词输入框的变更保存设置
|
||||
this.systemPromptInput.addEventListener('change', (e) => {
|
||||
// 阻止事件冒泡
|
||||
e.stopPropagation();
|
||||
this.saveSettings();
|
||||
});
|
||||
|
||||
// 最大Token输入框事件处理
|
||||
if (this.maxTokensInput) {
|
||||
this.maxTokensInput.addEventListener('change', (e) => {
|
||||
@@ -653,12 +756,6 @@ class SettingsManager {
|
||||
this.saveSettings();
|
||||
});
|
||||
|
||||
this.systemPromptInput.addEventListener('change', (e) => {
|
||||
// 阻止事件冒泡
|
||||
e.stopPropagation();
|
||||
this.saveSettings();
|
||||
});
|
||||
|
||||
this.languageInput.addEventListener('change', (e) => {
|
||||
// 阻止事件冒泡
|
||||
e.stopPropagation();
|
||||
@@ -1069,6 +1166,267 @@ class SettingsManager {
|
||||
this.settingsPanel.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载系统提示词配置
|
||||
*/
|
||||
async loadPrompts() {
|
||||
try {
|
||||
// 从服务器获取提示词列表
|
||||
const response = await fetch('/api/prompts');
|
||||
if (response.ok) {
|
||||
this.prompts = await response.json();
|
||||
|
||||
// 更新提示词选择下拉框
|
||||
this.updatePromptSelect();
|
||||
|
||||
// 如果有当前选中的提示词,加载它
|
||||
if (this.currentPromptId && this.prompts[this.currentPromptId]) {
|
||||
this.loadPrompt(this.currentPromptId);
|
||||
} else if (Object.keys(this.prompts).length > 0) {
|
||||
// 否则加载第一个提示词
|
||||
this.loadPrompt(Object.keys(this.prompts)[0]);
|
||||
}
|
||||
|
||||
console.log('提示词配置加载成功');
|
||||
} else {
|
||||
console.error('加载提示词配置失败');
|
||||
window.uiManager?.showToast('加载提示词配置失败', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载提示词配置出错:', error);
|
||||
window.uiManager?.showToast('加载提示词配置出错', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新提示词选择下拉框
|
||||
*/
|
||||
updatePromptSelect() {
|
||||
if (!this.promptSelect) return;
|
||||
|
||||
// 清空现有选项
|
||||
this.promptSelect.innerHTML = '';
|
||||
|
||||
// 添加选项
|
||||
for (const [id, prompt] of Object.entries(this.prompts)) {
|
||||
const option = document.createElement('option');
|
||||
option.value = id;
|
||||
option.textContent = prompt.name;
|
||||
this.promptSelect.appendChild(option);
|
||||
}
|
||||
|
||||
// 选中当前提示词
|
||||
if (this.currentPromptId && this.prompts[this.currentPromptId]) {
|
||||
this.promptSelect.value = this.currentPromptId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载指定的提示词
|
||||
* @param {string} promptId 提示词ID
|
||||
*/
|
||||
loadPrompt(promptId) {
|
||||
if (!this.prompts[promptId]) return;
|
||||
|
||||
// 更新当前提示词ID
|
||||
this.currentPromptId = promptId;
|
||||
|
||||
// 更新提示词输入框
|
||||
this.systemPromptInput.value = this.prompts[promptId].content;
|
||||
|
||||
// 更新提示词选择下拉框
|
||||
if (this.promptSelect) {
|
||||
this.promptSelect.value = promptId;
|
||||
}
|
||||
|
||||
// 保存设置
|
||||
this.saveSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存当前提示词到服务器
|
||||
* @param {boolean} isNew 是否是新建提示词
|
||||
*/
|
||||
async savePrompt(isNew = false) {
|
||||
try {
|
||||
// 获取输入的提示词信息
|
||||
const promptId = this.promptIdInput.value.trim();
|
||||
const promptName = this.promptNameInput.value.trim();
|
||||
const promptContent = this.promptContentInput.value.trim();
|
||||
const promptDescription = this.promptDescriptionInput.value.trim();
|
||||
|
||||
// 验证必填字段
|
||||
if (!promptId) {
|
||||
window.uiManager?.showToast('提示词ID不能为空', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!promptName) {
|
||||
window.uiManager?.showToast('提示词名称不能为空', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!promptContent) {
|
||||
window.uiManager?.showToast('提示词内容不能为空', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建提示词数据
|
||||
const promptData = {
|
||||
id: promptId,
|
||||
name: promptName,
|
||||
content: promptContent,
|
||||
description: promptDescription
|
||||
};
|
||||
|
||||
// 发送到服务器
|
||||
const response = await fetch('/api/prompts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(promptData)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
// 更新本地提示词列表
|
||||
this.prompts[promptId] = {
|
||||
name: promptName,
|
||||
content: promptContent,
|
||||
description: promptDescription
|
||||
};
|
||||
|
||||
// 更新提示词选择下拉框
|
||||
this.updatePromptSelect();
|
||||
|
||||
// 加载新保存的提示词
|
||||
this.loadPrompt(promptId);
|
||||
|
||||
// 关闭对话框
|
||||
this.closePromptDialog();
|
||||
|
||||
window.uiManager?.showToast('提示词已保存', 'success');
|
||||
} else {
|
||||
window.uiManager?.showToast('保存提示词失败: ' + result.error, 'error');
|
||||
}
|
||||
} else {
|
||||
window.uiManager?.showToast('无法连接到服务器', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存提示词出错:', error);
|
||||
window.uiManager?.showToast('保存提示词出错: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除当前提示词
|
||||
*/
|
||||
async deletePrompt() {
|
||||
try {
|
||||
// 获取当前提示词ID
|
||||
const promptId = this.currentPromptId;
|
||||
|
||||
if (!promptId || !this.prompts[promptId]) {
|
||||
window.uiManager?.showToast('未选择提示词', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 弹窗确认删除
|
||||
if (!confirm(`确定要删除提示词 "${this.prompts[promptId].name}" 吗?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 发送到服务器
|
||||
const response = await fetch(`/api/prompts/${promptId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
// 删除本地提示词
|
||||
delete this.prompts[promptId];
|
||||
|
||||
// 更新提示词选择下拉框
|
||||
this.updatePromptSelect();
|
||||
|
||||
// 如果还有其他提示词,加载第一个
|
||||
const promptIds = Object.keys(this.prompts);
|
||||
if (promptIds.length > 0) {
|
||||
this.loadPrompt(promptIds[0]);
|
||||
} else {
|
||||
// 如果没有提示词了,清空输入框
|
||||
this.systemPromptInput.value = '';
|
||||
this.currentPromptId = '';
|
||||
}
|
||||
|
||||
window.uiManager?.showToast('提示词已删除', 'success');
|
||||
} else {
|
||||
window.uiManager?.showToast('删除提示词失败: ' + result.error, 'error');
|
||||
}
|
||||
} else {
|
||||
window.uiManager?.showToast('无法连接到服务器', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除提示词出错:', error);
|
||||
window.uiManager?.showToast('删除提示词出错: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开新建提示词对话框
|
||||
*/
|
||||
openNewPromptDialog() {
|
||||
// 清空输入框
|
||||
this.promptIdInput.value = '';
|
||||
this.promptNameInput.value = '';
|
||||
this.promptContentInput.value = this.systemPromptInput.value || '';
|
||||
this.promptDescriptionInput.value = '';
|
||||
|
||||
// 启用ID输入框
|
||||
this.promptIdInput.disabled = false;
|
||||
|
||||
// 显示对话框
|
||||
this.promptDialog.classList.add('active');
|
||||
this.promptDialogOverlay.classList.add('active');
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开编辑提示词对话框
|
||||
*/
|
||||
openEditPromptDialog() {
|
||||
// 获取当前提示词ID
|
||||
const promptId = this.currentPromptId;
|
||||
|
||||
if (!promptId || !this.prompts[promptId]) {
|
||||
window.uiManager?.showToast('未选择提示词', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 填充输入框
|
||||
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 || '';
|
||||
|
||||
// 禁用ID输入框(不允许修改ID)
|
||||
this.promptIdInput.disabled = true;
|
||||
|
||||
// 显示对话框
|
||||
this.promptDialog.classList.add('active');
|
||||
this.promptDialogOverlay.classList.add('active');
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭提示词对话框
|
||||
*/
|
||||
closePromptDialog() {
|
||||
this.promptDialog.classList.remove('active');
|
||||
this.promptDialogOverlay.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in other modules
|
||||
|
||||
280
static/style.css
280
static/style.css
@@ -3143,3 +3143,283 @@ textarea,
|
||||
#themeToggle:active i {
|
||||
transform: rotate(360deg) scale(0.8);
|
||||
}
|
||||
|
||||
/* 添加提示词管理相关样式 */
|
||||
.prompt-setting-group label {
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.prompt-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.prompt-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.prompt-actions select {
|
||||
min-width: 180px;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--surface-alt);
|
||||
color: var(--text-primary);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* 移动端提示词区域优化 */
|
||||
@media (max-width: 768px) {
|
||||
.prompt-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.prompt-actions {
|
||||
margin-top: 4px;
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.prompt-actions select {
|
||||
min-width: 0;
|
||||
width: calc(100% - 110px);
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
padding: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.prompt-actions {
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.prompt-actions select {
|
||||
width: calc(100% - 90px);
|
||||
padding: 3px 6px;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
textarea#systemPrompt {
|
||||
min-height: 100px;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
font-size: 0.9em;
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.icon-btn:hover {
|
||||
color: var(--text-color);
|
||||
background-color: var(--hover-color);
|
||||
}
|
||||
|
||||
.icon-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* 提示词对话框 */
|
||||
.prompt-dialog {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--surface);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.5);
|
||||
padding: 20px;
|
||||
z-index: 1000;
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
display: none;
|
||||
/* 确保背景完全不透明 */
|
||||
backdrop-filter: blur(5px);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* 适配暗模式 */
|
||||
[data-theme="dark"] .prompt-dialog {
|
||||
background-color: var(--surface);
|
||||
border: 1px solid var(--border-color);
|
||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
/* 移动端对话框优化 */
|
||||
@media (max-width: 480px) {
|
||||
.prompt-dialog {
|
||||
padding: 15px;
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.prompt-dialog h3 {
|
||||
font-size: 1.1rem;
|
||||
padding-bottom: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.prompt-dialog .form-group {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.prompt-dialog label {
|
||||
font-size: 0.9rem;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.prompt-dialog input {
|
||||
padding: 6px 8px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.prompt-dialog textarea {
|
||||
min-height: 100px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.prompt-dialog .dialog-buttons {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.prompt-dialog .dialog-buttons button {
|
||||
padding: 6px 12px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
.prompt-dialog.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.prompt-dialog h3 {
|
||||
margin-top: 0;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 10px;
|
||||
color: var(--text-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prompt-dialog h3 {
|
||||
color: var(--text-primary);
|
||||
border-bottom-color: var(--border-color);
|
||||
}
|
||||
|
||||
.prompt-dialog .form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.prompt-dialog label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.prompt-dialog input,
|
||||
.prompt-dialog textarea {
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--surface-alt);
|
||||
color: var(--text-primary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prompt-dialog input,
|
||||
[data-theme="dark"] .prompt-dialog textarea {
|
||||
background-color: var(--input-background);
|
||||
border: 1px solid var(--input-border);
|
||||
color: var(--input-text);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prompt-dialog input:focus,
|
||||
[data-theme="dark"] .prompt-dialog textarea:focus {
|
||||
border-color: var(--input-focus-border);
|
||||
box-shadow: 0 0 0 2px var(--input-focus-shadow);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.prompt-dialog textarea {
|
||||
min-height: 120px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.prompt-dialog .dialog-buttons {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.prompt-dialog .dialog-buttons button {
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.prompt-dialog .dialog-buttons .cancel-btn {
|
||||
background-color: var(--surface-alt);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.prompt-dialog .dialog-buttons .save-btn {
|
||||
background-color: var(--primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prompt-dialog .dialog-buttons .cancel-btn {
|
||||
background-color: var(--btn-secondary-bg);
|
||||
color: var(--btn-secondary-text);
|
||||
border: 1px solid var(--btn-secondary-border);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prompt-dialog .dialog-buttons .cancel-btn:hover {
|
||||
background-color: var(--btn-secondary-hover-bg);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .prompt-dialog .dialog-buttons .save-btn {
|
||||
background-color: var(--primary);
|
||||
}
|
||||
|
||||
.dialog-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
z-index: 999;
|
||||
display: none;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .dialog-overlay {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.dialog-overlay.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -169,12 +169,27 @@
|
||||
<span id="thinkBudgetPercentValue">50%</span>
|
||||
</div>
|
||||
<div class="setting-group">
|
||||
<label for="temperature"><i class="fas fa-thermometer-half"></i> 温度</label>
|
||||
<label for="temperature"><i class="fas fa-thermometer-half"></i> 温度<span id="temperatureValue">0.7</span></label>
|
||||
<input type="range" id="temperature" min="0" max="1" step="0.1" value="0.7">
|
||||
<span id="temperatureValue">0.7</span>
|
||||
</div>
|
||||
<div class="setting-group">
|
||||
<label for="systemPrompt"><i class="fas fa-comment-alt"></i> 系统提示词</label>
|
||||
<div class="setting-group prompt-setting-group">
|
||||
<div class="prompt-header">
|
||||
<label for="systemPrompt"><i class="fas fa-comment-alt"></i> 系统提示词</label>
|
||||
<div class="prompt-actions">
|
||||
<select id="promptSelect" title="选择预设提示词">
|
||||
<option value="">-- 选择预设提示词 --</option>
|
||||
</select>
|
||||
<button id="savePromptBtn" class="icon-btn" title="保存当前提示词">
|
||||
<i class="fas fa-save"></i>
|
||||
</button>
|
||||
<button id="newPromptBtn" class="icon-btn" title="新建提示词">
|
||||
<i class="fas fa-plus"></i>
|
||||
</button>
|
||||
<button id="deletePromptBtn" class="icon-btn" title="删除当前提示词">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<textarea id="systemPrompt" rows="3">您是一位专业的问题解决专家。请逐步分析问题,找出问题所在,并提供详细的解决方案。始终使用用户偏好的语言回答。</textarea>
|
||||
</div>
|
||||
</div>
|
||||
@@ -384,6 +399,32 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- 提示词对话框 -->
|
||||
<div class="dialog-overlay" id="promptDialogOverlay"></div>
|
||||
<div class="prompt-dialog" id="promptDialog">
|
||||
<h3>添加/编辑提示词</h3>
|
||||
<div class="form-group">
|
||||
<label for="promptId">提示词ID</label>
|
||||
<input type="text" id="promptId" placeholder="英文字母和下划线,如math_problems">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="promptName">名称</label>
|
||||
<input type="text" id="promptName" placeholder="提示词名称">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="promptContent">内容</label>
|
||||
<textarea id="promptContent" placeholder="输入提示词内容..."></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="promptDescription">描述(可选)</label>
|
||||
<input type="text" id="promptDescription" placeholder="简短描述">
|
||||
</div>
|
||||
<div class="dialog-buttons">
|
||||
<button class="cancel-btn" id="cancelPromptBtn">取消</button>
|
||||
<button class="save-btn" id="confirmPromptBtn">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 确保按照正确的顺序加载脚本 -->
|
||||
<!-- 先加载UI管理器,确保它能在DOM加载完成后初始化 -->
|
||||
<script src="{{ url_for('static', filename='js/ui.js') }}"></script>
|
||||
|
||||
Reference in New Issue
Block a user