diff --git a/app.py b/app.py index 75836e5..207ec5d 100644 --- a/app.py +++ b/app.py @@ -12,6 +12,7 @@ from models import ModelFactory import time import os import json +import traceback app = Flask(__name__) socketio = SocketIO(app, cors_allowed_origins="*", ping_timeout=30, ping_interval=5, max_http_buffer_size=50 * 1024 * 1024) @@ -73,7 +74,51 @@ def handle_connect(): def handle_disconnect(): print('Client disconnected') - +def create_model_instance(model_id, api_keys, settings): + """创建模型实例并配置参数""" + + # 获取模型信息 + model_info = settings.get('modelInfo', {}) + is_reasoning = model_info.get('isReasoning', False) + provider = model_info.get('provider', '').lower() + + # 确定API密钥ID + api_key_id = None + if provider == 'anthropic': + api_key_id = "AnthropicApiKey" + elif provider == 'openai': + api_key_id = "OpenaiApiKey" + elif provider == 'deepseek': + api_key_id = "DeepseekApiKey" + else: + # 根据模型名称 + if "claude" in model_id.lower(): + api_key_id = "AnthropicApiKey" + elif any(keyword in model_id.lower() for keyword in ["gpt", "openai"]): + api_key_id = "OpenaiApiKey" + elif "deepseek" in model_id.lower(): + api_key_id = "DeepseekApiKey" + + api_key = api_keys.get(api_key_id) + if not api_key: + raise ValueError(f"API key is required for the selected model (keyId: {api_key_id})") + + # 获取maxTokens参数,默认为8192 + max_tokens = int(settings.get('maxTokens', 8192)) + + # 创建模型实例 + model_instance = ModelFactory.create_model( + model_name=model_id, + api_key=api_key, + temperature=None if is_reasoning else float(settings.get('temperature', 0.7)), + system_prompt=settings.get('systemPrompt'), + language=settings.get('language', '中文') + ) + + # 设置最大输出Token + model_instance.max_tokens = max_tokens + + return model_instance def stream_model_response(response_generator, sid, model_name=None): """Stream model responses to the client""" @@ -284,232 +329,92 @@ def handle_text_extraction(data): @socketio.on('analyze_text') def handle_analyze_text(data): try: - text = data.get('text') + text = data.get('text', '') settings = data.get('settings', {}) - sid = request.sid + + # 获取推理配置 + reasoning_config = settings.get('reasoningConfig', {}) + + # 获取maxTokens + max_tokens = int(settings.get('maxTokens', 8192)) + + print(f"Debug - 文本分析请求: {text[:50]}...") + print(f"Debug - 最大Token: {max_tokens}, 推理配置: {reasoning_config}") + + # 获取模型和API密钥 + model_id = settings.get('model', 'claude-3-7-sonnet-20250219') + api_keys = settings.get('apiKeys', {}) + + if not text: + socketio.emit('error', {'message': '文本内容不能为空'}) + return - # 从前端传递的设置中获取模型能力信息 - model_capabilities = settings.get('modelCapabilities', {}) - is_reasoning = model_capabilities.get('isReasoning', False) + model_instance = create_model_instance(model_id, api_keys, settings) - # 获取模型名称、提供商和API密钥 - model_name = settings.get('model', 'claude-3-7-sonnet-20250219') - model_provider = settings.get('modelInfo', {}).get('provider', '').lower() + # 将推理配置传递给模型 + if reasoning_config: + model_instance.reasoning_config = reasoning_config - print(f"Selected model: {model_name}, Provider: {model_provider}") - - # 获取API密钥 - 同时支持apiKeys和api_keys两种格式 - api_keys = settings.get('apiKeys', {}) or settings.get('api_keys', {}) - print("Debug - 接收到的API密钥(文本分析):", api_keys) - - # 根据提供商或模型名称确定使用哪个API密钥ID - api_key_id = None - - # 首先尝试通过provider匹配 - if model_provider == 'anthropic': - api_key_id = "AnthropicApiKey" - elif model_provider == 'openai': - api_key_id = "OpenaiApiKey" - elif model_provider == 'deepseek': - api_key_id = "DeepseekApiKey" - else: - # 如果provider不可用,尝试通过模型名称匹配 - if "claude" in model_name.lower(): - api_key_id = "AnthropicApiKey" - elif any(keyword in model_name.lower() for keyword in ["gpt", "openai"]): - api_key_id = "OpenaiApiKey" - elif "deepseek" in model_name.lower(): - api_key_id = "DeepseekApiKey" - - api_key = api_keys.get(api_key_id) - print(f"Debug - 使用API密钥ID: {api_key_id}, 密钥值是否存在: {bool(api_key)}") - - language = settings.get('language', '中文') - - # Validate required settings - if not api_key: - raise ValueError(f"API key is required for the selected model (keyId: {api_key_id})") - - # Log with model name for better debugging - print(f"Using API key for {model_name}: {api_key[:6] if api_key else 'None'}...") - print("Selected model:", model_name) - print("Response language:", language) - print(f"Model features: Reasoning={is_reasoning}") - - # Configure proxy settings if enabled + # 如果启用代理,配置代理设置 proxies = None - if settings.get('proxyEnabled', False): - proxy_host = settings.get('proxyHost', '127.0.0.1') - proxy_port = settings.get('proxyPort', '4780') + if settings.get('proxyEnabled'): proxies = { - 'http': f'http://{proxy_host}:{proxy_port}', - 'https': f'http://{proxy_host}:{proxy_port}' + 'http': f"http://{settings.get('proxyHost')}:{settings.get('proxyPort')}", + 'https': f"http://{settings.get('proxyHost')}:{settings.get('proxyPort')}" } - try: - # Create model instance using factory - 推理模型不使用temperature参数 - model = ModelFactory.create_model( - model_name=model_name, - api_key=api_key, - temperature=None if is_reasoning else float(settings.get('temperature', 0.7)), - system_prompt=settings.get('systemPrompt'), - language=language - ) + for response in model_instance.analyze_text(text, proxies=proxies): + socketio.emit('claude_response', response) - # Start streaming in a separate thread - Thread( - target=stream_model_response, - args=(model.analyze_text(text, proxies), sid, model_name) - ).start() - - except Exception as e: - socketio.emit('claude_response', { - 'status': 'error', - 'error': f'API error: {str(e)}' - }, room=sid) - except Exception as e: - print(f"Analysis error: {str(e)}") - socketio.emit('claude_response', { - 'status': 'error', - 'error': f'Analysis error: {str(e)}' - }, room=request.sid) + print(f"Error in analyze_text: {str(e)}") + traceback.print_exc() + socketio.emit('error', {'message': f'分析文本时出错: {str(e)}'}) @socketio.on('analyze_image') def handle_analyze_image(data): try: - print("Starting image analysis...") - base64_data = data.get('image', '') + image_data = data.get('image') settings = data.get('settings', {}) - # 首先从前端传递的设置中获取模型能力信息 - model_capabilities = settings.get('modelCapabilities', {}) - is_multimodal = model_capabilities.get('supportsMultimodal', False) - is_reasoning = model_capabilities.get('isReasoning', False) + # 获取推理配置 + reasoning_config = settings.get('reasoningConfig', {}) - # 获取模型名称、提供商和API密钥 - model_name = settings.get('model', 'claude-3-7-sonnet-20250219') - model_provider = settings.get('modelInfo', {}).get('provider', '').lower() + # 获取maxTokens + max_tokens = int(settings.get('maxTokens', 8192)) - print(f"Selected model: {model_name}, Provider: {model_provider}") + print(f"Debug - 图像分析请求") + print(f"Debug - 最大Token: {max_tokens}, 推理配置: {reasoning_config}") - # 获取API密钥 - 同时支持apiKeys和api_keys两种格式 - api_keys = settings.get('apiKeys', {}) or settings.get('api_keys', {}) - print("Debug - 接收到的API密钥:", api_keys) + # 获取模型和API密钥 + model_id = settings.get('model', 'claude-3-7-sonnet-20250219') + api_keys = settings.get('apiKeys', {}) - # 根据提供商或模型名称确定使用哪个API密钥ID - api_key_id = None + if not image_data: + socketio.emit('error', {'message': '图像数据不能为空'}) + return + + model_instance = create_model_instance(model_id, api_keys, settings) - # 首先尝试通过provider匹配 - if model_provider == 'anthropic': - api_key_id = "AnthropicApiKey" - elif model_provider == 'openai': - api_key_id = "OpenaiApiKey" - elif model_provider == 'deepseek': - api_key_id = "DeepseekApiKey" - else: - # 如果provider不可用,尝试通过模型名称匹配 - if "claude" in model_name.lower(): - api_key_id = "AnthropicApiKey" - elif any(keyword in model_name.lower() for keyword in ["gpt", "openai"]): - api_key_id = "OpenaiApiKey" - elif "deepseek" in model_name.lower(): - api_key_id = "DeepseekApiKey" - - api_key = api_keys.get(api_key_id) - print(f"Debug - 使用API密钥ID: {api_key_id}, 密钥值是否存在: {bool(api_key)}") - - language = settings.get('language', '中文') - - # Validate required params - if not base64_data: - raise ValueError("No image data provided") - - if not api_key: - raise ValueError(f"API key is required for the selected model (keyId: {api_key_id})") - - # 记录模型信息以便调试 - print("Selected model:", model_name) - print("Response language:", language) - print(f"Model capabilities: Multimodal={is_multimodal}, Reasoning={is_reasoning}") - - # Configure proxy settings if enabled + # 将推理配置传递给模型 + if reasoning_config: + model_instance.reasoning_config = reasoning_config + + # 如果启用代理,配置代理设置 proxies = None - if settings.get('proxyEnabled', False): - proxy_host = settings.get('proxyHost', '127.0.0.1') - proxy_port = settings.get('proxyPort', '4780') + if settings.get('proxyEnabled'): proxies = { - 'http': f'http://{proxy_host}:{proxy_port}', - 'https': f'http://{proxy_host}:{proxy_port}' + 'http': f"http://{settings.get('proxyHost')}:{settings.get('proxyPort')}", + 'https': f"http://{settings.get('proxyHost')}:{settings.get('proxyPort')}" } - # 先回复客户端,确认已收到请求,防止超时断开 - socketio.emit('request_acknowledged', { - 'status': 'received', - 'message': 'Image received, analysis in progress' - }, room=request.sid) - - # 如果不是多模态模型,需要先提取文本 - extracted_text = None - if not is_multimodal: - mathpix_key = settings.get('mathpixApiKey') - if not mathpix_key: - raise ValueError("非多模态模型需要Mathpix API Key进行文本提取") - - print("非多模态模型,需要先提取文本...") - mathpix_model = ModelFactory.create_model('mathpix', mathpix_key) + for response in model_instance.analyze_image(image_data, proxies=proxies): + socketio.emit('claude_response', response) - # 这里假设MathpixModel有一个extract_full_text方法 - # 如果没有,需要实现或调用其他方法来提取文本 - try: - extracted_text = mathpix_model.extract_full_text(base64_data) - print("文本提取成功,长度:", len(extracted_text)) - - # 提示用户文本提取已完成 - socketio.emit('text_extracted', { - 'status': 'success', - 'message': '图像文本提取成功,正在分析...', - 'for_analysis': True - }, room=request.sid) - except Exception as e: - raise ValueError(f"文本提取失败: {str(e)}") - - try: - # Create model instance using factory - 推理模型不使用temperature参数 - model = ModelFactory.create_model( - model_name=model_name, - api_key=api_key, - temperature=None if is_reasoning else float(settings.get('temperature', 0.7)), - system_prompt=settings.get('systemPrompt'), - language=language - ) - - # Start streaming in a separate thread - if not is_multimodal and extracted_text: - # 对于非多模态模型,使用提取的文本 - Thread( - target=stream_model_response, - args=(model.analyze_text(extracted_text, proxies), request.sid, model_name) - ).start() - else: - # 对于多模态模型,直接使用图像 - Thread( - target=stream_model_response, - args=(model.analyze_image(base64_data, proxies), request.sid, model_name) - ).start() - - except Exception as e: - socketio.emit('claude_response', { - 'status': 'error', - 'error': f'API error: {str(e)}' - }, room=request.sid) - except Exception as e: - print(f"Analysis error: {str(e)}") - socketio.emit('claude_response', { - 'status': 'error', - 'error': f'Analysis error: {str(e)}' - }, room=request.sid) + print(f"Error in analyze_image: {str(e)}") + traceback.print_exc() + socketio.emit('error', {'message': f'分析图像时出错: {str(e)}'}) @socketio.on('capture_screenshot') def handle_capture_screenshot(data): diff --git a/models/anthropic.py b/models/anthropic.py index 6d70941..a566ed3 100644 --- a/models/anthropic.py +++ b/models/anthropic.py @@ -31,16 +31,17 @@ class AnthropicModel(BaseModel): 'accept': 'application/json', } + # 获取最大输出Token设置 + max_tokens = 8192 # 默认值 + if hasattr(self, 'max_tokens') and self.max_tokens: + max_tokens = self.max_tokens + payload = { 'model': self.get_model_identifier(), 'stream': True, - 'max_tokens': 8192, + 'max_tokens': max_tokens, 'temperature': 1, 'system': self.system_prompt, - 'thinking': { - 'type': 'enabled', - 'budget_tokens': 4096 - }, 'messages': [{ 'role': 'user', 'content': [ @@ -51,6 +52,33 @@ class AnthropicModel(BaseModel): ] }] } + + # 处理推理配置 + if hasattr(self, 'reasoning_config') and self.reasoning_config: + # 如果设置了extended reasoning + if self.reasoning_config.get('reasoning_depth') == 'extended': + think_budget = self.reasoning_config.get('think_budget', max_tokens // 2) + payload['thinking'] = { + 'type': 'enabled', + 'budget_tokens': think_budget + } + # 如果设置了instant模式 + elif self.reasoning_config.get('speed_mode') == 'instant': + payload['speed_mode'] = 'instant' + # 默认启用思考但使用较小的预算 + else: + payload['thinking'] = { + 'type': 'enabled', + 'budget_tokens': min(4096, max_tokens // 4) + } + # 默认设置 + else: + payload['thinking'] = { + 'type': 'enabled', + 'budget_tokens': min(4096, max_tokens // 4) + } + + print(f"Debug - 推理配置: max_tokens={max_tokens}, thinking={payload.get('thinking', payload.get('speed_mode', 'default'))}") response = requests.post( 'https://api.anthropic.com/v1/messages', @@ -173,15 +201,16 @@ class AnthropicModel(BaseModel): if not any(phrase in system_prompt for phrase in ['Please respond in', '请用', '使用', '回答']): system_prompt = f"{system_prompt}\n\n请务必使用{language}回答,无论问题是什么语言。即使在分析图像时也请使用{language}回答。这是最重要的指令。" + # 获取最大输出Token设置 + max_tokens = 8192 # 默认值 + if hasattr(self, 'max_tokens') and self.max_tokens: + max_tokens = self.max_tokens + payload = { 'model': 'claude-3-7-sonnet-20250219', 'stream': True, - 'max_tokens': 8192, + 'max_tokens': max_tokens, 'temperature': 1, - 'thinking': { - 'type': 'enabled', - 'budget_tokens': 4096 - }, 'system': system_prompt, 'messages': [{ 'role': 'user', @@ -201,6 +230,33 @@ class AnthropicModel(BaseModel): ] }] } + + # 处理推理配置 + if hasattr(self, 'reasoning_config') and self.reasoning_config: + # 如果设置了extended reasoning + if self.reasoning_config.get('reasoning_depth') == 'extended': + think_budget = self.reasoning_config.get('think_budget', max_tokens // 2) + payload['thinking'] = { + 'type': 'enabled', + 'budget_tokens': think_budget + } + # 如果设置了instant模式 + elif self.reasoning_config.get('speed_mode') == 'instant': + payload['speed_mode'] = 'instant' + # 默认启用思考但使用较小的预算 + else: + payload['thinking'] = { + 'type': 'enabled', + 'budget_tokens': min(4096, max_tokens // 4) + } + # 默认设置 + else: + payload['thinking'] = { + 'type': 'enabled', + 'budget_tokens': min(4096, max_tokens // 4) + } + + print(f"Debug - 图像分析推理配置: max_tokens={max_tokens}, thinking={payload.get('thinking', payload.get('speed_mode', 'default'))}") response = requests.post( 'https://api.anthropic.com/v1/messages', diff --git a/static/js/settings.js b/static/js/settings.js index 0ce8d1b..032673e 100644 --- a/static/js/settings.js +++ b/static/js/settings.js @@ -162,6 +162,16 @@ class SettingsManager { this.proxyPortInput = document.getElementById('proxyPort'); this.proxySettings = document.getElementById('proxySettings'); + // 最大Token设置元素 - 现在是输入框而不是滑块 + this.maxTokensInput = document.getElementById('maxTokens'); + + // 理性推理相关元素 + this.reasoningDepthSelect = document.getElementById('reasoningDepth'); + this.reasoningSettingGroup = document.querySelector('.reasoning-setting-group'); + this.thinkBudgetPercentInput = document.getElementById('thinkBudgetPercent'); + this.thinkBudgetPercentValue = document.getElementById('thinkBudgetPercentValue'); + this.thinkBudgetGroup = document.querySelector('.think-budget-group'); + // Initialize Mathpix inputs this.mathpixAppIdInput = document.getElementById('mathpixAppId'); this.mathpixAppKeyInput = document.getElementById('mathpixAppKey'); @@ -252,42 +262,71 @@ class SettingsManager { // Load API keys if (settings.apiKeys) { - Object.entries(this.apiKeyInputs).forEach(([keyId, input]) => { - if (settings.apiKeys[keyId]) { - input.value = settings.apiKeys[keyId]; + Object.entries(settings.apiKeys).forEach(([keyId, value]) => { + const input = this.apiKeyInputs[keyId]; + if (input) { + input.value = value; } }); } - // 选择模型并更新相关UI - let selectedModel = ''; - + // Load model selection if (settings.model && this.modelExists(settings.model)) { - selectedModel = settings.model; - this.modelSelect.value = selectedModel; - } else { - // Default to first model if none selected or if saved model no longer exists - selectedModel = this.modelSelect.value; + this.modelSelect.value = settings.model; + this.updateVisibleApiKey(settings.model); } - // 更新相关UI显示 - this.updateVisibleApiKey(selectedModel); - this.updateModelVersionDisplay(selectedModel); + // Load max tokens setting - 现在直接设置输入框值 + const maxTokens = parseInt(settings.maxTokens || '8192'); + this.maxTokensInput.value = maxTokens; + // Load reasoning depth & think budget settings + if (settings.reasoningDepth) { + this.reasoningDepthSelect.value = settings.reasoningDepth; + } + + // 加载思考预算百分比 + const thinkBudgetPercent = parseInt(settings.thinkBudgetPercent || '50'); + if (this.thinkBudgetPercentInput) { + this.thinkBudgetPercentInput.value = thinkBudgetPercent; + } + + // 更新思考预算显示 + this.updateThinkBudgetDisplay(); + + // 初始化思考预算滑块背景颜色 + this.updateRangeSliderBackground(this.thinkBudgetPercentInput); + + // Load other settings if (settings.temperature) { this.temperatureInput.value = settings.temperature; this.temperatureValue.textContent = settings.temperature; + this.updateRangeSliderBackground(this.temperatureInput); } - if (settings.language) this.languageInput.value = settings.language; - if (settings.systemPrompt) this.systemPromptInput.value = settings.systemPrompt; + + if (settings.systemPrompt) { + this.systemPromptInput.value = settings.systemPrompt; + } + + if (settings.language) { + this.languageInput.value = settings.language; + } + + // Load proxy settings if (settings.proxyEnabled !== undefined) { this.proxyEnabledInput.checked = settings.proxyEnabled; + this.proxySettings.style.display = settings.proxyEnabled ? 'block' : 'none'; } - if (settings.proxyHost) this.proxyHostInput.value = settings.proxyHost; - if (settings.proxyPort) this.proxyPortInput.value = settings.proxyPort; - this.proxySettings.style.display = this.proxyEnabledInput.checked ? 'block' : 'none'; + if (settings.proxyHost) { + this.proxyHostInput.value = settings.proxyHost; + } + if (settings.proxyPort) { + this.proxyPortInput.value = settings.proxyPort; + } + + // Update UI based on model type this.updateUIBasedOnModelType(); } @@ -333,9 +372,26 @@ class SettingsManager { if (!modelInfo) return; + // 处理温度设置显示 if (this.temperatureGroup) { this.temperatureGroup.style.display = modelInfo.isReasoning ? 'none' : 'block'; } + + // 处理深度推理设置显示 + const isAnthropicReasoning = modelInfo.isReasoning && modelInfo.provider === 'anthropic'; + + // 只有对Claude 3.7 Sonnet这样的Anthropic推理模型才显示深度推理设置 + if (this.reasoningSettingGroup) { + this.reasoningSettingGroup.style.display = isAnthropicReasoning ? 'block' : 'none'; + } + + // 只有当启用深度推理且是Anthropic推理模型时才显示思考预算设置 + if (this.thinkBudgetGroup) { + const showThinkBudget = isAnthropicReasoning && + this.reasoningDepthSelect && + this.reasoningDepthSelect.value === 'extended'; + this.thinkBudgetGroup.style.display = showThinkBudget ? 'block' : 'none'; + } } saveSettings() { @@ -344,6 +400,9 @@ class SettingsManager { mathpixAppId: this.mathpixAppIdInput.value, mathpixAppKey: this.mathpixAppKeyInput.value, model: this.modelSelect.value, + maxTokens: this.maxTokensInput.value, + reasoningDepth: this.reasoningDepthSelect?.value || 'standard', + thinkBudgetPercent: this.thinkBudgetPercentInput?.value || '50', temperature: this.temperatureInput.value, language: this.languageInput.value, systemPrompt: this.systemPromptInput.value, @@ -392,8 +451,30 @@ class SettingsManager { const selectedModel = this.modelSelect.value; const modelInfo = this.modelDefinitions[selectedModel] || {}; + // 获取最大Token数 + const maxTokens = parseInt(this.maxTokensInput?.value || '8192'); + + // 获取推理深度设置 + const reasoningDepth = this.reasoningDepthSelect?.value || 'standard'; + const thinkBudgetPercent = parseInt(this.thinkBudgetPercentInput?.value || '50'); + + // 计算思考预算的实际Token数 + const thinkBudget = Math.floor(maxTokens * (thinkBudgetPercent / 100)); + + // 构建推理配置参数 + const reasoningConfig = {}; + if (modelInfo.provider === 'anthropic' && modelInfo.isReasoning) { + if (reasoningDepth === 'extended') { + reasoningConfig.reasoning_depth = 'extended'; + reasoningConfig.think_budget = thinkBudget; + } else { + reasoningConfig.speed_mode = 'instant'; + } + } + return { model: selectedModel, + maxTokens: maxTokens, temperature: this.temperatureInput.value, language: language, systemPrompt: systemPrompt, @@ -405,7 +486,8 @@ class SettingsManager { supportsMultimodal: modelInfo.supportsMultimodal || false, isReasoning: modelInfo.isReasoning || false, provider: modelInfo.provider || 'unknown' - } + }, + reasoningConfig: reasoningConfig }; } @@ -441,8 +523,50 @@ class SettingsManager { } }); + // 最大Token输入框事件处理 + if (this.maxTokensInput) { + this.maxTokensInput.addEventListener('change', (e) => { + // 验证输入值在有效范围内 + let value = parseInt(e.target.value); + if (isNaN(value)) value = 8192; + value = Math.max(1000, Math.min(128000, value)); + this.maxTokensInput.value = value; + + // 更新思考预算显示 + this.updateThinkBudgetDisplay(); + + this.saveSettings(); + }); + } + + // 推理深度选择事件处理 + if (this.reasoningDepthSelect) { + this.reasoningDepthSelect.addEventListener('change', () => { + // 更新思考预算组的可见性 + if (this.thinkBudgetGroup) { + const showThinkBudget = this.reasoningDepthSelect.value === 'extended'; + this.thinkBudgetGroup.style.display = showThinkBudget ? 'block' : 'none'; + } + this.saveSettings(); + }); + } + + // 思考预算占比滑块事件处理 + if (this.thinkBudgetPercentInput && this.thinkBudgetPercentValue) { + this.thinkBudgetPercentInput.addEventListener('input', (e) => { + // 更新思考预算显示 + this.updateThinkBudgetDisplay(); + + // 更新滑块背景 + this.updateRangeSliderBackground(e.target); + + this.saveSettings(); + }); + } + this.temperatureInput.addEventListener('input', (e) => { this.temperatureValue.textContent = e.target.value; + this.updateRangeSliderBackground(e.target); this.saveSettings(); }); @@ -465,6 +589,30 @@ class SettingsManager { this.settingsPanel.classList.add('hidden'); }); } + + // 辅助方法:更新滑块的背景颜色 + updateRangeSliderBackground(slider) { + if (!slider) return; + + const value = slider.value; + const min = slider.min || 0; + const max = slider.max || 100; + const percentage = (value - min) / (max - min) * 100; + slider.style.background = `linear-gradient(to right, var(--primary) 0%, var(--primary) ${percentage}%, var(--border-color) ${percentage}%, var(--border-color) 100%)`; + } + + // 更新思考预算显示 + updateThinkBudgetDisplay() { + if (this.thinkBudgetPercentInput && this.thinkBudgetPercentValue) { + const percent = parseInt(this.thinkBudgetPercentInput.value); + + // 只显示百分比,不显示token数量 + this.thinkBudgetPercentValue.textContent = `${percent}%`; + + // 更新滑块背景 + this.updateRangeSliderBackground(this.thinkBudgetPercentInput); + } + } /** * 初始化可折叠内容的交互逻辑 diff --git a/static/style.css b/static/style.css index 204dfd2..d60137e 100644 --- a/static/style.css +++ b/static/style.css @@ -716,6 +716,28 @@ textarea:focus { transform: translateY(-1px); } +.input-styled { + width: 100%; + padding: 8px 10px; + background-color: var(--input-bg-color); + border: 1px solid var(--border-color); + border-radius: 4px; + color: var(--input-text-color); + font-size: 1rem; + transition: border-color 0.2s; +} + +.input-styled:focus { + border-color: var(--accent-color); + outline: none; +} + +[data-theme="dark"] .input-styled { + background-color: var(--input-bg-color-dark); + color: var(--input-text-color-dark); + border-color: var(--border-color-dark); +} + .input-group { position: relative; display: flex; @@ -748,7 +770,7 @@ input[type="range"] { flex: 1; height: 6px; -webkit-appearance: none; - background: linear-gradient(to right, var(--primary) 0%, var(--primary) 70%, var(--border-color) 70%, var(--border-color) 100%); + background: linear-gradient(to right, var(--primary) 0%, var(--primary) 50%, var(--border-color) 50%, var(--border-color) 100%); border-radius: 3px; cursor: pointer; } @@ -1341,3 +1363,23 @@ button:disabled { padding-top: 1rem; border-top: 1px dashed var(--border-color); } + +/* 推理深度设置相关样式 */ +.reasoning-setting-group { + padding: 10px; + border-radius: 4px; + background-color: var(--highlight-bg-color); +} + +.think-budget-group { + margin-top: 0; + padding: 10px; + border-radius: 4px; + background-color: var(--highlight-bg-color); +} + +/* 推理设置在暗模式下的样式 */ +[data-theme="dark"] .reasoning-setting-group, +[data-theme="dark"] .think-budget-group { + background-color: var(--highlight-bg-color-dark); +} diff --git a/templates/index.html b/templates/index.html index eefc5bb..290dce6 100644 --- a/templates/index.html +++ b/templates/index.html @@ -123,6 +123,24 @@ 版本: - +
+ + +
+
+ + +
+
+ +
+ + 50% +
+