feat: harden pipeline UX — preprocess, rembg alpha, remix/lightbox

Downscale to 2000px JPEG before rembg, random blend opacity, timeout
retries, per-variant remix prefill, lightbox, and longer SFTP idle.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Frank Schwenk
2026-07-16 23:35:06 +02:00
parent 8ef123393a
commit 83d4468b69
16 changed files with 594 additions and 50 deletions
+45
View File
@@ -13,5 +13,50 @@
<main>
{% block content %}{% endblock %}
</main>
<div id="lightbox" class="lightbox" hidden>
<button type="button" class="lightbox-close" aria-label="Schliessen">&times;</button>
<img id="lightbox-img" alt="">
</div>
<script>
(function () {
var box = document.getElementById("lightbox");
var img = document.getElementById("lightbox-img");
if (!box || !img) return;
function openLightbox(src, alt) {
img.src = src;
img.alt = alt || "";
box.hidden = false;
document.body.classList.add("lightbox-open");
}
function closeLightbox() {
box.hidden = true;
img.removeAttribute("src");
document.body.classList.remove("lightbox-open");
}
document.addEventListener("click", function (e) {
var target = e.target;
if (!(target instanceof HTMLImageElement)) return;
if (!target.classList.contains("lightboxable")) return;
e.preventDefault();
e.stopPropagation();
openLightbox(target.currentSrc || target.src, target.alt);
});
box.addEventListener("click", function (e) {
if (e.target === box || (e.target && e.target.classList.contains("lightbox-close"))) {
closeLightbox();
}
});
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && !box.hidden) closeLightbox();
});
})();
</script>
</body>
</html>
+1 -1
View File
@@ -9,7 +9,7 @@
<li class="job-card">
<a href="/jobs/{{ job.job_id }}">
{% if job.thumbnail %}
<img class="thumb" src="/jobs/{{ job.job_id }}/files/{{ job.thumbnail }}" alt="Variante von Job {{ job.job_id }}" loading="lazy">
<img class="thumb lightboxable" src="/jobs/{{ job.job_id }}/files/{{ job.thumbnail }}" alt="Variante von Job {{ job.job_id }}" loading="lazy">
{% else %}
<span class="thumb thumb-placeholder status-{{ job.status }}">{{ job.status }}</span>
{% endif %}
+10 -5
View File
@@ -14,7 +14,7 @@
<div class="pair-grid">
{% if original_name %}
<figure>
<img src="/jobs/{{ job_id }}/files/{{ original_name }}" alt="Original" loading="lazy">
<img class="lightboxable" src="/jobs/{{ job_id }}/files/{{ original_name }}" alt="Original" loading="lazy">
<figcaption>
Original &middot;
<a href="/jobs/{{ job_id }}/files/{{ original_name }}" download>Download</a>
@@ -23,7 +23,7 @@
{% endif %}
{% if has_rembg %}
<figure>
<img src="/jobs/{{ job_id }}/files/rembg.png" alt="Freigestellt (rembg)" loading="lazy">
<img class="lightboxable" src="/jobs/{{ job_id }}/files/rembg.png" alt="Freigestellt (rembg)" loading="lazy">
<figcaption>
Rembg &middot;
<a href="/jobs/{{ job_id }}/files/rembg.png" download>Download</a>
@@ -46,7 +46,7 @@
<ul class="variant-grid">
{% for v in manifest.variants %}
<li class="variant-card">
<img src="/jobs/{{ job_id }}/files/{{ v.file }}" alt="Variante {{ v.id }}" loading="lazy">
<img class="lightboxable" src="/jobs/{{ job_id }}/files/{{ v.file }}" alt="Variante {{ v.id }}" loading="lazy">
<div class="variant-meta">
<strong>{{ v.id }}</strong> <span class="tag">{{ v.source }}</span>
<dl>
@@ -54,7 +54,12 @@
<dt>FG</dt><dd>{{ v.foreground_filter }} + {{ v.foreground_blend }}</dd>
<dt>Opacity</dt><dd>{{ v.blend_opacity }}</dd>
</dl>
<a href="/jobs/{{ job_id }}/files/{{ v.file }}" download>Download</a>
<div class="variant-actions">
{% if has_rembg %}
<a class="button" href="/jobs/{{ job_id }}/remix?from={{ v.id }}">Remix</a>
{% endif %}
<a href="/jobs/{{ job_id }}/files/{{ v.file }}" download>Download</a>
</div>
</div>
</li>
{% endfor %}
@@ -68,7 +73,7 @@
<ul class="intermediate-grid">
{% for name in intermediates %}
<li>
<img src="/jobs/{{ job_id }}/files/intermediates/{{ name }}" alt="{{ name }}" loading="lazy">
<img class="lightboxable" src="/jobs/{{ job_id }}/files/intermediates/{{ name }}" alt="{{ name }}" loading="lazy">
<span>{{ name }}</span>
</li>
{% endfor %}
+8 -5
View File
@@ -4,6 +4,9 @@
<p class="back-link"><a href="/jobs/{{ job_id }}">&larr; Zurueck zum Job</a></p>
<h1>Remix &middot; Job {{ job_id }}</h1>
{% if from_variant %}
<p class="job-status">Vorausgefüllt von {{ from_variant }}</p>
{% endif %}
{% if error %}
<p class="error-box">{{ error }}</p>
@@ -13,35 +16,35 @@
<label for="bg_filter">Background-Filter</label>
<select name="bg_filter" id="bg_filter" required>
{% for name in options.background_filters %}
<option value="{{ name }}">{{ name }}</option>
<option value="{{ name }}" {% if name == prefill.bg_filter %}selected{% endif %}>{{ name }}</option>
{% endfor %}
</select>
<label for="bg_blend">Background-Blend</label>
<select name="bg_blend" id="bg_blend" required>
{% for mode in options.blend_modes %}
<option value="{{ mode }}">{{ mode }}</option>
<option value="{{ mode }}" {% if mode == prefill.bg_blend %}selected{% endif %}>{{ mode }}</option>
{% endfor %}
</select>
<label for="fg_filter">Foreground-Filter</label>
<select name="fg_filter" id="fg_filter" required>
{% for name in options.foreground_filters %}
<option value="{{ name }}">{{ name }}</option>
<option value="{{ name }}" {% if name == prefill.fg_filter %}selected{% endif %}>{{ name }}</option>
{% endfor %}
</select>
<label for="fg_blend">Foreground-Blend</label>
<select name="fg_blend" id="fg_blend" required>
{% for mode in options.blend_modes %}
<option value="{{ mode }}">{{ mode }}</option>
<option value="{{ mode }}" {% if mode == prefill.fg_blend %}selected{% endif %}>{{ mode }}</option>
{% endfor %}
</select>
<label for="opacity">Blend-Opacity</label>
<select name="opacity" id="opacity" required>
{% for value in opacity_choices %}
<option value="{{ value }}" {% if value == "30%" %}selected{% endif %}>{{ value }}</option>
<option value="{{ value }}" {% if value == prefill.opacity %}selected{% endif %}>{{ value }}</option>
{% endfor %}
</select>