@rslsp1/fa-app-tools 5.1.0 → 6.1.0

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.
@@ -5225,6 +5225,133 @@ function findPromptRun(events, runId) {
5225
5225
  return collectPromptRuns(events).find((run) => run.run_id === runId) ?? null;
5226
5226
  }
5227
5227
 
5228
+ // src/lib/szenen/seedListe.ts
5229
+ var DEFAULT_SEED_VORLAGE = {
5230
+ name: "Default",
5231
+ seeds: "1; 10; 100; 1000",
5232
+ builtin: true
5233
+ };
5234
+ function parseSeedListe(eingabe) {
5235
+ if (!eingabe) return [];
5236
+ return eingabe.split(/[,;]/).map((teil) => teil.trim()).filter((teil) => teil.length > 0);
5237
+ }
5238
+ function formatSeedListe(tokens) {
5239
+ return tokens.join("; ");
5240
+ }
5241
+ var GANZZAHL = /^-?\d+$/;
5242
+ function ordneSeedsZu(eingabe, anzahl) {
5243
+ const tokens = Array.isArray(eingabe) ? [...eingabe] : parseSeedListe(eingabe);
5244
+ const eintraege = [];
5245
+ for (let position = 0; position < anzahl; position++) {
5246
+ const roh = tokens[position] ?? null;
5247
+ if (roh === null) {
5248
+ eintraege.push({ position, roh: null, seed: null, ungueltig: false });
5249
+ continue;
5250
+ }
5251
+ const zahl = GANZZAHL.test(roh) ? Number(roh) : Number.NaN;
5252
+ const gueltig = Number.isSafeInteger(zahl);
5253
+ eintraege.push({
5254
+ position,
5255
+ roh,
5256
+ seed: gueltig ? zahl : null,
5257
+ ungueltig: !gueltig
5258
+ });
5259
+ }
5260
+ return {
5261
+ eintraege,
5262
+ ungueltige: eintraege.filter((e) => e.ungueltig),
5263
+ ueberzaehlig: Math.max(0, tokens.length - anzahl)
5264
+ };
5265
+ }
5266
+ function seedFehlerText(ungueltige) {
5267
+ const teile = ungueltige.map((e) => `Bild ${e.position + 1}: "${e.roh}"`);
5268
+ return `Die Seedliste enth\xE4lt keine ganze Zahl an ${teile.length === 1 ? "Position" : "Positionen"} \u2014 ${teile.join(", ")}. Kein Bild wurde importiert.`;
5269
+ }
5270
+ function mitDefaultVorlage(custom) {
5271
+ return [DEFAULT_SEED_VORLAGE, ...custom.filter((v) => !v.builtin)];
5272
+ }
5273
+
5274
+ // src/lib/szenen/zipBilder.ts
5275
+ var BILD_ENDUNGEN = {
5276
+ png: "image/png",
5277
+ jpg: "image/jpeg",
5278
+ jpeg: "image/jpeg",
5279
+ webp: "image/webp"
5280
+ };
5281
+ var UPLOAD_MAX_BYTES = 45 * 1024 * 1024;
5282
+ function byteLaenge(base64) {
5283
+ const fuellzeichen = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
5284
+ return Math.floor(base64.length * 3 / 4) - fuellzeichen;
5285
+ }
5286
+ function angekuendigteGroesse(eintrag) {
5287
+ const roh = eintrag._data?.uncompressedSize;
5288
+ return typeof roh === "number" && Number.isFinite(roh) && roh >= 0 ? roh : null;
5289
+ }
5290
+ function bildGroesseVerdikt(archiv, pfad, bytes) {
5291
+ if (bytes === null || bytes <= UPLOAD_MAX_BYTES) return null;
5292
+ 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.`;
5293
+ }
5294
+ function mb(bytes) {
5295
+ return (bytes / 1024 / 1024).toFixed(1);
5296
+ }
5297
+ function istZipDatei(datei) {
5298
+ return /\.zip$/i.test(datei.name);
5299
+ }
5300
+ function endungVon(pfad) {
5301
+ const punkt = pfad.lastIndexOf(".");
5302
+ return punkt >= 0 ? pfad.slice(punkt + 1).toLowerCase() : "";
5303
+ }
5304
+ function istImportierbar(pfad, istVerzeichnis) {
5305
+ if (istVerzeichnis) return false;
5306
+ if (pfad.startsWith("__MACOSX/")) return false;
5307
+ const datei = pfad.slice(pfad.lastIndexOf("/") + 1);
5308
+ if (datei.startsWith(".")) return false;
5309
+ return endungVon(datei) in BILD_ENDUNGEN;
5310
+ }
5311
+ function nachPfad(a, b) {
5312
+ return a.localeCompare(b, "en", { numeric: true, sensitivity: "base" }) || (a < b ? -1 : a > b ? 1 : 0);
5313
+ }
5314
+ async function leseBilderAusZip(datei) {
5315
+ const name = datei.name ?? "Archiv";
5316
+ if (datei.size > UPLOAD_MAX_BYTES) {
5317
+ throw new Error(
5318
+ `${name} ist ${mb(datei.size)} MB gro\xDF \u2014 die Grenze liegt bei ${UPLOAD_MAX_BYTES / 1024 / 1024} MB.`
5319
+ );
5320
+ }
5321
+ const { default: JSZip } = await import("jszip");
5322
+ const archiv = await JSZip.loadAsync(datei).catch((fehler4) => {
5323
+ throw new Error(
5324
+ `${name} konnte nicht als ZIP gelesen werden: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
5325
+ );
5326
+ });
5327
+ const pfade = [];
5328
+ archiv.forEach((pfad, eintrag) => {
5329
+ if (istImportierbar(pfad, eintrag.dir)) pfade.push(pfad);
5330
+ });
5331
+ pfade.sort(nachPfad);
5332
+ if (pfade.length === 0) {
5333
+ throw new Error(
5334
+ `${name} enth\xE4lt kein unterst\xFCtztes Bild (erwartet: ${Object.keys(BILD_ENDUNGEN).map((e) => `.${e}`).join(", ")}).`
5335
+ );
5336
+ }
5337
+ const bilder = [];
5338
+ for (const pfad of pfade) {
5339
+ const eintrag = archiv.file(pfad);
5340
+ if (!eintrag) continue;
5341
+ const angekuendigt = bildGroesseVerdikt(name, pfad, angekuendigteGroesse(eintrag));
5342
+ if (angekuendigt) throw new Error(angekuendigt);
5343
+ const base64 = await eintrag.async("base64");
5344
+ const nachgemessen = bildGroesseVerdikt(name, pfad, byteLaenge(base64));
5345
+ if (nachgemessen) throw new Error(nachgemessen);
5346
+ bilder.push({
5347
+ name: pfad,
5348
+ base64,
5349
+ mimeType: BILD_ENDUNGEN[endungVon(pfad)]
5350
+ });
5351
+ }
5352
+ return bilder;
5353
+ }
5354
+
5228
5355
  // src/lib/szenen/bildVorschau.ts
5229
5356
  async function ladeVorschauen(port, mediaRefs, fertig, optionen = {}) {
5230
5357
  const laden = port.loadPreview;
@@ -6242,7 +6369,7 @@ function usePortQuery(frage, abhaengigkeiten) {
6242
6369
  }
6243
6370
 
6244
6371
  // src/features/szenen/areas/ElementDetail.tsx
6245
- import { useEffect as useEffect5, useMemo as useMemo2, useState as useState10 } from "react";
6372
+ import { useEffect as useEffect6, useMemo as useMemo2, useState as useState10 } from "react";
6246
6373
 
6247
6374
  // src/features/szenen/elementLabels.ts
6248
6375
  var LABELS = {
@@ -8334,10 +8461,10 @@ var VersionNavigator = ({
8334
8461
  };
8335
8462
 
8336
8463
  // src/features/szenen/areas/RunsPanel.tsx
8337
- import { useState as useState9 } from "react";
8464
+ import { useEffect as useEffect5, useRef as useRef3, useState as useState9 } from "react";
8338
8465
 
8339
8466
  // src/features/szenen/areas/RunImagesPanel.tsx
8340
- import { useEffect as useEffect4, useRef as useRef2, useState as useState8 } from "react";
8467
+ import React7, { useEffect as useEffect4, useRef as useRef2, useState as useState8 } from "react";
8341
8468
 
8342
8469
  // src/features/szenen/areas/Bildbausteine.tsx
8343
8470
  import { useCallback, useEffect as useEffect3, useState as useState7 } from "react";
@@ -8777,6 +8904,7 @@ function useBildQuellen(port, bilder) {
8777
8904
  const [stand, setStand] = useState8(() => ({ quellen: /* @__PURE__ */ new Map(), fehler: /* @__PURE__ */ new Map() }));
8778
8905
  const gehalten = useRef2(/* @__PURE__ */ new Map());
8779
8906
  const gescheitert = useRef2(/* @__PURE__ */ new Map());
8907
+ const [versuch, setVersuch] = useState8(0);
8780
8908
  const schluessel = bilder.map((b) => `${b.media_ref}|${b.mime_type ?? ""}`).join(" ");
8781
8909
  useEffect4(() => {
8782
8910
  const bestand = port.imageLibrary;
@@ -8785,7 +8913,9 @@ function useBildQuellen(port, bilder) {
8785
8913
  void (async () => {
8786
8914
  for (const bild of bilder) {
8787
8915
  if (abgebrochen) return;
8788
- if (gehalten.current.has(bild.media_ref) || gescheitert.current.has(bild.media_ref)) continue;
8916
+ const vorher = gescheitert.current.get(bild.media_ref);
8917
+ const endgueltig = vorher !== void 0 && !vorher.retryable;
8918
+ if (gehalten.current.has(bild.media_ref) || endgueltig) continue;
8789
8919
  const ergebnis = await bestand.resolveImage(bild.media_ref, bild.mime_type ?? null);
8790
8920
  if (abgebrochen) return;
8791
8921
  if (ergebnis.kind === "ok" && ergebnis.value) {
@@ -8793,8 +8923,12 @@ function useBildQuellen(port, bilder) {
8793
8923
  bild.media_ref,
8794
8924
  `data:${ergebnis.value.mime_type};base64,${ergebnis.value.data}`
8795
8925
  );
8926
+ gescheitert.current.delete(bild.media_ref);
8796
8927
  } else if (ergebnis.kind === "error") {
8797
- gescheitert.current.set(bild.media_ref, ergebnis.message);
8928
+ gescheitert.current.set(bild.media_ref, {
8929
+ message: ergebnis.message,
8930
+ retryable: ergebnis.retryable
8931
+ });
8798
8932
  }
8799
8933
  setStand({
8800
8934
  quellen: new Map(gehalten.current),
@@ -8805,8 +8939,19 @@ function useBildQuellen(port, bilder) {
8805
8939
  return () => {
8806
8940
  abgebrochen = true;
8807
8941
  };
8808
- }, [port, schluessel]);
8809
- return stand;
8942
+ }, [port, schluessel, versuch]);
8943
+ const fehlerNachricht = /* @__PURE__ */ new Map();
8944
+ let wiederholbar = false;
8945
+ for (const [ref, f] of stand.fehler) {
8946
+ fehlerNachricht.set(ref, f.message);
8947
+ if (f.retryable) wiederholbar = true;
8948
+ }
8949
+ return {
8950
+ quellen: stand.quellen,
8951
+ fehler: fehlerNachricht,
8952
+ wiederholbar,
8953
+ erneutVersuchen: () => setVersuch((n) => n + 1)
8954
+ };
8810
8955
  }
8811
8956
  var BildDetails = ({ bild, fehler: fehler4, aufloesbar }) => /* @__PURE__ */ jsxs8("span", { className: "szenen-bilder__meta", "data-testid": `batchbild-${bild.media_ref}`, children: [
8812
8957
  /* @__PURE__ */ jsx8("code", { children: bild.media_ref }),
@@ -8827,8 +8972,17 @@ var BildDetails = ({ bild, fehler: fehler4, aufloesbar }) => /* @__PURE__ */ jsx
8827
8972
  // Serverroute (A6).
8828
8973
  /* @__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
8974
  ] });
8975
+ var HERKUNFT_TEXT = {
8976
+ generated_in_app: "in der App erzeugt",
8977
+ external_import: "extern importiert",
8978
+ mixed: "gemischte Herkunft"
8979
+ };
8980
+ var HERKUNFT_KURZ = {
8981
+ generated_in_app: "in der App erzeugt",
8982
+ external_import: "extern importiert"
8983
+ };
8830
8984
  var BatchKachel = ({ port, batch }) => {
8831
- const { quellen, fehler: fehler4 } = useBildQuellen(port, batch.images);
8985
+ const { quellen, fehler: fehler4, wiederholbar, erneutVersuchen } = useBildQuellen(port, batch.images);
8832
8986
  const aufloesbar = Boolean(port.imageLibrary);
8833
8987
  const bilder = batch.images.map((bild) => ({
8834
8988
  id: bild.media_ref,
@@ -8854,7 +9008,7 @@ var BatchKachel = ({ port, batch }) => {
8854
9008
  /* @__PURE__ */ jsxs8("header", { className: "szenen-bilder__batchkopf", children: [
8855
9009
  /* @__PURE__ */ jsx8("strong", { children: batch.title }),
8856
9010
  /* @__PURE__ */ jsxs8("span", { className: "szenen-bilder__herkunft", children: [
8857
- batch.source_kind === "generated_in_app" ? "in der App erzeugt" : "extern importiert",
9011
+ HERKUNFT_TEXT[batch.source_kind],
8858
9012
  " \xB7 ",
8859
9013
  batch.images.length,
8860
9014
  " Bild(er)"
@@ -8868,6 +9022,16 @@ var BatchKachel = ({ port, batch }) => {
8868
9022
  satzTitel: batch.title
8869
9023
  }
8870
9024
  ),
9025
+ wiederholbar && /* @__PURE__ */ jsx8(
9026
+ "button",
9027
+ {
9028
+ type: "button",
9029
+ className: "szenen-bilder__retry",
9030
+ onClick: erneutVersuchen,
9031
+ "data-testid": `batch-retry-${batch.item_id}`,
9032
+ children: "Bilder erneut laden"
9033
+ }
9034
+ ),
8871
9035
  /* @__PURE__ */ jsxs8("details", { className: "szenen-bilder__technik", "data-testid": `batch-technik-${batch.item_id}`, children: [
8872
9036
  /* @__PURE__ */ jsx8("summary", { children: "Technische Details" }),
8873
9037
  /* @__PURE__ */ jsxs8("p", { className: "szenen-bilder__ids", children: [
@@ -8876,8 +9040,13 @@ var BatchKachel = ({ port, batch }) => {
8876
9040
  /* @__PURE__ */ jsx8("code", { "data-testid": `batch-item-${batch.item_id}`, children: batch.item_id })
8877
9041
  ] }),
8878
9042
  /* @__PURE__ */ jsxs8("span", { children: [
8879
- "Batches ",
8880
- /* @__PURE__ */ jsx8("code", { children: batch.generation_batch_ids.join(", ") })
9043
+ "Batches",
9044
+ " ",
9045
+ batch.origins.length > 0 ? batch.origins.map((h, i) => /* @__PURE__ */ jsxs8(React7.Fragment, { children: [
9046
+ i > 0 ? ", " : "",
9047
+ /* @__PURE__ */ jsx8("code", { children: h.generation_batch_id }),
9048
+ ` (${HERKUNFT_KURZ[h.source_kind]})`
9049
+ ] }, h.generation_batch_id)) : /* @__PURE__ */ jsx8("code", { children: batch.generation_batch_ids.join(", ") })
8881
9050
  ] })
8882
9051
  ] }),
8883
9052
  /* @__PURE__ */ jsx8("ul", { className: "szenen-bilder__batchliste", children: batch.images.map((bild) => /* @__PURE__ */ jsx8("li", { children: /* @__PURE__ */ jsx8(
@@ -9081,21 +9250,114 @@ var ErzeugenAktion = ({ port, lauf, wahl, wahlFehler, onFertig }) => {
9081
9250
  )
9082
9251
  ] });
9083
9252
  };
9253
+ var DEFAULT_VORLAGE_KEY = "__default__";
9084
9254
  var ZuordnenAktion = ({ port, lauf, onFertig }) => {
9085
9255
  const [dateien, setDateien] = useState8([]);
9086
9256
  const [seeds, setSeeds] = useState8({});
9257
+ const [zip, setZip] = useState8(null);
9258
+ const [zipLaedt, setZipLaedt] = useState8(false);
9259
+ const [seedListe, setSeedListe] = useState8("");
9260
+ const [vorlagen, setVorlagen] = useState8([DEFAULT_SEED_VORLAGE]);
9261
+ const [vorlageKey, setVorlageKey] = useState8(DEFAULT_VORLAGE_KEY);
9262
+ const [vorlagenStand, setVorlagenStand] = useState8(0);
9263
+ const [vorlageBenennen, setVorlageBenennen] = useState8(false);
9264
+ const [vorlageName, setVorlageName] = useState8("");
9087
9265
  const [modell, setModell] = useState8("");
9088
9266
  const [itemId, setItemId] = useState8("");
9089
9267
  const [laeuft, setLaeuft] = useState8(false);
9090
9268
  const [notiz, setNotiz] = useState8(null);
9269
+ const auswahlMarke = useRef2(0);
9270
+ const zipAktiv = zip !== null;
9271
+ useEffect4(() => {
9272
+ if (!zipAktiv) return;
9273
+ let abgebrochen = false;
9274
+ void (async () => {
9275
+ const gelesen = port.seedTemplates ? await port.seedTemplates.listSeedTemplates() : null;
9276
+ if (abgebrochen) return;
9277
+ setVorlagen(mitDefaultVorlage(
9278
+ gelesen && gelesen.kind === "ok" ? gelesen.value : []
9279
+ ));
9280
+ })();
9281
+ return () => {
9282
+ abgebrochen = true;
9283
+ };
9284
+ }, [zipAktiv, port, vorlagenStand]);
9091
9285
  if (!port.imageBatchWrite) {
9092
9286
  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
9287
  }
9094
- const importieren = async () => {
9288
+ const zuordnung = zip ? ordneSeedsZu(seedListe, zip.bilder.length) : null;
9289
+ const dateiWahl = async (liste) => {
9290
+ const gewaehlt = [...liste ?? []];
9291
+ const warZip = zip !== null;
9292
+ const meine = auswahlMarke.current += 1;
9293
+ const veraltet = () => auswahlMarke.current !== meine;
9294
+ const zips = gewaehlt.filter(istZipDatei);
9295
+ const nurEinZip = zips.length === 1 && gewaehlt.length === 1;
9296
+ setNotiz(null);
9297
+ setSeeds({});
9298
+ setZip(null);
9299
+ setDateien(zips.length === 0 ? gewaehlt : []);
9300
+ setZipLaedt(nurEinZip);
9301
+ if (zips.length > 0 && zips.length !== gewaehlt.length) {
9302
+ setNotiz({
9303
+ tone: "error",
9304
+ 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.`
9305
+ });
9306
+ return;
9307
+ }
9308
+ if (zips.length > 1) {
9309
+ setNotiz({
9310
+ tone: "error",
9311
+ text: `${zips.length} ZIP-Dateien gew\xE4hlt (${zips.map((z) => z.name).join(", ")}). Ein Import nimmt genau ein ZIP.`
9312
+ });
9313
+ return;
9314
+ }
9315
+ if (!nurEinZip) return;
9316
+ const datei = zips[0];
9317
+ try {
9318
+ const bilder = await leseBilderAusZip(datei);
9319
+ if (veraltet()) return;
9320
+ setZip({ datei, bilder });
9321
+ if (!warZip) {
9322
+ setSeedListe(DEFAULT_SEED_VORLAGE.seeds);
9323
+ setVorlageKey(DEFAULT_VORLAGE_KEY);
9324
+ }
9325
+ } catch (fehler4) {
9326
+ if (veraltet()) return;
9327
+ setNotiz({
9328
+ tone: "error",
9329
+ text: fehler4 instanceof Error ? fehler4.message : String(fehler4)
9330
+ });
9331
+ }
9332
+ if (veraltet()) return;
9333
+ setZipLaedt(false);
9334
+ };
9335
+ const schreiben = async (bilder, generationBatchId, titel) => {
9095
9336
  setLaeuft(true);
9096
9337
  setNotiz(null);
9097
9338
  try {
9098
- const bilder = [];
9339
+ const ergebnis = await port.imageBatchWrite.importBatch({
9340
+ runId: lauf.run_id,
9341
+ generationBatchId,
9342
+ images: bilder,
9343
+ title: titel
9344
+ });
9345
+ setNotiz(
9346
+ ergebnis.kind === "ok" ? { tone: "ok", text: `Importiert als Item ${ergebnis.value.item_id} mit ${bilder.length} Bild(ern).` } : noticeFor(ergebnis, "Externe Bilder importieren")
9347
+ );
9348
+ } catch (fehler4) {
9349
+ setNotiz({
9350
+ tone: "error",
9351
+ text: `Externe Bilder importieren: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
9352
+ });
9353
+ }
9354
+ setLaeuft(false);
9355
+ onFertig();
9356
+ };
9357
+ const importieren = async () => {
9358
+ let bilder;
9359
+ try {
9360
+ bilder = [];
9099
9361
  for (let index = 0; index < dateien.length; index++) {
9100
9362
  const roh = (seeds[index] ?? "").trim();
9101
9363
  const seed = roh === "" ? void 0 : Number.parseInt(roh, 10);
@@ -9108,25 +9370,42 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
9108
9370
  ...seed !== void 0 ? { seed } : {}
9109
9371
  });
9110
9372
  }
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
9373
  } catch (fehler4) {
9123
9374
  setNotiz({
9124
9375
  tone: "error",
9125
9376
  text: `Externe Bilder importieren: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
9126
9377
  });
9378
+ return;
9127
9379
  }
9128
- setLaeuft(false);
9129
- onFertig();
9380
+ await schreiben(
9381
+ bilder,
9382
+ // Vor dem Aufruf gebildet und stabil (D-113): eine Wiederholung
9383
+ // derselben Einreichung erzeugt kein zweites Item.
9384
+ `imp-${lauf.run_id}-${dateien.length}-${dateien.map((d) => d.size).join("_")}`,
9385
+ `Externer Import zu Lauf ${lauf.run_id}`
9386
+ );
9387
+ };
9388
+ const zipImportieren = async () => {
9389
+ if (!zip || !zuordnung) return;
9390
+ if (zuordnung.ungueltige.length > 0) {
9391
+ setNotiz({ tone: "error", text: seedFehlerText(zuordnung.ungueltige) });
9392
+ return;
9393
+ }
9394
+ const bilder = zip.bilder.map((bild, index) => {
9395
+ const seed = zuordnung.eintraege[index]?.seed ?? null;
9396
+ return {
9397
+ imageBase64: bild.base64,
9398
+ ...modell.trim() ? { model: modell.trim() } : {},
9399
+ // Unbekannt bleibt unbekannt: kein Feld statt eines erfundenen
9400
+ // Werts (§6.1).
9401
+ ...seed !== null ? { seed } : {}
9402
+ };
9403
+ });
9404
+ await schreiben(
9405
+ bilder,
9406
+ `imp-${lauf.run_id}-zip-${zip.datei.size}-${bilder.length}`,
9407
+ `Externer Import zu Lauf ${lauf.run_id} (${zip.datei.name})`
9408
+ );
9130
9409
  };
9131
9410
  const zuordnen = async () => {
9132
9411
  setLaeuft(true);
@@ -9144,35 +9423,63 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
9144
9423
  setLaeuft(false);
9145
9424
  onFertig();
9146
9425
  };
9426
+ const vorlageWaehlen = (key) => {
9427
+ setVorlageKey(key);
9428
+ const vorlage = key === DEFAULT_VORLAGE_KEY ? DEFAULT_SEED_VORLAGE : vorlagen.find((v) => String(v.id) === key);
9429
+ if (vorlage) setSeedListe(vorlage.seeds);
9430
+ };
9431
+ const vorlageSpeichern = async () => {
9432
+ const name = vorlageName.trim();
9433
+ if (!name || !seedListe.trim() || !port.seedTemplates?.saveSeedTemplate) return;
9434
+ const ergebnis = await port.seedTemplates.saveSeedTemplate({ name, seeds: seedListe.trim() });
9435
+ if (ergebnis.kind !== "ok") {
9436
+ setNotiz(noticeFor(ergebnis, "Seed-Vorlage speichern"));
9437
+ return;
9438
+ }
9439
+ setVorlageBenennen(false);
9440
+ setVorlageName("");
9441
+ if (ergebnis.value.id !== void 0) setVorlageKey(String(ergebnis.value.id));
9442
+ setVorlagenStand((n) => n + 1);
9443
+ };
9444
+ const vorlageLoeschen = async () => {
9445
+ if (vorlageKey === DEFAULT_VORLAGE_KEY || !port.seedTemplates?.deleteSeedTemplate) return;
9446
+ const ergebnis = await port.seedTemplates.deleteSeedTemplate(vorlageKey);
9447
+ if (ergebnis.kind !== "ok") {
9448
+ setNotiz(noticeFor(ergebnis, "Seed-Vorlage l\xF6schen"));
9449
+ return;
9450
+ }
9451
+ setVorlageKey(DEFAULT_VORLAGE_KEY);
9452
+ setVorlagenStand((n) => n + 1);
9453
+ };
9147
9454
  return /* @__PURE__ */ jsxs8("div", { className: "szenen-bilder__aktion-inhalt", "data-testid": `zuordnen-${lauf.run_id}`, children: [
9148
9455
  /* @__PURE__ */ jsxs8("label", { children: [
9149
- "Bilddateien",
9456
+ "Bilddateien oder ein ZIP",
9150
9457
  /* @__PURE__ */ jsx8(
9151
9458
  "input",
9152
9459
  {
9153
9460
  type: "file",
9154
- accept: "image/*",
9461
+ accept: "image/*,.zip",
9155
9462
  multiple: true,
9156
9463
  "data-testid": `zuordnen-dateien-${lauf.run_id}`,
9157
9464
  onChange: (e) => {
9158
- setDateien([...e.target.files ?? []]);
9159
- setSeeds({});
9465
+ void dateiWahl(e.target.files);
9160
9466
  }
9161
9467
  }
9162
9468
  )
9163
9469
  ] }),
9470
+ zipLaedt && /* @__PURE__ */ jsx8("p", { "data-testid": `zuordnen-zip-laedt-${lauf.run_id}`, children: "ZIP wird gelesen \u2026" }),
9471
+ (dateien.length > 0 || zip) && /* @__PURE__ */ jsxs8("label", { children: [
9472
+ "Modell (f\xFCr alle Bilder, leer = unbekannt)",
9473
+ /* @__PURE__ */ jsx8(
9474
+ "input",
9475
+ {
9476
+ value: modell,
9477
+ "data-testid": `zuordnen-modell-${lauf.run_id}`,
9478
+ onChange: (e) => setModell(e.target.value)
9479
+ }
9480
+ )
9481
+ ] }),
9164
9482
  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
9483
  /* @__PURE__ */ jsx8("ol", { "data-testid": `zuordnen-vorschau-${lauf.run_id}`, children: dateien.map((datei, index) => /* @__PURE__ */ jsxs8("li", { children: [
9177
9484
  /* @__PURE__ */ jsxs8("span", { children: [
9178
9485
  "Position ",
@@ -9206,6 +9513,157 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
9206
9513
  }
9207
9514
  )
9208
9515
  ] }),
9516
+ zip && zuordnung && /* @__PURE__ */ jsxs8("div", { className: "szenen-bilder__zip", "data-testid": `zuordnen-zip-${lauf.run_id}`, children: [
9517
+ /* @__PURE__ */ jsxs8("p", { className: "szenen-bilder__zipkopf", children: [
9518
+ "ZIP ",
9519
+ /* @__PURE__ */ jsx8("code", { children: zip.datei.name }),
9520
+ " \u2014 ",
9521
+ zip.bilder.length,
9522
+ " Bild(er) erkannt"
9523
+ ] }),
9524
+ /* @__PURE__ */ jsxs8("label", { children: [
9525
+ "Seeds (pro Bild ein Seed, Trenner , oder ;)",
9526
+ /* @__PURE__ */ jsx8(
9527
+ "input",
9528
+ {
9529
+ "data-testid": `zuordnen-seedliste-${lauf.run_id}`,
9530
+ value: seedListe,
9531
+ placeholder: "z. B. 1; 10; 100; 1000",
9532
+ onChange: (e) => setSeedListe(e.target.value)
9533
+ }
9534
+ )
9535
+ ] }),
9536
+ /* @__PURE__ */ jsxs8("label", { children: [
9537
+ "Seed-Vorlage",
9538
+ /* @__PURE__ */ jsxs8(
9539
+ "select",
9540
+ {
9541
+ "data-testid": `zuordnen-seedvorlage-${lauf.run_id}`,
9542
+ value: vorlageKey,
9543
+ onChange: (e) => vorlageWaehlen(e.target.value),
9544
+ children: [
9545
+ /* @__PURE__ */ jsxs8("option", { value: DEFAULT_VORLAGE_KEY, children: [
9546
+ "Default (",
9547
+ DEFAULT_SEED_VORLAGE.seeds,
9548
+ ")"
9549
+ ] }),
9550
+ vorlagen.filter((v) => !v.builtin).map((v) => /* @__PURE__ */ jsxs8("option", { value: String(v.id), children: [
9551
+ v.name,
9552
+ " (",
9553
+ v.seeds,
9554
+ ")"
9555
+ ] }, String(v.id)))
9556
+ ]
9557
+ }
9558
+ )
9559
+ ] }),
9560
+ (port.seedTemplates?.saveSeedTemplate || port.seedTemplates?.deleteSeedTemplate) && /* @__PURE__ */ jsxs8("p", { className: "szenen-bilder__zipvorlagen", children: [
9561
+ port.seedTemplates?.saveSeedTemplate && !vorlageBenennen && /* @__PURE__ */ jsx8(
9562
+ "button",
9563
+ {
9564
+ type: "button",
9565
+ "data-testid": `zuordnen-vorlage-neu-${lauf.run_id}`,
9566
+ disabled: !seedListe.trim(),
9567
+ onClick: () => {
9568
+ setVorlageName("");
9569
+ setVorlageBenennen(true);
9570
+ },
9571
+ children: "Als Vorlage speichern"
9572
+ }
9573
+ ),
9574
+ port.seedTemplates?.deleteSeedTemplate && /* @__PURE__ */ jsx8(
9575
+ "button",
9576
+ {
9577
+ type: "button",
9578
+ "data-testid": `zuordnen-vorlage-loeschen-${lauf.run_id}`,
9579
+ disabled: vorlageKey === DEFAULT_VORLAGE_KEY,
9580
+ onClick: () => {
9581
+ void vorlageLoeschen();
9582
+ },
9583
+ children: "Vorlage l\xF6schen"
9584
+ }
9585
+ )
9586
+ ] }),
9587
+ vorlageBenennen && /* @__PURE__ */ jsxs8(Fragment4, { children: [
9588
+ /* @__PURE__ */ jsxs8("label", { children: [
9589
+ "Name der Vorlage",
9590
+ /* @__PURE__ */ jsx8(
9591
+ "input",
9592
+ {
9593
+ "data-testid": `zuordnen-vorlage-name-${lauf.run_id}`,
9594
+ value: vorlageName,
9595
+ onChange: (e) => setVorlageName(e.target.value)
9596
+ }
9597
+ )
9598
+ ] }),
9599
+ /* @__PURE__ */ jsxs8("p", { className: "szenen-bilder__zipvorlagen", children: [
9600
+ /* @__PURE__ */ jsx8(
9601
+ "button",
9602
+ {
9603
+ type: "button",
9604
+ "data-testid": `zuordnen-vorlage-sichern-${lauf.run_id}`,
9605
+ disabled: !vorlageName.trim(),
9606
+ onClick: () => {
9607
+ void vorlageSpeichern();
9608
+ },
9609
+ children: "Speichern"
9610
+ }
9611
+ ),
9612
+ /* @__PURE__ */ jsx8(
9613
+ "button",
9614
+ {
9615
+ type: "button",
9616
+ "data-testid": `zuordnen-vorlage-abbrechen-${lauf.run_id}`,
9617
+ onClick: () => setVorlageBenennen(false),
9618
+ children: "Abbrechen"
9619
+ }
9620
+ )
9621
+ ] })
9622
+ ] }),
9623
+ zuordnung.ueberzaehlig > 0 && /* @__PURE__ */ jsxs8("p", { "data-testid": `zuordnen-ueberzaehlig-${lauf.run_id}`, children: [
9624
+ zuordnung.ueberzaehlig,
9625
+ " Seed(s) mehr als Bilder \u2014 die \xFCberz\xE4hligen werden nicht geschrieben."
9626
+ ] }),
9627
+ /* @__PURE__ */ jsx8(
9628
+ "ol",
9629
+ {
9630
+ className: "szenen-bilder__zipliste",
9631
+ "data-testid": `zuordnen-zip-vorschau-${lauf.run_id}`,
9632
+ children: zip.bilder.map((bild, index) => {
9633
+ const eintrag = zuordnung.eintraege[index];
9634
+ return /* @__PURE__ */ jsxs8("li", { children: [
9635
+ /* @__PURE__ */ jsxs8("span", { children: [
9636
+ "Position ",
9637
+ index,
9638
+ ": ",
9639
+ bild.name
9640
+ ] }),
9641
+ /* @__PURE__ */ jsx8(
9642
+ "span",
9643
+ {
9644
+ "data-testid": `zuordnen-zip-seed-${lauf.run_id}-${index}`,
9645
+ "data-seed": eintrag.seed === null ? "" : String(eintrag.seed),
9646
+ children: eintrag.ungueltig ? `Seed ung\xFCltig: "${eintrag.roh}"` : eintrag.seed === null ? "Seed unbekannt" : `Seed ${eintrag.seed}`
9647
+ }
9648
+ )
9649
+ ] }, `${bild.name}-${index}`);
9650
+ })
9651
+ }
9652
+ ),
9653
+ /* @__PURE__ */ jsxs8(
9654
+ "button",
9655
+ {
9656
+ type: "button",
9657
+ "data-testid": `zuordnen-zip-import-${lauf.run_id}`,
9658
+ disabled: laeuft || zipLaedt,
9659
+ onClick: zipImportieren,
9660
+ children: [
9661
+ zip.bilder.length,
9662
+ " Bild(er) aus ZIP importieren"
9663
+ ]
9664
+ }
9665
+ )
9666
+ ] }),
9209
9667
  /* @__PURE__ */ jsxs8("label", { children: [
9210
9668
  "Oder ein bestehendes Library-Item",
9211
9669
  /* @__PURE__ */ jsx8(
@@ -9261,6 +9719,8 @@ var RunsPanel = ({ port, source, contextId }) => {
9261
9719
  const [nachladen, setNachladen] = useState9(0);
9262
9720
  const faehig = capabilitiesOf(port);
9263
9721
  const quellSchluessel = source.kind === "published" ? source.shotVersionId : source.kind === "draft_chain" ? source.chainId : source.sceneElementId;
9722
+ const aktuelleQuelle = useRef3({ kind: source.kind, id: quellSchluessel });
9723
+ aktuelleQuelle.current = { kind: source.kind, id: quellSchluessel };
9264
9724
  const [phase, neuLaden] = usePortQuery(
9265
9725
  () => {
9266
9726
  if (source.kind === "published") return port.runs.listRunsForShot(source.shotVersionId);
@@ -9271,12 +9731,16 @@ var RunsPanel = ({ port, source, contextId }) => {
9271
9731
  );
9272
9732
  const starten = async () => {
9273
9733
  if (source.kind !== "published") return;
9734
+ const gestartetFuer = { kind: source.kind, id: source.shotVersionId };
9274
9735
  setLaeuft(true);
9275
9736
  const ergebnis = await port.runs.startRun({
9276
9737
  shotVersionId: source.shotVersionId,
9277
9738
  ...anweisung.trim() ? { userInstruction: anweisung.trim() } : {},
9278
9739
  contextId
9279
9740
  });
9741
+ if (aktuelleQuelle.current.kind !== gestartetFuer.kind || aktuelleQuelle.current.id !== gestartetFuer.id) {
9742
+ return;
9743
+ }
9280
9744
  setNotiz(noticeFor(ergebnis, "Prompt-Lauf starten"));
9281
9745
  setLaeuft(false);
9282
9746
  setNachladen((n) => n + 1);
@@ -9286,6 +9750,12 @@ var RunsPanel = ({ port, source, contextId }) => {
9286
9750
  const [gewaehlteRunde, setGewaehlteRunde] = useState9(void 0);
9287
9751
  const neueste = laeufe.length > 0 ? laeufe[laeufe.length - 1].run_id : null;
9288
9752
  const offeneRunde = gewaehlteRunde === void 0 ? neueste : gewaehlteRunde;
9753
+ useEffect5(() => {
9754
+ setGewaehlteRunde(void 0);
9755
+ setAnweisung("");
9756
+ setNotiz(null);
9757
+ setLaeuft(false);
9758
+ }, [source.kind, quellSchluessel]);
9289
9759
  return /* @__PURE__ */ jsxs9("div", { className: "szenen-runs", "data-testid": "laeufe-bereich", "data-shot-version": quellSchluessel, children: [
9290
9760
  /* @__PURE__ */ jsx9("h4", { children: "Prompt-L\xE4ufe" }),
9291
9761
  source.kind === "published" && /* @__PURE__ */ jsxs9("div", { className: "szenen-runs__start", children: [
@@ -9356,6 +9826,26 @@ var Sekundaertext = ({ titel, text, testId }) => {
9356
9826
  )
9357
9827
  ] });
9358
9828
  };
9829
+ var LISTE_ANFANG = 3;
9830
+ var SekundaerListe = ({ titel, eintraege, testId, className }) => {
9831
+ const [alle, setAlle] = useState9(false);
9832
+ const lang = eintraege.length > LISTE_ANFANG;
9833
+ const gezeigt = lang && !alle ? eintraege.slice(0, LISTE_ANFANG) : eintraege;
9834
+ return /* @__PURE__ */ jsxs9("section", { className: "szenen-runs__sekundaer", children: [
9835
+ /* @__PURE__ */ jsx9("h5", { children: titel }),
9836
+ /* @__PURE__ */ jsx9("ul", { className, "data-testid": testId, children: gezeigt.map((eintrag, i) => /* @__PURE__ */ jsx9("li", { children: eintrag }, i)) }),
9837
+ lang && /* @__PURE__ */ jsx9(
9838
+ "button",
9839
+ {
9840
+ type: "button",
9841
+ className: "szenen-runs__mehr",
9842
+ onClick: () => setAlle(!alle),
9843
+ "data-testid": `${testId}-mehr`,
9844
+ children: alle ? "weniger anzeigen" : `${eintraege.length - LISTE_ANFANG} weitere anzeigen`
9845
+ }
9846
+ )
9847
+ ] });
9848
+ };
9359
9849
  var RundenEintrag = ({ port, lauf, runde, offen, onUmschalten, itemMoeglich, onItemGespeichert }) => {
9360
9850
  const [notiz, setNotiz] = useState9(null);
9361
9851
  const [laeuft, setLaeuft] = useState9(false);
@@ -9441,11 +9931,23 @@ var RundenEintrag = ({ port, lauf, runde, offen, onUmschalten, itemMoeglich, onI
9441
9931
  testId: `feedback-${lauf.run_id}`
9442
9932
  }
9443
9933
  ),
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)) })
9934
+ lauf.output.visibility.assumptions.length > 0 && /* @__PURE__ */ jsx9(
9935
+ SekundaerListe,
9936
+ {
9937
+ titel: "Annahmen",
9938
+ eintraege: lauf.output.visibility.assumptions,
9939
+ testId: `annahmen-${lauf.run_id}`
9940
+ }
9941
+ ),
9942
+ lauf.output.notes.length > 0 && /* @__PURE__ */ jsx9(
9943
+ SekundaerListe,
9944
+ {
9945
+ titel: "Notizen",
9946
+ eintraege: lauf.output.notes,
9947
+ testId: `notizen-${lauf.run_id}`,
9948
+ className: "szenen-runs__notizen"
9949
+ }
9950
+ )
9449
9951
  ] }),
9450
9952
  lauf.raw_output && /* @__PURE__ */ jsxs9("details", { "data-testid": `diagnose-${lauf.run_id}`, children: [
9451
9953
  /* @__PURE__ */ jsx9("summary", { children: "Rohantwort (Diagnose)" }),
@@ -9533,7 +10035,7 @@ var ElementDetail = ({
9533
10035
  () => schnappschuss ? fieldsForType(schnappschuss.element.type) : [],
9534
10036
  [schnappschuss?.element.type]
9535
10037
  );
9536
- useEffect5(() => {
10038
+ useEffect6(() => {
9537
10039
  setWahl(null);
9538
10040
  setBasiswechsel(null);
9539
10041
  setNotiz(null);
@@ -9564,7 +10066,7 @@ var ElementDetail = ({
9564
10066
  return {};
9565
10067
  }, [schnappschuss, angezeigt]);
9566
10068
  const anzeigeSchluessel = angezeigt.kind === "version" ? `v:${angezeigt.versionId}` : angezeigt.kind;
9567
- useEffect5(() => {
10069
+ useEffect6(() => {
9568
10070
  if (!schnappschuss) return;
9569
10071
  setEntwurf(formFromPayload(felder, angezeigtePayload));
9570
10072
  }, [
@@ -9996,7 +10498,7 @@ var ElementDetail = ({
9996
10498
  };
9997
10499
 
9998
10500
  // src/features/szenen/areas/useSchmalesDisplay.ts
9999
- import { useEffect as useEffect6, useState as useState11 } from "react";
10501
+ import { useEffect as useEffect7, useState as useState11 } from "react";
10000
10502
  var SCHMALE_ANSICHT = "(max-width: 760px)";
10001
10503
  function messen(abfrage) {
10002
10504
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
@@ -10004,7 +10506,7 @@ function messen(abfrage) {
10004
10506
  }
10005
10507
  function useSchmalesDisplay(abfrage = SCHMALE_ANSICHT) {
10006
10508
  const [schmal, setSchmal] = useState11(() => messen(abfrage));
10007
- useEffect6(() => {
10509
+ useEffect7(() => {
10008
10510
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
10009
10511
  const liste = window.matchMedia(abfrage);
10010
10512
  const aktualisieren = () => setSchmal(liste.matches);
@@ -10431,7 +10933,7 @@ var MediaArea = ({ port }) => {
10431
10933
  };
10432
10934
 
10433
10935
  // src/features/szenen/areas/DeriveArea.tsx
10434
- import { useEffect as useEffect7, useMemo as useMemo3, useRef as useRef3, useState as useState16 } from "react";
10936
+ import { useEffect as useEffect8, useMemo as useMemo3, useRef as useRef4, useState as useState16 } from "react";
10435
10937
 
10436
10938
  // src/features/szenen/areas/RecoveryPanel.tsx
10437
10939
  import { useState as useState14 } from "react";
@@ -10704,7 +11206,7 @@ var MODUS_TEXT = {
10704
11206
  };
10705
11207
  var BelegFeld = ({ titel, text, testId, offen }) => {
10706
11208
  const [kopiert, setKopiert] = useState16(false);
10707
- const feld = useRef3(null);
11209
+ const feld = useRef4(null);
10708
11210
  async function kopieren() {
10709
11211
  try {
10710
11212
  await navigator.clipboard.writeText(text);
@@ -10803,7 +11305,7 @@ var ERSTE_KACHELN = 12;
10803
11305
  function useSichtbareKacheln(alle, wurzel) {
10804
11306
  const [gesehen, setGesehen] = useState16([]);
10805
11307
  const schluessel = alle.join(" ");
10806
- useEffect7(() => {
11308
+ useEffect8(() => {
10807
11309
  const ziel = wurzel.current;
10808
11310
  if (!ziel || typeof IntersectionObserver === "undefined") return void 0;
10809
11311
  const beobachter = new IntersectionObserver((eintraege) => {
@@ -10822,10 +11324,10 @@ function useSichtbareKacheln(alle, wurzel) {
10822
11324
  }
10823
11325
  function useVorschauen(port, mediaRefs) {
10824
11326
  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());
11327
+ const gehalten = useRef4(/* @__PURE__ */ new Map());
11328
+ const ohneBild = useRef4(/* @__PURE__ */ new Set());
10827
11329
  const schluessel = mediaRefs.join(" ");
10828
- useEffect7(() => {
11330
+ useEffect8(() => {
10829
11331
  const bestand = port.imageLibrary;
10830
11332
  if (!bestand?.loadPreview) return void 0;
10831
11333
  const refs = schluessel ? schluessel.split(" ") : [];
@@ -10865,7 +11367,7 @@ function useVorschauen(port, mediaRefs) {
10865
11367
  var BildWaehler = ({ port, gewaehlt, onWaehlen, onEntfernen }) => {
10866
11368
  const [suche, setSuche] = useState16("");
10867
11369
  const [fokus, setFokus] = useState16(null);
10868
- const browser = useRef3(null);
11370
+ const browser = useRef4(null);
10869
11371
  const [items] = usePortQuery(
10870
11372
  () => port.imageLibrary.listItems({ search: suche.trim() || void 0, limit: 60 }),
10871
11373
  [port, suche]
@@ -11092,8 +11594,6 @@ var DeriveArea = ({ port, contextId }) => {
11092
11594
  if (ergebnis.kind === "ok") setLauf(ergebnis.value);
11093
11595
  else if (ergebnis.kind === "unsupported") {
11094
11596
  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
11597
  } else {
11098
11598
  setMeldung(ergebnis.message);
11099
11599
  }
@@ -11183,8 +11683,6 @@ ${teile.map((t) => `- ${t}`).join("\n")}`;
11183
11683
  });
11184
11684
  if (ergebnis.kind === "ok") {
11185
11685
  setUebernommen((v) => ({ ...v, [type]: ergebnis.value.elementId }));
11186
- } else if (ergebnis.kind === "rejected") {
11187
- setMeldung(ergebnis.errors.map((e) => e.message).join("; "));
11188
11686
  } else if (ergebnis.kind === "unsupported") {
11189
11687
  setMeldung(`Dieser Host bietet \u201E${ergebnis.capability}" nicht an.`);
11190
11688
  } else {
@@ -14950,11 +15448,14 @@ function findImageGenerationOrder(events, batchId) {
14950
15448
 
14951
15449
  // src/lib/szenen/imageBatchStore.ts
14952
15450
  var istText6 = (wert) => typeof wert === "string" && wert.trim().length > 0;
14953
- function alsBilder(rohe) {
15451
+ function alsBilder(rohe, generationBatchId) {
14954
15452
  if (!Array.isArray(rohe)) return [];
14955
15453
  return rohe.filter((b) => !!b && typeof b === "object").filter((b) => istText6(b.media_ref) && typeof b.position === "number").map((b) => ({
14956
15454
  media_ref: b.media_ref,
14957
15455
  position: b.position,
15456
+ // Welcher Provideraufruf dieses Bild geliefert hat (§8) — aus dem
15457
+ // Ereignis, in dem es steht, nicht aus seiner Position abgeleitet.
15458
+ generation_batch_id: generationBatchId,
14958
15459
  ...istText6(b.asset_id) ? { asset_id: b.asset_id } : {},
14959
15460
  // Der bestätigte MIME-Typ (Review 2026-08-18 §5 P2) muss durch das
14960
15461
  // Rücklesen hindurch: Genau hier braucht ihn der Lesepfad, um den
@@ -14967,6 +15468,11 @@ function alsBilder(rohe) {
14967
15468
  height: b.height ?? null
14968
15469
  }));
14969
15470
  }
15471
+ function gesamtHerkunft(origins) {
15472
+ const vertreten = new Set(origins.map((o) => o.source_kind));
15473
+ if (vertreten.size === 1) return [...vertreten][0];
15474
+ return "mixed";
15475
+ }
14970
15476
  function collectImageBatches(events) {
14971
15477
  const byItemId = /* @__PURE__ */ new Map();
14972
15478
  for (const event of events) {
@@ -14976,21 +15482,29 @@ function collectImageBatches(events) {
14976
15482
  if (payload.source_kind !== "generated_in_app" && payload.source_kind !== "external_import") continue;
14977
15483
  const bestehend = byItemId.get(payload.item_id);
14978
15484
  if (bestehend?.generation_batch_ids.includes(payload.generation_batch_id)) continue;
14979
- const bilder = alsBilder(payload.images);
15485
+ const bilder = alsBilder(payload.images, payload.generation_batch_id);
15486
+ const herkunft = {
15487
+ generation_batch_id: payload.generation_batch_id,
15488
+ source_kind: payload.source_kind
15489
+ };
14980
15490
  if (!bestehend) {
14981
15491
  byItemId.set(payload.item_id, {
14982
15492
  item_id: payload.item_id,
14983
15493
  prompt_run_id: payload.prompt_run_id,
14984
15494
  generation_batch_ids: [payload.generation_batch_id],
14985
15495
  source_kind: payload.source_kind,
15496
+ origins: [herkunft],
14986
15497
  title: istText6(payload.title) ? payload.title : `Batch aus Lauf ${payload.prompt_run_id}`,
14987
15498
  images: bilder
14988
15499
  });
14989
15500
  continue;
14990
15501
  }
15502
+ const origins = [...bestehend.origins, herkunft];
14991
15503
  byItemId.set(payload.item_id, {
14992
15504
  ...bestehend,
14993
15505
  generation_batch_ids: [...bestehend.generation_batch_ids, payload.generation_batch_id],
15506
+ origins,
15507
+ source_kind: gesamtHerkunft(origins),
14994
15508
  images: [...bestehend.images, ...bilder]
14995
15509
  });
14996
15510
  }
@@ -15109,13 +15623,21 @@ async function legeBatchAb(options, input) {
15109
15623
  `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
15624
  );
15111
15625
  }
15626
+ const origins = [
15627
+ ...bestehend?.origins ?? [],
15628
+ { generation_batch_id: input.generationBatchId, source_kind: input.sourceKind }
15629
+ ];
15112
15630
  const zusammengefuehrt = {
15113
15631
  item_id: itemId,
15114
15632
  prompt_run_id: input.runId,
15115
15633
  generation_batch_ids: bestehend ? [...bestehend.generation_batch_ids, input.generationBatchId] : [input.generationBatchId],
15116
- source_kind: bestehend?.source_kind ?? input.sourceKind,
15634
+ source_kind: gesamtHerkunft(origins),
15635
+ origins,
15117
15636
  title: titel,
15118
- images: [...bestehend?.images ?? [], ...abgelegt]
15637
+ images: [
15638
+ ...bestehend?.images ?? [],
15639
+ ...abgelegt.map((bild) => ({ ...bild, generation_batch_id: input.generationBatchId }))
15640
+ ]
15119
15641
  };
15120
15642
  return { kind: "ok", value: zusammengefuehrt };
15121
15643
  }
@@ -15560,6 +16082,15 @@ export {
15560
16082
  preferredHeadRevisionOf,
15561
16083
  collectPromptRuns,
15562
16084
  findPromptRun,
16085
+ DEFAULT_SEED_VORLAGE,
16086
+ parseSeedListe,
16087
+ formatSeedListe,
16088
+ ordneSeedsZu,
16089
+ seedFehlerText,
16090
+ mitDefaultVorlage,
16091
+ UPLOAD_MAX_BYTES,
16092
+ istZipDatei,
16093
+ leseBilderAusZip,
15563
16094
  ladeVorschauen,
15564
16095
  simulatedAnswerFor,
15565
16096
  SIMULATION_PROVIDER,
@@ -15642,6 +16173,7 @@ export {
15642
16173
  collectImageGenerationOrders,
15643
16174
  ordersForPromptRun,
15644
16175
  findImageGenerationOrder,
16176
+ gesamtHerkunft,
15645
16177
  collectImageBatches,
15646
16178
  batchesForPromptRun,
15647
16179
  findImageBatch,