update queue
This commit is contained in:
parent
41d14dcd24
commit
b95950978f
4 changed files with 208 additions and 22 deletions
|
|
@ -231,7 +231,6 @@ export function createTemplateGallery({ container, onSelectTemplate }) {
|
||||||
|
|
||||||
const deleteBtn = document.createElement('button');
|
const deleteBtn = document.createElement('button');
|
||||||
deleteBtn.className = 'template-edit-btn'; // Reuse same style
|
deleteBtn.className = 'template-edit-btn'; // Reuse same style
|
||||||
deleteBtn.style.marginLeft = '4px';
|
|
||||||
deleteBtn.innerHTML = `
|
deleteBtn.innerHTML = `
|
||||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M3 6H5H21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M3 6H5H21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
|
|
||||||
113
static/script.js
113
static/script.js
|
|
@ -195,31 +195,43 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
aspectRatioInput.addEventListener('change', persistSettings);
|
aspectRatioInput.addEventListener('change', persistSettings);
|
||||||
resolutionInput.addEventListener('change', persistSettings);
|
resolutionInput.addEventListener('change', persistSettings);
|
||||||
|
|
||||||
generateBtn.addEventListener('click', async () => {
|
const queueCounter = document.getElementById('queue-counter');
|
||||||
const prompt = promptInput.value.trim();
|
const queueCountText = document.getElementById('queue-count-text');
|
||||||
const aspectRatio = aspectRatioInput.value;
|
|
||||||
const resolution = resolutionInput.value;
|
|
||||||
const apiKey = apiKeyInput.value.trim();
|
|
||||||
|
|
||||||
if (!apiKey) {
|
let generationQueue = [];
|
||||||
openApiSettings();
|
let isProcessingQueue = false;
|
||||||
|
|
||||||
|
function updateQueueCounter() {
|
||||||
|
// Count includes current processing item + items in queue
|
||||||
|
const count = generationQueue.length + (isProcessingQueue ? 1 : 0);
|
||||||
|
if (count > 0) {
|
||||||
|
queueCounter.classList.remove('hidden');
|
||||||
|
queueCountText.textContent = count;
|
||||||
|
} else {
|
||||||
|
queueCounter.classList.add('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processNextInQueue() {
|
||||||
|
if (generationQueue.length === 0) {
|
||||||
|
isProcessingQueue = false;
|
||||||
|
updateQueueCounter();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!prompt) {
|
// Take task from queue FIRST, then update state
|
||||||
showError('Please enter a prompt.');
|
const task = generationQueue.shift();
|
||||||
return;
|
isProcessingQueue = true;
|
||||||
}
|
updateQueueCounter();
|
||||||
|
|
||||||
setViewState('loading');
|
|
||||||
generateBtn.disabled = true;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
setViewState('loading');
|
||||||
|
|
||||||
const formData = buildGenerateFormData({
|
const formData = buildGenerateFormData({
|
||||||
prompt,
|
prompt: task.prompt,
|
||||||
aspect_ratio: aspectRatio,
|
aspect_ratio: task.aspectRatio,
|
||||||
resolution,
|
resolution: task.resolution,
|
||||||
api_key: apiKey,
|
api_key: task.apiKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await fetch('/generate', {
|
const response = await fetch('/generate', {
|
||||||
|
|
@ -241,9 +253,46 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showError(error.message);
|
showError(error.message);
|
||||||
|
// Wait a bit before next task if error
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
} finally {
|
} finally {
|
||||||
generateBtn.disabled = false;
|
// Process next task
|
||||||
|
processNextInQueue();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToQueue() {
|
||||||
|
const prompt = promptInput.value.trim();
|
||||||
|
const aspectRatio = aspectRatioInput.value;
|
||||||
|
const resolution = resolutionInput.value;
|
||||||
|
const apiKey = apiKeyInput.value.trim();
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
openApiSettings();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!prompt) {
|
||||||
|
showError('Please enter a prompt.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
generationQueue.push({
|
||||||
|
prompt,
|
||||||
|
aspectRatio,
|
||||||
|
resolution,
|
||||||
|
apiKey
|
||||||
|
});
|
||||||
|
|
||||||
|
updateQueueCounter();
|
||||||
|
|
||||||
|
if (!isProcessingQueue) {
|
||||||
|
processNextInQueue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateBtn.addEventListener('click', () => {
|
||||||
|
addToQueue();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('keydown', handleGenerateShortcut);
|
document.addEventListener('keydown', handleGenerateShortcut);
|
||||||
|
|
@ -251,6 +300,32 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||||
document.addEventListener('keydown', handleDownloadShortcut);
|
document.addEventListener('keydown', handleDownloadShortcut);
|
||||||
document.addEventListener('keydown', handleTemplateShortcut);
|
document.addEventListener('keydown', handleTemplateShortcut);
|
||||||
|
|
||||||
|
// Fix for download issue: use fetch/blob to force download
|
||||||
|
if (downloadLink) {
|
||||||
|
downloadLink.addEventListener('click', async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const url = downloadLink.href;
|
||||||
|
const filename = downloadLink.getAttribute('download') || 'image.png';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const blob = await response.blob();
|
||||||
|
const blobUrl = window.URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
const tempLink = document.createElement('a');
|
||||||
|
tempLink.href = blobUrl;
|
||||||
|
tempLink.download = filename;
|
||||||
|
document.body.appendChild(tempLink);
|
||||||
|
tempLink.click();
|
||||||
|
document.body.removeChild(tempLink);
|
||||||
|
window.URL.revokeObjectURL(blobUrl);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Download failed:', error);
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (imageDisplayArea) {
|
if (imageDisplayArea) {
|
||||||
imageDisplayArea.addEventListener('wheel', handleCanvasWheel, { passive: false });
|
imageDisplayArea.addEventListener('wheel', handleCanvasWheel, { passive: false });
|
||||||
imageDisplayArea.addEventListener('pointerdown', handleCanvasPointerDown);
|
imageDisplayArea.addEventListener('pointerdown', handleCanvasPointerDown);
|
||||||
|
|
|
||||||
|
|
@ -1182,7 +1182,7 @@ button#generate-btn:disabled {
|
||||||
top: 0.5rem;
|
top: 0.5rem;
|
||||||
right: 0.5rem;
|
right: 0.5rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
gap: 0.35rem;
|
gap: 0.35rem;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
|
@ -1616,6 +1616,97 @@ button#generate-btn:disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#queue-btn {
|
||||||
|
position: relative;
|
||||||
|
padding: 1rem;
|
||||||
|
background: linear-gradient(135deg, #3b82f6, #60a5fa);
|
||||||
|
color: #fff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 10px 15px rgba(59, 130, 246, 0.25);
|
||||||
|
transition: transform 0.1s, filter 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#queue-btn:hover {
|
||||||
|
filter: brightness(1.1);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 15px 25px rgba(59, 130, 246, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
#queue-btn:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
#queue-btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-counter {
|
||||||
|
position: absolute;
|
||||||
|
top: 1.5rem;
|
||||||
|
right: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 20px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
z-index: 100;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-counter.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-spinner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--accent-color);
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
from {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls-footer button {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.category-input-wrapper {
|
.category-input-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,27 @@
|
||||||
<div class="content-area">
|
<div class="content-area">
|
||||||
<main class="main-content">
|
<main class="main-content">
|
||||||
<div class="image-display-area">
|
<div class="image-display-area">
|
||||||
|
<div id="queue-counter" class="queue-counter hidden">
|
||||||
|
<span class="queue-spinner">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M12 2V6M12 18V22M6 12H2M22 12H18M19.07 4.93L16.24 7.76M7.76 16.24L4.93 19.07M19.07 19.07L16.24 16.24M7.76 7.76L4.93 4.93"
|
||||||
|
stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span class="queue-icon">
|
||||||
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
d="M19 11H5M19 11C20.1046 11 21 11.8954 21 13V19C21 20.1046 20.1046 21 19 21H5C3.89543 21 3 20.1046 3 19V13C3 11.8954 3.89543 11 5 11M19 11V9C19 7.89543 18.1046 7 17 7M5 11V9C5 7.89543 5.89543 7 7 7M7 7V5C7 3.89543 7.89543 3 9 3H15C16.1046 3 17 3.89543 17 5V7M7 7H17"
|
||||||
|
stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<span id="queue-count-text">0</span>
|
||||||
|
</div>
|
||||||
<div id="placeholder-state" class="state-view">
|
<div id="placeholder-state" class="state-view">
|
||||||
<div class="icon-placeholder"></div>
|
<div class="icon-placeholder"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue