/** * 模型选择器类 - 处理模型选择下拉列表的所有交互 */ class ModelSelector { /** * 构造函数 * @param {Object} options 配置选项 * @param {Function} options.onChange 选择变更回调 * @param {Object} options.models 模型定义对象 * @param {Object} options.providers 提供商定义对象 */ constructor(options) { this.options = options || {}; this.models = this.options.models || {}; this.providers = this.options.providers || {}; this.onChange = this.options.onChange || (() => {}); // 元素引用 this.container = document.getElementById('modelSelector'); this.display = this.container?.querySelector('.model-display'); this.currentNameEl = document.getElementById('currentModelName'); this.currentProviderEl = document.getElementById('currentModelProvider'); this.badgesContainer = document.getElementById('modelBadges'); this.originalSelect = document.getElementById('modelSelect'); // 创建下拉面板和遮罩 this.createDropdownElements(); // 当前选中的模型ID this.selectedModelId = null; // 初始化 this.initEvents(); } /** * 创建下拉面板和遮罩元素 */ createDropdownElements() { // 创建遮罩层 this.overlay = document.createElement('div'); this.overlay.className = 'model-dropdown-overlay'; document.body.appendChild(this.overlay); // 创建下拉面板 this.dropdown = document.createElement('div'); this.dropdown.className = 'model-dropdown-panel'; document.body.appendChild(this.dropdown); } /** * 初始化事件监听 */ initEvents() { if (!this.display) return; // 点击选择器显示下拉框 this.display.addEventListener('click', (e) => { e.stopPropagation(); this.toggleDropdown(); }); // 点击遮罩层关闭下拉框 this.overlay.addEventListener('click', () => { this.closeDropdown(); }); // 监听原始select变化,保持同步 if (this.originalSelect) { this.originalSelect.addEventListener('change', () => { const modelId = this.originalSelect.value; if (modelId && modelId !== this.selectedModelId) { this.selectModel(modelId, false); // 不触发onChange避免循环 } }); } // 防止面板内部点击冒泡到遮罩 this.dropdown.addEventListener('click', (e) => { e.stopPropagation(); }); console.log('模型选择器事件初始化完成'); } /** * 加载模型选项到下拉面板 */ loadModelOptions() { if (!this.dropdown) return; // 清空下拉面板 this.dropdown.innerHTML = ''; // 按提供商分组模型 const groupedModels = {}; Object.entries(this.models).forEach(([modelId, model]) => { const providerId = model.provider; if (!groupedModels[providerId]) { groupedModels[providerId] = []; } groupedModels[providerId].push({ id: modelId, ...model }); }); // 创建分组选项 Object.entries(groupedModels).forEach(([providerId, models]) => { const provider = this.providers[providerId] || { name: providerId }; // 创建分组容器 const group = document.createElement('div'); group.className = 'model-group'; // 创建分组标题 const title = document.createElement('div'); title.className = 'model-group-title'; title.textContent = provider.name; group.appendChild(title); // 添加该分组的模型选项 models.sort((a, b) => a.name.localeCompare(b.name)) .forEach(model => { const option = document.createElement('div'); option.className = 'model-option'; option.dataset.modelId = model.id; if (model.id === this.selectedModelId) { option.classList.add('selected'); } // 模型名称 - 移除版本显示 const nameEl = document.createElement('div'); nameEl.className = 'model-option-name'; nameEl.textContent = model.name; option.appendChild(nameEl); // 能力徽章 if (model.supportsMultimodal || model.isReasoning) { const badges = document.createElement('div'); badges.className = 'model-option-badges'; if (model.supportsMultimodal) { const badge = document.createElement('div'); badge.className = 'model-option-badge'; badge.title = '支持图像'; badge.innerHTML = ''; badges.appendChild(badge); } if (model.isReasoning) { const badge = document.createElement('div'); badge.className = 'model-option-badge'; badge.title = '支持深度推理'; badge.innerHTML = ''; badges.appendChild(badge); } option.appendChild(badges); } // 点击选项选择模型 option.addEventListener('click', () => { this.selectModel(model.id); this.closeDropdown(); }); group.appendChild(option); }); // 只添加有模型的分组 if (group.childElementCount > 1) { // > 1 因为包含标题 this.dropdown.appendChild(group); } }); console.log('模型选项加载完成'); } /** * 打开下拉面板 */ openDropdown() { if (!this.container || !this.dropdown || !this.overlay) return; // 加载模型选项 this.loadModelOptions(); // 显示遮罩和下拉面板 this.overlay.style.display = 'block'; this.dropdown.style.display = 'block'; // 设置面板位置 - 相对于视口 this.adjustDropdownPosition(); // 添加窗口调整大小和滚动事件监听器 window.addEventListener('resize', this.adjustDropdownPosition.bind(this)); window.addEventListener('scroll', this.adjustDropdownPosition.bind(this)); // 延迟添加可见类以启用过渡效果 setTimeout(() => { this.dropdown.classList.add('visible'); }, 10); // 添加打开状态类 this.container.classList.add('open'); // 确保当前选中的选项可见 setTimeout(() => { const selectedOption = this.dropdown.querySelector('.model-option.selected'); if (selectedOption) { selectedOption.scrollIntoView({ block: 'nearest', behavior: 'smooth' }); } }, 100); } /** * 调整下拉面板位置 */ adjustDropdownPosition() { if (!this.display || !this.dropdown) return; // 获取模型选择器的位置 const rect = this.display.getBoundingClientRect(); // 设置下拉面板宽度 this.dropdown.style.width = `${rect.width}px`; // 计算最佳位置 const viewportHeight = window.innerHeight; const spaceBelow = viewportHeight - rect.bottom; const spaceAbove = rect.top; const dropdownHeight = Math.min(300, Math.max(this.dropdown.scrollHeight, 100)); // 检查下方空间是否足够 if (spaceBelow >= dropdownHeight || spaceBelow >= spaceAbove) { // 显示在下方 this.dropdown.style.top = `${rect.bottom + 5}px`; this.dropdown.style.bottom = 'auto'; } else { // 显示在上方 this.dropdown.style.bottom = `${viewportHeight - rect.top + 5}px`; this.dropdown.style.top = 'auto'; } // 水平定位 this.dropdown.style.left = `${rect.left}px`; // 检查是否超出右侧边界 const rightEdge = rect.left + rect.width; const viewportWidth = window.innerWidth; if (rightEdge > viewportWidth) { this.dropdown.style.left = 'auto'; this.dropdown.style.right = '10px'; } } /** * 关闭下拉面板 */ closeDropdown() { if (!this.container || !this.dropdown || !this.overlay) return; // 移除可见类 this.dropdown.classList.remove('visible'); // 移除打开状态类 this.container.classList.remove('open'); // 移除事件监听器 window.removeEventListener('resize', this.adjustDropdownPosition.bind(this)); window.removeEventListener('scroll', this.adjustDropdownPosition.bind(this)); // 延迟隐藏元素,以便完成过渡动画 setTimeout(() => { this.overlay.style.display = 'none'; this.dropdown.style.display = 'none'; }, 200); } /** * 切换下拉面板显示状态 */ toggleDropdown() { if (this.container.classList.contains('open')) { this.closeDropdown(); } else { this.openDropdown(); } } /** * 选择模型 * @param {string} modelId 模型ID * @param {boolean} triggerChange 是否触发onChange回调,默认true */ selectModel(modelId, triggerChange = true) { const model = this.models[modelId]; if (!model) return; // 更新选中的模型ID this.selectedModelId = modelId; // 更新显示信息 this.updateDisplayInfo(model); // 更新原始select if (this.originalSelect && this.originalSelect.value !== modelId) { this.originalSelect.value = modelId; } // 触发变更回调 if (triggerChange) { this.onChange(modelId, model); } console.log('已选择模型:', modelId); } /** * 更新显示信息 * @param {Object} model 模型信息 */ updateDisplayInfo(model) { if (!this.currentNameEl || !this.currentProviderEl || !this.badgesContainer) return; // 更新模型名称 - 不在这里显示版本号 this.currentNameEl.textContent = model.name; // 更新提供商 const provider = this.providers[model.provider] || { name: model.provider }; this.currentProviderEl.textContent = provider.name; // 更新能力徽章 this.badgesContainer.innerHTML = ''; if (model.supportsMultimodal) { const badge = document.createElement('div'); badge.className = 'model-badge'; badge.title = '支持图像'; badge.innerHTML = ''; this.badgesContainer.appendChild(badge); } if (model.isReasoning) { const badge = document.createElement('div'); badge.className = 'model-badge'; badge.title = '支持深度推理'; badge.innerHTML = ''; this.badgesContainer.appendChild(badge); } } /** * 设置模型数据 * @param {Object} models 模型定义对象 * @param {Object} providers 提供商定义对象 */ setModelData(models, providers) { this.models = models || {}; this.providers = providers || {}; } } class SettingsManager { constructor() { // 初始化属性 this.modelDefinitions = {}; this.providerDefinitions = {}; // 初始化界面元素 this.initializeElements(); // 提示词配置 this.prompts = {}; this.currentPromptId = 'default'; // 模型选择器对象 this.modelSelector = null; // OCR源配置 this.ocrSource = 'auto'; // 默认自动选择 // 存储API密钥的对象 this.apiKeyValues = { 'AnthropicApiKey': '', 'OpenaiApiKey': '', 'DeepseekApiKey': '', 'AlibabaApiKey': '', 'GoogleApiKey': '', 'DoubaoApiKey': '', 'BaiduApiKey': '', 'BaiduSecretKey': '', 'MathpixAppId': '', 'MathpixAppKey': '' }; // 存储API基础URL的对象 this.apiBaseUrlValues = { 'AnthropicApiBaseUrl': '', 'OpenaiApiBaseUrl': '', 'DeepseekApiBaseUrl': '', 'AlibabaApiBaseUrl': '', 'GoogleApiBaseUrl': '', 'DoubaoApiBaseUrl': '' }; // 加载模型配置 this.isInitialized = false; this.initialize(); } async initialize() { try { // 加载模型配置 await this.loadModelConfig(); // 成功加载配置后,执行后续初始化 this.updateModelOptions(); await this.loadSettings(); await this.loadPrompts(); // 加载提示词配置 this.setupEventListeners(); this.updateUIBasedOnModelType(); // 刷新API密钥状态 await this.refreshApiKeyStatus(); // 刷新API基础URL状态 await this.refreshApiBaseUrlStatus(); // 初始化可折叠内容逻辑 this.initCollapsibleContent(); // 初始化Token显示 if (this.maxTokens && this.maxTokensValue) { this.updateTokenValueDisplay(); this.highlightActivePreset(); } // 初始化模型选择器 this.initModelSelector(); // 添加到window对象,方便在控制台调试 window.debugModelSelector = { open: () => this.modelSelector?.openDropdown(), close: () => this.modelSelector?.closeDropdown(), toggle: () => this.modelSelector?.toggleDropdown(), instance: this.modelSelector }; // 绑定提示词预览区域点击事件 this.initPromptPreviewEvents(); this.isInitialized = true; console.log('设置管理器初始化完成'); } catch (error) { console.error('初始化设置管理器失败:', error); window.uiManager?.showToast('加载模型配置失败,使用默认配置', 'error'); // 使用默认配置作为备份 this.setupDefaultModels(); this.updateModelOptions(); await this.loadSettings(); await this.loadPrompts(); // 加载提示词配置 this.setupEventListeners(); this.updateUIBasedOnModelType(); // 刷新API密钥状态(即使在出错情况下也尝试) try { await this.refreshApiKeyStatus(); await this.refreshApiBaseUrlStatus(); } catch (e) { console.error('刷新API状态失败:', e); } // 初始化可折叠内容逻辑 this.initCollapsibleContent(); // 初始化模型选择器 this.initModelSelector(); this.isInitialized = true; } } // 初始化新的模型选择器 initModelSelector() { if (this.modelSelector) { // 如果已存在,更新数据 this.modelSelector.setModelData(this.modelDefinitions, this.providerDefinitions); } else { // 创建新实例 this.modelSelector = new ModelSelector({ models: this.modelDefinitions, providers: this.providerDefinitions, onChange: (modelId) => { // 处理模型变更 console.log('模型已变更:', modelId); this.updateVisibleApiKey(modelId); this.updateUIBasedOnModelType(); this.updateModelVersionDisplay(modelId); this.saveSettings(); } }); } // 设置当前选择的模型 if (this.modelSelect && this.modelSelect.value) { this.modelSelector.selectModel(this.modelSelect.value, false); } } // 更新模型选择下拉框 updateModelOptions() { // 清空现有选项 this.modelSelect.innerHTML = ''; // 提取提供商信息 const providers = {}; Object.entries(this.providerDefinitions).forEach(([providerId, provider]) => { providers[providerId] = provider.name; }); // 为每个提供商创建一个选项组 for (const [providerId, providerName] of Object.entries(providers)) { const optgroup = document.createElement('optgroup'); optgroup.label = providerName; // 过滤该提供商的模型 const providerModels = Object.entries(this.modelDefinitions) .filter(([_, modelInfo]) => modelInfo.provider === providerId) .sort((a, b) => a[1].name.localeCompare(b[1].name)); // 添加该提供商的模型选项 for (const [modelId, modelInfo] of providerModels) { // 添加到原始select元素 const option = document.createElement('option'); option.value = modelId; // 只显示模型名称,不再显示版本号 option.textContent = modelInfo.name; optgroup.appendChild(option); } // 只添加有模型的提供商 if (optgroup.children.length > 0) { this.modelSelect.appendChild(optgroup); } } } async loadSettings() { try { // 先从localStorage加载大部分设置 const settings = JSON.parse(localStorage.getItem('aiSettings') || '{}'); // 刷新API密钥状态(自动从服务器获取最新状态) await this.refreshApiKeyStatus(); console.log('已自动刷新API密钥状态'); // 加载 API 基础 URL 设置 if (settings.apiBaseUrlValues) { this.apiBaseUrlValues = settings.apiBaseUrlValues; await this.refreshApiBaseUrlStatus(); console.log('已加载 API 基础 URL 设置'); } // 加载其他设置 // Load model selection if (settings.model && this.modelExists(settings.model)) { this.modelSelect.value = settings.model; this.updateVisibleApiKey(settings.model); // 使用新的模型选择器更新UI if (this.modelSelector) { this.modelSelector.selectModel(settings.model, false); } } // Load max tokens setting - 现在直接设置输入框值 if (settings.maxTokens) { this.maxTokens.value = settings.maxTokens; this.updateTokenValueDisplay(); this.highlightActivePreset(); } // Load reasoning depth & think budget settings if (settings.reasoningDepth) { this.reasoningDepthSelect.value = settings.reasoningDepth; // 更新推理深度选项UI this.updateReasoningOptionUI(settings.reasoningDepth); } // 加载豆包思考模式设置 if (settings.doubaoThinkingMode && this.doubaoThinkingModeSelect) { this.doubaoThinkingModeSelect.value = settings.doubaoThinkingMode; // 更新豆包思考选项UI this.updateDoubaoThinkingOptionUI(settings.doubaoThinkingMode); } // 加载思考预算百分比 const thinkBudgetPercent = parseInt(settings.thinkBudgetPercent || '50'); if (this.thinkBudgetPercentInput) { this.thinkBudgetPercentInput.value = thinkBudgetPercent; } // 更新思考预算显示和滑块背景 this.updateThinkBudgetDisplay(); this.updateThinkBudgetSliderBackground(); this.highlightActiveThinkPreset(); // Load temperature setting if (settings.temperature) { this.temperatureInput.value = settings.temperature; } // 先记录用户设置的提示词ID(如果有) if (settings.currentPromptId) { this.currentPromptId = settings.currentPromptId; } // 如果系统提示词内容已保存在设置中,先恢复它 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; } // Load OCR source setting if (settings.ocrSource) { this.ocrSource = settings.ocrSource; if (this.ocrSourceSelect) { this.ocrSourceSelect.value = settings.ocrSource; } } // Update UI based on model type this.updateUIBasedOnModelType(); } catch (error) { console.error('加载设置出错:', error); window.uiManager?.showToast('加载设置出错', 'error'); } } modelExists(modelId) { return this.modelDefinitions.hasOwnProperty(modelId); } // 更新模型版本显示 updateModelVersionDisplay(modelId) { const modelVersionInfo = document.getElementById('modelVersionInfo'); const modelVersionText = document.getElementById('modelVersionText'); if (!modelVersionText || !modelVersionInfo) return; const model = this.modelDefinitions[modelId]; if (!model) { modelVersionText.textContent = '-'; modelVersionInfo.classList.remove('has-version'); return; } // 显示版本信息(如果有) if (model.version && model.version !== 'latest') { // 设置版本文本 modelVersionText.textContent = model.version; // 添加具有版本的类 modelVersionInfo.classList.add('has-version'); // 统一使用分支图标和紫色 const icon = modelVersionInfo.querySelector('i'); if (icon) { icon.className = 'fas fa-code-branch'; icon.title = `版本 ${model.version}`; } // 移除所有特定版本类型的类 modelVersionInfo.classList.remove('date-version', 'semantic-version'); } else if (model.version === 'latest') { // 使用英文"latest"而不是中文 modelVersionText.textContent = 'latest'; modelVersionInfo.classList.add('has-version'); // 对latest版本也使用相同的分支图标 const icon = modelVersionInfo.querySelector('i'); if (icon) { icon.className = 'fas fa-code-branch'; icon.title = '最新版本'; } // 移除所有特定版本类型的类 modelVersionInfo.classList.remove('date-version', 'semantic-version'); } else { modelVersionText.textContent = '-'; modelVersionInfo.classList.remove('has-version', 'date-version', 'semantic-version'); } } /** * 根据选择的模型类型更新UI显示 */ updateUIBasedOnModelType() { // 更新UI元素显示,根据所选模型类型 const selectedModel = this.modelSelect.value; const modelInfo = this.modelDefinitions[selectedModel]; // 更新当前可见的API密钥 this.updateVisibleApiKey(selectedModel); 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'; } // 处理豆包深度思考设置显示 const isDoubaoReasoning = modelInfo.isReasoning && modelInfo.provider === 'doubao'; // 只有对豆包推理模型才显示深度思考设置 if (this.doubaoThinkingGroup) { this.doubaoThinkingGroup.style.display = isDoubaoReasoning ? 'block' : 'none'; } // 控制最大Token设置的显示 // 阿里巴巴模型不支持自定义Token设置 const maxTokensGroup = this.maxTokens ? this.maxTokens.closest('.setting-group') : null; if (maxTokensGroup) { // 如果是阿里巴巴模型,隐藏Token设置 const isAlibabaModel = modelInfo.provider === 'alibaba'; maxTokensGroup.style.display = isAlibabaModel ? 'none' : 'block'; } // 更新模型版本显示 this.updateModelVersionDisplay(selectedModel); } /** * 根据选择的模型更新显示的API密钥 * @param {string} modelType 模型类型 */ updateVisibleApiKey(modelType) { // 高亮显示对应的API密钥 const allApiKeys = document.querySelectorAll('.api-key-status'); // 先清除所有高亮 allApiKeys.forEach(key => { key.classList.remove('highlight'); }); // 根据模型类型高亮相应的API密钥 let apiKeyToHighlight = null; if (modelType && modelType.toLowerCase().includes('claude')) { apiKeyToHighlight = document.querySelector('.api-key-status:nth-child(1)'); // Anthropic } else if (modelType && (modelType.toLowerCase().includes('gpt') || modelType.toLowerCase().includes('openai'))) { apiKeyToHighlight = document.querySelector('.api-key-status:nth-child(2)'); // OpenAI } else if (modelType && modelType.toLowerCase().includes('deepseek')) { apiKeyToHighlight = document.querySelector('.api-key-status:nth-child(3)'); // DeepSeek } 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 } else if (modelType && modelType.toLowerCase().includes('doubao')) { apiKeyToHighlight = document.querySelector('.api-key-status:nth-child(6)'); // 豆包 } if (apiKeyToHighlight) { apiKeyToHighlight.classList.add('highlight'); } } async saveSettings() { try { // 保存UI设置到localStorage(不包含API密钥) const settings = { apiKeys: this.apiKeyValues, // 保存到localStorage(向后兼容) apiBaseUrlValues: this.apiBaseUrlValues, // 添加API基础URL保存到localStorage model: this.modelSelect.value, maxTokens: this.maxTokens.value, reasoningDepth: this.reasoningDepthSelect?.value || 'standard', doubaoThinkingMode: this.doubaoThinkingModeSelect?.value || 'auto', thinkBudgetPercent: this.thinkBudgetPercentInput?.value || '50', 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, ocrSource: this.ocrSource // 添加OCR源配置保存 }; // 保存设置到localStorage localStorage.setItem('aiSettings', JSON.stringify(settings)); window.uiManager?.showToast('设置已保存', 'success'); } catch (error) { console.error('保存设置出错:', error); window.uiManager?.showToast('保存设置出错: ' + error.message, 'error'); } } getApiKey() { const selectedModel = this.modelSelect.value; const modelInfo = this.modelDefinitions[selectedModel]; if (!modelInfo) return ''; const apiKeyId = modelInfo.apiKeyId; const apiKey = this.apiKeyInputs[apiKeyId]?.value; if (!apiKey) { window.showToast('Please enter API key for the selected model', 'error'); return ''; } return apiKey; } getSettings() { const language = this.languageInput.value || '中文'; const basePrompt = this.systemPromptInput.value || ''; // 直接使用带语言提示的系统提示词 // 注意:systemPromptInput.value 可能已经在 loadPrompt 中设置了语言提示 // 为避免重复添加,我们先提取提示词的主体部分(不含语言提示) const promptMainPart = basePrompt.split("\n\n请务必使用")[0]; const systemPrompt = `${promptMainPart}\n\n请务必使用${language}回答。`; const selectedModel = this.modelSelect.value; const modelInfo = this.modelDefinitions[selectedModel] || {}; // 获取最大Token数 const maxTokens = parseInt(this.maxTokens?.value || '8192'); // 获取推理深度设置 const reasoningDepth = this.reasoningDepthSelect?.value || 'standard'; const thinkBudgetPercent = parseInt(this.thinkBudgetPercentInput?.value || '50'); // 获取豆包思考模式设置 const doubaoThinkingMode = this.doubaoThinkingModeSelect?.value || 'auto'; // 计算思考预算的实际Token数 const thinkBudget = Math.floor(maxTokens * (thinkBudgetPercent / 100)); // 构建推理配置参数 const reasoningConfig = {}; // 处理不同模型的推理配置 if (modelInfo.isReasoning) { // 对于Anthropic模型 if (modelInfo.provider === 'anthropic') { if (reasoningDepth === 'extended') { reasoningConfig.reasoning_depth = 'extended'; reasoningConfig.think_budget = thinkBudget; } else { reasoningConfig.speed_mode = 'instant'; } } // 对于豆包模型 if (modelInfo.provider === 'doubao') { reasoningConfig.thinking_mode = doubaoThinkingMode; } } // 从apiKeyValues获取Mathpix信息,而不是直接从DOM读取 const mathpixAppId = this.apiKeyValues['MathpixAppId'] || ''; const mathpixAppKey = this.apiKeyValues['MathpixAppKey'] || ''; const mathpixApiKey = mathpixAppId && mathpixAppKey ? `${mathpixAppId}:${mathpixAppKey}` : ''; // 从apiBaseUrlValues映射到服务器API所需格式 const apiBaseUrls = {}; if (this.apiBaseUrlValues) { if (this.apiBaseUrlValues['AnthropicApiBaseUrl']) { apiBaseUrls.anthropic = this.apiBaseUrlValues['AnthropicApiBaseUrl']; } if (this.apiBaseUrlValues['OpenaiApiBaseUrl']) { apiBaseUrls.openai = this.apiBaseUrlValues['OpenaiApiBaseUrl']; } if (this.apiBaseUrlValues['DeepseekApiBaseUrl']) { apiBaseUrls.deepseek = this.apiBaseUrlValues['DeepseekApiBaseUrl']; } if (this.apiBaseUrlValues['AlibabaApiBaseUrl']) { apiBaseUrls.alibaba = this.apiBaseUrlValues['AlibabaApiBaseUrl']; } if (this.apiBaseUrlValues['GoogleApiBaseUrl']) { apiBaseUrls.google = this.apiBaseUrlValues['GoogleApiBaseUrl']; } if (this.apiBaseUrlValues['DoubaoApiBaseUrl']) { apiBaseUrls.doubao = this.apiBaseUrlValues['DoubaoApiBaseUrl']; } } return { model: selectedModel, maxTokens: maxTokens, temperature: this.temperatureInput.value, language: language, systemPrompt: systemPrompt, proxyEnabled: this.proxyEnabledInput.checked, proxyHost: this.proxyHostInput.value, proxyPort: this.proxyPortInput.value, mathpixApiKey: mathpixApiKey, ocrSource: this.ocrSource, // 添加OCR源配置 doubaoThinkingMode: doubaoThinkingMode, // 添加豆包思考模式配置 modelInfo: { supportsMultimodal: modelInfo.supportsMultimodal || false, isReasoning: modelInfo.isReasoning || false, provider: modelInfo.provider || 'unknown' }, reasoningConfig: reasoningConfig, apiBaseUrls: apiBaseUrls, apiKeys: this.apiKeyValues // 确保传递API密钥 }; } getModelCapabilities(modelId) { const model = this.modelDefinitions[modelId]; if (!model) return { supportsMultimodal: false, isReasoning: false }; return { supportsMultimodal: model.supportsMultimodal, isReasoning: model.isReasoning }; } setupEventListeners() { this.modelSelect.addEventListener('change', (e) => { this.updateVisibleApiKey(e.target.value); this.updateUIBasedOnModelType(); this.updateModelVersionDisplay(e.target.value); this.saveSettings(); // 通知应用更新图像操作按钮 if (window.app && typeof window.app.updateImageActionButtons === 'function') { window.app.updateImageActionButtons(); } }); // 提示词相关事件监听 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(); }); } // 最大Token输入框事件处理 if (this.maxTokens) { this.maxTokens.addEventListener('input', () => { this.updateTokenValueDisplay(); this.updateTokenSliderBackground(); this.highlightActivePreset(); this.saveSettings(); }); this.maxTokens.addEventListener('change', () => { this.saveSettings(); }); } // 推理深度选择事件处理 - 新增标签式UI if (this.reasoningOptions && this.reasoningOptions.length > 0) { this.reasoningOptions.forEach(option => { option.addEventListener('click', (e) => { // 阻止事件冒泡 e.stopPropagation(); // 获取选择的值 const value = option.getAttribute('data-value'); // 更新隐藏的select元素值 if (this.reasoningDepthSelect) { this.reasoningDepthSelect.value = value; } // 更新视觉效果 this.reasoningOptions.forEach(opt => { opt.classList.remove('active'); }); option.classList.add('active'); // 更新思考预算组的可见性 if (this.thinkBudgetGroup) { const showThinkBudget = value === 'extended'; this.thinkBudgetGroup.style.display = showThinkBudget ? 'block' : 'none'; } this.saveSettings(); }); }); } // 思考预算预设按钮事件 if (this.thinkPresets && this.thinkPresets.length > 0) { this.thinkPresets.forEach(preset => { preset.addEventListener('click', (e) => { // 阻止事件冒泡 e.stopPropagation(); // 获取预设值 const value = parseInt(preset.getAttribute('data-value')); // 更新滑块值 if (this.thinkBudgetPercentInput) { this.thinkBudgetPercentInput.value = value; // 更新显示和滑块背景 this.updateThinkBudgetDisplay(); this.updateThinkBudgetSliderBackground(); } // 更新预设按钮样式 this.highlightActiveThinkPreset(); this.saveSettings(); }); }); } // 思考预算占比滑块事件处理 if (this.thinkBudgetPercentInput && this.thinkBudgetPercentValue) { this.thinkBudgetPercentInput.addEventListener('input', (e) => { // 阻止事件冒泡 e.stopPropagation(); // 更新思考预算显示 this.updateThinkBudgetDisplay(); // 更新滑块背景 this.updateThinkBudgetSliderBackground(); // 更新预设按钮高亮状态 this.highlightActiveThinkPreset(); }); this.thinkBudgetPercentInput.addEventListener('change', () => { this.saveSettings(); }); } this.temperatureInput.addEventListener('input', (e) => { // 阻止事件冒泡 e.stopPropagation(); this.saveSettings(); }); this.languageInput.addEventListener('change', (e) => { // 阻止事件冒泡 e.stopPropagation(); // 如果当前有加载提示词,重新加载它以更新语言 if (this.currentPromptId && this.prompts[this.currentPromptId]) { this.loadPrompt(this.currentPromptId); } else { // 没有当前提示词,只保存设置 this.saveSettings(); } }); this.proxyEnabledInput.addEventListener('change', (e) => { // 阻止事件冒泡 e.stopPropagation(); this.proxySettings.style.display = e.target.checked ? 'block' : 'none'; this.saveSettings(); }); this.proxyHostInput.addEventListener('change', (e) => { // 阻止事件冒泡 e.stopPropagation(); this.saveSettings(); }); this.proxyPortInput.addEventListener('change', (e) => { // 阻止事件冒泡 e.stopPropagation(); this.saveSettings(); }); // OCR源选择器事件监听 if (this.ocrSourceSelect) { this.ocrSourceSelect.addEventListener('change', (e) => { // 阻止事件冒泡 e.stopPropagation(); // 更新OCR源配置 this.ocrSource = e.target.value; this.saveSettings(); console.log('OCR源已切换为:', this.ocrSource); }); } // Panel visibility if (this.settingsToggle) { this.settingsToggle.addEventListener('click', () => { this.toggleSettingsPanel(); }); } if (this.closeSettings) { this.closeSettings.addEventListener('click', () => { this.closeSettingsPanel(); }); } // 确保设置面板自身的点击不会干扰内部操作 if (this.settingsPanel) { const settingsSections = this.settingsPanel.querySelectorAll('.settings-section'); settingsSections.forEach(section => { section.addEventListener('click', (e) => { // 只阻止直接点击设置部分的事件 if (e.target === section) { e.stopPropagation(); } }); }); // 设置内容区域防止冒泡 const settingsContent = this.settingsPanel.querySelector('.settings-content'); if (settingsContent) { settingsContent.addEventListener('click', (e) => { // 只阻止直接点击设置内容区域的事件 if (e.target === settingsContent) { e.stopPropagation(); } }); } } if (this.tokenPresets) { this.tokenPresets.forEach(preset => { preset.addEventListener('click', e => { const value = parseInt(e.currentTarget.dataset.value); this.maxTokens.value = value; this.updateTokenValueDisplay(); this.highlightActivePreset(); this.saveSettings(); }); }); } // 主题切换监听 const themeToggle = document.getElementById('themeToggle'); if (themeToggle) { themeToggle.addEventListener('click', () => { const currentTheme = document.documentElement.getAttribute('data-theme') || 'light'; const newTheme = currentTheme === 'light' ? 'dark' : 'light'; document.documentElement.setAttribute('data-theme', newTheme); localStorage.setItem('theme', newTheme); // 更新滑块背景 this.updateTokenSliderBackground(); this.updateThinkBudgetSliderBackground(); }); } // 确保自定义模型选择器事件监听器被初始化 if (this.modelSelectorDisplay && this.modelDropdown) { this.initCustomSelectorEvents(); } // 初始化API基础URL编辑功能 this.initApiBaseUrlEditFunctions(); // 初始化API密钥编辑功能 this.initApiKeyEditFunctions(); // 初始化推理选项事件 this.initReasoningOptionEvents(); // 初始化豆包思考选项事件 this.initDoubaoThinkingOptionEvents(); } // 初始化推理选项事件 initReasoningOptionEvents() { const reasoningOptions = document.querySelectorAll('.reasoning-option'); reasoningOptions.forEach(option => { option.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const value = option.getAttribute('data-value'); if (value && this.reasoningDepthSelect) { // 更新select值 this.reasoningDepthSelect.value = value; // 更新UI this.updateReasoningOptionUI(value); // 保存设置 this.saveSettings(); } }); }); } // 初始化豆包思考选项事件 initDoubaoThinkingOptionEvents() { const doubaoThinkingOptions = document.querySelectorAll('.doubao-thinking-option'); doubaoThinkingOptions.forEach(option => { option.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); const value = option.getAttribute('data-value'); if (value && this.doubaoThinkingModeSelect) { // 更新select值 this.doubaoThinkingModeSelect.value = value; // 更新UI this.updateDoubaoThinkingOptionUI(value); // 保存设置 this.saveSettings(); } }); }); } // 更新豆包思考选项UI updateDoubaoThinkingOptionUI(value) { const doubaoThinkingOptions = document.querySelectorAll('.doubao-thinking-option'); doubaoThinkingOptions.forEach(option => { const optionValue = option.getAttribute('data-value'); if (optionValue === value) { option.classList.add('active'); } else { option.classList.remove('active'); } }); } // 更新思考预算显示 updateThinkBudgetDisplay() { if (this.thinkBudgetPercentInput && this.thinkBudgetPercentValue) { const percent = parseInt(this.thinkBudgetPercentInput.value); this.thinkBudgetPercentValue.textContent = `${percent}%`; } } // 更新思考预算滑块背景 updateThinkBudgetSliderBackground() { if (!this.thinkBudgetPercentInput) return; const min = parseInt(this.thinkBudgetPercentInput.min); const max = parseInt(this.thinkBudgetPercentInput.max); const value = parseInt(this.thinkBudgetPercentInput.value); const percentage = ((value - min) / (max - min)) * 100; // 获取当前主题 const isDarkMode = document.documentElement.getAttribute('data-theme') === 'dark'; const primaryColor = isDarkMode ? 'rgba(72, 149, 239, 0.8)' : 'rgba(58, 134, 255, 0.8)'; const secondaryColor = isDarkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; this.thinkBudgetPercentInput.style.background = `linear-gradient(to right, ${primaryColor} 0%, ${primaryColor} ${percentage}%, ${secondaryColor} ${percentage}%, ${secondaryColor} 100%)`; } // 更新推理深度选项UI updateReasoningOptionUI(value) { if (!this.reasoningOptions) return; this.reasoningOptions.forEach(option => { const optionValue = option.getAttribute('data-value'); if (optionValue === value) { option.classList.add('active'); } else { option.classList.remove('active'); } }); // 更新思考预算组的可见性 if (this.thinkBudgetGroup) { const showThinkBudget = value === 'extended'; this.thinkBudgetGroup.style.display = showThinkBudget ? 'block' : 'none'; } } // 高亮当前激活的思考预算预设按钮 highlightActiveThinkPreset() { if (!this.thinkPresets || !this.thinkBudgetPercentInput) return; const value = parseInt(this.thinkBudgetPercentInput.value); this.thinkPresets.forEach(preset => { const presetValue = parseInt(preset.getAttribute('data-value')); if (presetValue === value) { preset.classList.add('active'); } else { preset.classList.remove('active'); } }); } /** * 初始化可折叠内容的交互逻辑 */ initCollapsibleContent() { const collapsibleHeaders = document.querySelectorAll('.collapsible-header'); collapsibleHeaders.forEach(header => { header.addEventListener('click', () => { const content = header.nextElementSibling; if (content && content.classList.contains('collapsible-content')) { // 切换展开/折叠状态 content.classList.toggle('expanded'); // 切换箭头方向 const arrow = header.querySelector('i.fa-chevron-down, i.fa-chevron-up'); if (arrow) { arrow.classList.toggle('fa-chevron-down'); arrow.classList.toggle('fa-chevron-up'); } } }); }); // 默认展开API基础URL设置区域 const apiBaseUrlHeader = document.querySelector('.api-url-settings .collapsible-header'); if (apiBaseUrlHeader) { const content = apiBaseUrlHeader.nextElementSibling; if (content) { content.classList.add('expanded'); const arrow = apiBaseUrlHeader.querySelector('i.fa-chevron-down'); if (arrow) { arrow.classList.remove('fa-chevron-down'); arrow.classList.add('fa-chevron-up'); } } } } /** * 初始化API密钥编辑相关功能 */ initApiKeyEditFunctions() { // 1. 编辑按钮点击事件 document.querySelectorAll('.edit-api-key').forEach(button => { button.addEventListener('click', (e) => { // 阻止事件冒泡 e.stopPropagation(); const keyType = e.currentTarget.getAttribute('data-key-type'); const keyStatus = e.currentTarget.closest('.key-status-wrapper'); if (keyStatus) { // 隐藏显示区域 const displayArea = keyStatus.querySelector('.key-display'); if (displayArea) displayArea.classList.add('hidden'); // 显示编辑区域 const editArea = keyStatus.querySelector('.key-edit'); if (editArea) { editArea.classList.remove('hidden'); // 获取当前密钥值并填入输入框 const keyInput = editArea.querySelector('.key-input'); if (keyInput) { // 从状态文本中获取当前值(如果不是"未设置") const statusElement = keyStatus.querySelector('.key-status'); if (statusElement && statusElement.textContent !== '未设置') { keyInput.value = this.apiKeyValues[keyType] || ''; } else { keyInput.value = ''; } // 聚焦输入框 setTimeout(() => { keyInput.focus(); }, 100); } } } }); }); // 2. 保存按钮点击事件 document.querySelectorAll('.save-api-key').forEach(button => { button.addEventListener('click', (e) => { // 阻止事件冒泡 e.stopPropagation(); const keyType = e.currentTarget.getAttribute('data-key-type'); const keyStatus = e.currentTarget.closest('.key-status-wrapper'); if (keyStatus) { // 获取输入的新密钥值 const keyInput = keyStatus.querySelector('.key-input'); if (keyInput) { const newValue = keyInput.value.trim(); // 保存到内存中 this.apiKeyValues[keyType] = newValue; // 创建要保存的API密钥对象 const apiKeysToSave = {}; apiKeysToSave[keyType] = newValue; // 保存到服务器 this.saveApiKey(keyType, newValue, keyStatus); // 隐藏编辑区域 const editArea = keyStatus.querySelector('.key-edit'); if (editArea) editArea.classList.add('hidden'); // 显示状态区域 const displayArea = keyStatus.querySelector('.key-display'); if (displayArea) displayArea.classList.remove('hidden'); } } }); }); // 3. 切换密码可见性按钮 document.querySelectorAll('.toggle-visibility').forEach(button => { button.addEventListener('click', (e) => { // 阻止事件冒泡 e.stopPropagation(); const keyInput = e.currentTarget.closest('.key-edit').querySelector('.key-input'); if (keyInput) { const type = keyInput.type === 'password' ? 'text' : 'password'; keyInput.type = type; // 更新图标 const icon = e.currentTarget.querySelector('i'); if (icon) { icon.className = `fas fa-${type === 'password' ? 'eye' : 'eye-slash'}`; } } }); }); // 4. 输入框按下Enter保存 document.querySelectorAll('.key-input').forEach(input => { input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { // 阻止事件冒泡 e.stopPropagation(); const saveButton = e.currentTarget.closest('.key-edit').querySelector('.save-api-key'); if (saveButton) { saveButton.click(); } } else if (e.key === 'Escape') { // 阻止事件冒泡 e.stopPropagation(); // 取消编辑 const keyStatus = e.currentTarget.closest('.key-status-wrapper'); if (keyStatus) { const editArea = keyStatus.querySelector('.key-edit'); if (editArea) editArea.classList.add('hidden'); const displayArea = keyStatus.querySelector('.key-display'); if (displayArea) displayArea.classList.remove('hidden'); } } }); }); } /** * 更新API密钥状态显示 * @param {Object} apiKeys 密钥对象 */ updateApiKeyStatus(apiKeys) { if (!this.apiKeysList) return; // 保存API密钥值到内存中 for (const [key, value] of Object.entries(apiKeys)) { this.apiKeyValues[key] = value; } // 找到所有密钥状态元素 Object.keys(apiKeys).forEach(keyId => { const statusElement = document.getElementById(`${keyId}Status`); if (!statusElement) return; const value = apiKeys[keyId]; if (value && value.trim() !== '') { // 显示密钥状态 - 已设置 statusElement.className = 'key-status set'; statusElement.innerHTML = ` 已设置`; } else { // 显示密钥状态 - 未设置 statusElement.className = 'key-status not-set'; statusElement.innerHTML = ` 未设置`; } }); } /** * 保存单个API密钥 * @param {string} keyType 密钥类型 * @param {string} value 密钥值 * @param {HTMLElement} keyStatus 密钥状态容器 */ async saveApiKey(keyType, value, keyStatus) { try { // 显示保存中状态 const saveToast = this.createToast('正在保存密钥...', 'info', true); // 创建要保存的数据对象 const apiKeysData = {}; apiKeysData[keyType] = value; // 发送到服务器 const response = await fetch('/api/keys', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(apiKeysData) }); // 移除保存中提示 if (saveToast) { saveToast.remove(); } if (response.ok) { const result = await response.json(); if (result.success) { // 更新密钥状态显示 const statusElem = document.getElementById(`${keyType}Status`); if (statusElem) { if (value && value.trim() !== '') { statusElem.className = 'key-status set'; statusElem.innerHTML = ` 已设置`; } else { statusElem.className = 'key-status not-set'; statusElem.innerHTML = ` 未设置`; } } // 改用全局UIManager的showToast方法来显示成功消息 if (window.uiManager) { window.uiManager.showToast('密钥已保存', 'success'); } else { // 如果UIManager不可用,使用自己的方法作为备选 this.createToast('密钥已保存', 'success'); } } else { if (window.uiManager) { window.uiManager.showToast('保存密钥失败: ' + result.message, 'error'); } else { this.createToast('保存密钥失败: ' + result.message, 'error'); } } } else { if (window.uiManager) { window.uiManager.showToast('无法连接到服务器', 'error'); } else { this.createToast('无法连接到服务器', 'error'); } } } catch (error) { console.error('保存密钥出错:', error); if (window.uiManager) { window.uiManager.showToast('保存密钥出错: ' + error.message, 'error'); } else { this.createToast('保存密钥出错: ' + error.message, 'error'); } } } /** * 创建一个Toast提示消息 * @param {string} message 提示消息内容 * @param {string} type 提示类型:'success', 'error', 'warning', 'info' * @param {boolean} persistent 是否为持久性提示(需要手动关闭) */ createToast(message, type = 'success', persistent = false) { const toastContainer = document.querySelector('.toast-container') || this.createToastContainer(); // 创建Toast元素 const toast = document.createElement('div'); toast.className = `toast ${type}`; if (persistent) { toast.classList.add('persistent'); } // 设置消息内容 toast.textContent = message; // 如果是持久性提示,添加关闭按钮 if (persistent) { const closeBtn = document.createElement('button'); closeBtn.className = 'toast-close'; closeBtn.innerHTML = '×'; closeBtn.addEventListener('click', () => { toast.remove(); }); toast.appendChild(closeBtn); } // 添加到容器 toastContainer.appendChild(toast); // 非持久性提示自动消失 if (!persistent) { setTimeout(() => { toast.remove(); }, 3000); } return toast; } /** * 创建Toast容器 * @returns {HTMLElement} Toast容器元素 */ createToastContainer() { const container = document.createElement('div'); container.className = 'toast-container'; document.body.appendChild(container); return container; } /** * 刷新API密钥状态 * 每次加载设置时自动调用,无需用户手动点击按钮 */ async refreshApiKeyStatus() { try { // 先将所有状态显示为"检查中" Object.keys(this.apiKeyValues).forEach(keyId => { const statusElement = document.getElementById(`${keyId}Status`); if (statusElement) { statusElement.className = 'key-status checking'; statusElement.innerHTML = ' 检查中...'; } }); // 发送请求获取API密钥状态 const response = await fetch('/api/keys', { method: 'GET', headers: { 'Content-Type': 'application/json', } }); if (response.ok) { const apiKeys = await response.json(); this.updateApiKeyStatus(apiKeys); console.log('API密钥状态已刷新'); } else { console.error('刷新API密钥状态失败'); } } catch (error) { console.error('刷新API密钥状态出错:', error); } } toggleSettingsPanel() { if (this.settingsPanel) { this.settingsPanel.classList.toggle('active'); } } closeSettingsPanel() { if (this.settingsPanel) { this.settingsPanel.classList.remove('active'); } } /** * 从服务器加载提示词列表 */ 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(); } // 如果当前已有选中的提示词,尝试加载它 if (this.currentPromptId && this.prompts[this.currentPromptId]) { this.loadPrompt(this.currentPromptId); } // 否则尝试加载默认提示词 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('提示词加载成功:', this.prompts); } else { console.error('加载提示词失败:', response.statusText); } } catch (error) { console.error('加载提示词错误:', error); // 显示错误描述 if (this.promptDescriptionElement) { this.promptDescriptionElement.innerHTML = '

加载提示词错误,请检查网络连接

'; } } } /** * 更新提示词选择下拉框 */ updatePromptSelect() { if (!this.promptSelect) return; // 暂存当前选中的提示词ID const currentPromptId = this.promptSelect.value; // 清空下拉框 this.promptSelect.innerHTML = ''; // 按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); } } /** * 加载指定的提示词 * @param {string} promptId 提示词ID */ loadPrompt(promptId) { if (!this.prompts[promptId]) return; // 更新当前提示词ID this.currentPromptId = promptId; // 获取当前选择的语言 const language = this.languageInput.value || '中文'; // 获取原始提示词内容 const basePrompt = this.prompts[promptId].content; // 添加语言指令(如果原始提示词中不包含语言指令) let systemPrompt = basePrompt; if (!basePrompt.includes('Please respond in') && !basePrompt.includes('请用') && !basePrompt.includes('使用')) { systemPrompt = `${basePrompt}\n\n请务必使用${language}回答。`; } // 更新提示词输入框 (隐藏,但仍需保存正确的内容) this.systemPromptInput.value = systemPrompt; // 更新提示词描述显示 - 使用完整的系统提示词,包括语言指令 if (this.promptDescriptionElement) { const description = this.prompts[promptId].description || systemPrompt; this.promptDescriptionElement.innerHTML = `

${description}

`; } // 更新提示词选择下拉框 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 }; // 如果是新增提示词,将其添加到顺序数组末尾 if (isNew && this.promptsOrder && !this.promptsOrder.includes(promptId)) { this.promptsOrder.push(promptId); } // 更新提示词选择下拉框 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) { // 从顺序数组中移除该提示词 if (this.promptsOrder && this.promptsOrder.includes(promptId)) { this.promptsOrder = this.promptsOrder.filter(id => id !== promptId); } // 删除本地提示词 delete this.prompts[promptId]; // 更新提示词选择下拉框 this.updatePromptSelect(); // 如果还有其他提示词,加载第一个 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 = ''; if (this.promptDescriptionElement) { this.promptDescriptionElement.innerHTML = '

暂无提示词,请点击"+"创建新提示词

'; } 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.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]) { // 如果没有选择提示词,但有系统提示词内容,将其作为新提示词 if (this.systemPromptInput.value.trim()) { this.openNewPromptDialog(); return; } 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() { if (this.promptDialog) { this.promptDialog.classList.remove('active'); } if (this.promptDialogOverlay) { this.promptDialogOverlay.classList.remove('active'); } } // 更新token值显示 updateTokenValueDisplay() { const value = parseInt(this.maxTokens.value); let displayValue = value.toString(); // 格式化大数字显示 if (value >= 1000) { if (value % 1000 === 0) { displayValue = (value / 1000) + 'K'; } else { displayValue = (value / 1000).toFixed(1) + 'K'; } } this.maxTokensValue.textContent = displayValue; this.updateTokenSliderBackground(); } // 更新滑块背景 updateTokenSliderBackground() { const min = parseInt(this.maxTokens.min); const max = parseInt(this.maxTokens.max); const value = parseInt(this.maxTokens.value); const percentage = ((value - min) / (max - min)) * 100; // 获取当前主题 const isDarkMode = document.documentElement.getAttribute('data-theme') === 'dark'; const primaryColor = isDarkMode ? 'rgba(72, 149, 239, 0.8)' : 'rgba(58, 134, 255, 0.8)'; const secondaryColor = isDarkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; this.maxTokens.style.background = `linear-gradient(to right, ${primaryColor} 0%, ${primaryColor} ${percentage}%, ${secondaryColor} ${percentage}%, ${secondaryColor} 100%)`; } // 高亮当前激活的预设按钮 highlightActivePreset() { const value = parseInt(this.maxTokens.value); this.tokenPresets.forEach(preset => { const presetValue = parseInt(preset.dataset.value); if (presetValue === value) { preset.classList.add('active'); } else { preset.classList.remove('active'); } }); } // 从配置文件加载模型定义 async loadModelConfig() { try { // 使用API端点获取模型列表 const response = await fetch('/api/models'); if (!response.ok) { throw new Error(`加载模型列表失败: ${response.status} ${response.statusText}`); } // 获取模型列表 const modelsList = await response.json(); // 获取提供商配置 const configResponse = await fetch('/config/models.json'); if (!configResponse.ok) { throw new Error(`加载提供商配置失败: ${configResponse.status} ${configResponse.statusText}`); } const config = await configResponse.json(); // 保存提供商定义 this.providerDefinitions = config.providers || {}; // 处理模型定义 this.modelDefinitions = {}; // 从API获取的模型列表创建模型定义 modelsList.forEach(model => { this.modelDefinitions[model.id] = { name: model.display_name, provider: this.getProviderIdByModel(model.id, config), supportsMultimodal: model.is_multimodal, isReasoning: model.is_reasoning, apiKeyId: this.getApiKeyIdByModel(model.id, config), description: model.description, version: this.getVersionByModel(model.id, config) }; }); console.log('模型配置加载成功:', this.modelDefinitions); } catch (error) { console.error('加载模型配置时出错:', error); throw error; } } // 从配置中根据模型ID获取提供商ID getProviderIdByModel(modelId, config) { const modelConfig = config.models[modelId]; return modelConfig ? modelConfig.provider : 'unknown'; } // 从配置中根据模型ID获取API密钥ID getApiKeyIdByModel(modelId, config) { const modelConfig = config.models[modelId]; if (!modelConfig) return null; const providerId = modelConfig.provider; const provider = config.providers[providerId]; return provider ? provider.api_key_id : null; } // 从配置中根据模型ID获取版本信息 getVersionByModel(modelId, config) { const modelConfig = config.models[modelId]; return modelConfig ? modelConfig.version : 'latest'; } // 设置默认模型定义(当配置加载失败时使用) setupDefaultModels() { this.providerDefinitions = { 'anthropic': { name: 'Anthropic', api_key_id: 'AnthropicApiKey' }, 'openai': { name: 'OpenAI', api_key_id: 'OpenaiApiKey' }, 'deepseek': { name: 'DeepSeek', api_key_id: 'DeepseekApiKey' } }; this.modelDefinitions = { 'claude-3-7-sonnet-20250219': { name: 'Claude 3.7 Sonnet', provider: 'anthropic', supportsMultimodal: true, isReasoning: true, apiKeyId: 'AnthropicApiKey', version: '20250219' }, 'gpt-4o-2024-11-20': { name: 'GPT-4o', provider: 'openai', supportsMultimodal: true, isReasoning: false, apiKeyId: 'OpenaiApiKey', version: '2024-11-20' }, 'deepseek-reasoner': { name: 'DeepSeek Reasoner', provider: 'deepseek', supportsMultimodal: false, isReasoning: true, apiKeyId: 'DeepseekApiKey', version: 'latest' } }; console.log('使用默认模型配置'); } initializeElements() { // Settings panel elements this.settingsPanel = document.getElementById('settingsPanel'); this.modelSelect = document.getElementById('modelSelect'); this.temperatureInput = document.getElementById('temperature'); this.temperatureGroup = document.querySelector('.setting-group:has(#temperature)') || document.querySelector('div.setting-group:has(input[id="temperature"])'); this.systemPromptInput = document.getElementById('systemPrompt'); this.promptDescriptionElement = document.getElementById('promptDescription'); this.languageInput = document.getElementById('language'); this.proxyEnabledInput = document.getElementById('proxyEnabled'); this.proxyHostInput = document.getElementById('proxyHost'); this.proxyPortInput = document.getElementById('proxyPort'); this.proxySettings = document.getElementById('proxySettings'); // API基础URL相关元素 this.apiBaseUrlsList = document.getElementById('apiBaseUrlsList'); // 获取所有API基础URL状态元素 this.apiBaseUrlStatusElements = { 'AnthropicApiBaseUrl': document.getElementById('AnthropicApiBaseUrlStatus'), 'OpenaiApiBaseUrl': document.getElementById('OpenaiApiBaseUrlStatus'), 'DeepseekApiBaseUrl': document.getElementById('DeepseekApiBaseUrlStatus'), 'AlibabaApiBaseUrl': document.getElementById('AlibabaApiBaseUrlStatus'), 'GoogleApiBaseUrl': document.getElementById('GoogleApiBaseUrlStatus') }; // 提示词管理相关元素 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('promptDescriptionEdit'); this.cancelPromptBtn = document.getElementById('cancelPromptBtn'); this.confirmPromptBtn = document.getElementById('confirmPromptBtn'); // 最大Token设置元素 - 现在是输入框而不是滑块 this.maxTokens = document.getElementById('maxTokens'); this.maxTokensValue = document.getElementById('maxTokensValue'); this.tokenPresets = document.querySelectorAll('.token-preset'); // 理性推理相关元素 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'); // 豆包深度思考相关元素 this.doubaoThinkingModeSelect = document.getElementById('doubaoThinkingMode'); this.doubaoThinkingGroup = document.querySelector('.doubao-thinking-group'); // Initialize Mathpix inputs this.mathpixAppIdInput = document.getElementById('mathpixAppId'); this.mathpixAppKeyInput = document.getElementById('mathpixAppKey'); // OCR源选择器 this.ocrSourceSelect = document.getElementById('ocrSourceSelect'); // API Key elements - 所有的密钥输入框 this.apiKeyInputs = { 'AnthropicApiKey': document.getElementById('AnthropicApiKey'), 'OpenaiApiKey': document.getElementById('OpenaiApiKey'), 'DeepseekApiKey': document.getElementById('DeepseekApiKey'), 'AlibabaApiKey': document.getElementById('AlibabaApiKey'), 'GoogleApiKey': document.getElementById('GoogleApiKey'), 'mathpixAppId': this.mathpixAppIdInput, 'mathpixAppKey': this.mathpixAppKeyInput }; // API密钥状态显示相关元素 this.apiKeysList = document.getElementById('apiKeysList'); // 防止API密钥区域的点击事件冒泡 if (this.apiKeysList) { this.apiKeysList.addEventListener('click', (e) => { e.stopPropagation(); }); } // Settings toggle elements this.settingsToggle = document.getElementById('settingsToggle'); this.closeSettings = document.getElementById('closeSettings'); // 获取所有密钥输入组元素 this.apiKeyGroups = document.querySelectorAll('.api-key-group'); // Initialize API key toggle buttons document.querySelectorAll('.toggle-api-key').forEach(button => { button.addEventListener('click', (e) => { const input = e.currentTarget.closest('.input-group').querySelector('input'); const type = input.type === 'password' ? 'text' : 'password'; input.type = type; const icon = e.currentTarget.querySelector('i'); if (icon) { icon.className = `fas fa-${type === 'password' ? 'eye' : 'eye-slash'}`; } }); }); // 存储API密钥的对象 this.apiKeyValues = { 'AnthropicApiKey': '', 'OpenaiApiKey': '', 'DeepseekApiKey': '', 'AlibabaApiKey': '', 'GoogleApiKey': '', 'DoubaoApiKey': '', 'BaiduApiKey': '', 'BaiduSecretKey': '', 'MathpixAppId': '', 'MathpixAppKey': '' }; this.reasoningOptions = document.querySelectorAll('.reasoning-option'); this.thinkPresets = document.querySelectorAll('.think-preset'); } // 绑定提示词预览区域点击事件 initPromptPreviewEvents() { const promptPreview = document.querySelector('.prompt-preview'); if (promptPreview) { promptPreview.addEventListener('click', () => { // 触发保存按钮点击事件,打开编辑对话框 document.getElementById('savePromptBtn').click(); }); } } /** * 打开提示词编辑对话框 * @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'); } } /** * 刷新API基础URL状态 */ async refreshApiBaseUrlStatus() { try { // 先将所有状态显示为"检查中" Object.keys(this.apiBaseUrlValues).forEach(urlId => { const statusElement = document.getElementById(`${urlId}Status`); if (statusElement) { statusElement.className = 'key-status checking'; statusElement.innerHTML = ' 检查中...'; } }); // 发送请求获取API基础URL const response = await fetch('/api/proxy-api', { method: 'GET', headers: { 'Content-Type': 'application/json', } }); if (response.ok) { const proxyApiConfig = await response.json(); // 提取APIs对象并更新状态 const apiBaseUrls = { 'AnthropicApiBaseUrl': proxyApiConfig.apis?.anthropic || '', 'OpenaiApiBaseUrl': proxyApiConfig.apis?.openai || '', 'DeepseekApiBaseUrl': proxyApiConfig.apis?.deepseek || '', 'AlibabaApiBaseUrl': proxyApiConfig.apis?.alibaba || '', 'GoogleApiBaseUrl': proxyApiConfig.apis?.google || '', 'DoubaoApiBaseUrl': proxyApiConfig.apis?.doubao || '' }; this.updateApiBaseUrlStatus(apiBaseUrls); console.log('API基础URL状态已刷新'); } else { console.error('刷新API基础URL状态失败'); } } catch (error) { console.error('刷新API基础URL状态出错:', error); } } /** * 更新API基础URL状态显示 * @param {Object} apiBaseUrls 基础URL对象 */ updateApiBaseUrlStatus(apiBaseUrls) { if (!this.apiBaseUrlsList) return; // 保存API基础URL值到内存中 for (const [key, value] of Object.entries(apiBaseUrls)) { this.apiBaseUrlValues[key] = value; } // 找到所有基础URL状态元素 Object.keys(apiBaseUrls).forEach(urlId => { const statusElement = document.getElementById(`${urlId}Status`); if (!statusElement) return; const value = apiBaseUrls[urlId]; if (value && value.trim() !== '') { // 显示基础URL状态 - 已设置 statusElement.className = 'key-status set'; statusElement.innerHTML = ` 已设置`; } else { // 显示基础URL状态 - 未设置 statusElement.className = 'key-status not-set'; statusElement.innerHTML = ` 未设置`; } }); } /** * 保存单个API基础URL * @param {string} urlType URL类型 * @param {string} value URL值 * @param {HTMLElement} urlStatus URL状态容器 */ async saveApiBaseUrl(urlType, value, urlStatus) { try { // 显示保存中状态 const saveToast = this.createToast('正在保存API基础URL...', 'info', true); // 获取当前中转API配置 const response = await fetch('/api/proxy-api', { method: 'GET', headers: { 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error('获取现有配置失败'); } const config = await response.json(); // 确保apis对象存在 if (!config.apis) { config.apis = {}; } // 根据URL类型更新对应的API URL switch(urlType) { case 'AnthropicApiBaseUrl': config.apis.anthropic = value; break; case 'OpenaiApiBaseUrl': config.apis.openai = value; break; case 'DeepseekApiBaseUrl': config.apis.deepseek = value; break; case 'AlibabaApiBaseUrl': config.apis.alibaba = value; break; case 'GoogleApiBaseUrl': config.apis.google = value; break; case 'DoubaoApiBaseUrl': config.apis.doubao = value; break; } // 确保启用中转API if (value && value.trim() !== '') { config.enabled = true; } // 保存到服务器 const saveResponse = await fetch('/api/proxy-api', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config) }); // 移除保存中提示 if (saveToast) { saveToast.remove(); } if (saveResponse.ok) { const result = await saveResponse.json(); if (result.success) { // 更新基础URL状态显示 const statusElem = document.getElementById(`${urlType}Status`); if (statusElem) { if (value && value.trim() !== '') { statusElem.className = 'key-status set'; statusElem.innerHTML = ` 已设置`; } else { statusElem.className = 'key-status not-set'; statusElem.innerHTML = ` 未设置`; } } // 保存到内存 this.apiBaseUrlValues[urlType] = value; // 显示成功提示 this.createToast('API基础URL已保存', 'success'); } else { this.createToast(`保存失败: ${result.message || '未知错误'}`, 'error'); } } else { this.createToast('保存API基础URL失败', 'error'); } } catch (error) { console.error('保存API基础URL错误:', error); this.createToast(`保存失败: ${error.message || '未知错误'}`, 'error'); } } /** * 初始化API基础URL编辑相关功能 */ initApiBaseUrlEditFunctions() { // 1. 编辑按钮点击事件 document.querySelectorAll('.edit-api-base-url').forEach(button => { button.addEventListener('click', (e) => { // 阻止事件冒泡 e.stopPropagation(); const urlType = e.currentTarget.getAttribute('data-key-type'); const urlStatus = e.currentTarget.closest('.key-status-wrapper'); if (urlStatus) { // 隐藏显示区域 const displayArea = urlStatus.querySelector('.key-display'); if (displayArea) displayArea.classList.add('hidden'); // 显示编辑区域 const editArea = urlStatus.querySelector('.key-edit'); if (editArea) { editArea.classList.remove('hidden'); // 获取当前URL值并填入输入框 const urlInput = editArea.querySelector('.key-input'); if (urlInput) { // 从状态文本中获取当前值(如果不是"未设置") const statusElement = urlStatus.querySelector('.key-status'); if (statusElement && statusElement.textContent !== '未设置') { urlInput.value = this.apiBaseUrlValues[urlType] || ''; } else { urlInput.value = ''; } // 聚焦输入框 setTimeout(() => { urlInput.focus(); }, 100); } } } }); }); // 2. 保存按钮点击事件 document.querySelectorAll('.save-api-base-url').forEach(button => { button.addEventListener('click', (e) => { // 阻止事件冒泡 e.stopPropagation(); const urlType = e.currentTarget.getAttribute('data-key-type'); const urlStatus = e.currentTarget.closest('.key-status-wrapper'); if (urlStatus) { // 获取输入的新URL值 const urlInput = urlStatus.querySelector('.key-input'); if (urlInput) { const newValue = urlInput.value.trim(); // 保存到服务器 this.saveApiBaseUrl(urlType, newValue, urlStatus); // 隐藏编辑区域 const editArea = urlStatus.querySelector('.key-edit'); if (editArea) editArea.classList.add('hidden'); // 显示状态区域 const displayArea = urlStatus.querySelector('.key-display'); if (displayArea) displayArea.classList.remove('hidden'); } } }); }); // 3. 输入框按下Enter保存 document.querySelectorAll('#apiBaseUrlsList .key-input').forEach(input => { input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { // 阻止事件冒泡 e.stopPropagation(); const saveButton = e.currentTarget.closest('.key-edit').querySelector('.save-api-base-url'); if (saveButton) { saveButton.click(); } } else if (e.key === 'Escape') { // 阻止事件冒泡 e.stopPropagation(); // 取消编辑 const urlStatus = e.currentTarget.closest('.key-status-wrapper'); if (urlStatus) { const editArea = urlStatus.querySelector('.key-edit'); if (editArea) editArea.classList.add('hidden'); const displayArea = urlStatus.querySelector('.key-display'); if (displayArea) displayArea.classList.remove('hidden'); } } }); }); } } // Export for use in other modules window.SettingsManager = SettingsManager;