// frontend/js/pages/keys/requestSettingsModal.js import { modalManager } from '../../components/ui.js'; export default class RequestSettingsModal { constructor({ onSave }) { this.modalId = 'request-settings-modal'; this.modal = document.getElementById(this.modalId); this.onSave = onSave; // 注入保存回調函數 if (!this.modal) { throw new Error(`Modal with id "${this.modalId}" not found.`); } // 映射所有內部DOM元素 this.elements = { saveBtn: document.getElementById('request-settings-save-btn'), customHeadersContainer: document.getElementById('CUSTOM_HEADERS_container'), addCustomHeaderBtn: document.getElementById('addCustomHeaderBtn'), streamOptimizerEnabled: document.getElementById('STREAM_OPTIMIZER_ENABLED'), streamingSettingsPanel: document.getElementById('streaming-settings-panel'), streamMinDelay: document.getElementById('STREAM_MIN_DELAY'), streamMaxDelay: document.getElementById('STREAM_MAX_DELAY'), streamShortTextThresh: document.getElementById('STREAM_SHORT_TEXT_THRESHOLD'), streamLongTextThresh: document.getElementById('STREAM_LONG_TEXT_THRESHOLD'), streamChunkSize: document.getElementById('STREAM_CHUNK_SIZE'), fakeStreamEnabled: document.getElementById('FAKE_STREAM_ENABLED'), fakeStreamInterval: document.getElementById('FAKE_STREAM_EMPTY_DATA_INTERVAL_SECONDS'), toolsCodeExecutionEnabled: document.getElementById('TOOLS_CODE_EXECUTION_ENABLED'), urlContextEnabled: document.getElementById('URL_CONTEXT_ENABLED'), showSearchLink: document.getElementById('SHOW_SEARCH_LINK'), showThinkingProcess: document.getElementById('SHOW_THINKING_PROCESS'), safetySettingsContainer: document.getElementById('SAFETY_SETTINGS_container'), addSafetySettingBtn: document.getElementById('addSafetySettingBtn'), configOverrides: document.getElementById('group-config-overrides'), }; this._initEventListeners(); } // --- 公共 API --- /** * 打開模態框並填充數據 * @param {object} data - 用於填充表單的數據 */ open(data) { this._populateForm(data); modalManager.show(this.modalId); } /** * 關閉模態框 */ close() { modalManager.hide(this.modalId); } // --- 內部事件與邏輯 --- _initEventListeners() { // 事件委託,處理動態添加元素的移除 this.modal.addEventListener('click', (e) => { const removeBtn = e.target.closest('.remove-btn'); if (removeBtn) { removeBtn.parentElement.remove(); } }); if (this.elements.addCustomHeaderBtn) { this.elements.addCustomHeaderBtn.addEventListener('click', () => this.addCustomHeaderItem()); } if (this.elements.addSafetySettingBtn) { this.elements.addSafetySettingBtn.addEventListener('click', () => this.addSafetySettingItem()); } if (this.elements.saveBtn) { this.elements.saveBtn.addEventListener('click', this._handleSave.bind(this)); } if (this.elements.streamOptimizerEnabled) { this.elements.streamOptimizerEnabled.addEventListener('change', (e) => { this._toggleStreamingPanel(e.target.checked); }); } // --- 完整的、統一的關閉邏輯 --- const closeAction = () => { // 此處無需 _reset(),因為每次 open 都會重新 populate modalManager.hide(this.modalId); }; // 綁定所有帶有 data-modal-close 屬性的按鈕 const closeTriggers = this.modal.querySelectorAll(`[data-modal-close="${this.modalId}"]`); closeTriggers.forEach(trigger => { trigger.addEventListener('click', closeAction); }); // 綁定點擊模態框背景遮罩層的事件 this.modal.addEventListener('click', (event) => { if (event.target === this.modal) { closeAction(); } }); } async _handleSave() { const data = this._collectFormData(); if (this.onSave) { try { if (this.elements.saveBtn) { this.elements.saveBtn.disabled = true; this.elements.saveBtn.textContent = 'Saving...'; } // 調用注入的回調函數,將數據傳遞給頁面控制器處理 await this.onSave(data); this.close(); } catch (error) { console.error("Failed to save request settings:", error); alert(`保存失敗: ${error.message}`); // 在模態框內給出反饋 } finally { if (this.elements.saveBtn) { this.elements.saveBtn.disabled = false; this.elements.saveBtn.textContent = 'Save Changes'; } } } } // --- 所有表單處理輔助方法 --- _populateForm(data = {}) { // [完整遷移] 填充表單的邏輯 const isStreamOptimizerEnabled = !!data.stream_optimizer_enabled; this._setToggle(this.elements.streamOptimizerEnabled, isStreamOptimizerEnabled); this._toggleStreamingPanel(isStreamOptimizerEnabled); this._setValue(this.elements.streamMinDelay, data.stream_min_delay); this._setValue(this.elements.streamMaxDelay, data.stream_max_delay); this._setValue(this.elements.streamShortTextThresh, data.stream_short_text_threshold); this._setValue(this.elements.streamLongTextThresh, data.stream_long_text_threshold); this._setValue(this.elements.streamChunkSize, data.stream_chunk_size); this._setToggle(this.elements.fakeStreamEnabled, data.fake_stream_enabled); this._setValue(this.elements.fakeStreamInterval, data.fake_stream_empty_data_interval_seconds); this._setToggle(this.elements.toolsCodeExecutionEnabled, data.tools_code_execution_enabled); this._setToggle(this.elements.urlContextEnabled, data.url_context_enabled); this._setToggle(this.elements.showSearchLink, data.show_search_link); this._setToggle(this.elements.showThinkingProcess, data.show_thinking_process); this._setValue(this.elements.configOverrides, data.config_overrides); // --- Dynamic & Complex Fields --- this._populateKVItems(this.elements.customHeadersContainer, data.custom_headers, this.addCustomHeaderItem.bind(this)); this._clearContainer(this.elements.safetySettingsContainer); if (data.safety_settings && typeof data.safety_settings === 'object') { for (const [key, value] of Object.entries(data.safety_settings)) { this.addSafetySettingItem(key, value); } } } /** * Collects all data from the form fields and returns it as an object. * @returns {object} The collected request configuration data. */ collectFormData() { return { // Simple Toggles & Inputs stream_optimizer_enabled: this.elements.streamOptimizerEnabled.checked, stream_min_delay: parseInt(this.elements.streamMinDelay.value, 10), stream_max_delay: parseInt(this.elements.streamMaxDelay.value, 10), stream_short_text_threshold: parseInt(this.elements.streamShortTextThresh.value, 10), stream_long_text_threshold: parseInt(this.elements.streamLongTextThresh.value, 10), stream_chunk_size: parseInt(this.elements.streamChunkSize.value, 10), fake_stream_enabled: this.elements.fakeStreamEnabled.checked, fake_stream_empty_data_interval_seconds: parseInt(this.elements.fakeStreamInterval.value, 10), tools_code_execution_enabled: this.elements.toolsCodeExecutionEnabled.checked, url_context_enabled: this.elements.urlContextEnabled.checked, show_search_link: this.elements.showSearchLink.checked, show_thinking_process: this.elements.showThinkingProcess.checked, config_overrides: this.elements.configOverrides.value, // Dynamic & Complex Fields custom_headers: this._collectKVItems(this.elements.customHeadersContainer), safety_settings: this._collectSafetySettings(this.elements.safetySettingsContainer), // TODO: Collect from Tag Inputs // image_models: this.imageModelsInput.getValues(), }; } // 控制流式面板显示/隐藏的辅助函数 _toggleStreamingPanel(is_enabled) { if (this.elements.streamingSettingsPanel) { if (is_enabled) { this.elements.streamingSettingsPanel.classList.remove('hidden'); } else { this.elements.streamingSettingsPanel.classList.add('hidden'); } } } /** * Adds a new key-value pair item for Custom Headers. * @param {string} [key=''] - The initial key. * @param {string} [value=''] - The initial value. */ addCustomHeaderItem(key = '', value = '') { const container = this.elements.customHeadersContainer; const item = document.createElement('div'); item.className = 'dynamic-kv-item'; item.innerHTML = ` `; container.appendChild(item); } /** * Adds a new item for Safety Settings. * @param {string} [category=''] - The initial category. * @param {string} [threshold=''] - The initial threshold. */ addSafetySettingItem(category = '', threshold = '') { const container = this.elements.safetySettingsContainer; const item = document.createElement('div'); item.className = 'safety-setting-item flex items-center gap-x-2'; const harmCategories = [ "HARM_CATEGORY_HARASSMENT", "HARM_CATEGORY_HATE_SPEECH", "HARM_CATEGORY_SEXUALLY_EXPLICIT", "HARM_CATEGORY_DANGEROUS_CONTENT","HARM_CATEGORY_DANGEROUS_CONTENT", "HARM_CATEGORY_CIVIC_INTEGRITY" ]; const harmThresholds = [ "BLOCK_OFF","BLOCK_NONE", "BLOCK_LOW_AND_ABOVE", "BLOCK_MEDIUM_AND_ABOVE", "BLOCK_ONLY_HIGH" ]; const categorySelect = document.createElement('select'); categorySelect.className = 'modal-input flex-grow'; // .modal-input 在静态