@rslsp1/fa-app-tools 2.0.30 → 2.0.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -566,30 +566,39 @@ async function hfUploadImage(base64, id, token, mimeType = "image/jpeg") {
566
566
  commitTitle: `Add image ${id}`
567
567
  });
568
568
  }
569
- async function hfLoadImageAsBase64(id, token, namespace) {
570
- const paths = [`images/${id}`];
571
- if (namespace) paths.push(`${namespace}images/${id}`);
572
- for (const basePath of paths) {
573
- for (const ext of ["jpg", "png"]) {
574
- try {
575
- const res = await fetch(
576
- `${HF_BASE}/datasets/${HF_REPO}/resolve/main/${basePath}.${ext}?download=true`,
577
- { headers: { Authorization: `Bearer ${token}` } }
578
- );
579
- if (!res.ok) continue;
580
- const blob = await res.blob();
581
- return new Promise((resolve, reject) => {
582
- const reader = new FileReader();
583
- reader.onload = () => resolve(reader.result.split(",")[1]);
584
- reader.onerror = reject;
585
- reader.readAsDataURL(blob);
586
- });
587
- } catch {
588
- continue;
569
+ async function hfLoadImageAsBase64(id, token, namespace, filename, on404, mimeType, hasThumb) {
570
+ const fetchBase64 = async (repoPath) => {
571
+ try {
572
+ const res = await fetch(
573
+ `${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
574
+ { headers: { Authorization: `Bearer ${token}` } }
575
+ );
576
+ if (!res.ok) {
577
+ if (on404 && on404()) return "ABORT";
578
+ return null;
589
579
  }
580
+ const blob = await res.blob();
581
+ return new Promise((resolve, reject) => {
582
+ const reader = new FileReader();
583
+ reader.onload = () => resolve(reader.result.split(",")[1]);
584
+ reader.onerror = reject;
585
+ reader.readAsDataURL(blob);
586
+ });
587
+ } catch {
588
+ return null;
590
589
  }
590
+ };
591
+ if (filename) {
592
+ if (hasThumb) {
593
+ const thumb = await fetchBase64(`thumbs/${filename}`);
594
+ if (thumb && thumb !== "ABORT") return thumb;
595
+ }
596
+ const result2 = await fetchBase64(`images/${filename}`);
597
+ return result2 === "ABORT" ? null : result2;
591
598
  }
592
- return null;
599
+ const ext = mimeType === "image/png" ? "png" : "jpg";
600
+ const result = await fetchBase64(`images/${id}.${ext}`);
601
+ return result === "ABORT" ? null : result;
593
602
  }
594
603
  var import_jszip2, import_hub, HF_BASE, HF_REPO, HF_TOKEN_KEY, SESSION_CLIENT_ID;
595
604
  var init_hfStateService = __esm({
@@ -650,10 +659,6 @@ __export(index_exports, {
650
659
  cleanAiResponse: () => cleanAiResponse,
651
660
  createFlowServices: () => createFlowServices,
652
661
  exportProjectToZip: () => exportProjectToZip,
653
- faServerDelete: () => faServerDelete,
654
- faServerGet: () => faServerGet,
655
- faServerPost: () => faServerPost,
656
- faServerPut: () => faServerPut,
657
662
  findForks: () => findForks,
658
663
  findTips: () => findTips,
659
664
  formatTreeToMarkdown: () => formatTreeToMarkdown,
@@ -1023,8 +1028,10 @@ var CompactDropdown = ({
1023
1028
  };
1024
1029
 
1025
1030
  // src/components/HistoryPanel.tsx
1026
- var import_react4 = require("motion/react");
1031
+ var import_react4 = require("react");
1032
+ var import_react5 = require("motion/react");
1027
1033
  var import_jsx_runtime5 = require("react/jsx-runtime");
1034
+ var PAGE_SIZE = 20;
1028
1035
  var formatFriendlyTimestamp = (timestamp) => {
1029
1036
  const date = new Date(timestamp);
1030
1037
  const now = /* @__PURE__ */ new Date();
@@ -1037,46 +1044,63 @@ var formatFriendlyTimestamp = (timestamp) => {
1037
1044
  return `${date.toLocaleDateString([], { day: "2-digit", month: "2-digit" })}, ${timeStr}`;
1038
1045
  };
1039
1046
  var HistoryPanel = ({ history, currentResultId, onSelect, onDelete }) => {
1047
+ const [visibleCount, setVisibleCount] = (0, import_react4.useState)(PAGE_SIZE);
1048
+ const visibleHistory = history.slice(0, visibleCount);
1040
1049
  if (history.length === 0) {
1041
1050
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col items-center justify-center py-20 text-center gap-4 opacity-10", children: [
1042
1051
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "material-symbols-outlined text-[64px]", children: "history" }),
1043
1052
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-[10px] font-bold uppercase tracking-widest", children: "Keine Historie" })
1044
1053
  ] });
1045
1054
  }
1046
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react4.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 p-4 flex flex-col gap-3 overflow-y-auto dark-scrollbar", children: history.map((gen) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1047
- "div",
1048
- {
1049
- className: `flex gap-3 p-2 bg-white/5 border rounded-2xl cursor-pointer group transition-all relative ${currentResultId === gen.id ? "border-white/40 bg-white/10 shadow-lg" : "border-white/5 hover:border-white/20"}`,
1050
- onClick: () => onSelect(gen),
1051
- children: [
1052
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-14 h-14 rounded-xl overflow-hidden bg-black shrink-0 border border-white/5", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: gen.base64 ? gen.base64.startsWith("data:") ? gen.base64 : `data:image/png;base64,${gen.base64}` : "", className: "w-full h-full object-cover", alt: "Thumbnail" }) }),
1053
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex-1 py-0.5 overflow-hidden", children: [
1054
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between mb-1", children: [
1055
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[7px] font-bold text-white/20 uppercase whitespace-nowrap", children: formatFriendlyTimestamp(gen.timestamp) }),
1056
- gen.model && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[7px] font-bold text-white/40 bg-white/5 px-1 rounded uppercase truncate max-w-[60px]", children: gen.model })
1055
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_react5.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 p-4 flex flex-col gap-3 overflow-y-auto dark-scrollbar", children: [
1056
+ visibleHistory.map((gen) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1057
+ "div",
1058
+ {
1059
+ className: `flex gap-3 p-2 bg-white/5 border rounded-2xl cursor-pointer group transition-all relative ${currentResultId === gen.id ? "border-white/40 bg-white/10 shadow-lg" : "border-white/5 hover:border-white/20"}`,
1060
+ onClick: () => onSelect(gen),
1061
+ children: [
1062
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-14 h-14 rounded-xl overflow-hidden bg-black shrink-0 border border-white/5 flex items-center justify-center", children: gen.status === "processing" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-5 h-5 border-t-2 border-white/40 rounded-full animate-spin" }) : gen.status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "material-symbols-outlined text-red-400 text-[20px]", children: "warning" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: gen.base64 ? gen.base64.startsWith("data:") ? gen.base64 : `data:image/png;base64,${gen.base64}` : "", className: "w-full h-full object-cover", alt: "Thumbnail" }) }),
1063
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex-1 py-0.5 overflow-hidden", children: [
1064
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between mb-1", children: [
1065
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[7px] font-bold text-white/20 uppercase whitespace-nowrap", children: formatFriendlyTimestamp(gen.timestamp) }),
1066
+ gen.model && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[7px] font-bold text-white/40 bg-white/5 px-1 rounded uppercase truncate max-w-[60px]", children: gen.model })
1067
+ ] }),
1068
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "text-[9px] text-white/60 line-clamp-2 leading-tight", children: [
1069
+ '"',
1070
+ gen.prompt,
1071
+ '"'
1072
+ ] }),
1073
+ gen.status === "processing" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-[8px] text-white/30 mt-0.5", children: "Generiert\u2026" }),
1074
+ gen.status === "error" && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-[8px] text-red-400/70 mt-0.5 truncate", children: gen.error?.message })
1057
1075
  ] }),
1058
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "text-[9px] text-white/60 line-clamp-2 leading-tight", children: [
1059
- '"',
1060
- gen.prompt,
1061
- '"'
1062
- ] })
1063
- ] }),
1064
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { onClick: (e) => {
1065
- e.stopPropagation();
1066
- onDelete(gen.id);
1067
- }, className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 w-6 h-6 flex items-center justify-center bg-red-500/20 text-red-400 hover:bg-red-500 hover:text-white rounded-md transition-all", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "material-symbols-outlined text-[14px]", children: "delete" }) })
1068
- ]
1069
- },
1070
- gen.id
1071
- )) });
1076
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1077
+ "button",
1078
+ {
1079
+ onClick: (e) => {
1080
+ e.stopPropagation();
1081
+ onDelete(gen.id);
1082
+ },
1083
+ className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 w-6 h-6 flex items-center justify-center bg-red-500/20 text-red-400 hover:bg-red-500 hover:text-white rounded-md transition-all",
1084
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "material-symbols-outlined text-[14px]", children: "delete" })
1085
+ }
1086
+ )
1087
+ ]
1088
+ },
1089
+ gen.id
1090
+ )),
1091
+ visibleCount < history.length && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("button", { type: "button", onClick: () => setVisibleCount((c) => c + PAGE_SIZE), className: "w-full py-2 bg-white/5 hover:bg-white/10 border border-white/10 rounded-xl text-[10px] font-bold uppercase text-white/60 hover:text-white transition-all", children: [
1092
+ history.length - visibleCount,
1093
+ " weitere laden"
1094
+ ] })
1095
+ ] });
1072
1096
  };
1073
1097
 
1074
1098
  // src/components/InspectPanel.tsx
1075
- var import_react5 = require("react");
1076
- var import_react6 = require("motion/react");
1099
+ var import_react6 = require("react");
1100
+ var import_react7 = require("motion/react");
1077
1101
  var import_jsx_runtime6 = require("react/jsx-runtime");
1078
1102
  var InspectPanel = ({ currentResult, history, onSelect, workspaceTags, onTagToggle }) => {
1079
- const currentIndex = (0, import_react5.useMemo)(() => history.findIndex((h) => h.id === currentResult?.id), [history, currentResult]);
1103
+ const currentIndex = (0, import_react6.useMemo)(() => history.findIndex((h) => h.id === currentResult?.id), [history, currentResult]);
1080
1104
  if (!currentResult) {
1081
1105
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col items-center justify-center py-20 text-center gap-4 opacity-10", children: [
1082
1106
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "material-symbols-outlined text-[64px]", children: "info" }),
@@ -1084,7 +1108,7 @@ var InspectPanel = ({ currentResult, history, onSelect, workspaceTags, onTagTogg
1084
1108
  ] });
1085
1109
  }
1086
1110
  const fullDateStr = new Date(currentResult.timestamp).toLocaleString([], { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit" });
1087
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react6.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 flex flex-col overflow-hidden", children: [
1111
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react7.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 flex flex-col overflow-hidden", children: [
1088
1112
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-shrink-0 border-b border-white/5 bg-black/20 py-3 px-4", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex gap-2 overflow-x-auto no-scrollbar pb-1", children: history.map((gen) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { onClick: () => onSelect(gen), className: `w-10 h-10 rounded-lg overflow-hidden border shrink-0 transition-all ${currentResult.id === gen.id ? "border-white scale-105 shadow-lg" : "border-white/5 opacity-40 hover:opacity-100"}`, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { src: gen.base64 ? gen.base64.startsWith("data:") ? gen.base64 : `data:image/png;base64,${gen.base64}` : "", className: "w-full h-full object-cover", alt: "Thumbnail" }) }, gen.id)) }) }),
1089
1113
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-1 overflow-y-auto p-4 flex flex-col gap-6 dark-scrollbar pb-10", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col gap-4", children: [
1090
1114
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SectionLabel, { children: "Vorschau" }),
@@ -1137,8 +1161,8 @@ var InspectPanel = ({ currentResult, history, onSelect, workspaceTags, onTagTogg
1137
1161
  };
1138
1162
 
1139
1163
  // src/components/SetupPanel.tsx
1140
- var import_react7 = require("react");
1141
- var import_react8 = require("motion/react");
1164
+ var import_react8 = require("react");
1165
+ var import_react9 = require("motion/react");
1142
1166
  var import_jsx_runtime7 = require("react/jsx-runtime");
1143
1167
  var PRESET_URLS = [
1144
1168
  "https://jsonplaceholder.typicode.com/todos/1",
@@ -1146,12 +1170,12 @@ var PRESET_URLS = [
1146
1170
  "https://esm.sh/@rslsp1/fa-app-tools@latest"
1147
1171
  ];
1148
1172
  var SetupPanel = ({ onWorkspaceImport, buildInfo }) => {
1149
- const workspaceInputRef = (0, import_react7.useRef)(null);
1150
- const [urlInput, setUrlInput] = (0, import_react7.useState)("");
1151
- const [tokenInput, setTokenInput] = (0, import_react7.useState)("");
1152
- const [testStatus, setTestStatus] = (0, import_react7.useState)("idle");
1153
- const [result, setResult] = (0, import_react7.useState)(null);
1154
- const [fetchError, setFetchError] = (0, import_react7.useState)(null);
1173
+ const workspaceInputRef = (0, import_react8.useRef)(null);
1174
+ const [urlInput, setUrlInput] = (0, import_react8.useState)("");
1175
+ const [tokenInput, setTokenInput] = (0, import_react8.useState)("");
1176
+ const [testStatus, setTestStatus] = (0, import_react8.useState)("idle");
1177
+ const [result, setResult] = (0, import_react8.useState)(null);
1178
+ const [fetchError, setFetchError] = (0, import_react8.useState)(null);
1155
1179
  const runTest = async (url) => {
1156
1180
  if (!url.trim()) return;
1157
1181
  setTestStatus("loading");
@@ -1191,7 +1215,7 @@ var SetupPanel = ({ onWorkspaceImport, buildInfo }) => {
1191
1215
  setTestStatus("error");
1192
1216
  }
1193
1217
  };
1194
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react8.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 p-6 flex flex-col gap-10 overflow-y-auto", children: [
1218
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react9.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 p-6 flex flex-col gap-10 overflow-y-auto", children: [
1195
1219
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-4", children: [
1196
1220
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-1", children: [
1197
1221
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SectionLabel, { children: "Workspace Management" }),
@@ -1314,18 +1338,48 @@ var SetupPanel = ({ onWorkspaceImport, buildInfo }) => {
1314
1338
  };
1315
1339
 
1316
1340
  // src/components/MediaLibrary.tsx
1317
- var import_react9 = require("motion/react");
1341
+ var import_react10 = require("react");
1342
+ var import_react11 = require("motion/react");
1343
+
1344
+ // src/lib/grouping.ts
1345
+ function groupByPrompt(items) {
1346
+ const map = /* @__PURE__ */ new Map();
1347
+ for (const item of items) {
1348
+ const key = item.prompt ?? "";
1349
+ if (!map.has(key)) map.set(key, []);
1350
+ map.get(key).push(item);
1351
+ }
1352
+ const groups = [];
1353
+ for (const [prompt, groupItems] of map) {
1354
+ const titled = groupItems.filter((i) => i.titleTs);
1355
+ const representative = titled.length > 0 ? titled.reduce((a, b) => a.titleTs > b.titleTs ? a : b) : groupItems.reduce((a, b) => a.timestamp > b.timestamp ? a : b);
1356
+ groups.push({ prompt, items: groupItems, representative });
1357
+ }
1358
+ return groups.sort((a, b) => {
1359
+ const aMax = Math.max(...a.items.map((i) => i.timestamp));
1360
+ const bMax = Math.max(...b.items.map((i) => i.timestamp));
1361
+ return bMax - aMax;
1362
+ });
1363
+ }
1364
+
1365
+ // src/components/MediaLibrary.tsx
1318
1366
  var import_jsx_runtime8 = require("react/jsx-runtime");
1367
+ var PAGE_SIZE2 = 20;
1319
1368
  var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, onBatchDownload, onGenerateReference }) => {
1369
+ const [visibleCount, setVisibleCount] = (0, import_react10.useState)(PAGE_SIZE2);
1320
1370
  const selectedCount = items.filter((i) => i.selectedForExport).length;
1371
+ const groups = groupByPrompt(items);
1372
+ const visibleGroups = groups.slice(0, visibleCount);
1321
1373
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col h-full overflow-hidden", children: [
1322
1374
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col p-4 border-b border-white/5 gap-3 bg-black/20", children: [
1323
1375
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center justify-between", children: [
1324
1376
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col", children: [
1325
1377
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-[10px] font-bold text-white uppercase tracking-widest", children: "Projekt Galerie" }),
1326
1378
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "text-[9px] text-white/30 uppercase tracking-tighter", children: [
1379
+ visibleCount < groups.length ? `${visibleCount} / ${groups.length}` : groups.length,
1380
+ " Gruppen \xB7 ",
1327
1381
  items.length,
1328
- " Assets geladen"
1382
+ " Assets"
1329
1383
  ] })
1330
1384
  ] }),
1331
1385
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("button", { type: "button", onClick: () => onImport?.(), className: "flex items-center gap-1.5 px-3 py-1.5 bg-blue-500 hover:bg-blue-400 text-white rounded-lg text-[10px] font-bold uppercase transition-all shadow-[0_0_15px_rgba(59,130,246,0.3)]", children: [
@@ -1333,7 +1387,7 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
1333
1387
  "Laden"
1334
1388
  ] })
1335
1389
  ] }),
1336
- selectedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react9.motion.div, { initial: { opacity: 0, y: -10 }, animate: { opacity: 1, y: 0 }, className: "flex items-center justify-between bg-blue-500/10 border border-blue-500/20 p-2 rounded-xl", children: [
1390
+ selectedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react11.motion.div, { initial: { opacity: 0, y: -10 }, animate: { opacity: 1, y: 0 }, className: "flex items-center justify-between bg-blue-500/10 border border-blue-500/20 p-2 rounded-xl", children: [
1337
1391
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "text-[9px] font-bold uppercase text-blue-400 ml-1", children: [
1338
1392
  selectedCount,
1339
1393
  " ausgew\xE4hlt"
@@ -1344,44 +1398,60 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
1344
1398
  ] })
1345
1399
  ] })
1346
1400
  ] }),
1347
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex-1 overflow-y-auto p-4 dark-scrollbar", children: items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col items-center justify-center py-20 text-center gap-4 opacity-10", children: [
1401
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex-1 overflow-y-auto p-4 dark-scrollbar", children: groups.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col items-center justify-center py-20 text-center gap-4 opacity-10", children: [
1348
1402
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "material-symbols-outlined text-[64px]", children: "photo_library" }),
1349
1403
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col gap-1", children: [
1350
1404
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-[12px] font-bold uppercase tracking-widest", children: "Keine Medien" }),
1351
1405
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-[10px] italic", children: "Importiere Assets aus deinem Projekt." })
1352
1406
  ] })
1353
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "grid grid-cols-2 gap-3 pb-10", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react9.motion.div, { initial: { opacity: 0, scale: 0.9 }, animate: { opacity: 1, scale: 1 }, className: `relative aspect-square group/item rounded-xl overflow-hidden border transition-all bg-white/5 cursor-pointer shadow-lg ${item.selectedForExport ? "border-blue-500 shadow-[0_0_15px_rgba(59,130,246,0.2)]" : "border-white/10 opacity-70 hover:opacity-100"}`, onClick: () => onSelect?.(item), children: [
1354
- item.status === "processing" ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "w-full h-full flex flex-col items-center justify-center gap-2 bg-black/40", children: [
1355
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "w-6 h-6 border-t-2 border-white rounded-full animate-spin" }),
1356
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-[8px] text-white/40 uppercase font-bold tracking-widest", children: "Generiere\u2026" })
1357
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: item.base64 ? item.base64.startsWith("data:") ? item.base64 : `data:image/png;base64,${item.base64}` : "", className: "w-full h-full object-cover transition-transform duration-500 group-hover/item:scale-110", alt: item.prompt }),
1358
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: `absolute top-2 left-2 w-5 h-5 rounded-md border flex items-center justify-center transition-all z-30 pointer-events-auto ${item.selectedForExport ? "bg-blue-500 border-blue-400" : "bg-black/60 border-white/20"}`, onClick: (e) => {
1359
- e.stopPropagation();
1360
- onToggleSelection?.(item.id);
1361
- }, children: item.selectedForExport && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "material-symbols-outlined text-[14px] text-white", children: "check" }) }),
1362
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-0 group-hover/item:opacity-100 transition-opacity flex flex-col justify-end p-2 gap-2 pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center justify-between", children: [
1363
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-[8px] font-bold text-white/80 truncate max-w-[80px] uppercase tracking-tighter", children: item.prompt || "Importiert" }),
1364
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-1", children: [
1365
- onGenerateReference && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: (e) => {
1366
- e.stopPropagation();
1367
- onGenerateReference(item);
1368
- }, className: "w-6 h-6 flex items-center justify-center bg-blue-500/20 text-blue-400 hover:bg-blue-500 rounded-md transition-all hover:text-white pointer-events-auto", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "material-symbols-outlined text-[14px]", children: "auto_fix_high" }) }),
1369
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: (e) => {
1370
- e.stopPropagation();
1371
- onDelete?.(item.id);
1372
- }, className: "w-6 h-6 flex items-center justify-center bg-red-500/20 text-red-400 hover:bg-red-500 rounded-md transition-all hover:text-white pointer-events-auto", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "material-symbols-outlined text-[14px]", children: "delete" }) })
1373
- ] })
1374
- ] }) }),
1375
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "absolute top-1 right-1 px-1.5 py-0.5 bg-black/60 backdrop-blur-md rounded text-[7px] font-bold text-white/60 uppercase tracking-tight", children: item.type === "import" ? "Library" : "Gen" })
1376
- ] }, item.id)) }) })
1407
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "grid grid-cols-2 gap-3 pb-10", children: [
1408
+ visibleGroups.map((group) => {
1409
+ const rep = group.representative;
1410
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1411
+ import_react11.motion.div,
1412
+ {
1413
+ initial: { opacity: 0, scale: 0.9 },
1414
+ animate: { opacity: 1, scale: 1 },
1415
+ className: "relative aspect-square group/item rounded-xl overflow-hidden border border-white/10 opacity-70 hover:opacity-100 transition-all bg-white/5 cursor-pointer shadow-lg",
1416
+ onClick: () => onSelect(rep),
1417
+ children: [
1418
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: rep.base64 ? rep.base64.startsWith("data:") ? rep.base64 : `data:image/png;base64,${rep.base64}` : "", className: "w-full h-full object-cover transition-transform duration-500 group-hover/item:scale-110", alt: rep.prompt }),
1419
+ group.items.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "absolute top-1.5 left-1.5 px-1.5 py-0.5 bg-black/70 backdrop-blur-md rounded text-[8px] font-bold text-white/80 flex items-center gap-1", children: [
1420
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "material-symbols-outlined text-[10px]", children: "photo_library" }),
1421
+ group.items.length
1422
+ ] }),
1423
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-0 group-hover/item:opacity-100 transition-opacity flex flex-col justify-end p-2 gap-2 pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center justify-between", children: [
1424
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-[8px] font-bold text-white/80 truncate max-w-[80px] uppercase tracking-tighter", children: rep.prompt || "Importiert" }),
1425
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-1", children: [
1426
+ onGenerateReference && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: (e) => {
1427
+ e.stopPropagation();
1428
+ onGenerateReference(rep);
1429
+ }, className: "w-6 h-6 flex items-center justify-center bg-blue-500/20 text-blue-400 hover:bg-blue-500 rounded-md transition-all hover:text-white pointer-events-auto", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "material-symbols-outlined text-[14px]", children: "auto_fix_high" }) }),
1430
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: (e) => {
1431
+ e.stopPropagation();
1432
+ onDelete(rep.id);
1433
+ }, className: "w-6 h-6 flex items-center justify-center bg-red-500/20 text-red-400 hover:bg-red-500 rounded-md transition-all hover:text-white pointer-events-auto", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "material-symbols-outlined text-[14px]", children: "delete" }) })
1434
+ ] })
1435
+ ] }) }),
1436
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "absolute top-1 right-1 px-1.5 py-0.5 bg-black/60 backdrop-blur-md rounded text-[7px] font-bold text-white/60 uppercase tracking-tight", children: rep.type === "import" ? "Library" : "Gen" })
1437
+ ]
1438
+ },
1439
+ rep.id
1440
+ );
1441
+ }),
1442
+ visibleCount < groups.length && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "col-span-2 flex justify-center pt-2 pb-4", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("button", { type: "button", onClick: () => setVisibleCount((c) => c + PAGE_SIZE2), className: "px-4 py-2 bg-white/5 hover:bg-white/10 border border-white/10 rounded-lg text-[10px] font-bold uppercase text-white/60 hover:text-white transition-all", children: [
1443
+ groups.length - visibleCount,
1444
+ " weitere laden"
1445
+ ] }) })
1446
+ ] }) })
1377
1447
  ] });
1378
1448
  };
1379
1449
 
1380
1450
  // src/components/ListView.tsx
1381
- var import_react10 = require("react");
1451
+ var import_react12 = require("react");
1382
1452
  var import_jsx_runtime9 = require("react/jsx-runtime");
1383
1453
  var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, isActive, isInPath, onFocus, onGenerate, onGenerateBranch, onGenerateSubtree, isGenerating, isCollapsed, toggleCollapse, renderNode, children }) => {
1384
- const inputRef = (0, import_react10.useRef)(null);
1454
+ const inputRef = (0, import_react12.useRef)(null);
1385
1455
  const hasChildren = children && children.length > 0;
1386
1456
  const handleKeyDown = (e) => {
1387
1457
  if (e.key === "Tab") {
@@ -1409,7 +1479,7 @@ var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNod
1409
1479
  onMoveNode(node.id, "down");
1410
1480
  }
1411
1481
  };
1412
- (0, import_react10.useEffect)(() => {
1482
+ (0, import_react12.useEffect)(() => {
1413
1483
  if (isActive && inputRef.current) inputRef.current.focus();
1414
1484
  }, [isActive]);
1415
1485
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col w-full", children: [
@@ -1443,7 +1513,7 @@ var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNod
1443
1513
  ] });
1444
1514
  };
1445
1515
  function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, focusedNodeId, onFocus, activePath, onGenerate, onGenerateBranch, onGenerateSubtree, isGeneratingNodeId }) {
1446
- const [collapsed, setCollapsed] = (0, import_react10.useState)(/* @__PURE__ */ new Set());
1516
+ const [collapsed, setCollapsed] = (0, import_react12.useState)(/* @__PURE__ */ new Set());
1447
1517
  const toggleCollapse = (id) => {
1448
1518
  setCollapsed((prev) => {
1449
1519
  const next = new Set(prev);
@@ -1471,13 +1541,13 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
1471
1541
  }
1472
1542
 
1473
1543
  // src/components/AvatarArchitectApp.tsx
1474
- var import_react25 = require("react");
1544
+ var import_react27 = require("react");
1475
1545
 
1476
1546
  // src/components/PromptTab.tsx
1477
- var import_react12 = require("react");
1547
+ var import_react14 = require("react");
1478
1548
 
1479
1549
  // src/components/CollapsibleCard.tsx
1480
- var import_react11 = require("react");
1550
+ var import_react13 = require("react");
1481
1551
  var import_jsx_runtime10 = require("react/jsx-runtime");
1482
1552
  var CollapsibleCard = ({
1483
1553
  title,
@@ -1488,7 +1558,7 @@ var CollapsibleCard = ({
1488
1558
  collapsible = true,
1489
1559
  className = ""
1490
1560
  }) => {
1491
- const [isOpen, setIsOpen] = (0, import_react11.useState)(defaultOpen);
1561
+ const [isOpen, setIsOpen] = (0, import_react13.useState)(defaultOpen);
1492
1562
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `border border-neutral-800 rounded-lg ${className}`, children: [
1493
1563
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1494
1564
  "div",
@@ -1535,20 +1605,20 @@ var PromptTab = ({
1535
1605
  onTagUpdate,
1536
1606
  onTagDelete
1537
1607
  }) => {
1538
- const [selectedLabels, setSelectedLabels] = (0, import_react12.useState)(/* @__PURE__ */ new Set());
1539
- const [instructions, setInstructions] = (0, import_react12.useState)("");
1540
- const [rules, setRules] = (0, import_react12.useState)("");
1541
- const [activeCategory, setActiveCategory] = (0, import_react12.useState)(null);
1542
- const [copied, setCopied] = (0, import_react12.useState)(false);
1543
- const imgInputRef = (0, import_react12.useRef)(null);
1544
- const [addingInCat, setAddingInCat] = (0, import_react12.useState)(null);
1545
- const [newLabel, setNewLabel] = (0, import_react12.useState)("");
1546
- const [newValue, setNewValue] = (0, import_react12.useState)("");
1547
- const [editingTag, setEditingTag] = (0, import_react12.useState)(null);
1548
- const [editLabel, setEditLabel] = (0, import_react12.useState)("");
1549
- const [editValue, setEditValue] = (0, import_react12.useState)("");
1550
- const longPressTimer = (0, import_react12.useRef)(null);
1551
- const longPressActivated = (0, import_react12.useRef)(false);
1608
+ const [selectedLabels, setSelectedLabels] = (0, import_react14.useState)(/* @__PURE__ */ new Set());
1609
+ const [instructions, setInstructions] = (0, import_react14.useState)("");
1610
+ const [rules, setRules] = (0, import_react14.useState)("");
1611
+ const [activeCategory, setActiveCategory] = (0, import_react14.useState)(null);
1612
+ const [copied, setCopied] = (0, import_react14.useState)(false);
1613
+ const imgInputRef = (0, import_react14.useRef)(null);
1614
+ const [addingInCat, setAddingInCat] = (0, import_react14.useState)(null);
1615
+ const [newLabel, setNewLabel] = (0, import_react14.useState)("");
1616
+ const [newValue, setNewValue] = (0, import_react14.useState)("");
1617
+ const [editingTag, setEditingTag] = (0, import_react14.useState)(null);
1618
+ const [editLabel, setEditLabel] = (0, import_react14.useState)("");
1619
+ const [editValue, setEditValue] = (0, import_react14.useState)("");
1620
+ const longPressTimer = (0, import_react14.useRef)(null);
1621
+ const longPressActivated = (0, import_react14.useRef)(false);
1552
1622
  const toggleTag = (label) => {
1553
1623
  setSelectedLabels((prev) => {
1554
1624
  const next = new Set(prev);
@@ -1995,7 +2065,7 @@ var PromptTab = ({
1995
2065
  };
1996
2066
 
1997
2067
  // src/components/ProjectSyncTab.tsx
1998
- var import_react13 = require("react");
2068
+ var import_react15 = require("react");
1999
2069
  init_hfStateService();
2000
2070
  var import_jsx_runtime12 = require("react/jsx-runtime");
2001
2071
  var ProjectSyncTab = ({
@@ -2016,14 +2086,14 @@ var ProjectSyncTab = ({
2016
2086
  onProjectExportBase64,
2017
2087
  onHfInitialSync
2018
2088
  }) => {
2019
- const projectInputRef = (0, import_react13.useRef)(null);
2020
- const workspaceInputRef = (0, import_react13.useRef)(null);
2021
- const [saveName, setSaveName] = (0, import_react13.useState)("");
2022
- const [isSaving, setIsSaving] = (0, import_react13.useState)(false);
2023
- const [isExporting, setIsExporting] = (0, import_react13.useState)(false);
2024
- const [syncState, setSyncState] = (0, import_react13.useState)("idle");
2025
- const [syncDiff, setSyncDiff] = (0, import_react13.useState)(null);
2026
- const [selectedLocalIds, setSelectedLocalIds] = (0, import_react13.useState)(/* @__PURE__ */ new Set());
2089
+ const projectInputRef = (0, import_react15.useRef)(null);
2090
+ const workspaceInputRef = (0, import_react15.useRef)(null);
2091
+ const [saveName, setSaveName] = (0, import_react15.useState)("");
2092
+ const [isSaving, setIsSaving] = (0, import_react15.useState)(false);
2093
+ const [isExporting, setIsExporting] = (0, import_react15.useState)(false);
2094
+ const [syncState, setSyncState] = (0, import_react15.useState)("idle");
2095
+ const [syncDiff, setSyncDiff] = (0, import_react15.useState)(null);
2096
+ const [selectedLocalIds, setSelectedLocalIds] = (0, import_react15.useState)(/* @__PURE__ */ new Set());
2027
2097
  const handleExport = async () => {
2028
2098
  if (!onProjectExport) return;
2029
2099
  setIsExporting(true);
@@ -2069,13 +2139,13 @@ var ProjectSyncTab = ({
2069
2139
  });
2070
2140
  };
2071
2141
  const isWorking = projectActionState === "working" || projectActionState === "working-full";
2072
- const [hfProjects, setHfProjects] = (0, import_react13.useState)([]);
2073
- const [hfLoading, setHfLoading] = (0, import_react13.useState)(false);
2074
- const [hfSaving, setHfSaving] = (0, import_react13.useState)(false);
2075
- const [hfError, setHfError] = (0, import_react13.useState)(null);
2076
- const [hfSaveName, setHfSaveName] = (0, import_react13.useState)("");
2077
- const [hfSyncProgress, setHfSyncProgress] = (0, import_react13.useState)(null);
2078
- const [hfSyncing, setHfSyncing] = (0, import_react13.useState)(false);
2142
+ const [hfProjects, setHfProjects] = (0, import_react15.useState)([]);
2143
+ const [hfLoading, setHfLoading] = (0, import_react15.useState)(false);
2144
+ const [hfSaving, setHfSaving] = (0, import_react15.useState)(false);
2145
+ const [hfError, setHfError] = (0, import_react15.useState)(null);
2146
+ const [hfSaveName, setHfSaveName] = (0, import_react15.useState)("");
2147
+ const [hfSyncProgress, setHfSyncProgress] = (0, import_react15.useState)(null);
2148
+ const [hfSyncing, setHfSyncing] = (0, import_react15.useState)(false);
2079
2149
  const loadHfProjects = async (token) => {
2080
2150
  setHfLoading(true);
2081
2151
  setHfError(null);
@@ -2087,7 +2157,7 @@ var ProjectSyncTab = ({
2087
2157
  setHfLoading(false);
2088
2158
  }
2089
2159
  };
2090
- (0, import_react13.useEffect)(() => {
2160
+ (0, import_react15.useEffect)(() => {
2091
2161
  if (hfToken) loadHfProjects(hfToken);
2092
2162
  }, [hfToken]);
2093
2163
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "absolute inset-0 overflow-y-auto dark-scrollbar", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "p-6 flex flex-col gap-8", children: [
@@ -2501,7 +2571,7 @@ function toPromptImages(images) {
2501
2571
  init_hfStateService();
2502
2572
 
2503
2573
  // src/hooks/useHFState.ts
2504
- var import_react14 = require("react");
2574
+ var import_react16 = require("react");
2505
2575
  init_hfStateService();
2506
2576
 
2507
2577
  // src/lib/hfReducer.ts
@@ -2523,6 +2593,13 @@ function applyEvent(state, event) {
2523
2593
  const newAll = tags.all.some((t) => t.value === p.value && t.category === p.category) ? tags.all.map((t) => t.value === p.value && t.category === p.category ? { ...t, ...updated, category: p.category } : t) : [...tags.all, { ...updated, category: p.category }];
2524
2594
  return { ...state, tags: { by_category: { ...tags.by_category, [p.category]: newCat }, all: newAll } };
2525
2595
  }
2596
+ case "thumb_added": {
2597
+ const p = event.payload;
2598
+ return {
2599
+ ...state,
2600
+ metadata: state.metadata.map((m) => m.id === p.id ? { ...m, hasThumb: true } : m)
2601
+ };
2602
+ }
2526
2603
  case "metadata_updated": {
2527
2604
  const p = event.payload;
2528
2605
  return {
@@ -2530,6 +2607,13 @@ function applyEvent(state, event) {
2530
2607
  metadata: state.metadata.map((m) => m.id === p.id ? { ...m, ...p.delta } : m)
2531
2608
  };
2532
2609
  }
2610
+ case "title_set": {
2611
+ const p = event.payload;
2612
+ return {
2613
+ ...state,
2614
+ metadata: state.metadata.map((m) => m.id === p.id ? { ...m, titleTs: event.ts } : m)
2615
+ };
2616
+ }
2533
2617
  default:
2534
2618
  return state;
2535
2619
  }
@@ -2620,18 +2704,18 @@ function writeOfflineBuffer(events) {
2620
2704
  }
2621
2705
  }
2622
2706
  function useHFState(token, namespace) {
2623
- const [state, setState] = (0, import_react14.useState)(null);
2624
- const [isLoading, setIsLoading] = (0, import_react14.useState)(false);
2625
- const [error, setError] = (0, import_react14.useState)(null);
2626
- const [eventCount, setEventCount] = (0, import_react14.useState)(0);
2627
- const [localOnlyCount, setLocalOnlyCount] = (0, import_react14.useState)(0);
2628
- const [forks, setForks] = (0, import_react14.useState)([]);
2629
- const [pendingBufferCount, setPendingBufferCount] = (0, import_react14.useState)(readOfflineBuffer().length);
2630
- const [lastEventTs, setLastEventTs] = (0, import_react14.useState)(0);
2631
- const [hasStateZip, setHasStateZip] = (0, import_react14.useState)(false);
2632
- const knownEventPaths = (0, import_react14.useRef)(/* @__PURE__ */ new Set());
2633
- const allEventsRef = (0, import_react14.useRef)([]);
2634
- const applyNewEvents = (0, import_react14.useCallback)((snapshot, newEvents) => {
2707
+ const [state, setState] = (0, import_react16.useState)(null);
2708
+ const [isLoading, setIsLoading] = (0, import_react16.useState)(false);
2709
+ const [error, setError] = (0, import_react16.useState)(null);
2710
+ const [eventCount, setEventCount] = (0, import_react16.useState)(0);
2711
+ const [localOnlyCount, setLocalOnlyCount] = (0, import_react16.useState)(0);
2712
+ const [forks, setForks] = (0, import_react16.useState)([]);
2713
+ const [pendingBufferCount, setPendingBufferCount] = (0, import_react16.useState)(readOfflineBuffer().length);
2714
+ const [lastEventTs, setLastEventTs] = (0, import_react16.useState)(0);
2715
+ const [hasStateZip, setHasStateZip] = (0, import_react16.useState)(false);
2716
+ const knownEventPaths = (0, import_react16.useRef)(/* @__PURE__ */ new Set());
2717
+ const allEventsRef = (0, import_react16.useRef)([]);
2718
+ const applyNewEvents = (0, import_react16.useCallback)((snapshot, newEvents) => {
2635
2719
  if (!newEvents.length && allEventsRef.current.length === 0) {
2636
2720
  setEventCount(0);
2637
2721
  return snapshot;
@@ -2645,7 +2729,7 @@ function useHFState(token, namespace) {
2645
2729
  if (afterConsolidation.length) setLastEventTs(Math.max(...afterConsolidation.map((e) => e.ts)));
2646
2730
  return applyEvents(snapshot, afterConsolidation);
2647
2731
  }, []);
2648
- const loadFull = (0, import_react14.useCallback)(async () => {
2732
+ const loadFull = (0, import_react16.useCallback)(async () => {
2649
2733
  if (!token || !namespace) return;
2650
2734
  setIsLoading(true);
2651
2735
  setError(null);
@@ -2692,7 +2776,7 @@ function useHFState(token, namespace) {
2692
2776
  setIsLoading(false);
2693
2777
  }
2694
2778
  }, [token, namespace, applyNewEvents]);
2695
- const pollNew = (0, import_react14.useCallback)(async () => {
2779
+ const pollNew = (0, import_react16.useCallback)(async () => {
2696
2780
  if (!token || !namespace || !state) return;
2697
2781
  try {
2698
2782
  const events = await loadPendingEvents(namespace, token, state.meta.consolidatedAt);
@@ -2706,15 +2790,15 @@ function useHFState(token, namespace) {
2706
2790
  } catch {
2707
2791
  }
2708
2792
  }, [token, namespace, state, applyNewEvents]);
2709
- (0, import_react14.useEffect)(() => {
2793
+ (0, import_react16.useEffect)(() => {
2710
2794
  if (token && namespace) loadFull();
2711
2795
  }, [token, namespace]);
2712
- (0, import_react14.useEffect)(() => {
2796
+ (0, import_react16.useEffect)(() => {
2713
2797
  if (!token || !namespace) return;
2714
2798
  const id = setInterval(pollNew, POLL_INTERVAL_MS);
2715
2799
  return () => clearInterval(id);
2716
2800
  }, [token, namespace, pollNew]);
2717
- const writeEvent = (0, import_react14.useCallback)(async (type, payload) => {
2801
+ const writeEvent = (0, import_react16.useCallback)(async (type, payload) => {
2718
2802
  const prevTs = lastEventTs ? [lastEventTs] : [state?.meta.consolidatedAt ?? 0];
2719
2803
  console.log("[HF] writeEvent called:", { type, namespace, tokenOk: !!token, prevTs });
2720
2804
  await pollNew();
@@ -2764,13 +2848,13 @@ function useHFState(token, namespace) {
2764
2848
  }
2765
2849
 
2766
2850
  // src/components/labs/LabsTab.tsx
2767
- var import_react21 = require("react");
2851
+ var import_react23 = require("react");
2768
2852
 
2769
2853
  // src/components/labs/LabRemix.tsx
2770
- var import_react16 = require("react");
2854
+ var import_react18 = require("react");
2771
2855
 
2772
2856
  // src/components/labs/LabImagePicker.tsx
2773
- var import_react15 = require("react");
2857
+ var import_react17 = require("react");
2774
2858
  var import_jsx_runtime13 = require("react/jsx-runtime");
2775
2859
  var LabImagePicker = ({
2776
2860
  availableItems,
@@ -2779,8 +2863,8 @@ var LabImagePicker = ({
2779
2863
  onClose,
2780
2864
  title = "Bild w\xE4hlen"
2781
2865
  }) => {
2782
- const [search, setSearch] = (0, import_react15.useState)("");
2783
- const [drillItem, setDrillItem] = (0, import_react15.useState)(null);
2866
+ const [search, setSearch] = (0, import_react17.useState)("");
2867
+ const [drillItem, setDrillItem] = (0, import_react17.useState)(null);
2784
2868
  const filtered = availableItems.filter(
2785
2869
  (item) => !search || item.prompt.toLowerCase().includes(search.toLowerCase())
2786
2870
  );
@@ -2882,13 +2966,13 @@ var LabImagePicker = ({
2882
2966
  // src/components/labs/LabRemix.tsx
2883
2967
  var import_jsx_runtime14 = require("react/jsx-runtime");
2884
2968
  var LabRemix = ({ services, onResult }) => {
2885
- const [showPicker, setShowPicker] = (0, import_react16.useState)(false);
2886
- const [selected, setSelected] = (0, import_react16.useState)(null);
2887
- const [instruction, setInstruction] = (0, import_react16.useState)("");
2888
- const [generatedPrompt, setGeneratedPrompt] = (0, import_react16.useState)("");
2889
- const [resultImage, setResultImage] = (0, import_react16.useState)(null);
2890
- const [isGeneratingPrompt, setIsGeneratingPrompt] = (0, import_react16.useState)(false);
2891
- const [isGeneratingImage, setIsGeneratingImage] = (0, import_react16.useState)(false);
2969
+ const [showPicker, setShowPicker] = (0, import_react18.useState)(false);
2970
+ const [selected, setSelected] = (0, import_react18.useState)(null);
2971
+ const [instruction, setInstruction] = (0, import_react18.useState)("");
2972
+ const [generatedPrompt, setGeneratedPrompt] = (0, import_react18.useState)("");
2973
+ const [resultImage, setResultImage] = (0, import_react18.useState)(null);
2974
+ const [isGeneratingPrompt, setIsGeneratingPrompt] = (0, import_react18.useState)(false);
2975
+ const [isGeneratingImage, setIsGeneratingImage] = (0, import_react18.useState)(false);
2892
2976
  const handleSelectImage = (item, frame) => {
2893
2977
  services.onItemUsed(item);
2894
2978
  setSelected({
@@ -3071,16 +3155,16 @@ var LabRemix = ({ services, onResult }) => {
3071
3155
  };
3072
3156
 
3073
3157
  // src/components/labs/LabBlend.tsx
3074
- var import_react17 = require("react");
3158
+ var import_react19 = require("react");
3075
3159
  var import_jsx_runtime15 = require("react/jsx-runtime");
3076
3160
  var LabBlend = ({ services, onResult }) => {
3077
- const [showPickerFor, setShowPickerFor] = (0, import_react17.useState)(null);
3078
- const [selectedImages, setSelectedImages] = (0, import_react17.useState)([]);
3079
- const [instruction, setInstruction] = (0, import_react17.useState)("");
3080
- const [generatedPrompt, setGeneratedPrompt] = (0, import_react17.useState)("");
3081
- const [resultImage, setResultImage] = (0, import_react17.useState)(null);
3082
- const [isGeneratingPrompt, setIsGeneratingPrompt] = (0, import_react17.useState)(false);
3083
- const [isGeneratingImage, setIsGeneratingImage] = (0, import_react17.useState)(false);
3161
+ const [showPickerFor, setShowPickerFor] = (0, import_react19.useState)(null);
3162
+ const [selectedImages, setSelectedImages] = (0, import_react19.useState)([]);
3163
+ const [instruction, setInstruction] = (0, import_react19.useState)("");
3164
+ const [generatedPrompt, setGeneratedPrompt] = (0, import_react19.useState)("");
3165
+ const [resultImage, setResultImage] = (0, import_react19.useState)(null);
3166
+ const [isGeneratingPrompt, setIsGeneratingPrompt] = (0, import_react19.useState)(false);
3167
+ const [isGeneratingImage, setIsGeneratingImage] = (0, import_react19.useState)(false);
3084
3168
  const handleSelectImage = (index, item, frame) => {
3085
3169
  services.onItemUsed(item);
3086
3170
  const newImg = {
@@ -3267,17 +3351,17 @@ var LabBlend = ({ services, onResult }) => {
3267
3351
  };
3268
3352
 
3269
3353
  // src/components/labs/LabCompare.tsx
3270
- var import_react18 = require("react");
3354
+ var import_react20 = require("react");
3271
3355
  var import_jsx_runtime16 = require("react/jsx-runtime");
3272
3356
  var LabCompare = ({ services, onResult }) => {
3273
- const [showPickerFor, setShowPickerFor] = (0, import_react18.useState)(null);
3274
- const [selectedImages, setSelectedImages] = (0, import_react18.useState)([]);
3275
- const [instruction, setInstruction] = (0, import_react18.useState)("");
3276
- const [analysis, setAnalysis] = (0, import_react18.useState)("");
3277
- const [generatedPrompt, setGeneratedPrompt] = (0, import_react18.useState)("");
3278
- const [resultImage, setResultImage] = (0, import_react18.useState)(null);
3279
- const [isAnalyzing, setIsAnalyzing] = (0, import_react18.useState)(false);
3280
- const [isGeneratingImage, setIsGeneratingImage] = (0, import_react18.useState)(false);
3357
+ const [showPickerFor, setShowPickerFor] = (0, import_react20.useState)(null);
3358
+ const [selectedImages, setSelectedImages] = (0, import_react20.useState)([]);
3359
+ const [instruction, setInstruction] = (0, import_react20.useState)("");
3360
+ const [analysis, setAnalysis] = (0, import_react20.useState)("");
3361
+ const [generatedPrompt, setGeneratedPrompt] = (0, import_react20.useState)("");
3362
+ const [resultImage, setResultImage] = (0, import_react20.useState)(null);
3363
+ const [isAnalyzing, setIsAnalyzing] = (0, import_react20.useState)(false);
3364
+ const [isGeneratingImage, setIsGeneratingImage] = (0, import_react20.useState)(false);
3281
3365
  const handleSelectImage = (index, item, frame) => {
3282
3366
  services.onItemUsed(item);
3283
3367
  const newImg = {
@@ -3440,14 +3524,14 @@ var LabCompare = ({ services, onResult }) => {
3440
3524
  };
3441
3525
 
3442
3526
  // src/components/labs/LabLoop.tsx
3443
- var import_react19 = require("react");
3527
+ var import_react21 = require("react");
3444
3528
  var import_jsx_runtime17 = require("react/jsx-runtime");
3445
3529
  var LabLoop = ({ services, onResult }) => {
3446
- const [rounds, setRounds] = (0, import_react19.useState)([]);
3447
- const [currentInstruction, setCurrentInstruction] = (0, import_react19.useState)("");
3448
- const [showPickerForRound, setShowPickerForRound] = (0, import_react19.useState)(null);
3449
- const [pendingImages, setPendingImages] = (0, import_react19.useState)([]);
3450
- const [isGenerating, setIsGenerating] = (0, import_react19.useState)(false);
3530
+ const [rounds, setRounds] = (0, import_react21.useState)([]);
3531
+ const [currentInstruction, setCurrentInstruction] = (0, import_react21.useState)("");
3532
+ const [showPickerForRound, setShowPickerForRound] = (0, import_react21.useState)(null);
3533
+ const [pendingImages, setPendingImages] = (0, import_react21.useState)([]);
3534
+ const [isGenerating, setIsGenerating] = (0, import_react21.useState)(false);
3451
3535
  const currentPrompt = rounds.length > 0 ? rounds[rounds.length - 1].prompt : "";
3452
3536
  const handleAddImage = (item, frame) => {
3453
3537
  services.onItemUsed(item);
@@ -3603,7 +3687,7 @@ var LabLoop = ({ services, onResult }) => {
3603
3687
  };
3604
3688
 
3605
3689
  // src/components/labs/LabFrameExtractor.tsx
3606
- var import_react20 = require("react");
3690
+ var import_react22 = require("react");
3607
3691
  var import_jsx_runtime18 = require("react/jsx-runtime");
3608
3692
  var formatTime = (s) => {
3609
3693
  const m = Math.floor(s / 60);
@@ -3617,15 +3701,15 @@ var LabFrameExtractor = ({
3617
3701
  onResult,
3618
3702
  resolveVideoUrl
3619
3703
  }) => {
3620
- const videoRef = (0, import_react20.useRef)(null);
3621
- const canvasRef = (0, import_react20.useRef)(null);
3622
- const cancelledRef = (0, import_react20.useRef)(false);
3623
- const [selectedItem, setSelectedItem] = (0, import_react20.useState)(null);
3624
- const [videoSrc, setVideoSrc] = (0, import_react20.useState)(null);
3625
- const [videoReady, setVideoReady] = (0, import_react20.useState)(false);
3626
- const [frames, setFrames] = (0, import_react20.useState)([]);
3627
- const [isExtracting, setIsExtracting] = (0, import_react20.useState)(false);
3628
- const [intervalSec, setIntervalSec] = (0, import_react20.useState)("1");
3704
+ const videoRef = (0, import_react22.useRef)(null);
3705
+ const canvasRef = (0, import_react22.useRef)(null);
3706
+ const cancelledRef = (0, import_react22.useRef)(false);
3707
+ const [selectedItem, setSelectedItem] = (0, import_react22.useState)(null);
3708
+ const [videoSrc, setVideoSrc] = (0, import_react22.useState)(null);
3709
+ const [videoReady, setVideoReady] = (0, import_react22.useState)(false);
3710
+ const [frames, setFrames] = (0, import_react22.useState)([]);
3711
+ const [isExtracting, setIsExtracting] = (0, import_react22.useState)(false);
3712
+ const [intervalSec, setIntervalSec] = (0, import_react22.useState)("1");
3629
3713
  const handleVideoSelect = (item) => {
3630
3714
  const mediaId = item.frames[0]?.mediaId;
3631
3715
  if (!mediaId) return;
@@ -3635,7 +3719,7 @@ var LabFrameExtractor = ({
3635
3719
  setVideoReady(false);
3636
3720
  cancelledRef.current = false;
3637
3721
  };
3638
- const captureAt = (0, import_react20.useCallback)(
3722
+ const captureAt = (0, import_react22.useCallback)(
3639
3723
  (t, label) => new Promise((resolve) => {
3640
3724
  const video = videoRef.current;
3641
3725
  const canvas = canvasRef.current;
@@ -3862,7 +3946,7 @@ var BASE_TABS = [
3862
3946
  ];
3863
3947
  var FRAMES_TAB = { key: "frames", label: "Frames", icon: "crop_original" };
3864
3948
  var LabsTab = ({ services, onResult, videoItems, resolveVideoUrl }) => {
3865
- const [activeTab, setActiveTab] = (0, import_react21.useState)("remix");
3949
+ const [activeTab, setActiveTab] = (0, import_react23.useState)("remix");
3866
3950
  const showFrames = !!(videoItems && resolveVideoUrl);
3867
3951
  const tabs = showFrames ? [...BASE_TABS, FRAMES_TAB] : BASE_TABS;
3868
3952
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-col h-full overflow-hidden", children: [
@@ -3896,19 +3980,19 @@ var LabsTab = ({ services, onResult, videoItems, resolveVideoUrl }) => {
3896
3980
  };
3897
3981
 
3898
3982
  // src/components/TagManagerPanel.tsx
3899
- var import_react22 = require("react");
3983
+ var import_react24 = require("react");
3900
3984
  var import_jsx_runtime20 = require("react/jsx-runtime");
3901
3985
  function TagManagerPanel({ workspaceTags, onTagCreate, onTagUpdate, onTagDelete, onTagReorder, onTagMove }) {
3902
3986
  const categories = Object.keys(workspaceTags.by_category).filter(
3903
3987
  (cat) => (workspaceTags.by_category[cat] || []).some((t) => !t.is_deleted)
3904
3988
  );
3905
- const [selectedCategory, setSelectedCategory] = (0, import_react22.useState)(categories[0] || "");
3989
+ const [selectedCategory, setSelectedCategory] = (0, import_react24.useState)(categories[0] || "");
3906
3990
  const effectiveCategory = categories.includes(selectedCategory) ? selectedCategory : categories[0] || "";
3907
- const [editingLabel, setEditingLabel] = (0, import_react22.useState)(null);
3908
- const [editState, setEditState] = (0, import_react22.useState)({ label: "", value: "" });
3909
- const [newTag, setNewTag] = (0, import_react22.useState)({ label: "", value: "" });
3910
- const [movingLabel, setMovingLabel] = (0, import_react22.useState)(null);
3911
- const [moveTarget, setMoveTarget] = (0, import_react22.useState)("");
3991
+ const [editingLabel, setEditingLabel] = (0, import_react24.useState)(null);
3992
+ const [editState, setEditState] = (0, import_react24.useState)({ label: "", value: "" });
3993
+ const [newTag, setNewTag] = (0, import_react24.useState)({ label: "", value: "" });
3994
+ const [movingLabel, setMovingLabel] = (0, import_react24.useState)(null);
3995
+ const [moveTarget, setMoveTarget] = (0, import_react24.useState)("");
3912
3996
  const tags = (workspaceTags.by_category[effectiveCategory] || []).filter((t) => !t.is_deleted);
3913
3997
  const otherCategories = categories.filter((c) => c !== effectiveCategory);
3914
3998
  const startEdit = (tag) => {
@@ -4104,7 +4188,7 @@ function TagManagerPanel({ workspaceTags, onTagCreate, onTagUpdate, onTagDelete,
4104
4188
  }
4105
4189
 
4106
4190
  // src/components/HFTestTab.tsx
4107
- var import_react23 = require("react");
4191
+ var import_react25 = require("react");
4108
4192
  var import_jsx_runtime21 = require("react/jsx-runtime");
4109
4193
  var HF_BASE2 = "https://huggingface.co";
4110
4194
  var HF_REPO2 = "RolandSch/fa-app-state";
@@ -4297,7 +4381,7 @@ function tryFmt(s) {
4297
4381
  }
4298
4382
  }
4299
4383
  function CopyBtn({ text }) {
4300
- const [done, setDone] = (0, import_react23.useState)(false);
4384
+ const [done, setDone] = (0, import_react25.useState)(false);
4301
4385
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4302
4386
  "button",
4303
4387
  {
@@ -4468,10 +4552,10 @@ function EventMonitor({ events, confirmedEventKeys, galleryItems, imageUploadSta
4468
4552
  ] })
4469
4553
  ] });
4470
4554
  }
4471
- function HFTestTab({ token, namespace, galleryItems, allEvents = [], confirmedEventKeys = /* @__PURE__ */ new Set(), imageUploadStatus = /* @__PURE__ */ new Map() }) {
4472
- const [selected, setSelected] = (0, import_react23.useState)(null);
4473
- const [results, setResults] = (0, import_react23.useState)({});
4474
- const [expanded, setExpanded] = (0, import_react23.useState)({});
4555
+ function HFTestTab({ token, namespace, galleryItems, allEvents = [], confirmedEventKeys = /* @__PURE__ */ new Set(), imageUploadStatus = /* @__PURE__ */ new Map(), missingImages = [] }) {
4556
+ const [selected, setSelected] = (0, import_react25.useState)(null);
4557
+ const [results, setResults] = (0, import_react25.useState)({});
4558
+ const [expanded, setExpanded] = (0, import_react25.useState)({});
4475
4559
  const withResults = galleryItems.filter((g) => g.base64 && g.status === "done");
4476
4560
  const setRunning = (id) => setResults((r) => ({ ...r, [id]: { status: "running", steps: [], totalMs: 0 } }));
4477
4561
  const setDone = (id, steps, t0) => {
@@ -4538,6 +4622,33 @@ function HFTestTab({ token, namespace, galleryItems, allEvents = [], confirmedEv
4538
4622
  )
4539
4623
  }
4540
4624
  ) }),
4625
+ missingImages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: { marginBottom: 12 }, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4626
+ CollapsibleCard,
4627
+ {
4628
+ title: `Fehlende Bilder auf HF (${missingImages.length})`,
4629
+ icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 16 }, children: "broken_image" }),
4630
+ defaultOpen: false,
4631
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { padding: "8px 10px 4px" }, children: [
4632
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: { fontSize: 11, color: "rgba(255,255,255,0.4)", marginBottom: 8 }, children: "Metadata-Eintr\xE4ge ohne Bild auf HuggingFace \u2014 Orphaned entries." }),
4633
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { display: "flex", gap: 8, marginBottom: 8, fontSize: 11, color: "rgba(255,255,255,0.5)" }, children: [
4634
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { children: [
4635
+ "UUID (Flow-App): ",
4636
+ missingImages.filter((e) => !e.filename).length
4637
+ ] }),
4638
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: "\xB7" }),
4639
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { children: [
4640
+ "Filename (Server-Upload): ",
4641
+ missingImages.filter((e) => !!e.filename).length
4642
+ ] })
4643
+ ] }),
4644
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: { maxHeight: 300, overflowY: "auto", fontFamily: "monospace", fontSize: 10 }, children: missingImages.map((e) => /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { padding: "3px 0", borderBottom: "1px solid rgba(255,255,255,0.04)", color: "rgba(255,255,255,0.5)", display: "flex", gap: 8 }, children: [
4645
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { style: { color: e.filename ? "#fb923c" : "#60a5fa", minWidth: 60 }, children: e.filename ? "filename" : "uuid" }),
4646
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: e.filename ?? e.id }),
4647
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { style: { color: "rgba(255,255,255,0.3)" }, children: new Date(e.timestamp).toLocaleDateString("de") })
4648
+ ] }, e.id)) })
4649
+ ] })
4650
+ }
4651
+ ) }),
4541
4652
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4542
4653
  CollapsibleCard,
4543
4654
  {
@@ -4642,77 +4753,44 @@ function HFTestTab({ token, namespace, galleryItems, allEvents = [], confirmedEv
4642
4753
  }
4643
4754
 
4644
4755
  // src/components/ServerTab.tsx
4645
- var import_react24 = require("react");
4646
-
4647
- // src/lib/faHfServerService.ts
4648
- init_hfStateService();
4649
- var FA_APP_SPACE = "https://rolandsch-fa-app.hf.space";
4650
- async function request(method, path, env = "prod", body, tokenOverride) {
4651
- const token = tokenOverride || getHFToken();
4652
- if (!token) throw new Error("fa-app gateway: kein HF Token gesetzt");
4653
- const res = await fetch(`${FA_APP_SPACE}/${path.replace(/^\//, "")}`, {
4654
- method,
4655
- headers: {
4656
- "Authorization": `Bearer ${token}`,
4657
- "X-Env": env,
4658
- ...body !== void 0 ? { "Content-Type": "application/json" } : {}
4659
- },
4660
- ...body !== void 0 ? { body: JSON.stringify(body) } : {}
4661
- });
4662
- if (!res.ok) {
4663
- const text = await res.text().catch(() => "");
4664
- throw new Error(`fa-app gateway ${method} /${path} [${env}] \u2192 ${res.status}: ${text.slice(0, 200)}`);
4665
- }
4666
- return res.json();
4667
- }
4668
- function faServerGet(path, env = "prod", token) {
4669
- return request("GET", path, env, void 0, token);
4670
- }
4671
- function faServerPost(path, body, env = "prod", token) {
4672
- return request("POST", path, env, body, token);
4673
- }
4674
- function faServerPut(path, body, env = "prod", token) {
4675
- return request("PUT", path, env, body, token);
4676
- }
4677
- function faServerDelete(path, env = "prod", token) {
4678
- return request("DELETE", path, env, void 0, token);
4679
- }
4680
-
4681
- // src/components/ServerTab.tsx
4682
- init_hfStateService();
4756
+ var import_react26 = require("react");
4683
4757
  var import_jsx_runtime22 = require("react/jsx-runtime");
4684
4758
  function StarRating({ rating = 0 }) {
4685
4759
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex gap-[2px]", children: [1, 2, 3, 4, 5].map((i) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: `material-symbols-outlined text-[12px] ${i <= rating ? "text-yellow-400" : "text-white/15"}`, children: "star" }, i)) });
4686
4760
  }
4687
- function ServerTab({ hfToken }) {
4688
- const token = hfToken || getHFToken() || "";
4689
- const hasToken = !!token;
4690
- const [env, setEnv] = (0, import_react24.useState)("prod");
4691
- const [step, setStep] = (0, import_react24.useState)("user");
4692
- const [users, setUsers] = (0, import_react24.useState)([]);
4693
- const [usersLoading, setUsersLoading] = (0, import_react24.useState)(false);
4694
- const [usersError, setUsersError] = (0, import_react24.useState)(null);
4695
- const [selectedUser, setSelectedUser] = (0, import_react24.useState)(null);
4696
- const [contexts, setContexts] = (0, import_react24.useState)([]);
4697
- const [contextsLoading, setContextsLoading] = (0, import_react24.useState)(false);
4698
- const [selectedContext, setSelectedContext] = (0, import_react24.useState)(null);
4699
- const [tags, setTags] = (0, import_react24.useState)([]);
4700
- const [items, setItems] = (0, import_react24.useState)([]);
4701
- const [libLoading, setLibLoading] = (0, import_react24.useState)(false);
4702
- const [libError, setLibError] = (0, import_react24.useState)(null);
4703
- const [activeTag, setActiveTag] = (0, import_react24.useState)(null);
4704
- const [preview, setPreview] = (0, import_react24.useState)(null);
4705
- (0, import_react24.useEffect)(() => {
4706
- if (!hasToken) return;
4761
+ async function serverGet(baseUrl, path) {
4762
+ const url = `${baseUrl.replace(/\/$/, "")}${path}`;
4763
+ const res = await fetch(url, { credentials: "include" });
4764
+ if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
4765
+ const json = await res.json();
4766
+ return json && typeof json === "object" && "data" in json ? json.data : json;
4767
+ }
4768
+ function ServerTab({ serverBaseUrl }) {
4769
+ const [step, setStep] = (0, import_react26.useState)("user");
4770
+ const [users, setUsers] = (0, import_react26.useState)([]);
4771
+ const [usersLoading, setUsersLoading] = (0, import_react26.useState)(false);
4772
+ const [usersError, setUsersError] = (0, import_react26.useState)(null);
4773
+ const [selectedUser, setSelectedUser] = (0, import_react26.useState)(null);
4774
+ const [contexts, setContexts] = (0, import_react26.useState)([]);
4775
+ const [contextsLoading, setContextsLoading] = (0, import_react26.useState)(false);
4776
+ const [selectedContext, setSelectedContext] = (0, import_react26.useState)(null);
4777
+ const [tags, setTags] = (0, import_react26.useState)([]);
4778
+ const [items, setItems] = (0, import_react26.useState)([]);
4779
+ const [libLoading, setLibLoading] = (0, import_react26.useState)(false);
4780
+ const [libError, setLibError] = (0, import_react26.useState)(null);
4781
+ const [activeTag, setActiveTag] = (0, import_react26.useState)(null);
4782
+ const [preview, setPreview] = (0, import_react26.useState)(null);
4783
+ (0, import_react26.useEffect)(() => {
4784
+ if (!serverBaseUrl) return;
4707
4785
  setUsersLoading(true);
4708
4786
  setUsersError(null);
4709
- faServerGet("/api/v2/users", env, token).then(setUsers).catch((e) => setUsersError(String(e))).finally(() => setUsersLoading(false));
4710
- }, [env, hasToken]);
4787
+ serverGet(serverBaseUrl, "/api/v2/users").then(setUsers).catch((e) => setUsersError(String(e))).finally(() => setUsersLoading(false));
4788
+ }, [serverBaseUrl]);
4711
4789
  const selectUser = async (user) => {
4712
4790
  setSelectedUser(user);
4713
4791
  setContextsLoading(true);
4714
4792
  try {
4715
- const data = await faServerGet(`/api2/contexts?user_id=${user.id}`, env, token);
4793
+ const data = await serverGet(serverBaseUrl, `/api2/contexts?user_id=${user.id}`);
4716
4794
  if (data.length === 1) {
4717
4795
  await loadLibrary(user, data[0]);
4718
4796
  } else {
@@ -4732,12 +4810,11 @@ function ServerTab({ hfToken }) {
4732
4810
  setLibError(null);
4733
4811
  try {
4734
4812
  const [tagsRes, libRes] = await Promise.all([
4735
- faServerGet(`/api2/tags?user_id=${user.id}`, env, token),
4736
- faServerGet(`/api2/user-library?user_id=${user.id}&context_id=${ctx.id}&limit=100`, env, token)
4813
+ serverGet(serverBaseUrl, `/api2/tags?user_id=${user.id}`),
4814
+ serverGet(serverBaseUrl, `/api/storage/${user.id}/library`)
4737
4815
  ]);
4738
4816
  setTags(Array.isArray(tagsRes) ? tagsRes : []);
4739
- const raw = Array.isArray(libRes) ? libRes : libRes.data ?? [];
4740
- setItems(raw);
4817
+ setItems(Array.isArray(libRes) ? libRes : []);
4741
4818
  } catch (e) {
4742
4819
  setLibError(String(e));
4743
4820
  } finally {
@@ -4756,13 +4833,7 @@ function ServerTab({ hfToken }) {
4756
4833
  setLibError(null);
4757
4834
  };
4758
4835
  const filteredItems = activeTag ? items.filter((item) => item.tags?.some((t) => t.l === activeTag)) : items;
4759
- if (!hasToken) {
4760
- return /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex items-center justify-center h-full p-6", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("p", { className: "text-white/30 text-[12px] text-center", children: [
4761
- "Kein HF Token gesetzt.",
4762
- /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("br", {}),
4763
- "Bitte zuerst im Setup-Tab einrichten."
4764
- ] }) });
4765
- }
4836
+ if (!serverBaseUrl) return null;
4766
4837
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex flex-col h-full min-h-0", children: [
4767
4838
  /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-white/8", children: [
4768
4839
  step !== "user" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("button", { onClick: reset, className: "text-white/40 hover:text-white transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("span", { className: "material-symbols-outlined text-[18px]", children: "arrow_back" }) }),
@@ -4770,16 +4841,7 @@ function ServerTab({ hfToken }) {
4770
4841
  step === "user" && "Server Browser",
4771
4842
  step === "context" && `${selectedUser?.username} \u2014 Kontext w\xE4hlen`,
4772
4843
  step === "library" && `${selectedUser?.username} / ${selectedContext?.label || selectedContext?.name || selectedContext?.id}`
4773
- ] }),
4774
- step === "user" && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("div", { className: "flex gap-1", children: ["prod", "dev"].map((e) => /* @__PURE__ */ (0, import_jsx_runtime22.jsx)(
4775
- "button",
4776
- {
4777
- onClick: () => setEnv(e),
4778
- className: `text-[10px] font-bold px-2 py-0.5 rounded-lg transition-colors ${env === e ? "bg-white/15 text-white" : "text-white/30 hover:text-white/60"}`,
4779
- children: e
4780
- },
4781
- e
4782
- )) })
4844
+ ] })
4783
4845
  ] }),
4784
4846
  step === "user" && /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex flex-col flex-1 min-h-0 overflow-y-auto p-3 gap-2", children: [
4785
4847
  usersLoading && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-white/30 text-[11px] text-center py-4", children: "Lade User\u2026" }),
@@ -4870,8 +4932,8 @@ function ServerTab({ hfToken }) {
4870
4932
 
4871
4933
  // src/components/AvatarArchitectApp.tsx
4872
4934
  var import_jsx_runtime23 = require("react/jsx-runtime");
4873
- function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia, buildInfo, initialHfToken, hfNamespace, allowDevNamespace, onFetchServerProjects, onServerSave, onServerLoad, onServerDelete }) {
4874
- (0, import_react25.useEffect)(() => {
4935
+ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia, buildInfo, initialHfToken, hfNamespace, allowDevNamespace, serverBaseUrl, onFetchServerProjects, onServerSave, onServerLoad, onServerDelete }) {
4936
+ (0, import_react27.useEffect)(() => {
4875
4937
  const id = "flow-styles";
4876
4938
  if (!document.getElementById(id)) {
4877
4939
  const style = document.createElement("style");
@@ -4880,29 +4942,29 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4880
4942
  document.head.appendChild(style);
4881
4943
  }
4882
4944
  }, []);
4883
- const [showStart, setShowStart] = (0, import_react25.useState)(true);
4884
- const [layoutChoice, setLayoutChoice] = (0, import_react25.useState)(() => {
4945
+ const [showStart, setShowStart] = (0, import_react27.useState)(true);
4946
+ const [layoutChoice, setLayoutChoice] = (0, import_react27.useState)(() => {
4885
4947
  try {
4886
4948
  return localStorage.getItem("aa-layout") || null;
4887
4949
  } catch {
4888
4950
  return null;
4889
4951
  }
4890
4952
  });
4891
- const [projectLoaded, setProjectLoaded] = (0, import_react25.useState)(false);
4892
- const [hfToken, setHfToken] = (0, import_react25.useState)(initialHfToken || "");
4893
- const [hfTokenInput, setHfTokenInput] = (0, import_react25.useState)(initialHfToken || "");
4894
- const [isLoadingFromHF, setIsLoadingFromHF] = (0, import_react25.useState)(false);
4895
- const [hfNamespaceLocal, setHfNamespaceLocal] = (0, import_react25.useState)(() => {
4953
+ const [projectLoaded, setProjectLoaded] = (0, import_react27.useState)(false);
4954
+ const [hfToken, setHfToken] = (0, import_react27.useState)(initialHfToken || "");
4955
+ const [hfTokenInput, setHfTokenInput] = (0, import_react27.useState)(initialHfToken || "");
4956
+ const [isLoadingFromHF, setIsLoadingFromHF] = (0, import_react27.useState)(false);
4957
+ const [hfNamespaceLocal, setHfNamespaceLocal] = (0, import_react27.useState)(() => {
4896
4958
  try {
4897
4959
  const stored = localStorage.getItem("aa-hf-namespace");
4898
4960
  if (stored !== null) return stored;
4899
- return allowDevNamespace ? "app.art-by-rolands.de/" : "";
4961
+ return "app.art-by-rolands.de/";
4900
4962
  } catch {
4901
4963
  return "";
4902
4964
  }
4903
4965
  });
4904
- const [hfNamespaceFromServer, setHfNamespaceFromServer] = (0, import_react25.useState)(null);
4905
- (0, import_react25.useEffect)(() => {
4966
+ const [hfNamespaceFromServer, setHfNamespaceFromServer] = (0, import_react27.useState)(null);
4967
+ (0, import_react27.useEffect)(() => {
4906
4968
  if (hfNamespace !== void 0) return;
4907
4969
  const backendUrl = typeof window !== "undefined" ? window.BACKEND_URL || window.location.origin : null;
4908
4970
  if (!backendUrl) return;
@@ -4924,9 +4986,10 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4924
4986
  refresh: refreshHF,
4925
4987
  hasStateZip
4926
4988
  } = useHFState(hfToken, effectiveNamespace);
4927
- const [imageUploadStatus, setImageUploadStatus] = (0, import_react25.useState)(/* @__PURE__ */ new Map());
4928
- const [bootstrapLog, setBootstrapLog] = (0, import_react25.useState)([]);
4929
- const [isBootstrapping, setIsBootstrapping] = (0, import_react25.useState)(false);
4989
+ const [imageUploadStatus, setImageUploadStatus] = (0, import_react27.useState)(/* @__PURE__ */ new Map());
4990
+ const [bootstrapLog, setBootstrapLog] = (0, import_react27.useState)([]);
4991
+ const [isBootstrapping, setIsBootstrapping] = (0, import_react27.useState)(false);
4992
+ const [hfMissingImages, setHfMissingImages] = (0, import_react27.useState)([]);
4930
4993
  const syncTopSlot = /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
4931
4994
  localOnlyCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.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: [
4932
4995
  "\u26A0 ",
@@ -4976,7 +5039,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4976
5039
  bootstrapLog.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { marginTop: 6, fontFamily: "monospace", fontSize: 10, color: "#78716c", lineHeight: 1.6 }, children: bootstrapLog.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { children: l }, i)) })
4977
5040
  ] })
4978
5041
  ] });
4979
- const wsInputRef = (0, import_react25.useRef)(null);
5042
+ const wsInputRef = (0, import_react27.useRef)(null);
4980
5043
  const startApp = (choice) => {
4981
5044
  try {
4982
5045
  localStorage.setItem("aa-layout", choice);
@@ -4985,16 +5048,16 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4985
5048
  setLayoutChoice(choice);
4986
5049
  setShowStart(false);
4987
5050
  };
4988
- const [nodes, setNodes] = (0, import_react25.useState)([{ id: "1", type: "custom", position: { x: 0, y: 0 }, data: { label: "Fine Art Project", placeholder: "Name..." } }]);
4989
- const [edges, setEdges] = (0, import_react25.useState)([]);
4990
- const [history, setHistory] = (0, import_react25.useState)([]);
4991
- const [galleryItems, setGalleryItems] = (0, import_react25.useState)([]);
4992
- const galleryItemsRef = (0, import_react25.useRef)([]);
4993
- (0, import_react25.useEffect)(() => {
5051
+ const [nodes, setNodes] = (0, import_react27.useState)([{ id: "1", type: "custom", position: { x: 0, y: 0 }, data: { label: "Fine Art Project", placeholder: "Name..." } }]);
5052
+ const [edges, setEdges] = (0, import_react27.useState)([]);
5053
+ const [history, setHistory] = (0, import_react27.useState)([]);
5054
+ const [galleryItems, setGalleryItems] = (0, import_react27.useState)([]);
5055
+ const galleryItemsRef = (0, import_react27.useRef)([]);
5056
+ (0, import_react27.useEffect)(() => {
4994
5057
  galleryItemsRef.current = galleryItems;
4995
5058
  }, [galleryItems]);
4996
- const hfImageNotFoundRef = (0, import_react25.useRef)(/* @__PURE__ */ new Map());
4997
- (0, import_react25.useEffect)(() => {
5059
+ const hfImageNotFoundRef = (0, import_react27.useRef)(/* @__PURE__ */ new Map());
5060
+ (0, import_react27.useEffect)(() => {
4998
5061
  if (!hfState) return;
4999
5062
  if (hfState.tags?.by_category) setWorkspaceTags(hfState.tags);
5000
5063
  const hfIds = new Set(hfState.metadata.map((m) => m.id));
@@ -5006,7 +5069,9 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5006
5069
  model: m.model,
5007
5070
  tags: m.tags || [],
5008
5071
  timestamp: m.timestamp,
5009
- status: "done"
5072
+ status: "done",
5073
+ filename: m.filename,
5074
+ hasThumb: m.hasThumb
5010
5075
  }));
5011
5076
  setGalleryItems((prev) => {
5012
5077
  const localOnly = prev.filter((g) => !hfIds.has(g.id));
@@ -5018,80 +5083,86 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5018
5083
  const merged = skeletons.map((s) => prev.find((g) => g.id === s.id) ?? s);
5019
5084
  return [...localOnly, ...merged].sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
5020
5085
  });
5021
- const COOLDOWN_MS = 5 * 60 * 1e3;
5022
- for (const entry of hfState.metadata) {
5023
- if (galleryItemsRef.current.find((g) => g.id === entry.id)?.base64) continue;
5024
- const failedAt = hfImageNotFoundRef.current.get(entry.id);
5025
- if (failedAt && Date.now() - failedAt < COOLDOWN_MS) continue;
5026
- hfLoadImageAsBase64(entry.id, hfToken).then((b64) => {
5027
- if (!b64) {
5086
+ const sortedEntries = [...hfState.metadata].sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
5087
+ (async () => {
5088
+ for (const entry of sortedEntries) {
5089
+ if (galleryItemsRef.current.find((g) => g.id === entry.id)?.base64) continue;
5090
+ if (hfImageNotFoundRef.current.has(entry.id)) continue;
5091
+ try {
5092
+ const b64 = await hfLoadImageAsBase64(entry.id, hfToken, effectiveNamespace, entry.filename, void 0, entry.mimeType, entry.hasThumb);
5093
+ if (!b64) {
5094
+ hfImageNotFoundRef.current.set(entry.id, Date.now());
5095
+ setHfMissingImages((prev) => {
5096
+ if (prev.find((e) => e.id === entry.id)) return prev;
5097
+ return [...prev, { id: entry.id, filename: entry.filename, mimeType: entry.mimeType, timestamp: entry.timestamp }];
5098
+ });
5099
+ continue;
5100
+ }
5101
+ const prefix = `data:${entry.mimeType || "image/jpeg"};base64,`;
5102
+ setGalleryItems((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
5103
+ setHistory((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
5104
+ } catch {
5028
5105
  hfImageNotFoundRef.current.set(entry.id, Date.now());
5029
- return;
5030
5106
  }
5031
- const prefix = `data:${entry.mimeType || "image/jpeg"};base64,`;
5032
- setGalleryItems((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
5033
- setHistory((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
5034
- }).catch(() => {
5035
- hfImageNotFoundRef.current.set(entry.id, Date.now());
5036
- });
5037
- }
5107
+ }
5108
+ })();
5038
5109
  }, [hfState]);
5039
- const [activePrompt, setActivePrompt] = (0, import_react25.useState)("");
5040
- const [isSynthesizing, setIsSynthesizing] = (0, import_react25.useState)(false);
5041
- const [activeGenerationsCount, setActiveGenerationsCount] = (0, import_react25.useState)(0);
5042
- const [currentResult, setCurrentResult] = (0, import_react25.useState)(null);
5043
- const [focusedNodeId, setFocusedNodeId] = (0, import_react25.useState)(null);
5044
- const [leftTab, setLeftTab] = (0, import_react25.useState)("prompt");
5045
- const [promptFeedback, setPromptFeedback] = (0, import_react25.useState)(null);
5046
- const [lastPromptPayload, setLastPromptPayload] = (0, import_react25.useState)(null);
5047
- const [isPromptTabGenerating, setIsPromptTabGenerating] = (0, import_react25.useState)(false);
5048
- const [activeTab, setActiveTab] = (0, import_react25.useState)("history");
5049
- const [mobileTab, setMobileTab] = (0, import_react25.useState)("stage");
5050
- const [middlePanel, setMiddlePanel] = (0, import_react25.useState)("stage");
5051
- const [recentLabItems, setRecentLabItems] = (0, import_react25.useState)([]);
5052
- const [aspectRatio, setAspectRatio] = (0, import_react25.useState)("1:1");
5053
- const [selectedModel, setSelectedModel] = (0, import_react25.useState)("\u{1F34C} Nano Banana Pro");
5054
- const [seed, setSeed] = (0, import_react25.useState)(Math.floor(Math.random() * 1e6));
5055
- const [seedMode, setSeedMode] = (0, import_react25.useState)("random");
5056
- const [isLeftCollapsed, setIsLeftCollapsed] = (0, import_react25.useState)(false);
5057
- const [isRightCollapsed, setIsRightCollapsed] = (0, import_react25.useState)(false);
5058
- const [leftPanelWidth, setLeftPanelWidth] = (0, import_react25.useState)(() => {
5110
+ const [activePrompt, setActivePrompt] = (0, import_react27.useState)("");
5111
+ const [isSynthesizing, setIsSynthesizing] = (0, import_react27.useState)(false);
5112
+ const [activeGenerationsCount, setActiveGenerationsCount] = (0, import_react27.useState)(0);
5113
+ const [currentResult, setCurrentResult] = (0, import_react27.useState)(null);
5114
+ const [focusedNodeId, setFocusedNodeId] = (0, import_react27.useState)(null);
5115
+ const [leftTab, setLeftTab] = (0, import_react27.useState)("prompt");
5116
+ const [promptFeedback, setPromptFeedback] = (0, import_react27.useState)(null);
5117
+ const [lastPromptPayload, setLastPromptPayload] = (0, import_react27.useState)(null);
5118
+ const [isPromptTabGenerating, setIsPromptTabGenerating] = (0, import_react27.useState)(false);
5119
+ const [activeTab, setActiveTab] = (0, import_react27.useState)("history");
5120
+ const [mobileTab, setMobileTab] = (0, import_react27.useState)("stage");
5121
+ const [middlePanel, setMiddlePanel] = (0, import_react27.useState)("stage");
5122
+ const [recentLabItems, setRecentLabItems] = (0, import_react27.useState)([]);
5123
+ const [aspectRatio, setAspectRatio] = (0, import_react27.useState)("1:1");
5124
+ const [selectedModel, setSelectedModel] = (0, import_react27.useState)("\u{1F34C} Nano Banana Pro");
5125
+ const [seed, setSeed] = (0, import_react27.useState)(Math.floor(Math.random() * 1e6));
5126
+ const [seedMode, setSeedMode] = (0, import_react27.useState)("random");
5127
+ const [isLeftCollapsed, setIsLeftCollapsed] = (0, import_react27.useState)(false);
5128
+ const [isRightCollapsed, setIsRightCollapsed] = (0, import_react27.useState)(false);
5129
+ const [leftPanelWidth, setLeftPanelWidth] = (0, import_react27.useState)(() => {
5059
5130
  try {
5060
5131
  return parseInt(localStorage.getItem("aa-left-width") || "260", 10);
5061
5132
  } catch {
5062
5133
  return 260;
5063
5134
  }
5064
5135
  });
5065
- const [rightPanelWidth, setRightPanelWidth] = (0, import_react25.useState)(() => {
5136
+ const [rightPanelWidth, setRightPanelWidth] = (0, import_react27.useState)(() => {
5066
5137
  try {
5067
5138
  return parseInt(localStorage.getItem("aa-right-width") || "320", 10);
5068
5139
  } catch {
5069
5140
  return 320;
5070
5141
  }
5071
5142
  });
5072
- const [isPromptCollapsed, setIsPromptCollapsed] = (0, import_react25.useState)(false);
5073
- const [projectActionState, setProjectActionState] = (0, import_react25.useState)("idle");
5074
- const syncServerDataRef = (0, import_react25.useRef)(null);
5075
- const [workspaceTags, setWorkspaceTags] = (0, import_react25.useState)(null);
5076
- const [serverProjects, setServerProjects] = (0, import_react25.useState)([]);
5077
- const [isLoadingFromServer, setIsLoadingFromServer] = (0, import_react25.useState)(false);
5078
- const [highContrast, setHighContrast] = (0, import_react25.useState)(() => {
5143
+ const [isPromptCollapsed, setIsPromptCollapsed] = (0, import_react27.useState)(false);
5144
+ const [projectActionState, setProjectActionState] = (0, import_react27.useState)("idle");
5145
+ const syncServerDataRef = (0, import_react27.useRef)(null);
5146
+ const [workspaceTags, setWorkspaceTags] = (0, import_react27.useState)(null);
5147
+ const [serverProjects, setServerProjects] = (0, import_react27.useState)([]);
5148
+ const [isLoadingFromServer, setIsLoadingFromServer] = (0, import_react27.useState)(false);
5149
+ const [highContrast, setHighContrast] = (0, import_react27.useState)(() => {
5079
5150
  try {
5080
5151
  return localStorage.getItem("aa-contrast") === "high";
5081
5152
  } catch {
5082
5153
  return false;
5083
5154
  }
5084
5155
  });
5085
- const [activeReferenceId, setActiveReferenceId] = (0, import_react25.useState)(null);
5086
- const [activeReferenceThumbnail, setActiveReferenceThumbnail] = (0, import_react25.useState)(null);
5087
- const [isScanningImage, setIsScanningImage] = (0, import_react25.useState)(false);
5088
- const [touchStartX, setTouchStartX] = (0, import_react25.useState)(null);
5089
- const [isFullscreen, setIsFullscreen] = (0, import_react25.useState)(false);
5090
- const [zoomScale, setZoomScale] = (0, import_react25.useState)(1);
5091
- const [zoomOffset, setZoomOffset] = (0, import_react25.useState)({ x: 0, y: 0 });
5092
- const lastPinchDist = (0, import_react25.useRef)(null);
5093
- const lastTapTime = (0, import_react25.useRef)(0);
5094
- const dragStart = (0, import_react25.useRef)(null);
5156
+ const [activeReferenceId, setActiveReferenceId] = (0, import_react27.useState)(null);
5157
+ const [activeReferenceThumbnail, setActiveReferenceThumbnail] = (0, import_react27.useState)(null);
5158
+ const [isScanningImage, setIsScanningImage] = (0, import_react27.useState)(false);
5159
+ const [touchStartX, setTouchStartX] = (0, import_react27.useState)(null);
5160
+ const [isFullscreen, setIsFullscreen] = (0, import_react27.useState)(false);
5161
+ const [zoomScale, setZoomScale] = (0, import_react27.useState)(1);
5162
+ const [zoomOffset, setZoomOffset] = (0, import_react27.useState)({ x: 0, y: 0 });
5163
+ const lastPinchDist = (0, import_react27.useRef)(null);
5164
+ const lastTapTime = (0, import_react27.useRef)(0);
5165
+ const dragStart = (0, import_react27.useRef)(null);
5095
5166
  const openFullscreen = () => {
5096
5167
  setIsFullscreen(true);
5097
5168
  setZoomScale(1);
@@ -5154,7 +5225,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5154
5225
  setActiveReferenceId(null);
5155
5226
  setActiveReferenceThumbnail(null);
5156
5227
  };
5157
- const labServices = (0, import_react25.useMemo)(() => {
5228
+ const labServices = (0, import_react27.useMemo)(() => {
5158
5229
  const available = groupGenerationsToLabItems([...galleryItems, ...history]);
5159
5230
  return {
5160
5231
  availableItems: available,
@@ -5234,17 +5305,45 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5234
5305
  setIsScanningImage(false);
5235
5306
  }
5236
5307
  };
5237
- const currentIndex = (0, import_react25.useMemo)(() => history.findIndex((h) => h.id === currentResult?.id), [history, currentResult]);
5238
- const goToPrev = (0, import_react25.useCallback)(() => {
5308
+ const currentIndex = (0, import_react27.useMemo)(() => history.findIndex((h) => h.id === currentResult?.id), [history, currentResult]);
5309
+ const goToPrev = (0, import_react27.useCallback)(() => {
5239
5310
  if (currentIndex > 0) setCurrentResult(history[currentIndex - 1]);
5240
5311
  }, [currentIndex, history]);
5241
- const goToNext = (0, import_react25.useCallback)(() => {
5312
+ const goToNext = (0, import_react27.useCallback)(() => {
5242
5313
  if (currentIndex < history.length - 1) setCurrentResult(history[currentIndex + 1]);
5243
5314
  }, [currentIndex, history]);
5315
+ const handleGallerySelect = (0, import_react27.useCallback)((g) => {
5316
+ setCurrentResult(g);
5317
+ setMobileTab("stage");
5318
+ if (g.filename && hfToken && !g.fullBase64) {
5319
+ hfLoadImageAsBase64(g.id, hfToken, effectiveNamespace, g.filename, void 0, g.mimeType, false).then((b64) => {
5320
+ if (!b64) return;
5321
+ const full = `data:${g.mimeType || "image/jpeg"};base64,${b64}`;
5322
+ setCurrentResult((prev) => prev?.id === g.id ? { ...prev, fullBase64: full } : prev);
5323
+ setGalleryItems((prev) => prev.map((item) => item.id === g.id ? { ...item, fullBase64: full } : item));
5324
+ }).catch(() => {
5325
+ });
5326
+ }
5327
+ }, [hfToken, effectiveNamespace]);
5328
+ const handleTitleSet = (0, import_react27.useCallback)((id) => {
5329
+ const ts = Date.now();
5330
+ setGalleryItems((prev) => prev.map((g) => g.id === id ? { ...g, titleTs: ts } : g));
5331
+ setHistory((prev) => prev.map((g) => g.id === id ? { ...g, titleTs: ts } : g));
5332
+ if (hfToken && effectiveNamespace) {
5333
+ hfWriteEvent("title_set", { id }).catch(() => {
5334
+ });
5335
+ }
5336
+ }, [hfToken, effectiveNamespace, hfWriteEvent]);
5337
+ const currentGroup = (0, import_react27.useMemo)(() => {
5338
+ if (!currentResult?.prompt) return [];
5339
+ const groups = groupByPrompt(galleryItems.filter((g) => g.status === "done" && !!g.base64));
5340
+ const group = groups.find((gr) => gr.prompt === currentResult.prompt);
5341
+ return group ? group.items : [];
5342
+ }, [galleryItems, currentResult?.prompt]);
5244
5343
  const hcStyle = highContrast ? { filter: "brightness(1.6) contrast(1.05)" } : void 0;
5245
5344
  const isGenerating = activeGenerationsCount > 0;
5246
5345
  useKeyboardNavigation(history, currentResult, setCurrentResult);
5247
- const getSubtreeFormat = (0, import_react25.useCallback)((nodeId, depth = 0) => {
5346
+ const getSubtreeFormat = (0, import_react27.useCallback)((nodeId, depth = 0) => {
5248
5347
  const node = nodes.find((n) => n.id === nodeId);
5249
5348
  if (!node) return "";
5250
5349
  const childrenIds = edges.filter((e) => e.source === nodeId).map((e) => e.target);
@@ -5252,7 +5351,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5252
5351
  return `${indent}- ${node.data.label || "(unbenannt)"}
5253
5352
  ` + childrenIds.map((id) => getSubtreeFormat(id, depth + 1)).join("");
5254
5353
  }, [nodes, edges]);
5255
- const activePath = (0, import_react25.useMemo)(() => {
5354
+ const activePath = (0, import_react27.useMemo)(() => {
5256
5355
  if (!focusedNodeId) return /* @__PURE__ */ new Set();
5257
5356
  const path = /* @__PURE__ */ new Set([focusedNodeId]);
5258
5357
  let currId = focusedNodeId;
@@ -5422,8 +5521,9 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5422
5521
  const handleDownloadSingle = async () => {
5423
5522
  if (!currentResult?.base64) return;
5424
5523
  try {
5425
- const base64Data = currentResult.base64.split(",")[1];
5426
- const mimeType = currentResult.base64.split(";")[0].split(":")[1] || "image/png";
5524
+ const downloadSrc = currentResult.fullBase64 || currentResult.base64;
5525
+ const base64Data = downloadSrc.split(",")[1];
5526
+ const mimeType = downloadSrc.split(";")[0].split(":")[1] || "image/png";
5427
5527
  let finalBase64 = base64Data;
5428
5528
  if (mimeType === "image/png") finalBase64 = injectXMPMetadata(base64Data, currentResult.prompt || "", currentResult.seed, currentResult.model, currentResult.id, currentResult.tags || [], getSubtreeFormat(currentResult.nodeId));
5429
5529
  await onDownload(finalBase64, mimeType, `avatar_${currentResult.id.slice(0, 5)}.${mimeType.split("/")[1]}`);
@@ -5600,7 +5700,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5600
5700
  setTimeout(() => setProjectActionState("idle"), 4e3);
5601
5701
  }
5602
5702
  };
5603
- (0, import_react25.useEffect)(() => {
5703
+ (0, import_react27.useEffect)(() => {
5604
5704
  if (activeTab === "setup" || activeTab === "sync") fetchServerProjects();
5605
5705
  }, [activeTab]);
5606
5706
  const mergeWorkspaceTags = (local, remote) => {
@@ -5623,7 +5723,8 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5623
5723
  return { by_category: merged, all };
5624
5724
  };
5625
5725
  if (isFullscreen && currentResult?.base64) {
5626
- const fsBase64 = currentResult.base64.startsWith("data:") ? currentResult.base64 : `data:image/png;base64,${currentResult.base64}`;
5726
+ const fsRaw = currentResult.fullBase64 || currentResult.base64;
5727
+ const fsBase64 = fsRaw.startsWith("data:") ? fsRaw : `data:image/png;base64,${fsRaw}`;
5627
5728
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
5628
5729
  "div",
5629
5730
  {
@@ -5870,7 +5971,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5870
5971
  setMobileTab("stage");
5871
5972
  }
5872
5973
  } }) }),
5873
- mobileTab === "server" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex flex-col flex-1 min-h-0 relative", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ServerTab, { hfToken: hfToken || void 0 }) }),
5974
+ mobileTab === "server" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex flex-col flex-1 min-h-0 relative", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ServerTab, { serverBaseUrl }) }),
5874
5975
  mobileTab === "tags" && workspaceTags && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex flex-col flex-1 min-h-0", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5875
5976
  TagManagerPanel,
5876
5977
  {
@@ -5948,7 +6049,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5948
6049
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-white/50 text-[13px]", children: currentResult.error?.message }),
5949
6050
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { onClick: () => handleGenerateImage(currentResult.prompt), className: "px-4 py-2 rounded-lg border border-white/20 text-[13px] text-white/70 active:bg-white/10", children: "Erneut versuchen" })
5950
6051
  ] }),
5951
- currentResult?.status === "done" && currentResult.base64 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("img", { src: currentResult.base64, className: "w-full h-full object-contain" }),
6052
+ currentResult?.status === "done" && currentResult.base64 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("img", { src: currentResult.fullBase64 || currentResult.base64, className: "w-full h-full object-contain" }),
5952
6053
  !currentResult && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col items-center gap-2 opacity-10", children: [
5953
6054
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[64px]", children: "palette" }),
5954
6055
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-[11px] font-bold uppercase tracking-[0.2em]", children: "Avatar Architect" })
@@ -5966,6 +6067,49 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5966
6067
  ]
5967
6068
  }
5968
6069
  ),
6070
+ currentResult?.status === "done" && currentGroup.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "mt-2 flex gap-1.5 overflow-x-auto pb-1", style: { scrollbarWidth: "none" }, children: currentGroup.map((item) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { position: "relative", flexShrink: 0 }, children: [
6071
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6072
+ "div",
6073
+ {
6074
+ onClick: () => handleGallerySelect(item),
6075
+ style: {
6076
+ width: 44,
6077
+ height: 44,
6078
+ borderRadius: 8,
6079
+ overflow: "hidden",
6080
+ cursor: "pointer",
6081
+ border: item.id === currentResult.id ? "2px solid rgba(255,255,255,0.8)" : "2px solid rgba(255,255,255,0.15)",
6082
+ opacity: item.id === currentResult.id ? 1 : 0.55
6083
+ },
6084
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("img", { src: item.base64, style: { width: "100%", height: "100%", objectFit: "cover" }, alt: "" })
6085
+ }
6086
+ ),
6087
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6088
+ "button",
6089
+ {
6090
+ onClick: (e) => {
6091
+ e.stopPropagation();
6092
+ handleTitleSet(item.id);
6093
+ },
6094
+ style: {
6095
+ position: "absolute",
6096
+ bottom: 2,
6097
+ right: 2,
6098
+ width: 14,
6099
+ height: 14,
6100
+ background: item.titleTs ? "#f59e0b" : "rgba(0,0,0,0.75)",
6101
+ border: "1px solid rgba(255,255,255,0.25)",
6102
+ borderRadius: 3,
6103
+ cursor: "pointer",
6104
+ padding: 0,
6105
+ display: "flex",
6106
+ alignItems: "center",
6107
+ justifyContent: "center"
6108
+ },
6109
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 9, color: item.titleTs ? "#fff" : "rgba(255,255,255,0.5)", lineHeight: 1 }, children: "star" })
6110
+ }
6111
+ )
6112
+ ] }, item.id)) }),
5969
6113
  currentResult?.status === "done" && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex gap-2 mt-3", children: [
5970
6114
  /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("button", { onClick: () => setActivePrompt(currentResult.prompt || ""), className: "flex-1 flex items-center justify-center gap-1.5 rounded-xl border border-white/10 bg-white/5 active:bg-white/10 transition-colors", style: { height: 44 }, children: [
5971
6115
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[18px] text-white/60", children: "replay" }),
@@ -5988,10 +6132,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5988
6132
  hfToken && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { onClick: () => refreshHF(), disabled: isHfRefreshing, className: "w-12 flex items-center justify-center text-white/20 active:text-white transition-colors disabled:opacity-30", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: `material-symbols-outlined text-[20px]${isHfRefreshing ? " animate-spin" : ""}`, children: "sync" }) })
5989
6133
  ] }),
5990
6134
  /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex-1 overflow-hidden relative", children: [
5991
- activeTab === "history" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(HistoryPanel, { history, currentResultId: currentResult?.id || null, onSelect: (g) => {
5992
- setCurrentResult(g);
5993
- setMobileTab("stage");
5994
- }, onDelete: (id) => setHistory((h) => h.filter((x) => x.id !== id)) }),
6135
+ activeTab === "history" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(HistoryPanel, { history, currentResultId: currentResult?.id || null, onSelect: handleGallerySelect, onDelete: (id) => setHistory((h) => h.filter((x) => x.id !== id)) }),
5995
6136
  activeTab === "gallery" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5996
6137
  MediaLibrary,
5997
6138
  {
@@ -6002,10 +6143,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6002
6143
  setGalleryItems((prev) => [...media.map((m) => ({ id: crypto.randomUUID(), nodeId: "1", base64: `data:${m.mimeType};base64,${m.base64}`, mediaId: m.mediaId, prompt: m.name || "Importiert", timestamp: Date.now(), status: "done", tags: [], type: "import" })), ...prev]);
6003
6144
  },
6004
6145
  onDelete: (id) => setGalleryItems((g) => g.filter((x) => x.id !== id)),
6005
- onSelect: (g) => {
6006
- setCurrentResult(g);
6007
- setMobileTab("stage");
6008
- },
6146
+ onSelect: handleGallerySelect,
6009
6147
  onGenerateReference: (item) => {
6010
6148
  handleGenerateImage(item.prompt || activePrompt, item.mediaId, void 0, { silent: true });
6011
6149
  setMobileTab("stage");
@@ -6045,7 +6183,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6045
6183
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[20px]", children: "biotech" }),
6046
6184
  "HF"
6047
6185
  ] }),
6048
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { onClick: () => setActiveTab("server"), className: `flex-1 flex items-center justify-center gap-1.5 text-[11px] font-bold uppercase tracking-wide transition-colors ${activeTab === "server" ? "text-white border-b-2 border-white" : "text-white/30"}`, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[20px]", children: "storage" }) }),
6186
+ serverBaseUrl && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { onClick: () => setActiveTab("server"), className: `flex-1 flex items-center justify-center gap-1.5 text-[11px] font-bold uppercase tracking-wide transition-colors ${activeTab === "server" ? "text-white border-b-2 border-white" : "text-white/30"}`, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[20px]", children: "storage" }) }),
6049
6187
  workspaceTags && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { onClick: () => setActiveTab("tags"), className: `flex-1 flex items-center justify-center gap-1.5 text-[11px] font-bold uppercase tracking-wide transition-colors ${activeTab === "tags" ? "text-white border-b-2 border-white" : "text-white/30"}`, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[20px]", children: "label" }) })
6050
6188
  ] }),
6051
6189
  /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex-1 overflow-hidden relative", children: [
@@ -6128,10 +6266,11 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6128
6266
  galleryItems,
6129
6267
  allEvents: hfAllEvents,
6130
6268
  confirmedEventKeys: hfConfirmedKeys,
6131
- imageUploadStatus
6269
+ imageUploadStatus,
6270
+ missingImages: hfMissingImages
6132
6271
  }
6133
6272
  ) }),
6134
- activeTab === "server" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "absolute inset-0", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ServerTab, { hfToken: hfToken || void 0 }) })
6273
+ activeTab === "server" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "absolute inset-0", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ServerTab, { serverBaseUrl }) })
6135
6274
  ] })
6136
6275
  ] }),
6137
6276
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex border-t border-white/10 bg-black shrink-0", style: { height: 56, paddingBottom: "env(safe-area-inset-bottom, 0px)" }, children: [
@@ -6140,7 +6279,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6140
6279
  { id: "labs", icon: "science", label: "Labs" },
6141
6280
  ...workspaceTags ? [{ id: "tags", icon: "label", label: "Tags" }] : [],
6142
6281
  { id: "browse", icon: "photo_library", label: "Galerie" },
6143
- { id: "server", icon: "storage", label: "Server" }
6282
+ ...serverBaseUrl ? [{ id: "server", icon: "storage", label: "Server" }] : []
6144
6283
  ].map((tab) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("button", { onClick: () => setMobileTab(tab.id), className: `flex-1 flex flex-col items-center justify-center gap-0.5 transition-colors ${mobileTab === tab.id ? "text-white" : "text-white/30"}`, children: [
6145
6284
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[24px]", children: tab.icon }),
6146
6285
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-[10px] font-bold uppercase tracking-wide", children: tab.label })
@@ -6191,7 +6330,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6191
6330
  ] })
6192
6331
  }
6193
6332
  ) }),
6194
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { flex: 1, overflow: "hidden", position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(HistoryPanel, { history, currentResultId: currentResult?.id || null, onSelect: setCurrentResult, onDelete: (id) => setHistory((h) => h.filter((x) => x.id !== id)) }) })
6333
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { style: { flex: 1, overflow: "hidden", position: "relative" }, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(HistoryPanel, { history, currentResultId: currentResult?.id || null, onSelect: handleGallerySelect, onDelete: (id) => setHistory((h) => h.filter((x) => x.id !== id)) }) })
6195
6334
  ] }),
6196
6335
  /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { flex: 1, height: tlH, display: "flex", flexDirection: "column", background: "#0b0b0b" }, children: [
6197
6336
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
@@ -6216,7 +6355,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6216
6355
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { style: { fontSize: 11, color: "rgba(255,255,255,0.5)", margin: 0 }, children: currentResult.error?.message }),
6217
6356
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { onClick: () => handleGenerateImage(currentResult.prompt), style: { padding: "8px 16px", borderRadius: 8, border: "1px solid rgba(255,255,255,0.2)", fontSize: 11, color: "rgba(255,255,255,0.7)", background: "none", cursor: "pointer", fontFamily: "inherit" }, children: "Erneut versuchen" })
6218
6357
  ] }),
6219
- currentResult?.status === "done" && currentResult.base64 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("img", { src: currentResult.base64, style: { maxWidth: "100%", maxHeight: "100%", objectFit: "contain" } }),
6358
+ currentResult?.status === "done" && currentResult.base64 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("img", { src: currentResult.fullBase64 || currentResult.base64, style: { maxWidth: "100%", maxHeight: "100%", objectFit: "contain" } }),
6220
6359
  !currentResult && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 8, opacity: 0.1 }, children: [
6221
6360
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 64 }, children: "palette" }),
6222
6361
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { style: { fontSize: 11, fontWeight: "bold", textTransform: "uppercase", letterSpacing: "0.2em" }, children: "Avatar Architect" })
@@ -6358,33 +6497,81 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6358
6497
  middlePanel === "labs" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 overflow-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(LabsTab, { services: labServices, onResult: (item) => {
6359
6498
  const frame = item.frames[0];
6360
6499
  if (frame?.base64) setCurrentResult(frameToGeneration(frame, item));
6361
- } }) }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 p-6 overflow-hidden flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "h-full w-full max-w-4xl aspect-square rounded-3xl border border-white/5 bg-black/40 relative overflow-hidden flex items-center justify-center group shadow-2xl", children: [
6362
- isGenerating && currentResult?.status === "done" && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "absolute top-6 right-6 z-30 bg-black/60 backdrop-blur-md px-4 py-2 rounded-full border border-white/10 flex items-center gap-3", children: [
6363
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "w-3 h-3 border-t-2 border-white rounded-full animate-spin" }),
6364
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-[10px] text-white/60 uppercase font-bold tracking-widest", children: "Neue Referenz..." })
6365
- ] }),
6366
- currentResult ? currentResult.status === "processing" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
6367
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "w-10 h-10 border-t-2 border-white rounded-full animate-spin" }),
6368
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-[10px] text-white/40 uppercase font-bold tracking-widest", children: "Erstelle Bild..." })
6369
- ] }) : currentResult.status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "p-10 text-center flex flex-col items-center gap-5 max-w-md", children: [
6370
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "w-16 h-16 rounded-full bg-red-500/10 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-red-500 text-[32px]", children: "warning" }) }),
6371
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col gap-2", children: [
6372
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h3", { className: "text-[11px] font-bold uppercase tracking-widest text-red-400", children: "Generierungsfehler" }),
6373
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-white/60 text-[12px] leading-relaxed", children: currentResult.error?.message })
6500
+ } }) }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex-1 overflow-hidden flex flex-col", children: [
6501
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "flex-1 p-6 overflow-hidden flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "h-full w-full max-w-4xl aspect-square rounded-3xl border border-white/5 bg-black/40 relative overflow-hidden flex items-center justify-center group shadow-2xl", children: [
6502
+ isGenerating && currentResult?.status === "done" && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "absolute top-6 right-6 z-30 bg-black/60 backdrop-blur-md px-4 py-2 rounded-full border border-white/10 flex items-center gap-3", children: [
6503
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "w-3 h-3 border-t-2 border-white rounded-full animate-spin" }),
6504
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-[10px] text-white/60 uppercase font-bold tracking-widest", children: "Neue Referenz..." })
6374
6505
  ] }),
6375
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PillButton, { variant: "outline", icon: "refresh", onClick: () => handleGenerateImage(currentResult.prompt), children: "Erneut versuchen" })
6376
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "h-full w-full relative flex items-center justify-center", children: [
6377
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("img", { src: currentResult.base64, className: "max-h-full max-w-full object-contain rounded-xl shadow-2xl" }),
6378
- /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "absolute bottom-6 flex gap-2 opacity-0 group-hover:opacity-100 transition-all translate-y-4 group-hover:translate-y-0 z-20", children: [
6379
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PillButton, { variant: "outline", icon: "replay", onClick: () => setActivePrompt(currentResult.prompt || ""), children: "Prompt" }),
6380
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PillButton, { variant: "solid", icon: "auto_fix_high", onClick: () => handleGenerateImage(currentResult.prompt || activePrompt, currentResult.mediaId, void 0, { silent: true }), children: "Referenz" }),
6381
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PillButton, { variant: "outline", icon: "download", onClick: handleDownloadSingle, children: "Speichern" })
6506
+ currentResult ? currentResult.status === "processing" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
6507
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "w-10 h-10 border-t-2 border-white rounded-full animate-spin" }),
6508
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-[10px] text-white/40 uppercase font-bold tracking-widest", children: "Erstelle Bild..." })
6509
+ ] }) : currentResult.status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "p-10 text-center flex flex-col items-center gap-5 max-w-md", children: [
6510
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "w-16 h-16 rounded-full bg-red-500/10 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-red-500 text-[32px]", children: "warning" }) }),
6511
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col gap-2", children: [
6512
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h3", { className: "text-[11px] font-bold uppercase tracking-widest text-red-400", children: "Generierungsfehler" }),
6513
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-white/60 text-[12px] leading-relaxed", children: currentResult.error?.message })
6514
+ ] }),
6515
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PillButton, { variant: "outline", icon: "refresh", onClick: () => handleGenerateImage(currentResult.prompt), children: "Erneut versuchen" })
6516
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "h-full w-full relative flex items-center justify-center", children: [
6517
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("img", { src: currentResult.fullBase64 || currentResult.base64, className: "max-h-full max-w-full object-contain rounded-xl shadow-2xl" }),
6518
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "absolute bottom-6 flex gap-2 opacity-0 group-hover:opacity-100 transition-all translate-y-4 group-hover:translate-y-0 z-20", children: [
6519
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PillButton, { variant: "outline", icon: "replay", onClick: () => setActivePrompt(currentResult.prompt || ""), children: "Prompt" }),
6520
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PillButton, { variant: "solid", icon: "auto_fix_high", onClick: () => handleGenerateImage(currentResult.prompt || activePrompt, currentResult.mediaId, void 0, { silent: true }), children: "Referenz" }),
6521
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PillButton, { variant: "outline", icon: "download", onClick: handleDownloadSingle, children: "Speichern" })
6522
+ ] })
6523
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col items-center gap-2 opacity-10", children: [
6524
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[100px]", children: "palette" }),
6525
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-[12px] font-bold uppercase tracking-[0.2em]", children: "Avatar Architect" })
6382
6526
  ] })
6383
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col items-center gap-2 opacity-10", children: [
6384
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[100px]", children: "palette" }),
6385
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-[12px] font-bold uppercase tracking-[0.2em]", children: "Avatar Architect" })
6386
- ] })
6387
- ] }) })
6527
+ ] }) }),
6528
+ currentResult?.status === "done" && currentGroup.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "px-6 pb-4 shrink-0 flex gap-2 overflow-x-auto", style: { scrollbarWidth: "none" }, children: currentGroup.map((item) => /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { position: "relative", flexShrink: 0 }, children: [
6529
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6530
+ "div",
6531
+ {
6532
+ onClick: () => handleGallerySelect(item),
6533
+ style: {
6534
+ width: 52,
6535
+ height: 52,
6536
+ borderRadius: 10,
6537
+ overflow: "hidden",
6538
+ cursor: "pointer",
6539
+ border: item.id === currentResult.id ? "2px solid rgba(255,255,255,0.8)" : "2px solid rgba(255,255,255,0.12)",
6540
+ opacity: item.id === currentResult.id ? 1 : 0.5,
6541
+ transition: "opacity 0.15s, border-color 0.15s"
6542
+ },
6543
+ className: "hover:opacity-80",
6544
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("img", { src: item.base64, style: { width: "100%", height: "100%", objectFit: "cover" }, alt: "" })
6545
+ }
6546
+ ),
6547
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6548
+ "button",
6549
+ {
6550
+ onClick: (e) => {
6551
+ e.stopPropagation();
6552
+ handleTitleSet(item.id);
6553
+ },
6554
+ title: "Als Titel markieren",
6555
+ style: {
6556
+ position: "absolute",
6557
+ bottom: 3,
6558
+ right: 3,
6559
+ width: 16,
6560
+ height: 16,
6561
+ background: item.titleTs ? "#f59e0b" : "rgba(0,0,0,0.75)",
6562
+ border: "1px solid rgba(255,255,255,0.25)",
6563
+ borderRadius: 4,
6564
+ cursor: "pointer",
6565
+ padding: 0,
6566
+ display: "flex",
6567
+ alignItems: "center",
6568
+ justifyContent: "center"
6569
+ },
6570
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 10, color: item.titleTs ? "#fff" : "rgba(255,255,255,0.5)", lineHeight: 1 }, children: "star" })
6571
+ }
6572
+ )
6573
+ ] }, item.id)) })
6574
+ ] })
6388
6575
  ] })
6389
6576
  ] }),
6390
6577
  !isRightCollapsed && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { onMouseDown: startRightResize, className: "w-1 shrink-0 cursor-col-resize hover:bg-white/20 active:bg-white/30 transition-colors", style: { background: "transparent" } }),
@@ -6395,7 +6582,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6395
6582
  setActiveTab(tab);
6396
6583
  setIsRightCollapsed(false);
6397
6584
  }, className: `flex-1 flex items-center justify-center relative transition-colors ${activeTab === tab ? "text-white" : "text-white/20"}`, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[20px]", children: tab === "history" ? "history" : tab === "gallery" ? "photo_library" : tab === "inspect" ? "info" : tab === "setup" ? "settings" : tab === "sync" ? "cloud_sync" : "label" }) }, tab)),
6398
- /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { onClick: () => {
6585
+ serverBaseUrl && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { onClick: () => {
6399
6586
  setActiveTab("server");
6400
6587
  setIsRightCollapsed(false);
6401
6588
  }, className: `w-10 flex items-center justify-center relative transition-colors ${activeTab === "server" ? "text-white" : "text-white/20"}`, children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[20px]", children: "storage" }) })
@@ -6419,7 +6606,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6419
6606
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[40px] text-white/10 block mb-3", children: "label_off" }),
6420
6607
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-[11px] text-white/20", children: "Erst Workspace importieren um Tags zu verwalten." })
6421
6608
  ] }) }),
6422
- activeTab === "history" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(HistoryPanel, { history, currentResultId: currentResult?.id || null, onSelect: setCurrentResult, onDelete: (id) => setHistory((h) => h.filter((x) => x.id !== id)) }),
6609
+ activeTab === "history" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(HistoryPanel, { history, currentResultId: currentResult?.id || null, onSelect: handleGallerySelect, onDelete: (id) => setHistory((h) => h.filter((x) => x.id !== id)) }),
6423
6610
  activeTab === "gallery" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6424
6611
  MediaLibrary,
6425
6612
  {
@@ -6430,7 +6617,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6430
6617
  setGalleryItems((prev) => [...media.map((m) => ({ id: crypto.randomUUID(), nodeId: "1", base64: `data:${m.mimeType};base64,${m.base64}`, mediaId: m.mediaId, prompt: m.name || "Importiert", timestamp: Date.now(), status: "done", tags: [], type: "import" })), ...prev]);
6431
6618
  },
6432
6619
  onDelete: (id) => setGalleryItems((g) => g.filter((x) => x.id !== id)),
6433
- onSelect: setCurrentResult,
6620
+ onSelect: handleGallerySelect,
6434
6621
  onGenerateReference: (item) => handleGenerateImage(item.prompt || activePrompt, item.mediaId, void 0, { silent: true })
6435
6622
  }
6436
6623
  ),
@@ -6460,14 +6647,14 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6460
6647
  onHfInitialSync: hfToken ? handleHfInitialSync : void 0
6461
6648
  }
6462
6649
  ),
6463
- activeTab === "server" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ServerTab, { hfToken: hfToken || void 0 })
6650
+ activeTab === "server" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ServerTab, { serverBaseUrl })
6464
6651
  ] })
6465
6652
  ] })
6466
6653
  ] });
6467
6654
  }
6468
6655
 
6469
6656
  // src/components/FaApp.tsx
6470
- var import_react26 = require("react");
6657
+ var import_react28 = require("react");
6471
6658
  var import_jsx_runtime24 = require("react/jsx-runtime");
6472
6659
  function FaApp({
6473
6660
  onGenerateImage,
@@ -6487,8 +6674,8 @@ function FaApp({
6487
6674
  onServerDelete,
6488
6675
  buildInfo
6489
6676
  }) {
6490
- const [hfNamespace, setHfNamespace] = (0, import_react26.useState)(void 0);
6491
- (0, import_react26.useEffect)(() => {
6677
+ const [hfNamespace, setHfNamespace] = (0, import_react28.useState)(void 0);
6678
+ (0, import_react28.useEffect)(() => {
6492
6679
  if (!serverBaseUrl) return;
6493
6680
  fetch(`${serverBaseUrl}/api/status`).then((r) => r.json()).then((d) => {
6494
6681
  if (typeof d.hfNamespace === "string") setHfNamespace(d.hfNamespace);
@@ -6509,6 +6696,7 @@ function FaApp({
6509
6696
  initialHfToken: libToken ? libToken.startsWith("hf_") ? libToken : `hf_${libToken}` : void 0,
6510
6697
  hfNamespace,
6511
6698
  allowDevNamespace: !hfNamespace && allowDevNamespace,
6699
+ serverBaseUrl,
6512
6700
  onFetchServerProjects,
6513
6701
  onServerSave,
6514
6702
  onServerLoad,
@@ -6521,7 +6709,7 @@ function FaApp({
6521
6709
  // src/index.ts
6522
6710
  init_hfStateService();
6523
6711
  init_hfStateService();
6524
- var LIB_VERSION = "2.0.30";
6712
+ var LIB_VERSION = "2.0.48";
6525
6713
  // Annotate the CommonJS export names for ESM import in node:
6526
6714
  0 && (module.exports = {
6527
6715
  AvatarArchitectApp,
@@ -6566,10 +6754,6 @@ var LIB_VERSION = "2.0.30";
6566
6754
  cleanAiResponse,
6567
6755
  createFlowServices,
6568
6756
  exportProjectToZip,
6569
- faServerDelete,
6570
- faServerGet,
6571
- faServerPost,
6572
- faServerPut,
6573
6757
  findForks,
6574
6758
  findTips,
6575
6759
  formatTreeToMarkdown,