@rslsp1/fa-app-tools 1.2.4 → 1.2.6

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.mjs CHANGED
@@ -1,3 +1,14 @@
1
+ import {
2
+ exportProjectToZip,
3
+ importProjectFromZip,
4
+ injectXMPMetadata
5
+ } from "./chunk-UFXDXENC.mjs";
6
+ import {
7
+ hfDeleteProject,
8
+ hfListProjects,
9
+ hfUploadProject
10
+ } from "./chunk-3GEDIA7J.mjs";
11
+
1
12
  // src/hooks/useOnClickOutside.ts
2
13
  import { useEffect } from "react";
3
14
  function useOnClickOutside(ref, handler) {
@@ -51,217 +62,6 @@ var GLOBAL_STYLES = `
51
62
  .animate-dropdown { animation: dropdown-enter 0.1s ease-out forwards; }
52
63
  `;
53
64
 
54
- // src/lib/metadata.ts
55
- var crcTable = (() => {
56
- let c;
57
- const table = new Uint32Array(256);
58
- for (let n = 0; n < 256; n++) {
59
- c = n;
60
- for (let k = 0; k < 8; k++) {
61
- c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
62
- }
63
- table[n] = c;
64
- }
65
- return table;
66
- })();
67
- function calculateCRC(bytes) {
68
- let crc = 4294967295;
69
- for (let i = 0; i < bytes.length; i++) {
70
- crc = crcTable[(crc ^ bytes[i]) & 255] ^ crc >>> 8;
71
- }
72
- return (crc ^ 4294967295) >>> 0;
73
- }
74
- function indexOfBytes(buffer, search, start = 0) {
75
- for (let i = start; i <= buffer.length - search.length; i++) {
76
- let found = true;
77
- for (let j = 0; j < search.length; j++) {
78
- if (buffer[i + j] !== search[j]) {
79
- found = false;
80
- break;
81
- }
82
- }
83
- if (found) return i;
84
- }
85
- return -1;
86
- }
87
- function createXMPPacket(prompt, seed, model, id, tags = [], hierarchy) {
88
- const sanitize = (str) => (str || "").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
89
- const escPrompt = sanitize(prompt);
90
- const escModel = sanitize(model || "AI Model");
91
- const escHierarchy = sanitize(hierarchy || "");
92
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
93
- const allTags = [.../* @__PURE__ */ new Set([...tags, "Avatar Architect", escModel])];
94
- const rdfTags = allTags.map((t) => `<rdf:li>${sanitize(t)}</rdf:li>`).join("");
95
- const BOM = "\uFEFF";
96
- return `<?xpacket begin="${BOM}" id="W5M0MpCehiHzreSzNTczkc9d"?>
97
- <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c140">
98
- <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
99
- <rdf:Description rdf:about=""
100
- xmlns:xmp="http://ns.adobe.com/xap/1.0/"
101
- xmlns:dc="http://purl.org/dc/elements/1.1/"
102
- xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
103
- xmlns:ai="http://ns.flow.creative/ai/1.0/">
104
- <dc:format>image/png</dc:format>
105
- <dc:description><rdf:Alt><rdf:li xml:lang="x-default">${escPrompt}</rdf:li></rdf:Alt></dc:description>
106
- <dc:subject><rdf:Bag>${rdfTags}</rdf:Bag></dc:subject>
107
- <photoshop:Instructions>Seed: ${seed} | Model: ${escModel}</photoshop:Instructions>
108
- <xmp:CreateDate>${timestamp}</xmp:CreateDate>
109
- <ai:prompt>${escPrompt}</ai:prompt>
110
- <ai:seed>${seed ?? "0"}</ai:seed>
111
- <ai:model>${escModel}</ai:model>
112
- <ai:hierarchy>${escHierarchy}</ai:hierarchy>
113
- </rdf:Description>
114
- </rdf:RDF>
115
- </x:xmpmeta>${" ".repeat(1024)}<?xpacket end="w"?>`;
116
- }
117
- function bytesToBase64(bytes) {
118
- let binary = "";
119
- const len = bytes.byteLength;
120
- for (let i = 0; i < len; i++) binary += String.fromCharCode(bytes[i]);
121
- return btoa(binary);
122
- }
123
- function base64ToBytes(base64) {
124
- const clean = base64.includes(",") ? base64.split(",")[1] : base64.trim();
125
- try {
126
- const binary = atob(clean);
127
- const bytes = new Uint8Array(binary.length);
128
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
129
- return bytes;
130
- } catch (e) {
131
- return new Uint8Array(0);
132
- }
133
- }
134
- function injectXMPMetadata(base64, prompt, seed, model, id, tags = [], hierarchy) {
135
- try {
136
- const bytes = base64ToBytes(base64);
137
- if (bytes.length < 8 || bytes[0] !== 137 || bytes[1] !== 80) {
138
- console.warn("Metadaten-Injektion \xFCbersprungen: Datei ist kein PNG.");
139
- return base64;
140
- }
141
- const xmpData = createXMPPacket(prompt, seed, model, id, tags, hierarchy);
142
- const encoder = new TextEncoder();
143
- const keyword = encoder.encode("XML:com.adobe.xmp");
144
- const textBytes = encoder.encode(xmpData);
145
- const chunkData = new Uint8Array(keyword.length + 5 + textBytes.length);
146
- chunkData.set(keyword, 0);
147
- chunkData.set(textBytes, keyword.length + 5);
148
- const type = encoder.encode("iTXt");
149
- const crcInput = new Uint8Array(type.length + chunkData.length);
150
- crcInput.set(type, 0);
151
- crcInput.set(chunkData, type.length);
152
- const crc = calculateCRC(crcInput);
153
- const fullChunk = new Uint8Array(4 + 4 + chunkData.length + 4);
154
- const view = new DataView(fullChunk.buffer);
155
- view.setUint32(0, chunkData.length);
156
- fullChunk.set(type, 4);
157
- fullChunk.set(chunkData, 8);
158
- view.setUint32(8 + chunkData.length, crc);
159
- const IDAT = encoder.encode("IDAT");
160
- const idatPos = indexOfBytes(bytes, IDAT);
161
- const insertPos = idatPos !== -1 ? idatPos - 4 : 8;
162
- const result = new Uint8Array(bytes.length + fullChunk.length);
163
- result.set(bytes.slice(0, insertPos), 0);
164
- result.set(fullChunk, insertPos);
165
- result.set(bytes.slice(insertPos), insertPos + fullChunk.length);
166
- return bytesToBase64(result);
167
- } catch (e) {
168
- console.error("Fehler beim Einbetten der Metadaten:", e);
169
- return base64;
170
- }
171
- }
172
-
173
- // src/lib/project.ts
174
- import JSZip from "jszip";
175
- function generateMarkdownReport(nodes, history) {
176
- let md = "# Avatar Architect - Projekt Report\n\n";
177
- md += `Exportiert: ${(/* @__PURE__ */ new Date()).toLocaleString()}
178
-
179
- `;
180
- md += "## Hierarchie\n";
181
- nodes.forEach((n) => {
182
- md += `- **${n.data?.label || "Unbenannt"}**
183
- `;
184
- });
185
- md += "\n## Historie der Generationen\n";
186
- history.forEach((h, i) => {
187
- md += `### ${i + 1}. Generation (${h.model || "AI"})
188
- `;
189
- md += `- **Zeitstempel**: ${new Date(h.timestamp).toLocaleString()}
190
- `;
191
- md += `- **Seed**: ${h.seed}
192
- `;
193
- md += `- **Prompt**:
194
- > ${h.prompt}
195
-
196
- `;
197
- });
198
- return md;
199
- }
200
- async function exportProjectToZip(nodes, edges, history, galleryItems, settings, workspaceTags, recentLabItemIds) {
201
- const zip = new JSZip();
202
- const projectData = {
203
- nodes,
204
- edges,
205
- history: history.map((h) => ({ ...h, base64: void 0 })),
206
- galleryItems: galleryItems.map((g) => ({ ...g, base64: void 0 })),
207
- settings,
208
- workspaceTags: workspaceTags || null,
209
- recentLabItemIds: recentLabItemIds || [],
210
- version: "1.3.0"
211
- };
212
- zip.file("project.json", JSON.stringify(projectData, null, 2));
213
- zip.file("projekt_bericht.md", generateMarkdownReport(nodes, history));
214
- const imgFolder = zip.folder("images");
215
- if (imgFolder) {
216
- const allItems = [...history, ...galleryItems];
217
- for (const item of allItems) {
218
- if (item.base64) {
219
- try {
220
- const cleanB64 = item.base64.includes(",") ? item.base64.split(",")[1] : item.base64;
221
- const withMeta = injectXMPMetadata(cleanB64, item.prompt || "", item.seed, item.model, item.id, item.tags || []);
222
- imgFolder.file(`${item.id}.png`, withMeta, { base64: true });
223
- } catch (e) {
224
- console.error("Fehler beim Packen eines Bildes:", e);
225
- }
226
- }
227
- }
228
- }
229
- const blob = await zip.generateAsync({ type: "blob", compression: "STORE" });
230
- return blobToBase64(blob);
231
- }
232
- async function blobToBase64(blob) {
233
- return new Promise((resolve, reject) => {
234
- const reader = new FileReader();
235
- reader.onloadend = () => {
236
- const result = reader.result;
237
- resolve({ base64: result.split(",")[1] });
238
- };
239
- reader.onerror = reject;
240
- reader.readAsDataURL(blob);
241
- });
242
- }
243
- async function importProjectFromZip(file) {
244
- const zip = await JSZip.loadAsync(file);
245
- const jsonStr = await zip.file("project.json")?.async("string");
246
- if (!jsonStr) throw new Error("Keine project.json gefunden.");
247
- const data = JSON.parse(jsonStr);
248
- const imgFolder = zip.folder("images");
249
- if (imgFolder) {
250
- const loadImgs = async (items) => {
251
- if (!items) return [];
252
- return Promise.all(items.map(async (h) => {
253
- const imgFile = zip.file(`images/${h.id}.png`);
254
- if (!imgFile) return h;
255
- const b64 = await imgFile.async("base64");
256
- return { ...h, base64: `data:image/png;base64,${b64}` };
257
- }));
258
- };
259
- data.history = await loadImgs(data.history || []);
260
- data.galleryItems = await loadImgs(data.galleryItems || []);
261
- }
262
- return data;
263
- }
264
-
265
65
  // src/lib/aiHelpers.ts
266
66
  function buildGenerationPrompt(hierarchyText, mode = "creative") {
267
67
  const safe = hierarchyText.length > 2500 ? hierarchyText.slice(0, 2500) + "..." : hierarchyText;
@@ -984,7 +784,7 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
984
784
  }
985
785
 
986
786
  // src/components/AvatarArchitectApp.tsx
987
- import { useState as useState14, useCallback, useMemo as useMemo2, useEffect as useEffect4, useRef as useRef6 } from "react";
787
+ import { useState as useState14, useCallback, useMemo as useMemo2, useEffect as useEffect5, useRef as useRef6 } from "react";
988
788
 
989
789
  // src/components/PromptTab.tsx
990
790
  import { useRef as useRef4, useState as useState5 } from "react";
@@ -1508,7 +1308,7 @@ var PromptTab = ({
1508
1308
  };
1509
1309
 
1510
1310
  // src/components/ProjectSyncTab.tsx
1511
- import { useRef as useRef5, useState as useState6 } from "react";
1311
+ import { useRef as useRef5, useState as useState6, useEffect as useEffect4 } from "react";
1512
1312
  import { Fragment as Fragment3, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1513
1313
  var ProjectSyncTab = ({
1514
1314
  onProjectExport,
@@ -1521,7 +1321,10 @@ var ProjectSyncTab = ({
1521
1321
  onServerDelete,
1522
1322
  onRefreshServerProjects,
1523
1323
  onComputeSyncDiff,
1524
- onExecuteSync
1324
+ onExecuteSync,
1325
+ hfToken,
1326
+ onHfLoad,
1327
+ onProjectExportBase64
1525
1328
  }) => {
1526
1329
  const projectInputRef = useRef5(null);
1527
1330
  const workspaceInputRef = useRef5(null);
@@ -1576,6 +1379,25 @@ var ProjectSyncTab = ({
1576
1379
  });
1577
1380
  };
1578
1381
  const isWorking = projectActionState === "working" || projectActionState === "working-full";
1382
+ const [hfProjects, setHfProjects] = useState6([]);
1383
+ const [hfLoading, setHfLoading] = useState6(false);
1384
+ const [hfSaving, setHfSaving] = useState6(false);
1385
+ const [hfError, setHfError] = useState6(null);
1386
+ const [hfSaveName, setHfSaveName] = useState6("");
1387
+ const loadHfProjects = async (token) => {
1388
+ setHfLoading(true);
1389
+ setHfError(null);
1390
+ try {
1391
+ setHfProjects(await hfListProjects(token));
1392
+ } catch (e) {
1393
+ setHfError(e.message);
1394
+ } finally {
1395
+ setHfLoading(false);
1396
+ }
1397
+ };
1398
+ useEffect4(() => {
1399
+ if (hfToken) loadHfProjects(hfToken);
1400
+ }, [hfToken]);
1579
1401
  return /* @__PURE__ */ jsx12("div", { className: "absolute inset-0 overflow-y-auto dark-scrollbar", children: /* @__PURE__ */ jsxs10("div", { className: "p-6 flex flex-col gap-8", children: [
1580
1402
  (onProjectExport || onProjectImport || onWorkspaceImport) && /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-4", children: [
1581
1403
  /* @__PURE__ */ jsx12(SectionLabel, { children: "Projekt-ZIP" }),
@@ -1666,6 +1488,94 @@ var ProjectSyncTab = ({
1666
1488
  /* @__PURE__ */ jsx12("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__ */ jsx12("span", { className: "material-symbols-outlined text-[18px]", children: "delete" }) })
1667
1489
  ] }, p.id)) })
1668
1490
  ] }),
1491
+ hfToken && /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-4", children: [
1492
+ /* @__PURE__ */ jsxs10("div", { className: "flex items-center justify-between", children: [
1493
+ /* @__PURE__ */ jsx12(SectionLabel, { children: "Hugging Face" }),
1494
+ /* @__PURE__ */ jsx12("button", { onClick: () => loadHfProjects(hfToken), className: "text-white/30 hover:text-white/60 transition", children: /* @__PURE__ */ jsx12("span", { className: `material-symbols-outlined text-[18px]${hfLoading ? " animate-spin" : ""}`, children: "refresh" }) })
1495
+ ] }),
1496
+ onProjectExportBase64 && /* @__PURE__ */ jsxs10("div", { className: "flex gap-2", children: [
1497
+ /* @__PURE__ */ jsx12(
1498
+ "input",
1499
+ {
1500
+ type: "text",
1501
+ value: hfSaveName,
1502
+ onChange: (e) => setHfSaveName(e.target.value),
1503
+ placeholder: "Name (optional)",
1504
+ 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",
1505
+ style: { height: 40 }
1506
+ }
1507
+ ),
1508
+ /* @__PURE__ */ jsx12(
1509
+ PillButton,
1510
+ {
1511
+ variant: "filled",
1512
+ icon: "cloud_upload",
1513
+ loading: hfSaving,
1514
+ onClick: async () => {
1515
+ if (!onProjectExportBase64) return;
1516
+ setHfSaving(true);
1517
+ setHfError(null);
1518
+ try {
1519
+ const base64 = await onProjectExportBase64();
1520
+ const name = hfSaveName.trim() || `project_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16).replace("T", "_").replace(":", "")}`;
1521
+ await hfUploadProject(base64, name, hfToken);
1522
+ setHfSaveName("");
1523
+ await loadHfProjects(hfToken);
1524
+ } catch (e) {
1525
+ setHfError(e.message);
1526
+ } finally {
1527
+ setHfSaving(false);
1528
+ }
1529
+ },
1530
+ children: "Speichern"
1531
+ }
1532
+ )
1533
+ ] }),
1534
+ hfError && /* @__PURE__ */ jsx12("p", { className: "text-[10px] text-red-400 font-mono px-1", children: hfError }),
1535
+ !hfLoading && hfProjects.length === 0 && /* @__PURE__ */ jsx12("p", { className: "text-[10px] text-white/20 px-2", children: "Noch nichts auf HF gespeichert." }),
1536
+ hfProjects.map((p) => /* @__PURE__ */ jsxs10("div", { className: "flex items-center gap-2 px-3 py-2 rounded-xl bg-white/5 border border-white/5", children: [
1537
+ /* @__PURE__ */ jsxs10("div", { className: "flex-1 min-w-0", children: [
1538
+ /* @__PURE__ */ jsx12("p", { className: "text-[11px] text-white/70 font-bold truncate", children: p.name }),
1539
+ /* @__PURE__ */ jsxs10("p", { className: "text-[9px] text-white/25", children: [
1540
+ (p.size / 1024 / 1024).toFixed(1),
1541
+ " MB",
1542
+ p.isLfs ? " \xB7 LFS" : ""
1543
+ ] })
1544
+ ] }),
1545
+ onHfLoad && /* @__PURE__ */ jsx12(
1546
+ "button",
1547
+ {
1548
+ onClick: async () => {
1549
+ try {
1550
+ const { hfDownloadProject } = await import("./hfStateService-MQ33VAMR.mjs");
1551
+ const file = await hfDownloadProject(p.path, hfToken);
1552
+ onHfLoad(file);
1553
+ } catch (e) {
1554
+ setHfError(e.message);
1555
+ }
1556
+ },
1557
+ className: "w-8 h-8 flex items-center justify-center text-white/30 active:text-white transition-colors",
1558
+ children: /* @__PURE__ */ jsx12("span", { className: "material-symbols-outlined text-[18px]", children: "cloud_download" })
1559
+ }
1560
+ ),
1561
+ /* @__PURE__ */ jsx12(
1562
+ "button",
1563
+ {
1564
+ onClick: async () => {
1565
+ if (!confirm(`"${p.name}" auf HF l\xF6schen?`)) return;
1566
+ try {
1567
+ await hfDeleteProject(p.path, hfToken);
1568
+ await loadHfProjects(hfToken);
1569
+ } catch (e) {
1570
+ setHfError(e.message);
1571
+ }
1572
+ },
1573
+ className: "w-8 h-8 flex items-center justify-center text-white/20 active:text-red-400 transition-colors",
1574
+ children: /* @__PURE__ */ jsx12("span", { className: "material-symbols-outlined text-[18px]", children: "delete" })
1575
+ }
1576
+ )
1577
+ ] }, p.id))
1578
+ ] }),
1669
1579
  onComputeSyncDiff && onExecuteSync && /* @__PURE__ */ jsxs10("div", { className: "flex flex-col gap-4", children: [
1670
1580
  /* @__PURE__ */ jsx12(SectionLabel, { children: "Sync" }),
1671
1581
  (syncState === "idle" || syncState === "computing") && /* @__PURE__ */ jsx12(PillButton, { variant: "outline", icon: "compare_arrows", loading: syncState === "computing", onClick: handleComputeDiff, children: "Diff berechnen" }),
@@ -2932,7 +2842,7 @@ function TagManagerPanel({ workspaceTags, onTagCreate, onTagUpdate, onTagDelete,
2932
2842
  // src/components/AvatarArchitectApp.tsx
2933
2843
  import { Fragment as Fragment9, jsx as jsx20, jsxs as jsxs18 } from "react/jsx-runtime";
2934
2844
  function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia, buildInfo, onFetchServerProjects, onServerSave, onServerLoad, onServerDelete }) {
2935
- useEffect4(() => {
2845
+ useEffect5(() => {
2936
2846
  const id = "flow-styles";
2937
2847
  if (!document.getElementById(id)) {
2938
2848
  const style = document.createElement("style");
@@ -2950,6 +2860,9 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
2950
2860
  }
2951
2861
  });
2952
2862
  const [projectLoaded, setProjectLoaded] = useState14(false);
2863
+ const [hfToken, setHfToken] = useState14("");
2864
+ const [hfTokenInput, setHfTokenInput] = useState14("");
2865
+ const [isLoadingFromHF, setIsLoadingFromHF] = useState14(false);
2953
2866
  const wsInputRef = useRef6(null);
2954
2867
  const startApp = (choice) => {
2955
2868
  try {
@@ -3452,7 +3365,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3452
3365
  setTimeout(() => setProjectActionState("idle"), 4e3);
3453
3366
  }
3454
3367
  };
3455
- useEffect4(() => {
3368
+ useEffect5(() => {
3456
3369
  if (activeTab === "setup" || activeTab === "sync") fetchServerProjects();
3457
3370
  }, [activeTab]);
3458
3371
  if (isFullscreen && currentResult?.base64) {
@@ -3550,6 +3463,59 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3550
3463
  ),
3551
3464
  !projectLoaded && /* @__PURE__ */ jsx20("span", { className: "text-white/20 text-[10px] text-center", children: "Baum, Bilder und Einstellungen wiederherstellen" })
3552
3465
  ] }),
3466
+ /* @__PURE__ */ jsxs18("div", { className: "flex flex-col items-center gap-2 w-full max-w-[280px]", children: [
3467
+ /* @__PURE__ */ jsxs18("div", { className: "flex gap-2 w-full", children: [
3468
+ /* @__PURE__ */ jsx20(
3469
+ "input",
3470
+ {
3471
+ type: "password",
3472
+ value: hfTokenInput,
3473
+ onChange: (e) => setHfTokenInput(e.target.value),
3474
+ onKeyDown: (e) => e.key === "Enter" && hfTokenInput.trim() && setHfToken(hfTokenInput.trim()),
3475
+ placeholder: "HF Token (hf_\u2026)",
3476
+ className: "flex-1 rounded-xl px-3 text-[11px] font-mono outline-none",
3477
+ 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)" }
3478
+ }
3479
+ ),
3480
+ hfTokenInput.trim() && /* @__PURE__ */ jsx20(
3481
+ "button",
3482
+ {
3483
+ onClick: () => setHfToken(hfTokenInput.trim()),
3484
+ className: "px-3 rounded-xl text-[11px] font-bold text-white",
3485
+ style: { background: "#f59e0b", height: 44 },
3486
+ children: "OK"
3487
+ }
3488
+ )
3489
+ ] }),
3490
+ hfToken && /* @__PURE__ */ jsxs18(
3491
+ "button",
3492
+ {
3493
+ disabled: isLoadingFromHF,
3494
+ onClick: async () => {
3495
+ setIsLoadingFromHF(true);
3496
+ try {
3497
+ const { hfListProjects: hfListProjects2, hfDownloadProject } = await import("./hfStateService-MQ33VAMR.mjs");
3498
+ const projects = await hfListProjects2(hfToken);
3499
+ if (projects.length > 0) {
3500
+ const file = await hfDownloadProject(projects[0].path, hfToken);
3501
+ await handleProjectImport(file);
3502
+ }
3503
+ } catch (e) {
3504
+ alert("HF Fehler: " + e.message);
3505
+ } finally {
3506
+ setIsLoadingFromHF(false);
3507
+ }
3508
+ },
3509
+ 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",
3510
+ style: { height: 56, background: "#f59e0b" },
3511
+ children: [
3512
+ /* @__PURE__ */ jsx20("span", { className: `material-symbols-outlined text-[22px]${isLoadingFromHF ? " animate-spin" : ""}`, children: isLoadingFromHF ? "sync" : "cloud_download" }),
3513
+ isLoadingFromHF ? "Laden\u2026" : "Von HF laden"
3514
+ ]
3515
+ }
3516
+ ),
3517
+ hfToken && /* @__PURE__ */ jsx20("span", { className: "text-white/20 text-[10px] text-center", children: "Letzten Stand von Hugging Face laden" })
3518
+ ] }),
3553
3519
  onFetchServerProjects && /* @__PURE__ */ jsxs18("div", { className: "flex flex-col items-center gap-2 w-full max-w-[280px]", children: [
3554
3520
  /* @__PURE__ */ jsxs18(
3555
3521
  "button",
@@ -3842,7 +3808,13 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
3842
3808
  onServerDelete: onServerDelete ? handleServerDelete : void 0,
3843
3809
  onRefreshServerProjects: onFetchServerProjects ? fetchServerProjects : void 0,
3844
3810
  onComputeSyncDiff: onFetchServerProjects && onServerLoad && onServerSave ? handleComputeSyncDiff : void 0,
3845
- onExecuteSync: onFetchServerProjects && onServerLoad && onServerSave ? handleExecuteSync : void 0
3811
+ onExecuteSync: onFetchServerProjects && onServerLoad && onServerSave ? handleExecuteSync : void 0,
3812
+ hfToken: hfToken || void 0,
3813
+ onHfLoad: (f) => handleProjectImport(f),
3814
+ onProjectExportBase64: async () => {
3815
+ const { base64 } = await import("./project-O4ORKXY5.mjs").then((m) => m.exportProjectToZip(nodes, edges, history, galleryItems, { aspectRatio, selectedModel, seed, seedMode }, workspaceTags, recentLabItems.map((i) => i.id)));
3816
+ return base64;
3817
+ }
3846
3818
  }
3847
3819
  ),
3848
3820
  activeTab === "tags" && workspaceTags && /* @__PURE__ */ jsx20(
@@ -4165,7 +4137,13 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4165
4137
  onServerDelete: onServerDelete ? handleServerDelete : void 0,
4166
4138
  onRefreshServerProjects: onFetchServerProjects ? fetchServerProjects : void 0,
4167
4139
  onComputeSyncDiff: onFetchServerProjects && onServerLoad && onServerSave ? handleComputeSyncDiff : void 0,
4168
- onExecuteSync: onFetchServerProjects && onServerLoad && onServerSave ? handleExecuteSync : void 0
4140
+ onExecuteSync: onFetchServerProjects && onServerLoad && onServerSave ? handleExecuteSync : void 0,
4141
+ hfToken: hfToken || void 0,
4142
+ onHfLoad: (f) => handleProjectImport(f),
4143
+ onProjectExportBase64: async () => {
4144
+ const { base64 } = await import("./project-O4ORKXY5.mjs").then((m) => m.exportProjectToZip(nodes, edges, history, galleryItems, { aspectRatio, selectedModel, seed, seedMode }, workspaceTags, recentLabItems.map((i) => i.id)));
4145
+ return base64;
4146
+ }
4169
4147
  }
4170
4148
  )
4171
4149
  ] })
@@ -4174,7 +4152,7 @@ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onS
4174
4152
  }
4175
4153
 
4176
4154
  // src/index.ts
4177
- var LIB_VERSION = "1.2.4";
4155
+ var LIB_VERSION = "1.2.6";
4178
4156
  export {
4179
4157
  AvatarArchitectApp,
4180
4158
  CollapsibleCard,
@@ -0,0 +1,8 @@
1
+ import {
2
+ exportProjectToZip,
3
+ importProjectFromZip
4
+ } from "./chunk-UFXDXENC.mjs";
5
+ export {
6
+ exportProjectToZip,
7
+ importProjectFromZip
8
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rslsp1/fa-app-tools",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "description": "Shared tools and hooks for Fine Art flow apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",