@rslsp1/fa-app-tools 2.0.83 → 2.0.85

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.
@@ -15,6 +15,21 @@ function treeUrl(namespace, subdir = "") {
15
15
  var DROSSEL_VERSUCHE = 4;
16
16
  var DROSSEL_BASIS_MS = 400;
17
17
  var schlafen = (ms) => new Promise((r) => setTimeout(r, ms));
18
+ var HF_LESE_ZEITLIMIT_MS = 2e4;
19
+ async function holeMitZeitlimit(url, init) {
20
+ const abbruch = new AbortController();
21
+ const timer = setTimeout(() => abbruch.abort(), HF_LESE_ZEITLIMIT_MS);
22
+ try {
23
+ return await fetch(url, { ...init, signal: abbruch.signal });
24
+ } catch (error) {
25
+ if (abbruch.signal.aborted) {
26
+ throw new Error(`HF-Anfrage ohne Antwort nach ${HF_LESE_ZEITLIMIT_MS}ms abgebrochen: ${url}`);
27
+ }
28
+ throw error;
29
+ } finally {
30
+ clearTimeout(timer);
31
+ }
32
+ }
18
33
  async function mitGeduld(anfrage) {
19
34
  let letzte = null;
20
35
  for (let versuch = 0; versuch < DROSSEL_VERSUCHE; versuch += 1) {
@@ -29,7 +44,7 @@ async function mitGeduld(anfrage) {
29
44
  return letzte;
30
45
  }
31
46
  async function hfListDir(namespace, subdir, token) {
32
- const res = await mitGeduld(() => fetch(treeUrl(namespace, subdir), {
47
+ const res = await mitGeduld(() => holeMitZeitlimit(treeUrl(namespace, subdir), {
33
48
  headers: { Authorization: `Bearer ${token}` }
34
49
  }));
35
50
  if (res.status === 404) return [];
@@ -37,7 +52,7 @@ async function hfListDir(namespace, subdir, token) {
37
52
  return res.json();
38
53
  }
39
54
  async function hfDownloadJsonByPath(repoPath, token) {
40
- const res = await mitGeduld(() => fetch(
55
+ const res = await mitGeduld(() => holeMitZeitlimit(
41
56
  `${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
42
57
  { headers: { Authorization: `Bearer ${token}` } }
43
58
  ));
@@ -45,7 +60,7 @@ async function hfDownloadJsonByPath(repoPath, token) {
45
60
  return res.json();
46
61
  }
47
62
  async function hfDownloadBinaryByPath(repoPath, token) {
48
- const res = await mitGeduld(() => fetch(
63
+ const res = await mitGeduld(() => holeMitZeitlimit(
49
64
  `${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
50
65
  { headers: { Authorization: `Bearer ${token}` } }
51
66
  ));
@@ -10,7 +10,7 @@ import {
10
10
  loadHFState,
11
11
  loadPendingEvents,
12
12
  writeHFEvent
13
- } from "./chunk-VBWQP7PO.mjs";
13
+ } from "./chunk-LFPOXNOZ.mjs";
14
14
 
15
15
  // src/lib/szenen/viewPort.ts
16
16
  function capabilitiesOf(port) {
@@ -11109,25 +11109,29 @@ function createHfImageLibrary(options) {
11109
11109
  try {
11110
11110
  const zustand = await loadHFState(namespace, token);
11111
11111
  const eintrag = (zustand?.metadata ?? []).find((e) => e.id === mediaRef);
11112
- if (!eintrag) {
11113
- return fehler2(
11114
- `Das Bild ${mediaRef} steht nicht im Bestand. Moeglicherweise hat der Abgleich es noch nicht uebertragen.`,
11115
- true
11112
+ if (eintrag) {
11113
+ const base64 = await hfLoadImageAsBase64(
11114
+ mediaRef,
11115
+ token,
11116
+ void 0,
11117
+ eintrag.filename,
11118
+ void 0,
11119
+ eintrag.mimeType,
11120
+ false
11116
11121
  );
11122
+ if (!base64) {
11123
+ return fehler2(`Das Bild ${mediaRef} ist im Bestand verzeichnet, aber nicht ladbar.`, true);
11124
+ }
11125
+ return { kind: "ok", value: { data: base64, mime_type: eintrag.mimeType || "image/jpeg" } };
11117
11126
  }
11118
- const base64 = await hfLoadImageAsBase64(
11119
- mediaRef,
11120
- token,
11121
- void 0,
11122
- eintrag.filename,
11123
- void 0,
11124
- eintrag.mimeType,
11125
- false
11126
- );
11127
- if (!base64) {
11128
- return fehler2(`Das Bild ${mediaRef} ist im Bestand verzeichnet, aber nicht ladbar.`, true);
11127
+ for (const mimeType of ["image/png", "image/jpeg"]) {
11128
+ const base64 = await hfLoadImageAsBase64(mediaRef, token, void 0, void 0, void 0, mimeType, false);
11129
+ if (base64) return { kind: "ok", value: { data: base64, mime_type: mimeType } };
11129
11130
  }
11130
- return { kind: "ok", value: { data: base64, mime_type: eintrag.mimeType || "image/jpeg" } };
11131
+ return fehler2(
11132
+ `Das Bild ${mediaRef} steht nicht im Bestand. Moeglicherweise hat der Abgleich es noch nicht uebertragen.`,
11133
+ true
11134
+ );
11131
11135
  } catch (error) {
11132
11136
  return fehler2(
11133
11137
  `Das Bild ${mediaRef} konnte nicht geladen werden: ${error instanceof Error ? error.message : String(error)}`,
@@ -11163,7 +11167,9 @@ function createHfImageLibrary(options) {
11163
11167
 
11164
11168
  // src/lib/szenen/flowImageProvider.ts
11165
11169
  var FLOW_MODEL_DISPLAY_NAMES = {
11166
- "imagen-4.0-generate-001": "Imagen 4"
11170
+ "imagen-4.0-generate-001": "Imagen 4",
11171
+ "flow-nano-banana-pro": "\u{1F34C} Nano Banana Pro",
11172
+ "flow-nano-banana-2": "\u{1F34C} Nano Banana 2"
11167
11173
  };
11168
11174
  function flowImageProviderHasAccess(model) {
11169
11175
  return model.callable_model_id in FLOW_MODEL_DISPLAY_NAMES;
@@ -11514,6 +11520,32 @@ var IMAGE_MODEL_CATALOGUE = {
11514
11520
  max_images: 4,
11515
11521
  aspect_ratios: ["1:1", "3:4", "4:3", "9:16", "16:9"],
11516
11522
  supports_seed: true
11523
+ },
11524
+ /**
11525
+ * Stufe 6.9.2, Nachtrag — real gegen Flow geprüft (2026-08-17): Der
11526
+ * Modellwähler im Flow-„Generieren"-Tab zeigt aktuell nur "Nano Banana Pro"
11527
+ * und "Nano Banana 2" als wählbar an, "Imagen 4" erscheint dort ausgegraut.
11528
+ * `provider: 'flow'` statt `'google'` ist bewusst: Diese Modelle sind nur
11529
+ * über `Flow.generate.image` erreichbar, nicht über die direkte
11530
+ * Google-Imagen-REST-Route, die der React-Host benutzt (`reactImageProvider.ts`)
11531
+ * — React meldet sie deshalb korrekt als `unsupported`, ohne eine eigene
11532
+ * Host-Verzweigung zu brauchen (D-115/R-1: `hasAccess` prüft `provider`).
11533
+ */
11534
+ nano_banana_pro: {
11535
+ callable_model_id: "flow-nano-banana-pro",
11536
+ provider: "flow",
11537
+ max_images: 4,
11538
+ // Flow.generate.image dokumentiert nur '1:1' | '16:9' | '9:16' (howto-flow-sdk.md).
11539
+ aspect_ratios: ["1:1", "16:9", "9:16"],
11540
+ // Weder als Eingabe- noch als Ausgabeparameter dokumentiert — nichts erfunden (D-107).
11541
+ supports_seed: false
11542
+ },
11543
+ nano_banana_2: {
11544
+ callable_model_id: "flow-nano-banana-2",
11545
+ provider: "flow",
11546
+ max_images: 4,
11547
+ aspect_ratios: ["1:1", "16:9", "9:16"],
11548
+ supports_seed: false
11517
11549
  }
11518
11550
  };
11519
11551
  function callableImageModelFor(targetModelKey) {
@@ -25,7 +25,7 @@ import {
25
25
  setHFToken,
26
26
  tsFromEventPath,
27
27
  writeHFEvent
28
- } from "./chunk-VBWQP7PO.mjs";
28
+ } from "./chunk-LFPOXNOZ.mjs";
29
29
  export {
30
30
  HF_TOKEN_KEY,
31
31
  getHFToken,
package/dist/index.d.mts CHANGED
@@ -6,7 +6,7 @@ import { Node, Edge } from '@xyflow/react';
6
6
  import * as zustand_vanilla from 'zustand/vanilla';
7
7
  import { F as FeatureSliceId, b as SliceContract, a as PersistenceDescriptor, W as WorkspacePersistence, P as PersistedSlice, L as LoadResult, f as WriteResult, e as WorkspaceTabId, C as CommandResult, R as RequestContext, Q as QueryResult } from './contracts-DdYOMBl9.mjs';
8
8
 
9
- var version = "2.0.83";
9
+ var version = "2.0.85";
10
10
 
11
11
  declare function useOnClickOutside(ref: RefObject<HTMLElement | null>, handler: (e: MouseEvent | TouchEvent) => void): void;
12
12
 
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@ import { Node, Edge } from '@xyflow/react';
6
6
  import * as zustand_vanilla from 'zustand/vanilla';
7
7
  import { F as FeatureSliceId, b as SliceContract, a as PersistenceDescriptor, W as WorkspacePersistence, P as PersistedSlice, L as LoadResult, f as WriteResult, e as WorkspaceTabId, C as CommandResult, R as RequestContext, Q as QueryResult } from './contracts-DdYOMBl9.js';
8
8
 
9
- var version = "2.0.83";
9
+ var version = "2.0.85";
10
10
 
11
11
  declare function useOnClickOutside(ref: RefObject<HTMLElement | null>, handler: (e: MouseEvent | TouchEvent) => void): void;
12
12
 
package/dist/index.js CHANGED
@@ -275,6 +275,20 @@ function treeUrl(namespace, subdir = "") {
275
275
  const parts = [namespace.replace(/\/$/, ""), subdir].filter(Boolean).join("/");
276
276
  return `${HF_BASE}/api/datasets/${HF_REPO}/tree/main${parts ? "/" + parts : ""}`;
277
277
  }
278
+ async function holeMitZeitlimit(url, init) {
279
+ const abbruch = new AbortController();
280
+ const timer = setTimeout(() => abbruch.abort(), HF_LESE_ZEITLIMIT_MS);
281
+ try {
282
+ return await fetch(url, { ...init, signal: abbruch.signal });
283
+ } catch (error) {
284
+ if (abbruch.signal.aborted) {
285
+ throw new Error(`HF-Anfrage ohne Antwort nach ${HF_LESE_ZEITLIMIT_MS}ms abgebrochen: ${url}`);
286
+ }
287
+ throw error;
288
+ } finally {
289
+ clearTimeout(timer);
290
+ }
291
+ }
278
292
  async function mitGeduld(anfrage) {
279
293
  let letzte = null;
280
294
  for (let versuch = 0; versuch < DROSSEL_VERSUCHE; versuch += 1) {
@@ -289,7 +303,7 @@ async function mitGeduld(anfrage) {
289
303
  return letzte;
290
304
  }
291
305
  async function hfListDir(namespace, subdir, token) {
292
- const res = await mitGeduld(() => fetch(treeUrl(namespace, subdir), {
306
+ const res = await mitGeduld(() => holeMitZeitlimit(treeUrl(namespace, subdir), {
293
307
  headers: { Authorization: `Bearer ${token}` }
294
308
  }));
295
309
  if (res.status === 404) return [];
@@ -297,7 +311,7 @@ async function hfListDir(namespace, subdir, token) {
297
311
  return res.json();
298
312
  }
299
313
  async function hfDownloadJsonByPath(repoPath, token) {
300
- const res = await mitGeduld(() => fetch(
314
+ const res = await mitGeduld(() => holeMitZeitlimit(
301
315
  `${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
302
316
  { headers: { Authorization: `Bearer ${token}` } }
303
317
  ));
@@ -305,7 +319,7 @@ async function hfDownloadJsonByPath(repoPath, token) {
305
319
  return res.json();
306
320
  }
307
321
  async function hfDownloadBinaryByPath(repoPath, token) {
308
- const res = await mitGeduld(() => fetch(
322
+ const res = await mitGeduld(() => holeMitZeitlimit(
309
323
  `${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
310
324
  { headers: { Authorization: `Bearer ${token}` } }
311
325
  ));
@@ -417,7 +431,7 @@ async function writeHFEvent(namespace, token, type, payload, prevTs, options) {
417
431
  await hfUploadSmallFile(repoPath, JSON.stringify(event, null, 2), token, `Event: ${type}`);
418
432
  return event;
419
433
  }
420
- var DROSSEL_VERSUCHE, DROSSEL_BASIS_MS, schlafen, EVENTDOWNLOAD_PARALLELITAET, SESSION_CLIENT_ID, writeSeq, lastOrderedTs;
434
+ var DROSSEL_VERSUCHE, DROSSEL_BASIS_MS, schlafen, HF_LESE_ZEITLIMIT_MS, EVENTDOWNLOAD_PARALLELITAET, SESSION_CLIENT_ID, writeSeq, lastOrderedTs;
421
435
  var init_hfEventLog = __esm({
422
436
  "src/lib/hfEventLog.ts"() {
423
437
  "use strict";
@@ -425,6 +439,7 @@ var init_hfEventLog = __esm({
425
439
  DROSSEL_VERSUCHE = 4;
426
440
  DROSSEL_BASIS_MS = 400;
427
441
  schlafen = (ms) => new Promise((r) => setTimeout(r, ms));
442
+ HF_LESE_ZEITLIMIT_MS = 2e4;
428
443
  EVENTDOWNLOAD_PARALLELITAET = 3;
429
444
  SESSION_CLIENT_ID = `client-${crypto.randomUUID().slice(0, 8)}`;
430
445
  writeSeq = 0;
@@ -786,7 +801,7 @@ __export(index_exports, {
786
801
  module.exports = __toCommonJS(index_exports);
787
802
 
788
803
  // package.json
789
- var version = "2.0.83";
804
+ var version = "2.0.85";
790
805
 
791
806
  // src/hooks/useOnClickOutside.ts
792
807
  var import_react = require("react");
@@ -17467,25 +17482,29 @@ function createHfImageLibrary(options) {
17467
17482
  try {
17468
17483
  const zustand = await loadHFState(namespace, token);
17469
17484
  const eintrag = (zustand?.metadata ?? []).find((e) => e.id === mediaRef);
17470
- if (!eintrag) {
17471
- return fehler2(
17472
- `Das Bild ${mediaRef} steht nicht im Bestand. Moeglicherweise hat der Abgleich es noch nicht uebertragen.`,
17473
- true
17485
+ if (eintrag) {
17486
+ const base64 = await hfLoadImageAsBase64(
17487
+ mediaRef,
17488
+ token,
17489
+ void 0,
17490
+ eintrag.filename,
17491
+ void 0,
17492
+ eintrag.mimeType,
17493
+ false
17474
17494
  );
17495
+ if (!base64) {
17496
+ return fehler2(`Das Bild ${mediaRef} ist im Bestand verzeichnet, aber nicht ladbar.`, true);
17497
+ }
17498
+ return { kind: "ok", value: { data: base64, mime_type: eintrag.mimeType || "image/jpeg" } };
17475
17499
  }
17476
- const base64 = await hfLoadImageAsBase64(
17477
- mediaRef,
17478
- token,
17479
- void 0,
17480
- eintrag.filename,
17481
- void 0,
17482
- eintrag.mimeType,
17483
- false
17484
- );
17485
- if (!base64) {
17486
- return fehler2(`Das Bild ${mediaRef} ist im Bestand verzeichnet, aber nicht ladbar.`, true);
17500
+ for (const mimeType of ["image/png", "image/jpeg"]) {
17501
+ const base64 = await hfLoadImageAsBase64(mediaRef, token, void 0, void 0, void 0, mimeType, false);
17502
+ if (base64) return { kind: "ok", value: { data: base64, mime_type: mimeType } };
17487
17503
  }
17488
- return { kind: "ok", value: { data: base64, mime_type: eintrag.mimeType || "image/jpeg" } };
17504
+ return fehler2(
17505
+ `Das Bild ${mediaRef} steht nicht im Bestand. Moeglicherweise hat der Abgleich es noch nicht uebertragen.`,
17506
+ true
17507
+ );
17489
17508
  } catch (error) {
17490
17509
  return fehler2(
17491
17510
  `Das Bild ${mediaRef} konnte nicht geladen werden: ${error instanceof Error ? error.message : String(error)}`,
@@ -17521,7 +17540,9 @@ function createHfImageLibrary(options) {
17521
17540
 
17522
17541
  // src/lib/szenen/flowImageProvider.ts
17523
17542
  var FLOW_MODEL_DISPLAY_NAMES = {
17524
- "imagen-4.0-generate-001": "Imagen 4"
17543
+ "imagen-4.0-generate-001": "Imagen 4",
17544
+ "flow-nano-banana-pro": "\u{1F34C} Nano Banana Pro",
17545
+ "flow-nano-banana-2": "\u{1F34C} Nano Banana 2"
17525
17546
  };
17526
17547
  function flowImageProviderHasAccess(model) {
17527
17548
  return model.callable_model_id in FLOW_MODEL_DISPLAY_NAMES;
@@ -17871,6 +17892,32 @@ var IMAGE_MODEL_CATALOGUE = {
17871
17892
  max_images: 4,
17872
17893
  aspect_ratios: ["1:1", "3:4", "4:3", "9:16", "16:9"],
17873
17894
  supports_seed: true
17895
+ },
17896
+ /**
17897
+ * Stufe 6.9.2, Nachtrag — real gegen Flow geprüft (2026-08-17): Der
17898
+ * Modellwähler im Flow-„Generieren"-Tab zeigt aktuell nur "Nano Banana Pro"
17899
+ * und "Nano Banana 2" als wählbar an, "Imagen 4" erscheint dort ausgegraut.
17900
+ * `provider: 'flow'` statt `'google'` ist bewusst: Diese Modelle sind nur
17901
+ * über `Flow.generate.image` erreichbar, nicht über die direkte
17902
+ * Google-Imagen-REST-Route, die der React-Host benutzt (`reactImageProvider.ts`)
17903
+ * — React meldet sie deshalb korrekt als `unsupported`, ohne eine eigene
17904
+ * Host-Verzweigung zu brauchen (D-115/R-1: `hasAccess` prüft `provider`).
17905
+ */
17906
+ nano_banana_pro: {
17907
+ callable_model_id: "flow-nano-banana-pro",
17908
+ provider: "flow",
17909
+ max_images: 4,
17910
+ // Flow.generate.image dokumentiert nur '1:1' | '16:9' | '9:16' (howto-flow-sdk.md).
17911
+ aspect_ratios: ["1:1", "16:9", "9:16"],
17912
+ // Weder als Eingabe- noch als Ausgabeparameter dokumentiert — nichts erfunden (D-107).
17913
+ supports_seed: false
17914
+ },
17915
+ nano_banana_2: {
17916
+ callable_model_id: "flow-nano-banana-2",
17917
+ provider: "flow",
17918
+ max_images: 4,
17919
+ aspect_ratios: ["1:1", "16:9", "9:16"],
17920
+ supports_seed: false
17874
17921
  }
17875
17922
  };
17876
17923
  function callableImageModelFor(targetModelKey) {
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  SzenenView,
3
3
  createFlowSzenenViewPort
4
- } from "./chunk-JTGC5PXJ.mjs";
4
+ } from "./chunk-MILMVOK7.mjs";
5
5
  import "./chunk-FUY2OCVC.mjs";
6
6
  import {
7
7
  exportProjectToZip,
@@ -27,10 +27,10 @@ import {
27
27
  setHFToken,
28
28
  tsFromEventPath,
29
29
  writeHFEvent
30
- } from "./chunk-VBWQP7PO.mjs";
30
+ } from "./chunk-LFPOXNOZ.mjs";
31
31
 
32
32
  // package.json
33
- var version = "2.0.83";
33
+ var version = "2.0.85";
34
34
 
35
35
  // src/hooks/useOnClickOutside.ts
36
36
  import { useEffect } from "react";
@@ -2308,7 +2308,7 @@ var ProjectSyncTab = ({
2308
2308
  {
2309
2309
  onClick: async () => {
2310
2310
  try {
2311
- const { hfDownloadProject: hfDownloadProject2 } = await import("./hfStateService-WKQLNDHO.mjs");
2311
+ const { hfDownloadProject: hfDownloadProject2 } = await import("./hfStateService-PZH2IDKF.mjs");
2312
2312
  const file = await hfDownloadProject2(p.path, hfToken);
2313
2313
  onHfLoad(file);
2314
2314
  } catch (e) {
@@ -6856,7 +6856,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6856
6856
  onClick: async () => {
6857
6857
  setIsLoadingFromHF(true);
6858
6858
  try {
6859
- const { hfListProjects: hfListProjects2, hfDownloadProject: hfDownloadProject2 } = await import("./hfStateService-WKQLNDHO.mjs");
6859
+ const { hfListProjects: hfListProjects2, hfDownloadProject: hfDownloadProject2 } = await import("./hfStateService-PZH2IDKF.mjs");
6860
6860
  const projects = await hfListProjects2(hfToken);
6861
6861
  if (projects.length > 0) {
6862
6862
  const file = await hfDownloadProject2(projects[0].path, hfToken);
package/dist/szenen.d.mts CHANGED
@@ -2488,9 +2488,19 @@ type FlowGenerateImage = (params: FlowGenerateImageParams) => Promise<{
2488
2488
  * Flow-Display-Namen (`howto-flow-sdk.md` — bekannt sind "🍌 Nano Banana Pro",
2489
2489
  * "🍌 Nano Banana 2", "Imagen 4").
2490
2490
  *
2491
- * Nur `imagen-4.0-generate-001` hat einen dokumentierten Flow-Display-Namen.
2492
- * `imagen-3.0-generate-001` bleibt bewusst draußen: Ein stiller Griff zu
2493
- * "Imagen 4" wäre genau der Modell-Fallback, den D-115 verbietet.
2491
+ * Der genaue String für "Nano Banana Pro" ist real belegt, nicht nur
2492
+ * dokumentiert: `AvatarArchitectApp.tsx`s produktive Labs-Generierung
2493
+ * verwendet `'🍌 Nano Banana Pro'` als Vorgabewert und reicht ihn
2494
+ * unverändert als `modelDisplayName` an `Flow.generate.image()` durch
2495
+ * (`aiHelpers.ts#buildImageGenerationOptions`).
2496
+ *
2497
+ * "Imagen 4" bleibt katalogisiert, obwohl es im Modellwähler des
2498
+ * Flow-„Generieren"-Tabs am 2026-08-17 ausgegraut erschien (Screenshot,
2499
+ * nur "Nano Banana Pro"/"Nano Banana 2" wählbar) — das kann kontoabhängig
2500
+ * sein. Ein Aufruf schlägt dann mit der echten Flow-Fehlermeldung fehl,
2501
+ * statt einen stillen Fallback zu erzeugen (D-6.9.2-3).
2502
+ * `imagen-3.0-generate-001` bleibt bewusst draußen: Dafür ist kein
2503
+ * Flow-Display-Name dokumentiert oder belegt.
2494
2504
  */
2495
2505
  declare const FLOW_MODEL_DISPLAY_NAMES: Readonly<Record<string, string>>;
2496
2506
  /**
package/dist/szenen.d.ts CHANGED
@@ -2488,9 +2488,19 @@ type FlowGenerateImage = (params: FlowGenerateImageParams) => Promise<{
2488
2488
  * Flow-Display-Namen (`howto-flow-sdk.md` — bekannt sind "🍌 Nano Banana Pro",
2489
2489
  * "🍌 Nano Banana 2", "Imagen 4").
2490
2490
  *
2491
- * Nur `imagen-4.0-generate-001` hat einen dokumentierten Flow-Display-Namen.
2492
- * `imagen-3.0-generate-001` bleibt bewusst draußen: Ein stiller Griff zu
2493
- * "Imagen 4" wäre genau der Modell-Fallback, den D-115 verbietet.
2491
+ * Der genaue String für "Nano Banana Pro" ist real belegt, nicht nur
2492
+ * dokumentiert: `AvatarArchitectApp.tsx`s produktive Labs-Generierung
2493
+ * verwendet `'🍌 Nano Banana Pro'` als Vorgabewert und reicht ihn
2494
+ * unverändert als `modelDisplayName` an `Flow.generate.image()` durch
2495
+ * (`aiHelpers.ts#buildImageGenerationOptions`).
2496
+ *
2497
+ * "Imagen 4" bleibt katalogisiert, obwohl es im Modellwähler des
2498
+ * Flow-„Generieren"-Tabs am 2026-08-17 ausgegraut erschien (Screenshot,
2499
+ * nur "Nano Banana Pro"/"Nano Banana 2" wählbar) — das kann kontoabhängig
2500
+ * sein. Ein Aufruf schlägt dann mit der echten Flow-Fehlermeldung fehl,
2501
+ * statt einen stillen Fallback zu erzeugen (D-6.9.2-3).
2502
+ * `imagen-3.0-generate-001` bleibt bewusst draußen: Dafür ist kein
2503
+ * Flow-Display-Name dokumentiert oder belegt.
2494
2504
  */
2495
2505
  declare const FLOW_MODEL_DISPLAY_NAMES: Readonly<Record<string, string>>;
2496
2506
  /**
package/dist/szenen.js CHANGED
@@ -7572,6 +7572,21 @@ function treeUrl(namespace, subdir = "") {
7572
7572
  var DROSSEL_VERSUCHE = 4;
7573
7573
  var DROSSEL_BASIS_MS = 400;
7574
7574
  var schlafen = (ms) => new Promise((r) => setTimeout(r, ms));
7575
+ var HF_LESE_ZEITLIMIT_MS = 2e4;
7576
+ async function holeMitZeitlimit(url, init) {
7577
+ const abbruch = new AbortController();
7578
+ const timer = setTimeout(() => abbruch.abort(), HF_LESE_ZEITLIMIT_MS);
7579
+ try {
7580
+ return await fetch(url, { ...init, signal: abbruch.signal });
7581
+ } catch (error) {
7582
+ if (abbruch.signal.aborted) {
7583
+ throw new Error(`HF-Anfrage ohne Antwort nach ${HF_LESE_ZEITLIMIT_MS}ms abgebrochen: ${url}`);
7584
+ }
7585
+ throw error;
7586
+ } finally {
7587
+ clearTimeout(timer);
7588
+ }
7589
+ }
7575
7590
  async function mitGeduld(anfrage) {
7576
7591
  let letzte = null;
7577
7592
  for (let versuch = 0; versuch < DROSSEL_VERSUCHE; versuch += 1) {
@@ -7586,7 +7601,7 @@ async function mitGeduld(anfrage) {
7586
7601
  return letzte;
7587
7602
  }
7588
7603
  async function hfListDir(namespace, subdir, token) {
7589
- const res = await mitGeduld(() => fetch(treeUrl(namespace, subdir), {
7604
+ const res = await mitGeduld(() => holeMitZeitlimit(treeUrl(namespace, subdir), {
7590
7605
  headers: { Authorization: `Bearer ${token}` }
7591
7606
  }));
7592
7607
  if (res.status === 404) return [];
@@ -7594,7 +7609,7 @@ async function hfListDir(namespace, subdir, token) {
7594
7609
  return res.json();
7595
7610
  }
7596
7611
  async function hfDownloadJsonByPath(repoPath, token) {
7597
- const res = await mitGeduld(() => fetch(
7612
+ const res = await mitGeduld(() => holeMitZeitlimit(
7598
7613
  `${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
7599
7614
  { headers: { Authorization: `Bearer ${token}` } }
7600
7615
  ));
@@ -7602,7 +7617,7 @@ async function hfDownloadJsonByPath(repoPath, token) {
7602
7617
  return res.json();
7603
7618
  }
7604
7619
  async function hfDownloadBinaryByPath(repoPath, token) {
7605
- const res = await mitGeduld(() => fetch(
7620
+ const res = await mitGeduld(() => holeMitZeitlimit(
7606
7621
  `${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
7607
7622
  { headers: { Authorization: `Bearer ${token}` } }
7608
7623
  ));
@@ -9379,25 +9394,29 @@ function createHfImageLibrary(options) {
9379
9394
  try {
9380
9395
  const zustand = await loadHFState(namespace, token);
9381
9396
  const eintrag = (zustand?.metadata ?? []).find((e) => e.id === mediaRef);
9382
- if (!eintrag) {
9383
- return fehler(
9384
- `Das Bild ${mediaRef} steht nicht im Bestand. Moeglicherweise hat der Abgleich es noch nicht uebertragen.`,
9385
- true
9397
+ if (eintrag) {
9398
+ const base64 = await hfLoadImageAsBase64(
9399
+ mediaRef,
9400
+ token,
9401
+ void 0,
9402
+ eintrag.filename,
9403
+ void 0,
9404
+ eintrag.mimeType,
9405
+ false
9386
9406
  );
9407
+ if (!base64) {
9408
+ return fehler(`Das Bild ${mediaRef} ist im Bestand verzeichnet, aber nicht ladbar.`, true);
9409
+ }
9410
+ return { kind: "ok", value: { data: base64, mime_type: eintrag.mimeType || "image/jpeg" } };
9387
9411
  }
9388
- const base64 = await hfLoadImageAsBase64(
9389
- mediaRef,
9390
- token,
9391
- void 0,
9392
- eintrag.filename,
9393
- void 0,
9394
- eintrag.mimeType,
9395
- false
9396
- );
9397
- if (!base64) {
9398
- return fehler(`Das Bild ${mediaRef} ist im Bestand verzeichnet, aber nicht ladbar.`, true);
9412
+ for (const mimeType of ["image/png", "image/jpeg"]) {
9413
+ const base64 = await hfLoadImageAsBase64(mediaRef, token, void 0, void 0, void 0, mimeType, false);
9414
+ if (base64) return { kind: "ok", value: { data: base64, mime_type: mimeType } };
9399
9415
  }
9400
- return { kind: "ok", value: { data: base64, mime_type: eintrag.mimeType || "image/jpeg" } };
9416
+ return fehler(
9417
+ `Das Bild ${mediaRef} steht nicht im Bestand. Moeglicherweise hat der Abgleich es noch nicht uebertragen.`,
9418
+ true
9419
+ );
9401
9420
  } catch (error) {
9402
9421
  return fehler(
9403
9422
  `Das Bild ${mediaRef} konnte nicht geladen werden: ${error instanceof Error ? error.message : String(error)}`,
@@ -9447,6 +9466,32 @@ var IMAGE_MODEL_CATALOGUE = {
9447
9466
  max_images: 4,
9448
9467
  aspect_ratios: ["1:1", "3:4", "4:3", "9:16", "16:9"],
9449
9468
  supports_seed: true
9469
+ },
9470
+ /**
9471
+ * Stufe 6.9.2, Nachtrag — real gegen Flow geprüft (2026-08-17): Der
9472
+ * Modellwähler im Flow-„Generieren"-Tab zeigt aktuell nur "Nano Banana Pro"
9473
+ * und "Nano Banana 2" als wählbar an, "Imagen 4" erscheint dort ausgegraut.
9474
+ * `provider: 'flow'` statt `'google'` ist bewusst: Diese Modelle sind nur
9475
+ * über `Flow.generate.image` erreichbar, nicht über die direkte
9476
+ * Google-Imagen-REST-Route, die der React-Host benutzt (`reactImageProvider.ts`)
9477
+ * — React meldet sie deshalb korrekt als `unsupported`, ohne eine eigene
9478
+ * Host-Verzweigung zu brauchen (D-115/R-1: `hasAccess` prüft `provider`).
9479
+ */
9480
+ nano_banana_pro: {
9481
+ callable_model_id: "flow-nano-banana-pro",
9482
+ provider: "flow",
9483
+ max_images: 4,
9484
+ // Flow.generate.image dokumentiert nur '1:1' | '16:9' | '9:16' (howto-flow-sdk.md).
9485
+ aspect_ratios: ["1:1", "16:9", "9:16"],
9486
+ // Weder als Eingabe- noch als Ausgabeparameter dokumentiert — nichts erfunden (D-107).
9487
+ supports_seed: false
9488
+ },
9489
+ nano_banana_2: {
9490
+ callable_model_id: "flow-nano-banana-2",
9491
+ provider: "flow",
9492
+ max_images: 4,
9493
+ aspect_ratios: ["1:1", "16:9", "9:16"],
9494
+ supports_seed: false
9450
9495
  }
9451
9496
  };
9452
9497
  function callableImageModelFor(targetModelKey) {
@@ -11030,7 +11075,9 @@ function createClientRunPort(options) {
11030
11075
 
11031
11076
  // src/lib/szenen/flowImageProvider.ts
11032
11077
  var FLOW_MODEL_DISPLAY_NAMES = {
11033
- "imagen-4.0-generate-001": "Imagen 4"
11078
+ "imagen-4.0-generate-001": "Imagen 4",
11079
+ "flow-nano-banana-pro": "\u{1F34C} Nano Banana Pro",
11080
+ "flow-nano-banana-2": "\u{1F34C} Nano Banana 2"
11034
11081
  };
11035
11082
  function flowImageProviderHasAccess(model) {
11036
11083
  return model.callable_model_id in FLOW_MODEL_DISPLAY_NAMES;
package/dist/szenen.mjs CHANGED
@@ -130,7 +130,7 @@ import {
130
130
  validateElementVersion,
131
131
  validatePayload,
132
132
  withReference
133
- } from "./chunk-JTGC5PXJ.mjs";
133
+ } from "./chunk-MILMVOK7.mjs";
134
134
  import {
135
135
  ContractViolation,
136
136
  STYLE_PAYLOAD,
@@ -144,7 +144,7 @@ import {
144
144
  preferredVersionId,
145
145
  resolvePreferredHead
146
146
  } from "./chunk-FUY2OCVC.mjs";
147
- import "./chunk-VBWQP7PO.mjs";
147
+ import "./chunk-LFPOXNOZ.mjs";
148
148
 
149
149
  // src/lib/szenen/providerCheckStore.ts
150
150
  function collectProviderChecks(events) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rslsp1/fa-app-tools",
3
- "version": "2.0.83",
3
+ "version": "2.0.85",
4
4
  "description": "Shared tools and hooks for Fine Art flow apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",