@rslsp1/fa-app-tools 9.4.4 → 10.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/szenen.js CHANGED
@@ -82,6 +82,7 @@ __export(szenen_exports, {
82
82
  assessDerivationResponse: () => assessDerivationResponse,
83
83
  batchesForPromptRun: () => batchesForPromptRun,
84
84
  bildPfad: () => bildPfad,
85
+ bilderDesLaufs: () => bilderDesLaufs,
85
86
  buildDerivationSystemInstruction: () => buildSystemInstruction2,
86
87
  buildDerivationUserPrompt: () => buildUserPrompt2,
87
88
  buildManualPromptRunRecord: () => buildManualPromptRunRecord,
@@ -14319,7 +14320,8 @@ function kuerze(text, zeichen) {
14319
14320
  const sauber = text.replace(/\s+/g, " ").trim();
14320
14321
  return sauber.length <= zeichen ? sauber : `${sauber.slice(0, zeichen - 1)}\u2026`;
14321
14322
  }
14322
- function herkunftFuer(job, laeufe, elementTitel) {
14323
+ function herkunftFuer(job, laeufe, elementTitel, bilderJeLauf2) {
14324
+ const bilder_im_lauf = bilderJeLauf2.get(job.prompt_run_id)?.length ?? 0;
14323
14325
  const lauf = laeufe.get(job.prompt_run_id);
14324
14326
  if (!lauf) {
14325
14327
  return {
@@ -14327,7 +14329,8 @@ function herkunftFuer(job, laeufe, elementTitel) {
14327
14329
  szene_titel: null,
14328
14330
  szene_element_id: null,
14329
14331
  prompt_auszug: null,
14330
- lauf_gefunden: false
14332
+ lauf_gefunden: false,
14333
+ bilder_im_lauf
14331
14334
  };
14332
14335
  }
14333
14336
  const elementId = benennendesElement(lauf);
@@ -14337,7 +14340,8 @@ function herkunftFuer(job, laeufe, elementTitel) {
14337
14340
  szene_titel: elementId ? elementTitel.get(elementId) ?? null : null,
14338
14341
  szene_element_id: elementId,
14339
14342
  prompt_auszug: istText9(positiv) ? kuerze(positiv, PROMPT_AUSZUG_ZEICHEN) : null,
14340
- lauf_gefunden: true
14343
+ lauf_gefunden: true,
14344
+ bilder_im_lauf
14341
14345
  };
14342
14346
  }
14343
14347
  function sortiere(jobs, logIndex, aelteste_zuerst) {
@@ -14370,57 +14374,90 @@ function selectImageJobs(events, storeId, query = {}) {
14370
14374
  const ausschnitt = geordnet.slice(ab, ab + limit);
14371
14375
  const laeufe = new Map(collectPromptRuns(events).map((r) => [r.run_id, r]));
14372
14376
  const elementTitel = titelJeElement(events);
14377
+ const { jeLauf: bilder } = bilderJeLauf(events, alle);
14373
14378
  return {
14374
14379
  eintraege: ausschnitt.map((job) => ({
14375
14380
  job,
14376
- herkunft: herkunftFuer(job, laeufe, elementTitel)
14381
+ herkunft: herkunftFuer(job, laeufe, elementTitel, bilder)
14377
14382
  })),
14378
14383
  nextCursor: ab + ausschnitt.length < geordnet.length ? ausschnitt[ausschnitt.length - 1].job_id : null,
14379
14384
  gesamt: geordnet.length,
14380
14385
  offen
14381
14386
  };
14382
14387
  }
14383
- function letztesErzeugtesBild(events, storeId) {
14384
- const jobs = collectImageJobs(events).filter((j) => j.store_id === storeId);
14388
+ function bildRefAus(roh, batchId) {
14389
+ return {
14390
+ media_ref: roh.media_ref,
14391
+ position: roh.position,
14392
+ generation_batch_id: batchId,
14393
+ ...istText9(roh.asset_id) ? { asset_id: roh.asset_id } : {},
14394
+ ...istText9(roh.mime_type) ? { mime_type: roh.mime_type } : {},
14395
+ model: roh.model ?? null,
14396
+ seed: roh.seed ?? null,
14397
+ width: roh.width ?? null,
14398
+ height: roh.height ?? null
14399
+ };
14400
+ }
14401
+ function bilderJeLauf(events, jobs) {
14385
14402
  const jobJeBatch = new Map(jobs.map((j) => [generationBatchIdOf(j.job_id), j]));
14386
- for (let i = events.length - 1; i >= 0; i -= 1) {
14387
- const event = events[i];
14403
+ const jeLauf = /* @__PURE__ */ new Map();
14404
+ const gesehen = /* @__PURE__ */ new Set();
14405
+ let juengsterLauf = null;
14406
+ for (const event of events) {
14388
14407
  if (event.type !== SZENEN_EVENT_TYPES.imageBatchPersisted) continue;
14389
14408
  const p = event.payload;
14390
14409
  if (!p || !istText9(p.item_id) || !istText9(p.prompt_run_id) || !istText9(p.generation_batch_id)) continue;
14391
- const bilder = Array.isArray(p.images) ? p.images : [];
14392
- const letztes = bilder.filter((b) => !!b && istText9(b.media_ref) && typeof b.position === "number").reduce(
14393
- (bisher, b) => bisher === null || b.position > bisher.position ? b : bisher,
14394
- null
14395
- );
14396
- if (!letztes) continue;
14410
+ if (gesehen.has(p.generation_batch_id)) continue;
14411
+ gesehen.add(p.generation_batch_id);
14412
+ const roh = (Array.isArray(p.images) ? p.images : []).filter((b) => !!b && istText9(b.media_ref) && typeof b.position === "number").sort((a, b) => a.position - b.position);
14413
+ if (roh.length === 0) continue;
14397
14414
  const job = jobJeBatch.get(p.generation_batch_id) ?? null;
14398
- const laeufe = new Map(collectPromptRuns(events).map((r) => [r.run_id, r]));
14399
- const herkunft = job ? herkunftFuer(job, laeufe, titelJeElement(events)) : null;
14400
- return {
14401
- job,
14402
- prompt_run_id: p.prompt_run_id,
14403
- item_id: p.item_id,
14404
- generation_batch_id: p.generation_batch_id,
14405
- bild: {
14406
- media_ref: letztes.media_ref,
14407
- position: letztes.position,
14415
+ const liste = jeLauf.get(p.prompt_run_id) ?? [];
14416
+ for (const einzeln of roh) {
14417
+ liste.push({
14418
+ bild: bildRefAus(einzeln, p.generation_batch_id),
14419
+ prompt_run_id: p.prompt_run_id,
14420
+ item_id: p.item_id,
14408
14421
  generation_batch_id: p.generation_batch_id,
14409
- ...istText9(letztes.asset_id) ? { asset_id: letztes.asset_id } : {},
14410
- ...istText9(letztes.mime_type) ? { mime_type: letztes.mime_type } : {},
14411
- model: letztes.model ?? null,
14412
- seed: letztes.seed ?? null,
14413
- width: letztes.width ?? null,
14414
- height: letztes.height ?? null
14415
- },
14416
- position: letztes.position + 1,
14417
- anzahl: bilder.length,
14418
- gespeichert_am: istText9(p.created_at) ? p.created_at : null,
14419
- execution_target_model_key: job?.execution_target_model_key ?? null,
14420
- herkunft
14421
- };
14422
+ job_id: job?.job_id ?? null,
14423
+ position_im_batch: einzeln.position + 1,
14424
+ anzahl_im_batch: roh.length,
14425
+ gespeichert_am: istText9(p.created_at) ? p.created_at : null,
14426
+ execution_target_model_key: job?.execution_target_model_key ?? null
14427
+ });
14428
+ }
14429
+ jeLauf.set(p.prompt_run_id, liste);
14430
+ juengsterLauf = p.prompt_run_id;
14422
14431
  }
14423
- return null;
14432
+ return { jeLauf, juengsterLauf };
14433
+ }
14434
+ function bilderDesLaufs(events, storeId, runId) {
14435
+ const jobs = collectImageJobs(events).filter((j) => j.store_id === storeId);
14436
+ return bilderJeLauf(events, jobs).jeLauf.get(runId) ?? [];
14437
+ }
14438
+ function letztesErzeugtesBild(events, storeId) {
14439
+ const jobs = collectImageJobs(events).filter((j) => j.store_id === storeId);
14440
+ const { jeLauf, juengsterLauf } = bilderJeLauf(events, jobs);
14441
+ if (!juengsterLauf) return null;
14442
+ const folge = jeLauf.get(juengsterLauf) ?? [];
14443
+ const letztes = folge[folge.length - 1];
14444
+ if (!letztes) return null;
14445
+ const laeufe = new Map(collectPromptRuns(events).map((r) => [r.run_id, r]));
14446
+ const job = letztes.job_id ? jobs.find((j) => j.job_id === letztes.job_id) ?? null : null;
14447
+ const herkunft = job ? herkunftFuer(job, laeufe, titelJeElement(events), jeLauf) : null;
14448
+ return {
14449
+ job,
14450
+ prompt_run_id: letztes.prompt_run_id,
14451
+ item_id: letztes.item_id,
14452
+ generation_batch_id: letztes.generation_batch_id,
14453
+ bild: letztes.bild,
14454
+ position: letztes.position_im_batch,
14455
+ anzahl: letztes.anzahl_im_batch,
14456
+ gespeichert_am: letztes.gespeichert_am,
14457
+ execution_target_model_key: letztes.execution_target_model_key,
14458
+ herkunft,
14459
+ folge
14460
+ };
14424
14461
  }
14425
14462
 
14426
14463
  // src/lib/szenen/imageJobPort.ts
@@ -14433,6 +14470,7 @@ function fehler4(error, aktion) {
14433
14470
  function createImageJobPort(options) {
14434
14471
  const { channel, imageGeneration, storeId, clientId } = options;
14435
14472
  const newClaimId = options.newClaimId ?? (() => crypto.randomUUID());
14473
+ const newJobId = options.newJobId ?? (() => crypto.randomUUID());
14436
14474
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
14437
14475
  return {
14438
14476
  async listJobs(runId) {
@@ -14474,6 +14512,113 @@ function createImageJobPort(options) {
14474
14512
  };
14475
14513
  }
14476
14514
  },
14515
+ async requestMoreImages(input) {
14516
+ const { sourceJobId, imageCount } = input;
14517
+ let quelle;
14518
+ try {
14519
+ quelle = findImageJob(await channel.readEvents(), sourceJobId);
14520
+ } catch (error) {
14521
+ return {
14522
+ kind: "error",
14523
+ ...fehler4(error, `Der Ausgangsauftrag ${sourceJobId} konnte nicht gelesen werden`),
14524
+ retryable: true
14525
+ };
14526
+ }
14527
+ if (!quelle) {
14528
+ return {
14529
+ kind: "rejected",
14530
+ message: `Der Bildauftrag ${sourceJobId} steht nicht im autoritativen Log dieses Bestands. Ohne ihn gibt es keine Ausfuehrungsparameter, die uebernommen werden koennten.`
14531
+ };
14532
+ }
14533
+ if (quelle.store_id !== storeId) {
14534
+ return {
14535
+ kind: "rejected",
14536
+ message: `Der Bildauftrag ${sourceJobId} gilt fuer den Bestand "${quelle.store_id}", dieser Host arbeitet auf "${storeId}".`
14537
+ };
14538
+ }
14539
+ if (!Number.isInteger(imageCount) || imageCount < 1) {
14540
+ return {
14541
+ kind: "rejected",
14542
+ message: `Die Bildanzahl "${imageCount}" ist keine ganze Zahl >= 1.`
14543
+ };
14544
+ }
14545
+ const wahl = await imageGeneration.imageModelChoiceForRun(quelle.prompt_run_id);
14546
+ if (wahl.kind !== "ok") {
14547
+ return {
14548
+ kind: "error",
14549
+ message: `Die ausfuehrbaren Bildmodelle des Laufs ${quelle.prompt_run_id} waren nicht lesbar: ${wahl.message}`,
14550
+ retryable: wahl.kind === "error" ? wahl.retryable : true
14551
+ };
14552
+ }
14553
+ const gewaehlt = findExecutableImageModel(
14554
+ wahl.value,
14555
+ quelle.execution_target_model_key,
14556
+ quelle.execution_profile_id
14557
+ );
14558
+ if (!gewaehlt) {
14559
+ const auswahl = wahl.value.models.map((m) => m.selection_id);
14560
+ return {
14561
+ kind: "rejected",
14562
+ message: `Das Profil "${quelle.execution_profile_id}" von "${quelle.execution_target_model_key}" ist in dieser Umgebung nicht (mehr) ausfuehrbar. Es wird kein Ersatz geraten. ` + (auswahl.length > 0 ? `Ausfuehrbar waeren: ${auswahl.join(", ")} \u2014 bitte den Lauf oeffnen und dort ausdruecklich waehlen.` : `Derzeit ist hier gar kein Bildmodell ausfuehrbar: ${wahl.value.reason ?? "kein sichtbares Zielmodell traegt ein passendes Profil."}`)
14563
+ };
14564
+ }
14565
+ const befunde = checkImageModelParameters(gewaehlt, {
14566
+ imageCount,
14567
+ ...quelle.aspect_ratio ? { aspectRatio: quelle.aspect_ratio } : {}
14568
+ });
14569
+ if (befunde.length > 0) {
14570
+ return {
14571
+ kind: "rejected",
14572
+ message: `Der neue Auftrag passt nicht zu ${gewaehlt.display_name} (Profil ${gewaehlt.profile_id}, bis zu ${gewaehlt.max_images} Bilder): ` + befunde.join(" ")
14573
+ };
14574
+ }
14575
+ const jobId = input.jobId ?? newJobId();
14576
+ let vorhanden;
14577
+ try {
14578
+ vorhanden = findImageJob(await channel.readEvents(), jobId);
14579
+ } catch (error) {
14580
+ return {
14581
+ kind: "error",
14582
+ ...fehler4(error, `Der Bestand war vor dem Anlegen von ${jobId} nicht lesbar`),
14583
+ retryable: true
14584
+ };
14585
+ }
14586
+ if (vorhanden) {
14587
+ return {
14588
+ kind: "rejected",
14589
+ message: `Zur Auftragskennung ${jobId} gibt es bereits einen Auftrag (${vorhanden.status}). Ein Auftrag ist unveraenderlich.`
14590
+ };
14591
+ }
14592
+ const payload = {
14593
+ job_id: jobId,
14594
+ prompt_run_id: quelle.prompt_run_id,
14595
+ execution_target_model_key: gewaehlt.target_model_key,
14596
+ execution_profile_id: gewaehlt.profile_id,
14597
+ image_count: imageCount,
14598
+ ...quelle.aspect_ratio ? { aspect_ratio: quelle.aspect_ratio } : {},
14599
+ // **Nicht geraten und nicht erfunden.** Dieser Host kennt keinen
14600
+ // Nutzerschluessel — der Flow-Client hat keine Identitaet, nur einen
14601
+ // Bestand. Der Fortsetzungsauftrag traegt deshalb den Auftraggeber des
14602
+ // Auftrags, den er fortsetzt; ein ausgedachter Schluessel waere genau
14603
+ // der Fall, den Regel 6 verbietet.
14604
+ requested_by: quelle.requested_by,
14605
+ store_id: storeId,
14606
+ created_at: now().toISOString()
14607
+ };
14608
+ try {
14609
+ await channel.appendEvent(SZENEN_EVENT_TYPES.imageJobRequested, payload);
14610
+ } catch (error) {
14611
+ return {
14612
+ kind: "error",
14613
+ ...fehler4(error, `Der Bildauftrag ${jobId} konnte nicht angelegt werden`),
14614
+ retryable: true
14615
+ };
14616
+ }
14617
+ return {
14618
+ kind: "ok",
14619
+ job: { ...payload, status: "pending", claim_attempts: [] }
14620
+ };
14621
+ },
14477
14622
  async claimAndRun(jobId) {
14478
14623
  let letzterGrund = "";
14479
14624
  for (let versuch = 1; versuch <= ANSPRUCH_VERSUCHE; versuch += 1) {
@@ -23212,7 +23357,6 @@ var MediaArea = ({ port }) => {
23212
23357
  // src/features/szenen/areas/ImageJobsArea.tsx
23213
23358
  var import_react27 = require("react");
23214
23359
  var import_jsx_runtime24 = require("react/jsx-runtime");
23215
- var AKTUALISIERUNG_MS = 5e3;
23216
23360
  var STATUS_TEXT2 = {
23217
23361
  pending: "wartet auf \xDCbernahme",
23218
23362
  claimed: "\xFCbernommen (l\xE4uft oder unterbrochen)",
@@ -23228,7 +23372,10 @@ var FILTER = [
23228
23372
  { id: "failed", titel: "Fehlgeschlagen" },
23229
23373
  { id: "all", titel: "Alle" }
23230
23374
  ];
23231
- function useDauerAbfrage(frage, abhaengigkeiten) {
23375
+ function istAbgeschlossen(job) {
23376
+ return job.status !== "pending" && job.status !== "claimed";
23377
+ }
23378
+ function useNachladbareAbfrage(frage, abhaengigkeiten) {
23232
23379
  const [stand, setStand] = (0, import_react27.useState)(
23233
23380
  () => ({ wert: null, fehler: null, laedt: true })
23234
23381
  );
@@ -23260,10 +23407,11 @@ function useDauerAbfrage(frage, abhaengigkeiten) {
23260
23407
  var ImageJobsArea = ({
23261
23408
  port,
23262
23409
  onPromptOeffnen,
23263
- aktualisierungMs = AKTUALISIERUNG_MS,
23264
- warte
23410
+ warte,
23411
+ jetzt
23265
23412
  }) => {
23266
23413
  const auftraege = port.imageJobs;
23414
+ const uhr = jetzt ?? (() => /* @__PURE__ */ new Date());
23267
23415
  const [filter, setFilter] = (0, import_react27.useState)("pending");
23268
23416
  const [pauseSekunden, setPauseSekunden] = (0, import_react27.useState)(PAUSE_SEKUNDEN_VORGABE);
23269
23417
  const [takt, setTakt] = (0, import_react27.useState)(0);
@@ -23273,22 +23421,21 @@ var ImageJobsArea = ({
23273
23421
  const abbruchRef = (0, import_react27.useRef)(false);
23274
23422
  const [einzeln, setEinzeln] = (0, import_react27.useState)(null);
23275
23423
  const [notiz, setNotiz] = (0, import_react27.useState)(null);
23276
- (0, import_react27.useEffect)(() => {
23277
- if (aktualisierungMs <= 0) return void 0;
23278
- const uhr = setInterval(() => setTakt((t) => t + 1), aktualisierungMs);
23279
- return () => clearInterval(uhr);
23280
- }, [aktualisierungMs]);
23281
- const liste = useDauerAbfrage(
23424
+ const liste = useNachladbareAbfrage(
23282
23425
  () => auftraege?.listAllJobs ? auftraege.listAllJobs({ status: filter }) : Promise.resolve({
23283
23426
  kind: "ok",
23284
23427
  value: { eintraege: [], nextCursor: null, gesamt: 0, offen: 0 }
23285
23428
  }),
23286
23429
  [auftraege, filter, takt]
23287
23430
  );
23288
- const vorschau = useDauerAbfrage(
23431
+ const vorschau = useNachladbareAbfrage(
23289
23432
  () => auftraege?.latestImage ? auftraege.latestImage() : Promise.resolve({ kind: "ok", value: null }),
23290
23433
  [auftraege, takt]
23291
23434
  );
23435
+ const [zuletzt, setZuletzt] = (0, import_react27.useState)(null);
23436
+ (0, import_react27.useEffect)(() => {
23437
+ if (!liste.laedt && liste.wert !== null) setZuletzt(uhr().toISOString());
23438
+ }, [liste.laedt, liste.wert]);
23292
23439
  const seite = liste.wert;
23293
23440
  const eintraege = seite?.eintraege ?? [];
23294
23441
  const offen = seite?.offen ?? 0;
@@ -23356,16 +23503,28 @@ var ImageJobsArea = ({
23356
23503
  }
23357
23504
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("div", { className: "szenen-bildauftraege", "data-testid": "bereich-bildauftraege", children: [
23358
23505
  /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { className: "szenen-area__lead", children: [
23359
- "Alle Bildauftr\xE4ge dieses Bestands \u2014 unabh\xE4ngig davon, zu welchem Promptlauf sie geh\xF6ren. Der Bereich ist als dauerhaft ge\xF6ffnetes Arbeitsfenster gedacht und liest den Bestand alle",
23360
- " ",
23361
- Math.round(aktualisierungMs / 1e3),
23362
- " Sekunden nach."
23506
+ "Alle Bildauftr\xE4ge dieses Bestands \u2014 unabh\xE4ngig davon, zu welchem Promptlauf sie geh\xF6ren. Der Bereich ist als dauerhaft ge\xF6ffnetes Arbeitsfenster gedacht und l\xE4dt ",
23507
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("strong", { children: "nur auf Klick" }),
23508
+ " nach: Es l\xE4uft kein Timer und keine Hintergrundabfrage."
23509
+ ] }),
23510
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("section", { className: "szenen-bildauftraege__nachladen", "aria-label": "Nachladen", children: [
23511
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
23512
+ "button",
23513
+ {
23514
+ type: "button",
23515
+ "data-testid": "bildauftraege-aktualisieren",
23516
+ onClick: neuLesen,
23517
+ title: "Auftr\xE4ge, Zust\xE4nde und neue Bilder neu vom Bestand lesen",
23518
+ children: "\u27F3 Aktualisieren"
23519
+ }
23520
+ ),
23521
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("span", { "data-testid": "bildauftraege-zuletzt", children: zuletzt ? `zuletzt aktualisiert ${zuletzt}` : "noch nicht aktualisiert" })
23363
23522
  ] }),
23364
23523
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
23365
- LetztesBild,
23524
+ Bildkontrolle,
23366
23525
  {
23367
23526
  port,
23368
- bild: vorschau.wert ?? null,
23527
+ stand: vorschau.wert ?? null,
23369
23528
  fehler: vorschau.fehler,
23370
23529
  laedt: vorschau.laedt && vorschau.wert === null,
23371
23530
  ...onPromptOeffnen ? { onPromptOeffnen } : {}
@@ -23452,6 +23611,10 @@ var ImageJobsArea = ({
23452
23611
  gesperrt: queueLaeuft || einzeln !== null,
23453
23612
  laufend: einzeln === eintrag.job.job_id || lauf?.aktuell === eintrag.job.job_id,
23454
23613
  onStart: () => void einzelStart(eintrag.job.job_id),
23614
+ onAngelegt: (text) => {
23615
+ setNotiz({ tone: "ok", text });
23616
+ neuLesen();
23617
+ },
23455
23618
  ...onPromptOeffnen ? { onPromptOeffnen } : {}
23456
23619
  },
23457
23620
  eintrag.job.job_id
@@ -23503,11 +23666,14 @@ var AuftragsZeile = ({
23503
23666
  gesperrt,
23504
23667
  laufend,
23505
23668
  onStart,
23669
+ onAngelegt,
23506
23670
  onPromptOeffnen
23507
23671
  }) => {
23508
23672
  const { job, herkunft } = eintrag;
23509
23673
  const [ergebnisOffen, setErgebnisOffen] = (0, import_react27.useState)(false);
23674
+ const [weitereOffen, setWeitereOffen] = (0, import_react27.useState)(false);
23510
23675
  const hatErgebnis = Boolean(job.generation_batch_id) && (job.status === "succeeded" || job.status === "partial");
23676
+ const kannWeitere = Boolean(port.imageJobs?.requestMoreImages) && istAbgeschlossen(job);
23511
23677
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
23512
23678
  "li",
23513
23679
  {
@@ -23546,6 +23712,13 @@ var AuftragsZeile = ({
23546
23712
  "Erstellt ",
23547
23713
  job.created_at || "\u2014"
23548
23714
  ] }),
23715
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { "data-testid": `bildauftrag-lauf-bilder-${job.job_id}`, children: [
23716
+ "Bisher ",
23717
+ herkunft.bilder_im_lauf,
23718
+ " ",
23719
+ herkunft.bilder_im_lauf === 1 ? "Bild" : "Bilder",
23720
+ " in diesem Promptlauf"
23721
+ ] }),
23549
23722
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("code", { className: "szenen-bilder__auftragskennung", children: job.job_id })
23550
23723
  ] }),
23551
23724
  herkunft.prompt_auszug && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
@@ -23585,8 +23758,18 @@ var AuftragsZeile = ({
23585
23758
  onClick: () => setErgebnisOffen((o) => !o),
23586
23759
  children: ergebnisOffen ? "Ergebnisse ausblenden" : "Ergebnisse ansehen"
23587
23760
  }
23761
+ ),
23762
+ kannWeitere && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
23763
+ "button",
23764
+ {
23765
+ type: "button",
23766
+ "data-testid": `bildauftrag-weitere-${job.job_id}`,
23767
+ onClick: () => setWeitereOffen((o) => !o),
23768
+ children: weitereOffen ? "Weitere Bilder schlie\xDFen" : "Weitere Bilder"
23769
+ }
23588
23770
  )
23589
23771
  ] }),
23772
+ kannWeitere && weitereOffen && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(WeitereBilder, { port, job, onAngelegt }),
23590
23773
  ergebnisOffen && job.generation_batch_id && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
23591
23774
  AuftragsErgebnis,
23592
23775
  {
@@ -23599,8 +23782,135 @@ var AuftragsZeile = ({
23599
23782
  }
23600
23783
  );
23601
23784
  };
23785
+ var WeitereBilder = ({ port, job, onAngelegt }) => {
23786
+ const [anzahl, setAnzahl] = (0, import_react27.useState)(1);
23787
+ const [laeuft, setLaeuft] = (0, import_react27.useState)(false);
23788
+ const [notiz, setNotiz] = (0, import_react27.useState)(null);
23789
+ const wahl = useNachladbareAbfrage(
23790
+ () => port.imageGeneration ? port.imageGeneration.imageModelChoiceForRun(job.prompt_run_id) : Promise.resolve({ kind: "ok", value: null }),
23791
+ [port, job.prompt_run_id]
23792
+ );
23793
+ const gewaehlt = wahl.wert ? findExecutableImageModel(
23794
+ wahl.wert,
23795
+ job.execution_target_model_key,
23796
+ job.execution_profile_id
23797
+ ) : null;
23798
+ const maxBilder = gewaehlt?.max_images ?? null;
23799
+ const anlegen = async (wieViele) => {
23800
+ const anfordern = port.imageJobs?.requestMoreImages;
23801
+ if (!anfordern || laeuft) return;
23802
+ setLaeuft(true);
23803
+ setNotiz(null);
23804
+ try {
23805
+ const ergebnis = await anfordern({
23806
+ sourceJobId: job.job_id,
23807
+ imageCount: wieViele
23808
+ });
23809
+ if (ergebnis.kind === "ok") {
23810
+ onAngelegt(
23811
+ `Neuer Bildauftrag ${ergebnis.job.job_id} f\xFCr Promptlauf ${ergebnis.job.prompt_run_id}: ${ergebnis.job.image_count} ${ergebnis.job.image_count === 1 ? "Bild" : "Bilder"} mit ${ergebnis.job.execution_target_model_key} (Profil ${ergebnis.job.execution_profile_id}). Auftrag ${job.job_id} bleibt unver\xE4ndert abgeschlossen.`
23812
+ );
23813
+ } else {
23814
+ setNotiz({
23815
+ tone: ergebnis.kind === "rejected" ? "rejected" : "error",
23816
+ text: ergebnis.message
23817
+ });
23818
+ }
23819
+ } catch (e) {
23820
+ setNotiz({
23821
+ tone: "error",
23822
+ text: `Der neue Bildauftrag zu ${job.job_id} konnte nicht angelegt werden: ` + (e instanceof Error ? e.message : String(e))
23823
+ });
23824
+ } finally {
23825
+ setLaeuft(false);
23826
+ }
23827
+ };
23828
+ return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
23829
+ "section",
23830
+ {
23831
+ className: "szenen-bildauftraege__weitere",
23832
+ "data-testid": `bildauftrag-weitere-panel-${job.job_id}`,
23833
+ "aria-label": "Weitere Bilder",
23834
+ children: [
23835
+ wahl.fehler && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { role: "alert", "data-testid": `bildauftrag-weitere-fehler-${job.job_id}`, children: [
23836
+ "Die ausf\xFChrbaren Bildmodelle waren nicht lesbar: ",
23837
+ wahl.fehler
23838
+ ] }),
23839
+ wahl.laedt && wahl.wert === null && !wahl.fehler && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { "data-testid": `bildauftrag-weitere-laden-${job.job_id}`, children: "Profil wird gepr\xFCft \u2026" }),
23840
+ wahl.wert !== null && !gewaehlt && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { role: "alert", "data-testid": `bildauftrag-weitere-ungueltig-${job.job_id}`, children: [
23841
+ "Das Profil ",
23842
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("code", { children: job.execution_profile_id }),
23843
+ " von",
23844
+ " ",
23845
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("code", { children: job.execution_target_model_key }),
23846
+ " ist hier nicht mehr ausf\xFChrbar. Es wird kein Ersatz geraten \u2014",
23847
+ " ",
23848
+ wahl.wert.models.length > 0 ? `ausf\xFChrbar w\xE4ren: ${wahl.wert.models.map((m) => m.selection_id).join(", ")}. Bitte den Promptlauf \xF6ffnen und dort ausdr\xFCcklich w\xE4hlen.` : `derzeit ist hier gar kein Bildmodell ausf\xFChrbar: ${wahl.wert.reason ?? "kein sichtbares Zielmodell tr\xE4gt ein passendes Profil."}`
23849
+ ] }),
23850
+ gewaehlt && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
23851
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { "data-testid": `bildauftrag-weitere-profil-${job.job_id}`, children: [
23852
+ gewaehlt.display_name,
23853
+ " bei ",
23854
+ gewaehlt.provider,
23855
+ " \xB7 bis zu",
23856
+ " ",
23857
+ gewaehlt.max_images,
23858
+ " Bilder je Auftrag \xB7 Format",
23859
+ " ",
23860
+ job.aspect_ratio ?? "\u2014"
23861
+ ] }),
23862
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { className: "szenen-bildauftraege__aktionen", children: [
23863
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
23864
+ "button",
23865
+ {
23866
+ type: "button",
23867
+ "data-testid": `bildauftrag-plus1-${job.job_id}`,
23868
+ disabled: laeuft,
23869
+ onClick: () => void anlegen(1),
23870
+ children: "+1 Bild"
23871
+ }
23872
+ ),
23873
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("label", { htmlFor: `bildauftrag-weitere-anzahl-${job.job_id}`, children: "Anzahl" }),
23874
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
23875
+ "select",
23876
+ {
23877
+ id: `bildauftrag-weitere-anzahl-${job.job_id}`,
23878
+ "data-testid": `bildauftrag-weitere-anzahl-${job.job_id}`,
23879
+ value: anzahl,
23880
+ disabled: laeuft,
23881
+ onChange: (e) => setAnzahl(Number(e.target.value)),
23882
+ children: Array.from(
23883
+ { length: Math.max(1, maxBilder ?? 1) },
23884
+ (_, i) => i + 1
23885
+ ).map((n) => /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("option", { value: n, children: n }, n))
23886
+ }
23887
+ ),
23888
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
23889
+ "button",
23890
+ {
23891
+ type: "button",
23892
+ "data-testid": `bildauftrag-weitere-start-${job.job_id}`,
23893
+ disabled: laeuft,
23894
+ onClick: () => void anlegen(anzahl),
23895
+ children: laeuft ? "Wird angelegt \u2026" : "Erzeugen"
23896
+ }
23897
+ )
23898
+ ] })
23899
+ ] }),
23900
+ notiz && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
23901
+ "p",
23902
+ {
23903
+ role: notiz.tone === "error" ? "alert" : void 0,
23904
+ "data-testid": `bildauftrag-weitere-notiz-${job.job_id}`,
23905
+ children: notiz.text
23906
+ }
23907
+ )
23908
+ ]
23909
+ }
23910
+ );
23911
+ };
23602
23912
  var AuftragsErgebnis = ({ port, jobId, generationBatchId }) => {
23603
- const abfrage = useDauerAbfrage(
23913
+ const abfrage = useNachladbareAbfrage(
23604
23914
  () => port.imageBatchHistory ? port.imageBatchHistory.getBatch(generationBatchId) : Promise.resolve({ kind: "ok", value: null }),
23605
23915
  [port, generationBatchId]
23606
23916
  );
@@ -23639,54 +23949,108 @@ var AuftragsErgebnis = ({ port, jobId, generationBatchId }) => {
23639
23949
  }
23640
23950
  ) });
23641
23951
  };
23642
- var LetztesBild = ({ port, bild, fehler: fehler5, laedt, onPromptOeffnen }) => {
23643
- const { quellen, fehler: bildFehler } = useBildQuellen(port, bild ? [bild.bild] : []);
23952
+ var Bildkontrolle = ({ port, stand, fehler: fehler5, laedt, onPromptOeffnen }) => {
23953
+ const folge = stand?.folge ?? [];
23954
+ const neuestes = folge.length > 0 ? folge[folge.length - 1].bild.media_ref : null;
23955
+ const [gewaehlt, setGewaehlt] = (0, import_react27.useState)(null);
23956
+ const neuestesVorher = (0, import_react27.useRef)(null);
23957
+ (0, import_react27.useEffect)(() => {
23958
+ if (neuestes !== neuestesVorher.current) {
23959
+ neuestesVorher.current = neuestes;
23960
+ setGewaehlt(neuestes);
23961
+ return;
23962
+ }
23963
+ if (gewaehlt !== null && !folge.some((b) => b.bild.media_ref === gewaehlt)) {
23964
+ setGewaehlt(neuestes);
23965
+ }
23966
+ }, [neuestes, folge, gewaehlt]);
23967
+ const stelle = folge.findIndex((b) => b.bild.media_ref === gewaehlt);
23968
+ const index = stelle >= 0 ? stelle : folge.length - 1;
23969
+ const aktuell = index >= 0 ? folge[index] ?? null : null;
23970
+ const { quellen, fehler: bildFehler } = useBildQuellen(port, aktuell ? [aktuell.bild] : []);
23971
+ const blaettern = (schritt) => {
23972
+ const ziel = folge[index + schritt];
23973
+ if (ziel) setGewaehlt(ziel.bild.media_ref);
23974
+ };
23644
23975
  return /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(
23645
23976
  "section",
23646
23977
  {
23647
23978
  className: "szenen-bildauftraege__letztes",
23648
23979
  "data-testid": "bildauftraege-letztes-bild",
23649
- "aria-label": "Letztes erzeugtes Bild",
23980
+ "aria-label": "Bildkontrolle",
23650
23981
  children: [
23651
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h3", { children: "Letztes erzeugtes Bild" }),
23982
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("h3", { children: "Bildkontrolle" }),
23652
23983
  fehler5 && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { role: "alert", "data-testid": "bildauftraege-letztes-fehler", children: [
23653
- "Das zuletzt erzeugte Bild war nicht lesbar: ",
23984
+ "Der Bildstand war nicht lesbar: ",
23654
23985
  fehler5
23655
23986
  ] }),
23656
- !bild && laedt && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { "data-testid": "bildauftraege-letztes-laden", children: "Wird geladen \u2026" }),
23657
- !bild && !laedt && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { "data-testid": "bildauftraege-letztes-leer", children: "In diesem Bestand ist noch kein Bild aus einem Auftrag gespeichert." }),
23658
- bild && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
23987
+ !aktuell && laedt && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { "data-testid": "bildauftraege-letztes-laden", children: "Wird geladen \u2026" }),
23988
+ !aktuell && !laedt && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { "data-testid": "bildauftraege-letztes-leer", children: "In diesem Bestand ist noch kein Bild aus einem Auftrag gespeichert." }),
23989
+ aktuell && /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)(import_jsx_runtime24.Fragment, { children: [
23659
23990
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
23660
23991
  Kachel,
23661
23992
  {
23662
- quelle: quellen.get(bild.bild.media_ref) ?? null,
23663
- alt: `Letztes erzeugtes Bild ${bild.bild.media_ref}`,
23664
- ...bildFehler.get(bild.bild.media_ref) ? { leerText: "Nicht ladbar" } : {}
23993
+ quelle: quellen.get(aktuell.bild.media_ref) ?? null,
23994
+ alt: `Bild ${index + 1} von ${folge.length} \u2014 ${aktuell.bild.media_ref}`,
23995
+ ...bildFehler.get(aktuell.bild.media_ref) ? { leerText: "Nicht ladbar" } : {}
23665
23996
  }
23666
23997
  ),
23667
- /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { className: "szenen-bildauftraege__meta", "data-testid": "bildauftraege-letztes-meta", children: [
23998
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { className: "szenen-bildauftraege__blaettern", children: [
23999
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
24000
+ "button",
24001
+ {
24002
+ type: "button",
24003
+ "data-testid": "bildauftraege-zurueck",
24004
+ disabled: index <= 0,
24005
+ onClick: () => blaettern(-1),
24006
+ children: "\u2190 Zur\xFCck"
24007
+ }
24008
+ ),
23668
24009
  /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { "data-testid": "bildauftraege-letztes-position", children: [
23669
- bild.position,
24010
+ index + 1,
23670
24011
  " / ",
23671
- bild.anzahl
24012
+ folge.length
23672
24013
  ] }),
24014
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
24015
+ "button",
24016
+ {
24017
+ type: "button",
24018
+ "data-testid": "bildauftraege-vor",
24019
+ disabled: index >= folge.length - 1,
24020
+ onClick: () => blaettern(1),
24021
+ children: "Vor \u2192"
24022
+ }
24023
+ )
24024
+ ] }),
24025
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("p", { className: "szenen-bildauftraege__meta", "data-testid": "bildauftraege-letztes-meta", children: [
23673
24026
  /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { children: [
23674
24027
  "Auftrag",
23675
24028
  " ",
23676
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("code", { "data-testid": "bildauftraege-letztes-job", children: bild.job?.job_id ?? "ohne Auftrag" })
24029
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("code", { "data-testid": "bildauftraege-letztes-job", children: aktuell.job_id ?? "ohne Auftrag" })
24030
+ ] }),
24031
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { children: [
24032
+ "Batch",
24033
+ " ",
24034
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("code", { "data-testid": "bildauftraege-letztes-batch", children: aktuell.generation_batch_id }),
24035
+ " ",
24036
+ "(Bild ",
24037
+ aktuell.position_im_batch,
24038
+ " von ",
24039
+ aktuell.anzahl_im_batch,
24040
+ ")"
23677
24041
  ] }),
23678
24042
  /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { children: [
23679
24043
  "Promptlauf ",
23680
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("code", { children: bild.prompt_run_id })
24044
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("code", { children: aktuell.prompt_run_id })
23681
24045
  ] }),
23682
24046
  /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { children: [
23683
24047
  "Modell",
23684
24048
  " ",
23685
- /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("code", { children: bild.execution_target_model_key ?? bild.bild.model ?? "\u2014" })
24049
+ /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("code", { children: aktuell.execution_target_model_key ?? aktuell.bild.model ?? "\u2014" })
23686
24050
  ] }),
23687
24051
  /* @__PURE__ */ (0, import_jsx_runtime24.jsxs)("span", { children: [
23688
24052
  "Gespeichert ",
23689
- bild.gespeichert_am ?? "\u2014"
24053
+ aktuell.gespeichert_am ?? "\u2014"
23690
24054
  ] })
23691
24055
  ] }),
23692
24056
  /* @__PURE__ */ (0, import_jsx_runtime24.jsx)("p", { className: "szenen-bildauftraege__aktionen", children: onPromptOeffnen && /* @__PURE__ */ (0, import_jsx_runtime24.jsx)(
@@ -23694,7 +24058,7 @@ var LetztesBild = ({ port, bild, fehler: fehler5, laedt, onPromptOeffnen }) => {
23694
24058
  {
23695
24059
  type: "button",
23696
24060
  "data-testid": "bildauftraege-letztes-prompt",
23697
- onClick: () => onPromptOeffnen(bild.prompt_run_id),
24061
+ onClick: () => onPromptOeffnen(aktuell.prompt_run_id),
23698
24062
  children: "Prompt \xF6ffnen \xB7 alle Ergebnisse ansehen"
23699
24063
  }
23700
24064
  ) })
@@ -23867,6 +24231,7 @@ var SzenenView = ({
23867
24231
  assessDerivationResponse,
23868
24232
  batchesForPromptRun,
23869
24233
  bildPfad,
24234
+ bilderDesLaufs,
23870
24235
  buildDerivationSystemInstruction,
23871
24236
  buildDerivationUserPrompt,
23872
24237
  buildManualPromptRunRecord,