From 5414630b21509f28a6982a0727cf41fee0257941 Mon Sep 17 00:00:00 2001 From: Zylan Date: Wed, 2 Apr 2025 23:31:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E6=8F=90=E7=A4=BA=E8=AF=8D?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=8C=E5=8C=85=E6=8B=AC?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=E3=80=81=E4=BF=9D=E5=AD=98=E3=80=81=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E6=8F=90=E7=A4=BA=E8=AF=8D=E7=9A=84API=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E5=92=8C=E5=89=8D=E7=AB=AF=E4=BA=A4=E4=BA=92=EF=BC=9B?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=A0=B7=E5=BC=8F=E4=BB=A5=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D=E7=AE=A1=E7=90=86=E7=95=8C=E9=9D=A2?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 174 +++++++++++++++++-- config/prompts.json | 27 +++ models/base.py | 5 +- static/js/main.js | 8 +- static/js/settings.js | 378 ++++++++++++++++++++++++++++++++++++++++-- static/style.css | 280 +++++++++++++++++++++++++++++++ templates/index.html | 49 +++++- 7 files changed, 886 insertions(+), 35 deletions(-) create mode 100644 config/prompts.json diff --git a/app.py b/app.py index c38da0c..75d06bc 100644 --- a/app.py +++ b/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/', 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/', 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}") diff --git a/config/prompts.json b/config/prompts.json new file mode 100644 index 0000000..6f2a400 --- /dev/null +++ b/config/prompts.json @@ -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": "" + } +} \ No newline at end of file diff --git a/models/base.py b/models/base.py index 0d80e69..b61ad9f 100644 --- a/models/base.py +++ b/models/base.py @@ -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: diff --git a/static/js/main.js b/static/js/main.js index 59ae4b7..c1a9589 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -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'); diff --git a/static/js/settings.js b/static/js/settings.js index 3f75d54..f319eb2 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -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 diff --git a/static/style.css b/static/style.css index 2948c0a..aa91ca8 100644 --- a/static/style.css +++ b/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; +} diff --git a/templates/index.html b/templates/index.html index c8ca34f..e3a412d 100644 --- a/templates/index.html +++ b/templates/index.html @@ -169,12 +169,27 @@ 50%
- + - 0.7
-
- +
+
+ +
+ + + + +
+
@@ -384,6 +399,32 @@ + +
+
+

添加/编辑提示词

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+