@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.
@@ -96,9 +96,9 @@ function bildEndungFuer(mimeType) {
96
96
  }
97
97
 
98
98
  // src/lib/hfEventLog.ts
99
- function treeUrl(namespace, subdir = "") {
99
+ function treeUrl(namespace, subdir = "", revision = "main") {
100
100
  const parts = [namespace.replace(/\/$/, ""), subdir].filter(Boolean).join("/");
101
- return `${HF_BASE}/api/datasets/${HF_REPO}/tree/main${parts ? "/" + parts : ""}`;
101
+ return `${HF_BASE}/api/datasets/${HF_REPO}/tree/${encodeURIComponent(revision)}${parts ? "/" + parts : ""}`;
102
102
  }
103
103
  var DROSSEL_VERSUCHE = 4;
104
104
  var DROSSEL_BASIS_MS = 400;
@@ -131,8 +131,8 @@ async function mitGeduld(anfrage) {
131
131
  }
132
132
  return letzte;
133
133
  }
134
- async function hfListDir(namespace, subdir, token) {
135
- const res = await mitGeduld(() => holeMitZeitlimit(treeUrl(namespace, subdir), {
134
+ async function hfListDir(namespace, subdir, token, revision = "main") {
135
+ const res = await mitGeduld(() => holeMitZeitlimit(treeUrl(namespace, subdir, revision), {
136
136
  headers: { Authorization: `Bearer ${token}` }
137
137
  }));
138
138
  if (res.status === 404) return [];
@@ -161,17 +161,46 @@ function toBase64(text) {
161
161
  bytes.forEach((b) => binary += String.fromCharCode(b));
162
162
  return btoa(binary);
163
163
  }
164
- async function hfUploadSmallFile(repoPath, content, token, summary = `Update ${repoPath}`) {
164
+ var HfPreconditionFailedError = class extends Error {
165
+ constructor(message) {
166
+ super(message);
167
+ this.name = "HfPreconditionFailed";
168
+ }
169
+ };
170
+ async function hfRepoHead(token, revision = "main") {
171
+ const res = await mitGeduld(() => holeMitZeitlimit(
172
+ `${HF_BASE}/api/datasets/${HF_REPO}/revision/${encodeURIComponent(revision)}`,
173
+ { headers: { Authorization: `Bearer ${token}` } }
174
+ ));
175
+ if (!res.ok) {
176
+ throw new Error(`HF revision failed: ${res.status} ${res.statusText} (${revision})`);
177
+ }
178
+ const daten = await res.json();
179
+ if (typeof daten.sha !== "string" || daten.sha.length === 0) {
180
+ throw new Error(
181
+ `HF revision ${revision}: die Antwort traegt kein "sha". Erhaltene Felder: ${Object.keys(daten || {}).join(", ") || "(keine)"}.`
182
+ );
183
+ }
184
+ return daten.sha;
185
+ }
186
+ async function hfUploadSmallFile(repoPath, content, token, summary = `Update ${repoPath}`, options) {
187
+ const kopf = { summary, description: "" };
188
+ if (options?.parentCommit) kopf.parentCommit = options.parentCommit;
165
189
  const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
166
190
  method: "POST",
167
191
  headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
168
192
  body: [
169
- JSON.stringify({ key: "header", value: { summary, description: "" } }),
193
+ JSON.stringify({ key: "header", value: kopf }),
170
194
  JSON.stringify({ key: "file", value: { path: repoPath, encoding: "base64", content: toBase64(content) } })
171
195
  ].join("\n")
172
196
  });
173
197
  if (!res.ok) {
174
198
  const err = await res.text().catch(() => "");
199
+ if (options?.parentCommit && (res.status === 412 || res.status === 409)) {
200
+ throw new HfPreconditionFailedError(
201
+ `HF commit abgelehnt: Der Branch hat sich seit ${options.parentCommit} bewegt (${res.status}). Es wurde nichts geschrieben.`
202
+ );
203
+ }
175
204
  throw new Error(`HF upload failed: ${res.status} \u2014 ${err.slice(0, 200)}`);
176
205
  }
177
206
  }
@@ -221,8 +250,8 @@ async function mitBegrenzterParallelitaet(count, limit, task) {
221
250
  await Promise.all(Array.from({ length: Math.min(limit, count) }, worker));
222
251
  if (fehlerAufgetreten) throw ersterFehler;
223
252
  }
224
- async function loadPendingEvents(namespace, token, sinceTs, knownPaths) {
225
- const files = await hfListDir(namespace, "events", token);
253
+ async function loadPendingEvents(namespace, token, sinceTs, knownPaths, revision = "main") {
254
+ const files = await hfListDir(namespace, "events", token, revision);
226
255
  const pending = files.filter((f) => f.type === "file" && tsFromEventPath(f.path) > sinceTs && !knownPaths?.has(f.path)).sort((a, b) => a.path.localeCompare(b.path));
227
256
  if (!pending.length) return [];
228
257
  const raw = new Array(pending.length);
@@ -267,7 +296,13 @@ async function writeHFEvent(namespace, token, type, payload, prevTs, options) {
267
296
  const suffix = options?.ordered ? `${String(++writeSeq % 1e6).padStart(6, "0")}${uuid}` : uuid;
268
297
  const filename = `${tsToFilename(ts)}_${suffix}.json`;
269
298
  const repoPath = `${namespace}events/${filename}`;
270
- await hfUploadSmallFile(repoPath, JSON.stringify(event, null, 2), token, `Event: ${type}`);
299
+ await hfUploadSmallFile(
300
+ repoPath,
301
+ JSON.stringify(event, null, 2),
302
+ token,
303
+ `Event: ${type}`,
304
+ options?.parentCommit ? { parentCommit: options.parentCommit } : void 0
305
+ );
271
306
  return event;
272
307
  }
273
308
 
@@ -407,7 +442,8 @@ function capabilitiesOf(port) {
407
442
  imageBatchHistory: typeof port.imageBatchHistory?.listBatchesForPromptRun === "function",
408
443
  imageOrderHistory: typeof port.imageBatchHistory?.listOrdersForPromptRun === "function",
409
444
  imageBatchWrite: typeof port.imageBatchWrite?.importBatch === "function",
410
- imageGeneration: typeof port.imageGeneration?.startFromPromptRun === "function"
445
+ imageGeneration: typeof port.imageGeneration?.startFromPromptRun === "function",
446
+ imageJobs: typeof port.imageJobs?.claimAndRun === "function"
411
447
  };
412
448
  }
413
449
  function unsupportedReason(capability) {
@@ -3557,13 +3593,13 @@ function noticeFor(ergebnis, was) {
3557
3593
  };
3558
3594
  }
3559
3595
  case "rejected": {
3560
- const fehler4 = ergebnis.errors ?? [];
3596
+ const fehler5 = ergebnis.errors ?? [];
3561
3597
  const ungeprueft = ergebnis.unchecked ?? [];
3562
3598
  return {
3563
3599
  tone: "rejected",
3564
- text: `${was}: abgelehnt \u2014 ${fehler4.length} Befund${fehler4.length === 1 ? "" : "e"}.`,
3600
+ text: `${was}: abgelehnt \u2014 ${fehler5.length} Befund${fehler5.length === 1 ? "" : "e"}.`,
3565
3601
  details: [
3566
- ...fehler4.map((f) => `${f.path ?? "(ohne Pfad)"}: ${f.message ?? "ohne Meldung"}`),
3602
+ ...fehler5.map((f) => `${f.path ?? "(ohne Pfad)"}: ${f.message ?? "ohne Meldung"}`),
3567
3603
  // Nicht verschweigen: was nicht geprüft wurde, ist nicht in Ordnung,
3568
3604
  // sondern ungeprüft.
3569
3605
  ...ungeprueft.map((u) => `ungepr\xFCft: ${u}`)
@@ -5337,7 +5373,60 @@ var SZENEN_EVENT_TYPES = {
5337
5373
  * Der Elementreducer ignoriert auch diesen Typ (`default:`-Zweig); gelesen
5338
5374
  * wird mit `collectImageBatches` (`imageBatchStore.ts`).
5339
5375
  */
5340
- imageBatchPersisted: "szenen.image_batch_persisted"
5376
+ imageBatchPersisted: "szenen.image_batch_persisted",
5377
+ /**
5378
+ * Ein vorbereiteter Promptvorgang — der Durchstich „Szene → ChatGPT → Lauf".
5379
+ *
5380
+ * Der bestehende Promptpfad ruft das Modell selbst
5381
+ * (`runPromptDerivation()`); ein aufrufendes Chatmodell formuliert dagegen
5382
+ * **zwischen** zwei Werkzeugaufrufen. Damit zerfaellt der eine Vorgang in
5383
+ * zwei, und der zweite braucht genau den Stand, den der erste eingefroren
5384
+ * hat — sonst loeste der Abschluss die inzwischen geaenderte Szene neu auf
5385
+ * und der Lauf behauptete eine Herkunft, die er nicht hat (D-22).
5386
+ *
5387
+ * Deshalb ein eigenes Ereignis und **kein** fluechtiger Prozesszustand: Der
5388
+ * Snapshot ueberlebt Neustart, Hostwechsel und Sitzungsende, weil er im
5389
+ * selben autoritativen Log liegt wie der Lauf, der daraus entsteht.
5390
+ *
5391
+ * Der Elementreducer ignoriert diesen Typ (`default:`-Zweig); gelesen wird
5392
+ * mit `collectPromptOperations` (`scenePromptOperation.ts`).
5393
+ */
5394
+ promptOperationPrepared: "szenen.prompt_operation_prepared",
5395
+ /**
5396
+ * Ein Bildauftrag wartet auf einen ausfuehrenden Flow-Host.
5397
+ *
5398
+ * Die vorhandene Auftragshistorie (`imageGenerationStarted`) beginnt
5399
+ * **unmittelbar vor** dem Provideraufruf und kennt deshalb keinen wartenden
5400
+ * Auftrag. Genau der entsteht hier: Ein Chatmodell beauftragt ueber MCP ein
5401
+ * Bild, der Auftrag wird dauerhaft abgelegt, und der Werkzeugaufruf endet
5402
+ * sofort mit seiner Kennung — er haelt keine Antwort minutenlang offen.
5403
+ *
5404
+ * Traegt **keinen** Prompt: Der steht im referenzierten Lauf, und ihn hier
5405
+ * zu kopieren waere eine zweite Wahrheit ueber denselben eingefrorenen Text
5406
+ * (D-103).
5407
+ */
5408
+ imageJobRequested: "szenen.image_job_requested",
5409
+ /**
5410
+ * Ein Flow-Host beansprucht einen wartenden Bildauftrag.
5411
+ *
5412
+ * Ein append-only Log kennt kein Lock: Zwei Tabs koennen beide anhaengen.
5413
+ * Deshalb ist die Uebernahme **nicht** der Append, sondern das Ergebnis
5414
+ * einer deterministischen Schiedsregel darueber (`winningClaimOf`) — der
5415
+ * erste Anspruch in Logreihenfolge gewinnt, und jeder Host liest nach
5416
+ * seinem Append erneut, bevor er einen bezahlten Provideraufruf startet.
5417
+ * Ein unterlegener Anspruch bleibt sichtbar stehen; er wird nicht geloescht,
5418
+ * weil das Log das nicht kann und weil der Versuch ein Beleg ist.
5419
+ */
5420
+ imageJobClaimed: "szenen.image_job_claimed",
5421
+ /**
5422
+ * Der Bildauftrag ist erledigt — mit dem Ausgang, den der Host gesehen hat.
5423
+ *
5424
+ * Getrennt von `imageGenerationFinished`: Jenes gehoert zum bezahlten
5425
+ * Provideraufruf, dieses zur Zustellung. Ein Auftrag kann fehlschlagen,
5426
+ * **bevor** ein Provider gerufen wurde (abgelehntes Profil), und dann gibt
5427
+ * es kein `imageGenerationFinished`, das den Wartenden erloesen wuerde.
5428
+ */
5429
+ imageJobSettled: "szenen.image_job_settled"
5341
5430
  };
5342
5431
  function sortForReplay(events) {
5343
5432
  return [...events].sort((a, b) => {
@@ -5700,9 +5789,9 @@ async function leseBilderAusZip(datei) {
5700
5789
  );
5701
5790
  }
5702
5791
  const { default: JSZip2 } = await import("jszip");
5703
- const archiv = await JSZip2.loadAsync(datei).catch((fehler4) => {
5792
+ const archiv = await JSZip2.loadAsync(datei).catch((fehler5) => {
5704
5793
  throw new Error(
5705
- `${name} konnte nicht als ZIP gelesen werden: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
5794
+ `${name} konnte nicht als ZIP gelesen werden: ${fehler5 instanceof Error ? fehler5.message : String(fehler5)}`
5706
5795
  );
5707
5796
  });
5708
5797
  const pfade = [];
@@ -8581,7 +8670,7 @@ var ERKLAERUNG = {
8581
8670
  };
8582
8671
  var CapabilityPanel = ({ port, elementId, setupPayload }) => {
8583
8672
  const [bericht, setBericht] = useState5(null);
8584
- const [fehler4, setFehler] = useState5(null);
8673
+ const [fehler5, setFehler] = useState5(null);
8585
8674
  useEffect2(() => {
8586
8675
  let abgemeldet = false;
8587
8676
  setBericht(null);
@@ -8603,10 +8692,10 @@ var CapabilityPanel = ({ port, elementId, setupPayload }) => {
8603
8692
  abgemeldet = true;
8604
8693
  };
8605
8694
  }, [port, elementId, setupPayload]);
8606
- if (fehler4) {
8695
+ if (fehler5) {
8607
8696
  return /* @__PURE__ */ jsxs4("p", { "data-testid": "capability-fehler", role: "alert", children: [
8608
8697
  "F\xE4higkeitsbericht nicht erstellbar: ",
8609
- fehler4
8698
+ fehler5
8610
8699
  ] });
8611
8700
  }
8612
8701
  if (!bericht) return /* @__PURE__ */ jsx4("p", { "data-testid": "capability-laden", children: "F\xE4higkeitsbericht wird erstellt \u2026" });
@@ -9013,6 +9102,7 @@ var RunImagesPanel = ({ port, lauf }) => {
9013
9102
  const neuLesen = () => setNachladen((n) => n + 1);
9014
9103
  const [erzeugenOffen, setErzeugenOffen] = useState8(null);
9015
9104
  const [zuordnenOffen, setZuordnenOffen] = useState8(null);
9105
+ const [chatAuftraegeOffen, setChatAuftraegeOffen] = useState8(null);
9016
9106
  const quelle = sourceOf(lauf);
9017
9107
  const [wahlPhase] = usePortQuery(
9018
9108
  () => port.imageGeneration ? port.imageGeneration.imageModelChoiceForRun(lauf.run_id) : Promise.resolve({ kind: "ok", value: null }),
@@ -9027,10 +9117,17 @@ var RunImagesPanel = ({ port, lauf }) => {
9027
9117
  () => port.imageBatchHistory ? port.imageBatchHistory.listBatchesForPromptRun(lauf.run_id) : Promise.resolve({ kind: "ok", value: [] }),
9028
9118
  [port, lauf.run_id, nachladen]
9029
9119
  );
9120
+ const [chatAuftraegePhase] = usePortQuery(
9121
+ () => port.imageJobs ? port.imageJobs.listJobs(lauf.run_id) : Promise.resolve({ kind: "ok", value: [] }),
9122
+ [port, lauf.run_id, nachladen]
9123
+ );
9030
9124
  const auftraege = auftraegePhase.status === "ready" ? auftraegePhase.value : [];
9031
9125
  const batches = batchesPhase.status === "ready" ? batchesPhase.value : [];
9126
+ const chatAuftraege = chatAuftraegePhase.status === "ready" ? chatAuftraegePhase.value : [];
9127
+ const offeneChatAuftraege = chatAuftraege.filter((a) => a.status === "pending").length;
9032
9128
  const auffaellige = auftraege.filter((a) => a.status !== "succeeded");
9033
9129
  const erzeugenAuf = erzeugenOffen ?? batches.length === 0;
9130
+ const chatAuftraegeAuf = chatAuftraegeOffen ?? offeneChatAuftraege > 0;
9034
9131
  return /* @__PURE__ */ jsxs8("div", { className: "szenen-bilder", "data-testid": `bilder-${lauf.run_id}`, children: [
9035
9132
  /* @__PURE__ */ jsxs8("section", { className: "szenen-bilder__eingang", "data-testid": `eingang-${lauf.run_id}`, children: [
9036
9133
  /* @__PURE__ */ jsx8("h5", { children: "Eingang \u2014 woraus dieser Prompt entstand" }),
@@ -9151,6 +9248,43 @@ var RunImagesPanel = ({ port, lauf }) => {
9151
9248
  }
9152
9249
  )
9153
9250
  ] }),
9251
+ faehig.imageJobs && /* @__PURE__ */ jsxs8(
9252
+ "details",
9253
+ {
9254
+ className: "szenen-bilder__aktion",
9255
+ "data-testid": `auftraege-chat-bereich-${lauf.run_id}`,
9256
+ open: chatAuftraegeAuf,
9257
+ children: [
9258
+ /* @__PURE__ */ jsxs8(
9259
+ "summary",
9260
+ {
9261
+ onClick: (e) => {
9262
+ e.preventDefault();
9263
+ setChatAuftraegeOffen(!chatAuftraegeAuf);
9264
+ },
9265
+ children: [
9266
+ "Bildauftraege aus dem Chat",
9267
+ offeneChatAuftraege > 0 ? ` (${offeneChatAuftraege} wartend)` : ""
9268
+ ]
9269
+ }
9270
+ ),
9271
+ /* @__PURE__ */ jsx8(
9272
+ ChatAuftraegeAktion,
9273
+ {
9274
+ port,
9275
+ lauf,
9276
+ auftraege: chatAuftraege,
9277
+ fehler: chatAuftraegePhase.status === "error" ? chatAuftraegePhase.message : null,
9278
+ laedt: chatAuftraegePhase.status === "loading",
9279
+ onFertig: () => {
9280
+ setChatAuftraegeOffen(true);
9281
+ neuLesen();
9282
+ }
9283
+ }
9284
+ )
9285
+ ]
9286
+ }
9287
+ ),
9154
9288
  /* @__PURE__ */ jsxs8(
9155
9289
  "details",
9156
9290
  {
@@ -9334,7 +9468,7 @@ function useBildQuellen(port, bilder) {
9334
9468
  erneutVersuchen: () => setVersuch((n) => n + 1)
9335
9469
  };
9336
9470
  }
9337
- var BildDetails = ({ bild, fehler: fehler4, aufloesbar }) => /* @__PURE__ */ jsxs8("span", { className: "szenen-bilder__meta", "data-testid": `batchbild-${bild.media_ref}`, children: [
9471
+ var BildDetails = ({ bild, fehler: fehler5, aufloesbar }) => /* @__PURE__ */ jsxs8("span", { className: "szenen-bilder__meta", "data-testid": `batchbild-${bild.media_ref}`, children: [
9338
9472
  /* @__PURE__ */ jsx8("code", { children: bild.media_ref }),
9339
9473
  /* @__PURE__ */ jsxs8("span", { children: [
9340
9474
  "Position ",
@@ -9347,7 +9481,7 @@ var BildDetails = ({ bild, fehler: fehler4, aufloesbar }) => /* @__PURE__ */ jsx
9347
9481
  "\xD7",
9348
9482
  bild.height
9349
9483
  ] }) : null,
9350
- fehler4 && /* @__PURE__ */ jsx8("em", { "data-testid": `batchbild-fehler-${bild.media_ref}`, children: fehler4 }),
9484
+ fehler5 && /* @__PURE__ */ jsx8("em", { "data-testid": `batchbild-fehler-${bild.media_ref}`, children: fehler5 }),
9351
9485
  !aufloesbar && // Capability-Grenze, kein Fehler: Dieser Host kann Batches lesen,
9352
9486
  // aber keine Bilder auflösen (E). Kein erfundener Fallback auf eine
9353
9487
  // Serverroute (A6).
@@ -9363,18 +9497,18 @@ var HERKUNFT_KURZ = {
9363
9497
  external_import: "extern importiert"
9364
9498
  };
9365
9499
  var BatchKachel = ({ port, batch }) => {
9366
- const { quellen, fehler: fehler4, wiederholbar, erneutVersuchen } = useBildQuellen(port, batch.images);
9500
+ const { quellen, fehler: fehler5, wiederholbar, erneutVersuchen } = useBildQuellen(port, batch.images);
9367
9501
  const aufloesbar = Boolean(port.imageLibrary);
9368
9502
  const bilder = batch.images.map((bild) => ({
9369
9503
  id: bild.media_ref,
9370
9504
  quelle: quellen.get(bild.media_ref) ?? null,
9371
9505
  titel: `Bild ${bild.position + 1}`,
9372
- ...fehler4.get(bild.media_ref) ? { leerText: "Nicht ladbar" } : {},
9506
+ ...fehler5.get(bild.media_ref) ? { leerText: "Nicht ladbar" } : {},
9373
9507
  details: /* @__PURE__ */ jsx8(
9374
9508
  BildDetails,
9375
9509
  {
9376
9510
  bild,
9377
- fehler: fehler4.get(bild.media_ref) ?? null,
9511
+ fehler: fehler5.get(bild.media_ref) ?? null,
9378
9512
  aufloesbar
9379
9513
  }
9380
9514
  )
@@ -9434,7 +9568,7 @@ var BatchKachel = ({ port, batch }) => {
9434
9568
  BildDetails,
9435
9569
  {
9436
9570
  bild,
9437
- fehler: fehler4.get(bild.media_ref) ?? null,
9571
+ fehler: fehler5.get(bild.media_ref) ?? null,
9438
9572
  aufloesbar
9439
9573
  }
9440
9574
  ) }, bild.media_ref)) })
@@ -9703,11 +9837,11 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
9703
9837
  setSeedListe(DEFAULT_SEED_VORLAGE.seeds);
9704
9838
  setVorlageKey(DEFAULT_VORLAGE_KEY);
9705
9839
  }
9706
- } catch (fehler4) {
9840
+ } catch (fehler5) {
9707
9841
  if (veraltet()) return;
9708
9842
  setNotiz({
9709
9843
  tone: "error",
9710
- text: fehler4 instanceof Error ? fehler4.message : String(fehler4)
9844
+ text: fehler5 instanceof Error ? fehler5.message : String(fehler5)
9711
9845
  });
9712
9846
  }
9713
9847
  if (veraltet()) return;
@@ -9726,10 +9860,10 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
9726
9860
  setNotiz(
9727
9861
  ergebnis.kind === "ok" ? { tone: "ok", text: `Importiert als Item ${ergebnis.value.item_id} mit ${bilder.length} Bild(ern).` } : noticeFor(ergebnis, "Externe Bilder importieren")
9728
9862
  );
9729
- } catch (fehler4) {
9863
+ } catch (fehler5) {
9730
9864
  setNotiz({
9731
9865
  tone: "error",
9732
- text: `Externe Bilder importieren: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
9866
+ text: `Externe Bilder importieren: ${fehler5 instanceof Error ? fehler5.message : String(fehler5)}`
9733
9867
  });
9734
9868
  }
9735
9869
  setLaeuft(false);
@@ -9751,10 +9885,10 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
9751
9885
  ...seed !== void 0 ? { seed } : {}
9752
9886
  });
9753
9887
  }
9754
- } catch (fehler4) {
9888
+ } catch (fehler5) {
9755
9889
  setNotiz({
9756
9890
  tone: "error",
9757
- text: `Externe Bilder importieren: ${fehler4 instanceof Error ? fehler4.message : String(fehler4)}`
9891
+ text: `Externe Bilder importieren: ${fehler5 instanceof Error ? fehler5.message : String(fehler5)}`
9758
9892
  });
9759
9893
  return;
9760
9894
  }
@@ -10090,6 +10224,101 @@ function readAsBase64(datei) {
10090
10224
  leser.readAsDataURL(datei);
10091
10225
  });
10092
10226
  }
10227
+ var CHAT_AUFTRAG_TEXT = {
10228
+ pending: "wartet auf \xDCbernahme",
10229
+ // Wie bei `started` (D-119): laufend und abgebrochen sind aus dem Log
10230
+ // nicht unterscheidbar, und es gibt keinen automatischen Retry, der die
10231
+ // Unterscheidung bräuchte.
10232
+ claimed: "\xFCbernommen (l\xE4uft oder unterbrochen)",
10233
+ succeeded: "erfolgreich",
10234
+ partial: "teilweise",
10235
+ failed: "fehlgeschlagen",
10236
+ rejected: "abgelehnt \u2014 es wurde nichts ausgef\xFChrt"
10237
+ };
10238
+ var ChatAuftraegeAktion = ({
10239
+ port,
10240
+ lauf,
10241
+ auftraege,
10242
+ fehler: fehler5,
10243
+ laedt,
10244
+ onFertig
10245
+ }) => {
10246
+ const [laufend, setLaufend] = useState8(null);
10247
+ const [notiz, setNotiz] = useState8(null);
10248
+ async function uebernehmen(jobId) {
10249
+ if (!port.imageJobs) return;
10250
+ setLaufend(jobId);
10251
+ setNotiz(null);
10252
+ try {
10253
+ const ergebnis = await port.imageJobs.claimAndRun(jobId);
10254
+ if (ergebnis.kind === "ok") {
10255
+ setNotiz({
10256
+ tone: ergebnis.job.status === "succeeded" ? "ok" : "rejected",
10257
+ text: `Auftrag ${jobId}: ${CHAT_AUFTRAG_TEXT[ergebnis.job.status]}.` + (ergebnis.job.diagnostic ? ` ${ergebnis.job.diagnostic}` : "")
10258
+ });
10259
+ } else if (ergebnis.kind === "lost") {
10260
+ setNotiz({ tone: "rejected", text: ergebnis.message });
10261
+ } else {
10262
+ setNotiz({ tone: "error", text: ergebnis.message });
10263
+ }
10264
+ } catch (e) {
10265
+ setNotiz({
10266
+ tone: "error",
10267
+ text: `Die \xDCbernahme von Auftrag ${jobId} ist technisch gescheitert: ` + (e instanceof Error ? e.message : String(e))
10268
+ });
10269
+ } finally {
10270
+ setLaufend(null);
10271
+ onFertig();
10272
+ }
10273
+ }
10274
+ return /* @__PURE__ */ jsxs8("div", { className: "szenen-bilder__aktion-inhalt", "data-testid": `chat-auftraege-${lauf.run_id}`, children: [
10275
+ /* @__PURE__ */ jsx8("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." }),
10276
+ fehler5 && /* @__PURE__ */ jsxs8("p", { role: "alert", "data-testid": `chat-auftraege-fehler-${lauf.run_id}`, children: [
10277
+ "Bildauftr\xE4ge aus dem Chat konnten nicht gelesen werden: ",
10278
+ fehler5
10279
+ ] }),
10280
+ !fehler5 && !laedt && auftraege.length === 0 && /* @__PURE__ */ jsx8("p", { "data-testid": `chat-auftraege-leer-${lauf.run_id}`, children: "F\xFCr diesen Lauf liegt kein Auftrag aus dem Chat vor." }),
10281
+ notiz && /* @__PURE__ */ jsx8(
10282
+ "p",
10283
+ {
10284
+ role: notiz.tone === "error" ? "alert" : void 0,
10285
+ "data-testid": `chat-auftraege-notiz-${lauf.run_id}`,
10286
+ children: notiz.text
10287
+ }
10288
+ ),
10289
+ /* @__PURE__ */ jsx8("ul", { className: "szenen-bilder__auftraege", children: auftraege.map((auftrag) => /* @__PURE__ */ jsxs8("li", { "data-testid": `chat-auftrag-${auftrag.job_id}`, children: [
10290
+ /* @__PURE__ */ jsx8("span", { children: CHAT_AUFTRAG_TEXT[auftrag.status] }),
10291
+ " \u2014 ",
10292
+ /* @__PURE__ */ jsxs8("span", { children: [
10293
+ auftrag.image_count,
10294
+ " ",
10295
+ auftrag.image_count === 1 ? "Bild" : "Bilder",
10296
+ " mit",
10297
+ " ",
10298
+ /* @__PURE__ */ jsx8("code", { children: auftrag.execution_target_model_key }),
10299
+ " / ",
10300
+ /* @__PURE__ */ jsx8("code", { children: auftrag.execution_profile_id }),
10301
+ auftrag.aspect_ratio ? ` im Format ${auftrag.aspect_ratio}` : ""
10302
+ ] }),
10303
+ auftrag.diagnostic && /* @__PURE__ */ jsxs8("span", { children: [
10304
+ " ",
10305
+ auftrag.diagnostic
10306
+ ] }),
10307
+ /* @__PURE__ */ jsx8("br", {}),
10308
+ /* @__PURE__ */ jsx8("code", { className: "szenen-bilder__auftragskennung", children: auftrag.job_id }),
10309
+ auftrag.status === "pending" && /* @__PURE__ */ jsx8(
10310
+ "button",
10311
+ {
10312
+ type: "button",
10313
+ disabled: laufend !== null,
10314
+ onClick: () => void uebernehmen(auftrag.job_id),
10315
+ "data-testid": `chat-auftrag-uebernehmen-${auftrag.job_id}`,
10316
+ children: laufend === auftrag.job_id ? "\xDCbernahme l\xE4uft\u2026" : "\xDCbernehmen und ausf\xFChren (kostet einen Bildaufruf)"
10317
+ }
10318
+ )
10319
+ ] }, auftrag.job_id)) })
10320
+ ] });
10321
+ };
10093
10322
 
10094
10323
  // src/features/szenen/areas/RunsPanel.tsx
10095
10324
  import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
@@ -13796,7 +14025,7 @@ function createHfEventChannel(options) {
13796
14025
  let syncInFlight = null;
13797
14026
  let snapshot = null;
13798
14027
  let snapshotGeladen = ohneSnapshot;
13799
- const refresh = async () => {
14028
+ const refresh = async (revision = "main") => {
13800
14029
  if (!snapshotGeladen) {
13801
14030
  snapshot = await loadLatestSzenenSnapshot(namespace, token);
13802
14031
  snapshotGeladen = true;
@@ -13804,7 +14033,7 @@ function createHfEventChannel(options) {
13804
14033
  knownPaths.add(`${namespace}events/${name}`);
13805
14034
  }
13806
14035
  }
13807
- const neue = await loadPendingEvents(namespace, token, sinceTs, knownPaths);
14036
+ const neue = await loadPendingEvents(namespace, token, sinceTs, knownPaths, revision);
13808
14037
  if (neue.length) cachedRawEvents = [...cachedRawEvents, ...neue];
13809
14038
  };
13810
14039
  const sync = () => {
@@ -13815,16 +14044,34 @@ function createHfEventChannel(options) {
13815
14044
  }
13816
14045
  return syncInFlight;
13817
14046
  };
14047
+ const sicht = () => {
14048
+ const pending = cachedRawEvents.filter((event) => typeof event.type === "string" && event.type.startsWith(SZENEN_EVENT_PREFIX)).map((event) => ({
14049
+ type: event.type,
14050
+ payload: event.payload,
14051
+ clientId: event.clientId,
14052
+ ts: event.ts
14053
+ }));
14054
+ return mergeSnapshotAndPending(snapshot, pending).events;
14055
+ };
13818
14056
  return {
13819
14057
  async readEvents() {
13820
14058
  await sync();
13821
- const pending = cachedRawEvents.filter((event) => typeof event.type === "string" && event.type.startsWith(SZENEN_EVENT_PREFIX)).map((event) => ({
13822
- type: event.type,
13823
- payload: event.payload,
13824
- clientId: event.clientId,
13825
- ts: event.ts
13826
- }));
13827
- return mergeSnapshotAndPending(snapshot, pending).events;
14059
+ return sicht();
14060
+ },
14061
+ async readHead() {
14062
+ return hfRepoHead(token);
14063
+ },
14064
+ async readEventsAt(revision) {
14065
+ await refresh(revision);
14066
+ return sicht();
14067
+ },
14068
+ async appendEventAt(type, payload, parentCommit) {
14069
+ if (!type.startsWith(SZENEN_EVENT_PREFIX)) {
14070
+ throw new Error(
14071
+ `Event-Typ "${type}" gehoert nicht zur Domain szenen. Der geteilte Kanal wird ueber das Praefix "${SZENEN_EVENT_PREFIX}" getrennt.`
14072
+ );
14073
+ }
14074
+ await writeHFEvent(namespace, token, type, payload, [], { ordered: true, parentCommit });
13828
14075
  },
13829
14076
  async appendEvent(type, payload) {
13830
14077
  if (!type.startsWith(SZENEN_EVENT_PREFIX)) {
@@ -14008,11 +14255,11 @@ async function resolveShotPayload(input) {
14008
14255
  };
14009
14256
  }
14010
14257
  const shot = shotPayload;
14011
- for (const fehler4 of ohneKettenReferenz(
14258
+ for (const fehler5 of ohneKettenReferenz(
14012
14259
  validatePayload("shot", shotPayload).errors,
14013
14260
  chain ? "scene_setup_ref" : null
14014
14261
  )) {
14015
- diagnostics.push(diagnosticOfSchemaError(fehler4, `Shot ${shotElementId}`));
14262
+ diagnostics.push(diagnosticOfSchemaError(fehler5, `Shot ${shotElementId}`));
14016
14263
  }
14017
14264
  let setup = null;
14018
14265
  let report = null;
@@ -14124,8 +14371,8 @@ async function resolveScenePayload(input) {
14124
14371
  "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)."
14125
14372
  ];
14126
14373
  const diagnostics = [];
14127
- for (const fehler4 of validatePayload("scene", scenePayload).errors) {
14128
- diagnostics.push(diagnosticOfSchemaError(fehler4, `Szene ${sceneElementId}`));
14374
+ for (const fehler5 of validatePayload("scene", scenePayload).errors) {
14375
+ diagnostics.push(diagnosticOfSchemaError(fehler5, `Szene ${sceneElementId}`));
14129
14376
  }
14130
14377
  const faehig = sceneOnlyCapabilities(scenePayload);
14131
14378
  return komponiere({
@@ -15646,6 +15893,44 @@ function gruppiereZuItems(bilder) {
15646
15893
  items.sort((a, b) => b.juengstes - a.juengstes);
15647
15894
  return items.map((e) => e.item);
15648
15895
  }
15896
+ function locatorAus(itemId, position, anzahl) {
15897
+ return anzahl === 1 ? itemId : `${itemId}-${position}`;
15898
+ }
15899
+ function mimeTypAus(dateiname) {
15900
+ const n = dateiname.toLowerCase();
15901
+ if (n.endsWith(".png")) return "image/png";
15902
+ if (n.endsWith(".webp")) return "image/webp";
15903
+ return "image/jpeg";
15904
+ }
15905
+ function passt(item, suche) {
15906
+ const f = suche.trim().toLowerCase();
15907
+ if (!f) return true;
15908
+ return (item.title ?? "").toLowerCase().includes(f) || item.id.toLowerCase().includes(f);
15909
+ }
15910
+ function itemsAusBestand(items, options = {}) {
15911
+ const suche = options.search ?? "";
15912
+ const werke = [];
15913
+ const passende = items.filter((i) => i && typeof i.id === "string" && i.id.length > 0 && passt(i, suche)).sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0) || (a.id < b.id ? 1 : a.id > b.id ? -1 : 0));
15914
+ for (const item of passende) {
15915
+ const galerie = Array.isArray(item.gallery) ? item.gallery : [];
15916
+ const titel = item.title || item.id;
15917
+ const bilder = [];
15918
+ galerie.forEach((eintrag, position) => {
15919
+ const datei = eintrag?.file;
15920
+ if (!datei) return;
15921
+ bilder.push({
15922
+ mediaRef: locatorAus(item.id, position, galerie.length),
15923
+ title: galerie.length === 1 ? titel : `${titel} \xB7 Bild ${position + 1}`,
15924
+ mimeType: mimeTypAus(String(datei)),
15925
+ thumbUrl: options.thumbUrl ? options.thumbUrl(String(datei)) : null
15926
+ });
15927
+ });
15928
+ if (bilder.length === 0) continue;
15929
+ werke.push({ itemRef: item.id, title: titel, images: bilder });
15930
+ if (options.limit && werke.length >= options.limit) break;
15931
+ }
15932
+ return werke;
15933
+ }
15649
15934
 
15650
15935
  // src/lib/szenen/hfImageLibrary.ts
15651
15936
  function fehler2(nachricht, retryable = false) {
@@ -15776,8 +16061,8 @@ function createFlowImageProvider(generateImage) {
15776
16061
  // Kein Seed: weder als Eingabe dokumentiert noch als Ausgabe
15777
16062
  // geliefert — nichts wird erfunden (D-107).
15778
16063
  });
15779
- } catch (fehler4) {
15780
- ersterFehler = fehler4 instanceof Error ? fehler4.message : String(fehler4);
16064
+ } catch (fehler5) {
16065
+ ersterFehler = fehler5 instanceof Error ? fehler5.message : String(fehler5);
15781
16066
  break;
15782
16067
  }
15783
16068
  }
@@ -16306,6 +16591,252 @@ function createImageGenerationPort(options) {
16306
16591
  };
16307
16592
  }
16308
16593
 
16594
+ // src/lib/szenen/imageJobStore.ts
16595
+ var istText7 = (wert) => typeof wert === "string" && wert.trim().length > 0;
16596
+ var SETTLED_STATUS = ["succeeded", "partial", "failed", "rejected"];
16597
+ function collectImageJobs(events) {
16598
+ const nachId = /* @__PURE__ */ new Map();
16599
+ for (const event of events) {
16600
+ if (event.type !== SZENEN_EVENT_TYPES.imageJobRequested) continue;
16601
+ const payload = event.payload;
16602
+ if (!payload || !istText7(payload.job_id) || !istText7(payload.prompt_run_id)) continue;
16603
+ if (nachId.has(payload.job_id)) continue;
16604
+ nachId.set(payload.job_id, {
16605
+ job_id: payload.job_id,
16606
+ prompt_run_id: payload.prompt_run_id,
16607
+ execution_target_model_key: payload.execution_target_model_key ?? "",
16608
+ execution_profile_id: payload.execution_profile_id ?? "",
16609
+ image_count: typeof payload.image_count === "number" ? payload.image_count : 0,
16610
+ ...payload.aspect_ratio ? { aspect_ratio: payload.aspect_ratio } : {},
16611
+ requested_by: payload.requested_by ?? "",
16612
+ store_id: payload.store_id ?? "",
16613
+ created_at: payload.created_at ?? "",
16614
+ status: "pending",
16615
+ claim_attempts: []
16616
+ });
16617
+ }
16618
+ for (const event of events) {
16619
+ if (event.type !== SZENEN_EVENT_TYPES.imageJobClaimed) continue;
16620
+ const payload = event.payload;
16621
+ if (!payload || !istText7(payload.job_id) || !istText7(payload.claim_id)) continue;
16622
+ const auftrag = nachId.get(payload.job_id);
16623
+ if (!auftrag) continue;
16624
+ const anspruch = {
16625
+ job_id: payload.job_id,
16626
+ claim_id: payload.claim_id,
16627
+ client_id: payload.client_id ?? "",
16628
+ claimed_at: payload.claimed_at ?? ""
16629
+ };
16630
+ nachId.set(payload.job_id, {
16631
+ ...auftrag,
16632
+ claim: auftrag.claim ?? anspruch,
16633
+ claim_attempts: [...auftrag.claim_attempts, anspruch],
16634
+ status: auftrag.status === "pending" ? "claimed" : auftrag.status
16635
+ });
16636
+ }
16637
+ for (const event of events) {
16638
+ if (event.type !== SZENEN_EVENT_TYPES.imageJobSettled) continue;
16639
+ const payload = event.payload;
16640
+ if (!payload || !istText7(payload.job_id)) continue;
16641
+ const auftrag = nachId.get(payload.job_id);
16642
+ if (!auftrag) continue;
16643
+ if (!auftrag.claim || auftrag.claim.claim_id !== payload.claim_id) continue;
16644
+ if (auftrag.status !== "claimed") continue;
16645
+ if (!SETTLED_STATUS.includes(payload.status)) continue;
16646
+ nachId.set(payload.job_id, {
16647
+ ...auftrag,
16648
+ status: payload.status,
16649
+ ...payload.generation_batch_id ? { generation_batch_id: payload.generation_batch_id } : {},
16650
+ ...payload.item_id ? { item_id: payload.item_id } : {},
16651
+ ...typeof payload.produced_count === "number" ? { produced_count: payload.produced_count } : {},
16652
+ ...payload.diagnostic ? { diagnostic: payload.diagnostic } : {},
16653
+ settled_at: payload.settled_at ?? ""
16654
+ });
16655
+ }
16656
+ return [...nachId.values()];
16657
+ }
16658
+ function jobsForPromptRun(events, runId) {
16659
+ return collectImageJobs(events).filter((j) => j.prompt_run_id === runId);
16660
+ }
16661
+ function findImageJob(events, jobId) {
16662
+ return collectImageJobs(events).find((j) => j.job_id === jobId) ?? null;
16663
+ }
16664
+ function claimWon(job, claimId) {
16665
+ return Boolean(job && job.claim && job.claim.claim_id === claimId);
16666
+ }
16667
+ function generationBatchIdOf(jobId) {
16668
+ return `job-${jobId}`;
16669
+ }
16670
+
16671
+ // src/lib/szenen/imageJobPort.ts
16672
+ var ANSPRUCH_VERSUCHE = 4;
16673
+ function fehler4(error, aktion) {
16674
+ return {
16675
+ message: `${aktion}: ${error instanceof Error ? error.message : String(error)}`
16676
+ };
16677
+ }
16678
+ function createImageJobPort(options) {
16679
+ const { channel, imageGeneration, storeId, clientId } = options;
16680
+ const newClaimId = options.newClaimId ?? (() => crypto.randomUUID());
16681
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
16682
+ return {
16683
+ async listJobs(runId) {
16684
+ try {
16685
+ return { kind: "ok", value: jobsForPromptRun(await channel.readEvents(), runId) };
16686
+ } catch (error) {
16687
+ return {
16688
+ kind: "error",
16689
+ ...fehler4(error, `Die Bildauftraege des Laufs ${runId} konnten nicht gelesen werden`),
16690
+ retryable: true
16691
+ };
16692
+ }
16693
+ },
16694
+ async claimAndRun(jobId) {
16695
+ let letzterGrund = "";
16696
+ for (let versuch = 1; versuch <= ANSPRUCH_VERSUCHE; versuch += 1) {
16697
+ let kopf;
16698
+ try {
16699
+ kopf = await channel.readHead();
16700
+ } catch (error) {
16701
+ return {
16702
+ kind: "error",
16703
+ ...fehler4(error, `Der Kopf-Commit des Bestands war nicht lesbar; Bildauftrag ${jobId} wurde nicht uebernommen`),
16704
+ retryable: true
16705
+ };
16706
+ }
16707
+ let auftrag;
16708
+ try {
16709
+ auftrag = findImageJob(await channel.readEventsAt(kopf), jobId);
16710
+ } catch (error) {
16711
+ return {
16712
+ kind: "error",
16713
+ ...fehler4(error, `Der Bildauftrag ${jobId} konnte nicht gelesen werden`),
16714
+ retryable: true
16715
+ };
16716
+ }
16717
+ if (!auftrag) {
16718
+ return {
16719
+ kind: "rejected",
16720
+ message: `Der Bildauftrag ${jobId} steht nicht im autoritativen Log dieses Bestands.`
16721
+ };
16722
+ }
16723
+ if (auftrag.store_id !== storeId) {
16724
+ return {
16725
+ kind: "rejected",
16726
+ message: `Der Bildauftrag ${jobId} gilt fuer den Bestand "${auftrag.store_id}", dieser Host arbeitet auf "${storeId}". Nutzer, Umgebung und Bestand muessen zusammenpassen.`
16727
+ };
16728
+ }
16729
+ if (auftrag.status !== "pending") {
16730
+ return {
16731
+ kind: "lost",
16732
+ job: auftrag,
16733
+ 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."
16734
+ };
16735
+ }
16736
+ const anspruch = {
16737
+ job_id: jobId,
16738
+ claim_id: newClaimId(),
16739
+ client_id: clientId,
16740
+ claimed_at: now().toISOString()
16741
+ };
16742
+ try {
16743
+ await channel.appendEventAt(
16744
+ SZENEN_EVENT_TYPES.imageJobClaimed,
16745
+ anspruch,
16746
+ kopf
16747
+ );
16748
+ } catch (error) {
16749
+ if (istPreconditionFehler(error)) {
16750
+ letzterGrund = error instanceof Error ? error.message : String(error);
16751
+ continue;
16752
+ }
16753
+ return {
16754
+ kind: "error",
16755
+ ...fehler4(error, `Der Anspruch auf Bildauftrag ${jobId} konnte nicht angehaengt werden`),
16756
+ retryable: true
16757
+ };
16758
+ }
16759
+ const gewonnen = {
16760
+ ...auftrag,
16761
+ status: "claimed",
16762
+ claim: anspruch,
16763
+ claim_attempts: [...auftrag.claim_attempts, anspruch]
16764
+ };
16765
+ return await fuehreAus(gewonnen, anspruch, jobId);
16766
+ }
16767
+ return {
16768
+ kind: "error",
16769
+ 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.`,
16770
+ retryable: true
16771
+ };
16772
+ }
16773
+ };
16774
+ async function fuehreAus(gewonnen, anspruch, jobId) {
16775
+ const ergebnis = await imageGeneration.startFromPromptRun({
16776
+ runId: gewonnen.prompt_run_id,
16777
+ generationBatchId: generationBatchIdOf(jobId),
16778
+ executionTargetModelKey: gewonnen.execution_target_model_key,
16779
+ executionProfileId: gewonnen.execution_profile_id,
16780
+ imageCount: gewonnen.image_count,
16781
+ ...gewonnen.aspect_ratio ? { aspectRatio: gewonnen.aspect_ratio } : {}
16782
+ });
16783
+ const abschluss = {
16784
+ job_id: jobId,
16785
+ claim_id: anspruch.claim_id,
16786
+ ...abschlussFelder(ergebnis, jobId),
16787
+ settled_at: now().toISOString()
16788
+ };
16789
+ try {
16790
+ await channel.appendEvent(SZENEN_EVENT_TYPES.imageJobSettled, abschluss);
16791
+ } catch {
16792
+ }
16793
+ const gelesen = await channel.readEvents().catch(() => null);
16794
+ return {
16795
+ kind: "ok",
16796
+ job: (gelesen && findImageJob(gelesen, jobId)) ?? {
16797
+ ...gewonnen,
16798
+ status: abschluss.status,
16799
+ ...abschluss.generation_batch_id ? { generation_batch_id: abschluss.generation_batch_id } : {},
16800
+ ...abschluss.diagnostic ? { diagnostic: abschluss.diagnostic } : {},
16801
+ settled_at: abschluss.settled_at
16802
+ }
16803
+ };
16804
+ }
16805
+ }
16806
+ function istPreconditionFehler(error) {
16807
+ return Boolean(error && typeof error === "object" && error.name === "HfPreconditionFailed");
16808
+ }
16809
+ function abschlussFelder(ergebnis, jobId) {
16810
+ const batchId = generationBatchIdOf(jobId);
16811
+ switch (ergebnis.kind) {
16812
+ case "succeeded":
16813
+ return {
16814
+ status: "succeeded",
16815
+ generation_batch_id: batchId,
16816
+ item_id: ergebnis.batch.item_id,
16817
+ produced_count: ergebnis.batch.images.length
16818
+ };
16819
+ case "partial":
16820
+ return {
16821
+ status: "partial",
16822
+ generation_batch_id: batchId,
16823
+ item_id: ergebnis.batch.item_id,
16824
+ produced_count: ergebnis.produced,
16825
+ diagnostic: `Angefordert ${ergebnis.requested}, geliefert ${ergebnis.produced}.`
16826
+ };
16827
+ case "failed":
16828
+ return { status: "failed", produced_count: 0, diagnostic: ergebnis.message };
16829
+ case "unsupported":
16830
+ return {
16831
+ status: "rejected",
16832
+ produced_count: 0,
16833
+ diagnostic: `Zielmodell "${ergebnis.targetModelKey}" ist in dieser Umgebung nicht ausfuehrbar: ${ergebnis.reason}`
16834
+ };
16835
+ default:
16836
+ return { status: "rejected", produced_count: 0, diagnostic: ergebnis.message };
16837
+ }
16838
+ }
16839
+
16309
16840
  // src/lib/szenen/flowViewPort.ts
16310
16841
  var defaultContext = {
16311
16842
  async chainFor(contextId) {
@@ -16404,10 +16935,21 @@ function createFlowSzenenViewPort(options) {
16404
16935
  },
16405
16936
  ...options.now ? { now: options.now } : {}
16406
16937
  }) : void 0;
16938
+ const kanalMitRevision = typeof channel.readHead === "function" && typeof channel.readEventsAt === "function" && typeof channel.appendEventAt === "function";
16939
+ const imageJobs = imageGeneration && hatHfZugangsdaten && kanalMitRevision ? createImageJobPort({
16940
+ channel,
16941
+ imageGeneration,
16942
+ // Dieselbe Form, in der der Server seinen Bestand kennt (`storeId`).
16943
+ storeId: `hf:${options.namespace}`,
16944
+ clientId: options.imageJobClientId ?? crypto.randomUUID(),
16945
+ ...options.newImageJobClaimId ? { newClaimId: options.newImageJobClaimId } : {},
16946
+ ...options.now ? { now: options.now } : {}
16947
+ }) : void 0;
16407
16948
  const port = { elements, context, runs, derivation, imageBatchHistory };
16408
16949
  if (imageLibrary) port.imageLibrary = imageLibrary;
16409
16950
  if (imageBatchWrite) port.imageBatchWrite = imageBatchWrite;
16410
16951
  if (imageGeneration) port.imageGeneration = imageGeneration;
16952
+ if (imageJobs) port.imageJobs = imageJobs;
16411
16953
  return port;
16412
16954
  }
16413
16955
 
@@ -16580,6 +17122,9 @@ export {
16580
17122
  createHfImageStore,
16581
17123
  locatorTeile,
16582
17124
  gruppiereZuItems,
17125
+ locatorAus,
17126
+ mimeTypAus,
17127
+ itemsAusBestand,
16583
17128
  createHfImageLibrary,
16584
17129
  FLOW_EXECUTION_PATH,
16585
17130
  createFlowImageProvider,
@@ -16594,5 +17139,11 @@ export {
16594
17139
  createHfImageGenerationBatchPersistence,
16595
17140
  createHfImageBatchWrite,
16596
17141
  createImageGenerationPort,
17142
+ collectImageJobs,
17143
+ jobsForPromptRun,
17144
+ findImageJob,
17145
+ claimWon,
17146
+ generationBatchIdOf,
17147
+ createImageJobPort,
16597
17148
  createFlowSzenenViewPort
16598
17149
  };