Add progress overlay and fix txt2img for local GPU
- New progress_overlay.js: animated fullscreen overlay with shimmer bar, fake progress creep, Esc-to-cancel, used by all slow AI operations - text_to_image.js: allow local_gpu provider (was incorrectly blocked); show provider/model/VRAM info in dialog; show estimated generation time; use progress overlay during generation - upscale.js: replace alertify.message with progress overlay (90s estimate for AI upscale, 10s for Lanczos) - frame_fit.js: progress overlay for extend mode (AI outpaint ~45s) - print_prepare.js: progress overlay for full upscale+frame chain (~2 min) - selection_actions.js: progress overlay for all AI region edits https://claude.ai/code/session_01WVDg7amsy1TTtxvpku7bcM
This commit is contained in:
@@ -0,0 +1,142 @@
|
|||||||
|
/**
|
||||||
|
* ProgressOverlay — shared animated progress indicator for long AI operations.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* import { showProgress, updateProgress, hideProgress } from './progress_overlay.js';
|
||||||
|
*
|
||||||
|
* showProgress('Generating image…');
|
||||||
|
* updateProgress(50, 'Denoising step 15/30…'); // optional step updates
|
||||||
|
* hideProgress();
|
||||||
|
*
|
||||||
|
* When you don't have real step counts, call showProgress() and hideProgress() only —
|
||||||
|
* the bar animates automatically with a shimmer to signal activity.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var _overlay = null;
|
||||||
|
var _bar = null;
|
||||||
|
var _label = null;
|
||||||
|
var _shimmerAnim = null;
|
||||||
|
var _fakeTimer = null;
|
||||||
|
var _currentPct = 0;
|
||||||
|
|
||||||
|
export function showProgress(message, estimatedSeconds) {
|
||||||
|
hideProgress();
|
||||||
|
|
||||||
|
_currentPct = 0;
|
||||||
|
|
||||||
|
// ── Backdrop ──────────────────────────────────────────────────────────────
|
||||||
|
_overlay = document.createElement('div');
|
||||||
|
_overlay.id = 'ai-progress-overlay';
|
||||||
|
_overlay.style.cssText = [
|
||||||
|
'position:fixed', 'inset:0', 'z-index:99999',
|
||||||
|
'display:flex', 'flex-direction:column',
|
||||||
|
'align-items:center', 'justify-content:center',
|
||||||
|
'background:rgba(0,0,0,0.55)',
|
||||||
|
'backdrop-filter:blur(2px)',
|
||||||
|
'-webkit-backdrop-filter:blur(2px)',
|
||||||
|
].join(';');
|
||||||
|
|
||||||
|
// ── Card ──────────────────────────────────────────────────────────────────
|
||||||
|
var card = document.createElement('div');
|
||||||
|
card.style.cssText = [
|
||||||
|
'background:#1a1a2e',
|
||||||
|
'border:1px solid #3a3a6a',
|
||||||
|
'border-radius:14px',
|
||||||
|
'padding:28px 36px',
|
||||||
|
'min-width:320px', 'max-width:480px',
|
||||||
|
'box-shadow:0 12px 48px rgba(0,0,0,0.8)',
|
||||||
|
'display:flex', 'flex-direction:column', 'gap:14px',
|
||||||
|
'text-align:center',
|
||||||
|
].join(';');
|
||||||
|
|
||||||
|
// ── Label ─────────────────────────────────────────────────────────────────
|
||||||
|
_label = document.createElement('div');
|
||||||
|
_label.textContent = message || 'Processing…';
|
||||||
|
_label.style.cssText = 'font-family:sans-serif;font-size:13px;color:#c0c0e0;line-height:1.4;min-height:2.8em';
|
||||||
|
|
||||||
|
// ── Track ─────────────────────────────────────────────────────────────────
|
||||||
|
var track = document.createElement('div');
|
||||||
|
track.style.cssText = [
|
||||||
|
'width:100%', 'height:6px',
|
||||||
|
'background:#0f0f2a',
|
||||||
|
'border-radius:3px',
|
||||||
|
'overflow:hidden',
|
||||||
|
'position:relative',
|
||||||
|
].join(';');
|
||||||
|
|
||||||
|
// ── Shimmer (indeterminate stripe) ────────────────────────────────────────
|
||||||
|
var shimmer = document.createElement('div');
|
||||||
|
shimmer.style.cssText = [
|
||||||
|
'position:absolute', 'inset:0',
|
||||||
|
'background:linear-gradient(90deg,transparent 0%,rgba(120,120,255,0.25) 50%,transparent 100%)',
|
||||||
|
'transform:translateX(-100%)',
|
||||||
|
'will-change:transform',
|
||||||
|
].join(';');
|
||||||
|
|
||||||
|
// ── Filled bar ────────────────────────────────────────────────────────────
|
||||||
|
_bar = document.createElement('div');
|
||||||
|
_bar.style.cssText = [
|
||||||
|
'position:absolute', 'inset-block:0', 'left:0',
|
||||||
|
'width:0%',
|
||||||
|
'background:linear-gradient(90deg,#5577ff,#88aaff)',
|
||||||
|
'border-radius:3px',
|
||||||
|
'transition:width 0.35s ease',
|
||||||
|
].join(';');
|
||||||
|
|
||||||
|
// ── Cancel hint ───────────────────────────────────────────────────────────
|
||||||
|
var hint = document.createElement('div');
|
||||||
|
hint.textContent = 'Press Esc to cancel';
|
||||||
|
hint.style.cssText = 'font-family:sans-serif;font-size:10px;color:#444;margin-top:2px';
|
||||||
|
|
||||||
|
track.appendChild(shimmer);
|
||||||
|
track.appendChild(_bar);
|
||||||
|
card.appendChild(_label);
|
||||||
|
card.appendChild(track);
|
||||||
|
card.appendChild(hint);
|
||||||
|
_overlay.appendChild(card);
|
||||||
|
document.body.appendChild(_overlay);
|
||||||
|
|
||||||
|
// Animate shimmer
|
||||||
|
var pos = -100;
|
||||||
|
_shimmerAnim = setInterval(() => {
|
||||||
|
pos += 2.5;
|
||||||
|
if (pos > 200) pos = -100;
|
||||||
|
shimmer.style.transform = `translateX(${pos}%)`;
|
||||||
|
}, 16);
|
||||||
|
|
||||||
|
// Fake progress that creeps toward 90% if no real steps given
|
||||||
|
if (estimatedSeconds) {
|
||||||
|
var totalMs = estimatedSeconds * 1000;
|
||||||
|
var step = 90 / (totalMs / 200);
|
||||||
|
_fakeTimer = setInterval(() => {
|
||||||
|
if (_currentPct < 90) {
|
||||||
|
_currentPct = Math.min(90, _currentPct + step);
|
||||||
|
_bar.style.width = _currentPct + '%';
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Esc to cancel
|
||||||
|
_overlay._escHandler = (e) => { if (e.key === 'Escape') hideProgress(); };
|
||||||
|
document.addEventListener('keydown', _overlay._escHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateProgress(pct, message) {
|
||||||
|
if (!_overlay) return;
|
||||||
|
_currentPct = Math.max(_currentPct, Math.min(100, pct));
|
||||||
|
if (_bar) _bar.style.width = _currentPct + '%';
|
||||||
|
if (_label && message) _label.textContent = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hideProgress() {
|
||||||
|
if (_shimmerAnim) { clearInterval(_shimmerAnim); _shimmerAnim = null; }
|
||||||
|
if (_fakeTimer) { clearInterval(_fakeTimer); _fakeTimer = null; }
|
||||||
|
if (_overlay) {
|
||||||
|
document.removeEventListener('keydown', _overlay._escHandler);
|
||||||
|
_overlay.remove();
|
||||||
|
_overlay = null;
|
||||||
|
}
|
||||||
|
_bar = null;
|
||||||
|
_label = null;
|
||||||
|
_currentPct = 0;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Text → Image — opens a sidebar-style dialog, generates via remote provider,
|
* Text → Image — generates via remote or local-GPU provider,
|
||||||
* pastes result as a new layer on the current canvas.
|
* pastes result as a new layer on the current canvas.
|
||||||
*
|
*
|
||||||
* Menu target: generate/text_to_image.text_to_image
|
* Menu target: generate/text_to_image.text_to_image
|
||||||
@@ -12,6 +12,7 @@ import Dialog_class from './../../libs/popup.js';
|
|||||||
import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js';
|
import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js';
|
||||||
import apiService from './../../services/api.js';
|
import apiService from './../../services/api.js';
|
||||||
import { getCapabilities } from './../../api/capabilities.js';
|
import { getCapabilities } from './../../api/capabilities.js';
|
||||||
|
import { showProgress, updateProgress, hideProgress } from './../../libs/progress_overlay.js';
|
||||||
|
|
||||||
var instance = null;
|
var instance = null;
|
||||||
|
|
||||||
@@ -27,21 +28,54 @@ class Generate_text_to_image_class {
|
|||||||
|
|
||||||
async text_to_image() {
|
async text_to_image() {
|
||||||
var caps = await getCapabilities();
|
var caps = await getCapabilities();
|
||||||
if (!caps.remote || !caps.remote.healthy) {
|
var hasRemote = caps.remote && caps.remote.healthy;
|
||||||
|
var hasLocal = caps.local && caps.local.local_gpu_available;
|
||||||
|
|
||||||
|
if (!hasRemote && !hasLocal) {
|
||||||
alertify.error(
|
alertify.error(
|
||||||
'Text → Image requires a remote AI provider. ' +
|
'Text → Image requires an AI provider. ' +
|
||||||
'Set AI_PROVIDER (openai / invokeai / comfyui) in .env and restart.'
|
'Set AI_PROVIDER=openai / invokeai / comfyui / local_gpu in .env and restart, ' +
|
||||||
|
'or configure one in Image → AI Provider Settings.'
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var _this = this;
|
var _this = this;
|
||||||
var canvasW = config.WIDTH || 1024;
|
var canvasW = config.WIDTH || 1024;
|
||||||
var canvasH = config.HEIGHT || 1024;
|
var canvasH = config.HEIGHT || 1024;
|
||||||
|
|
||||||
|
// Build provider info line
|
||||||
|
var providerHtml = hasRemote
|
||||||
|
? `<span style="color:#44cc44">● ${caps.remote.provider}</span>`
|
||||||
|
: `<span style="color:#44cc44">● local GPU · ${caps.local.gpu_tier || ''} · ${_shortGpu(caps.local.gpu_device)}</span>`;
|
||||||
|
|
||||||
|
// Model note for local GPU
|
||||||
|
var modelNote = '';
|
||||||
|
if (hasLocal && !hasRemote) {
|
||||||
|
var rec = caps.local.local_gpu_capabilities && caps.local.local_gpu_capabilities.recommended;
|
||||||
|
var m = rec && rec.txt2img;
|
||||||
|
if (m) {
|
||||||
|
modelNote = `Model: <span style="color:#ddd">${m.model_id.split('/').pop()}</span>`;
|
||||||
|
if (m.memory_opt && m.memory_opt !== 'none') modelNote += ` · <span style="color:#aaa">${m.memory_opt}</span>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estimate generation time (rough guide for the progress bar)
|
||||||
|
var estSec = hasLocal ? 60 : 15; // local GPU ~1 min; OpenAI ~15s
|
||||||
|
|
||||||
|
var defaultW = Math.min(canvasW, hasLocal ? (caps.local.local_gpu_capabilities?.recommended?.txt2img?.native_res || 1024) : 1024);
|
||||||
|
var defaultH = Math.min(canvasH, defaultW);
|
||||||
|
|
||||||
this.Dialog.show({
|
this.Dialog.show({
|
||||||
title: 'Text → Image',
|
title: 'Text → Image',
|
||||||
params: [
|
params: [
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
html: `<div style="font-size:11px;margin:0 0 8px">
|
||||||
|
Provider: ${providerHtml}${modelNote ? ' · ' + modelNote : ''}<br>
|
||||||
|
<span style="color:#777">Generation typically takes ${estSec < 30 ? 'a few seconds' : estSec < 90 ? '30–90 seconds on local GPU' : '1–3 minutes on local GPU'}.</span>
|
||||||
|
</div>`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'prompt',
|
name: 'prompt',
|
||||||
title: 'Describe your image:',
|
title: 'Describe your image:',
|
||||||
@@ -58,7 +92,7 @@ class Generate_text_to_image_class {
|
|||||||
{
|
{
|
||||||
name: 'width',
|
name: 'width',
|
||||||
title: 'Width (px):',
|
title: 'Width (px):',
|
||||||
value: Math.min(canvasW, 1024),
|
value: defaultW,
|
||||||
range: [256, 2048],
|
range: [256, 2048],
|
||||||
step: 64,
|
step: 64,
|
||||||
type: 'range',
|
type: 'range',
|
||||||
@@ -66,7 +100,7 @@ class Generate_text_to_image_class {
|
|||||||
{
|
{
|
||||||
name: 'height',
|
name: 'height',
|
||||||
title: 'Height (px):',
|
title: 'Height (px):',
|
||||||
value: Math.min(canvasH, 1024),
|
value: defaultH,
|
||||||
range: [256, 2048],
|
range: [256, 2048],
|
||||||
step: 64,
|
step: 64,
|
||||||
type: 'range',
|
type: 'range',
|
||||||
@@ -99,29 +133,31 @@ class Generate_text_to_image_class {
|
|||||||
alertify.warning('Please enter a description.');
|
alertify.warning('Please enter a description.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await _this._generate(params);
|
await _this._generate(params, estSec);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async _generate(params) {
|
async _generate(params, estSec) {
|
||||||
if (this.isProcessing) return;
|
if (this.isProcessing) return;
|
||||||
this.isProcessing = true;
|
this.isProcessing = true;
|
||||||
alertify.message('Generating image... please wait', 0);
|
|
||||||
|
showProgress('Generating image… this may take a minute on local GPU', estSec || 60);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var result = await apiService.textToImage(params.prompt, {
|
var result = await apiService.textToImage(params.prompt, {
|
||||||
width: params.width || 1024,
|
width: params.width || 1024,
|
||||||
height: params.height || 1024,
|
height: params.height || 1024,
|
||||||
negativePrompt: params.negative_prompt || '',
|
negativePrompt: params.negative_prompt || '',
|
||||||
steps: params.steps || 30,
|
steps: params.steps || 30,
|
||||||
seed: params.seed || 0,
|
seed: params.seed || 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
updateProgress(95, 'Placing image…');
|
||||||
|
|
||||||
var img = new Image();
|
var img = new Image();
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
if (params.placement === 'replace_canvas') {
|
if (params.placement === 'replace_canvas') {
|
||||||
// Resize canvas and replace bottom layer
|
|
||||||
config.WIDTH = img.naturalWidth;
|
config.WIDTH = img.naturalWidth;
|
||||||
config.HEIGHT = img.naturalHeight;
|
config.HEIGHT = img.naturalHeight;
|
||||||
var resultCanvas = document.createElement('canvas');
|
var resultCanvas = document.createElement('canvas');
|
||||||
@@ -134,41 +170,43 @@ class Generate_text_to_image_class {
|
|||||||
])
|
])
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Add as new layer on top
|
|
||||||
var dataURL = img.src;
|
|
||||||
app.State.do_action(
|
app.State.do_action(
|
||||||
new app.Actions.Bundle_action('txt2img_layer', 'Text → Image Layer', [
|
new app.Actions.Bundle_action('txt2img_layer', 'Text → Image Layer', [
|
||||||
new app.Actions.Insert_layer_action({
|
new app.Actions.Insert_layer_action({
|
||||||
name: params.prompt.slice(0, 30),
|
name: params.prompt.slice(0, 30),
|
||||||
type: 'image',
|
type: 'image',
|
||||||
data: dataURL,
|
data: img.src,
|
||||||
x: 0,
|
x: 0, y: 0,
|
||||||
y: 0,
|
width: img.naturalWidth,
|
||||||
width: img.naturalWidth,
|
height: img.naturalHeight,
|
||||||
height: img.naturalHeight,
|
|
||||||
width_original: img.naturalWidth,
|
width_original: img.naturalWidth,
|
||||||
height_original: img.naturalHeight,
|
height_original: img.naturalHeight,
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
alertify.dismissAll();
|
hideProgress();
|
||||||
alertify.success('Image generated!');
|
alertify.success('Image generated!');
|
||||||
this.isProcessing = false;
|
this.isProcessing = false;
|
||||||
};
|
};
|
||||||
img.onerror = () => {
|
img.onerror = () => {
|
||||||
alertify.dismissAll();
|
hideProgress();
|
||||||
alertify.error('Failed to load generated image.');
|
alertify.error('Failed to load generated image.');
|
||||||
this.isProcessing = false;
|
this.isProcessing = false;
|
||||||
};
|
};
|
||||||
img.src = 'data:image/png;base64,' + result.result;
|
img.src = 'data:image/png;base64,' + result.result;
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alertify.dismissAll();
|
hideProgress();
|
||||||
alertify.error('Generation failed: ' + (err.message || err));
|
alertify.error('Generation failed: ' + (err.message || err));
|
||||||
this.isProcessing = false;
|
this.isProcessing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _shortGpu(name) {
|
||||||
|
if (!name) return 'GPU';
|
||||||
|
return name.replace(/^NVIDIA GeForce /i, '').replace(/^NVIDIA /i, '');
|
||||||
|
}
|
||||||
|
|
||||||
export default Generate_text_to_image_class;
|
export default Generate_text_to_image_class;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import Base_layers_class from './../../core/base-layers.js';
|
|||||||
import Dialog_class from './../../libs/popup.js';
|
import Dialog_class from './../../libs/popup.js';
|
||||||
import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js';
|
import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js';
|
||||||
import { getCapabilities } from './../../api/capabilities.js';
|
import { getCapabilities } from './../../api/capabilities.js';
|
||||||
|
import { showProgress, hideProgress } from './../../libs/progress_overlay.js';
|
||||||
|
|
||||||
var instance = null;
|
var instance = null;
|
||||||
|
|
||||||
@@ -123,11 +124,11 @@ class Image_frame_fit_class {
|
|||||||
this.isProcessing = true;
|
this.isProcessing = true;
|
||||||
|
|
||||||
var mode = params.mode || 'smart';
|
var mode = params.mode || 'smart';
|
||||||
alertify.message(
|
showProgress(
|
||||||
mode === 'extend'
|
mode === 'extend'
|
||||||
? 'Fitting to frame with AI extension... please wait'
|
? 'Fitting to frame with AI extension…'
|
||||||
: 'Fitting to frame...',
|
: 'Fitting to frame…',
|
||||||
0
|
mode === 'extend' ? 45 : 5
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -203,7 +204,7 @@ class Image_frame_fit_class {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
alertify.dismissAll();
|
hideProgress();
|
||||||
alertify.success(
|
alertify.success(
|
||||||
`Done! ${result.output_pixels.width}×${result.output_pixels.height}px` +
|
`Done! ${result.output_pixels.width}×${result.output_pixels.height}px` +
|
||||||
` (${result.frame} ${result.orientation}, ${result.mode_used})`
|
` (${result.frame} ${result.orientation}, ${result.mode_used})`
|
||||||
@@ -211,14 +212,14 @@ class Image_frame_fit_class {
|
|||||||
this.isProcessing = false;
|
this.isProcessing = false;
|
||||||
};
|
};
|
||||||
img.onerror = () => {
|
img.onerror = () => {
|
||||||
alertify.dismissAll();
|
hideProgress();
|
||||||
alertify.error('Failed to load result.');
|
alertify.error('Failed to load result.');
|
||||||
this.isProcessing = false;
|
this.isProcessing = false;
|
||||||
};
|
};
|
||||||
img.src = 'data:image/png;base64,' + result.result;
|
img.src = 'data:image/png;base64,' + result.result;
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alertify.dismissAll();
|
hideProgress();
|
||||||
alertify.error('Frame fit failed: ' + (err.message || err));
|
alertify.error('Frame fit failed: ' + (err.message || err));
|
||||||
this.isProcessing = false;
|
this.isProcessing = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import Base_layers_class from './../../core/base-layers.js';
|
|||||||
import Dialog_class from './../../libs/popup.js';
|
import Dialog_class from './../../libs/popup.js';
|
||||||
import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js';
|
import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js';
|
||||||
import { getCapabilities } from './../../api/capabilities.js';
|
import { getCapabilities } from './../../api/capabilities.js';
|
||||||
|
import { showProgress, updateProgress, hideProgress } from './../../libs/progress_overlay.js';
|
||||||
|
|
||||||
const FRAME_SIZES = [
|
const FRAME_SIZES = [
|
||||||
'5x7', '8x10', '11x14', '18x24', '16x20', '20x24', '24x36',
|
'5x7', '8x10', '11x14', '18x24', '16x20', '20x24', '24x36',
|
||||||
@@ -153,11 +154,11 @@ class Image_print_prepare_class {
|
|||||||
var neededScale = Math.max(targetW / origW, targetH / origH);
|
var neededScale = Math.max(targetW / origW, targetH / origH);
|
||||||
var willUpscale = neededScale > 1.05;
|
var willUpscale = neededScale > 1.05;
|
||||||
|
|
||||||
alertify.message(
|
showProgress(
|
||||||
willUpscale
|
willUpscale
|
||||||
? `Upscaling ${neededScale.toFixed(1)}× with AI, then fitting to frame… this may take a minute`
|
? `Upscaling ${neededScale.toFixed(1)}× with AI, then fitting to frame…\nAI is reconstructing detail — this may take 1–3 minutes.`
|
||||||
: 'Fitting to frame…',
|
: 'Fitting to frame…',
|
||||||
0
|
willUpscale ? 120 : 8
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -188,6 +189,7 @@ class Image_print_prepare_class {
|
|||||||
}
|
}
|
||||||
var result = await r.json();
|
var result = await r.json();
|
||||||
|
|
||||||
|
updateProgress(90, 'Placing result…');
|
||||||
var img = new Image();
|
var img = new Image();
|
||||||
img.onload = () => {
|
img.onload = () => {
|
||||||
var resultCanvas = document.createElement('canvas');
|
var resultCanvas = document.createElement('canvas');
|
||||||
@@ -225,7 +227,7 @@ class Image_print_prepare_class {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
alertify.dismissAll();
|
hideProgress();
|
||||||
var upscaleNote = result.upscale_applied
|
var upscaleNote = result.upscale_applied
|
||||||
? ` · ${result.upscale_factor}× ${result.upscale_method}`
|
? ` · ${result.upscale_factor}× ${result.upscale_method}`
|
||||||
: ' · no upscale needed';
|
: ' · no upscale needed';
|
||||||
@@ -235,14 +237,14 @@ class Image_print_prepare_class {
|
|||||||
this.isProcessing = false;
|
this.isProcessing = false;
|
||||||
};
|
};
|
||||||
img.onerror = () => {
|
img.onerror = () => {
|
||||||
alertify.dismissAll();
|
hideProgress();
|
||||||
alertify.error('Failed to load result.');
|
alertify.error('Failed to load result.');
|
||||||
this.isProcessing = false;
|
this.isProcessing = false;
|
||||||
};
|
};
|
||||||
img.src = 'data:image/png;base64,' + result.result;
|
img.src = 'data:image/png;base64,' + result.result;
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alertify.dismissAll();
|
hideProgress();
|
||||||
alertify.error('Prepare for Print failed: ' + (err.message || err));
|
alertify.error('Prepare for Print failed: ' + (err.message || err));
|
||||||
this.isProcessing = false;
|
this.isProcessing = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import config from './../../config.js';
|
|||||||
import Base_layers_class from './../../core/base-layers.js';
|
import Base_layers_class from './../../core/base-layers.js';
|
||||||
import Dialog_class from './../../libs/popup.js';
|
import Dialog_class from './../../libs/popup.js';
|
||||||
import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js';
|
import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js';
|
||||||
|
import { showProgress, updateProgress, hideProgress } from './../../libs/progress_overlay.js';
|
||||||
|
|
||||||
var instance = null;
|
var instance = null;
|
||||||
|
|
||||||
@@ -221,7 +222,12 @@ class Image_upscale_class {
|
|||||||
? `Auto (${caps.recommended_label || 'best available'})`
|
? `Auto (${caps.recommended_label || 'best available'})`
|
||||||
: (METHOD_LABELS[method] || method);
|
: (METHOD_LABELS[method] || method);
|
||||||
|
|
||||||
alertify.message(`Upscaling ${scale}× · ${methodLabel}…`, 0);
|
var isAI = method !== 'lanczos';
|
||||||
|
showProgress(
|
||||||
|
`Upscaling ${scale}× with ${methodLabel}…` +
|
||||||
|
(isAI ? '\nAI is reconstructing detail — this may take 30–120 seconds.' : ''),
|
||||||
|
isAI ? 90 : 10
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var layerCanvas = document.createElement('canvas');
|
var layerCanvas = document.createElement('canvas');
|
||||||
@@ -276,21 +282,21 @@ class Image_upscale_class {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
alertify.dismissAll();
|
hideProgress();
|
||||||
alertify.success(
|
alertify.success(
|
||||||
`${result.output.width}×${result.output.height}px · ${usedLabel}`
|
`${result.output.width}×${result.output.height}px · ${usedLabel}`
|
||||||
);
|
);
|
||||||
this.isProcessing = false;
|
this.isProcessing = false;
|
||||||
};
|
};
|
||||||
img.onerror = () => {
|
img.onerror = () => {
|
||||||
alertify.dismissAll();
|
hideProgress();
|
||||||
alertify.error('Failed to load upscaled image.');
|
alertify.error('Failed to load upscaled image.');
|
||||||
this.isProcessing = false;
|
this.isProcessing = false;
|
||||||
};
|
};
|
||||||
img.src = 'data:image/png;base64,' + result.result;
|
img.src = 'data:image/png;base64,' + result.result;
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
alertify.dismissAll();
|
hideProgress();
|
||||||
alertify.error('Upscale failed: ' + (err.message || err));
|
alertify.error('Upscale failed: ' + (err.message || err));
|
||||||
this.isProcessing = false;
|
this.isProcessing = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import app from './../app.js';
|
|||||||
import config from './../config.js';
|
import config from './../config.js';
|
||||||
import Base_layers_class from './../core/base-layers.js';
|
import Base_layers_class from './../core/base-layers.js';
|
||||||
import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js';
|
import alertify from './../../../node_modules/alertifyjs/build/alertify.min.js';
|
||||||
|
import { showProgress, hideProgress } from './../libs/progress_overlay.js';
|
||||||
|
|
||||||
const BASE = window.API_BASE_URL || '';
|
const BASE = window.API_BASE_URL || '';
|
||||||
|
|
||||||
@@ -175,7 +176,7 @@ export class SelectionActions {
|
|||||||
async _scaleSelection(scalePct) {
|
async _scaleSelection(scalePct) {
|
||||||
if (!this._check()) return;
|
if (!this._check()) return;
|
||||||
this.hide();
|
this.hide();
|
||||||
alertify.message('Scaling object and filling gap…');
|
showProgress('Scaling object and AI-filling the gap…', 30);
|
||||||
try {
|
try {
|
||||||
var res = await _post('/api/image/scale-selection', {
|
var res = await _post('/api/image/scale-selection', {
|
||||||
image: this._imageData,
|
image: this._imageData,
|
||||||
@@ -184,8 +185,10 @@ export class SelectionActions {
|
|||||||
});
|
});
|
||||||
this.tool.updateLayerWithResult(res.result);
|
this.tool.updateLayerWithResult(res.result);
|
||||||
this.tool.clearSelection();
|
this.tool.clearSelection();
|
||||||
|
hideProgress();
|
||||||
alertify.success('Scaled by ' + scalePct + '%!');
|
alertify.success('Scaled by ' + scalePct + '%!');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
hideProgress();
|
||||||
alertify.error('Scale failed: ' + e.message);
|
alertify.error('Scale failed: ' + e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,7 +196,7 @@ export class SelectionActions {
|
|||||||
async _makeAsymmetric() {
|
async _makeAsymmetric() {
|
||||||
if (!this._check()) return;
|
if (!this._check()) return;
|
||||||
this.hide();
|
this.hide();
|
||||||
alertify.message('AI is adding natural asymmetry…');
|
showProgress('AI is adding natural asymmetry…', 60);
|
||||||
try {
|
try {
|
||||||
var res = await _post('/api/image/ai-edit-region', {
|
var res = await _post('/api/image/ai-edit-region', {
|
||||||
image: this._imageData,
|
image: this._imageData,
|
||||||
@@ -205,8 +208,10 @@ export class SelectionActions {
|
|||||||
});
|
});
|
||||||
this.tool.updateLayerWithResult(res.result);
|
this.tool.updateLayerWithResult(res.result);
|
||||||
this.tool.clearSelection();
|
this.tool.clearSelection();
|
||||||
|
hideProgress();
|
||||||
alertify.success('Made less symmetrical!');
|
alertify.success('Made less symmetrical!');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
hideProgress();
|
||||||
alertify.error('AI edit failed: ' + e.message);
|
alertify.error('AI edit failed: ' + e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -214,7 +219,7 @@ export class SelectionActions {
|
|||||||
async _aiEditRegion(instruction) {
|
async _aiEditRegion(instruction) {
|
||||||
if (!this._check()) return;
|
if (!this._check()) return;
|
||||||
this.hide();
|
this.hide();
|
||||||
alertify.message('AI is editing the region…');
|
showProgress('AI is editing the region…', 60);
|
||||||
try {
|
try {
|
||||||
var res = await _post('/api/image/ai-edit-region', {
|
var res = await _post('/api/image/ai-edit-region', {
|
||||||
image: this._imageData,
|
image: this._imageData,
|
||||||
@@ -225,8 +230,10 @@ export class SelectionActions {
|
|||||||
});
|
});
|
||||||
this.tool.updateLayerWithResult(res.result);
|
this.tool.updateLayerWithResult(res.result);
|
||||||
this.tool.clearSelection();
|
this.tool.clearSelection();
|
||||||
|
hideProgress();
|
||||||
alertify.success('Done!');
|
alertify.success('Done!');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
hideProgress();
|
||||||
alertify.error('AI edit failed: ' + e.message);
|
alertify.error('AI edit failed: ' + e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -257,7 +264,7 @@ export class SelectionActions {
|
|||||||
|
|
||||||
var clipBase64 = await _blobToBase64(clipBlob);
|
var clipBase64 = await _blobToBase64(clipBlob);
|
||||||
this.hide();
|
this.hide();
|
||||||
alertify.message('Pasting clipboard into selection…');
|
showProgress('Pasting clipboard into selection…', 10);
|
||||||
|
|
||||||
var res = await _post('/api/image/paste-into-selection', {
|
var res = await _post('/api/image/paste-into-selection', {
|
||||||
image: this._imageData,
|
image: this._imageData,
|
||||||
@@ -266,8 +273,10 @@ export class SelectionActions {
|
|||||||
});
|
});
|
||||||
this.tool.updateLayerWithResult(res.result);
|
this.tool.updateLayerWithResult(res.result);
|
||||||
this.tool.clearSelection();
|
this.tool.clearSelection();
|
||||||
|
hideProgress();
|
||||||
alertify.success('Clipboard pasted into selection!');
|
alertify.success('Clipboard pasted into selection!');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
hideProgress();
|
||||||
alertify.error('Paste failed: ' + e.message);
|
alertify.error('Paste failed: ' + e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user