mirror of
https://github.com/Zippland/Snap-Solver.git
synced 2026-01-20 15:11:11 +08:00
648 lines
25 KiB
JavaScript
648 lines
25 KiB
JavaScript
class SettingsManager {
|
||
constructor() {
|
||
// 初始化属性
|
||
this.modelDefinitions = {};
|
||
this.providerDefinitions = {};
|
||
|
||
// 初始化界面元素
|
||
this.initializeElements();
|
||
|
||
// 加载模型配置
|
||
this.loadModelConfig()
|
||
.then(() => {
|
||
// 成功加载配置后,执行后续初始化
|
||
this.updateModelOptions();
|
||
this.loadSettings();
|
||
this.setupEventListeners();
|
||
this.updateUIBasedOnModelType();
|
||
})
|
||
.catch(error => {
|
||
console.error('加载模型配置失败:', error);
|
||
window.uiManager?.showToast('加载模型配置失败,使用默认配置', 'error');
|
||
|
||
// 使用默认配置作为备份
|
||
this.setupDefaultModels();
|
||
this.updateModelOptions();
|
||
this.loadSettings();
|
||
this.setupEventListeners();
|
||
this.updateUIBasedOnModelType();
|
||
});
|
||
|
||
// 初始化可折叠内容逻辑
|
||
this.initCollapsibleContent();
|
||
}
|
||
|
||
// 从配置文件加载模型定义
|
||
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.temperatureValue = document.getElementById('temperatureValue');
|
||
this.temperatureGroup = document.querySelector('.setting-group:has(#temperature)') ||
|
||
document.querySelector('div.setting-group:has(input[id="temperature"])');
|
||
this.systemPromptInput = document.getElementById('systemPrompt');
|
||
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');
|
||
|
||
// 最大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');
|
||
|
||
// API Key elements - 所有的密钥输入框
|
||
this.apiKeyInputs = {
|
||
'AnthropicApiKey': document.getElementById('AnthropicApiKey'),
|
||
'OpenaiApiKey': document.getElementById('OpenaiApiKey'),
|
||
'DeepseekApiKey': document.getElementById('DeepseekApiKey'),
|
||
'AlibabaApiKey': document.getElementById('AlibabaApiKey'),
|
||
'mathpixAppId': this.mathpixAppIdInput,
|
||
'mathpixAppKey': this.mathpixAppKeyInput
|
||
};
|
||
|
||
// 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'}`;
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// 更新模型选择下拉框
|
||
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) {
|
||
const option = document.createElement('option');
|
||
option.value = modelId;
|
||
|
||
// 显示模型名称和版本(如果不是latest)
|
||
let displayName = modelInfo.name;
|
||
if (modelInfo.version && modelInfo.version !== 'latest') {
|
||
displayName += ` (${modelInfo.version})`;
|
||
}
|
||
|
||
option.textContent = displayName;
|
||
optgroup.appendChild(option);
|
||
}
|
||
|
||
// 只添加有模型的提供商
|
||
if (optgroup.children.length > 0) {
|
||
this.modelSelect.appendChild(optgroup);
|
||
}
|
||
}
|
||
}
|
||
|
||
loadSettings() {
|
||
const settings = JSON.parse(localStorage.getItem('aiSettings') || '{}');
|
||
|
||
// Load Mathpix credentials
|
||
if (settings.mathpixAppId) {
|
||
this.mathpixAppIdInput.value = settings.mathpixAppId;
|
||
}
|
||
if (settings.mathpixAppKey) {
|
||
this.mathpixAppKeyInput.value = settings.mathpixAppKey;
|
||
}
|
||
|
||
// Load API keys
|
||
if (settings.apiKeys) {
|
||
Object.entries(settings.apiKeys).forEach(([keyId, value]) => {
|
||
const input = this.apiKeyInputs[keyId];
|
||
if (input) {
|
||
input.value = value;
|
||
}
|
||
});
|
||
}
|
||
|
||
// Load model selection
|
||
if (settings.model && this.modelExists(settings.model)) {
|
||
this.modelSelect.value = settings.model;
|
||
this.updateVisibleApiKey(settings.model);
|
||
}
|
||
|
||
// 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.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;
|
||
}
|
||
|
||
// Update UI based on model type
|
||
this.updateUIBasedOnModelType();
|
||
}
|
||
|
||
modelExists(modelId) {
|
||
return this.modelDefinitions.hasOwnProperty(modelId);
|
||
}
|
||
|
||
// 更新模型版本显示
|
||
updateModelVersionDisplay(modelId) {
|
||
const modelVersionText = document.getElementById('modelVersionText');
|
||
if (!modelVersionText) return;
|
||
|
||
const model = this.modelDefinitions[modelId];
|
||
if (!model) {
|
||
modelVersionText.textContent = '-';
|
||
return;
|
||
}
|
||
|
||
// 显示版本信息(如果有)
|
||
if (model.version && model.version !== 'latest') {
|
||
modelVersionText.textContent = model.version;
|
||
} else if (model.version === 'latest') {
|
||
modelVersionText.textContent = '最新版';
|
||
} else {
|
||
modelVersionText.textContent = '-';
|
||
}
|
||
}
|
||
|
||
updateVisibleApiKey(selectedModel) {
|
||
const modelInfo = this.modelDefinitions[selectedModel];
|
||
if (!modelInfo) return;
|
||
|
||
// 仅更新模型版本显示
|
||
this.updateModelVersionDisplay(selectedModel);
|
||
|
||
// 不再需要高亮API密钥
|
||
// 这里我们不再进行API密钥的高亮处理
|
||
}
|
||
|
||
updateUIBasedOnModelType() {
|
||
const selectedModel = this.modelSelect.value;
|
||
const modelInfo = this.modelDefinitions[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';
|
||
}
|
||
}
|
||
|
||
saveSettings() {
|
||
const settings = {
|
||
apiKeys: {},
|
||
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,
|
||
proxyEnabled: this.proxyEnabledInput.checked,
|
||
proxyHost: this.proxyHostInput.value,
|
||
proxyPort: this.proxyPortInput.value
|
||
};
|
||
|
||
// Save all API keys
|
||
Object.entries(this.apiKeyInputs).forEach(([keyId, input]) => {
|
||
if (input.value) {
|
||
settings.apiKeys[keyId] = input.value;
|
||
}
|
||
});
|
||
|
||
localStorage.setItem('aiSettings', JSON.stringify(settings));
|
||
window.showToast('Settings saved successfully');
|
||
}
|
||
|
||
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 || '';
|
||
|
||
let systemPrompt = basePrompt;
|
||
if (!basePrompt.includes('Please respond in') && !basePrompt.includes('请用') && !basePrompt.includes('使用')) {
|
||
systemPrompt = `${basePrompt}\n\n请务必使用${language}回答。`;
|
||
}
|
||
|
||
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,
|
||
proxyEnabled: this.proxyEnabledInput.checked,
|
||
proxyHost: this.proxyHostInput.value,
|
||
proxyPort: this.proxyPortInput.value,
|
||
mathpixApiKey: `${this.mathpixAppIdInput.value}:${this.mathpixAppKeyInput.value}`,
|
||
modelInfo: {
|
||
supportsMultimodal: modelInfo.supportsMultimodal || false,
|
||
isReasoning: modelInfo.isReasoning || false,
|
||
provider: modelInfo.provider || 'unknown'
|
||
},
|
||
reasoningConfig: reasoningConfig
|
||
};
|
||
}
|
||
|
||
getModelCapabilities(modelId) {
|
||
const model = this.modelDefinitions[modelId];
|
||
if (!model) return { supportsMultimodal: false, isReasoning: false };
|
||
|
||
return {
|
||
supportsMultimodal: model.supportsMultimodal,
|
||
isReasoning: model.isReasoning
|
||
};
|
||
}
|
||
|
||
setupEventListeners() {
|
||
// Save settings on change
|
||
Object.values(this.apiKeyInputs).forEach(input => {
|
||
input.addEventListener('change', () => this.saveSettings());
|
||
});
|
||
|
||
// Save Mathpix settings on change
|
||
this.mathpixAppIdInput.addEventListener('change', () => this.saveSettings());
|
||
this.mathpixAppKeyInput.addEventListener('change', () => this.saveSettings());
|
||
|
||
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();
|
||
}
|
||
});
|
||
|
||
// 最大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();
|
||
});
|
||
|
||
this.systemPromptInput.addEventListener('change', () => this.saveSettings());
|
||
this.languageInput.addEventListener('change', () => this.saveSettings());
|
||
this.proxyEnabledInput.addEventListener('change', (e) => {
|
||
this.proxySettings.style.display = e.target.checked ? 'block' : 'none';
|
||
this.saveSettings();
|
||
});
|
||
this.proxyHostInput.addEventListener('change', () => this.saveSettings());
|
||
this.proxyPortInput.addEventListener('change', () => this.saveSettings());
|
||
|
||
// Panel visibility
|
||
this.settingsToggle.addEventListener('click', () => {
|
||
window.closeAllPanels();
|
||
this.settingsPanel.classList.toggle('hidden');
|
||
});
|
||
|
||
this.closeSettings.addEventListener('click', () => {
|
||
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);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 初始化可折叠内容的交互逻辑
|
||
*/
|
||
initCollapsibleContent() {
|
||
// 获取API密钥折叠切换按钮和内容
|
||
const apiKeysToggle = document.getElementById('apiKeysCollapseToggle');
|
||
const apiKeysContent = document.getElementById('apiKeysContent');
|
||
|
||
// 添加点击事件以切换折叠状态
|
||
if (apiKeysToggle && apiKeysContent) {
|
||
apiKeysToggle.addEventListener('click', () => {
|
||
// 切换折叠状态
|
||
apiKeysContent.classList.toggle('collapsed');
|
||
|
||
// 更新图标方向
|
||
const icon = apiKeysToggle.querySelector('.fa-chevron-down, .fa-chevron-up');
|
||
if (icon) {
|
||
if (apiKeysContent.classList.contains('collapsed')) {
|
||
icon.classList.replace('fa-chevron-up', 'fa-chevron-down');
|
||
} else {
|
||
icon.classList.replace('fa-chevron-down', 'fa-chevron-up');
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
// Export for use in other modules
|
||
window.SettingsManager = SettingsManager;
|