@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.
package/dist/szenen.js CHANGED
@@ -36,6 +36,7 @@ __export(szenen_exports, {
36
36
  CHAIN_LINK_FIELDS: () => CHAIN_LINK_FIELDS,
37
37
  CapabilityAnchorError: () => CapabilityAnchorError,
38
38
  ContractViolation: () => ContractViolation,
39
+ DEFAULT_SEED_VORLAGE: () => DEFAULT_SEED_VORLAGE,
39
40
  DERIVABLE_TYPES: () => DERIVABLE_TYPES,
40
41
  ELEMENT_VERSION_SCHEMA_FILE: () => ELEMENT_VERSION_SCHEMA_FILE,
41
42
  FLOW_EXECUTION_PATH: () => FLOW_EXECUTION_PATH,
@@ -55,6 +56,7 @@ __export(szenen_exports, {
55
56
  SZENEN_SCHEMAS: () => SZENEN_SCHEMAS,
56
57
  SzenenView: () => SzenenView,
57
58
  UNCHECKED_PUBLISH_RULES: () => UNCHECKED_PUBLISH_RULES,
59
+ UPLOAD_MAX_BYTES: () => UPLOAD_MAX_BYTES,
58
60
  UnsupportedSchemaError: () => UnsupportedSchemaError,
59
61
  VersionDagError: () => VersionDagError,
60
62
  areasFor: () => areasFor,
@@ -143,8 +145,10 @@ __export(szenen_exports, {
143
145
  findSceneDerivationRecovery: () => findSceneDerivationRecovery,
144
146
  firstError: () => firstError,
145
147
  formatSchemaErrors: () => formatSchemaErrors,
148
+ formatSeedListe: () => formatSeedListe,
146
149
  freezeDraftChain: () => freezeDraftChain,
147
150
  freezeSceneWorkingState: () => freezeSceneWorkingState,
151
+ gesamtHerkunft: () => gesamtHerkunft,
148
152
  getSzenenSchemas: () => getSzenenSchemas,
149
153
  gruppiereZuItems: () => gruppiereZuItems,
150
154
  hasPlacementCapability: () => hasPlacementCapability,
@@ -152,16 +156,21 @@ __export(szenen_exports, {
152
156
  isOk: () => isOk,
153
157
  isProjectionCurrent: () => isProjectionCurrent,
154
158
  isSzenenItemId: () => isSzenenItemId,
159
+ istZipDatei: () => istZipDatei,
155
160
  itemIdForRun: () => itemIdForRun,
156
161
  ladeVorschauen: () => ladeVorschauen,
162
+ leseBilderAusZip: () => leseBilderAusZip,
157
163
  linkFor: () => linkFor,
158
164
  locatorTeile: () => locatorTeile,
159
165
  logRevisionOf: () => logRevisionOf,
160
166
  mergeSzenenBatchOrigin: () => mergeSzenenBatchOrigin,
167
+ mitDefaultVorlage: () => mitDefaultVorlage,
161
168
  noticeFor: () => noticeFor,
162
169
  ordersForPromptRun: () => ordersForPromptRun,
170
+ ordneSeedsZu: () => ordneSeedsZu,
163
171
  parseDerivationResponse: () => parseDerivationResponse,
164
172
  parseRunResponse: () => parseRunResponse,
173
+ parseSeedListe: () => parseSeedListe,
165
174
  preferredHeadRevisionOf: () => preferredHeadRevisionOf,
166
175
  preferredVersionId: () => preferredVersionId,
167
176
  promptDiagnosticsOf: () => promptDiagnosticsOf,
@@ -185,6 +194,7 @@ __export(szenen_exports, {
185
194
  runStoreDifferential: () => runStoreDifferential,
186
195
  sceneOnlyCapabilities: () => sceneOnlyCapabilities,
187
196
  scopeOfPath: () => scopeOfPath,
197
+ seedFehlerText: () => seedFehlerText,
188
198
  selectableTargetModels: () => selectableTargetModels,
189
199
  showChain: () => showChain,
190
200
  simulatedAnswerFor: () => simulatedAnswerFor,
@@ -10467,23 +10477,94 @@ async function ladeVorschauen(port, mediaRefs, fertig, optionen = {}) {
10467
10477
  );
10468
10478
  }
10469
10479
 
10480
+ // src/lib/szenen/imageBatchStore.ts
10481
+ var istText5 = (wert) => typeof wert === "string" && wert.trim().length > 0;
10482
+ function alsBilder(rohe, generationBatchId) {
10483
+ if (!Array.isArray(rohe)) return [];
10484
+ return rohe.filter((b) => !!b && typeof b === "object").filter((b) => istText5(b.media_ref) && typeof b.position === "number").map((b) => ({
10485
+ media_ref: b.media_ref,
10486
+ position: b.position,
10487
+ // Welcher Provideraufruf dieses Bild geliefert hat (§8) — aus dem
10488
+ // Ereignis, in dem es steht, nicht aus seiner Position abgeleitet.
10489
+ generation_batch_id: generationBatchId,
10490
+ ...istText5(b.asset_id) ? { asset_id: b.asset_id } : {},
10491
+ // Der bestätigte MIME-Typ (Review 2026-08-18 §5 P2) muss durch das
10492
+ // Rücklesen hindurch: Genau hier braucht ihn der Lesepfad, um den
10493
+ // Dateipfad `images/<id>.<ext>` zu bilden, statt ihn zu erraten. Nur
10494
+ // gesetzt, wenn er im Ereignis steht — ältere Batches tragen ihn nicht.
10495
+ ...istText5(b.mime_type) ? { mime_type: b.mime_type } : {},
10496
+ model: b.model ?? null,
10497
+ seed: b.seed ?? null,
10498
+ width: b.width ?? null,
10499
+ height: b.height ?? null
10500
+ }));
10501
+ }
10502
+ function gesamtHerkunft(origins) {
10503
+ const vertreten = new Set(origins.map((o) => o.source_kind));
10504
+ if (vertreten.size === 1) return [...vertreten][0];
10505
+ return "mixed";
10506
+ }
10507
+ function collectImageBatches(events) {
10508
+ const byItemId = /* @__PURE__ */ new Map();
10509
+ for (const event of events) {
10510
+ if (event.type !== SZENEN_EVENT_TYPES.imageBatchPersisted) continue;
10511
+ const payload = event.payload;
10512
+ if (!payload || !istText5(payload.item_id) || !istText5(payload.prompt_run_id) || !istText5(payload.generation_batch_id)) continue;
10513
+ if (payload.source_kind !== "generated_in_app" && payload.source_kind !== "external_import") continue;
10514
+ const bestehend = byItemId.get(payload.item_id);
10515
+ if (bestehend?.generation_batch_ids.includes(payload.generation_batch_id)) continue;
10516
+ const bilder = alsBilder(payload.images, payload.generation_batch_id);
10517
+ const herkunft = {
10518
+ generation_batch_id: payload.generation_batch_id,
10519
+ source_kind: payload.source_kind
10520
+ };
10521
+ if (!bestehend) {
10522
+ byItemId.set(payload.item_id, {
10523
+ item_id: payload.item_id,
10524
+ prompt_run_id: payload.prompt_run_id,
10525
+ generation_batch_ids: [payload.generation_batch_id],
10526
+ source_kind: payload.source_kind,
10527
+ origins: [herkunft],
10528
+ title: istText5(payload.title) ? payload.title : `Batch aus Lauf ${payload.prompt_run_id}`,
10529
+ images: bilder
10530
+ });
10531
+ continue;
10532
+ }
10533
+ const origins = [...bestehend.origins, herkunft];
10534
+ byItemId.set(payload.item_id, {
10535
+ ...bestehend,
10536
+ generation_batch_ids: [...bestehend.generation_batch_ids, payload.generation_batch_id],
10537
+ origins,
10538
+ source_kind: gesamtHerkunft(origins),
10539
+ images: [...bestehend.images, ...bilder]
10540
+ });
10541
+ }
10542
+ return [...byItemId.values()];
10543
+ }
10544
+ function batchesForPromptRun(events, runId) {
10545
+ return collectImageBatches(events).filter((b) => b.prompt_run_id === runId);
10546
+ }
10547
+ function findImageBatch(events, batchId) {
10548
+ return collectImageBatches(events).find((b) => b.generation_batch_ids.includes(batchId)) ?? null;
10549
+ }
10550
+
10470
10551
  // src/lib/szenen/imageGenerationCapability.ts
10471
10552
  var isObject11 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
10472
- var istText5 = (v) => typeof v === "string" && v.trim().length > 0;
10553
+ var istText6 = (v) => typeof v === "string" && v.trim().length > 0;
10473
10554
  function checkImageExecutionProfile(profile) {
10474
10555
  const befunde = [];
10475
10556
  if (!isObject11(profile)) return ["Das Ausfuehrungsprofil ist kein Objekt."];
10476
10557
  for (const feld of ["profile_id", "execution_path", "execution_model_id"]) {
10477
- if (!istText5(profile[feld])) befunde.push(`"${feld}" fehlt als nicht-leerer Text.`);
10558
+ if (!istText6(profile[feld])) befunde.push(`"${feld}" fehlt als nicht-leerer Text.`);
10478
10559
  }
10479
10560
  if (typeof profile.enabled !== "boolean") befunde.push('"enabled" fehlt als Boolean.');
10480
- if (!Array.isArray(profile.environments) || profile.environments.length === 0 || !profile.environments.every(istText5)) {
10561
+ if (!Array.isArray(profile.environments) || profile.environments.length === 0 || !profile.environments.every(istText6)) {
10481
10562
  befunde.push('"environments" fehlt als nicht-leere Liste von Umgebungskennungen.');
10482
10563
  }
10483
10564
  if (!Number.isInteger(profile.max_images) || profile.max_images < 1) {
10484
10565
  befunde.push(`"max_images" ist ${JSON.stringify(profile.max_images)}; erwartet eine ganze Zahl >= 1.`);
10485
10566
  }
10486
- if (!Array.isArray(profile.aspect_ratios) || !profile.aspect_ratios.every(istText5)) {
10567
+ if (!Array.isArray(profile.aspect_ratios) || !profile.aspect_ratios.every(istText6)) {
10487
10568
  befunde.push('"aspect_ratios" fehlt als Liste von Seitenverhaeltnissen (darf leer sein).');
10488
10569
  }
10489
10570
  if (typeof profile.accepts_seed !== "boolean") befunde.push('"accepts_seed" fehlt als Boolean.');
@@ -10495,12 +10576,12 @@ function checkImageExecutionProfile(profile) {
10495
10576
  function checkImageExecutionProfiles(profiles) {
10496
10577
  const anzahlJeKennung = /* @__PURE__ */ new Map();
10497
10578
  for (const profil of profiles ?? []) {
10498
- if (!isObject11(profil) || !istText5(profil.profile_id)) continue;
10579
+ if (!isObject11(profil) || !istText6(profil.profile_id)) continue;
10499
10580
  anzahlJeKennung.set(profil.profile_id, (anzahlJeKennung.get(profil.profile_id) ?? 0) + 1);
10500
10581
  }
10501
10582
  return (profiles ?? []).map((profil) => {
10502
10583
  const befunde = [...checkImageExecutionProfile(profil)];
10503
- if (isObject11(profil) && istText5(profil.profile_id) && (anzahlJeKennung.get(profil.profile_id) ?? 0) > 1) {
10584
+ if (isObject11(profil) && istText6(profil.profile_id) && (anzahlJeKennung.get(profil.profile_id) ?? 0) > 1) {
10504
10585
  befunde.push(
10505
10586
  `Die profile_id "${profil.profile_id}" kommt in diesem Zielmodell mehrfach vor \u2014 die Auswahl waere nicht mehr eindeutig aufloesbar.`
10506
10587
  );
@@ -10511,7 +10592,7 @@ function checkImageExecutionProfiles(profiles) {
10511
10592
  function executableImageModels(promptTargetModelKey, targets, capability) {
10512
10593
  const models = [];
10513
10594
  const invalid = [];
10514
- if (!istText5(capability?.environment_id) || !Array.isArray(capability?.execution_paths)) {
10595
+ if (!istText6(capability?.environment_id) || !Array.isArray(capability?.execution_paths)) {
10515
10596
  return {
10516
10597
  prompt_target_model_key: promptTargetModelKey,
10517
10598
  models: [],
@@ -10525,7 +10606,7 @@ function executableImageModels(promptTargetModelKey, targets, capability) {
10525
10606
  for (const [index, roh] of (target.executionProfiles ?? []).entries()) {
10526
10607
  const befunde = profilBefunde[index] ?? [];
10527
10608
  if (befunde.length > 0) {
10528
- const kennung = isObject11(roh) && istText5(roh.profile_id) ? roh.profile_id : "(ohne profile_id)";
10609
+ const kennung = isObject11(roh) && istText6(roh.profile_id) ? roh.profile_id : "(ohne profile_id)";
10529
10610
  invalid.push(`${target.key}/${kennung}: ${befunde.join(" ")}`);
10530
10611
  continue;
10531
10612
  }
@@ -11226,11 +11307,16 @@ function createMemoryViewPort(options = {}) {
11226
11307
  const neueBilder = input.images.map((bild, offset) => ({
11227
11308
  media_ref: `${input.generationBatchId}-${startPosition + offset}`,
11228
11309
  position: startPosition + offset,
11310
+ generation_batch_id: input.generationBatchId,
11229
11311
  model: bild.model ?? null,
11230
11312
  seed: bild.seed ?? null,
11231
11313
  width: bild.width ?? null,
11232
11314
  height: bild.height ?? null
11233
11315
  }));
11316
+ const importOrigins = [
11317
+ ...vorhandenesItem?.origins ?? [],
11318
+ { generation_batch_id: input.generationBatchId, source_kind: "external_import" }
11319
+ ];
11234
11320
  const neu = {
11235
11321
  item_id: vorhandenesItem?.item_id ?? `szn-mem-batch-${input.generationBatchId}`,
11236
11322
  prompt_run_id: input.runId,
@@ -11238,7 +11324,8 @@ function createMemoryViewPort(options = {}) {
11238
11324
  ...vorhandenesItem?.generation_batch_ids ?? [],
11239
11325
  input.generationBatchId
11240
11326
  ],
11241
- source_kind: vorhandenesItem?.source_kind ?? "external_import",
11327
+ source_kind: gesamtHerkunft(importOrigins),
11328
+ origins: importOrigins,
11242
11329
  title: vorhandenesItem?.title ?? input.title ?? `Batch aus Lauf ${input.runId}`,
11243
11330
  images: [...vorhandenesItem?.images ?? [], ...neueBilder]
11244
11331
  };
@@ -11292,11 +11379,16 @@ function createMemoryViewPort(options = {}) {
11292
11379
  const neueBilder = input.images.map((bild, offset) => ({
11293
11380
  media_ref: `${input.generationBatchId}-${startPosition + offset}`,
11294
11381
  position: startPosition + offset,
11382
+ generation_batch_id: input.generationBatchId,
11295
11383
  model: bild.model ?? null,
11296
11384
  seed: bild.seed ?? null,
11297
11385
  width: bild.width ?? null,
11298
11386
  height: bild.height ?? null
11299
11387
  }));
11388
+ const genOrigins = [
11389
+ ...vorhandenesItem?.origins ?? [],
11390
+ { generation_batch_id: input.generationBatchId, source_kind: "generated_in_app" }
11391
+ ];
11300
11392
  const neu = {
11301
11393
  item_id: vorhandenesItem?.item_id ?? `szn-mem-gen-${input.generationBatchId}`,
11302
11394
  prompt_run_id: input.runId,
@@ -11304,7 +11396,8 @@ function createMemoryViewPort(options = {}) {
11304
11396
  ...vorhandenesItem?.generation_batch_ids ?? [],
11305
11397
  input.generationBatchId
11306
11398
  ],
11307
- source_kind: "generated_in_app",
11399
+ source_kind: gesamtHerkunft(genOrigins),
11400
+ origins: genOrigins,
11308
11401
  title: vorhandenesItem?.title ?? `Batch aus Lauf ${input.runId}`,
11309
11402
  images: [...vorhandenesItem?.images ?? [], ...neueBilder]
11310
11403
  };
@@ -11335,6 +11428,31 @@ function createMemoryViewPort(options = {}) {
11335
11428
  }
11336
11429
  }
11337
11430
  });
11431
+ const seedVorlagen = [...options.seedTemplates ?? []];
11432
+ let seedVorlagenZaehler = 0;
11433
+ const seedTemplatePort = {
11434
+ async listSeedTemplates() {
11435
+ if (options.seedTemplatesFehler) {
11436
+ return { kind: "error", message: "Vorlagenbestand nicht lesbar (Simulation).", retryable: true };
11437
+ }
11438
+ return ok2(seedVorlagen);
11439
+ },
11440
+ async saveSeedTemplate(input) {
11441
+ const vorlage = {
11442
+ id: `seedtpl-${seedVorlagenZaehler += 1}`,
11443
+ name: input.name,
11444
+ seeds: input.seeds
11445
+ };
11446
+ seedVorlagen.push(vorlage);
11447
+ return done(vorlage);
11448
+ },
11449
+ async deleteSeedTemplate(id) {
11450
+ const index = seedVorlagen.findIndex((v) => String(v.id) === String(id));
11451
+ if (index < 0) return { kind: "error", message: `Seed-Vorlage nicht gefunden: ${id}`, retryable: false };
11452
+ seedVorlagen.splice(index, 1);
11453
+ return done(null);
11454
+ }
11455
+ };
11338
11456
  const port = {
11339
11457
  elements,
11340
11458
  context: {
@@ -11352,6 +11470,9 @@ function createMemoryViewPort(options = {}) {
11352
11470
  if (options.derivation !== false) port.derivation = derivationPort;
11353
11471
  if (options.imageBatchHistory !== false) port.imageBatchHistory = imageBatchHistoryPort;
11354
11472
  if (options.imageBatchWrite !== false) port.imageBatchWrite = imageBatchWritePort;
11473
+ if (options.seedTemplates !== void 0 || options.seedTemplatesFehler) {
11474
+ port.seedTemplates = seedTemplatePort;
11475
+ }
11355
11476
  if (options.imageGeneration !== false) port.imageGeneration = imageGenerationPort;
11356
11477
  return port;
11357
11478
  }
@@ -12454,61 +12575,6 @@ function createFlowImageProvider(generateImage) {
12454
12575
  };
12455
12576
  }
12456
12577
 
12457
- // src/lib/szenen/imageBatchStore.ts
12458
- var istText6 = (wert) => typeof wert === "string" && wert.trim().length > 0;
12459
- function alsBilder(rohe) {
12460
- if (!Array.isArray(rohe)) return [];
12461
- return rohe.filter((b) => !!b && typeof b === "object").filter((b) => istText6(b.media_ref) && typeof b.position === "number").map((b) => ({
12462
- media_ref: b.media_ref,
12463
- position: b.position,
12464
- ...istText6(b.asset_id) ? { asset_id: b.asset_id } : {},
12465
- // Der bestätigte MIME-Typ (Review 2026-08-18 §5 P2) muss durch das
12466
- // Rücklesen hindurch: Genau hier braucht ihn der Lesepfad, um den
12467
- // Dateipfad `images/<id>.<ext>` zu bilden, statt ihn zu erraten. Nur
12468
- // gesetzt, wenn er im Ereignis steht — ältere Batches tragen ihn nicht.
12469
- ...istText6(b.mime_type) ? { mime_type: b.mime_type } : {},
12470
- model: b.model ?? null,
12471
- seed: b.seed ?? null,
12472
- width: b.width ?? null,
12473
- height: b.height ?? null
12474
- }));
12475
- }
12476
- function collectImageBatches(events) {
12477
- const byItemId = /* @__PURE__ */ new Map();
12478
- for (const event of events) {
12479
- if (event.type !== SZENEN_EVENT_TYPES.imageBatchPersisted) continue;
12480
- const payload = event.payload;
12481
- if (!payload || !istText6(payload.item_id) || !istText6(payload.prompt_run_id) || !istText6(payload.generation_batch_id)) continue;
12482
- if (payload.source_kind !== "generated_in_app" && payload.source_kind !== "external_import") continue;
12483
- const bestehend = byItemId.get(payload.item_id);
12484
- if (bestehend?.generation_batch_ids.includes(payload.generation_batch_id)) continue;
12485
- const bilder = alsBilder(payload.images);
12486
- if (!bestehend) {
12487
- byItemId.set(payload.item_id, {
12488
- item_id: payload.item_id,
12489
- prompt_run_id: payload.prompt_run_id,
12490
- generation_batch_ids: [payload.generation_batch_id],
12491
- source_kind: payload.source_kind,
12492
- title: istText6(payload.title) ? payload.title : `Batch aus Lauf ${payload.prompt_run_id}`,
12493
- images: bilder
12494
- });
12495
- continue;
12496
- }
12497
- byItemId.set(payload.item_id, {
12498
- ...bestehend,
12499
- generation_batch_ids: [...bestehend.generation_batch_ids, payload.generation_batch_id],
12500
- images: [...bestehend.images, ...bilder]
12501
- });
12502
- }
12503
- return [...byItemId.values()];
12504
- }
12505
- function batchesForPromptRun(events, runId) {
12506
- return collectImageBatches(events).filter((b) => b.prompt_run_id === runId);
12507
- }
12508
- function findImageBatch(events, batchId) {
12509
- return collectImageBatches(events).find((b) => b.generation_batch_ids.includes(batchId)) ?? null;
12510
- }
12511
-
12512
12578
  // src/lib/szenen/hfImageBatchHistory.ts
12513
12579
  function lesefehler(aktion, error) {
12514
12580
  return {
@@ -12615,13 +12681,21 @@ async function legeBatchAb(options, input) {
12615
12681
  `Die Batch-Metadaten fuer ${input.generationBatchId} konnten nicht protokolliert werden, obwohl die Bilder bereits im Bestand liegen: ${error instanceof Error ? error.message : String(error)}`
12616
12682
  );
12617
12683
  }
12684
+ const origins = [
12685
+ ...bestehend?.origins ?? [],
12686
+ { generation_batch_id: input.generationBatchId, source_kind: input.sourceKind }
12687
+ ];
12618
12688
  const zusammengefuehrt = {
12619
12689
  item_id: itemId,
12620
12690
  prompt_run_id: input.runId,
12621
12691
  generation_batch_ids: bestehend ? [...bestehend.generation_batch_ids, input.generationBatchId] : [input.generationBatchId],
12622
- source_kind: bestehend?.source_kind ?? input.sourceKind,
12692
+ source_kind: gesamtHerkunft(origins),
12693
+ origins,
12623
12694
  title: titel,
12624
- images: [...bestehend?.images ?? [], ...abgelegt]
12695
+ images: [
12696
+ ...bestehend?.images ?? [],
12697
+ ...abgelegt.map((bild) => ({ ...bild, generation_batch_id: input.generationBatchId }))
12698
+ ]
12625
12699
  };
12626
12700
  return { kind: "ok", value: zusammengefuehrt };
12627
12701
  }
@@ -13440,6 +13514,133 @@ function checkSceneDerivationRecoveryRecord(candidate) {
13440
13514
  return { ok: true };
13441
13515
  }
13442
13516
 
13517
+ // src/lib/szenen/seedListe.ts
13518
+ var DEFAULT_SEED_VORLAGE = {
13519
+ name: "Default",
13520
+ seeds: "1; 10; 100; 1000",
13521
+ builtin: true
13522
+ };
13523
+ function parseSeedListe(eingabe) {
13524
+ if (!eingabe) return [];
13525
+ return eingabe.split(/[,;]/).map((teil) => teil.trim()).filter((teil) => teil.length > 0);
13526
+ }
13527
+ function formatSeedListe(tokens) {
13528
+ return tokens.join("; ");
13529
+ }
13530
+ var GANZZAHL = /^-?\d+$/;
13531
+ function ordneSeedsZu(eingabe, anzahl) {
13532
+ const tokens = Array.isArray(eingabe) ? [...eingabe] : parseSeedListe(eingabe);
13533
+ const eintraege = [];
13534
+ for (let position = 0; position < anzahl; position++) {
13535
+ const roh = tokens[position] ?? null;
13536
+ if (roh === null) {
13537
+ eintraege.push({ position, roh: null, seed: null, ungueltig: false });
13538
+ continue;
13539
+ }
13540
+ const zahl = GANZZAHL.test(roh) ? Number(roh) : Number.NaN;
13541
+ const gueltig = Number.isSafeInteger(zahl);
13542
+ eintraege.push({
13543
+ position,
13544
+ roh,
13545
+ seed: gueltig ? zahl : null,
13546
+ ungueltig: !gueltig
13547
+ });
13548
+ }
13549
+ return {
13550
+ eintraege,
13551
+ ungueltige: eintraege.filter((e) => e.ungueltig),
13552
+ ueberzaehlig: Math.max(0, tokens.length - anzahl)
13553
+ };
13554
+ }
13555
+ function seedFehlerText(ungueltige) {
13556
+ const teile = ungueltige.map((e) => `Bild ${e.position + 1}: "${e.roh}"`);
13557
+ return `Die Seedliste enth\xE4lt keine ganze Zahl an ${teile.length === 1 ? "Position" : "Positionen"} \u2014 ${teile.join(", ")}. Kein Bild wurde importiert.`;
13558
+ }
13559
+ function mitDefaultVorlage(custom) {
13560
+ return [DEFAULT_SEED_VORLAGE, ...custom.filter((v) => !v.builtin)];
13561
+ }
13562
+
13563
+ // src/lib/szenen/zipBilder.ts
13564
+ var BILD_ENDUNGEN = {
13565
+ png: "image/png",
13566
+ jpg: "image/jpeg",
13567
+ jpeg: "image/jpeg",
13568
+ webp: "image/webp"
13569
+ };
13570
+ var UPLOAD_MAX_BYTES = 45 * 1024 * 1024;
13571
+ function byteLaenge(base64) {
13572
+ const fuellzeichen = base64.endsWith("==") ? 2 : base64.endsWith("=") ? 1 : 0;
13573
+ return Math.floor(base64.length * 3 / 4) - fuellzeichen;
13574
+ }
13575
+ function angekuendigteGroesse(eintrag) {
13576
+ const roh = eintrag._data?.uncompressedSize;
13577
+ return typeof roh === "number" && Number.isFinite(roh) && roh >= 0 ? roh : null;
13578
+ }
13579
+ function bildGroesseVerdikt(archiv, pfad, bytes) {
13580
+ if (bytes === null || bytes <= UPLOAD_MAX_BYTES) return null;
13581
+ 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.`;
13582
+ }
13583
+ function mb(bytes) {
13584
+ return (bytes / 1024 / 1024).toFixed(1);
13585
+ }
13586
+ function istZipDatei(datei) {
13587
+ return /\.zip$/i.test(datei.name);
13588
+ }
13589
+ function endungVon(pfad) {
13590
+ const punkt = pfad.lastIndexOf(".");
13591
+ return punkt >= 0 ? pfad.slice(punkt + 1).toLowerCase() : "";
13592
+ }
13593
+ function istImportierbar(pfad, istVerzeichnis) {
13594
+ if (istVerzeichnis) return false;
13595
+ if (pfad.startsWith("__MACOSX/")) return false;
13596
+ const datei = pfad.slice(pfad.lastIndexOf("/") + 1);
13597
+ if (datei.startsWith(".")) return false;
13598
+ return endungVon(datei) in BILD_ENDUNGEN;
13599
+ }
13600
+ function nachPfad(a, b) {
13601
+ return a.localeCompare(b, "en", { numeric: true, sensitivity: "base" }) || (a < b ? -1 : a > b ? 1 : 0);
13602
+ }
13603
+ async function leseBilderAusZip(datei) {
13604
+ const name = datei.name ?? "Archiv";
13605
+ if (datei.size > UPLOAD_MAX_BYTES) {
13606
+ throw new Error(
13607
+ `${name} ist ${mb(datei.size)} MB gro\xDF \u2014 die Grenze liegt bei ${UPLOAD_MAX_BYTES / 1024 / 1024} MB.`
13608
+ );
13609
+ }
13610
+ const { default: JSZip2 } = await import("jszip");
13611
+ const archiv = await JSZip2.loadAsync(datei).catch((fehler4) => {
13612
+ throw new Error(
13613
+ `${name} konnte nicht als ZIP gelesen werden: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
13614
+ );
13615
+ });
13616
+ const pfade = [];
13617
+ archiv.forEach((pfad, eintrag) => {
13618
+ if (istImportierbar(pfad, eintrag.dir)) pfade.push(pfad);
13619
+ });
13620
+ pfade.sort(nachPfad);
13621
+ if (pfade.length === 0) {
13622
+ throw new Error(
13623
+ `${name} enth\xE4lt kein unterst\xFCtztes Bild (erwartet: ${Object.keys(BILD_ENDUNGEN).map((e) => `.${e}`).join(", ")}).`
13624
+ );
13625
+ }
13626
+ const bilder = [];
13627
+ for (const pfad of pfade) {
13628
+ const eintrag = archiv.file(pfad);
13629
+ if (!eintrag) continue;
13630
+ const angekuendigt = bildGroesseVerdikt(name, pfad, angekuendigteGroesse(eintrag));
13631
+ if (angekuendigt) throw new Error(angekuendigt);
13632
+ const base64 = await eintrag.async("base64");
13633
+ const nachgemessen = bildGroesseVerdikt(name, pfad, byteLaenge(base64));
13634
+ if (nachgemessen) throw new Error(nachgemessen);
13635
+ bilder.push({
13636
+ name: pfad,
13637
+ base64,
13638
+ mimeType: BILD_ENDUNGEN[endungVon(pfad)]
13639
+ });
13640
+ }
13641
+ return bilder;
13642
+ }
13643
+
13443
13644
  // src/features/szenen/SzenenView.tsx
13444
13645
  var import_react17 = require("react");
13445
13646
 
@@ -15661,7 +15862,7 @@ var VersionNavigator = ({
15661
15862
  var import_react9 = require("react");
15662
15863
 
15663
15864
  // src/features/szenen/areas/RunImagesPanel.tsx
15664
- var import_react8 = require("react");
15865
+ var import_react8 = __toESM(require("react"));
15665
15866
 
15666
15867
  // src/features/szenen/areas/Bildbausteine.tsx
15667
15868
  var import_react7 = require("react");
@@ -16101,6 +16302,7 @@ function useBildQuellen(port, bilder) {
16101
16302
  const [stand, setStand] = (0, import_react8.useState)(() => ({ quellen: /* @__PURE__ */ new Map(), fehler: /* @__PURE__ */ new Map() }));
16102
16303
  const gehalten = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
16103
16304
  const gescheitert = (0, import_react8.useRef)(/* @__PURE__ */ new Map());
16305
+ const [versuch, setVersuch] = (0, import_react8.useState)(0);
16104
16306
  const schluessel = bilder.map((b) => `${b.media_ref}|${b.mime_type ?? ""}`).join(" ");
16105
16307
  (0, import_react8.useEffect)(() => {
16106
16308
  const bestand = port.imageLibrary;
@@ -16109,7 +16311,9 @@ function useBildQuellen(port, bilder) {
16109
16311
  void (async () => {
16110
16312
  for (const bild of bilder) {
16111
16313
  if (abgebrochen) return;
16112
- if (gehalten.current.has(bild.media_ref) || gescheitert.current.has(bild.media_ref)) continue;
16314
+ const vorher = gescheitert.current.get(bild.media_ref);
16315
+ const endgueltig = vorher !== void 0 && !vorher.retryable;
16316
+ if (gehalten.current.has(bild.media_ref) || endgueltig) continue;
16113
16317
  const ergebnis = await bestand.resolveImage(bild.media_ref, bild.mime_type ?? null);
16114
16318
  if (abgebrochen) return;
16115
16319
  if (ergebnis.kind === "ok" && ergebnis.value) {
@@ -16117,8 +16321,12 @@ function useBildQuellen(port, bilder) {
16117
16321
  bild.media_ref,
16118
16322
  `data:${ergebnis.value.mime_type};base64,${ergebnis.value.data}`
16119
16323
  );
16324
+ gescheitert.current.delete(bild.media_ref);
16120
16325
  } else if (ergebnis.kind === "error") {
16121
- gescheitert.current.set(bild.media_ref, ergebnis.message);
16326
+ gescheitert.current.set(bild.media_ref, {
16327
+ message: ergebnis.message,
16328
+ retryable: ergebnis.retryable
16329
+ });
16122
16330
  }
16123
16331
  setStand({
16124
16332
  quellen: new Map(gehalten.current),
@@ -16129,8 +16337,19 @@ function useBildQuellen(port, bilder) {
16129
16337
  return () => {
16130
16338
  abgebrochen = true;
16131
16339
  };
16132
- }, [port, schluessel]);
16133
- return stand;
16340
+ }, [port, schluessel, versuch]);
16341
+ const fehlerNachricht = /* @__PURE__ */ new Map();
16342
+ let wiederholbar = false;
16343
+ for (const [ref, f] of stand.fehler) {
16344
+ fehlerNachricht.set(ref, f.message);
16345
+ if (f.retryable) wiederholbar = true;
16346
+ }
16347
+ return {
16348
+ quellen: stand.quellen,
16349
+ fehler: fehlerNachricht,
16350
+ wiederholbar,
16351
+ erneutVersuchen: () => setVersuch((n) => n + 1)
16352
+ };
16134
16353
  }
16135
16354
  var BildDetails = ({ bild, fehler: fehler4, aufloesbar }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "szenen-bilder__meta", "data-testid": `batchbild-${bild.media_ref}`, children: [
16136
16355
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { children: bild.media_ref }),
@@ -16151,8 +16370,17 @@ var BildDetails = ({ bild, fehler: fehler4, aufloesbar }) => /* @__PURE__ */ (0,
16151
16370
  // Serverroute (A6).
16152
16371
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("em", { "data-testid": `batchbild-unsupported-${bild.media_ref}`, children: "Dieser Host l\xF6st keine Bilder auf \u2014 nur die Metadaten sind bekannt." })
16153
16372
  ] });
16373
+ var HERKUNFT_TEXT = {
16374
+ generated_in_app: "in der App erzeugt",
16375
+ external_import: "extern importiert",
16376
+ mixed: "gemischte Herkunft"
16377
+ };
16378
+ var HERKUNFT_KURZ = {
16379
+ generated_in_app: "in der App erzeugt",
16380
+ external_import: "extern importiert"
16381
+ };
16154
16382
  var BatchKachel = ({ port, batch }) => {
16155
- const { quellen, fehler: fehler4 } = useBildQuellen(port, batch.images);
16383
+ const { quellen, fehler: fehler4, wiederholbar, erneutVersuchen } = useBildQuellen(port, batch.images);
16156
16384
  const aufloesbar = Boolean(port.imageLibrary);
16157
16385
  const bilder = batch.images.map((bild) => ({
16158
16386
  id: bild.media_ref,
@@ -16178,7 +16406,7 @@ var BatchKachel = ({ port, batch }) => {
16178
16406
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("header", { className: "szenen-bilder__batchkopf", children: [
16179
16407
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("strong", { children: batch.title }),
16180
16408
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "szenen-bilder__herkunft", children: [
16181
- batch.source_kind === "generated_in_app" ? "in der App erzeugt" : "extern importiert",
16409
+ HERKUNFT_TEXT[batch.source_kind],
16182
16410
  " \xB7 ",
16183
16411
  batch.images.length,
16184
16412
  " Bild(er)"
@@ -16192,6 +16420,16 @@ var BatchKachel = ({ port, batch }) => {
16192
16420
  satzTitel: batch.title
16193
16421
  }
16194
16422
  ),
16423
+ wiederholbar && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
16424
+ "button",
16425
+ {
16426
+ type: "button",
16427
+ className: "szenen-bilder__retry",
16428
+ onClick: erneutVersuchen,
16429
+ "data-testid": `batch-retry-${batch.item_id}`,
16430
+ children: "Bilder erneut laden"
16431
+ }
16432
+ ),
16195
16433
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("details", { className: "szenen-bilder__technik", "data-testid": `batch-technik-${batch.item_id}`, children: [
16196
16434
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("summary", { children: "Technische Details" }),
16197
16435
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("p", { className: "szenen-bilder__ids", children: [
@@ -16200,8 +16438,13 @@ var BatchKachel = ({ port, batch }) => {
16200
16438
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { "data-testid": `batch-item-${batch.item_id}`, children: batch.item_id })
16201
16439
  ] }),
16202
16440
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
16203
- "Batches ",
16204
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { children: batch.generation_batch_ids.join(", ") })
16441
+ "Batches",
16442
+ " ",
16443
+ batch.origins.length > 0 ? batch.origins.map((h, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react8.default.Fragment, { children: [
16444
+ i > 0 ? ", " : "",
16445
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { children: h.generation_batch_id }),
16446
+ ` (${HERKUNFT_KURZ[h.source_kind]})`
16447
+ ] }, h.generation_batch_id)) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { children: batch.generation_batch_ids.join(", ") })
16205
16448
  ] })
16206
16449
  ] }),
16207
16450
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("ul", { className: "szenen-bilder__batchliste", children: batch.images.map((bild) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
@@ -16405,21 +16648,114 @@ var ErzeugenAktion = ({ port, lauf, wahl, wahlFehler, onFertig }) => {
16405
16648
  )
16406
16649
  ] });
16407
16650
  };
16651
+ var DEFAULT_VORLAGE_KEY = "__default__";
16408
16652
  var ZuordnenAktion = ({ port, lauf, onFertig }) => {
16409
16653
  const [dateien, setDateien] = (0, import_react8.useState)([]);
16410
16654
  const [seeds, setSeeds] = (0, import_react8.useState)({});
16655
+ const [zip, setZip] = (0, import_react8.useState)(null);
16656
+ const [zipLaedt, setZipLaedt] = (0, import_react8.useState)(false);
16657
+ const [seedListe, setSeedListe] = (0, import_react8.useState)("");
16658
+ const [vorlagen, setVorlagen] = (0, import_react8.useState)([DEFAULT_SEED_VORLAGE]);
16659
+ const [vorlageKey, setVorlageKey] = (0, import_react8.useState)(DEFAULT_VORLAGE_KEY);
16660
+ const [vorlagenStand, setVorlagenStand] = (0, import_react8.useState)(0);
16661
+ const [vorlageBenennen, setVorlageBenennen] = (0, import_react8.useState)(false);
16662
+ const [vorlageName, setVorlageName] = (0, import_react8.useState)("");
16411
16663
  const [modell, setModell] = (0, import_react8.useState)("");
16412
16664
  const [itemId, setItemId] = (0, import_react8.useState)("");
16413
16665
  const [laeuft, setLaeuft] = (0, import_react8.useState)(false);
16414
16666
  const [notiz, setNotiz] = (0, import_react8.useState)(null);
16667
+ const auswahlMarke = (0, import_react8.useRef)(0);
16668
+ const zipAktiv = zip !== null;
16669
+ (0, import_react8.useEffect)(() => {
16670
+ if (!zipAktiv) return;
16671
+ let abgebrochen = false;
16672
+ void (async () => {
16673
+ const gelesen = port.seedTemplates ? await port.seedTemplates.listSeedTemplates() : null;
16674
+ if (abgebrochen) return;
16675
+ setVorlagen(mitDefaultVorlage(
16676
+ gelesen && gelesen.kind === "ok" ? gelesen.value : []
16677
+ ));
16678
+ })();
16679
+ return () => {
16680
+ abgebrochen = true;
16681
+ };
16682
+ }, [zipAktiv, port, vorlagenStand]);
16415
16683
  if (!port.imageBatchWrite) {
16416
16684
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("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." });
16417
16685
  }
16418
- const importieren = async () => {
16686
+ const zuordnung = zip ? ordneSeedsZu(seedListe, zip.bilder.length) : null;
16687
+ const dateiWahl = async (liste) => {
16688
+ const gewaehlt = [...liste ?? []];
16689
+ const warZip = zip !== null;
16690
+ const meine = auswahlMarke.current += 1;
16691
+ const veraltet = () => auswahlMarke.current !== meine;
16692
+ const zips = gewaehlt.filter(istZipDatei);
16693
+ const nurEinZip = zips.length === 1 && gewaehlt.length === 1;
16694
+ setNotiz(null);
16695
+ setSeeds({});
16696
+ setZip(null);
16697
+ setDateien(zips.length === 0 ? gewaehlt : []);
16698
+ setZipLaedt(nurEinZip);
16699
+ if (zips.length > 0 && zips.length !== gewaehlt.length) {
16700
+ setNotiz({
16701
+ tone: "error",
16702
+ 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.`
16703
+ });
16704
+ return;
16705
+ }
16706
+ if (zips.length > 1) {
16707
+ setNotiz({
16708
+ tone: "error",
16709
+ text: `${zips.length} ZIP-Dateien gew\xE4hlt (${zips.map((z) => z.name).join(", ")}). Ein Import nimmt genau ein ZIP.`
16710
+ });
16711
+ return;
16712
+ }
16713
+ if (!nurEinZip) return;
16714
+ const datei = zips[0];
16715
+ try {
16716
+ const bilder = await leseBilderAusZip(datei);
16717
+ if (veraltet()) return;
16718
+ setZip({ datei, bilder });
16719
+ if (!warZip) {
16720
+ setSeedListe(DEFAULT_SEED_VORLAGE.seeds);
16721
+ setVorlageKey(DEFAULT_VORLAGE_KEY);
16722
+ }
16723
+ } catch (fehler4) {
16724
+ if (veraltet()) return;
16725
+ setNotiz({
16726
+ tone: "error",
16727
+ text: fehler4 instanceof Error ? fehler4.message : String(fehler4)
16728
+ });
16729
+ }
16730
+ if (veraltet()) return;
16731
+ setZipLaedt(false);
16732
+ };
16733
+ const schreiben = async (bilder, generationBatchId, titel) => {
16419
16734
  setLaeuft(true);
16420
16735
  setNotiz(null);
16421
16736
  try {
16422
- const bilder = [];
16737
+ const ergebnis = await port.imageBatchWrite.importBatch({
16738
+ runId: lauf.run_id,
16739
+ generationBatchId,
16740
+ images: bilder,
16741
+ title: titel
16742
+ });
16743
+ setNotiz(
16744
+ ergebnis.kind === "ok" ? { tone: "ok", text: `Importiert als Item ${ergebnis.value.item_id} mit ${bilder.length} Bild(ern).` } : noticeFor(ergebnis, "Externe Bilder importieren")
16745
+ );
16746
+ } catch (fehler4) {
16747
+ setNotiz({
16748
+ tone: "error",
16749
+ text: `Externe Bilder importieren: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
16750
+ });
16751
+ }
16752
+ setLaeuft(false);
16753
+ onFertig();
16754
+ };
16755
+ const importieren = async () => {
16756
+ let bilder;
16757
+ try {
16758
+ bilder = [];
16423
16759
  for (let index = 0; index < dateien.length; index++) {
16424
16760
  const roh = (seeds[index] ?? "").trim();
16425
16761
  const seed = roh === "" ? void 0 : Number.parseInt(roh, 10);
@@ -16432,25 +16768,42 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
16432
16768
  ...seed !== void 0 ? { seed } : {}
16433
16769
  });
16434
16770
  }
16435
- const ergebnis = await port.imageBatchWrite.importBatch({
16436
- runId: lauf.run_id,
16437
- // Vor dem Aufruf gebildet und stabil (D-113): eine Wiederholung
16438
- // derselben Einreichung erzeugt kein zweites Item.
16439
- generationBatchId: `imp-${lauf.run_id}-${dateien.length}-${dateien.map((d) => d.size).join("_")}`,
16440
- images: bilder,
16441
- title: `Externer Import zu Lauf ${lauf.run_id}`
16442
- });
16443
- setNotiz(
16444
- ergebnis.kind === "ok" ? { tone: "ok", text: `Importiert als Item ${ergebnis.value.item_id} mit ${bilder.length} Bild(ern).` } : noticeFor(ergebnis, "Externe Bilder importieren")
16445
- );
16446
16771
  } catch (fehler4) {
16447
16772
  setNotiz({
16448
16773
  tone: "error",
16449
16774
  text: `Externe Bilder importieren: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
16450
16775
  });
16776
+ return;
16451
16777
  }
16452
- setLaeuft(false);
16453
- onFertig();
16778
+ await schreiben(
16779
+ bilder,
16780
+ // Vor dem Aufruf gebildet und stabil (D-113): eine Wiederholung
16781
+ // derselben Einreichung erzeugt kein zweites Item.
16782
+ `imp-${lauf.run_id}-${dateien.length}-${dateien.map((d) => d.size).join("_")}`,
16783
+ `Externer Import zu Lauf ${lauf.run_id}`
16784
+ );
16785
+ };
16786
+ const zipImportieren = async () => {
16787
+ if (!zip || !zuordnung) return;
16788
+ if (zuordnung.ungueltige.length > 0) {
16789
+ setNotiz({ tone: "error", text: seedFehlerText(zuordnung.ungueltige) });
16790
+ return;
16791
+ }
16792
+ const bilder = zip.bilder.map((bild, index) => {
16793
+ const seed = zuordnung.eintraege[index]?.seed ?? null;
16794
+ return {
16795
+ imageBase64: bild.base64,
16796
+ ...modell.trim() ? { model: modell.trim() } : {},
16797
+ // Unbekannt bleibt unbekannt: kein Feld statt eines erfundenen
16798
+ // Werts (§6.1).
16799
+ ...seed !== null ? { seed } : {}
16800
+ };
16801
+ });
16802
+ await schreiben(
16803
+ bilder,
16804
+ `imp-${lauf.run_id}-zip-${zip.datei.size}-${bilder.length}`,
16805
+ `Externer Import zu Lauf ${lauf.run_id} (${zip.datei.name})`
16806
+ );
16454
16807
  };
16455
16808
  const zuordnen = async () => {
16456
16809
  setLaeuft(true);
@@ -16468,35 +16821,63 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
16468
16821
  setLaeuft(false);
16469
16822
  onFertig();
16470
16823
  };
16824
+ const vorlageWaehlen = (key) => {
16825
+ setVorlageKey(key);
16826
+ const vorlage = key === DEFAULT_VORLAGE_KEY ? DEFAULT_SEED_VORLAGE : vorlagen.find((v) => String(v.id) === key);
16827
+ if (vorlage) setSeedListe(vorlage.seeds);
16828
+ };
16829
+ const vorlageSpeichern = async () => {
16830
+ const name = vorlageName.trim();
16831
+ if (!name || !seedListe.trim() || !port.seedTemplates?.saveSeedTemplate) return;
16832
+ const ergebnis = await port.seedTemplates.saveSeedTemplate({ name, seeds: seedListe.trim() });
16833
+ if (ergebnis.kind !== "ok") {
16834
+ setNotiz(noticeFor(ergebnis, "Seed-Vorlage speichern"));
16835
+ return;
16836
+ }
16837
+ setVorlageBenennen(false);
16838
+ setVorlageName("");
16839
+ if (ergebnis.value.id !== void 0) setVorlageKey(String(ergebnis.value.id));
16840
+ setVorlagenStand((n) => n + 1);
16841
+ };
16842
+ const vorlageLoeschen = async () => {
16843
+ if (vorlageKey === DEFAULT_VORLAGE_KEY || !port.seedTemplates?.deleteSeedTemplate) return;
16844
+ const ergebnis = await port.seedTemplates.deleteSeedTemplate(vorlageKey);
16845
+ if (ergebnis.kind !== "ok") {
16846
+ setNotiz(noticeFor(ergebnis, "Seed-Vorlage l\xF6schen"));
16847
+ return;
16848
+ }
16849
+ setVorlageKey(DEFAULT_VORLAGE_KEY);
16850
+ setVorlagenStand((n) => n + 1);
16851
+ };
16471
16852
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "szenen-bilder__aktion-inhalt", "data-testid": `zuordnen-${lauf.run_id}`, children: [
16472
16853
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { children: [
16473
- "Bilddateien",
16854
+ "Bilddateien oder ein ZIP",
16474
16855
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
16475
16856
  "input",
16476
16857
  {
16477
16858
  type: "file",
16478
- accept: "image/*",
16859
+ accept: "image/*,.zip",
16479
16860
  multiple: true,
16480
16861
  "data-testid": `zuordnen-dateien-${lauf.run_id}`,
16481
16862
  onChange: (e) => {
16482
- setDateien([...e.target.files ?? []]);
16483
- setSeeds({});
16863
+ void dateiWahl(e.target.files);
16484
16864
  }
16485
16865
  }
16486
16866
  )
16487
16867
  ] }),
16868
+ zipLaedt && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { "data-testid": `zuordnen-zip-laedt-${lauf.run_id}`, children: "ZIP wird gelesen \u2026" }),
16869
+ (dateien.length > 0 || zip) && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { children: [
16870
+ "Modell (f\xFCr alle Bilder, leer = unbekannt)",
16871
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
16872
+ "input",
16873
+ {
16874
+ value: modell,
16875
+ "data-testid": `zuordnen-modell-${lauf.run_id}`,
16876
+ onChange: (e) => setModell(e.target.value)
16877
+ }
16878
+ )
16879
+ ] }),
16488
16880
  dateien.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
16489
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { children: [
16490
- "Modell (f\xFCr alle Bilder, leer = unbekannt)",
16491
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
16492
- "input",
16493
- {
16494
- value: modell,
16495
- "data-testid": `zuordnen-modell-${lauf.run_id}`,
16496
- onChange: (e) => setModell(e.target.value)
16497
- }
16498
- )
16499
- ] }),
16500
16881
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("ol", { "data-testid": `zuordnen-vorschau-${lauf.run_id}`, children: dateien.map((datei, index) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("li", { children: [
16501
16882
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
16502
16883
  "Position ",
@@ -16530,6 +16911,157 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
16530
16911
  }
16531
16912
  )
16532
16913
  ] }),
16914
+ zip && zuordnung && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "szenen-bilder__zip", "data-testid": `zuordnen-zip-${lauf.run_id}`, children: [
16915
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("p", { className: "szenen-bilder__zipkopf", children: [
16916
+ "ZIP ",
16917
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("code", { children: zip.datei.name }),
16918
+ " \u2014 ",
16919
+ zip.bilder.length,
16920
+ " Bild(er) erkannt"
16921
+ ] }),
16922
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { children: [
16923
+ "Seeds (pro Bild ein Seed, Trenner , oder ;)",
16924
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
16925
+ "input",
16926
+ {
16927
+ "data-testid": `zuordnen-seedliste-${lauf.run_id}`,
16928
+ value: seedListe,
16929
+ placeholder: "z. B. 1; 10; 100; 1000",
16930
+ onChange: (e) => setSeedListe(e.target.value)
16931
+ }
16932
+ )
16933
+ ] }),
16934
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { children: [
16935
+ "Seed-Vorlage",
16936
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
16937
+ "select",
16938
+ {
16939
+ "data-testid": `zuordnen-seedvorlage-${lauf.run_id}`,
16940
+ value: vorlageKey,
16941
+ onChange: (e) => vorlageWaehlen(e.target.value),
16942
+ children: [
16943
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("option", { value: DEFAULT_VORLAGE_KEY, children: [
16944
+ "Default (",
16945
+ DEFAULT_SEED_VORLAGE.seeds,
16946
+ ")"
16947
+ ] }),
16948
+ vorlagen.filter((v) => !v.builtin).map((v) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("option", { value: String(v.id), children: [
16949
+ v.name,
16950
+ " (",
16951
+ v.seeds,
16952
+ ")"
16953
+ ] }, String(v.id)))
16954
+ ]
16955
+ }
16956
+ )
16957
+ ] }),
16958
+ (port.seedTemplates?.saveSeedTemplate || port.seedTemplates?.deleteSeedTemplate) && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("p", { className: "szenen-bilder__zipvorlagen", children: [
16959
+ port.seedTemplates?.saveSeedTemplate && !vorlageBenennen && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
16960
+ "button",
16961
+ {
16962
+ type: "button",
16963
+ "data-testid": `zuordnen-vorlage-neu-${lauf.run_id}`,
16964
+ disabled: !seedListe.trim(),
16965
+ onClick: () => {
16966
+ setVorlageName("");
16967
+ setVorlageBenennen(true);
16968
+ },
16969
+ children: "Als Vorlage speichern"
16970
+ }
16971
+ ),
16972
+ port.seedTemplates?.deleteSeedTemplate && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
16973
+ "button",
16974
+ {
16975
+ type: "button",
16976
+ "data-testid": `zuordnen-vorlage-loeschen-${lauf.run_id}`,
16977
+ disabled: vorlageKey === DEFAULT_VORLAGE_KEY,
16978
+ onClick: () => {
16979
+ void vorlageLoeschen();
16980
+ },
16981
+ children: "Vorlage l\xF6schen"
16982
+ }
16983
+ )
16984
+ ] }),
16985
+ vorlageBenennen && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
16986
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { children: [
16987
+ "Name der Vorlage",
16988
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
16989
+ "input",
16990
+ {
16991
+ "data-testid": `zuordnen-vorlage-name-${lauf.run_id}`,
16992
+ value: vorlageName,
16993
+ onChange: (e) => setVorlageName(e.target.value)
16994
+ }
16995
+ )
16996
+ ] }),
16997
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("p", { className: "szenen-bilder__zipvorlagen", children: [
16998
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
16999
+ "button",
17000
+ {
17001
+ type: "button",
17002
+ "data-testid": `zuordnen-vorlage-sichern-${lauf.run_id}`,
17003
+ disabled: !vorlageName.trim(),
17004
+ onClick: () => {
17005
+ void vorlageSpeichern();
17006
+ },
17007
+ children: "Speichern"
17008
+ }
17009
+ ),
17010
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
17011
+ "button",
17012
+ {
17013
+ type: "button",
17014
+ "data-testid": `zuordnen-vorlage-abbrechen-${lauf.run_id}`,
17015
+ onClick: () => setVorlageBenennen(false),
17016
+ children: "Abbrechen"
17017
+ }
17018
+ )
17019
+ ] })
17020
+ ] }),
17021
+ zuordnung.ueberzaehlig > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("p", { "data-testid": `zuordnen-ueberzaehlig-${lauf.run_id}`, children: [
17022
+ zuordnung.ueberzaehlig,
17023
+ " Seed(s) mehr als Bilder \u2014 die \xFCberz\xE4hligen werden nicht geschrieben."
17024
+ ] }),
17025
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
17026
+ "ol",
17027
+ {
17028
+ className: "szenen-bilder__zipliste",
17029
+ "data-testid": `zuordnen-zip-vorschau-${lauf.run_id}`,
17030
+ children: zip.bilder.map((bild, index) => {
17031
+ const eintrag = zuordnung.eintraege[index];
17032
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("li", { children: [
17033
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { children: [
17034
+ "Position ",
17035
+ index,
17036
+ ": ",
17037
+ bild.name
17038
+ ] }),
17039
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
17040
+ "span",
17041
+ {
17042
+ "data-testid": `zuordnen-zip-seed-${lauf.run_id}-${index}`,
17043
+ "data-seed": eintrag.seed === null ? "" : String(eintrag.seed),
17044
+ children: eintrag.ungueltig ? `Seed ung\xFCltig: "${eintrag.roh}"` : eintrag.seed === null ? "Seed unbekannt" : `Seed ${eintrag.seed}`
17045
+ }
17046
+ )
17047
+ ] }, `${bild.name}-${index}`);
17048
+ })
17049
+ }
17050
+ ),
17051
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
17052
+ "button",
17053
+ {
17054
+ type: "button",
17055
+ "data-testid": `zuordnen-zip-import-${lauf.run_id}`,
17056
+ disabled: laeuft || zipLaedt,
17057
+ onClick: zipImportieren,
17058
+ children: [
17059
+ zip.bilder.length,
17060
+ " Bild(er) aus ZIP importieren"
17061
+ ]
17062
+ }
17063
+ )
17064
+ ] }),
16533
17065
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("label", { children: [
16534
17066
  "Oder ein bestehendes Library-Item",
16535
17067
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
@@ -16585,6 +17117,8 @@ var RunsPanel = ({ port, source, contextId }) => {
16585
17117
  const [nachladen, setNachladen] = (0, import_react9.useState)(0);
16586
17118
  const faehig = capabilitiesOf(port);
16587
17119
  const quellSchluessel = source.kind === "published" ? source.shotVersionId : source.kind === "draft_chain" ? source.chainId : source.sceneElementId;
17120
+ const aktuelleQuelle = (0, import_react9.useRef)({ kind: source.kind, id: quellSchluessel });
17121
+ aktuelleQuelle.current = { kind: source.kind, id: quellSchluessel };
16588
17122
  const [phase, neuLaden] = usePortQuery(
16589
17123
  () => {
16590
17124
  if (source.kind === "published") return port.runs.listRunsForShot(source.shotVersionId);
@@ -16595,12 +17129,16 @@ var RunsPanel = ({ port, source, contextId }) => {
16595
17129
  );
16596
17130
  const starten = async () => {
16597
17131
  if (source.kind !== "published") return;
17132
+ const gestartetFuer = { kind: source.kind, id: source.shotVersionId };
16598
17133
  setLaeuft(true);
16599
17134
  const ergebnis = await port.runs.startRun({
16600
17135
  shotVersionId: source.shotVersionId,
16601
17136
  ...anweisung.trim() ? { userInstruction: anweisung.trim() } : {},
16602
17137
  contextId
16603
17138
  });
17139
+ if (aktuelleQuelle.current.kind !== gestartetFuer.kind || aktuelleQuelle.current.id !== gestartetFuer.id) {
17140
+ return;
17141
+ }
16604
17142
  setNotiz(noticeFor(ergebnis, "Prompt-Lauf starten"));
16605
17143
  setLaeuft(false);
16606
17144
  setNachladen((n) => n + 1);
@@ -16610,6 +17148,12 @@ var RunsPanel = ({ port, source, contextId }) => {
16610
17148
  const [gewaehlteRunde, setGewaehlteRunde] = (0, import_react9.useState)(void 0);
16611
17149
  const neueste = laeufe.length > 0 ? laeufe[laeufe.length - 1].run_id : null;
16612
17150
  const offeneRunde = gewaehlteRunde === void 0 ? neueste : gewaehlteRunde;
17151
+ (0, import_react9.useEffect)(() => {
17152
+ setGewaehlteRunde(void 0);
17153
+ setAnweisung("");
17154
+ setNotiz(null);
17155
+ setLaeuft(false);
17156
+ }, [source.kind, quellSchluessel]);
16613
17157
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "szenen-runs", "data-testid": "laeufe-bereich", "data-shot-version": quellSchluessel, children: [
16614
17158
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h4", { children: "Prompt-L\xE4ufe" }),
16615
17159
  source.kind === "published" && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "szenen-runs__start", children: [
@@ -16680,6 +17224,26 @@ var Sekundaertext = ({ titel, text, testId }) => {
16680
17224
  )
16681
17225
  ] });
16682
17226
  };
17227
+ var LISTE_ANFANG = 3;
17228
+ var SekundaerListe = ({ titel, eintraege, testId, className }) => {
17229
+ const [alle, setAlle] = (0, import_react9.useState)(false);
17230
+ const lang = eintraege.length > LISTE_ANFANG;
17231
+ const gezeigt = lang && !alle ? eintraege.slice(0, LISTE_ANFANG) : eintraege;
17232
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { className: "szenen-runs__sekundaer", children: [
17233
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h5", { children: titel }),
17234
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ul", { className, "data-testid": testId, children: gezeigt.map((eintrag, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("li", { children: eintrag }, i)) }),
17235
+ lang && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
17236
+ "button",
17237
+ {
17238
+ type: "button",
17239
+ className: "szenen-runs__mehr",
17240
+ onClick: () => setAlle(!alle),
17241
+ "data-testid": `${testId}-mehr`,
17242
+ children: alle ? "weniger anzeigen" : `${eintraege.length - LISTE_ANFANG} weitere anzeigen`
17243
+ }
17244
+ )
17245
+ ] });
17246
+ };
16683
17247
  var RundenEintrag = ({ port, lauf, runde, offen, onUmschalten, itemMoeglich, onItemGespeichert }) => {
16684
17248
  const [notiz, setNotiz] = (0, import_react9.useState)(null);
16685
17249
  const [laeuft, setLaeuft] = (0, import_react9.useState)(false);
@@ -16765,11 +17329,23 @@ var RundenEintrag = ({ port, lauf, runde, offen, onUmschalten, itemMoeglich, onI
16765
17329
  testId: `feedback-${lauf.run_id}`
16766
17330
  }
16767
17331
  ),
16768
- lauf.output.visibility.assumptions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { className: "szenen-runs__sekundaer", children: [
16769
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h5", { children: "Annahmen" }),
16770
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ul", { "data-testid": `annahmen-${lauf.run_id}`, children: lauf.output.visibility.assumptions.map((a, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("li", { children: a }, i)) })
16771
- ] }),
16772
- lauf.output.notes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("ul", { className: "szenen-runs__notizen", "data-testid": `notizen-${lauf.run_id}`, children: lauf.output.notes.map((n, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("li", { children: n }, i)) })
17332
+ lauf.output.visibility.assumptions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
17333
+ SekundaerListe,
17334
+ {
17335
+ titel: "Annahmen",
17336
+ eintraege: lauf.output.visibility.assumptions,
17337
+ testId: `annahmen-${lauf.run_id}`
17338
+ }
17339
+ ),
17340
+ lauf.output.notes.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
17341
+ SekundaerListe,
17342
+ {
17343
+ titel: "Notizen",
17344
+ eintraege: lauf.output.notes,
17345
+ testId: `notizen-${lauf.run_id}`,
17346
+ className: "szenen-runs__notizen"
17347
+ }
17348
+ )
16773
17349
  ] }),
16774
17350
  lauf.raw_output && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("details", { "data-testid": `diagnose-${lauf.run_id}`, children: [
16775
17351
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("summary", { children: "Rohantwort (Diagnose)" }),
@@ -18416,8 +18992,6 @@ var DeriveArea = ({ port, contextId }) => {
18416
18992
  if (ergebnis.kind === "ok") setLauf(ergebnis.value);
18417
18993
  else if (ergebnis.kind === "unsupported") {
18418
18994
  setMeldung(`Dieser Host bietet \u201E${ergebnis.capability}" nicht an.`);
18419
- } else if (ergebnis.kind === "rejected") {
18420
- setMeldung(ergebnis.errors.map((e) => e.message).join("; "));
18421
18995
  } else {
18422
18996
  setMeldung(ergebnis.message);
18423
18997
  }
@@ -18507,8 +19081,6 @@ ${teile.map((t) => `- ${t}`).join("\n")}`;
18507
19081
  });
18508
19082
  if (ergebnis.kind === "ok") {
18509
19083
  setUebernommen((v) => ({ ...v, [type]: ergebnis.value.elementId }));
18510
- } else if (ergebnis.kind === "rejected") {
18511
- setMeldung(ergebnis.errors.map((e) => e.message).join("; "));
18512
19084
  } else if (ergebnis.kind === "unsupported") {
18513
19085
  setMeldung(`Dieser Host bietet \u201E${ergebnis.capability}" nicht an.`);
18514
19086
  } else {
@@ -18813,6 +19385,7 @@ var SzenenView = ({
18813
19385
  CHAIN_LINK_FIELDS,
18814
19386
  CapabilityAnchorError,
18815
19387
  ContractViolation,
19388
+ DEFAULT_SEED_VORLAGE,
18816
19389
  DERIVABLE_TYPES,
18817
19390
  ELEMENT_VERSION_SCHEMA_FILE,
18818
19391
  FLOW_EXECUTION_PATH,
@@ -18832,6 +19405,7 @@ var SzenenView = ({
18832
19405
  SZENEN_SCHEMAS,
18833
19406
  SzenenView,
18834
19407
  UNCHECKED_PUBLISH_RULES,
19408
+ UPLOAD_MAX_BYTES,
18835
19409
  UnsupportedSchemaError,
18836
19410
  VersionDagError,
18837
19411
  areasFor,
@@ -18920,8 +19494,10 @@ var SzenenView = ({
18920
19494
  findSceneDerivationRecovery,
18921
19495
  firstError,
18922
19496
  formatSchemaErrors,
19497
+ formatSeedListe,
18923
19498
  freezeDraftChain,
18924
19499
  freezeSceneWorkingState,
19500
+ gesamtHerkunft,
18925
19501
  getSzenenSchemas,
18926
19502
  gruppiereZuItems,
18927
19503
  hasPlacementCapability,
@@ -18929,16 +19505,21 @@ var SzenenView = ({
18929
19505
  isOk,
18930
19506
  isProjectionCurrent,
18931
19507
  isSzenenItemId,
19508
+ istZipDatei,
18932
19509
  itemIdForRun,
18933
19510
  ladeVorschauen,
19511
+ leseBilderAusZip,
18934
19512
  linkFor,
18935
19513
  locatorTeile,
18936
19514
  logRevisionOf,
18937
19515
  mergeSzenenBatchOrigin,
19516
+ mitDefaultVorlage,
18938
19517
  noticeFor,
18939
19518
  ordersForPromptRun,
19519
+ ordneSeedsZu,
18940
19520
  parseDerivationResponse,
18941
19521
  parseRunResponse,
19522
+ parseSeedListe,
18942
19523
  preferredHeadRevisionOf,
18943
19524
  preferredVersionId,
18944
19525
  promptDiagnosticsOf,
@@ -18962,6 +19543,7 @@ var SzenenView = ({
18962
19543
  runStoreDifferential,
18963
19544
  sceneOnlyCapabilities,
18964
19545
  scopeOfPath,
19546
+ seedFehlerText,
18965
19547
  selectableTargetModels,
18966
19548
  showChain,
18967
19549
  simulatedAnswerFor,