From 5105c7d529ea44f3ed6d1494eedc341ae17ddaba Mon Sep 17 00:00:00 2001 From: Till Heidrich Date: Thu, 23 Jul 2026 20:01:17 +0000 Subject: [PATCH] fix: before/after works for compose (source fallback), hidden for text-only; add reuse result as new Studio input Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01XNQ8ghPfzAfsyVYd6HgFb6 --- src/components/LibraryApp.tsx | 8 ++++++-- src/components/StudioApp.tsx | 19 +++++++++++++++++++ src/pages/api/items/[id]/file.ts | 7 ++++--- src/pages/api/items/[id]/reuse.ts | 26 ++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 src/pages/api/items/[id]/reuse.ts diff --git a/src/components/LibraryApp.tsx b/src/components/LibraryApp.tsx index 1046715..5db7e84 100644 --- a/src/components/LibraryApp.tsx +++ b/src/components/LibraryApp.tsx @@ -53,6 +53,8 @@ export default function LibraryApp() { if (r.ok) notify('Alternative wird erzeugt — erscheint gleich hier.'); else notify(r.error || 'Fehler.'); setTimeout(load, 2500); }; + // Ergebnis als neue Vorlage ins Studio übernehmen und weiterbearbeiten. + const reuse = (id: string) => { window.location.href = `/?reuse=${id}`; }; const newFolder = async () => { const name = prompt('Name des Ordners?'); if (!name) return; @@ -143,8 +145,9 @@ export default function LibraryApp() { {v.delivery_status === 'failed' && Picdrop ✕}
- + {v.mode !== 'generate' && } Laden + { const o = document.getElementById('ov'); if (o) o.style.clipPath = `inset(0 0 0 ${e.target.value}%)`; }} />
-
{compare.filename} · {compare.output_px}px — Regler: links Original, rechts Ergebnis
+
{compare.filename} · {compare.output_px}px — Regler: links {compare.mode === 'compose' ? 'Vorlage' : 'Original'}, rechts Ergebnis
)} {toast &&
{toast}
} @@ -208,6 +211,7 @@ function LibStyles() { .name{margin-top:8px;width:100%;font-size:12.5px;padding:6px 8px;font-family:var(--font-mono);border:1px solid var(--line);border-radius:3px;background:#fff;} .meta{font-size:11px;color:var(--soft);margin-top:5px;} .mini.stark{background:var(--accent);border-color:var(--accent);color:#fff;font-weight:600;} + .mini.stark2{border-color:var(--accent);color:var(--accent);font-weight:600;} .dbadge{display:inline-block;margin-left:6px;font-family:var(--font-mono);font-size:9px;letter-spacing:.08em;text-transform:uppercase;background:var(--paper);color:var(--soft);padding:1px 6px;border-radius:20px;} .dbadge.ok{background:var(--accent-bg);color:var(--accent);} .dbadge.err{color:var(--err);} diff --git a/src/components/StudioApp.tsx b/src/components/StudioApp.tsx index 2c73103..62d48a8 100644 --- a/src/components/StudioApp.tsx +++ b/src/components/StudioApp.tsx @@ -115,6 +115,25 @@ export default function StudioApp({ recipes }: { recipes: any[] }) { }).catch(() => {}); }, []); + // Ergebnis aus der Bibliothek übernehmen: ?reuse= lädt es als Vorlage. + useEffect(() => { + const params = new URLSearchParams(window.location.search); + const reuseId = params.get('reuse'); + if (!reuseId) return; + (async () => { + const id = crypto.randomUUID(); + setPics((p) => [...p, { id, src: `/api/items/${reuseId}/file?thumb=1`, name: 'weiterbearbeiten.png', uploading: true }]); + try { + const j = await fetch(`/api/items/${reuseId}/reuse`, { method: 'POST' }).then((r) => r.json()); + setPics((p) => p.map((x) => x.id === id + ? { ...x, uploading: false, source_path: j.source_path, name: j.filename || x.name, error: j.error } : x)); + } catch { + setPics((p) => p.map((x) => x.id === id ? { ...x, uploading: false, error: 'Konnte Bild nicht laden' } : x)); + } + history.replaceState(null, '', '/'); + })(); + }, []); + function toggleTask(id: string) { setTasks((t) => { let next = t.includes(id) ? t.filter((x) => x !== id) : [...t, id]; diff --git a/src/pages/api/items/[id]/file.ts b/src/pages/api/items/[id]/file.ts index 2ada5e3..6e0f441 100644 --- a/src/pages/api/items/[id]/file.ts +++ b/src/pages/api/items/[id]/file.ts @@ -11,9 +11,10 @@ export const GET: APIRoute = async ({ params, url, locals }) => { const wantThumb = q.get('thumb') === '1'; const wantSrc = q.get('src') === '1'; const item = await one( - `SELECT source_path, result_path, thumb_path, filename, has_alpha FROM items WHERE id=$1`, [params.id]); - // Vorschau: Thumbnail bevorzugen, sonst Ergebnis. Quelle nur explizit. - const path = wantSrc ? item?.source_path + `SELECT source_path, source_paths, result_path, thumb_path, filename, has_alpha FROM items WHERE id=$1`, [params.id]); + // Quelle: Einzelquelle, sonst erste Kombinier-Quelle (für Vorher/Nachher). + const firstSource = item?.source_path || (Array.isArray(item?.source_paths) ? item.source_paths[0] : null); + const path = wantSrc ? firstSource : wantThumb ? (item?.thumb_path || item?.result_path) : item?.result_path; if (!path) return new Response('Nicht gefunden', { status: 404 }); diff --git a/src/pages/api/items/[id]/reuse.ts b/src/pages/api/items/[id]/reuse.ts new file mode 100644 index 0000000..dd64432 --- /dev/null +++ b/src/pages/api/items/[id]/reuse.ts @@ -0,0 +1,26 @@ +import type { APIRoute } from 'astro'; +import { randomUUID } from 'node:crypto'; +import { one } from '../../../../lib/db'; +import { getObject, putObject, sourceKey } from '../../../../lib/storage'; + +export const prerender = false; +const json = (b: unknown, s = 200) => + new Response(JSON.stringify(b), { status: s, headers: { 'Content-Type': 'application/json' } }); + +/** Kopiert das Ergebnis eines Items in eine neue Quelle, damit es im Studio + * als Vorlage weiterbearbeitet werden kann. */ +export const POST: APIRoute = async ({ params, locals }) => { + if (!locals.user) return new Response('Unauthorized', { status: 401 }); + const it = await one<{ result_path: string | null; filename: string | null }>( + 'SELECT result_path, filename FROM items WHERE id=$1', [params.id]); + if (!it?.result_path) return json({ error: 'Kein Ergebnis vorhanden.' }, 404); + try { + const buf = await getObject(it.result_path); + const key = sourceKey(randomUUID(), 'png'); + await putObject(key, buf, 'image/png'); + const name = (it.filename || 'bild').replace(/\.[^.]+$/, '') + '.png'; + return json({ source_path: key, filename: name }); + } catch (e: any) { + return json({ error: e?.message || 'Konnte Ergebnis nicht laden.' }, 500); + } +};