@rslsp1/fa-app-tools 9.4.4 → 10.0.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.
@@ -14575,7 +14575,6 @@ var MediaArea = ({ port }) => {
14575
14575
  // src/features/szenen/areas/ImageJobsArea.tsx
14576
14576
  import { useCallback as useCallback7, useEffect as useEffect15, useRef as useRef9, useState as useState27 } from "react";
14577
14577
  import { Fragment as Fragment12, jsx as jsx24, jsxs as jsxs24 } from "react/jsx-runtime";
14578
- var AKTUALISIERUNG_MS = 5e3;
14579
14578
  var STATUS_TEXT2 = {
14580
14579
  pending: "wartet auf \xDCbernahme",
14581
14580
  claimed: "\xFCbernommen (l\xE4uft oder unterbrochen)",
@@ -14591,7 +14590,10 @@ var FILTER = [
14591
14590
  { id: "failed", titel: "Fehlgeschlagen" },
14592
14591
  { id: "all", titel: "Alle" }
14593
14592
  ];
14594
- function useDauerAbfrage(frage, abhaengigkeiten) {
14593
+ function istAbgeschlossen(job) {
14594
+ return job.status !== "pending" && job.status !== "claimed";
14595
+ }
14596
+ function useNachladbareAbfrage(frage, abhaengigkeiten) {
14595
14597
  const [stand, setStand] = useState27(
14596
14598
  () => ({ wert: null, fehler: null, laedt: true })
14597
14599
  );
@@ -14623,10 +14625,11 @@ function useDauerAbfrage(frage, abhaengigkeiten) {
14623
14625
  var ImageJobsArea = ({
14624
14626
  port,
14625
14627
  onPromptOeffnen,
14626
- aktualisierungMs = AKTUALISIERUNG_MS,
14627
- warte
14628
+ warte,
14629
+ jetzt
14628
14630
  }) => {
14629
14631
  const auftraege = port.imageJobs;
14632
+ const uhr = jetzt ?? (() => /* @__PURE__ */ new Date());
14630
14633
  const [filter, setFilter] = useState27("pending");
14631
14634
  const [pauseSekunden, setPauseSekunden] = useState27(PAUSE_SEKUNDEN_VORGABE);
14632
14635
  const [takt, setTakt] = useState27(0);
@@ -14636,22 +14639,21 @@ var ImageJobsArea = ({
14636
14639
  const abbruchRef = useRef9(false);
14637
14640
  const [einzeln, setEinzeln] = useState27(null);
14638
14641
  const [notiz, setNotiz] = useState27(null);
14639
- useEffect15(() => {
14640
- if (aktualisierungMs <= 0) return void 0;
14641
- const uhr = setInterval(() => setTakt((t) => t + 1), aktualisierungMs);
14642
- return () => clearInterval(uhr);
14643
- }, [aktualisierungMs]);
14644
- const liste = useDauerAbfrage(
14642
+ const liste = useNachladbareAbfrage(
14645
14643
  () => auftraege?.listAllJobs ? auftraege.listAllJobs({ status: filter }) : Promise.resolve({
14646
14644
  kind: "ok",
14647
14645
  value: { eintraege: [], nextCursor: null, gesamt: 0, offen: 0 }
14648
14646
  }),
14649
14647
  [auftraege, filter, takt]
14650
14648
  );
14651
- const vorschau = useDauerAbfrage(
14649
+ const vorschau = useNachladbareAbfrage(
14652
14650
  () => auftraege?.latestImage ? auftraege.latestImage() : Promise.resolve({ kind: "ok", value: null }),
14653
14651
  [auftraege, takt]
14654
14652
  );
14653
+ const [zuletzt, setZuletzt] = useState27(null);
14654
+ useEffect15(() => {
14655
+ if (!liste.laedt && liste.wert !== null) setZuletzt(uhr().toISOString());
14656
+ }, [liste.laedt, liste.wert]);
14655
14657
  const seite = liste.wert;
14656
14658
  const eintraege = seite?.eintraege ?? [];
14657
14659
  const offen = seite?.offen ?? 0;
@@ -14719,16 +14721,28 @@ var ImageJobsArea = ({
14719
14721
  }
14720
14722
  return /* @__PURE__ */ jsxs24("div", { className: "szenen-bildauftraege", "data-testid": "bereich-bildauftraege", children: [
14721
14723
  /* @__PURE__ */ jsxs24("p", { className: "szenen-area__lead", children: [
14722
- "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",
14723
- " ",
14724
- Math.round(aktualisierungMs / 1e3),
14725
- " Sekunden nach."
14724
+ "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 ",
14725
+ /* @__PURE__ */ jsx24("strong", { children: "nur auf Klick" }),
14726
+ " nach: Es l\xE4uft kein Timer und keine Hintergrundabfrage."
14727
+ ] }),
14728
+ /* @__PURE__ */ jsxs24("section", { className: "szenen-bildauftraege__nachladen", "aria-label": "Nachladen", children: [
14729
+ /* @__PURE__ */ jsx24(
14730
+ "button",
14731
+ {
14732
+ type: "button",
14733
+ "data-testid": "bildauftraege-aktualisieren",
14734
+ onClick: neuLesen,
14735
+ title: "Auftr\xE4ge, Zust\xE4nde und neue Bilder neu vom Bestand lesen",
14736
+ children: "\u27F3 Aktualisieren"
14737
+ }
14738
+ ),
14739
+ /* @__PURE__ */ jsx24("span", { "data-testid": "bildauftraege-zuletzt", children: zuletzt ? `zuletzt aktualisiert ${zuletzt}` : "noch nicht aktualisiert" })
14726
14740
  ] }),
14727
14741
  /* @__PURE__ */ jsx24(
14728
- LetztesBild,
14742
+ Bildkontrolle,
14729
14743
  {
14730
14744
  port,
14731
- bild: vorschau.wert ?? null,
14745
+ stand: vorschau.wert ?? null,
14732
14746
  fehler: vorschau.fehler,
14733
14747
  laedt: vorschau.laedt && vorschau.wert === null,
14734
14748
  ...onPromptOeffnen ? { onPromptOeffnen } : {}
@@ -14815,6 +14829,10 @@ var ImageJobsArea = ({
14815
14829
  gesperrt: queueLaeuft || einzeln !== null,
14816
14830
  laufend: einzeln === eintrag.job.job_id || lauf?.aktuell === eintrag.job.job_id,
14817
14831
  onStart: () => void einzelStart(eintrag.job.job_id),
14832
+ onAngelegt: (text) => {
14833
+ setNotiz({ tone: "ok", text });
14834
+ neuLesen();
14835
+ },
14818
14836
  ...onPromptOeffnen ? { onPromptOeffnen } : {}
14819
14837
  },
14820
14838
  eintrag.job.job_id
@@ -14866,11 +14884,14 @@ var AuftragsZeile = ({
14866
14884
  gesperrt,
14867
14885
  laufend,
14868
14886
  onStart,
14887
+ onAngelegt,
14869
14888
  onPromptOeffnen
14870
14889
  }) => {
14871
14890
  const { job, herkunft } = eintrag;
14872
14891
  const [ergebnisOffen, setErgebnisOffen] = useState27(false);
14892
+ const [weitereOffen, setWeitereOffen] = useState27(false);
14873
14893
  const hatErgebnis = Boolean(job.generation_batch_id) && (job.status === "succeeded" || job.status === "partial");
14894
+ const kannWeitere = Boolean(port.imageJobs?.requestMoreImages) && istAbgeschlossen(job);
14874
14895
  return /* @__PURE__ */ jsxs24(
14875
14896
  "li",
14876
14897
  {
@@ -14909,6 +14930,13 @@ var AuftragsZeile = ({
14909
14930
  "Erstellt ",
14910
14931
  job.created_at || "\u2014"
14911
14932
  ] }),
14933
+ /* @__PURE__ */ jsxs24("span", { "data-testid": `bildauftrag-lauf-bilder-${job.job_id}`, children: [
14934
+ "Bisher ",
14935
+ herkunft.bilder_im_lauf,
14936
+ " ",
14937
+ herkunft.bilder_im_lauf === 1 ? "Bild" : "Bilder",
14938
+ " in diesem Promptlauf"
14939
+ ] }),
14912
14940
  /* @__PURE__ */ jsx24("code", { className: "szenen-bilder__auftragskennung", children: job.job_id })
14913
14941
  ] }),
14914
14942
  herkunft.prompt_auszug && /* @__PURE__ */ jsx24(
@@ -14948,8 +14976,18 @@ var AuftragsZeile = ({
14948
14976
  onClick: () => setErgebnisOffen((o) => !o),
14949
14977
  children: ergebnisOffen ? "Ergebnisse ausblenden" : "Ergebnisse ansehen"
14950
14978
  }
14979
+ ),
14980
+ kannWeitere && /* @__PURE__ */ jsx24(
14981
+ "button",
14982
+ {
14983
+ type: "button",
14984
+ "data-testid": `bildauftrag-weitere-${job.job_id}`,
14985
+ onClick: () => setWeitereOffen((o) => !o),
14986
+ children: weitereOffen ? "Weitere Bilder schlie\xDFen" : "Weitere Bilder"
14987
+ }
14951
14988
  )
14952
14989
  ] }),
14990
+ kannWeitere && weitereOffen && /* @__PURE__ */ jsx24(WeitereBilder, { port, job, onAngelegt }),
14953
14991
  ergebnisOffen && job.generation_batch_id && /* @__PURE__ */ jsx24(
14954
14992
  AuftragsErgebnis,
14955
14993
  {
@@ -14962,8 +15000,135 @@ var AuftragsZeile = ({
14962
15000
  }
14963
15001
  );
14964
15002
  };
15003
+ var WeitereBilder = ({ port, job, onAngelegt }) => {
15004
+ const [anzahl, setAnzahl] = useState27(1);
15005
+ const [laeuft, setLaeuft] = useState27(false);
15006
+ const [notiz, setNotiz] = useState27(null);
15007
+ const wahl = useNachladbareAbfrage(
15008
+ () => port.imageGeneration ? port.imageGeneration.imageModelChoiceForRun(job.prompt_run_id) : Promise.resolve({ kind: "ok", value: null }),
15009
+ [port, job.prompt_run_id]
15010
+ );
15011
+ const gewaehlt = wahl.wert ? findExecutableImageModel(
15012
+ wahl.wert,
15013
+ job.execution_target_model_key,
15014
+ job.execution_profile_id
15015
+ ) : null;
15016
+ const maxBilder = gewaehlt?.max_images ?? null;
15017
+ const anlegen = async (wieViele) => {
15018
+ const anfordern = port.imageJobs?.requestMoreImages;
15019
+ if (!anfordern || laeuft) return;
15020
+ setLaeuft(true);
15021
+ setNotiz(null);
15022
+ try {
15023
+ const ergebnis = await anfordern({
15024
+ sourceJobId: job.job_id,
15025
+ imageCount: wieViele
15026
+ });
15027
+ if (ergebnis.kind === "ok") {
15028
+ onAngelegt(
15029
+ `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.`
15030
+ );
15031
+ } else {
15032
+ setNotiz({
15033
+ tone: ergebnis.kind === "rejected" ? "rejected" : "error",
15034
+ text: ergebnis.message
15035
+ });
15036
+ }
15037
+ } catch (e) {
15038
+ setNotiz({
15039
+ tone: "error",
15040
+ text: `Der neue Bildauftrag zu ${job.job_id} konnte nicht angelegt werden: ` + (e instanceof Error ? e.message : String(e))
15041
+ });
15042
+ } finally {
15043
+ setLaeuft(false);
15044
+ }
15045
+ };
15046
+ return /* @__PURE__ */ jsxs24(
15047
+ "section",
15048
+ {
15049
+ className: "szenen-bildauftraege__weitere",
15050
+ "data-testid": `bildauftrag-weitere-panel-${job.job_id}`,
15051
+ "aria-label": "Weitere Bilder",
15052
+ children: [
15053
+ wahl.fehler && /* @__PURE__ */ jsxs24("p", { role: "alert", "data-testid": `bildauftrag-weitere-fehler-${job.job_id}`, children: [
15054
+ "Die ausf\xFChrbaren Bildmodelle waren nicht lesbar: ",
15055
+ wahl.fehler
15056
+ ] }),
15057
+ wahl.laedt && wahl.wert === null && !wahl.fehler && /* @__PURE__ */ jsx24("p", { "data-testid": `bildauftrag-weitere-laden-${job.job_id}`, children: "Profil wird gepr\xFCft \u2026" }),
15058
+ wahl.wert !== null && !gewaehlt && /* @__PURE__ */ jsxs24("p", { role: "alert", "data-testid": `bildauftrag-weitere-ungueltig-${job.job_id}`, children: [
15059
+ "Das Profil ",
15060
+ /* @__PURE__ */ jsx24("code", { children: job.execution_profile_id }),
15061
+ " von",
15062
+ " ",
15063
+ /* @__PURE__ */ jsx24("code", { children: job.execution_target_model_key }),
15064
+ " ist hier nicht mehr ausf\xFChrbar. Es wird kein Ersatz geraten \u2014",
15065
+ " ",
15066
+ 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."}`
15067
+ ] }),
15068
+ gewaehlt && /* @__PURE__ */ jsxs24(Fragment12, { children: [
15069
+ /* @__PURE__ */ jsxs24("p", { "data-testid": `bildauftrag-weitere-profil-${job.job_id}`, children: [
15070
+ gewaehlt.display_name,
15071
+ " bei ",
15072
+ gewaehlt.provider,
15073
+ " \xB7 bis zu",
15074
+ " ",
15075
+ gewaehlt.max_images,
15076
+ " Bilder je Auftrag \xB7 Format",
15077
+ " ",
15078
+ job.aspect_ratio ?? "\u2014"
15079
+ ] }),
15080
+ /* @__PURE__ */ jsxs24("p", { className: "szenen-bildauftraege__aktionen", children: [
15081
+ /* @__PURE__ */ jsx24(
15082
+ "button",
15083
+ {
15084
+ type: "button",
15085
+ "data-testid": `bildauftrag-plus1-${job.job_id}`,
15086
+ disabled: laeuft,
15087
+ onClick: () => void anlegen(1),
15088
+ children: "+1 Bild"
15089
+ }
15090
+ ),
15091
+ /* @__PURE__ */ jsx24("label", { htmlFor: `bildauftrag-weitere-anzahl-${job.job_id}`, children: "Anzahl" }),
15092
+ /* @__PURE__ */ jsx24(
15093
+ "select",
15094
+ {
15095
+ id: `bildauftrag-weitere-anzahl-${job.job_id}`,
15096
+ "data-testid": `bildauftrag-weitere-anzahl-${job.job_id}`,
15097
+ value: anzahl,
15098
+ disabled: laeuft,
15099
+ onChange: (e) => setAnzahl(Number(e.target.value)),
15100
+ children: Array.from(
15101
+ { length: Math.max(1, maxBilder ?? 1) },
15102
+ (_, i) => i + 1
15103
+ ).map((n) => /* @__PURE__ */ jsx24("option", { value: n, children: n }, n))
15104
+ }
15105
+ ),
15106
+ /* @__PURE__ */ jsx24(
15107
+ "button",
15108
+ {
15109
+ type: "button",
15110
+ "data-testid": `bildauftrag-weitere-start-${job.job_id}`,
15111
+ disabled: laeuft,
15112
+ onClick: () => void anlegen(anzahl),
15113
+ children: laeuft ? "Wird angelegt \u2026" : "Erzeugen"
15114
+ }
15115
+ )
15116
+ ] })
15117
+ ] }),
15118
+ notiz && /* @__PURE__ */ jsx24(
15119
+ "p",
15120
+ {
15121
+ role: notiz.tone === "error" ? "alert" : void 0,
15122
+ "data-testid": `bildauftrag-weitere-notiz-${job.job_id}`,
15123
+ children: notiz.text
15124
+ }
15125
+ )
15126
+ ]
15127
+ }
15128
+ );
15129
+ };
14965
15130
  var AuftragsErgebnis = ({ port, jobId, generationBatchId }) => {
14966
- const abfrage = useDauerAbfrage(
15131
+ const abfrage = useNachladbareAbfrage(
14967
15132
  () => port.imageBatchHistory ? port.imageBatchHistory.getBatch(generationBatchId) : Promise.resolve({ kind: "ok", value: null }),
14968
15133
  [port, generationBatchId]
14969
15134
  );
@@ -15002,54 +15167,108 @@ var AuftragsErgebnis = ({ port, jobId, generationBatchId }) => {
15002
15167
  }
15003
15168
  ) });
15004
15169
  };
15005
- var LetztesBild = ({ port, bild, fehler: fehler5, laedt, onPromptOeffnen }) => {
15006
- const { quellen, fehler: bildFehler } = useBildQuellen(port, bild ? [bild.bild] : []);
15170
+ var Bildkontrolle = ({ port, stand, fehler: fehler5, laedt, onPromptOeffnen }) => {
15171
+ const folge = stand?.folge ?? [];
15172
+ const neuestes = folge.length > 0 ? folge[folge.length - 1].bild.media_ref : null;
15173
+ const [gewaehlt, setGewaehlt] = useState27(null);
15174
+ const neuestesVorher = useRef9(null);
15175
+ useEffect15(() => {
15176
+ if (neuestes !== neuestesVorher.current) {
15177
+ neuestesVorher.current = neuestes;
15178
+ setGewaehlt(neuestes);
15179
+ return;
15180
+ }
15181
+ if (gewaehlt !== null && !folge.some((b) => b.bild.media_ref === gewaehlt)) {
15182
+ setGewaehlt(neuestes);
15183
+ }
15184
+ }, [neuestes, folge, gewaehlt]);
15185
+ const stelle = folge.findIndex((b) => b.bild.media_ref === gewaehlt);
15186
+ const index = stelle >= 0 ? stelle : folge.length - 1;
15187
+ const aktuell = index >= 0 ? folge[index] ?? null : null;
15188
+ const { quellen, fehler: bildFehler } = useBildQuellen(port, aktuell ? [aktuell.bild] : []);
15189
+ const blaettern = (schritt) => {
15190
+ const ziel = folge[index + schritt];
15191
+ if (ziel) setGewaehlt(ziel.bild.media_ref);
15192
+ };
15007
15193
  return /* @__PURE__ */ jsxs24(
15008
15194
  "section",
15009
15195
  {
15010
15196
  className: "szenen-bildauftraege__letztes",
15011
15197
  "data-testid": "bildauftraege-letztes-bild",
15012
- "aria-label": "Letztes erzeugtes Bild",
15198
+ "aria-label": "Bildkontrolle",
15013
15199
  children: [
15014
- /* @__PURE__ */ jsx24("h3", { children: "Letztes erzeugtes Bild" }),
15200
+ /* @__PURE__ */ jsx24("h3", { children: "Bildkontrolle" }),
15015
15201
  fehler5 && /* @__PURE__ */ jsxs24("p", { role: "alert", "data-testid": "bildauftraege-letztes-fehler", children: [
15016
- "Das zuletzt erzeugte Bild war nicht lesbar: ",
15202
+ "Der Bildstand war nicht lesbar: ",
15017
15203
  fehler5
15018
15204
  ] }),
15019
- !bild && laedt && /* @__PURE__ */ jsx24("p", { "data-testid": "bildauftraege-letztes-laden", children: "Wird geladen \u2026" }),
15020
- !bild && !laedt && /* @__PURE__ */ jsx24("p", { "data-testid": "bildauftraege-letztes-leer", children: "In diesem Bestand ist noch kein Bild aus einem Auftrag gespeichert." }),
15021
- bild && /* @__PURE__ */ jsxs24(Fragment12, { children: [
15205
+ !aktuell && laedt && /* @__PURE__ */ jsx24("p", { "data-testid": "bildauftraege-letztes-laden", children: "Wird geladen \u2026" }),
15206
+ !aktuell && !laedt && /* @__PURE__ */ jsx24("p", { "data-testid": "bildauftraege-letztes-leer", children: "In diesem Bestand ist noch kein Bild aus einem Auftrag gespeichert." }),
15207
+ aktuell && /* @__PURE__ */ jsxs24(Fragment12, { children: [
15022
15208
  /* @__PURE__ */ jsx24(
15023
15209
  Kachel,
15024
15210
  {
15025
- quelle: quellen.get(bild.bild.media_ref) ?? null,
15026
- alt: `Letztes erzeugtes Bild ${bild.bild.media_ref}`,
15027
- ...bildFehler.get(bild.bild.media_ref) ? { leerText: "Nicht ladbar" } : {}
15211
+ quelle: quellen.get(aktuell.bild.media_ref) ?? null,
15212
+ alt: `Bild ${index + 1} von ${folge.length} \u2014 ${aktuell.bild.media_ref}`,
15213
+ ...bildFehler.get(aktuell.bild.media_ref) ? { leerText: "Nicht ladbar" } : {}
15028
15214
  }
15029
15215
  ),
15030
- /* @__PURE__ */ jsxs24("p", { className: "szenen-bildauftraege__meta", "data-testid": "bildauftraege-letztes-meta", children: [
15216
+ /* @__PURE__ */ jsxs24("p", { className: "szenen-bildauftraege__blaettern", children: [
15217
+ /* @__PURE__ */ jsx24(
15218
+ "button",
15219
+ {
15220
+ type: "button",
15221
+ "data-testid": "bildauftraege-zurueck",
15222
+ disabled: index <= 0,
15223
+ onClick: () => blaettern(-1),
15224
+ children: "\u2190 Zur\xFCck"
15225
+ }
15226
+ ),
15031
15227
  /* @__PURE__ */ jsxs24("span", { "data-testid": "bildauftraege-letztes-position", children: [
15032
- bild.position,
15228
+ index + 1,
15033
15229
  " / ",
15034
- bild.anzahl
15230
+ folge.length
15035
15231
  ] }),
15232
+ /* @__PURE__ */ jsx24(
15233
+ "button",
15234
+ {
15235
+ type: "button",
15236
+ "data-testid": "bildauftraege-vor",
15237
+ disabled: index >= folge.length - 1,
15238
+ onClick: () => blaettern(1),
15239
+ children: "Vor \u2192"
15240
+ }
15241
+ )
15242
+ ] }),
15243
+ /* @__PURE__ */ jsxs24("p", { className: "szenen-bildauftraege__meta", "data-testid": "bildauftraege-letztes-meta", children: [
15036
15244
  /* @__PURE__ */ jsxs24("span", { children: [
15037
15245
  "Auftrag",
15038
15246
  " ",
15039
- /* @__PURE__ */ jsx24("code", { "data-testid": "bildauftraege-letztes-job", children: bild.job?.job_id ?? "ohne Auftrag" })
15247
+ /* @__PURE__ */ jsx24("code", { "data-testid": "bildauftraege-letztes-job", children: aktuell.job_id ?? "ohne Auftrag" })
15248
+ ] }),
15249
+ /* @__PURE__ */ jsxs24("span", { children: [
15250
+ "Batch",
15251
+ " ",
15252
+ /* @__PURE__ */ jsx24("code", { "data-testid": "bildauftraege-letztes-batch", children: aktuell.generation_batch_id }),
15253
+ " ",
15254
+ "(Bild ",
15255
+ aktuell.position_im_batch,
15256
+ " von ",
15257
+ aktuell.anzahl_im_batch,
15258
+ ")"
15040
15259
  ] }),
15041
15260
  /* @__PURE__ */ jsxs24("span", { children: [
15042
15261
  "Promptlauf ",
15043
- /* @__PURE__ */ jsx24("code", { children: bild.prompt_run_id })
15262
+ /* @__PURE__ */ jsx24("code", { children: aktuell.prompt_run_id })
15044
15263
  ] }),
15045
15264
  /* @__PURE__ */ jsxs24("span", { children: [
15046
15265
  "Modell",
15047
15266
  " ",
15048
- /* @__PURE__ */ jsx24("code", { children: bild.execution_target_model_key ?? bild.bild.model ?? "\u2014" })
15267
+ /* @__PURE__ */ jsx24("code", { children: aktuell.execution_target_model_key ?? aktuell.bild.model ?? "\u2014" })
15049
15268
  ] }),
15050
15269
  /* @__PURE__ */ jsxs24("span", { children: [
15051
15270
  "Gespeichert ",
15052
- bild.gespeichert_am ?? "\u2014"
15271
+ aktuell.gespeichert_am ?? "\u2014"
15053
15272
  ] })
15054
15273
  ] }),
15055
15274
  /* @__PURE__ */ jsx24("p", { className: "szenen-bildauftraege__aktionen", children: onPromptOeffnen && /* @__PURE__ */ jsx24(
@@ -15057,7 +15276,7 @@ var LetztesBild = ({ port, bild, fehler: fehler5, laedt, onPromptOeffnen }) => {
15057
15276
  {
15058
15277
  type: "button",
15059
15278
  "data-testid": "bildauftraege-letztes-prompt",
15060
- onClick: () => onPromptOeffnen(bild.prompt_run_id),
15279
+ onClick: () => onPromptOeffnen(aktuell.prompt_run_id),
15061
15280
  children: "Prompt \xF6ffnen \xB7 alle Ergebnisse ansehen"
15062
15281
  }
15063
15282
  ) })
@@ -20004,7 +20223,8 @@ function kuerze(text, zeichen) {
20004
20223
  const sauber = text.replace(/\s+/g, " ").trim();
20005
20224
  return sauber.length <= zeichen ? sauber : `${sauber.slice(0, zeichen - 1)}\u2026`;
20006
20225
  }
20007
- function herkunftFuer(job, laeufe, elementTitel) {
20226
+ function herkunftFuer(job, laeufe, elementTitel, bilderJeLauf2) {
20227
+ const bilder_im_lauf = bilderJeLauf2.get(job.prompt_run_id)?.length ?? 0;
20008
20228
  const lauf = laeufe.get(job.prompt_run_id);
20009
20229
  if (!lauf) {
20010
20230
  return {
@@ -20012,7 +20232,8 @@ function herkunftFuer(job, laeufe, elementTitel) {
20012
20232
  szene_titel: null,
20013
20233
  szene_element_id: null,
20014
20234
  prompt_auszug: null,
20015
- lauf_gefunden: false
20235
+ lauf_gefunden: false,
20236
+ bilder_im_lauf
20016
20237
  };
20017
20238
  }
20018
20239
  const elementId = benennendesElement(lauf);
@@ -20022,7 +20243,8 @@ function herkunftFuer(job, laeufe, elementTitel) {
20022
20243
  szene_titel: elementId ? elementTitel.get(elementId) ?? null : null,
20023
20244
  szene_element_id: elementId,
20024
20245
  prompt_auszug: istText9(positiv) ? kuerze(positiv, PROMPT_AUSZUG_ZEICHEN) : null,
20025
- lauf_gefunden: true
20246
+ lauf_gefunden: true,
20247
+ bilder_im_lauf
20026
20248
  };
20027
20249
  }
20028
20250
  function sortiere(jobs, logIndex, aelteste_zuerst) {
@@ -20055,57 +20277,90 @@ function selectImageJobs(events, storeId, query = {}) {
20055
20277
  const ausschnitt = geordnet.slice(ab, ab + limit);
20056
20278
  const laeufe = new Map(collectPromptRuns(events).map((r) => [r.run_id, r]));
20057
20279
  const elementTitel = titelJeElement(events);
20280
+ const { jeLauf: bilder } = bilderJeLauf(events, alle);
20058
20281
  return {
20059
20282
  eintraege: ausschnitt.map((job) => ({
20060
20283
  job,
20061
- herkunft: herkunftFuer(job, laeufe, elementTitel)
20284
+ herkunft: herkunftFuer(job, laeufe, elementTitel, bilder)
20062
20285
  })),
20063
20286
  nextCursor: ab + ausschnitt.length < geordnet.length ? ausschnitt[ausschnitt.length - 1].job_id : null,
20064
20287
  gesamt: geordnet.length,
20065
20288
  offen
20066
20289
  };
20067
20290
  }
20068
- function letztesErzeugtesBild(events, storeId) {
20069
- const jobs = collectImageJobs(events).filter((j) => j.store_id === storeId);
20291
+ function bildRefAus(roh, batchId) {
20292
+ return {
20293
+ media_ref: roh.media_ref,
20294
+ position: roh.position,
20295
+ generation_batch_id: batchId,
20296
+ ...istText9(roh.asset_id) ? { asset_id: roh.asset_id } : {},
20297
+ ...istText9(roh.mime_type) ? { mime_type: roh.mime_type } : {},
20298
+ model: roh.model ?? null,
20299
+ seed: roh.seed ?? null,
20300
+ width: roh.width ?? null,
20301
+ height: roh.height ?? null
20302
+ };
20303
+ }
20304
+ function bilderJeLauf(events, jobs) {
20070
20305
  const jobJeBatch = new Map(jobs.map((j) => [generationBatchIdOf(j.job_id), j]));
20071
- for (let i = events.length - 1; i >= 0; i -= 1) {
20072
- const event = events[i];
20306
+ const jeLauf = /* @__PURE__ */ new Map();
20307
+ const gesehen = /* @__PURE__ */ new Set();
20308
+ let juengsterLauf = null;
20309
+ for (const event of events) {
20073
20310
  if (event.type !== SZENEN_EVENT_TYPES.imageBatchPersisted) continue;
20074
20311
  const p = event.payload;
20075
20312
  if (!p || !istText9(p.item_id) || !istText9(p.prompt_run_id) || !istText9(p.generation_batch_id)) continue;
20076
- const bilder = Array.isArray(p.images) ? p.images : [];
20077
- const letztes = bilder.filter((b) => !!b && istText9(b.media_ref) && typeof b.position === "number").reduce(
20078
- (bisher, b) => bisher === null || b.position > bisher.position ? b : bisher,
20079
- null
20080
- );
20081
- if (!letztes) continue;
20313
+ if (gesehen.has(p.generation_batch_id)) continue;
20314
+ gesehen.add(p.generation_batch_id);
20315
+ 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);
20316
+ if (roh.length === 0) continue;
20082
20317
  const job = jobJeBatch.get(p.generation_batch_id) ?? null;
20083
- const laeufe = new Map(collectPromptRuns(events).map((r) => [r.run_id, r]));
20084
- const herkunft = job ? herkunftFuer(job, laeufe, titelJeElement(events)) : null;
20085
- return {
20086
- job,
20087
- prompt_run_id: p.prompt_run_id,
20088
- item_id: p.item_id,
20089
- generation_batch_id: p.generation_batch_id,
20090
- bild: {
20091
- media_ref: letztes.media_ref,
20092
- position: letztes.position,
20318
+ const liste = jeLauf.get(p.prompt_run_id) ?? [];
20319
+ for (const einzeln of roh) {
20320
+ liste.push({
20321
+ bild: bildRefAus(einzeln, p.generation_batch_id),
20322
+ prompt_run_id: p.prompt_run_id,
20323
+ item_id: p.item_id,
20093
20324
  generation_batch_id: p.generation_batch_id,
20094
- ...istText9(letztes.asset_id) ? { asset_id: letztes.asset_id } : {},
20095
- ...istText9(letztes.mime_type) ? { mime_type: letztes.mime_type } : {},
20096
- model: letztes.model ?? null,
20097
- seed: letztes.seed ?? null,
20098
- width: letztes.width ?? null,
20099
- height: letztes.height ?? null
20100
- },
20101
- position: letztes.position + 1,
20102
- anzahl: bilder.length,
20103
- gespeichert_am: istText9(p.created_at) ? p.created_at : null,
20104
- execution_target_model_key: job?.execution_target_model_key ?? null,
20105
- herkunft
20106
- };
20325
+ job_id: job?.job_id ?? null,
20326
+ position_im_batch: einzeln.position + 1,
20327
+ anzahl_im_batch: roh.length,
20328
+ gespeichert_am: istText9(p.created_at) ? p.created_at : null,
20329
+ execution_target_model_key: job?.execution_target_model_key ?? null
20330
+ });
20331
+ }
20332
+ jeLauf.set(p.prompt_run_id, liste);
20333
+ juengsterLauf = p.prompt_run_id;
20107
20334
  }
20108
- return null;
20335
+ return { jeLauf, juengsterLauf };
20336
+ }
20337
+ function bilderDesLaufs(events, storeId, runId) {
20338
+ const jobs = collectImageJobs(events).filter((j) => j.store_id === storeId);
20339
+ return bilderJeLauf(events, jobs).jeLauf.get(runId) ?? [];
20340
+ }
20341
+ function letztesErzeugtesBild(events, storeId) {
20342
+ const jobs = collectImageJobs(events).filter((j) => j.store_id === storeId);
20343
+ const { jeLauf, juengsterLauf } = bilderJeLauf(events, jobs);
20344
+ if (!juengsterLauf) return null;
20345
+ const folge = jeLauf.get(juengsterLauf) ?? [];
20346
+ const letztes = folge[folge.length - 1];
20347
+ if (!letztes) return null;
20348
+ const laeufe = new Map(collectPromptRuns(events).map((r) => [r.run_id, r]));
20349
+ const job = letztes.job_id ? jobs.find((j) => j.job_id === letztes.job_id) ?? null : null;
20350
+ const herkunft = job ? herkunftFuer(job, laeufe, titelJeElement(events), jeLauf) : null;
20351
+ return {
20352
+ job,
20353
+ prompt_run_id: letztes.prompt_run_id,
20354
+ item_id: letztes.item_id,
20355
+ generation_batch_id: letztes.generation_batch_id,
20356
+ bild: letztes.bild,
20357
+ position: letztes.position_im_batch,
20358
+ anzahl: letztes.anzahl_im_batch,
20359
+ gespeichert_am: letztes.gespeichert_am,
20360
+ execution_target_model_key: letztes.execution_target_model_key,
20361
+ herkunft,
20362
+ folge
20363
+ };
20109
20364
  }
20110
20365
 
20111
20366
  // src/lib/szenen/imageJobPort.ts
@@ -20118,6 +20373,7 @@ function fehler4(error, aktion) {
20118
20373
  function createImageJobPort(options) {
20119
20374
  const { channel, imageGeneration, storeId, clientId } = options;
20120
20375
  const newClaimId = options.newClaimId ?? (() => crypto.randomUUID());
20376
+ const newJobId = options.newJobId ?? (() => crypto.randomUUID());
20121
20377
  const now = options.now ?? (() => /* @__PURE__ */ new Date());
20122
20378
  return {
20123
20379
  async listJobs(runId) {
@@ -20159,6 +20415,113 @@ function createImageJobPort(options) {
20159
20415
  };
20160
20416
  }
20161
20417
  },
20418
+ async requestMoreImages(input) {
20419
+ const { sourceJobId, imageCount } = input;
20420
+ let quelle;
20421
+ try {
20422
+ quelle = findImageJob(await channel.readEvents(), sourceJobId);
20423
+ } catch (error) {
20424
+ return {
20425
+ kind: "error",
20426
+ ...fehler4(error, `Der Ausgangsauftrag ${sourceJobId} konnte nicht gelesen werden`),
20427
+ retryable: true
20428
+ };
20429
+ }
20430
+ if (!quelle) {
20431
+ return {
20432
+ kind: "rejected",
20433
+ message: `Der Bildauftrag ${sourceJobId} steht nicht im autoritativen Log dieses Bestands. Ohne ihn gibt es keine Ausfuehrungsparameter, die uebernommen werden koennten.`
20434
+ };
20435
+ }
20436
+ if (quelle.store_id !== storeId) {
20437
+ return {
20438
+ kind: "rejected",
20439
+ message: `Der Bildauftrag ${sourceJobId} gilt fuer den Bestand "${quelle.store_id}", dieser Host arbeitet auf "${storeId}".`
20440
+ };
20441
+ }
20442
+ if (!Number.isInteger(imageCount) || imageCount < 1) {
20443
+ return {
20444
+ kind: "rejected",
20445
+ message: `Die Bildanzahl "${imageCount}" ist keine ganze Zahl >= 1.`
20446
+ };
20447
+ }
20448
+ const wahl = await imageGeneration.imageModelChoiceForRun(quelle.prompt_run_id);
20449
+ if (wahl.kind !== "ok") {
20450
+ return {
20451
+ kind: "error",
20452
+ message: `Die ausfuehrbaren Bildmodelle des Laufs ${quelle.prompt_run_id} waren nicht lesbar: ${wahl.message}`,
20453
+ retryable: wahl.kind === "error" ? wahl.retryable : true
20454
+ };
20455
+ }
20456
+ const gewaehlt = findExecutableImageModel(
20457
+ wahl.value,
20458
+ quelle.execution_target_model_key,
20459
+ quelle.execution_profile_id
20460
+ );
20461
+ if (!gewaehlt) {
20462
+ const auswahl = wahl.value.models.map((m) => m.selection_id);
20463
+ return {
20464
+ kind: "rejected",
20465
+ 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."}`)
20466
+ };
20467
+ }
20468
+ const befunde = checkImageModelParameters(gewaehlt, {
20469
+ imageCount,
20470
+ ...quelle.aspect_ratio ? { aspectRatio: quelle.aspect_ratio } : {}
20471
+ });
20472
+ if (befunde.length > 0) {
20473
+ return {
20474
+ kind: "rejected",
20475
+ message: `Der neue Auftrag passt nicht zu ${gewaehlt.display_name} (Profil ${gewaehlt.profile_id}, bis zu ${gewaehlt.max_images} Bilder): ` + befunde.join(" ")
20476
+ };
20477
+ }
20478
+ const jobId = input.jobId ?? newJobId();
20479
+ let vorhanden;
20480
+ try {
20481
+ vorhanden = findImageJob(await channel.readEvents(), jobId);
20482
+ } catch (error) {
20483
+ return {
20484
+ kind: "error",
20485
+ ...fehler4(error, `Der Bestand war vor dem Anlegen von ${jobId} nicht lesbar`),
20486
+ retryable: true
20487
+ };
20488
+ }
20489
+ if (vorhanden) {
20490
+ return {
20491
+ kind: "rejected",
20492
+ message: `Zur Auftragskennung ${jobId} gibt es bereits einen Auftrag (${vorhanden.status}). Ein Auftrag ist unveraenderlich.`
20493
+ };
20494
+ }
20495
+ const payload = {
20496
+ job_id: jobId,
20497
+ prompt_run_id: quelle.prompt_run_id,
20498
+ execution_target_model_key: gewaehlt.target_model_key,
20499
+ execution_profile_id: gewaehlt.profile_id,
20500
+ image_count: imageCount,
20501
+ ...quelle.aspect_ratio ? { aspect_ratio: quelle.aspect_ratio } : {},
20502
+ // **Nicht geraten und nicht erfunden.** Dieser Host kennt keinen
20503
+ // Nutzerschluessel — der Flow-Client hat keine Identitaet, nur einen
20504
+ // Bestand. Der Fortsetzungsauftrag traegt deshalb den Auftraggeber des
20505
+ // Auftrags, den er fortsetzt; ein ausgedachter Schluessel waere genau
20506
+ // der Fall, den Regel 6 verbietet.
20507
+ requested_by: quelle.requested_by,
20508
+ store_id: storeId,
20509
+ created_at: now().toISOString()
20510
+ };
20511
+ try {
20512
+ await channel.appendEvent(SZENEN_EVENT_TYPES.imageJobRequested, payload);
20513
+ } catch (error) {
20514
+ return {
20515
+ kind: "error",
20516
+ ...fehler4(error, `Der Bildauftrag ${jobId} konnte nicht angelegt werden`),
20517
+ retryable: true
20518
+ };
20519
+ }
20520
+ return {
20521
+ kind: "ok",
20522
+ job: { ...payload, status: "pending", claim_attempts: [] }
20523
+ };
20524
+ },
20162
20525
  async claimAndRun(jobId) {
20163
20526
  let letzterGrund = "";
20164
20527
  for (let versuch = 1; versuch <= ANSPRUCH_VERSUCHE; versuch += 1) {
@@ -20655,6 +21018,7 @@ export {
20655
21018
  istOffen,
20656
21019
  PROMPT_AUSZUG_ZEICHEN,
20657
21020
  selectImageJobs,
21021
+ bilderDesLaufs,
20658
21022
  letztesErzeugtesBild,
20659
21023
  createImageJobPort,
20660
21024
  createFlowSzenenViewPort