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.
|
||||
*
|
||||
* 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 apiService from './../../services/api.js';
|
||||
import { getCapabilities } from './../../api/capabilities.js';
|
||||
import { showProgress, updateProgress, hideProgress } from './../../libs/progress_overlay.js';
|
||||
|
||||
var instance = null;
|
||||
|
||||
@@ -27,21 +28,54 @@ class Generate_text_to_image_class {
|
||||
|
||||
async text_to_image() {
|
||||
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(
|
||||
'Text → Image requires a remote AI provider. ' +
|
||||
'Set AI_PROVIDER (openai / invokeai / comfyui) in .env and restart.'
|
||||
'Text → Image requires an AI provider. ' +
|
||||
'Set AI_PROVIDER=openai / invokeai / comfyui / local_gpu in .env and restart, ' +
|
||||
'or configure one in Image → AI Provider Settings.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var _this = this;
|
||||
var _this = this;
|
||||
var canvasW = config.WIDTH || 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({
|
||||
title: 'Text → Image',
|
||||
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',
|
||||
title: 'Describe your image:',
|
||||
@@ -58,7 +92,7 @@ class Generate_text_to_image_class {
|
||||
{
|
||||
name: 'width',
|
||||
title: 'Width (px):',
|
||||
value: Math.min(canvasW, 1024),
|
||||
value: defaultW,
|
||||
range: [256, 2048],
|
||||
step: 64,
|
||||
type: 'range',
|
||||
@@ -66,7 +100,7 @@ class Generate_text_to_image_class {
|
||||
{
|
||||
name: 'height',
|
||||
title: 'Height (px):',
|
||||
value: Math.min(canvasH, 1024),
|
||||
value: defaultH,
|
||||
range: [256, 2048],
|
||||
step: 64,
|
||||
type: 'range',
|
||||
@@ -99,29 +133,31 @@ class Generate_text_to_image_class {
|
||||
alertify.warning('Please enter a description.');
|
||||
return;
|
||||
}
|
||||
await _this._generate(params);
|
||||
await _this._generate(params, estSec);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async _generate(params) {
|
||||
async _generate(params, estSec) {
|
||||
if (this.isProcessing) return;
|
||||
this.isProcessing = true;
|
||||
alertify.message('Generating image... please wait', 0);
|
||||
|
||||
showProgress('Generating image… this may take a minute on local GPU', estSec || 60);
|
||||
|
||||
try {
|
||||
var result = await apiService.textToImage(params.prompt, {
|
||||
width: params.width || 1024,
|
||||
height: params.height || 1024,
|
||||
width: params.width || 1024,
|
||||
height: params.height || 1024,
|
||||
negativePrompt: params.negative_prompt || '',
|
||||
steps: params.steps || 30,
|
||||
seed: params.seed || 0,
|
||||
steps: params.steps || 30,
|
||||
seed: params.seed || 0,
|
||||
});
|
||||
|
||||
updateProgress(95, 'Placing image…');
|
||||
|
||||
var img = new Image();
|
||||
img.onload = () => {
|
||||
if (params.placement === 'replace_canvas') {
|
||||
// Resize canvas and replace bottom layer
|
||||
config.WIDTH = img.naturalWidth;
|
||||
config.HEIGHT = img.naturalHeight;
|
||||
var resultCanvas = document.createElement('canvas');
|
||||
@@ -134,41 +170,43 @@ class Generate_text_to_image_class {
|
||||
])
|
||||
);
|
||||
} else {
|
||||
// Add as new layer on top
|
||||
var dataURL = img.src;
|
||||
app.State.do_action(
|
||||
new app.Actions.Bundle_action('txt2img_layer', 'Text → Image Layer', [
|
||||
new app.Actions.Insert_layer_action({
|
||||
name: params.prompt.slice(0, 30),
|
||||
type: 'image',
|
||||
data: dataURL,
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: img.naturalWidth,
|
||||
height: img.naturalHeight,
|
||||
data: img.src,
|
||||
x: 0, y: 0,
|
||||
width: img.naturalWidth,
|
||||
height: img.naturalHeight,
|
||||
width_original: img.naturalWidth,
|
||||
height_original: img.naturalHeight,
|
||||
})
|
||||
])
|
||||
);
|
||||
}
|
||||
alertify.dismissAll();
|
||||
hideProgress();
|
||||
alertify.success('Image generated!');
|
||||
this.isProcessing = false;
|
||||
};
|
||||
img.onerror = () => {
|
||||
alertify.dismissAll();
|
||||
hideProgress();
|
||||
alertify.error('Failed to load generated image.');
|
||||
this.isProcessing = false;
|
||||
};
|
||||
img.src = 'data:image/png;base64,' + result.result;
|
||||
|
||||
} catch (err) {
|
||||
alertify.dismissAll();
|
||||
hideProgress();
|
||||
alertify.error('Generation failed: ' + (err.message || err));
|
||||
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;
|
||||
|
||||
@@ -15,6 +15,7 @@ import Base_layers_class from './../../core/base-layers.js';
|
||||
import Dialog_class from './../../libs/popup.js';
|
||||
import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js';
|
||||
import { getCapabilities } from './../../api/capabilities.js';
|
||||
import { showProgress, hideProgress } from './../../libs/progress_overlay.js';
|
||||
|
||||
var instance = null;
|
||||
|
||||
@@ -123,11 +124,11 @@ class Image_frame_fit_class {
|
||||
this.isProcessing = true;
|
||||
|
||||
var mode = params.mode || 'smart';
|
||||
alertify.message(
|
||||
showProgress(
|
||||
mode === 'extend'
|
||||
? 'Fitting to frame with AI extension... please wait'
|
||||
: 'Fitting to frame...',
|
||||
0
|
||||
? 'Fitting to frame with AI extension…'
|
||||
: 'Fitting to frame…',
|
||||
mode === 'extend' ? 45 : 5
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -203,7 +204,7 @@ class Image_frame_fit_class {
|
||||
);
|
||||
}
|
||||
|
||||
alertify.dismissAll();
|
||||
hideProgress();
|
||||
alertify.success(
|
||||
`Done! ${result.output_pixels.width}×${result.output_pixels.height}px` +
|
||||
` (${result.frame} ${result.orientation}, ${result.mode_used})`
|
||||
@@ -211,14 +212,14 @@ class Image_frame_fit_class {
|
||||
this.isProcessing = false;
|
||||
};
|
||||
img.onerror = () => {
|
||||
alertify.dismissAll();
|
||||
hideProgress();
|
||||
alertify.error('Failed to load result.');
|
||||
this.isProcessing = false;
|
||||
};
|
||||
img.src = 'data:image/png;base64,' + result.result;
|
||||
|
||||
} catch (err) {
|
||||
alertify.dismissAll();
|
||||
hideProgress();
|
||||
alertify.error('Frame fit failed: ' + (err.message || err));
|
||||
this.isProcessing = false;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import Base_layers_class from './../../core/base-layers.js';
|
||||
import Dialog_class from './../../libs/popup.js';
|
||||
import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js';
|
||||
import { getCapabilities } from './../../api/capabilities.js';
|
||||
import { showProgress, updateProgress, hideProgress } from './../../libs/progress_overlay.js';
|
||||
|
||||
const FRAME_SIZES = [
|
||||
'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 willUpscale = neededScale > 1.05;
|
||||
|
||||
alertify.message(
|
||||
showProgress(
|
||||
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…',
|
||||
0
|
||||
willUpscale ? 120 : 8
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -188,6 +189,7 @@ class Image_print_prepare_class {
|
||||
}
|
||||
var result = await r.json();
|
||||
|
||||
updateProgress(90, 'Placing result…');
|
||||
var img = new Image();
|
||||
img.onload = () => {
|
||||
var resultCanvas = document.createElement('canvas');
|
||||
@@ -225,7 +227,7 @@ class Image_print_prepare_class {
|
||||
);
|
||||
}
|
||||
|
||||
alertify.dismissAll();
|
||||
hideProgress();
|
||||
var upscaleNote = result.upscale_applied
|
||||
? ` · ${result.upscale_factor}× ${result.upscale_method}`
|
||||
: ' · no upscale needed';
|
||||
@@ -235,14 +237,14 @@ class Image_print_prepare_class {
|
||||
this.isProcessing = false;
|
||||
};
|
||||
img.onerror = () => {
|
||||
alertify.dismissAll();
|
||||
hideProgress();
|
||||
alertify.error('Failed to load result.');
|
||||
this.isProcessing = false;
|
||||
};
|
||||
img.src = 'data:image/png;base64,' + result.result;
|
||||
|
||||
} catch (err) {
|
||||
alertify.dismissAll();
|
||||
hideProgress();
|
||||
alertify.error('Prepare for Print failed: ' + (err.message || err));
|
||||
this.isProcessing = false;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import config from './../../config.js';
|
||||
import Base_layers_class from './../../core/base-layers.js';
|
||||
import Dialog_class from './../../libs/popup.js';
|
||||
import alertify from './../../../../node_modules/alertifyjs/build/alertify.min.js';
|
||||
import { showProgress, updateProgress, hideProgress } from './../../libs/progress_overlay.js';
|
||||
|
||||
var instance = null;
|
||||
|
||||
@@ -221,7 +222,12 @@ class Image_upscale_class {
|
||||
? `Auto (${caps.recommended_label || 'best available'})`
|
||||
: (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 {
|
||||
var layerCanvas = document.createElement('canvas');
|
||||
@@ -276,21 +282,21 @@ class Image_upscale_class {
|
||||
);
|
||||
}
|
||||
|
||||
alertify.dismissAll();
|
||||
hideProgress();
|
||||
alertify.success(
|
||||
`${result.output.width}×${result.output.height}px · ${usedLabel}`
|
||||
);
|
||||
this.isProcessing = false;
|
||||
};
|
||||
img.onerror = () => {
|
||||
alertify.dismissAll();
|
||||
hideProgress();
|
||||
alertify.error('Failed to load upscaled image.');
|
||||
this.isProcessing = false;
|
||||
};
|
||||
img.src = 'data:image/png;base64,' + result.result;
|
||||
|
||||
} catch (err) {
|
||||
alertify.dismissAll();
|
||||
hideProgress();
|
||||
alertify.error('Upscale failed: ' + (err.message || err));
|
||||
this.isProcessing = false;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import app from './../app.js';
|
||||
import config from './../config.js';
|
||||
import Base_layers_class from './../core/base-layers.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 || '';
|
||||
|
||||
@@ -175,7 +176,7 @@ export class SelectionActions {
|
||||
async _scaleSelection(scalePct) {
|
||||
if (!this._check()) return;
|
||||
this.hide();
|
||||
alertify.message('Scaling object and filling gap…');
|
||||
showProgress('Scaling object and AI-filling the gap…', 30);
|
||||
try {
|
||||
var res = await _post('/api/image/scale-selection', {
|
||||
image: this._imageData,
|
||||
@@ -184,8 +185,10 @@ export class SelectionActions {
|
||||
});
|
||||
this.tool.updateLayerWithResult(res.result);
|
||||
this.tool.clearSelection();
|
||||
hideProgress();
|
||||
alertify.success('Scaled by ' + scalePct + '%!');
|
||||
} catch (e) {
|
||||
hideProgress();
|
||||
alertify.error('Scale failed: ' + e.message);
|
||||
}
|
||||
}
|
||||
@@ -193,7 +196,7 @@ export class SelectionActions {
|
||||
async _makeAsymmetric() {
|
||||
if (!this._check()) return;
|
||||
this.hide();
|
||||
alertify.message('AI is adding natural asymmetry…');
|
||||
showProgress('AI is adding natural asymmetry…', 60);
|
||||
try {
|
||||
var res = await _post('/api/image/ai-edit-region', {
|
||||
image: this._imageData,
|
||||
@@ -205,8 +208,10 @@ export class SelectionActions {
|
||||
});
|
||||
this.tool.updateLayerWithResult(res.result);
|
||||
this.tool.clearSelection();
|
||||
hideProgress();
|
||||
alertify.success('Made less symmetrical!');
|
||||
} catch (e) {
|
||||
hideProgress();
|
||||
alertify.error('AI edit failed: ' + e.message);
|
||||
}
|
||||
}
|
||||
@@ -214,7 +219,7 @@ export class SelectionActions {
|
||||
async _aiEditRegion(instruction) {
|
||||
if (!this._check()) return;
|
||||
this.hide();
|
||||
alertify.message('AI is editing the region…');
|
||||
showProgress('AI is editing the region…', 60);
|
||||
try {
|
||||
var res = await _post('/api/image/ai-edit-region', {
|
||||
image: this._imageData,
|
||||
@@ -225,8 +230,10 @@ export class SelectionActions {
|
||||
});
|
||||
this.tool.updateLayerWithResult(res.result);
|
||||
this.tool.clearSelection();
|
||||
hideProgress();
|
||||
alertify.success('Done!');
|
||||
} catch (e) {
|
||||
hideProgress();
|
||||
alertify.error('AI edit failed: ' + e.message);
|
||||
}
|
||||
}
|
||||
@@ -257,7 +264,7 @@ export class SelectionActions {
|
||||
|
||||
var clipBase64 = await _blobToBase64(clipBlob);
|
||||
this.hide();
|
||||
alertify.message('Pasting clipboard into selection…');
|
||||
showProgress('Pasting clipboard into selection…', 10);
|
||||
|
||||
var res = await _post('/api/image/paste-into-selection', {
|
||||
image: this._imageData,
|
||||
@@ -266,8 +273,10 @@ export class SelectionActions {
|
||||
});
|
||||
this.tool.updateLayerWithResult(res.result);
|
||||
this.tool.clearSelection();
|
||||
hideProgress();
|
||||
alertify.success('Clipboard pasted into selection!');
|
||||
} catch (e) {
|
||||
hideProgress();
|
||||
alertify.error('Paste failed: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user