@rslsp1/fa-app-tools 1.3.18 → 2.0.12

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
@@ -259,13 +259,28 @@ var init_project = __esm({
259
259
  }
260
260
  });
261
261
 
262
+ // src/lib/hfEventTypes.ts
263
+ var CURRENT_EVENT_VERSION;
264
+ var init_hfEventTypes = __esm({
265
+ "src/lib/hfEventTypes.ts"() {
266
+ "use strict";
267
+ CURRENT_EVENT_VERSION = { major: 1, minor: 0 };
268
+ }
269
+ });
270
+
262
271
  // src/lib/hfStateService.ts
263
272
  var hfStateService_exports = {};
264
273
  __export(hfStateService_exports, {
265
274
  HF_TOKEN_KEY: () => HF_TOKEN_KEY,
266
275
  getHFToken: () => getHFToken,
276
+ getSessionClientId: () => getSessionClientId,
277
+ hfBatchArchive: () => hfBatchArchive,
278
+ hfBootstrapFromLegacy: () => hfBootstrapFromLegacy,
267
279
  hfDeleteProject: () => hfDeleteProject,
280
+ hfDownloadBinaryByPath: () => hfDownloadBinaryByPath,
281
+ hfDownloadJsonByPath: () => hfDownloadJsonByPath,
268
282
  hfDownloadProject: () => hfDownloadProject,
283
+ hfListDir: () => hfListDir,
269
284
  hfListProjects: () => hfListProjects,
270
285
  hfLoadImageAsBase64: () => hfLoadImageAsBase64,
271
286
  hfLoadMetadata: () => hfLoadMetadata,
@@ -275,7 +290,12 @@ __export(hfStateService_exports, {
275
290
  hfUploadImage: () => hfUploadImage,
276
291
  hfUploadProject: () => hfUploadProject,
277
292
  hfUploadProjectForm: () => hfUploadProjectForm,
278
- setHFToken: () => setHFToken
293
+ hfUploadSmallFile: () => hfUploadSmallFile,
294
+ loadHFState: () => loadHFState,
295
+ loadPendingEvents: () => loadPendingEvents,
296
+ setHFToken: () => setHFToken,
297
+ tsFromEventPath: () => tsFromEventPath,
298
+ writeHFEvent: () => writeHFEvent
279
299
  });
280
300
  function getHFToken() {
281
301
  try {
@@ -290,6 +310,177 @@ function setHFToken(token) {
290
310
  } catch {
291
311
  }
292
312
  }
313
+ function treeUrl(namespace, subdir = "") {
314
+ const parts = [namespace.replace(/\/$/, ""), subdir].filter(Boolean).join("/");
315
+ return `${HF_BASE}/api/datasets/${HF_REPO}/tree/main${parts ? "/" + parts : ""}`;
316
+ }
317
+ async function hfListDir(namespace, subdir, token) {
318
+ const res = await fetch(treeUrl(namespace, subdir), {
319
+ headers: { Authorization: `Bearer ${token}` }
320
+ });
321
+ if (res.status === 404) return [];
322
+ if (!res.ok) throw new Error(`HF list failed: ${res.status} ${res.statusText}`);
323
+ return res.json();
324
+ }
325
+ async function hfDownloadJsonByPath(repoPath, token) {
326
+ const res = await fetch(
327
+ `${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
328
+ { headers: { Authorization: `Bearer ${token}` } }
329
+ );
330
+ if (!res.ok) throw new Error(`HF download failed: ${res.status}`);
331
+ return res.json();
332
+ }
333
+ async function hfDownloadBinaryByPath(repoPath, token) {
334
+ const res = await fetch(
335
+ `${HF_BASE}/datasets/${HF_REPO}/resolve/main/${repoPath}?download=true`,
336
+ { headers: { Authorization: `Bearer ${token}` } }
337
+ );
338
+ if (!res.ok) throw new Error(`HF download binary failed: ${res.status}`);
339
+ return new Uint8Array(await res.arrayBuffer());
340
+ }
341
+ async function hfUploadSmallFile(repoPath, content, token, summary = `Update ${repoPath}`) {
342
+ const bytes = new TextEncoder().encode(content);
343
+ let binary = "";
344
+ bytes.forEach((b) => binary += String.fromCharCode(b));
345
+ const b64 = btoa(binary);
346
+ const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
347
+ method: "POST",
348
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
349
+ body: [
350
+ JSON.stringify({ key: "header", value: { summary, description: "" } }),
351
+ JSON.stringify({ key: "file", value: { path: repoPath, encoding: "base64", content: b64 } })
352
+ ].join("\n")
353
+ });
354
+ if (!res.ok) {
355
+ const err = await res.text().catch(() => "");
356
+ throw new Error(`HF upload failed: ${res.status} \u2014 ${err.slice(0, 200)}`);
357
+ }
358
+ }
359
+ async function hfBatchArchive(moves, token, summary) {
360
+ const lines = [
361
+ JSON.stringify({ key: "header", value: { summary, description: "" } }),
362
+ ...moves.flatMap(({ from, to, content }) => {
363
+ const bytes = new TextEncoder().encode(content);
364
+ let binary = "";
365
+ bytes.forEach((b) => binary += String.fromCharCode(b));
366
+ return [
367
+ JSON.stringify({ key: "file", value: { path: to, encoding: "base64", content: btoa(binary) } }),
368
+ JSON.stringify({ key: "deletedFile", value: { path: from } })
369
+ ];
370
+ })
371
+ ];
372
+ const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
373
+ method: "POST",
374
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
375
+ body: lines.join("\n")
376
+ });
377
+ if (!res.ok) {
378
+ const err = await res.text().catch(() => "");
379
+ throw new Error(`HF batch archive failed: ${res.status} \u2014 ${err.slice(0, 200)}`);
380
+ }
381
+ }
382
+ function tsFromEventPath(repoPath) {
383
+ const filename = repoPath.split("/").pop() || "";
384
+ const iso = filename.replace(/_[^_]+\.json$/, "").replace(/-/g, (m, i) => i > 7 ? ":" : m);
385
+ const ts = Date.parse(iso.replace(/T(\d{2}):(\d{2}):(\d{2})\.(\d{3})Z/, "T$1:$2:$3.$4Z"));
386
+ return isNaN(ts) ? 0 : ts;
387
+ }
388
+ async function loadHFState(namespace, token) {
389
+ const files = await hfListDir(namespace, "", token);
390
+ const stateFiles = files.filter((f) => f.type === "file" && /state-[\dT\-]+Z\.zip$/.test(f.path.split("/").pop() || "")).sort((a, b) => b.path.localeCompare(a.path));
391
+ if (!stateFiles.length) return null;
392
+ const zipBytes = await hfDownloadBinaryByPath(stateFiles[0].path, token);
393
+ const zip = await import_jszip2.default.loadAsync(zipBytes);
394
+ const [metadataStr, tagsStr, metaStr] = await Promise.all([
395
+ zip.file("metadata.json")?.async("string"),
396
+ zip.file("tags.json")?.async("string"),
397
+ zip.file("state_meta.json")?.async("string")
398
+ ]);
399
+ if (!metadataStr || !tagsStr || !metaStr) throw new Error("state.zip: fehlende Pflicht-Dateien");
400
+ return {
401
+ metadata: JSON.parse(metadataStr),
402
+ tags: JSON.parse(tagsStr),
403
+ meta: JSON.parse(metaStr)
404
+ };
405
+ }
406
+ async function loadPendingEvents(namespace, token, sinceTs) {
407
+ const files = await hfListDir(namespace, "events", token);
408
+ const pending = files.filter((f) => f.type === "file" && tsFromEventPath(f.path) > sinceTs).sort((a, b) => a.path.localeCompare(b.path));
409
+ const events = await Promise.all(
410
+ pending.map((f) => hfDownloadJsonByPath(f.path, token))
411
+ );
412
+ return events;
413
+ }
414
+ function getSessionClientId() {
415
+ return SESSION_CLIENT_ID;
416
+ }
417
+ function tsToFilename(ts) {
418
+ return new Date(ts).toISOString().replace(/:/g, "-").replace(".", "-");
419
+ }
420
+ async function writeHFEvent(namespace, token, type, payload, prevTs) {
421
+ const ts = Date.now();
422
+ const uuid = crypto.randomUUID().slice(0, 8);
423
+ const event = {
424
+ v: CURRENT_EVENT_VERSION,
425
+ type,
426
+ ts,
427
+ prevTs,
428
+ clientId: SESSION_CLIENT_ID,
429
+ payload
430
+ };
431
+ const filename = `${tsToFilename(ts)}_${uuid}.json`;
432
+ const repoPath = `${namespace}events/${filename}`;
433
+ await hfUploadSmallFile(repoPath, JSON.stringify(event, null, 2), token, `Event: ${type}`);
434
+ return event;
435
+ }
436
+ async function hfBootstrapFromLegacy(namespace, token, onProgress) {
437
+ const log = (msg) => {
438
+ if (onProgress) onProgress(msg);
439
+ };
440
+ log("Lese tags.json \u2026");
441
+ const tags = await fetch(
442
+ `${HF_BASE}/datasets/${HF_REPO}/resolve/main/tags.json?download=true`,
443
+ { headers: { Authorization: `Bearer ${token}` } }
444
+ ).then((r) => r.ok ? r.json() : null).catch(() => null);
445
+ log("Lese metadata.json \u2026");
446
+ const metadata = await fetch(
447
+ `${HF_BASE}/datasets/${HF_REPO}/resolve/main/metadata.json?download=true`,
448
+ { headers: { Authorization: `Bearer ${token}` } }
449
+ ).then((r) => r.ok ? r.json() : []).catch(() => []);
450
+ const snapshot = {
451
+ metadata: Array.isArray(metadata) ? metadata : [],
452
+ tags: tags?.by_category ? tags : { by_category: {}, all: [] },
453
+ meta: { consolidatedAt: Date.now(), version: 1 }
454
+ };
455
+ log(`Erstelle state.zip (${snapshot.metadata.length} Bilder, ${snapshot.tags.all?.length ?? 0} Tags) \u2026`);
456
+ const zip = new import_jszip2.default();
457
+ zip.file("metadata.json", JSON.stringify(snapshot.metadata, null, 2));
458
+ zip.file("tags.json", JSON.stringify(snapshot.tags, null, 2));
459
+ zip.file("state_meta.json", JSON.stringify(snapshot.meta, null, 2));
460
+ const zipBytes = await zip.generateAsync({ type: "uint8array", compression: "DEFLATE" });
461
+ const ts = snapshot.meta.consolidatedAt;
462
+ const stateFilename = `state-${new Date(ts).toISOString().replace(/:/g, "-").replace(".", "-")}.zip`;
463
+ const repoPath = `${namespace}${stateFilename}`;
464
+ let binary = "";
465
+ zipBytes.forEach((b) => {
466
+ binary += String.fromCharCode(b);
467
+ });
468
+ const b64 = btoa(binary);
469
+ log("Lade state.zip hoch \u2026");
470
+ const commitRes = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
471
+ method: "POST",
472
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
473
+ body: [
474
+ JSON.stringify({ key: "header", value: { summary: `Bootstrap: initialer State aus Legacy-Daten`, description: "" } }),
475
+ JSON.stringify({ key: "file", value: { path: repoPath, encoding: "base64", content: b64 } })
476
+ ].join("\n")
477
+ });
478
+ if (!commitRes.ok) {
479
+ const err = await commitRes.text().catch(() => "");
480
+ throw new Error(`HF commit failed: ${commitRes.status} \u2014 ${err.slice(0, 300)}`);
481
+ }
482
+ log(`Fertig \u2014 ${stateFilename}`);
483
+ }
293
484
  async function hfListProjects(token) {
294
485
  const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/tree/main`, {
295
486
  headers: { Authorization: `Bearer ${token}` }
@@ -333,7 +524,6 @@ async function hfUploadProject(zipBase64, name, token) {
333
524
  if (!preRes.ok) throw new Error(`HF preupload failed: ${preRes.status} ${preRes.statusText}`);
334
525
  const preData = await preRes.json();
335
526
  const fileInfo = preData.files?.[0];
336
- const debugInfo = JSON.stringify({ uploadMode: fileInfo?.uploadMode, hasUploadUrl: !!fileInfo?.uploadUrl, hasVerifyUrl: !!fileInfo?.verifyUrl, headerKeys: Object.keys(fileInfo?.header || {}), verifyHeaderKeys: Object.keys(fileInfo?.verifyHeader || {}) });
337
527
  if (!fileInfo?.uploadMode) throw new Error(`HF preupload kein fileInfo: ${JSON.stringify(preData)}`);
338
528
  if (fileInfo.uploadMode === "lfs" && fileInfo.uploadUrl) {
339
529
  let uploadStatus = 0;
@@ -379,21 +569,8 @@ async function hfUploadProject(zipBase64, name, token) {
379
569
  }
380
570
  return { id: filename.replace(/\.zip$/, ""), name: filename.replace(/\.zip$/, ""), path: filename, size, isLfs: true };
381
571
  }
382
- async function hfUploadProjectForm(zipBase64, name, token) {
383
- const filename = name.endsWith(".zip") ? name : `${name}.zip`;
384
- const binary = atob(zipBase64);
385
- const bytes = new Uint8Array(binary.length);
386
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
387
- const blob = new Blob([bytes], { type: "application/zip" });
388
- const { uploadFile } = await import("@huggingface/hub");
389
- await uploadFile({
390
- repo: { type: "dataset", name: HF_REPO },
391
- credentials: { accessToken: token },
392
- file: { path: filename, content: blob },
393
- branch: "main",
394
- commitTitle: `Upload ${filename}`
395
- });
396
- return { id: filename.replace(/\.zip$/, ""), name: filename.replace(/\.zip$/, ""), path: filename, size: bytes.length, isLfs: true };
572
+ async function hfUploadProjectForm(_zipBase64, _name, _token) {
573
+ throw new Error("hfUploadProjectForm is deprecated. Use hfUploadProject instead.");
397
574
  }
398
575
  async function hfDeleteProject(path, token) {
399
576
  const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
@@ -406,16 +583,8 @@ async function hfDeleteProject(path, token) {
406
583
  });
407
584
  if (!res.ok) throw new Error(`HF delete failed: ${res.status} ${res.statusText}`);
408
585
  }
409
- async function hfSaveTags(workspaceTags, token) {
410
- const content = new Blob([JSON.stringify(workspaceTags, null, 2)], { type: "application/json" });
411
- const { uploadFile } = await import("@huggingface/hub");
412
- await uploadFile({
413
- repo: { type: "dataset", name: HF_REPO },
414
- credentials: { accessToken: token },
415
- file: { path: "tags.json", content },
416
- branch: "main",
417
- commitTitle: "Auto-sync tags"
418
- });
586
+ async function hfSaveTags(_workspaceTags, _token) {
587
+ throw new Error("hfSaveTags is deprecated. Use writeHFEvent instead.");
419
588
  }
420
589
  async function hfLoadTags(token) {
421
590
  try {
@@ -429,16 +598,8 @@ async function hfLoadTags(token) {
429
598
  return null;
430
599
  }
431
600
  }
432
- async function hfSaveMetadata(entries, token) {
433
- const content = new Blob([JSON.stringify(entries, null, 2)], { type: "application/json" });
434
- const { uploadFile } = await import("@huggingface/hub");
435
- await uploadFile({
436
- repo: { type: "dataset", name: HF_REPO },
437
- credentials: { accessToken: token },
438
- file: { path: "metadata.json", content },
439
- branch: "main",
440
- commitTitle: "Auto-sync metadata"
441
- });
601
+ async function hfSaveMetadata(_entries, _token) {
602
+ throw new Error("hfSaveMetadata is deprecated. Use writeHFEvent instead.");
442
603
  }
443
604
  async function hfLoadMetadata(token) {
444
605
  try {
@@ -457,14 +618,32 @@ async function hfUploadImage(base64, id, token, mimeType = "image/jpeg") {
457
618
  const binary = atob(base64);
458
619
  const bytes = new Uint8Array(binary.length);
459
620
  for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
460
- const content = new Blob([bytes], { type: mimeType });
461
- const { uploadFile } = await import("@huggingface/hub");
462
- await uploadFile({
463
- repo: { type: "dataset", name: HF_REPO },
464
- credentials: { accessToken: token },
465
- file: { path: `images/${id}.${ext}`, content },
466
- branch: "main",
467
- commitTitle: `Add image ${id}`
621
+ const hashBuffer = await crypto.subtle.digest("SHA-256", bytes);
622
+ const oid = Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
623
+ const size = bytes.length;
624
+ const filename = `images/${id}.${ext}`;
625
+ const preRes = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/preupload/main`, {
626
+ method: "POST",
627
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
628
+ body: JSON.stringify({ files: [{ path: filename, size }] })
629
+ });
630
+ if (!preRes.ok) throw new Error(`HF preupload failed: ${preRes.status}`);
631
+ const preData = await preRes.json();
632
+ const fileInfo = preData.files?.[0];
633
+ if (fileInfo?.uploadUrl) {
634
+ await fetch(fileInfo.uploadUrl, {
635
+ method: "PUT",
636
+ headers: { "Content-Type": mimeType, ...fileInfo.header || {} },
637
+ body: bytes
638
+ });
639
+ }
640
+ await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
641
+ method: "POST",
642
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
643
+ body: [
644
+ JSON.stringify({ key: "header", value: { summary: `Add image ${id}`, description: "" } }),
645
+ JSON.stringify({ key: "lfsFile", value: { path: filename, algo: "sha256", oid, size } })
646
+ ].join("\n")
468
647
  });
469
648
  }
470
649
  async function hfLoadImageAsBase64(id, token) {
@@ -488,13 +667,16 @@ async function hfLoadImageAsBase64(id, token) {
488
667
  }
489
668
  return null;
490
669
  }
491
- var HF_BASE, HF_REPO, HF_TOKEN_KEY;
670
+ var import_jszip2, HF_BASE, HF_REPO, HF_TOKEN_KEY, SESSION_CLIENT_ID;
492
671
  var init_hfStateService = __esm({
493
672
  "src/lib/hfStateService.ts"() {
494
673
  "use strict";
674
+ import_jszip2 = __toESM(require("jszip"));
675
+ init_hfEventTypes();
495
676
  HF_BASE = "https://huggingface.co";
496
677
  HF_REPO = "RolandSch/fa-app-state";
497
678
  HF_TOKEN_KEY = "hf-token";
679
+ SESSION_CLIENT_ID = `client-${crypto.randomUUID().slice(0, 8)}`;
498
680
  }
499
681
  });
500
682
 
@@ -524,9 +706,12 @@ __export(index_exports, {
524
706
  SectionLabel: () => SectionLabel,
525
707
  SetupPanel: () => SetupPanel,
526
708
  TagManagerPanel: () => TagManagerPanel,
709
+ applyEvent: () => applyEvent,
710
+ applyEvents: () => applyEvents,
527
711
  autoLabel: () => autoLabel,
528
712
  buildBlendInstruction: () => buildBlendInstruction,
529
713
  buildCompareInstruction: () => buildCompareInstruction,
714
+ buildDag: () => buildDag,
530
715
  buildFallbackPrompt: () => buildFallbackPrompt,
531
716
  buildGenerationPrompt: () => buildGenerationPrompt,
532
717
  buildImageGenerationOptions: () => buildImageGenerationOptions,
@@ -538,29 +723,38 @@ __export(index_exports, {
538
723
  cleanAiResponse: () => cleanAiResponse,
539
724
  createFlowServices: () => createFlowServices,
540
725
  exportProjectToZip: () => exportProjectToZip,
726
+ findForks: () => findForks,
727
+ findTips: () => findTips,
541
728
  formatTreeToMarkdown: () => formatTreeToMarkdown,
542
729
  frameToGeneration: () => frameToGeneration,
543
730
  getFormattedTimestamp: () => getFormattedTimestamp,
544
731
  getHFToken: () => getHFToken,
732
+ getSessionClientId: () => getSessionClientId,
545
733
  groupGenerationsToLabItems: () => groupGenerationsToLabItems,
734
+ hfBatchArchive: () => hfBatchArchive,
735
+ hfBootstrapFromLegacy: () => hfBootstrapFromLegacy,
546
736
  hfDeleteProject: () => hfDeleteProject,
547
737
  hfDownloadProject: () => hfDownloadProject,
738
+ hfListDir: () => hfListDir,
548
739
  hfListProjects: () => hfListProjects,
549
740
  hfLoadImageAsBase64: () => hfLoadImageAsBase64,
550
- hfLoadMetadata: () => hfLoadMetadata,
551
- hfLoadTags: () => hfLoadTags,
552
- hfSaveMetadata: () => hfSaveMetadata,
553
- hfSaveTags: () => hfSaveTags,
554
741
  hfUploadImage: () => hfUploadImage,
555
742
  hfUploadProjectForm: () => hfUploadProjectForm,
743
+ hfUploadSmallFile: () => hfUploadSmallFile,
556
744
  importProjectFromZip: () => importProjectFromZip,
557
745
  injectXMPMetadata: () => injectXMPMetadata,
558
746
  interpretSdkError: () => interpretSdkError,
747
+ loadHFState: () => loadHFState,
748
+ loadPendingEvents: () => loadPendingEvents,
559
749
  parsePromptFile: () => parsePromptFile,
560
750
  parsePromptResponse: () => parsePromptResponse,
561
751
  setHFToken: () => setHFToken,
752
+ topoSort: () => topoSort,
753
+ tsFromEventPath: () => tsFromEventPath,
754
+ useHFState: () => useHFState,
562
755
  useKeyboardNavigation: () => useKeyboardNavigation,
563
- useOnClickOutside: () => useOnClickOutside
756
+ useOnClickOutside: () => useOnClickOutside,
757
+ writeHFEvent: () => writeHFEvent
564
758
  });
565
759
  module.exports = __toCommonJS(index_exports);
566
760
 
@@ -1343,7 +1537,7 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
1343
1537
  }
1344
1538
 
1345
1539
  // src/components/AvatarArchitectApp.tsx
1346
- var import_react21 = require("react");
1540
+ var import_react22 = require("react");
1347
1541
 
1348
1542
  // src/components/PromptTab.tsx
1349
1543
  var import_react12 = require("react");
@@ -1871,6 +2065,7 @@ var import_react13 = require("react");
1871
2065
  init_hfStateService();
1872
2066
  var import_jsx_runtime12 = require("react/jsx-runtime");
1873
2067
  var ProjectSyncTab = ({
2068
+ topSlot,
1874
2069
  onProjectExport,
1875
2070
  onProjectImport,
1876
2071
  onWorkspaceImport,
@@ -1962,6 +2157,7 @@ var ProjectSyncTab = ({
1962
2157
  if (hfToken) loadHfProjects(hfToken);
1963
2158
  }, [hfToken]);
1964
2159
  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: [
2160
+ topSlot && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { children: topSlot }),
1965
2161
  (onProjectExport || onProjectImport || onWorkspaceImport) && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-4", children: [
1966
2162
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SectionLabel, { children: "Projekt-ZIP" }),
1967
2163
  /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-2", children: [
@@ -2370,14 +2566,255 @@ function toPromptImages(images) {
2370
2566
  // src/components/AvatarArchitectApp.tsx
2371
2567
  init_hfStateService();
2372
2568
 
2569
+ // src/hooks/useHFState.ts
2570
+ var import_react14 = require("react");
2571
+ init_hfStateService();
2572
+
2573
+ // src/lib/hfReducer.ts
2574
+ function applyEvent(state, event) {
2575
+ if (event.v.major > 1) return state;
2576
+ switch (event.type) {
2577
+ case "image_added": {
2578
+ const p = event.payload;
2579
+ if (state.metadata.some((m) => m.id === p.id)) return state;
2580
+ return { ...state, metadata: [...state.metadata, p] };
2581
+ }
2582
+ case "tag_upserted": {
2583
+ const p = event.payload;
2584
+ const tags = state.tags;
2585
+ const cat = tags.by_category[p.category] || [];
2586
+ const existing = cat.find((t) => t.value === p.value);
2587
+ const updated = { label: p.label, value: p.value, is_user_created: p.is_user_created, is_deleted: p.is_deleted };
2588
+ const newCat = existing ? cat.map((t) => t.value === p.value ? { ...t, ...updated } : t) : [...cat, updated];
2589
+ 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 }];
2590
+ return { ...state, tags: { by_category: { ...tags.by_category, [p.category]: newCat }, all: newAll } };
2591
+ }
2592
+ case "metadata_updated": {
2593
+ const p = event.payload;
2594
+ return {
2595
+ ...state,
2596
+ metadata: state.metadata.map((m) => m.id === p.id ? { ...m, ...p.delta } : m)
2597
+ };
2598
+ }
2599
+ default:
2600
+ return state;
2601
+ }
2602
+ }
2603
+ function applyEvents(state, events) {
2604
+ return events.reduce(applyEvent, state);
2605
+ }
2606
+
2607
+ // src/lib/hfDag.ts
2608
+ function buildDag(events) {
2609
+ const dag = /* @__PURE__ */ new Map();
2610
+ for (const event of events) {
2611
+ dag.set(event.ts, { event, children: [] });
2612
+ }
2613
+ for (const event of events) {
2614
+ for (const parent of event.prevTs) {
2615
+ const node = dag.get(parent);
2616
+ if (node && !node.children.includes(event.ts)) {
2617
+ node.children.push(event.ts);
2618
+ }
2619
+ }
2620
+ }
2621
+ return dag;
2622
+ }
2623
+ function findTips(dag) {
2624
+ return [...dag.values()].filter((n) => n.children.length === 0).map((n) => n.event.ts);
2625
+ }
2626
+ function findForks(dag) {
2627
+ const forks = [];
2628
+ for (const [ts, node] of dag) {
2629
+ if (node.children.length > 1) {
2630
+ forks.push({ parentTs: ts, childTs: node.children });
2631
+ }
2632
+ }
2633
+ return forks;
2634
+ }
2635
+ function topoSort(events) {
2636
+ if (!events.length) return [];
2637
+ const inDegree = /* @__PURE__ */ new Map();
2638
+ const children = /* @__PURE__ */ new Map();
2639
+ const tsSet = new Set(events.map((e) => e.ts));
2640
+ for (const e of events) {
2641
+ inDegree.set(e.ts, 0);
2642
+ children.set(e.ts, []);
2643
+ }
2644
+ for (const e of events) {
2645
+ for (const p of e.prevTs) {
2646
+ if (tsSet.has(p)) {
2647
+ children.get(p).push(e.ts);
2648
+ inDegree.set(e.ts, (inDegree.get(e.ts) || 0) + 1);
2649
+ }
2650
+ }
2651
+ }
2652
+ const queue = events.filter((e) => (inDegree.get(e.ts) || 0) === 0).sort((a, b) => a.ts - b.ts);
2653
+ const result = [];
2654
+ const byTs = new Map(events.map((e) => [e.ts, e]));
2655
+ while (queue.length) {
2656
+ const node = queue.shift();
2657
+ result.push(node);
2658
+ for (const childTs of children.get(node.ts) || []) {
2659
+ const newDeg = (inDegree.get(childTs) || 0) - 1;
2660
+ inDegree.set(childTs, newDeg);
2661
+ if (newDeg === 0) {
2662
+ const child = byTs.get(childTs);
2663
+ const insertAt = queue.findIndex((q) => q.ts > child.ts);
2664
+ if (insertAt === -1) queue.push(child);
2665
+ else queue.splice(insertAt, 0, child);
2666
+ }
2667
+ }
2668
+ }
2669
+ return result;
2670
+ }
2671
+
2672
+ // src/hooks/useHFState.ts
2673
+ var OFFLINE_BUFFER_KEY = "hf-offline-buffer";
2674
+ var POLL_INTERVAL_MS = 3e4;
2675
+ function readOfflineBuffer() {
2676
+ try {
2677
+ return JSON.parse(localStorage.getItem(OFFLINE_BUFFER_KEY) || "[]");
2678
+ } catch {
2679
+ return [];
2680
+ }
2681
+ }
2682
+ function writeOfflineBuffer(events) {
2683
+ try {
2684
+ localStorage.setItem(OFFLINE_BUFFER_KEY, JSON.stringify(events));
2685
+ } catch {
2686
+ }
2687
+ }
2688
+ function useHFState(token, namespace) {
2689
+ const [state, setState] = (0, import_react14.useState)(null);
2690
+ const [isLoading, setIsLoading] = (0, import_react14.useState)(false);
2691
+ const [error, setError] = (0, import_react14.useState)(null);
2692
+ const [eventCount, setEventCount] = (0, import_react14.useState)(0);
2693
+ const [forks, setForks] = (0, import_react14.useState)([]);
2694
+ const [pendingBufferCount, setPendingBufferCount] = (0, import_react14.useState)(readOfflineBuffer().length);
2695
+ const [lastEventTs, setLastEventTs] = (0, import_react14.useState)(0);
2696
+ const [hasStateZip, setHasStateZip] = (0, import_react14.useState)(false);
2697
+ const knownEventPaths = (0, import_react14.useRef)(/* @__PURE__ */ new Set());
2698
+ const allEventsRef = (0, import_react14.useRef)([]);
2699
+ const applyNewEvents = (0, import_react14.useCallback)((snapshot, newEvents) => {
2700
+ if (!newEvents.length && allEventsRef.current.length === 0) {
2701
+ setEventCount(0);
2702
+ return snapshot;
2703
+ }
2704
+ const sorted = topoSort([...allEventsRef.current, ...newEvents]);
2705
+ allEventsRef.current = sorted;
2706
+ const afterConsolidation = sorted.filter((e) => e.ts > snapshot.meta.consolidatedAt);
2707
+ const dag = buildDag(afterConsolidation);
2708
+ setForks(findForks(dag));
2709
+ setEventCount(afterConsolidation.length);
2710
+ if (afterConsolidation.length) setLastEventTs(Math.max(...afterConsolidation.map((e) => e.ts)));
2711
+ return applyEvents(snapshot, afterConsolidation);
2712
+ }, []);
2713
+ const loadFull = (0, import_react14.useCallback)(async () => {
2714
+ if (!token || !namespace) return;
2715
+ setIsLoading(true);
2716
+ setError(null);
2717
+ try {
2718
+ const snapshot = await loadHFState(namespace, token);
2719
+ setHasStateZip(snapshot !== null);
2720
+ const base = snapshot ?? {
2721
+ metadata: [],
2722
+ tags: { by_category: {}, all: [] },
2723
+ meta: { consolidatedAt: 0, version: 1 }
2724
+ };
2725
+ const events = await loadPendingEvents(namespace, token, base.meta.consolidatedAt);
2726
+ events.forEach((e) => knownEventPaths.current.add(`${e.ts}_${e.clientId}`));
2727
+ allEventsRef.current = [];
2728
+ const finalState = applyNewEvents(base, events);
2729
+ setState(finalState);
2730
+ const buffer = readOfflineBuffer();
2731
+ if (buffer.length) {
2732
+ for (const evt of buffer) {
2733
+ await writeHFEvent(namespace, token, evt.type, evt.payload, evt.prevTs).catch(() => {
2734
+ });
2735
+ }
2736
+ writeOfflineBuffer([]);
2737
+ setPendingBufferCount(0);
2738
+ const freshEvents = await loadPendingEvents(namespace, token, base.meta.consolidatedAt);
2739
+ freshEvents.forEach((e) => knownEventPaths.current.add(`${e.ts}_${e.clientId}`));
2740
+ setState((prev) => prev ? applyNewEvents(base, freshEvents) : prev);
2741
+ }
2742
+ } catch (e) {
2743
+ setError(e.message);
2744
+ } finally {
2745
+ setIsLoading(false);
2746
+ }
2747
+ }, [token, namespace, applyNewEvents]);
2748
+ const pollNew = (0, import_react14.useCallback)(async () => {
2749
+ if (!token || !namespace || !state) return;
2750
+ try {
2751
+ const events = await loadPendingEvents(namespace, token, state.meta.consolidatedAt);
2752
+ const newEvents = events.filter((e) => !knownEventPaths.current.has(`${e.ts}_${e.clientId}`));
2753
+ if (!newEvents.length) return;
2754
+ newEvents.forEach((e) => knownEventPaths.current.add(`${e.ts}_${e.clientId}`));
2755
+ setState((prev) => prev ? applyNewEvents(prev, newEvents) : prev);
2756
+ } catch {
2757
+ }
2758
+ }, [token, namespace, state, applyNewEvents]);
2759
+ (0, import_react14.useEffect)(() => {
2760
+ if (token && namespace) loadFull();
2761
+ }, [token, namespace]);
2762
+ (0, import_react14.useEffect)(() => {
2763
+ if (!token || !namespace) return;
2764
+ const id = setInterval(pollNew, POLL_INTERVAL_MS);
2765
+ return () => clearInterval(id);
2766
+ }, [token, namespace, pollNew]);
2767
+ const writeEvent = (0, import_react14.useCallback)(async (type, payload) => {
2768
+ const prevTs = lastEventTs ? [lastEventTs] : [state?.meta.consolidatedAt ?? 0];
2769
+ console.log("[HF] writeEvent called:", { type, namespace, tokenOk: !!token, prevTs });
2770
+ await pollNew();
2771
+ try {
2772
+ console.log("[HF] writeHFEvent start, path will be:", `${namespace}events/...`);
2773
+ const event = await writeHFEvent(namespace, token, type, payload, prevTs);
2774
+ console.log("[HF] writeHFEvent success:", event.ts);
2775
+ knownEventPaths.current.add(`${event.ts}_${event.clientId}`);
2776
+ setState((prev) => prev ? applyNewEvents(prev, [event]) : prev);
2777
+ setLastEventTs(event.ts);
2778
+ await pollNew();
2779
+ } catch (e) {
2780
+ console.error("[HF] writeHFEvent FAILED, going to offline buffer:", e);
2781
+ const buffer = readOfflineBuffer();
2782
+ const offline = {
2783
+ v: { major: 1, minor: 0 },
2784
+ type,
2785
+ ts: Date.now(),
2786
+ prevTs,
2787
+ clientId: getSessionClientId(),
2788
+ payload
2789
+ };
2790
+ writeOfflineBuffer([...buffer, offline]);
2791
+ setPendingBufferCount(buffer.length + 1);
2792
+ setState((prev) => prev ? applyNewEvents(prev, [offline]) : prev);
2793
+ }
2794
+ }, [namespace, token, lastEventTs, state, pollNew, applyNewEvents]);
2795
+ return {
2796
+ state,
2797
+ isLoading,
2798
+ error,
2799
+ pendingBufferCount,
2800
+ eventCount,
2801
+ forks,
2802
+ writeEvent,
2803
+ refresh: loadFull,
2804
+ lastEventTs,
2805
+ allEvents: allEventsRef.current,
2806
+ hasStateZip
2807
+ };
2808
+ }
2809
+
2373
2810
  // src/components/labs/LabsTab.tsx
2374
- var import_react19 = require("react");
2811
+ var import_react20 = require("react");
2375
2812
 
2376
2813
  // src/components/labs/LabRemix.tsx
2377
- var import_react15 = require("react");
2814
+ var import_react16 = require("react");
2378
2815
 
2379
2816
  // src/components/labs/LabImagePicker.tsx
2380
- var import_react14 = require("react");
2817
+ var import_react15 = require("react");
2381
2818
  var import_jsx_runtime13 = require("react/jsx-runtime");
2382
2819
  var LabImagePicker = ({
2383
2820
  availableItems,
@@ -2386,8 +2823,8 @@ var LabImagePicker = ({
2386
2823
  onClose,
2387
2824
  title = "Bild w\xE4hlen"
2388
2825
  }) => {
2389
- const [search, setSearch] = (0, import_react14.useState)("");
2390
- const [drillItem, setDrillItem] = (0, import_react14.useState)(null);
2826
+ const [search, setSearch] = (0, import_react15.useState)("");
2827
+ const [drillItem, setDrillItem] = (0, import_react15.useState)(null);
2391
2828
  const filtered = availableItems.filter(
2392
2829
  (item) => !search || item.prompt.toLowerCase().includes(search.toLowerCase())
2393
2830
  );
@@ -2489,13 +2926,13 @@ var LabImagePicker = ({
2489
2926
  // src/components/labs/LabRemix.tsx
2490
2927
  var import_jsx_runtime14 = require("react/jsx-runtime");
2491
2928
  var LabRemix = ({ services, onResult }) => {
2492
- const [showPicker, setShowPicker] = (0, import_react15.useState)(false);
2493
- const [selected, setSelected] = (0, import_react15.useState)(null);
2494
- const [instruction, setInstruction] = (0, import_react15.useState)("");
2495
- const [generatedPrompt, setGeneratedPrompt] = (0, import_react15.useState)("");
2496
- const [resultImage, setResultImage] = (0, import_react15.useState)(null);
2497
- const [isGeneratingPrompt, setIsGeneratingPrompt] = (0, import_react15.useState)(false);
2498
- const [isGeneratingImage, setIsGeneratingImage] = (0, import_react15.useState)(false);
2929
+ const [showPicker, setShowPicker] = (0, import_react16.useState)(false);
2930
+ const [selected, setSelected] = (0, import_react16.useState)(null);
2931
+ const [instruction, setInstruction] = (0, import_react16.useState)("");
2932
+ const [generatedPrompt, setGeneratedPrompt] = (0, import_react16.useState)("");
2933
+ const [resultImage, setResultImage] = (0, import_react16.useState)(null);
2934
+ const [isGeneratingPrompt, setIsGeneratingPrompt] = (0, import_react16.useState)(false);
2935
+ const [isGeneratingImage, setIsGeneratingImage] = (0, import_react16.useState)(false);
2499
2936
  const handleSelectImage = (item, frame) => {
2500
2937
  services.onItemUsed(item);
2501
2938
  setSelected({
@@ -2678,16 +3115,16 @@ var LabRemix = ({ services, onResult }) => {
2678
3115
  };
2679
3116
 
2680
3117
  // src/components/labs/LabBlend.tsx
2681
- var import_react16 = require("react");
3118
+ var import_react17 = require("react");
2682
3119
  var import_jsx_runtime15 = require("react/jsx-runtime");
2683
3120
  var LabBlend = ({ services, onResult }) => {
2684
- const [showPickerFor, setShowPickerFor] = (0, import_react16.useState)(null);
2685
- const [selectedImages, setSelectedImages] = (0, import_react16.useState)([]);
2686
- const [instruction, setInstruction] = (0, import_react16.useState)("");
2687
- const [generatedPrompt, setGeneratedPrompt] = (0, import_react16.useState)("");
2688
- const [resultImage, setResultImage] = (0, import_react16.useState)(null);
2689
- const [isGeneratingPrompt, setIsGeneratingPrompt] = (0, import_react16.useState)(false);
2690
- const [isGeneratingImage, setIsGeneratingImage] = (0, import_react16.useState)(false);
3121
+ const [showPickerFor, setShowPickerFor] = (0, import_react17.useState)(null);
3122
+ const [selectedImages, setSelectedImages] = (0, import_react17.useState)([]);
3123
+ const [instruction, setInstruction] = (0, import_react17.useState)("");
3124
+ const [generatedPrompt, setGeneratedPrompt] = (0, import_react17.useState)("");
3125
+ const [resultImage, setResultImage] = (0, import_react17.useState)(null);
3126
+ const [isGeneratingPrompt, setIsGeneratingPrompt] = (0, import_react17.useState)(false);
3127
+ const [isGeneratingImage, setIsGeneratingImage] = (0, import_react17.useState)(false);
2691
3128
  const handleSelectImage = (index, item, frame) => {
2692
3129
  services.onItemUsed(item);
2693
3130
  const newImg = {
@@ -2874,17 +3311,17 @@ var LabBlend = ({ services, onResult }) => {
2874
3311
  };
2875
3312
 
2876
3313
  // src/components/labs/LabCompare.tsx
2877
- var import_react17 = require("react");
3314
+ var import_react18 = require("react");
2878
3315
  var import_jsx_runtime16 = require("react/jsx-runtime");
2879
3316
  var LabCompare = ({ services, onResult }) => {
2880
- const [showPickerFor, setShowPickerFor] = (0, import_react17.useState)(null);
2881
- const [selectedImages, setSelectedImages] = (0, import_react17.useState)([]);
2882
- const [instruction, setInstruction] = (0, import_react17.useState)("");
2883
- const [analysis, setAnalysis] = (0, import_react17.useState)("");
2884
- const [generatedPrompt, setGeneratedPrompt] = (0, import_react17.useState)("");
2885
- const [resultImage, setResultImage] = (0, import_react17.useState)(null);
2886
- const [isAnalyzing, setIsAnalyzing] = (0, import_react17.useState)(false);
2887
- const [isGeneratingImage, setIsGeneratingImage] = (0, import_react17.useState)(false);
3317
+ const [showPickerFor, setShowPickerFor] = (0, import_react18.useState)(null);
3318
+ const [selectedImages, setSelectedImages] = (0, import_react18.useState)([]);
3319
+ const [instruction, setInstruction] = (0, import_react18.useState)("");
3320
+ const [analysis, setAnalysis] = (0, import_react18.useState)("");
3321
+ const [generatedPrompt, setGeneratedPrompt] = (0, import_react18.useState)("");
3322
+ const [resultImage, setResultImage] = (0, import_react18.useState)(null);
3323
+ const [isAnalyzing, setIsAnalyzing] = (0, import_react18.useState)(false);
3324
+ const [isGeneratingImage, setIsGeneratingImage] = (0, import_react18.useState)(false);
2888
3325
  const handleSelectImage = (index, item, frame) => {
2889
3326
  services.onItemUsed(item);
2890
3327
  const newImg = {
@@ -3047,14 +3484,14 @@ var LabCompare = ({ services, onResult }) => {
3047
3484
  };
3048
3485
 
3049
3486
  // src/components/labs/LabLoop.tsx
3050
- var import_react18 = require("react");
3487
+ var import_react19 = require("react");
3051
3488
  var import_jsx_runtime17 = require("react/jsx-runtime");
3052
3489
  var LabLoop = ({ services, onResult }) => {
3053
- const [rounds, setRounds] = (0, import_react18.useState)([]);
3054
- const [currentInstruction, setCurrentInstruction] = (0, import_react18.useState)("");
3055
- const [showPickerForRound, setShowPickerForRound] = (0, import_react18.useState)(null);
3056
- const [pendingImages, setPendingImages] = (0, import_react18.useState)([]);
3057
- const [isGenerating, setIsGenerating] = (0, import_react18.useState)(false);
3490
+ const [rounds, setRounds] = (0, import_react19.useState)([]);
3491
+ const [currentInstruction, setCurrentInstruction] = (0, import_react19.useState)("");
3492
+ const [showPickerForRound, setShowPickerForRound] = (0, import_react19.useState)(null);
3493
+ const [pendingImages, setPendingImages] = (0, import_react19.useState)([]);
3494
+ const [isGenerating, setIsGenerating] = (0, import_react19.useState)(false);
3058
3495
  const currentPrompt = rounds.length > 0 ? rounds[rounds.length - 1].prompt : "";
3059
3496
  const handleAddImage = (item, frame) => {
3060
3497
  services.onItemUsed(item);
@@ -3218,7 +3655,7 @@ var TABS = [
3218
3655
  { key: "loop", label: "Loop", icon: "loop" }
3219
3656
  ];
3220
3657
  var LabsTab = ({ services, onResult }) => {
3221
- const [activeTab, setActiveTab] = (0, import_react19.useState)("remix");
3658
+ const [activeTab, setActiveTab] = (0, import_react20.useState)("remix");
3222
3659
  return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex flex-col h-full overflow-hidden", children: [
3223
3660
  /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "flex border-b border-white/5 shrink-0", children: TABS.map((tab) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)(
3224
3661
  "button",
@@ -3242,19 +3679,19 @@ var LabsTab = ({ services, onResult }) => {
3242
3679
  };
3243
3680
 
3244
3681
  // src/components/TagManagerPanel.tsx
3245
- var import_react20 = require("react");
3682
+ var import_react21 = require("react");
3246
3683
  var import_jsx_runtime19 = require("react/jsx-runtime");
3247
3684
  function TagManagerPanel({ workspaceTags, onTagCreate, onTagUpdate, onTagDelete, onTagReorder, onTagMove }) {
3248
3685
  const categories = Object.keys(workspaceTags.by_category).filter(
3249
3686
  (cat) => (workspaceTags.by_category[cat] || []).some((t) => !t.is_deleted)
3250
3687
  );
3251
- const [selectedCategory, setSelectedCategory] = (0, import_react20.useState)(categories[0] || "");
3688
+ const [selectedCategory, setSelectedCategory] = (0, import_react21.useState)(categories[0] || "");
3252
3689
  const effectiveCategory = categories.includes(selectedCategory) ? selectedCategory : categories[0] || "";
3253
- const [editingLabel, setEditingLabel] = (0, import_react20.useState)(null);
3254
- const [editState, setEditState] = (0, import_react20.useState)({ label: "", value: "" });
3255
- const [newTag, setNewTag] = (0, import_react20.useState)({ label: "", value: "" });
3256
- const [movingLabel, setMovingLabel] = (0, import_react20.useState)(null);
3257
- const [moveTarget, setMoveTarget] = (0, import_react20.useState)("");
3690
+ const [editingLabel, setEditingLabel] = (0, import_react21.useState)(null);
3691
+ const [editState, setEditState] = (0, import_react21.useState)({ label: "", value: "" });
3692
+ const [newTag, setNewTag] = (0, import_react21.useState)({ label: "", value: "" });
3693
+ const [movingLabel, setMovingLabel] = (0, import_react21.useState)(null);
3694
+ const [moveTarget, setMoveTarget] = (0, import_react21.useState)("");
3258
3695
  const tags = (workspaceTags.by_category[effectiveCategory] || []).filter((t) => !t.is_deleted);
3259
3696
  const otherCategories = categories.filter((c) => c !== effectiveCategory);
3260
3697
  const startEdit = (tag) => {
@@ -3451,8 +3888,8 @@ function TagManagerPanel({ workspaceTags, onTagCreate, onTagUpdate, onTagDelete,
3451
3888
 
3452
3889
  // src/components/AvatarArchitectApp.tsx
3453
3890
  var import_jsx_runtime20 = require("react/jsx-runtime");
3454
- function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia, buildInfo, initialHfToken, onFetchServerProjects, onServerSave, onServerLoad, onServerDelete }) {
3455
- (0, import_react21.useEffect)(() => {
3891
+ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia, buildInfo, initialHfToken, hfNamespace, allowDevNamespace, onFetchServerProjects, onServerSave, onServerLoad, onServerDelete }) {
3892
+ (0, import_react22.useEffect)(() => {
3456
3893
  const id = "flow-styles";
3457
3894
  if (!document.getElementById(id)) {
3458
3895
  const style = document.createElement("style");
@@ -3461,22 +3898,92 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3461
3898
  document.head.appendChild(style);
3462
3899
  }
3463
3900
  }, []);
3464
- const [showStart, setShowStart] = (0, import_react21.useState)(true);
3465
- const [layoutChoice, setLayoutChoice] = (0, import_react21.useState)(() => {
3901
+ const [showStart, setShowStart] = (0, import_react22.useState)(true);
3902
+ const [layoutChoice, setLayoutChoice] = (0, import_react22.useState)(() => {
3466
3903
  try {
3467
3904
  return localStorage.getItem("aa-layout") || null;
3468
3905
  } catch {
3469
3906
  return null;
3470
3907
  }
3471
3908
  });
3472
- const [projectLoaded, setProjectLoaded] = (0, import_react21.useState)(false);
3473
- const [hfToken, setHfToken] = (0, import_react21.useState)(initialHfToken || "");
3474
- const [hfTokenInput, setHfTokenInput] = (0, import_react21.useState)(initialHfToken || "");
3475
- const [isLoadingFromHF, setIsLoadingFromHF] = (0, import_react21.useState)(false);
3476
- const [hfMetadata, setHfMetadata] = (0, import_react21.useState)([]);
3477
- const hfTagSaveTimer = (0, import_react21.useRef)(null);
3478
- const hfMetaSaveQueue = (0, import_react21.useRef)(Promise.resolve());
3479
- const wsInputRef = (0, import_react21.useRef)(null);
3909
+ const [projectLoaded, setProjectLoaded] = (0, import_react22.useState)(false);
3910
+ const [hfToken, setHfToken] = (0, import_react22.useState)(initialHfToken || "");
3911
+ const [hfTokenInput, setHfTokenInput] = (0, import_react22.useState)(initialHfToken || "");
3912
+ const [isLoadingFromHF, setIsLoadingFromHF] = (0, import_react22.useState)(false);
3913
+ const [hfNamespaceLocal, setHfNamespaceLocal] = (0, import_react22.useState)(() => {
3914
+ try {
3915
+ const stored = localStorage.getItem("aa-hf-namespace");
3916
+ if (stored !== null) return stored;
3917
+ return allowDevNamespace ? "app.art-by-rolands.de/" : "";
3918
+ } catch {
3919
+ return "";
3920
+ }
3921
+ });
3922
+ const [hfNamespaceFromServer, setHfNamespaceFromServer] = (0, import_react22.useState)(null);
3923
+ (0, import_react22.useEffect)(() => {
3924
+ if (hfNamespace !== void 0) return;
3925
+ const backendUrl = typeof window !== "undefined" ? window.BACKEND_URL || window.location.origin : null;
3926
+ if (!backendUrl) return;
3927
+ fetch(`${backendUrl}/api/status`).then((r) => r.json()).then((d) => {
3928
+ if (typeof d.hfNamespace === "string" && d.hfNamespace) setHfNamespaceFromServer(d.hfNamespace);
3929
+ }).catch(() => {
3930
+ });
3931
+ }, [hfNamespace]);
3932
+ const effectiveNamespace = hfNamespace ?? hfNamespaceFromServer ?? hfNamespaceLocal;
3933
+ const {
3934
+ state: hfState,
3935
+ isLoading: isHfRefreshing,
3936
+ pendingBufferCount,
3937
+ eventCount,
3938
+ writeEvent: hfWriteEvent,
3939
+ refresh: refreshHF,
3940
+ hasStateZip
3941
+ } = useHFState(hfToken, effectiveNamespace);
3942
+ const [bootstrapLog, setBootstrapLog] = (0, import_react22.useState)([]);
3943
+ const [isBootstrapping, setIsBootstrapping] = (0, import_react22.useState)(false);
3944
+ const syncTopSlot = /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(import_jsx_runtime20.Fragment, { children: [
3945
+ pendingBufferCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { background: "linear-gradient(90deg,#f59e0b,#ef4444)", padding: "4px 10px", fontSize: 11, color: "#fff", borderRadius: 4, marginBottom: 4 }, children: [
3946
+ pendingBufferCount,
3947
+ " \xC4nderung",
3948
+ pendingBufferCount > 1 ? "en" : "",
3949
+ " lokal \u2014 bei Flow-Reload verloren wenn nicht synchronisiert"
3950
+ ] }),
3951
+ eventCount > 100 && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { background: "#dc2626", color: "#fff", padding: "5px 10px", borderRadius: 4, marginBottom: 4, fontWeight: 600, fontSize: 11 }, children: [
3952
+ "\u26A0 ",
3953
+ eventCount,
3954
+ " Events nicht konsolidiert \u2014 Konsolidierung dringend empfohlen"
3955
+ ] }),
3956
+ eventCount > 50 && eventCount <= 100 && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { background: "#44403c", color: "#a8a29e", padding: "4px 10px", borderRadius: 4, marginBottom: 4, fontSize: 11 }, children: [
3957
+ eventCount,
3958
+ " Events seit letzter Konsolidierung \u2014 Konsolidierung empfohlen"
3959
+ ] }),
3960
+ hfToken && !hasStateZip && !isHfRefreshing && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { style: { background: "#1c1917", border: "1px solid #44403c", borderRadius: 6, padding: "10px 12px" }, children: [
3961
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { fontSize: 12, color: "#a8a29e", marginBottom: 6 }, children: effectiveNamespace ? `Kein State-Snapshot in HF (${effectiveNamespace}) \u2014 aus Legacy-Daten (tags.json + metadata.json) migrieren?` : "Namespace wird geladen\u2026" }),
3962
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3963
+ "button",
3964
+ {
3965
+ disabled: isBootstrapping || !effectiveNamespace,
3966
+ onClick: async () => {
3967
+ setIsBootstrapping(true);
3968
+ setBootstrapLog([]);
3969
+ try {
3970
+ await hfBootstrapFromLegacy(effectiveNamespace, hfToken, (msg) => setBootstrapLog((prev) => [...prev, msg]));
3971
+ setBootstrapLog((prev) => [...prev, "\u2713 Fertig"]);
3972
+ setTimeout(() => refreshHF(), 1500);
3973
+ } catch (e) {
3974
+ setBootstrapLog((prev) => [...prev, `Fehler: ${e.message}`]);
3975
+ } finally {
3976
+ setIsBootstrapping(false);
3977
+ }
3978
+ },
3979
+ style: { padding: "5px 12px", background: "#0ea5e9", color: "#fff", border: "none", borderRadius: 4, cursor: isBootstrapping || !effectiveNamespace ? "not-allowed" : "pointer", fontSize: 11, fontWeight: 600, opacity: isBootstrapping || !effectiveNamespace ? 0.6 : 1 },
3980
+ children: isBootstrapping ? "Migriere\u2026" : "Legacy-Migration starten"
3981
+ }
3982
+ ),
3983
+ bootstrapLog.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { style: { marginTop: 6, fontFamily: "monospace", fontSize: 10, color: "#78716c", lineHeight: 1.6 }, children: bootstrapLog.map((l, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { children: l }, i)) })
3984
+ ] })
3985
+ ] });
3986
+ const wsInputRef = (0, import_react22.useRef)(null);
3480
3987
  const startApp = (choice) => {
3481
3988
  try {
3482
3989
  localStorage.setItem("aa-layout", choice);
@@ -3485,70 +3992,110 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3485
3992
  setLayoutChoice(choice);
3486
3993
  setShowStart(false);
3487
3994
  };
3488
- const [nodes, setNodes] = (0, import_react21.useState)([{ id: "1", type: "custom", position: { x: 0, y: 0 }, data: { label: "Fine Art Project", placeholder: "Name..." } }]);
3489
- const [edges, setEdges] = (0, import_react21.useState)([]);
3490
- const [history, setHistory] = (0, import_react21.useState)([]);
3491
- const [galleryItems, setGalleryItems] = (0, import_react21.useState)([]);
3492
- const galleryItemsRef = (0, import_react21.useRef)([]);
3493
- (0, import_react21.useEffect)(() => {
3995
+ const [nodes, setNodes] = (0, import_react22.useState)([{ id: "1", type: "custom", position: { x: 0, y: 0 }, data: { label: "Fine Art Project", placeholder: "Name..." } }]);
3996
+ const [edges, setEdges] = (0, import_react22.useState)([]);
3997
+ const [history, setHistory] = (0, import_react22.useState)([]);
3998
+ const [galleryItems, setGalleryItems] = (0, import_react22.useState)([]);
3999
+ const galleryItemsRef = (0, import_react22.useRef)([]);
4000
+ (0, import_react22.useEffect)(() => {
3494
4001
  galleryItemsRef.current = galleryItems;
3495
4002
  }, [galleryItems]);
3496
- const [activePrompt, setActivePrompt] = (0, import_react21.useState)("");
3497
- const [isSynthesizing, setIsSynthesizing] = (0, import_react21.useState)(false);
3498
- const [activeGenerationsCount, setActiveGenerationsCount] = (0, import_react21.useState)(0);
3499
- const [currentResult, setCurrentResult] = (0, import_react21.useState)(null);
3500
- const [focusedNodeId, setFocusedNodeId] = (0, import_react21.useState)(null);
3501
- const [leftTab, setLeftTab] = (0, import_react21.useState)("prompt");
3502
- const [promptFeedback, setPromptFeedback] = (0, import_react21.useState)(null);
3503
- const [lastPromptPayload, setLastPromptPayload] = (0, import_react21.useState)(null);
3504
- const [isPromptTabGenerating, setIsPromptTabGenerating] = (0, import_react21.useState)(false);
3505
- const [activeTab, setActiveTab] = (0, import_react21.useState)("history");
3506
- const [mobileTab, setMobileTab] = (0, import_react21.useState)("stage");
3507
- const [middlePanel, setMiddlePanel] = (0, import_react21.useState)("stage");
3508
- const [recentLabItems, setRecentLabItems] = (0, import_react21.useState)([]);
3509
- const [aspectRatio, setAspectRatio] = (0, import_react21.useState)("1:1");
3510
- const [selectedModel, setSelectedModel] = (0, import_react21.useState)("\u{1F34C} Nano Banana Pro");
3511
- const [seed, setSeed] = (0, import_react21.useState)(Math.floor(Math.random() * 1e6));
3512
- const [seedMode, setSeedMode] = (0, import_react21.useState)("random");
3513
- const [isLeftCollapsed, setIsLeftCollapsed] = (0, import_react21.useState)(false);
3514
- const [isRightCollapsed, setIsRightCollapsed] = (0, import_react21.useState)(false);
3515
- const [leftPanelWidth, setLeftPanelWidth] = (0, import_react21.useState)(() => {
4003
+ const hfImageNotFoundRef = (0, import_react22.useRef)(/* @__PURE__ */ new Set());
4004
+ (0, import_react22.useEffect)(() => {
4005
+ if (!hfState) return;
4006
+ if (hfState.tags?.by_category) setWorkspaceTags(hfState.tags);
4007
+ const hfIds = new Set(hfState.metadata.map((m) => m.id));
4008
+ const skeletons = hfState.metadata.map((m) => ({
4009
+ id: m.id,
4010
+ nodeId: m.id,
4011
+ prompt: m.prompt,
4012
+ seed: m.seed,
4013
+ model: m.model,
4014
+ tags: m.tags || [],
4015
+ timestamp: m.timestamp,
4016
+ status: "done"
4017
+ }));
4018
+ setGalleryItems((prev) => {
4019
+ const localOnly = prev.filter((g) => !hfIds.has(g.id));
4020
+ const merged = skeletons.map((s) => prev.find((g) => g.id === s.id) ?? s);
4021
+ return [...localOnly, ...merged];
4022
+ });
4023
+ setHistory((prev) => {
4024
+ const localOnly = prev.filter((g) => !hfIds.has(g.id));
4025
+ const merged = skeletons.map((s) => prev.find((g) => g.id === s.id) ?? s);
4026
+ return [...localOnly, ...merged];
4027
+ });
4028
+ for (const entry of hfState.metadata) {
4029
+ if (hfImageNotFoundRef.current.has(entry.id)) continue;
4030
+ hfLoadImageAsBase64(entry.id, hfToken).then((b64) => {
4031
+ if (!b64) {
4032
+ hfImageNotFoundRef.current.add(entry.id);
4033
+ return;
4034
+ }
4035
+ const prefix = `data:${entry.mimeType || "image/jpeg"};base64,`;
4036
+ setGalleryItems((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
4037
+ setHistory((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
4038
+ }).catch(() => {
4039
+ hfImageNotFoundRef.current.add(entry.id);
4040
+ });
4041
+ }
4042
+ }, [hfState]);
4043
+ const [activePrompt, setActivePrompt] = (0, import_react22.useState)("");
4044
+ const [isSynthesizing, setIsSynthesizing] = (0, import_react22.useState)(false);
4045
+ const [activeGenerationsCount, setActiveGenerationsCount] = (0, import_react22.useState)(0);
4046
+ const [currentResult, setCurrentResult] = (0, import_react22.useState)(null);
4047
+ const [focusedNodeId, setFocusedNodeId] = (0, import_react22.useState)(null);
4048
+ const [leftTab, setLeftTab] = (0, import_react22.useState)("prompt");
4049
+ const [promptFeedback, setPromptFeedback] = (0, import_react22.useState)(null);
4050
+ const [lastPromptPayload, setLastPromptPayload] = (0, import_react22.useState)(null);
4051
+ const [isPromptTabGenerating, setIsPromptTabGenerating] = (0, import_react22.useState)(false);
4052
+ const [activeTab, setActiveTab] = (0, import_react22.useState)("history");
4053
+ const [mobileTab, setMobileTab] = (0, import_react22.useState)("stage");
4054
+ const [middlePanel, setMiddlePanel] = (0, import_react22.useState)("stage");
4055
+ const [recentLabItems, setRecentLabItems] = (0, import_react22.useState)([]);
4056
+ const [aspectRatio, setAspectRatio] = (0, import_react22.useState)("1:1");
4057
+ const [selectedModel, setSelectedModel] = (0, import_react22.useState)("\u{1F34C} Nano Banana Pro");
4058
+ const [seed, setSeed] = (0, import_react22.useState)(Math.floor(Math.random() * 1e6));
4059
+ const [seedMode, setSeedMode] = (0, import_react22.useState)("random");
4060
+ const [isLeftCollapsed, setIsLeftCollapsed] = (0, import_react22.useState)(false);
4061
+ const [isRightCollapsed, setIsRightCollapsed] = (0, import_react22.useState)(false);
4062
+ const [leftPanelWidth, setLeftPanelWidth] = (0, import_react22.useState)(() => {
3516
4063
  try {
3517
4064
  return parseInt(localStorage.getItem("aa-left-width") || "260", 10);
3518
4065
  } catch {
3519
4066
  return 260;
3520
4067
  }
3521
4068
  });
3522
- const [rightPanelWidth, setRightPanelWidth] = (0, import_react21.useState)(() => {
4069
+ const [rightPanelWidth, setRightPanelWidth] = (0, import_react22.useState)(() => {
3523
4070
  try {
3524
4071
  return parseInt(localStorage.getItem("aa-right-width") || "320", 10);
3525
4072
  } catch {
3526
4073
  return 320;
3527
4074
  }
3528
4075
  });
3529
- const [isPromptCollapsed, setIsPromptCollapsed] = (0, import_react21.useState)(false);
3530
- const [projectActionState, setProjectActionState] = (0, import_react21.useState)("idle");
3531
- const syncServerDataRef = (0, import_react21.useRef)(null);
3532
- const [workspaceTags, setWorkspaceTags] = (0, import_react21.useState)(null);
3533
- const [serverProjects, setServerProjects] = (0, import_react21.useState)([]);
3534
- const [isLoadingFromServer, setIsLoadingFromServer] = (0, import_react21.useState)(false);
3535
- const [highContrast, setHighContrast] = (0, import_react21.useState)(() => {
4076
+ const [isPromptCollapsed, setIsPromptCollapsed] = (0, import_react22.useState)(false);
4077
+ const [projectActionState, setProjectActionState] = (0, import_react22.useState)("idle");
4078
+ const syncServerDataRef = (0, import_react22.useRef)(null);
4079
+ const [workspaceTags, setWorkspaceTags] = (0, import_react22.useState)(null);
4080
+ const [serverProjects, setServerProjects] = (0, import_react22.useState)([]);
4081
+ const [isLoadingFromServer, setIsLoadingFromServer] = (0, import_react22.useState)(false);
4082
+ const [highContrast, setHighContrast] = (0, import_react22.useState)(() => {
3536
4083
  try {
3537
4084
  return localStorage.getItem("aa-contrast") === "high";
3538
4085
  } catch {
3539
4086
  return false;
3540
4087
  }
3541
4088
  });
3542
- const [activeReferenceId, setActiveReferenceId] = (0, import_react21.useState)(null);
3543
- const [activeReferenceThumbnail, setActiveReferenceThumbnail] = (0, import_react21.useState)(null);
3544
- const [isScanningImage, setIsScanningImage] = (0, import_react21.useState)(false);
3545
- const [touchStartX, setTouchStartX] = (0, import_react21.useState)(null);
3546
- const [isFullscreen, setIsFullscreen] = (0, import_react21.useState)(false);
3547
- const [zoomScale, setZoomScale] = (0, import_react21.useState)(1);
3548
- const [zoomOffset, setZoomOffset] = (0, import_react21.useState)({ x: 0, y: 0 });
3549
- const lastPinchDist = (0, import_react21.useRef)(null);
3550
- const lastTapTime = (0, import_react21.useRef)(0);
3551
- const dragStart = (0, import_react21.useRef)(null);
4089
+ const [activeReferenceId, setActiveReferenceId] = (0, import_react22.useState)(null);
4090
+ const [activeReferenceThumbnail, setActiveReferenceThumbnail] = (0, import_react22.useState)(null);
4091
+ const [isScanningImage, setIsScanningImage] = (0, import_react22.useState)(false);
4092
+ const [touchStartX, setTouchStartX] = (0, import_react22.useState)(null);
4093
+ const [isFullscreen, setIsFullscreen] = (0, import_react22.useState)(false);
4094
+ const [zoomScale, setZoomScale] = (0, import_react22.useState)(1);
4095
+ const [zoomOffset, setZoomOffset] = (0, import_react22.useState)({ x: 0, y: 0 });
4096
+ const lastPinchDist = (0, import_react22.useRef)(null);
4097
+ const lastTapTime = (0, import_react22.useRef)(0);
4098
+ const dragStart = (0, import_react22.useRef)(null);
3552
4099
  const openFullscreen = () => {
3553
4100
  setIsFullscreen(true);
3554
4101
  setZoomScale(1);
@@ -3611,7 +4158,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3611
4158
  setActiveReferenceId(null);
3612
4159
  setActiveReferenceThumbnail(null);
3613
4160
  };
3614
- const labServices = (0, import_react21.useMemo)(() => {
4161
+ const labServices = (0, import_react22.useMemo)(() => {
3615
4162
  const available = groupGenerationsToLabItems([...galleryItems, ...history]);
3616
4163
  return {
3617
4164
  availableItems: available,
@@ -3691,17 +4238,17 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3691
4238
  setIsScanningImage(false);
3692
4239
  }
3693
4240
  };
3694
- const currentIndex = (0, import_react21.useMemo)(() => history.findIndex((h) => h.id === currentResult?.id), [history, currentResult]);
3695
- const goToPrev = (0, import_react21.useCallback)(() => {
4241
+ const currentIndex = (0, import_react22.useMemo)(() => history.findIndex((h) => h.id === currentResult?.id), [history, currentResult]);
4242
+ const goToPrev = (0, import_react22.useCallback)(() => {
3696
4243
  if (currentIndex > 0) setCurrentResult(history[currentIndex - 1]);
3697
4244
  }, [currentIndex, history]);
3698
- const goToNext = (0, import_react21.useCallback)(() => {
4245
+ const goToNext = (0, import_react22.useCallback)(() => {
3699
4246
  if (currentIndex < history.length - 1) setCurrentResult(history[currentIndex + 1]);
3700
4247
  }, [currentIndex, history]);
3701
4248
  const hcStyle = highContrast ? { filter: "brightness(1.6) contrast(1.05)" } : void 0;
3702
4249
  const isGenerating = activeGenerationsCount > 0;
3703
4250
  useKeyboardNavigation(history, currentResult, setCurrentResult);
3704
- const getSubtreeFormat = (0, import_react21.useCallback)((nodeId, depth = 0) => {
4251
+ const getSubtreeFormat = (0, import_react22.useCallback)((nodeId, depth = 0) => {
3705
4252
  const node = nodes.find((n) => n.id === nodeId);
3706
4253
  if (!node) return "";
3707
4254
  const childrenIds = edges.filter((e) => e.source === nodeId).map((e) => e.target);
@@ -3709,7 +4256,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3709
4256
  return `${indent}- ${node.data.label || "(unbenannt)"}
3710
4257
  ` + childrenIds.map((id) => getSubtreeFormat(id, depth + 1)).join("");
3711
4258
  }, [nodes, edges]);
3712
- const activePath = (0, import_react21.useMemo)(() => {
4259
+ const activePath = (0, import_react22.useMemo)(() => {
3713
4260
  if (!focusedNodeId) return /* @__PURE__ */ new Set();
3714
4261
  const path = /* @__PURE__ */ new Set([focusedNodeId]);
3715
4262
  let currId = focusedNodeId;
@@ -3741,7 +4288,11 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3741
4288
  if (prev.id === genId || !options.silent) return finishedGen;
3742
4289
  return prev;
3743
4290
  });
3744
- if (hfToken && base64) {
4291
+ console.log("[HF] handleGenerateImage \u2014 condition check:", { hfToken: !!hfToken, base64: !!base64, effectiveNamespace });
4292
+ if (hfToken && base64 && effectiveNamespace) {
4293
+ hfUploadImage(base64, genId, hfToken).catch((e) => {
4294
+ console.error("[HF] hfUploadImage failed:", e);
4295
+ });
3745
4296
  const entry = {
3746
4297
  id: genId,
3747
4298
  prompt: promptToUse || void 0,
@@ -3751,20 +4302,9 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3751
4302
  timestamp: Date.now(),
3752
4303
  mimeType: "image/jpeg"
3753
4304
  };
3754
- hfUploadImage(base64, genId, hfToken).catch(() => {
3755
- });
3756
- const token = hfToken;
3757
- hfMetaSaveQueue.current = hfMetaSaveQueue.current.then(async () => {
3758
- try {
3759
- const existing = await hfLoadMetadata(token);
3760
- const ids = new Set((existing || []).map((e) => e.id));
3761
- if (!ids.has(genId)) {
3762
- const next = [...existing || [], entry];
3763
- await hfSaveMetadata(next, token);
3764
- setHfMetadata(next);
3765
- }
3766
- } catch {
3767
- }
4305
+ console.log("[HF] calling hfWriteEvent, namespace:", effectiveNamespace);
4306
+ hfWriteEvent("image_added", entry).catch((e) => {
4307
+ console.error("[HF] hfWriteEvent outer catch:", e);
3768
4308
  });
3769
4309
  }
3770
4310
  } catch (err) {
@@ -3800,6 +4340,11 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3800
4340
  all: [...prev.all, { ...newTagOption, category: tag.category }]
3801
4341
  };
3802
4342
  });
4343
+ if (hfToken && effectiveNamespace) {
4344
+ const p = { category: tag.category, label: tag.label, value: tag.value, is_user_created: true };
4345
+ hfWriteEvent("tag_upserted", p).catch(() => {
4346
+ });
4347
+ }
3803
4348
  };
3804
4349
  const handleTagUpdate = (originalLabel, originalCategory, updates) => {
3805
4350
  setWorkspaceTags((prev) => {
@@ -3812,8 +4357,14 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3812
4357
  );
3813
4358
  return { by_category: { ...prev.by_category, [originalCategory]: updatedCat }, all: updatedAll };
3814
4359
  });
4360
+ if (hfToken && effectiveNamespace) {
4361
+ const p = { category: originalCategory, label: updates.label, value: updates.value, is_user_created: true };
4362
+ hfWriteEvent("tag_upserted", p).catch(() => {
4363
+ });
4364
+ }
3815
4365
  };
3816
4366
  const handleTagDelete = (label, category) => {
4367
+ const tagValue = workspaceTags?.by_category[category]?.find((t) => t.label === label)?.value;
3817
4368
  setWorkspaceTags((prev) => {
3818
4369
  if (!prev) return prev;
3819
4370
  const updatedCat = (prev.by_category[category] || []).map(
@@ -3824,6 +4375,11 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3824
4375
  );
3825
4376
  return { by_category: { ...prev.by_category, [category]: updatedCat }, all: updatedAll };
3826
4377
  });
4378
+ if (hfToken && effectiveNamespace && tagValue) {
4379
+ const p = { category, label, value: tagValue, is_deleted: true };
4380
+ hfWriteEvent("tag_upserted", p).catch(() => {
4381
+ });
4382
+ }
3827
4383
  };
3828
4384
  const handleTagReorder = (category, reorderedTags) => {
3829
4385
  setWorkspaceTags((prev) => {
@@ -3973,38 +4529,39 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3973
4529
  await fetchServerProjects();
3974
4530
  };
3975
4531
  const handleHfInitialSync = async (onProgress) => {
3976
- if (!hfToken) return;
3977
- const gens = galleryItems.filter((g) => g.base64 && g.status === "done");
4532
+ if (!hfToken || !effectiveNamespace) return;
4533
+ const existingIds = new Set((hfState?.metadata || []).map((m) => m.id));
4534
+ const gens = galleryItems.filter((g) => g.base64 && g.status === "done" && !existingIds.has(g.id));
3978
4535
  const total = gens.length;
3979
4536
  onProgress(0, total);
3980
4537
  let done = 0;
3981
4538
  for (const gen of gens) {
3982
4539
  const raw = gen.base64.includes(",") ? gen.base64.split(",")[1] : gen.base64;
3983
4540
  const mimeType = gen.base64.startsWith("data:image/png") ? "image/png" : "image/jpeg";
3984
- await hfUploadImage(raw, gen.id, hfToken, mimeType);
4541
+ await hfUploadImage(raw, gen.id, hfToken, mimeType).catch(() => {
4542
+ });
4543
+ const entry = {
4544
+ id: gen.id,
4545
+ prompt: gen.prompt || void 0,
4546
+ seed: gen.seed,
4547
+ model: gen.model,
4548
+ tags: gen.tags || [],
4549
+ timestamp: gen.timestamp,
4550
+ mimeType
4551
+ };
4552
+ await hfWriteEvent("image_added", entry).catch(() => {
4553
+ });
3985
4554
  done++;
3986
4555
  onProgress(done, total);
3987
4556
  }
3988
- const localEntries = gens.map((g) => ({
3989
- id: g.id,
3990
- prompt: g.prompt || void 0,
3991
- seed: g.seed,
3992
- model: g.model,
3993
- tags: g.tags || [],
3994
- timestamp: g.timestamp,
3995
- mimeType: g.base64.startsWith("data:image/png") ? "image/png" : "image/jpeg"
3996
- }));
3997
- const existingMeta = await hfLoadMetadata(hfToken);
3998
- const existingIds = new Set((existingMeta || []).map((e) => e.id));
3999
- const newEntries = localEntries.filter((e) => !existingIds.has(e.id));
4000
- const mergedMeta = [...existingMeta || [], ...newEntries];
4001
- await hfSaveMetadata(mergedMeta, hfToken);
4002
- setHfMetadata(mergedMeta);
4003
4557
  if (workspaceTags) {
4004
- const remoteTags = await hfLoadTags(hfToken).catch(() => null);
4005
- const mergedTags = mergeWorkspaceTags(workspaceTags, remoteTags);
4006
- await hfSaveTags(mergedTags, hfToken);
4007
- setWorkspaceTags(mergedTags);
4558
+ for (const [category, tags] of Object.entries(workspaceTags.by_category)) {
4559
+ for (const tag of tags) {
4560
+ const p = { category, label: tag.label, value: tag.value, is_user_created: tag.is_user_created, is_deleted: tag.is_deleted };
4561
+ await hfWriteEvent("tag_upserted", p).catch(() => {
4562
+ });
4563
+ }
4564
+ }
4008
4565
  }
4009
4566
  };
4010
4567
  const handleComputeSyncDiff = async () => {
@@ -4043,87 +4600,9 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4043
4600
  setTimeout(() => setProjectActionState("idle"), 4e3);
4044
4601
  }
4045
4602
  };
4046
- (0, import_react21.useEffect)(() => {
4603
+ (0, import_react22.useEffect)(() => {
4047
4604
  if (activeTab === "setup" || activeTab === "sync") fetchServerProjects();
4048
4605
  }, [activeTab]);
4049
- const [isHfRefreshing, setIsHfRefreshing] = (0, import_react21.useState)(false);
4050
- const loadFromHF = (0, import_react21.useCallback)(async (token) => {
4051
- setIsHfRefreshing(true);
4052
- try {
4053
- hfLoadTags(token).then((tags) => {
4054
- if (tags?.by_category) setWorkspaceTags(tags);
4055
- }).catch(() => {
4056
- });
4057
- const entries = await hfLoadMetadata(token);
4058
- if (!Array.isArray(entries) || entries.length === 0) return;
4059
- setHfMetadata(entries);
4060
- const hfIds = new Set(entries.map((e) => e.id));
4061
- const hfSkeletons = entries.map((e) => ({
4062
- id: e.id,
4063
- nodeId: e.id,
4064
- prompt: e.prompt,
4065
- seed: e.seed,
4066
- model: e.model,
4067
- tags: e.tags || [],
4068
- timestamp: e.timestamp,
4069
- status: "done"
4070
- }));
4071
- setGalleryItems((prev) => {
4072
- const localOnly = prev.filter((g) => !hfIds.has(g.id));
4073
- const merged = hfSkeletons.map((s) => prev.find((g) => g.id === s.id) ?? s);
4074
- return [...localOnly, ...merged];
4075
- });
4076
- setHistory((prev) => {
4077
- const localOnly = prev.filter((g) => !hfIds.has(g.id));
4078
- const merged = hfSkeletons.map((s) => prev.find((g) => g.id === s.id) ?? s);
4079
- return [...localOnly, ...merged];
4080
- });
4081
- for (const entry of entries) {
4082
- hfLoadImageAsBase64(entry.id, token).then((b64) => {
4083
- if (!b64) return;
4084
- const prefix = `data:${entry.mimeType || "image/jpeg"};base64,`;
4085
- setGalleryItems((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
4086
- setHistory((prev) => prev.map((g) => g.id === entry.id && !g.base64 ? { ...g, base64: prefix + b64 } : g));
4087
- }).catch(() => {
4088
- });
4089
- }
4090
- const localOnlyItems = galleryItemsRef.current.filter((g) => !hfIds.has(g.id) && g.base64 && g.status === "done");
4091
- if (localOnlyItems.length > 0) {
4092
- hfMetaSaveQueue.current = hfMetaSaveQueue.current.then(async () => {
4093
- try {
4094
- let currentMeta = await hfLoadMetadata(token);
4095
- const existingIds = new Set((currentMeta || []).map((e) => e.id));
4096
- for (const gen of localOnlyItems) {
4097
- if (existingIds.has(gen.id)) continue;
4098
- const raw = gen.base64.includes(",") ? gen.base64.split(",")[1] : gen.base64;
4099
- const mimeType = gen.base64.startsWith("data:image/png") ? "image/png" : "image/jpeg";
4100
- await hfUploadImage(raw, gen.id, token, mimeType).catch(() => {
4101
- });
4102
- const entry = {
4103
- id: gen.id,
4104
- prompt: gen.prompt || void 0,
4105
- seed: gen.seed,
4106
- model: gen.model,
4107
- tags: gen.tags || [],
4108
- timestamp: gen.timestamp,
4109
- mimeType
4110
- };
4111
- currentMeta = [...currentMeta || [], entry];
4112
- existingIds.add(gen.id);
4113
- }
4114
- await hfSaveMetadata(currentMeta, token);
4115
- setHfMetadata(currentMeta);
4116
- } catch {
4117
- }
4118
- });
4119
- }
4120
- } finally {
4121
- setIsHfRefreshing(false);
4122
- }
4123
- }, []);
4124
- (0, import_react21.useEffect)(() => {
4125
- if (hfToken) loadFromHF(hfToken);
4126
- }, [hfToken]);
4127
4606
  const mergeWorkspaceTags = (local, remote) => {
4128
4607
  if (!remote?.by_category) return local;
4129
4608
  const merged = {};
@@ -4143,22 +4622,6 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4143
4622
  const all = Object.entries(merged).flatMap(([cat, tags]) => tags.map((t) => ({ ...t, category: cat })));
4144
4623
  return { by_category: merged, all };
4145
4624
  };
4146
- (0, import_react21.useEffect)(() => {
4147
- if (!hfToken || !workspaceTags) return;
4148
- if (hfTagSaveTimer.current) clearTimeout(hfTagSaveTimer.current);
4149
- hfTagSaveTimer.current = setTimeout(async () => {
4150
- const remote = await hfLoadTags(hfToken).catch(() => null);
4151
- const merged = mergeWorkspaceTags(workspaceTags, remote);
4152
- await hfSaveTags(merged, hfToken).catch(() => {
4153
- });
4154
- if (Object.values(merged.by_category).some(
4155
- (tags, i) => tags.length !== Object.values(workspaceTags.by_category)[i]?.length
4156
- )) setWorkspaceTags(merged);
4157
- }, 1500);
4158
- return () => {
4159
- if (hfTagSaveTimer.current) clearTimeout(hfTagSaveTimer.current);
4160
- };
4161
- }, [workspaceTags, hfToken]);
4162
4625
  if (isFullscreen && currentResult?.base64) {
4163
4626
  const fsBase64 = currentResult.base64.startsWith("data:") ? currentResult.base64 : `data:image/png;base64,${currentResult.base64}`;
4164
4627
  return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
@@ -4363,6 +4826,28 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4363
4826
  )) }),
4364
4827
  layoutChoice === "mobile-desktop" && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-white/20 text-[9px] text-center", children: "Mobil-Layout skaliert f\xFCr Desktop-Modus" }),
4365
4828
  layoutChoice === "tablet-landscape" && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-white/20 text-[9px] text-center", children: "2-Spalten-Layout f\xFCr Landscape-Tablet im Desktop-Mode" })
4829
+ ] }),
4830
+ !hfNamespace && !hfNamespaceFromServer && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-3 w-full max-w-[280px]", children: [
4831
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-white/25 text-[10px] uppercase tracking-widest font-bold", children: "State:" }),
4832
+ ["app.art-by-rolands.de/", "dev-app.art-by-rolands.de/"].map((ns, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4833
+ "button",
4834
+ {
4835
+ onClick: () => {
4836
+ setHfNamespaceLocal(ns);
4837
+ try {
4838
+ localStorage.setItem("aa-hf-namespace", ns);
4839
+ } catch {
4840
+ }
4841
+ },
4842
+ className: "px-3 py-1 rounded-lg text-[11px] font-bold transition-colors",
4843
+ style: {
4844
+ background: hfNamespaceLocal === ns ? "#e7e5e4" : "#44403c",
4845
+ color: hfNamespaceLocal === ns ? "#1c1917" : "#e7e5e4"
4846
+ },
4847
+ children: i === 0 ? "PROD" : "DEV"
4848
+ },
4849
+ ns
4850
+ ))
4366
4851
  ] })
4367
4852
  ] });
4368
4853
  }
@@ -4499,7 +4984,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4499
4984
  mobileTab === "browse" && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex flex-col flex-1 min-h-0", children: [
4500
4985
  /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex border-b border-white/5 shrink-0", style: { height: 52 }, children: [
4501
4986
  ["history", "gallery", "inspect"].map((tab) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { onClick: () => setActiveTab(tab), className: `flex-1 flex items-center justify-center gap-1.5 transition-colors text-[11px] font-bold uppercase tracking-wide ${activeTab === tab ? "text-white border-b-2 border-white" : "text-white/30"}`, children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "material-symbols-outlined text-[20px]", children: tab === "history" ? "history" : tab === "gallery" ? "photo_library" : "info" }) }, tab)),
4502
- hfToken && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { onClick: () => loadFromHF(hfToken), 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_runtime20.jsx)("span", { className: `material-symbols-outlined text-[20px]${isHfRefreshing ? " animate-spin" : ""}`, children: "sync" }) })
4987
+ hfToken && /* @__PURE__ */ (0, import_jsx_runtime20.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_runtime20.jsx)("span", { className: `material-symbols-outlined text-[20px]${isHfRefreshing ? " animate-spin" : ""}`, children: "sync" }) })
4503
4988
  ] }),
4504
4989
  /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex-1 overflow-hidden relative", children: [
4505
4990
  activeTab === "history" && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(HistoryPanel, { history, currentResultId: currentResult?.id || null, onSelect: (g) => {
@@ -4597,6 +5082,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4597
5082
  activeTab === "sync" && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4598
5083
  ProjectSyncTab,
4599
5084
  {
5085
+ topSlot: syncTopSlot,
4600
5086
  onProjectExport: handleProjectExport,
4601
5087
  onProjectImport: (f) => handleProjectImport(f),
4602
5088
  onWorkspaceImport: handleWorkspaceImport,
@@ -4889,7 +5375,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4889
5375
  setActiveTab(tab);
4890
5376
  setIsRightCollapsed(false);
4891
5377
  }, className: `flex-1 flex items-center justify-center relative transition-colors ${activeTab === tab ? "text-white" : "text-white/20"}`, children: /* @__PURE__ */ (0, import_jsx_runtime20.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)) }),
4892
- hfToken && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { onClick: () => loadFromHF(hfToken), disabled: isHfRefreshing, className: "w-10 flex items-center justify-center text-white/20 hover:text-white/60 transition-colors disabled:opacity-30", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: `material-symbols-outlined text-[18px]${isHfRefreshing ? " animate-spin" : ""}`, children: "sync" }) }),
5378
+ hfToken && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { onClick: () => refreshHF(), disabled: isHfRefreshing, className: "w-10 flex items-center justify-center text-white/20 hover:text-white/60 transition-colors disabled:opacity-30", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: `material-symbols-outlined text-[18px]${isHfRefreshing ? " animate-spin" : ""}`, children: "sync" }) }),
4893
5379
  /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("button", { onClick: () => setIsRightCollapsed(!isRightCollapsed), className: "w-10 flex items-center justify-center text-white/20 hover:text-white", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "material-symbols-outlined text-[18px]", children: isRightCollapsed ? "chevron_left" : "chevron_right" }) })
4894
5380
  ] }),
4895
5381
  !isRightCollapsed && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex-1 overflow-hidden relative", children: [
@@ -4928,6 +5414,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4928
5414
  activeTab === "sync" && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
4929
5415
  ProjectSyncTab,
4930
5416
  {
5417
+ topSlot: syncTopSlot,
4931
5418
  onProjectExport: handleProjectExport,
4932
5419
  onProjectImport: (f) => handleProjectImport(f),
4933
5420
  onWorkspaceImport: handleWorkspaceImport,
@@ -4954,6 +5441,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4954
5441
  }
4955
5442
 
4956
5443
  // src/components/FaApp.tsx
5444
+ var import_react23 = require("react");
4957
5445
  var import_jsx_runtime21 = require("react/jsx-runtime");
4958
5446
  function FaApp({
4959
5447
  onGenerateImage,
@@ -4965,12 +5453,22 @@ function FaApp({
4965
5453
  onFlowUpload: _onFlowUpload,
4966
5454
  onFlowMediaUpload: _onFlowMediaUpload,
4967
5455
  libToken,
5456
+ allowDevNamespace,
5457
+ serverBaseUrl,
4968
5458
  onFetchServerProjects,
4969
5459
  onServerSave,
4970
5460
  onServerLoad,
4971
5461
  onServerDelete,
4972
5462
  buildInfo
4973
5463
  }) {
5464
+ const [hfNamespace, setHfNamespace] = (0, import_react23.useState)(void 0);
5465
+ (0, import_react23.useEffect)(() => {
5466
+ if (!serverBaseUrl) return;
5467
+ fetch(`${serverBaseUrl}/api/status`).then((r) => r.json()).then((d) => {
5468
+ if (typeof d.hfNamespace === "string") setHfNamespace(d.hfNamespace);
5469
+ }).catch(() => {
5470
+ });
5471
+ }, [serverBaseUrl]);
4974
5472
  const wrappedPrompt = async (text, options) => {
4975
5473
  const result = await onGeneratePrompt(text, options);
4976
5474
  return result.text;
@@ -4983,6 +5481,8 @@ function FaApp({
4983
5481
  onDownload,
4984
5482
  onSelectMedia,
4985
5483
  initialHfToken: libToken ? libToken.startsWith("hf_") ? libToken : `hf_${libToken}` : void 0,
5484
+ hfNamespace,
5485
+ allowDevNamespace: !hfNamespace && allowDevNamespace,
4986
5486
  onFetchServerProjects,
4987
5487
  onServerSave,
4988
5488
  onServerLoad,
@@ -4994,7 +5494,8 @@ function FaApp({
4994
5494
 
4995
5495
  // src/index.ts
4996
5496
  init_hfStateService();
4997
- var LIB_VERSION = "1.3.18";
5497
+ init_hfStateService();
5498
+ var LIB_VERSION = "2.0.12";
4998
5499
  // Annotate the CommonJS export names for ESM import in node:
4999
5500
  0 && (module.exports = {
5000
5501
  AvatarArchitectApp,
@@ -5020,9 +5521,12 @@ var LIB_VERSION = "1.3.18";
5020
5521
  SectionLabel,
5021
5522
  SetupPanel,
5022
5523
  TagManagerPanel,
5524
+ applyEvent,
5525
+ applyEvents,
5023
5526
  autoLabel,
5024
5527
  buildBlendInstruction,
5025
5528
  buildCompareInstruction,
5529
+ buildDag,
5026
5530
  buildFallbackPrompt,
5027
5531
  buildGenerationPrompt,
5028
5532
  buildImageGenerationOptions,
@@ -5034,27 +5538,36 @@ var LIB_VERSION = "1.3.18";
5034
5538
  cleanAiResponse,
5035
5539
  createFlowServices,
5036
5540
  exportProjectToZip,
5541
+ findForks,
5542
+ findTips,
5037
5543
  formatTreeToMarkdown,
5038
5544
  frameToGeneration,
5039
5545
  getFormattedTimestamp,
5040
5546
  getHFToken,
5547
+ getSessionClientId,
5041
5548
  groupGenerationsToLabItems,
5549
+ hfBatchArchive,
5550
+ hfBootstrapFromLegacy,
5042
5551
  hfDeleteProject,
5043
5552
  hfDownloadProject,
5553
+ hfListDir,
5044
5554
  hfListProjects,
5045
5555
  hfLoadImageAsBase64,
5046
- hfLoadMetadata,
5047
- hfLoadTags,
5048
- hfSaveMetadata,
5049
- hfSaveTags,
5050
5556
  hfUploadImage,
5051
5557
  hfUploadProjectForm,
5558
+ hfUploadSmallFile,
5052
5559
  importProjectFromZip,
5053
5560
  injectXMPMetadata,
5054
5561
  interpretSdkError,
5562
+ loadHFState,
5563
+ loadPendingEvents,
5055
5564
  parsePromptFile,
5056
5565
  parsePromptResponse,
5057
5566
  setHFToken,
5567
+ topoSort,
5568
+ tsFromEventPath,
5569
+ useHFState,
5058
5570
  useKeyboardNavigation,
5059
- useOnClickOutside
5571
+ useOnClickOutside,
5572
+ writeHFEvent
5060
5573
  });