Spaces:
Sleeping
Sleeping
| /* ===== Settings Page ===== */ | |
| window.NAISettings = { | |
| STORAGE_KEY: 'nai_settings', | |
| // Default values | |
| defaults: { | |
| // General | |
| theme: 'system', // 'light' | 'dark' | 'system' | |
| freeOnly: false, | |
| // Generate defaults | |
| genPrompt: '', | |
| genQuality: 'best quality, amazing quality, very aesthetic, absurdres', | |
| genNegative: 'lowres, {bad}, error, fewer, extra, missing, worst quality, jpeg artifacts, bad quality, watermark, unfinished, displeasing, chromatic aberration, signature, extra digits, artistic error, username, scan, [abstract]', | |
| genSampler: 'k_euler', | |
| genCfg: 5, | |
| genSteps: 28, | |
| genWidth: 1024, | |
| genHeight: 1024, | |
| genScheduler: 'karras', | |
| genSmea: false, | |
| genSmeaDyn: false, | |
| genVariety: false, | |
| genLegacy: false, | |
| genDecrisp: false, | |
| genCfgRescale: 0, | |
| // History | |
| clearHistoryOnRefresh: true, | |
| // Cloud | |
| saveToCloud: true, | |
| // Tag autocomplete | |
| tacEnabled: true, | |
| tacShowZh: true, | |
| tacMatchZh: true, | |
| tacMaxResults: 20, | |
| tacAutoCorrect: true, | |
| tacAddComma: true, | |
| tacChineseComma: false, | |
| tacUnderscoreToSpace: true, | |
| }, | |
| current: {}, | |
| load() { | |
| try { | |
| const stored = localStorage.getItem(this.STORAGE_KEY); | |
| const parsed = stored ? JSON.parse(stored) : {}; | |
| this.current = { ...this.defaults, ...parsed }; | |
| } catch (e) { | |
| this.current = { ...this.defaults }; | |
| } | |
| }, | |
| save() { | |
| try { | |
| localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.current)); | |
| } catch (e) { | |
| console.error('Failed to save settings:', e); | |
| } | |
| }, | |
| get(key) { | |
| return this.current[key] ?? this.defaults[key]; | |
| }, | |
| set(key, value) { | |
| this.current[key] = value; | |
| this.save(); | |
| }, | |
| // ── Apply settings to UI ── | |
| applyAll() { | |
| this.applyTheme(); | |
| this.applyGenerateDefaults(); | |
| this.applyCloudCheckbox(); | |
| this.applyHistoryClear(); | |
| }, | |
| applyTheme() { | |
| const theme = this.get('theme'); | |
| let effective; | |
| if (theme === 'system') { | |
| effective = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; | |
| } else { | |
| effective = theme; | |
| } | |
| document.documentElement.setAttribute('data-theme', effective); | |
| NAIState.theme = effective; | |
| }, | |
| applyGenerateDefaults() { | |
| const s = this.current; | |
| const setVal = (id, val) => { | |
| const el = document.getElementById(id); | |
| if (!el) return; | |
| el.value = val; | |
| el.dispatchEvent(new Event('input', { bubbles: true })); | |
| }; | |
| const setCheck = (id, val) => { | |
| const el = document.getElementById(id); | |
| if (el) el.checked = !!val; | |
| }; | |
| // Only apply defaults if prompt is empty (first load) | |
| const promptEl = $('#prompt-input'); | |
| if (promptEl && promptEl.value === '' && s.genPrompt) { | |
| promptEl.value = s.genPrompt; | |
| NAIUtils.autoResize(promptEl); | |
| } | |
| const qualEl = $('#quality-input'); | |
| if (qualEl && s.genQuality !== undefined) { | |
| // Always apply quality default on load | |
| if (qualEl.value === this.defaults.genQuality || qualEl.value === '') { | |
| qualEl.value = s.genQuality; | |
| NAIUtils.autoResize(qualEl); | |
| } | |
| } | |
| const negEl = $('#negative-input'); | |
| if (negEl && s.genNegative !== undefined) { | |
| if (negEl.value === this.defaults.genNegative || negEl.value === '') { | |
| negEl.value = s.genNegative; | |
| NAIUtils.autoResize(negEl); | |
| } | |
| } | |
| // Sampler | |
| const samplerEl = $('#sampler-select'); | |
| if (samplerEl && s.genSampler) { | |
| const opts = [...samplerEl.options].map(o => o.value); | |
| if (opts.includes(s.genSampler)) samplerEl.value = s.genSampler; | |
| } | |
| setVal('cfg-slider', s.genCfg); | |
| setVal('steps-slider', s.genSteps); | |
| setVal('width-slider', s.genWidth); | |
| setVal('height-slider', s.genHeight); | |
| // Advanced | |
| const schedEl = $('#adv-scheduler'); | |
| if (schedEl && s.genScheduler) { | |
| const opts = [...schedEl.options].map(o => o.value); | |
| if (opts.includes(s.genScheduler)) schedEl.value = s.genScheduler; | |
| } | |
| setCheck('adv-smea', s.genSmea); | |
| setCheck('adv-smea-dyn', s.genSmeaDyn); | |
| setCheck('adv-variety', s.genVariety); | |
| setCheck('adv-legacy', s.genLegacy); | |
| setCheck('adv-decrisp', s.genDecrisp); | |
| setVal('adv-cfg-rescale', s.genCfgRescale); | |
| }, | |
| applyCloudCheckbox() { | |
| const el = $('#save-to-cloud'); | |
| if (el) el.checked = this.get('saveToCloud'); | |
| }, | |
| applyHistoryClear() { | |
| if (this.get('clearHistoryOnRefresh')) { | |
| NAIIDB.clearAllHistory().catch(() => {}); | |
| } | |
| }, | |
| // ── Capture current generate params as defaults ── | |
| captureCurrentAsDefaults() { | |
| const s = this.current; | |
| s.genPrompt = $('#prompt-input')?.value || ''; | |
| s.genQuality = $('#quality-input')?.value || ''; | |
| s.genNegative = $('#negative-input')?.value || ''; | |
| s.genSampler = $('#sampler-select')?.value || 'k_euler'; | |
| s.genCfg = parseFloat($('#cfg-slider')?.value) || 5; | |
| s.genSteps = parseInt($('#steps-slider')?.value) || 28; | |
| s.genWidth = parseInt($('#width-slider')?.value) || 1024; | |
| s.genHeight = parseInt($('#height-slider')?.value) || 1024; | |
| s.genScheduler = $('#adv-scheduler')?.value || 'karras'; | |
| s.genSmea = $('#adv-smea')?.checked || false; | |
| s.genSmeaDyn = $('#adv-smea-dyn')?.checked || false; | |
| s.genVariety = $('#adv-variety')?.checked || false; | |
| s.genLegacy = $('#adv-legacy')?.checked || false; | |
| s.genDecrisp = $('#adv-decrisp')?.checked || false; | |
| s.genCfgRescale = parseFloat($('#adv-cfg-rescale')?.value) || 0; | |
| this.save(); | |
| }, | |
| // ── Render settings page ── | |
| renderPage() { | |
| const s = this.current; | |
| // Theme | |
| $('#setting-theme').value = s.theme; | |
| $('#setting-free-only').checked = s.freeOnly; | |
| // Generate | |
| $('#setting-gen-prompt').value = s.genPrompt; | |
| $('#setting-gen-quality').value = s.genQuality; | |
| $('#setting-gen-negative').value = s.genNegative; | |
| $('#setting-gen-sampler').value = s.genSampler; | |
| $('#setting-gen-cfg').value = s.genCfg; | |
| $('#setting-gen-cfg-val').textContent = parseFloat(s.genCfg).toFixed(1); | |
| $('#setting-gen-steps').value = s.genSteps; | |
| $('#setting-gen-steps-val').textContent = s.genSteps; | |
| $('#setting-gen-width').value = s.genWidth; | |
| $('#setting-gen-width-val').textContent = s.genWidth; | |
| $('#setting-gen-height').value = s.genHeight; | |
| $('#setting-gen-height-val').textContent = s.genHeight; | |
| $('#setting-gen-scheduler').value = s.genScheduler; | |
| $('#setting-gen-smea').checked = s.genSmea; | |
| $('#setting-gen-smea-dyn').checked = s.genSmeaDyn; | |
| $('#setting-gen-variety').checked = s.genVariety; | |
| $('#setting-gen-legacy').checked = s.genLegacy; | |
| $('#setting-gen-decrisp').checked = s.genDecrisp; | |
| $('#setting-gen-cfg-rescale').value = s.genCfgRescale; | |
| $('#setting-gen-cfg-rescale-val').textContent = parseFloat(s.genCfgRescale).toFixed(2); | |
| // History | |
| $('#setting-clear-history').checked = s.clearHistoryOnRefresh; | |
| // Cloud | |
| $('#setting-save-cloud').checked = s.saveToCloud; | |
| // TAC | |
| $('#setting-tac-enabled').checked = s.tacEnabled; | |
| $('#setting-tac-show-zh').checked = s.tacShowZh; | |
| $('#setting-tac-match-zh').checked = s.tacMatchZh; | |
| $('#setting-tac-max').value = s.tacMaxResults; | |
| $('#setting-tac-autocorrect').checked = s.tacAutoCorrect; | |
| $('#setting-tac-add-comma').checked = s.tacAddComma; | |
| $('#setting-tac-zh-comma').checked = s.tacChineseComma; | |
| $('#setting-tac-underscore').checked = s.tacUnderscoreToSpace; | |
| // Auto-resize textareas | |
| $$('#page-settings .form-textarea').forEach(t => NAIUtils.autoResize(t)); | |
| }, | |
| // ── Bind all settings inputs ── | |
| _bindInputs() { | |
| const bind = (id, key, type = 'value') => { | |
| const el = $(`#${id}`); | |
| if (!el) return; | |
| const event = el.type === 'checkbox' ? 'change' : 'input'; | |
| el.addEventListener(event, () => { | |
| let val; | |
| if (type === 'check') val = el.checked; | |
| else if (type === 'float') val = parseFloat(el.value); | |
| else if (type === 'int') val = parseInt(el.value); | |
| else val = el.value; | |
| this.set(key, val); | |
| this._onSettingChanged(key, val); | |
| }); | |
| }; | |
| // General | |
| bind('setting-theme', 'theme'); | |
| bind('setting-free-only', 'freeOnly', 'check'); | |
| // Generate | |
| bind('setting-gen-prompt', 'genPrompt'); | |
| bind('setting-gen-quality', 'genQuality'); | |
| bind('setting-gen-negative', 'genNegative'); | |
| bind('setting-gen-sampler', 'genSampler'); | |
| bind('setting-gen-cfg', 'genCfg', 'float'); | |
| bind('setting-gen-steps', 'genSteps', 'int'); | |
| bind('setting-gen-width', 'genWidth', 'int'); | |
| bind('setting-gen-height', 'genHeight', 'int'); | |
| bind('setting-gen-scheduler', 'genScheduler'); | |
| bind('setting-gen-smea', 'genSmea', 'check'); | |
| bind('setting-gen-smea-dyn', 'genSmeaDyn', 'check'); | |
| bind('setting-gen-variety', 'genVariety', 'check'); | |
| bind('setting-gen-legacy', 'genLegacy', 'check'); | |
| bind('setting-gen-decrisp', 'genDecrisp', 'check'); | |
| bind('setting-gen-cfg-rescale', 'genCfgRescale', 'float'); | |
| // History | |
| bind('setting-clear-history', 'clearHistoryOnRefresh', 'check'); | |
| // Cloud | |
| bind('setting-save-cloud', 'saveToCloud', 'check'); | |
| // TAC | |
| bind('setting-tac-enabled', 'tacEnabled', 'check'); | |
| bind('setting-tac-show-zh', 'tacShowZh', 'check'); | |
| bind('setting-tac-match-zh', 'tacMatchZh', 'check'); | |
| bind('setting-tac-max', 'tacMaxResults', 'int'); | |
| bind('setting-tac-autocorrect', 'tacAutoCorrect', 'check'); | |
| bind('setting-tac-add-comma', 'tacAddComma', 'check'); | |
| bind('setting-tac-zh-comma', 'tacChineseComma', 'check'); | |
| bind('setting-tac-underscore', 'tacUnderscoreToSpace', 'check'); | |
| // Slider value displays | |
| NAIUtils.bindSlider('setting-gen-cfg', 'setting-gen-cfg-val', v => v.toFixed(1)); | |
| NAIUtils.bindSlider('setting-gen-steps', 'setting-gen-steps-val'); | |
| NAIUtils.bindSlider('setting-gen-width', 'setting-gen-width-val'); | |
| NAIUtils.bindSlider('setting-gen-height', 'setting-gen-height-val'); | |
| NAIUtils.bindSlider('setting-gen-cfg-rescale', 'setting-gen-cfg-rescale-val', v => v.toFixed(2)); | |
| // Capture button | |
| $('#btn-setting-capture').addEventListener('click', () => { | |
| this.captureCurrentAsDefaults(); | |
| this.renderPage(); | |
| alert('已将当前参数设为默认值'); | |
| }); | |
| $('#btn-setting-reset').addEventListener('click', () => { | |
| if (!confirm('确定要重置所有设置吗?')) return; | |
| localStorage.removeItem(this.STORAGE_KEY); | |
| this.load(); | |
| this.applyAll(); | |
| this.renderPage(); | |
| }); | |
| $('#btn-tac-cache-refresh').addEventListener('click', () => { | |
| caches.delete('nai-tac-v2'); | |
| NAITagComplete.loaded = false; | |
| NAITagComplete.loadTags(); | |
| alert('标签自动补全缓存已刷新'); | |
| }); | |
| }, | |
| _onSettingChanged(key, value) { | |
| if (key === 'theme') this.applyTheme(); | |
| if (key === 'saveToCloud') this.applyCloudCheckbox(); | |
| if (key === 'freeOnly') this._updateFreeOnlyState(); | |
| }, | |
| _updateFreeOnlyState() { | |
| // This is checked by cost.js when updating buttons | |
| // Force a cost recalc | |
| if (window.NAICost) NAICost.updateCostDisplay(); | |
| }, | |
| init() { | |
| this.load(); | |
| this._bindInputs(); | |
| // Render when switching to settings page | |
| document.querySelector('.nav-item[data-page="settings"]')?.addEventListener('click', () => { | |
| this.renderPage(); | |
| }); | |
| // System theme listener | |
| window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { | |
| if (this.get('theme') === 'system') this.applyTheme(); | |
| }); | |
| // Apply settings after a short delay (let other modules init first) | |
| setTimeout(() => this.applyAll(), 100); | |
| }, | |
| }; |