// settings.js class SettingsApp { constructor() { this.els = this.getElements(); } getElements() { return { apiKey: document.getElementById('apiKey'), apiModel: document.getElementById('apiModel'), batchRPM: document.getElementById('batchRPM'), batchSize: document.getElementById('batchSize'), cleanupToggle: document.getElementById('cleanupToggle'), darkToggle: document.getElementById('darkToggle'), expRetry: document.getElementById('exponentialRetry'), exportBtn: document.getElementById('exportSettings'), highCostToggle: document.getElementById('highCostToggle'), importBtn: document.getElementById('importSettings'), langSelect: document.getElementById('language'), noBSToggle: document.getElementById('noBSToggle'), numberedLinesToggle: document.getElementById('numberedLinesToggle'), reasoningBox: document.getElementById('reasoningEffortContainer'), reasoningEffort: document.getElementById('reasoningEffort'), rendererSelect: document.getElementById('renderer'), saveBtn: document.getElementById('saveSettings'), settingsText: document.getElementById('settingsTextArea'), streamToggle: document.getElementById('streamingToggle'), thinkingBox: document.getElementById('thinkingBudgetContainer'), thinkingNum: document.getElementById('thinkingBudgetNumber'), thinkingRange: document.getElementById('thinkingBudgetRange'), transcribeLang: document.getElementById('transcribeLanguage'), transcribeModel: document.getElementById('transcriptionApiModel'), wideToggle: document.getElementById('wideToggle'), whisperBoxes: { openai: document.getElementById('openaiWhisperModelContainer'), groq: document.getElementById('groqWhisperModelContainer'), }, whisperModels: { openai: document.getElementById('openaiWhisperModel'), groq: document.getElementById('groqWhisperModel'), }, modelBoxes: { openai: document.getElementById('openaiModelContainer'), anthropic: document.getElementById('anthropicModelContainer'), deepseek: document.getElementById('deepseekModelContainer'), google: document.getElementById('googleModelContainer'), x: document.getElementById('xModelContainer'), groq: document.getElementById('groqModelContainer'), lambda: document.getElementById('lambdaModelContainer'), openrouter: document.getElementById('openrouterModelContainer'), perplexity: document.getElementById('perplexityModelContainer'), alibaba: document.getElementById('alibabaModelContainer'), sambanova: document.getElementById('sambanovaModelContainer'), together: document.getElementById('togetherModelContainer') }, modelSelects: { openai: document.getElementById('openaiModel'), anthropic: document.getElementById('anthropicModel'), deepseek: document.getElementById('deepseekModel'), google: document.getElementById('googleModel'), x: document.getElementById('xModel'), groq: document.getElementById('groqModel'), lambda: document.getElementById('lambdaModel'), openrouter: document.getElementById('openrouterModel'), perplexity: document.getElementById('perplexityModel'), alibaba: document.getElementById('alibabaModel'), sambanova: document.getElementById('sambanovaModel'), together: document.getElementById('togetherModel') }, }; } init() { this.loadSettings(); this.setupEvents(); this.updateModelVisibility(this.els.apiModel.value); this.updateTranscribeModelVisibility(this.els.transcribeModel.value); this.updateThemeAndLayout(); this.syncThinkingInputs(); } updateThemeAndLayout() { UIState.updateTheme(this.els.darkToggle.checked); UIState.updateLayout(this.els.wideToggle.checked); } loadSettings() { this.loadCheckbox('cleanupToggle', 'cleanup_enabled', true); this.loadCheckbox('darkToggle', 'dark_enabled', true); this.loadCheckbox('highCostToggle', 'high_cost_enabled', false); this.loadCheckbox('noBSToggle', 'no_bs_enabled', false); this.loadCheckbox('numberedLinesToggle', 'numbered_lines_enabled', false); this.loadCheckbox('streamToggle', 'streaming_enabled', true); this.loadCheckbox('wideToggle', 'wide_enabled', false); this.loadInput('apiKey', CONFIG.API.KEYS[this.els.apiModel.value] || '', this.els.apiModel.value); this.loadInput('batchRPM', 'translation_batch_rpm', 0, 'number'); this.loadInput('batchSize', 'translation_batch_size', 10, 'number'); this.loadInput('expRetry', 'exponential_retry', 10, 'number'); this.loadInput('langSelect', 'selected_language', 'English'); this.loadInput('transcribeLang', 'transcribe_language', 'en'); this.loadSelect('apiModel', 'selected_api_model', 'openai'); this.loadSelect('rendererSelect', 'selected_renderer', 'katex'); this.loadSelect('transcribeModel', 'selected_transcription_api_model', 'groq'); if (this.els.reasoningEffort) { this.loadSelect('reasoningEffort', 'reasoning_effort', 'low'); } const thinkingBudget = parseInt(StorageService.load('thinking', 0), 10); this.els.thinkingRange.value = thinkingBudget; this.els.thinkingNum.value = thinkingBudget; this.loadModelOptions(); this.loadWhisperOptions(); this.updateModelVisibility(this.els.apiModel.value); this.updateTranscribeModelVisibility(this.els.transcribeModel.value); this.updateApiKeyLabel(this.els.apiModel.value); this.displaySettings(); this.syncThinkingInputs(); } loadSelect(elemKey, storeKey, defValue) { if (this.els[elemKey]) { this.els[elemKey].value = StorageService.load(storeKey, defValue); } } loadInput(elemKey, storeKey, defValue, type = 'text') { if (this.els[elemKey]) { let value = StorageService.load(storeKey, defValue); if (type === 'number') { value = parseInt(value, 10); if (isNaN(value)) { value = defValue; } } this.els[elemKey].value = value; } } loadCheckbox(elemKey, storeKey, defValue) { if (this.els[elemKey]) { this.els[elemKey].checked = StorageService.load(storeKey, defValue); } } loadModelOptions() { const isHighCostEnabled = StorageService.load('high_cost_enabled', false); Object.entries(CONFIG.API.MODELS.COMPLETION) .forEach(([provider, config]) => { const modelSelect = this.els.modelSelects[provider]; if (!modelSelect) return; modelSelect.innerHTML = ''; this.addModelsToSelect(modelSelect, config.options); if (isHighCostEnabled && CONFIG.API.MODELS.COMPLETION_HIGH_COST[provider]) { const highCostConfig = CONFIG.API.MODELS.COMPLETION_HIGH_COST[provider]; this.addModelsToSelect(modelSelect, highCostConfig.options, true); } modelSelect.value = StorageService.load(`${provider}_model`, config.default); }); } addModelsToSelect(select, models, isHighCost = false) { models.forEach(model => { const option = document.createElement('option'); option.value = model.name; option.textContent = isHighCost ? `${model.name} ($15+/1M)` : model.name; if (isHighCost) { option.className = 'high-cost-model'; } select.appendChild(option); }); } loadWhisperOptions() { Object.entries(CONFIG.API.MODELS.TRANSCRIPTION) .forEach(([provider, config]) => { const modelSelect = this.els.whisperModels[provider]; if (!modelSelect) return; modelSelect.innerHTML = ''; config.options.forEach(model => { const option = document.createElement('option'); option.value = model.name; option.textContent = model.name; modelSelect.appendChild(option); }); modelSelect.value = StorageService.load(`${provider}_whisper_model`, config.default); }); } updateModelVisibility(provider) { Object.values(this.els.modelBoxes) .forEach(box => { if (box) box.classList.remove('active'); }); const selectedBox = this.els.modelBoxes[provider]; if (selectedBox) { selectedBox.classList.add('active'); this.updateReasoningVisibility(provider); this.updateThinkingVisibility(provider); } } updateTranscribeModelVisibility(provider) { Object.values(this.els.whisperBoxes) .forEach(box => { box.style.display = 'none'; }); const selectedBox = this.els.whisperBoxes[provider]; if (selectedBox) { selectedBox.style.display = 'block'; } } updateReasoningVisibility(provider) { const modelSelect = this.els.modelSelects[provider]; const modelName = modelSelect ? modelSelect.value : null; const modelDetails = this.getModelDetails(provider, modelName); this.els.reasoningBox.style.display = (modelDetails && modelDetails.reasoning_effort) ? 'block' : 'none'; } updateThinkingVisibility(provider) { const modelSelect = this.els.modelSelects[provider]; const modelName = modelSelect ? modelSelect.value : null; const modelDetails = this.getModelDetails(provider, modelName); this.els.thinkingBox.style.display = (modelDetails && modelDetails.thinking) ? 'block' : 'none'; } getModelDetails(provider, modelName) { let modelDetails = CONFIG.API.MODELS.COMPLETION[provider]?.options.find(m => m.name === modelName); if (!modelDetails && StorageService.load('high_cost_enabled', false) && CONFIG.API.MODELS.COMPLETION_HIGH_COST[provider]) { modelDetails = CONFIG.API.MODELS.COMPLETION_HIGH_COST[provider].options.find(m => m.name === modelName); } return modelDetails; } setupEvents() { this.els.apiKey?.addEventListener('change', this.handleApiKeyChange.bind(this)); this.els.apiModel?.addEventListener('change', this.handleApiModelChange.bind(this)); this.els.batchRPM?.addEventListener('change', this.handleNumericChange.bind(this, 'batchRPM', 'translation_batch_rpm', 0, 60000)); this.els.batchSize?.addEventListener('change', this.handleNumericChange.bind(this, 'batchSize', 'translation_batch_size', 1, 60000)); this.els.cleanupToggle?.addEventListener('change', this.handleToggleChange.bind(this, 'cleanupToggle', 'cleanup_enabled')); this.els.darkToggle?.addEventListener('change', this.handleDarkToggleChange.bind(this)); this.els.expRetry?.addEventListener('change', this.handleNumericChange.bind(this, 'expRetry', 'exponential_retry', 0)); this.els.exportBtn?.addEventListener('click', this.exportSettings.bind(this)); this.els.highCostToggle?.addEventListener('change', this.handleHighCostToggleChange.bind(this)); this.els.importBtn?.addEventListener('click', this.importSettings.bind(this)); this.els.langSelect?.addEventListener('change', this.handleLangChange.bind(this)); this.els.noBSToggle?.addEventListener('change', this.handleToggleChange.bind(this, 'noBSToggle', 'no_bs_enabled')); this.els.numberedLinesToggle?.addEventListener('change', this.handleToggleChange.bind(this, 'numberedLinesToggle', 'numbered_lines_enabled')); this.els.reasoningEffort?.addEventListener('change', this.handleReasoningChange.bind(this)); this.els.rendererSelect?.addEventListener('change', this.handleRendererChange.bind(this)); this.els.saveBtn?.addEventListener('click', this.saveSettings.bind(this)); this.els.streamToggle?.addEventListener('change', this.handleToggleChange.bind(this, 'streamToggle', 'streaming_enabled')); this.els.transcribeLang?.addEventListener('change', this.handleTranscribeLangChange.bind(this)); this.els.transcribeModel?.addEventListener('change', this.handleTranscribeModelChange.bind(this)); this.els.wideToggle?.addEventListener('change', this.handleWideToggleChange.bind(this)); this.setupThinkingEvents(); this.setupModelSelectEvents(); this.setupWhisperModelEvents(); } setupThinkingEvents() { this.els.thinkingRange?.addEventListener('input', () => { this.els.thinkingNum.value = this.els.thinkingRange.value; this.handleThinkingChange(); }); this.els.thinkingNum?.addEventListener('input', () => { this.els.thinkingRange.value = this.els.thinkingNum.value; this.handleThinkingChange(); }); } setupModelSelectEvents() { Object.entries(this.els.modelSelects) .forEach(([provider, select]) => { select?.addEventListener('change', () => { const selectedModel = select.value; StorageService.save(`${provider}_model`, selectedModel); this.updateReasoningVisibility(provider); this.updateThinkingVisibility(provider); }); }); } setupWhisperModelEvents() { Object.entries(this.els.whisperModels) .forEach(([provider, select]) => { select?.addEventListener('change', () => { const selectedModel = select.value; StorageService.save(`${provider}_whisper_model`, selectedModel); }); }); } handleApiModelChange() { const model = this.els.apiModel.value; StorageService.save('selected_api_model', model); this.updateApiKeyLabel(model); this.updateModelVisibility(model); this.els.apiKey.value = StorageService.load(CONFIG.API.KEYS[model] || '', ''); } handleTranscribeModelChange() { const model = this.els.transcribeModel.value; StorageService.save('selected_transcription_api_model', model); this.updateTranscribeModelVisibility(model); } handleApiKeyChange() { const apiType = this.els.apiModel.value; const apiKey = this.els.apiKey.value.trim(); StorageService.save(CONFIG.API.KEYS[apiType], apiKey); } handleToggleChange(elemKey, storeKey) { StorageService.save(storeKey, this.els[elemKey].checked); } handleDarkToggleChange() { const isDark = this.els.darkToggle.checked; UIState.updateTheme(isDark); StorageService.save('dark_enabled', isDark); } handleWideToggleChange() { const isWide = this.els.wideToggle.checked; UIState.updateLayout(isWide); StorageService.save('wide_enabled', isWide); } handleHighCostToggleChange() { const isHighCostEnabled = this.els.highCostToggle.checked; StorageService.save('high_cost_enabled', isHighCostEnabled); this.loadModelOptions(); const provider = this.els.apiModel.value; this.updateReasoningVisibility(provider); this.updateThinkingVisibility(provider); } handleRendererChange() { StorageService.save('selected_renderer', this.els.rendererSelect.value); } handleReasoningChange() { StorageService.save('reasoning_effort', this.els.reasoningEffort.value); } handleThinkingChange() { const value = parseInt(this.els.thinkingNum.value, 10); StorageService.save('thinking', value); } handleLangChange() { StorageService.save('selected_language', this.els.langSelect.value); } handleTranscribeLangChange() { StorageService.save('transcribe_language', this.els.transcribeLang.value); } handleNumericChange(elemKey, storeKey, min = null, max = null) { let value = parseInt(this.els[elemKey].value, 10); if (isNaN(value)) { value = StorageService.load(storeKey, 0); } if (min !== null && value < min) { value = min; } if (max !== null && value > max) { value = max; } this.els[elemKey].value = value; StorageService.save(storeKey, value); } updateApiKeyLabel(model) { const apiKeyLabel = document.querySelector('label[for="apiKey"]'); if (apiKeyLabel) { apiKeyLabel.textContent = CONFIG.UI.API_KEY_LABELS[model] || 'API Key:'; } } getSettings() { const settings = { cleanup_enabled: this.els.cleanupToggle.checked, dark_enabled: this.els.darkToggle.checked, exponential_retry: parseInt(this.els.expRetry.value, 10), high_cost_enabled: this.els.highCostToggle.checked, no_bs_enabled: this.els.noBSToggle.checked, numbered_lines_enabled: this.els.numberedLinesToggle.checked, prompts: StorageService.load('prompts', []), selected_api_model: this.els.apiModel.value, selected_language: this.els.langSelect.value, selected_renderer: this.els.rendererSelect.value, selected_transcription_api_model: this.els.transcribeModel.value, streaming_enabled: this.els.streamToggle.checked, thinking: parseInt(this.els.thinkingNum.value, 10), transcribe_language: this.els.transcribeLang.value, translation_batch_rpm: parseInt(this.els.batchRPM.value, 10), translation_batch_size: parseInt(this.els.batchSize.value, 10), translation_enabled: StorageService.load('translation_enabled', false), wide_enabled: this.els.wideToggle.checked }; if (this.els.reasoningEffort) { settings.reasoning_effort = this.els.reasoningEffort.value; } this.addAPIKeySettings(settings); this.addModelSettings(settings); this.addWhisperSettings(settings); return settings; } addAPIKeySettings(settings) { Object.entries(CONFIG.API.KEYS) .forEach(([provider, key]) => { settings[key] = StorageService.load(key, ''); }); } addModelSettings(settings) { Object.entries(CONFIG.API.MODELS.COMPLETION) .forEach(([provider, config]) => { settings[`${provider}_model`] = this.els.modelSelects[provider].value; }); } addWhisperSettings(settings) { Object.entries(CONFIG.API.MODELS.TRANSCRIPTION) .forEach(([provider, config]) => { settings[`${provider}_whisper_model`] = this.els.whisperModels[provider].value; }); } displaySettings() { const settings = this.getSettings(); this.els.settingsText.value = JSON.stringify(settings, null, 2); } exportSettings() { const settings = this.getSettings(); const settingsJSON = JSON.stringify(settings, null, 2); const blob = new Blob([settingsJSON], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'CoFlu.json'; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } importSettings() { const input = document.createElement('input'); input.type = 'file'; input.accept = '.json'; input.addEventListener('change', (event) => { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = (e) => { let settings = e.target.result; if (!settings.trim() || settings.trim() === '{}') { this.clearAllData(); alert('Data wiped successfully!'); this.loadSettings(); return; } settings = JSON.parse(settings); if (typeof settings !== 'object' || settings === null) { alert('Invalid settings file: Not a JSON object.'); return; } this.els.settingsText.value = JSON.stringify(settings, null, 2); Object.entries(settings) .forEach(([key, value]) => { StorageService.save(key, value); }); this.loadSettings(); alert('Settings imported successfully!'); }; reader.readAsText(file); }); input.click(); } saveSettings() { let settingsText = this.els.settingsText.value; if (!settingsText.trim() || settingsText.trim() === '{}') { this.clearAllData(); alert('Data wiped successfully!'); this.loadSettings(); return; } const settings = JSON.parse(settingsText); if (typeof settings !== 'object' || settings === null) { alert('Invalid settings: Not a JSON object.'); return; } Object.entries(settings) .forEach(([key, value]) => { StorageService.save(key, value); }); this.loadSettings(); alert('Settings saved successfully!'); } clearAllData() { localStorage.clear(); } syncThinkingInputs() { this.els.thinkingNum.value = this.els.thinkingRange.value; } } document.addEventListener('DOMContentLoaded', () => { const settingsApp = new SettingsApp(); settingsApp.init(); });