From d630f7c8a400dca8015f59df1c8b0a556778a707 Mon Sep 17 00:00:00 2001 From: Jason Hoku Date: Tue, 17 Mar 2026 23:59:59 -0700 Subject: [PATCH] feat: rewrite upscale modal with inline config editor Replace preset-only UI with inline upscale settings: mode selector (hires/model/both), upscale model dropdown, ratio, denoise, hires steps. Mode changes dynamically show/hide relevant fields. Presets load/save below the config editor. No longer requires going to the Builder UI first. Co-Authored-By: Claude Opus 4.6 (1M context) --- resources/logic_upscale_modal.js | 276 ++++++++++++++++++++----------- 1 file changed, 178 insertions(+), 98 deletions(-) diff --git a/resources/logic_upscale_modal.js b/resources/logic_upscale_modal.js index df31649..d32c1a7 100644 --- a/resources/logic_upscale_modal.js +++ b/resources/logic_upscale_modal.js @@ -1,5 +1,5 @@ // ============================================================================ -// UPSCALE MODAL — Dashboard image upscaling with preset management +// UPSCALE MODAL — Dashboard image upscaling with inline config + presets // ============================================================================ // State for the upscale modal @@ -11,13 +11,32 @@ var upscaleModalState = { currentConfig: null, jobId: null, pollInterval: null, + availableModels: [], // Upscale model list from server }; +// Default upscale step config +function _defaultUpscaleStep() { + return { + active: true, mode: "hires_only", repeat: 1, + upscale_models: [], upscale_ratios: "1.5", upscale_size: "2.0", + hires_denoise: "0.3", hires_steps: 0, tiled_vae: false, tile_size: 512, + resize_method: "bilinear", + }; +} + +// Default config +function _defaultUpscaleConfig() { + return { + pipelines: [{ active: true, name: "Pipeline 1", steps: [_defaultUpscaleStep()] }], + hires_prompt_adjust: false, hires_prompt_behavior: "append_end", hires_prompt_text: "", + }; +} + /** * Open the upscale modal for a specific image */ function openUpscaleModal(imageId) { - const item = activeData ? activeData.find(d => d.id === imageId) : null; + var item = activeData ? activeData.find(function(d) { return d.id === imageId; }) : null; if (!item) return; upscaleModalState.open = true; @@ -25,8 +44,16 @@ function openUpscaleModal(imageId) { upscaleModalState.imageData = item; upscaleModalState.jobId = null; - // Fetch presets then render - fetchUpscalePresets().then(() => renderUpscaleModal()); + // Initialize with default config if none set + if (!upscaleModalState.currentConfig) { + upscaleModalState.currentConfig = _defaultUpscaleConfig(); + } + + // Fetch presets and model list in parallel, then render + Promise.all([ + fetchUpscalePresets(), + fetchUpscaleModels() + ]).then(function() { renderUpscaleModal(); }); } /** @@ -38,7 +65,7 @@ function closeUpscaleModal() { clearInterval(upscaleModalState.pollInterval); upscaleModalState.pollInterval = null; } - const modal = document.getElementById('upscale-modal-overlay'); + var modal = document.getElementById('upscale-modal-overlay'); if (modal) modal.style.display = 'none'; } @@ -47,20 +74,33 @@ function closeUpscaleModal() { */ async function fetchUpscalePresets() { try { - const resp = await fetch('/configbuilder/upscale_presets'); + var resp = await fetch('/configbuilder/upscale_presets'); if (resp.ok) { - const data = await resp.json(); + var data = await resp.json(); upscaleModalState.presets = data.presets || []; } } catch (e) { console.error('Failed to fetch upscale presets:', e); } } +/** + * Fetch available upscale models from server + */ +async function fetchUpscaleModels() { + try { + var resp = await fetch('/configbuilder/model_lists', { method: 'POST' }); + if (resp.ok) { + var data = await resp.json(); + upscaleModalState.availableModels = data.upscale_models || []; + } + } catch (e) { console.error('Failed to fetch upscale models:', e); } +} + /** * Save current config as a new preset */ async function saveUpscalePreset(name) { if (!name || !upscaleModalState.currentConfig) return; - const preset = { + var preset = { name: name, pipelines: JSON.parse(JSON.stringify(upscaleModalState.currentConfig.pipelines)), hires_prompt_adjust: upscaleModalState.currentConfig.hires_prompt_adjust || false, @@ -90,7 +130,6 @@ async function deleteUpscalePreset(index) { body: JSON.stringify({ presets: upscaleModalState.presets }) }); } catch (e) { console.error('Failed to delete preset:', e); } - upscaleModalState.currentConfig = null; renderUpscaleModal(); } @@ -98,7 +137,7 @@ async function deleteUpscalePreset(index) { * Load a preset into the current config */ function loadUpscalePreset(index) { - const preset = upscaleModalState.presets[index]; + var preset = upscaleModalState.presets[index]; if (!preset) return; upscaleModalState.currentConfig = { pipelines: JSON.parse(JSON.stringify(preset.pipelines)), @@ -110,32 +149,47 @@ function loadUpscalePreset(index) { } /** - * Build a readable summary of the current upscale config + * Helper: create a labeled select */ -function buildUpscaleConfigSummary(config) { - if (!config || !config.pipelines) return 'No configuration loaded. Select and load a preset above.'; - const lines = []; - config.pipelines.forEach((p, pi) => { - if (p.active === false) return; - const steps = (p.steps || []).filter(s => s.active !== false); - lines.push((p.name || ('Pipeline ' + (pi + 1))) + ': ' + steps.length + ' step(s)'); - steps.forEach((s, si) => { - const mode = (s.mode || 'hires_only').replace(/_/g, ' '); - const modelList = (s.upscale_models || []).map(m => m.replace(/\\/g, '/').split('/').pop().replace(/\.[^.]+$/, '')); - const ratio = s.upscale_ratios || '1.5'; - const denoise = s.hires_denoise || '0.3'; - let desc = ' Step ' + (si + 1) + ': ' + mode; - if (modelList.length > 0 && modelList[0]) desc += ' \u2014 ' + modelList.join(', '); - desc += ' \u2014 ratio ' + ratio + 'x, denoise ' + denoise; - if (s.repeat && s.repeat > 1) desc += ' (repeat x' + s.repeat + ')'; - lines.push(desc); - }); +function _makeSelect(label, options, currentVal, onChange) { + var row = document.createElement('div'); + row.style.cssText = 'display: flex; align-items: center; gap: 6px; margin-bottom: 6px;'; + var lbl = document.createElement('span'); + lbl.textContent = label; + lbl.style.cssText = 'font-size: 11px; color: #999; min-width: 70px;'; + var sel = document.createElement('select'); + sel.style.cssText = 'flex: 1; background: #1a1a1a; color: #ccc; border: 1px solid #444; border-radius: 4px; padding: 3px 6px; font-size: 11px;'; + options.forEach(function(o) { + var opt = document.createElement('option'); + opt.value = typeof o === 'object' ? o.value : o; + opt.textContent = typeof o === 'object' ? o.label : o; + if (opt.value === String(currentVal)) opt.selected = true; + sel.appendChild(opt); }); - if (config.hires_prompt_adjust && config.hires_prompt_text) { - lines.push(''); - lines.push('HiRes Prompt: ' + config.hires_prompt_behavior + ' \u2014 "' + config.hires_prompt_text + '"'); - } - return lines.join('\n'); + sel.onchange = function() { onChange(sel.value); }; + row.appendChild(lbl); + row.appendChild(sel); + return row; +} + +/** + * Helper: create a labeled text input + */ +function _makeInput(label, currentVal, placeholder, onChange) { + var row = document.createElement('div'); + row.style.cssText = 'display: flex; align-items: center; gap: 6px; margin-bottom: 6px;'; + var lbl = document.createElement('span'); + lbl.textContent = label; + lbl.style.cssText = 'font-size: 11px; color: #999; min-width: 70px;'; + var inp = document.createElement('input'); + inp.type = 'text'; + inp.value = currentVal || ''; + inp.placeholder = placeholder || ''; + inp.style.cssText = 'flex: 1; background: #1a1a1a; color: #ccc; border: 1px solid #444; border-radius: 4px; padding: 3px 6px; font-size: 11px;'; + inp.onchange = function() { onChange(inp.value); }; + row.appendChild(lbl); + row.appendChild(inp); + return row; } /** @@ -147,7 +201,8 @@ function renderUpscaleModal() { overlay.style.display = 'flex'; var item = upscaleModalState.imageData; - var favCount = activeData ? activeData.filter(d => d.favorited).length : 0; + var favCount = activeData ? activeData.filter(function(d) { return d.favorited; }).length : 0; + var cfg = upscaleModalState.currentConfig; var modal = document.getElementById('upscale-modal-content'); if (!modal) return; @@ -169,12 +224,7 @@ function renderUpscaleModal() { // Scope toggle var scopeDiv = document.createElement('div'); - scopeDiv.style.cssText = 'margin-bottom: 12px; padding: 8px; background: #252525; border-radius: 4px;'; - var scopeLabel = document.createElement('div'); - scopeLabel.style.cssText = 'font-size: 11px; color: #999; margin-bottom: 6px;'; - scopeLabel.textContent = 'Scope:'; - scopeDiv.appendChild(scopeLabel); - + scopeDiv.style.cssText = 'margin-bottom: 10px; padding: 8px; background: #252525; border-radius: 4px;'; var radioThis = document.createElement('input'); radioThis.type = 'radio'; radioThis.name = 'upscale-scope'; radioThis.value = 'single'; radioThis.checked = true; radioThis.id = 'upscale-scope-single'; @@ -182,7 +232,6 @@ function renderUpscaleModal() { labelThis.htmlFor = 'upscale-scope-single'; labelThis.style.cssText = 'color: #ccc; font-size: 12px; cursor: pointer; margin-right: 16px;'; labelThis.textContent = ' Upscale This Image'; - var radioAll = document.createElement('input'); radioAll.type = 'radio'; radioAll.name = 'upscale-scope'; radioAll.value = 'favorites'; radioAll.id = 'upscale-scope-favorites'; @@ -190,26 +239,80 @@ function renderUpscaleModal() { labelAll.htmlFor = 'upscale-scope-favorites'; labelAll.style.cssText = 'color: #ccc; font-size: 12px; cursor: pointer;'; labelAll.textContent = ' Upscale All Favorited (' + favCount + ' images)'; - scopeDiv.appendChild(radioThis); scopeDiv.appendChild(labelThis); scopeDiv.appendChild(radioAll); scopeDiv.appendChild(labelAll); modal.appendChild(scopeDiv); - // Preset selector - var presetDiv = document.createElement('div'); - presetDiv.style.cssText = 'margin-bottom: 12px; padding: 8px; background: #252525; border-radius: 4px;'; - var presetLabel = document.createElement('div'); - presetLabel.style.cssText = 'font-size: 11px; color: #999; margin-bottom: 6px;'; - presetLabel.textContent = 'Upscale Preset:'; - presetDiv.appendChild(presetLabel); + // === INLINE UPSCALE CONFIGURATION === + var configDiv = document.createElement('div'); + configDiv.style.cssText = 'margin-bottom: 10px; padding: 8px; background: #252525; border-radius: 4px;'; + var configTitle = document.createElement('div'); + configTitle.style.cssText = 'font-size: 12px; color: #fff; font-weight: bold; margin-bottom: 8px;'; + configTitle.textContent = 'Upscale Settings'; + configDiv.appendChild(configTitle); + + // Ensure config exists + if (!cfg) { cfg = _defaultUpscaleConfig(); upscaleModalState.currentConfig = cfg; } + var step = cfg.pipelines[0] && cfg.pipelines[0].steps[0] ? cfg.pipelines[0].steps[0] : _defaultUpscaleStep(); + + // Mode selector + configDiv.appendChild(_makeSelect('Mode:', [ + { value: 'hires_only', label: 'HiRes Fix Only' }, + { value: 'model_only', label: 'Model Upscale Only' }, + { value: 'model_then_hires', label: 'Model + HiRes Fix' }, + ], step.mode, function(v) { + step.mode = v; + // Show/hide model selector based on mode + if (modelRow) modelRow.style.display = (v === 'hires_only') ? 'none' : 'flex'; + if (denoiseRow) denoiseRow.style.display = (v === 'model_only') ? 'none' : 'flex'; + })); + + // Upscale model selector + var showModel = step.mode !== 'hires_only'; + var modelOptions = [{ value: '', label: '-- Select Model --' }]; + upscaleModalState.availableModels.forEach(function(m) { + var short = m.replace(/\\/g, '/').split('/').pop(); + modelOptions.push({ value: m, label: short }); + }); + var modelRow = _makeSelect('Model:', modelOptions, step.upscale_models[0] || '', function(v) { + step.upscale_models = v ? [v] : []; + }); + modelRow.style.display = showModel ? 'flex' : 'none'; + configDiv.appendChild(modelRow); + + // Ratio + configDiv.appendChild(_makeInput('Ratio:', step.upscale_ratios, '1.5', function(v) { + step.upscale_ratios = v; + })); + + // Denoise + var showDenoise = step.mode !== 'model_only'; + var denoiseRow = _makeInput('Denoise:', step.hires_denoise, '0.3', function(v) { + step.hires_denoise = v; + }); + denoiseRow.style.display = showDenoise ? 'flex' : 'none'; + configDiv.appendChild(denoiseRow); + + // HiRes Steps (0 = use original steps) + configDiv.appendChild(_makeInput('HiRes Steps:', step.hires_steps || '0', '0 = use original', function(v) { + step.hires_steps = parseInt(v) || 0; + })); + + modal.appendChild(configDiv); + + // === PRESET SELECTOR === + var presetDiv = document.createElement('div'); + presetDiv.style.cssText = 'margin-bottom: 10px; padding: 8px; background: #1e1e1e; border-radius: 4px; border: 1px solid #333;'; var presetRow = document.createElement('div'); presetRow.style.cssText = 'display: flex; gap: 4px; align-items: center;'; + var presetLbl = document.createElement('span'); + presetLbl.textContent = 'Presets:'; + presetLbl.style.cssText = 'font-size: 10px; color: #666; white-space: nowrap;'; var presetSelect = document.createElement('select'); - presetSelect.id = 'upscale-preset-select'; - presetSelect.style.cssText = 'flex: 1; background: #1a1a1a; color: #ccc; border: 1px solid #444; border-radius: 4px; padding: 4px 6px; font-size: 11px;'; + presetSelect.style.cssText = 'flex: 1; background: #1a1a1a; color: #ccc; border: 1px solid #444; border-radius: 4px; padding: 3px 6px; font-size: 10px;'; var defaultOpt = document.createElement('option'); - defaultOpt.value = ''; defaultOpt.textContent = '-- Select a preset --'; + defaultOpt.value = ''; defaultOpt.textContent = '-- Presets --'; presetSelect.appendChild(defaultOpt); upscaleModalState.presets.forEach(function(p, i) { var opt = document.createElement('option'); @@ -217,55 +320,38 @@ function renderUpscaleModal() { presetSelect.appendChild(opt); }); - var loadBtn = document.createElement('button'); - loadBtn.textContent = 'Load'; - loadBtn.style.cssText = 'background: var(--accent); color: #000; border: none; border-radius: 4px; padding: 4px 8px; font-size: 11px; cursor: pointer; font-weight: bold;'; - loadBtn.onclick = function() { - var idx = parseInt(presetSelect.value); - if (!isNaN(idx)) loadUpscalePreset(idx); - }; + var pLoadBtn = document.createElement('button'); + pLoadBtn.textContent = 'Load'; + pLoadBtn.style.cssText = 'background: var(--accent); color: #000; border: none; border-radius: 3px; padding: 2px 6px; font-size: 10px; cursor: pointer; font-weight: bold;'; + pLoadBtn.onclick = function() { var idx = parseInt(presetSelect.value); if (!isNaN(idx)) loadUpscalePreset(idx); }; - var saveBtn = document.createElement('button'); - saveBtn.textContent = 'Save'; - saveBtn.style.cssText = 'background: #555; color: #fff; border: none; border-radius: 4px; padding: 4px 8px; font-size: 11px; cursor: pointer;'; - saveBtn.onclick = function() { - if (!upscaleModalState.currentConfig) { alert('Load or configure a preset first'); return; } - var name = prompt('Preset name:'); - if (name) saveUpscalePreset(name); - }; + var pSaveBtn = document.createElement('button'); + pSaveBtn.textContent = 'Save'; + pSaveBtn.style.cssText = 'background: #555; color: #fff; border: none; border-radius: 3px; padding: 2px 6px; font-size: 10px; cursor: pointer;'; + pSaveBtn.onclick = function() { var n = prompt('Preset name:'); if (n) saveUpscalePreset(n); }; - var delBtn = document.createElement('button'); - delBtn.textContent = '\uD83D\uDDD1'; // 🗑 - delBtn.style.cssText = 'background: var(--danger); color: #fff; border: none; border-radius: 4px; padding: 4px 6px; font-size: 11px; cursor: pointer;'; - delBtn.onclick = function() { + var pDelBtn = document.createElement('button'); + pDelBtn.textContent = 'Del'; + pDelBtn.style.cssText = 'background: var(--danger); color: #fff; border: none; border-radius: 3px; padding: 2px 6px; font-size: 10px; cursor: pointer;'; + pDelBtn.onclick = function() { var idx = parseInt(presetSelect.value); if (!isNaN(idx) && idx >= 0 && idx < upscaleModalState.presets.length) { - if (confirm('Delete preset "' + upscaleModalState.presets[idx].name + '"?')) { - deleteUpscalePreset(idx); - } + if (confirm('Delete "' + upscaleModalState.presets[idx].name + '"?')) deleteUpscalePreset(idx); } }; + presetRow.appendChild(presetLbl); presetRow.appendChild(presetSelect); - presetRow.appendChild(loadBtn); - presetRow.appendChild(saveBtn); - presetRow.appendChild(delBtn); + presetRow.appendChild(pLoadBtn); + presetRow.appendChild(pSaveBtn); + presetRow.appendChild(pDelBtn); presetDiv.appendChild(presetRow); modal.appendChild(presetDiv); - // Config summary - var summaryDiv = document.createElement('div'); - summaryDiv.style.cssText = 'margin-bottom: 12px; padding: 8px; background: #1a1a1a; border-radius: 4px; border: 1px solid #333;'; - var summaryPre = document.createElement('pre'); - summaryPre.style.cssText = 'font-size: 10px; color: #aaa; margin: 0; white-space: pre-wrap; font-family: monospace;'; - summaryPre.textContent = buildUpscaleConfigSummary(upscaleModalState.currentConfig); - summaryDiv.appendChild(summaryPre); - modal.appendChild(summaryDiv); - // Progress area (hidden until upscale starts) var progressDiv = document.createElement('div'); progressDiv.id = 'upscale-modal-progress'; - progressDiv.style.cssText = 'margin-bottom: 12px; display: none;'; + progressDiv.style.cssText = 'margin-bottom: 10px; display: none;'; var progressBar = document.createElement('div'); progressBar.style.cssText = 'height: 6px; background: #333; border-radius: 3px; overflow: hidden;'; var progressFill = document.createElement('div'); @@ -282,29 +368,23 @@ function renderUpscaleModal() { // Action buttons var actionsDiv = document.createElement('div'); actionsDiv.style.cssText = 'display: flex; gap: 8px; justify-content: flex-end;'; - var cancelBtn = document.createElement('button'); cancelBtn.textContent = 'Cancel'; cancelBtn.style.cssText = 'background: #444; color: #fff; border: none; border-radius: 4px; padding: 6px 16px; font-size: 12px; cursor: pointer;'; cancelBtn.onclick = function() { if (upscaleModalState.jobId) { fetch('/config_tester/cancel_upscale', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, + method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ job_id: upscaleModalState.jobId }) }); } closeUpscaleModal(); }; - var startBtn = document.createElement('button'); startBtn.textContent = 'Start Upscale'; startBtn.id = 'upscale-start-btn'; startBtn.style.cssText = 'background: var(--accent); color: #000; border: none; border-radius: 4px; padding: 6px 16px; font-size: 12px; cursor: pointer; font-weight: bold;'; - startBtn.disabled = !upscaleModalState.currentConfig; - if (!upscaleModalState.currentConfig) startBtn.style.opacity = '0.5'; startBtn.onclick = startUpscaleFromModal; - actionsDiv.appendChild(cancelBtn); actionsDiv.appendChild(startBtn); modal.appendChild(actionsDiv); @@ -314,14 +394,14 @@ function renderUpscaleModal() { * Start the upscale job from the modal */ async function startUpscaleFromModal() { - if (!upscaleModalState.currentConfig) return; + var cfg = upscaleModalState.currentConfig; + if (!cfg) return; var scopeEl = document.querySelector('input[name="upscale-scope"]:checked'); var scope = scopeEl ? scopeEl.value : 'single'; var allFavorited = scope === 'favorites'; var imageIds = allFavorited ? [] : [upscaleModalState.imageId]; - // Disable start button, show progress var startBtn = document.getElementById('upscale-start-btn'); if (startBtn) { startBtn.disabled = true; startBtn.style.opacity = '0.5'; startBtn.textContent = 'Starting...'; } var progressDiv = document.getElementById('upscale-modal-progress'); @@ -334,7 +414,7 @@ async function startUpscaleFromModal() { body: JSON.stringify({ session_name: document.getElementById('session-input')?.value || "default", image_ids: imageIds, - upscale_config: upscaleModalState.currentConfig, + upscale_config: cfg, all_favorited: allFavorited, }) });