Spaces:
Running
Running
Update index.html
Browse files- index.html +106 -11
index.html
CHANGED
@@ -279,6 +279,19 @@
|
|
279 |
border-radius: 20px;
|
280 |
font-size: 0.8rem;
|
281 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
282 |
</style>
|
283 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
284 |
</head>
|
@@ -310,7 +323,7 @@
|
|
310 |
|
311 |
<div class="form-group">
|
312 |
<label for="negativePrompt">Negative Prompt</label>
|
313 |
-
<input type="text" id="negativePrompt" value="blurry, distorted" placeholder="Things to avoid in the result">
|
314 |
</div>
|
315 |
|
316 |
<div class="form-group">
|
@@ -323,9 +336,18 @@
|
|
323 |
<input type="range" id="steps" min="5" max="50" value="20">
|
324 |
</div>
|
325 |
|
|
|
|
|
|
|
|
|
|
|
326 |
<button class="btn" id="runEdit">
|
327 |
<i class="fas fa-magic"></i> Apply Edits
|
328 |
</button>
|
|
|
|
|
|
|
|
|
329 |
</div>
|
330 |
|
331 |
<div class="panel">
|
@@ -377,6 +399,10 @@
|
|
377 |
<i class="fas fa-check-circle"></i>
|
378 |
<p>More steps (20-30) generally improve quality but take longer to process</p>
|
379 |
</div>
|
|
|
|
|
|
|
|
|
380 |
</div>
|
381 |
|
382 |
<div class="footer">
|
@@ -408,10 +434,15 @@
|
|
408 |
const scaleValue = document.getElementById('scaleValue');
|
409 |
const stepsInput = document.getElementById('steps');
|
410 |
const stepsValue = document.getElementById('stepsValue');
|
|
|
|
|
|
|
|
|
411 |
|
412 |
// Set initial slider values
|
413 |
scaleValue.textContent = guidanceScaleInput.value;
|
414 |
stepsValue.textContent = stepsInput.value;
|
|
|
415 |
|
416 |
// Event listeners for sliders
|
417 |
guidanceScaleInput.addEventListener('input', () => {
|
@@ -422,6 +453,10 @@
|
|
422 |
stepsValue.textContent = stepsInput.value;
|
423 |
});
|
424 |
|
|
|
|
|
|
|
|
|
425 |
// File selection
|
426 |
browseBtn.addEventListener('click', () => {
|
427 |
imageUpload.click();
|
@@ -472,11 +507,65 @@
|
|
472 |
// Clear previous result
|
473 |
resultImage.style.display = 'none';
|
474 |
resultPlaceholder.style.display = 'block';
|
|
|
|
|
|
|
475 |
};
|
476 |
|
477 |
reader.readAsDataURL(file);
|
478 |
}
|
479 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
480 |
// Run the edit process
|
481 |
runEditBtn.addEventListener('click', async () => {
|
482 |
// Validate inputs
|
@@ -493,33 +582,40 @@
|
|
493 |
// Show loading indicator
|
494 |
runEditBtn.disabled = true;
|
495 |
loadingIndicator.style.display = 'block';
|
|
|
496 |
|
497 |
try {
|
|
|
|
|
|
|
|
|
498 |
// Create FormData
|
499 |
const formData = new FormData();
|
500 |
-
|
501 |
-
// Convert data URL to blob
|
502 |
-
const blob = await fetch(originalImage.src).then(r => r.blob());
|
503 |
-
formData.append('image', blob, 'image.png');
|
504 |
-
|
505 |
-
// Add other parameters
|
506 |
formData.append('prompt', promptInput.value);
|
507 |
formData.append('negative_prompt', negativePromptInput.value);
|
508 |
formData.append('guidance_scale', guidanceScaleInput.value);
|
509 |
formData.append('steps', stepsInput.value);
|
510 |
|
511 |
// Make request to Hugging Face API
|
512 |
-
const response = await fetch('https://multimodalart-cosxl.hf.space/run/
|
513 |
method: 'POST',
|
514 |
body: formData
|
515 |
});
|
516 |
|
517 |
if (!response.ok) {
|
518 |
-
|
|
|
519 |
}
|
520 |
|
521 |
// Get the result image
|
522 |
const resultBlob = await response.blob();
|
|
|
|
|
|
|
|
|
|
|
|
|
523 |
const resultUrl = URL.createObjectURL(resultBlob);
|
524 |
|
525 |
// Display result
|
@@ -528,8 +624,7 @@
|
|
528 |
resultPlaceholder.style.display = 'none';
|
529 |
|
530 |
} catch (error) {
|
531 |
-
|
532 |
-
alert('Error processing image. Please try again.');
|
533 |
} finally {
|
534 |
// Hide loading indicator
|
535 |
runEditBtn.disabled = false;
|
|
|
279 |
border-radius: 20px;
|
280 |
font-size: 0.8rem;
|
281 |
}
|
282 |
+
|
283 |
+
.error-box {
|
284 |
+
display: none;
|
285 |
+
background: rgba(200, 0, 0, 0.2);
|
286 |
+
border: 1px solid rgba(255, 0, 0, 0.3);
|
287 |
+
border-radius: 8px;
|
288 |
+
padding: 15px;
|
289 |
+
margin-top: 20px;
|
290 |
+
font-family: monospace;
|
291 |
+
font-size: 0.9rem;
|
292 |
+
max-height: 200px;
|
293 |
+
overflow-y: auto;
|
294 |
+
}
|
295 |
</style>
|
296 |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
297 |
</head>
|
|
|
323 |
|
324 |
<div class="form-group">
|
325 |
<label for="negativePrompt">Negative Prompt</label>
|
326 |
+
<input type="text" id="negativePrompt" value="blurry, distorted, low quality, artifacts" placeholder="Things to avoid in the result">
|
327 |
</div>
|
328 |
|
329 |
<div class="form-group">
|
|
|
336 |
<input type="range" id="steps" min="5" max="50" value="20">
|
337 |
</div>
|
338 |
|
339 |
+
<div class="form-group">
|
340 |
+
<label for="quality">Image Quality: <span id="qualityValue">85%</span></label>
|
341 |
+
<input type="range" id="quality" min="50" max="95" step="5" value="85">
|
342 |
+
</div>
|
343 |
+
|
344 |
<button class="btn" id="runEdit">
|
345 |
<i class="fas fa-magic"></i> Apply Edits
|
346 |
</button>
|
347 |
+
|
348 |
+
<div class="error-box" id="errorBox">
|
349 |
+
<pre id="errorDetails"></pre>
|
350 |
+
</div>
|
351 |
</div>
|
352 |
|
353 |
<div class="panel">
|
|
|
399 |
<i class="fas fa-check-circle"></i>
|
400 |
<p>More steps (20-30) generally improve quality but take longer to process</p>
|
401 |
</div>
|
402 |
+
<div class="tip">
|
403 |
+
<i class="fas fa-check-circle"></i>
|
404 |
+
<p>Reduce image quality to 80% for faster processing with minimal quality loss</p>
|
405 |
+
</div>
|
406 |
</div>
|
407 |
|
408 |
<div class="footer">
|
|
|
434 |
const scaleValue = document.getElementById('scaleValue');
|
435 |
const stepsInput = document.getElementById('steps');
|
436 |
const stepsValue = document.getElementById('stepsValue');
|
437 |
+
const qualityInput = document.getElementById('quality');
|
438 |
+
const qualityValue = document.getElementById('qualityValue');
|
439 |
+
const errorBox = document.getElementById('errorBox');
|
440 |
+
const errorDetails = document.getElementById('errorDetails');
|
441 |
|
442 |
// Set initial slider values
|
443 |
scaleValue.textContent = guidanceScaleInput.value;
|
444 |
stepsValue.textContent = stepsInput.value;
|
445 |
+
qualityValue.textContent = qualityInput.value + '%';
|
446 |
|
447 |
// Event listeners for sliders
|
448 |
guidanceScaleInput.addEventListener('input', () => {
|
|
|
453 |
stepsValue.textContent = stepsInput.value;
|
454 |
});
|
455 |
|
456 |
+
qualityInput.addEventListener('input', () => {
|
457 |
+
qualityValue.textContent = qualityInput.value + '%';
|
458 |
+
});
|
459 |
+
|
460 |
// File selection
|
461 |
browseBtn.addEventListener('click', () => {
|
462 |
imageUpload.click();
|
|
|
507 |
// Clear previous result
|
508 |
resultImage.style.display = 'none';
|
509 |
resultPlaceholder.style.display = 'block';
|
510 |
+
|
511 |
+
// Hide any previous errors
|
512 |
+
errorBox.style.display = 'none';
|
513 |
};
|
514 |
|
515 |
reader.readAsDataURL(file);
|
516 |
}
|
517 |
|
518 |
+
// Function to compress image
|
519 |
+
async function compressImage(imageSrc, quality) {
|
520 |
+
return new Promise((resolve, reject) => {
|
521 |
+
const img = new Image();
|
522 |
+
img.crossOrigin = 'Anonymous';
|
523 |
+
img.src = imageSrc;
|
524 |
+
|
525 |
+
img.onload = () => {
|
526 |
+
const canvas = document.createElement('canvas');
|
527 |
+
const ctx = canvas.getContext('2d');
|
528 |
+
|
529 |
+
// Calculate new dimensions to max 1024px
|
530 |
+
let width = img.width;
|
531 |
+
let height = img.height;
|
532 |
+
const maxDimension = 1024;
|
533 |
+
|
534 |
+
if (width > maxDimension || height > maxDimension) {
|
535 |
+
const ratio = Math.min(maxDimension / width, maxDimension / height);
|
536 |
+
width = Math.floor(width * ratio);
|
537 |
+
height = Math.floor(height * ratio);
|
538 |
+
}
|
539 |
+
|
540 |
+
canvas.width = width;
|
541 |
+
canvas.height = height;
|
542 |
+
|
543 |
+
// Draw image on canvas
|
544 |
+
ctx.drawImage(img, 0, 0, width, height);
|
545 |
+
|
546 |
+
// Convert to blob with specified quality
|
547 |
+
canvas.toBlob(blob => {
|
548 |
+
if (!blob) {
|
549 |
+
reject(new Error('Image compression failed'));
|
550 |
+
return;
|
551 |
+
}
|
552 |
+
resolve(blob);
|
553 |
+
}, 'image/jpeg', quality / 100);
|
554 |
+
};
|
555 |
+
|
556 |
+
img.onerror = () => {
|
557 |
+
reject(new Error('Failed to load image for compression'));
|
558 |
+
};
|
559 |
+
});
|
560 |
+
}
|
561 |
+
|
562 |
+
// Show error details
|
563 |
+
function showError(message, details) {
|
564 |
+
errorDetails.textContent = details || message;
|
565 |
+
errorBox.style.display = 'block';
|
566 |
+
console.error(message, details);
|
567 |
+
}
|
568 |
+
|
569 |
// Run the edit process
|
570 |
runEditBtn.addEventListener('click', async () => {
|
571 |
// Validate inputs
|
|
|
582 |
// Show loading indicator
|
583 |
runEditBtn.disabled = true;
|
584 |
loadingIndicator.style.display = 'block';
|
585 |
+
errorBox.style.display = 'none';
|
586 |
|
587 |
try {
|
588 |
+
// Compress image
|
589 |
+
const quality = parseInt(qualityInput.value) / 100;
|
590 |
+
const compressedBlob = await compressImage(originalImage.src, quality);
|
591 |
+
|
592 |
// Create FormData
|
593 |
const formData = new FormData();
|
594 |
+
formData.append('image', compressedBlob, 'image.jpg');
|
|
|
|
|
|
|
|
|
|
|
595 |
formData.append('prompt', promptInput.value);
|
596 |
formData.append('negative_prompt', negativePromptInput.value);
|
597 |
formData.append('guidance_scale', guidanceScaleInput.value);
|
598 |
formData.append('steps', stepsInput.value);
|
599 |
|
600 |
// Make request to Hugging Face API
|
601 |
+
const response = await fetch('https://multimodalart-cosxl.hf.space/run/predict', {
|
602 |
method: 'POST',
|
603 |
body: formData
|
604 |
});
|
605 |
|
606 |
if (!response.ok) {
|
607 |
+
const errorText = await response.text();
|
608 |
+
throw new Error(`API request failed: ${response.status} ${response.statusText}\n${errorText}`);
|
609 |
}
|
610 |
|
611 |
// Get the result image
|
612 |
const resultBlob = await response.blob();
|
613 |
+
|
614 |
+
if (!resultBlob.type.startsWith('image/')) {
|
615 |
+
const errorData = await resultBlob.text();
|
616 |
+
throw new Error(`API returned non-image response: ${errorData}`);
|
617 |
+
}
|
618 |
+
|
619 |
const resultUrl = URL.createObjectURL(resultBlob);
|
620 |
|
621 |
// Display result
|
|
|
624 |
resultPlaceholder.style.display = 'none';
|
625 |
|
626 |
} catch (error) {
|
627 |
+
showError('Error processing image:', error.message);
|
|
|
628 |
} finally {
|
629 |
// Hide loading indicator
|
630 |
runEditBtn.disabled = false;
|