@rslsp1/fa-app-tools 7.2.0 → 7.4.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/index.js CHANGED
@@ -132,7 +132,7 @@ __export(index_exports, {
132
132
  module.exports = __toCommonJS(index_exports);
133
133
 
134
134
  // package.json
135
- var version = "7.2.0";
135
+ var version = "7.4.0";
136
136
 
137
137
  // src/hooks/useOnClickOutside.ts
138
138
  var import_react = require("react");
@@ -2495,9 +2495,9 @@ function mergeSnapshotAndPending(snapshot, pending, bereitsImSnapshot = 0) {
2495
2495
  }
2496
2496
 
2497
2497
  // src/lib/hfEventLog.ts
2498
- function treeUrl(namespace, subdir = "") {
2498
+ function treeUrl(namespace, subdir = "", revision = "main") {
2499
2499
  const parts = [namespace.replace(/\/$/, ""), subdir].filter(Boolean).join("/");
2500
- return `${HF_BASE}/api/datasets/${HF_REPO}/tree/main${parts ? "/" + parts : ""}`;
2500
+ return `${HF_BASE}/api/datasets/${HF_REPO}/tree/${encodeURIComponent(revision)}${parts ? "/" + parts : ""}`;
2501
2501
  }
2502
2502
  var DROSSEL_VERSUCHE = 4;
2503
2503
  var DROSSEL_BASIS_MS = 400;
@@ -2530,8 +2530,8 @@ async function mitGeduld(anfrage) {
2530
2530
  }
2531
2531
  return letzte;
2532
2532
  }
2533
- async function hfListDir(namespace, subdir, token) {
2534
- const res = await mitGeduld(() => holeMitZeitlimit(treeUrl(namespace, subdir), {
2533
+ async function hfListDir(namespace, subdir, token, revision = "main") {
2534
+ const res = await mitGeduld(() => holeMitZeitlimit(treeUrl(namespace, subdir, revision), {
2535
2535
  headers: { Authorization: `Bearer ${token}` }
2536
2536
  }));
2537
2537
  if (res.status === 404) return [];
@@ -2560,17 +2560,46 @@ function toBase64(text) {
2560
2560
  bytes.forEach((b) => binary += String.fromCharCode(b));
2561
2561
  return btoa(binary);
2562
2562
  }
2563
- async function hfUploadSmallFile(repoPath, content, token, summary = `Update ${repoPath}`) {
2563
+ var HfPreconditionFailedError = class extends Error {
2564
+ constructor(message) {
2565
+ super(message);
2566
+ this.name = "HfPreconditionFailed";
2567
+ }
2568
+ };
2569
+ async function hfRepoHead(token, revision = "main") {
2570
+ const res = await mitGeduld(() => holeMitZeitlimit(
2571
+ `${HF_BASE}/api/datasets/${HF_REPO}/revision/${encodeURIComponent(revision)}`,
2572
+ { headers: { Authorization: `Bearer ${token}` } }
2573
+ ));
2574
+ if (!res.ok) {
2575
+ throw new Error(`HF revision failed: ${res.status} ${res.statusText} (${revision})`);
2576
+ }
2577
+ const daten = await res.json();
2578
+ if (typeof daten.sha !== "string" || daten.sha.length === 0) {
2579
+ throw new Error(
2580
+ `HF revision ${revision}: die Antwort traegt kein "sha". Erhaltene Felder: ${Object.keys(daten || {}).join(", ") || "(keine)"}.`
2581
+ );
2582
+ }
2583
+ return daten.sha;
2584
+ }
2585
+ async function hfUploadSmallFile(repoPath, content, token, summary = `Update ${repoPath}`, options) {
2586
+ const kopf = { summary, description: "" };
2587
+ if (options?.parentCommit) kopf.parentCommit = options.parentCommit;
2564
2588
  const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
2565
2589
  method: "POST",
2566
2590
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
2567
2591
  body: [
2568
- JSON.stringify({ key: "header", value: { summary, description: "" } }),
2592
+ JSON.stringify({ key: "header", value: kopf }),
2569
2593
  JSON.stringify({ key: "file", value: { path: repoPath, encoding: "base64", content: toBase64(content) } })
2570
2594
  ].join("\n")
2571
2595
  });
2572
2596
  if (!res.ok) {
2573
2597
  const err = await res.text().catch(() => "");
2598
+ if (options?.parentCommit && (res.status === 412 || res.status === 409)) {
2599
+ throw new HfPreconditionFailedError(
2600
+ `HF commit abgelehnt: Der Branch hat sich seit ${options.parentCommit} bewegt (${res.status}). Es wurde nichts geschrieben.`
2601
+ );
2602
+ }
2574
2603
  throw new Error(`HF upload failed: ${res.status} \u2014 ${err.slice(0, 200)}`);
2575
2604
  }
2576
2605
  }
@@ -2620,8 +2649,8 @@ async function mitBegrenzterParallelitaet(count, limit, task) {
2620
2649
  await Promise.all(Array.from({ length: Math.min(limit, count) }, worker));
2621
2650
  if (fehlerAufgetreten) throw ersterFehler;
2622
2651
  }
2623
- async function loadPendingEvents(namespace, token, sinceTs, knownPaths) {
2624
- const files = await hfListDir(namespace, "events", token);
2652
+ async function loadPendingEvents(namespace, token, sinceTs, knownPaths, revision = "main") {
2653
+ const files = await hfListDir(namespace, "events", token, revision);
2625
2654
  const pending = files.filter((f) => f.type === "file" && tsFromEventPath(f.path) > sinceTs && !knownPaths?.has(f.path)).sort((a, b) => a.path.localeCompare(b.path));
2626
2655
  if (!pending.length) return [];
2627
2656
  const raw = new Array(pending.length);
@@ -2666,7 +2695,13 @@ async function writeHFEvent(namespace, token, type, payload, prevTs, options) {
2666
2695
  const suffix = options?.ordered ? `${String(++writeSeq % 1e6).padStart(6, "0")}${uuid}` : uuid;
2667
2696
  const filename = `${tsToFilename(ts)}_${suffix}.json`;
2668
2697
  const repoPath = `${namespace}events/${filename}`;
2669
- await hfUploadSmallFile(repoPath, JSON.stringify(event, null, 2), token, `Event: ${type}`);
2698
+ await hfUploadSmallFile(
2699
+ repoPath,
2700
+ JSON.stringify(event, null, 2),
2701
+ token,
2702
+ `Event: ${type}`,
2703
+ options?.parentCommit ? { parentCommit: options.parentCommit } : void 0
2704
+ );
2670
2705
  return event;
2671
2706
  }
2672
2707
 
@@ -6018,7 +6053,8 @@ function capabilitiesOf(port) {
6018
6053
  imageBatchHistory: typeof port.imageBatchHistory?.listBatchesForPromptRun === "function",
6019
6054
  imageOrderHistory: typeof port.imageBatchHistory?.listOrdersForPromptRun === "function",
6020
6055
  imageBatchWrite: typeof port.imageBatchWrite?.importBatch === "function",
6021
- imageGeneration: typeof port.imageGeneration?.startFromPromptRun === "function"
6056
+ imageGeneration: typeof port.imageGeneration?.startFromPromptRun === "function",
6057
+ imageJobs: typeof port.imageJobs?.claimAndRun === "function"
6022
6058
  };
6023
6059
  }
6024
6060
  async function callOptional(capability, operation) {
@@ -9165,13 +9201,13 @@ function noticeFor(ergebnis, was) {
9165
9201
  };
9166
9202
  }
9167
9203
  case "rejected": {
9168
- const fehler4 = ergebnis.errors ?? [];
9204
+ const fehler5 = ergebnis.errors ?? [];
9169
9205
  const ungeprueft = ergebnis.unchecked ?? [];
9170
9206
  return {
9171
9207
  tone: "rejected",
9172
- text: `${was}: abgelehnt \u2014 ${fehler4.length} Befund${fehler4.length === 1 ? "" : "e"}.`,
9208
+ text: `${was}: abgelehnt \u2014 ${fehler5.length} Befund${fehler5.length === 1 ? "" : "e"}.`,
9173
9209
  details: [
9174
- ...fehler4.map((f) => `${f.path ?? "(ohne Pfad)"}: ${f.message ?? "ohne Meldung"}`),
9210
+ ...fehler5.map((f) => `${f.path ?? "(ohne Pfad)"}: ${f.message ?? "ohne Meldung"}`),
9175
9211
  // Nicht verschweigen: was nicht geprüft wurde, ist nicht in Ordnung,
9176
9212
  // sondern ungeprüft.
9177
9213
  ...ungeprueft.map((u) => `ungepr\xFCft: ${u}`)
@@ -12671,7 +12707,7 @@ var ERKLAERUNG = {
12671
12707
  };
12672
12708
  var CapabilityPanel = ({ port, elementId, setupPayload }) => {
12673
12709
  const [bericht, setBericht] = (0, import_react37.useState)(null);
12674
- const [fehler4, setFehler] = (0, import_react37.useState)(null);
12710
+ const [fehler5, setFehler] = (0, import_react37.useState)(null);
12675
12711
  (0, import_react37.useEffect)(() => {
12676
12712
  let abgemeldet = false;
12677
12713
  setBericht(null);
@@ -12693,10 +12729,10 @@ var CapabilityPanel = ({ port, elementId, setupPayload }) => {
12693
12729
  abgemeldet = true;
12694
12730
  };
12695
12731
  }, [port, elementId, setupPayload]);
12696
- if (fehler4) {
12732
+ if (fehler5) {
12697
12733
  return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("p", { "data-testid": "capability-fehler", role: "alert", children: [
12698
12734
  "F\xE4higkeitsbericht nicht erstellbar: ",
12699
- fehler4
12735
+ fehler5
12700
12736
  ] });
12701
12737
  }
12702
12738
  if (!bericht) return /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { "data-testid": "capability-laden", children: "F\xE4higkeitsbericht wird erstellt \u2026" });
@@ -13063,7 +13099,60 @@ var SZENEN_EVENT_TYPES = {
13063
13099
  * Der Elementreducer ignoriert auch diesen Typ (`default:`-Zweig); gelesen
13064
13100
  * wird mit `collectImageBatches` (`imageBatchStore.ts`).
13065
13101
  */
13066
- imageBatchPersisted: "szenen.image_batch_persisted"
13102
+ imageBatchPersisted: "szenen.image_batch_persisted",
13103
+ /**
13104
+ * Ein vorbereiteter Promptvorgang — der Durchstich „Szene → ChatGPT → Lauf".
13105
+ *
13106
+ * Der bestehende Promptpfad ruft das Modell selbst
13107
+ * (`runPromptDerivation()`); ein aufrufendes Chatmodell formuliert dagegen
13108
+ * **zwischen** zwei Werkzeugaufrufen. Damit zerfaellt der eine Vorgang in
13109
+ * zwei, und der zweite braucht genau den Stand, den der erste eingefroren
13110
+ * hat — sonst loeste der Abschluss die inzwischen geaenderte Szene neu auf
13111
+ * und der Lauf behauptete eine Herkunft, die er nicht hat (D-22).
13112
+ *
13113
+ * Deshalb ein eigenes Ereignis und **kein** fluechtiger Prozesszustand: Der
13114
+ * Snapshot ueberlebt Neustart, Hostwechsel und Sitzungsende, weil er im
13115
+ * selben autoritativen Log liegt wie der Lauf, der daraus entsteht.
13116
+ *
13117
+ * Der Elementreducer ignoriert diesen Typ (`default:`-Zweig); gelesen wird
13118
+ * mit `collectPromptOperations` (`scenePromptOperation.ts`).
13119
+ */
13120
+ promptOperationPrepared: "szenen.prompt_operation_prepared",
13121
+ /**
13122
+ * Ein Bildauftrag wartet auf einen ausfuehrenden Flow-Host.
13123
+ *
13124
+ * Die vorhandene Auftragshistorie (`imageGenerationStarted`) beginnt
13125
+ * **unmittelbar vor** dem Provideraufruf und kennt deshalb keinen wartenden
13126
+ * Auftrag. Genau der entsteht hier: Ein Chatmodell beauftragt ueber MCP ein
13127
+ * Bild, der Auftrag wird dauerhaft abgelegt, und der Werkzeugaufruf endet
13128
+ * sofort mit seiner Kennung — er haelt keine Antwort minutenlang offen.
13129
+ *
13130
+ * Traegt **keinen** Prompt: Der steht im referenzierten Lauf, und ihn hier
13131
+ * zu kopieren waere eine zweite Wahrheit ueber denselben eingefrorenen Text
13132
+ * (D-103).
13133
+ */
13134
+ imageJobRequested: "szenen.image_job_requested",
13135
+ /**
13136
+ * Ein Flow-Host beansprucht einen wartenden Bildauftrag.
13137
+ *
13138
+ * Ein append-only Log kennt kein Lock: Zwei Tabs koennen beide anhaengen.
13139
+ * Deshalb ist die Uebernahme **nicht** der Append, sondern das Ergebnis
13140
+ * einer deterministischen Schiedsregel darueber (`winningClaimOf`) — der
13141
+ * erste Anspruch in Logreihenfolge gewinnt, und jeder Host liest nach
13142
+ * seinem Append erneut, bevor er einen bezahlten Provideraufruf startet.
13143
+ * Ein unterlegener Anspruch bleibt sichtbar stehen; er wird nicht geloescht,
13144
+ * weil das Log das nicht kann und weil der Versuch ein Beleg ist.
13145
+ */
13146
+ imageJobClaimed: "szenen.image_job_claimed",
13147
+ /**
13148
+ * Der Bildauftrag ist erledigt — mit dem Ausgang, den der Host gesehen hat.
13149
+ *
13150
+ * Getrennt von `imageGenerationFinished`: Jenes gehoert zum bezahlten
13151
+ * Provideraufruf, dieses zur Zustellung. Ein Auftrag kann fehlschlagen,
13152
+ * **bevor** ein Provider gerufen wurde (abgelehntes Profil), und dann gibt
13153
+ * es kein `imageGenerationFinished`, das den Wartenden erloesen wuerde.
13154
+ */
13155
+ imageJobSettled: "szenen.image_job_settled"
13067
13156
  };
13068
13157
  function sortForReplay(events) {
13069
13158
  return [...events].sort((a, b) => {
@@ -13416,9 +13505,9 @@ async function leseBilderAusZip(datei) {
13416
13505
  );
13417
13506
  }
13418
13507
  const { default: JSZip3 } = await import("jszip");
13419
- const archiv = await JSZip3.loadAsync(datei).catch((fehler4) => {
13508
+ const archiv = await JSZip3.loadAsync(datei).catch((fehler5) => {
13420
13509
  throw new Error(
13421
- `${name} konnte nicht als ZIP gelesen werden: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
13510
+ `${name} konnte nicht als ZIP gelesen werden: ${fehler5 instanceof Error ? fehler5.message : String(fehler5)}`
13422
13511
  );
13423
13512
  });
13424
13513
  const pfade = [];
@@ -13615,6 +13704,7 @@ var RunImagesPanel = ({ port, lauf }) => {
13615
13704
  const neuLesen = () => setNachladen((n) => n + 1);
13616
13705
  const [erzeugenOffen, setErzeugenOffen] = (0, import_react40.useState)(null);
13617
13706
  const [zuordnenOffen, setZuordnenOffen] = (0, import_react40.useState)(null);
13707
+ const [chatAuftraegeOffen, setChatAuftraegeOffen] = (0, import_react40.useState)(null);
13618
13708
  const quelle = sourceOf(lauf);
13619
13709
  const [wahlPhase] = usePortQuery(
13620
13710
  () => port.imageGeneration ? port.imageGeneration.imageModelChoiceForRun(lauf.run_id) : Promise.resolve({ kind: "ok", value: null }),
@@ -13629,10 +13719,17 @@ var RunImagesPanel = ({ port, lauf }) => {
13629
13719
  () => port.imageBatchHistory ? port.imageBatchHistory.listBatchesForPromptRun(lauf.run_id) : Promise.resolve({ kind: "ok", value: [] }),
13630
13720
  [port, lauf.run_id, nachladen]
13631
13721
  );
13722
+ const [chatAuftraegePhase] = usePortQuery(
13723
+ () => port.imageJobs ? port.imageJobs.listJobs(lauf.run_id) : Promise.resolve({ kind: "ok", value: [] }),
13724
+ [port, lauf.run_id, nachladen]
13725
+ );
13632
13726
  const auftraege = auftraegePhase.status === "ready" ? auftraegePhase.value : [];
13633
13727
  const batches = batchesPhase.status === "ready" ? batchesPhase.value : [];
13728
+ const chatAuftraege = chatAuftraegePhase.status === "ready" ? chatAuftraegePhase.value : [];
13729
+ const offeneChatAuftraege = chatAuftraege.filter((a) => a.status === "pending").length;
13634
13730
  const auffaellige = auftraege.filter((a) => a.status !== "succeeded");
13635
13731
  const erzeugenAuf = erzeugenOffen ?? batches.length === 0;
13732
+ const chatAuftraegeAuf = chatAuftraegeOffen ?? offeneChatAuftraege > 0;
13636
13733
  return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "szenen-bilder", "data-testid": `bilder-${lauf.run_id}`, children: [
13637
13734
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("section", { className: "szenen-bilder__eingang", "data-testid": `eingang-${lauf.run_id}`, children: [
13638
13735
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("h5", { children: "Eingang \u2014 woraus dieser Prompt entstand" }),
@@ -13753,6 +13850,43 @@ var RunImagesPanel = ({ port, lauf }) => {
13753
13850
  }
13754
13851
  )
13755
13852
  ] }),
13853
+ faehig.imageJobs && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
13854
+ "details",
13855
+ {
13856
+ className: "szenen-bilder__aktion",
13857
+ "data-testid": `auftraege-chat-bereich-${lauf.run_id}`,
13858
+ open: chatAuftraegeAuf,
13859
+ children: [
13860
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
13861
+ "summary",
13862
+ {
13863
+ onClick: (e) => {
13864
+ e.preventDefault();
13865
+ setChatAuftraegeOffen(!chatAuftraegeAuf);
13866
+ },
13867
+ children: [
13868
+ "Bildauftraege aus dem Chat",
13869
+ offeneChatAuftraege > 0 ? ` (${offeneChatAuftraege} wartend)` : ""
13870
+ ]
13871
+ }
13872
+ ),
13873
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
13874
+ ChatAuftraegeAktion,
13875
+ {
13876
+ port,
13877
+ lauf,
13878
+ auftraege: chatAuftraege,
13879
+ fehler: chatAuftraegePhase.status === "error" ? chatAuftraegePhase.message : null,
13880
+ laedt: chatAuftraegePhase.status === "loading",
13881
+ onFertig: () => {
13882
+ setChatAuftraegeOffen(true);
13883
+ neuLesen();
13884
+ }
13885
+ }
13886
+ )
13887
+ ]
13888
+ }
13889
+ ),
13756
13890
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(
13757
13891
  "details",
13758
13892
  {
@@ -13936,7 +14070,7 @@ function useBildQuellen(port, bilder) {
13936
14070
  erneutVersuchen: () => setVersuch((n) => n + 1)
13937
14071
  };
13938
14072
  }
13939
- var BildDetails = ({ bild, fehler: fehler4, aufloesbar }) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { className: "szenen-bilder__meta", "data-testid": `batchbild-${bild.media_ref}`, children: [
14073
+ var BildDetails = ({ bild, fehler: fehler5, aufloesbar }) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { className: "szenen-bilder__meta", "data-testid": `batchbild-${bild.media_ref}`, children: [
13940
14074
  /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("code", { children: bild.media_ref }),
13941
14075
  /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { children: [
13942
14076
  "Position ",
@@ -13949,7 +14083,7 @@ var BildDetails = ({ bild, fehler: fehler4, aufloesbar }) => /* @__PURE__ */ (0,
13949
14083
  "\xD7",
13950
14084
  bild.height
13951
14085
  ] }) : null,
13952
- fehler4 && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("em", { "data-testid": `batchbild-fehler-${bild.media_ref}`, children: fehler4 }),
14086
+ fehler5 && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("em", { "data-testid": `batchbild-fehler-${bild.media_ref}`, children: fehler5 }),
13953
14087
  !aufloesbar && // Capability-Grenze, kein Fehler: Dieser Host kann Batches lesen,
13954
14088
  // aber keine Bilder auflösen (E). Kein erfundener Fallback auf eine
13955
14089
  // Serverroute (A6).
@@ -13965,18 +14099,18 @@ var HERKUNFT_KURZ = {
13965
14099
  external_import: "extern importiert"
13966
14100
  };
13967
14101
  var BatchKachel = ({ port, batch }) => {
13968
- const { quellen, fehler: fehler4, wiederholbar, erneutVersuchen } = useBildQuellen(port, batch.images);
14102
+ const { quellen, fehler: fehler5, wiederholbar, erneutVersuchen } = useBildQuellen(port, batch.images);
13969
14103
  const aufloesbar = Boolean(port.imageLibrary);
13970
14104
  const bilder = batch.images.map((bild) => ({
13971
14105
  id: bild.media_ref,
13972
14106
  quelle: quellen.get(bild.media_ref) ?? null,
13973
14107
  titel: `Bild ${bild.position + 1}`,
13974
- ...fehler4.get(bild.media_ref) ? { leerText: "Nicht ladbar" } : {},
14108
+ ...fehler5.get(bild.media_ref) ? { leerText: "Nicht ladbar" } : {},
13975
14109
  details: /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
13976
14110
  BildDetails,
13977
14111
  {
13978
14112
  bild,
13979
- fehler: fehler4.get(bild.media_ref) ?? null,
14113
+ fehler: fehler5.get(bild.media_ref) ?? null,
13980
14114
  aufloesbar
13981
14115
  }
13982
14116
  )
@@ -14036,7 +14170,7 @@ var BatchKachel = ({ port, batch }) => {
14036
14170
  BildDetails,
14037
14171
  {
14038
14172
  bild,
14039
- fehler: fehler4.get(bild.media_ref) ?? null,
14173
+ fehler: fehler5.get(bild.media_ref) ?? null,
14040
14174
  aufloesbar
14041
14175
  }
14042
14176
  ) }, bild.media_ref)) })
@@ -14305,11 +14439,11 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
14305
14439
  setSeedListe(DEFAULT_SEED_VORLAGE.seeds);
14306
14440
  setVorlageKey(DEFAULT_VORLAGE_KEY);
14307
14441
  }
14308
- } catch (fehler4) {
14442
+ } catch (fehler5) {
14309
14443
  if (veraltet()) return;
14310
14444
  setNotiz({
14311
14445
  tone: "error",
14312
- text: fehler4 instanceof Error ? fehler4.message : String(fehler4)
14446
+ text: fehler5 instanceof Error ? fehler5.message : String(fehler5)
14313
14447
  });
14314
14448
  }
14315
14449
  if (veraltet()) return;
@@ -14328,10 +14462,10 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
14328
14462
  setNotiz(
14329
14463
  ergebnis.kind === "ok" ? { tone: "ok", text: `Importiert als Item ${ergebnis.value.item_id} mit ${bilder.length} Bild(ern).` } : noticeFor(ergebnis, "Externe Bilder importieren")
14330
14464
  );
14331
- } catch (fehler4) {
14465
+ } catch (fehler5) {
14332
14466
  setNotiz({
14333
14467
  tone: "error",
14334
- text: `Externe Bilder importieren: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
14468
+ text: `Externe Bilder importieren: ${fehler5 instanceof Error ? fehler5.message : String(fehler5)}`
14335
14469
  });
14336
14470
  }
14337
14471
  setLaeuft(false);
@@ -14353,10 +14487,10 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
14353
14487
  ...seed !== void 0 ? { seed } : {}
14354
14488
  });
14355
14489
  }
14356
- } catch (fehler4) {
14490
+ } catch (fehler5) {
14357
14491
  setNotiz({
14358
14492
  tone: "error",
14359
- text: `Externe Bilder importieren: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
14493
+ text: `Externe Bilder importieren: ${fehler5 instanceof Error ? fehler5.message : String(fehler5)}`
14360
14494
  });
14361
14495
  return;
14362
14496
  }
@@ -14692,6 +14826,101 @@ function readAsBase64(datei) {
14692
14826
  leser.readAsDataURL(datei);
14693
14827
  });
14694
14828
  }
14829
+ var CHAT_AUFTRAG_TEXT = {
14830
+ pending: "wartet auf \xDCbernahme",
14831
+ // Wie bei `started` (D-119): laufend und abgebrochen sind aus dem Log
14832
+ // nicht unterscheidbar, und es gibt keinen automatischen Retry, der die
14833
+ // Unterscheidung bräuchte.
14834
+ claimed: "\xFCbernommen (l\xE4uft oder unterbrochen)",
14835
+ succeeded: "erfolgreich",
14836
+ partial: "teilweise",
14837
+ failed: "fehlgeschlagen",
14838
+ rejected: "abgelehnt \u2014 es wurde nichts ausgef\xFChrt"
14839
+ };
14840
+ var ChatAuftraegeAktion = ({
14841
+ port,
14842
+ lauf,
14843
+ auftraege,
14844
+ fehler: fehler5,
14845
+ laedt,
14846
+ onFertig
14847
+ }) => {
14848
+ const [laufend, setLaufend] = (0, import_react40.useState)(null);
14849
+ const [notiz, setNotiz] = (0, import_react40.useState)(null);
14850
+ async function uebernehmen(jobId) {
14851
+ if (!port.imageJobs) return;
14852
+ setLaufend(jobId);
14853
+ setNotiz(null);
14854
+ try {
14855
+ const ergebnis = await port.imageJobs.claimAndRun(jobId);
14856
+ if (ergebnis.kind === "ok") {
14857
+ setNotiz({
14858
+ tone: ergebnis.job.status === "succeeded" ? "ok" : "rejected",
14859
+ text: `Auftrag ${jobId}: ${CHAT_AUFTRAG_TEXT[ergebnis.job.status]}.` + (ergebnis.job.diagnostic ? ` ${ergebnis.job.diagnostic}` : "")
14860
+ });
14861
+ } else if (ergebnis.kind === "lost") {
14862
+ setNotiz({ tone: "rejected", text: ergebnis.message });
14863
+ } else {
14864
+ setNotiz({ tone: "error", text: ergebnis.message });
14865
+ }
14866
+ } catch (e) {
14867
+ setNotiz({
14868
+ tone: "error",
14869
+ text: `Die \xDCbernahme von Auftrag ${jobId} ist technisch gescheitert: ` + (e instanceof Error ? e.message : String(e))
14870
+ });
14871
+ } finally {
14872
+ setLaufend(null);
14873
+ onFertig();
14874
+ }
14875
+ }
14876
+ return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("div", { className: "szenen-bilder__aktion-inhalt", "data-testid": `chat-auftraege-${lauf.run_id}`, children: [
14877
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { className: "szenen-area__lead", children: "Auftr\xE4ge, die \xFCber den MCP aus dem Chat kamen. Der Prompt wird nicht kopiert \u2014 ausgef\xFChrt wird der eingefrorene Prompt dieses Laufs." }),
14878
+ fehler5 && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("p", { role: "alert", "data-testid": `chat-auftraege-fehler-${lauf.run_id}`, children: [
14879
+ "Bildauftr\xE4ge aus dem Chat konnten nicht gelesen werden: ",
14880
+ fehler5
14881
+ ] }),
14882
+ !fehler5 && !laedt && auftraege.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("p", { "data-testid": `chat-auftraege-leer-${lauf.run_id}`, children: "F\xFCr diesen Lauf liegt kein Auftrag aus dem Chat vor." }),
14883
+ notiz && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
14884
+ "p",
14885
+ {
14886
+ role: notiz.tone === "error" ? "alert" : void 0,
14887
+ "data-testid": `chat-auftraege-notiz-${lauf.run_id}`,
14888
+ children: notiz.text
14889
+ }
14890
+ ),
14891
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("ul", { className: "szenen-bilder__auftraege", children: auftraege.map((auftrag) => /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("li", { "data-testid": `chat-auftrag-${auftrag.job_id}`, children: [
14892
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("span", { children: CHAT_AUFTRAG_TEXT[auftrag.status] }),
14893
+ " \u2014 ",
14894
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { children: [
14895
+ auftrag.image_count,
14896
+ " ",
14897
+ auftrag.image_count === 1 ? "Bild" : "Bilder",
14898
+ " mit",
14899
+ " ",
14900
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("code", { children: auftrag.execution_target_model_key }),
14901
+ " / ",
14902
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("code", { children: auftrag.execution_profile_id }),
14903
+ auftrag.aspect_ratio ? ` im Format ${auftrag.aspect_ratio}` : ""
14904
+ ] }),
14905
+ auftrag.diagnostic && /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)("span", { children: [
14906
+ " ",
14907
+ auftrag.diagnostic
14908
+ ] }),
14909
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("br", {}),
14910
+ /* @__PURE__ */ (0, import_jsx_runtime36.jsx)("code", { className: "szenen-bilder__auftragskennung", children: auftrag.job_id }),
14911
+ auftrag.status === "pending" && /* @__PURE__ */ (0, import_jsx_runtime36.jsx)(
14912
+ "button",
14913
+ {
14914
+ type: "button",
14915
+ disabled: laufend !== null,
14916
+ onClick: () => void uebernehmen(auftrag.job_id),
14917
+ "data-testid": `chat-auftrag-uebernehmen-${auftrag.job_id}`,
14918
+ children: laufend === auftrag.job_id ? "\xDCbernahme l\xE4uft\u2026" : "\xDCbernehmen und ausf\xFChren (kostet einen Bildaufruf)"
14919
+ }
14920
+ )
14921
+ ] }, auftrag.job_id)) })
14922
+ ] });
14923
+ };
14695
14924
 
14696
14925
  // src/features/szenen/areas/RunsPanel.tsx
14697
14926
  var import_jsx_runtime37 = require("react/jsx-runtime");
@@ -19253,7 +19482,7 @@ function createHfEventChannel(options) {
19253
19482
  let syncInFlight = null;
19254
19483
  let snapshot = null;
19255
19484
  let snapshotGeladen = ohneSnapshot;
19256
- const refresh = async () => {
19485
+ const refresh = async (revision = "main") => {
19257
19486
  if (!snapshotGeladen) {
19258
19487
  snapshot = await loadLatestSzenenSnapshot(namespace, token);
19259
19488
  snapshotGeladen = true;
@@ -19261,7 +19490,7 @@ function createHfEventChannel(options) {
19261
19490
  knownPaths.add(`${namespace}events/${name}`);
19262
19491
  }
19263
19492
  }
19264
- const neue = await loadPendingEvents(namespace, token, sinceTs, knownPaths);
19493
+ const neue = await loadPendingEvents(namespace, token, sinceTs, knownPaths, revision);
19265
19494
  if (neue.length) cachedRawEvents = [...cachedRawEvents, ...neue];
19266
19495
  };
19267
19496
  const sync = () => {
@@ -19272,16 +19501,34 @@ function createHfEventChannel(options) {
19272
19501
  }
19273
19502
  return syncInFlight;
19274
19503
  };
19504
+ const sicht = () => {
19505
+ const pending = cachedRawEvents.filter((event) => typeof event.type === "string" && event.type.startsWith(SZENEN_EVENT_PREFIX)).map((event) => ({
19506
+ type: event.type,
19507
+ payload: event.payload,
19508
+ clientId: event.clientId,
19509
+ ts: event.ts
19510
+ }));
19511
+ return mergeSnapshotAndPending(snapshot, pending).events;
19512
+ };
19275
19513
  return {
19276
19514
  async readEvents() {
19277
19515
  await sync();
19278
- const pending = cachedRawEvents.filter((event) => typeof event.type === "string" && event.type.startsWith(SZENEN_EVENT_PREFIX)).map((event) => ({
19279
- type: event.type,
19280
- payload: event.payload,
19281
- clientId: event.clientId,
19282
- ts: event.ts
19283
- }));
19284
- return mergeSnapshotAndPending(snapshot, pending).events;
19516
+ return sicht();
19517
+ },
19518
+ async readHead() {
19519
+ return hfRepoHead(token);
19520
+ },
19521
+ async readEventsAt(revision) {
19522
+ await refresh(revision);
19523
+ return sicht();
19524
+ },
19525
+ async appendEventAt(type, payload, parentCommit) {
19526
+ if (!type.startsWith(SZENEN_EVENT_PREFIX)) {
19527
+ throw new Error(
19528
+ `Event-Typ "${type}" gehoert nicht zur Domain szenen. Der geteilte Kanal wird ueber das Praefix "${SZENEN_EVENT_PREFIX}" getrennt.`
19529
+ );
19530
+ }
19531
+ await writeHFEvent(namespace, token, type, payload, [], { ordered: true, parentCommit });
19285
19532
  },
19286
19533
  async appendEvent(type, payload) {
19287
19534
  if (!type.startsWith(SZENEN_EVENT_PREFIX)) {
@@ -19465,11 +19712,11 @@ async function resolveShotPayload(input) {
19465
19712
  };
19466
19713
  }
19467
19714
  const shot = shotPayload;
19468
- for (const fehler4 of ohneKettenReferenz(
19715
+ for (const fehler5 of ohneKettenReferenz(
19469
19716
  validatePayload("shot", shotPayload).errors,
19470
19717
  chain ? "scene_setup_ref" : null
19471
19718
  )) {
19472
- diagnostics.push(diagnosticOfSchemaError(fehler4, `Shot ${shotElementId}`));
19719
+ diagnostics.push(diagnosticOfSchemaError(fehler5, `Shot ${shotElementId}`));
19473
19720
  }
19474
19721
  let setup = null;
19475
19722
  let report = null;
@@ -19581,8 +19828,8 @@ async function resolveScenePayload(input) {
19581
19828
  "Kein Scene Setup, kein Shot: Der Lauf entsteht aus der Szene allein. Bildaufbau, Kamera und Zielmodell wurden nicht ergaenzt, sondern sind nicht vorhanden (D-135)."
19582
19829
  ];
19583
19830
  const diagnostics = [];
19584
- for (const fehler4 of validatePayload("scene", scenePayload).errors) {
19585
- diagnostics.push(diagnosticOfSchemaError(fehler4, `Szene ${sceneElementId}`));
19831
+ for (const fehler5 of validatePayload("scene", scenePayload).errors) {
19832
+ diagnostics.push(diagnosticOfSchemaError(fehler5, `Szene ${sceneElementId}`));
19586
19833
  }
19587
19834
  const faehig = sceneOnlyCapabilities(scenePayload);
19588
19835
  return komponiere({
@@ -21131,8 +21378,8 @@ function createFlowImageProvider(generateImage) {
21131
21378
  // Kein Seed: weder als Eingabe dokumentiert noch als Ausgabe
21132
21379
  // geliefert — nichts wird erfunden (D-107).
21133
21380
  });
21134
- } catch (fehler4) {
21135
- ersterFehler = fehler4 instanceof Error ? fehler4.message : String(fehler4);
21381
+ } catch (fehler5) {
21382
+ ersterFehler = fehler5 instanceof Error ? fehler5.message : String(fehler5);
21136
21383
  break;
21137
21384
  }
21138
21385
  }
@@ -21658,6 +21905,249 @@ function createImageGenerationPort(options) {
21658
21905
  };
21659
21906
  }
21660
21907
 
21908
+ // src/lib/szenen/imageJobStore.ts
21909
+ var istText6 = (wert) => typeof wert === "string" && wert.trim().length > 0;
21910
+ var SETTLED_STATUS = ["succeeded", "partial", "failed", "rejected"];
21911
+ function collectImageJobs(events) {
21912
+ const nachId = /* @__PURE__ */ new Map();
21913
+ for (const event of events) {
21914
+ if (event.type !== SZENEN_EVENT_TYPES.imageJobRequested) continue;
21915
+ const payload = event.payload;
21916
+ if (!payload || !istText6(payload.job_id) || !istText6(payload.prompt_run_id)) continue;
21917
+ if (nachId.has(payload.job_id)) continue;
21918
+ nachId.set(payload.job_id, {
21919
+ job_id: payload.job_id,
21920
+ prompt_run_id: payload.prompt_run_id,
21921
+ execution_target_model_key: payload.execution_target_model_key ?? "",
21922
+ execution_profile_id: payload.execution_profile_id ?? "",
21923
+ image_count: typeof payload.image_count === "number" ? payload.image_count : 0,
21924
+ ...payload.aspect_ratio ? { aspect_ratio: payload.aspect_ratio } : {},
21925
+ requested_by: payload.requested_by ?? "",
21926
+ store_id: payload.store_id ?? "",
21927
+ created_at: payload.created_at ?? "",
21928
+ status: "pending",
21929
+ claim_attempts: []
21930
+ });
21931
+ }
21932
+ for (const event of events) {
21933
+ if (event.type !== SZENEN_EVENT_TYPES.imageJobClaimed) continue;
21934
+ const payload = event.payload;
21935
+ if (!payload || !istText6(payload.job_id) || !istText6(payload.claim_id)) continue;
21936
+ const auftrag = nachId.get(payload.job_id);
21937
+ if (!auftrag) continue;
21938
+ const anspruch = {
21939
+ job_id: payload.job_id,
21940
+ claim_id: payload.claim_id,
21941
+ client_id: payload.client_id ?? "",
21942
+ claimed_at: payload.claimed_at ?? ""
21943
+ };
21944
+ nachId.set(payload.job_id, {
21945
+ ...auftrag,
21946
+ claim: auftrag.claim ?? anspruch,
21947
+ claim_attempts: [...auftrag.claim_attempts, anspruch],
21948
+ status: auftrag.status === "pending" ? "claimed" : auftrag.status
21949
+ });
21950
+ }
21951
+ for (const event of events) {
21952
+ if (event.type !== SZENEN_EVENT_TYPES.imageJobSettled) continue;
21953
+ const payload = event.payload;
21954
+ if (!payload || !istText6(payload.job_id)) continue;
21955
+ const auftrag = nachId.get(payload.job_id);
21956
+ if (!auftrag) continue;
21957
+ if (!auftrag.claim || auftrag.claim.claim_id !== payload.claim_id) continue;
21958
+ if (auftrag.status !== "claimed") continue;
21959
+ if (!SETTLED_STATUS.includes(payload.status)) continue;
21960
+ nachId.set(payload.job_id, {
21961
+ ...auftrag,
21962
+ status: payload.status,
21963
+ ...payload.generation_batch_id ? { generation_batch_id: payload.generation_batch_id } : {},
21964
+ ...payload.item_id ? { item_id: payload.item_id } : {},
21965
+ ...typeof payload.produced_count === "number" ? { produced_count: payload.produced_count } : {},
21966
+ ...payload.diagnostic ? { diagnostic: payload.diagnostic } : {},
21967
+ settled_at: payload.settled_at ?? ""
21968
+ });
21969
+ }
21970
+ return [...nachId.values()];
21971
+ }
21972
+ function jobsForPromptRun(events, runId) {
21973
+ return collectImageJobs(events).filter((j) => j.prompt_run_id === runId);
21974
+ }
21975
+ function findImageJob(events, jobId) {
21976
+ return collectImageJobs(events).find((j) => j.job_id === jobId) ?? null;
21977
+ }
21978
+ function generationBatchIdOf(jobId) {
21979
+ return `job-${jobId}`;
21980
+ }
21981
+
21982
+ // src/lib/szenen/imageJobPort.ts
21983
+ var ANSPRUCH_VERSUCHE = 4;
21984
+ function fehler4(error, aktion) {
21985
+ return {
21986
+ message: `${aktion}: ${error instanceof Error ? error.message : String(error)}`
21987
+ };
21988
+ }
21989
+ function createImageJobPort(options) {
21990
+ const { channel, imageGeneration, storeId, clientId } = options;
21991
+ const newClaimId = options.newClaimId ?? (() => crypto.randomUUID());
21992
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
21993
+ return {
21994
+ async listJobs(runId) {
21995
+ try {
21996
+ return { kind: "ok", value: jobsForPromptRun(await channel.readEvents(), runId) };
21997
+ } catch (error) {
21998
+ return {
21999
+ kind: "error",
22000
+ ...fehler4(error, `Die Bildauftraege des Laufs ${runId} konnten nicht gelesen werden`),
22001
+ retryable: true
22002
+ };
22003
+ }
22004
+ },
22005
+ async claimAndRun(jobId) {
22006
+ let letzterGrund = "";
22007
+ for (let versuch = 1; versuch <= ANSPRUCH_VERSUCHE; versuch += 1) {
22008
+ let kopf;
22009
+ try {
22010
+ kopf = await channel.readHead();
22011
+ } catch (error) {
22012
+ return {
22013
+ kind: "error",
22014
+ ...fehler4(error, `Der Kopf-Commit des Bestands war nicht lesbar; Bildauftrag ${jobId} wurde nicht uebernommen`),
22015
+ retryable: true
22016
+ };
22017
+ }
22018
+ let auftrag;
22019
+ try {
22020
+ auftrag = findImageJob(await channel.readEventsAt(kopf), jobId);
22021
+ } catch (error) {
22022
+ return {
22023
+ kind: "error",
22024
+ ...fehler4(error, `Der Bildauftrag ${jobId} konnte nicht gelesen werden`),
22025
+ retryable: true
22026
+ };
22027
+ }
22028
+ if (!auftrag) {
22029
+ return {
22030
+ kind: "rejected",
22031
+ message: `Der Bildauftrag ${jobId} steht nicht im autoritativen Log dieses Bestands.`
22032
+ };
22033
+ }
22034
+ if (auftrag.store_id !== storeId) {
22035
+ return {
22036
+ kind: "rejected",
22037
+ message: `Der Bildauftrag ${jobId} gilt fuer den Bestand "${auftrag.store_id}", dieser Host arbeitet auf "${storeId}". Nutzer, Umgebung und Bestand muessen zusammenpassen.`
22038
+ };
22039
+ }
22040
+ if (auftrag.status !== "pending") {
22041
+ return {
22042
+ kind: "lost",
22043
+ job: auftrag,
22044
+ message: `Der Bildauftrag ${jobId} ist bereits "${auftrag.status}"` + (auftrag.claim ? ` (Client ${auftrag.claim.client_id})` : "") + ". Er wird nicht erneut ausgefuehrt \u2014 ein zweiter Lauf waere ein zweites Mal bezahlte Rechenzeit."
22045
+ };
22046
+ }
22047
+ const anspruch = {
22048
+ job_id: jobId,
22049
+ claim_id: newClaimId(),
22050
+ client_id: clientId,
22051
+ claimed_at: now().toISOString()
22052
+ };
22053
+ try {
22054
+ await channel.appendEventAt(
22055
+ SZENEN_EVENT_TYPES.imageJobClaimed,
22056
+ anspruch,
22057
+ kopf
22058
+ );
22059
+ } catch (error) {
22060
+ if (istPreconditionFehler(error)) {
22061
+ letzterGrund = error instanceof Error ? error.message : String(error);
22062
+ continue;
22063
+ }
22064
+ return {
22065
+ kind: "error",
22066
+ ...fehler4(error, `Der Anspruch auf Bildauftrag ${jobId} konnte nicht angehaengt werden`),
22067
+ retryable: true
22068
+ };
22069
+ }
22070
+ const gewonnen = {
22071
+ ...auftrag,
22072
+ status: "claimed",
22073
+ claim: anspruch,
22074
+ claim_attempts: [...auftrag.claim_attempts, anspruch]
22075
+ };
22076
+ return await fuehreAus(gewonnen, anspruch, jobId);
22077
+ }
22078
+ return {
22079
+ kind: "error",
22080
+ message: `Der Bildauftrag ${jobId} liess sich in ${ANSPRUCH_VERSUCHE} Versuchen nicht exklusiv uebernehmen \u2014 der Bestand wird gerade von anderer Seite beschrieben (${letzterGrund}). Es wurde nichts ausgefuehrt und nichts bezahlt; ein neuer Versuch ist gefahrlos.`,
22081
+ retryable: true
22082
+ };
22083
+ }
22084
+ };
22085
+ async function fuehreAus(gewonnen, anspruch, jobId) {
22086
+ const ergebnis = await imageGeneration.startFromPromptRun({
22087
+ runId: gewonnen.prompt_run_id,
22088
+ generationBatchId: generationBatchIdOf(jobId),
22089
+ executionTargetModelKey: gewonnen.execution_target_model_key,
22090
+ executionProfileId: gewonnen.execution_profile_id,
22091
+ imageCount: gewonnen.image_count,
22092
+ ...gewonnen.aspect_ratio ? { aspectRatio: gewonnen.aspect_ratio } : {}
22093
+ });
22094
+ const abschluss = {
22095
+ job_id: jobId,
22096
+ claim_id: anspruch.claim_id,
22097
+ ...abschlussFelder(ergebnis, jobId),
22098
+ settled_at: now().toISOString()
22099
+ };
22100
+ try {
22101
+ await channel.appendEvent(SZENEN_EVENT_TYPES.imageJobSettled, abschluss);
22102
+ } catch {
22103
+ }
22104
+ const gelesen = await channel.readEvents().catch(() => null);
22105
+ return {
22106
+ kind: "ok",
22107
+ job: (gelesen && findImageJob(gelesen, jobId)) ?? {
22108
+ ...gewonnen,
22109
+ status: abschluss.status,
22110
+ ...abschluss.generation_batch_id ? { generation_batch_id: abschluss.generation_batch_id } : {},
22111
+ ...abschluss.diagnostic ? { diagnostic: abschluss.diagnostic } : {},
22112
+ settled_at: abschluss.settled_at
22113
+ }
22114
+ };
22115
+ }
22116
+ }
22117
+ function istPreconditionFehler(error) {
22118
+ return Boolean(error && typeof error === "object" && error.name === "HfPreconditionFailed");
22119
+ }
22120
+ function abschlussFelder(ergebnis, jobId) {
22121
+ const batchId = generationBatchIdOf(jobId);
22122
+ switch (ergebnis.kind) {
22123
+ case "succeeded":
22124
+ return {
22125
+ status: "succeeded",
22126
+ generation_batch_id: batchId,
22127
+ item_id: ergebnis.batch.item_id,
22128
+ produced_count: ergebnis.batch.images.length
22129
+ };
22130
+ case "partial":
22131
+ return {
22132
+ status: "partial",
22133
+ generation_batch_id: batchId,
22134
+ item_id: ergebnis.batch.item_id,
22135
+ produced_count: ergebnis.produced,
22136
+ diagnostic: `Angefordert ${ergebnis.requested}, geliefert ${ergebnis.produced}.`
22137
+ };
22138
+ case "failed":
22139
+ return { status: "failed", produced_count: 0, diagnostic: ergebnis.message };
22140
+ case "unsupported":
22141
+ return {
22142
+ status: "rejected",
22143
+ produced_count: 0,
22144
+ diagnostic: `Zielmodell "${ergebnis.targetModelKey}" ist in dieser Umgebung nicht ausfuehrbar: ${ergebnis.reason}`
22145
+ };
22146
+ default:
22147
+ return { status: "rejected", produced_count: 0, diagnostic: ergebnis.message };
22148
+ }
22149
+ }
22150
+
21661
22151
  // src/lib/szenen/flowViewPort.ts
21662
22152
  var defaultContext = {
21663
22153
  async chainFor(contextId) {
@@ -21756,10 +22246,21 @@ function createFlowSzenenViewPort(options) {
21756
22246
  },
21757
22247
  ...options.now ? { now: options.now } : {}
21758
22248
  }) : void 0;
22249
+ const kanalMitRevision = typeof channel.readHead === "function" && typeof channel.readEventsAt === "function" && typeof channel.appendEventAt === "function";
22250
+ const imageJobs = imageGeneration && hatHfZugangsdaten && kanalMitRevision ? createImageJobPort({
22251
+ channel,
22252
+ imageGeneration,
22253
+ // Dieselbe Form, in der der Server seinen Bestand kennt (`storeId`).
22254
+ storeId: `hf:${options.namespace}`,
22255
+ clientId: options.imageJobClientId ?? crypto.randomUUID(),
22256
+ ...options.newImageJobClaimId ? { newClaimId: options.newImageJobClaimId } : {},
22257
+ ...options.now ? { now: options.now } : {}
22258
+ }) : void 0;
21759
22259
  const port = { elements, context, runs, derivation, imageBatchHistory };
21760
22260
  if (imageLibrary) port.imageLibrary = imageLibrary;
21761
22261
  if (imageBatchWrite) port.imageBatchWrite = imageBatchWrite;
21762
22262
  if (imageGeneration) port.imageGeneration = imageGeneration;
22263
+ if (imageJobs) port.imageJobs = imageJobs;
21763
22264
  return port;
21764
22265
  }
21765
22266