@rslsp1/fa-app-tools 5.1.0 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,15 +4,307 @@ import {
4
4
  preferredVersionId,
5
5
  resolvePreferredHead
6
6
  } from "./chunk-FUY2OCVC.mjs";
7
- import {
8
- SPEICHERBARE_BILDTYPEN,
9
- bildEndungFuer,
10
- hfLoadImageAsBase64,
11
- hfUploadImage,
12
- loadHFState,
13
- loadPendingEvents,
14
- writeHFEvent
15
- } from "./chunk-NBUS7AWR.mjs";
7
+
8
+ // src/lib/hfEventTypes.ts
9
+ var HF_BASE = "https://huggingface.co";
10
+ var HF_REPO = "RolandSch/fa-app-state";
11
+ var CURRENT_EVENT_VERSION = { major: 1, minor: 0 };
12
+ var SPEICHERBARE_BILDTYPEN = ["image/png", "image/jpeg", "image/webp"];
13
+ function bildEndungFuer(mimeType) {
14
+ if (mimeType === "image/png") return "png";
15
+ if (mimeType === "image/webp") return "webp";
16
+ return "jpg";
17
+ }
18
+
19
+ // src/lib/hfEventLog.ts
20
+ function treeUrl(namespace, subdir = "") {
21
+ const parts = [namespace.replace(/\/$/, ""), subdir].filter(Boolean).join("/");
22
+ return `${HF_BASE}/api/datasets/${HF_REPO}/tree/main${parts ? "/" + parts : ""}`;
23
+ }
24
+ var DROSSEL_VERSUCHE = 4;
25
+ var DROSSEL_BASIS_MS = 400;
26
+ var schlafen = (ms) => new Promise((r) => setTimeout(r, ms));
27
+ var HF_LESE_ZEITLIMIT_MS = 2e4;
28
+ async function holeMitZeitlimit(url, init) {
29
+ const abbruch = new AbortController();
30
+ const timer = setTimeout(() => abbruch.abort(), HF_LESE_ZEITLIMIT_MS);
31
+ try {
32
+ return await fetch(url, { ...init, signal: abbruch.signal });
33
+ } catch (error) {
34
+ if (abbruch.signal.aborted) {
35
+ throw new Error(`HF-Anfrage ohne Antwort nach ${HF_LESE_ZEITLIMIT_MS}ms abgebrochen: ${url}`);
36
+ }
37
+ throw error;
38
+ } finally {
39
+ clearTimeout(timer);
40
+ }
41
+ }
42
+ async function mitGeduld(anfrage) {
43
+ let letzte = null;
44
+ for (let versuch = 0; versuch < DROSSEL_VERSUCHE; versuch += 1) {
45
+ const res = await anfrage();
46
+ if (res.status !== 429 && res.status < 500) return res;
47
+ letzte = res;
48
+ if (versuch === DROSSEL_VERSUCHE - 1) break;
49
+ const angesagt = Number(res.headers.get("retry-after"));
50
+ const warten2 = Number.isFinite(angesagt) && angesagt > 0 ? angesagt * 1e3 : DROSSEL_BASIS_MS * 2 ** versuch;
51
+ await schlafen(warten2);
52
+ }
53
+ return letzte;
54
+ }
55
+ async function hfListDir(namespace, subdir, token) {
56
+ const res = await mitGeduld(() => holeMitZeitlimit(treeUrl(namespace, subdir), {
57
+ headers: { Authorization: `Bearer ${token}` }
58
+ }));
59
+ if (res.status === 404) return [];
60
+ if (!res.ok) throw new Error(`HF list failed: ${res.status} ${res.statusText}`);
61
+ return res.json();
62
+ }
63
+ async function hfDownloadJsonByPath(repoPath, token) {
64
+ const res = await mitGeduld(() => holeMitZeitlimit(
65
+ `${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
66
+ { headers: { Authorization: `Bearer ${token}` } }
67
+ ));
68
+ if (!res.ok) throw new Error(`HF download failed: ${res.status}`);
69
+ return res.json();
70
+ }
71
+ async function hfDownloadBinaryByPath(repoPath, token) {
72
+ const res = await mitGeduld(() => holeMitZeitlimit(
73
+ `${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
74
+ { headers: { Authorization: `Bearer ${token}` } }
75
+ ));
76
+ if (!res.ok) throw new Error(`HF download binary failed: ${res.status}`);
77
+ return new Uint8Array(await res.arrayBuffer());
78
+ }
79
+ function toBase64(text) {
80
+ const bytes = new TextEncoder().encode(text);
81
+ let binary = "";
82
+ bytes.forEach((b) => binary += String.fromCharCode(b));
83
+ return btoa(binary);
84
+ }
85
+ async function hfUploadSmallFile(repoPath, content, token, summary = `Update ${repoPath}`) {
86
+ const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
87
+ method: "POST",
88
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
89
+ body: [
90
+ JSON.stringify({ key: "header", value: { summary, description: "" } }),
91
+ JSON.stringify({ key: "file", value: { path: repoPath, encoding: "base64", content: toBase64(content) } })
92
+ ].join("\n")
93
+ });
94
+ if (!res.ok) {
95
+ const err = await res.text().catch(() => "");
96
+ throw new Error(`HF upload failed: ${res.status} \u2014 ${err.slice(0, 200)}`);
97
+ }
98
+ }
99
+ async function hfBatchArchive(moves, token, summary) {
100
+ const lines = [
101
+ JSON.stringify({ key: "header", value: { summary, description: "" } }),
102
+ ...moves.flatMap(({ from, to, content }) => [
103
+ JSON.stringify({ key: "file", value: { path: to, encoding: "base64", content: toBase64(content) } }),
104
+ JSON.stringify({ key: "deletedFile", value: { path: from } })
105
+ ])
106
+ ];
107
+ const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
108
+ method: "POST",
109
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
110
+ body: lines.join("\n")
111
+ });
112
+ if (!res.ok) {
113
+ const err = await res.text().catch(() => "");
114
+ throw new Error(`HF batch archive failed: ${res.status} \u2014 ${err.slice(0, 200)}`);
115
+ }
116
+ }
117
+ function tsFromEventPath(repoPath) {
118
+ const filename = repoPath.split("/").pop() || "";
119
+ const base = filename.replace(/_[^_]+\.json$/, "");
120
+ const iso = base.replace(/T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z$/, "T$1:$2:$3.$4Z");
121
+ const ts = Date.parse(iso);
122
+ return isNaN(ts) ? 0 : ts;
123
+ }
124
+ var EVENTDOWNLOAD_PARALLELITAET = 3;
125
+ async function mitBegrenzterParallelitaet(count, limit, task) {
126
+ let cursor = 0;
127
+ let ersterFehler;
128
+ let fehlerAufgetreten = false;
129
+ const worker = async () => {
130
+ while (cursor < count) {
131
+ const index = cursor++;
132
+ try {
133
+ await task(index);
134
+ } catch (error) {
135
+ if (!fehlerAufgetreten) {
136
+ fehlerAufgetreten = true;
137
+ ersterFehler = error;
138
+ }
139
+ }
140
+ }
141
+ };
142
+ await Promise.all(Array.from({ length: Math.min(limit, count) }, worker));
143
+ if (fehlerAufgetreten) throw ersterFehler;
144
+ }
145
+ async function loadPendingEvents(namespace, token, sinceTs, knownPaths) {
146
+ const files = await hfListDir(namespace, "events", token);
147
+ const pending = files.filter((f) => f.type === "file" && tsFromEventPath(f.path) > sinceTs && !knownPaths?.has(f.path)).sort((a, b) => a.path.localeCompare(b.path));
148
+ if (!pending.length) return [];
149
+ const raw = new Array(pending.length);
150
+ await mitBegrenzterParallelitaet(pending.length, EVENTDOWNLOAD_PARALLELITAET, async (index) => {
151
+ raw[index] = await hfDownloadJsonByPath(pending[index].path, token);
152
+ });
153
+ for (const f of pending) knownPaths?.add(f.path);
154
+ return raw.flatMap((e) => Array.isArray(e) ? e : [e]);
155
+ }
156
+ var SESSION_CLIENT_ID = `client-${crypto.randomUUID().slice(0, 8)}`;
157
+ function getSessionClientId() {
158
+ return SESSION_CLIENT_ID;
159
+ }
160
+ function tsToFilename(ts) {
161
+ return new Date(ts).toISOString().replace(/:/g, "-").replace(".", "-");
162
+ }
163
+ var writeSeq = 0;
164
+ var lastOrderedTs = 0;
165
+ async function writeHFEvent(namespace, token, type, payload, prevTs, options) {
166
+ const wallTs = Date.now();
167
+ const ts = options?.ordered ? Math.max(wallTs, lastOrderedTs) : wallTs;
168
+ if (options?.ordered) lastOrderedTs = ts;
169
+ const uuid = crypto.randomUUID().slice(0, 8);
170
+ const event = {
171
+ v: CURRENT_EVENT_VERSION,
172
+ type,
173
+ ts,
174
+ prevTs,
175
+ clientId: SESSION_CLIENT_ID,
176
+ payload
177
+ };
178
+ const suffix = options?.ordered ? `${String(++writeSeq % 1e6).padStart(6, "0")}${uuid}` : uuid;
179
+ const filename = `${tsToFilename(ts)}_${suffix}.json`;
180
+ const repoPath = `${namespace}events/${filename}`;
181
+ await hfUploadSmallFile(repoPath, JSON.stringify(event, null, 2), token, `Event: ${type}`);
182
+ return event;
183
+ }
184
+
185
+ // src/lib/hfStateService.ts
186
+ import JSZip from "jszip";
187
+ import { uploadFile } from "@huggingface/hub";
188
+ var HF_TOKEN_KEY = "hf-token";
189
+ function getHFToken() {
190
+ try {
191
+ return localStorage.getItem(HF_TOKEN_KEY);
192
+ } catch {
193
+ return null;
194
+ }
195
+ }
196
+ function setHFToken(token) {
197
+ try {
198
+ localStorage.setItem(HF_TOKEN_KEY, token);
199
+ } catch {
200
+ }
201
+ }
202
+ async function loadHFState(namespace, token) {
203
+ const files = await hfListDir(namespace, "", token);
204
+ const stateFiles = files.filter((f) => f.type === "file" && /^state-[\dT\-]+Z\.zip$/.test(f.path.split("/").pop() || "")).sort((a, b) => b.path.localeCompare(a.path));
205
+ if (!stateFiles.length) return null;
206
+ const zipBytes = await hfDownloadBinaryByPath(stateFiles[0].path, token);
207
+ const zip = await JSZip.loadAsync(zipBytes);
208
+ const [metadataStr, tagsStr, metaStr, openTasksStr] = await Promise.all([
209
+ zip.file("metadata.json")?.async("string"),
210
+ zip.file("tags.json")?.async("string"),
211
+ zip.file("state_meta.json")?.async("string"),
212
+ zip.file("open_tasks.json")?.async("string")
213
+ // optional — ältere Snapshots haben es nicht
214
+ ]);
215
+ if (!metadataStr || !tagsStr || !metaStr) throw new Error("state.zip: fehlende Pflicht-Dateien");
216
+ return {
217
+ metadata: JSON.parse(metadataStr),
218
+ tags: JSON.parse(tagsStr),
219
+ meta: JSON.parse(metaStr),
220
+ openTasks: openTasksStr ? JSON.parse(openTasksStr) : {}
221
+ };
222
+ }
223
+ async function hfBootstrapFromLegacy(namespace, token, onProgress) {
224
+ const log = (msg) => {
225
+ if (onProgress) onProgress(msg);
226
+ };
227
+ log("Lese tags.json \u2026");
228
+ const tags = await fetch(
229
+ `${HF_BASE}/datasets/${HF_REPO}/resolve/main/tags.json?download=true`,
230
+ { headers: { Authorization: `Bearer ${token}` } }
231
+ ).then((r) => r.ok ? r.json() : null).catch(() => null);
232
+ log("Lese metadata.json \u2026");
233
+ const metadata = await fetch(
234
+ `${HF_BASE}/datasets/${HF_REPO}/resolve/main/metadata.json?download=true`,
235
+ { headers: { Authorization: `Bearer ${token}` } }
236
+ ).then((r) => r.ok ? r.json() : []).catch(() => []);
237
+ const snapshot = {
238
+ metadata: Array.isArray(metadata) ? metadata : [],
239
+ tags: tags?.by_category ? tags : { by_category: {}, all: [] },
240
+ meta: { consolidatedAt: Date.now(), version: 1 }
241
+ };
242
+ log(`Erstelle state.zip (${snapshot.metadata.length} Bilder, ${snapshot.tags.all?.length ?? 0} Tags) \u2026`);
243
+ const zip = new JSZip();
244
+ zip.file("metadata.json", JSON.stringify(snapshot.metadata, null, 2));
245
+ zip.file("tags.json", JSON.stringify(snapshot.tags, null, 2));
246
+ zip.file("state_meta.json", JSON.stringify(snapshot.meta, null, 2));
247
+ const zipBytes = await zip.generateAsync({ type: "uint8array", compression: "DEFLATE" });
248
+ const ts = snapshot.meta.consolidatedAt;
249
+ const stateFilename = `state-${new Date(ts).toISOString().replace(/:/g, "-").replace(".", "-")}.zip`;
250
+ const repoPath = `${namespace}${stateFilename}`;
251
+ log("Lade state.zip hoch \u2026");
252
+ await uploadFile({
253
+ repo: { type: "dataset", name: HF_REPO },
254
+ credentials: { accessToken: token },
255
+ file: { path: repoPath, content: new Blob([zipBytes.buffer], { type: "application/zip" }) },
256
+ branch: "main",
257
+ commitTitle: `Bootstrap: initialer State aus Legacy-Daten`
258
+ });
259
+ log(`Fertig \u2014 ${stateFilename}`);
260
+ }
261
+ async function hfUploadImage(base64, id, token, mimeType = "image/jpeg") {
262
+ const ext = bildEndungFuer(mimeType);
263
+ const binary = atob(base64);
264
+ const bytes = new Uint8Array(binary.length);
265
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
266
+ await uploadFile({
267
+ repo: { type: "dataset", name: HF_REPO },
268
+ credentials: { accessToken: token },
269
+ file: { path: `images/${id}.${ext}`, content: new Blob([bytes], { type: mimeType }) },
270
+ branch: "main",
271
+ commitTitle: `Add image ${id}`
272
+ });
273
+ }
274
+ async function hfLoadImageAsBase64(id, token, namespace, filename, on404, mimeType, hasThumb) {
275
+ const fetchBase64 = async (repoPath) => {
276
+ try {
277
+ const res = await fetch(
278
+ `${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
279
+ { headers: { Authorization: `Bearer ${token}` } }
280
+ );
281
+ if (!res.ok) {
282
+ if (on404 && on404()) return "ABORT";
283
+ return null;
284
+ }
285
+ const blob = await res.blob();
286
+ return new Promise((resolve, reject) => {
287
+ const reader = new FileReader();
288
+ reader.onload = () => resolve(reader.result.split(",")[1]);
289
+ reader.onerror = reject;
290
+ reader.readAsDataURL(blob);
291
+ });
292
+ } catch {
293
+ return null;
294
+ }
295
+ };
296
+ if (filename) {
297
+ if (hasThumb) {
298
+ const thumb = await fetchBase64(`thumbs/${filename}`);
299
+ if (thumb && thumb !== "ABORT") return thumb;
300
+ }
301
+ const result2 = await fetchBase64(`images/${filename}`);
302
+ return result2 === "ABORT" ? null : result2;
303
+ }
304
+ const ext = bildEndungFuer(mimeType);
305
+ const result = await fetchBase64(`images/${id}.${ext}`);
306
+ return result === "ABORT" ? null : result;
307
+ }
16
308
 
17
309
  // src/lib/szenen/viewPort.ts
18
310
  function capabilitiesOf(port) {
@@ -5225,6 +5517,133 @@ function findPromptRun(events, runId) {
5225
5517
  return collectPromptRuns(events).find((run) => run.run_id === runId) ?? null;
5226
5518
  }
5227
5519
 
5520
+ // src/lib/szenen/seedListe.ts
5521
+ var DEFAULT_SEED_VORLAGE = {
5522
+ name: "Default",
5523
+ seeds: "1; 10; 100; 1000",
5524
+ builtin: true
5525
+ };
5526
+ function parseSeedListe(eingabe) {
5527
+ if (!eingabe) return [];
5528
+ return eingabe.split(/[,;]/).map((teil) => teil.trim()).filter((teil) => teil.length > 0);
5529
+ }
5530
+ function formatSeedListe(tokens) {
5531
+ return tokens.join("; ");
5532
+ }
5533
+ var GANZZAHL = /^-?\d+$/;
5534
+ function ordneSeedsZu(eingabe, anzahl) {
5535
+ const tokens = Array.isArray(eingabe) ? [...eingabe] : parseSeedListe(eingabe);
5536
+ const eintraege = [];
5537
+ for (let position = 0; position < anzahl; position++) {
5538
+ const roh = tokens[position] ?? null;
5539
+ if (roh === null) {
5540
+ eintraege.push({ position, roh: null, seed: null, ungueltig: false });
5541
+ continue;
5542
+ }
5543
+ const zahl = GANZZAHL.test(roh) ? Number(roh) : Number.NaN;
5544
+ const gueltig = Number.isSafeInteger(zahl);
5545
+ eintraege.push({
5546
+ position,
5547
+ roh,
5548
+ seed: gueltig ? zahl : null,
5549
+ ungueltig: !gueltig
5550
+ });
5551
+ }
5552
+ return {
5553
+ eintraege,
5554
+ ungueltige: eintraege.filter((e) => e.ungueltig),
5555
+ ueberzaehlig: Math.max(0, tokens.length - anzahl)
5556
+ };
5557
+ }
5558
+ function seedFehlerText(ungueltige) {
5559
+ const teile = ungueltige.map((e) => `Bild ${e.position + 1}: "${e.roh}"`);
5560
+ return `Die Seedliste enth\xE4lt keine ganze Zahl an ${teile.length === 1 ? "Position" : "Positionen"} \u2014 ${teile.join(", ")}. Kein Bild wurde importiert.`;
5561
+ }
5562
+ function mitDefaultVorlage(custom) {
5563
+ return [DEFAULT_SEED_VORLAGE, ...custom.filter((v) => !v.builtin)];
5564
+ }
5565
+
5566
+ // src/lib/szenen/zipBilder.ts
5567
+ var BILD_ENDUNGEN = {
5568
+ png: "image/png",
5569
+ jpg: "image/jpeg",
5570
+ jpeg: "image/jpeg",
5571
+ webp: "image/webp"
5572
+ };
5573
+ var UPLOAD_MAX_BYTES = 45 * 1024 * 1024;
5574
+ function byteLaenge(base64) {
5575
+ const fuellzeichen = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
5576
+ return Math.floor(base64.length * 3 / 4) - fuellzeichen;
5577
+ }
5578
+ function angekuendigteGroesse(eintrag) {
5579
+ const roh = eintrag._data?.uncompressedSize;
5580
+ return typeof roh === "number" && Number.isFinite(roh) && roh >= 0 ? roh : null;
5581
+ }
5582
+ function bildGroesseVerdikt(archiv, pfad, bytes) {
5583
+ if (bytes === null || bytes <= UPLOAD_MAX_BYTES) return null;
5584
+ return `${archiv}: Das Bild ${pfad} ist entpackt ${mb(bytes)} MB gro\xDF \u2014 die Grenze f\xFCr eine einzelne Bilddatei liegt bei ${UPLOAD_MAX_BYTES / 1024 / 1024} MB. Kein Bild wurde importiert.`;
5585
+ }
5586
+ function mb(bytes) {
5587
+ return (bytes / 1024 / 1024).toFixed(1);
5588
+ }
5589
+ function istZipDatei(datei) {
5590
+ return /\.zip$/i.test(datei.name);
5591
+ }
5592
+ function endungVon(pfad) {
5593
+ const punkt = pfad.lastIndexOf(".");
5594
+ return punkt >= 0 ? pfad.slice(punkt + 1).toLowerCase() : "";
5595
+ }
5596
+ function istImportierbar(pfad, istVerzeichnis) {
5597
+ if (istVerzeichnis) return false;
5598
+ if (pfad.startsWith("__MACOSX/")) return false;
5599
+ const datei = pfad.slice(pfad.lastIndexOf("/") + 1);
5600
+ if (datei.startsWith(".")) return false;
5601
+ return endungVon(datei) in BILD_ENDUNGEN;
5602
+ }
5603
+ function nachPfad(a, b) {
5604
+ return a.localeCompare(b, "en", { numeric: true, sensitivity: "base" }) || (a < b ? -1 : a > b ? 1 : 0);
5605
+ }
5606
+ async function leseBilderAusZip(datei) {
5607
+ const name = datei.name ?? "Archiv";
5608
+ if (datei.size > UPLOAD_MAX_BYTES) {
5609
+ throw new Error(
5610
+ `${name} ist ${mb(datei.size)} MB gro\xDF \u2014 die Grenze liegt bei ${UPLOAD_MAX_BYTES / 1024 / 1024} MB.`
5611
+ );
5612
+ }
5613
+ const { default: JSZip2 } = await import("jszip");
5614
+ const archiv = await JSZip2.loadAsync(datei).catch((fehler4) => {
5615
+ throw new Error(
5616
+ `${name} konnte nicht als ZIP gelesen werden: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
5617
+ );
5618
+ });
5619
+ const pfade = [];
5620
+ archiv.forEach((pfad, eintrag) => {
5621
+ if (istImportierbar(pfad, eintrag.dir)) pfade.push(pfad);
5622
+ });
5623
+ pfade.sort(nachPfad);
5624
+ if (pfade.length === 0) {
5625
+ throw new Error(
5626
+ `${name} enth\xE4lt kein unterst\xFCtztes Bild (erwartet: ${Object.keys(BILD_ENDUNGEN).map((e) => `.${e}`).join(", ")}).`
5627
+ );
5628
+ }
5629
+ const bilder = [];
5630
+ for (const pfad of pfade) {
5631
+ const eintrag = archiv.file(pfad);
5632
+ if (!eintrag) continue;
5633
+ const angekuendigt = bildGroesseVerdikt(name, pfad, angekuendigteGroesse(eintrag));
5634
+ if (angekuendigt) throw new Error(angekuendigt);
5635
+ const base64 = await eintrag.async("base64");
5636
+ const nachgemessen = bildGroesseVerdikt(name, pfad, byteLaenge(base64));
5637
+ if (nachgemessen) throw new Error(nachgemessen);
5638
+ bilder.push({
5639
+ name: pfad,
5640
+ base64,
5641
+ mimeType: BILD_ENDUNGEN[endungVon(pfad)]
5642
+ });
5643
+ }
5644
+ return bilder;
5645
+ }
5646
+
5228
5647
  // src/lib/szenen/bildVorschau.ts
5229
5648
  async function ladeVorschauen(port, mediaRefs, fertig, optionen = {}) {
5230
5649
  const laden = port.loadPreview;
@@ -6242,7 +6661,7 @@ function usePortQuery(frage, abhaengigkeiten) {
6242
6661
  }
6243
6662
 
6244
6663
  // src/features/szenen/areas/ElementDetail.tsx
6245
- import { useEffect as useEffect5, useMemo as useMemo2, useState as useState10 } from "react";
6664
+ import { useEffect as useEffect6, useMemo as useMemo2, useState as useState10 } from "react";
6246
6665
 
6247
6666
  // src/features/szenen/elementLabels.ts
6248
6667
  var LABELS = {
@@ -8334,10 +8753,10 @@ var VersionNavigator = ({
8334
8753
  };
8335
8754
 
8336
8755
  // src/features/szenen/areas/RunsPanel.tsx
8337
- import { useState as useState9 } from "react";
8756
+ import { useEffect as useEffect5, useRef as useRef3, useState as useState9 } from "react";
8338
8757
 
8339
8758
  // src/features/szenen/areas/RunImagesPanel.tsx
8340
- import { useEffect as useEffect4, useRef as useRef2, useState as useState8 } from "react";
8759
+ import React7, { useEffect as useEffect4, useRef as useRef2, useState as useState8 } from "react";
8341
8760
 
8342
8761
  // src/features/szenen/areas/Bildbausteine.tsx
8343
8762
  import { useCallback, useEffect as useEffect3, useState as useState7 } from "react";
@@ -8777,6 +9196,7 @@ function useBildQuellen(port, bilder) {
8777
9196
  const [stand, setStand] = useState8(() => ({ quellen: /* @__PURE__ */ new Map(), fehler: /* @__PURE__ */ new Map() }));
8778
9197
  const gehalten = useRef2(/* @__PURE__ */ new Map());
8779
9198
  const gescheitert = useRef2(/* @__PURE__ */ new Map());
9199
+ const [versuch, setVersuch] = useState8(0);
8780
9200
  const schluessel = bilder.map((b) => `${b.media_ref}|${b.mime_type ?? ""}`).join(" ");
8781
9201
  useEffect4(() => {
8782
9202
  const bestand = port.imageLibrary;
@@ -8785,7 +9205,9 @@ function useBildQuellen(port, bilder) {
8785
9205
  void (async () => {
8786
9206
  for (const bild of bilder) {
8787
9207
  if (abgebrochen) return;
8788
- if (gehalten.current.has(bild.media_ref) || gescheitert.current.has(bild.media_ref)) continue;
9208
+ const vorher = gescheitert.current.get(bild.media_ref);
9209
+ const endgueltig = vorher !== void 0 && !vorher.retryable;
9210
+ if (gehalten.current.has(bild.media_ref) || endgueltig) continue;
8789
9211
  const ergebnis = await bestand.resolveImage(bild.media_ref, bild.mime_type ?? null);
8790
9212
  if (abgebrochen) return;
8791
9213
  if (ergebnis.kind === "ok" && ergebnis.value) {
@@ -8793,8 +9215,12 @@ function useBildQuellen(port, bilder) {
8793
9215
  bild.media_ref,
8794
9216
  `data:${ergebnis.value.mime_type};base64,${ergebnis.value.data}`
8795
9217
  );
9218
+ gescheitert.current.delete(bild.media_ref);
8796
9219
  } else if (ergebnis.kind === "error") {
8797
- gescheitert.current.set(bild.media_ref, ergebnis.message);
9220
+ gescheitert.current.set(bild.media_ref, {
9221
+ message: ergebnis.message,
9222
+ retryable: ergebnis.retryable
9223
+ });
8798
9224
  }
8799
9225
  setStand({
8800
9226
  quellen: new Map(gehalten.current),
@@ -8805,8 +9231,19 @@ function useBildQuellen(port, bilder) {
8805
9231
  return () => {
8806
9232
  abgebrochen = true;
8807
9233
  };
8808
- }, [port, schluessel]);
8809
- return stand;
9234
+ }, [port, schluessel, versuch]);
9235
+ const fehlerNachricht = /* @__PURE__ */ new Map();
9236
+ let wiederholbar = false;
9237
+ for (const [ref, f] of stand.fehler) {
9238
+ fehlerNachricht.set(ref, f.message);
9239
+ if (f.retryable) wiederholbar = true;
9240
+ }
9241
+ return {
9242
+ quellen: stand.quellen,
9243
+ fehler: fehlerNachricht,
9244
+ wiederholbar,
9245
+ erneutVersuchen: () => setVersuch((n) => n + 1)
9246
+ };
8810
9247
  }
8811
9248
  var BildDetails = ({ bild, fehler: fehler4, aufloesbar }) => /* @__PURE__ */ jsxs8("span", { className: "szenen-bilder__meta", "data-testid": `batchbild-${bild.media_ref}`, children: [
8812
9249
  /* @__PURE__ */ jsx8("code", { children: bild.media_ref }),
@@ -8827,8 +9264,17 @@ var BildDetails = ({ bild, fehler: fehler4, aufloesbar }) => /* @__PURE__ */ jsx
8827
9264
  // Serverroute (A6).
8828
9265
  /* @__PURE__ */ jsx8("em", { "data-testid": `batchbild-unsupported-${bild.media_ref}`, children: "Dieser Host l\xF6st keine Bilder auf \u2014 nur die Metadaten sind bekannt." })
8829
9266
  ] });
9267
+ var HERKUNFT_TEXT = {
9268
+ generated_in_app: "in der App erzeugt",
9269
+ external_import: "extern importiert",
9270
+ mixed: "gemischte Herkunft"
9271
+ };
9272
+ var HERKUNFT_KURZ = {
9273
+ generated_in_app: "in der App erzeugt",
9274
+ external_import: "extern importiert"
9275
+ };
8830
9276
  var BatchKachel = ({ port, batch }) => {
8831
- const { quellen, fehler: fehler4 } = useBildQuellen(port, batch.images);
9277
+ const { quellen, fehler: fehler4, wiederholbar, erneutVersuchen } = useBildQuellen(port, batch.images);
8832
9278
  const aufloesbar = Boolean(port.imageLibrary);
8833
9279
  const bilder = batch.images.map((bild) => ({
8834
9280
  id: bild.media_ref,
@@ -8854,7 +9300,7 @@ var BatchKachel = ({ port, batch }) => {
8854
9300
  /* @__PURE__ */ jsxs8("header", { className: "szenen-bilder__batchkopf", children: [
8855
9301
  /* @__PURE__ */ jsx8("strong", { children: batch.title }),
8856
9302
  /* @__PURE__ */ jsxs8("span", { className: "szenen-bilder__herkunft", children: [
8857
- batch.source_kind === "generated_in_app" ? "in der App erzeugt" : "extern importiert",
9303
+ HERKUNFT_TEXT[batch.source_kind],
8858
9304
  " \xB7 ",
8859
9305
  batch.images.length,
8860
9306
  " Bild(er)"
@@ -8868,6 +9314,16 @@ var BatchKachel = ({ port, batch }) => {
8868
9314
  satzTitel: batch.title
8869
9315
  }
8870
9316
  ),
9317
+ wiederholbar && /* @__PURE__ */ jsx8(
9318
+ "button",
9319
+ {
9320
+ type: "button",
9321
+ className: "szenen-bilder__retry",
9322
+ onClick: erneutVersuchen,
9323
+ "data-testid": `batch-retry-${batch.item_id}`,
9324
+ children: "Bilder erneut laden"
9325
+ }
9326
+ ),
8871
9327
  /* @__PURE__ */ jsxs8("details", { className: "szenen-bilder__technik", "data-testid": `batch-technik-${batch.item_id}`, children: [
8872
9328
  /* @__PURE__ */ jsx8("summary", { children: "Technische Details" }),
8873
9329
  /* @__PURE__ */ jsxs8("p", { className: "szenen-bilder__ids", children: [
@@ -8876,8 +9332,13 @@ var BatchKachel = ({ port, batch }) => {
8876
9332
  /* @__PURE__ */ jsx8("code", { "data-testid": `batch-item-${batch.item_id}`, children: batch.item_id })
8877
9333
  ] }),
8878
9334
  /* @__PURE__ */ jsxs8("span", { children: [
8879
- "Batches ",
8880
- /* @__PURE__ */ jsx8("code", { children: batch.generation_batch_ids.join(", ") })
9335
+ "Batches",
9336
+ " ",
9337
+ batch.origins.length > 0 ? batch.origins.map((h, i) => /* @__PURE__ */ jsxs8(React7.Fragment, { children: [
9338
+ i > 0 ? ", " : "",
9339
+ /* @__PURE__ */ jsx8("code", { children: h.generation_batch_id }),
9340
+ ` (${HERKUNFT_KURZ[h.source_kind]})`
9341
+ ] }, h.generation_batch_id)) : /* @__PURE__ */ jsx8("code", { children: batch.generation_batch_ids.join(", ") })
8881
9342
  ] })
8882
9343
  ] }),
8883
9344
  /* @__PURE__ */ jsx8("ul", { className: "szenen-bilder__batchliste", children: batch.images.map((bild) => /* @__PURE__ */ jsx8("li", { children: /* @__PURE__ */ jsx8(
@@ -9081,21 +9542,114 @@ var ErzeugenAktion = ({ port, lauf, wahl, wahlFehler, onFertig }) => {
9081
9542
  )
9082
9543
  ] });
9083
9544
  };
9545
+ var DEFAULT_VORLAGE_KEY = "__default__";
9084
9546
  var ZuordnenAktion = ({ port, lauf, onFertig }) => {
9085
9547
  const [dateien, setDateien] = useState8([]);
9086
9548
  const [seeds, setSeeds] = useState8({});
9549
+ const [zip, setZip] = useState8(null);
9550
+ const [zipLaedt, setZipLaedt] = useState8(false);
9551
+ const [seedListe, setSeedListe] = useState8("");
9552
+ const [vorlagen, setVorlagen] = useState8([DEFAULT_SEED_VORLAGE]);
9553
+ const [vorlageKey, setVorlageKey] = useState8(DEFAULT_VORLAGE_KEY);
9554
+ const [vorlagenStand, setVorlagenStand] = useState8(0);
9555
+ const [vorlageBenennen, setVorlageBenennen] = useState8(false);
9556
+ const [vorlageName, setVorlageName] = useState8("");
9087
9557
  const [modell, setModell] = useState8("");
9088
9558
  const [itemId, setItemId] = useState8("");
9089
9559
  const [laeuft, setLaeuft] = useState8(false);
9090
9560
  const [notiz, setNotiz] = useState8(null);
9561
+ const auswahlMarke = useRef2(0);
9562
+ const zipAktiv = zip !== null;
9563
+ useEffect4(() => {
9564
+ if (!zipAktiv) return;
9565
+ let abgebrochen = false;
9566
+ void (async () => {
9567
+ const gelesen = port.seedTemplates ? await port.seedTemplates.listSeedTemplates() : null;
9568
+ if (abgebrochen) return;
9569
+ setVorlagen(mitDefaultVorlage(
9570
+ gelesen && gelesen.kind === "ok" ? gelesen.value : []
9571
+ ));
9572
+ })();
9573
+ return () => {
9574
+ abgebrochen = true;
9575
+ };
9576
+ }, [zipAktiv, port, vorlagenStand]);
9091
9577
  if (!port.imageBatchWrite) {
9092
9578
  return /* @__PURE__ */ jsx8("p", { "data-testid": `zuordnen-unsupported-${lauf.run_id}`, children: "Dieser Host ordnet keine externen Bilder zu \u2014 er hat keinen gesicherten Batch-Schreibweg. Die Historie oben bleibt trotzdem sichtbar." });
9093
9579
  }
9094
- const importieren = async () => {
9580
+ const zuordnung = zip ? ordneSeedsZu(seedListe, zip.bilder.length) : null;
9581
+ const dateiWahl = async (liste) => {
9582
+ const gewaehlt = [...liste ?? []];
9583
+ const warZip = zip !== null;
9584
+ const meine = auswahlMarke.current += 1;
9585
+ const veraltet = () => auswahlMarke.current !== meine;
9586
+ const zips = gewaehlt.filter(istZipDatei);
9587
+ const nurEinZip = zips.length === 1 && gewaehlt.length === 1;
9588
+ setNotiz(null);
9589
+ setSeeds({});
9590
+ setZip(null);
9591
+ setDateien(zips.length === 0 ? gewaehlt : []);
9592
+ setZipLaedt(nurEinZip);
9593
+ if (zips.length > 0 && zips.length !== gewaehlt.length) {
9594
+ setNotiz({
9595
+ tone: "error",
9596
+ text: `Die Auswahl enth\xE4lt ${zips.length} ZIP-Datei(en) und ${gewaehlt.length - zips.length} Bilddatei(en). Bitte entweder ein ZIP oder einzelne Bilder w\xE4hlen \u2014 nicht beides in einem Import.`
9597
+ });
9598
+ return;
9599
+ }
9600
+ if (zips.length > 1) {
9601
+ setNotiz({
9602
+ tone: "error",
9603
+ text: `${zips.length} ZIP-Dateien gew\xE4hlt (${zips.map((z) => z.name).join(", ")}). Ein Import nimmt genau ein ZIP.`
9604
+ });
9605
+ return;
9606
+ }
9607
+ if (!nurEinZip) return;
9608
+ const datei = zips[0];
9609
+ try {
9610
+ const bilder = await leseBilderAusZip(datei);
9611
+ if (veraltet()) return;
9612
+ setZip({ datei, bilder });
9613
+ if (!warZip) {
9614
+ setSeedListe(DEFAULT_SEED_VORLAGE.seeds);
9615
+ setVorlageKey(DEFAULT_VORLAGE_KEY);
9616
+ }
9617
+ } catch (fehler4) {
9618
+ if (veraltet()) return;
9619
+ setNotiz({
9620
+ tone: "error",
9621
+ text: fehler4 instanceof Error ? fehler4.message : String(fehler4)
9622
+ });
9623
+ }
9624
+ if (veraltet()) return;
9625
+ setZipLaedt(false);
9626
+ };
9627
+ const schreiben = async (bilder, generationBatchId, titel) => {
9095
9628
  setLaeuft(true);
9096
9629
  setNotiz(null);
9097
9630
  try {
9098
- const bilder = [];
9631
+ const ergebnis = await port.imageBatchWrite.importBatch({
9632
+ runId: lauf.run_id,
9633
+ generationBatchId,
9634
+ images: bilder,
9635
+ title: titel
9636
+ });
9637
+ setNotiz(
9638
+ ergebnis.kind === "ok" ? { tone: "ok", text: `Importiert als Item ${ergebnis.value.item_id} mit ${bilder.length} Bild(ern).` } : noticeFor(ergebnis, "Externe Bilder importieren")
9639
+ );
9640
+ } catch (fehler4) {
9641
+ setNotiz({
9642
+ tone: "error",
9643
+ text: `Externe Bilder importieren: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
9644
+ });
9645
+ }
9646
+ setLaeuft(false);
9647
+ onFertig();
9648
+ };
9649
+ const importieren = async () => {
9650
+ let bilder;
9651
+ try {
9652
+ bilder = [];
9099
9653
  for (let index = 0; index < dateien.length; index++) {
9100
9654
  const roh = (seeds[index] ?? "").trim();
9101
9655
  const seed = roh === "" ? void 0 : Number.parseInt(roh, 10);
@@ -9108,25 +9662,42 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
9108
9662
  ...seed !== void 0 ? { seed } : {}
9109
9663
  });
9110
9664
  }
9111
- const ergebnis = await port.imageBatchWrite.importBatch({
9112
- runId: lauf.run_id,
9113
- // Vor dem Aufruf gebildet und stabil (D-113): eine Wiederholung
9114
- // derselben Einreichung erzeugt kein zweites Item.
9115
- generationBatchId: `imp-${lauf.run_id}-${dateien.length}-${dateien.map((d) => d.size).join("_")}`,
9116
- images: bilder,
9117
- title: `Externer Import zu Lauf ${lauf.run_id}`
9118
- });
9119
- setNotiz(
9120
- ergebnis.kind === "ok" ? { tone: "ok", text: `Importiert als Item ${ergebnis.value.item_id} mit ${bilder.length} Bild(ern).` } : noticeFor(ergebnis, "Externe Bilder importieren")
9121
- );
9122
9665
  } catch (fehler4) {
9123
9666
  setNotiz({
9124
9667
  tone: "error",
9125
9668
  text: `Externe Bilder importieren: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
9126
9669
  });
9670
+ return;
9127
9671
  }
9128
- setLaeuft(false);
9129
- onFertig();
9672
+ await schreiben(
9673
+ bilder,
9674
+ // Vor dem Aufruf gebildet und stabil (D-113): eine Wiederholung
9675
+ // derselben Einreichung erzeugt kein zweites Item.
9676
+ `imp-${lauf.run_id}-${dateien.length}-${dateien.map((d) => d.size).join("_")}`,
9677
+ `Externer Import zu Lauf ${lauf.run_id}`
9678
+ );
9679
+ };
9680
+ const zipImportieren = async () => {
9681
+ if (!zip || !zuordnung) return;
9682
+ if (zuordnung.ungueltige.length > 0) {
9683
+ setNotiz({ tone: "error", text: seedFehlerText(zuordnung.ungueltige) });
9684
+ return;
9685
+ }
9686
+ const bilder = zip.bilder.map((bild, index) => {
9687
+ const seed = zuordnung.eintraege[index]?.seed ?? null;
9688
+ return {
9689
+ imageBase64: bild.base64,
9690
+ ...modell.trim() ? { model: modell.trim() } : {},
9691
+ // Unbekannt bleibt unbekannt: kein Feld statt eines erfundenen
9692
+ // Werts (§6.1).
9693
+ ...seed !== null ? { seed } : {}
9694
+ };
9695
+ });
9696
+ await schreiben(
9697
+ bilder,
9698
+ `imp-${lauf.run_id}-zip-${zip.datei.size}-${bilder.length}`,
9699
+ `Externer Import zu Lauf ${lauf.run_id} (${zip.datei.name})`
9700
+ );
9130
9701
  };
9131
9702
  const zuordnen = async () => {
9132
9703
  setLaeuft(true);
@@ -9144,35 +9715,63 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
9144
9715
  setLaeuft(false);
9145
9716
  onFertig();
9146
9717
  };
9718
+ const vorlageWaehlen = (key) => {
9719
+ setVorlageKey(key);
9720
+ const vorlage = key === DEFAULT_VORLAGE_KEY ? DEFAULT_SEED_VORLAGE : vorlagen.find((v) => String(v.id) === key);
9721
+ if (vorlage) setSeedListe(vorlage.seeds);
9722
+ };
9723
+ const vorlageSpeichern = async () => {
9724
+ const name = vorlageName.trim();
9725
+ if (!name || !seedListe.trim() || !port.seedTemplates?.saveSeedTemplate) return;
9726
+ const ergebnis = await port.seedTemplates.saveSeedTemplate({ name, seeds: seedListe.trim() });
9727
+ if (ergebnis.kind !== "ok") {
9728
+ setNotiz(noticeFor(ergebnis, "Seed-Vorlage speichern"));
9729
+ return;
9730
+ }
9731
+ setVorlageBenennen(false);
9732
+ setVorlageName("");
9733
+ if (ergebnis.value.id !== void 0) setVorlageKey(String(ergebnis.value.id));
9734
+ setVorlagenStand((n) => n + 1);
9735
+ };
9736
+ const vorlageLoeschen = async () => {
9737
+ if (vorlageKey === DEFAULT_VORLAGE_KEY || !port.seedTemplates?.deleteSeedTemplate) return;
9738
+ const ergebnis = await port.seedTemplates.deleteSeedTemplate(vorlageKey);
9739
+ if (ergebnis.kind !== "ok") {
9740
+ setNotiz(noticeFor(ergebnis, "Seed-Vorlage l\xF6schen"));
9741
+ return;
9742
+ }
9743
+ setVorlageKey(DEFAULT_VORLAGE_KEY);
9744
+ setVorlagenStand((n) => n + 1);
9745
+ };
9147
9746
  return /* @__PURE__ */ jsxs8("div", { className: "szenen-bilder__aktion-inhalt", "data-testid": `zuordnen-${lauf.run_id}`, children: [
9148
9747
  /* @__PURE__ */ jsxs8("label", { children: [
9149
- "Bilddateien",
9748
+ "Bilddateien oder ein ZIP",
9150
9749
  /* @__PURE__ */ jsx8(
9151
9750
  "input",
9152
9751
  {
9153
9752
  type: "file",
9154
- accept: "image/*",
9753
+ accept: "image/*,.zip",
9155
9754
  multiple: true,
9156
9755
  "data-testid": `zuordnen-dateien-${lauf.run_id}`,
9157
9756
  onChange: (e) => {
9158
- setDateien([...e.target.files ?? []]);
9159
- setSeeds({});
9757
+ void dateiWahl(e.target.files);
9160
9758
  }
9161
9759
  }
9162
9760
  )
9163
9761
  ] }),
9762
+ zipLaedt && /* @__PURE__ */ jsx8("p", { "data-testid": `zuordnen-zip-laedt-${lauf.run_id}`, children: "ZIP wird gelesen \u2026" }),
9763
+ (dateien.length > 0 || zip) && /* @__PURE__ */ jsxs8("label", { children: [
9764
+ "Modell (f\xFCr alle Bilder, leer = unbekannt)",
9765
+ /* @__PURE__ */ jsx8(
9766
+ "input",
9767
+ {
9768
+ value: modell,
9769
+ "data-testid": `zuordnen-modell-${lauf.run_id}`,
9770
+ onChange: (e) => setModell(e.target.value)
9771
+ }
9772
+ )
9773
+ ] }),
9164
9774
  dateien.length > 0 && /* @__PURE__ */ jsxs8(Fragment4, { children: [
9165
- /* @__PURE__ */ jsxs8("label", { children: [
9166
- "Modell (f\xFCr alle Bilder, leer = unbekannt)",
9167
- /* @__PURE__ */ jsx8(
9168
- "input",
9169
- {
9170
- value: modell,
9171
- "data-testid": `zuordnen-modell-${lauf.run_id}`,
9172
- onChange: (e) => setModell(e.target.value)
9173
- }
9174
- )
9175
- ] }),
9176
9775
  /* @__PURE__ */ jsx8("ol", { "data-testid": `zuordnen-vorschau-${lauf.run_id}`, children: dateien.map((datei, index) => /* @__PURE__ */ jsxs8("li", { children: [
9177
9776
  /* @__PURE__ */ jsxs8("span", { children: [
9178
9777
  "Position ",
@@ -9206,6 +9805,157 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
9206
9805
  }
9207
9806
  )
9208
9807
  ] }),
9808
+ zip && zuordnung && /* @__PURE__ */ jsxs8("div", { className: "szenen-bilder__zip", "data-testid": `zuordnen-zip-${lauf.run_id}`, children: [
9809
+ /* @__PURE__ */ jsxs8("p", { className: "szenen-bilder__zipkopf", children: [
9810
+ "ZIP ",
9811
+ /* @__PURE__ */ jsx8("code", { children: zip.datei.name }),
9812
+ " \u2014 ",
9813
+ zip.bilder.length,
9814
+ " Bild(er) erkannt"
9815
+ ] }),
9816
+ /* @__PURE__ */ jsxs8("label", { children: [
9817
+ "Seeds (pro Bild ein Seed, Trenner , oder ;)",
9818
+ /* @__PURE__ */ jsx8(
9819
+ "input",
9820
+ {
9821
+ "data-testid": `zuordnen-seedliste-${lauf.run_id}`,
9822
+ value: seedListe,
9823
+ placeholder: "z. B. 1; 10; 100; 1000",
9824
+ onChange: (e) => setSeedListe(e.target.value)
9825
+ }
9826
+ )
9827
+ ] }),
9828
+ /* @__PURE__ */ jsxs8("label", { children: [
9829
+ "Seed-Vorlage",
9830
+ /* @__PURE__ */ jsxs8(
9831
+ "select",
9832
+ {
9833
+ "data-testid": `zuordnen-seedvorlage-${lauf.run_id}`,
9834
+ value: vorlageKey,
9835
+ onChange: (e) => vorlageWaehlen(e.target.value),
9836
+ children: [
9837
+ /* @__PURE__ */ jsxs8("option", { value: DEFAULT_VORLAGE_KEY, children: [
9838
+ "Default (",
9839
+ DEFAULT_SEED_VORLAGE.seeds,
9840
+ ")"
9841
+ ] }),
9842
+ vorlagen.filter((v) => !v.builtin).map((v) => /* @__PURE__ */ jsxs8("option", { value: String(v.id), children: [
9843
+ v.name,
9844
+ " (",
9845
+ v.seeds,
9846
+ ")"
9847
+ ] }, String(v.id)))
9848
+ ]
9849
+ }
9850
+ )
9851
+ ] }),
9852
+ (port.seedTemplates?.saveSeedTemplate || port.seedTemplates?.deleteSeedTemplate) && /* @__PURE__ */ jsxs8("p", { className: "szenen-bilder__zipvorlagen", children: [
9853
+ port.seedTemplates?.saveSeedTemplate && !vorlageBenennen && /* @__PURE__ */ jsx8(
9854
+ "button",
9855
+ {
9856
+ type: "button",
9857
+ "data-testid": `zuordnen-vorlage-neu-${lauf.run_id}`,
9858
+ disabled: !seedListe.trim(),
9859
+ onClick: () => {
9860
+ setVorlageName("");
9861
+ setVorlageBenennen(true);
9862
+ },
9863
+ children: "Als Vorlage speichern"
9864
+ }
9865
+ ),
9866
+ port.seedTemplates?.deleteSeedTemplate && /* @__PURE__ */ jsx8(
9867
+ "button",
9868
+ {
9869
+ type: "button",
9870
+ "data-testid": `zuordnen-vorlage-loeschen-${lauf.run_id}`,
9871
+ disabled: vorlageKey === DEFAULT_VORLAGE_KEY,
9872
+ onClick: () => {
9873
+ void vorlageLoeschen();
9874
+ },
9875
+ children: "Vorlage l\xF6schen"
9876
+ }
9877
+ )
9878
+ ] }),
9879
+ vorlageBenennen && /* @__PURE__ */ jsxs8(Fragment4, { children: [
9880
+ /* @__PURE__ */ jsxs8("label", { children: [
9881
+ "Name der Vorlage",
9882
+ /* @__PURE__ */ jsx8(
9883
+ "input",
9884
+ {
9885
+ "data-testid": `zuordnen-vorlage-name-${lauf.run_id}`,
9886
+ value: vorlageName,
9887
+ onChange: (e) => setVorlageName(e.target.value)
9888
+ }
9889
+ )
9890
+ ] }),
9891
+ /* @__PURE__ */ jsxs8("p", { className: "szenen-bilder__zipvorlagen", children: [
9892
+ /* @__PURE__ */ jsx8(
9893
+ "button",
9894
+ {
9895
+ type: "button",
9896
+ "data-testid": `zuordnen-vorlage-sichern-${lauf.run_id}`,
9897
+ disabled: !vorlageName.trim(),
9898
+ onClick: () => {
9899
+ void vorlageSpeichern();
9900
+ },
9901
+ children: "Speichern"
9902
+ }
9903
+ ),
9904
+ /* @__PURE__ */ jsx8(
9905
+ "button",
9906
+ {
9907
+ type: "button",
9908
+ "data-testid": `zuordnen-vorlage-abbrechen-${lauf.run_id}`,
9909
+ onClick: () => setVorlageBenennen(false),
9910
+ children: "Abbrechen"
9911
+ }
9912
+ )
9913
+ ] })
9914
+ ] }),
9915
+ zuordnung.ueberzaehlig > 0 && /* @__PURE__ */ jsxs8("p", { "data-testid": `zuordnen-ueberzaehlig-${lauf.run_id}`, children: [
9916
+ zuordnung.ueberzaehlig,
9917
+ " Seed(s) mehr als Bilder \u2014 die \xFCberz\xE4hligen werden nicht geschrieben."
9918
+ ] }),
9919
+ /* @__PURE__ */ jsx8(
9920
+ "ol",
9921
+ {
9922
+ className: "szenen-bilder__zipliste",
9923
+ "data-testid": `zuordnen-zip-vorschau-${lauf.run_id}`,
9924
+ children: zip.bilder.map((bild, index) => {
9925
+ const eintrag = zuordnung.eintraege[index];
9926
+ return /* @__PURE__ */ jsxs8("li", { children: [
9927
+ /* @__PURE__ */ jsxs8("span", { children: [
9928
+ "Position ",
9929
+ index,
9930
+ ": ",
9931
+ bild.name
9932
+ ] }),
9933
+ /* @__PURE__ */ jsx8(
9934
+ "span",
9935
+ {
9936
+ "data-testid": `zuordnen-zip-seed-${lauf.run_id}-${index}`,
9937
+ "data-seed": eintrag.seed === null ? "" : String(eintrag.seed),
9938
+ children: eintrag.ungueltig ? `Seed ung\xFCltig: "${eintrag.roh}"` : eintrag.seed === null ? "Seed unbekannt" : `Seed ${eintrag.seed}`
9939
+ }
9940
+ )
9941
+ ] }, `${bild.name}-${index}`);
9942
+ })
9943
+ }
9944
+ ),
9945
+ /* @__PURE__ */ jsxs8(
9946
+ "button",
9947
+ {
9948
+ type: "button",
9949
+ "data-testid": `zuordnen-zip-import-${lauf.run_id}`,
9950
+ disabled: laeuft || zipLaedt,
9951
+ onClick: zipImportieren,
9952
+ children: [
9953
+ zip.bilder.length,
9954
+ " Bild(er) aus ZIP importieren"
9955
+ ]
9956
+ }
9957
+ )
9958
+ ] }),
9209
9959
  /* @__PURE__ */ jsxs8("label", { children: [
9210
9960
  "Oder ein bestehendes Library-Item",
9211
9961
  /* @__PURE__ */ jsx8(
@@ -9261,6 +10011,8 @@ var RunsPanel = ({ port, source, contextId }) => {
9261
10011
  const [nachladen, setNachladen] = useState9(0);
9262
10012
  const faehig = capabilitiesOf(port);
9263
10013
  const quellSchluessel = source.kind === "published" ? source.shotVersionId : source.kind === "draft_chain" ? source.chainId : source.sceneElementId;
10014
+ const aktuelleQuelle = useRef3({ kind: source.kind, id: quellSchluessel });
10015
+ aktuelleQuelle.current = { kind: source.kind, id: quellSchluessel };
9264
10016
  const [phase, neuLaden] = usePortQuery(
9265
10017
  () => {
9266
10018
  if (source.kind === "published") return port.runs.listRunsForShot(source.shotVersionId);
@@ -9271,12 +10023,16 @@ var RunsPanel = ({ port, source, contextId }) => {
9271
10023
  );
9272
10024
  const starten = async () => {
9273
10025
  if (source.kind !== "published") return;
10026
+ const gestartetFuer = { kind: source.kind, id: source.shotVersionId };
9274
10027
  setLaeuft(true);
9275
10028
  const ergebnis = await port.runs.startRun({
9276
10029
  shotVersionId: source.shotVersionId,
9277
10030
  ...anweisung.trim() ? { userInstruction: anweisung.trim() } : {},
9278
10031
  contextId
9279
10032
  });
10033
+ if (aktuelleQuelle.current.kind !== gestartetFuer.kind || aktuelleQuelle.current.id !== gestartetFuer.id) {
10034
+ return;
10035
+ }
9280
10036
  setNotiz(noticeFor(ergebnis, "Prompt-Lauf starten"));
9281
10037
  setLaeuft(false);
9282
10038
  setNachladen((n) => n + 1);
@@ -9286,6 +10042,12 @@ var RunsPanel = ({ port, source, contextId }) => {
9286
10042
  const [gewaehlteRunde, setGewaehlteRunde] = useState9(void 0);
9287
10043
  const neueste = laeufe.length > 0 ? laeufe[laeufe.length - 1].run_id : null;
9288
10044
  const offeneRunde = gewaehlteRunde === void 0 ? neueste : gewaehlteRunde;
10045
+ useEffect5(() => {
10046
+ setGewaehlteRunde(void 0);
10047
+ setAnweisung("");
10048
+ setNotiz(null);
10049
+ setLaeuft(false);
10050
+ }, [source.kind, quellSchluessel]);
9289
10051
  return /* @__PURE__ */ jsxs9("div", { className: "szenen-runs", "data-testid": "laeufe-bereich", "data-shot-version": quellSchluessel, children: [
9290
10052
  /* @__PURE__ */ jsx9("h4", { children: "Prompt-L\xE4ufe" }),
9291
10053
  source.kind === "published" && /* @__PURE__ */ jsxs9("div", { className: "szenen-runs__start", children: [
@@ -9356,6 +10118,26 @@ var Sekundaertext = ({ titel, text, testId }) => {
9356
10118
  )
9357
10119
  ] });
9358
10120
  };
10121
+ var LISTE_ANFANG = 3;
10122
+ var SekundaerListe = ({ titel, eintraege, testId, className }) => {
10123
+ const [alle, setAlle] = useState9(false);
10124
+ const lang = eintraege.length > LISTE_ANFANG;
10125
+ const gezeigt = lang && !alle ? eintraege.slice(0, LISTE_ANFANG) : eintraege;
10126
+ return /* @__PURE__ */ jsxs9("section", { className: "szenen-runs__sekundaer", children: [
10127
+ /* @__PURE__ */ jsx9("h5", { children: titel }),
10128
+ /* @__PURE__ */ jsx9("ul", { className, "data-testid": testId, children: gezeigt.map((eintrag, i) => /* @__PURE__ */ jsx9("li", { children: eintrag }, i)) }),
10129
+ lang && /* @__PURE__ */ jsx9(
10130
+ "button",
10131
+ {
10132
+ type: "button",
10133
+ className: "szenen-runs__mehr",
10134
+ onClick: () => setAlle(!alle),
10135
+ "data-testid": `${testId}-mehr`,
10136
+ children: alle ? "weniger anzeigen" : `${eintraege.length - LISTE_ANFANG} weitere anzeigen`
10137
+ }
10138
+ )
10139
+ ] });
10140
+ };
9359
10141
  var RundenEintrag = ({ port, lauf, runde, offen, onUmschalten, itemMoeglich, onItemGespeichert }) => {
9360
10142
  const [notiz, setNotiz] = useState9(null);
9361
10143
  const [laeuft, setLaeuft] = useState9(false);
@@ -9441,11 +10223,23 @@ var RundenEintrag = ({ port, lauf, runde, offen, onUmschalten, itemMoeglich, onI
9441
10223
  testId: `feedback-${lauf.run_id}`
9442
10224
  }
9443
10225
  ),
9444
- lauf.output.visibility.assumptions.length > 0 && /* @__PURE__ */ jsxs9("section", { className: "szenen-runs__sekundaer", children: [
9445
- /* @__PURE__ */ jsx9("h5", { children: "Annahmen" }),
9446
- /* @__PURE__ */ jsx9("ul", { "data-testid": `annahmen-${lauf.run_id}`, children: lauf.output.visibility.assumptions.map((a, i) => /* @__PURE__ */ jsx9("li", { children: a }, i)) })
9447
- ] }),
9448
- lauf.output.notes.length > 0 && /* @__PURE__ */ jsx9("ul", { className: "szenen-runs__notizen", "data-testid": `notizen-${lauf.run_id}`, children: lauf.output.notes.map((n, i) => /* @__PURE__ */ jsx9("li", { children: n }, i)) })
10226
+ lauf.output.visibility.assumptions.length > 0 && /* @__PURE__ */ jsx9(
10227
+ SekundaerListe,
10228
+ {
10229
+ titel: "Annahmen",
10230
+ eintraege: lauf.output.visibility.assumptions,
10231
+ testId: `annahmen-${lauf.run_id}`
10232
+ }
10233
+ ),
10234
+ lauf.output.notes.length > 0 && /* @__PURE__ */ jsx9(
10235
+ SekundaerListe,
10236
+ {
10237
+ titel: "Notizen",
10238
+ eintraege: lauf.output.notes,
10239
+ testId: `notizen-${lauf.run_id}`,
10240
+ className: "szenen-runs__notizen"
10241
+ }
10242
+ )
9449
10243
  ] }),
9450
10244
  lauf.raw_output && /* @__PURE__ */ jsxs9("details", { "data-testid": `diagnose-${lauf.run_id}`, children: [
9451
10245
  /* @__PURE__ */ jsx9("summary", { children: "Rohantwort (Diagnose)" }),
@@ -9533,7 +10327,7 @@ var ElementDetail = ({
9533
10327
  () => schnappschuss ? fieldsForType(schnappschuss.element.type) : [],
9534
10328
  [schnappschuss?.element.type]
9535
10329
  );
9536
- useEffect5(() => {
10330
+ useEffect6(() => {
9537
10331
  setWahl(null);
9538
10332
  setBasiswechsel(null);
9539
10333
  setNotiz(null);
@@ -9564,7 +10358,7 @@ var ElementDetail = ({
9564
10358
  return {};
9565
10359
  }, [schnappschuss, angezeigt]);
9566
10360
  const anzeigeSchluessel = angezeigt.kind === "version" ? `v:${angezeigt.versionId}` : angezeigt.kind;
9567
- useEffect5(() => {
10361
+ useEffect6(() => {
9568
10362
  if (!schnappschuss) return;
9569
10363
  setEntwurf(formFromPayload(felder, angezeigtePayload));
9570
10364
  }, [
@@ -9996,7 +10790,7 @@ var ElementDetail = ({
9996
10790
  };
9997
10791
 
9998
10792
  // src/features/szenen/areas/useSchmalesDisplay.ts
9999
- import { useEffect as useEffect6, useState as useState11 } from "react";
10793
+ import { useEffect as useEffect7, useState as useState11 } from "react";
10000
10794
  var SCHMALE_ANSICHT = "(max-width: 760px)";
10001
10795
  function messen(abfrage) {
10002
10796
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
@@ -10004,7 +10798,7 @@ function messen(abfrage) {
10004
10798
  }
10005
10799
  function useSchmalesDisplay(abfrage = SCHMALE_ANSICHT) {
10006
10800
  const [schmal, setSchmal] = useState11(() => messen(abfrage));
10007
- useEffect6(() => {
10801
+ useEffect7(() => {
10008
10802
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
10009
10803
  const liste = window.matchMedia(abfrage);
10010
10804
  const aktualisieren = () => setSchmal(liste.matches);
@@ -10431,7 +11225,7 @@ var MediaArea = ({ port }) => {
10431
11225
  };
10432
11226
 
10433
11227
  // src/features/szenen/areas/DeriveArea.tsx
10434
- import { useEffect as useEffect7, useMemo as useMemo3, useRef as useRef3, useState as useState16 } from "react";
11228
+ import { useEffect as useEffect8, useMemo as useMemo3, useRef as useRef4, useState as useState16 } from "react";
10435
11229
 
10436
11230
  // src/features/szenen/areas/RecoveryPanel.tsx
10437
11231
  import { useState as useState14 } from "react";
@@ -10704,7 +11498,7 @@ var MODUS_TEXT = {
10704
11498
  };
10705
11499
  var BelegFeld = ({ titel, text, testId, offen }) => {
10706
11500
  const [kopiert, setKopiert] = useState16(false);
10707
- const feld = useRef3(null);
11501
+ const feld = useRef4(null);
10708
11502
  async function kopieren() {
10709
11503
  try {
10710
11504
  await navigator.clipboard.writeText(text);
@@ -10803,7 +11597,7 @@ var ERSTE_KACHELN = 12;
10803
11597
  function useSichtbareKacheln(alle, wurzel) {
10804
11598
  const [gesehen, setGesehen] = useState16([]);
10805
11599
  const schluessel = alle.join(" ");
10806
- useEffect7(() => {
11600
+ useEffect8(() => {
10807
11601
  const ziel = wurzel.current;
10808
11602
  if (!ziel || typeof IntersectionObserver === "undefined") return void 0;
10809
11603
  const beobachter = new IntersectionObserver((eintraege) => {
@@ -10822,10 +11616,10 @@ function useSichtbareKacheln(alle, wurzel) {
10822
11616
  }
10823
11617
  function useVorschauen(port, mediaRefs) {
10824
11618
  const [stand, setStand] = useState16(() => ({ quellen: /* @__PURE__ */ new Map(), gescheitert: /* @__PURE__ */ new Set() }));
10825
- const gehalten = useRef3(/* @__PURE__ */ new Map());
10826
- const ohneBild = useRef3(/* @__PURE__ */ new Set());
11619
+ const gehalten = useRef4(/* @__PURE__ */ new Map());
11620
+ const ohneBild = useRef4(/* @__PURE__ */ new Set());
10827
11621
  const schluessel = mediaRefs.join(" ");
10828
- useEffect7(() => {
11622
+ useEffect8(() => {
10829
11623
  const bestand = port.imageLibrary;
10830
11624
  if (!bestand?.loadPreview) return void 0;
10831
11625
  const refs = schluessel ? schluessel.split(" ") : [];
@@ -10865,7 +11659,7 @@ function useVorschauen(port, mediaRefs) {
10865
11659
  var BildWaehler = ({ port, gewaehlt, onWaehlen, onEntfernen }) => {
10866
11660
  const [suche, setSuche] = useState16("");
10867
11661
  const [fokus, setFokus] = useState16(null);
10868
- const browser = useRef3(null);
11662
+ const browser = useRef4(null);
10869
11663
  const [items] = usePortQuery(
10870
11664
  () => port.imageLibrary.listItems({ search: suche.trim() || void 0, limit: 60 }),
10871
11665
  [port, suche]
@@ -11092,8 +11886,6 @@ var DeriveArea = ({ port, contextId }) => {
11092
11886
  if (ergebnis.kind === "ok") setLauf(ergebnis.value);
11093
11887
  else if (ergebnis.kind === "unsupported") {
11094
11888
  setMeldung(`Dieser Host bietet \u201E${ergebnis.capability}" nicht an.`);
11095
- } else if (ergebnis.kind === "rejected") {
11096
- setMeldung(ergebnis.errors.map((e) => e.message).join("; "));
11097
11889
  } else {
11098
11890
  setMeldung(ergebnis.message);
11099
11891
  }
@@ -11183,8 +11975,6 @@ ${teile.map((t) => `- ${t}`).join("\n")}`;
11183
11975
  });
11184
11976
  if (ergebnis.kind === "ok") {
11185
11977
  setUebernommen((v) => ({ ...v, [type]: ergebnis.value.elementId }));
11186
- } else if (ergebnis.kind === "rejected") {
11187
- setMeldung(ergebnis.errors.map((e) => e.message).join("; "));
11188
11978
  } else if (ergebnis.kind === "unsupported") {
11189
11979
  setMeldung(`Dieser Host bietet \u201E${ergebnis.capability}" nicht an.`);
11190
11980
  } else {
@@ -14601,7 +15391,7 @@ function createHfImageStore(options) {
14601
15391
  const now = options.now ?? (() => Date.now());
14602
15392
  const versuche = options.leseVersuche ?? 4;
14603
15393
  const basisMs = options.leseBasisMs ?? 400;
14604
- const schlafen = options.schlafen ?? warten;
15394
+ const schlafen2 = options.schlafen ?? warten;
14605
15395
  const abgelegt = /* @__PURE__ */ new Map();
14606
15396
  const vorschauen = /* @__PURE__ */ new Map();
14607
15397
  const bekanntePfade = /* @__PURE__ */ new Set();
@@ -14698,7 +15488,7 @@ function createHfImageStore(options) {
14698
15488
  }
14699
15489
  const pfadTyp = typ ?? "image/png";
14700
15490
  for (let versuch = 0; versuch < versuche; versuch += 1) {
14701
- if (versuch > 0) await schlafen(basisMs * 2 ** (versuch - 1));
15491
+ if (versuch > 0) await schlafen2(basisMs * 2 ** (versuch - 1));
14702
15492
  const base64 = await hfLoadImageAsBase64(
14703
15493
  id,
14704
15494
  token,
@@ -14950,11 +15740,14 @@ function findImageGenerationOrder(events, batchId) {
14950
15740
 
14951
15741
  // src/lib/szenen/imageBatchStore.ts
14952
15742
  var istText6 = (wert) => typeof wert === "string" && wert.trim().length > 0;
14953
- function alsBilder(rohe) {
15743
+ function alsBilder(rohe, generationBatchId) {
14954
15744
  if (!Array.isArray(rohe)) return [];
14955
15745
  return rohe.filter((b) => !!b && typeof b === "object").filter((b) => istText6(b.media_ref) && typeof b.position === "number").map((b) => ({
14956
15746
  media_ref: b.media_ref,
14957
15747
  position: b.position,
15748
+ // Welcher Provideraufruf dieses Bild geliefert hat (§8) — aus dem
15749
+ // Ereignis, in dem es steht, nicht aus seiner Position abgeleitet.
15750
+ generation_batch_id: generationBatchId,
14958
15751
  ...istText6(b.asset_id) ? { asset_id: b.asset_id } : {},
14959
15752
  // Der bestätigte MIME-Typ (Review 2026-08-18 §5 P2) muss durch das
14960
15753
  // Rücklesen hindurch: Genau hier braucht ihn der Lesepfad, um den
@@ -14967,6 +15760,11 @@ function alsBilder(rohe) {
14967
15760
  height: b.height ?? null
14968
15761
  }));
14969
15762
  }
15763
+ function gesamtHerkunft(origins) {
15764
+ const vertreten = new Set(origins.map((o) => o.source_kind));
15765
+ if (vertreten.size === 1) return [...vertreten][0];
15766
+ return "mixed";
15767
+ }
14970
15768
  function collectImageBatches(events) {
14971
15769
  const byItemId = /* @__PURE__ */ new Map();
14972
15770
  for (const event of events) {
@@ -14976,21 +15774,29 @@ function collectImageBatches(events) {
14976
15774
  if (payload.source_kind !== "generated_in_app" && payload.source_kind !== "external_import") continue;
14977
15775
  const bestehend = byItemId.get(payload.item_id);
14978
15776
  if (bestehend?.generation_batch_ids.includes(payload.generation_batch_id)) continue;
14979
- const bilder = alsBilder(payload.images);
15777
+ const bilder = alsBilder(payload.images, payload.generation_batch_id);
15778
+ const herkunft = {
15779
+ generation_batch_id: payload.generation_batch_id,
15780
+ source_kind: payload.source_kind
15781
+ };
14980
15782
  if (!bestehend) {
14981
15783
  byItemId.set(payload.item_id, {
14982
15784
  item_id: payload.item_id,
14983
15785
  prompt_run_id: payload.prompt_run_id,
14984
15786
  generation_batch_ids: [payload.generation_batch_id],
14985
15787
  source_kind: payload.source_kind,
15788
+ origins: [herkunft],
14986
15789
  title: istText6(payload.title) ? payload.title : `Batch aus Lauf ${payload.prompt_run_id}`,
14987
15790
  images: bilder
14988
15791
  });
14989
15792
  continue;
14990
15793
  }
15794
+ const origins = [...bestehend.origins, herkunft];
14991
15795
  byItemId.set(payload.item_id, {
14992
15796
  ...bestehend,
14993
15797
  generation_batch_ids: [...bestehend.generation_batch_ids, payload.generation_batch_id],
15798
+ origins,
15799
+ source_kind: gesamtHerkunft(origins),
14994
15800
  images: [...bestehend.images, ...bilder]
14995
15801
  });
14996
15802
  }
@@ -15109,13 +15915,21 @@ async function legeBatchAb(options, input) {
15109
15915
  `Die Batch-Metadaten fuer ${input.generationBatchId} konnten nicht protokolliert werden, obwohl die Bilder bereits im Bestand liegen: ${error instanceof Error ? error.message : String(error)}`
15110
15916
  );
15111
15917
  }
15918
+ const origins = [
15919
+ ...bestehend?.origins ?? [],
15920
+ { generation_batch_id: input.generationBatchId, source_kind: input.sourceKind }
15921
+ ];
15112
15922
  const zusammengefuehrt = {
15113
15923
  item_id: itemId,
15114
15924
  prompt_run_id: input.runId,
15115
15925
  generation_batch_ids: bestehend ? [...bestehend.generation_batch_ids, input.generationBatchId] : [input.generationBatchId],
15116
- source_kind: bestehend?.source_kind ?? input.sourceKind,
15926
+ source_kind: gesamtHerkunft(origins),
15927
+ origins,
15117
15928
  title: titel,
15118
- images: [...bestehend?.images ?? [], ...abgelegt]
15929
+ images: [
15930
+ ...bestehend?.images ?? [],
15931
+ ...abgelegt.map((bild) => ({ ...bild, generation_batch_id: input.generationBatchId }))
15932
+ ]
15119
15933
  };
15120
15934
  return { kind: "ok", value: zusammengefuehrt };
15121
15935
  }
@@ -15499,6 +16313,20 @@ function createFlowSzenenViewPort(options) {
15499
16313
  }
15500
16314
 
15501
16315
  export {
16316
+ hfListDir,
16317
+ hfDownloadBinaryByPath,
16318
+ hfUploadSmallFile,
16319
+ hfBatchArchive,
16320
+ tsFromEventPath,
16321
+ loadPendingEvents,
16322
+ getSessionClientId,
16323
+ writeHFEvent,
16324
+ getHFToken,
16325
+ setHFToken,
16326
+ loadHFState,
16327
+ hfBootstrapFromLegacy,
16328
+ hfUploadImage,
16329
+ hfLoadImageAsBase64,
15502
16330
  applyEvents,
15503
16331
  capabilitiesOf,
15504
16332
  unsupportedReason,
@@ -15560,6 +16388,15 @@ export {
15560
16388
  preferredHeadRevisionOf,
15561
16389
  collectPromptRuns,
15562
16390
  findPromptRun,
16391
+ DEFAULT_SEED_VORLAGE,
16392
+ parseSeedListe,
16393
+ formatSeedListe,
16394
+ ordneSeedsZu,
16395
+ seedFehlerText,
16396
+ mitDefaultVorlage,
16397
+ UPLOAD_MAX_BYTES,
16398
+ istZipDatei,
16399
+ leseBilderAusZip,
15563
16400
  ladeVorschauen,
15564
16401
  simulatedAnswerFor,
15565
16402
  SIMULATION_PROVIDER,
@@ -15642,6 +16479,7 @@ export {
15642
16479
  collectImageGenerationOrders,
15643
16480
  ordersForPromptRun,
15644
16481
  findImageGenerationOrder,
16482
+ gesamtHerkunft,
15645
16483
  collectImageBatches,
15646
16484
  batchesForPromptRun,
15647
16485
  findImageBatch,