@rslsp1/fa-app-tools 10.0.0 → 10.1.3
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/{chunk-UDBGDCKI.mjs → chunk-5SYVLULG.mjs} +189 -131
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +751 -609
- package/dist/index.mjs +121 -37
- package/dist/szenen.d.mts +20 -0
- package/dist/szenen.d.ts +20 -0
- package/dist/szenen.js +189 -131
- package/dist/szenen.mjs +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -135,7 +135,7 @@ __export(index_exports, {
|
|
|
135
135
|
module.exports = __toCommonJS(index_exports);
|
|
136
136
|
|
|
137
137
|
// package.json
|
|
138
|
-
var version = "10.
|
|
138
|
+
var version = "10.1.3";
|
|
139
139
|
|
|
140
140
|
// src/hooks/useOnClickOutside.ts
|
|
141
141
|
var import_react = require("react");
|
|
@@ -174,7 +174,7 @@ function useKeyboardNavigation(history, currentResult, setCurrentResult) {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
// src/features/szenen/szenenViewStyles.generated.ts
|
|
177
|
-
var SZENEN_VIEW_STYLES = '/* ==========================================================================\n SZENEN VIEW\n ========================================================================== */\n\n.szenen-view {\n display: grid;\n gap: var(--space-md);\n width: 100%;\n min-width: 0;\n font-family: var(--font-sans);\n padding: var(--space-md);\n}\n\n@media (max-width: 760px) {\n .szenen-view {\n padding: var(--space-sm);\n gap: var(--space-sm);\n }\n}\n\n.szenen-view__intro {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n padding: clamp(0.6rem, 1.2vw, 0.9rem) clamp(0.75rem, 1.6vw, 1.1rem);\n color: var(--color-text-primary);\n background:\n radial-gradient(circle at 85% 15%, rgba(45, 212, 191, 0.16), transparent 34%),\n linear-gradient(135deg, var(--color-bg-surface), var(--color-bg-elevated));\n border: 1px solid var(--color-border);\n border-radius: var(--radius-lg);\n}\n\n.szenen-view__intro h2,\n.szenen-view__intro p {\n margin: 0;\n}\n\n.szenen-view__intro h2 {\n margin-bottom: 0.1rem;\n font-size: clamp(1rem, 1.9vw, 1.3rem);\n line-height: 1.2;\n}\n\n.szenen-view__intro > div > p:last-child {\n max-width: 44rem;\n color: var(--color-text-secondary);\n font-size: 0.82rem;\n line-height: 1.35;\n}\n\n.szenen-view__eyebrow {\n margin-bottom: 0.1rem !important;\n color: var(--color-accent-teal, #2dd4bf);\n font-size: 0.62rem;\n font-weight: 800;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n}\n\n.szenen-view__step {\n flex: none;\n padding: 0.25rem 0.6rem;\n color: var(--color-text-primary);\n background: rgba(45, 212, 191, 0.12);\n border: 1px solid rgba(45, 212, 191, 0.35);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n font-weight: 700;\n}\n\n.szenen-view__nav {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-view__nav button,\n.szenen-view button {\n min-height: 2.25rem;\n padding: 0.45rem 0.75rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-view button:hover:not(:disabled) {\n color: var(--color-text-primary);\n background: var(--color-bg-surface-hover);\n border-color: var(--color-border-hover);\n}\n\n.szenen-view button:focus-visible,\n.szenen-view input:focus-visible,\n.szenen-view select:focus-visible,\n.szenen-view textarea:focus-visible {\n outline: 2px solid var(--color-accent-blue);\n outline-offset: 2px;\n}\n\n.szenen-view button:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.szenen-view__nav button.is-active,\n.szenen-view__nav button[aria-selected="true"] {\n color: var(--color-text-primary);\n background: var(--color-accent-blue-dim);\n border-color: var(--color-accent-blue);\n}\n\n.szenen-view__area {\n min-width: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-view__capability-note,\n.szenen-area__lead {\n margin: 0;\n padding: var(--space-sm) var(--space-md);\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border-left: 3px solid var(--color-accent-purple);\n border-radius: var(--radius-sm);\n}\n\n.szenen-view__host-notes {\n color: var(--color-text-secondary);\n}\n\n.szenen-view__host-notes > summary {\n cursor: pointer;\n font-size: 0.8rem;\n}\n\n/* `box-sizing` steht hier und nicht beim Host: Die React-App bringt in\n * `master.css` einen globalen `* { box-sizing: border-box }` mit, Flow **nicht**\n * \u2014 dort ragte `width: 100%` plus Innenabstand um die Randbreite aus dem\n * Elternelement heraus und erzeugte auf ~390 px einen horizontalen Scroll. Die\n * gemeinsame Ansicht darf sich auf keinen Host-Reset verlassen. */\n.szenen-view input:not([type="checkbox"]),\n.szenen-view select,\n.szenen-view textarea {\n box-sizing: border-box;\n width: 100%;\n min-height: 2.25rem;\n padding: 0.5rem 0.65rem;\n color: var(--color-text-primary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n font: inherit;\n}\n\n.szenen-view textarea {\n min-height: 7rem;\n resize: vertical;\n font-family: var(--font-mono);\n font-size: 0.8rem;\n}\n\n.szenen-view label {\n display: grid;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.85rem;\n}\n\n.szenen-view code,\n.szenen-view pre {\n font-family: var(--font-mono);\n overflow-wrap: anywhere;\n}\n\n.szenen-view h3,\n.szenen-view h4,\n.szenen-view h5 {\n margin-top: 0;\n}\n\n.szenen-view ul,\n.szenen-view ol {\n padding-left: 1.25rem;\n}\n\n.szenen-elements,\n.szenen-detail,\n.szenen-scene,\n.szenen-setup,\n.szenen-erweitert,\n.szenen-prompts,\n.szenen-shot,\n.szenen-media,\n.szenen-runs {\n display: grid;\n gap: var(--space-md);\n min-width: 0;\n}\n\n/* Arbeitsoberflaeche: Liste links, Bearbeitung rechts.\n *\n * Breit stehen beide Spalten nebeneinander \u2014 die Auswahl bleibt sichtbar,\n * waehrend bearbeitet wird. Schmal rendert die Komponente nur eine der beiden;\n * hier steht deshalb keine Regel, die die andere versteckt. */\n@media (min-width: 761px) {\n .szenen-elements {\n grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);\n align-items: start;\n }\n\n .szenen-elements__master,\n .szenen-elements__detail {\n min-width: 0;\n }\n}\n\n.szenen-elements__master,\n.szenen-elements__detail {\n display: grid;\n gap: var(--space-sm);\n align-content: start;\n min-width: 0;\n}\n\n.szenen-elements__toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n}\n\n.szenen-elements__heading {\n margin: 0;\n font-size: 1.05rem;\n}\n\n.szenen-elements__scope {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n min-width: 0;\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-elements__scope > select {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.szenen-elements__new {\n flex: none;\n font-weight: 700;\n}\n\n.szenen-elements__create {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-accent-blue);\n border-radius: var(--radius-sm);\n}\n\n.szenen-elements__create label {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-elements__create-actions {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n}\n\n.szenen-elements__options > summary {\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n cursor: pointer;\n}\n\n.szenen-elements__back {\n justify-self: start;\n font-weight: 700;\n}\n\n.szenen-elements__empty-detail {\n margin: 0;\n color: var(--color-text-tertiary);\n}\n\n.szenen-elements__filter,\n.szenen-detail__actions,\n.szenen-runs__start,\n.szenen-scene-editor__add,\n.szenen-scene-editor__add-relation {\n display: flex;\n flex-wrap: wrap;\n align-items: end;\n gap: var(--space-sm);\n}\n\n.szenen-elements__filter > label,\n.szenen-runs__start > label,\n.szenen-scene-editor__add > input {\n flex: 1 1 16rem;\n}\n\n.szenen-elements__filter > label:has(input[type="checkbox"]) {\n display: flex;\n flex: 1 1 16rem;\n flex-direction: row;\n align-items: center;\n}\n\n.szenen-elements__filter input[type="checkbox"] {\n width: 1rem;\n height: 1rem;\n accent-color: var(--color-accent-blue);\n}\n\n.szenen-elements__list {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-elements__list > li > button {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs) var(--space-sm);\n width: 100%;\n height: 100%;\n text-align: left;\n}\n\n.szenen-elements__list > li > button > .szenen-elements__title {\n flex: 1 1 100%;\n}\n\n.szenen-elements__list > li > button.is-selected,\n.szenen-elements__list > li > button[aria-current="true"] {\n background: var(--color-accent-blue-dim);\n border-color: var(--color-accent-blue);\n}\n\n.szenen-elements__type {\n color: var(--color-accent-blue);\n font-size: 0.75rem;\n font-weight: 700;\n text-transform: uppercase;\n}\n\n.szenen-elements__title {\n color: var(--color-text-primary);\n font-size: 0.95rem;\n}\n\n.szenen-elements__subtype,\n.szenen-elements__retired {\n color: var(--color-text-secondary);\n font-size: 0.75rem;\n}\n\n.szenen-elements__retired {\n color: var(--color-warning);\n}\n\n.szenen-detail {\n margin-top: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n@media (min-width: 761px) {\n .szenen-detail {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n }\n}\n\n.szenen-detail > header,\n.szenen-detail__heads,\n.szenen-detail__draft,\n.szenen-capability {\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n/* .szenen-runs ist ein eigenst\xE4ndiger sichtbarer Bereich, beh\xE4lt Styling auf Desktop */\n@media (min-width: 761px) {\n .szenen-runs {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n }\n}\n\n@media (max-width: 760px) {\n .szenen-runs {\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n }\n}\n\n.szenen-detail__ids {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin-bottom: 0;\n color: var(--color-text-secondary);\n}\n\n.szenen-detail__heads ul,\n.szenen-scene-editor ul,\n.szenen-runs > ol,\n.szenen-capability > ul {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-detail__heads li,\n.szenen-scene-editor li,\n.szenen-runs > ol > li,\n.szenen-capability > ul > li {\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n\n/* Stufe 6.7.2 (D-126) \u2014 Advanced-/Debug-Fl\xE4che f\xFCr die rohe, schema-getriebene\n * Feldliste einer Szene. Gestrichelt wie die anderen \u201EAngebot statt\n * Auskunft"-Bereiche, aber ohne Akzentfarbe: sie ist kein Vorschlag, sondern\n * ein Eskalationsweg. */\n.szenen-detail__technical {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__technical > summary {\n margin-bottom: var(--space-sm);\n color: var(--color-text-secondary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-detail__technical[open] > summary {\n margin-bottom: var(--space-md);\n}\n\n.szenen-detail__field small,\n.szenen-capability small {\n color: var(--color-text-tertiary);\n}\n\n.szenen-detail__preview-errors,\n.szenen-detail__notice--error,\n.szenen-view [role="alert"] {\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-color: var(--color-accent-red);\n}\n\n.szenen-detail__notice {\n padding: var(--space-sm) var(--space-md);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__notice--success,\n.szenen-view [data-tone="success"] {\n color: #bbf7d0;\n background: var(--color-accent-green-dim);\n border-color: var(--color-accent-green);\n}\n\n.szenen-scene-editor {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: var(--space-md);\n margin-bottom: 0;\n}\n\n.szenen-scene-editor > section {\n min-width: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n/* \u2500\u2500 Stufe 6.7.2 (D-126): Node-/Relationskarten \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Nodes und Beziehungen sind `<details>` \u2014 dieselbe Interaktionssprache wie\n * \u201ERohantwort (Diagnose)" und \u201EBilder zu diesem Lauf" in RunsPanel.tsx. Kopf\n * (immer sichtbar) tr\xE4gt Inhalt zuerst, Technik zuletzt; Body (nur bei\n * ge\xF6ffneter Karte) tr\xE4gt die Bearbeitungscontrols. Padding wandert von der\n * `<li>` in Kopf/Body, damit die Karte keinen doppelten Rand bekommt. */\n.szenen-scene-editor li {\n padding: 0;\n overflow: hidden;\n}\n\n.szenen-node-karte__kopf,\n.szenen-relation-karte__kopf {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs) var(--space-sm);\n padding: var(--space-sm);\n cursor: pointer;\n list-style: none;\n}\n\n.szenen-node-karte__kopf::-webkit-details-marker,\n.szenen-relation-karte__kopf::-webkit-details-marker {\n display: none;\n}\n\n.szenen-node-karte__titel {\n flex: 1 1 12rem;\n min-width: 0;\n color: var(--color-text-primary);\n font-weight: 600;\n overflow-wrap: anywhere;\n}\n\n.szenen-node-karte__typ {\n flex: none;\n padding: 0.15rem 0.55rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n white-space: nowrap;\n}\n\n/* node_id klein, monospace, sekund\xE4r \u2014 sichtbar auch bei zugeklappter Karte,\n * aber optisch klar hinter Beschreibung und Typ. */\n.szenen-node-karte__meta {\n flex: 1 1 100%;\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n}\n\n.szenen-node-karte__meta code {\n font-size: inherit;\n}\n\n.szenen-node-karte__body,\n.szenen-relation-karte__body {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm) var(--space-sm) var(--space-sm);\n border-top: 1px solid var(--color-border);\n}\n\n/* \xA74 \u2014 Prim\xE4raktion und destruktive Aktion optisch getrennt: \u201ENode/Beziehung\n * entfernen" bekommt einen eigenen, warnenden Ton statt gleichgewichtig neben\n * der normalen Bearbeitung zu stehen. */\n.szenen-node-karte__entfernen,\n.szenen-relation-karte__entfernen {\n justify-self: start;\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-color: var(--color-accent-red);\n}\n\n.szenen-relation-karte__kopf {\n font-size: 0.9rem;\n}\n\n.szenen-relation-karte__kopf code {\n overflow-wrap: anywhere;\n}\n\n.szenen-relation-karte__pfeil {\n flex: none;\n color: var(--color-text-secondary);\n}\n\n/* Die Composer-\xD6ffner (\u201E+ Node hinzuf\xFCgen", \u201E+ Beziehung hinzuf\xFCgen") sind\n * Angebote, keine Ausk\xFCnfte \u2014 dasselbe gestrichelte Muster wie beim Folgelauf\n * und der Fehlerrettung (\xA73.3/\xA73.5). */\n.szenen-scene-editor__oeffnen {\n width: 100%;\n border-style: dashed;\n}\n\n.szenen-refpicker {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n margin: var(--space-sm) 0;\n padding: var(--space-sm) 0;\n border: none;\n border-radius: 0;\n}\n\n.szenen-refpicker legend {\n padding: 0 var(--space-xs);\n color: var(--color-text-secondary);\n font-weight: 600;\n}\n\n.szenen-capability > ul > li {\n display: grid;\n grid-template-columns: minmax(10rem, 1fr) auto minmax(14rem, 2fr);\n align-items: start;\n gap: var(--space-sm);\n}\n\n.szenen-capability__state {\n padding: 0.1rem 0.5rem;\n border-radius: var(--radius-full);\n font-size: 0.75rem;\n font-weight: 700;\n text-transform: uppercase;\n}\n\n.szenen-capability__state--yes {\n color: #bbf7d0;\n background: var(--color-accent-green-dim);\n}\n\n.szenen-capability__state--no {\n color: #fecaca;\n background: var(--color-accent-red-dim);\n}\n\n.szenen-capability__state--unknown {\n color: #fef08a;\n background: rgba(234, 179, 8, 0.2);\n}\n\n.szenen-runs > ol > li {\n display: grid;\n gap: var(--space-sm);\n}\n\n/* B1 \u2014 die geschlossene Runde. Vorher stand die `run_id` als erstes und war\n die einzige Information, die niemand liest; jetzt beantwortet die Kopfzeile\n welche Runde, ob sie durchging, mit welchem Modell und was ungef\xE4hr\n herauskam. */\n.szenen-runs__hinweis {\n margin: var(--space-xs) 0 0;\n color: var(--color-text-secondary);\n font-size: 0.78rem;\n}\n\n.szenen-runs__kopf {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-sm);\n width: 100%;\n padding: var(--space-xs) 0;\n text-align: left;\n color: var(--color-text-secondary);\n background: transparent;\n border: none;\n cursor: pointer;\n}\n\n.szenen-runs__marke {\n font-weight: 700;\n color: var(--color-text-primary);\n}\n\n.szenen-runs__status {\n padding: 0.05rem 0.4rem;\n font-size: 0.6875rem;\n font-weight: 600;\n text-transform: uppercase;\n border-radius: var(--radius-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n}\n\n.szenen-runs__status[data-status="succeeded"] {\n border-color: var(--color-success);\n}\n\n.szenen-runs__status[data-status="failed"] {\n border-color: var(--color-danger);\n}\n\n.szenen-runs__modell {\n font-size: 0.75rem;\n}\n\n/* Der Auszug ist die Inhaltsvorschau der Runde. Er bekommt eine eigene Zeile,\n damit er nicht mit Marke und Modell um dieselbe Zeile ringt. */\n.szenen-runs__auszug {\n flex: 1 1 100%;\n min-width: 0;\n color: var(--color-text-tertiary);\n font-size: 0.8125rem;\n}\n\n/* B2 \u2014 ein Fehler steht ausserhalb der aufklappbaren Fl\xE4che: Wer eine rote\n Runde sieht, soll nicht erst klicken m\xFCssen, um zu erfahren, woran sie\n gescheitert ist. */\n.szenen-runs__fehler {\n display: grid;\n gap: var(--space-xs);\n padding: var(--space-sm);\n border-radius: var(--radius-sm);\n}\n\n.szenen-runs__fehler p {\n margin: 0;\n}\n\n.szenen-runs__inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n}\n\n.szenen-runs__prompt > h5,\n.szenen-runs__sekundaer > h5 {\n margin: 0 0 var(--space-xs);\n}\n\n/* Im Kopf traegt die Zeile selbst den Abstand \u2014 sonst kaeme er doppelt. */\n.szenen-runs__promptkopf > h5 {\n margin: 0;\n}\n\n/* Ueberschrift und Kopierknopf in einer Zeile. Der Knopf gehoert an den Text,\n den er kopiert \u2014 nicht an den Rand der Runde, wo er neben dem falschen\n Abschnitt stuende. */\n.szenen-runs__promptkopf {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-sm);\n margin: 0 0 var(--space-xs);\n}\n\n/* Ein kompakter Knopf, kein zweiter Titel: Der Prompt ist die Hauptsache des\n Abschnitts, die Aktion daneben darf ihn nicht ueberstimmen. Rahmen und\n Hintergrund kommen aus `.szenen-view button` und bleiben \u2014 dieselbe Optik wie\n jeder andere Knopf der Ansicht; hier wird nur die Groesse zurueckgenommen.\n Zwei Klassen im Selektor, weil `.szenen-view button` sonst spezifischer ist. */\n.szenen-runs__promptkopf .szenen-runs__kopieren {\n min-height: 0;\n padding: 0.1rem 0.45rem;\n font-size: 0.72rem;\n}\n\n/* Regel 11: die Bestaetigung nennt die Zahl, nicht nur \u201EErfolgreich". */\n.szenen-runs__kopiert {\n color: var(--color-text-tertiary, var(--color-text-secondary));\n font-size: 0.72rem;\n}\n\n.szenen-runs__prompt > p {\n margin: 0;\n white-space: pre-wrap;\n}\n\n.szenen-runs__prompt--negativ > p,\n.szenen-runs__sekundaer > p {\n margin: 0;\n color: var(--color-text-secondary);\n white-space: pre-wrap;\n}\n\n.szenen-runs__mehr {\n justify-self: start;\n margin-top: var(--space-xs);\n padding: 0;\n color: var(--color-accent-teal, var(--color-text-secondary));\n background: transparent;\n border: none;\n font-size: 0.75rem;\n text-decoration: underline;\n cursor: pointer;\n}\n\n.szenen-runs > ol > li[data-status="succeeded"] {\n border-left: 3px solid var(--color-success);\n}\n\n.szenen-runs > ol > li[data-status="failed"] {\n border-left: 3px solid var(--color-danger);\n}\n\n.szenen-runs details pre {\n max-height: 20rem;\n overflow: auto;\n padding: var(--space-sm);\n white-space: pre-wrap;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border-radius: var(--radius-sm);\n}\n\n.szenen-media > section {\n display: grid;\n gap: var(--space-sm);\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-media__lookup,\n.szenen-media__attach {\n display: flex;\n flex-wrap: wrap;\n align-items: end;\n gap: var(--space-sm);\n}\n\n.szenen-media__lookup > label,\n.szenen-media__attach > label:first-child {\n flex: 1 1 18rem;\n}\n\n.szenen-media__list {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-media__list > li {\n display: grid;\n grid-template-columns: minmax(12rem, 1fr) auto auto auto;\n align-items: center;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n@media (max-width: 760px) {\n .szenen-view {\n gap: var(--space-sm);\n }\n\n /* C1 \u2014 auf 390 px bleiben drei Kacheln je Reihe gross genug zum Erkennen\n und klein genug, dass vier Bilder eine Reihe sind und keine Seite. */\n .szenen-bildgalerie {\n grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));\n }\n\n /* Der Viewer bekommt den ganzen Bildschirm; die Bl\xE4tterkn\xF6pfe bleiben am\n Rand, wo der Daumen sie erreicht. */\n .szenen-bildviewer {\n padding: var(--space-xs);\n }\n\n .szenen-bildviewer__blaettern {\n width: 2.25rem;\n height: 4rem;\n }\n\n .szenen-view__intro {\n align-items: start;\n flex-direction: column;\n gap: var(--space-xs);\n padding: var(--space-sm);\n }\n\n .szenen-view__intro h2 {\n font-size: 1.05rem;\n }\n\n .szenen-view__intro > div > p:last-child {\n font-size: 0.78rem;\n }\n\n .szenen-view__nav {\n position: sticky;\n top: 0;\n z-index: 2;\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: var(--space-xs);\n padding: var(--space-xs);\n }\n\n .szenen-view__nav button {\n width: 100%;\n min-width: 0;\n min-height: 2.5rem;\n padding: 0.35rem 0.5rem;\n }\n\n\n .szenen-scene-editor {\n grid-template-columns: 1fr;\n }\n\n .szenen-capability > ul > li {\n grid-template-columns: 1fr;\n }\n\n .szenen-derive,\n .szenen-elements,\n .szenen-scene,\n .szenen-setup,\n .szenen-shot,\n .szenen-media,\n .szenen-runs {\n gap: var(--space-sm);\n }\n\n .szenen-elements__filter {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: center;\n gap: var(--space-sm);\n }\n\n .szenen-elements__filter > label {\n width: 100%;\n min-width: 0;\n flex: none;\n }\n\n .szenen-elements__filter > label:has(input[type="checkbox"]) {\n flex: none;\n grid-column: 1 / -1;\n }\n\n /* Schmal ist die Liste eine Spalte, und die Auswahl fuehrt weiter \u2014 der\n * Eintrag ist deshalb ein Ziel fuer den Finger, keine Kachel. */\n .szenen-elements__list {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .szenen-elements__list > li > button {\n min-height: 2.75rem;\n }\n\n .szenen-elements__back {\n justify-self: stretch;\n min-height: 2.5rem;\n }\n\n .szenen-detail__actions,\n .szenen-runs__start,\n .szenen-scene-editor__add,\n .szenen-scene-editor__add-relation {\n align-items: stretch;\n flex-direction: column;\n }\n\n .szenen-runs__start > *,\n .szenen-scene-editor__add > *,\n .szenen-scene-editor__add-relation > * {\n width: 100%;\n flex: none;\n }\n}\n\n/* \u2500\u2500 Stufe 6.5: Rekonstruktion \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Bereich zeigt drei Dinge nebeneinander: den Vorschlag, das nicht\n * Ableitbare und die Widerspr\xFCche. Die Diagnosen sind deshalb bewusst nicht\n * kleiner gesetzt als der Vorschlag \u2014 wer sie \xFCbersieht, \xFCbernimmt Geratenes.\n */\n\n.szenen-derive {\n display: grid;\n gap: var(--space-md);\n}\n\n.szenen-form {\n display: grid;\n gap: var(--space-sm);\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-field {\n display: grid;\n gap: var(--space-xs);\n}\n\n.szenen-field > span {\n color: var(--color-text-secondary);\n font-size: 0.875rem;\n}\n\n.szenen-error {\n padding: var(--space-sm) var(--space-md);\n color: var(--color-danger);\n background: var(--color-accent-red-dim);\n border: 1px solid var(--color-accent-red);\n border-radius: var(--radius-sm);\n}\n\n.szenen-empty {\n padding: var(--space-md);\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-diagnose {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-warning);\n border-radius: var(--radius-sm);\n}\n\n/* Ein Widerspruch ist der schwerste der drei Befunde: dort steht eine\n * Entscheidung aus, die das System bewusst nicht getroffen hat (D-74). */\n.szenen-diagnose--konflikt {\n border-left-color: var(--color-danger);\n}\n\n.szenen-diagnose > h4 {\n margin: 0 0 var(--space-sm);\n font-size: 0.9375rem;\n}\n\n.szenen-diagnose ul {\n display: grid;\n gap: var(--space-xs);\n margin: 0;\n padding-left: var(--space-md);\n}\n\n.szenen-proposal {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-proposal > h4 {\n margin: 0 0 var(--space-sm);\n font-family: var(--font-mono);\n font-size: 0.9375rem;\n}\n\n.szenen-proposal__payload {\n max-height: 18rem;\n margin: 0 0 var(--space-sm);\n padding: var(--space-sm);\n overflow: auto;\n /* Ein erzeugter Prompt ist ein einziger langer Satz \u2014 ohne Umbruch\n verschwindet er nach rechts aus dem Bild. JSON-Payloads bringen ihre\n eigenen Zeilenumbrueche mit und leiden nicht darunter. */\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n font-family: var(--font-mono);\n font-size: 0.8125rem;\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-proposal__done {\n margin: var(--space-sm) 0 0;\n color: var(--color-success);\n}\n\n/* Die Belege eines Laufs: Rohantwort und Systemprompt. Zum Lesen und Kopieren,\n nicht zum Bedienen \u2014 deshalb voll markierbar und in Monospace. */\n.szenen-beleg__text {\n width: 100%;\n margin: var(--space-sm) 0 0;\n padding: var(--space-sm);\n font-family: var(--font-mono);\n font-size: 0.8125rem;\n line-height: 1.5;\n white-space: pre;\n background: var(--color-bg-app);\n color: var(--color-text);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n resize: vertical;\n}\n\n/* Der Folgelauf (D-87): abgesetzt, damit er nicht wie Teil des Ergebnisses\n aussieht \u2014 er ist eine Frage, keine Auskunft. */\n.szenen-folgelauf {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-folgelauf > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-run__herkunft {\n margin: 0 0 var(--space-sm);\n font-size: 0.875rem;\n color: var(--color-text-muted, inherit);\n}\n\n/* Widerspricht das Modell, ist das das Wichtigste auf der Seite. */\n.szenen-diagnose--antwort {\n border-left: 3px solid var(--color-accent, var(--color-border));\n}\n\n/* Der Bildw\xE4hler (D-92) \u2014 Browser und Inspector, dem Labs-Tab nachgebildet,\n aber mit dem CSS dieser Ansicht: eine gemeinsame Komponente \xFCber zwei\n Dom\xE4nengrenzen w\xE4re eine Kopplung, die keiner gewollt hat.\n\n Der Review vom 2026-08-22 (R12.1/R12.2) hat den Ma\xDFstab gesetzt: Der Bestand\n ist ein **Browser**, kein Seiteninhalt. Vierzig Werke d\xFCrfen die Szenenseite\n nicht meterlang machen, und zw\xF6lf Varianten eines Werkes keine zw\xF6lf\n Bildschirmseiten. Deshalb steht hier alles unter zwei Regeln \u2014 begrenzte\n Fl\xE4che mit eigenem Scroll, und Kacheln statt Karten. */\n\n/* \u2500\u2500 Ebene 1: der Browser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl {\n /* Dieselbe Begr\xFCndung wie beim Inspector: Als Grid-Element w\xFCrde der\n W\xE4hler sonst so breit wie sein breitester Inhalt. */\n min-width: 0;\n}\n\n.szenen-bildwahl__browser {\n /* Die eigene Scrollfl\xE4che ist der ganze Punkt: Der Bestand w\xE4chst nach\n innen, nicht nach unten. Dieselbe Gr\xF6\xDFenordnung wie `max-h-[300px]` im\n Labs-Browser. */\n max-height: 19rem;\n overflow-y: auto;\n overscroll-behavior: contain;\n margin-top: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__raster {\n display: grid;\n /* Vier Spalten auf schmalen Displays \u2014 der gemeldete Screenshot zeigte eine\n einzige Spalte gro\xDFer Karten. Breiter wird automatisch dichter. */\n grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildwahl__karte {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n overflow: hidden;\n background: var(--color-bg-surface);\n text-align: left;\n cursor: pointer;\n}\n\n/* Das fokussierte Werk ist klar markiert \u2014 sonst wei\xDF niemand, wessen Galerie\n der Inspector unten zeigt. */\n.szenen-bildwahl__karte.is-fokus {\n border-color: var(--color-accent, var(--color-text-primary));\n box-shadow: 0 0 0 2px var(--color-accent, var(--color-text-primary));\n}\n\n/* \u2500\u2500 Die Kachel selbst \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/* Der Platz steht, bevor das Bild da ist \u2014 sonst springt das Raster, sobald\n die erste Vorschau eintrifft. */\n.szenen-bildkachel {\n display: block;\n width: 100%;\n aspect-ratio: 1;\n overflow: hidden;\n background: var(--color-bg-app);\n}\n\n.szenen-bildkachel img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n\n.szenen-bildkachel__leer {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n background: linear-gradient(135deg, var(--color-bg-app), var(--color-bg-surface));\n}\n\n/* R12.3: Eine fehlende Vorschau bleibt kachelgro\xDF. Vorher fiel die Karte auf\n ihren Prompttext zur\xFCck \u2014 und ein Prompt hat im echten Bestand mehrere\n hundert Zeichen. */\n.szenen-bildkachel__zeichen {\n font-size: 0.875rem;\n font-weight: 700;\n color: var(--color-text-secondary);\n opacity: 0.6;\n}\n\n/* Wie viele Varianten das Werk hat \u2014 klein und \xFCberlagernd, nie als\n dominanter Text. */\n.szenen-bildwahl__anzahl {\n position: absolute;\n top: 0.15rem;\n right: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n font-weight: 700;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* Eine Zeile, dann Schluss. Der Prompt eines Werkes ist oft mehrere hundert\n Zeichen lang und w\xE4re als Prim\xE4rdarstellung genau die Textw\xFCste, gegen die\n der Review antritt. */\n.szenen-bildwahl__kacheltitel {\n display: block;\n padding: 0.15rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.3;\n color: var(--color-text-secondary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 Ebene 2: der Inspector \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl__inspector {\n /* Ohne `min-width: 0` gewinnt der Inhalt: Der W\xE4hler ist Element eines\n Grids (`.szenen-form`), und dort ist `min-width: auto` die Vorgabe. Der\n Streifen unten ist rund 1000 px breit \u2014 gemessen am 2026-08-22 im echten\n Browser zog er die Auswahl von 374 px auf 1088 px auf und schob die\n ganze Seite waagerecht heraus, statt selbst zu scrollen. */\n min-width: 0;\n margin-top: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n /* Sichtbar abgesetzt vom Browser: zwei Ebenen, nicht eine lange Liste. */\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-accent, var(--color-border));\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__inspektorkopf {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bildwahl__werktitel {\n font-size: 0.75rem;\n font-weight: 600;\n color: var(--color-text-primary);\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.szenen-bildwahl__zurueck {\n flex: 0 0 auto;\n font-size: 0.6875rem;\n padding: 0.15rem 0.5rem;\n}\n\n/* Der Streifen: waagerecht scrollend, feste kleine Kacheln \u2014 zw\xF6lf Varianten\n bleiben ein \xDCberblick statt zw\xF6lf Bildschirmseiten. */\n.szenen-bildwahl__streifen {\n /* Die Breite kommt vom Elter, nie vom Inhalt \u2014 sonst scrollt hier nichts. */\n max-width: 100%;\n display: flex;\n gap: var(--space-sm);\n margin: 0;\n padding: 0 0 0.25rem;\n list-style: none;\n overflow-x: auto;\n overscroll-behavior-x: contain;\n}\n\n.szenen-bildwahl__streifen > li {\n flex: 0 0 auto;\n width: 5rem;\n}\n\n.szenen-bildwahl__variante {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n overflow: hidden;\n background: var(--color-bg-app);\n cursor: pointer;\n}\n\n.szenen-bildwahl__variante.is-gewaehlt {\n border-color: var(--color-accent, var(--color-text-primary));\n box-shadow: 0 0 0 2px var(--color-accent, var(--color-text-primary));\n}\n\n.szenen-bildwahl__nummer {\n position: absolute;\n bottom: 0.15rem;\n left: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border-radius: var(--radius-sm);\n}\n\n/* \u2500\u2500 Das Gew\xE4hlte \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl__gewaehlt {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: var(--space-sm) 0 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildwahl__gewaehlt li {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.25rem 0.5rem;\n font-size: 0.8125rem;\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n max-width: 100%;\n}\n\n/* Das gew\xE4hlte Bild wird best\xE4tigt, nicht nur benannt: eine kleine Vorschau\n neben dem Titel. */\n.szenen-bildwahl__gewaehlt .szenen-bildkachel {\n width: 2.25rem;\n flex: 0 0 auto;\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__gewaehlt .szenen-bildwahl__kacheltitel {\n min-width: 0;\n max-width: 12rem;\n font-size: 0.75rem;\n}\n\n.szenen-beleg__kopf {\n display: flex;\n align-items: center;\n gap: var(--space-sm);\n margin-top: var(--space-sm);\n}\n\n.szenen-diagnose > details + details {\n margin-top: var(--space-sm);\n}\n\n.szenen-diagnose > details > summary {\n cursor: pointer;\n font-weight: 600;\n}\n\n/* \u2500\u2500 Die Fehlerrettung (Stufe 6.6, A5) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Sie steht im Fehlerblock und muss trotzdem als Werkzeug lesbar sein, nicht\n * als weitere Fehlermeldung. Deshalb abgesetzt wie der Folgelauf \u2014 und aus\n * demselben Grund: Beides sind Angebote, keine Ausk\xFCnfte.\n *\n * Der wichtigste optische Punkt ist die Trennung von Original und\n * Arbeitskopie. Das Original bekommt den ruhigen, \u201Efesten" Hintergrund der\n * Belege; die Kopie sieht aus wie ein normales Eingabefeld, weil sie eines\n * ist. */\n.szenen-rettung {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-rettung > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-rettung textarea[readonly] {\n background: var(--color-bg-surface);\n color: var(--color-text-secondary);\n font-family: var(--font-mono, monospace);\n font-size: 0.8125rem;\n}\n\n.szenen-rettung textarea:not([readonly]) {\n font-family: var(--font-mono, monospace);\n font-size: 0.8125rem;\n}\n\n.szenen-rettung__aktionen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: var(--space-sm) 0;\n}\n\n/* Ein Befund tr\xE4gt Code, Bezug und Meldung. Der Code steht klein und\n * monospaced davor: Er ist die Kennung, nicht die Aussage. */\n.szenen-diagnose__liste {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding-left: var(--space-md);\n}\n\n.szenen-diagnose__liste code {\n font-size: 0.8125rem;\n}\n\n.szenen-diagnose__liste > li[data-severity=\'warning\'] {\n color: var(--color-text-secondary);\n}\n\n/* \u2500\u2500 Die Draft-Kette (Stufe 6.6, C4) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Hauptweg. Er steht nach den Einzel\xFCbernahmen und muss als das erkennbar\n * sein, was er ist: nicht eine weitere Option, sondern der k\xFCrzere Weg zum\n * selben Ziel. Deshalb durchgezogener Rahmen statt gestricheltem \u2014 er ist\n * Angebot **und** Empfehlung. */\n.szenen-kette {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-accent, var(--color-border));\n border-radius: var(--radius-sm);\n}\n\n.szenen-kette > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-kette ul {\n display: grid;\n gap: var(--space-xs);\n margin: 0 0 var(--space-sm);\n padding-left: var(--space-md);\n}\n\n.szenen-kette code {\n font-size: 0.8125rem;\n}\n\n/* Ein Hinweis, der eine Bedingung erklaert \u2014 nicht rot, weil er kein Fehler\n ist, aber abgesetzt, weil er eine Folge hat. */\n.szenen-detail__hint {\n margin: var(--space-sm) 0 0;\n padding: var(--space-sm);\n font-size: 0.875rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border-left: 3px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* \u2500\u2500 Stufe 6.7: Bilder am Prompt-Lauf (H0/H1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Bereich tr\xE4gt zwei Dinge, die man nie verwechseln darf: den **Eingang**\n * (woraus der Prompt entstand) und den **Ausgang** (was daraus wurde). Sie\n * bekommen deshalb verschiedene Akzentkanten statt nur verschiedener\n * \xDCberschriften \u2014 wer schnell liest, sieht die Trennung, bevor er sie liest.\n */\n.szenen-bilder {\n display: grid;\n gap: var(--space-md);\n margin-top: var(--space-sm);\n}\n\n.szenen-bilder__eingang,\n.szenen-bilder__ausgang {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__eingang {\n border-left: 3px solid var(--color-accent-purple, var(--color-border));\n}\n\n.szenen-bilder__ausgang {\n border-left: 3px solid var(--color-accent-teal, var(--color-border));\n}\n\n.szenen-bilder h5 {\n margin: 0;\n}\n\n.szenen-bilder__ids {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: 0;\n align-items: baseline;\n color: var(--color-text-secondary);\n font-size: 0.875rem;\n}\n\n.szenen-bilder__eingangsliste,\n.szenen-bilder__batchliste,\n.szenen-bilder__auftraege {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bilder__eingangsliste > li,\n.szenen-bilder__batchliste > li,\n.szenen-bilder__auftraege > li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n font-size: 0.8125rem;\n}\n\n/* Ein unterbrochener Auftrag ist der Fall, f\xFCr den der Auftragssatz existiert.\n Er wird deshalb markiert \u2014 nicht rot, denn er ist kein Fehler, sondern eine\n offene Entscheidung. */\n.szenen-bilder__auftraege > li[data-status="started"] {\n border-left: 3px solid var(--color-warning);\n}\n\n.szenen-bilder__auftraege > li[data-status="succeeded"] {\n border-left: 3px solid var(--color-success);\n}\n\n.szenen-bilder__auftraege > li[data-status="partial"] {\n border-left: 3px solid var(--color-accent-blue, var(--color-border));\n}\n\n.szenen-bilder__auftraege > li[data-status="failed"] {\n border-left: 3px solid var(--color-danger);\n}\n\n/* Das Eingangsbild benutzt seit C3 dieselbe Kachel wie W\xE4hler und Galerie.\n Sie ist von Haus aus so breit wie ihr Platz \u2014 in dieser Zeile bekommt sie\n deshalb eine feste Gr\xF6sse, sonst sch\xF6be sie die Metadaten heraus. */\n.szenen-bilder__eingangsliste .szenen-bildkachel {\n flex: 0 0 auto;\n width: 4rem;\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__batch {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__batchkopf {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--space-sm);\n}\n\n.szenen-bilder__herkunft {\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n}\n\n/* Review-Befund \u2014 ein transienter Bildfehler ist wiederholbar, kein\n endg\xFCltiges \u201Efehlt". Dezent wie ein Angebot, nicht wie ein Fehlerzustand. */\n.szenen-bilder__retry {\n justify-self: start;\n padding: 0.15rem 0.5rem;\n font-size: 0.75rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n/* C4 \u2014 die Provenienz vollst\xE4ndig, aber sekund\xE4r. */\n.szenen-bilder__technik > summary {\n color: var(--color-text-secondary);\n font-size: 0.75rem;\n cursor: pointer;\n}\n\n.szenen-bilder__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n font-size: 0.75rem;\n color: var(--color-text-secondary);\n}\n\n/* C5 \u2014 der Verlauf verdichtet, seine Ausreisser nicht. */\n.szenen-bilder__verlauf > summary {\n color: var(--color-text-secondary);\n font-size: 0.8125rem;\n cursor: pointer;\n}\n\n.szenen-bilder__verlauf[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bilder__auffaellig {\n margin: 0;\n padding: var(--space-sm);\n border-radius: var(--radius-sm);\n font-size: 0.8125rem;\n}\n\n.szenen-bilder__auftragskennung {\n flex-basis: 100%;\n color: var(--color-text-tertiary);\n font-size: 0.6875rem;\n}\n\n/* \u2500\u2500 C1/C2 \u2014 Galerie und grosse Ansicht \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/* Das Raster w\xE4chst mit dem Platz: auf 390 px drei bis vier Kacheln je Reihe,\n auf dem Desktop entsprechend mehr. Vorher war jedes Bild eine Zeile aus\n Daumennagel plus vier Metadatenfeldern \u2014 vier Bilder ergaben vier Textzeilen\n und das Protokoll war die Hauptsache. */\n.szenen-bildgalerie {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr));\n gap: var(--space-sm);\n min-width: 0;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildgalerie__kachel {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n overflow: hidden;\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-bildgalerie__kachel:hover,\n.szenen-bildgalerie__kachel:focus-visible {\n border-color: var(--color-accent-teal, var(--color-text-primary));\n}\n\n.szenen-bildgalerie__nummer {\n position: absolute;\n bottom: 0.15rem;\n left: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border-radius: var(--radius-sm);\n}\n\n/* Die grosse Ansicht liegt \xFCber allem und bringt ihren eigenen Grund mit \u2014\n `position: fixed` bezieht sich im Flow-Canvas auf dessen Rahmen, in React auf\n das Fenster; in beiden F\xE4llen genau das Gew\xFCnschte. */\n.szenen-bildviewer {\n position: fixed;\n inset: 0;\n z-index: 1000;\n display: grid;\n grid-template-rows: auto 1fr auto;\n gap: var(--space-sm);\n padding: var(--space-sm);\n /* Deckend, nicht get\xF6nt: Bei 0.92 schimmerte der Laufbereich durch und man\n las Prompttext quer \xFCber dem Bild \u2014 im Browser gemessen. */\n background: #000;\n}\n\n/* Titel, Z\xE4hlung, Schliessen \u2014 drei Spalten, damit ein langer Werktitel die\n Z\xE4hlung nicht in die n\xE4chste Zeile und \xFCber das Bild schiebt. */\n.szenen-bildviewer__kopf {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto auto;\n align-items: center;\n gap: var(--space-sm);\n color: var(--color-text-primary);\n}\n\n.szenen-bildviewer__titel {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 600;\n}\n\n.szenen-bildviewer__zaehler {\n color: var(--color-text-secondary);\n font-size: 0.8125rem;\n}\n\n.szenen-bildviewer__buehne {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--space-sm);\n min-height: 0;\n min-width: 0;\n}\n\n/* Der Rahmen nimmt den Platz, der zwischen den Bl\xE4tterkn\xF6pfen \xFCbrig ist \u2014\n `min-width: 0`, damit er auch wirklich schrumpft statt sie hinauszuschieben. */\n.szenen-bildviewer__rahmen {\n flex: 1 1 auto;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n min-height: 0;\n height: 100%;\n}\n\n.szenen-bildviewer__bild {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n}\n\n.szenen-bildviewer__leer {\n color: var(--color-text-secondary);\n text-align: center;\n}\n\n/* Die Bl\xE4tterkn\xF6pfe sind daumengross und liegen an den R\xE4ndern \u2014 auf einem\n Telefon ist der Rand erreichbar, die Bildmitte nicht. */\n.szenen-bildviewer__blaettern {\n flex: 0 0 auto;\n width: 2.75rem;\n height: 3.5rem;\n font-size: 1.75rem;\n line-height: 1;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-bildviewer__details {\n max-height: 8rem;\n overflow-y: auto;\n color: var(--color-text-secondary);\n}\n\n/* Die Aktionen stehen abgesetzt: Sie sind Angebote, keine Ausk\xFCnfte \u2014 dasselbe\n Muster wie beim Folgelauf und bei der Fehlerrettung. */\n/* D \u2014 die Aktionen stehen abgesetzt und standardm\xE4ssig kompakt: Sie sind\n Angebote, keine Ausk\xFCnfte. Vorher hielten beide Formulare dauerhaft offen und\n machten aus jedem Lauf zwei zus\xE4tzliche Bildschirmseiten. */\n.szenen-bilder__aktion {\n min-width: 0;\n padding: var(--space-sm) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__aktion > summary {\n color: var(--color-text-primary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-bilder__aktion[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bilder__aktion-inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding-bottom: var(--space-sm);\n}\n\n.szenen-bilder__aktion h5 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-bilder__aktion ol {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding-left: var(--space-md);\n font-size: 0.8125rem;\n}\n\n/* Regelwerk-UX Schritt 16 \u2014 der ZIP-Sammelimport.\n *\n * \xA78: Der gemeldete Einsatz ist mobil. Deshalb **ein** Seedfeld statt einer\n * gestapelten Eingabe je Bild, und die Zuordnung Bild -> Seed als umbrechende\n * Zeile statt als Tabelle: Eine Tabelle waere auf 390 px abgeschnitten, und\n * abgeschnitten heisst hier, dass der Nutzer die Zuordnung nicht mehr pruefen\n * kann \u2014 genau die Pruefung, wegen der die Vorschau existiert. */\n.szenen-bilder__zip {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n}\n\n.szenen-bilder__zipkopf {\n margin: 0;\n overflow-wrap: anywhere;\n}\n\n.szenen-bilder__zipvorlagen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n margin: 0;\n}\n\n/* Eine laengere Liste bleibt scrollbar, statt die Sektion und damit den\n * Import-Knopf aus dem Blick zu schieben (\xA78, letzter Punkt). */\n.szenen-bilder__zipliste {\n max-height: 16rem;\n overflow-y: auto;\n}\n\n.szenen-bilder__zipliste > li {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-bilder__zipliste > li > span:first-child {\n overflow-wrap: anywhere;\n}\n\n.szenen-bilder__zipliste > li > span:last-child {\n color: var(--color-text-secondary);\n white-space: nowrap;\n}\n\n/* ==========================================================================\n STUFE 6.10 \u2014 ELEMENTEDITOR UND VERSIONSNAVIGATION\n ========================================================================== */\n\n/* \xA74.2 \u2014 vier Sachverhalte, vier Marken. Die Marken stehen in einer eigenen\n * Spalte, damit ANGEZEIGT + BEVORZUGT nebeneinander lesbar bleiben, statt sich\n * durch den Text zu schieben. */\n.szenen-detail__heads li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n}\n\n.szenen-version__titel {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n min-width: 0;\n margin-right: auto;\n}\n\n/* V17 \u2014 Kennungen bleiben vollst\xE4ndig sichtbar, aber optisch sekund\xE4r. */\n.szenen-version__id,\n.szenen-ref__id,\n.szenen-detail__field-key {\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n overflow-wrap: anywhere;\n}\n\n.szenen-version__marken {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n.szenen-version__marken > span {\n padding: 0.1rem 0.5rem;\n color: var(--color-text-primary);\n background: rgba(45, 212, 191, 0.12);\n border: 1px solid rgba(45, 212, 191, 0.35);\n border-radius: var(--radius-full);\n font-size: 0.7rem;\n font-weight: 800;\n letter-spacing: 0.08em;\n}\n\n.szenen-version--angezeigt {\n border-color: var(--color-accent-teal, #2dd4bf) !important;\n}\n\n/* V9 \u2014 der Basiswechsel ist eine Entscheidung, keine Nebenwirkung. */\n.szenen-detail__confirm {\n display: grid;\n gap: var(--space-sm);\n margin: var(--space-md) 0;\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-accent-amber, #f59e0b);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__confirm p {\n margin: 0;\n}\n\n/* Untergruppen von Objektfeldern (`prompt_render_settings`, `overrides`,\n `blender_render_settings`) \u2014 seit A1/A2 auf- und zuklappbar. Eine optionale\n Gruppe startet zu: Blender und `overrides` f\xFCllten auf einem 390-px-Display\n sonst mehrere Bildschirmseiten, obwohl die meisten Shots sie nie anfassen. */\n.szenen-detail__group {\n min-width: 0;\n margin: var(--space-md) 0;\n padding: var(--space-sm) var(--space-md);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__group > summary {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-detail__group[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n/* A2 \u2014 dass eine zugeklappte Gruppe schon Werte tr\xE4gt, muss man sehen, ohne\n sie zu \xF6ffnen. Sonst \xFCbersieht man eine gesetzte Blender-Ausgabe genau so\n lange, bis sie im Ergebnis auftaucht. */\n.szenen-detail__group-status {\n padding: 0.05rem 0.4rem;\n font-size: 0.6875rem;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--color-text-tertiary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__group[data-konfiguriert="ja"] > summary .szenen-detail__group-status {\n color: var(--color-text-primary);\n border-color: var(--color-accent-teal, var(--color-border));\n}\n\n.szenen-detail__group-inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding-bottom: var(--space-sm);\n}\n\n/* A3 \u2014 die Kernaussage steht am Feld, die Begr\xFCndung dahinter. Nicht als\n Tooltip: Auf einem Telefon gibt es kein Hover, und was dort unerreichbar ist,\n ist gestrichen \u2014 nur ohne dass es jemand entschieden h\xE4tte. */\n.szenen-detail__warum > summary {\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n cursor: pointer;\n}\n\n.szenen-detail__warum > small {\n display: block;\n padding-top: var(--space-xs);\n color: var(--color-text-tertiary);\n}\n\n.szenen-detail__fieldrow {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n margin-bottom: var(--space-md);\n}\n\n/* A4 \u2014 der technische Feldname bleibt lesbar, tritt aber hinter das fachliche\n Label zur\xFCck. */\n.szenen-detail__field-label {\n color: var(--color-text-primary);\n}\n\n.szenen-detail__field {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-detail__field > span {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n}\n\n/* Mehrfachauswahl und Listeneditoren. */\n.szenen-feld__mehrfach,\n.szenen-feld__liste,\n.szenen-feld__reflist,\n.szenen-feld__ref,\n.szenen-feld__zielmodell,\n.szenen-bilder__modellwahl,\n.szenen-feld__kamera {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n/* \u201ENeues Modell hinzufuegen" im Importdialog \u2014 dieselben Abstaende wie jedes\n andere Formular dieser Ansicht, damit es kein Fremdkoerper wird. */\n.szenen-feld__zielmodell-neu {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n padding: var(--space-sm);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-feld__zielmodell-neu-aktionen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n.szenen-feld__mehrfach > label,\n.szenen-feld__null {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.85rem;\n}\n\n.szenen-feld__liste-zeile {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n.szenen-feld__reflist ol {\n display: grid;\n gap: var(--space-xs);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-feld__reflist li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs);\n padding: var(--space-xs) var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* \xA76.1 \u2014 Titel und Typ zuerst, Kennungen daneben. */\n.szenen-ref__anzeige {\n display: inline-flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-ref__typ {\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-ref__warn {\n padding: 0.1rem 0.5rem;\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n font-weight: 700;\n}\n\n/* \xA76.5 \u2014 abgeleitete Angaben, ausdr\xFCcklich nur Anzeige. */\n.szenen-detail__derived {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__derived dl {\n display: grid;\n grid-template-columns: minmax(10rem, auto) 1fr;\n gap: var(--space-xs) var(--space-sm);\n margin: 0;\n}\n\n.szenen-detail__derived dt {\n color: var(--color-text-secondary);\n}\n\n.szenen-detail__derived dd {\n margin: 0;\n}\n\n.szenen-feld__abgeleitet {\n margin: 0;\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n}\n\n/* Bereich `Prompts` \u2014 die herkunftsuebergreifende Sicht (P1-A).\n *\n * Die Eintraege selbst rendert `RundenEintrag` und tragen damit die\n * `szenen-runs__*`-Regeln. Hier stehen nur Rahmen und Kennzeichen; eine zweite\n * Laufdarstellung gibt es nicht, also braucht sie auch kein zweites Aussehen. */\n.szenen-prompts__toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n}\n\n.szenen-prompts__heading {\n margin: 0;\n font-size: 1.05rem;\n margin-right: auto;\n}\n\n.szenen-view__fokus {\n display: flex;\n align-items: center;\n gap: var(--space-sm);\n margin-bottom: var(--space-sm);\n}\n\n.szenen-prompts__fokus {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n margin-bottom: var(--space-sm);\n padding: var(--space-xs) var(--space-sm);\n border: 1px solid var(--color-accent, #0d9488);\n border-radius: var(--radius-sm, 4px);\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-prompts__filter {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-prompts__eintrag {\n list-style: none;\n margin-bottom: var(--space-md);\n}\n\n.szenen-prompts__lauf {\n padding-left: 0;\n}\n\n.szenen-prompts__herkunft {\n margin: 0 0 var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.78rem;\n}\n\n.szenen-prompts__hinweis {\n font-style: italic;\n}\n\n.szenen-prompts__ausgeblendet {\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n/* ==========================================================================\n ZIP-MODELLVORSCHLAG (modaler Best\xE4tigungsdialog)\n ========================================================================== */\n\n/* Wie die grosse Bildansicht: eigener Grund, `position: fixed`, damit der\n Dialog im Flow-Canvas und in React gleichermassen \xFCber allem liegt. */\n.szenen-zipmodell {\n position: fixed;\n inset: 0;\n z-index: 1100;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--space-md);\n background: rgba(0, 0, 0, 0.72);\n}\n\n.szenen-zipmodell__karte {\n display: grid;\n gap: var(--space-sm);\n width: min(34rem, 100%);\n max-height: 100%;\n overflow-y: auto;\n padding: var(--space-md);\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-lg);\n}\n\n.szenen-zipmodell__titel {\n margin: 0;\n font-size: 1rem;\n}\n\n.szenen-zipmodell__quelle,\n.szenen-zipmodell__treffer,\n.szenen-zipmodell__vertrauen,\n.szenen-zipmodell__fallback {\n margin: 0;\n font-size: 0.85rem;\n}\n\n.szenen-zipmodell__quelle,\n.szenen-zipmodell__vertrauen {\n color: var(--color-text-secondary);\n}\n\n/* Ein schwacher oder mehrdeutiger Treffer muss sich vom sicheren unterscheiden,\n sonst ist die Warnung nur ein weiterer Satz. */\n.szenen-zipmodell__warnung {\n margin: 0;\n padding: var(--space-xs) var(--space-sm);\n color: var(--color-text-primary);\n background: rgba(234, 179, 8, 0.14);\n border-left: 3px solid #eab308;\n border-radius: var(--radius-sm);\n font-size: 0.85rem;\n}\n\n.szenen-zipmodell__alternativen {\n display: grid;\n gap: 0.2rem;\n margin: 0;\n padding: var(--space-xs) var(--space-sm);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n font-size: 0.85rem;\n}\n\n.szenen-zipmodell__alternativen > label {\n display: flex;\n align-items: center;\n gap: 0.4rem;\n}\n\n.szenen-zipmodell__aktionen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n/* ==========================================================================\n MEHR LADEN \u2014 der gemeinsame Fuss jeder seitenweise geladenen Liste\n ========================================================================== */\n\n.szenen-mehr-laden {\n display: grid;\n gap: var(--space-xs);\n justify-items: start;\n padding: var(--space-xs) 0;\n}\n\n.szenen-mehr-laden__button {\n padding: var(--space-xs) var(--space-sm);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n background: var(--color-bg-surface);\n color: var(--color-text-primary);\n font-size: 0.85rem;\n cursor: pointer;\n}\n\n.szenen-mehr-laden__button:disabled {\n cursor: progress;\n opacity: 0.6;\n}\n\n.szenen-mehr-laden__fehler {\n margin: 0;\n color: var(--color-danger, #f87171);\n font-size: 0.8rem;\n}\n\n/* ==========================================================================\n PROMPTKARTE \u2014 Tags, Vorschau und Bildzahl\n (Kette `szenen-prompt-anzeige-bilder-tags`)\n\n Der Zuschnitt ist der der Library-Karte (`.library-item` + der absolut\n gesetzte Bildstreifen in `LibraryTab`): Textteil links, Deckbild rechts am\n Rand ueber die ganze Kartenhoehe, Bildzahl als Plakette darauf. Dieselben\n Werte (7rem Streifenbreite, linke Trennlinie, Plakette unten rechts), nur\n hier statt in Tailwind-Klassen \u2014 die Szenen-Ansicht teilt dieses Stylesheet\n byteweise mit Flow, und Flow hat kein Tailwind.\n ========================================================================== */\n\n.szenen-promptkarte {\n position: relative;\n overflow: hidden;\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n padding: var(--space-sm);\n}\n\n/* Nur wo ein Bild liegt, wird Platz dafuer freigehalten. Ohne Bild nutzt der\n Textteil die ganze Breite \u2014 keine leere Spalte. */\n.szenen-promptkarte--mitbild .szenen-promptkarte__inhalt {\n padding-right: calc(7rem + var(--space-sm));\n}\n\n.szenen-promptkarte__bild {\n position: absolute;\n top: 0;\n right: 0;\n width: 7rem;\n height: 100%;\n /* Hoechstens so hoch wie breit \u2014 der Streifen bleibt eine Kachel.\n `height: 100%` allein band ihn an die **Kartenhoehe**, und die ist in der\n aufgeklappten Runde die Hoehe des ganzen Prompts: Das Deckbild sprang\n dann als mehrere hundert Pixel hoher, auf Streifenbreite beschnittener\n Balken an den rechten Rand. Geschlossen aendert die Grenze nichts \u2014 dort\n ist die Karte niedriger als 7rem. */\n max-height: 7rem;\n border-left: 1px solid var(--color-border);\n background: #000;\n}\n\n/* Die gemeinsame Kachel bringt `width: 100%` und `aspect-ratio: 1` mit \u2014 hier\n fuellt sie stattdessen den Streifen, genau wie das Library-Deckbild. */\n.szenen-promptkarte__bild .szenen-bildkachel {\n width: 100%;\n height: 100%;\n aspect-ratio: auto;\n}\n\n.szenen-promptkarte__bildzahl {\n position: absolute;\n right: 0.25rem;\n bottom: 0.25rem;\n padding: 0.1rem 0.35rem;\n border-radius: var(--radius-sm);\n background: rgba(0, 0, 0, 0.6);\n color: #fff;\n font-size: 0.62rem;\n font-weight: 700;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n/* Ein Kasten je Karte, nicht zwei.\n *\n * `.szenen-view button` (master.css) gibt **jedem** Knopf der Ansicht Rahmen\n * und Hintergrund und schlaegt dabei `.szenen-runs__kopf` (eine Klasse gegen\n * Klasse plus Element). In der Laufhistorie ist das richtig: Dort ist der Kopf\n * der Eintrag. In der Promptkarte ist die Karte der Eintrag \u2014 ein zweiter\n * Kasten darin waere die doppelte Umrandung, die die Library nicht hat.\n * Der Hover braucht eine eigene Zeile, weil die Regel dort spezifischer ist. */\n.szenen-promptkarte .szenen-runs__kopf,\n.szenen-promptkarte .szenen-runs__kopf:hover:not(:disabled) {\n min-height: 0;\n padding: var(--space-xs) 0;\n background: transparent;\n border: none;\n}\n\n/* Dieselbe Karte in der Elementliste.\n *\n * Dort ist der Auswahlknopf die Karte \u2014 er traegt Rahmen, Hintergrund und die\n * Auswahlmarkierung. Der Bildstreifen liegt deshalb **darueber** statt darin:\n * Ein `<button>` nimmt nur Phrasing Content auf, ein Streifen-`div` darin\n * waere ungueltiges Markup. Damit der rechte Kartenrand trotzdem auswaehlbar\n * bleibt, laesst der Streifen Klicks durch. */\n.szenen-elementkarte {\n position: relative;\n}\n\n.szenen-elementkarte--mitbild > button {\n padding-right: calc(7rem + var(--space-sm));\n}\n\n.szenen-elementkarte > .szenen-promptkarte__bild {\n border-radius: 0 var(--radius-sm) var(--radius-sm) 0;\n}\n\n.szenen-promptkarte__bild--durchklickbar {\n pointer-events: none;\n}\n\n/* Null Bilder: die Zahl, keine Flaeche. */\n.szenen-promptkarte__ohnebild {\n margin: 0 0 var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.72rem;\n}\n\n/* Die Tagliste bricht um. Auf einem Telefon ist das der Unterschied zwischen\n einer kompakten Karte und einer Zeile, die seitlich herauslaeuft. */\n.szenen-tagliste {\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n align-items: center;\n margin: 0 0 var(--space-xs);\n padding: 0;\n list-style: none;\n}\n\n.szenen-tag {\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n padding: 0.05rem 0.4rem;\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n background: var(--color-bg-app);\n font-size: 0.72rem;\n line-height: 1.5;\n white-space: nowrap;\n}\n\n/* Herkunft sichtbar, aber ohne zweite Bedeutungsebene: Ein Prompt-Tag steht\n voll, ein reiner Library-Tag zurueckgenommen. Die Karte soll schnell lesbar\n sein, nicht zwei Farbcodes lehren. */\n.szenen-tag[data-herkunft="library"] {\n border-style: dashed;\n opacity: 0.85;\n}\n\n.szenen-tag--rest {\n border-style: none;\n opacity: 0.7;\n}\n\n.szenen-tag__weg {\n border: none;\n background: none;\n padding: 0;\n color: var(--color-text-secondary, inherit);\n font-size: 0.85rem;\n line-height: 1;\n cursor: pointer;\n}\n\n.szenen-tag__weg:disabled {\n cursor: progress;\n opacity: 0.5;\n}\n\n.szenen-taggeber {\n display: grid;\n gap: var(--space-xs);\n padding: var(--space-xs);\n margin-bottom: var(--space-xs);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-taggeber__zeile {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs);\n margin: 0;\n}\n\n.szenen-taggeber__label {\n font-size: 0.72rem;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n opacity: 0.75;\n}\n\n.szenen-taggeber__leer,\n.szenen-taggeber__hinweis {\n font-size: 0.72rem;\n opacity: 0.75;\n}\n\n.szenen-taggeber input[type="text"] {\n flex: 1 1 8rem;\n min-width: 0;\n padding: var(--space-xs);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n background: var(--color-bg-surface);\n color: var(--color-text-primary);\n font-size: 0.8rem;\n}\n\n/* ==========================================================================\n BILDAUFTR\xC4GE \u2014 die bestandsweite Arbeitsqueue\n Kette `szenen-bildauftraege-arbeitsqueue`, Schritt 01.\n ========================================================================== */\n\n.szenen-bildauftraege {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n}\n\n.szenen-bildauftraege__steuerung,\n.szenen-bildauftraege__filter,\n.szenen-bildauftraege__aktionen,\n.szenen-bildauftraege__nachladen,\n.szenen-bildauftraege__blaettern {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs);\n}\n\n/* Der manuelle Refresh \u2014 Schritt 03 \xA72. Er steht oben und sichtbar, weil er\n die einzige Stelle ist, an der dieser Bereich nachlaedt: Es gibt keinen\n Timer mehr, den er ersetzen koennte. */\n.szenen-bildauftraege__nachladen {\n justify-content: flex-start;\n}\n\n.szenen-bildauftraege__nachladen span {\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n/* Die Blaetterleiste der Bildkontrolle \u2014 Schritt 03 \xA73.1. */\n.szenen-bildauftraege__blaettern {\n margin: 0;\n}\n\n/* \u201EWeitere Bilder" \u2014 Schritt 03 \xA74. Eingerueckt, weil es zu genau einer\n Auftragszeile gehoert und kein eigener Bereich ist. */\n.szenen-bildauftraege__weitere {\n display: grid;\n gap: var(--space-xs);\n margin-top: var(--space-xs);\n padding: var(--space-xs) var(--space-sm);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-bildauftraege__weitere p {\n margin: 0;\n}\n\n/* Die Vorschau steht oben und beh\xE4lt ihren Platz, auch solange noch kein Bild\n vorliegt \u2014 sie ist die Kontrollfl\xE4che des zweiten Browserfensters (\xA75). */\n.szenen-bildauftraege__letztes {\n display: grid;\n gap: var(--space-xs);\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-lg);\n}\n\n.szenen-bildauftraege__letztes h3 {\n margin: 0;\n font-size: 0.95rem;\n}\n\n.szenen-bildauftraege__letztes .szenen-bildkachel {\n width: min(100%, 22rem);\n height: auto;\n}\n\n.szenen-bildauftraege__lauf {\n padding: var(--space-xs) var(--space-sm);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-bildauftraege__lauf p {\n margin: 0.1rem 0;\n}\n\n.szenen-bildauftraege__liste {\n display: grid;\n gap: var(--space-xs);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildauftraege__zeile {\n display: grid;\n gap: 0.25rem;\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-bildauftraege__kopf,\n.szenen-bildauftraege__prompt {\n margin: 0;\n}\n\n.szenen-bildauftraege__meta {\n display: flex;\n flex-wrap: wrap;\n gap: 0.15rem var(--space-sm);\n margin: 0;\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-bildauftraege__prompt {\n color: var(--color-text-secondary);\n font-size: 0.85rem;\n}\n';
|
|
177
|
+
var SZENEN_VIEW_STYLES = '/* ==========================================================================\n SZENEN VIEW\n ========================================================================== */\n\n.szenen-view {\n display: grid;\n gap: var(--space-md);\n width: 100%;\n min-width: 0;\n font-family: var(--font-sans);\n padding: var(--space-md);\n}\n\n@media (max-width: 760px) {\n .szenen-view {\n padding: var(--space-sm);\n gap: var(--space-sm);\n }\n}\n\n.szenen-view__intro {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n padding: clamp(0.6rem, 1.2vw, 0.9rem) clamp(0.75rem, 1.6vw, 1.1rem);\n color: var(--color-text-primary);\n background:\n radial-gradient(circle at 85% 15%, rgba(45, 212, 191, 0.16), transparent 34%),\n linear-gradient(135deg, var(--color-bg-surface), var(--color-bg-elevated));\n border: 1px solid var(--color-border);\n border-radius: var(--radius-lg);\n}\n\n.szenen-view__intro h2,\n.szenen-view__intro p {\n margin: 0;\n}\n\n.szenen-view__intro h2 {\n margin-bottom: 0.1rem;\n font-size: clamp(1rem, 1.9vw, 1.3rem);\n line-height: 1.2;\n}\n\n.szenen-view__intro > div > p:last-child {\n max-width: 44rem;\n color: var(--color-text-secondary);\n font-size: 0.82rem;\n line-height: 1.35;\n}\n\n.szenen-view__eyebrow {\n margin-bottom: 0.1rem !important;\n color: var(--color-accent-teal, #2dd4bf);\n font-size: 0.62rem;\n font-weight: 800;\n letter-spacing: 0.12em;\n text-transform: uppercase;\n}\n\n.szenen-view__step {\n flex: none;\n padding: 0.25rem 0.6rem;\n color: var(--color-text-primary);\n background: rgba(45, 212, 191, 0.12);\n border: 1px solid rgba(45, 212, 191, 0.35);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n font-weight: 700;\n}\n\n.szenen-view__nav {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-view__nav button,\n.szenen-view button {\n min-height: 2.25rem;\n padding: 0.45rem 0.75rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-view button:hover:not(:disabled) {\n color: var(--color-text-primary);\n background: var(--color-bg-surface-hover);\n border-color: var(--color-border-hover);\n}\n\n.szenen-view button:focus-visible,\n.szenen-view input:focus-visible,\n.szenen-view select:focus-visible,\n.szenen-view textarea:focus-visible {\n outline: 2px solid var(--color-accent-blue);\n outline-offset: 2px;\n}\n\n.szenen-view button:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n}\n\n.szenen-view__nav button.is-active,\n.szenen-view__nav button[aria-selected="true"] {\n color: var(--color-text-primary);\n background: var(--color-accent-blue-dim);\n border-color: var(--color-accent-blue);\n}\n\n.szenen-view__area {\n min-width: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-view__capability-note,\n.szenen-area__lead {\n margin: 0;\n padding: var(--space-sm) var(--space-md);\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border-left: 3px solid var(--color-accent-purple);\n border-radius: var(--radius-sm);\n}\n\n.szenen-view__host-notes {\n color: var(--color-text-secondary);\n}\n\n.szenen-view__host-notes > summary {\n cursor: pointer;\n font-size: 0.8rem;\n}\n\n/* `box-sizing` steht hier und nicht beim Host: Die React-App bringt in\n * `master.css` einen globalen `* { box-sizing: border-box }` mit, Flow **nicht**\n * \u2014 dort ragte `width: 100%` plus Innenabstand um die Randbreite aus dem\n * Elternelement heraus und erzeugte auf ~390 px einen horizontalen Scroll. Die\n * gemeinsame Ansicht darf sich auf keinen Host-Reset verlassen. */\n.szenen-view input:not([type="checkbox"]),\n.szenen-view select,\n.szenen-view textarea {\n box-sizing: border-box;\n width: 100%;\n min-height: 2.25rem;\n padding: 0.5rem 0.65rem;\n color: var(--color-text-primary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n font: inherit;\n}\n\n.szenen-view textarea {\n min-height: 7rem;\n resize: vertical;\n font-family: var(--font-mono);\n font-size: 0.8rem;\n}\n\n.szenen-view label {\n display: grid;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.85rem;\n}\n\n.szenen-view code,\n.szenen-view pre {\n font-family: var(--font-mono);\n overflow-wrap: anywhere;\n}\n\n.szenen-view h3,\n.szenen-view h4,\n.szenen-view h5 {\n margin-top: 0;\n}\n\n.szenen-view ul,\n.szenen-view ol {\n padding-left: 1.25rem;\n}\n\n.szenen-elements,\n.szenen-detail,\n.szenen-scene,\n.szenen-setup,\n.szenen-erweitert,\n.szenen-prompts,\n.szenen-shot,\n.szenen-media,\n.szenen-runs {\n display: grid;\n gap: var(--space-md);\n min-width: 0;\n}\n\n/* Arbeitsoberflaeche: Liste links, Bearbeitung rechts.\n *\n * Breit stehen beide Spalten nebeneinander \u2014 die Auswahl bleibt sichtbar,\n * waehrend bearbeitet wird. Schmal rendert die Komponente nur eine der beiden;\n * hier steht deshalb keine Regel, die die andere versteckt. */\n@media (min-width: 761px) {\n .szenen-elements {\n grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);\n align-items: start;\n }\n\n .szenen-elements__master,\n .szenen-elements__detail {\n min-width: 0;\n }\n}\n\n.szenen-elements__master,\n.szenen-elements__detail {\n display: grid;\n gap: var(--space-sm);\n align-content: start;\n min-width: 0;\n}\n\n.szenen-elements__toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n}\n\n.szenen-elements__heading {\n margin: 0;\n font-size: 1.05rem;\n}\n\n.szenen-elements__scope {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n min-width: 0;\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-elements__scope > select {\n flex: 1 1 auto;\n min-width: 0;\n}\n\n.szenen-elements__new {\n flex: none;\n font-weight: 700;\n}\n\n.szenen-elements__create {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-accent-blue);\n border-radius: var(--radius-sm);\n}\n\n.szenen-elements__create label {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-elements__create-actions {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n}\n\n.szenen-elements__options > summary {\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n cursor: pointer;\n}\n\n.szenen-elements__back {\n justify-self: start;\n font-weight: 700;\n}\n\n.szenen-elements__empty-detail {\n margin: 0;\n color: var(--color-text-tertiary);\n}\n\n.szenen-elements__filter,\n.szenen-detail__actions,\n.szenen-runs__start,\n.szenen-scene-editor__add,\n.szenen-scene-editor__add-relation {\n display: flex;\n flex-wrap: wrap;\n align-items: end;\n gap: var(--space-sm);\n}\n\n.szenen-elements__filter > label,\n.szenen-runs__start > label,\n.szenen-scene-editor__add > input {\n flex: 1 1 16rem;\n}\n\n.szenen-elements__filter > label:has(input[type="checkbox"]) {\n display: flex;\n flex: 1 1 16rem;\n flex-direction: row;\n align-items: center;\n}\n\n.szenen-elements__filter input[type="checkbox"] {\n width: 1rem;\n height: 1rem;\n accent-color: var(--color-accent-blue);\n}\n\n.szenen-elements__list {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-elements__list > li > button {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs) var(--space-sm);\n width: 100%;\n height: 100%;\n text-align: left;\n}\n\n.szenen-elements__list > li > button > .szenen-elements__title {\n flex: 1 1 100%;\n}\n\n.szenen-elements__list > li > button.is-selected,\n.szenen-elements__list > li > button[aria-current="true"] {\n background: var(--color-accent-blue-dim);\n border-color: var(--color-accent-blue);\n}\n\n.szenen-elements__type {\n color: var(--color-accent-blue);\n font-size: 0.75rem;\n font-weight: 700;\n text-transform: uppercase;\n}\n\n.szenen-elements__title {\n color: var(--color-text-primary);\n font-size: 0.95rem;\n}\n\n.szenen-elements__subtype,\n.szenen-elements__retired {\n color: var(--color-text-secondary);\n font-size: 0.75rem;\n}\n\n.szenen-elements__retired {\n color: var(--color-warning);\n}\n\n.szenen-detail {\n margin-top: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n@media (min-width: 761px) {\n .szenen-detail {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n }\n}\n\n.szenen-detail > header,\n.szenen-detail__heads,\n.szenen-detail__draft,\n.szenen-capability {\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n/* .szenen-runs ist ein eigenst\xE4ndiger sichtbarer Bereich, beh\xE4lt Styling auf Desktop */\n@media (min-width: 761px) {\n .szenen-runs {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n }\n}\n\n@media (max-width: 760px) {\n .szenen-runs {\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n }\n}\n\n.szenen-detail__ids {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin-bottom: 0;\n color: var(--color-text-secondary);\n}\n\n.szenen-detail__heads ul,\n.szenen-scene-editor ul,\n.szenen-runs > ol,\n.szenen-capability > ul {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-detail__heads li,\n.szenen-scene-editor li,\n.szenen-runs > ol > li,\n.szenen-capability > ul > li {\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n\n/* Stufe 6.7.2 (D-126) \u2014 Advanced-/Debug-Fl\xE4che f\xFCr die rohe, schema-getriebene\n * Feldliste einer Szene. Gestrichelt wie die anderen \u201EAngebot statt\n * Auskunft"-Bereiche, aber ohne Akzentfarbe: sie ist kein Vorschlag, sondern\n * ein Eskalationsweg. */\n.szenen-detail__technical {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__technical > summary {\n margin-bottom: var(--space-sm);\n color: var(--color-text-secondary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-detail__technical[open] > summary {\n margin-bottom: var(--space-md);\n}\n\n.szenen-detail__field small,\n.szenen-capability small {\n color: var(--color-text-tertiary);\n}\n\n.szenen-detail__preview-errors,\n.szenen-detail__notice--error,\n.szenen-view [role="alert"] {\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-color: var(--color-accent-red);\n}\n\n.szenen-detail__notice {\n padding: var(--space-sm) var(--space-md);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__notice--success,\n.szenen-view [data-tone="success"] {\n color: #bbf7d0;\n background: var(--color-accent-green-dim);\n border-color: var(--color-accent-green);\n}\n\n.szenen-scene-editor {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: var(--space-md);\n margin-bottom: 0;\n}\n\n.szenen-scene-editor > section {\n min-width: 0;\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n/* \u2500\u2500 Stufe 6.7.2 (D-126): Node-/Relationskarten \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Nodes und Beziehungen sind `<details>` \u2014 dieselbe Interaktionssprache wie\n * \u201ERohantwort (Diagnose)" und \u201EBilder zu diesem Lauf" in RunsPanel.tsx. Kopf\n * (immer sichtbar) tr\xE4gt Inhalt zuerst, Technik zuletzt; Body (nur bei\n * ge\xF6ffneter Karte) tr\xE4gt die Bearbeitungscontrols. Padding wandert von der\n * `<li>` in Kopf/Body, damit die Karte keinen doppelten Rand bekommt. */\n.szenen-scene-editor li {\n padding: 0;\n overflow: hidden;\n}\n\n.szenen-node-karte__kopf,\n.szenen-relation-karte__kopf {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs) var(--space-sm);\n padding: var(--space-sm);\n cursor: pointer;\n list-style: none;\n}\n\n.szenen-node-karte__kopf::-webkit-details-marker,\n.szenen-relation-karte__kopf::-webkit-details-marker {\n display: none;\n}\n\n.szenen-node-karte__titel {\n flex: 1 1 12rem;\n min-width: 0;\n color: var(--color-text-primary);\n font-weight: 600;\n overflow-wrap: anywhere;\n}\n\n.szenen-node-karte__typ {\n flex: none;\n padding: 0.15rem 0.55rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n white-space: nowrap;\n}\n\n/* node_id klein, monospace, sekund\xE4r \u2014 sichtbar auch bei zugeklappter Karte,\n * aber optisch klar hinter Beschreibung und Typ. */\n.szenen-node-karte__meta {\n flex: 1 1 100%;\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n}\n\n.szenen-node-karte__meta code {\n font-size: inherit;\n}\n\n.szenen-node-karte__body,\n.szenen-relation-karte__body {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm) var(--space-sm) var(--space-sm);\n border-top: 1px solid var(--color-border);\n}\n\n/* \xA74 \u2014 Prim\xE4raktion und destruktive Aktion optisch getrennt: \u201ENode/Beziehung\n * entfernen" bekommt einen eigenen, warnenden Ton statt gleichgewichtig neben\n * der normalen Bearbeitung zu stehen. */\n.szenen-node-karte__entfernen,\n.szenen-relation-karte__entfernen {\n justify-self: start;\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-color: var(--color-accent-red);\n}\n\n.szenen-relation-karte__kopf {\n font-size: 0.9rem;\n}\n\n.szenen-relation-karte__kopf code {\n overflow-wrap: anywhere;\n}\n\n.szenen-relation-karte__pfeil {\n flex: none;\n color: var(--color-text-secondary);\n}\n\n/* Die Composer-\xD6ffner (\u201E+ Node hinzuf\xFCgen", \u201E+ Beziehung hinzuf\xFCgen") sind\n * Angebote, keine Ausk\xFCnfte \u2014 dasselbe gestrichelte Muster wie beim Folgelauf\n * und der Fehlerrettung (\xA73.3/\xA73.5). */\n.szenen-scene-editor__oeffnen {\n width: 100%;\n border-style: dashed;\n}\n\n.szenen-refpicker {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n margin: var(--space-sm) 0;\n padding: var(--space-sm) 0;\n border: none;\n border-radius: 0;\n}\n\n.szenen-refpicker legend {\n padding: 0 var(--space-xs);\n color: var(--color-text-secondary);\n font-weight: 600;\n}\n\n.szenen-capability > ul > li {\n display: grid;\n grid-template-columns: minmax(10rem, 1fr) auto minmax(14rem, 2fr);\n align-items: start;\n gap: var(--space-sm);\n}\n\n.szenen-capability__state {\n padding: 0.1rem 0.5rem;\n border-radius: var(--radius-full);\n font-size: 0.75rem;\n font-weight: 700;\n text-transform: uppercase;\n}\n\n.szenen-capability__state--yes {\n color: #bbf7d0;\n background: var(--color-accent-green-dim);\n}\n\n.szenen-capability__state--no {\n color: #fecaca;\n background: var(--color-accent-red-dim);\n}\n\n.szenen-capability__state--unknown {\n color: #fef08a;\n background: rgba(234, 179, 8, 0.2);\n}\n\n.szenen-runs > ol > li {\n display: grid;\n gap: var(--space-sm);\n}\n\n/* B1 \u2014 die geschlossene Runde. Vorher stand die `run_id` als erstes und war\n die einzige Information, die niemand liest; jetzt beantwortet die Kopfzeile\n welche Runde, ob sie durchging, mit welchem Modell und was ungef\xE4hr\n herauskam. */\n.szenen-runs__hinweis {\n margin: var(--space-xs) 0 0;\n color: var(--color-text-secondary);\n font-size: 0.78rem;\n}\n\n.szenen-runs__kopf {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-sm);\n width: 100%;\n padding: var(--space-xs) 0;\n text-align: left;\n color: var(--color-text-secondary);\n background: transparent;\n border: none;\n cursor: pointer;\n}\n\n.szenen-runs__marke {\n font-weight: 700;\n color: var(--color-text-primary);\n}\n\n.szenen-runs__status {\n padding: 0.05rem 0.4rem;\n font-size: 0.6875rem;\n font-weight: 600;\n text-transform: uppercase;\n border-radius: var(--radius-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n}\n\n.szenen-runs__status[data-status="succeeded"] {\n border-color: var(--color-success);\n}\n\n.szenen-runs__status[data-status="failed"] {\n border-color: var(--color-danger);\n}\n\n.szenen-runs__modell {\n font-size: 0.75rem;\n}\n\n/* Der Auszug ist die Inhaltsvorschau der Runde. Er bekommt eine eigene Zeile,\n damit er nicht mit Marke und Modell um dieselbe Zeile ringt. */\n.szenen-runs__auszug {\n flex: 1 1 100%;\n min-width: 0;\n color: var(--color-text-tertiary);\n font-size: 0.8125rem;\n}\n\n/* B2 \u2014 ein Fehler steht ausserhalb der aufklappbaren Fl\xE4che: Wer eine rote\n Runde sieht, soll nicht erst klicken m\xFCssen, um zu erfahren, woran sie\n gescheitert ist. */\n.szenen-runs__fehler {\n display: grid;\n gap: var(--space-xs);\n padding: var(--space-sm);\n border-radius: var(--radius-sm);\n}\n\n.szenen-runs__fehler p {\n margin: 0;\n}\n\n.szenen-runs__inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n}\n\n.szenen-runs__prompt > h5,\n.szenen-runs__sekundaer > h5 {\n margin: 0 0 var(--space-xs);\n}\n\n/* Im Kopf traegt die Zeile selbst den Abstand \u2014 sonst kaeme er doppelt. */\n.szenen-runs__promptkopf > h5 {\n margin: 0;\n}\n\n/* Ueberschrift und Kopierknopf in einer Zeile. Der Knopf gehoert an den Text,\n den er kopiert \u2014 nicht an den Rand der Runde, wo er neben dem falschen\n Abschnitt stuende. */\n.szenen-runs__promptkopf {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-sm);\n margin: 0 0 var(--space-xs);\n}\n\n/* Ein kompakter Knopf, kein zweiter Titel: Der Prompt ist die Hauptsache des\n Abschnitts, die Aktion daneben darf ihn nicht ueberstimmen. Rahmen und\n Hintergrund kommen aus `.szenen-view button` und bleiben \u2014 dieselbe Optik wie\n jeder andere Knopf der Ansicht; hier wird nur die Groesse zurueckgenommen.\n Zwei Klassen im Selektor, weil `.szenen-view button` sonst spezifischer ist. */\n.szenen-runs__promptkopf .szenen-runs__kopieren {\n min-height: 0;\n padding: 0.1rem 0.45rem;\n font-size: 0.72rem;\n}\n\n/* Regel 11: die Bestaetigung nennt die Zahl, nicht nur \u201EErfolgreich". */\n.szenen-runs__kopiert {\n color: var(--color-text-tertiary, var(--color-text-secondary));\n font-size: 0.72rem;\n}\n\n.szenen-runs__prompt > p {\n margin: 0;\n white-space: pre-wrap;\n}\n\n.szenen-runs__prompt--negativ > p,\n.szenen-runs__sekundaer > p {\n margin: 0;\n color: var(--color-text-secondary);\n white-space: pre-wrap;\n}\n\n.szenen-runs__mehr {\n justify-self: start;\n margin-top: var(--space-xs);\n padding: 0;\n color: var(--color-accent-teal, var(--color-text-secondary));\n background: transparent;\n border: none;\n font-size: 0.75rem;\n text-decoration: underline;\n cursor: pointer;\n}\n\n.szenen-runs > ol > li[data-status="succeeded"] {\n border-left: 3px solid var(--color-success);\n}\n\n.szenen-runs > ol > li[data-status="failed"] {\n border-left: 3px solid var(--color-danger);\n}\n\n.szenen-runs details pre {\n max-height: 20rem;\n overflow: auto;\n padding: var(--space-sm);\n white-space: pre-wrap;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border-radius: var(--radius-sm);\n}\n\n.szenen-media > section {\n display: grid;\n gap: var(--space-sm);\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-media__lookup,\n.szenen-media__attach {\n display: flex;\n flex-wrap: wrap;\n align-items: end;\n gap: var(--space-sm);\n}\n\n.szenen-media__lookup > label,\n.szenen-media__attach > label:first-child {\n flex: 1 1 18rem;\n}\n\n.szenen-media__list {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-media__list > li {\n display: grid;\n grid-template-columns: minmax(12rem, 1fr) auto auto auto;\n align-items: center;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n@media (max-width: 760px) {\n .szenen-view {\n gap: var(--space-sm);\n }\n\n /* C1 \u2014 auf 390 px bleiben drei Kacheln je Reihe gross genug zum Erkennen\n und klein genug, dass vier Bilder eine Reihe sind und keine Seite. */\n .szenen-bildgalerie {\n grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));\n }\n\n /* Der Viewer bekommt den ganzen Bildschirm; die Bl\xE4tterkn\xF6pfe bleiben am\n Rand, wo der Daumen sie erreicht. */\n .szenen-bildviewer {\n padding: var(--space-xs);\n }\n\n .szenen-bildviewer__blaettern {\n width: 2.25rem;\n height: 4rem;\n }\n\n .szenen-view__intro {\n align-items: start;\n flex-direction: column;\n gap: var(--space-xs);\n padding: var(--space-sm);\n }\n\n .szenen-view__intro h2 {\n font-size: 1.05rem;\n }\n\n .szenen-view__intro > div > p:last-child {\n font-size: 0.78rem;\n }\n\n .szenen-view__nav {\n position: sticky;\n top: 0;\n z-index: 2;\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: var(--space-xs);\n padding: var(--space-xs);\n }\n\n .szenen-view__nav button {\n width: 100%;\n min-width: 0;\n min-height: 2.5rem;\n padding: 0.35rem 0.5rem;\n }\n\n\n .szenen-scene-editor {\n grid-template-columns: 1fr;\n }\n\n .szenen-capability > ul > li {\n grid-template-columns: 1fr;\n }\n\n .szenen-derive,\n .szenen-elements,\n .szenen-scene,\n .szenen-setup,\n .szenen-shot,\n .szenen-media,\n .szenen-runs {\n gap: var(--space-sm);\n }\n\n .szenen-elements__filter {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto;\n align-items: center;\n gap: var(--space-sm);\n }\n\n .szenen-elements__filter > label {\n width: 100%;\n min-width: 0;\n flex: none;\n }\n\n .szenen-elements__filter > label:has(input[type="checkbox"]) {\n flex: none;\n grid-column: 1 / -1;\n }\n\n /* Schmal ist die Liste eine Spalte, und die Auswahl fuehrt weiter \u2014 der\n * Eintrag ist deshalb ein Ziel fuer den Finger, keine Kachel. */\n .szenen-elements__list {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .szenen-elements__list > li > button {\n min-height: 2.75rem;\n }\n\n .szenen-elements__back {\n justify-self: stretch;\n min-height: 2.5rem;\n }\n\n .szenen-detail__actions,\n .szenen-runs__start,\n .szenen-scene-editor__add,\n .szenen-scene-editor__add-relation {\n align-items: stretch;\n flex-direction: column;\n }\n\n .szenen-runs__start > *,\n .szenen-scene-editor__add > *,\n .szenen-scene-editor__add-relation > * {\n width: 100%;\n flex: none;\n }\n}\n\n/* \u2500\u2500 Stufe 6.5: Rekonstruktion \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Bereich zeigt drei Dinge nebeneinander: den Vorschlag, das nicht\n * Ableitbare und die Widerspr\xFCche. Die Diagnosen sind deshalb bewusst nicht\n * kleiner gesetzt als der Vorschlag \u2014 wer sie \xFCbersieht, \xFCbernimmt Geratenes.\n */\n\n.szenen-derive {\n display: grid;\n gap: var(--space-md);\n}\n\n.szenen-form {\n display: grid;\n gap: var(--space-sm);\n padding: 0;\n background: transparent;\n border: none;\n border-radius: 0;\n}\n\n.szenen-field {\n display: grid;\n gap: var(--space-xs);\n}\n\n.szenen-field > span {\n color: var(--color-text-secondary);\n font-size: 0.875rem;\n}\n\n.szenen-error {\n padding: var(--space-sm) var(--space-md);\n color: var(--color-danger);\n background: var(--color-accent-red-dim);\n border: 1px solid var(--color-accent-red);\n border-radius: var(--radius-sm);\n}\n\n.szenen-empty {\n padding: var(--space-md);\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-diagnose {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-warning);\n border-radius: var(--radius-sm);\n}\n\n/* Ein Widerspruch ist der schwerste der drei Befunde: dort steht eine\n * Entscheidung aus, die das System bewusst nicht getroffen hat (D-74). */\n.szenen-diagnose--konflikt {\n border-left-color: var(--color-danger);\n}\n\n.szenen-diagnose > h4 {\n margin: 0 0 var(--space-sm);\n font-size: 0.9375rem;\n}\n\n.szenen-diagnose ul {\n display: grid;\n gap: var(--space-xs);\n margin: 0;\n padding-left: var(--space-md);\n}\n\n.szenen-proposal {\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-proposal > h4 {\n margin: 0 0 var(--space-sm);\n font-family: var(--font-mono);\n font-size: 0.9375rem;\n}\n\n.szenen-proposal__payload {\n max-height: 18rem;\n margin: 0 0 var(--space-sm);\n padding: var(--space-sm);\n overflow: auto;\n /* Ein erzeugter Prompt ist ein einziger langer Satz \u2014 ohne Umbruch\n verschwindet er nach rechts aus dem Bild. JSON-Payloads bringen ihre\n eigenen Zeilenumbrueche mit und leiden nicht darunter. */\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n font-family: var(--font-mono);\n font-size: 0.8125rem;\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-proposal__done {\n margin: var(--space-sm) 0 0;\n color: var(--color-success);\n}\n\n/* Die Belege eines Laufs: Rohantwort und Systemprompt. Zum Lesen und Kopieren,\n nicht zum Bedienen \u2014 deshalb voll markierbar und in Monospace. */\n.szenen-beleg__text {\n width: 100%;\n margin: var(--space-sm) 0 0;\n padding: var(--space-sm);\n font-family: var(--font-mono);\n font-size: 0.8125rem;\n line-height: 1.5;\n white-space: pre;\n background: var(--color-bg-app);\n color: var(--color-text);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n resize: vertical;\n}\n\n/* Der Folgelauf (D-87): abgesetzt, damit er nicht wie Teil des Ergebnisses\n aussieht \u2014 er ist eine Frage, keine Auskunft. */\n.szenen-folgelauf {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-folgelauf > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-run__herkunft {\n margin: 0 0 var(--space-sm);\n font-size: 0.875rem;\n color: var(--color-text-muted, inherit);\n}\n\n/* Widerspricht das Modell, ist das das Wichtigste auf der Seite. */\n.szenen-diagnose--antwort {\n border-left: 3px solid var(--color-accent, var(--color-border));\n}\n\n/* Der Bildw\xE4hler (D-92) \u2014 Browser und Inspector, dem Labs-Tab nachgebildet,\n aber mit dem CSS dieser Ansicht: eine gemeinsame Komponente \xFCber zwei\n Dom\xE4nengrenzen w\xE4re eine Kopplung, die keiner gewollt hat.\n\n Der Review vom 2026-08-22 (R12.1/R12.2) hat den Ma\xDFstab gesetzt: Der Bestand\n ist ein **Browser**, kein Seiteninhalt. Vierzig Werke d\xFCrfen die Szenenseite\n nicht meterlang machen, und zw\xF6lf Varianten eines Werkes keine zw\xF6lf\n Bildschirmseiten. Deshalb steht hier alles unter zwei Regeln \u2014 begrenzte\n Fl\xE4che mit eigenem Scroll, und Kacheln statt Karten. */\n\n/* \u2500\u2500 Ebene 1: der Browser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl {\n /* Dieselbe Begr\xFCndung wie beim Inspector: Als Grid-Element w\xFCrde der\n W\xE4hler sonst so breit wie sein breitester Inhalt. */\n min-width: 0;\n}\n\n.szenen-bildwahl__browser {\n /* Die eigene Scrollfl\xE4che ist der ganze Punkt: Der Bestand w\xE4chst nach\n innen, nicht nach unten. Dieselbe Gr\xF6\xDFenordnung wie `max-h-[300px]` im\n Labs-Browser. */\n max-height: 19rem;\n overflow-y: auto;\n overscroll-behavior: contain;\n margin-top: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__raster {\n display: grid;\n /* Vier Spalten auf schmalen Displays \u2014 der gemeldete Screenshot zeigte eine\n einzige Spalte gro\xDFer Karten. Breiter wird automatisch dichter. */\n grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildwahl__karte {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n overflow: hidden;\n background: var(--color-bg-surface);\n text-align: left;\n cursor: pointer;\n}\n\n/* Das fokussierte Werk ist klar markiert \u2014 sonst wei\xDF niemand, wessen Galerie\n der Inspector unten zeigt. */\n.szenen-bildwahl__karte.is-fokus {\n border-color: var(--color-accent, var(--color-text-primary));\n box-shadow: 0 0 0 2px var(--color-accent, var(--color-text-primary));\n}\n\n/* \u2500\u2500 Die Kachel selbst \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/* Der Platz steht, bevor das Bild da ist \u2014 sonst springt das Raster, sobald\n die erste Vorschau eintrifft. */\n.szenen-bildkachel {\n display: block;\n width: 100%;\n aspect-ratio: 1;\n overflow: hidden;\n background: var(--color-bg-app);\n}\n\n.szenen-bildkachel img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n\n.szenen-bildkachel__leer {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n background: linear-gradient(135deg, var(--color-bg-app), var(--color-bg-surface));\n}\n\n/* R12.3: Eine fehlende Vorschau bleibt kachelgro\xDF. Vorher fiel die Karte auf\n ihren Prompttext zur\xFCck \u2014 und ein Prompt hat im echten Bestand mehrere\n hundert Zeichen. */\n.szenen-bildkachel__zeichen {\n font-size: 0.875rem;\n font-weight: 700;\n color: var(--color-text-secondary);\n opacity: 0.6;\n}\n\n/* Wie viele Varianten das Werk hat \u2014 klein und \xFCberlagernd, nie als\n dominanter Text. */\n.szenen-bildwahl__anzahl {\n position: absolute;\n top: 0.15rem;\n right: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n font-weight: 700;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* Eine Zeile, dann Schluss. Der Prompt eines Werkes ist oft mehrere hundert\n Zeichen lang und w\xE4re als Prim\xE4rdarstellung genau die Textw\xFCste, gegen die\n der Review antritt. */\n.szenen-bildwahl__kacheltitel {\n display: block;\n padding: 0.15rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.3;\n color: var(--color-text-secondary);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2500\u2500 Ebene 2: der Inspector \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl__inspector {\n /* Ohne `min-width: 0` gewinnt der Inhalt: Der W\xE4hler ist Element eines\n Grids (`.szenen-form`), und dort ist `min-width: auto` die Vorgabe. Der\n Streifen unten ist rund 1000 px breit \u2014 gemessen am 2026-08-22 im echten\n Browser zog er die Auswahl von 374 px auf 1088 px auf und schob die\n ganze Seite waagerecht heraus, statt selbst zu scrollen. */\n min-width: 0;\n margin-top: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n /* Sichtbar abgesetzt vom Browser: zwei Ebenen, nicht eine lange Liste. */\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-accent, var(--color-border));\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__inspektorkopf {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--space-sm);\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bildwahl__werktitel {\n font-size: 0.75rem;\n font-weight: 600;\n color: var(--color-text-primary);\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.szenen-bildwahl__zurueck {\n flex: 0 0 auto;\n font-size: 0.6875rem;\n padding: 0.15rem 0.5rem;\n}\n\n/* Der Streifen: waagerecht scrollend, feste kleine Kacheln \u2014 zw\xF6lf Varianten\n bleiben ein \xDCberblick statt zw\xF6lf Bildschirmseiten. */\n.szenen-bildwahl__streifen {\n /* Die Breite kommt vom Elter, nie vom Inhalt \u2014 sonst scrollt hier nichts. */\n max-width: 100%;\n display: flex;\n gap: var(--space-sm);\n margin: 0;\n padding: 0 0 0.25rem;\n list-style: none;\n overflow-x: auto;\n overscroll-behavior-x: contain;\n}\n\n.szenen-bildwahl__streifen > li {\n flex: 0 0 auto;\n width: 5rem;\n}\n\n.szenen-bildwahl__variante {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n overflow: hidden;\n background: var(--color-bg-app);\n cursor: pointer;\n}\n\n.szenen-bildwahl__variante.is-gewaehlt {\n border-color: var(--color-accent, var(--color-text-primary));\n box-shadow: 0 0 0 2px var(--color-accent, var(--color-text-primary));\n}\n\n.szenen-bildwahl__nummer {\n position: absolute;\n bottom: 0.15rem;\n left: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border-radius: var(--radius-sm);\n}\n\n/* \u2500\u2500 Das Gew\xE4hlte \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n.szenen-bildwahl__gewaehlt {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: var(--space-sm) 0 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildwahl__gewaehlt li {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.25rem 0.5rem;\n font-size: 0.8125rem;\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n max-width: 100%;\n}\n\n/* Das gew\xE4hlte Bild wird best\xE4tigt, nicht nur benannt: eine kleine Vorschau\n neben dem Titel. */\n.szenen-bildwahl__gewaehlt .szenen-bildkachel {\n width: 2.25rem;\n flex: 0 0 auto;\n border-radius: var(--radius-sm);\n}\n\n.szenen-bildwahl__gewaehlt .szenen-bildwahl__kacheltitel {\n min-width: 0;\n max-width: 12rem;\n font-size: 0.75rem;\n}\n\n.szenen-beleg__kopf {\n display: flex;\n align-items: center;\n gap: var(--space-sm);\n margin-top: var(--space-sm);\n}\n\n.szenen-diagnose > details + details {\n margin-top: var(--space-sm);\n}\n\n.szenen-diagnose > details > summary {\n cursor: pointer;\n font-weight: 600;\n}\n\n/* \u2500\u2500 Die Fehlerrettung (Stufe 6.6, A5) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Sie steht im Fehlerblock und muss trotzdem als Werkzeug lesbar sein, nicht\n * als weitere Fehlermeldung. Deshalb abgesetzt wie der Folgelauf \u2014 und aus\n * demselben Grund: Beides sind Angebote, keine Ausk\xFCnfte.\n *\n * Der wichtigste optische Punkt ist die Trennung von Original und\n * Arbeitskopie. Das Original bekommt den ruhigen, \u201Efesten" Hintergrund der\n * Belege; die Kopie sieht aus wie ein normales Eingabefeld, weil sie eines\n * ist. */\n.szenen-rettung {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-rettung > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-rettung textarea[readonly] {\n background: var(--color-bg-surface);\n color: var(--color-text-secondary);\n font-family: var(--font-mono, monospace);\n font-size: 0.8125rem;\n}\n\n.szenen-rettung textarea:not([readonly]) {\n font-family: var(--font-mono, monospace);\n font-size: 0.8125rem;\n}\n\n.szenen-rettung__aktionen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: var(--space-sm) 0;\n}\n\n/* Ein Befund tr\xE4gt Code, Bezug und Meldung. Der Code steht klein und\n * monospaced davor: Er ist die Kennung, nicht die Aussage. */\n.szenen-diagnose__liste {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding-left: var(--space-md);\n}\n\n.szenen-diagnose__liste code {\n font-size: 0.8125rem;\n}\n\n.szenen-diagnose__liste > li[data-severity=\'warning\'] {\n color: var(--color-text-secondary);\n}\n\n/* \u2500\u2500 Die Draft-Kette (Stufe 6.6, C4) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Hauptweg. Er steht nach den Einzel\xFCbernahmen und muss als das erkennbar\n * sein, was er ist: nicht eine weitere Option, sondern der k\xFCrzere Weg zum\n * selben Ziel. Deshalb durchgezogener Rahmen statt gestricheltem \u2014 er ist\n * Angebot **und** Empfehlung. */\n.szenen-kette {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-left: 3px solid var(--color-accent, var(--color-border));\n border-radius: var(--radius-sm);\n}\n\n.szenen-kette > h4 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-kette ul {\n display: grid;\n gap: var(--space-xs);\n margin: 0 0 var(--space-sm);\n padding-left: var(--space-md);\n}\n\n.szenen-kette code {\n font-size: 0.8125rem;\n}\n\n/* Ein Hinweis, der eine Bedingung erklaert \u2014 nicht rot, weil er kein Fehler\n ist, aber abgesetzt, weil er eine Folge hat. */\n.szenen-detail__hint {\n margin: var(--space-sm) 0 0;\n padding: var(--space-sm);\n font-size: 0.875rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-surface);\n border-left: 3px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* \u2500\u2500 Stufe 6.7: Bilder am Prompt-Lauf (H0/H1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n *\n * Der Bereich tr\xE4gt zwei Dinge, die man nie verwechseln darf: den **Eingang**\n * (woraus der Prompt entstand) und den **Ausgang** (was daraus wurde). Sie\n * bekommen deshalb verschiedene Akzentkanten statt nur verschiedener\n * \xDCberschriften \u2014 wer schnell liest, sieht die Trennung, bevor er sie liest.\n */\n.szenen-bilder {\n display: grid;\n gap: var(--space-md);\n margin-top: var(--space-sm);\n}\n\n.szenen-bilder__eingang,\n.szenen-bilder__ausgang {\n display: grid;\n gap: var(--space-sm);\n padding: var(--space-sm) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__eingang {\n border-left: 3px solid var(--color-accent-purple, var(--color-border));\n}\n\n.szenen-bilder__ausgang {\n border-left: 3px solid var(--color-accent-teal, var(--color-border));\n}\n\n.szenen-bilder h5 {\n margin: 0;\n}\n\n.szenen-bilder__ids {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-sm);\n margin: 0;\n align-items: baseline;\n color: var(--color-text-secondary);\n font-size: 0.875rem;\n}\n\n.szenen-bilder__eingangsliste,\n.szenen-bilder__batchliste,\n.szenen-bilder__auftraege {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bilder__eingangsliste > li,\n.szenen-bilder__batchliste > li,\n.szenen-bilder__auftraege > li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n font-size: 0.8125rem;\n}\n\n/* Ein unterbrochener Auftrag ist der Fall, f\xFCr den der Auftragssatz existiert.\n Er wird deshalb markiert \u2014 nicht rot, denn er ist kein Fehler, sondern eine\n offene Entscheidung. */\n.szenen-bilder__auftraege > li[data-status="started"] {\n border-left: 3px solid var(--color-warning);\n}\n\n.szenen-bilder__auftraege > li[data-status="succeeded"] {\n border-left: 3px solid var(--color-success);\n}\n\n.szenen-bilder__auftraege > li[data-status="partial"] {\n border-left: 3px solid var(--color-accent-blue, var(--color-border));\n}\n\n.szenen-bilder__auftraege > li[data-status="failed"] {\n border-left: 3px solid var(--color-danger);\n}\n\n/* Das Eingangsbild benutzt seit C3 dieselbe Kachel wie W\xE4hler und Galerie.\n Sie ist von Haus aus so breit wie ihr Platz \u2014 in dieser Zeile bekommt sie\n deshalb eine feste Gr\xF6sse, sonst sch\xF6be sie die Metadaten heraus. */\n.szenen-bilder__eingangsliste .szenen-bildkachel {\n flex: 0 0 auto;\n width: 4rem;\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__batch {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding: var(--space-sm);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__batchkopf {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n justify-content: space-between;\n gap: var(--space-sm);\n}\n\n.szenen-bilder__herkunft {\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n}\n\n/* Review-Befund \u2014 ein transienter Bildfehler ist wiederholbar, kein\n endg\xFCltiges \u201Efehlt". Dezent wie ein Angebot, nicht wie ein Fehlerzustand. */\n.szenen-bilder__retry {\n justify-self: start;\n padding: 0.15rem 0.5rem;\n font-size: 0.75rem;\n color: var(--color-text-secondary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n/* C4 \u2014 die Provenienz vollst\xE4ndig, aber sekund\xE4r. */\n.szenen-bilder__technik > summary {\n color: var(--color-text-secondary);\n font-size: 0.75rem;\n cursor: pointer;\n}\n\n.szenen-bilder__meta {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n font-size: 0.75rem;\n color: var(--color-text-secondary);\n}\n\n/* C5 \u2014 der Verlauf verdichtet, seine Ausreisser nicht. */\n.szenen-bilder__verlauf > summary {\n color: var(--color-text-secondary);\n font-size: 0.8125rem;\n cursor: pointer;\n}\n\n.szenen-bilder__verlauf[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bilder__auffaellig {\n margin: 0;\n padding: var(--space-sm);\n border-radius: var(--radius-sm);\n font-size: 0.8125rem;\n}\n\n.szenen-bilder__auftragskennung {\n flex-basis: 100%;\n color: var(--color-text-tertiary);\n font-size: 0.6875rem;\n}\n\n/* \u2500\u2500 C1/C2 \u2014 Galerie und grosse Ansicht \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n\n/* Das Raster w\xE4chst mit dem Platz: auf 390 px drei bis vier Kacheln je Reihe,\n auf dem Desktop entsprechend mehr. Vorher war jedes Bild eine Zeile aus\n Daumennagel plus vier Metadatenfeldern \u2014 vier Bilder ergaben vier Textzeilen\n und das Protokoll war die Hauptsache. */\n.szenen-bildgalerie {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr));\n gap: var(--space-sm);\n min-width: 0;\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildgalerie__kachel {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n overflow: hidden;\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-bildgalerie__kachel:hover,\n.szenen-bildgalerie__kachel:focus-visible {\n border-color: var(--color-accent-teal, var(--color-text-primary));\n}\n\n.szenen-bildgalerie__nummer {\n position: absolute;\n bottom: 0.15rem;\n left: 0.15rem;\n padding: 0.05rem 0.3rem;\n font-size: 0.625rem;\n line-height: 1.4;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border-radius: var(--radius-sm);\n}\n\n/* Die grosse Ansicht liegt \xFCber allem und bringt ihren eigenen Grund mit \u2014\n `position: fixed` bezieht sich im Flow-Canvas auf dessen Rahmen, in React auf\n das Fenster; in beiden F\xE4llen genau das Gew\xFCnschte. */\n.szenen-bildviewer {\n position: fixed;\n inset: 0;\n z-index: 1000;\n display: grid;\n grid-template-rows: auto 1fr auto;\n gap: var(--space-sm);\n padding: var(--space-sm);\n /* Deckend, nicht get\xF6nt: Bei 0.92 schimmerte der Laufbereich durch und man\n las Prompttext quer \xFCber dem Bild \u2014 im Browser gemessen. */\n background: #000;\n}\n\n/* Titel, Z\xE4hlung, Schliessen \u2014 drei Spalten, damit ein langer Werktitel die\n Z\xE4hlung nicht in die n\xE4chste Zeile und \xFCber das Bild schiebt. */\n.szenen-bildviewer__kopf {\n display: grid;\n grid-template-columns: minmax(0, 1fr) auto auto;\n align-items: center;\n gap: var(--space-sm);\n color: var(--color-text-primary);\n}\n\n.szenen-bildviewer__titel {\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-weight: 600;\n}\n\n.szenen-bildviewer__zaehler {\n color: var(--color-text-secondary);\n font-size: 0.8125rem;\n}\n\n.szenen-bildviewer__buehne {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--space-sm);\n min-height: 0;\n min-width: 0;\n}\n\n/* Der Rahmen nimmt den Platz, der zwischen den Bl\xE4tterkn\xF6pfen \xFCbrig ist \u2014\n `min-width: 0`, damit er auch wirklich schrumpft statt sie hinauszuschieben. */\n.szenen-bildviewer__rahmen {\n flex: 1 1 auto;\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n min-height: 0;\n height: 100%;\n}\n\n.szenen-bildviewer__bild {\n max-width: 100%;\n max-height: 100%;\n object-fit: contain;\n}\n\n.szenen-bildviewer__leer {\n color: var(--color-text-secondary);\n text-align: center;\n}\n\n/* Die Bl\xE4tterkn\xF6pfe sind daumengross und liegen an den R\xE4ndern \u2014 auf einem\n Telefon ist der Rand erreichbar, die Bildmitte nicht. */\n.szenen-bildviewer__blaettern {\n flex: 0 0 auto;\n width: 2.75rem;\n height: 3.5rem;\n font-size: 1.75rem;\n line-height: 1;\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n cursor: pointer;\n}\n\n.szenen-bildviewer__details {\n max-height: 8rem;\n overflow-y: auto;\n color: var(--color-text-secondary);\n}\n\n/* Die Aktionen stehen abgesetzt: Sie sind Angebote, keine Ausk\xFCnfte \u2014 dasselbe\n Muster wie beim Folgelauf und bei der Fehlerrettung. */\n/* D \u2014 die Aktionen stehen abgesetzt und standardm\xE4ssig kompakt: Sie sind\n Angebote, keine Ausk\xFCnfte. Vorher hielten beide Formulare dauerhaft offen und\n machten aus jedem Lauf zwei zus\xE4tzliche Bildschirmseiten. */\n.szenen-bilder__aktion {\n min-width: 0;\n padding: var(--space-sm) var(--space-md);\n border: 1px dashed var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-bilder__aktion > summary {\n color: var(--color-text-primary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-bilder__aktion[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n.szenen-bilder__aktion-inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding-bottom: var(--space-sm);\n}\n\n.szenen-bilder__aktion h5 {\n margin: var(--space-sm) 0 0;\n}\n\n.szenen-bilder__aktion ol {\n display: grid;\n gap: var(--space-sm);\n margin: 0;\n padding-left: var(--space-md);\n font-size: 0.8125rem;\n}\n\n/* Regelwerk-UX Schritt 16 \u2014 der ZIP-Sammelimport.\n *\n * \xA78: Der gemeldete Einsatz ist mobil. Deshalb **ein** Seedfeld statt einer\n * gestapelten Eingabe je Bild, und die Zuordnung Bild -> Seed als umbrechende\n * Zeile statt als Tabelle: Eine Tabelle waere auf 390 px abgeschnitten, und\n * abgeschnitten heisst hier, dass der Nutzer die Zuordnung nicht mehr pruefen\n * kann \u2014 genau die Pruefung, wegen der die Vorschau existiert. */\n.szenen-bilder__zip {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n}\n\n.szenen-bilder__zipkopf {\n margin: 0;\n overflow-wrap: anywhere;\n}\n\n.szenen-bilder__zipvorlagen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n margin: 0;\n}\n\n/* Eine laengere Liste bleibt scrollbar, statt die Sektion und damit den\n * Import-Knopf aus dem Blick zu schieben (\xA78, letzter Punkt). */\n.szenen-bilder__zipliste {\n max-height: 16rem;\n overflow-y: auto;\n}\n\n.szenen-bilder__zipliste > li {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-bilder__zipliste > li > span:first-child {\n overflow-wrap: anywhere;\n}\n\n.szenen-bilder__zipliste > li > span:last-child {\n color: var(--color-text-secondary);\n white-space: nowrap;\n}\n\n/* ==========================================================================\n STUFE 6.10 \u2014 ELEMENTEDITOR UND VERSIONSNAVIGATION\n ========================================================================== */\n\n/* \xA74.2 \u2014 vier Sachverhalte, vier Marken. Die Marken stehen in einer eigenen\n * Spalte, damit ANGEZEIGT + BEVORZUGT nebeneinander lesbar bleiben, statt sich\n * durch den Text zu schieben. */\n.szenen-detail__heads li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n}\n\n.szenen-version__titel {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n min-width: 0;\n margin-right: auto;\n}\n\n/* V17 \u2014 Kennungen bleiben vollst\xE4ndig sichtbar, aber optisch sekund\xE4r. */\n.szenen-version__id,\n.szenen-ref__id,\n.szenen-detail__field-key {\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n overflow-wrap: anywhere;\n}\n\n.szenen-version__marken {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n.szenen-version__marken > span {\n padding: 0.1rem 0.5rem;\n color: var(--color-text-primary);\n background: rgba(45, 212, 191, 0.12);\n border: 1px solid rgba(45, 212, 191, 0.35);\n border-radius: var(--radius-full);\n font-size: 0.7rem;\n font-weight: 800;\n letter-spacing: 0.08em;\n}\n\n.szenen-version--angezeigt {\n border-color: var(--color-accent-teal, #2dd4bf) !important;\n}\n\n/* V9 \u2014 der Basiswechsel ist eine Entscheidung, keine Nebenwirkung. */\n.szenen-detail__confirm {\n display: grid;\n gap: var(--space-sm);\n margin: var(--space-md) 0;\n padding: var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-accent-amber, #f59e0b);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__confirm p {\n margin: 0;\n}\n\n/* Untergruppen von Objektfeldern (`prompt_render_settings`, `overrides`,\n `blender_render_settings`) \u2014 seit A1/A2 auf- und zuklappbar. Eine optionale\n Gruppe startet zu: Blender und `overrides` f\xFCllten auf einem 390-px-Display\n sonst mehrere Bildschirmseiten, obwohl die meisten Shots sie nie anfassen. */\n.szenen-detail__group {\n min-width: 0;\n margin: var(--space-md) 0;\n padding: var(--space-sm) var(--space-md);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__group > summary {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-weight: 600;\n cursor: pointer;\n}\n\n.szenen-detail__group[open] > summary {\n margin-bottom: var(--space-sm);\n}\n\n/* A2 \u2014 dass eine zugeklappte Gruppe schon Werte tr\xE4gt, muss man sehen, ohne\n sie zu \xF6ffnen. Sonst \xFCbersieht man eine gesetzte Blender-Ausgabe genau so\n lange, bis sie im Ergebnis auftaucht. */\n.szenen-detail__group-status {\n padding: 0.05rem 0.4rem;\n font-size: 0.6875rem;\n font-weight: 600;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n color: var(--color-text-tertiary);\n background: var(--color-bg-app);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__group[data-konfiguriert="ja"] > summary .szenen-detail__group-status {\n color: var(--color-text-primary);\n border-color: var(--color-accent-teal, var(--color-border));\n}\n\n.szenen-detail__group-inhalt {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n padding-bottom: var(--space-sm);\n}\n\n/* A3 \u2014 die Kernaussage steht am Feld, die Begr\xFCndung dahinter. Nicht als\n Tooltip: Auf einem Telefon gibt es kein Hover, und was dort unerreichbar ist,\n ist gestrichen \u2014 nur ohne dass es jemand entschieden h\xE4tte. */\n.szenen-detail__warum > summary {\n color: var(--color-text-tertiary);\n font-size: 0.75rem;\n cursor: pointer;\n}\n\n.szenen-detail__warum > small {\n display: block;\n padding-top: var(--space-xs);\n color: var(--color-text-tertiary);\n}\n\n.szenen-detail__fieldrow {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n margin-bottom: var(--space-md);\n}\n\n/* A4 \u2014 der technische Feldname bleibt lesbar, tritt aber hinter das fachliche\n Label zur\xFCck. */\n.szenen-detail__field-label {\n color: var(--color-text-primary);\n}\n\n.szenen-detail__field {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-detail__field > span {\n display: flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n}\n\n/* Mehrfachauswahl und Listeneditoren. */\n.szenen-feld__mehrfach,\n.szenen-feld__liste,\n.szenen-feld__reflist,\n.szenen-feld__ref,\n.szenen-feld__zielmodell,\n.szenen-bilder__modellwahl,\n.szenen-feld__kamera {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n/* \u201ENeues Modell hinzufuegen" im Importdialog \u2014 dieselben Abstaende wie jedes\n andere Formular dieser Ansicht, damit es kein Fremdkoerper wird. */\n.szenen-feld__zielmodell-neu {\n display: grid;\n gap: var(--space-xs);\n min-width: 0;\n padding: var(--space-sm);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-feld__zielmodell-neu-aktionen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n.szenen-feld__mehrfach > label,\n.szenen-feld__null {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.85rem;\n}\n\n.szenen-feld__liste-zeile {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n.szenen-feld__reflist ol {\n display: grid;\n gap: var(--space-xs);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-feld__reflist li {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs);\n padding: var(--space-xs) var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n/* \xA76.1 \u2014 Titel und Typ zuerst, Kennungen daneben. */\n.szenen-ref__anzeige {\n display: inline-flex;\n flex-wrap: wrap;\n align-items: baseline;\n gap: var(--space-xs);\n min-width: 0;\n}\n\n.szenen-ref__typ {\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-ref__warn {\n padding: 0.1rem 0.5rem;\n color: #fecaca;\n background: var(--color-accent-red-dim);\n border-radius: var(--radius-full);\n font-size: 0.72rem;\n font-weight: 700;\n}\n\n/* \xA76.5 \u2014 abgeleitete Angaben, ausdr\xFCcklich nur Anzeige. */\n.szenen-detail__derived {\n margin-top: var(--space-md);\n padding: var(--space-sm) var(--space-md);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-detail__derived dl {\n display: grid;\n grid-template-columns: minmax(10rem, auto) 1fr;\n gap: var(--space-xs) var(--space-sm);\n margin: 0;\n}\n\n.szenen-detail__derived dt {\n color: var(--color-text-secondary);\n}\n\n.szenen-detail__derived dd {\n margin: 0;\n}\n\n.szenen-feld__abgeleitet {\n margin: 0;\n color: var(--color-text-tertiary);\n font-size: 0.78rem;\n}\n\n/* Bereich `Prompts` \u2014 die herkunftsuebergreifende Sicht (P1-A).\n *\n * Die Eintraege selbst rendert `RundenEintrag` und tragen damit die\n * `szenen-runs__*`-Regeln. Hier stehen nur Rahmen und Kennzeichen; eine zweite\n * Laufdarstellung gibt es nicht, also braucht sie auch kein zweites Aussehen. */\n.szenen-prompts__toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n}\n\n.szenen-prompts__heading {\n margin: 0;\n font-size: 1.05rem;\n margin-right: auto;\n}\n\n.szenen-view__fokus {\n display: flex;\n align-items: center;\n gap: var(--space-sm);\n margin-bottom: var(--space-sm);\n}\n\n.szenen-prompts__fokus {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-sm);\n margin-bottom: var(--space-sm);\n padding: var(--space-xs) var(--space-sm);\n border: 1px solid var(--color-accent, #0d9488);\n border-radius: var(--radius-sm, 4px);\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-prompts__filter {\n display: flex;\n align-items: center;\n gap: var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-prompts__eintrag {\n list-style: none;\n margin-bottom: var(--space-md);\n}\n\n.szenen-prompts__lauf {\n padding-left: 0;\n}\n\n.szenen-prompts__herkunft {\n margin: 0 0 var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.78rem;\n}\n\n.szenen-prompts__hinweis {\n font-style: italic;\n}\n\n.szenen-prompts__ausgeblendet {\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n/* ==========================================================================\n ZIP-MODELLVORSCHLAG (modaler Best\xE4tigungsdialog)\n ========================================================================== */\n\n/* Wie die grosse Bildansicht: eigener Grund, `position: fixed`, damit der\n Dialog im Flow-Canvas und in React gleichermassen \xFCber allem liegt. */\n.szenen-zipmodell {\n position: fixed;\n inset: 0;\n z-index: 1100;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--space-md);\n background: rgba(0, 0, 0, 0.72);\n}\n\n.szenen-zipmodell__karte {\n display: grid;\n gap: var(--space-sm);\n width: min(34rem, 100%);\n max-height: 100%;\n overflow-y: auto;\n padding: var(--space-md);\n color: var(--color-text-primary);\n background: var(--color-bg-elevated);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-lg);\n}\n\n.szenen-zipmodell__titel {\n margin: 0;\n font-size: 1rem;\n}\n\n.szenen-zipmodell__quelle,\n.szenen-zipmodell__treffer,\n.szenen-zipmodell__vertrauen,\n.szenen-zipmodell__fallback {\n margin: 0;\n font-size: 0.85rem;\n}\n\n.szenen-zipmodell__quelle,\n.szenen-zipmodell__vertrauen {\n color: var(--color-text-secondary);\n}\n\n/* Ein schwacher oder mehrdeutiger Treffer muss sich vom sicheren unterscheiden,\n sonst ist die Warnung nur ein weiterer Satz. */\n.szenen-zipmodell__warnung {\n margin: 0;\n padding: var(--space-xs) var(--space-sm);\n color: var(--color-text-primary);\n background: rgba(234, 179, 8, 0.14);\n border-left: 3px solid #eab308;\n border-radius: var(--radius-sm);\n font-size: 0.85rem;\n}\n\n.szenen-zipmodell__alternativen {\n display: grid;\n gap: 0.2rem;\n margin: 0;\n padding: var(--space-xs) var(--space-sm);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n font-size: 0.85rem;\n}\n\n.szenen-zipmodell__alternativen > label {\n display: flex;\n align-items: center;\n gap: 0.4rem;\n}\n\n.szenen-zipmodell__aktionen {\n display: flex;\n flex-wrap: wrap;\n gap: var(--space-xs);\n}\n\n/* ==========================================================================\n MEHR LADEN \u2014 der gemeinsame Fuss jeder seitenweise geladenen Liste\n ========================================================================== */\n\n.szenen-mehr-laden {\n display: grid;\n gap: var(--space-xs);\n justify-items: start;\n padding: var(--space-xs) 0;\n}\n\n.szenen-mehr-laden__button {\n padding: var(--space-xs) var(--space-sm);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n background: var(--color-bg-surface);\n color: var(--color-text-primary);\n font-size: 0.85rem;\n cursor: pointer;\n}\n\n.szenen-mehr-laden__button:disabled {\n cursor: progress;\n opacity: 0.6;\n}\n\n.szenen-mehr-laden__fehler {\n margin: 0;\n color: var(--color-danger, #f87171);\n font-size: 0.8rem;\n}\n\n/* ==========================================================================\n PROMPTKARTE \u2014 Tags, Vorschau und Bildzahl\n (Kette `szenen-prompt-anzeige-bilder-tags`)\n\n Der Zuschnitt ist der der Library-Karte (`.library-item` + der absolut\n gesetzte Bildstreifen in `LibraryTab`): Textteil links, Deckbild rechts am\n Rand ueber die ganze Kartenhoehe, Bildzahl als Plakette darauf. Dieselben\n Werte (7rem Streifenbreite, linke Trennlinie, Plakette unten rechts), nur\n hier statt in Tailwind-Klassen \u2014 die Szenen-Ansicht teilt dieses Stylesheet\n byteweise mit Flow, und Flow hat kein Tailwind.\n ========================================================================== */\n\n.szenen-promptkarte {\n position: relative;\n overflow: hidden;\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n padding: var(--space-sm);\n}\n\n/* Nur wo ein Bild liegt, wird Platz dafuer freigehalten. Ohne Bild nutzt der\n Textteil die ganze Breite \u2014 keine leere Spalte. */\n.szenen-promptkarte--mitbild .szenen-promptkarte__inhalt {\n padding-right: calc(7rem + var(--space-sm));\n}\n\n.szenen-promptkarte__bild {\n position: absolute;\n top: 0;\n right: 0;\n width: 7rem;\n height: 100%;\n /* Hoechstens so hoch wie breit \u2014 der Streifen bleibt eine Kachel.\n `height: 100%` allein band ihn an die **Kartenhoehe**, und die ist in der\n aufgeklappten Runde die Hoehe des ganzen Prompts: Das Deckbild sprang\n dann als mehrere hundert Pixel hoher, auf Streifenbreite beschnittener\n Balken an den rechten Rand. Geschlossen aendert die Grenze nichts \u2014 dort\n ist die Karte niedriger als 7rem. */\n max-height: 7rem;\n border-left: 1px solid var(--color-border);\n background: #000;\n}\n\n/* Die gemeinsame Kachel bringt `width: 100%` und `aspect-ratio: 1` mit \u2014 hier\n fuellt sie stattdessen den Streifen, genau wie das Library-Deckbild. */\n.szenen-promptkarte__bild .szenen-bildkachel {\n width: 100%;\n height: 100%;\n aspect-ratio: auto;\n}\n\n.szenen-promptkarte__bildzahl {\n position: absolute;\n right: 0.25rem;\n bottom: 0.25rem;\n padding: 0.1rem 0.35rem;\n border-radius: var(--radius-sm);\n background: rgba(0, 0, 0, 0.6);\n color: #fff;\n font-size: 0.62rem;\n font-weight: 700;\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n/* Ein Kasten je Karte, nicht zwei.\n *\n * `.szenen-view button` (master.css) gibt **jedem** Knopf der Ansicht Rahmen\n * und Hintergrund und schlaegt dabei `.szenen-runs__kopf` (eine Klasse gegen\n * Klasse plus Element). In der Laufhistorie ist das richtig: Dort ist der Kopf\n * der Eintrag. In der Promptkarte ist die Karte der Eintrag \u2014 ein zweiter\n * Kasten darin waere die doppelte Umrandung, die die Library nicht hat.\n * Der Hover braucht eine eigene Zeile, weil die Regel dort spezifischer ist. */\n.szenen-promptkarte .szenen-runs__kopf,\n.szenen-promptkarte .szenen-runs__kopf:hover:not(:disabled) {\n min-height: 0;\n padding: var(--space-xs) 0;\n background: transparent;\n border: none;\n}\n\n/* Dieselbe Karte in der Elementliste.\n *\n * Dort ist der Auswahlknopf die Karte \u2014 er traegt Rahmen, Hintergrund und die\n * Auswahlmarkierung. Der Bildstreifen liegt deshalb **darueber** statt darin:\n * Ein `<button>` nimmt nur Phrasing Content auf, ein Streifen-`div` darin\n * waere ungueltiges Markup. Damit der rechte Kartenrand trotzdem auswaehlbar\n * bleibt, laesst der Streifen Klicks durch. */\n.szenen-elementkarte {\n position: relative;\n}\n\n.szenen-elementkarte--mitbild > button {\n padding-right: calc(7rem + var(--space-sm));\n}\n\n.szenen-elementkarte > .szenen-promptkarte__bild {\n border-radius: 0 var(--radius-sm) var(--radius-sm) 0;\n}\n\n.szenen-promptkarte__bild--durchklickbar {\n pointer-events: none;\n}\n\n/* Null Bilder: die Zahl, keine Flaeche. */\n.szenen-promptkarte__ohnebild {\n margin: 0 0 var(--space-xs);\n color: var(--color-text-secondary);\n font-size: 0.72rem;\n}\n\n/* Die Tagliste bricht um. Auf einem Telefon ist das der Unterschied zwischen\n einer kompakten Karte und einer Zeile, die seitlich herauslaeuft. */\n.szenen-tagliste {\n display: flex;\n flex-wrap: wrap;\n gap: 0.25rem;\n align-items: center;\n margin: 0 0 var(--space-xs);\n padding: 0;\n list-style: none;\n}\n\n.szenen-tag {\n display: inline-flex;\n align-items: center;\n gap: 0.25rem;\n padding: 0.05rem 0.4rem;\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n background: var(--color-bg-app);\n font-size: 0.72rem;\n line-height: 1.5;\n white-space: nowrap;\n}\n\n/* Herkunft sichtbar, aber ohne zweite Bedeutungsebene: Ein Prompt-Tag steht\n voll, ein reiner Library-Tag zurueckgenommen. Die Karte soll schnell lesbar\n sein, nicht zwei Farbcodes lehren. */\n.szenen-tag[data-herkunft="library"] {\n border-style: dashed;\n opacity: 0.85;\n}\n\n.szenen-tag--rest {\n border-style: none;\n opacity: 0.7;\n}\n\n.szenen-tag__weg {\n border: none;\n background: none;\n padding: 0;\n color: var(--color-text-secondary, inherit);\n font-size: 0.85rem;\n line-height: 1;\n cursor: pointer;\n}\n\n.szenen-tag__weg:disabled {\n cursor: progress;\n opacity: 0.5;\n}\n\n.szenen-taggeber {\n display: grid;\n gap: var(--space-xs);\n padding: var(--space-xs);\n margin-bottom: var(--space-xs);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n}\n\n.szenen-taggeber__zeile {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs);\n margin: 0;\n}\n\n.szenen-taggeber__label {\n font-size: 0.72rem;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n opacity: 0.75;\n}\n\n.szenen-taggeber__leer,\n.szenen-taggeber__hinweis {\n font-size: 0.72rem;\n opacity: 0.75;\n}\n\n.szenen-taggeber input[type="text"] {\n flex: 1 1 8rem;\n min-width: 0;\n padding: var(--space-xs);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm);\n background: var(--color-bg-surface);\n color: var(--color-text-primary);\n font-size: 0.8rem;\n}\n\n/* ==========================================================================\n BILDAUFTR\xC4GE \u2014 die bestandsweite Arbeitsqueue\n Kette `szenen-bildauftraege-arbeitsqueue`, Schritt 01.\n ========================================================================== */\n\n.szenen-bildauftraege {\n display: grid;\n gap: var(--space-sm);\n min-width: 0;\n}\n\n.szenen-bildauftraege__steuerung,\n.szenen-bildauftraege__filter,\n.szenen-bildauftraege__aktionen,\n.szenen-bildauftraege__nachladen,\n.szenen-bildauftraege__blaettern {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs);\n}\n\n/* Der manuelle Refresh \u2014 Schritt 03 \xA72. Er steht oben und sichtbar, weil er\n die einzige Stelle ist, an der dieser Bereich nachlaedt: Es gibt keinen\n Timer mehr, den er ersetzen koennte. */\n.szenen-bildauftraege__nachladen {\n justify-content: flex-start;\n}\n\n.szenen-bildauftraege__nachladen span {\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n/* Die Blaetterleiste der Bildkontrolle \u2014 Schritt 03 \xA73.1. */\n.szenen-bildauftraege__blaettern {\n margin: 0;\n}\n\n/* \u201EWeitere Bilder" \u2014 seit Schritt 06 eine **Leiste in der Zeile**, kein\n aufklappbares Formular: Modell, Anzahl, Knopf nebeneinander. */\n.szenen-bildauftraege__weitere {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--space-xs);\n margin: var(--space-xs) 0 0;\n font-size: 0.85rem;\n}\n\n.szenen-bildauftraege__weitere label {\n color: var(--color-text-secondary);\n}\n\n/* Der Lauf-Zaehler: sichtbar, aber nicht im Weg (Schritt 06). */\n.szenen-bildauftraege__laufzaehler {\n padding: 0.05rem var(--space-xs);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-sm, 4px);\n font-size: 0.8rem;\n font-variant-numeric: tabular-nums;\n}\n\n/* Die Vorschau steht oben und beh\xE4lt ihren Platz, auch solange noch kein Bild\n vorliegt \u2014 sie ist die Kontrollfl\xE4che des zweiten Browserfensters (\xA75). */\n.szenen-bildauftraege__letztes {\n display: grid;\n gap: var(--space-xs);\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-lg);\n}\n\n.szenen-bildauftraege__letztes h3 {\n margin: 0;\n font-size: 0.95rem;\n}\n\n.szenen-bildauftraege__letztes .szenen-bildkachel {\n width: min(100%, 22rem);\n height: auto;\n}\n\n.szenen-bildauftraege__lauf {\n padding: var(--space-xs) var(--space-sm);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-bildauftraege__lauf p {\n margin: 0.1rem 0;\n}\n\n.szenen-bildauftraege__liste {\n display: grid;\n gap: var(--space-xs);\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.szenen-bildauftraege__zeile {\n display: grid;\n gap: 0.25rem;\n padding: var(--space-sm);\n background: var(--color-bg-surface);\n border: 1px solid var(--color-border);\n border-radius: var(--radius-md);\n}\n\n.szenen-bildauftraege__kopf,\n.szenen-bildauftraege__prompt {\n margin: 0;\n}\n\n.szenen-bildauftraege__meta {\n display: flex;\n flex-wrap: wrap;\n gap: 0.15rem var(--space-sm);\n margin: 0;\n color: var(--color-text-secondary);\n font-size: 0.8rem;\n}\n\n.szenen-bildauftraege__prompt {\n color: var(--color-text-secondary);\n font-size: 0.85rem;\n}\n';
|
|
178
178
|
|
|
179
179
|
// src/lib/utils.ts
|
|
180
180
|
var getFormattedTimestamp = () => {
|
|
@@ -1659,7 +1659,7 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
|
|
|
1659
1659
|
}
|
|
1660
1660
|
|
|
1661
1661
|
// src/components/AvatarArchitectApp.tsx
|
|
1662
|
-
var
|
|
1662
|
+
var import_react62 = require("react");
|
|
1663
1663
|
|
|
1664
1664
|
// src/components/PromptTab.tsx
|
|
1665
1665
|
var import_react15 = require("react");
|
|
@@ -3405,14 +3405,92 @@ function useTagBase(token, namespace) {
|
|
|
3405
3405
|
};
|
|
3406
3406
|
}
|
|
3407
3407
|
|
|
3408
|
+
// src/lib/imagePrefetch.ts
|
|
3409
|
+
var import_react20 = require("react");
|
|
3410
|
+
function computePrefetchVisibility(input) {
|
|
3411
|
+
const { layoutChoice, appShellVisible, mobileTab, middlePanel, activeTab, isRightCollapsed } = input;
|
|
3412
|
+
const isMobile = layoutChoice === "mobile" || layoutChoice === "mobile-desktop";
|
|
3413
|
+
const isTabletLandscape = layoutChoice === "tablet-landscape";
|
|
3414
|
+
const szenenActive = isMobile ? mobileTab === "szenen" : isTabletLandscape ? false : middlePanel === "szenen";
|
|
3415
|
+
if (!appShellVisible) {
|
|
3416
|
+
return { historyVisible: false, galleryVisible: false, szenenActive };
|
|
3417
|
+
}
|
|
3418
|
+
if (isMobile) {
|
|
3419
|
+
const browseVisible = mobileTab === "browse";
|
|
3420
|
+
return {
|
|
3421
|
+
historyVisible: browseVisible && activeTab === "history",
|
|
3422
|
+
galleryVisible: browseVisible && activeTab === "gallery",
|
|
3423
|
+
szenenActive
|
|
3424
|
+
};
|
|
3425
|
+
}
|
|
3426
|
+
if (isTabletLandscape) {
|
|
3427
|
+
return { historyVisible: true, galleryVisible: false, szenenActive };
|
|
3428
|
+
}
|
|
3429
|
+
return {
|
|
3430
|
+
historyVisible: !isRightCollapsed && activeTab === "history",
|
|
3431
|
+
galleryVisible: !isRightCollapsed && activeTab === "gallery",
|
|
3432
|
+
szenenActive
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
function sortByTimestampDesc(entries) {
|
|
3436
|
+
return [...entries].sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
|
|
3437
|
+
}
|
|
3438
|
+
function selectHistoryPrefetchEntries(metadata, historyVisibleCount) {
|
|
3439
|
+
return sortByTimestampDesc(metadata).slice(0, historyVisibleCount);
|
|
3440
|
+
}
|
|
3441
|
+
function selectGalleryPrefetchEntries(metadata, galleryVisibleCount) {
|
|
3442
|
+
const sorted = sortByTimestampDesc(metadata);
|
|
3443
|
+
const skeletons = sorted.map((m) => ({
|
|
3444
|
+
id: m.id,
|
|
3445
|
+
prompt: m.prompt,
|
|
3446
|
+
timestamp: m.timestamp,
|
|
3447
|
+
status: "done",
|
|
3448
|
+
nodeId: m.id,
|
|
3449
|
+
tags: m.tags || []
|
|
3450
|
+
}));
|
|
3451
|
+
const repIds = new Set(groupByPrompt(skeletons).slice(0, galleryVisibleCount).map((g) => g.representative.id));
|
|
3452
|
+
return sorted.filter((e) => repIds.has(e.id));
|
|
3453
|
+
}
|
|
3454
|
+
function useThumbnailPrefetch(options) {
|
|
3455
|
+
const { metadata, historyVisible, galleryVisible, historyVisibleCount, galleryVisibleCount, loadThumbnailsForEntries } = options;
|
|
3456
|
+
(0, import_react20.useEffect)(() => {
|
|
3457
|
+
if (!metadata || !galleryVisible) return;
|
|
3458
|
+
loadThumbnailsForEntries(selectGalleryPrefetchEntries(metadata, galleryVisibleCount));
|
|
3459
|
+
}, [metadata, galleryVisible, galleryVisibleCount, loadThumbnailsForEntries]);
|
|
3460
|
+
(0, import_react20.useEffect)(() => {
|
|
3461
|
+
if (!metadata || !historyVisible) return;
|
|
3462
|
+
loadThumbnailsForEntries(selectHistoryPrefetchEntries(metadata, historyVisibleCount));
|
|
3463
|
+
}, [metadata, historyVisible, historyVisibleCount, loadThumbnailsForEntries]);
|
|
3464
|
+
}
|
|
3465
|
+
async function loadThumbnailsSequentially(entries, io) {
|
|
3466
|
+
for (const entry of entries) {
|
|
3467
|
+
if (io.inFlight.has(entry.id)) continue;
|
|
3468
|
+
if (io.isAlreadyLoaded(entry)) continue;
|
|
3469
|
+
if (io.isKnownMissing(entry)) continue;
|
|
3470
|
+
io.inFlight.add(entry.id);
|
|
3471
|
+
try {
|
|
3472
|
+
const b64 = await io.loadImage(entry);
|
|
3473
|
+
if (!b64) {
|
|
3474
|
+
io.onMissing(entry);
|
|
3475
|
+
continue;
|
|
3476
|
+
}
|
|
3477
|
+
io.onLoaded(entry, b64);
|
|
3478
|
+
} catch {
|
|
3479
|
+
io.onError(entry);
|
|
3480
|
+
} finally {
|
|
3481
|
+
io.inFlight.delete(entry.id);
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
|
|
3408
3486
|
// src/components/labs/LabsTab.tsx
|
|
3409
|
-
var
|
|
3487
|
+
var import_react28 = require("react");
|
|
3410
3488
|
|
|
3411
3489
|
// src/components/labs/LabRemix.tsx
|
|
3412
|
-
var
|
|
3490
|
+
var import_react23 = require("react");
|
|
3413
3491
|
|
|
3414
3492
|
// src/components/labs/LabImagePicker.tsx
|
|
3415
|
-
var
|
|
3493
|
+
var import_react21 = require("react");
|
|
3416
3494
|
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
3417
3495
|
var LabImagePicker = ({
|
|
3418
3496
|
availableItems,
|
|
@@ -3421,8 +3499,8 @@ var LabImagePicker = ({
|
|
|
3421
3499
|
onClose,
|
|
3422
3500
|
title = "Bild w\xE4hlen"
|
|
3423
3501
|
}) => {
|
|
3424
|
-
const [search, setSearch] = (0,
|
|
3425
|
-
const [drillItem, setDrillItem] = (0,
|
|
3502
|
+
const [search, setSearch] = (0, import_react21.useState)("");
|
|
3503
|
+
const [drillItem, setDrillItem] = (0, import_react21.useState)(null);
|
|
3426
3504
|
const filtered = availableItems.filter(
|
|
3427
3505
|
(item) => !search || item.prompt.toLowerCase().includes(search.toLowerCase())
|
|
3428
3506
|
);
|
|
@@ -3575,23 +3653,23 @@ var GenerationControls = ({
|
|
|
3575
3653
|
] });
|
|
3576
3654
|
|
|
3577
3655
|
// src/hooks/useGenerationSettings.ts
|
|
3578
|
-
var
|
|
3656
|
+
var import_react22 = require("react");
|
|
3579
3657
|
function useGenerationSettings() {
|
|
3580
|
-
const [aspectRatio, setAspectRatio] = (0,
|
|
3658
|
+
const [aspectRatio, setAspectRatio] = (0, import_react22.useState)(() => {
|
|
3581
3659
|
try {
|
|
3582
3660
|
return localStorage.getItem("aa-lab-aspect") || "1:1";
|
|
3583
3661
|
} catch {
|
|
3584
3662
|
return "1:1";
|
|
3585
3663
|
}
|
|
3586
3664
|
});
|
|
3587
|
-
const [model, setModel] = (0,
|
|
3665
|
+
const [model, setModel] = (0, import_react22.useState)(() => {
|
|
3588
3666
|
try {
|
|
3589
3667
|
return localStorage.getItem("aa-lab-model") || "\u{1F34C} Nano Banana Pro";
|
|
3590
3668
|
} catch {
|
|
3591
3669
|
return "\u{1F34C} Nano Banana Pro";
|
|
3592
3670
|
}
|
|
3593
3671
|
});
|
|
3594
|
-
const [imageCount, setImageCount] = (0,
|
|
3672
|
+
const [imageCount, setImageCount] = (0, import_react22.useState)(() => {
|
|
3595
3673
|
try {
|
|
3596
3674
|
const v = parseInt(localStorage.getItem("aa-image-count") || "", 10);
|
|
3597
3675
|
return v >= 1 && v <= 8 ? v : 4;
|
|
@@ -3627,13 +3705,13 @@ function useGenerationSettings() {
|
|
|
3627
3705
|
// src/components/labs/LabRemix.tsx
|
|
3628
3706
|
var import_jsx_runtime18 = require("react/jsx-runtime");
|
|
3629
3707
|
var LabRemix = ({ services, onResult }) => {
|
|
3630
|
-
const [showPicker, setShowPicker] = (0,
|
|
3631
|
-
const [selected, setSelected] = (0,
|
|
3632
|
-
const [instruction, setInstruction] = (0,
|
|
3633
|
-
const [generatedPrompt, setGeneratedPrompt] = (0,
|
|
3634
|
-
const [resultImages, setResultImages] = (0,
|
|
3635
|
-
const [isGeneratingPrompt, setIsGeneratingPrompt] = (0,
|
|
3636
|
-
const [runningCount, setRunningCount] = (0,
|
|
3708
|
+
const [showPicker, setShowPicker] = (0, import_react23.useState)(false);
|
|
3709
|
+
const [selected, setSelected] = (0, import_react23.useState)(null);
|
|
3710
|
+
const [instruction, setInstruction] = (0, import_react23.useState)("");
|
|
3711
|
+
const [generatedPrompt, setGeneratedPrompt] = (0, import_react23.useState)("");
|
|
3712
|
+
const [resultImages, setResultImages] = (0, import_react23.useState)([]);
|
|
3713
|
+
const [isGeneratingPrompt, setIsGeneratingPrompt] = (0, import_react23.useState)(false);
|
|
3714
|
+
const [runningCount, setRunningCount] = (0, import_react23.useState)(0);
|
|
3637
3715
|
const { aspectRatio, model, imageCount, updateAspectRatio, updateModel, updateImageCount } = useGenerationSettings();
|
|
3638
3716
|
const handleSelectImage = (item, frame) => {
|
|
3639
3717
|
services.onItemUsed(item);
|
|
@@ -3830,16 +3908,16 @@ var LabRemix = ({ services, onResult }) => {
|
|
|
3830
3908
|
};
|
|
3831
3909
|
|
|
3832
3910
|
// src/components/labs/LabBlend.tsx
|
|
3833
|
-
var
|
|
3911
|
+
var import_react24 = require("react");
|
|
3834
3912
|
var import_jsx_runtime19 = require("react/jsx-runtime");
|
|
3835
3913
|
var LabBlend = ({ services, onResult }) => {
|
|
3836
|
-
const [showPickerFor, setShowPickerFor] = (0,
|
|
3837
|
-
const [selectedImages, setSelectedImages] = (0,
|
|
3838
|
-
const [instruction, setInstruction] = (0,
|
|
3839
|
-
const [generatedPrompt, setGeneratedPrompt] = (0,
|
|
3840
|
-
const [resultImages, setResultImages] = (0,
|
|
3841
|
-
const [isGeneratingPrompt, setIsGeneratingPrompt] = (0,
|
|
3842
|
-
const [runningCount, setRunningCount] = (0,
|
|
3914
|
+
const [showPickerFor, setShowPickerFor] = (0, import_react24.useState)(null);
|
|
3915
|
+
const [selectedImages, setSelectedImages] = (0, import_react24.useState)([]);
|
|
3916
|
+
const [instruction, setInstruction] = (0, import_react24.useState)("");
|
|
3917
|
+
const [generatedPrompt, setGeneratedPrompt] = (0, import_react24.useState)("");
|
|
3918
|
+
const [resultImages, setResultImages] = (0, import_react24.useState)([]);
|
|
3919
|
+
const [isGeneratingPrompt, setIsGeneratingPrompt] = (0, import_react24.useState)(false);
|
|
3920
|
+
const [runningCount, setRunningCount] = (0, import_react24.useState)(0);
|
|
3843
3921
|
const { aspectRatio, model, imageCount, updateAspectRatio, updateModel, updateImageCount } = useGenerationSettings();
|
|
3844
3922
|
const handleSelectImage = (index, item, frame) => {
|
|
3845
3923
|
services.onItemUsed(item);
|
|
@@ -4041,17 +4119,17 @@ var LabBlend = ({ services, onResult }) => {
|
|
|
4041
4119
|
};
|
|
4042
4120
|
|
|
4043
4121
|
// src/components/labs/LabCompare.tsx
|
|
4044
|
-
var
|
|
4122
|
+
var import_react25 = require("react");
|
|
4045
4123
|
var import_jsx_runtime20 = require("react/jsx-runtime");
|
|
4046
4124
|
var LabCompare = ({ services, onResult }) => {
|
|
4047
|
-
const [showPickerFor, setShowPickerFor] = (0,
|
|
4048
|
-
const [selectedImages, setSelectedImages] = (0,
|
|
4049
|
-
const [instruction, setInstruction] = (0,
|
|
4050
|
-
const [analysis, setAnalysis] = (0,
|
|
4051
|
-
const [generatedPrompt, setGeneratedPrompt] = (0,
|
|
4052
|
-
const [resultImage, setResultImage] = (0,
|
|
4053
|
-
const [isAnalyzing, setIsAnalyzing] = (0,
|
|
4054
|
-
const [isGeneratingImage, setIsGeneratingImage] = (0,
|
|
4125
|
+
const [showPickerFor, setShowPickerFor] = (0, import_react25.useState)(null);
|
|
4126
|
+
const [selectedImages, setSelectedImages] = (0, import_react25.useState)([]);
|
|
4127
|
+
const [instruction, setInstruction] = (0, import_react25.useState)("");
|
|
4128
|
+
const [analysis, setAnalysis] = (0, import_react25.useState)("");
|
|
4129
|
+
const [generatedPrompt, setGeneratedPrompt] = (0, import_react25.useState)("");
|
|
4130
|
+
const [resultImage, setResultImage] = (0, import_react25.useState)(null);
|
|
4131
|
+
const [isAnalyzing, setIsAnalyzing] = (0, import_react25.useState)(false);
|
|
4132
|
+
const [isGeneratingImage, setIsGeneratingImage] = (0, import_react25.useState)(false);
|
|
4055
4133
|
const handleSelectImage = (index, item, frame) => {
|
|
4056
4134
|
services.onItemUsed(item);
|
|
4057
4135
|
const newImg = {
|
|
@@ -4214,14 +4292,14 @@ var LabCompare = ({ services, onResult }) => {
|
|
|
4214
4292
|
};
|
|
4215
4293
|
|
|
4216
4294
|
// src/components/labs/LabLoop.tsx
|
|
4217
|
-
var
|
|
4295
|
+
var import_react26 = require("react");
|
|
4218
4296
|
var import_jsx_runtime21 = require("react/jsx-runtime");
|
|
4219
4297
|
var LabLoop = ({ services, onResult }) => {
|
|
4220
|
-
const [rounds, setRounds] = (0,
|
|
4221
|
-
const [currentInstruction, setCurrentInstruction] = (0,
|
|
4222
|
-
const [showPickerForRound, setShowPickerForRound] = (0,
|
|
4223
|
-
const [pendingImages, setPendingImages] = (0,
|
|
4224
|
-
const [isGenerating, setIsGenerating] = (0,
|
|
4298
|
+
const [rounds, setRounds] = (0, import_react26.useState)([]);
|
|
4299
|
+
const [currentInstruction, setCurrentInstruction] = (0, import_react26.useState)("");
|
|
4300
|
+
const [showPickerForRound, setShowPickerForRound] = (0, import_react26.useState)(null);
|
|
4301
|
+
const [pendingImages, setPendingImages] = (0, import_react26.useState)([]);
|
|
4302
|
+
const [isGenerating, setIsGenerating] = (0, import_react26.useState)(false);
|
|
4225
4303
|
const { aspectRatio, model, updateAspectRatio, updateModel } = useGenerationSettings();
|
|
4226
4304
|
const currentPrompt = rounds.length > 0 ? rounds[rounds.length - 1].prompt : "";
|
|
4227
4305
|
const handleAddImage = (item, frame) => {
|
|
@@ -4385,7 +4463,7 @@ var LabLoop = ({ services, onResult }) => {
|
|
|
4385
4463
|
};
|
|
4386
4464
|
|
|
4387
4465
|
// src/components/labs/LabFrameExtractor.tsx
|
|
4388
|
-
var
|
|
4466
|
+
var import_react27 = require("react");
|
|
4389
4467
|
var import_jsx_runtime22 = require("react/jsx-runtime");
|
|
4390
4468
|
var formatTime = (s) => {
|
|
4391
4469
|
const m = Math.floor(s / 60);
|
|
@@ -4399,15 +4477,15 @@ var LabFrameExtractor = ({
|
|
|
4399
4477
|
onResult,
|
|
4400
4478
|
resolveVideoUrl
|
|
4401
4479
|
}) => {
|
|
4402
|
-
const videoRef = (0,
|
|
4403
|
-
const canvasRef = (0,
|
|
4404
|
-
const cancelledRef = (0,
|
|
4405
|
-
const [selectedItem, setSelectedItem] = (0,
|
|
4406
|
-
const [videoSrc, setVideoSrc] = (0,
|
|
4407
|
-
const [videoReady, setVideoReady] = (0,
|
|
4408
|
-
const [frames, setFrames] = (0,
|
|
4409
|
-
const [isExtracting, setIsExtracting] = (0,
|
|
4410
|
-
const [intervalSec, setIntervalSec] = (0,
|
|
4480
|
+
const videoRef = (0, import_react27.useRef)(null);
|
|
4481
|
+
const canvasRef = (0, import_react27.useRef)(null);
|
|
4482
|
+
const cancelledRef = (0, import_react27.useRef)(false);
|
|
4483
|
+
const [selectedItem, setSelectedItem] = (0, import_react27.useState)(null);
|
|
4484
|
+
const [videoSrc, setVideoSrc] = (0, import_react27.useState)(null);
|
|
4485
|
+
const [videoReady, setVideoReady] = (0, import_react27.useState)(false);
|
|
4486
|
+
const [frames, setFrames] = (0, import_react27.useState)([]);
|
|
4487
|
+
const [isExtracting, setIsExtracting] = (0, import_react27.useState)(false);
|
|
4488
|
+
const [intervalSec, setIntervalSec] = (0, import_react27.useState)("1");
|
|
4411
4489
|
const handleVideoSelect = (item) => {
|
|
4412
4490
|
const mediaId = item.frames[0]?.mediaId;
|
|
4413
4491
|
if (!mediaId) return;
|
|
@@ -4417,7 +4495,7 @@ var LabFrameExtractor = ({
|
|
|
4417
4495
|
setVideoReady(false);
|
|
4418
4496
|
cancelledRef.current = false;
|
|
4419
4497
|
};
|
|
4420
|
-
const captureAt = (0,
|
|
4498
|
+
const captureAt = (0, import_react27.useCallback)(
|
|
4421
4499
|
(t, label) => new Promise((resolve) => {
|
|
4422
4500
|
const video = videoRef.current;
|
|
4423
4501
|
const canvas = canvasRef.current;
|
|
@@ -4644,7 +4722,7 @@ var BASE_TABS = [
|
|
|
4644
4722
|
];
|
|
4645
4723
|
var FRAMES_TAB = { key: "frames", label: "Frames", icon: "crop_original" };
|
|
4646
4724
|
var LabsTab = ({ services, onResult, videoItems, resolveVideoUrl }) => {
|
|
4647
|
-
const [activeTab, setActiveTab] = (0,
|
|
4725
|
+
const [activeTab, setActiveTab] = (0, import_react28.useState)("remix");
|
|
4648
4726
|
const showFrames = !!(videoItems && resolveVideoUrl);
|
|
4649
4727
|
const tabs = showFrames ? [...BASE_TABS, FRAMES_TAB] : BASE_TABS;
|
|
4650
4728
|
return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col h-full overflow-hidden", children: [
|
|
@@ -4678,10 +4756,10 @@ var LabsTab = ({ services, onResult, videoItems, resolveVideoUrl }) => {
|
|
|
4678
4756
|
};
|
|
4679
4757
|
|
|
4680
4758
|
// src/components/TagManagerPanel.tsx
|
|
4681
|
-
var
|
|
4759
|
+
var import_react30 = require("react");
|
|
4682
4760
|
|
|
4683
4761
|
// src/features/tags/TagManager.tsx
|
|
4684
|
-
var
|
|
4762
|
+
var import_react29 = require("react");
|
|
4685
4763
|
|
|
4686
4764
|
// src/features/tags/tagSelectors.ts
|
|
4687
4765
|
var CATEGORY_DEFAULTS = {
|
|
@@ -4864,17 +4942,17 @@ function listCategories(model) {
|
|
|
4864
4942
|
// src/features/tags/TagManager.tsx
|
|
4865
4943
|
var import_jsx_runtime24 = require("react/jsx-runtime");
|
|
4866
4944
|
var TagManager = ({ tags, commands = {}, selection, onSelectionChange, emptyText = "Keine Tags in dieser Kategorie.", newCategoryName, ensureScopes }) => {
|
|
4867
|
-
const [internalSelection, setInternalSelection] = (0,
|
|
4868
|
-
const [notice, setNotice] = (0,
|
|
4869
|
-
const [editingId, setEditingId] = (0,
|
|
4870
|
-
const [editState, setEditState] = (0,
|
|
4871
|
-
const [newTag, setNewTag] = (0,
|
|
4872
|
-
const [newCategoryInput, setNewCategoryInput] = (0,
|
|
4873
|
-
const [movingId, setMovingId] = (0,
|
|
4874
|
-
const [moveTarget, setMoveTarget] = (0,
|
|
4875
|
-
const [showInherited, setShowInherited] = (0,
|
|
4945
|
+
const [internalSelection, setInternalSelection] = (0, import_react29.useState)({ scopeKey: null, categoryKey: null });
|
|
4946
|
+
const [notice, setNotice] = (0, import_react29.useState)(null);
|
|
4947
|
+
const [editingId, setEditingId] = (0, import_react29.useState)(null);
|
|
4948
|
+
const [editState, setEditState] = (0, import_react29.useState)({ label: "", value: "" });
|
|
4949
|
+
const [newTag, setNewTag] = (0, import_react29.useState)({ label: "", value: "" });
|
|
4950
|
+
const [newCategoryInput, setNewCategoryInput] = (0, import_react29.useState)("");
|
|
4951
|
+
const [movingId, setMovingId] = (0, import_react29.useState)(null);
|
|
4952
|
+
const [moveTarget, setMoveTarget] = (0, import_react29.useState)("");
|
|
4953
|
+
const [showInherited, setShowInherited] = (0, import_react29.useState)(true);
|
|
4876
4954
|
const ensureScopesKey = (ensureScopes ?? []).join("|");
|
|
4877
|
-
const derived = (0,
|
|
4955
|
+
const derived = (0, import_react29.useMemo)(() => {
|
|
4878
4956
|
try {
|
|
4879
4957
|
let model = deriveTagViewModel(tags);
|
|
4880
4958
|
if (ensureScopes?.length) model = ensureScopesInModel(model, ensureScopes);
|
|
@@ -5176,7 +5254,7 @@ var TagManager = ({ tags, commands = {}, selection, onSelectionChange, emptyText
|
|
|
5176
5254
|
var import_jsx_runtime25 = require("react/jsx-runtime");
|
|
5177
5255
|
var OK = { kind: "ok", value: void 0 };
|
|
5178
5256
|
function TagManagerPanel({ workspaceTags, onTagCreate, onTagUpdate, onTagDelete, onTagReorder, onTagMove }) {
|
|
5179
|
-
const adapters = (0,
|
|
5257
|
+
const adapters = (0, import_react30.useMemo)(() => {
|
|
5180
5258
|
const byId = /* @__PURE__ */ new Map();
|
|
5181
5259
|
const entries = [];
|
|
5182
5260
|
Object.entries(workspaceTags.by_category).forEach(([category, tags]) => {
|
|
@@ -5188,7 +5266,7 @@ function TagManagerPanel({ workspaceTags, onTagCreate, onTagUpdate, onTagDelete,
|
|
|
5188
5266
|
});
|
|
5189
5267
|
return { byId, entries };
|
|
5190
5268
|
}, [workspaceTags]);
|
|
5191
|
-
const commands = (0,
|
|
5269
|
+
const commands = (0, import_react30.useMemo)(() => ({
|
|
5192
5270
|
createTag: async ({ category, label, value }) => {
|
|
5193
5271
|
onTagCreate({ label, value, category, is_user_created: true });
|
|
5194
5272
|
return OK;
|
|
@@ -5228,7 +5306,7 @@ function TagManagerPanel({ workspaceTags, onTagCreate, onTagUpdate, onTagDelete,
|
|
|
5228
5306
|
}
|
|
5229
5307
|
|
|
5230
5308
|
// src/components/HFTestTab.tsx
|
|
5231
|
-
var
|
|
5309
|
+
var import_react31 = require("react");
|
|
5232
5310
|
var import_jsx_runtime26 = require("react/jsx-runtime");
|
|
5233
5311
|
var HF_BASE2 = "https://huggingface.co";
|
|
5234
5312
|
var HF_REPO2 = "RolandSch/fa-app-state";
|
|
@@ -5421,7 +5499,7 @@ function tryFmt(s) {
|
|
|
5421
5499
|
}
|
|
5422
5500
|
}
|
|
5423
5501
|
function CopyBtn({ text }) {
|
|
5424
|
-
const [done, setDone] = (0,
|
|
5502
|
+
const [done, setDone] = (0, import_react31.useState)(false);
|
|
5425
5503
|
return /* @__PURE__ */ (0, import_jsx_runtime26.jsxs)(
|
|
5426
5504
|
"button",
|
|
5427
5505
|
{
|
|
@@ -5593,9 +5671,9 @@ function EventMonitor({ events, confirmedEventKeys, galleryItems, imageUploadSta
|
|
|
5593
5671
|
] });
|
|
5594
5672
|
}
|
|
5595
5673
|
function HFTestTab({ token, namespace, galleryItems, allEvents = [], confirmedEventKeys = /* @__PURE__ */ new Set(), imageUploadStatus = /* @__PURE__ */ new Map(), missingImages = [] }) {
|
|
5596
|
-
const [selected, setSelected] = (0,
|
|
5597
|
-
const [results, setResults] = (0,
|
|
5598
|
-
const [expanded, setExpanded] = (0,
|
|
5674
|
+
const [selected, setSelected] = (0, import_react31.useState)(null);
|
|
5675
|
+
const [results, setResults] = (0, import_react31.useState)({});
|
|
5676
|
+
const [expanded, setExpanded] = (0, import_react31.useState)({});
|
|
5599
5677
|
const withResults = galleryItems.filter((g) => g.base64 && g.status === "done");
|
|
5600
5678
|
const setRunning = (id) => setResults((r) => ({ ...r, [id]: { status: "running", steps: [], totalMs: 0 } }));
|
|
5601
5679
|
const setDone = (id, steps, t0) => {
|
|
@@ -5793,7 +5871,7 @@ function HFTestTab({ token, namespace, galleryItems, allEvents = [], confirmedEv
|
|
|
5793
5871
|
}
|
|
5794
5872
|
|
|
5795
5873
|
// src/components/ServerTab.tsx
|
|
5796
|
-
var
|
|
5874
|
+
var import_react32 = require("react");
|
|
5797
5875
|
var import_jsx_runtime27 = require("react/jsx-runtime");
|
|
5798
5876
|
function StarRating({ rating = 0 }) {
|
|
5799
5877
|
return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("div", { className: "flex gap-[2px]", children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: `material-symbols-outlined text-[12px] ${i <= rating ? "text-yellow-400" : "text-white/15"}`, children: "star" }, i)) });
|
|
@@ -5806,21 +5884,21 @@ async function serverGet(baseUrl, path) {
|
|
|
5806
5884
|
return json && typeof json === "object" && "data" in json ? json.data : json;
|
|
5807
5885
|
}
|
|
5808
5886
|
function ServerTab({ serverBaseUrl }) {
|
|
5809
|
-
const [step, setStep] = (0,
|
|
5810
|
-
const [users, setUsers] = (0,
|
|
5811
|
-
const [usersLoading, setUsersLoading] = (0,
|
|
5812
|
-
const [usersError, setUsersError] = (0,
|
|
5813
|
-
const [selectedUser, setSelectedUser] = (0,
|
|
5814
|
-
const [contexts, setContexts] = (0,
|
|
5815
|
-
const [contextsLoading, setContextsLoading] = (0,
|
|
5816
|
-
const [selectedContext, setSelectedContext] = (0,
|
|
5817
|
-
const [tags, setTags] = (0,
|
|
5818
|
-
const [items, setItems] = (0,
|
|
5819
|
-
const [libLoading, setLibLoading] = (0,
|
|
5820
|
-
const [libError, setLibError] = (0,
|
|
5821
|
-
const [activeTag, setActiveTag] = (0,
|
|
5822
|
-
const [preview, setPreview] = (0,
|
|
5823
|
-
(0,
|
|
5887
|
+
const [step, setStep] = (0, import_react32.useState)("user");
|
|
5888
|
+
const [users, setUsers] = (0, import_react32.useState)([]);
|
|
5889
|
+
const [usersLoading, setUsersLoading] = (0, import_react32.useState)(false);
|
|
5890
|
+
const [usersError, setUsersError] = (0, import_react32.useState)(null);
|
|
5891
|
+
const [selectedUser, setSelectedUser] = (0, import_react32.useState)(null);
|
|
5892
|
+
const [contexts, setContexts] = (0, import_react32.useState)([]);
|
|
5893
|
+
const [contextsLoading, setContextsLoading] = (0, import_react32.useState)(false);
|
|
5894
|
+
const [selectedContext, setSelectedContext] = (0, import_react32.useState)(null);
|
|
5895
|
+
const [tags, setTags] = (0, import_react32.useState)([]);
|
|
5896
|
+
const [items, setItems] = (0, import_react32.useState)([]);
|
|
5897
|
+
const [libLoading, setLibLoading] = (0, import_react32.useState)(false);
|
|
5898
|
+
const [libError, setLibError] = (0, import_react32.useState)(null);
|
|
5899
|
+
const [activeTag, setActiveTag] = (0, import_react32.useState)(null);
|
|
5900
|
+
const [preview, setPreview] = (0, import_react32.useState)(null);
|
|
5901
|
+
(0, import_react32.useEffect)(() => {
|
|
5824
5902
|
if (!serverBaseUrl) return;
|
|
5825
5903
|
setUsersLoading(true);
|
|
5826
5904
|
setUsersError(null);
|
|
@@ -5971,7 +6049,7 @@ function ServerTab({ serverBaseUrl }) {
|
|
|
5971
6049
|
}
|
|
5972
6050
|
|
|
5973
6051
|
// src/components/FlowWorkspacePicker.tsx
|
|
5974
|
-
var
|
|
6052
|
+
var import_react33 = require("react");
|
|
5975
6053
|
var import_jsx_runtime28 = require("react/jsx-runtime");
|
|
5976
6054
|
function FlowWorkspacePicker({
|
|
5977
6055
|
tagBase,
|
|
@@ -5980,9 +6058,9 @@ function FlowWorkspacePicker({
|
|
|
5980
6058
|
onSelectUser,
|
|
5981
6059
|
onSelectContext
|
|
5982
6060
|
}) {
|
|
5983
|
-
const [step, setStep] = (0,
|
|
6061
|
+
const [step, setStep] = (0, import_react33.useState)(selectedUserId === null ? "user" : "context");
|
|
5984
6062
|
const selectedUser = tagBase.users.find((user) => user.id === selectedUserId) ?? null;
|
|
5985
|
-
const contexts = (0,
|
|
6063
|
+
const contexts = (0, import_react33.useMemo)(() => {
|
|
5986
6064
|
if (!selectedUser) return [];
|
|
5987
6065
|
if (!Array.isArray(selectedUser.context_ids)) return [];
|
|
5988
6066
|
const allowed = new Set(selectedUser.context_ids);
|
|
@@ -6042,7 +6120,7 @@ function FlowWorkspacePicker({
|
|
|
6042
6120
|
}
|
|
6043
6121
|
|
|
6044
6122
|
// src/features/szenen/SzenenView.tsx
|
|
6045
|
-
var
|
|
6123
|
+
var import_react61 = require("react");
|
|
6046
6124
|
|
|
6047
6125
|
// src/lib/szenen/elementEvents.ts
|
|
6048
6126
|
var SZENEN_EVENT_PREFIX = "szenen.";
|
|
@@ -10003,10 +10081,10 @@ function noticeFor(ergebnis, was) {
|
|
|
10003
10081
|
}
|
|
10004
10082
|
|
|
10005
10083
|
// src/features/szenen/areas/ElementsArea.tsx
|
|
10006
|
-
var
|
|
10084
|
+
var import_react53 = require("react");
|
|
10007
10085
|
|
|
10008
10086
|
// src/features/szenen/areas/useVorschauen.ts
|
|
10009
|
-
var
|
|
10087
|
+
var import_react34 = require("react");
|
|
10010
10088
|
|
|
10011
10089
|
// src/lib/szenen/bildVorschau.ts
|
|
10012
10090
|
async function ladeVorschauen(port, mediaRefs, fertig, optionen = {}) {
|
|
@@ -10035,16 +10113,16 @@ async function ladeVorschauen(port, mediaRefs, fertig, optionen = {}) {
|
|
|
10035
10113
|
// src/features/szenen/areas/useVorschauen.ts
|
|
10036
10114
|
function useVorschauen(port, wuensche) {
|
|
10037
10115
|
const bestand = port.imageLibrary;
|
|
10038
|
-
const [stand, setStand] = (0,
|
|
10116
|
+
const [stand, setStand] = (0, import_react34.useState)(
|
|
10039
10117
|
() => ({ quellen: /* @__PURE__ */ new Map(), fehler: /* @__PURE__ */ new Map() })
|
|
10040
10118
|
);
|
|
10041
|
-
const gehalten = (0,
|
|
10042
|
-
const gescheitert = (0,
|
|
10119
|
+
const gehalten = (0, import_react34.useRef)(/* @__PURE__ */ new Map());
|
|
10120
|
+
const gescheitert = (0, import_react34.useRef)(/* @__PURE__ */ new Map());
|
|
10043
10121
|
const auftrag = wuensche.filter(
|
|
10044
10122
|
(e) => typeof e.mediaRef === "string"
|
|
10045
10123
|
);
|
|
10046
10124
|
const schluessel = auftrag.map((e) => `${e.id}|${e.mediaRef}`).join(" ");
|
|
10047
|
-
(0,
|
|
10125
|
+
(0, import_react34.useEffect)(() => {
|
|
10048
10126
|
if (!bestand?.loadPreview) return void 0;
|
|
10049
10127
|
const lauf = { abgebrochen: false };
|
|
10050
10128
|
const offen = auftrag.filter(
|
|
@@ -10083,7 +10161,7 @@ function useVorschauen(port, wuensche) {
|
|
|
10083
10161
|
}
|
|
10084
10162
|
|
|
10085
10163
|
// src/features/szenen/areas/PromptKarteMeta.tsx
|
|
10086
|
-
var
|
|
10164
|
+
var import_react37 = require("react");
|
|
10087
10165
|
|
|
10088
10166
|
// src/lib/szenen/promptRunTags.ts
|
|
10089
10167
|
function tagRefsAus(roh, quelle) {
|
|
@@ -10145,7 +10223,7 @@ function labelsOf(tags) {
|
|
|
10145
10223
|
}
|
|
10146
10224
|
|
|
10147
10225
|
// src/features/szenen/areas/Bildbausteine.tsx
|
|
10148
|
-
var
|
|
10226
|
+
var import_react35 = require("react");
|
|
10149
10227
|
var import_jsx_runtime29 = require("react/jsx-runtime");
|
|
10150
10228
|
var Kachel = ({
|
|
10151
10229
|
quelle,
|
|
@@ -10162,11 +10240,11 @@ var BildViewer = ({
|
|
|
10162
10240
|
}) => {
|
|
10163
10241
|
const anzahl = bilder.length;
|
|
10164
10242
|
const bild = bilder[position];
|
|
10165
|
-
const blaettern = (0,
|
|
10243
|
+
const blaettern = (0, import_react35.useCallback)((schritt) => {
|
|
10166
10244
|
if (anzahl === 0) return;
|
|
10167
10245
|
onWechseln((position + schritt + anzahl) % anzahl);
|
|
10168
10246
|
}, [anzahl, position, onWechseln]);
|
|
10169
|
-
(0,
|
|
10247
|
+
(0, import_react35.useEffect)(() => {
|
|
10170
10248
|
const taste = (e) => {
|
|
10171
10249
|
if (e.key === "Escape") onSchliessen();
|
|
10172
10250
|
else if (e.key === "ArrowLeft") blaettern(-1);
|
|
@@ -10249,8 +10327,8 @@ var BildViewer = ({
|
|
|
10249
10327
|
);
|
|
10250
10328
|
};
|
|
10251
10329
|
var BildGalerie = ({ bilder, testId, satzTitel }) => {
|
|
10252
|
-
const [offen, setOffen] = (0,
|
|
10253
|
-
(0,
|
|
10330
|
+
const [offen, setOffen] = (0, import_react35.useState)(null);
|
|
10331
|
+
(0, import_react35.useEffect)(() => {
|
|
10254
10332
|
setOffen((vorher) => vorher !== null && vorher >= bilder.length ? null : vorher);
|
|
10255
10333
|
}, [bilder.length]);
|
|
10256
10334
|
if (bilder.length === 0) return null;
|
|
@@ -10294,12 +10372,12 @@ var BildGalerie = ({ bilder, testId, satzTitel }) => {
|
|
|
10294
10372
|
};
|
|
10295
10373
|
|
|
10296
10374
|
// src/features/szenen/areas/usePortQuery.ts
|
|
10297
|
-
var
|
|
10375
|
+
var import_react36 = require("react");
|
|
10298
10376
|
function usePortQuery(frage, abhaengigkeiten) {
|
|
10299
|
-
const [phase, setPhase] = (0,
|
|
10300
|
-
const [token, setToken] = (0,
|
|
10301
|
-
const sequenz = (0,
|
|
10302
|
-
(0,
|
|
10377
|
+
const [phase, setPhase] = (0, import_react36.useState)({ status: "loading" });
|
|
10378
|
+
const [token, setToken] = (0, import_react36.useState)(0);
|
|
10379
|
+
const sequenz = (0, import_react36.useRef)(0);
|
|
10380
|
+
(0, import_react36.useEffect)(() => {
|
|
10303
10381
|
const meine = sequenz.current += 1;
|
|
10304
10382
|
let abgemeldet = false;
|
|
10305
10383
|
setPhase({ status: "loading" });
|
|
@@ -10332,7 +10410,7 @@ function bilderText(anzahl) {
|
|
|
10332
10410
|
return anzahl === 1 ? "1 Bild" : `${anzahl} Bilder`;
|
|
10333
10411
|
}
|
|
10334
10412
|
var PromptKarteTags = ({ runId, promptTags, annotation, testId }) => {
|
|
10335
|
-
const tags = (0,
|
|
10413
|
+
const tags = (0, import_react37.useMemo)(() => karteTags(promptTags, annotation), [promptTags, annotation]);
|
|
10336
10414
|
if (tags.length === 0) return null;
|
|
10337
10415
|
const gezeigt = tags.slice(0, TAGS_IN_KARTE);
|
|
10338
10416
|
const weitere = tags.length - gezeigt.length;
|
|
@@ -10393,15 +10471,15 @@ var PromptTagEditor = ({
|
|
|
10393
10471
|
onGespeichert
|
|
10394
10472
|
}) => {
|
|
10395
10473
|
const faehig = capabilitiesOf(port);
|
|
10396
|
-
const [eingabe, setEingabe] = (0,
|
|
10397
|
-
const [laeuft, setLaeuft] = (0,
|
|
10398
|
-
const [notiz, setNotiz] = (0,
|
|
10474
|
+
const [eingabe, setEingabe] = (0, import_react37.useState)("");
|
|
10475
|
+
const [laeuft, setLaeuft] = (0, import_react37.useState)(false);
|
|
10476
|
+
const [notiz, setNotiz] = (0, import_react37.useState)(null);
|
|
10399
10477
|
const [vokabelPhase] = usePortQuery(
|
|
10400
10478
|
() => port.tags ? port.tags.listTags() : Promise.resolve({ kind: "ok", value: [] }),
|
|
10401
10479
|
[port]
|
|
10402
10480
|
);
|
|
10403
10481
|
const vokabel = vokabelPhase.status === "ready" ? vokabelPhase.value : [];
|
|
10404
|
-
const speichern = (0,
|
|
10482
|
+
const speichern = (0, import_react37.useCallback)(async (labels) => {
|
|
10405
10483
|
if (!port.runs.setRunTags) return;
|
|
10406
10484
|
setLaeuft(true);
|
|
10407
10485
|
setNotiz(null);
|
|
@@ -10416,7 +10494,7 @@ var PromptTagEditor = ({
|
|
|
10416
10494
|
setLaeuft(false);
|
|
10417
10495
|
}
|
|
10418
10496
|
}, [port, runId, onGespeichert]);
|
|
10419
|
-
const hinzufuegen = (0,
|
|
10497
|
+
const hinzufuegen = (0, import_react37.useCallback)(() => {
|
|
10420
10498
|
const label = eingabe.trim();
|
|
10421
10499
|
if (label === "") return;
|
|
10422
10500
|
if (promptTags.some((t) => t.l === label)) {
|
|
@@ -10494,15 +10572,15 @@ var PromptTagEditor = ({
|
|
|
10494
10572
|
};
|
|
10495
10573
|
|
|
10496
10574
|
// src/features/szenen/areas/usePagedPortQuery.ts
|
|
10497
|
-
var
|
|
10575
|
+
var import_react38 = require("react");
|
|
10498
10576
|
function usePagedPortQuery(seite, abhaengigkeiten, limit = LOAD_MORE_PAGE_SIZE) {
|
|
10499
|
-
const [phase, setPhase] = (0,
|
|
10500
|
-
const [seiten, setSeiten] = (0,
|
|
10501
|
-
const [laedtMehr, setLaedtMehr] = (0,
|
|
10502
|
-
const [mehrFehler, setMehrFehler] = (0,
|
|
10503
|
-
const [token, setToken] = (0,
|
|
10504
|
-
const sequenz = (0,
|
|
10505
|
-
(0,
|
|
10577
|
+
const [phase, setPhase] = (0, import_react38.useState)({ status: "loading" });
|
|
10578
|
+
const [seiten, setSeiten] = (0, import_react38.useState)([]);
|
|
10579
|
+
const [laedtMehr, setLaedtMehr] = (0, import_react38.useState)(false);
|
|
10580
|
+
const [mehrFehler, setMehrFehler] = (0, import_react38.useState)(null);
|
|
10581
|
+
const [token, setToken] = (0, import_react38.useState)(0);
|
|
10582
|
+
const sequenz = (0, import_react38.useRef)(0);
|
|
10583
|
+
(0, import_react38.useEffect)(() => {
|
|
10506
10584
|
const meine = sequenz.current += 1;
|
|
10507
10585
|
let abgemeldet = false;
|
|
10508
10586
|
setPhase({ status: "loading" });
|
|
@@ -10535,7 +10613,7 @@ function usePagedPortQuery(seite, abhaengigkeiten, limit = LOAD_MORE_PAGE_SIZE)
|
|
|
10535
10613
|
}, [...abhaengigkeiten, token, limit]);
|
|
10536
10614
|
const letzte = seiten.length > 0 ? seiten[seiten.length - 1] : null;
|
|
10537
10615
|
const hasMore = letzte?.hasMore === true;
|
|
10538
|
-
const mehrLaden = (0,
|
|
10616
|
+
const mehrLaden = (0, import_react38.useCallback)(() => {
|
|
10539
10617
|
if (!letzte || !letzte.hasMore || laedtMehr) return;
|
|
10540
10618
|
const meine = sequenz.current;
|
|
10541
10619
|
const cursor = letzte.nextCursor;
|
|
@@ -10587,7 +10665,7 @@ var MehrLaden = ({ zustand, geladen, gesamt, testId }) => {
|
|
|
10587
10665
|
};
|
|
10588
10666
|
|
|
10589
10667
|
// src/features/szenen/areas/ElementDetail.tsx
|
|
10590
|
-
var
|
|
10668
|
+
var import_react51 = require("react");
|
|
10591
10669
|
|
|
10592
10670
|
// src/features/szenen/elementLabels.ts
|
|
10593
10671
|
var LABELS = {
|
|
@@ -10852,7 +10930,7 @@ function commonEnumOptions(zeiger) {
|
|
|
10852
10930
|
}
|
|
10853
10931
|
|
|
10854
10932
|
// src/features/szenen/areas/SchemaFields.tsx
|
|
10855
|
-
var
|
|
10933
|
+
var import_react40 = require("react");
|
|
10856
10934
|
|
|
10857
10935
|
// src/lib/szenen/versionDag.ts
|
|
10858
10936
|
var VersionDagError = class extends Error {
|
|
@@ -11163,7 +11241,7 @@ async function selectableTargetModels(queries, contextChain) {
|
|
|
11163
11241
|
}
|
|
11164
11242
|
|
|
11165
11243
|
// src/features/szenen/areas/Zielmodellwahl.tsx
|
|
11166
|
-
var
|
|
11244
|
+
var import_react39 = require("react");
|
|
11167
11245
|
var import_jsx_runtime32 = (
|
|
11168
11246
|
// Der Schluessel traegt den Key mit: Seit `unselectable`
|
|
11169
11247
|
// keybezogen ist, erzeugt ein Fork mit zwei verschieden
|
|
@@ -11238,12 +11316,12 @@ function slugVorschlag(name) {
|
|
|
11238
11316
|
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
11239
11317
|
}
|
|
11240
11318
|
var NeuesZielmodellFormular = ({ testId, laeuft, onAnlegen, onAbbrechen }) => {
|
|
11241
|
-
const [name, setName] = (0,
|
|
11242
|
-
const [key, setKey] = (0,
|
|
11243
|
-
const [provider, setProvider] = (0,
|
|
11244
|
-
const [sprache, setSprache] = (0,
|
|
11245
|
-
const [negativ, setNegativ] = (0,
|
|
11246
|
-
const [keyBer\u00FChrt, setKeyBer\u00FChrt] = (0,
|
|
11319
|
+
const [name, setName] = (0, import_react39.useState)("");
|
|
11320
|
+
const [key, setKey] = (0, import_react39.useState)("");
|
|
11321
|
+
const [provider, setProvider] = (0, import_react39.useState)("");
|
|
11322
|
+
const [sprache, setSprache] = (0, import_react39.useState)("en");
|
|
11323
|
+
const [negativ, setNegativ] = (0, import_react39.useState)(false);
|
|
11324
|
+
const [keyBer\u00FChrt, setKeyBer\u00FChrt] = (0, import_react39.useState)(false);
|
|
11247
11325
|
const wirksamerKey = keyBer\u00FChrt ? key : slugVorschlag(name);
|
|
11248
11326
|
const vollstaendig = name.trim() !== "" && wirksamerKey !== "" && provider.trim() !== "";
|
|
11249
11327
|
return /* @__PURE__ */ (0, import_jsx_runtime32.jsxs)("div", { className: "szenen-feld__zielmodell-neu", "data-testid": testId, children: [
|
|
@@ -11621,7 +11699,7 @@ var KetteNichtVerfuegbar = ({
|
|
|
11621
11699
|
return null;
|
|
11622
11700
|
};
|
|
11623
11701
|
var VersionsAuswahl = ({ port, kontextkette, zieltypen, testId, onPick, disabled }) => {
|
|
11624
|
-
const [elementId, setElementId] = (0,
|
|
11702
|
+
const [elementId, setElementId] = (0, import_react40.useState)("");
|
|
11625
11703
|
const typenSchluessel = zieltypen.join(",");
|
|
11626
11704
|
const kette = kontextkette.status === "ready" ? kontextkette.chain : null;
|
|
11627
11705
|
const kettenSchluessel = kette ? kette.join(">") : `!${kontextkette.status}`;
|
|
@@ -11734,7 +11812,7 @@ var VersionRefFeld = ({
|
|
|
11734
11812
|
zieltypen
|
|
11735
11813
|
}) => {
|
|
11736
11814
|
const aktuell = leseJson(wert);
|
|
11737
|
-
const [wechseln, setWechseln] = (0,
|
|
11815
|
+
const [wechseln, setWechseln] = (0, import_react40.useState)(false);
|
|
11738
11816
|
const gesetzt = istVersionRef(aktuell) ? aktuell : null;
|
|
11739
11817
|
return /* @__PURE__ */ (0, import_jsx_runtime33.jsxs)("div", { className: "szenen-feld__ref", children: [
|
|
11740
11818
|
gesetzt ? /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(RefAnzeige, { port, ref_: gesetzt, testId: `ref-${feld.path}-aktuell` }) : /* @__PURE__ */ (0, import_jsx_runtime33.jsx)("p", { "data-testid": `ref-${feld.path}-leer`, children: "Keine Referenz gesetzt." }),
|
|
@@ -11780,7 +11858,7 @@ var VersionRefFeld = ({
|
|
|
11780
11858
|
};
|
|
11781
11859
|
var VersionRefListeFeld = ({ port, kontextkette, feld, wert, onChange, readOnly, zieltypen, mitRolle, mitGewicht }) => {
|
|
11782
11860
|
const liste = leseListe(wert);
|
|
11783
|
-
const rollen = (0,
|
|
11861
|
+
const rollen = (0, import_react40.useMemo)(
|
|
11784
11862
|
() => mitRolle === "styleRole" ? commonEnumOptions("styleRole") : [],
|
|
11785
11863
|
[mitRolle]
|
|
11786
11864
|
);
|
|
@@ -12024,7 +12102,7 @@ var KameraFeld = ({
|
|
|
12024
12102
|
() => setupVersionId ? port.elements.getVersion(setupVersionId) : Promise.resolve({ kind: "ok", value: null }),
|
|
12025
12103
|
[port, setupVersionId]
|
|
12026
12104
|
);
|
|
12027
|
-
const kameras = (0,
|
|
12105
|
+
const kameras = (0, import_react40.useMemo)(() => {
|
|
12028
12106
|
if (phase.status !== "ready" || !phase.value) return [];
|
|
12029
12107
|
const cameras = valueAtPath(phase.value.payload, "camera_rig.cameras");
|
|
12030
12108
|
if (!Array.isArray(cameras)) return [];
|
|
@@ -12158,7 +12236,7 @@ var StringListeFeld = ({ feld, wert, onChange, readOnly }) => {
|
|
|
12158
12236
|
};
|
|
12159
12237
|
var MediaRefListeFeld = ({ feld, wert, onChange, readOnly }) => {
|
|
12160
12238
|
const liste = leseListe(wert);
|
|
12161
|
-
const rollen = (0,
|
|
12239
|
+
const rollen = (0, import_react40.useMemo)(() => commonEnumOptions("$defs/mediaRef/properties/role"), []);
|
|
12162
12240
|
if (liste === null) {
|
|
12163
12241
|
return /* @__PURE__ */ (0, import_jsx_runtime33.jsx)(
|
|
12164
12242
|
"textarea",
|
|
@@ -12593,10 +12671,10 @@ var SchemaFieldRow = (props) => {
|
|
|
12593
12671
|
};
|
|
12594
12672
|
|
|
12595
12673
|
// src/features/szenen/areas/SceneEditor.tsx
|
|
12596
|
-
var
|
|
12674
|
+
var import_react42 = require("react");
|
|
12597
12675
|
|
|
12598
12676
|
// src/features/szenen/areas/ReferencePicker.tsx
|
|
12599
|
-
var
|
|
12677
|
+
var import_react41 = require("react");
|
|
12600
12678
|
var import_jsx_runtime34 = require("react/jsx-runtime");
|
|
12601
12679
|
var ReferencePicker = ({
|
|
12602
12680
|
port,
|
|
@@ -12606,7 +12684,7 @@ var ReferencePicker = ({
|
|
|
12606
12684
|
testId,
|
|
12607
12685
|
onPick
|
|
12608
12686
|
}) => {
|
|
12609
|
-
const [elementId, setElementId] = (0,
|
|
12687
|
+
const [elementId, setElementId] = (0, import_react41.useState)("");
|
|
12610
12688
|
const [liste] = usePortQuery(
|
|
12611
12689
|
() => port.elements.listElements({ contextId, type }),
|
|
12612
12690
|
[port, contextId, type]
|
|
@@ -12731,11 +12809,11 @@ var SceneEditor = ({
|
|
|
12731
12809
|
}) => {
|
|
12732
12810
|
const nodes = payload.nodes ?? [];
|
|
12733
12811
|
const relations = payload.relations ?? [];
|
|
12734
|
-
const [neuerNodeOffen, setNeuerNodeOffen] = (0,
|
|
12735
|
-
const [neueBeschreibung, setNeueBeschreibung] = (0,
|
|
12736
|
-
const [neuerTyp, setNeuerTyp] = (0,
|
|
12737
|
-
const [refZielNode, setRefZielNode] = (0,
|
|
12738
|
-
const [neueBeziehungOffen, setNeueBeziehungOffen] = (0,
|
|
12812
|
+
const [neuerNodeOffen, setNeuerNodeOffen] = (0, import_react42.useState)(false);
|
|
12813
|
+
const [neueBeschreibung, setNeueBeschreibung] = (0, import_react42.useState)("");
|
|
12814
|
+
const [neuerTyp, setNeuerTyp] = (0, import_react42.useState)("subject");
|
|
12815
|
+
const [refZielNode, setRefZielNode] = (0, import_react42.useState)(null);
|
|
12816
|
+
const [neueBeziehungOffen, setNeueBeziehungOffen] = (0, import_react42.useState)(false);
|
|
12739
12817
|
const schreibe = (next) => {
|
|
12740
12818
|
if (readOnly) return;
|
|
12741
12819
|
onChange(next);
|
|
@@ -12957,9 +13035,9 @@ var SceneEditor = ({
|
|
|
12957
13035
|
);
|
|
12958
13036
|
};
|
|
12959
13037
|
var BeziehungAnlegen = ({ nodes, onAdd, onAbbrechen }) => {
|
|
12960
|
-
const [von, setVon] = (0,
|
|
12961
|
-
const [typ, setTyp] = (0,
|
|
12962
|
-
const [nach, setNach] = (0,
|
|
13038
|
+
const [von, setVon] = (0, import_react42.useState)(nodes[0].node_id);
|
|
13039
|
+
const [typ, setTyp] = (0, import_react42.useState)("left_of");
|
|
13040
|
+
const [nach, setNach] = (0, import_react42.useState)(nodes[1].node_id);
|
|
12963
13041
|
return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "szenen-scene-editor__add-relation", children: [
|
|
12964
13042
|
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("select", { "data-testid": "relation-von", value: von, onChange: (e) => setVon(e.target.value), children: nodes.map((n) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("option", { value: n.node_id, children: n.node_id }, n.node_id)) }),
|
|
12965
13043
|
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("select", { "data-testid": "relation-neu-typ", value: typ, onChange: (e) => setTyp(e.target.value), children: RELATION_TYPES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("option", { value: t, children: RELATION_TYPE_LABELS[t] }, t)) }),
|
|
@@ -12980,7 +13058,7 @@ var BeziehungAnlegen = ({ nodes, onAdd, onAbbrechen }) => {
|
|
|
12980
13058
|
};
|
|
12981
13059
|
|
|
12982
13060
|
// src/features/szenen/areas/CapabilityPanel.tsx
|
|
12983
|
-
var
|
|
13061
|
+
var import_react43 = require("react");
|
|
12984
13062
|
|
|
12985
13063
|
// src/lib/szenen/placement.ts
|
|
12986
13064
|
var isObject5 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -14121,9 +14199,9 @@ var ERKLAERUNG = {
|
|
|
14121
14199
|
unknown: "Die Pr\xFCfung konnte nicht laufen. Das ist keine Absage \u2014 die Frage ist offen."
|
|
14122
14200
|
};
|
|
14123
14201
|
var CapabilityPanel = ({ port, elementId, setupPayload }) => {
|
|
14124
|
-
const [bericht, setBericht] = (0,
|
|
14125
|
-
const [fehler5, setFehler] = (0,
|
|
14126
|
-
(0,
|
|
14202
|
+
const [bericht, setBericht] = (0, import_react43.useState)(null);
|
|
14203
|
+
const [fehler5, setFehler] = (0, import_react43.useState)(null);
|
|
14204
|
+
(0, import_react43.useEffect)(() => {
|
|
14127
14205
|
let abgemeldet = false;
|
|
14128
14206
|
setBericht(null);
|
|
14129
14207
|
setFehler(null);
|
|
@@ -14179,7 +14257,7 @@ var CapabilityPanel = ({ port, elementId, setupPayload }) => {
|
|
|
14179
14257
|
};
|
|
14180
14258
|
|
|
14181
14259
|
// src/features/szenen/areas/PromptStartAction.tsx
|
|
14182
|
-
var
|
|
14260
|
+
var import_react44 = require("react");
|
|
14183
14261
|
var import_jsx_runtime37 = require("react/jsx-runtime");
|
|
14184
14262
|
var PromptStartAction = ({
|
|
14185
14263
|
port,
|
|
@@ -14188,10 +14266,10 @@ var PromptStartAction = ({
|
|
|
14188
14266
|
blocker = null,
|
|
14189
14267
|
onLauf
|
|
14190
14268
|
}) => {
|
|
14191
|
-
const [lauf, setLauf] = (0,
|
|
14192
|
-
const [meldung, setMeldung] = (0,
|
|
14193
|
-
const [ausgang, setAusgang] = (0,
|
|
14194
|
-
const [laeuft, setLaeuft] = (0,
|
|
14269
|
+
const [lauf, setLauf] = (0, import_react44.useState)(null);
|
|
14270
|
+
const [meldung, setMeldung] = (0, import_react44.useState)(null);
|
|
14271
|
+
const [ausgang, setAusgang] = (0, import_react44.useState)(null);
|
|
14272
|
+
const [laeuft, setLaeuft] = (0, import_react44.useState)(false);
|
|
14195
14273
|
const runs = port.runs;
|
|
14196
14274
|
const ausKette = source.kind === "draft_chain";
|
|
14197
14275
|
const id = (name) => ausKette ? `kette-${name}` : `szene-${name}`;
|
|
@@ -14266,7 +14344,7 @@ var PromptStartAction = ({
|
|
|
14266
14344
|
};
|
|
14267
14345
|
|
|
14268
14346
|
// src/features/szenen/areas/ManuellerLaufAction.tsx
|
|
14269
|
-
var
|
|
14347
|
+
var import_react45 = require("react");
|
|
14270
14348
|
var import_jsx_runtime38 = require("react/jsx-runtime");
|
|
14271
14349
|
var OHNE_ZIELMODELL = "";
|
|
14272
14350
|
var OHNE_URSPRUNG = "";
|
|
@@ -14282,15 +14360,15 @@ var ManuellerLaufAction = ({
|
|
|
14282
14360
|
author,
|
|
14283
14361
|
onLauf
|
|
14284
14362
|
}) => {
|
|
14285
|
-
const [offen, setOffen] = (0,
|
|
14286
|
-
const [prompttext, setPrompttext] = (0,
|
|
14287
|
-
const [zielmodell, setZielmodell] = (0,
|
|
14288
|
-
const [notiz, setNotiz] = (0,
|
|
14289
|
-
const [ursprung, setUrsprung] = (0,
|
|
14290
|
-
const [laeuft, setLaeuft] = (0,
|
|
14291
|
-
const [meldung, setMeldung] = (0,
|
|
14292
|
-
const [ausgang, setAusgang] = (0,
|
|
14293
|
-
const [angelegt, setAngelegt] = (0,
|
|
14363
|
+
const [offen, setOffen] = (0, import_react45.useState)(false);
|
|
14364
|
+
const [prompttext, setPrompttext] = (0, import_react45.useState)("");
|
|
14365
|
+
const [zielmodell, setZielmodell] = (0, import_react45.useState)(OHNE_ZIELMODELL);
|
|
14366
|
+
const [notiz, setNotiz] = (0, import_react45.useState)("");
|
|
14367
|
+
const [ursprung, setUrsprung] = (0, import_react45.useState)(OHNE_URSPRUNG);
|
|
14368
|
+
const [laeuft, setLaeuft] = (0, import_react45.useState)(false);
|
|
14369
|
+
const [meldung, setMeldung] = (0, import_react45.useState)(null);
|
|
14370
|
+
const [ausgang, setAusgang] = (0, import_react45.useState)(null);
|
|
14371
|
+
const [angelegt, setAngelegt] = (0, import_react45.useState)(null);
|
|
14294
14372
|
const [modellePhase] = useZielmodelle(port, kontextkette);
|
|
14295
14373
|
const [laeufePhase] = usePortQuery(
|
|
14296
14374
|
() => port.runs.listRunsForScene(sceneElementId),
|
|
@@ -14575,7 +14653,7 @@ var VersionNavigator = ({
|
|
|
14575
14653
|
};
|
|
14576
14654
|
|
|
14577
14655
|
// src/features/szenen/areas/RunsPanel.tsx
|
|
14578
|
-
var
|
|
14656
|
+
var import_react50 = require("react");
|
|
14579
14657
|
|
|
14580
14658
|
// src/lib/szenen/manualPromptRun.ts
|
|
14581
14659
|
var MANUAL_PROVIDER = "manual";
|
|
@@ -14675,16 +14753,16 @@ function manualEntryOf(run) {
|
|
|
14675
14753
|
}
|
|
14676
14754
|
|
|
14677
14755
|
// src/features/szenen/areas/useLaufTitel.ts
|
|
14678
|
-
var
|
|
14756
|
+
var import_react46 = require("react");
|
|
14679
14757
|
function useLaufTitel(port, laeufe, bekannt = {}) {
|
|
14680
|
-
const [titelJeElement2, setTitelJeElement] = (0,
|
|
14758
|
+
const [titelJeElement2, setTitelJeElement] = (0, import_react46.useState)(
|
|
14681
14759
|
() => /* @__PURE__ */ new Map()
|
|
14682
14760
|
);
|
|
14683
14761
|
const gebraucht = [...new Set(
|
|
14684
14762
|
laeufe.filter((lauf) => bekannt[lauf.run_id] === void 0).map(namingElementOf).filter((id) => id !== null)
|
|
14685
14763
|
)].sort();
|
|
14686
14764
|
const schluessel = gebraucht.join(",");
|
|
14687
|
-
(0,
|
|
14765
|
+
(0, import_react46.useEffect)(() => {
|
|
14688
14766
|
let abgemeldet = false;
|
|
14689
14767
|
const offen = gebraucht.filter((id) => !titelJeElement2.has(id));
|
|
14690
14768
|
if (offen.length === 0) return void 0;
|
|
@@ -14724,7 +14802,7 @@ function useLaufTitel(port, laeufe, bekannt = {}) {
|
|
|
14724
14802
|
}
|
|
14725
14803
|
|
|
14726
14804
|
// src/features/szenen/areas/RunImagesPanel.tsx
|
|
14727
|
-
var
|
|
14805
|
+
var import_react49 = __toESM(require("react"));
|
|
14728
14806
|
|
|
14729
14807
|
// src/lib/szenen/seedListe.ts
|
|
14730
14808
|
var DEFAULT_SEED_VORLAGE = {
|
|
@@ -14959,7 +15037,7 @@ function schlageZipModellVor(dateiname, modelle) {
|
|
|
14959
15037
|
}
|
|
14960
15038
|
|
|
14961
15039
|
// src/features/szenen/areas/ZipModellDialog.tsx
|
|
14962
|
-
var
|
|
15040
|
+
var import_react47 = require("react");
|
|
14963
15041
|
var import_jsx_runtime40 = require("react/jsx-runtime");
|
|
14964
15042
|
var VERTRAUEN_TEXT = {
|
|
14965
15043
|
hoch: "hoch \u2014 der Dateiname nennt dieses Modell praktisch w\xF6rtlich",
|
|
@@ -14968,8 +15046,8 @@ var VERTRAUEN_TEXT = {
|
|
|
14968
15046
|
};
|
|
14969
15047
|
var ZipModellDialog = ({ testId, dateiname, bildAnzahl, vorschlag, laeuft, onUebernehmen, onImportieren, onSchliessen }) => {
|
|
14970
15048
|
const auswahlbar = vorschlag.art === "treffer" ? [vorschlag.beste, ...vorschlag.alternativen] : [vorschlag.beste];
|
|
14971
|
-
const [gewaehlt, setGewaehlt] = (0,
|
|
14972
|
-
(0,
|
|
15049
|
+
const [gewaehlt, setGewaehlt] = (0, import_react47.useState)(vorschlag.beste.key);
|
|
15050
|
+
(0, import_react47.useEffect)(() => {
|
|
14973
15051
|
const taste = (e) => {
|
|
14974
15052
|
if (e.key === "Escape") onSchliessen();
|
|
14975
15053
|
};
|
|
@@ -15088,14 +15166,14 @@ var ZipModellDialog = ({ testId, dateiname, bildAnzahl, vorschlag, laeuft, onUeb
|
|
|
15088
15166
|
};
|
|
15089
15167
|
|
|
15090
15168
|
// src/features/szenen/areas/useBildQuellen.ts
|
|
15091
|
-
var
|
|
15169
|
+
var import_react48 = require("react");
|
|
15092
15170
|
function useBildQuellen(port, bilder) {
|
|
15093
|
-
const [stand, setStand] = (0,
|
|
15094
|
-
const gehalten = (0,
|
|
15095
|
-
const gescheitert = (0,
|
|
15096
|
-
const [versuch, setVersuch] = (0,
|
|
15171
|
+
const [stand, setStand] = (0, import_react48.useState)(() => ({ quellen: /* @__PURE__ */ new Map(), fehler: /* @__PURE__ */ new Map() }));
|
|
15172
|
+
const gehalten = (0, import_react48.useRef)(/* @__PURE__ */ new Map());
|
|
15173
|
+
const gescheitert = (0, import_react48.useRef)(/* @__PURE__ */ new Map());
|
|
15174
|
+
const [versuch, setVersuch] = (0, import_react48.useState)(0);
|
|
15097
15175
|
const schluessel = bilder.map((b) => `${b.media_ref}|${b.mime_type ?? ""}`).join(" ");
|
|
15098
|
-
(0,
|
|
15176
|
+
(0, import_react48.useEffect)(() => {
|
|
15099
15177
|
const bestand = port.imageLibrary;
|
|
15100
15178
|
if (!bestand) return void 0;
|
|
15101
15179
|
let abgebrochen = false;
|
|
@@ -15156,11 +15234,11 @@ var AUFTRAG_TEXT = {
|
|
|
15156
15234
|
};
|
|
15157
15235
|
var RunImagesPanel = ({ port, lauf }) => {
|
|
15158
15236
|
const faehig = capabilitiesOf(port);
|
|
15159
|
-
const [nachladen, setNachladen] = (0,
|
|
15237
|
+
const [nachladen, setNachladen] = (0, import_react49.useState)(0);
|
|
15160
15238
|
const neuLesen = () => setNachladen((n) => n + 1);
|
|
15161
|
-
const [erzeugenOffen, setErzeugenOffen] = (0,
|
|
15162
|
-
const [zuordnenOffen, setZuordnenOffen] = (0,
|
|
15163
|
-
const [chatAuftraegeOffen, setChatAuftraegeOffen] = (0,
|
|
15239
|
+
const [erzeugenOffen, setErzeugenOffen] = (0, import_react49.useState)(null);
|
|
15240
|
+
const [zuordnenOffen, setZuordnenOffen] = (0, import_react49.useState)(null);
|
|
15241
|
+
const [chatAuftraegeOffen, setChatAuftraegeOffen] = (0, import_react49.useState)(null);
|
|
15164
15242
|
const quelle = sourceOf(lauf);
|
|
15165
15243
|
const [wahlPhase] = usePortQuery(
|
|
15166
15244
|
() => port.imageGeneration ? port.imageGeneration.imageModelChoiceForRun(lauf.run_id) : Promise.resolve({ kind: "ok", value: null }),
|
|
@@ -15562,7 +15640,7 @@ var BatchKachel = ({ port, batch }) => {
|
|
|
15562
15640
|
/* @__PURE__ */ (0, import_jsx_runtime41.jsxs)("span", { children: [
|
|
15563
15641
|
"Batches",
|
|
15564
15642
|
" ",
|
|
15565
|
-
batch.origins.length > 0 ? batch.origins.map((h, i) => /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(
|
|
15643
|
+
batch.origins.length > 0 ? batch.origins.map((h, i) => /* @__PURE__ */ (0, import_jsx_runtime41.jsxs)(import_react49.default.Fragment, { children: [
|
|
15566
15644
|
i > 0 ? ", " : "",
|
|
15567
15645
|
/* @__PURE__ */ (0, import_jsx_runtime41.jsx)("code", { children: h.generation_batch_id }),
|
|
15568
15646
|
` (${HERKUNFT_KURZ[h.source_kind]})`
|
|
@@ -15591,11 +15669,11 @@ var ProfilBefunde = ({ wahl, runId }) => {
|
|
|
15591
15669
|
] });
|
|
15592
15670
|
};
|
|
15593
15671
|
var ErzeugenAktion = ({ port, lauf, wahl, wahlFehler, onFertig }) => {
|
|
15594
|
-
const [modellWahl, setModellWahl] = (0,
|
|
15595
|
-
const [anzahlText, setAnzahlText] = (0,
|
|
15596
|
-
const [seitenverhaeltnis, setSeitenverhaeltnis] = (0,
|
|
15597
|
-
const [laeuft, setLaeuft] = (0,
|
|
15598
|
-
const [notiz, setNotiz] = (0,
|
|
15672
|
+
const [modellWahl, setModellWahl] = (0, import_react49.useState)(null);
|
|
15673
|
+
const [anzahlText, setAnzahlText] = (0, import_react49.useState)("1");
|
|
15674
|
+
const [seitenverhaeltnis, setSeitenverhaeltnis] = (0, import_react49.useState)("");
|
|
15675
|
+
const [laeuft, setLaeuft] = (0, import_react49.useState)(false);
|
|
15676
|
+
const [notiz, setNotiz] = (0, import_react49.useState)(null);
|
|
15599
15677
|
if (!port.imageGeneration) {
|
|
15600
15678
|
return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)("p", { "data-testid": `erzeugen-unsupported-${lauf.run_id}`, children: "Dieser Host erzeugt keine Bilder \u2014 entweder fehlt der Providerzugang oder der gesicherte Speicherweg. Externe Bilder lassen sich trotzdem zuordnen." });
|
|
15601
15679
|
}
|
|
@@ -15772,26 +15850,26 @@ var ErzeugenAktion = ({ port, lauf, wahl, wahlFehler, onFertig }) => {
|
|
|
15772
15850
|
};
|
|
15773
15851
|
var DEFAULT_VORLAGE_KEY = "__default__";
|
|
15774
15852
|
var ZuordnenAktion = ({ port, lauf, onFertig }) => {
|
|
15775
|
-
const [dateien, setDateien] = (0,
|
|
15776
|
-
const [seeds, setSeeds] = (0,
|
|
15777
|
-
const [zip, setZip] = (0,
|
|
15778
|
-
const [zipLaedt, setZipLaedt] = (0,
|
|
15779
|
-
const [seedListe, setSeedListe] = (0,
|
|
15780
|
-
const [vorlagen, setVorlagen] = (0,
|
|
15781
|
-
const [vorlageKey, setVorlageKey] = (0,
|
|
15782
|
-
const [vorlagenStand, setVorlagenStand] = (0,
|
|
15783
|
-
const [vorlageBenennen, setVorlageBenennen] = (0,
|
|
15784
|
-
const [vorlageName, setVorlageName] = (0,
|
|
15785
|
-
const [modell, setModell] = (0,
|
|
15786
|
-
const [modellNeu, setModellNeu] = (0,
|
|
15787
|
-
const [modellLaeuft, setModellLaeuft] = (0,
|
|
15788
|
-
const [vorschlagOffen, setVorschlagOffen] = (0,
|
|
15789
|
-
const [vorschlag, setVorschlag] = (0,
|
|
15790
|
-
const [itemId, setItemId] = (0,
|
|
15791
|
-
const [laeuft, setLaeuft] = (0,
|
|
15792
|
-
const [notiz, setNotiz] = (0,
|
|
15793
|
-
const dateifeld = (0,
|
|
15794
|
-
const auswahlMarke = (0,
|
|
15853
|
+
const [dateien, setDateien] = (0, import_react49.useState)([]);
|
|
15854
|
+
const [seeds, setSeeds] = (0, import_react49.useState)({});
|
|
15855
|
+
const [zip, setZip] = (0, import_react49.useState)(null);
|
|
15856
|
+
const [zipLaedt, setZipLaedt] = (0, import_react49.useState)(false);
|
|
15857
|
+
const [seedListe, setSeedListe] = (0, import_react49.useState)("");
|
|
15858
|
+
const [vorlagen, setVorlagen] = (0, import_react49.useState)([DEFAULT_SEED_VORLAGE]);
|
|
15859
|
+
const [vorlageKey, setVorlageKey] = (0, import_react49.useState)(DEFAULT_VORLAGE_KEY);
|
|
15860
|
+
const [vorlagenStand, setVorlagenStand] = (0, import_react49.useState)(0);
|
|
15861
|
+
const [vorlageBenennen, setVorlageBenennen] = (0, import_react49.useState)(false);
|
|
15862
|
+
const [vorlageName, setVorlageName] = (0, import_react49.useState)("");
|
|
15863
|
+
const [modell, setModell] = (0, import_react49.useState)("");
|
|
15864
|
+
const [modellNeu, setModellNeu] = (0, import_react49.useState)(false);
|
|
15865
|
+
const [modellLaeuft, setModellLaeuft] = (0, import_react49.useState)(false);
|
|
15866
|
+
const [vorschlagOffen, setVorschlagOffen] = (0, import_react49.useState)(null);
|
|
15867
|
+
const [vorschlag, setVorschlag] = (0, import_react49.useState)(null);
|
|
15868
|
+
const [itemId, setItemId] = (0, import_react49.useState)("");
|
|
15869
|
+
const [laeuft, setLaeuft] = (0, import_react49.useState)(false);
|
|
15870
|
+
const [notiz, setNotiz] = (0, import_react49.useState)(null);
|
|
15871
|
+
const dateifeld = (0, import_react49.useRef)(null);
|
|
15872
|
+
const auswahlMarke = (0, import_react49.useRef)(0);
|
|
15795
15873
|
const eingefroreneKette = lauf.input_payload?.context_chain;
|
|
15796
15874
|
const kontextkette = Array.isArray(eingefroreneKette) && eingefroreneKette.length > 0 ? { status: "ready", chain: eingefroreneKette } : {
|
|
15797
15875
|
status: "error",
|
|
@@ -15800,11 +15878,11 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
|
|
|
15800
15878
|
const [modellePhase, modelleNeuLesen] = useZielmodelle(port, kontextkette);
|
|
15801
15879
|
const modelle = modellePhase.status === "ready" ? modellePhase.value.selectable : [];
|
|
15802
15880
|
const modelleGesperrt = modellePhase.status === "ready" ? modellePhase.value.unselectable : [];
|
|
15803
|
-
const modelleRef = (0,
|
|
15881
|
+
const modelleRef = (0, import_react49.useRef)(modelle);
|
|
15804
15882
|
modelleRef.current = modelle;
|
|
15805
15883
|
const anlageKontext = kontextkette.status === "ready" ? kontextkette.chain[0] ?? null : null;
|
|
15806
15884
|
const zipAktiv = zip !== null;
|
|
15807
|
-
(0,
|
|
15885
|
+
(0, import_react49.useEffect)(() => {
|
|
15808
15886
|
if (!zipAktiv) return;
|
|
15809
15887
|
let abgebrochen = false;
|
|
15810
15888
|
void (async () => {
|
|
@@ -15819,7 +15897,7 @@ var ZuordnenAktion = ({ port, lauf, onFertig }) => {
|
|
|
15819
15897
|
};
|
|
15820
15898
|
}, [zipAktiv, port, vorlagenStand]);
|
|
15821
15899
|
const vorschlagPhase = modellePhase.status;
|
|
15822
|
-
(0,
|
|
15900
|
+
(0, import_react49.useEffect)(() => {
|
|
15823
15901
|
if (!vorschlagOffen) return;
|
|
15824
15902
|
if (vorschlagPhase === "loading") return;
|
|
15825
15903
|
setVorschlagOffen(null);
|
|
@@ -16370,8 +16448,8 @@ var ChatAuftraegeAktion = ({
|
|
|
16370
16448
|
laedt,
|
|
16371
16449
|
onFertig
|
|
16372
16450
|
}) => {
|
|
16373
|
-
const [laufend, setLaufend] = (0,
|
|
16374
|
-
const [notiz, setNotiz] = (0,
|
|
16451
|
+
const [laufend, setLaufend] = (0, import_react49.useState)(null);
|
|
16452
|
+
const [notiz, setNotiz] = (0, import_react49.useState)(null);
|
|
16375
16453
|
async function uebernehmen(jobId) {
|
|
16376
16454
|
if (!port.imageJobs) return;
|
|
16377
16455
|
setLaufend(jobId);
|
|
@@ -16455,16 +16533,16 @@ var RunsPanel = ({
|
|
|
16455
16533
|
contextId,
|
|
16456
16534
|
initialOpenRunId = null
|
|
16457
16535
|
}) => {
|
|
16458
|
-
const [anweisung, setAnweisung] = (0,
|
|
16459
|
-
const [laeuft, setLaeuft] = (0,
|
|
16460
|
-
const [notiz, setNotiz] = (0,
|
|
16461
|
-
const [nachladen, setNachladen] = (0,
|
|
16536
|
+
const [anweisung, setAnweisung] = (0, import_react50.useState)("");
|
|
16537
|
+
const [laeuft, setLaeuft] = (0, import_react50.useState)(false);
|
|
16538
|
+
const [notiz, setNotiz] = (0, import_react50.useState)(null);
|
|
16539
|
+
const [nachladen, setNachladen] = (0, import_react50.useState)(0);
|
|
16462
16540
|
const faehig = capabilitiesOf(port);
|
|
16463
16541
|
const quellSchluessel = source.kind === "published" ? source.shotVersionId : source.kind === "draft_chain" ? source.chainId : source.sceneElementId;
|
|
16464
|
-
const aktuelleQuelle = (0,
|
|
16542
|
+
const aktuelleQuelle = (0, import_react50.useRef)({ kind: source.kind, id: quellSchluessel });
|
|
16465
16543
|
aktuelleQuelle.current = { kind: source.kind, id: quellSchluessel };
|
|
16466
|
-
const laden = (0,
|
|
16467
|
-
const seite = (0,
|
|
16544
|
+
const laden = (0, import_react50.useMemo)(() => runsPageLoader(port), [port]);
|
|
16545
|
+
const seite = (0, import_react50.useCallback)(
|
|
16468
16546
|
(after, limit) => laden({
|
|
16469
16547
|
source: source.kind === "published" ? { kind: "published", shotVersionId: source.shotVersionId } : source.kind === "draft_chain" ? { kind: "draft_chain", chainId: source.chainId } : { kind: "scene", sceneElementId: source.sceneElementId },
|
|
16470
16548
|
filter: { status: "all" },
|
|
@@ -16496,26 +16574,26 @@ var RunsPanel = ({
|
|
|
16496
16574
|
setNachladen((n) => n + 1);
|
|
16497
16575
|
neuLaden();
|
|
16498
16576
|
};
|
|
16499
|
-
const laeufe = (0,
|
|
16500
|
-
const runden = (0,
|
|
16577
|
+
const laeufe = (0, import_react50.useMemo)(() => liste.seiten.flatMap((s) => s.runs), [liste.seiten]);
|
|
16578
|
+
const runden = (0, import_react50.useMemo)(() => {
|
|
16501
16579
|
const alle = {};
|
|
16502
16580
|
for (const s of liste.seiten) Object.assign(alle, s.rounds);
|
|
16503
16581
|
return alle;
|
|
16504
16582
|
}, [liste.seiten]);
|
|
16505
16583
|
const gesamt = phase.status === "ready" ? phase.value.total : laeufe.length;
|
|
16506
|
-
const seitenTitel = (0,
|
|
16584
|
+
const seitenTitel = (0, import_react50.useMemo)(() => {
|
|
16507
16585
|
const alle = {};
|
|
16508
16586
|
for (const s of liste.seiten) Object.assign(alle, s.titles ?? {});
|
|
16509
16587
|
return alle;
|
|
16510
16588
|
}, [liste.seiten]);
|
|
16511
16589
|
const titel = useLaufTitel(port, laeufe, seitenTitel);
|
|
16512
|
-
const [gewaehlteRunde, setGewaehlteRunde] = (0,
|
|
16590
|
+
const [gewaehlteRunde, setGewaehlteRunde] = (0, import_react50.useState)(void 0);
|
|
16513
16591
|
const neueste = laeufe.length > 0 ? laeufe[0].run_id : null;
|
|
16514
|
-
const startQuelle = (0,
|
|
16592
|
+
const startQuelle = (0, import_react50.useRef)(quellSchluessel);
|
|
16515
16593
|
const angesteuert = initialOpenRunId && startQuelle.current === quellSchluessel ? initialOpenRunId : null;
|
|
16516
16594
|
const vorauswahl = angesteuert ? laeufe.some((lauf) => lauf.run_id === angesteuert) ? angesteuert : null : neueste;
|
|
16517
16595
|
const offeneRunde = gewaehlteRunde === void 0 ? vorauswahl : gewaehlteRunde;
|
|
16518
|
-
(0,
|
|
16596
|
+
(0, import_react50.useEffect)(() => {
|
|
16519
16597
|
setGewaehlteRunde(void 0);
|
|
16520
16598
|
setAnweisung("");
|
|
16521
16599
|
setNotiz(null);
|
|
@@ -16585,8 +16663,8 @@ var STATUS_TEXT = {
|
|
|
16585
16663
|
};
|
|
16586
16664
|
var LANGER_TEXT = 240;
|
|
16587
16665
|
var PromptAbschnitt = ({ titel, text, ersatz, testId, kopierTestId, className }) => {
|
|
16588
|
-
const [kopiert, setKopiert] = (0,
|
|
16589
|
-
const absatz = (0,
|
|
16666
|
+
const [kopiert, setKopiert] = (0, import_react50.useState)(false);
|
|
16667
|
+
const absatz = (0, import_react50.useRef)(null);
|
|
16590
16668
|
async function kopieren() {
|
|
16591
16669
|
if (text === null) return;
|
|
16592
16670
|
try {
|
|
@@ -16623,7 +16701,7 @@ var PromptAbschnitt = ({ titel, text, ersatz, testId, kopierTestId, className })
|
|
|
16623
16701
|
] });
|
|
16624
16702
|
};
|
|
16625
16703
|
var Sekundaertext = ({ titel, text, testId }) => {
|
|
16626
|
-
const [ganz, setGanz] = (0,
|
|
16704
|
+
const [ganz, setGanz] = (0, import_react50.useState)(false);
|
|
16627
16705
|
const lang = text.length > LANGER_TEXT;
|
|
16628
16706
|
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("section", { className: "szenen-runs__sekundaer", children: [
|
|
16629
16707
|
/* @__PURE__ */ (0, import_jsx_runtime42.jsx)("h5", { children: titel }),
|
|
@@ -16642,7 +16720,7 @@ var Sekundaertext = ({ titel, text, testId }) => {
|
|
|
16642
16720
|
};
|
|
16643
16721
|
var LISTE_ANFANG = 3;
|
|
16644
16722
|
var SekundaerListe = ({ titel, eintraege, testId, className }) => {
|
|
16645
|
-
const [alle, setAlle] = (0,
|
|
16723
|
+
const [alle, setAlle] = (0, import_react50.useState)(false);
|
|
16646
16724
|
const lang = eintraege.length > LISTE_ANFANG;
|
|
16647
16725
|
const gezeigt = lang && !alle ? eintraege.slice(0, LISTE_ANFANG) : eintraege;
|
|
16648
16726
|
return /* @__PURE__ */ (0, import_jsx_runtime42.jsxs)("section", { className: "szenen-runs__sekundaer", children: [
|
|
@@ -16661,9 +16739,9 @@ var SekundaerListe = ({ titel, eintraege, testId, className }) => {
|
|
|
16661
16739
|
] });
|
|
16662
16740
|
};
|
|
16663
16741
|
var RundenEintrag = ({ port, lauf, runde, titel, offen, onUmschalten, itemMoeglich, onItemGespeichert }) => {
|
|
16664
|
-
const [notiz, setNotiz] = (0,
|
|
16665
|
-
const [laeuft, setLaeuft] = (0,
|
|
16666
|
-
const [nachladen, setNachladen] = (0,
|
|
16742
|
+
const [notiz, setNotiz] = (0, import_react50.useState)(null);
|
|
16743
|
+
const [laeuft, setLaeuft] = (0, import_react50.useState)(false);
|
|
16744
|
+
const [nachladen, setNachladen] = (0, import_react50.useState)(0);
|
|
16667
16745
|
const [itemPhase] = usePortQuery(
|
|
16668
16746
|
() => offen && port.items ? port.items.getItemForRun(lauf.run_id) : Promise.resolve({ kind: "ok", value: null }),
|
|
16669
16747
|
[port, lauf.run_id, nachladen, offen]
|
|
@@ -16873,43 +16951,43 @@ var ElementDetail = ({
|
|
|
16873
16951
|
onChanged,
|
|
16874
16952
|
initialOpenRunId = null
|
|
16875
16953
|
}) => {
|
|
16876
|
-
const [nachladen, setNachladen] = (0,
|
|
16954
|
+
const [nachladen, setNachladen] = (0, import_react51.useState)(0);
|
|
16877
16955
|
const [phase] = usePortQuery(
|
|
16878
16956
|
() => port.elements.getElement(elementId),
|
|
16879
16957
|
[port, elementId, nachladen]
|
|
16880
16958
|
);
|
|
16881
|
-
const [entwurf, setEntwurf] = (0,
|
|
16882
|
-
const [notiz, setNotiz] = (0,
|
|
16883
|
-
const [laeuft, setLaeuft] = (0,
|
|
16884
|
-
const [wahl, setWahl] = (0,
|
|
16885
|
-
const [basiswechsel, setBasiswechsel] = (0,
|
|
16959
|
+
const [entwurf, setEntwurf] = (0, import_react51.useState)({});
|
|
16960
|
+
const [notiz, setNotiz] = (0, import_react51.useState)(null);
|
|
16961
|
+
const [laeuft, setLaeuft] = (0, import_react51.useState)(false);
|
|
16962
|
+
const [wahl, setWahl] = (0, import_react51.useState)(null);
|
|
16963
|
+
const [basiswechsel, setBasiswechsel] = (0, import_react51.useState)(null);
|
|
16886
16964
|
const [kettenPhase] = usePortQuery(
|
|
16887
16965
|
() => port.context.chainFor(contextId),
|
|
16888
16966
|
[port, contextId]
|
|
16889
16967
|
);
|
|
16890
|
-
const kontextkette = (0,
|
|
16968
|
+
const kontextkette = (0, import_react51.useMemo)(() => {
|
|
16891
16969
|
if (kettenPhase.status === "ready") return { status: "ready", chain: kettenPhase.value };
|
|
16892
16970
|
if (kettenPhase.status === "error") return { status: "error", message: kettenPhase.message };
|
|
16893
16971
|
return { status: "loading" };
|
|
16894
16972
|
}, [kettenPhase]);
|
|
16895
16973
|
const kette = kontextkette.status === "ready" ? kontextkette.chain : null;
|
|
16896
16974
|
const schnappschuss = phase.status === "ready" ? phase.value : null;
|
|
16897
|
-
const kopfErgebnis = (0,
|
|
16975
|
+
const kopfErgebnis = (0, import_react51.useMemo)(
|
|
16898
16976
|
() => schnappschuss && kette ? effectivePreferredHeadSafe(schnappschuss, kette) : null,
|
|
16899
16977
|
[schnappschuss, kette]
|
|
16900
16978
|
);
|
|
16901
16979
|
const kopfAufloesung = resolutionOf(kopfErgebnis);
|
|
16902
16980
|
const bevorzugteVersion = selectedHeadVersionId(kopfAufloesung);
|
|
16903
|
-
const felder = (0,
|
|
16981
|
+
const felder = (0, import_react51.useMemo)(
|
|
16904
16982
|
() => schnappschuss ? fieldsForType(schnappschuss.element.type) : [],
|
|
16905
16983
|
[schnappschuss?.element.type]
|
|
16906
16984
|
);
|
|
16907
|
-
(0,
|
|
16985
|
+
(0, import_react51.useEffect)(() => {
|
|
16908
16986
|
setWahl(null);
|
|
16909
16987
|
setBasiswechsel(null);
|
|
16910
16988
|
setNotiz(null);
|
|
16911
16989
|
}, [elementId]);
|
|
16912
|
-
const automatisch = (0,
|
|
16990
|
+
const automatisch = (0, import_react51.useMemo)(() => {
|
|
16913
16991
|
if (!schnappschuss) return { kind: "keiner" };
|
|
16914
16992
|
if (schnappschuss.draft) return { kind: "draft" };
|
|
16915
16993
|
if (bevorzugteVersion) return { kind: "version", versionId: bevorzugteVersion };
|
|
@@ -16919,14 +16997,14 @@ var ElementDetail = ({
|
|
|
16919
16997
|
if (schnappschuss.versions.length === 0) return { kind: "draft" };
|
|
16920
16998
|
return { kind: "keiner" };
|
|
16921
16999
|
}, [schnappschuss, bevorzugteVersion]);
|
|
16922
|
-
const angezeigt = (0,
|
|
17000
|
+
const angezeigt = (0, import_react51.useMemo)(() => {
|
|
16923
17001
|
if (!wahl) return automatisch;
|
|
16924
17002
|
if (wahl.kind === "version" && !schnappschuss?.versions.some((v) => v.version_id === wahl.versionId)) {
|
|
16925
17003
|
return automatisch;
|
|
16926
17004
|
}
|
|
16927
17005
|
return wahl;
|
|
16928
17006
|
}, [wahl, automatisch, schnappschuss]);
|
|
16929
|
-
const angezeigtePayload = (0,
|
|
17007
|
+
const angezeigtePayload = (0, import_react51.useMemo)(() => {
|
|
16930
17008
|
if (!schnappschuss) return {};
|
|
16931
17009
|
if (angezeigt.kind === "draft") return schnappschuss.draft?.payload ?? {};
|
|
16932
17010
|
if (angezeigt.kind === "version") {
|
|
@@ -16935,7 +17013,7 @@ var ElementDetail = ({
|
|
|
16935
17013
|
return {};
|
|
16936
17014
|
}, [schnappschuss, angezeigt]);
|
|
16937
17015
|
const anzeigeSchluessel = angezeigt.kind === "version" ? `v:${angezeigt.versionId}` : angezeigt.kind;
|
|
16938
|
-
(0,
|
|
17016
|
+
(0, import_react51.useEffect)(() => {
|
|
16939
17017
|
if (!schnappschuss) return;
|
|
16940
17018
|
setEntwurf(formFromPayload(felder, angezeigtePayload));
|
|
16941
17019
|
}, [
|
|
@@ -16948,7 +17026,7 @@ var ElementDetail = ({
|
|
|
16948
17026
|
const bearbeitbar = angezeigt.kind === "draft";
|
|
16949
17027
|
const setzeFeld = (pfad, wert) => setEntwurf((vorher) => ({ ...vorher, [pfad]: wert }));
|
|
16950
17028
|
const payloadAusFormular = () => payloadFromForm(felder, entwurf);
|
|
16951
|
-
const strukturPayload = (0,
|
|
17029
|
+
const strukturPayload = (0, import_react51.useMemo)(() => {
|
|
16952
17030
|
const lies = (name) => {
|
|
16953
17031
|
try {
|
|
16954
17032
|
const roh = JSON.parse(entwurf[name] || "[]");
|
|
@@ -16959,19 +17037,19 @@ var ElementDetail = ({
|
|
|
16959
17037
|
};
|
|
16960
17038
|
return { nodes: lies("nodes"), relations: lies("relations") };
|
|
16961
17039
|
}, [entwurf.nodes, entwurf.relations]);
|
|
16962
|
-
const kettenfeld = (0,
|
|
17040
|
+
const kettenfeld = (0, import_react51.useMemo)(() => {
|
|
16963
17041
|
const kette2 = schnappschuss?.draft?.chain;
|
|
16964
17042
|
if (!kette2) return null;
|
|
16965
17043
|
return kette2.links[0]?.field ?? null;
|
|
16966
17044
|
}, [schnappschuss?.draft?.chain]);
|
|
16967
|
-
const laufStand = (0,
|
|
17045
|
+
const laufStand = (0, import_react51.useMemo)(() => {
|
|
16968
17046
|
if (!schnappschuss || schnappschuss.element.type !== "scene") return null;
|
|
16969
17047
|
if (schnappschuss.draft) return schnappschuss.draft.payload;
|
|
16970
17048
|
const kopf = usableHeadVersionId(kopfAufloesung);
|
|
16971
17049
|
if (!kopf) return null;
|
|
16972
17050
|
return schnappschuss.versions.find((v) => v.version_id === kopf)?.payload ?? null;
|
|
16973
17051
|
}, [schnappschuss, kopfAufloesung]);
|
|
16974
|
-
const szenenBlocker = (0,
|
|
17052
|
+
const szenenBlocker = (0, import_react51.useMemo)(() => {
|
|
16975
17053
|
if (!schnappschuss || schnappschuss.element.type !== "scene") return null;
|
|
16976
17054
|
if (laufStand === null) {
|
|
16977
17055
|
return schnappschuss.versions.length > 0 ? "Diese Szene hat keinen Entwurf und in diesem Kontext keinen bevorzugten Kopf. Welcher Stand gilt, entscheidet keine Reihenfolge (D-36) \u2014 bitte oben eine Version als bevorzugt setzen." : "Diese Szene hat noch keinen gespeicherten Stand. Ein Lauf friert ein, was gespeichert ist \u2014 bitte zuerst den Entwurf speichern.";
|
|
@@ -16981,7 +17059,7 @@ var ElementDetail = ({
|
|
|
16981
17059
|
const grund = flag.reasons[0];
|
|
16982
17060
|
return grund && "message" in grund ? grund.message : "Kein Lauf moeglich.";
|
|
16983
17061
|
}, [schnappschuss, laufStand]);
|
|
16984
|
-
const vorschau = (0,
|
|
17062
|
+
const vorschau = (0, import_react51.useMemo)(() => {
|
|
16985
17063
|
if (!schnappschuss) return null;
|
|
16986
17064
|
try {
|
|
16987
17065
|
const roh = validatePayload(schnappschuss.element.type, payloadAusFormular());
|
|
@@ -17378,15 +17456,15 @@ var ElementDetail = ({
|
|
|
17378
17456
|
};
|
|
17379
17457
|
|
|
17380
17458
|
// src/features/szenen/areas/useSchmalesDisplay.ts
|
|
17381
|
-
var
|
|
17459
|
+
var import_react52 = require("react");
|
|
17382
17460
|
var SCHMALE_ANSICHT = "(max-width: 760px)";
|
|
17383
17461
|
function messen(abfrage) {
|
|
17384
17462
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
17385
17463
|
return window.matchMedia(abfrage).matches;
|
|
17386
17464
|
}
|
|
17387
17465
|
function useSchmalesDisplay(abfrage = SCHMALE_ANSICHT) {
|
|
17388
|
-
const [schmal, setSchmal] = (0,
|
|
17389
|
-
(0,
|
|
17466
|
+
const [schmal, setSchmal] = (0, import_react52.useState)(() => messen(abfrage));
|
|
17467
|
+
(0, import_react52.useEffect)(() => {
|
|
17390
17468
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
17391
17469
|
const liste = window.matchMedia(abfrage);
|
|
17392
17470
|
const aktualisieren = () => setSchmal(liste.matches);
|
|
@@ -17416,16 +17494,16 @@ var ElementsArea = ({
|
|
|
17416
17494
|
const gemischt = auswahlTypen.length > 1;
|
|
17417
17495
|
const ueberschrift = listTitle ?? (einzelTyp ? elementTypPlural(einzelTyp) : "Bausteine");
|
|
17418
17496
|
const einzahl = einzelTyp ? elementTypLabel(einzelTyp) : "Objekt";
|
|
17419
|
-
const [typ, setTyp] = (0,
|
|
17420
|
-
const [zeigeStillgelegte, setZeigeStillgelegte] = (0,
|
|
17421
|
-
const [gewaehlt, setGewaehlt] = (0,
|
|
17422
|
-
const [neuerTitel, setNeuerTitel] = (0,
|
|
17423
|
-
const [neuerTyp, setNeuerTyp] = (0,
|
|
17424
|
-
const [notiz, setNotiz] = (0,
|
|
17425
|
-
const [anlegend, setAnlegend] = (0,
|
|
17426
|
-
const [anlageOffen, setAnlageOffen] = (0,
|
|
17497
|
+
const [typ, setTyp] = (0, import_react53.useState)(einzelTyp ?? "");
|
|
17498
|
+
const [zeigeStillgelegte, setZeigeStillgelegte] = (0, import_react53.useState)(false);
|
|
17499
|
+
const [gewaehlt, setGewaehlt] = (0, import_react53.useState)(initialSelected);
|
|
17500
|
+
const [neuerTitel, setNeuerTitel] = (0, import_react53.useState)("");
|
|
17501
|
+
const [neuerTyp, setNeuerTyp] = (0, import_react53.useState)(auswahlTypen[0]);
|
|
17502
|
+
const [notiz, setNotiz] = (0, import_react53.useState)(null);
|
|
17503
|
+
const [anlegend, setAnlegend] = (0, import_react53.useState)(false);
|
|
17504
|
+
const [anlageOffen, setAnlageOffen] = (0, import_react53.useState)(false);
|
|
17427
17505
|
const schmal = useSchmalesDisplay();
|
|
17428
|
-
const [ansicht, setAnsicht] = (0,
|
|
17506
|
+
const [ansicht, setAnsicht] = (0, import_react53.useState)(
|
|
17429
17507
|
initialSelected ? "detail" : "liste"
|
|
17430
17508
|
);
|
|
17431
17509
|
const zeigeListe = !schmal || ansicht === "liste";
|
|
@@ -17435,9 +17513,9 @@ var ElementsArea = ({
|
|
|
17435
17513
|
setAnsicht("detail");
|
|
17436
17514
|
onSelect?.(elementId);
|
|
17437
17515
|
};
|
|
17438
|
-
const laden = (0,
|
|
17516
|
+
const laden = (0, import_react53.useMemo)(() => elementsPageLoader(port), [port]);
|
|
17439
17517
|
const typenSchluessel = restrictToTypes ? restrictToTypes.join(",") : "";
|
|
17440
|
-
const seite = (0,
|
|
17518
|
+
const seite = (0, import_react53.useCallback)(
|
|
17441
17519
|
(after, limit) => laden(
|
|
17442
17520
|
{
|
|
17443
17521
|
contextId,
|
|
@@ -17451,9 +17529,9 @@ var ElementsArea = ({
|
|
|
17451
17529
|
);
|
|
17452
17530
|
const liste = usePagedPortQuery(seite, [seite]);
|
|
17453
17531
|
const { phase, neuLaden } = liste;
|
|
17454
|
-
const elemente = (0,
|
|
17532
|
+
const elemente = (0, import_react53.useMemo)(() => liste.seiten.flatMap((s) => s.items), [liste.seiten]);
|
|
17455
17533
|
const gesamt = phase.status === "ready" ? phase.value.total : elemente.length;
|
|
17456
|
-
const annotationen = (0,
|
|
17534
|
+
const annotationen = (0, import_react53.useMemo)(() => {
|
|
17457
17535
|
const alle = {};
|
|
17458
17536
|
for (const s of liste.seiten) Object.assign(alle, s.annotations ?? {});
|
|
17459
17537
|
return alle;
|
|
@@ -17699,10 +17777,10 @@ var ElementsArea = ({
|
|
|
17699
17777
|
};
|
|
17700
17778
|
|
|
17701
17779
|
// src/features/szenen/areas/SceneArea.tsx
|
|
17702
|
-
var
|
|
17780
|
+
var import_react57 = require("react");
|
|
17703
17781
|
|
|
17704
17782
|
// src/features/szenen/areas/DeriveArea.tsx
|
|
17705
|
-
var
|
|
17783
|
+
var import_react56 = require("react");
|
|
17706
17784
|
|
|
17707
17785
|
// src/lib/szenen/derivationExamples.ts
|
|
17708
17786
|
var PROPOSAL_EXAMPLE = {
|
|
@@ -18541,7 +18619,7 @@ async function runSceneDerivation(input, port, userPrompt) {
|
|
|
18541
18619
|
}
|
|
18542
18620
|
|
|
18543
18621
|
// src/features/szenen/areas/RecoveryPanel.tsx
|
|
18544
|
-
var
|
|
18622
|
+
var import_react54 = require("react");
|
|
18545
18623
|
var import_jsx_runtime45 = require("react/jsx-runtime");
|
|
18546
18624
|
var Diagnosen = ({ befunde, testId }) => {
|
|
18547
18625
|
if (befunde.length === 0) return null;
|
|
@@ -18592,11 +18670,11 @@ var Ausbeute = ({ bewertung }) => /* @__PURE__ */ (0, import_jsx_runtime45.jsxs)
|
|
|
18592
18670
|
] })
|
|
18593
18671
|
] });
|
|
18594
18672
|
var RecoveryPanel = ({ port, lauf }) => {
|
|
18595
|
-
const [arbeitskopie, setArbeitskopie] = (0,
|
|
18596
|
-
const [bewertung, setBewertung] = (0,
|
|
18597
|
-
const [gesichert, setGesichert] = (0,
|
|
18598
|
-
const [meldung, setMeldung] = (0,
|
|
18599
|
-
const [laeuft, setLaeuft] = (0,
|
|
18673
|
+
const [arbeitskopie, setArbeitskopie] = (0, import_react54.useState)(null);
|
|
18674
|
+
const [bewertung, setBewertung] = (0, import_react54.useState)(null);
|
|
18675
|
+
const [gesichert, setGesichert] = (0, import_react54.useState)(null);
|
|
18676
|
+
const [meldung, setMeldung] = (0, import_react54.useState)(null);
|
|
18677
|
+
const [laeuft, setLaeuft] = (0, import_react54.useState)(false);
|
|
18600
18678
|
if (!port.derivation) return null;
|
|
18601
18679
|
if (typeof lauf.raw_output !== "string" || lauf.raw_output.length === 0) return null;
|
|
18602
18680
|
const derivation = port.derivation;
|
|
@@ -18719,12 +18797,12 @@ var RecoveryPanel = ({ port, lauf }) => {
|
|
|
18719
18797
|
};
|
|
18720
18798
|
|
|
18721
18799
|
// src/features/szenen/areas/ChainActions.tsx
|
|
18722
|
-
var
|
|
18800
|
+
var import_react55 = require("react");
|
|
18723
18801
|
var import_jsx_runtime46 = require("react/jsx-runtime");
|
|
18724
18802
|
var ChainActions = ({ port, runId, recoveryId }) => {
|
|
18725
|
-
const [graph, setGraph] = (0,
|
|
18726
|
-
const [meldung, setMeldung] = (0,
|
|
18727
|
-
const [laeuft, setLaeuft] = (0,
|
|
18803
|
+
const [graph, setGraph] = (0, import_react55.useState)(null);
|
|
18804
|
+
const [meldung, setMeldung] = (0, import_react55.useState)(null);
|
|
18805
|
+
const [laeuft, setLaeuft] = (0, import_react55.useState)(false);
|
|
18728
18806
|
if (!port.derivation) return null;
|
|
18729
18807
|
const derivation = port.derivation;
|
|
18730
18808
|
const runs = port.runs;
|
|
@@ -18810,8 +18888,8 @@ var MODUS_TEXT = {
|
|
|
18810
18888
|
image_prompt: "Bild und Prompt"
|
|
18811
18889
|
};
|
|
18812
18890
|
var BelegFeld = ({ titel, text, testId, offen }) => {
|
|
18813
|
-
const [kopiert, setKopiert] = (0,
|
|
18814
|
-
const feld = (0,
|
|
18891
|
+
const [kopiert, setKopiert] = (0, import_react56.useState)(false);
|
|
18892
|
+
const feld = (0, import_react56.useRef)(null);
|
|
18815
18893
|
async function kopieren() {
|
|
18816
18894
|
try {
|
|
18817
18895
|
await navigator.clipboard.writeText(text);
|
|
@@ -18908,9 +18986,9 @@ function readAsBase642(datei) {
|
|
|
18908
18986
|
}
|
|
18909
18987
|
var ERSTE_KACHELN = 12;
|
|
18910
18988
|
function useSichtbareKacheln(alle, wurzel) {
|
|
18911
|
-
const [gesehen, setGesehen] = (0,
|
|
18989
|
+
const [gesehen, setGesehen] = (0, import_react56.useState)([]);
|
|
18912
18990
|
const schluessel = alle.join(" ");
|
|
18913
|
-
(0,
|
|
18991
|
+
(0, import_react56.useEffect)(() => {
|
|
18914
18992
|
const ziel = wurzel.current;
|
|
18915
18993
|
if (!ziel || typeof IntersectionObserver === "undefined") return void 0;
|
|
18916
18994
|
const beobachter = new IntersectionObserver((eintraege) => {
|
|
@@ -18922,17 +19000,17 @@ function useSichtbareKacheln(alle, wurzel) {
|
|
|
18922
19000
|
ziel.querySelectorAll("[data-media-ref]").forEach((el) => beobachter.observe(el));
|
|
18923
19001
|
return () => beobachter.disconnect();
|
|
18924
19002
|
}, [wurzel, schluessel]);
|
|
18925
|
-
return (0,
|
|
19003
|
+
return (0, import_react56.useMemo)(
|
|
18926
19004
|
() => [.../* @__PURE__ */ new Set([...alle.slice(0, ERSTE_KACHELN), ...gesehen])],
|
|
18927
19005
|
[schluessel, gesehen]
|
|
18928
19006
|
);
|
|
18929
19007
|
}
|
|
18930
19008
|
function useVorschauen2(port, mediaRefs) {
|
|
18931
|
-
const [stand, setStand] = (0,
|
|
18932
|
-
const gehalten = (0,
|
|
18933
|
-
const ohneBild = (0,
|
|
19009
|
+
const [stand, setStand] = (0, import_react56.useState)(() => ({ quellen: /* @__PURE__ */ new Map(), gescheitert: /* @__PURE__ */ new Set() }));
|
|
19010
|
+
const gehalten = (0, import_react56.useRef)(/* @__PURE__ */ new Map());
|
|
19011
|
+
const ohneBild = (0, import_react56.useRef)(/* @__PURE__ */ new Set());
|
|
18934
19012
|
const schluessel = mediaRefs.join(" ");
|
|
18935
|
-
(0,
|
|
19013
|
+
(0, import_react56.useEffect)(() => {
|
|
18936
19014
|
const bestand = port.imageLibrary;
|
|
18937
19015
|
if (!bestand?.loadPreview) return void 0;
|
|
18938
19016
|
const refs = schluessel ? schluessel.split(" ") : [];
|
|
@@ -18970,21 +19048,21 @@ function useVorschauen2(port, mediaRefs) {
|
|
|
18970
19048
|
return stand;
|
|
18971
19049
|
}
|
|
18972
19050
|
var BildWaehler = ({ port, gewaehlt, onWaehlen, onEntfernen }) => {
|
|
18973
|
-
const [suche, setSuche] = (0,
|
|
18974
|
-
const [fokus, setFokus] = (0,
|
|
18975
|
-
const browser = (0,
|
|
19051
|
+
const [suche, setSuche] = (0, import_react56.useState)("");
|
|
19052
|
+
const [fokus, setFokus] = (0, import_react56.useState)(null);
|
|
19053
|
+
const browser = (0, import_react56.useRef)(null);
|
|
18976
19054
|
const [items] = usePortQuery(
|
|
18977
19055
|
() => port.imageLibrary.listItems({ search: suche.trim() || void 0, limit: 60 }),
|
|
18978
19056
|
[port, suche]
|
|
18979
19057
|
);
|
|
18980
19058
|
const werke = items.status === "ready" ? items.value : [];
|
|
18981
19059
|
const werk = werke.find((i) => i.itemRef === fokus) ?? null;
|
|
18982
|
-
const deckbilder = (0,
|
|
19060
|
+
const deckbilder = (0, import_react56.useMemo)(
|
|
18983
19061
|
() => werke.map((i) => i.images[0]?.mediaRef).filter((r) => Boolean(r)),
|
|
18984
19062
|
[werke]
|
|
18985
19063
|
);
|
|
18986
19064
|
const sichtbareDeckbilder = useSichtbareKacheln(deckbilder, browser);
|
|
18987
|
-
const anzufordern = (0,
|
|
19065
|
+
const anzufordern = (0, import_react56.useMemo)(() => [.../* @__PURE__ */ new Set([
|
|
18988
19066
|
...sichtbareDeckbilder,
|
|
18989
19067
|
...werk ? werk.images.map((b) => b.mediaRef) : [],
|
|
18990
19068
|
...gewaehlt.map((b) => b.mediaRef)
|
|
@@ -19134,28 +19212,28 @@ async function digestOf(datei) {
|
|
|
19134
19212
|
return [...new Uint8Array(hash)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
19135
19213
|
}
|
|
19136
19214
|
var DeriveArea = ({ port, contextId }) => {
|
|
19137
|
-
const [policyVersionId, setPolicyVersionId] = (0,
|
|
19138
|
-
const [modus, setModus] = (0,
|
|
19139
|
-
const [promptText, setPromptText] = (0,
|
|
19140
|
-
const [dateien, setDateien] = (0,
|
|
19141
|
-
const [bestandsbilder, setBestandsbilder] = (0,
|
|
19142
|
-
const [laeuft, setLaeuft] = (0,
|
|
19143
|
-
const [lauf, setLauf] = (0,
|
|
19144
|
-
const [meldung, setMeldung] = (0,
|
|
19145
|
-
const [uebernommen, setUebernommen] = (0,
|
|
19146
|
-
const [titel, setTitel] = (0,
|
|
19147
|
-
const [referenzen, setReferenzen] = (0,
|
|
19148
|
-
const [beanstandung, setBeanstandung] = (0,
|
|
19149
|
-
const [zusatz, setZusatz] = (0,
|
|
19215
|
+
const [policyVersionId, setPolicyVersionId] = (0, import_react56.useState)("");
|
|
19216
|
+
const [modus, setModus] = (0, import_react56.useState)("prompt");
|
|
19217
|
+
const [promptText, setPromptText] = (0, import_react56.useState)("");
|
|
19218
|
+
const [dateien, setDateien] = (0, import_react56.useState)([]);
|
|
19219
|
+
const [bestandsbilder, setBestandsbilder] = (0, import_react56.useState)([]);
|
|
19220
|
+
const [laeuft, setLaeuft] = (0, import_react56.useState)(false);
|
|
19221
|
+
const [lauf, setLauf] = (0, import_react56.useState)(null);
|
|
19222
|
+
const [meldung, setMeldung] = (0, import_react56.useState)(null);
|
|
19223
|
+
const [uebernommen, setUebernommen] = (0, import_react56.useState)({});
|
|
19224
|
+
const [titel, setTitel] = (0, import_react56.useState)({});
|
|
19225
|
+
const [referenzen, setReferenzen] = (0, import_react56.useState)({});
|
|
19226
|
+
const [beanstandung, setBeanstandung] = (0, import_react56.useState)(null);
|
|
19227
|
+
const [zusatz, setZusatz] = (0, import_react56.useState)("");
|
|
19150
19228
|
const [regeln] = usePortQuery(
|
|
19151
19229
|
() => port.derivation.listPolicies(contextId),
|
|
19152
19230
|
[port, contextId]
|
|
19153
19231
|
);
|
|
19154
|
-
const sortiert = (0,
|
|
19232
|
+
const sortiert = (0, import_react56.useMemo)(() => regeln.status === "ready" ? [...regeln.value].sort((a, b) => {
|
|
19155
19233
|
const szeneZuerst = Number(b.produces.includes("scene")) - Number(a.produces.includes("scene"));
|
|
19156
19234
|
return szeneZuerst !== 0 ? szeneZuerst : a.title.localeCompare(b.title, "de");
|
|
19157
19235
|
}) : [], [regeln]);
|
|
19158
|
-
(0,
|
|
19236
|
+
(0, import_react56.useEffect)(() => {
|
|
19159
19237
|
if (policyVersionId) return;
|
|
19160
19238
|
const fuerSzene = sortiert.find((r) => r.produces.includes("scene"));
|
|
19161
19239
|
if (fuerSzene) setPolicyVersionId(fuerSzene.versionId);
|
|
@@ -19539,7 +19617,7 @@ var SceneArea = ({
|
|
|
19539
19617
|
initialOpenRunId = null
|
|
19540
19618
|
}) => {
|
|
19541
19619
|
const kannAbleiten = capabilitiesOf(port).derivation;
|
|
19542
|
-
const [ableiten, setAbleiten] = (0,
|
|
19620
|
+
const [ableiten, setAbleiten] = (0, import_react57.useState)(false);
|
|
19543
19621
|
const zeigeDerive = kannAbleiten && ableiten;
|
|
19544
19622
|
return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "szenen-scene", "data-testid": "bereich-szene", children: [
|
|
19545
19623
|
/* @__PURE__ */ (0, import_jsx_runtime48.jsxs)("div", { className: "szenen-elements__toolbar", children: [
|
|
@@ -19587,7 +19665,7 @@ var AdvancedArea = ({ port, contextId }) => /* @__PURE__ */ (0, import_jsx_runti
|
|
|
19587
19665
|
] });
|
|
19588
19666
|
|
|
19589
19667
|
// src/features/szenen/areas/PromptsArea.tsx
|
|
19590
|
-
var
|
|
19668
|
+
var import_react58 = require("react");
|
|
19591
19669
|
var import_jsx_runtime50 = require("react/jsx-runtime");
|
|
19592
19670
|
var HERKUNFT_TEXT2 = {
|
|
19593
19671
|
published: "aus ver\xF6ffentlichtem Shot",
|
|
@@ -19615,15 +19693,15 @@ var PromptsArea = ({
|
|
|
19615
19693
|
fokusRunId = null
|
|
19616
19694
|
}) => {
|
|
19617
19695
|
const faehig = capabilitiesOf(port);
|
|
19618
|
-
const [nachladen, setNachladen] = (0,
|
|
19619
|
-
const [offen, setOffen] = (0,
|
|
19620
|
-
const [alleKontexte, setAlleKontexte] = (0,
|
|
19621
|
-
const [mitFehllaeufen, setMitFehllaeufen] = (0,
|
|
19622
|
-
const [herkunft, setHerkunft] = (0,
|
|
19623
|
-
const [fokus, setFokus] = (0,
|
|
19696
|
+
const [nachladen, setNachladen] = (0, import_react58.useState)(0);
|
|
19697
|
+
const [offen, setOffen] = (0, import_react58.useState)(null);
|
|
19698
|
+
const [alleKontexte, setAlleKontexte] = (0, import_react58.useState)(false);
|
|
19699
|
+
const [mitFehllaeufen, setMitFehllaeufen] = (0, import_react58.useState)(false);
|
|
19700
|
+
const [herkunft, setHerkunft] = (0, import_react58.useState)("alle");
|
|
19701
|
+
const [fokus, setFokus] = (0, import_react58.useState)(
|
|
19624
19702
|
fokusRunId ? { status: "laden", runId: fokusRunId } : null
|
|
19625
19703
|
);
|
|
19626
|
-
(0,
|
|
19704
|
+
(0, import_react58.useEffect)(() => {
|
|
19627
19705
|
if (!fokusRunId) {
|
|
19628
19706
|
setFokus(null);
|
|
19629
19707
|
return;
|
|
@@ -19659,9 +19737,9 @@ var PromptsArea = ({
|
|
|
19659
19737
|
aktuell = false;
|
|
19660
19738
|
};
|
|
19661
19739
|
}, [fokusRunId, port]);
|
|
19662
|
-
const laden = (0,
|
|
19740
|
+
const laden = (0, import_react58.useMemo)(() => runsPageLoader(port), [port]);
|
|
19663
19741
|
const imFokus = fokus?.status === "gefunden";
|
|
19664
|
-
const seite = (0,
|
|
19742
|
+
const seite = (0, import_react58.useCallback)(
|
|
19665
19743
|
(after, limit) => laden({
|
|
19666
19744
|
source: imFokus ? fokus.source : { kind: "all" },
|
|
19667
19745
|
filter: imFokus ? { status: "all" } : {
|
|
@@ -19678,35 +19756,35 @@ var PromptsArea = ({
|
|
|
19678
19756
|
[seite, nachladen]
|
|
19679
19757
|
);
|
|
19680
19758
|
const { phase } = liste;
|
|
19681
|
-
const gezeigt = (0,
|
|
19759
|
+
const gezeigt = (0, import_react58.useMemo)(
|
|
19682
19760
|
() => liste.seiten.flatMap((s) => s.runs),
|
|
19683
19761
|
[liste.seiten]
|
|
19684
19762
|
);
|
|
19685
|
-
const runden = (0,
|
|
19763
|
+
const runden = (0, import_react58.useMemo)(() => {
|
|
19686
19764
|
const alle = {};
|
|
19687
19765
|
for (const s of liste.seiten) Object.assign(alle, s.rounds);
|
|
19688
19766
|
return alle;
|
|
19689
19767
|
}, [liste.seiten]);
|
|
19690
|
-
const [bearbeitet, setBearbeitet] = (0,
|
|
19691
|
-
const promptTags = (0,
|
|
19768
|
+
const [bearbeitet, setBearbeitet] = (0, import_react58.useState)({});
|
|
19769
|
+
const promptTags = (0, import_react58.useMemo)(() => {
|
|
19692
19770
|
const alle = {};
|
|
19693
19771
|
for (const s of liste.seiten) Object.assign(alle, s.promptTags ?? {});
|
|
19694
19772
|
return { ...alle, ...bearbeitet };
|
|
19695
19773
|
}, [liste.seiten, bearbeitet]);
|
|
19696
|
-
const annotationen = (0,
|
|
19774
|
+
const annotationen = (0, import_react58.useMemo)(() => {
|
|
19697
19775
|
const alle = {};
|
|
19698
19776
|
for (const s of liste.seiten) Object.assign(alle, s.annotations ?? {});
|
|
19699
19777
|
return alle;
|
|
19700
19778
|
}, [liste.seiten]);
|
|
19701
19779
|
const sicht = phase.status === "ready" ? phase.value : null;
|
|
19702
19780
|
const fokusSichtbar = imFokus && gezeigt.some((l) => l.run_id === fokus.runId);
|
|
19703
|
-
(0,
|
|
19781
|
+
(0, import_react58.useEffect)(() => {
|
|
19704
19782
|
if (!imFokus || fokusSichtbar) return;
|
|
19705
19783
|
if (phase.status !== "ready" || liste.laedtMehr || !liste.hasMore) return;
|
|
19706
19784
|
liste.mehrLaden();
|
|
19707
19785
|
}, [imFokus, fokusSichtbar, phase.status, liste]);
|
|
19708
|
-
const angescrollt = (0,
|
|
19709
|
-
(0,
|
|
19786
|
+
const angescrollt = (0, import_react58.useRef)(null);
|
|
19787
|
+
(0, import_react58.useEffect)(() => {
|
|
19710
19788
|
if (!imFokus || !fokusSichtbar) return;
|
|
19711
19789
|
const runId = fokus.runId;
|
|
19712
19790
|
if (angescrollt.current === runId) return;
|
|
@@ -19716,7 +19794,7 @@ var PromptsArea = ({
|
|
|
19716
19794
|
knoten.scrollIntoView({ block: "center" });
|
|
19717
19795
|
}
|
|
19718
19796
|
}, [imFokus, fokusSichtbar, fokus]);
|
|
19719
|
-
const seitenTitel = (0,
|
|
19797
|
+
const seitenTitel = (0, import_react58.useMemo)(() => {
|
|
19720
19798
|
const alle = {};
|
|
19721
19799
|
for (const s of liste.seiten) Object.assign(alle, s.titles ?? {});
|
|
19722
19800
|
return alle;
|
|
@@ -19917,21 +19995,21 @@ var PromptEintrag = ({
|
|
|
19917
19995
|
};
|
|
19918
19996
|
|
|
19919
19997
|
// src/features/szenen/areas/MediaArea.tsx
|
|
19920
|
-
var
|
|
19998
|
+
var import_react59 = require("react");
|
|
19921
19999
|
var import_jsx_runtime51 = require("react/jsx-runtime");
|
|
19922
20000
|
var leer = async () => ({ kind: "ok", value: [] });
|
|
19923
20001
|
var MediaArea = ({ port }) => {
|
|
19924
20002
|
const media = port.media;
|
|
19925
|
-
const [versionInput, setVersionInput] = (0,
|
|
19926
|
-
const [versionId, setVersionId] = (0,
|
|
19927
|
-
const [itemInput, setItemInput] = (0,
|
|
19928
|
-
const [itemId, setItemId] = (0,
|
|
19929
|
-
const [assetId, setAssetId] = (0,
|
|
19930
|
-
const [role, setRole] = (0,
|
|
19931
|
-
const [position, setPosition] = (0,
|
|
19932
|
-
const [notiz, setNotiz] = (0,
|
|
19933
|
-
const [busy, setBusy] = (0,
|
|
19934
|
-
const [refresh, setRefresh] = (0,
|
|
20003
|
+
const [versionInput, setVersionInput] = (0, import_react59.useState)("");
|
|
20004
|
+
const [versionId, setVersionId] = (0, import_react59.useState)("");
|
|
20005
|
+
const [itemInput, setItemInput] = (0, import_react59.useState)("");
|
|
20006
|
+
const [itemId, setItemId] = (0, import_react59.useState)("");
|
|
20007
|
+
const [assetId, setAssetId] = (0, import_react59.useState)("");
|
|
20008
|
+
const [role, setRole] = (0, import_react59.useState)("reference");
|
|
20009
|
+
const [position, setPosition] = (0, import_react59.useState)("0");
|
|
20010
|
+
const [notiz, setNotiz] = (0, import_react59.useState)(null);
|
|
20011
|
+
const [busy, setBusy] = (0, import_react59.useState)(false);
|
|
20012
|
+
const [refresh, setRefresh] = (0, import_react59.useState)(0);
|
|
19935
20013
|
const [versionPhase] = usePortQuery(
|
|
19936
20014
|
() => versionId ? media.listVersionMedia(versionId) : leer(),
|
|
19937
20015
|
[media, versionId, refresh]
|
|
@@ -20057,7 +20135,7 @@ var MediaArea = ({ port }) => {
|
|
|
20057
20135
|
};
|
|
20058
20136
|
|
|
20059
20137
|
// src/features/szenen/areas/ImageJobsArea.tsx
|
|
20060
|
-
var
|
|
20138
|
+
var import_react60 = require("react");
|
|
20061
20139
|
|
|
20062
20140
|
// src/lib/szenen/imageJobQueueRunner.ts
|
|
20063
20141
|
var PAUSE_SEKUNDEN_OPTIONEN = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
|
|
@@ -20169,13 +20247,13 @@ function istAbgeschlossen(job) {
|
|
|
20169
20247
|
return job.status !== "pending" && job.status !== "claimed";
|
|
20170
20248
|
}
|
|
20171
20249
|
function useNachladbareAbfrage(frage, abhaengigkeiten) {
|
|
20172
|
-
const [stand, setStand] = (0,
|
|
20250
|
+
const [stand, setStand] = (0, import_react60.useState)(
|
|
20173
20251
|
() => ({ wert: null, fehler: null, laedt: true })
|
|
20174
20252
|
);
|
|
20175
|
-
const sequenz = (0,
|
|
20176
|
-
const frageRef = (0,
|
|
20253
|
+
const sequenz = (0, import_react60.useRef)(0);
|
|
20254
|
+
const frageRef = (0, import_react60.useRef)(frage);
|
|
20177
20255
|
frageRef.current = frage;
|
|
20178
|
-
(0,
|
|
20256
|
+
(0, import_react60.useEffect)(() => {
|
|
20179
20257
|
const meine = sequenz.current += 1;
|
|
20180
20258
|
let abgemeldet = false;
|
|
20181
20259
|
setStand((v) => ({ ...v, laedt: true }));
|
|
@@ -20205,15 +20283,20 @@ var ImageJobsArea = ({
|
|
|
20205
20283
|
}) => {
|
|
20206
20284
|
const auftraege = port.imageJobs;
|
|
20207
20285
|
const uhr = jetzt ?? (() => /* @__PURE__ */ new Date());
|
|
20208
|
-
const [filter, setFilter] = (0,
|
|
20209
|
-
const [pauseSekunden, setPauseSekunden] = (0,
|
|
20210
|
-
const [takt, setTakt] = (0,
|
|
20211
|
-
const neuLesen = (0,
|
|
20212
|
-
const [lauf, setLauf] = (0,
|
|
20213
|
-
const laeuftRef = (0,
|
|
20214
|
-
const abbruchRef = (0,
|
|
20215
|
-
const [einzeln, setEinzeln] = (0,
|
|
20216
|
-
const [notiz, setNotiz] = (0,
|
|
20286
|
+
const [filter, setFilter] = (0, import_react60.useState)("pending");
|
|
20287
|
+
const [pauseSekunden, setPauseSekunden] = (0, import_react60.useState)(PAUSE_SEKUNDEN_VORGABE);
|
|
20288
|
+
const [takt, setTakt] = (0, import_react60.useState)(0);
|
|
20289
|
+
const neuLesen = (0, import_react60.useCallback)(() => setTakt((t) => t + 1), []);
|
|
20290
|
+
const [lauf, setLauf] = (0, import_react60.useState)(null);
|
|
20291
|
+
const laeuftRef = (0, import_react60.useRef)(false);
|
|
20292
|
+
const abbruchRef = (0, import_react60.useRef)(false);
|
|
20293
|
+
const [einzeln, setEinzeln] = (0, import_react60.useState)(null);
|
|
20294
|
+
const [notiz, setNotiz] = (0, import_react60.useState)(null);
|
|
20295
|
+
const [modellLauf, setModellLauf] = (0, import_react60.useState)(null);
|
|
20296
|
+
const wahl = useNachladbareAbfrage(
|
|
20297
|
+
() => port.imageGeneration && modellLauf ? port.imageGeneration.imageModelChoiceForRun(modellLauf) : Promise.resolve({ kind: "ok", value: null }),
|
|
20298
|
+
[port, modellLauf]
|
|
20299
|
+
);
|
|
20217
20300
|
const liste = useNachladbareAbfrage(
|
|
20218
20301
|
() => auftraege?.listAllJobs ? auftraege.listAllJobs({ status: filter }) : Promise.resolve({
|
|
20219
20302
|
kind: "ok",
|
|
@@ -20225,15 +20308,20 @@ var ImageJobsArea = ({
|
|
|
20225
20308
|
() => auftraege?.latestImage ? auftraege.latestImage() : Promise.resolve({ kind: "ok", value: null }),
|
|
20226
20309
|
[auftraege, takt]
|
|
20227
20310
|
);
|
|
20228
|
-
const [zuletzt, setZuletzt] = (0,
|
|
20229
|
-
(0,
|
|
20311
|
+
const [zuletzt, setZuletzt] = (0, import_react60.useState)(null);
|
|
20312
|
+
(0, import_react60.useEffect)(() => {
|
|
20230
20313
|
if (!liste.laedt && liste.wert !== null) setZuletzt(uhr().toISOString());
|
|
20231
20314
|
}, [liste.laedt, liste.wert]);
|
|
20232
20315
|
const seite = liste.wert;
|
|
20233
20316
|
const eintraege = seite?.eintraege ?? [];
|
|
20234
20317
|
const offen = seite?.offen ?? 0;
|
|
20318
|
+
const ersterLauf = eintraege[0]?.job.prompt_run_id ?? null;
|
|
20319
|
+
(0, import_react60.useEffect)(() => {
|
|
20320
|
+
if (modellLauf === null && ersterLauf !== null) setModellLauf(ersterLauf);
|
|
20321
|
+
}, [modellLauf, ersterLauf]);
|
|
20235
20322
|
const queueLaeuft = lauf !== null && (lauf.zustand === "laeuft" || lauf.zustand === "pause");
|
|
20236
|
-
|
|
20323
|
+
const eigeneLaufend = (einzeln !== null ? 1 : 0) + (queueLaeuft && lauf?.aktuell && lauf.aktuell !== einzeln ? 1 : 0);
|
|
20324
|
+
async function einzelStart(jobId, vorspann = "") {
|
|
20237
20325
|
if (!auftraege || laeuftRef.current || einzeln !== null) return;
|
|
20238
20326
|
setEinzeln(jobId);
|
|
20239
20327
|
setNotiz(null);
|
|
@@ -20242,7 +20330,7 @@ var ImageJobsArea = ({
|
|
|
20242
20330
|
if (ergebnis.kind === "ok") {
|
|
20243
20331
|
setNotiz({
|
|
20244
20332
|
tone: ergebnis.job.status === "succeeded" ? "ok" : "rejected",
|
|
20245
|
-
text:
|
|
20333
|
+
text: `${vorspann}Auftrag ${jobId}: ${STATUS_TEXT2[ergebnis.job.status]}.` + (ergebnis.job.diagnostic ? ` ${ergebnis.job.diagnostic}` : "")
|
|
20246
20334
|
});
|
|
20247
20335
|
} else if (ergebnis.kind === "lost" || ergebnis.kind === "rejected") {
|
|
20248
20336
|
setNotiz({ tone: "rejected", text: ergebnis.message });
|
|
@@ -20259,6 +20347,46 @@ var ImageJobsArea = ({
|
|
|
20259
20347
|
neuLesen();
|
|
20260
20348
|
}
|
|
20261
20349
|
}
|
|
20350
|
+
async function erzeugenUndStarten(quelle, wieViele, modell) {
|
|
20351
|
+
const anfordern = auftraege?.requestMoreImages;
|
|
20352
|
+
if (!anfordern || laeuftRef.current || einzeln !== null) return;
|
|
20353
|
+
setNotiz(null);
|
|
20354
|
+
let neuer;
|
|
20355
|
+
try {
|
|
20356
|
+
const ergebnis = await anfordern({
|
|
20357
|
+
sourceJobId: quelle.job_id,
|
|
20358
|
+
imageCount: wieViele,
|
|
20359
|
+
// Nur mitgeben, wenn wirklich gewechselt wurde — sonst gilt der
|
|
20360
|
+
// Ausgangsauftrag, und das steht so im Port.
|
|
20361
|
+
...modell && (modell.target_model_key !== quelle.execution_target_model_key || modell.profile_id !== quelle.execution_profile_id) ? {
|
|
20362
|
+
executionTargetModelKey: modell.target_model_key,
|
|
20363
|
+
executionProfileId: modell.profile_id
|
|
20364
|
+
// Ein Formatwechsel ist nicht gefragt; traegt das neue
|
|
20365
|
+
// Modell das alte Format nicht, lehnt der Port ab und
|
|
20366
|
+
// sagt warum, statt still zu ersetzen.
|
|
20367
|
+
} : {}
|
|
20368
|
+
});
|
|
20369
|
+
if (ergebnis.kind !== "ok") {
|
|
20370
|
+
setNotiz({
|
|
20371
|
+
tone: ergebnis.kind === "rejected" ? "rejected" : "error",
|
|
20372
|
+
text: ergebnis.message
|
|
20373
|
+
});
|
|
20374
|
+
return;
|
|
20375
|
+
}
|
|
20376
|
+
neuer = ergebnis.job;
|
|
20377
|
+
} catch (e) {
|
|
20378
|
+
setNotiz({
|
|
20379
|
+
tone: "error",
|
|
20380
|
+
text: `Der neue Bildauftrag zu ${quelle.job_id} konnte nicht angelegt werden: ${e instanceof Error ? e.message : String(e)}`
|
|
20381
|
+
});
|
|
20382
|
+
return;
|
|
20383
|
+
}
|
|
20384
|
+
neuLesen();
|
|
20385
|
+
await einzelStart(
|
|
20386
|
+
neuer.job_id,
|
|
20387
|
+
`Neuer Auftrag \xFCber ${neuer.image_count} ${neuer.image_count === 1 ? "Bild" : "Bilder"} mit ${neuer.execution_target_model_key}; `
|
|
20388
|
+
);
|
|
20389
|
+
}
|
|
20262
20390
|
async function alleOffenenStarten() {
|
|
20263
20391
|
if (!auftraege?.listAllJobs || laeuftRef.current) return;
|
|
20264
20392
|
laeuftRef.current = true;
|
|
@@ -20311,7 +20439,26 @@ var ImageJobsArea = ({
|
|
|
20311
20439
|
children: "\u27F3 Aktualisieren"
|
|
20312
20440
|
}
|
|
20313
20441
|
),
|
|
20314
|
-
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)("span", { "data-testid": "bildauftraege-zuletzt", children: zuletzt ? `zuletzt aktualisiert ${zuletzt}` : "noch nicht aktualisiert" })
|
|
20442
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)("span", { "data-testid": "bildauftraege-zuletzt", children: zuletzt ? `zuletzt aktualisiert ${zuletzt}` : "noch nicht aktualisiert" }),
|
|
20443
|
+
eigeneLaufend > 0 && /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
|
|
20444
|
+
"span",
|
|
20445
|
+
{
|
|
20446
|
+
className: "szenen-bildauftraege__laufzaehler",
|
|
20447
|
+
"data-testid": "bildauftraege-laufzaehler",
|
|
20448
|
+
title: `${eigeneLaufend} Auftrag${eigeneLaufend === 1 ? "" : "e"} dieses Tabs l\xE4uft gerade`,
|
|
20449
|
+
children: [
|
|
20450
|
+
"\u27F3 ",
|
|
20451
|
+
eigeneLaufend,
|
|
20452
|
+
" l\xE4uft",
|
|
20453
|
+
eigeneLaufend === 1 ? "" : "en"
|
|
20454
|
+
]
|
|
20455
|
+
}
|
|
20456
|
+
)
|
|
20457
|
+
] }),
|
|
20458
|
+
(wahl.wert?.invalid_profiles.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { role: "alert", "data-testid": "bildauftraege-profilbefunde", children: [
|
|
20459
|
+
"Nicht angeboten, weil das Profil unvollst\xE4ndig ist:",
|
|
20460
|
+
" ",
|
|
20461
|
+
wahl.wert.invalid_profiles.join(" \xB7 ")
|
|
20315
20462
|
] }),
|
|
20316
20463
|
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
|
|
20317
20464
|
Bildkontrolle,
|
|
@@ -20404,10 +20551,9 @@ var ImageJobsArea = ({
|
|
|
20404
20551
|
gesperrt: queueLaeuft || einzeln !== null,
|
|
20405
20552
|
laufend: einzeln === eintrag.job.job_id || lauf?.aktuell === eintrag.job.job_id,
|
|
20406
20553
|
onStart: () => void einzelStart(eintrag.job.job_id),
|
|
20407
|
-
|
|
20408
|
-
|
|
20409
|
-
|
|
20410
|
-
},
|
|
20554
|
+
onErzeugen: (anzahl, modell) => erzeugenUndStarten(eintrag.job, anzahl, modell),
|
|
20555
|
+
modelle: wahl.wert?.models ?? null,
|
|
20556
|
+
modelleLaden: wahl.wert === null && wahl.fehler === null,
|
|
20411
20557
|
...onPromptOeffnen ? { onPromptOeffnen } : {}
|
|
20412
20558
|
},
|
|
20413
20559
|
eintrag.job.job_id
|
|
@@ -20459,12 +20605,13 @@ var AuftragsZeile = ({
|
|
|
20459
20605
|
gesperrt,
|
|
20460
20606
|
laufend,
|
|
20461
20607
|
onStart,
|
|
20462
|
-
|
|
20463
|
-
onPromptOeffnen
|
|
20608
|
+
onErzeugen,
|
|
20609
|
+
onPromptOeffnen,
|
|
20610
|
+
modelle,
|
|
20611
|
+
modelleLaden
|
|
20464
20612
|
}) => {
|
|
20465
20613
|
const { job, herkunft } = eintrag;
|
|
20466
|
-
const [ergebnisOffen, setErgebnisOffen] = (0,
|
|
20467
|
-
const [weitereOffen, setWeitereOffen] = (0, import_react59.useState)(false);
|
|
20614
|
+
const [ergebnisOffen, setErgebnisOffen] = (0, import_react60.useState)(false);
|
|
20468
20615
|
const hatErgebnis = Boolean(job.generation_batch_id) && (job.status === "succeeded" || job.status === "partial");
|
|
20469
20616
|
const kannWeitere = Boolean(port.imageJobs?.requestMoreImages) && istAbgeschlossen(job);
|
|
20470
20617
|
return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
|
|
@@ -20476,6 +20623,12 @@ var AuftragsZeile = ({
|
|
|
20476
20623
|
children: [
|
|
20477
20624
|
/* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("p", { className: "szenen-bildauftraege__kopf", children: [
|
|
20478
20625
|
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)("strong", { "data-testid": `bildauftrag-status-${job.job_id}`, children: STATUS_TEXT2[job.status] }),
|
|
20626
|
+
" \xB7 ",
|
|
20627
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("strong", { "data-testid": `bildauftrag-anzahl-${job.job_id}`, children: [
|
|
20628
|
+
job.image_count,
|
|
20629
|
+
" ",
|
|
20630
|
+
job.image_count === 1 ? "Bild" : "Bilder"
|
|
20631
|
+
] }),
|
|
20479
20632
|
" \u2014 ",
|
|
20480
20633
|
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)("span", { children: herkunft.szene_titel ?? (herkunft.lauf_gefunden ? "Lauf ohne benennbares Element" : "Lauf nicht in diesem Bestand") })
|
|
20481
20634
|
] }),
|
|
@@ -20492,11 +20645,6 @@ var AuftragsZeile = ({
|
|
|
20492
20645
|
"Profil ",
|
|
20493
20646
|
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)("code", { children: job.execution_profile_id })
|
|
20494
20647
|
] }),
|
|
20495
|
-
/* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("span", { children: [
|
|
20496
|
-
job.image_count,
|
|
20497
|
-
" ",
|
|
20498
|
-
job.image_count === 1 ? "Bild" : "Bilder"
|
|
20499
|
-
] }),
|
|
20500
20648
|
/* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("span", { children: [
|
|
20501
20649
|
"Format ",
|
|
20502
20650
|
job.aspect_ratio ?? "\u2014"
|
|
@@ -20531,7 +20679,7 @@ var AuftragsZeile = ({
|
|
|
20531
20679
|
"data-testid": `bildauftrag-start-${job.job_id}`,
|
|
20532
20680
|
disabled: gesperrt,
|
|
20533
20681
|
onClick: onStart,
|
|
20534
|
-
children: laufend ? "L\xE4uft \u2026" : "Start
|
|
20682
|
+
children: laufend ? "L\xE4uft \u2026" : "Start"
|
|
20535
20683
|
}
|
|
20536
20684
|
),
|
|
20537
20685
|
onPromptOeffnen && herkunft.lauf_gefunden && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
|
|
@@ -20551,18 +20699,18 @@ var AuftragsZeile = ({
|
|
|
20551
20699
|
onClick: () => setErgebnisOffen((o) => !o),
|
|
20552
20700
|
children: ergebnisOffen ? "Ergebnisse ausblenden" : "Ergebnisse ansehen"
|
|
20553
20701
|
}
|
|
20554
|
-
),
|
|
20555
|
-
kannWeitere && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
|
|
20556
|
-
"button",
|
|
20557
|
-
{
|
|
20558
|
-
type: "button",
|
|
20559
|
-
"data-testid": `bildauftrag-weitere-${job.job_id}`,
|
|
20560
|
-
onClick: () => setWeitereOffen((o) => !o),
|
|
20561
|
-
children: weitereOffen ? "Weitere Bilder schlie\xDFen" : "Weitere Bilder"
|
|
20562
|
-
}
|
|
20563
20702
|
)
|
|
20564
20703
|
] }),
|
|
20565
|
-
kannWeitere &&
|
|
20704
|
+
kannWeitere && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
|
|
20705
|
+
WeitereBilder,
|
|
20706
|
+
{
|
|
20707
|
+
job,
|
|
20708
|
+
gesperrt,
|
|
20709
|
+
modelle,
|
|
20710
|
+
modelleLaden,
|
|
20711
|
+
onErzeugen
|
|
20712
|
+
}
|
|
20713
|
+
),
|
|
20566
20714
|
ergebnisOffen && job.generation_batch_id && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
|
|
20567
20715
|
AuftragsErgebnis,
|
|
20568
20716
|
{
|
|
@@ -20575,123 +20723,106 @@ var AuftragsZeile = ({
|
|
|
20575
20723
|
}
|
|
20576
20724
|
);
|
|
20577
20725
|
};
|
|
20578
|
-
var WeitereBilder = ({
|
|
20579
|
-
const [anzahl, setAnzahl] = (0,
|
|
20580
|
-
const [
|
|
20581
|
-
const [
|
|
20582
|
-
const
|
|
20583
|
-
|
|
20584
|
-
|
|
20585
|
-
|
|
20586
|
-
const
|
|
20587
|
-
|
|
20588
|
-
job.execution_target_model_key,
|
|
20589
|
-
job.execution_profile_id
|
|
20590
|
-
) : null;
|
|
20591
|
-
const maxBilder = gewaehlt?.max_images ?? null;
|
|
20592
|
-
const anlegen = async (wieViele) => {
|
|
20593
|
-
const anfordern = port.imageJobs?.requestMoreImages;
|
|
20594
|
-
if (!anfordern || laeuft) return;
|
|
20726
|
+
var WeitereBilder = ({ job, gesperrt, modelle, modelleLaden, onErzeugen }) => {
|
|
20727
|
+
const [anzahl, setAnzahl] = (0, import_react60.useState)(1);
|
|
20728
|
+
const [wahlId, setWahlId] = (0, import_react60.useState)(null);
|
|
20729
|
+
const [laeuft, setLaeuft] = (0, import_react60.useState)(false);
|
|
20730
|
+
const [notiz, setNotiz] = (0, import_react60.useState)(null);
|
|
20731
|
+
const uebernommen = modelle ? modelle.find((m) => m.target_model_key === job.execution_target_model_key && m.profile_id === job.execution_profile_id) ?? null : null;
|
|
20732
|
+
const aktiv = wahlId && modelle?.find((m) => m.selection_id === wahlId) || uebernommen;
|
|
20733
|
+
const maxBilder = aktiv?.max_images ?? 1;
|
|
20734
|
+
const starten = async (wieViele) => {
|
|
20735
|
+
if (laeuft || gesperrt || !aktiv) return;
|
|
20595
20736
|
setLaeuft(true);
|
|
20596
20737
|
setNotiz(null);
|
|
20597
20738
|
try {
|
|
20598
|
-
|
|
20599
|
-
sourceJobId: job.job_id,
|
|
20600
|
-
imageCount: wieViele
|
|
20601
|
-
});
|
|
20602
|
-
if (ergebnis.kind === "ok") {
|
|
20603
|
-
onAngelegt(
|
|
20604
|
-
`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.`
|
|
20605
|
-
);
|
|
20606
|
-
} else {
|
|
20607
|
-
setNotiz({
|
|
20608
|
-
tone: ergebnis.kind === "rejected" ? "rejected" : "error",
|
|
20609
|
-
text: ergebnis.message
|
|
20610
|
-
});
|
|
20611
|
-
}
|
|
20739
|
+
await onErzeugen(wieViele, aktiv);
|
|
20612
20740
|
} catch (e) {
|
|
20613
20741
|
setNotiz({
|
|
20614
20742
|
tone: "error",
|
|
20615
|
-
text: `
|
|
20743
|
+
text: `Weitere Bilder zu ${job.job_id} sind gescheitert: ` + (e instanceof Error ? e.message : String(e))
|
|
20616
20744
|
});
|
|
20617
20745
|
} finally {
|
|
20618
20746
|
setLaeuft(false);
|
|
20619
20747
|
}
|
|
20620
20748
|
};
|
|
20749
|
+
if (modelleLaden && modelle === null) {
|
|
20750
|
+
return /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
|
|
20751
|
+
"p",
|
|
20752
|
+
{
|
|
20753
|
+
className: "szenen-bildauftraege__weitere",
|
|
20754
|
+
"data-testid": `bildauftrag-weitere-laden-${job.job_id}`,
|
|
20755
|
+
children: "Bildmodelle werden gepr\xFCft \u2026"
|
|
20756
|
+
}
|
|
20757
|
+
);
|
|
20758
|
+
}
|
|
20759
|
+
if (modelle !== null && modelle.length === 0) {
|
|
20760
|
+
return /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
|
|
20761
|
+
"p",
|
|
20762
|
+
{
|
|
20763
|
+
className: "szenen-bildauftraege__weitere",
|
|
20764
|
+
role: "alert",
|
|
20765
|
+
"data-testid": `bildauftrag-weitere-keine-${job.job_id}`,
|
|
20766
|
+
children: "Hier ist derzeit kein Bildmodell ausf\xFChrbar \u2014 weitere Bilder sind nicht m\xF6glich. Es wird kein Ersatz geraten."
|
|
20767
|
+
}
|
|
20768
|
+
);
|
|
20769
|
+
}
|
|
20621
20770
|
return /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
|
|
20622
|
-
"
|
|
20771
|
+
"p",
|
|
20623
20772
|
{
|
|
20624
20773
|
className: "szenen-bildauftraege__weitere",
|
|
20625
20774
|
"data-testid": `bildauftrag-weitere-panel-${job.job_id}`,
|
|
20626
|
-
"aria-label": "Weitere Bilder",
|
|
20627
20775
|
children: [
|
|
20628
|
-
|
|
20629
|
-
|
|
20630
|
-
|
|
20631
|
-
|
|
20632
|
-
|
|
20633
|
-
|
|
20634
|
-
|
|
20635
|
-
|
|
20636
|
-
|
|
20637
|
-
|
|
20638
|
-
|
|
20639
|
-
|
|
20640
|
-
|
|
20641
|
-
|
|
20642
|
-
|
|
20643
|
-
|
|
20644
|
-
|
|
20645
|
-
|
|
20646
|
-
|
|
20647
|
-
|
|
20648
|
-
|
|
20649
|
-
|
|
20650
|
-
|
|
20651
|
-
|
|
20652
|
-
|
|
20653
|
-
|
|
20654
|
-
|
|
20655
|
-
|
|
20656
|
-
|
|
20657
|
-
|
|
20658
|
-
|
|
20659
|
-
|
|
20660
|
-
|
|
20661
|
-
|
|
20662
|
-
|
|
20663
|
-
|
|
20664
|
-
|
|
20665
|
-
|
|
20666
|
-
|
|
20667
|
-
|
|
20668
|
-
|
|
20669
|
-
|
|
20670
|
-
|
|
20671
|
-
|
|
20672
|
-
|
|
20673
|
-
|
|
20674
|
-
|
|
20675
|
-
|
|
20676
|
-
{ length: Math.max(1, maxBilder ?? 1) },
|
|
20677
|
-
(_, i) => i + 1
|
|
20678
|
-
).map((n) => /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("option", { value: n, children: n }, n))
|
|
20679
|
-
}
|
|
20680
|
-
),
|
|
20681
|
-
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
|
|
20682
|
-
"button",
|
|
20683
|
-
{
|
|
20684
|
-
type: "button",
|
|
20685
|
-
"data-testid": `bildauftrag-weitere-start-${job.job_id}`,
|
|
20686
|
-
disabled: laeuft,
|
|
20687
|
-
onClick: () => void anlegen(anzahl),
|
|
20688
|
-
children: laeuft ? "Wird angelegt \u2026" : "Erzeugen"
|
|
20689
|
-
}
|
|
20690
|
-
)
|
|
20691
|
-
] })
|
|
20692
|
-
] }),
|
|
20776
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)("label", { htmlFor: `bildauftrag-modell-${job.job_id}`, children: "Weitere Bilder mit" }),
|
|
20777
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsxs)(
|
|
20778
|
+
"select",
|
|
20779
|
+
{
|
|
20780
|
+
id: `bildauftrag-modell-${job.job_id}`,
|
|
20781
|
+
"data-testid": `bildauftrag-modell-${job.job_id}`,
|
|
20782
|
+
value: aktiv?.selection_id ?? "",
|
|
20783
|
+
disabled: laeuft || gesperrt,
|
|
20784
|
+
onChange: (e) => {
|
|
20785
|
+
setWahlId(e.target.value);
|
|
20786
|
+
setNotiz(null);
|
|
20787
|
+
},
|
|
20788
|
+
children: [
|
|
20789
|
+
!aktiv && /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("option", { value: "", children: [
|
|
20790
|
+
job.execution_profile_id,
|
|
20791
|
+
" von ",
|
|
20792
|
+
job.execution_target_model_key,
|
|
20793
|
+
" \u2014 nicht mehr ausf\xFChrbar, bitte w\xE4hlen"
|
|
20794
|
+
] }),
|
|
20795
|
+
(modelle ?? []).map((m) => /* @__PURE__ */ (0, import_jsx_runtime52.jsxs)("option", { value: m.selection_id, children: [
|
|
20796
|
+
m.display_name,
|
|
20797
|
+
m.target_model_key === job.execution_target_model_key && m.profile_id === job.execution_profile_id ? " (wie gehabt)" : ""
|
|
20798
|
+
] }, m.selection_id))
|
|
20799
|
+
]
|
|
20800
|
+
}
|
|
20801
|
+
),
|
|
20802
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)("label", { htmlFor: `bildauftrag-weitere-anzahl-${job.job_id}`, children: "\xD7" }),
|
|
20803
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
|
|
20804
|
+
"select",
|
|
20805
|
+
{
|
|
20806
|
+
id: `bildauftrag-weitere-anzahl-${job.job_id}`,
|
|
20807
|
+
"data-testid": `bildauftrag-weitere-anzahl-${job.job_id}`,
|
|
20808
|
+
value: Math.min(anzahl, maxBilder),
|
|
20809
|
+
disabled: laeuft || gesperrt || !aktiv,
|
|
20810
|
+
onChange: (e) => setAnzahl(Number(e.target.value)),
|
|
20811
|
+
children: Array.from({ length: Math.max(1, maxBilder) }, (_, i) => i + 1).map((n) => /* @__PURE__ */ (0, import_jsx_runtime52.jsx)("option", { value: n, children: n }, n))
|
|
20812
|
+
}
|
|
20813
|
+
),
|
|
20814
|
+
/* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
|
|
20815
|
+
"button",
|
|
20816
|
+
{
|
|
20817
|
+
type: "button",
|
|
20818
|
+
"data-testid": `bildauftrag-weitere-start-${job.job_id}`,
|
|
20819
|
+
disabled: laeuft || gesperrt || !aktiv,
|
|
20820
|
+
onClick: () => void starten(Math.min(anzahl, maxBilder)),
|
|
20821
|
+
children: laeuft ? "L\xE4uft \u2026" : "Erzeugen"
|
|
20822
|
+
}
|
|
20823
|
+
),
|
|
20693
20824
|
notiz && /* @__PURE__ */ (0, import_jsx_runtime52.jsx)(
|
|
20694
|
-
"
|
|
20825
|
+
"span",
|
|
20695
20826
|
{
|
|
20696
20827
|
role: notiz.tone === "error" ? "alert" : void 0,
|
|
20697
20828
|
"data-testid": `bildauftrag-weitere-notiz-${job.job_id}`,
|
|
@@ -20745,9 +20876,9 @@ var AuftragsErgebnis = ({ port, jobId, generationBatchId }) => {
|
|
|
20745
20876
|
var Bildkontrolle = ({ port, stand, fehler: fehler5, laedt, onPromptOeffnen }) => {
|
|
20746
20877
|
const folge = stand?.folge ?? [];
|
|
20747
20878
|
const neuestes = folge.length > 0 ? folge[folge.length - 1].bild.media_ref : null;
|
|
20748
|
-
const [gewaehlt, setGewaehlt] = (0,
|
|
20749
|
-
const neuestesVorher = (0,
|
|
20750
|
-
(0,
|
|
20879
|
+
const [gewaehlt, setGewaehlt] = (0, import_react60.useState)(null);
|
|
20880
|
+
const neuestesVorher = (0, import_react60.useRef)(null);
|
|
20881
|
+
(0, import_react60.useEffect)(() => {
|
|
20751
20882
|
if (neuestes !== neuestesVorher.current) {
|
|
20752
20883
|
neuestesVorher.current = neuestes;
|
|
20753
20884
|
setGewaehlt(neuestes);
|
|
@@ -20874,13 +21005,13 @@ var SzenenView = ({
|
|
|
20874
21005
|
fokus = null,
|
|
20875
21006
|
onFokusVerlassen
|
|
20876
21007
|
}) => {
|
|
20877
|
-
const bereiche = (0,
|
|
20878
|
-
const faehig = (0,
|
|
20879
|
-
const [aktiv, setAktiv] = (0,
|
|
21008
|
+
const bereiche = (0, import_react61.useMemo)(() => areasFor(port), [port]);
|
|
21009
|
+
const faehig = (0, import_react61.useMemo)(() => capabilitiesOf(port), [port]);
|
|
21010
|
+
const [aktiv, setAktiv] = (0, import_react61.useState)(
|
|
20880
21011
|
() => resolveArea(port, fokus ? areaForFokus(fokus) : initialArea)
|
|
20881
21012
|
);
|
|
20882
|
-
const [interneRunWahl, setInterneRunWahl] = (0,
|
|
20883
|
-
(0,
|
|
21013
|
+
const [interneRunWahl, setInterneRunWahl] = (0, import_react61.useState)(null);
|
|
21014
|
+
(0, import_react61.useEffect)(() => {
|
|
20884
21015
|
if (!fokus) return;
|
|
20885
21016
|
setAktiv(resolveArea(port, areaForFokus(fokus)));
|
|
20886
21017
|
}, [fokus, port]);
|
|
@@ -25678,21 +25809,26 @@ function createImageJobPort(options) {
|
|
|
25678
25809
|
retryable: wahl.kind === "error" ? wahl.retryable : true
|
|
25679
25810
|
};
|
|
25680
25811
|
}
|
|
25681
|
-
const
|
|
25682
|
-
|
|
25683
|
-
|
|
25684
|
-
|
|
25685
|
-
|
|
25812
|
+
const zielModell = input.executionTargetModelKey ?? quelle.execution_target_model_key;
|
|
25813
|
+
const zielProfil = input.executionProfileId ?? quelle.execution_profile_id;
|
|
25814
|
+
if (input.executionTargetModelKey === void 0 !== (input.executionProfileId === void 0)) {
|
|
25815
|
+
return {
|
|
25816
|
+
kind: "rejected",
|
|
25817
|
+
message: `Zielmodell und Ausfuehrungsprofil gehoeren zusammen: Es wurde nur eines von beiden angegeben (Modell "${input.executionTargetModelKey ?? "\u2014"}", Profil "${input.executionProfileId ?? "\u2014"}"). Ein Zielmodell kann mehrere Profile tragen; welches gemeint ist, wird nicht geraten.`
|
|
25818
|
+
};
|
|
25819
|
+
}
|
|
25820
|
+
const gewaehlt = findExecutableImageModel(wahl.value, zielModell, zielProfil);
|
|
25686
25821
|
if (!gewaehlt) {
|
|
25687
25822
|
const auswahl = wahl.value.models.map((m) => m.selection_id);
|
|
25688
25823
|
return {
|
|
25689
25824
|
kind: "rejected",
|
|
25690
|
-
message: `Das Profil "${
|
|
25825
|
+
message: `Das Profil "${zielProfil}" von "${zielModell}" 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."}`)
|
|
25691
25826
|
};
|
|
25692
25827
|
}
|
|
25828
|
+
const format = input.aspectRatio === void 0 ? quelle.aspect_ratio ?? null : input.aspectRatio;
|
|
25693
25829
|
const befunde = checkImageModelParameters(gewaehlt, {
|
|
25694
25830
|
imageCount,
|
|
25695
|
-
...
|
|
25831
|
+
...format ? { aspectRatio: format } : {}
|
|
25696
25832
|
});
|
|
25697
25833
|
if (befunde.length > 0) {
|
|
25698
25834
|
return {
|
|
@@ -25723,7 +25859,7 @@ function createImageJobPort(options) {
|
|
|
25723
25859
|
execution_target_model_key: gewaehlt.target_model_key,
|
|
25724
25860
|
execution_profile_id: gewaehlt.profile_id,
|
|
25725
25861
|
image_count: imageCount,
|
|
25726
|
-
...
|
|
25862
|
+
...format ? { aspect_ratio: format } : {},
|
|
25727
25863
|
// **Nicht geraten und nicht erfunden.** Dieser Host kennt keinen
|
|
25728
25864
|
// Nutzerschluessel — der Flow-Client hat keine Identitaet, nur einen
|
|
25729
25865
|
// Bestand. Der Fortsetzungsauftrag traegt deshalb den Auftraggeber des
|
|
@@ -26063,7 +26199,7 @@ function resolveBootstrapTarget(input) {
|
|
|
26063
26199
|
// src/components/AvatarArchitectApp.tsx
|
|
26064
26200
|
var import_jsx_runtime54 = require("react/jsx-runtime");
|
|
26065
26201
|
function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia, buildInfo, initialHfToken, hfNamespace, allowDevNamespace, requireWorkspaceSelection = false, serverBaseUrl }) {
|
|
26066
|
-
(0,
|
|
26202
|
+
(0, import_react62.useEffect)(() => {
|
|
26067
26203
|
const id = "flow-styles";
|
|
26068
26204
|
if (!document.getElementById(id)) {
|
|
26069
26205
|
const style = document.createElement("style");
|
|
@@ -26072,33 +26208,33 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
|
|
|
26072
26208
|
document.head.appendChild(style);
|
|
26073
26209
|
}
|
|
26074
26210
|
}, []);
|
|
26075
|
-
const [showStart, setShowStart] = (0,
|
|
26076
|
-
const [layoutChoice, setLayoutChoice] = (0,
|
|
26211
|
+
const [showStart, setShowStart] = (0, import_react62.useState)(true);
|
|
26212
|
+
const [layoutChoice, setLayoutChoice] = (0, import_react62.useState)(() => {
|
|
26077
26213
|
try {
|
|
26078
26214
|
return localStorage.getItem("aa-layout") || null;
|
|
26079
26215
|
} catch {
|
|
26080
26216
|
return null;
|
|
26081
26217
|
}
|
|
26082
26218
|
});
|
|
26083
|
-
const [hfToken, setHfToken] = (0,
|
|
26084
|
-
const [hfTokenInput, setHfTokenInput] = (0,
|
|
26085
|
-
const [hfNamespaceLocal, setHfNamespaceLocal] = (0,
|
|
26219
|
+
const [hfToken, setHfToken] = (0, import_react62.useState)(initialHfToken || "");
|
|
26220
|
+
const [hfTokenInput, setHfTokenInput] = (0, import_react62.useState)(initialHfToken || "");
|
|
26221
|
+
const [hfNamespaceLocal, setHfNamespaceLocal] = (0, import_react62.useState)(() => {
|
|
26086
26222
|
try {
|
|
26087
26223
|
return normalizeSelectableNamespace(localStorage.getItem("aa-hf-namespace"));
|
|
26088
26224
|
} catch {
|
|
26089
26225
|
return DEFAULT_NAMESPACE;
|
|
26090
26226
|
}
|
|
26091
26227
|
});
|
|
26092
|
-
const [hfNamespaceFromServer, setHfNamespaceFromServer] = (0,
|
|
26093
|
-
const [serverProbeFailed, setServerProbeFailed] = (0,
|
|
26094
|
-
(0,
|
|
26228
|
+
const [hfNamespaceFromServer, setHfNamespaceFromServer] = (0, import_react62.useState)(null);
|
|
26229
|
+
const [serverProbeFailed, setServerProbeFailed] = (0, import_react62.useState)(false);
|
|
26230
|
+
(0, import_react62.useEffect)(() => {
|
|
26095
26231
|
if (hfNamespace !== void 0 || !serverBaseUrl) return;
|
|
26096
26232
|
fetch(`${serverBaseUrl}/api/status`).then((r) => r.json()).then((d) => {
|
|
26097
26233
|
if (typeof d.hfNamespace === "string" && d.hfNamespace) setHfNamespaceFromServer(d.hfNamespace);
|
|
26098
26234
|
else setServerProbeFailed(true);
|
|
26099
26235
|
}).catch(() => setServerProbeFailed(true));
|
|
26100
26236
|
}, [hfNamespace, serverBaseUrl]);
|
|
26101
|
-
const [targetConfirmed, setTargetConfirmed] = (0,
|
|
26237
|
+
const [targetConfirmed, setTargetConfirmed] = (0, import_react62.useState)(false);
|
|
26102
26238
|
const isDialogTarget = hfNamespace === void 0 && hfNamespaceFromServer === null && (!serverBaseUrl || serverProbeFailed);
|
|
26103
26239
|
const isProbingServer = hfNamespace === void 0 && !!serverBaseUrl && hfNamespaceFromServer === null && !serverProbeFailed;
|
|
26104
26240
|
const bootstrapTarget = resolveBootstrapTarget({
|
|
@@ -26109,8 +26245,8 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
|
|
|
26109
26245
|
isStartDialogOpen: isDialogTarget && !targetConfirmed
|
|
26110
26246
|
});
|
|
26111
26247
|
const effectiveNamespace = bootstrapTarget.namespace ?? "";
|
|
26112
|
-
const kontextRef = (0,
|
|
26113
|
-
const szenenPort = (0,
|
|
26248
|
+
const kontextRef = (0, import_react62.useRef)(null);
|
|
26249
|
+
const szenenPort = (0, import_react62.useMemo)(() => hfToken && effectiveNamespace ? createFlowSzenenViewPort({
|
|
26114
26250
|
generateText: onGeneratePrompt,
|
|
26115
26251
|
token: hfToken,
|
|
26116
26252
|
namespace: effectiveNamespace,
|
|
@@ -26132,10 +26268,10 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
|
|
|
26132
26268
|
const tagBaseResult = useTagBase(hfToken, effectiveNamespace);
|
|
26133
26269
|
const szenenKontextId = tagBaseResult.selectedContextId === null ? null : String(tagBaseResult.selectedContextId);
|
|
26134
26270
|
kontextRef.current = szenenKontextId;
|
|
26135
|
-
const [imageUploadStatus, setImageUploadStatus] = (0,
|
|
26136
|
-
const [bootstrapLog, setBootstrapLog] = (0,
|
|
26137
|
-
const [isBootstrapping, setIsBootstrapping] = (0,
|
|
26138
|
-
const [hfMissingImages, setHfMissingImages] = (0,
|
|
26271
|
+
const [imageUploadStatus, setImageUploadStatus] = (0, import_react62.useState)(/* @__PURE__ */ new Map());
|
|
26272
|
+
const [bootstrapLog, setBootstrapLog] = (0, import_react62.useState)([]);
|
|
26273
|
+
const [isBootstrapping, setIsBootstrapping] = (0, import_react62.useState)(false);
|
|
26274
|
+
const [hfMissingImages, setHfMissingImages] = (0, import_react62.useState)([]);
|
|
26139
26275
|
const syncTopSlot = /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)(import_jsx_runtime54.Fragment, { children: [
|
|
26140
26276
|
localOnlyCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime54.jsxs)("div", { style: { background: "rgba(234,179,8,0.15)", border: "1px solid rgba(234,179,8,0.3)", padding: "4px 10px", fontSize: 11, color: "#fbbf24", borderRadius: 4, marginBottom: 4 }, children: [
|
|
26141
26277
|
"\u26A0 ",
|
|
@@ -26194,37 +26330,39 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
|
|
|
26194
26330
|
setTargetConfirmed(true);
|
|
26195
26331
|
setShowStart(false);
|
|
26196
26332
|
};
|
|
26197
|
-
const [nodes, setNodes] = (0,
|
|
26198
|
-
const [edges, setEdges] = (0,
|
|
26199
|
-
const [history, setHistory] = (0,
|
|
26200
|
-
const [galleryItems, setGalleryItems] = (0,
|
|
26201
|
-
const galleryItemsRef = (0,
|
|
26202
|
-
(0,
|
|
26333
|
+
const [nodes, setNodes] = (0, import_react62.useState)([{ id: "1", type: "custom", position: { x: 0, y: 0 }, data: { label: "Fine Art Project", placeholder: "Name..." } }]);
|
|
26334
|
+
const [edges, setEdges] = (0, import_react62.useState)([]);
|
|
26335
|
+
const [history, setHistory] = (0, import_react62.useState)([]);
|
|
26336
|
+
const [galleryItems, setGalleryItems] = (0, import_react62.useState)([]);
|
|
26337
|
+
const galleryItemsRef = (0, import_react62.useRef)([]);
|
|
26338
|
+
(0, import_react62.useEffect)(() => {
|
|
26203
26339
|
galleryItemsRef.current = galleryItems;
|
|
26204
26340
|
}, [galleryItems]);
|
|
26205
|
-
const hfImageNotFoundRef = (0,
|
|
26206
|
-
const [galleryVisibleCount, setGalleryVisibleCount] = (0,
|
|
26207
|
-
const [historyVisibleCount, setHistoryVisibleCount] = (0,
|
|
26208
|
-
const
|
|
26209
|
-
|
|
26210
|
-
|
|
26211
|
-
|
|
26212
|
-
|
|
26213
|
-
|
|
26214
|
-
|
|
26215
|
-
|
|
26216
|
-
setHfMissingImages((prev) => prev.find((e) => e.id === entry.id) ? prev : [...prev, { id: entry.id, filename: entry.filename, mimeType: entry.mimeType ?? "image/jpeg", timestamp: entry.timestamp ?? 0 }]);
|
|
26217
|
-
continue;
|
|
26218
|
-
}
|
|
26341
|
+
const hfImageNotFoundRef = (0, import_react62.useRef)(/* @__PURE__ */ new Map());
|
|
26342
|
+
const [galleryVisibleCount, setGalleryVisibleCount] = (0, import_react62.useState)(20);
|
|
26343
|
+
const [historyVisibleCount, setHistoryVisibleCount] = (0, import_react62.useState)(20);
|
|
26344
|
+
const hfInFlightRef = (0, import_react62.useRef)(/* @__PURE__ */ new Set());
|
|
26345
|
+
const loadThumbnailsForEntries = (0, import_react62.useCallback)(async (entries) => {
|
|
26346
|
+
await loadThumbnailsSequentially(entries, {
|
|
26347
|
+
inFlight: hfInFlightRef.current,
|
|
26348
|
+
isAlreadyLoaded: (entry) => !!galleryItemsRef.current.find((g) => g.id === entry.id)?.base64,
|
|
26349
|
+
isKnownMissing: (entry) => hfImageNotFoundRef.current.has(entry.id),
|
|
26350
|
+
loadImage: (entry) => hfLoadImageAsBase64(entry.id, hfToken, effectiveNamespace, entry.filename, void 0, entry.mimeType, entry.hasThumb),
|
|
26351
|
+
onLoaded: (entry, b64) => {
|
|
26219
26352
|
const prefix = `data:${entry.mimeType || "image/jpeg"};base64,`;
|
|
26220
26353
|
setGalleryItems((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
|
|
26221
26354
|
setHistory((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
|
|
26222
|
-
}
|
|
26355
|
+
},
|
|
26356
|
+
onMissing: (entry) => {
|
|
26357
|
+
hfImageNotFoundRef.current.set(entry.id, Date.now());
|
|
26358
|
+
setHfMissingImages((prev) => prev.find((e) => e.id === entry.id) ? prev : [...prev, { id: entry.id, filename: entry.filename, mimeType: entry.mimeType ?? "image/jpeg", timestamp: entry.timestamp ?? 0 }]);
|
|
26359
|
+
},
|
|
26360
|
+
onError: (entry) => {
|
|
26223
26361
|
hfImageNotFoundRef.current.set(entry.id, Date.now());
|
|
26224
26362
|
}
|
|
26225
|
-
}
|
|
26363
|
+
});
|
|
26226
26364
|
}, [hfToken, effectiveNamespace]);
|
|
26227
|
-
(0,
|
|
26365
|
+
(0, import_react62.useEffect)(() => {
|
|
26228
26366
|
if (!hfState) return;
|
|
26229
26367
|
if (hfState.tags?.by_category) setWorkspaceTags(hfState.tags);
|
|
26230
26368
|
const hfIds = new Set(hfState.metadata.map((m) => m.id));
|
|
@@ -26251,36 +26389,24 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
|
|
|
26251
26389
|
return [...localOnly, ...merged].sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
|
|
26252
26390
|
});
|
|
26253
26391
|
}, [hfState]);
|
|
26254
|
-
(0,
|
|
26255
|
-
|
|
26256
|
-
|
|
26257
|
-
|
|
26258
|
-
|
|
26259
|
-
|
|
26260
|
-
|
|
26261
|
-
(0,
|
|
26262
|
-
|
|
26263
|
-
|
|
26264
|
-
|
|
26265
|
-
|
|
26266
|
-
const [
|
|
26267
|
-
const [
|
|
26268
|
-
const [
|
|
26269
|
-
const [
|
|
26270
|
-
const [
|
|
26271
|
-
const [
|
|
26272
|
-
const [promptFeedback, setPromptFeedback] = (0, import_react61.useState)(null);
|
|
26273
|
-
const [lastPromptPayload, setLastPromptPayload] = (0, import_react61.useState)(null);
|
|
26274
|
-
const [isPromptTabGenerating, setIsPromptTabGenerating] = (0, import_react61.useState)(false);
|
|
26275
|
-
const [activeTab, setActiveTab] = (0, import_react61.useState)("history");
|
|
26276
|
-
const [mobileTab, setMobileTab] = (0, import_react61.useState)("stage");
|
|
26277
|
-
const [middlePanel, setMiddlePanel] = (0, import_react61.useState)("stage");
|
|
26278
|
-
const [recentLabItems, setRecentLabItems] = (0, import_react61.useState)([]);
|
|
26279
|
-
const [aspectRatio, setAspectRatio] = (0, import_react61.useState)("1:1");
|
|
26280
|
-
const [selectedModel, setSelectedModel] = (0, import_react61.useState)("\u{1F34C} Nano Banana Pro");
|
|
26281
|
-
const [seed, setSeed] = (0, import_react61.useState)(Math.floor(Math.random() * 1e6));
|
|
26282
|
-
const [seedMode, setSeedMode] = (0, import_react61.useState)("random");
|
|
26283
|
-
const [imageCount, setImageCount] = (0, import_react61.useState)(() => {
|
|
26392
|
+
const [activePrompt, setActivePrompt] = (0, import_react62.useState)("");
|
|
26393
|
+
const [isSynthesizing, setIsSynthesizing] = (0, import_react62.useState)(false);
|
|
26394
|
+
const [activeGenerationsCount, setActiveGenerationsCount] = (0, import_react62.useState)(0);
|
|
26395
|
+
const [currentResult, setCurrentResult] = (0, import_react62.useState)(null);
|
|
26396
|
+
const [focusedNodeId, setFocusedNodeId] = (0, import_react62.useState)(null);
|
|
26397
|
+
const [leftTab, setLeftTab] = (0, import_react62.useState)("prompt");
|
|
26398
|
+
const [promptFeedback, setPromptFeedback] = (0, import_react62.useState)(null);
|
|
26399
|
+
const [lastPromptPayload, setLastPromptPayload] = (0, import_react62.useState)(null);
|
|
26400
|
+
const [isPromptTabGenerating, setIsPromptTabGenerating] = (0, import_react62.useState)(false);
|
|
26401
|
+
const [activeTab, setActiveTab] = (0, import_react62.useState)("history");
|
|
26402
|
+
const [mobileTab, setMobileTab] = (0, import_react62.useState)("stage");
|
|
26403
|
+
const [middlePanel, setMiddlePanel] = (0, import_react62.useState)("stage");
|
|
26404
|
+
const [recentLabItems, setRecentLabItems] = (0, import_react62.useState)([]);
|
|
26405
|
+
const [aspectRatio, setAspectRatio] = (0, import_react62.useState)("1:1");
|
|
26406
|
+
const [selectedModel, setSelectedModel] = (0, import_react62.useState)("\u{1F34C} Nano Banana Pro");
|
|
26407
|
+
const [seed, setSeed] = (0, import_react62.useState)(Math.floor(Math.random() * 1e6));
|
|
26408
|
+
const [seedMode, setSeedMode] = (0, import_react62.useState)("random");
|
|
26409
|
+
const [imageCount, setImageCount] = (0, import_react62.useState)(() => {
|
|
26284
26410
|
try {
|
|
26285
26411
|
const v = parseInt(localStorage.getItem("aa-image-count") || "", 10);
|
|
26286
26412
|
return v >= 1 && v <= 8 ? v : 4;
|
|
@@ -26302,41 +26428,57 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
|
|
|
26302
26428
|
{ label: "4 Bilder", value: "4" },
|
|
26303
26429
|
{ label: "8 Bilder", value: "8" }
|
|
26304
26430
|
];
|
|
26305
|
-
const [isLeftCollapsed, setIsLeftCollapsed] = (0,
|
|
26306
|
-
const [isRightCollapsed, setIsRightCollapsed] = (0,
|
|
26307
|
-
const [leftPanelWidth, setLeftPanelWidth] = (0,
|
|
26431
|
+
const [isLeftCollapsed, setIsLeftCollapsed] = (0, import_react62.useState)(false);
|
|
26432
|
+
const [isRightCollapsed, setIsRightCollapsed] = (0, import_react62.useState)(false);
|
|
26433
|
+
const [leftPanelWidth, setLeftPanelWidth] = (0, import_react62.useState)(() => {
|
|
26308
26434
|
try {
|
|
26309
26435
|
return parseInt(localStorage.getItem("aa-left-width") || "260", 10);
|
|
26310
26436
|
} catch {
|
|
26311
26437
|
return 260;
|
|
26312
26438
|
}
|
|
26313
26439
|
});
|
|
26314
|
-
const [rightPanelWidth, setRightPanelWidth] = (0,
|
|
26440
|
+
const [rightPanelWidth, setRightPanelWidth] = (0, import_react62.useState)(() => {
|
|
26315
26441
|
try {
|
|
26316
26442
|
return parseInt(localStorage.getItem("aa-right-width") || "320", 10);
|
|
26317
26443
|
} catch {
|
|
26318
26444
|
return 320;
|
|
26319
26445
|
}
|
|
26320
26446
|
});
|
|
26321
|
-
const [isPromptCollapsed, setIsPromptCollapsed] = (0,
|
|
26322
|
-
const [workspaceTags, setWorkspaceTags] = (0,
|
|
26323
|
-
const [highContrast, setHighContrast] = (0,
|
|
26447
|
+
const [isPromptCollapsed, setIsPromptCollapsed] = (0, import_react62.useState)(false);
|
|
26448
|
+
const [workspaceTags, setWorkspaceTags] = (0, import_react62.useState)(null);
|
|
26449
|
+
const [highContrast, setHighContrast] = (0, import_react62.useState)(() => {
|
|
26324
26450
|
try {
|
|
26325
26451
|
return localStorage.getItem("aa-contrast") === "high";
|
|
26326
26452
|
} catch {
|
|
26327
26453
|
return false;
|
|
26328
26454
|
}
|
|
26329
26455
|
});
|
|
26330
|
-
const [activeReferenceId, setActiveReferenceId] = (0,
|
|
26331
|
-
const [activeReferenceThumbnail, setActiveReferenceThumbnail] = (0,
|
|
26332
|
-
const [isScanningImage, setIsScanningImage] = (0,
|
|
26333
|
-
const [touchStartX, setTouchStartX] = (0,
|
|
26334
|
-
const [isFullscreen, setIsFullscreen] = (0,
|
|
26335
|
-
const [zoomScale, setZoomScale] = (0,
|
|
26336
|
-
const [zoomOffset, setZoomOffset] = (0,
|
|
26337
|
-
const
|
|
26338
|
-
|
|
26339
|
-
|
|
26456
|
+
const [activeReferenceId, setActiveReferenceId] = (0, import_react62.useState)(null);
|
|
26457
|
+
const [activeReferenceThumbnail, setActiveReferenceThumbnail] = (0, import_react62.useState)(null);
|
|
26458
|
+
const [isScanningImage, setIsScanningImage] = (0, import_react62.useState)(false);
|
|
26459
|
+
const [touchStartX, setTouchStartX] = (0, import_react62.useState)(null);
|
|
26460
|
+
const [isFullscreen, setIsFullscreen] = (0, import_react62.useState)(false);
|
|
26461
|
+
const [zoomScale, setZoomScale] = (0, import_react62.useState)(1);
|
|
26462
|
+
const [zoomOffset, setZoomOffset] = (0, import_react62.useState)({ x: 0, y: 0 });
|
|
26463
|
+
const { historyVisible, galleryVisible } = computePrefetchVisibility({
|
|
26464
|
+
layoutChoice,
|
|
26465
|
+
appShellVisible: !showStart && !requireWorkspaceSelection && !(isFullscreen && !!currentResult?.base64),
|
|
26466
|
+
mobileTab,
|
|
26467
|
+
middlePanel,
|
|
26468
|
+
activeTab,
|
|
26469
|
+
isRightCollapsed
|
|
26470
|
+
});
|
|
26471
|
+
useThumbnailPrefetch({
|
|
26472
|
+
metadata: hfState?.metadata,
|
|
26473
|
+
historyVisible,
|
|
26474
|
+
galleryVisible,
|
|
26475
|
+
historyVisibleCount,
|
|
26476
|
+
galleryVisibleCount,
|
|
26477
|
+
loadThumbnailsForEntries
|
|
26478
|
+
});
|
|
26479
|
+
const lastPinchDist = (0, import_react62.useRef)(null);
|
|
26480
|
+
const lastTapTime = (0, import_react62.useRef)(0);
|
|
26481
|
+
const dragStart = (0, import_react62.useRef)(null);
|
|
26340
26482
|
const openFullscreen = () => {
|
|
26341
26483
|
setIsFullscreen(true);
|
|
26342
26484
|
setZoomScale(1);
|
|
@@ -26399,7 +26541,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
|
|
|
26399
26541
|
setActiveReferenceId(null);
|
|
26400
26542
|
setActiveReferenceThumbnail(null);
|
|
26401
26543
|
};
|
|
26402
|
-
const labServices = (0,
|
|
26544
|
+
const labServices = (0, import_react62.useMemo)(() => {
|
|
26403
26545
|
const available = groupGenerationsToLabItems([...galleryItems, ...history]);
|
|
26404
26546
|
return {
|
|
26405
26547
|
availableItems: available,
|
|
@@ -26479,14 +26621,14 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
|
|
|
26479
26621
|
setIsScanningImage(false);
|
|
26480
26622
|
}
|
|
26481
26623
|
};
|
|
26482
|
-
const currentIndex = (0,
|
|
26483
|
-
const goToPrev = (0,
|
|
26624
|
+
const currentIndex = (0, import_react62.useMemo)(() => history.findIndex((h) => h.id === currentResult?.id), [history, currentResult]);
|
|
26625
|
+
const goToPrev = (0, import_react62.useCallback)(() => {
|
|
26484
26626
|
if (currentIndex > 0) setCurrentResult(history[currentIndex - 1]);
|
|
26485
26627
|
}, [currentIndex, history]);
|
|
26486
|
-
const goToNext = (0,
|
|
26628
|
+
const goToNext = (0, import_react62.useCallback)(() => {
|
|
26487
26629
|
if (currentIndex < history.length - 1) setCurrentResult(history[currentIndex + 1]);
|
|
26488
26630
|
}, [currentIndex, history]);
|
|
26489
|
-
const handleGallerySelect = (0,
|
|
26631
|
+
const handleGallerySelect = (0, import_react62.useCallback)((g) => {
|
|
26490
26632
|
setCurrentResult(g);
|
|
26491
26633
|
setMobileTab("stage");
|
|
26492
26634
|
if (g.hasThumb && g.filename && hfToken && !g.fullBase64) {
|
|
@@ -26499,7 +26641,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
|
|
|
26499
26641
|
});
|
|
26500
26642
|
}
|
|
26501
26643
|
}, [hfToken, effectiveNamespace]);
|
|
26502
|
-
const handleTitleSet = (0,
|
|
26644
|
+
const handleTitleSet = (0, import_react62.useCallback)((id) => {
|
|
26503
26645
|
const ts = Date.now();
|
|
26504
26646
|
setGalleryItems((prev) => prev.map((g) => g.id === id ? { ...g, titleTs: ts } : g));
|
|
26505
26647
|
setHistory((prev) => prev.map((g) => g.id === id ? { ...g, titleTs: ts } : g));
|
|
@@ -26508,13 +26650,13 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
|
|
|
26508
26650
|
});
|
|
26509
26651
|
}
|
|
26510
26652
|
}, [hfToken, effectiveNamespace, hfWriteEvent]);
|
|
26511
|
-
const currentGroup = (0,
|
|
26653
|
+
const currentGroup = (0, import_react62.useMemo)(() => {
|
|
26512
26654
|
if (!currentResult?.prompt) return [];
|
|
26513
26655
|
const groups = groupByPrompt(galleryItems.filter((g) => g.status === "done" && !!g.base64));
|
|
26514
26656
|
const group = groups.find((gr) => gr.prompt === currentResult.prompt);
|
|
26515
26657
|
return group ? group.items : [];
|
|
26516
26658
|
}, [galleryItems, currentResult?.prompt]);
|
|
26517
|
-
(0,
|
|
26659
|
+
(0, import_react62.useEffect)(() => {
|
|
26518
26660
|
if (!currentResult?.prompt || !hfToken || !effectiveNamespace || !hfState) return;
|
|
26519
26661
|
const siblings = galleryItemsRef.current.filter(
|
|
26520
26662
|
(g) => g.prompt === currentResult.prompt && !g.base64 && !hfImageNotFoundRef.current.has(g.id)
|
|
@@ -26553,7 +26695,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
|
|
|
26553
26695
|
}
|
|
26554
26696
|
) : null;
|
|
26555
26697
|
useKeyboardNavigation(history, currentResult, setCurrentResult);
|
|
26556
|
-
const getSubtreeFormat = (0,
|
|
26698
|
+
const getSubtreeFormat = (0, import_react62.useCallback)((nodeId, depth = 0) => {
|
|
26557
26699
|
const node = nodes.find((n) => n.id === nodeId);
|
|
26558
26700
|
if (!node) return "";
|
|
26559
26701
|
const childrenIds = edges.filter((e) => e.source === nodeId).map((e) => e.target);
|
|
@@ -26561,7 +26703,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
|
|
|
26561
26703
|
return `${indent}- ${node.data.label || "(unbenannt)"}
|
|
26562
26704
|
` + childrenIds.map((id) => getSubtreeFormat(id, depth + 1)).join("");
|
|
26563
26705
|
}, [nodes, edges]);
|
|
26564
|
-
const activePath = (0,
|
|
26706
|
+
const activePath = (0, import_react62.useMemo)(() => {
|
|
26565
26707
|
if (!focusedNodeId) return /* @__PURE__ */ new Set();
|
|
26566
26708
|
const path = /* @__PURE__ */ new Set([focusedNodeId]);
|
|
26567
26709
|
let currId = focusedNodeId;
|
|
@@ -27713,7 +27855,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
|
|
|
27713
27855
|
}
|
|
27714
27856
|
|
|
27715
27857
|
// src/components/FaApp.tsx
|
|
27716
|
-
var
|
|
27858
|
+
var import_react63 = require("react");
|
|
27717
27859
|
var import_jsx_runtime55 = require("react/jsx-runtime");
|
|
27718
27860
|
function FaApp({
|
|
27719
27861
|
onGenerateImage,
|
|
@@ -27729,15 +27871,15 @@ function FaApp({
|
|
|
27729
27871
|
serverBaseUrl,
|
|
27730
27872
|
buildInfo
|
|
27731
27873
|
}) {
|
|
27732
|
-
const [hfNamespace, setHfNamespace] = (0,
|
|
27733
|
-
(0,
|
|
27874
|
+
const [hfNamespace, setHfNamespace] = (0, import_react63.useState)(void 0);
|
|
27875
|
+
(0, import_react63.useEffect)(() => {
|
|
27734
27876
|
if (!serverBaseUrl) return;
|
|
27735
27877
|
fetch(`${serverBaseUrl}/api/status`).then((r) => r.json()).then((d) => {
|
|
27736
27878
|
if (typeof d.hfNamespace === "string") setHfNamespace(d.hfNamespace);
|
|
27737
27879
|
}).catch(() => {
|
|
27738
27880
|
});
|
|
27739
27881
|
}, [serverBaseUrl]);
|
|
27740
|
-
const wrappedPrompt = (0,
|
|
27882
|
+
const wrappedPrompt = (0, import_react63.useMemo)(
|
|
27741
27883
|
() => alsFlowTextZugang(onGeneratePrompt),
|
|
27742
27884
|
[onGeneratePrompt]
|
|
27743
27885
|
);
|
|
@@ -27991,15 +28133,15 @@ function createInMemoryWorkspacePersistence(now = () => Date.now()) {
|
|
|
27991
28133
|
}
|
|
27992
28134
|
|
|
27993
28135
|
// node_modules/zustand/esm/react.mjs
|
|
27994
|
-
var
|
|
28136
|
+
var import_react64 = __toESM(require("react"), 1);
|
|
27995
28137
|
var identity = (arg) => arg;
|
|
27996
28138
|
function useStore(api, selector = identity) {
|
|
27997
|
-
const slice =
|
|
28139
|
+
const slice = import_react64.default.useSyncExternalStore(
|
|
27998
28140
|
api.subscribe,
|
|
27999
|
-
|
|
28000
|
-
|
|
28141
|
+
import_react64.default.useCallback(() => selector(api.getState()), [api, selector]),
|
|
28142
|
+
import_react64.default.useCallback(() => selector(api.getInitialState()), [api, selector])
|
|
28001
28143
|
);
|
|
28002
|
-
|
|
28144
|
+
import_react64.default.useDebugValue(slice);
|
|
28003
28145
|
return slice;
|
|
28004
28146
|
}
|
|
28005
28147
|
|