@rslsp1/fa-app-tools 1.2.4 → 1.2.5

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
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,125 +30,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
30
- // src/index.ts
31
- var index_exports = {};
32
- __export(index_exports, {
33
- AvatarArchitectApp: () => AvatarArchitectApp,
34
- CollapsibleCard: () => CollapsibleCard,
35
- CompactDropdown: () => CompactDropdown,
36
- FaToolsBadge: () => FaToolsBadge,
37
- GLOBAL_STYLES: () => GLOBAL_STYLES,
38
- HistoryPanel: () => HistoryPanel,
39
- InspectPanel: () => InspectPanel,
40
- LIB_VERSION: () => LIB_VERSION,
41
- LabBlend: () => LabBlend,
42
- LabCompare: () => LabCompare,
43
- LabImagePicker: () => LabImagePicker,
44
- LabLoop: () => LabLoop,
45
- LabRemix: () => LabRemix,
46
- LabsTab: () => LabsTab,
47
- ListView: () => ListView,
48
- MediaLibrary: () => MediaLibrary,
49
- PillButton: () => PillButton,
50
- ProjectSyncTab: () => ProjectSyncTab,
51
- PromptTab: () => PromptTab,
52
- SectionLabel: () => SectionLabel,
53
- SetupPanel: () => SetupPanel,
54
- TagManagerPanel: () => TagManagerPanel,
55
- autoLabel: () => autoLabel,
56
- buildBlendInstruction: () => buildBlendInstruction,
57
- buildCompareInstruction: () => buildCompareInstruction,
58
- buildFallbackPrompt: () => buildFallbackPrompt,
59
- buildGenerationPrompt: () => buildGenerationPrompt,
60
- buildImageGenerationOptions: () => buildImageGenerationOptions,
61
- buildLoopInstruction: () => buildLoopInstruction,
62
- buildPromptTabPayload: () => buildPromptTabPayload,
63
- buildReferenceImageMediaIds: () => buildReferenceImageMediaIds,
64
- buildRemixInstruction: () => buildRemixInstruction,
65
- buildScanInstruction: () => buildScanInstruction,
66
- cleanAiResponse: () => cleanAiResponse,
67
- createFlowServices: () => createFlowServices,
68
- exportProjectToZip: () => exportProjectToZip,
69
- formatTreeToMarkdown: () => formatTreeToMarkdown,
70
- frameToGeneration: () => frameToGeneration,
71
- getFormattedTimestamp: () => getFormattedTimestamp,
72
- groupGenerationsToLabItems: () => groupGenerationsToLabItems,
73
- importProjectFromZip: () => importProjectFromZip,
74
- injectXMPMetadata: () => injectXMPMetadata,
75
- interpretSdkError: () => interpretSdkError,
76
- parsePromptFile: () => parsePromptFile,
77
- parsePromptResponse: () => parsePromptResponse,
78
- useKeyboardNavigation: () => useKeyboardNavigation,
79
- useOnClickOutside: () => useOnClickOutside
80
- });
81
- module.exports = __toCommonJS(index_exports);
82
-
83
- // src/hooks/useOnClickOutside.ts
84
- var import_react = require("react");
85
- function useOnClickOutside(ref, handler) {
86
- (0, import_react.useEffect)(() => {
87
- const listener = (event) => {
88
- if (!ref.current || ref.current.contains(event.target)) return;
89
- handler(event);
90
- };
91
- document.addEventListener("mousedown", listener);
92
- document.addEventListener("touchstart", listener);
93
- return () => {
94
- document.removeEventListener("mousedown", listener);
95
- document.removeEventListener("touchstart", listener);
96
- };
97
- }, [ref, handler]);
98
- }
99
-
100
- // src/hooks/useKeyboardNavigation.ts
101
- var import_react2 = require("react");
102
- function useKeyboardNavigation(history, currentResult, setCurrentResult) {
103
- (0, import_react2.useEffect)(() => {
104
- const handleKeyDown = (e) => {
105
- if (e.target instanceof HTMLTextAreaElement || e.target instanceof HTMLInputElement) return;
106
- if (!currentResult || history.length === 0) return;
107
- const currentIndex = history.findIndex((h) => h.id === currentResult.id);
108
- if (e.key === "ArrowRight" && currentIndex < history.length - 1) {
109
- setCurrentResult(history[currentIndex + 1]);
110
- } else if (e.key === "ArrowLeft" && currentIndex > 0) {
111
- setCurrentResult(history[currentIndex - 1]);
112
- }
113
- };
114
- window.addEventListener("keydown", handleKeyDown);
115
- return () => window.removeEventListener("keydown", handleKeyDown);
116
- }, [currentResult, history, setCurrentResult]);
117
- }
118
-
119
- // src/lib/utils.ts
120
- var getFormattedTimestamp = () => {
121
- const d = /* @__PURE__ */ new Date();
122
- const pad = (n) => n.toString().padStart(2, "0");
123
- return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
124
- };
125
- var GLOBAL_STYLES = `
126
- .dark-scrollbar::-webkit-scrollbar { width: 4px; height: 4px; }
127
- .dark-scrollbar::-webkit-scrollbar-track { background: transparent; }
128
- .dark-scrollbar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }
129
- html, body, #root { margin: 0; padding: 0; width: 100%; height: 100%; background: #0e0e0e; font-family: 'Google Sans Text', sans-serif; overflow: hidden; }
130
- @keyframes prompt-shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }
131
- .prompt-loading { background: linear-gradient(90deg, rgba(255,255,255,0.02) 25%, rgba(255,255,255,0.05) 50%, rgba(255,255,255,0.02) 75%); background-size: 200% 100%; animation: prompt-shimmer 2s infinite linear; }
132
- @keyframes dropdown-enter { from { opacity: 0; transform: scale(0.95) translateY(-5px); } to { opacity: 1; transform: scale(1) translateY(0); } }
133
- .animate-dropdown { animation: dropdown-enter 0.1s ease-out forwards; }
134
- `;
135
-
136
33
  // src/lib/metadata.ts
137
- var crcTable = (() => {
138
- let c;
139
- const table = new Uint32Array(256);
140
- for (let n = 0; n < 256; n++) {
141
- c = n;
142
- for (let k = 0; k < 8; k++) {
143
- c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
144
- }
145
- table[n] = c;
146
- }
147
- return table;
148
- })();
149
34
  function calculateCRC(bytes) {
150
35
  let crc = 4294967295;
151
36
  for (let i = 0; i < bytes.length; i++) {
@@ -251,9 +136,31 @@ function injectXMPMetadata(base64, prompt, seed, model, id, tags = [], hierarchy
251
136
  return base64;
252
137
  }
253
138
  }
139
+ var crcTable;
140
+ var init_metadata = __esm({
141
+ "src/lib/metadata.ts"() {
142
+ "use strict";
143
+ crcTable = (() => {
144
+ let c;
145
+ const table = new Uint32Array(256);
146
+ for (let n = 0; n < 256; n++) {
147
+ c = n;
148
+ for (let k = 0; k < 8; k++) {
149
+ c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
150
+ }
151
+ table[n] = c;
152
+ }
153
+ return table;
154
+ })();
155
+ }
156
+ });
254
157
 
255
158
  // src/lib/project.ts
256
- var import_jszip = __toESM(require("jszip"));
159
+ var project_exports = {};
160
+ __export(project_exports, {
161
+ exportProjectToZip: () => exportProjectToZip,
162
+ importProjectFromZip: () => importProjectFromZip
163
+ });
257
164
  function generateMarkdownReport(nodes, history) {
258
165
  let md = "# Avatar Architect - Projekt Report\n\n";
259
166
  md += `Exportiert: ${(/* @__PURE__ */ new Date()).toLocaleString()}
@@ -343,6 +250,230 @@ async function importProjectFromZip(file) {
343
250
  }
344
251
  return data;
345
252
  }
253
+ var import_jszip;
254
+ var init_project = __esm({
255
+ "src/lib/project.ts"() {
256
+ "use strict";
257
+ import_jszip = __toESM(require("jszip"));
258
+ init_metadata();
259
+ }
260
+ });
261
+
262
+ // src/lib/hfStateService.ts
263
+ var hfStateService_exports = {};
264
+ __export(hfStateService_exports, {
265
+ HF_TOKEN_KEY: () => HF_TOKEN_KEY,
266
+ getHFToken: () => getHFToken,
267
+ hfDeleteProject: () => hfDeleteProject,
268
+ hfDownloadProject: () => hfDownloadProject,
269
+ hfListProjects: () => hfListProjects,
270
+ hfUploadProject: () => hfUploadProject,
271
+ setHFToken: () => setHFToken
272
+ });
273
+ function getHFToken() {
274
+ try {
275
+ return localStorage.getItem(HF_TOKEN_KEY);
276
+ } catch {
277
+ return null;
278
+ }
279
+ }
280
+ function setHFToken(token) {
281
+ try {
282
+ localStorage.setItem(HF_TOKEN_KEY, token);
283
+ } catch {
284
+ }
285
+ }
286
+ async function hfListProjects(token) {
287
+ const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/tree/main`, {
288
+ headers: { Authorization: `Bearer ${token}` }
289
+ });
290
+ if (!res.ok) throw new Error(`HF list failed: ${res.status} ${res.statusText}`);
291
+ const files = await res.json();
292
+ return files.filter((f) => f.type === "file" && f.path.endsWith(".zip")).map((f) => ({
293
+ id: f.path.replace(/\.zip$/, ""),
294
+ name: f.path.replace(/\.zip$/, ""),
295
+ path: f.path,
296
+ size: f.size,
297
+ isLfs: !!f.lfs
298
+ }));
299
+ }
300
+ async function hfDownloadProject(path, token) {
301
+ const res = await fetch(
302
+ `${HF_BASE}/datasets/${HF_REPO}/resolve/main/${path}?download=true`,
303
+ { headers: { Authorization: `Bearer ${token}` } }
304
+ );
305
+ if (!res.ok) throw new Error(`HF download failed: ${res.status} ${res.statusText}`);
306
+ const blob = await res.blob();
307
+ return new File([blob], path, { type: "application/zip" });
308
+ }
309
+ async function hfUploadProject(zipBase64, name, token) {
310
+ const filename = name.endsWith(".zip") ? name : `${name}.zip`;
311
+ const binary = atob(zipBase64);
312
+ const bytes = new Uint8Array(binary.length);
313
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
314
+ const hashBuffer = await crypto.subtle.digest("SHA-256", bytes);
315
+ const oid = Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
316
+ const size = bytes.length;
317
+ const preRes = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/preupload/main`, {
318
+ method: "POST",
319
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
320
+ body: JSON.stringify({ files: [{ path: filename, sample: btoa(String.fromCharCode(...bytes.slice(0, 128))) }] })
321
+ });
322
+ if (!preRes.ok) throw new Error(`HF preupload failed: ${preRes.status} ${preRes.statusText}`);
323
+ const preData = await preRes.json();
324
+ const fileInfo = preData.files?.[0];
325
+ if (fileInfo?.uploadMode === "lfs" && fileInfo?.uploadUrl) {
326
+ const uploadRes = await fetch(fileInfo.uploadUrl, {
327
+ method: "PUT",
328
+ headers: { "Content-Type": "application/octet-stream", ...fileInfo.header || {} },
329
+ body: bytes
330
+ });
331
+ if (!uploadRes.ok) throw new Error(`HF LFS upload failed: ${uploadRes.status}`);
332
+ }
333
+ const commitRes = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
334
+ method: "POST",
335
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
336
+ body: [
337
+ JSON.stringify({ key: "header", value: { summary: `Upload ${filename}`, description: "" } }),
338
+ JSON.stringify({ key: "lfsFile", value: { path: filename, algo: "sha256", oid, size } })
339
+ ].join("\n")
340
+ });
341
+ if (!commitRes.ok) {
342
+ const err = await commitRes.text();
343
+ throw new Error(`HF commit failed: ${commitRes.status} \u2014 ${err}`);
344
+ }
345
+ return { id: filename.replace(/\.zip$/, ""), name: filename.replace(/\.zip$/, ""), path: filename, size, isLfs: true };
346
+ }
347
+ async function hfDeleteProject(path, token) {
348
+ const res = await fetch(`${HF_BASE}/api/datasets/${HF_REPO}/commit/main`, {
349
+ method: "POST",
350
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/x-ndjson" },
351
+ body: [
352
+ JSON.stringify({ key: "header", value: { summary: `Delete ${path}`, description: "" } }),
353
+ JSON.stringify({ key: "deletedFile", value: { path } })
354
+ ].join("\n")
355
+ });
356
+ if (!res.ok) throw new Error(`HF delete failed: ${res.status} ${res.statusText}`);
357
+ }
358
+ var HF_BASE, HF_REPO, HF_TOKEN_KEY;
359
+ var init_hfStateService = __esm({
360
+ "src/lib/hfStateService.ts"() {
361
+ "use strict";
362
+ HF_BASE = "https://huggingface.co";
363
+ HF_REPO = "RolandSch/fa-app-state";
364
+ HF_TOKEN_KEY = "hf-token";
365
+ }
366
+ });
367
+
368
+ // src/index.ts
369
+ var index_exports = {};
370
+ __export(index_exports, {
371
+ AvatarArchitectApp: () => AvatarArchitectApp,
372
+ CollapsibleCard: () => CollapsibleCard,
373
+ CompactDropdown: () => CompactDropdown,
374
+ FaToolsBadge: () => FaToolsBadge,
375
+ GLOBAL_STYLES: () => GLOBAL_STYLES,
376
+ HistoryPanel: () => HistoryPanel,
377
+ InspectPanel: () => InspectPanel,
378
+ LIB_VERSION: () => LIB_VERSION,
379
+ LabBlend: () => LabBlend,
380
+ LabCompare: () => LabCompare,
381
+ LabImagePicker: () => LabImagePicker,
382
+ LabLoop: () => LabLoop,
383
+ LabRemix: () => LabRemix,
384
+ LabsTab: () => LabsTab,
385
+ ListView: () => ListView,
386
+ MediaLibrary: () => MediaLibrary,
387
+ PillButton: () => PillButton,
388
+ ProjectSyncTab: () => ProjectSyncTab,
389
+ PromptTab: () => PromptTab,
390
+ SectionLabel: () => SectionLabel,
391
+ SetupPanel: () => SetupPanel,
392
+ TagManagerPanel: () => TagManagerPanel,
393
+ autoLabel: () => autoLabel,
394
+ buildBlendInstruction: () => buildBlendInstruction,
395
+ buildCompareInstruction: () => buildCompareInstruction,
396
+ buildFallbackPrompt: () => buildFallbackPrompt,
397
+ buildGenerationPrompt: () => buildGenerationPrompt,
398
+ buildImageGenerationOptions: () => buildImageGenerationOptions,
399
+ buildLoopInstruction: () => buildLoopInstruction,
400
+ buildPromptTabPayload: () => buildPromptTabPayload,
401
+ buildReferenceImageMediaIds: () => buildReferenceImageMediaIds,
402
+ buildRemixInstruction: () => buildRemixInstruction,
403
+ buildScanInstruction: () => buildScanInstruction,
404
+ cleanAiResponse: () => cleanAiResponse,
405
+ createFlowServices: () => createFlowServices,
406
+ exportProjectToZip: () => exportProjectToZip,
407
+ formatTreeToMarkdown: () => formatTreeToMarkdown,
408
+ frameToGeneration: () => frameToGeneration,
409
+ getFormattedTimestamp: () => getFormattedTimestamp,
410
+ groupGenerationsToLabItems: () => groupGenerationsToLabItems,
411
+ importProjectFromZip: () => importProjectFromZip,
412
+ injectXMPMetadata: () => injectXMPMetadata,
413
+ interpretSdkError: () => interpretSdkError,
414
+ parsePromptFile: () => parsePromptFile,
415
+ parsePromptResponse: () => parsePromptResponse,
416
+ useKeyboardNavigation: () => useKeyboardNavigation,
417
+ useOnClickOutside: () => useOnClickOutside
418
+ });
419
+ module.exports = __toCommonJS(index_exports);
420
+
421
+ // src/hooks/useOnClickOutside.ts
422
+ var import_react = require("react");
423
+ function useOnClickOutside(ref, handler) {
424
+ (0, import_react.useEffect)(() => {
425
+ const listener = (event) => {
426
+ if (!ref.current || ref.current.contains(event.target)) return;
427
+ handler(event);
428
+ };
429
+ document.addEventListener("mousedown", listener);
430
+ document.addEventListener("touchstart", listener);
431
+ return () => {
432
+ document.removeEventListener("mousedown", listener);
433
+ document.removeEventListener("touchstart", listener);
434
+ };
435
+ }, [ref, handler]);
436
+ }
437
+
438
+ // src/hooks/useKeyboardNavigation.ts
439
+ var import_react2 = require("react");
440
+ function useKeyboardNavigation(history, currentResult, setCurrentResult) {
441
+ (0, import_react2.useEffect)(() => {
442
+ const handleKeyDown = (e) => {
443
+ if (e.target instanceof HTMLTextAreaElement || e.target instanceof HTMLInputElement) return;
444
+ if (!currentResult || history.length === 0) return;
445
+ const currentIndex = history.findIndex((h) => h.id === currentResult.id);
446
+ if (e.key === "ArrowRight" && currentIndex < history.length - 1) {
447
+ setCurrentResult(history[currentIndex + 1]);
448
+ } else if (e.key === "ArrowLeft" && currentIndex > 0) {
449
+ setCurrentResult(history[currentIndex - 1]);
450
+ }
451
+ };
452
+ window.addEventListener("keydown", handleKeyDown);
453
+ return () => window.removeEventListener("keydown", handleKeyDown);
454
+ }, [currentResult, history, setCurrentResult]);
455
+ }
456
+
457
+ // src/lib/utils.ts
458
+ var getFormattedTimestamp = () => {
459
+ const d = /* @__PURE__ */ new Date();
460
+ const pad = (n) => n.toString().padStart(2, "0");
461
+ return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
462
+ };
463
+ var GLOBAL_STYLES = `
464
+ .dark-scrollbar::-webkit-scrollbar { width: 4px; height: 4px; }
465
+ .dark-scrollbar::-webkit-scrollbar-track { background: transparent; }
466
+ .dark-scrollbar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }
467
+ html, body, #root { margin: 0; padding: 0; width: 100%; height: 100%; background: #0e0e0e; font-family: 'Google Sans Text', sans-serif; overflow: hidden; }
468
+ @keyframes prompt-shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }
469
+ .prompt-loading { background: linear-gradient(90deg, rgba(255,255,255,0.02) 25%, rgba(255,255,255,0.05) 50%, rgba(255,255,255,0.02) 75%); background-size: 200% 100%; animation: prompt-shimmer 2s infinite linear; }
470
+ @keyframes dropdown-enter { from { opacity: 0; transform: scale(0.95) translateY(-5px); } to { opacity: 1; transform: scale(1) translateY(0); } }
471
+ .animate-dropdown { animation: dropdown-enter 0.1s ease-out forwards; }
472
+ `;
473
+
474
+ // src/index.ts
475
+ init_metadata();
476
+ init_project();
346
477
 
347
478
  // src/lib/aiHelpers.ts
348
479
  function buildGenerationPrompt(hierarchyText, mode = "creative") {
@@ -1591,6 +1722,7 @@ var PromptTab = ({
1591
1722
 
1592
1723
  // src/components/ProjectSyncTab.tsx
1593
1724
  var import_react13 = require("react");
1725
+ init_hfStateService();
1594
1726
  var import_jsx_runtime12 = require("react/jsx-runtime");
1595
1727
  var ProjectSyncTab = ({
1596
1728
  onProjectExport,
@@ -1603,7 +1735,10 @@ var ProjectSyncTab = ({
1603
1735
  onServerDelete,
1604
1736
  onRefreshServerProjects,
1605
1737
  onComputeSyncDiff,
1606
- onExecuteSync
1738
+ onExecuteSync,
1739
+ hfToken,
1740
+ onHfLoad,
1741
+ onProjectExportBase64
1607
1742
  }) => {
1608
1743
  const projectInputRef = (0, import_react13.useRef)(null);
1609
1744
  const workspaceInputRef = (0, import_react13.useRef)(null);
@@ -1658,6 +1793,25 @@ var ProjectSyncTab = ({
1658
1793
  });
1659
1794
  };
1660
1795
  const isWorking = projectActionState === "working" || projectActionState === "working-full";
1796
+ const [hfProjects, setHfProjects] = (0, import_react13.useState)([]);
1797
+ const [hfLoading, setHfLoading] = (0, import_react13.useState)(false);
1798
+ const [hfSaving, setHfSaving] = (0, import_react13.useState)(false);
1799
+ const [hfError, setHfError] = (0, import_react13.useState)(null);
1800
+ const [hfSaveName, setHfSaveName] = (0, import_react13.useState)("");
1801
+ const loadHfProjects = async (token) => {
1802
+ setHfLoading(true);
1803
+ setHfError(null);
1804
+ try {
1805
+ setHfProjects(await hfListProjects(token));
1806
+ } catch (e) {
1807
+ setHfError(e.message);
1808
+ } finally {
1809
+ setHfLoading(false);
1810
+ }
1811
+ };
1812
+ (0, import_react13.useEffect)(() => {
1813
+ if (hfToken) loadHfProjects(hfToken);
1814
+ }, [hfToken]);
1661
1815
  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: [
1662
1816
  (onProjectExport || onProjectImport || onWorkspaceImport) && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-4", children: [
1663
1817
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SectionLabel, { children: "Projekt-ZIP" }),
@@ -1748,6 +1902,94 @@ var ProjectSyncTab = ({
1748
1902
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { onClick: () => onServerDelete?.(p.id), className: "w-8 h-8 flex items-center justify-center text-white/20 active:text-red-400 transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "material-symbols-outlined text-[18px]", children: "delete" }) })
1749
1903
  ] }, p.id)) })
1750
1904
  ] }),
1905
+ hfToken && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-4", children: [
1906
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center justify-between", children: [
1907
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SectionLabel, { children: "Hugging Face" }),
1908
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { onClick: () => loadHfProjects(hfToken), className: "text-white/30 hover:text-white/60 transition", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: `material-symbols-outlined text-[18px]${hfLoading ? " animate-spin" : ""}`, children: "refresh" }) })
1909
+ ] }),
1910
+ onProjectExportBase64 && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex gap-2", children: [
1911
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1912
+ "input",
1913
+ {
1914
+ type: "text",
1915
+ value: hfSaveName,
1916
+ onChange: (e) => setHfSaveName(e.target.value),
1917
+ placeholder: "Name (optional)",
1918
+ className: "flex-1 bg-white/5 border border-white/10 rounded-xl px-3 text-[12px] text-white/70 outline-none placeholder:text-white/20",
1919
+ style: { height: 40 }
1920
+ }
1921
+ ),
1922
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1923
+ PillButton,
1924
+ {
1925
+ variant: "filled",
1926
+ icon: "cloud_upload",
1927
+ loading: hfSaving,
1928
+ onClick: async () => {
1929
+ if (!onProjectExportBase64) return;
1930
+ setHfSaving(true);
1931
+ setHfError(null);
1932
+ try {
1933
+ const base64 = await onProjectExportBase64();
1934
+ const name = hfSaveName.trim() || `project_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", "_").replace(":", "")}`;
1935
+ await hfUploadProject(base64, name, hfToken);
1936
+ setHfSaveName("");
1937
+ await loadHfProjects(hfToken);
1938
+ } catch (e) {
1939
+ setHfError(e.message);
1940
+ } finally {
1941
+ setHfSaving(false);
1942
+ }
1943
+ },
1944
+ children: "Speichern"
1945
+ }
1946
+ )
1947
+ ] }),
1948
+ hfError && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-[10px] text-red-400 font-mono px-1", children: hfError }),
1949
+ !hfLoading && hfProjects.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-[10px] text-white/20 px-2", children: "Noch nichts auf HF gespeichert." }),
1950
+ hfProjects.map((p) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex items-center gap-2 px-3 py-2 rounded-xl bg-white/5 border border-white/5", children: [
1951
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex-1 min-w-0", children: [
1952
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "text-[11px] text-white/70 font-bold truncate", children: p.name }),
1953
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "text-[9px] text-white/25", children: [
1954
+ (p.size / 1024 / 1024).toFixed(1),
1955
+ " MB",
1956
+ p.isLfs ? " \xB7 LFS" : ""
1957
+ ] })
1958
+ ] }),
1959
+ onHfLoad && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1960
+ "button",
1961
+ {
1962
+ onClick: async () => {
1963
+ try {
1964
+ const { hfDownloadProject: hfDownloadProject2 } = await Promise.resolve().then(() => (init_hfStateService(), hfStateService_exports));
1965
+ const file = await hfDownloadProject2(p.path, hfToken);
1966
+ onHfLoad(file);
1967
+ } catch (e) {
1968
+ setHfError(e.message);
1969
+ }
1970
+ },
1971
+ className: "w-8 h-8 flex items-center justify-center text-white/30 active:text-white transition-colors",
1972
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "material-symbols-outlined text-[18px]", children: "cloud_download" })
1973
+ }
1974
+ ),
1975
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1976
+ "button",
1977
+ {
1978
+ onClick: async () => {
1979
+ if (!confirm(`"${p.name}" auf HF l\xF6schen?`)) return;
1980
+ try {
1981
+ await hfDeleteProject(p.path, hfToken);
1982
+ await loadHfProjects(hfToken);
1983
+ } catch (e) {
1984
+ setHfError(e.message);
1985
+ }
1986
+ },
1987
+ className: "w-8 h-8 flex items-center justify-center text-white/20 active:text-red-400 transition-colors",
1988
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "material-symbols-outlined text-[18px]", children: "delete" })
1989
+ }
1990
+ )
1991
+ ] }, p.id))
1992
+ ] }),
1751
1993
  onComputeSyncDiff && onExecuteSync && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: "flex flex-col gap-4", children: [
1752
1994
  /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SectionLabel, { children: "Sync" }),
1753
1995
  (syncState === "idle" || syncState === "computing") && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(PillButton, { variant: "outline", icon: "compare_arrows", loading: syncState === "computing", onClick: handleComputeDiff, children: "Diff berechnen" }),
@@ -1814,6 +2056,10 @@ var ProjectSyncTab = ({
1814
2056
  ] }) });
1815
2057
  };
1816
2058
 
2059
+ // src/components/AvatarArchitectApp.tsx
2060
+ init_project();
2061
+ init_metadata();
2062
+
1817
2063
  // src/lib/labHelpers.ts
1818
2064
  function groupGenerationsToLabItems(generations) {
1819
2065
  const map = /* @__PURE__ */ new Map();
@@ -3032,6 +3278,9 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3032
3278
  }
3033
3279
  });
3034
3280
  const [projectLoaded, setProjectLoaded] = (0, import_react21.useState)(false);
3281
+ const [hfToken, setHfToken] = (0, import_react21.useState)("");
3282
+ const [hfTokenInput, setHfTokenInput] = (0, import_react21.useState)("");
3283
+ const [isLoadingFromHF, setIsLoadingFromHF] = (0, import_react21.useState)(false);
3035
3284
  const wsInputRef = (0, import_react21.useRef)(null);
3036
3285
  const startApp = (choice) => {
3037
3286
  try {
@@ -3632,6 +3881,59 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3632
3881
  ),
3633
3882
  !projectLoaded && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-white/20 text-[10px] text-center", children: "Baum, Bilder und Einstellungen wiederherstellen" })
3634
3883
  ] }),
3884
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex flex-col items-center gap-2 w-full max-w-[280px]", children: [
3885
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex gap-2 w-full", children: [
3886
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3887
+ "input",
3888
+ {
3889
+ type: "password",
3890
+ value: hfTokenInput,
3891
+ onChange: (e) => setHfTokenInput(e.target.value),
3892
+ onKeyDown: (e) => e.key === "Enter" && hfTokenInput.trim() && setHfToken(hfTokenInput.trim()),
3893
+ placeholder: "HF Token (hf_\u2026)",
3894
+ className: "flex-1 rounded-xl px-3 text-[11px] font-mono outline-none",
3895
+ style: { height: 44, background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", color: "rgba(255,255,255,0.7)" }
3896
+ }
3897
+ ),
3898
+ hfTokenInput.trim() && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
3899
+ "button",
3900
+ {
3901
+ onClick: () => setHfToken(hfTokenInput.trim()),
3902
+ className: "px-3 rounded-xl text-[11px] font-bold text-white",
3903
+ style: { background: "#f59e0b", height: 44 },
3904
+ children: "OK"
3905
+ }
3906
+ )
3907
+ ] }),
3908
+ hfToken && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
3909
+ "button",
3910
+ {
3911
+ disabled: isLoadingFromHF,
3912
+ onClick: async () => {
3913
+ setIsLoadingFromHF(true);
3914
+ try {
3915
+ const { hfListProjects: hfListProjects2, hfDownloadProject: hfDownloadProject2 } = await Promise.resolve().then(() => (init_hfStateService(), hfStateService_exports));
3916
+ const projects = await hfListProjects2(hfToken);
3917
+ if (projects.length > 0) {
3918
+ const file = await hfDownloadProject2(projects[0].path, hfToken);
3919
+ await handleProjectImport(file);
3920
+ }
3921
+ } catch (e) {
3922
+ alert("HF Fehler: " + e.message);
3923
+ } finally {
3924
+ setIsLoadingFromHF(false);
3925
+ }
3926
+ },
3927
+ className: "w-full flex items-center justify-center gap-3 rounded-2xl font-bold text-[14px] uppercase tracking-wide text-white active:scale-95 transition-transform disabled:opacity-50",
3928
+ style: { height: 56, background: "#f59e0b" },
3929
+ children: [
3930
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: `material-symbols-outlined text-[22px]${isLoadingFromHF ? " animate-spin" : ""}`, children: isLoadingFromHF ? "sync" : "cloud_download" }),
3931
+ isLoadingFromHF ? "Laden\u2026" : "Von HF laden"
3932
+ ]
3933
+ }
3934
+ ),
3935
+ hfToken && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "text-white/20 text-[10px] text-center", children: "Letzten Stand von Hugging Face laden" })
3936
+ ] }),
3635
3937
  onFetchServerProjects && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex flex-col items-center gap-2 w-full max-w-[280px]", children: [
3636
3938
  /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)(
3637
3939
  "button",
@@ -3924,7 +4226,13 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3924
4226
  onServerDelete: onServerDelete ? handleServerDelete : void 0,
3925
4227
  onRefreshServerProjects: onFetchServerProjects ? fetchServerProjects : void 0,
3926
4228
  onComputeSyncDiff: onFetchServerProjects && onServerLoad && onServerSave ? handleComputeSyncDiff : void 0,
3927
- onExecuteSync: onFetchServerProjects && onServerLoad && onServerSave ? handleExecuteSync : void 0
4229
+ onExecuteSync: onFetchServerProjects && onServerLoad && onServerSave ? handleExecuteSync : void 0,
4230
+ hfToken: hfToken || void 0,
4231
+ onHfLoad: (f) => handleProjectImport(f),
4232
+ onProjectExportBase64: async () => {
4233
+ const { base64 } = await Promise.resolve().then(() => (init_project(), project_exports)).then((m) => m.exportProjectToZip(nodes, edges, history, galleryItems, { aspectRatio, selectedModel, seed, seedMode }, workspaceTags, recentLabItems.map((i) => i.id)));
4234
+ return base64;
4235
+ }
3928
4236
  }
3929
4237
  ),
3930
4238
  activeTab === "tags" && workspaceTags && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
@@ -4247,7 +4555,13 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4247
4555
  onServerDelete: onServerDelete ? handleServerDelete : void 0,
4248
4556
  onRefreshServerProjects: onFetchServerProjects ? fetchServerProjects : void 0,
4249
4557
  onComputeSyncDiff: onFetchServerProjects && onServerLoad && onServerSave ? handleComputeSyncDiff : void 0,
4250
- onExecuteSync: onFetchServerProjects && onServerLoad && onServerSave ? handleExecuteSync : void 0
4558
+ onExecuteSync: onFetchServerProjects && onServerLoad && onServerSave ? handleExecuteSync : void 0,
4559
+ hfToken: hfToken || void 0,
4560
+ onHfLoad: (f) => handleProjectImport(f),
4561
+ onProjectExportBase64: async () => {
4562
+ const { base64 } = await Promise.resolve().then(() => (init_project(), project_exports)).then((m) => m.exportProjectToZip(nodes, edges, history, galleryItems, { aspectRatio, selectedModel, seed, seedMode }, workspaceTags, recentLabItems.map((i) => i.id)));
4563
+ return base64;
4564
+ }
4251
4565
  }
4252
4566
  )
4253
4567
  ] })
@@ -4256,7 +4570,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4256
4570
  }
4257
4571
 
4258
4572
  // src/index.ts
4259
- var LIB_VERSION = "1.2.4";
4573
+ var LIB_VERSION = "1.2.5";
4260
4574
  // Annotate the CommonJS export names for ESM import in node:
4261
4575
  0 && (module.exports = {
4262
4576
  AvatarArchitectApp,