@rslsp1/fa-app-tools 2.0.82 → 2.0.84

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.
@@ -95,13 +95,13 @@ function noticeFor(ergebnis, was) {
95
95
  };
96
96
  }
97
97
  case "rejected": {
98
- const fehler3 = ergebnis.errors ?? [];
98
+ const fehler4 = ergebnis.errors ?? [];
99
99
  const ungeprueft = ergebnis.unchecked ?? [];
100
100
  return {
101
101
  tone: "rejected",
102
- text: `${was}: abgelehnt \u2014 ${fehler3.length} Befund${fehler3.length === 1 ? "" : "e"}.`,
102
+ text: `${was}: abgelehnt \u2014 ${fehler4.length} Befund${fehler4.length === 1 ? "" : "e"}.`,
103
103
  details: [
104
- ...fehler3.map((f) => `${f.path ?? "(ohne Pfad)"}: ${f.message ?? "ohne Meldung"}`),
104
+ ...fehler4.map((f) => `${f.path ?? "(ohne Pfad)"}: ${f.message ?? "ohne Meldung"}`),
105
105
  // Nicht verschweigen: was nicht geprüft wurde, ist nicht in Ordnung,
106
106
  // sondern ungeprüft.
107
107
  ...ungeprueft.map((u) => `ungepr\xFCft: ${u}`)
@@ -4422,7 +4422,30 @@ var SZENEN_EVENT_TYPES = {
4422
4422
  * ebenfalls keine Bilddaten — nur Status, Zaehlung und die Item-Kennung,
4423
4423
  * falls eine entstand.
4424
4424
  */
4425
- imageGenerationFinished: "szenen.image_generation_finished"
4425
+ imageGenerationFinished: "szenen.image_generation_finished",
4426
+ /**
4427
+ * Ein Bild-Batch ist kanonisch gespeichert — Stufe 6.9.2, Gate D
4428
+ * (D-6.9.2-6).
4429
+ *
4430
+ * Erscheint erst, **nachdem** jedes Bild des Batches im Bildbestand liegt
4431
+ * (`hfUploadImage` + `image_added`, dieselbe Reihenfolge wie
4432
+ * `hfImageLibrary.putImage`) — kein Bild gilt als vorhanden, bevor sein
4433
+ * Upload bestätigt ist (R3). Trägt bewusst **keine Bilddaten**, nur den
4434
+ * vollständigen Metadatennachweis, den `listBatchesForPromptRun()` /
4435
+ * `getBatch()` ohne MySQL rekonstruieren können müssen: `item_id`,
4436
+ * `prompt_run_id`, `generation_batch_id`, `source_kind`, je Bild
4437
+ * `position`, `media_ref`, `asset_id`, `model`, `seed`, `width`/`height`.
4438
+ *
4439
+ * Additiv wie jedes Ereignis dieser Domain: ein zweiter Batch desselben
4440
+ * Laufs ist ein **weiteres** Ereignis mit derselben `item_id`, nicht eine
4441
+ * Änderung am ersten (D-113/D-121 — ein Lauf bleibt ein Item). Gilt für
4442
+ * `generated_in_app` **und** `external_import` — nur `source_kind`
4443
+ * unterscheidet die beiden Schreiber (D-116/D-117/D-120).
4444
+ *
4445
+ * Der Elementreducer ignoriert auch diesen Typ (`default:`-Zweig); gelesen
4446
+ * wird mit `collectImageBatches` (`imageBatchStore.ts`).
4447
+ */
4448
+ imageBatchPersisted: "szenen.image_batch_persisted"
4426
4449
  };
4427
4450
  function sortForReplay(events) {
4428
4451
  return [...events].sort((a, b) => {
@@ -6132,7 +6155,7 @@ var ERKLAERUNG = {
6132
6155
  };
6133
6156
  var CapabilityPanel = ({ port, elementId, setupPayload }) => {
6134
6157
  const [bericht, setBericht] = useState4(null);
6135
- const [fehler3, setFehler] = useState4(null);
6158
+ const [fehler4, setFehler] = useState4(null);
6136
6159
  useEffect2(() => {
6137
6160
  let abgemeldet = false;
6138
6161
  setBericht(null);
@@ -6154,10 +6177,10 @@ var CapabilityPanel = ({ port, elementId, setupPayload }) => {
6154
6177
  abgemeldet = true;
6155
6178
  };
6156
6179
  }, [port, elementId, setupPayload]);
6157
- if (fehler3) {
6180
+ if (fehler4) {
6158
6181
  return /* @__PURE__ */ jsxs3("p", { "data-testid": "capability-fehler", role: "alert", children: [
6159
6182
  "F\xE4higkeitsbericht nicht erstellbar: ",
6160
- fehler3
6183
+ fehler4
6161
6184
  ] });
6162
6185
  }
6163
6186
  if (!bericht) return /* @__PURE__ */ jsx3("p", { "data-testid": "capability-laden", children: "F\xE4higkeitsbericht wird erstellt \u2026" });
@@ -6659,10 +6682,10 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
6659
6682
  setNotiz(
6660
6683
  ergebnis.kind === "ok" ? { tone: "ok", text: `Importiert als Item ${ergebnis.value.item_id} mit ${bilder.length} Bild(ern).` } : noticeFor(ergebnis, "Externe Bilder importieren")
6661
6684
  );
6662
- } catch (fehler3) {
6685
+ } catch (fehler4) {
6663
6686
  setNotiz({
6664
6687
  tone: "error",
6665
- text: `Externe Bilder importieren: ${fehler3 instanceof Error ? fehler3.message : String(fehler3)}`
6688
+ text: `Externe Bilder importieren: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
6666
6689
  });
6667
6690
  }
6668
6691
  setLaeuft(false);
@@ -11138,6 +11161,560 @@ function createHfImageLibrary(options) {
11138
11161
  };
11139
11162
  }
11140
11163
 
11164
+ // src/lib/szenen/flowImageProvider.ts
11165
+ var FLOW_MODEL_DISPLAY_NAMES = {
11166
+ "imagen-4.0-generate-001": "Imagen 4",
11167
+ "flow-nano-banana-pro": "\u{1F34C} Nano Banana Pro",
11168
+ "flow-nano-banana-2": "\u{1F34C} Nano Banana 2"
11169
+ };
11170
+ function flowImageProviderHasAccess(model) {
11171
+ return model.callable_model_id in FLOW_MODEL_DISPLAY_NAMES;
11172
+ }
11173
+ function istNichtleererText(wert) {
11174
+ return typeof wert === "string" && wert.trim().length > 0;
11175
+ }
11176
+ function createFlowImageProvider(generateImage) {
11177
+ return {
11178
+ async generate(input) {
11179
+ const modelDisplayName = FLOW_MODEL_DISPLAY_NAMES[input.callableModelId];
11180
+ if (!modelDisplayName) {
11181
+ throw new Error(
11182
+ `Der Flow-Host kennt keinen Flow-Display-Namen zu "${input.callableModelId}". Bekannt sind: ${Object.keys(FLOW_MODEL_DISPLAY_NAMES).join(", ")}. Es wird kein anderes Modell ersatzweise gerufen (D-115/D-6.9.2-3).`
11183
+ );
11184
+ }
11185
+ if (istNichtleererText(input.negativePrompt)) {
11186
+ throw new Error(
11187
+ "Flow.generate.image kennt keinen dokumentierten Parameter f\xFCr einen negativen Prompt. Der Auftrag wird deshalb als fehlgeschlagen protokolliert, statt den negativen Prompt still zu verlieren (D-6.9.2-3)."
11188
+ );
11189
+ }
11190
+ const erzeugt = [];
11191
+ let ersterFehler = null;
11192
+ for (let position = 0; position < input.imageCount; position++) {
11193
+ try {
11194
+ const antwort = await generateImage({
11195
+ prompt: input.positivePrompt,
11196
+ modelDisplayName,
11197
+ ...input.aspectRatio ? { aspectRatio: input.aspectRatio } : {}
11198
+ });
11199
+ if (!antwort || typeof antwort.base64 !== "string" || antwort.base64.length === 0) {
11200
+ throw new Error("Flow.generate.image hat kein Bild geliefert.");
11201
+ }
11202
+ erzeugt.push({
11203
+ imageBase64: antwort.base64,
11204
+ // Das tatsaechlich gerufene Modell, nicht der Flow-Display-Name —
11205
+ // im Bestand soll stehen, was bezahlt wurde (wie reactImageProvider.ts).
11206
+ model: input.callableModelId
11207
+ // Kein Seed: weder als Eingabe dokumentiert noch als Ausgabe
11208
+ // geliefert — nichts wird erfunden (D-107).
11209
+ });
11210
+ } catch (fehler4) {
11211
+ ersterFehler = fehler4 instanceof Error ? fehler4.message : String(fehler4);
11212
+ break;
11213
+ }
11214
+ }
11215
+ if (erzeugt.length === 0) {
11216
+ throw new Error(
11217
+ ersterFehler ?? `Flow.generate.image hat zu ${input.callableModelId} kein verwertbares Bild geliefert.`
11218
+ );
11219
+ }
11220
+ return { images: erzeugt };
11221
+ }
11222
+ };
11223
+ }
11224
+
11225
+ // src/lib/szenen/imageGenerationOrderStore.ts
11226
+ function istText3(wert) {
11227
+ return typeof wert === "string" && wert.trim().length > 0;
11228
+ }
11229
+ function collectImageGenerationOrders(events) {
11230
+ const byBatchId = /* @__PURE__ */ new Map();
11231
+ for (const event of events) {
11232
+ if (event.type !== SZENEN_EVENT_TYPES.imageGenerationStarted) continue;
11233
+ const payload = event.payload;
11234
+ if (!payload || !istText3(payload.generation_batch_id) || !istText3(payload.prompt_run_id)) continue;
11235
+ if (byBatchId.has(payload.generation_batch_id)) continue;
11236
+ byBatchId.set(payload.generation_batch_id, {
11237
+ generation_batch_id: payload.generation_batch_id,
11238
+ prompt_run_id: payload.prompt_run_id,
11239
+ source_kind: "generated_in_app",
11240
+ request: payload.request,
11241
+ created_at: payload.created_at ?? "",
11242
+ status: "started"
11243
+ });
11244
+ }
11245
+ for (const event of events) {
11246
+ if (event.type !== SZENEN_EVENT_TYPES.imageGenerationFinished) continue;
11247
+ const payload = event.payload;
11248
+ if (!payload || !istText3(payload.generation_batch_id)) continue;
11249
+ const auftrag = byBatchId.get(payload.generation_batch_id);
11250
+ if (!auftrag) continue;
11251
+ if (auftrag.status !== "started") continue;
11252
+ if (payload.status !== "succeeded" && payload.status !== "partial" && payload.status !== "failed") continue;
11253
+ byBatchId.set(payload.generation_batch_id, {
11254
+ ...auftrag,
11255
+ status: payload.status,
11256
+ ...payload.item_id ? { item_id: payload.item_id } : {},
11257
+ produced_count: payload.produced_count ?? 0,
11258
+ ...payload.diagnostic ? { diagnostic: payload.diagnostic } : {},
11259
+ finished_at: payload.finished_at ?? ""
11260
+ });
11261
+ }
11262
+ return [...byBatchId.values()];
11263
+ }
11264
+ function ordersForPromptRun(events, runId) {
11265
+ return collectImageGenerationOrders(events).filter((o) => o.prompt_run_id === runId);
11266
+ }
11267
+ function findImageGenerationOrder(events, batchId) {
11268
+ return collectImageGenerationOrders(events).find((o) => o.generation_batch_id === batchId) ?? null;
11269
+ }
11270
+
11271
+ // src/lib/szenen/imageBatchStore.ts
11272
+ var istText4 = (wert) => typeof wert === "string" && wert.trim().length > 0;
11273
+ function alsBilder(rohe) {
11274
+ if (!Array.isArray(rohe)) return [];
11275
+ return rohe.filter((b) => !!b && typeof b === "object").filter((b) => istText4(b.media_ref) && typeof b.position === "number").map((b) => ({
11276
+ media_ref: b.media_ref,
11277
+ position: b.position,
11278
+ ...istText4(b.asset_id) ? { asset_id: b.asset_id } : {},
11279
+ model: b.model ?? null,
11280
+ seed: b.seed ?? null,
11281
+ width: b.width ?? null,
11282
+ height: b.height ?? null
11283
+ }));
11284
+ }
11285
+ function collectImageBatches(events) {
11286
+ const byItemId = /* @__PURE__ */ new Map();
11287
+ for (const event of events) {
11288
+ if (event.type !== SZENEN_EVENT_TYPES.imageBatchPersisted) continue;
11289
+ const payload = event.payload;
11290
+ if (!payload || !istText4(payload.item_id) || !istText4(payload.prompt_run_id) || !istText4(payload.generation_batch_id)) continue;
11291
+ if (payload.source_kind !== "generated_in_app" && payload.source_kind !== "external_import") continue;
11292
+ const bestehend = byItemId.get(payload.item_id);
11293
+ if (bestehend?.generation_batch_ids.includes(payload.generation_batch_id)) continue;
11294
+ const bilder = alsBilder(payload.images);
11295
+ if (!bestehend) {
11296
+ byItemId.set(payload.item_id, {
11297
+ item_id: payload.item_id,
11298
+ prompt_run_id: payload.prompt_run_id,
11299
+ generation_batch_ids: [payload.generation_batch_id],
11300
+ source_kind: payload.source_kind,
11301
+ title: istText4(payload.title) ? payload.title : `Batch aus Lauf ${payload.prompt_run_id}`,
11302
+ images: bilder
11303
+ });
11304
+ continue;
11305
+ }
11306
+ byItemId.set(payload.item_id, {
11307
+ ...bestehend,
11308
+ generation_batch_ids: [...bestehend.generation_batch_ids, payload.generation_batch_id],
11309
+ images: [...bestehend.images, ...bilder]
11310
+ });
11311
+ }
11312
+ return [...byItemId.values()];
11313
+ }
11314
+ function batchesForPromptRun(events, runId) {
11315
+ return collectImageBatches(events).filter((b) => b.prompt_run_id === runId);
11316
+ }
11317
+ function findImageBatch(events, batchId) {
11318
+ return collectImageBatches(events).find((b) => b.generation_batch_ids.includes(batchId)) ?? null;
11319
+ }
11320
+
11321
+ // src/lib/szenen/hfImageBatchHistory.ts
11322
+ function lesefehler(aktion, error) {
11323
+ return {
11324
+ kind: "error",
11325
+ message: `${aktion}: ${error instanceof Error ? error.message : String(error)}`,
11326
+ retryable: true
11327
+ };
11328
+ }
11329
+ function createHfImageBatchHistory(options) {
11330
+ const { channel } = options;
11331
+ return {
11332
+ async listBatchesForPromptRun(runId) {
11333
+ try {
11334
+ const events = await channel.readEvents();
11335
+ return { kind: "ok", value: batchesForPromptRun(events, runId) };
11336
+ } catch (error) {
11337
+ return lesefehler(`Batches fuer Lauf ${runId} konnten nicht gelesen werden`, error);
11338
+ }
11339
+ },
11340
+ async getBatch(batchId) {
11341
+ try {
11342
+ const events = await channel.readEvents();
11343
+ return { kind: "ok", value: findImageBatch(events, batchId) };
11344
+ } catch (error) {
11345
+ return lesefehler(`Batch ${batchId} konnte nicht gelesen werden`, error);
11346
+ }
11347
+ },
11348
+ async listOrdersForPromptRun(runId) {
11349
+ try {
11350
+ const events = await channel.readEvents();
11351
+ return { kind: "ok", value: ordersForPromptRun(events, runId) };
11352
+ } catch (error) {
11353
+ return lesefehler(`Bildgenerierungsauftraege fuer Lauf ${runId} konnten nicht gelesen werden`, error);
11354
+ }
11355
+ }
11356
+ };
11357
+ }
11358
+
11359
+ // src/lib/szenen/hfImageBatchPersistence.ts
11360
+ function fehler3(nachricht, retryable = true) {
11361
+ return { kind: "error", message: nachricht, retryable };
11362
+ }
11363
+ async function legeBatchAb(options, input) {
11364
+ const { namespace, token, channel } = options;
11365
+ const newImageId = options.newImageId ?? ((batchId, position) => `${batchId}-${position}`);
11366
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
11367
+ const itemId = itemIdForRun(input.runId);
11368
+ let vorhandeneEvents;
11369
+ try {
11370
+ vorhandeneEvents = await channel.readEvents();
11371
+ } catch (error) {
11372
+ return fehler3(
11373
+ `Bild-Batches von Lauf ${input.runId} konnten vor dem Ablegen nicht gelesen werden: ${error instanceof Error ? error.message : String(error)}`
11374
+ );
11375
+ }
11376
+ const bestehend = batchesForPromptRun(vorhandeneEvents, input.runId)[0] ?? null;
11377
+ if (bestehend?.generation_batch_ids.includes(input.generationBatchId)) {
11378
+ return { kind: "ok", value: bestehend };
11379
+ }
11380
+ const startPosition = bestehend?.images.length ?? 0;
11381
+ const abgelegt = [];
11382
+ for (let offset = 0; offset < input.images.length; offset++) {
11383
+ const bild = input.images[offset];
11384
+ const id = newImageId(input.generationBatchId, startPosition + offset);
11385
+ try {
11386
+ await hfUploadImage(bild.imageBase64, id, token, bild.mimeType);
11387
+ await writeHFEvent(namespace, token, "image_added", {
11388
+ id,
11389
+ prompt: input.title ?? `Batch ${input.generationBatchId}`,
11390
+ tags: [],
11391
+ timestamp: now().getTime(),
11392
+ mimeType: bild.mimeType
11393
+ }, []);
11394
+ } catch (error) {
11395
+ return fehler3(
11396
+ `Bild ${offset + 1}/${input.images.length} des Batches ${input.generationBatchId} konnte nicht im Bildbestand abgelegt werden: ${error instanceof Error ? error.message : String(error)}. Bereits abgelegte Bilder dieses Versuchs (${abgelegt.length}) bleiben im Bestand, geh\xF6ren aber zu keinem Batch, solange das Batch-Ereignis nicht geschrieben ist.`
11397
+ );
11398
+ }
11399
+ abgelegt.push({
11400
+ position: startPosition + offset,
11401
+ media_ref: id,
11402
+ asset_id: id,
11403
+ model: bild.model ?? null,
11404
+ seed: bild.seed ?? null,
11405
+ width: bild.width ?? null,
11406
+ height: bild.height ?? null
11407
+ });
11408
+ }
11409
+ const titel = bestehend?.title ?? (input.title?.trim() || `Batch aus Lauf ${input.runId}`);
11410
+ const payload = {
11411
+ item_id: itemId,
11412
+ prompt_run_id: input.runId,
11413
+ generation_batch_id: input.generationBatchId,
11414
+ source_kind: input.sourceKind,
11415
+ title: titel,
11416
+ images: abgelegt,
11417
+ created_at: now().toISOString()
11418
+ };
11419
+ try {
11420
+ await channel.appendEvent(SZENEN_EVENT_TYPES.imageBatchPersisted, payload);
11421
+ } catch (error) {
11422
+ return fehler3(
11423
+ `Die Batch-Metadaten fuer ${input.generationBatchId} konnten nicht protokolliert werden, obwohl die Bilder bereits im Bestand liegen: ${error instanceof Error ? error.message : String(error)}`
11424
+ );
11425
+ }
11426
+ const zusammengefuehrt = {
11427
+ item_id: itemId,
11428
+ prompt_run_id: input.runId,
11429
+ generation_batch_ids: bestehend ? [...bestehend.generation_batch_ids, input.generationBatchId] : [input.generationBatchId],
11430
+ source_kind: bestehend?.source_kind ?? input.sourceKind,
11431
+ title: titel,
11432
+ images: [...bestehend?.images ?? [], ...abgelegt]
11433
+ };
11434
+ return { kind: "ok", value: zusammengefuehrt };
11435
+ }
11436
+ function createHfImageGenerationBatchPersistence(options) {
11437
+ return {
11438
+ async persist(input) {
11439
+ return legeBatchAb(options, {
11440
+ runId: input.runId,
11441
+ generationBatchId: input.generationBatchId,
11442
+ sourceKind: "generated_in_app",
11443
+ images: input.images.map((bild) => ({
11444
+ imageBase64: bild.imageBase64,
11445
+ // Flow liefert PNG-Base64 (bestehende Konvention, s. AvatarArchitectApp.tsx
11446
+ // `data:image/png;base64,${base64}`); der gemeinsame Vertrag führt
11447
+ // keinen MIME-Typ je Bild.
11448
+ mimeType: "image/png",
11449
+ model: bild.model ?? null,
11450
+ seed: bild.seed ?? null,
11451
+ width: bild.width ?? null,
11452
+ height: bild.height ?? null
11453
+ }))
11454
+ });
11455
+ }
11456
+ };
11457
+ }
11458
+ function createHfImageBatchWrite(options) {
11459
+ return {
11460
+ async importBatch(input) {
11461
+ return legeBatchAb(options, {
11462
+ runId: input.runId,
11463
+ generationBatchId: input.generationBatchId,
11464
+ sourceKind: "external_import",
11465
+ title: input.title,
11466
+ images: input.images.map((bild) => ({
11467
+ imageBase64: bild.imageBase64,
11468
+ mimeType: "image/png",
11469
+ model: bild.model ?? null,
11470
+ seed: bild.seed ?? null,
11471
+ width: bild.width ?? null,
11472
+ height: bild.height ?? null
11473
+ }))
11474
+ });
11475
+ },
11476
+ async attachLibraryItem(input) {
11477
+ let events;
11478
+ try {
11479
+ events = await options.channel.readEvents();
11480
+ } catch (error) {
11481
+ return {
11482
+ kind: "error",
11483
+ message: `Batches konnten nicht gelesen werden: ${error instanceof Error ? error.message : String(error)}`,
11484
+ retryable: true
11485
+ };
11486
+ }
11487
+ const item = collectImageBatches(events).find((b) => b.item_id === input.itemId);
11488
+ if (!item) {
11489
+ return { kind: "error", message: `Item ${input.itemId} ist unbekannt.`, retryable: false };
11490
+ }
11491
+ if (item.prompt_run_id !== input.runId) {
11492
+ return {
11493
+ kind: "conflict",
11494
+ message: `Item ${input.itemId} geh\xF6rt bereits zu Lauf ${item.prompt_run_id}.`,
11495
+ existingPromptRunId: item.prompt_run_id
11496
+ };
11497
+ }
11498
+ return { kind: "ok", value: item };
11499
+ }
11500
+ };
11501
+ }
11502
+
11503
+ // src/lib/szenen/imageGenerationCapability.ts
11504
+ var isObject9 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
11505
+ var IMAGE_MODEL_CATALOGUE = {
11506
+ imagen_4: {
11507
+ callable_model_id: "imagen-4.0-generate-001",
11508
+ provider: "google",
11509
+ max_images: 4,
11510
+ aspect_ratios: ["1:1", "3:4", "4:3", "9:16", "16:9"],
11511
+ supports_seed: true
11512
+ },
11513
+ imagen_3: {
11514
+ callable_model_id: "imagen-3.0-generate-001",
11515
+ provider: "google",
11516
+ max_images: 4,
11517
+ aspect_ratios: ["1:1", "3:4", "4:3", "9:16", "16:9"],
11518
+ supports_seed: true
11519
+ },
11520
+ /**
11521
+ * Stufe 6.9.2, Nachtrag — real gegen Flow geprüft (2026-08-17): Der
11522
+ * Modellwähler im Flow-„Generieren"-Tab zeigt aktuell nur "Nano Banana Pro"
11523
+ * und "Nano Banana 2" als wählbar an, "Imagen 4" erscheint dort ausgegraut.
11524
+ * `provider: 'flow'` statt `'google'` ist bewusst: Diese Modelle sind nur
11525
+ * über `Flow.generate.image` erreichbar, nicht über die direkte
11526
+ * Google-Imagen-REST-Route, die der React-Host benutzt (`reactImageProvider.ts`)
11527
+ * — React meldet sie deshalb korrekt als `unsupported`, ohne eine eigene
11528
+ * Host-Verzweigung zu brauchen (D-115/R-1: `hasAccess` prüft `provider`).
11529
+ */
11530
+ nano_banana_pro: {
11531
+ callable_model_id: "flow-nano-banana-pro",
11532
+ provider: "flow",
11533
+ max_images: 4,
11534
+ // Flow.generate.image dokumentiert nur '1:1' | '16:9' | '9:16' (howto-flow-sdk.md).
11535
+ aspect_ratios: ["1:1", "16:9", "9:16"],
11536
+ // Weder als Eingabe- noch als Ausgabeparameter dokumentiert — nichts erfunden (D-107).
11537
+ supports_seed: false
11538
+ },
11539
+ nano_banana_2: {
11540
+ callable_model_id: "flow-nano-banana-2",
11541
+ provider: "flow",
11542
+ max_images: 4,
11543
+ aspect_ratios: ["1:1", "16:9", "9:16"],
11544
+ supports_seed: false
11545
+ }
11546
+ };
11547
+ function callableImageModelFor(targetModelKey) {
11548
+ return IMAGE_MODEL_CATALOGUE[targetModelKey] ?? null;
11549
+ }
11550
+ function targetModelKeyOf(targetModel) {
11551
+ if (!isObject9(targetModel)) return null;
11552
+ return typeof targetModel.key === "string" && targetModel.key.length > 0 ? targetModel.key : null;
11553
+ }
11554
+ function capabilityForTargetModel(targetModel, hasAccess) {
11555
+ const key = targetModelKeyOf(targetModel);
11556
+ if (!key) {
11557
+ return {
11558
+ kind: "unsupported",
11559
+ target_model_key: "(unbekannt)",
11560
+ reason: 'Der eingefrorene Lauf tr\xE4gt kein "target_model.key" \u2014 es gibt nichts zuzuordnen.'
11561
+ };
11562
+ }
11563
+ const modell = callableImageModelFor(key);
11564
+ if (!modell) {
11565
+ return {
11566
+ kind: "unsupported",
11567
+ target_model_key: key,
11568
+ reason: `F\xFCr das Zielmodell "${key}" ist in diesem Host kein Bildpfad katalogisiert.`
11569
+ };
11570
+ }
11571
+ if (!hasAccess(modell)) {
11572
+ return {
11573
+ kind: "unsupported",
11574
+ target_model_key: key,
11575
+ reason: `Dieser Host hat keinen Zugang zu ${modell.callable_model_id} (${modell.provider}).`
11576
+ };
11577
+ }
11578
+ return {
11579
+ kind: "supported",
11580
+ target_model_key: key,
11581
+ provider: modell.provider,
11582
+ callable_model_id: modell.callable_model_id,
11583
+ max_images: modell.max_images,
11584
+ aspect_ratios: modell.aspect_ratios,
11585
+ supports_seed: modell.supports_seed
11586
+ };
11587
+ }
11588
+
11589
+ // src/lib/szenen/imageGenerationPort.ts
11590
+ function queryError2(error, action) {
11591
+ return {
11592
+ kind: "error",
11593
+ message: `${action}: ${error instanceof Error ? error.message : String(error)}`,
11594
+ retryable: true
11595
+ };
11596
+ }
11597
+ function createImageGenerationPort(options) {
11598
+ const { runs, provider, batches, events, hasAccess } = options;
11599
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
11600
+ async function readRun(runId) {
11601
+ try {
11602
+ return await runs.getRun(runId);
11603
+ } catch (error) {
11604
+ return queryError2(error, `Lauf ${runId} konnte nicht gelesen werden`);
11605
+ }
11606
+ }
11607
+ return {
11608
+ async capabilityForRun(runId) {
11609
+ const gelesen = await readRun(runId);
11610
+ if (gelesen.kind !== "ok") return gelesen;
11611
+ if (!gelesen.value) {
11612
+ return { kind: "error", message: `Lauf ${runId} ist unbekannt.`, retryable: false };
11613
+ }
11614
+ const targetModel = gelesen.value.input_payload?.target_model;
11615
+ return { kind: "ok", value: capabilityForTargetModel(targetModel, hasAccess) };
11616
+ },
11617
+ async startFromPromptRun(input) {
11618
+ const gelesen = await readRun(input.runId);
11619
+ if (gelesen.kind !== "ok") return { kind: "rejected", message: gelesen.message };
11620
+ if (!gelesen.value) {
11621
+ return { kind: "rejected", message: `Lauf ${input.runId} ist unbekannt.` };
11622
+ }
11623
+ const run = gelesen.value;
11624
+ if (run.status !== "succeeded" || !run.output) {
11625
+ return {
11626
+ kind: "rejected",
11627
+ message: `Lauf ${input.runId} ist nicht erfolgreich \u2014 ein Bildauftrag braucht einen eingefrorenen Prompt (D-103).`
11628
+ };
11629
+ }
11630
+ const targetModel = run.input_payload?.target_model;
11631
+ const capability = capabilityForTargetModel(targetModel, hasAccess);
11632
+ if (capability.kind !== "supported") {
11633
+ return { kind: "unsupported", targetModelKey: capability.target_model_key, reason: capability.reason };
11634
+ }
11635
+ const startedPayload = {
11636
+ generation_batch_id: input.generationBatchId,
11637
+ prompt_run_id: input.runId,
11638
+ source_kind: "generated_in_app",
11639
+ request: {
11640
+ image_count: input.imageCount,
11641
+ ...input.aspectRatio ? { aspect_ratio: input.aspectRatio } : {},
11642
+ ...input.seeds ? { seeds: input.seeds } : {},
11643
+ callable_model_id: capability.callable_model_id
11644
+ },
11645
+ created_at: now().toISOString()
11646
+ };
11647
+ try {
11648
+ await events.appendEvent(SZENEN_EVENT_TYPES.imageGenerationStarted, startedPayload);
11649
+ } catch (error) {
11650
+ return {
11651
+ kind: "failed",
11652
+ message: `Der Auftrag ${input.generationBatchId} konnte nicht protokolliert werden: ${error instanceof Error ? error.message : String(error)}`
11653
+ };
11654
+ }
11655
+ let produced = [];
11656
+ let providerFehler = null;
11657
+ try {
11658
+ const antwort = await provider.generate({
11659
+ callableModelId: capability.callable_model_id,
11660
+ provider: capability.provider,
11661
+ positivePrompt: run.output.positive,
11662
+ negativePrompt: run.output.negative,
11663
+ imageCount: input.imageCount,
11664
+ ...input.aspectRatio ? { aspectRatio: input.aspectRatio } : {},
11665
+ ...input.seeds ? { seeds: input.seeds } : {}
11666
+ });
11667
+ produced = antwort.images;
11668
+ } catch (error) {
11669
+ providerFehler = error instanceof Error ? error.message : String(error);
11670
+ }
11671
+ if (providerFehler || produced.length === 0) {
11672
+ const finishedPayload2 = {
11673
+ generation_batch_id: input.generationBatchId,
11674
+ status: "failed",
11675
+ produced_count: 0,
11676
+ diagnostic: providerFehler ?? "Der Provider hat kein verwertbares Bild geliefert.",
11677
+ finished_at: now().toISOString()
11678
+ };
11679
+ await events.appendEvent(SZENEN_EVENT_TYPES.imageGenerationFinished, finishedPayload2).catch(() => {
11680
+ });
11681
+ return {
11682
+ kind: "failed",
11683
+ message: finishedPayload2.diagnostic
11684
+ };
11685
+ }
11686
+ const persistiert = await batches.persist({
11687
+ runId: input.runId,
11688
+ generationBatchId: input.generationBatchId,
11689
+ images: produced
11690
+ });
11691
+ if (persistiert.kind !== "ok") {
11692
+ const finishedPayload2 = {
11693
+ generation_batch_id: input.generationBatchId,
11694
+ status: "failed",
11695
+ produced_count: 0,
11696
+ diagnostic: persistiert.kind === "error" ? persistiert.message : `Batch ${input.generationBatchId} konnte nicht gespeichert werden (${persistiert.kind}).`,
11697
+ finished_at: now().toISOString()
11698
+ };
11699
+ await events.appendEvent(SZENEN_EVENT_TYPES.imageGenerationFinished, finishedPayload2).catch(() => {
11700
+ });
11701
+ return { kind: "failed", message: finishedPayload2.diagnostic };
11702
+ }
11703
+ const istPartial = produced.length < input.imageCount;
11704
+ const finishedPayload = {
11705
+ generation_batch_id: input.generationBatchId,
11706
+ status: istPartial ? "partial" : "succeeded",
11707
+ item_id: persistiert.value.item_id,
11708
+ produced_count: produced.length,
11709
+ finished_at: now().toISOString()
11710
+ };
11711
+ await events.appendEvent(SZENEN_EVENT_TYPES.imageGenerationFinished, finishedPayload).catch(() => {
11712
+ });
11713
+ return istPartial ? { kind: "partial", batch: persistiert.value, requested: input.imageCount, produced: produced.length } : { kind: "succeeded", batch: persistiert.value };
11714
+ }
11715
+ };
11716
+ }
11717
+
11141
11718
  // src/lib/szenen/flowViewPort.ts
11142
11719
  var defaultContext = {
11143
11720
  async chainFor(contextId) {
@@ -11185,8 +11762,36 @@ function createFlowSzenenViewPort(options) {
11185
11762
  ...options.newRunId ? { newRunId: options.newRunId } : {},
11186
11763
  ...options.now ? { now: options.now } : {}
11187
11764
  });
11188
- const port = { elements, context, runs, derivation };
11765
+ const imageBatchHistory = createHfImageBatchHistory({ channel });
11766
+ const hatHfZugangsdaten = Boolean(options.namespace?.trim() && options.token?.trim());
11767
+ const imageBatchWrite = hatHfZugangsdaten ? createHfImageBatchWrite({
11768
+ namespace: options.namespace,
11769
+ token: options.token,
11770
+ channel,
11771
+ ...options.newBatchImageId ? { newImageId: options.newBatchImageId } : {},
11772
+ ...options.now ? { now: options.now } : {}
11773
+ }) : void 0;
11774
+ const bildProvider = options.imageProvider ?? (options.generateImage ? createFlowImageProvider(options.generateImage) : void 0);
11775
+ const imageGeneration = bildProvider && imageBatchWrite ? createImageGenerationPort({
11776
+ runs: { async getRun(runId) {
11777
+ return runs.getRun(runId);
11778
+ } },
11779
+ provider: bildProvider,
11780
+ batches: createHfImageGenerationBatchPersistence({
11781
+ namespace: options.namespace,
11782
+ token: options.token,
11783
+ channel,
11784
+ ...options.newBatchImageId ? { newImageId: options.newBatchImageId } : {},
11785
+ ...options.now ? { now: options.now } : {}
11786
+ }),
11787
+ events: channel,
11788
+ hasAccess: options.imageProviderHasAccess ?? flowImageProviderHasAccess,
11789
+ ...options.now ? { now: options.now } : {}
11790
+ }) : void 0;
11791
+ const port = { elements, context, runs, derivation, imageBatchHistory };
11189
11792
  if (imageLibrary) port.imageLibrary = imageLibrary;
11793
+ if (imageBatchWrite) port.imageBatchWrite = imageBatchWrite;
11794
+ if (imageGeneration) port.imageGeneration = imageGeneration;
11190
11795
  return port;
11191
11796
  }
11192
11797
 
@@ -11306,5 +11911,20 @@ export {
11306
11911
  createEventLogRunPersistence,
11307
11912
  createClientRunPort,
11308
11913
  createHfImageLibrary,
11914
+ FLOW_MODEL_DISPLAY_NAMES,
11915
+ flowImageProviderHasAccess,
11916
+ createFlowImageProvider,
11917
+ collectImageGenerationOrders,
11918
+ ordersForPromptRun,
11919
+ findImageGenerationOrder,
11920
+ collectImageBatches,
11921
+ batchesForPromptRun,
11922
+ findImageBatch,
11923
+ createHfImageBatchHistory,
11924
+ createHfImageGenerationBatchPersistence,
11925
+ createHfImageBatchWrite,
11926
+ callableImageModelFor,
11927
+ capabilityForTargetModel,
11928
+ createImageGenerationPort,
11309
11929
  createFlowSzenenViewPort
11310
11930
  };