@rslsp1/fa-app-tools 2.0.30 → 2.0.46

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,33 @@ 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");
1033
+
1034
+ // src/lib/grouping.ts
1035
+ function groupByPrompt(items) {
1036
+ const map = /* @__PURE__ */ new Map();
1037
+ for (const item of items) {
1038
+ const key = item.prompt ?? "";
1039
+ if (!map.has(key)) map.set(key, []);
1040
+ map.get(key).push(item);
1041
+ }
1042
+ const groups = [];
1043
+ for (const [prompt, groupItems] of map) {
1044
+ const titled = groupItems.filter((i) => i.titleTs);
1045
+ 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);
1046
+ groups.push({ prompt, items: groupItems, representative });
1047
+ }
1048
+ return groups.sort((a, b) => {
1049
+ const aMax = Math.max(...a.items.map((i) => i.timestamp));
1050
+ const bMax = Math.max(...b.items.map((i) => i.timestamp));
1051
+ return bMax - aMax;
1052
+ });
1053
+ }
1054
+
1055
+ // src/components/HistoryPanel.tsx
1027
1056
  var import_jsx_runtime5 = require("react/jsx-runtime");
1057
+ var PAGE_SIZE = 20;
1028
1058
  var formatFriendlyTimestamp = (timestamp) => {
1029
1059
  const date = new Date(timestamp);
1030
1060
  const now = /* @__PURE__ */ new Date();
@@ -1037,46 +1067,83 @@ var formatFriendlyTimestamp = (timestamp) => {
1037
1067
  return `${date.toLocaleDateString([], { day: "2-digit", month: "2-digit" })}, ${timeStr}`;
1038
1068
  };
1039
1069
  var HistoryPanel = ({ history, currentResultId, onSelect, onDelete }) => {
1070
+ const [visibleCount, setVisibleCount] = (0, import_react4.useState)(PAGE_SIZE);
1071
+ const doneItems = history.filter((g) => g.status === "done");
1072
+ const processingItems = history.filter((g) => g.status !== "done");
1073
+ const groups = groupByPrompt(doneItems);
1074
+ const visibleGroups = groups.slice(0, visibleCount);
1040
1075
  if (history.length === 0) {
1041
1076
  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
1077
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "material-symbols-outlined text-[64px]", children: "history" }),
1043
1078
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-[10px] font-bold uppercase tracking-widest", children: "Keine Historie" })
1044
1079
  ] });
1045
1080
  }
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 })
1057
- ] }),
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
- '"'
1081
+ 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: [
1082
+ processingItems.map((gen) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1083
+ "div",
1084
+ {
1085
+ className: "flex gap-3 p-2 bg-white/5 border border-white/5 rounded-2xl cursor-pointer group transition-all relative",
1086
+ onClick: () => onSelect(gen),
1087
+ children: [
1088
+ /* @__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: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-5 h-5 border-t-2 border-white rounded-full animate-spin" }) }),
1089
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex-1 py-0.5 overflow-hidden", children: [
1090
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[7px] font-bold text-white/20 uppercase", children: "Generiere..." }),
1091
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "text-[9px] text-white/40 line-clamp-2 leading-tight mt-1", children: [
1092
+ '"',
1093
+ gen.prompt,
1094
+ '"'
1095
+ ] })
1062
1096
  ] })
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
- )) });
1097
+ ]
1098
+ },
1099
+ gen.id
1100
+ )),
1101
+ visibleGroups.map((group) => {
1102
+ const rep = group.representative;
1103
+ const isActive = group.items.some((i) => i.id === currentResultId);
1104
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
1105
+ "div",
1106
+ {
1107
+ className: `flex gap-3 p-2 bg-white/5 border rounded-2xl cursor-pointer group transition-all relative ${isActive ? "border-white/40 bg-white/10 shadow-lg" : "border-white/5 hover:border-white/20"}`,
1108
+ onClick: () => onSelect(rep),
1109
+ children: [
1110
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "w-14 h-14 rounded-xl overflow-hidden bg-black shrink-0 border border-white/5 relative", children: [
1111
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: rep.base64 ? rep.base64.startsWith("data:") ? rep.base64 : `data:image/png;base64,${rep.base64}` : "", className: "w-full h-full object-cover", alt: "Thumbnail" }),
1112
+ group.items.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "absolute bottom-0.5 right-0.5 px-1 bg-black/80 rounded text-[7px] font-bold text-white/70", children: group.items.length })
1113
+ ] }),
1114
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex-1 py-0.5 overflow-hidden", children: [
1115
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between mb-1", children: [
1116
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[7px] font-bold text-white/20 uppercase whitespace-nowrap", children: formatFriendlyTimestamp(rep.timestamp) }),
1117
+ rep.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: rep.model })
1118
+ ] }),
1119
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "text-[9px] text-white/60 line-clamp-2 leading-tight", children: [
1120
+ '"',
1121
+ rep.prompt,
1122
+ '"'
1123
+ ] })
1124
+ ] }),
1125
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { onClick: (e) => {
1126
+ e.stopPropagation();
1127
+ onDelete(rep.id);
1128
+ }, 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" }) })
1129
+ ]
1130
+ },
1131
+ rep.id
1132
+ );
1133
+ }),
1134
+ visibleCount < groups.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: [
1135
+ groups.length - visibleCount,
1136
+ " weitere laden"
1137
+ ] })
1138
+ ] });
1072
1139
  };
1073
1140
 
1074
1141
  // src/components/InspectPanel.tsx
1075
- var import_react5 = require("react");
1076
- var import_react6 = require("motion/react");
1142
+ var import_react6 = require("react");
1143
+ var import_react7 = require("motion/react");
1077
1144
  var import_jsx_runtime6 = require("react/jsx-runtime");
1078
1145
  var InspectPanel = ({ currentResult, history, onSelect, workspaceTags, onTagToggle }) => {
1079
- const currentIndex = (0, import_react5.useMemo)(() => history.findIndex((h) => h.id === currentResult?.id), [history, currentResult]);
1146
+ const currentIndex = (0, import_react6.useMemo)(() => history.findIndex((h) => h.id === currentResult?.id), [history, currentResult]);
1080
1147
  if (!currentResult) {
1081
1148
  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
1149
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "material-symbols-outlined text-[64px]", children: "info" }),
@@ -1084,7 +1151,7 @@ var InspectPanel = ({ currentResult, history, onSelect, workspaceTags, onTagTogg
1084
1151
  ] });
1085
1152
  }
1086
1153
  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: [
1154
+ 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
1155
  /* @__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
1156
  /* @__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
1157
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SectionLabel, { children: "Vorschau" }),
@@ -1137,8 +1204,8 @@ var InspectPanel = ({ currentResult, history, onSelect, workspaceTags, onTagTogg
1137
1204
  };
1138
1205
 
1139
1206
  // src/components/SetupPanel.tsx
1140
- var import_react7 = require("react");
1141
- var import_react8 = require("motion/react");
1207
+ var import_react8 = require("react");
1208
+ var import_react9 = require("motion/react");
1142
1209
  var import_jsx_runtime7 = require("react/jsx-runtime");
1143
1210
  var PRESET_URLS = [
1144
1211
  "https://jsonplaceholder.typicode.com/todos/1",
@@ -1146,12 +1213,12 @@ var PRESET_URLS = [
1146
1213
  "https://esm.sh/@rslsp1/fa-app-tools@latest"
1147
1214
  ];
1148
1215
  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);
1216
+ const workspaceInputRef = (0, import_react8.useRef)(null);
1217
+ const [urlInput, setUrlInput] = (0, import_react8.useState)("");
1218
+ const [tokenInput, setTokenInput] = (0, import_react8.useState)("");
1219
+ const [testStatus, setTestStatus] = (0, import_react8.useState)("idle");
1220
+ const [result, setResult] = (0, import_react8.useState)(null);
1221
+ const [fetchError, setFetchError] = (0, import_react8.useState)(null);
1155
1222
  const runTest = async (url) => {
1156
1223
  if (!url.trim()) return;
1157
1224
  setTestStatus("loading");
@@ -1191,7 +1258,7 @@ var SetupPanel = ({ onWorkspaceImport, buildInfo }) => {
1191
1258
  setTestStatus("error");
1192
1259
  }
1193
1260
  };
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: [
1261
+ 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
1262
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-4", children: [
1196
1263
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-1", children: [
1197
1264
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SectionLabel, { children: "Workspace Management" }),
@@ -1314,18 +1381,25 @@ var SetupPanel = ({ onWorkspaceImport, buildInfo }) => {
1314
1381
  };
1315
1382
 
1316
1383
  // src/components/MediaLibrary.tsx
1317
- var import_react9 = require("motion/react");
1384
+ var import_react10 = require("react");
1385
+ var import_react11 = require("motion/react");
1318
1386
  var import_jsx_runtime8 = require("react/jsx-runtime");
1387
+ var PAGE_SIZE2 = 20;
1319
1388
  var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, onBatchDownload, onGenerateReference }) => {
1389
+ const [visibleCount, setVisibleCount] = (0, import_react10.useState)(PAGE_SIZE2);
1320
1390
  const selectedCount = items.filter((i) => i.selectedForExport).length;
1391
+ const groups = groupByPrompt(items);
1392
+ const visibleGroups = groups.slice(0, visibleCount);
1321
1393
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col h-full overflow-hidden", children: [
1322
1394
  /* @__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
1395
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center justify-between", children: [
1324
1396
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col", children: [
1325
1397
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-[10px] font-bold text-white uppercase tracking-widest", children: "Projekt Galerie" }),
1326
1398
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "text-[9px] text-white/30 uppercase tracking-tighter", children: [
1399
+ visibleCount < groups.length ? `${visibleCount} / ${groups.length}` : groups.length,
1400
+ " Gruppen \xB7 ",
1327
1401
  items.length,
1328
- " Assets geladen"
1402
+ " Assets"
1329
1403
  ] })
1330
1404
  ] }),
1331
1405
  /* @__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 +1407,7 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
1333
1407
  "Laden"
1334
1408
  ] })
1335
1409
  ] }),
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: [
1410
+ 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
1411
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "text-[9px] font-bold uppercase text-blue-400 ml-1", children: [
1338
1412
  selectedCount,
1339
1413
  " ausgew\xE4hlt"
@@ -1344,44 +1418,60 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
1344
1418
  ] })
1345
1419
  ] })
1346
1420
  ] }),
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: [
1421
+ /* @__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
1422
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "material-symbols-outlined text-[64px]", children: "photo_library" }),
1349
1423
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col gap-1", children: [
1350
1424
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-[12px] font-bold uppercase tracking-widest", children: "Keine Medien" }),
1351
1425
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-[10px] italic", children: "Importiere Assets aus deinem Projekt." })
1352
1426
  ] })
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)) }) })
1427
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "grid grid-cols-2 gap-3 pb-10", children: [
1428
+ visibleGroups.map((group) => {
1429
+ const rep = group.representative;
1430
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1431
+ import_react11.motion.div,
1432
+ {
1433
+ initial: { opacity: 0, scale: 0.9 },
1434
+ animate: { opacity: 1, scale: 1 },
1435
+ 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",
1436
+ onClick: () => onSelect(rep),
1437
+ children: [
1438
+ /* @__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 }),
1439
+ 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: [
1440
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "material-symbols-outlined text-[10px]", children: "photo_library" }),
1441
+ group.items.length
1442
+ ] }),
1443
+ /* @__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: [
1444
+ /* @__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" }),
1445
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-1", children: [
1446
+ onGenerateReference && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: (e) => {
1447
+ e.stopPropagation();
1448
+ onGenerateReference(rep);
1449
+ }, 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" }) }),
1450
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: (e) => {
1451
+ e.stopPropagation();
1452
+ onDelete(rep.id);
1453
+ }, 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" }) })
1454
+ ] })
1455
+ ] }) }),
1456
+ /* @__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" })
1457
+ ]
1458
+ },
1459
+ rep.id
1460
+ );
1461
+ }),
1462
+ 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: [
1463
+ groups.length - visibleCount,
1464
+ " weitere laden"
1465
+ ] }) })
1466
+ ] }) })
1377
1467
  ] });
1378
1468
  };
1379
1469
 
1380
1470
  // src/components/ListView.tsx
1381
- var import_react10 = require("react");
1471
+ var import_react12 = require("react");
1382
1472
  var import_jsx_runtime9 = require("react/jsx-runtime");
1383
1473
  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);
1474
+ const inputRef = (0, import_react12.useRef)(null);
1385
1475
  const hasChildren = children && children.length > 0;
1386
1476
  const handleKeyDown = (e) => {
1387
1477
  if (e.key === "Tab") {
@@ -1409,7 +1499,7 @@ var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNod
1409
1499
  onMoveNode(node.id, "down");
1410
1500
  }
1411
1501
  };
1412
- (0, import_react10.useEffect)(() => {
1502
+ (0, import_react12.useEffect)(() => {
1413
1503
  if (isActive && inputRef.current) inputRef.current.focus();
1414
1504
  }, [isActive]);
1415
1505
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col w-full", children: [
@@ -1443,7 +1533,7 @@ var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNod
1443
1533
  ] });
1444
1534
  };
1445
1535
  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());
1536
+ const [collapsed, setCollapsed] = (0, import_react12.useState)(/* @__PURE__ */ new Set());
1447
1537
  const toggleCollapse = (id) => {
1448
1538
  setCollapsed((prev) => {
1449
1539
  const next = new Set(prev);
@@ -1471,13 +1561,13 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
1471
1561
  }
1472
1562
 
1473
1563
  // src/components/AvatarArchitectApp.tsx
1474
- var import_react25 = require("react");
1564
+ var import_react27 = require("react");
1475
1565
 
1476
1566
  // src/components/PromptTab.tsx
1477
- var import_react12 = require("react");
1567
+ var import_react14 = require("react");
1478
1568
 
1479
1569
  // src/components/CollapsibleCard.tsx
1480
- var import_react11 = require("react");
1570
+ var import_react13 = require("react");
1481
1571
  var import_jsx_runtime10 = require("react/jsx-runtime");
1482
1572
  var CollapsibleCard = ({
1483
1573
  title,
@@ -1488,7 +1578,7 @@ var CollapsibleCard = ({
1488
1578
  collapsible = true,
1489
1579
  className = ""
1490
1580
  }) => {
1491
- const [isOpen, setIsOpen] = (0, import_react11.useState)(defaultOpen);
1581
+ const [isOpen, setIsOpen] = (0, import_react13.useState)(defaultOpen);
1492
1582
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `border border-neutral-800 rounded-lg ${className}`, children: [
1493
1583
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1494
1584
  "div",
@@ -1535,20 +1625,20 @@ var PromptTab = ({
1535
1625
  onTagUpdate,
1536
1626
  onTagDelete
1537
1627
  }) => {
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);
1628
+ const [selectedLabels, setSelectedLabels] = (0, import_react14.useState)(/* @__PURE__ */ new Set());
1629
+ const [instructions, setInstructions] = (0, import_react14.useState)("");
1630
+ const [rules, setRules] = (0, import_react14.useState)("");
1631
+ const [activeCategory, setActiveCategory] = (0, import_react14.useState)(null);
1632
+ const [copied, setCopied] = (0, import_react14.useState)(false);
1633
+ const imgInputRef = (0, import_react14.useRef)(null);
1634
+ const [addingInCat, setAddingInCat] = (0, import_react14.useState)(null);
1635
+ const [newLabel, setNewLabel] = (0, import_react14.useState)("");
1636
+ const [newValue, setNewValue] = (0, import_react14.useState)("");
1637
+ const [editingTag, setEditingTag] = (0, import_react14.useState)(null);
1638
+ const [editLabel, setEditLabel] = (0, import_react14.useState)("");
1639
+ const [editValue, setEditValue] = (0, import_react14.useState)("");
1640
+ const longPressTimer = (0, import_react14.useRef)(null);
1641
+ const longPressActivated = (0, import_react14.useRef)(false);
1552
1642
  const toggleTag = (label) => {
1553
1643
  setSelectedLabels((prev) => {
1554
1644
  const next = new Set(prev);
@@ -1995,7 +2085,7 @@ var PromptTab = ({
1995
2085
  };
1996
2086
 
1997
2087
  // src/components/ProjectSyncTab.tsx
1998
- var import_react13 = require("react");
2088
+ var import_react15 = require("react");
1999
2089
  init_hfStateService();
2000
2090
  var import_jsx_runtime12 = require("react/jsx-runtime");
2001
2091
  var ProjectSyncTab = ({
@@ -2016,14 +2106,14 @@ var ProjectSyncTab = ({
2016
2106
  onProjectExportBase64,
2017
2107
  onHfInitialSync
2018
2108
  }) => {
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());
2109
+ const projectInputRef = (0, import_react15.useRef)(null);
2110
+ const workspaceInputRef = (0, import_react15.useRef)(null);
2111
+ const [saveName, setSaveName] = (0, import_react15.useState)("");
2112
+ const [isSaving, setIsSaving] = (0, import_react15.useState)(false);
2113
+ const [isExporting, setIsExporting] = (0, import_react15.useState)(false);
2114
+ const [syncState, setSyncState] = (0, import_react15.useState)("idle");
2115
+ const [syncDiff, setSyncDiff] = (0, import_react15.useState)(null);
2116
+ const [selectedLocalIds, setSelectedLocalIds] = (0, import_react15.useState)(/* @__PURE__ */ new Set());
2027
2117
  const handleExport = async () => {
2028
2118
  if (!onProjectExport) return;
2029
2119
  setIsExporting(true);
@@ -2069,13 +2159,13 @@ var ProjectSyncTab = ({
2069
2159
  });
2070
2160
  };
2071
2161
  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);
2162
+ const [hfProjects, setHfProjects] = (0, import_react15.useState)([]);
2163
+ const [hfLoading, setHfLoading] = (0, import_react15.useState)(false);
2164
+ const [hfSaving, setHfSaving] = (0, import_react15.useState)(false);
2165
+ const [hfError, setHfError] = (0, import_react15.useState)(null);
2166
+ const [hfSaveName, setHfSaveName] = (0, import_react15.useState)("");
2167
+ const [hfSyncProgress, setHfSyncProgress] = (0, import_react15.useState)(null);
2168
+ const [hfSyncing, setHfSyncing] = (0, import_react15.useState)(false);
2079
2169
  const loadHfProjects = async (token) => {
2080
2170
  setHfLoading(true);
2081
2171
  setHfError(null);
@@ -2087,7 +2177,7 @@ var ProjectSyncTab = ({
2087
2177
  setHfLoading(false);
2088
2178
  }
2089
2179
  };
2090
- (0, import_react13.useEffect)(() => {
2180
+ (0, import_react15.useEffect)(() => {
2091
2181
  if (hfToken) loadHfProjects(hfToken);
2092
2182
  }, [hfToken]);
2093
2183
  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 +2591,7 @@ function toPromptImages(images) {
2501
2591
  init_hfStateService();
2502
2592
 
2503
2593
  // src/hooks/useHFState.ts
2504
- var import_react14 = require("react");
2594
+ var import_react16 = require("react");
2505
2595
  init_hfStateService();
2506
2596
 
2507
2597
  // src/lib/hfReducer.ts
@@ -2523,6 +2613,13 @@ function applyEvent(state, event) {
2523
2613
  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
2614
  return { ...state, tags: { by_category: { ...tags.by_category, [p.category]: newCat }, all: newAll } };
2525
2615
  }
2616
+ case "thumb_added": {
2617
+ const p = event.payload;
2618
+ return {
2619
+ ...state,
2620
+ metadata: state.metadata.map((m) => m.id === p.id ? { ...m, hasThumb: true } : m)
2621
+ };
2622
+ }
2526
2623
  case "metadata_updated": {
2527
2624
  const p = event.payload;
2528
2625
  return {
@@ -2530,6 +2627,13 @@ function applyEvent(state, event) {
2530
2627
  metadata: state.metadata.map((m) => m.id === p.id ? { ...m, ...p.delta } : m)
2531
2628
  };
2532
2629
  }
2630
+ case "title_set": {
2631
+ const p = event.payload;
2632
+ return {
2633
+ ...state,
2634
+ metadata: state.metadata.map((m) => m.id === p.id ? { ...m, titleTs: event.ts } : m)
2635
+ };
2636
+ }
2533
2637
  default:
2534
2638
  return state;
2535
2639
  }
@@ -2620,18 +2724,18 @@ function writeOfflineBuffer(events) {
2620
2724
  }
2621
2725
  }
2622
2726
  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) => {
2727
+ const [state, setState] = (0, import_react16.useState)(null);
2728
+ const [isLoading, setIsLoading] = (0, import_react16.useState)(false);
2729
+ const [error, setError] = (0, import_react16.useState)(null);
2730
+ const [eventCount, setEventCount] = (0, import_react16.useState)(0);
2731
+ const [localOnlyCount, setLocalOnlyCount] = (0, import_react16.useState)(0);
2732
+ const [forks, setForks] = (0, import_react16.useState)([]);
2733
+ const [pendingBufferCount, setPendingBufferCount] = (0, import_react16.useState)(readOfflineBuffer().length);
2734
+ const [lastEventTs, setLastEventTs] = (0, import_react16.useState)(0);
2735
+ const [hasStateZip, setHasStateZip] = (0, import_react16.useState)(false);
2736
+ const knownEventPaths = (0, import_react16.useRef)(/* @__PURE__ */ new Set());
2737
+ const allEventsRef = (0, import_react16.useRef)([]);
2738
+ const applyNewEvents = (0, import_react16.useCallback)((snapshot, newEvents) => {
2635
2739
  if (!newEvents.length && allEventsRef.current.length === 0) {
2636
2740
  setEventCount(0);
2637
2741
  return snapshot;
@@ -2645,7 +2749,7 @@ function useHFState(token, namespace) {
2645
2749
  if (afterConsolidation.length) setLastEventTs(Math.max(...afterConsolidation.map((e) => e.ts)));
2646
2750
  return applyEvents(snapshot, afterConsolidation);
2647
2751
  }, []);
2648
- const loadFull = (0, import_react14.useCallback)(async () => {
2752
+ const loadFull = (0, import_react16.useCallback)(async () => {
2649
2753
  if (!token || !namespace) return;
2650
2754
  setIsLoading(true);
2651
2755
  setError(null);
@@ -2692,7 +2796,7 @@ function useHFState(token, namespace) {
2692
2796
  setIsLoading(false);
2693
2797
  }
2694
2798
  }, [token, namespace, applyNewEvents]);
2695
- const pollNew = (0, import_react14.useCallback)(async () => {
2799
+ const pollNew = (0, import_react16.useCallback)(async () => {
2696
2800
  if (!token || !namespace || !state) return;
2697
2801
  try {
2698
2802
  const events = await loadPendingEvents(namespace, token, state.meta.consolidatedAt);
@@ -2706,15 +2810,15 @@ function useHFState(token, namespace) {
2706
2810
  } catch {
2707
2811
  }
2708
2812
  }, [token, namespace, state, applyNewEvents]);
2709
- (0, import_react14.useEffect)(() => {
2813
+ (0, import_react16.useEffect)(() => {
2710
2814
  if (token && namespace) loadFull();
2711
2815
  }, [token, namespace]);
2712
- (0, import_react14.useEffect)(() => {
2816
+ (0, import_react16.useEffect)(() => {
2713
2817
  if (!token || !namespace) return;
2714
2818
  const id = setInterval(pollNew, POLL_INTERVAL_MS);
2715
2819
  return () => clearInterval(id);
2716
2820
  }, [token, namespace, pollNew]);
2717
- const writeEvent = (0, import_react14.useCallback)(async (type, payload) => {
2821
+ const writeEvent = (0, import_react16.useCallback)(async (type, payload) => {
2718
2822
  const prevTs = lastEventTs ? [lastEventTs] : [state?.meta.consolidatedAt ?? 0];
2719
2823
  console.log("[HF] writeEvent called:", { type, namespace, tokenOk: !!token, prevTs });
2720
2824
  await pollNew();
@@ -2764,13 +2868,13 @@ function useHFState(token, namespace) {
2764
2868
  }
2765
2869
 
2766
2870
  // src/components/labs/LabsTab.tsx
2767
- var import_react21 = require("react");
2871
+ var import_react23 = require("react");
2768
2872
 
2769
2873
  // src/components/labs/LabRemix.tsx
2770
- var import_react16 = require("react");
2874
+ var import_react18 = require("react");
2771
2875
 
2772
2876
  // src/components/labs/LabImagePicker.tsx
2773
- var import_react15 = require("react");
2877
+ var import_react17 = require("react");
2774
2878
  var import_jsx_runtime13 = require("react/jsx-runtime");
2775
2879
  var LabImagePicker = ({
2776
2880
  availableItems,
@@ -2779,8 +2883,8 @@ var LabImagePicker = ({
2779
2883
  onClose,
2780
2884
  title = "Bild w\xE4hlen"
2781
2885
  }) => {
2782
- const [search, setSearch] = (0, import_react15.useState)("");
2783
- const [drillItem, setDrillItem] = (0, import_react15.useState)(null);
2886
+ const [search, setSearch] = (0, import_react17.useState)("");
2887
+ const [drillItem, setDrillItem] = (0, import_react17.useState)(null);
2784
2888
  const filtered = availableItems.filter(
2785
2889
  (item) => !search || item.prompt.toLowerCase().includes(search.toLowerCase())
2786
2890
  );
@@ -2882,13 +2986,13 @@ var LabImagePicker = ({
2882
2986
  // src/components/labs/LabRemix.tsx
2883
2987
  var import_jsx_runtime14 = require("react/jsx-runtime");
2884
2988
  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);
2989
+ const [showPicker, setShowPicker] = (0, import_react18.useState)(false);
2990
+ const [selected, setSelected] = (0, import_react18.useState)(null);
2991
+ const [instruction, setInstruction] = (0, import_react18.useState)("");
2992
+ const [generatedPrompt, setGeneratedPrompt] = (0, import_react18.useState)("");
2993
+ const [resultImage, setResultImage] = (0, import_react18.useState)(null);
2994
+ const [isGeneratingPrompt, setIsGeneratingPrompt] = (0, import_react18.useState)(false);
2995
+ const [isGeneratingImage, setIsGeneratingImage] = (0, import_react18.useState)(false);
2892
2996
  const handleSelectImage = (item, frame) => {
2893
2997
  services.onItemUsed(item);
2894
2998
  setSelected({
@@ -3071,16 +3175,16 @@ var LabRemix = ({ services, onResult }) => {
3071
3175
  };
3072
3176
 
3073
3177
  // src/components/labs/LabBlend.tsx
3074
- var import_react17 = require("react");
3178
+ var import_react19 = require("react");
3075
3179
  var import_jsx_runtime15 = require("react/jsx-runtime");
3076
3180
  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);
3181
+ const [showPickerFor, setShowPickerFor] = (0, import_react19.useState)(null);
3182
+ const [selectedImages, setSelectedImages] = (0, import_react19.useState)([]);
3183
+ const [instruction, setInstruction] = (0, import_react19.useState)("");
3184
+ const [generatedPrompt, setGeneratedPrompt] = (0, import_react19.useState)("");
3185
+ const [resultImage, setResultImage] = (0, import_react19.useState)(null);
3186
+ const [isGeneratingPrompt, setIsGeneratingPrompt] = (0, import_react19.useState)(false);
3187
+ const [isGeneratingImage, setIsGeneratingImage] = (0, import_react19.useState)(false);
3084
3188
  const handleSelectImage = (index, item, frame) => {
3085
3189
  services.onItemUsed(item);
3086
3190
  const newImg = {
@@ -3267,17 +3371,17 @@ var LabBlend = ({ services, onResult }) => {
3267
3371
  };
3268
3372
 
3269
3373
  // src/components/labs/LabCompare.tsx
3270
- var import_react18 = require("react");
3374
+ var import_react20 = require("react");
3271
3375
  var import_jsx_runtime16 = require("react/jsx-runtime");
3272
3376
  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);
3377
+ const [showPickerFor, setShowPickerFor] = (0, import_react20.useState)(null);
3378
+ const [selectedImages, setSelectedImages] = (0, import_react20.useState)([]);
3379
+ const [instruction, setInstruction] = (0, import_react20.useState)("");
3380
+ const [analysis, setAnalysis] = (0, import_react20.useState)("");
3381
+ const [generatedPrompt, setGeneratedPrompt] = (0, import_react20.useState)("");
3382
+ const [resultImage, setResultImage] = (0, import_react20.useState)(null);
3383
+ const [isAnalyzing, setIsAnalyzing] = (0, import_react20.useState)(false);
3384
+ const [isGeneratingImage, setIsGeneratingImage] = (0, import_react20.useState)(false);
3281
3385
  const handleSelectImage = (index, item, frame) => {
3282
3386
  services.onItemUsed(item);
3283
3387
  const newImg = {
@@ -3440,14 +3544,14 @@ var LabCompare = ({ services, onResult }) => {
3440
3544
  };
3441
3545
 
3442
3546
  // src/components/labs/LabLoop.tsx
3443
- var import_react19 = require("react");
3547
+ var import_react21 = require("react");
3444
3548
  var import_jsx_runtime17 = require("react/jsx-runtime");
3445
3549
  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);
3550
+ const [rounds, setRounds] = (0, import_react21.useState)([]);
3551
+ const [currentInstruction, setCurrentInstruction] = (0, import_react21.useState)("");
3552
+ const [showPickerForRound, setShowPickerForRound] = (0, import_react21.useState)(null);
3553
+ const [pendingImages, setPendingImages] = (0, import_react21.useState)([]);
3554
+ const [isGenerating, setIsGenerating] = (0, import_react21.useState)(false);
3451
3555
  const currentPrompt = rounds.length > 0 ? rounds[rounds.length - 1].prompt : "";
3452
3556
  const handleAddImage = (item, frame) => {
3453
3557
  services.onItemUsed(item);
@@ -3603,7 +3707,7 @@ var LabLoop = ({ services, onResult }) => {
3603
3707
  };
3604
3708
 
3605
3709
  // src/components/labs/LabFrameExtractor.tsx
3606
- var import_react20 = require("react");
3710
+ var import_react22 = require("react");
3607
3711
  var import_jsx_runtime18 = require("react/jsx-runtime");
3608
3712
  var formatTime = (s) => {
3609
3713
  const m = Math.floor(s / 60);
@@ -3617,15 +3721,15 @@ var LabFrameExtractor = ({
3617
3721
  onResult,
3618
3722
  resolveVideoUrl
3619
3723
  }) => {
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");
3724
+ const videoRef = (0, import_react22.useRef)(null);
3725
+ const canvasRef = (0, import_react22.useRef)(null);
3726
+ const cancelledRef = (0, import_react22.useRef)(false);
3727
+ const [selectedItem, setSelectedItem] = (0, import_react22.useState)(null);
3728
+ const [videoSrc, setVideoSrc] = (0, import_react22.useState)(null);
3729
+ const [videoReady, setVideoReady] = (0, import_react22.useState)(false);
3730
+ const [frames, setFrames] = (0, import_react22.useState)([]);
3731
+ const [isExtracting, setIsExtracting] = (0, import_react22.useState)(false);
3732
+ const [intervalSec, setIntervalSec] = (0, import_react22.useState)("1");
3629
3733
  const handleVideoSelect = (item) => {
3630
3734
  const mediaId = item.frames[0]?.mediaId;
3631
3735
  if (!mediaId) return;
@@ -3635,7 +3739,7 @@ var LabFrameExtractor = ({
3635
3739
  setVideoReady(false);
3636
3740
  cancelledRef.current = false;
3637
3741
  };
3638
- const captureAt = (0, import_react20.useCallback)(
3742
+ const captureAt = (0, import_react22.useCallback)(
3639
3743
  (t, label) => new Promise((resolve) => {
3640
3744
  const video = videoRef.current;
3641
3745
  const canvas = canvasRef.current;
@@ -3862,7 +3966,7 @@ var BASE_TABS = [
3862
3966
  ];
3863
3967
  var FRAMES_TAB = { key: "frames", label: "Frames", icon: "crop_original" };
3864
3968
  var LabsTab = ({ services, onResult, videoItems, resolveVideoUrl }) => {
3865
- const [activeTab, setActiveTab] = (0, import_react21.useState)("remix");
3969
+ const [activeTab, setActiveTab] = (0, import_react23.useState)("remix");
3866
3970
  const showFrames = !!(videoItems && resolveVideoUrl);
3867
3971
  const tabs = showFrames ? [...BASE_TABS, FRAMES_TAB] : BASE_TABS;
3868
3972
  return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "flex flex-col h-full overflow-hidden", children: [
@@ -3896,19 +4000,19 @@ var LabsTab = ({ services, onResult, videoItems, resolveVideoUrl }) => {
3896
4000
  };
3897
4001
 
3898
4002
  // src/components/TagManagerPanel.tsx
3899
- var import_react22 = require("react");
4003
+ var import_react24 = require("react");
3900
4004
  var import_jsx_runtime20 = require("react/jsx-runtime");
3901
4005
  function TagManagerPanel({ workspaceTags, onTagCreate, onTagUpdate, onTagDelete, onTagReorder, onTagMove }) {
3902
4006
  const categories = Object.keys(workspaceTags.by_category).filter(
3903
4007
  (cat) => (workspaceTags.by_category[cat] || []).some((t) => !t.is_deleted)
3904
4008
  );
3905
- const [selectedCategory, setSelectedCategory] = (0, import_react22.useState)(categories[0] || "");
4009
+ const [selectedCategory, setSelectedCategory] = (0, import_react24.useState)(categories[0] || "");
3906
4010
  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)("");
4011
+ const [editingLabel, setEditingLabel] = (0, import_react24.useState)(null);
4012
+ const [editState, setEditState] = (0, import_react24.useState)({ label: "", value: "" });
4013
+ const [newTag, setNewTag] = (0, import_react24.useState)({ label: "", value: "" });
4014
+ const [movingLabel, setMovingLabel] = (0, import_react24.useState)(null);
4015
+ const [moveTarget, setMoveTarget] = (0, import_react24.useState)("");
3912
4016
  const tags = (workspaceTags.by_category[effectiveCategory] || []).filter((t) => !t.is_deleted);
3913
4017
  const otherCategories = categories.filter((c) => c !== effectiveCategory);
3914
4018
  const startEdit = (tag) => {
@@ -4104,7 +4208,7 @@ function TagManagerPanel({ workspaceTags, onTagCreate, onTagUpdate, onTagDelete,
4104
4208
  }
4105
4209
 
4106
4210
  // src/components/HFTestTab.tsx
4107
- var import_react23 = require("react");
4211
+ var import_react25 = require("react");
4108
4212
  var import_jsx_runtime21 = require("react/jsx-runtime");
4109
4213
  var HF_BASE2 = "https://huggingface.co";
4110
4214
  var HF_REPO2 = "RolandSch/fa-app-state";
@@ -4297,7 +4401,7 @@ function tryFmt(s) {
4297
4401
  }
4298
4402
  }
4299
4403
  function CopyBtn({ text }) {
4300
- const [done, setDone] = (0, import_react23.useState)(false);
4404
+ const [done, setDone] = (0, import_react25.useState)(false);
4301
4405
  return /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)(
4302
4406
  "button",
4303
4407
  {
@@ -4468,10 +4572,10 @@ function EventMonitor({ events, confirmedEventKeys, galleryItems, imageUploadSta
4468
4572
  ] })
4469
4573
  ] });
4470
4574
  }
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)({});
4575
+ function HFTestTab({ token, namespace, galleryItems, allEvents = [], confirmedEventKeys = /* @__PURE__ */ new Set(), imageUploadStatus = /* @__PURE__ */ new Map(), missingImages = [] }) {
4576
+ const [selected, setSelected] = (0, import_react25.useState)(null);
4577
+ const [results, setResults] = (0, import_react25.useState)({});
4578
+ const [expanded, setExpanded] = (0, import_react25.useState)({});
4475
4579
  const withResults = galleryItems.filter((g) => g.base64 && g.status === "done");
4476
4580
  const setRunning = (id) => setResults((r) => ({ ...r, [id]: { status: "running", steps: [], totalMs: 0 } }));
4477
4581
  const setDone = (id, steps, t0) => {
@@ -4538,6 +4642,33 @@ function HFTestTab({ token, namespace, galleryItems, allEvents = [], confirmedEv
4538
4642
  )
4539
4643
  }
4540
4644
  ) }),
4645
+ missingImages.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("div", { style: { marginBottom: 12 }, children: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4646
+ CollapsibleCard,
4647
+ {
4648
+ title: `Fehlende Bilder auf HF (${missingImages.length})`,
4649
+ icon: /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 16 }, children: "broken_image" }),
4650
+ defaultOpen: false,
4651
+ children: /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { padding: "8px 10px 4px" }, children: [
4652
+ /* @__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." }),
4653
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("div", { style: { display: "flex", gap: 8, marginBottom: 8, fontSize: 11, color: "rgba(255,255,255,0.5)" }, children: [
4654
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { children: [
4655
+ "UUID (Flow-App): ",
4656
+ missingImages.filter((e) => !e.filename).length
4657
+ ] }),
4658
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { children: "\xB7" }),
4659
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsxs)("span", { children: [
4660
+ "Filename (Server-Upload): ",
4661
+ missingImages.filter((e) => !!e.filename).length
4662
+ ] })
4663
+ ] }),
4664
+ /* @__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: [
4665
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { style: { color: e.filename ? "#fb923c" : "#60a5fa", minWidth: 60 }, children: e.filename ? "filename" : "uuid" }),
4666
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { style: { flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: e.filename ?? e.id }),
4667
+ /* @__PURE__ */ (0, import_jsx_runtime21.jsx)("span", { style: { color: "rgba(255,255,255,0.3)" }, children: new Date(e.timestamp).toLocaleDateString("de") })
4668
+ ] }, e.id)) })
4669
+ ] })
4670
+ }
4671
+ ) }),
4541
4672
  /* @__PURE__ */ (0, import_jsx_runtime21.jsx)(
4542
4673
  CollapsibleCard,
4543
4674
  {
@@ -4642,77 +4773,44 @@ function HFTestTab({ token, namespace, galleryItems, allEvents = [], confirmedEv
4642
4773
  }
4643
4774
 
4644
4775
  // 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();
4776
+ var import_react26 = require("react");
4683
4777
  var import_jsx_runtime22 = require("react/jsx-runtime");
4684
4778
  function StarRating({ rating = 0 }) {
4685
4779
  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
4780
  }
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;
4781
+ async function serverGet(baseUrl, path) {
4782
+ const url = `${baseUrl.replace(/\/$/, "")}${path}`;
4783
+ const res = await fetch(url, { credentials: "include" });
4784
+ if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
4785
+ const json = await res.json();
4786
+ return json && typeof json === "object" && "data" in json ? json.data : json;
4787
+ }
4788
+ function ServerTab({ serverBaseUrl }) {
4789
+ const [step, setStep] = (0, import_react26.useState)("user");
4790
+ const [users, setUsers] = (0, import_react26.useState)([]);
4791
+ const [usersLoading, setUsersLoading] = (0, import_react26.useState)(false);
4792
+ const [usersError, setUsersError] = (0, import_react26.useState)(null);
4793
+ const [selectedUser, setSelectedUser] = (0, import_react26.useState)(null);
4794
+ const [contexts, setContexts] = (0, import_react26.useState)([]);
4795
+ const [contextsLoading, setContextsLoading] = (0, import_react26.useState)(false);
4796
+ const [selectedContext, setSelectedContext] = (0, import_react26.useState)(null);
4797
+ const [tags, setTags] = (0, import_react26.useState)([]);
4798
+ const [items, setItems] = (0, import_react26.useState)([]);
4799
+ const [libLoading, setLibLoading] = (0, import_react26.useState)(false);
4800
+ const [libError, setLibError] = (0, import_react26.useState)(null);
4801
+ const [activeTag, setActiveTag] = (0, import_react26.useState)(null);
4802
+ const [preview, setPreview] = (0, import_react26.useState)(null);
4803
+ (0, import_react26.useEffect)(() => {
4804
+ if (!serverBaseUrl) return;
4707
4805
  setUsersLoading(true);
4708
4806
  setUsersError(null);
4709
- faServerGet("/api/v2/users", env, token).then(setUsers).catch((e) => setUsersError(String(e))).finally(() => setUsersLoading(false));
4710
- }, [env, hasToken]);
4807
+ serverGet(serverBaseUrl, "/api/v2/users").then(setUsers).catch((e) => setUsersError(String(e))).finally(() => setUsersLoading(false));
4808
+ }, [serverBaseUrl]);
4711
4809
  const selectUser = async (user) => {
4712
4810
  setSelectedUser(user);
4713
4811
  setContextsLoading(true);
4714
4812
  try {
4715
- const data = await faServerGet(`/api2/contexts?user_id=${user.id}`, env, token);
4813
+ const data = await serverGet(serverBaseUrl, `/api2/contexts?user_id=${user.id}`);
4716
4814
  if (data.length === 1) {
4717
4815
  await loadLibrary(user, data[0]);
4718
4816
  } else {
@@ -4732,12 +4830,11 @@ function ServerTab({ hfToken }) {
4732
4830
  setLibError(null);
4733
4831
  try {
4734
4832
  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)
4833
+ serverGet(serverBaseUrl, `/api2/tags?user_id=${user.id}`),
4834
+ serverGet(serverBaseUrl, `/api/storage/${user.id}/library`)
4737
4835
  ]);
4738
4836
  setTags(Array.isArray(tagsRes) ? tagsRes : []);
4739
- const raw = Array.isArray(libRes) ? libRes : libRes.data ?? [];
4740
- setItems(raw);
4837
+ setItems(Array.isArray(libRes) ? libRes : []);
4741
4838
  } catch (e) {
4742
4839
  setLibError(String(e));
4743
4840
  } finally {
@@ -4756,13 +4853,7 @@ function ServerTab({ hfToken }) {
4756
4853
  setLibError(null);
4757
4854
  };
4758
4855
  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
- }
4856
+ if (!serverBaseUrl) return null;
4766
4857
  return /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex flex-col h-full min-h-0", children: [
4767
4858
  /* @__PURE__ */ (0, import_jsx_runtime22.jsxs)("div", { className: "flex items-center gap-2 px-3 py-2 border-b border-white/8", children: [
4768
4859
  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 +4861,7 @@ function ServerTab({ hfToken }) {
4770
4861
  step === "user" && "Server Browser",
4771
4862
  step === "context" && `${selectedUser?.username} \u2014 Kontext w\xE4hlen`,
4772
4863
  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
- )) })
4864
+ ] })
4783
4865
  ] }),
4784
4866
  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
4867
  usersLoading && /* @__PURE__ */ (0, import_jsx_runtime22.jsx)("p", { className: "text-white/30 text-[11px] text-center py-4", children: "Lade User\u2026" }),
@@ -4870,8 +4952,8 @@ function ServerTab({ hfToken }) {
4870
4952
 
4871
4953
  // src/components/AvatarArchitectApp.tsx
4872
4954
  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)(() => {
4955
+ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia, buildInfo, initialHfToken, hfNamespace, allowDevNamespace, serverBaseUrl, onFetchServerProjects, onServerSave, onServerLoad, onServerDelete }) {
4956
+ (0, import_react27.useEffect)(() => {
4875
4957
  const id = "flow-styles";
4876
4958
  if (!document.getElementById(id)) {
4877
4959
  const style = document.createElement("style");
@@ -4880,19 +4962,19 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4880
4962
  document.head.appendChild(style);
4881
4963
  }
4882
4964
  }, []);
4883
- const [showStart, setShowStart] = (0, import_react25.useState)(true);
4884
- const [layoutChoice, setLayoutChoice] = (0, import_react25.useState)(() => {
4965
+ const [showStart, setShowStart] = (0, import_react27.useState)(true);
4966
+ const [layoutChoice, setLayoutChoice] = (0, import_react27.useState)(() => {
4885
4967
  try {
4886
4968
  return localStorage.getItem("aa-layout") || null;
4887
4969
  } catch {
4888
4970
  return null;
4889
4971
  }
4890
4972
  });
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)(() => {
4973
+ const [projectLoaded, setProjectLoaded] = (0, import_react27.useState)(false);
4974
+ const [hfToken, setHfToken] = (0, import_react27.useState)(initialHfToken || "");
4975
+ const [hfTokenInput, setHfTokenInput] = (0, import_react27.useState)(initialHfToken || "");
4976
+ const [isLoadingFromHF, setIsLoadingFromHF] = (0, import_react27.useState)(false);
4977
+ const [hfNamespaceLocal, setHfNamespaceLocal] = (0, import_react27.useState)(() => {
4896
4978
  try {
4897
4979
  const stored = localStorage.getItem("aa-hf-namespace");
4898
4980
  if (stored !== null) return stored;
@@ -4901,8 +4983,8 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4901
4983
  return "";
4902
4984
  }
4903
4985
  });
4904
- const [hfNamespaceFromServer, setHfNamespaceFromServer] = (0, import_react25.useState)(null);
4905
- (0, import_react25.useEffect)(() => {
4986
+ const [hfNamespaceFromServer, setHfNamespaceFromServer] = (0, import_react27.useState)(null);
4987
+ (0, import_react27.useEffect)(() => {
4906
4988
  if (hfNamespace !== void 0) return;
4907
4989
  const backendUrl = typeof window !== "undefined" ? window.BACKEND_URL || window.location.origin : null;
4908
4990
  if (!backendUrl) return;
@@ -4924,9 +5006,10 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4924
5006
  refresh: refreshHF,
4925
5007
  hasStateZip
4926
5008
  } = 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);
5009
+ const [imageUploadStatus, setImageUploadStatus] = (0, import_react27.useState)(/* @__PURE__ */ new Map());
5010
+ const [bootstrapLog, setBootstrapLog] = (0, import_react27.useState)([]);
5011
+ const [isBootstrapping, setIsBootstrapping] = (0, import_react27.useState)(false);
5012
+ const [hfMissingImages, setHfMissingImages] = (0, import_react27.useState)([]);
4930
5013
  const syncTopSlot = /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(import_jsx_runtime23.Fragment, { children: [
4931
5014
  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
5015
  "\u26A0 ",
@@ -4976,7 +5059,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4976
5059
  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
5060
  ] })
4978
5061
  ] });
4979
- const wsInputRef = (0, import_react25.useRef)(null);
5062
+ const wsInputRef = (0, import_react27.useRef)(null);
4980
5063
  const startApp = (choice) => {
4981
5064
  try {
4982
5065
  localStorage.setItem("aa-layout", choice);
@@ -4985,16 +5068,16 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4985
5068
  setLayoutChoice(choice);
4986
5069
  setShowStart(false);
4987
5070
  };
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)(() => {
5071
+ const [nodes, setNodes] = (0, import_react27.useState)([{ id: "1", type: "custom", position: { x: 0, y: 0 }, data: { label: "Fine Art Project", placeholder: "Name..." } }]);
5072
+ const [edges, setEdges] = (0, import_react27.useState)([]);
5073
+ const [history, setHistory] = (0, import_react27.useState)([]);
5074
+ const [galleryItems, setGalleryItems] = (0, import_react27.useState)([]);
5075
+ const galleryItemsRef = (0, import_react27.useRef)([]);
5076
+ (0, import_react27.useEffect)(() => {
4994
5077
  galleryItemsRef.current = galleryItems;
4995
5078
  }, [galleryItems]);
4996
- const hfImageNotFoundRef = (0, import_react25.useRef)(/* @__PURE__ */ new Map());
4997
- (0, import_react25.useEffect)(() => {
5079
+ const hfImageNotFoundRef = (0, import_react27.useRef)(/* @__PURE__ */ new Map());
5080
+ (0, import_react27.useEffect)(() => {
4998
5081
  if (!hfState) return;
4999
5082
  if (hfState.tags?.by_category) setWorkspaceTags(hfState.tags);
5000
5083
  const hfIds = new Set(hfState.metadata.map((m) => m.id));
@@ -5006,7 +5089,9 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5006
5089
  model: m.model,
5007
5090
  tags: m.tags || [],
5008
5091
  timestamp: m.timestamp,
5009
- status: "done"
5092
+ status: "done",
5093
+ filename: m.filename,
5094
+ hasThumb: m.hasThumb
5010
5095
  }));
5011
5096
  setGalleryItems((prev) => {
5012
5097
  const localOnly = prev.filter((g) => !hfIds.has(g.id));
@@ -5018,80 +5103,86 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5018
5103
  const merged = skeletons.map((s) => prev.find((g) => g.id === s.id) ?? s);
5019
5104
  return [...localOnly, ...merged].sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
5020
5105
  });
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) {
5106
+ const sortedEntries = [...hfState.metadata].sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0));
5107
+ (async () => {
5108
+ for (const entry of sortedEntries) {
5109
+ if (galleryItemsRef.current.find((g) => g.id === entry.id)?.base64) continue;
5110
+ if (hfImageNotFoundRef.current.has(entry.id)) continue;
5111
+ try {
5112
+ const b64 = await hfLoadImageAsBase64(entry.id, hfToken, effectiveNamespace, entry.filename, void 0, entry.mimeType, entry.hasThumb);
5113
+ if (!b64) {
5114
+ hfImageNotFoundRef.current.set(entry.id, Date.now());
5115
+ setHfMissingImages((prev) => {
5116
+ if (prev.find((e) => e.id === entry.id)) return prev;
5117
+ return [...prev, { id: entry.id, filename: entry.filename, mimeType: entry.mimeType, timestamp: entry.timestamp }];
5118
+ });
5119
+ continue;
5120
+ }
5121
+ const prefix = `data:${entry.mimeType || "image/jpeg"};base64,`;
5122
+ setGalleryItems((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
5123
+ setHistory((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
5124
+ } catch {
5028
5125
  hfImageNotFoundRef.current.set(entry.id, Date.now());
5029
- return;
5030
5126
  }
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
- }
5127
+ }
5128
+ })();
5038
5129
  }, [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)(() => {
5130
+ const [activePrompt, setActivePrompt] = (0, import_react27.useState)("");
5131
+ const [isSynthesizing, setIsSynthesizing] = (0, import_react27.useState)(false);
5132
+ const [activeGenerationsCount, setActiveGenerationsCount] = (0, import_react27.useState)(0);
5133
+ const [currentResult, setCurrentResult] = (0, import_react27.useState)(null);
5134
+ const [focusedNodeId, setFocusedNodeId] = (0, import_react27.useState)(null);
5135
+ const [leftTab, setLeftTab] = (0, import_react27.useState)("prompt");
5136
+ const [promptFeedback, setPromptFeedback] = (0, import_react27.useState)(null);
5137
+ const [lastPromptPayload, setLastPromptPayload] = (0, import_react27.useState)(null);
5138
+ const [isPromptTabGenerating, setIsPromptTabGenerating] = (0, import_react27.useState)(false);
5139
+ const [activeTab, setActiveTab] = (0, import_react27.useState)("history");
5140
+ const [mobileTab, setMobileTab] = (0, import_react27.useState)("stage");
5141
+ const [middlePanel, setMiddlePanel] = (0, import_react27.useState)("stage");
5142
+ const [recentLabItems, setRecentLabItems] = (0, import_react27.useState)([]);
5143
+ const [aspectRatio, setAspectRatio] = (0, import_react27.useState)("1:1");
5144
+ const [selectedModel, setSelectedModel] = (0, import_react27.useState)("\u{1F34C} Nano Banana Pro");
5145
+ const [seed, setSeed] = (0, import_react27.useState)(Math.floor(Math.random() * 1e6));
5146
+ const [seedMode, setSeedMode] = (0, import_react27.useState)("random");
5147
+ const [isLeftCollapsed, setIsLeftCollapsed] = (0, import_react27.useState)(false);
5148
+ const [isRightCollapsed, setIsRightCollapsed] = (0, import_react27.useState)(false);
5149
+ const [leftPanelWidth, setLeftPanelWidth] = (0, import_react27.useState)(() => {
5059
5150
  try {
5060
5151
  return parseInt(localStorage.getItem("aa-left-width") || "260", 10);
5061
5152
  } catch {
5062
5153
  return 260;
5063
5154
  }
5064
5155
  });
5065
- const [rightPanelWidth, setRightPanelWidth] = (0, import_react25.useState)(() => {
5156
+ const [rightPanelWidth, setRightPanelWidth] = (0, import_react27.useState)(() => {
5066
5157
  try {
5067
5158
  return parseInt(localStorage.getItem("aa-right-width") || "320", 10);
5068
5159
  } catch {
5069
5160
  return 320;
5070
5161
  }
5071
5162
  });
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)(() => {
5163
+ const [isPromptCollapsed, setIsPromptCollapsed] = (0, import_react27.useState)(false);
5164
+ const [projectActionState, setProjectActionState] = (0, import_react27.useState)("idle");
5165
+ const syncServerDataRef = (0, import_react27.useRef)(null);
5166
+ const [workspaceTags, setWorkspaceTags] = (0, import_react27.useState)(null);
5167
+ const [serverProjects, setServerProjects] = (0, import_react27.useState)([]);
5168
+ const [isLoadingFromServer, setIsLoadingFromServer] = (0, import_react27.useState)(false);
5169
+ const [highContrast, setHighContrast] = (0, import_react27.useState)(() => {
5079
5170
  try {
5080
5171
  return localStorage.getItem("aa-contrast") === "high";
5081
5172
  } catch {
5082
5173
  return false;
5083
5174
  }
5084
5175
  });
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);
5176
+ const [activeReferenceId, setActiveReferenceId] = (0, import_react27.useState)(null);
5177
+ const [activeReferenceThumbnail, setActiveReferenceThumbnail] = (0, import_react27.useState)(null);
5178
+ const [isScanningImage, setIsScanningImage] = (0, import_react27.useState)(false);
5179
+ const [touchStartX, setTouchStartX] = (0, import_react27.useState)(null);
5180
+ const [isFullscreen, setIsFullscreen] = (0, import_react27.useState)(false);
5181
+ const [zoomScale, setZoomScale] = (0, import_react27.useState)(1);
5182
+ const [zoomOffset, setZoomOffset] = (0, import_react27.useState)({ x: 0, y: 0 });
5183
+ const lastPinchDist = (0, import_react27.useRef)(null);
5184
+ const lastTapTime = (0, import_react27.useRef)(0);
5185
+ const dragStart = (0, import_react27.useRef)(null);
5095
5186
  const openFullscreen = () => {
5096
5187
  setIsFullscreen(true);
5097
5188
  setZoomScale(1);
@@ -5154,7 +5245,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5154
5245
  setActiveReferenceId(null);
5155
5246
  setActiveReferenceThumbnail(null);
5156
5247
  };
5157
- const labServices = (0, import_react25.useMemo)(() => {
5248
+ const labServices = (0, import_react27.useMemo)(() => {
5158
5249
  const available = groupGenerationsToLabItems([...galleryItems, ...history]);
5159
5250
  return {
5160
5251
  availableItems: available,
@@ -5234,17 +5325,45 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5234
5325
  setIsScanningImage(false);
5235
5326
  }
5236
5327
  };
5237
- const currentIndex = (0, import_react25.useMemo)(() => history.findIndex((h) => h.id === currentResult?.id), [history, currentResult]);
5238
- const goToPrev = (0, import_react25.useCallback)(() => {
5328
+ const currentIndex = (0, import_react27.useMemo)(() => history.findIndex((h) => h.id === currentResult?.id), [history, currentResult]);
5329
+ const goToPrev = (0, import_react27.useCallback)(() => {
5239
5330
  if (currentIndex > 0) setCurrentResult(history[currentIndex - 1]);
5240
5331
  }, [currentIndex, history]);
5241
- const goToNext = (0, import_react25.useCallback)(() => {
5332
+ const goToNext = (0, import_react27.useCallback)(() => {
5242
5333
  if (currentIndex < history.length - 1) setCurrentResult(history[currentIndex + 1]);
5243
5334
  }, [currentIndex, history]);
5335
+ const handleGallerySelect = (0, import_react27.useCallback)((g) => {
5336
+ setCurrentResult(g);
5337
+ setMobileTab("stage");
5338
+ if (g.filename && hfToken && !g.fullBase64) {
5339
+ hfLoadImageAsBase64(g.id, hfToken, effectiveNamespace, g.filename, void 0, g.mimeType, false).then((b64) => {
5340
+ if (!b64) return;
5341
+ const full = `data:${g.mimeType || "image/jpeg"};base64,${b64}`;
5342
+ setCurrentResult((prev) => prev?.id === g.id ? { ...prev, fullBase64: full } : prev);
5343
+ setGalleryItems((prev) => prev.map((item) => item.id === g.id ? { ...item, fullBase64: full } : item));
5344
+ }).catch(() => {
5345
+ });
5346
+ }
5347
+ }, [hfToken, effectiveNamespace]);
5348
+ const handleTitleSet = (0, import_react27.useCallback)((id) => {
5349
+ const ts = Date.now();
5350
+ setGalleryItems((prev) => prev.map((g) => g.id === id ? { ...g, titleTs: ts } : g));
5351
+ setHistory((prev) => prev.map((g) => g.id === id ? { ...g, titleTs: ts } : g));
5352
+ if (hfToken && effectiveNamespace) {
5353
+ hfWriteEvent("title_set", { id }).catch(() => {
5354
+ });
5355
+ }
5356
+ }, [hfToken, effectiveNamespace, hfWriteEvent]);
5357
+ const currentGroup = (0, import_react27.useMemo)(() => {
5358
+ if (!currentResult?.prompt) return [];
5359
+ const groups = groupByPrompt(galleryItems.filter((g) => g.status === "done" && !!g.base64));
5360
+ const group = groups.find((gr) => gr.prompt === currentResult.prompt);
5361
+ return group ? group.items : [];
5362
+ }, [galleryItems, currentResult?.prompt]);
5244
5363
  const hcStyle = highContrast ? { filter: "brightness(1.6) contrast(1.05)" } : void 0;
5245
5364
  const isGenerating = activeGenerationsCount > 0;
5246
5365
  useKeyboardNavigation(history, currentResult, setCurrentResult);
5247
- const getSubtreeFormat = (0, import_react25.useCallback)((nodeId, depth = 0) => {
5366
+ const getSubtreeFormat = (0, import_react27.useCallback)((nodeId, depth = 0) => {
5248
5367
  const node = nodes.find((n) => n.id === nodeId);
5249
5368
  if (!node) return "";
5250
5369
  const childrenIds = edges.filter((e) => e.source === nodeId).map((e) => e.target);
@@ -5252,7 +5371,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5252
5371
  return `${indent}- ${node.data.label || "(unbenannt)"}
5253
5372
  ` + childrenIds.map((id) => getSubtreeFormat(id, depth + 1)).join("");
5254
5373
  }, [nodes, edges]);
5255
- const activePath = (0, import_react25.useMemo)(() => {
5374
+ const activePath = (0, import_react27.useMemo)(() => {
5256
5375
  if (!focusedNodeId) return /* @__PURE__ */ new Set();
5257
5376
  const path = /* @__PURE__ */ new Set([focusedNodeId]);
5258
5377
  let currId = focusedNodeId;
@@ -5422,8 +5541,9 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5422
5541
  const handleDownloadSingle = async () => {
5423
5542
  if (!currentResult?.base64) return;
5424
5543
  try {
5425
- const base64Data = currentResult.base64.split(",")[1];
5426
- const mimeType = currentResult.base64.split(";")[0].split(":")[1] || "image/png";
5544
+ const downloadSrc = currentResult.fullBase64 || currentResult.base64;
5545
+ const base64Data = downloadSrc.split(",")[1];
5546
+ const mimeType = downloadSrc.split(";")[0].split(":")[1] || "image/png";
5427
5547
  let finalBase64 = base64Data;
5428
5548
  if (mimeType === "image/png") finalBase64 = injectXMPMetadata(base64Data, currentResult.prompt || "", currentResult.seed, currentResult.model, currentResult.id, currentResult.tags || [], getSubtreeFormat(currentResult.nodeId));
5429
5549
  await onDownload(finalBase64, mimeType, `avatar_${currentResult.id.slice(0, 5)}.${mimeType.split("/")[1]}`);
@@ -5600,7 +5720,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5600
5720
  setTimeout(() => setProjectActionState("idle"), 4e3);
5601
5721
  }
5602
5722
  };
5603
- (0, import_react25.useEffect)(() => {
5723
+ (0, import_react27.useEffect)(() => {
5604
5724
  if (activeTab === "setup" || activeTab === "sync") fetchServerProjects();
5605
5725
  }, [activeTab]);
5606
5726
  const mergeWorkspaceTags = (local, remote) => {
@@ -5623,7 +5743,8 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5623
5743
  return { by_category: merged, all };
5624
5744
  };
5625
5745
  if (isFullscreen && currentResult?.base64) {
5626
- const fsBase64 = currentResult.base64.startsWith("data:") ? currentResult.base64 : `data:image/png;base64,${currentResult.base64}`;
5746
+ const fsRaw = currentResult.fullBase64 || currentResult.base64;
5747
+ const fsBase64 = fsRaw.startsWith("data:") ? fsRaw : `data:image/png;base64,${fsRaw}`;
5627
5748
  return /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)(
5628
5749
  "div",
5629
5750
  {
@@ -5870,7 +5991,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5870
5991
  setMobileTab("stage");
5871
5992
  }
5872
5993
  } }) }),
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 }) }),
5994
+ 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
5995
  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
5996
  TagManagerPanel,
5876
5997
  {
@@ -5948,7 +6069,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5948
6069
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-white/50 text-[13px]", children: currentResult.error?.message }),
5949
6070
  /* @__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
6071
  ] }),
5951
- currentResult?.status === "done" && currentResult.base64 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("img", { src: currentResult.base64, className: "w-full h-full object-contain" }),
6072
+ 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
6073
  !currentResult && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col items-center gap-2 opacity-10", children: [
5953
6074
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[64px]", children: "palette" }),
5954
6075
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-[11px] font-bold uppercase tracking-[0.2em]", children: "Avatar Architect" })
@@ -5966,6 +6087,49 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5966
6087
  ]
5967
6088
  }
5968
6089
  ),
6090
+ 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: [
6091
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6092
+ "div",
6093
+ {
6094
+ onClick: () => handleGallerySelect(item),
6095
+ style: {
6096
+ width: 44,
6097
+ height: 44,
6098
+ borderRadius: 8,
6099
+ overflow: "hidden",
6100
+ cursor: "pointer",
6101
+ border: item.id === currentResult.id ? "2px solid rgba(255,255,255,0.8)" : "2px solid rgba(255,255,255,0.15)",
6102
+ opacity: item.id === currentResult.id ? 1 : 0.55
6103
+ },
6104
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("img", { src: item.base64, style: { width: "100%", height: "100%", objectFit: "cover" }, alt: "" })
6105
+ }
6106
+ ),
6107
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6108
+ "button",
6109
+ {
6110
+ onClick: (e) => {
6111
+ e.stopPropagation();
6112
+ handleTitleSet(item.id);
6113
+ },
6114
+ style: {
6115
+ position: "absolute",
6116
+ bottom: 2,
6117
+ right: 2,
6118
+ width: 14,
6119
+ height: 14,
6120
+ background: item.titleTs ? "#f59e0b" : "rgba(0,0,0,0.75)",
6121
+ border: "1px solid rgba(255,255,255,0.25)",
6122
+ borderRadius: 3,
6123
+ cursor: "pointer",
6124
+ padding: 0,
6125
+ display: "flex",
6126
+ alignItems: "center",
6127
+ justifyContent: "center"
6128
+ },
6129
+ 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" })
6130
+ }
6131
+ )
6132
+ ] }, item.id)) }),
5969
6133
  currentResult?.status === "done" && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex gap-2 mt-3", children: [
5970
6134
  /* @__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
6135
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[18px] text-white/60", children: "replay" }),
@@ -5988,10 +6152,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
5988
6152
  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
6153
  ] }),
5990
6154
  /* @__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)) }),
6155
+ 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
6156
  activeTab === "gallery" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
5996
6157
  MediaLibrary,
5997
6158
  {
@@ -6002,10 +6163,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6002
6163
  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
6164
  },
6004
6165
  onDelete: (id) => setGalleryItems((g) => g.filter((x) => x.id !== id)),
6005
- onSelect: (g) => {
6006
- setCurrentResult(g);
6007
- setMobileTab("stage");
6008
- },
6166
+ onSelect: handleGallerySelect,
6009
6167
  onGenerateReference: (item) => {
6010
6168
  handleGenerateImage(item.prompt || activePrompt, item.mediaId, void 0, { silent: true });
6011
6169
  setMobileTab("stage");
@@ -6045,7 +6203,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6045
6203
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[20px]", children: "biotech" }),
6046
6204
  "HF"
6047
6205
  ] }),
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" }) }),
6206
+ 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
6207
  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
6208
  ] }),
6051
6209
  /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex-1 overflow-hidden relative", children: [
@@ -6128,10 +6286,11 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6128
6286
  galleryItems,
6129
6287
  allEvents: hfAllEvents,
6130
6288
  confirmedEventKeys: hfConfirmedKeys,
6131
- imageUploadStatus
6289
+ imageUploadStatus,
6290
+ missingImages: hfMissingImages
6132
6291
  }
6133
6292
  ) }),
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 }) })
6293
+ activeTab === "server" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "absolute inset-0", children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ServerTab, { serverBaseUrl }) })
6135
6294
  ] })
6136
6295
  ] }),
6137
6296
  /* @__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 +6299,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6140
6299
  { id: "labs", icon: "science", label: "Labs" },
6141
6300
  ...workspaceTags ? [{ id: "tags", icon: "label", label: "Tags" }] : [],
6142
6301
  { id: "browse", icon: "photo_library", label: "Galerie" },
6143
- { id: "server", icon: "storage", label: "Server" }
6302
+ ...serverBaseUrl ? [{ id: "server", icon: "storage", label: "Server" }] : []
6144
6303
  ].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
6304
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[24px]", children: tab.icon }),
6146
6305
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-[10px] font-bold uppercase tracking-wide", children: tab.label })
@@ -6191,7 +6350,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6191
6350
  ] })
6192
6351
  }
6193
6352
  ) }),
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)) }) })
6353
+ /* @__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
6354
  ] }),
6196
6355
  /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { flex: 1, height: tlH, display: "flex", flexDirection: "column", background: "#0b0b0b" }, children: [
6197
6356
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
@@ -6216,7 +6375,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6216
6375
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { style: { fontSize: 11, color: "rgba(255,255,255,0.5)", margin: 0 }, children: currentResult.error?.message }),
6217
6376
  /* @__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
6377
  ] }),
6219
- currentResult?.status === "done" && currentResult.base64 && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("img", { src: currentResult.base64, style: { maxWidth: "100%", maxHeight: "100%", objectFit: "contain" } }),
6378
+ 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
6379
  !currentResult && /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", gap: 8, opacity: 0.1 }, children: [
6221
6380
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined", style: { fontSize: 64 }, children: "palette" }),
6222
6381
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { style: { fontSize: 11, fontWeight: "bold", textTransform: "uppercase", letterSpacing: "0.2em" }, children: "Avatar Architect" })
@@ -6358,33 +6517,81 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6358
6517
  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
6518
  const frame = item.frames[0];
6360
6519
  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 })
6520
+ } }) }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex-1 overflow-hidden flex flex-col", children: [
6521
+ /* @__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: [
6522
+ 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: [
6523
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "w-3 h-3 border-t-2 border-white rounded-full animate-spin" }),
6524
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-[10px] text-white/60 uppercase font-bold tracking-widest", children: "Neue Referenz..." })
6374
6525
  ] }),
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" })
6526
+ currentResult ? currentResult.status === "processing" ? /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
6527
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("div", { className: "w-10 h-10 border-t-2 border-white rounded-full animate-spin" }),
6528
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-[10px] text-white/40 uppercase font-bold tracking-widest", children: "Erstelle Bild..." })
6529
+ ] }) : 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: [
6530
+ /* @__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" }) }),
6531
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col gap-2", children: [
6532
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("h3", { className: "text-[11px] font-bold uppercase tracking-widest text-red-400", children: "Generierungsfehler" }),
6533
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-white/60 text-[12px] leading-relaxed", children: currentResult.error?.message })
6534
+ ] }),
6535
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PillButton, { variant: "outline", icon: "refresh", onClick: () => handleGenerateImage(currentResult.prompt), children: "Erneut versuchen" })
6536
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "h-full w-full relative flex items-center justify-center", children: [
6537
+ /* @__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" }),
6538
+ /* @__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: [
6539
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PillButton, { variant: "outline", icon: "replay", onClick: () => setActivePrompt(currentResult.prompt || ""), children: "Prompt" }),
6540
+ /* @__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" }),
6541
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(PillButton, { variant: "outline", icon: "download", onClick: handleDownloadSingle, children: "Speichern" })
6542
+ ] })
6543
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime23.jsxs)("div", { className: "flex flex-col items-center gap-2 opacity-10", children: [
6544
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[100px]", children: "palette" }),
6545
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "text-[12px] font-bold uppercase tracking-[0.2em]", children: "Avatar Architect" })
6382
6546
  ] })
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
- ] }) })
6547
+ ] }) }),
6548
+ 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: [
6549
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6550
+ "div",
6551
+ {
6552
+ onClick: () => handleGallerySelect(item),
6553
+ style: {
6554
+ width: 52,
6555
+ height: 52,
6556
+ borderRadius: 10,
6557
+ overflow: "hidden",
6558
+ cursor: "pointer",
6559
+ border: item.id === currentResult.id ? "2px solid rgba(255,255,255,0.8)" : "2px solid rgba(255,255,255,0.12)",
6560
+ opacity: item.id === currentResult.id ? 1 : 0.5,
6561
+ transition: "opacity 0.15s, border-color 0.15s"
6562
+ },
6563
+ className: "hover:opacity-80",
6564
+ children: /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("img", { src: item.base64, style: { width: "100%", height: "100%", objectFit: "cover" }, alt: "" })
6565
+ }
6566
+ ),
6567
+ /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6568
+ "button",
6569
+ {
6570
+ onClick: (e) => {
6571
+ e.stopPropagation();
6572
+ handleTitleSet(item.id);
6573
+ },
6574
+ title: "Als Titel markieren",
6575
+ style: {
6576
+ position: "absolute",
6577
+ bottom: 3,
6578
+ right: 3,
6579
+ width: 16,
6580
+ height: 16,
6581
+ background: item.titleTs ? "#f59e0b" : "rgba(0,0,0,0.75)",
6582
+ border: "1px solid rgba(255,255,255,0.25)",
6583
+ borderRadius: 4,
6584
+ cursor: "pointer",
6585
+ padding: 0,
6586
+ display: "flex",
6587
+ alignItems: "center",
6588
+ justifyContent: "center"
6589
+ },
6590
+ 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" })
6591
+ }
6592
+ )
6593
+ ] }, item.id)) })
6594
+ ] })
6388
6595
  ] })
6389
6596
  ] }),
6390
6597
  !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 +6602,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6395
6602
  setActiveTab(tab);
6396
6603
  setIsRightCollapsed(false);
6397
6604
  }, 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: () => {
6605
+ serverBaseUrl && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("button", { onClick: () => {
6399
6606
  setActiveTab("server");
6400
6607
  setIsRightCollapsed(false);
6401
6608
  }, 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 +6626,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6419
6626
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("span", { className: "material-symbols-outlined text-[40px] text-white/10 block mb-3", children: "label_off" }),
6420
6627
  /* @__PURE__ */ (0, import_jsx_runtime23.jsx)("p", { className: "text-[11px] text-white/20", children: "Erst Workspace importieren um Tags zu verwalten." })
6421
6628
  ] }) }),
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)) }),
6629
+ 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
6630
  activeTab === "gallery" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(
6424
6631
  MediaLibrary,
6425
6632
  {
@@ -6430,7 +6637,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6430
6637
  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
6638
  },
6432
6639
  onDelete: (id) => setGalleryItems((g) => g.filter((x) => x.id !== id)),
6433
- onSelect: setCurrentResult,
6640
+ onSelect: handleGallerySelect,
6434
6641
  onGenerateReference: (item) => handleGenerateImage(item.prompt || activePrompt, item.mediaId, void 0, { silent: true })
6435
6642
  }
6436
6643
  ),
@@ -6460,14 +6667,14 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
6460
6667
  onHfInitialSync: hfToken ? handleHfInitialSync : void 0
6461
6668
  }
6462
6669
  ),
6463
- activeTab === "server" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ServerTab, { hfToken: hfToken || void 0 })
6670
+ activeTab === "server" && /* @__PURE__ */ (0, import_jsx_runtime23.jsx)(ServerTab, { serverBaseUrl })
6464
6671
  ] })
6465
6672
  ] })
6466
6673
  ] });
6467
6674
  }
6468
6675
 
6469
6676
  // src/components/FaApp.tsx
6470
- var import_react26 = require("react");
6677
+ var import_react28 = require("react");
6471
6678
  var import_jsx_runtime24 = require("react/jsx-runtime");
6472
6679
  function FaApp({
6473
6680
  onGenerateImage,
@@ -6487,8 +6694,8 @@ function FaApp({
6487
6694
  onServerDelete,
6488
6695
  buildInfo
6489
6696
  }) {
6490
- const [hfNamespace, setHfNamespace] = (0, import_react26.useState)(void 0);
6491
- (0, import_react26.useEffect)(() => {
6697
+ const [hfNamespace, setHfNamespace] = (0, import_react28.useState)(void 0);
6698
+ (0, import_react28.useEffect)(() => {
6492
6699
  if (!serverBaseUrl) return;
6493
6700
  fetch(`${serverBaseUrl}/api/status`).then((r) => r.json()).then((d) => {
6494
6701
  if (typeof d.hfNamespace === "string") setHfNamespace(d.hfNamespace);
@@ -6509,6 +6716,7 @@ function FaApp({
6509
6716
  initialHfToken: libToken ? libToken.startsWith("hf_") ? libToken : `hf_${libToken}` : void 0,
6510
6717
  hfNamespace,
6511
6718
  allowDevNamespace: !hfNamespace && allowDevNamespace,
6719
+ serverBaseUrl,
6512
6720
  onFetchServerProjects,
6513
6721
  onServerSave,
6514
6722
  onServerLoad,
@@ -6521,7 +6729,7 @@ function FaApp({
6521
6729
  // src/index.ts
6522
6730
  init_hfStateService();
6523
6731
  init_hfStateService();
6524
- var LIB_VERSION = "2.0.30";
6732
+ var LIB_VERSION = "2.0.46";
6525
6733
  // Annotate the CommonJS export names for ESM import in node:
6526
6734
  0 && (module.exports = {
6527
6735
  AvatarArchitectApp,
@@ -6566,10 +6774,6 @@ var LIB_VERSION = "2.0.30";
6566
6774
  cleanAiResponse,
6567
6775
  createFlowServices,
6568
6776
  exportProjectToZip,
6569
- faServerDelete,
6570
- faServerGet,
6571
- faServerPost,
6572
- faServerPut,
6573
6777
  findForks,
6574
6778
  findTips,
6575
6779
  formatTreeToMarkdown,