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) <noreply@anthropic.com>
This commit is contained in:
Jason Hoku
2026-03-17 23:59:59 -07:00
co-authored by Claude Opus 4.6
parent ef7cc81edb
commit d630f7c8a4
+178 -98
View File
@@ -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,
})
});