@rslsp1/fa-app-tools 0.1.11 → 0.1.13

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.d.mts CHANGED
@@ -4,6 +4,57 @@ import { Node, Edge } from '@xyflow/react';
4
4
 
5
5
  declare function useOnClickOutside(ref: RefObject<HTMLElement | null>, handler: (e: MouseEvent | TouchEvent) => void): void;
6
6
 
7
+ interface WorkspaceTags {
8
+ by_category: Record<string, string[]>;
9
+ all: Array<{
10
+ label: string;
11
+ value: string;
12
+ category: string;
13
+ }>;
14
+ }
15
+ interface Generation {
16
+ id: string;
17
+ nodeId: string;
18
+ base64?: string;
19
+ mediaId?: string;
20
+ prompt?: string;
21
+ hierarchy?: string;
22
+ seed?: number;
23
+ model?: string;
24
+ timestamp: number;
25
+ status: 'processing' | 'done' | 'error';
26
+ step?: string;
27
+ tags: string[];
28
+ type?: 'generation' | 'import';
29
+ error?: {
30
+ message: string;
31
+ details?: string;
32
+ };
33
+ selectedForExport?: boolean;
34
+ }
35
+ interface ProjectSettings {
36
+ aspectRatio: string;
37
+ selectedModel: string;
38
+ seed: number;
39
+ seedMode: 'random' | 'fixed';
40
+ }
41
+
42
+ /**
43
+ * Hook für die Tastaturnavigation im Detailview.
44
+ * Rechts = Index + 1 (älter), Links = Index - 1 (neuer).
45
+ */
46
+ declare function useKeyboardNavigation(history: Generation[], currentResult: Generation | null, setCurrentResult: (gen: Generation) => void): void;
47
+
48
+ /**
49
+ * Erstellt einen lesbaren Zeitstempel im Format YYYYMMDD_HHMMSS.
50
+ * Dies ist das vom User explizit gewünschte Format für Exporte.
51
+ */
52
+ declare const getFormattedTimestamp: () => string;
53
+ /**
54
+ * Hilfskonstante für CSS-Styles (Dark Scrollbar etc.)
55
+ */
56
+ declare const GLOBAL_STYLES = "\n .dark-scrollbar::-webkit-scrollbar { width: 4px; height: 4px; }\n .dark-scrollbar::-webkit-scrollbar-track { background: transparent; }\n .dark-scrollbar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }\n html, body, #root { margin: 0; padding: 0; width: 100%; height: 100%; background: #0e0e0e; font-family: 'Google Sans Text', sans-serif; overflow: hidden; }\n @keyframes prompt-shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }\n .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; }\n @keyframes dropdown-enter { from { opacity: 0; transform: scale(0.95) translateY(-5px); } to { opacity: 1; transform: scale(1) translateY(0); } }\n .animate-dropdown { animation: dropdown-enter 0.1s ease-out forwards; }\n";
57
+
7
58
  declare function injectXMPMetadata(base64: string, prompt: string, seed?: number, model?: string, id?: string, tags?: string[], hierarchy?: string): string;
8
59
 
9
60
  /**
@@ -59,41 +110,6 @@ interface CompactDropdownProps {
59
110
  }
60
111
  declare const CompactDropdown: React.FC<CompactDropdownProps>;
61
112
 
62
- interface WorkspaceTags {
63
- by_category: Record<string, string[]>;
64
- all: Array<{
65
- label: string;
66
- value: string;
67
- category: string;
68
- }>;
69
- }
70
- interface Generation {
71
- id: string;
72
- nodeId: string;
73
- base64?: string;
74
- mediaId?: string;
75
- prompt?: string;
76
- hierarchy?: string;
77
- seed?: number;
78
- model?: string;
79
- timestamp: number;
80
- status: 'processing' | 'done' | 'error';
81
- step?: string;
82
- tags: string[];
83
- type?: 'generation' | 'import';
84
- error?: {
85
- message: string;
86
- details?: string;
87
- };
88
- selectedForExport?: boolean;
89
- }
90
- interface ProjectSettings {
91
- aspectRatio: string;
92
- selectedModel: string;
93
- seed: number;
94
- seedMode: 'random' | 'fixed';
95
- }
96
-
97
113
  interface HistoryPanelProps {
98
114
  history: Generation[];
99
115
  currentResultId: string | null;
@@ -106,7 +122,7 @@ interface InspectPanelProps {
106
122
  currentResult: Generation | null;
107
123
  history: Generation[];
108
124
  onSelect: (gen: Generation) => void;
109
- workspaceTags: WorkspaceTags | null;
125
+ workspaceTags?: WorkspaceTags | null;
110
126
  onTagToggle?: (tagName: string) => void;
111
127
  }
112
128
  declare const InspectPanel: React.FC<InspectPanelProps>;
@@ -144,10 +160,27 @@ interface ListViewProps {
144
160
  onFocus: (id: string | null) => void;
145
161
  activePath: Set<string>;
146
162
  onGenerate: (id: string) => void;
147
- onGenerateBranch: (id: string) => void;
148
- onGenerateSubtree: (id: string) => void;
163
+ onGenerateBranch?: (id: string) => void;
164
+ onGenerateSubtree?: (id: string) => void;
149
165
  isGeneratingNodeId: (id: string) => boolean;
150
166
  }
151
167
  declare function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, focusedNodeId, onFocus, activePath, onGenerate, onGenerateBranch, onGenerateSubtree, isGeneratingNodeId }: ListViewProps): react_jsx_runtime.JSX.Element;
152
168
 
153
- export { CompactDropdown, type ExtractedCharacter, FaToolsBadge, type Generation, HistoryPanel, InspectPanel, ListView, MediaLibrary, PillButton, type ProjectSettings, SectionLabel, SetupPanel, type WorkspaceTags, exportProjectToZip, formatTreeToMarkdown, importProjectFromZip, injectXMPMetadata, parsePromptFile, useOnClickOutside };
169
+ interface MediaItem {
170
+ base64: string;
171
+ mimeType: string;
172
+ mediaId?: string;
173
+ name?: string;
174
+ }
175
+ interface AvatarArchitectAppProps {
176
+ onGenerateImage: (prompt: string, aspectRatio: string, model: string, referenceId?: string) => Promise<{
177
+ base64: string;
178
+ mediaId?: string;
179
+ }>;
180
+ onGeneratePrompt: (treeText: string) => Promise<string>;
181
+ onDownload: (base64: string, mimeType: string, filename: string) => Promise<void>;
182
+ onSelectMedia: () => Promise<MediaItem[]>;
183
+ }
184
+ declare function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia }: AvatarArchitectAppProps): react_jsx_runtime.JSX.Element;
185
+
186
+ export { AvatarArchitectApp, type AvatarArchitectAppProps, CompactDropdown, type ExtractedCharacter, FaToolsBadge, GLOBAL_STYLES, type Generation, HistoryPanel, InspectPanel, ListView, type MediaItem, MediaLibrary, PillButton, type ProjectSettings, SectionLabel, SetupPanel, type WorkspaceTags, exportProjectToZip, formatTreeToMarkdown, getFormattedTimestamp, importProjectFromZip, injectXMPMetadata, parsePromptFile, useKeyboardNavigation, useOnClickOutside };
package/dist/index.d.ts CHANGED
@@ -4,6 +4,57 @@ import { Node, Edge } from '@xyflow/react';
4
4
 
5
5
  declare function useOnClickOutside(ref: RefObject<HTMLElement | null>, handler: (e: MouseEvent | TouchEvent) => void): void;
6
6
 
7
+ interface WorkspaceTags {
8
+ by_category: Record<string, string[]>;
9
+ all: Array<{
10
+ label: string;
11
+ value: string;
12
+ category: string;
13
+ }>;
14
+ }
15
+ interface Generation {
16
+ id: string;
17
+ nodeId: string;
18
+ base64?: string;
19
+ mediaId?: string;
20
+ prompt?: string;
21
+ hierarchy?: string;
22
+ seed?: number;
23
+ model?: string;
24
+ timestamp: number;
25
+ status: 'processing' | 'done' | 'error';
26
+ step?: string;
27
+ tags: string[];
28
+ type?: 'generation' | 'import';
29
+ error?: {
30
+ message: string;
31
+ details?: string;
32
+ };
33
+ selectedForExport?: boolean;
34
+ }
35
+ interface ProjectSettings {
36
+ aspectRatio: string;
37
+ selectedModel: string;
38
+ seed: number;
39
+ seedMode: 'random' | 'fixed';
40
+ }
41
+
42
+ /**
43
+ * Hook für die Tastaturnavigation im Detailview.
44
+ * Rechts = Index + 1 (älter), Links = Index - 1 (neuer).
45
+ */
46
+ declare function useKeyboardNavigation(history: Generation[], currentResult: Generation | null, setCurrentResult: (gen: Generation) => void): void;
47
+
48
+ /**
49
+ * Erstellt einen lesbaren Zeitstempel im Format YYYYMMDD_HHMMSS.
50
+ * Dies ist das vom User explizit gewünschte Format für Exporte.
51
+ */
52
+ declare const getFormattedTimestamp: () => string;
53
+ /**
54
+ * Hilfskonstante für CSS-Styles (Dark Scrollbar etc.)
55
+ */
56
+ declare const GLOBAL_STYLES = "\n .dark-scrollbar::-webkit-scrollbar { width: 4px; height: 4px; }\n .dark-scrollbar::-webkit-scrollbar-track { background: transparent; }\n .dark-scrollbar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }\n html, body, #root { margin: 0; padding: 0; width: 100%; height: 100%; background: #0e0e0e; font-family: 'Google Sans Text', sans-serif; overflow: hidden; }\n @keyframes prompt-shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }\n .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; }\n @keyframes dropdown-enter { from { opacity: 0; transform: scale(0.95) translateY(-5px); } to { opacity: 1; transform: scale(1) translateY(0); } }\n .animate-dropdown { animation: dropdown-enter 0.1s ease-out forwards; }\n";
57
+
7
58
  declare function injectXMPMetadata(base64: string, prompt: string, seed?: number, model?: string, id?: string, tags?: string[], hierarchy?: string): string;
8
59
 
9
60
  /**
@@ -59,41 +110,6 @@ interface CompactDropdownProps {
59
110
  }
60
111
  declare const CompactDropdown: React.FC<CompactDropdownProps>;
61
112
 
62
- interface WorkspaceTags {
63
- by_category: Record<string, string[]>;
64
- all: Array<{
65
- label: string;
66
- value: string;
67
- category: string;
68
- }>;
69
- }
70
- interface Generation {
71
- id: string;
72
- nodeId: string;
73
- base64?: string;
74
- mediaId?: string;
75
- prompt?: string;
76
- hierarchy?: string;
77
- seed?: number;
78
- model?: string;
79
- timestamp: number;
80
- status: 'processing' | 'done' | 'error';
81
- step?: string;
82
- tags: string[];
83
- type?: 'generation' | 'import';
84
- error?: {
85
- message: string;
86
- details?: string;
87
- };
88
- selectedForExport?: boolean;
89
- }
90
- interface ProjectSettings {
91
- aspectRatio: string;
92
- selectedModel: string;
93
- seed: number;
94
- seedMode: 'random' | 'fixed';
95
- }
96
-
97
113
  interface HistoryPanelProps {
98
114
  history: Generation[];
99
115
  currentResultId: string | null;
@@ -106,7 +122,7 @@ interface InspectPanelProps {
106
122
  currentResult: Generation | null;
107
123
  history: Generation[];
108
124
  onSelect: (gen: Generation) => void;
109
- workspaceTags: WorkspaceTags | null;
125
+ workspaceTags?: WorkspaceTags | null;
110
126
  onTagToggle?: (tagName: string) => void;
111
127
  }
112
128
  declare const InspectPanel: React.FC<InspectPanelProps>;
@@ -144,10 +160,27 @@ interface ListViewProps {
144
160
  onFocus: (id: string | null) => void;
145
161
  activePath: Set<string>;
146
162
  onGenerate: (id: string) => void;
147
- onGenerateBranch: (id: string) => void;
148
- onGenerateSubtree: (id: string) => void;
163
+ onGenerateBranch?: (id: string) => void;
164
+ onGenerateSubtree?: (id: string) => void;
149
165
  isGeneratingNodeId: (id: string) => boolean;
150
166
  }
151
167
  declare function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, focusedNodeId, onFocus, activePath, onGenerate, onGenerateBranch, onGenerateSubtree, isGeneratingNodeId }: ListViewProps): react_jsx_runtime.JSX.Element;
152
168
 
153
- export { CompactDropdown, type ExtractedCharacter, FaToolsBadge, type Generation, HistoryPanel, InspectPanel, ListView, MediaLibrary, PillButton, type ProjectSettings, SectionLabel, SetupPanel, type WorkspaceTags, exportProjectToZip, formatTreeToMarkdown, importProjectFromZip, injectXMPMetadata, parsePromptFile, useOnClickOutside };
169
+ interface MediaItem {
170
+ base64: string;
171
+ mimeType: string;
172
+ mediaId?: string;
173
+ name?: string;
174
+ }
175
+ interface AvatarArchitectAppProps {
176
+ onGenerateImage: (prompt: string, aspectRatio: string, model: string, referenceId?: string) => Promise<{
177
+ base64: string;
178
+ mediaId?: string;
179
+ }>;
180
+ onGeneratePrompt: (treeText: string) => Promise<string>;
181
+ onDownload: (base64: string, mimeType: string, filename: string) => Promise<void>;
182
+ onSelectMedia: () => Promise<MediaItem[]>;
183
+ }
184
+ declare function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia }: AvatarArchitectAppProps): react_jsx_runtime.JSX.Element;
185
+
186
+ export { AvatarArchitectApp, type AvatarArchitectAppProps, CompactDropdown, type ExtractedCharacter, FaToolsBadge, GLOBAL_STYLES, type Generation, HistoryPanel, InspectPanel, ListView, type MediaItem, MediaLibrary, PillButton, type ProjectSettings, SectionLabel, SetupPanel, type WorkspaceTags, exportProjectToZip, formatTreeToMarkdown, getFormattedTimestamp, importProjectFromZip, injectXMPMetadata, parsePromptFile, useKeyboardNavigation, useOnClickOutside };
package/dist/index.js CHANGED
@@ -30,8 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ AvatarArchitectApp: () => AvatarArchitectApp,
33
34
  CompactDropdown: () => CompactDropdown,
34
35
  FaToolsBadge: () => FaToolsBadge,
36
+ GLOBAL_STYLES: () => GLOBAL_STYLES,
35
37
  HistoryPanel: () => HistoryPanel,
36
38
  InspectPanel: () => InspectPanel,
37
39
  ListView: () => ListView,
@@ -41,9 +43,11 @@ __export(index_exports, {
41
43
  SetupPanel: () => SetupPanel,
42
44
  exportProjectToZip: () => exportProjectToZip,
43
45
  formatTreeToMarkdown: () => formatTreeToMarkdown,
46
+ getFormattedTimestamp: () => getFormattedTimestamp,
44
47
  importProjectFromZip: () => importProjectFromZip,
45
48
  injectXMPMetadata: () => injectXMPMetadata,
46
49
  parsePromptFile: () => parsePromptFile,
50
+ useKeyboardNavigation: () => useKeyboardNavigation,
47
51
  useOnClickOutside: () => useOnClickOutside
48
52
  });
49
53
  module.exports = __toCommonJS(index_exports);
@@ -65,6 +69,42 @@ function useOnClickOutside(ref, handler) {
65
69
  }, [ref, handler]);
66
70
  }
67
71
 
72
+ // src/hooks/useKeyboardNavigation.ts
73
+ var import_react2 = require("react");
74
+ function useKeyboardNavigation(history, currentResult, setCurrentResult) {
75
+ (0, import_react2.useEffect)(() => {
76
+ const handleKeyDown = (e) => {
77
+ if (e.target instanceof HTMLTextAreaElement || e.target instanceof HTMLInputElement) return;
78
+ if (!currentResult || history.length === 0) return;
79
+ const currentIndex = history.findIndex((h) => h.id === currentResult.id);
80
+ if (e.key === "ArrowRight" && currentIndex < history.length - 1) {
81
+ setCurrentResult(history[currentIndex + 1]);
82
+ } else if (e.key === "ArrowLeft" && currentIndex > 0) {
83
+ setCurrentResult(history[currentIndex - 1]);
84
+ }
85
+ };
86
+ window.addEventListener("keydown", handleKeyDown);
87
+ return () => window.removeEventListener("keydown", handleKeyDown);
88
+ }, [currentResult, history, setCurrentResult]);
89
+ }
90
+
91
+ // src/lib/utils.ts
92
+ var getFormattedTimestamp = () => {
93
+ const d = /* @__PURE__ */ new Date();
94
+ const pad = (n) => n.toString().padStart(2, "0");
95
+ return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
96
+ };
97
+ var GLOBAL_STYLES = `
98
+ .dark-scrollbar::-webkit-scrollbar { width: 4px; height: 4px; }
99
+ .dark-scrollbar::-webkit-scrollbar-track { background: transparent; }
100
+ .dark-scrollbar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }
101
+ html, body, #root { margin: 0; padding: 0; width: 100%; height: 100%; background: #0e0e0e; font-family: 'Google Sans Text', sans-serif; overflow: hidden; }
102
+ @keyframes prompt-shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }
103
+ .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; }
104
+ @keyframes dropdown-enter { from { opacity: 0; transform: scale(0.95) translateY(-5px); } to { opacity: 1; transform: scale(1) translateY(0); } }
105
+ .animate-dropdown { animation: dropdown-enter 0.1s ease-out forwards; }
106
+ `;
107
+
68
108
  // src/lib/metadata.ts
69
109
  var crcTable = (() => {
70
110
  let c;
@@ -381,7 +421,7 @@ var import_jsx_runtime3 = require("react/jsx-runtime");
381
421
  var SectionLabel = ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex items-center px-2", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[9px] font-bold text-white/30 uppercase tracking-widest text-nowrap", children }) });
382
422
 
383
423
  // src/components/CompactDropdown.tsx
384
- var import_react2 = require("react");
424
+ var import_react3 = require("react");
385
425
  var import_jsx_runtime4 = require("react/jsx-runtime");
386
426
  var CompactDropdown = ({
387
427
  value,
@@ -390,8 +430,8 @@ var CompactDropdown = ({
390
430
  onChange,
391
431
  className = ""
392
432
  }) => {
393
- const [isOpen, setIsOpen] = (0, import_react2.useState)(false);
394
- const ref = (0, import_react2.useRef)(null);
433
+ const [isOpen, setIsOpen] = (0, import_react3.useState)(false);
434
+ const ref = (0, import_react3.useRef)(null);
395
435
  useOnClickOutside(ref, () => setIsOpen(false));
396
436
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { ref, className: `relative shrink-0 ${className}`, children: [
397
437
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
@@ -423,7 +463,7 @@ var CompactDropdown = ({
423
463
  };
424
464
 
425
465
  // src/components/HistoryPanel.tsx
426
- var import_react3 = require("motion/react");
466
+ var import_react4 = require("motion/react");
427
467
  var import_jsx_runtime5 = require("react/jsx-runtime");
428
468
  var formatFriendlyTimestamp = (timestamp) => {
429
469
  const date = new Date(timestamp);
@@ -443,7 +483,7 @@ var HistoryPanel = ({ history, currentResultId, onSelect, onDelete }) => {
443
483
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-[10px] font-bold uppercase tracking-widest", children: "Keine Historie" })
444
484
  ] });
445
485
  }
446
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react3.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 p-4 flex flex-col gap-3 overflow-y-auto dark-scrollbar", children: history.map((gen) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
486
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react4.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 p-4 flex flex-col gap-3 overflow-y-auto dark-scrollbar", children: history.map((gen) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
447
487
  "div",
448
488
  {
449
489
  className: `flex gap-3 p-2 bg-white/5 border rounded-2xl cursor-pointer group transition-all relative ${currentResultId === gen.id ? "border-white/40 bg-white/10 shadow-lg" : "border-white/5 hover:border-white/20"}`,
@@ -472,7 +512,7 @@ var HistoryPanel = ({ history, currentResultId, onSelect, onDelete }) => {
472
512
  };
473
513
 
474
514
  // src/components/InspectPanel.tsx
475
- var import_react4 = require("motion/react");
515
+ var import_react5 = require("motion/react");
476
516
  var import_jsx_runtime6 = require("react/jsx-runtime");
477
517
  var InspectPanel = ({ currentResult, history, onSelect, workspaceTags, onTagToggle }) => {
478
518
  if (!currentResult) {
@@ -482,7 +522,7 @@ var InspectPanel = ({ currentResult, history, onSelect, workspaceTags, onTagTogg
482
522
  ] });
483
523
  }
484
524
  const fullDateStr = new Date(currentResult.timestamp).toLocaleString([], { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit" });
485
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react4.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 flex flex-col overflow-hidden", children: [
525
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react5.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 flex flex-col overflow-hidden", children: [
486
526
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-shrink-0 border-b border-white/5 bg-black/20 py-3 px-4", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex gap-2 overflow-x-auto no-scrollbar pb-1", children: history.map((gen) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { onClick: () => onSelect(gen), className: `w-10 h-10 rounded-lg overflow-hidden border shrink-0 transition-all ${currentResult.id === gen.id ? "border-white scale-105 shadow-lg" : "border-white/5 opacity-40 hover:opacity-100"}`, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { src: gen.base64 ? gen.base64.startsWith("data:") ? gen.base64 : `data:image/png;base64,${gen.base64}` : "", className: "w-full h-full object-cover", alt: "Thumbnail" }) }, gen.id)) }) }),
487
527
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-1 overflow-y-auto p-4 flex flex-col gap-6 dark-scrollbar pb-10", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col gap-4", children: [
488
528
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SectionLabel, { children: "Vorschau" }),
@@ -524,13 +564,13 @@ var InspectPanel = ({ currentResult, history, onSelect, workspaceTags, onTagTogg
524
564
  };
525
565
 
526
566
  // src/components/SetupPanel.tsx
527
- var import_react5 = require("react");
528
- var import_react6 = require("motion/react");
567
+ var import_react6 = require("react");
568
+ var import_react7 = require("motion/react");
529
569
  var import_jsx_runtime7 = require("react/jsx-runtime");
530
570
  var SetupPanel = ({ onWorkspaceImport, onProjectExport, onProjectImport, projectActionState }) => {
531
- const workspaceInputRef = (0, import_react5.useRef)(null);
532
- const projectInputRef = (0, import_react5.useRef)(null);
533
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react6.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 p-6 flex flex-col gap-10", children: [
571
+ const workspaceInputRef = (0, import_react6.useRef)(null);
572
+ const projectInputRef = (0, import_react6.useRef)(null);
573
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react7.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 p-6 flex flex-col gap-10", children: [
534
574
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-4", children: [
535
575
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-1", children: [
536
576
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SectionLabel, { children: "Workspace Management" }),
@@ -573,7 +613,7 @@ var SetupPanel = ({ onWorkspaceImport, onProjectExport, onProjectImport, project
573
613
  };
574
614
 
575
615
  // src/components/MediaLibrary.tsx
576
- var import_react7 = require("motion/react");
616
+ var import_react8 = require("motion/react");
577
617
  var import_jsx_runtime8 = require("react/jsx-runtime");
578
618
  var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, onBatchDownload, onGenerateReference }) => {
579
619
  const selectedCount = items.filter((i) => i.selectedForExport).length;
@@ -592,7 +632,7 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
592
632
  "Laden"
593
633
  ] })
594
634
  ] }),
595
- selectedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react7.motion.div, { initial: { opacity: 0, y: -10 }, animate: { opacity: 1, y: 0 }, className: "flex items-center justify-between bg-blue-500/10 border border-blue-500/20 p-2 rounded-xl", children: [
635
+ selectedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react8.motion.div, { initial: { opacity: 0, y: -10 }, animate: { opacity: 1, y: 0 }, className: "flex items-center justify-between bg-blue-500/10 border border-blue-500/20 p-2 rounded-xl", children: [
596
636
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "text-[9px] font-bold uppercase text-blue-400 ml-1", children: [
597
637
  selectedCount,
598
638
  " ausgew\xE4hlt"
@@ -609,7 +649,7 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
609
649
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-[12px] font-bold uppercase tracking-widest", children: "Keine Medien" }),
610
650
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-[10px] italic", children: "Importiere Assets aus deinem Projekt." })
611
651
  ] })
612
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "grid grid-cols-2 gap-3 pb-10", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react7.motion.div, { initial: { opacity: 0, scale: 0.9 }, animate: { opacity: 1, scale: 1 }, className: `relative aspect-square group/item rounded-xl overflow-hidden border transition-all bg-white/5 cursor-pointer shadow-lg ${item.selectedForExport ? "border-blue-500 shadow-[0_0_15px_rgba(59,130,246,0.2)]" : "border-white/10 opacity-70 hover:opacity-100"}`, onClick: () => onSelect?.(item), children: [
652
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "grid grid-cols-2 gap-3 pb-10", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react8.motion.div, { initial: { opacity: 0, scale: 0.9 }, animate: { opacity: 1, scale: 1 }, className: `relative aspect-square group/item rounded-xl overflow-hidden border transition-all bg-white/5 cursor-pointer shadow-lg ${item.selectedForExport ? "border-blue-500 shadow-[0_0_15px_rgba(59,130,246,0.2)]" : "border-white/10 opacity-70 hover:opacity-100"}`, onClick: () => onSelect?.(item), children: [
613
653
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: item.base64 ? item.base64.startsWith("data:") ? item.base64 : `data:image/png;base64,${item.base64}` : "", className: "w-full h-full object-cover transition-transform duration-500 group-hover/item:scale-110", alt: item.prompt }),
614
654
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: `absolute top-2 left-2 w-5 h-5 rounded-md border flex items-center justify-center transition-all z-30 pointer-events-auto ${item.selectedForExport ? "bg-blue-500 border-blue-400" : "bg-black/60 border-white/20"}`, onClick: (e) => {
615
655
  e.stopPropagation();
@@ -634,10 +674,10 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
634
674
  };
635
675
 
636
676
  // src/components/ListView.tsx
637
- var import_react8 = require("react");
677
+ var import_react9 = require("react");
638
678
  var import_jsx_runtime9 = require("react/jsx-runtime");
639
679
  var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, isActive, isInPath, onFocus, onGenerate, onGenerateBranch, onGenerateSubtree, isGenerating, isCollapsed, toggleCollapse, renderNode, children }) => {
640
- const inputRef = (0, import_react8.useRef)(null);
680
+ const inputRef = (0, import_react9.useRef)(null);
641
681
  const hasChildren = children && children.length > 0;
642
682
  const handleKeyDown = (e) => {
643
683
  if (e.key === "Tab") {
@@ -665,7 +705,7 @@ var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNod
665
705
  onMoveNode(node.id, "down");
666
706
  }
667
707
  };
668
- (0, import_react8.useEffect)(() => {
708
+ (0, import_react9.useEffect)(() => {
669
709
  if (isActive && inputRef.current) inputRef.current.focus();
670
710
  }, [isActive]);
671
711
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col w-full", children: [
@@ -699,7 +739,7 @@ var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNod
699
739
  ] });
700
740
  };
701
741
  function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, focusedNodeId, onFocus, activePath, onGenerate, onGenerateBranch, onGenerateSubtree, isGeneratingNodeId }) {
702
- const [collapsed, setCollapsed] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
742
+ const [collapsed, setCollapsed] = (0, import_react9.useState)(/* @__PURE__ */ new Set());
703
743
  const toggleCollapse = (id) => {
704
744
  setCollapsed((prev) => {
705
745
  const next = new Set(prev);
@@ -725,10 +765,315 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
725
765
  if (e.target === e.currentTarget) onFocus(null);
726
766
  }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "w-full flex flex-col", children: roots.map((root) => renderNode(root, 0)) }) });
727
767
  }
768
+
769
+ // src/components/AvatarArchitectApp.tsx
770
+ var import_react10 = require("react");
771
+ var import_react11 = require("motion/react");
772
+ var import_jsx_runtime10 = require("react/jsx-runtime");
773
+ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia }) {
774
+ (0, import_react10.useEffect)(() => {
775
+ const id = "flow-styles";
776
+ if (!document.getElementById(id)) {
777
+ const style = document.createElement("style");
778
+ style.id = id;
779
+ style.textContent = GLOBAL_STYLES;
780
+ document.head.appendChild(style);
781
+ }
782
+ }, []);
783
+ const [nodes, setNodes] = (0, import_react10.useState)([{ id: "1", type: "custom", position: { x: 0, y: 0 }, data: { label: "Fine Art Project", placeholder: "Name..." } }]);
784
+ const [edges, setEdges] = (0, import_react10.useState)([]);
785
+ const [history, setHistory] = (0, import_react10.useState)([]);
786
+ const [galleryItems, setGalleryItems] = (0, import_react10.useState)([]);
787
+ const [activePrompt, setActivePrompt] = (0, import_react10.useState)("");
788
+ const [isSynthesizing, setIsSynthesizing] = (0, import_react10.useState)(false);
789
+ const [activeGenerationsCount, setActiveGenerationsCount] = (0, import_react10.useState)(0);
790
+ const [currentResult, setCurrentResult] = (0, import_react10.useState)(null);
791
+ const [focusedNodeId, setFocusedNodeId] = (0, import_react10.useState)(null);
792
+ const [activeTab, setActiveTab] = (0, import_react10.useState)("history");
793
+ const [aspectRatio, setAspectRatio] = (0, import_react10.useState)("1:1");
794
+ const [selectedModel, setSelectedModel] = (0, import_react10.useState)("\u{1F34C} Nano Banana Pro");
795
+ const [seed, setSeed] = (0, import_react10.useState)(Math.floor(Math.random() * 1e6));
796
+ const [seedMode, setSeedMode] = (0, import_react10.useState)("random");
797
+ const [isLeftCollapsed, setIsLeftCollapsed] = (0, import_react10.useState)(false);
798
+ const [isRightCollapsed, setIsRightCollapsed] = (0, import_react10.useState)(false);
799
+ const [isPromptCollapsed, setIsPromptCollapsed] = (0, import_react10.useState)(false);
800
+ const [projectActionState, setProjectActionState] = (0, import_react10.useState)("idle");
801
+ const isGenerating = activeGenerationsCount > 0;
802
+ useKeyboardNavigation(history, currentResult, setCurrentResult);
803
+ const getSubtreeFormat = (0, import_react10.useCallback)((nodeId, depth = 0) => {
804
+ const node = nodes.find((n) => n.id === nodeId);
805
+ if (!node) return "";
806
+ const childrenIds = edges.filter((e) => e.source === nodeId).map((e) => e.target);
807
+ const indent = " ".repeat(depth);
808
+ return `${indent}- ${node.data.label || "(unbenannt)"}
809
+ ` + childrenIds.map((id) => getSubtreeFormat(id, depth + 1)).join("");
810
+ }, [nodes, edges]);
811
+ const activePath = (0, import_react10.useMemo)(() => {
812
+ if (!focusedNodeId) return /* @__PURE__ */ new Set();
813
+ const path = /* @__PURE__ */ new Set([focusedNodeId]);
814
+ let currId = focusedNodeId;
815
+ const parentMap = /* @__PURE__ */ new Map();
816
+ edges.forEach((e) => parentMap.set(e.target, e.source));
817
+ while (parentMap.has(currId)) {
818
+ currId = parentMap.get(currId);
819
+ path.add(currId);
820
+ }
821
+ return path;
822
+ }, [focusedNodeId, edges]);
823
+ const handleGenerateImage = async (customPrompt, useReferenceId, overrideNodeId, options = { silent: false }) => {
824
+ const promptToUse = (customPrompt || activePrompt).trim();
825
+ if (!promptToUse) return;
826
+ setActiveGenerationsCount((prev) => prev + 1);
827
+ const genId = crypto.randomUUID();
828
+ const activeSeed = seedMode === "random" ? Math.floor(Math.random() * 1e9) : seed;
829
+ const newGen = {
830
+ id: genId,
831
+ nodeId: overrideNodeId || focusedNodeId || "1",
832
+ status: "processing",
833
+ timestamp: Date.now(),
834
+ prompt: promptToUse,
835
+ seed: activeSeed,
836
+ model: selectedModel,
837
+ tags: [],
838
+ type: "generation"
839
+ };
840
+ setHistory((prev) => [newGen, ...prev]);
841
+ if (!options.silent) setCurrentResult(newGen);
842
+ if (seedMode === "random") setSeed(activeSeed);
843
+ try {
844
+ const { base64, mediaId } = await onGenerateImage(promptToUse, aspectRatio, selectedModel, useReferenceId);
845
+ const finishedGen = { ...newGen, status: "done", base64: `data:image/png;base64,${base64}`, mediaId };
846
+ setHistory((prev) => prev.map((g) => g.id === genId ? finishedGen : g));
847
+ setGalleryItems((prev) => [finishedGen, ...prev]);
848
+ setCurrentResult((prev) => {
849
+ if (!prev) return finishedGen;
850
+ if (prev.id === genId || !options.silent) return finishedGen;
851
+ return prev;
852
+ });
853
+ } catch (err) {
854
+ const errorGen = { ...newGen, status: "error", error: { message: err.message || "Unbekannter Fehler", details: err.toString() } };
855
+ setHistory((prev) => prev.map((g) => g.id === genId ? errorGen : g));
856
+ if (!options.silent || currentResult?.id === genId) setCurrentResult(errorGen);
857
+ } finally {
858
+ setActiveGenerationsCount((prev) => Math.max(0, prev - 1));
859
+ }
860
+ };
861
+ const handleSynthesizePrompt = async (nodeId, autoGenerate = false) => {
862
+ setIsSynthesizing(true);
863
+ try {
864
+ const prompt = await onGeneratePrompt(getSubtreeFormat(nodeId));
865
+ setActivePrompt(prompt);
866
+ setFocusedNodeId(nodeId);
867
+ if (autoGenerate) handleGenerateImage(prompt, void 0, nodeId);
868
+ } catch {
869
+ setActivePrompt("Synthese-Fehler");
870
+ } finally {
871
+ setIsSynthesizing(false);
872
+ }
873
+ };
874
+ const handleDownloadSingle = async () => {
875
+ if (!currentResult?.base64) return;
876
+ try {
877
+ const base64Data = currentResult.base64.split(",")[1];
878
+ const mimeType = currentResult.base64.split(";")[0].split(":")[1] || "image/png";
879
+ let finalBase64 = base64Data;
880
+ if (mimeType === "image/png") {
881
+ finalBase64 = injectXMPMetadata(base64Data, currentResult.prompt || "", currentResult.seed, currentResult.model, currentResult.id, currentResult.tags || [], getSubtreeFormat(currentResult.nodeId));
882
+ }
883
+ await onDownload(finalBase64, mimeType, `avatar_${currentResult.id.slice(0, 5)}.${mimeType.split("/")[1]}`);
884
+ } catch (e) {
885
+ console.error("Download Error", e);
886
+ }
887
+ };
888
+ const handleProjectExport = async () => {
889
+ setProjectActionState("working-full");
890
+ try {
891
+ const { base64 } = await exportProjectToZip(nodes, edges, history, galleryItems, { aspectRatio, selectedModel, seed, seedMode });
892
+ await onDownload(base64, "application/zip", `avatar_project_${getFormattedTimestamp()}.zip`);
893
+ setProjectActionState("done");
894
+ setTimeout(() => setProjectActionState("idle"), 3e3);
895
+ } catch {
896
+ setProjectActionState("error");
897
+ }
898
+ };
899
+ const handleProjectImport = async (input) => {
900
+ const file = input instanceof FileList ? input[0] : input;
901
+ if (!file) return;
902
+ setProjectActionState("working-full");
903
+ try {
904
+ const data = await importProjectFromZip(file);
905
+ if (data.nodes) setNodes(data.nodes);
906
+ if (data.edges) setEdges(data.edges);
907
+ if (data.history) setHistory(data.history);
908
+ if (data.galleryItems) setGalleryItems(data.galleryItems);
909
+ if (data.settings) {
910
+ setAspectRatio(data.settings.aspectRatio || "1:1");
911
+ setSelectedModel(data.settings.selectedModel || "\u{1F34C} Nano Banana Pro");
912
+ setSeed(data.settings.seed || 0);
913
+ setSeedMode(data.settings.seedMode || "random");
914
+ }
915
+ setProjectActionState("done");
916
+ setTimeout(() => setProjectActionState("idle"), 2e3);
917
+ } catch {
918
+ setProjectActionState("error");
919
+ setTimeout(() => setProjectActionState("idle"), 4e3);
920
+ }
921
+ };
922
+ const handleWorkspaceImport = (file) => {
923
+ const reader = new FileReader();
924
+ reader.onload = (ev) => {
925
+ try {
926
+ const root = JSON.parse(ev.target?.result);
927
+ const rawTags = root.tags || root;
928
+ if (!rawTags?.by_category) return;
929
+ } catch (err) {
930
+ console.error("Workspace Load failed", err);
931
+ }
932
+ };
933
+ reader.readAsText(file);
934
+ };
935
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex h-screen w-screen bg-[#0e0e0e] text-white overflow-hidden", children: [
936
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react11.motion.div, { animate: { width: isLeftCollapsed ? 48 : 260 }, className: "flex flex-col border-r border-white/5 overflow-hidden relative bg-black/10 shrink-0", children: [
937
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "h-14 px-4 border-b border-white/5 flex items-center justify-between shrink-0", children: [
938
+ !isLeftCollapsed && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[10px] font-bold uppercase text-white/40", children: "Hierarchie" }),
939
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { onClick: () => setIsLeftCollapsed(!isLeftCollapsed), className: "material-symbols-outlined text-[18px] text-white/40 hover:text-white transition-all", children: isLeftCollapsed ? "chevron_right" : "chevron_left" })
940
+ ] }),
941
+ !isLeftCollapsed && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
942
+ ListView,
943
+ {
944
+ nodes,
945
+ edges,
946
+ onNodeChange: (id, l) => setNodes((nds) => nds.map((n) => n.id === id ? { ...n, data: { ...n.data, label: l } } : n)),
947
+ onAddChild: (pId) => {
948
+ const newId = crypto.randomUUID();
949
+ setNodes((nds) => [...nds, { id: newId, type: "custom", position: { x: 0, y: 0 }, data: { label: "", placeholder: "Konzept..." } }]);
950
+ setEdges((eds) => [...eds, { id: `e-${pId}-${newId}`, source: pId, target: newId }]);
951
+ setFocusedNodeId(newId);
952
+ return newId;
953
+ },
954
+ onDeleteNode: (id) => {
955
+ setNodes((nds) => nds.filter((n) => n.id !== id));
956
+ setEdges((eds) => eds.filter((e) => e.source !== id && e.target !== id));
957
+ },
958
+ onFocus: setFocusedNodeId,
959
+ focusedNodeId,
960
+ activePath,
961
+ onGenerate: handleSynthesizePrompt,
962
+ onGenerateBranch: (id) => handleSynthesizePrompt(id, true),
963
+ isGeneratingNodeId: (id) => isSynthesizing && focusedNodeId === id
964
+ }
965
+ )
966
+ ] }),
967
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex-1 flex flex-col bg-[#0b0b0b] overflow-hidden", children: [
968
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "h-14 border-b border-white/5 flex items-center px-4 gap-2 justify-between shrink-0 bg-black/20", children: [
969
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-1.5", children: [
970
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(CompactDropdown, { value: aspectRatio, onChange: setAspectRatio, options: [{ label: "1:1", value: "1:1" }, { label: "16:9", value: "16:9" }, { label: "9:16", value: "9:16" }] }),
971
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(CompactDropdown, { value: selectedModel, onChange: setSelectedModel, options: [{ value: "\u{1F34C} Nano Banana Pro", label: "\u{1F34C} Nano Banana Pro" }, { value: "\u{1F34C} Nano Banana 2", label: "\u{1F34C} Nano Banana 2" }, { value: "Imagen 4", label: "Imagen 4" }] })
972
+ ] }),
973
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2", children: [
974
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { onClick: () => setIsPromptCollapsed(!isPromptCollapsed), className: "text-white/40 hover:text-white transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "material-symbols-outlined", children: isPromptCollapsed ? "expand_more" : "expand_less" }) }),
975
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PillButton, { variant: "solid", icon: "bolt", loading: isGenerating, disabled: !activePrompt.trim(), onClick: () => handleGenerateImage(), children: "Generieren" })
976
+ ] })
977
+ ] }),
978
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex-1 flex flex-col overflow-hidden relative", children: [
979
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react11.AnimatePresence, { children: !isPromptCollapsed && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react11.motion.div, { initial: { height: 0 }, animate: { height: "auto" }, exit: { height: 0 }, className: "px-6 py-4 border-b border-white/5 bg-black/10 overflow-hidden shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: `relative min-h-[60px] p-4 rounded-2xl border transition-all ${isSynthesizing ? "prompt-loading" : "bg-white/5 border-white/10"}`, children: [
980
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
981
+ "textarea",
982
+ {
983
+ value: activePrompt,
984
+ onChange: (e) => setActivePrompt(e.target.value),
985
+ className: "w-full bg-transparent border-none outline-none text-[12px] leading-relaxed text-white/80 resize-none h-20 dark-scrollbar",
986
+ placeholder: "W\xE4hle einen Knoten oder tippe einen Prompt..."
987
+ }
988
+ ),
989
+ activePrompt && !isSynthesizing && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { onClick: () => setActivePrompt(""), className: "absolute top-2 right-2 w-6 h-6 rounded-full bg-white/5 hover:bg-white/10 flex items-center justify-center transition-colors text-white/20 hover:text-white", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "material-symbols-outlined text-[14px]", children: "close" }) })
990
+ ] }) }) }),
991
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex-1 p-6 overflow-hidden flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "h-full w-full max-w-4xl aspect-square rounded-3xl border border-white/5 bg-black/40 relative overflow-hidden flex items-center justify-center group shadow-2xl", children: [
992
+ isGenerating && currentResult?.status === "done" && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "absolute top-6 right-6 z-30 bg-black/60 backdrop-blur-md px-4 py-2 rounded-full border border-white/10 flex items-center gap-3", children: [
993
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "w-3 h-3 border-t-2 border-white rounded-full animate-spin" }),
994
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[10px] text-white/60 uppercase font-bold tracking-widest", children: "Neue Referenz..." })
995
+ ] }),
996
+ currentResult ? currentResult.status === "processing" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
997
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "w-10 h-10 border-t-2 border-white rounded-full animate-spin" }),
998
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[10px] text-white/40 uppercase font-bold tracking-widest", children: "Erstelle Bild..." })
999
+ ] }) : currentResult.status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "p-10 text-center flex flex-col items-center gap-5 max-w-md", children: [
1000
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "w-16 h-16 rounded-full bg-red-500/10 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "material-symbols-outlined text-red-500 text-[32px]", children: "warning" }) }),
1001
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-2", children: [
1002
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { className: "text-[11px] font-bold uppercase tracking-widest text-red-400", children: "Generierungsfehler" }),
1003
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-white/60 text-[12px] leading-relaxed", children: currentResult.error?.message })
1004
+ ] }),
1005
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PillButton, { variant: "outline", icon: "refresh", onClick: () => handleGenerateImage(currentResult.prompt), children: "Erneut versuchen" })
1006
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "h-full w-full relative flex items-center justify-center", children: [
1007
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("img", { src: currentResult.base64, className: "max-h-full max-w-full object-contain rounded-xl shadow-2xl" }),
1008
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "absolute bottom-6 flex gap-2 opacity-0 group-hover:opacity-100 transition-all translate-y-4 group-hover:translate-y-0 z-20", children: [
1009
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PillButton, { variant: "outline", icon: "replay", onClick: () => setActivePrompt(currentResult.prompt || ""), children: "Prompt" }),
1010
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PillButton, { variant: "solid", icon: "auto_fix_high", onClick: () => handleGenerateImage(currentResult.prompt || activePrompt, currentResult.mediaId, void 0, { silent: true }), children: "Referenz" }),
1011
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PillButton, { variant: "outline", icon: "download", onClick: handleDownloadSingle, children: "Speichern" })
1012
+ ] })
1013
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col items-center gap-2 opacity-10", children: [
1014
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "material-symbols-outlined text-[100px]", children: "palette" }),
1015
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[12px] font-bold uppercase tracking-[0.2em]", children: "Avatar Architect" })
1016
+ ] })
1017
+ ] }) })
1018
+ ] })
1019
+ ] }),
1020
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react11.motion.div, { animate: { width: isRightCollapsed ? 60 : 320 }, className: "flex flex-col border-l border-white/5 bg-[#0e0e0e] shrink-0", children: [
1021
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex border-b border-white/5 h-14 shrink-0 overflow-hidden", children: [
1022
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex flex-1", children: ["history", "gallery", "inspect", "setup"].map((tab) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { onClick: () => {
1023
+ setActiveTab(tab);
1024
+ setIsRightCollapsed(false);
1025
+ }, className: `flex-1 flex items-center justify-center relative transition-colors ${activeTab === tab ? "text-white" : "text-white/20"}`, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "material-symbols-outlined text-[20px]", children: tab === "history" ? "history" : tab === "gallery" ? "photo_library" : tab === "inspect" ? "info" : "settings" }) }, tab)) }),
1026
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { onClick: () => setIsRightCollapsed(!isRightCollapsed), className: "w-10 flex items-center justify-center text-white/20 hover:text-white", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "material-symbols-outlined text-[18px]", children: isRightCollapsed ? "chevron_left" : "chevron_right" }) })
1027
+ ] }),
1028
+ !isRightCollapsed && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "flex-1 overflow-hidden relative", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react11.AnimatePresence, { mode: "wait", children: [
1029
+ activeTab === "history" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(HistoryPanel, { history, currentResultId: currentResult?.id || null, onSelect: setCurrentResult, onDelete: (id) => setHistory((h) => h.filter((x) => x.id !== id)) }, "history"),
1030
+ activeTab === "gallery" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1031
+ MediaLibrary,
1032
+ {
1033
+ items: galleryItems,
1034
+ onImport: async () => {
1035
+ const media = await onSelectMedia();
1036
+ if (!media?.length) return;
1037
+ const newItems = media.map((m) => ({
1038
+ id: crypto.randomUUID(),
1039
+ nodeId: "1",
1040
+ base64: `data:${m.mimeType};base64,${m.base64}`,
1041
+ mediaId: m.mediaId,
1042
+ prompt: m.name || "Importiert",
1043
+ timestamp: Date.now(),
1044
+ status: "done",
1045
+ tags: [],
1046
+ type: "import"
1047
+ }));
1048
+ setGalleryItems((prev) => [...newItems, ...prev]);
1049
+ },
1050
+ onDelete: (id) => setGalleryItems((g) => g.filter((x) => x.id !== id)),
1051
+ onSelect: setCurrentResult,
1052
+ onGenerateReference: (item) => handleGenerateImage(item.prompt || activePrompt, item.mediaId, void 0, { silent: true })
1053
+ },
1054
+ "gallery"
1055
+ ),
1056
+ activeTab === "inspect" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(InspectPanel, { currentResult, history, onSelect: setCurrentResult }, "inspect"),
1057
+ activeTab === "setup" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1058
+ SetupPanel,
1059
+ {
1060
+ onProjectExport: handleProjectExport,
1061
+ onProjectImport: handleProjectImport,
1062
+ onWorkspaceImport: handleWorkspaceImport,
1063
+ projectActionState
1064
+ },
1065
+ "setup"
1066
+ )
1067
+ ] }) })
1068
+ ] })
1069
+ ] });
1070
+ }
728
1071
  // Annotate the CommonJS export names for ESM import in node:
729
1072
  0 && (module.exports = {
1073
+ AvatarArchitectApp,
730
1074
  CompactDropdown,
731
1075
  FaToolsBadge,
1076
+ GLOBAL_STYLES,
732
1077
  HistoryPanel,
733
1078
  InspectPanel,
734
1079
  ListView,
@@ -738,8 +1083,10 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
738
1083
  SetupPanel,
739
1084
  exportProjectToZip,
740
1085
  formatTreeToMarkdown,
1086
+ getFormattedTimestamp,
741
1087
  importProjectFromZip,
742
1088
  injectXMPMetadata,
743
1089
  parsePromptFile,
1090
+ useKeyboardNavigation,
744
1091
  useOnClickOutside
745
1092
  });
package/dist/index.mjs CHANGED
@@ -15,6 +15,42 @@ function useOnClickOutside(ref, handler) {
15
15
  }, [ref, handler]);
16
16
  }
17
17
 
18
+ // src/hooks/useKeyboardNavigation.ts
19
+ import { useEffect as useEffect2 } from "react";
20
+ function useKeyboardNavigation(history, currentResult, setCurrentResult) {
21
+ useEffect2(() => {
22
+ const handleKeyDown = (e) => {
23
+ if (e.target instanceof HTMLTextAreaElement || e.target instanceof HTMLInputElement) return;
24
+ if (!currentResult || history.length === 0) return;
25
+ const currentIndex = history.findIndex((h) => h.id === currentResult.id);
26
+ if (e.key === "ArrowRight" && currentIndex < history.length - 1) {
27
+ setCurrentResult(history[currentIndex + 1]);
28
+ } else if (e.key === "ArrowLeft" && currentIndex > 0) {
29
+ setCurrentResult(history[currentIndex - 1]);
30
+ }
31
+ };
32
+ window.addEventListener("keydown", handleKeyDown);
33
+ return () => window.removeEventListener("keydown", handleKeyDown);
34
+ }, [currentResult, history, setCurrentResult]);
35
+ }
36
+
37
+ // src/lib/utils.ts
38
+ var getFormattedTimestamp = () => {
39
+ const d = /* @__PURE__ */ new Date();
40
+ const pad = (n) => n.toString().padStart(2, "0");
41
+ return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
42
+ };
43
+ var GLOBAL_STYLES = `
44
+ .dark-scrollbar::-webkit-scrollbar { width: 4px; height: 4px; }
45
+ .dark-scrollbar::-webkit-scrollbar-track { background: transparent; }
46
+ .dark-scrollbar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }
47
+ html, body, #root { margin: 0; padding: 0; width: 100%; height: 100%; background: #0e0e0e; font-family: 'Google Sans Text', sans-serif; overflow: hidden; }
48
+ @keyframes prompt-shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }
49
+ .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; }
50
+ @keyframes dropdown-enter { from { opacity: 0; transform: scale(0.95) translateY(-5px); } to { opacity: 1; transform: scale(1) translateY(0); } }
51
+ .animate-dropdown { animation: dropdown-enter 0.1s ease-out forwards; }
52
+ `;
53
+
18
54
  // src/lib/metadata.ts
19
55
  var crcTable = (() => {
20
56
  let c;
@@ -584,7 +620,7 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
584
620
  };
585
621
 
586
622
  // src/components/ListView.tsx
587
- import { useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
623
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
588
624
  import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
589
625
  var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, isActive, isInPath, onFocus, onGenerate, onGenerateBranch, onGenerateSubtree, isGenerating, isCollapsed, toggleCollapse, renderNode, children }) => {
590
626
  const inputRef = useRef3(null);
@@ -615,7 +651,7 @@ var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNod
615
651
  onMoveNode(node.id, "down");
616
652
  }
617
653
  };
618
- useEffect2(() => {
654
+ useEffect3(() => {
619
655
  if (isActive && inputRef.current) inputRef.current.focus();
620
656
  }, [isActive]);
621
657
  return /* @__PURE__ */ jsxs7("div", { className: "flex flex-col w-full", children: [
@@ -675,9 +711,314 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
675
711
  if (e.target === e.currentTarget) onFocus(null);
676
712
  }, children: /* @__PURE__ */ jsx9("div", { className: "w-full flex flex-col", children: roots.map((root) => renderNode(root, 0)) }) });
677
713
  }
714
+
715
+ // src/components/AvatarArchitectApp.tsx
716
+ import { useState as useState3, useCallback, useMemo, useEffect as useEffect4 } from "react";
717
+ import { motion as motion5, AnimatePresence } from "motion/react";
718
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
719
+ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia }) {
720
+ useEffect4(() => {
721
+ const id = "flow-styles";
722
+ if (!document.getElementById(id)) {
723
+ const style = document.createElement("style");
724
+ style.id = id;
725
+ style.textContent = GLOBAL_STYLES;
726
+ document.head.appendChild(style);
727
+ }
728
+ }, []);
729
+ const [nodes, setNodes] = useState3([{ id: "1", type: "custom", position: { x: 0, y: 0 }, data: { label: "Fine Art Project", placeholder: "Name..." } }]);
730
+ const [edges, setEdges] = useState3([]);
731
+ const [history, setHistory] = useState3([]);
732
+ const [galleryItems, setGalleryItems] = useState3([]);
733
+ const [activePrompt, setActivePrompt] = useState3("");
734
+ const [isSynthesizing, setIsSynthesizing] = useState3(false);
735
+ const [activeGenerationsCount, setActiveGenerationsCount] = useState3(0);
736
+ const [currentResult, setCurrentResult] = useState3(null);
737
+ const [focusedNodeId, setFocusedNodeId] = useState3(null);
738
+ const [activeTab, setActiveTab] = useState3("history");
739
+ const [aspectRatio, setAspectRatio] = useState3("1:1");
740
+ const [selectedModel, setSelectedModel] = useState3("\u{1F34C} Nano Banana Pro");
741
+ const [seed, setSeed] = useState3(Math.floor(Math.random() * 1e6));
742
+ const [seedMode, setSeedMode] = useState3("random");
743
+ const [isLeftCollapsed, setIsLeftCollapsed] = useState3(false);
744
+ const [isRightCollapsed, setIsRightCollapsed] = useState3(false);
745
+ const [isPromptCollapsed, setIsPromptCollapsed] = useState3(false);
746
+ const [projectActionState, setProjectActionState] = useState3("idle");
747
+ const isGenerating = activeGenerationsCount > 0;
748
+ useKeyboardNavigation(history, currentResult, setCurrentResult);
749
+ const getSubtreeFormat = useCallback((nodeId, depth = 0) => {
750
+ const node = nodes.find((n) => n.id === nodeId);
751
+ if (!node) return "";
752
+ const childrenIds = edges.filter((e) => e.source === nodeId).map((e) => e.target);
753
+ const indent = " ".repeat(depth);
754
+ return `${indent}- ${node.data.label || "(unbenannt)"}
755
+ ` + childrenIds.map((id) => getSubtreeFormat(id, depth + 1)).join("");
756
+ }, [nodes, edges]);
757
+ const activePath = useMemo(() => {
758
+ if (!focusedNodeId) return /* @__PURE__ */ new Set();
759
+ const path = /* @__PURE__ */ new Set([focusedNodeId]);
760
+ let currId = focusedNodeId;
761
+ const parentMap = /* @__PURE__ */ new Map();
762
+ edges.forEach((e) => parentMap.set(e.target, e.source));
763
+ while (parentMap.has(currId)) {
764
+ currId = parentMap.get(currId);
765
+ path.add(currId);
766
+ }
767
+ return path;
768
+ }, [focusedNodeId, edges]);
769
+ const handleGenerateImage = async (customPrompt, useReferenceId, overrideNodeId, options = { silent: false }) => {
770
+ const promptToUse = (customPrompt || activePrompt).trim();
771
+ if (!promptToUse) return;
772
+ setActiveGenerationsCount((prev) => prev + 1);
773
+ const genId = crypto.randomUUID();
774
+ const activeSeed = seedMode === "random" ? Math.floor(Math.random() * 1e9) : seed;
775
+ const newGen = {
776
+ id: genId,
777
+ nodeId: overrideNodeId || focusedNodeId || "1",
778
+ status: "processing",
779
+ timestamp: Date.now(),
780
+ prompt: promptToUse,
781
+ seed: activeSeed,
782
+ model: selectedModel,
783
+ tags: [],
784
+ type: "generation"
785
+ };
786
+ setHistory((prev) => [newGen, ...prev]);
787
+ if (!options.silent) setCurrentResult(newGen);
788
+ if (seedMode === "random") setSeed(activeSeed);
789
+ try {
790
+ const { base64, mediaId } = await onGenerateImage(promptToUse, aspectRatio, selectedModel, useReferenceId);
791
+ const finishedGen = { ...newGen, status: "done", base64: `data:image/png;base64,${base64}`, mediaId };
792
+ setHistory((prev) => prev.map((g) => g.id === genId ? finishedGen : g));
793
+ setGalleryItems((prev) => [finishedGen, ...prev]);
794
+ setCurrentResult((prev) => {
795
+ if (!prev) return finishedGen;
796
+ if (prev.id === genId || !options.silent) return finishedGen;
797
+ return prev;
798
+ });
799
+ } catch (err) {
800
+ const errorGen = { ...newGen, status: "error", error: { message: err.message || "Unbekannter Fehler", details: err.toString() } };
801
+ setHistory((prev) => prev.map((g) => g.id === genId ? errorGen : g));
802
+ if (!options.silent || currentResult?.id === genId) setCurrentResult(errorGen);
803
+ } finally {
804
+ setActiveGenerationsCount((prev) => Math.max(0, prev - 1));
805
+ }
806
+ };
807
+ const handleSynthesizePrompt = async (nodeId, autoGenerate = false) => {
808
+ setIsSynthesizing(true);
809
+ try {
810
+ const prompt = await onGeneratePrompt(getSubtreeFormat(nodeId));
811
+ setActivePrompt(prompt);
812
+ setFocusedNodeId(nodeId);
813
+ if (autoGenerate) handleGenerateImage(prompt, void 0, nodeId);
814
+ } catch {
815
+ setActivePrompt("Synthese-Fehler");
816
+ } finally {
817
+ setIsSynthesizing(false);
818
+ }
819
+ };
820
+ const handleDownloadSingle = async () => {
821
+ if (!currentResult?.base64) return;
822
+ try {
823
+ const base64Data = currentResult.base64.split(",")[1];
824
+ const mimeType = currentResult.base64.split(";")[0].split(":")[1] || "image/png";
825
+ let finalBase64 = base64Data;
826
+ if (mimeType === "image/png") {
827
+ finalBase64 = injectXMPMetadata(base64Data, currentResult.prompt || "", currentResult.seed, currentResult.model, currentResult.id, currentResult.tags || [], getSubtreeFormat(currentResult.nodeId));
828
+ }
829
+ await onDownload(finalBase64, mimeType, `avatar_${currentResult.id.slice(0, 5)}.${mimeType.split("/")[1]}`);
830
+ } catch (e) {
831
+ console.error("Download Error", e);
832
+ }
833
+ };
834
+ const handleProjectExport = async () => {
835
+ setProjectActionState("working-full");
836
+ try {
837
+ const { base64 } = await exportProjectToZip(nodes, edges, history, galleryItems, { aspectRatio, selectedModel, seed, seedMode });
838
+ await onDownload(base64, "application/zip", `avatar_project_${getFormattedTimestamp()}.zip`);
839
+ setProjectActionState("done");
840
+ setTimeout(() => setProjectActionState("idle"), 3e3);
841
+ } catch {
842
+ setProjectActionState("error");
843
+ }
844
+ };
845
+ const handleProjectImport = async (input) => {
846
+ const file = input instanceof FileList ? input[0] : input;
847
+ if (!file) return;
848
+ setProjectActionState("working-full");
849
+ try {
850
+ const data = await importProjectFromZip(file);
851
+ if (data.nodes) setNodes(data.nodes);
852
+ if (data.edges) setEdges(data.edges);
853
+ if (data.history) setHistory(data.history);
854
+ if (data.galleryItems) setGalleryItems(data.galleryItems);
855
+ if (data.settings) {
856
+ setAspectRatio(data.settings.aspectRatio || "1:1");
857
+ setSelectedModel(data.settings.selectedModel || "\u{1F34C} Nano Banana Pro");
858
+ setSeed(data.settings.seed || 0);
859
+ setSeedMode(data.settings.seedMode || "random");
860
+ }
861
+ setProjectActionState("done");
862
+ setTimeout(() => setProjectActionState("idle"), 2e3);
863
+ } catch {
864
+ setProjectActionState("error");
865
+ setTimeout(() => setProjectActionState("idle"), 4e3);
866
+ }
867
+ };
868
+ const handleWorkspaceImport = (file) => {
869
+ const reader = new FileReader();
870
+ reader.onload = (ev) => {
871
+ try {
872
+ const root = JSON.parse(ev.target?.result);
873
+ const rawTags = root.tags || root;
874
+ if (!rawTags?.by_category) return;
875
+ } catch (err) {
876
+ console.error("Workspace Load failed", err);
877
+ }
878
+ };
879
+ reader.readAsText(file);
880
+ };
881
+ return /* @__PURE__ */ jsxs8("div", { className: "flex h-screen w-screen bg-[#0e0e0e] text-white overflow-hidden", children: [
882
+ /* @__PURE__ */ jsxs8(motion5.div, { animate: { width: isLeftCollapsed ? 48 : 260 }, className: "flex flex-col border-r border-white/5 overflow-hidden relative bg-black/10 shrink-0", children: [
883
+ /* @__PURE__ */ jsxs8("div", { className: "h-14 px-4 border-b border-white/5 flex items-center justify-between shrink-0", children: [
884
+ !isLeftCollapsed && /* @__PURE__ */ jsx10("span", { className: "text-[10px] font-bold uppercase text-white/40", children: "Hierarchie" }),
885
+ /* @__PURE__ */ jsx10("button", { onClick: () => setIsLeftCollapsed(!isLeftCollapsed), className: "material-symbols-outlined text-[18px] text-white/40 hover:text-white transition-all", children: isLeftCollapsed ? "chevron_right" : "chevron_left" })
886
+ ] }),
887
+ !isLeftCollapsed && /* @__PURE__ */ jsx10(
888
+ ListView,
889
+ {
890
+ nodes,
891
+ edges,
892
+ onNodeChange: (id, l) => setNodes((nds) => nds.map((n) => n.id === id ? { ...n, data: { ...n.data, label: l } } : n)),
893
+ onAddChild: (pId) => {
894
+ const newId = crypto.randomUUID();
895
+ setNodes((nds) => [...nds, { id: newId, type: "custom", position: { x: 0, y: 0 }, data: { label: "", placeholder: "Konzept..." } }]);
896
+ setEdges((eds) => [...eds, { id: `e-${pId}-${newId}`, source: pId, target: newId }]);
897
+ setFocusedNodeId(newId);
898
+ return newId;
899
+ },
900
+ onDeleteNode: (id) => {
901
+ setNodes((nds) => nds.filter((n) => n.id !== id));
902
+ setEdges((eds) => eds.filter((e) => e.source !== id && e.target !== id));
903
+ },
904
+ onFocus: setFocusedNodeId,
905
+ focusedNodeId,
906
+ activePath,
907
+ onGenerate: handleSynthesizePrompt,
908
+ onGenerateBranch: (id) => handleSynthesizePrompt(id, true),
909
+ isGeneratingNodeId: (id) => isSynthesizing && focusedNodeId === id
910
+ }
911
+ )
912
+ ] }),
913
+ /* @__PURE__ */ jsxs8("div", { className: "flex-1 flex flex-col bg-[#0b0b0b] overflow-hidden", children: [
914
+ /* @__PURE__ */ jsxs8("div", { className: "h-14 border-b border-white/5 flex items-center px-4 gap-2 justify-between shrink-0 bg-black/20", children: [
915
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1.5", children: [
916
+ /* @__PURE__ */ jsx10(CompactDropdown, { value: aspectRatio, onChange: setAspectRatio, options: [{ label: "1:1", value: "1:1" }, { label: "16:9", value: "16:9" }, { label: "9:16", value: "9:16" }] }),
917
+ /* @__PURE__ */ jsx10(CompactDropdown, { value: selectedModel, onChange: setSelectedModel, options: [{ value: "\u{1F34C} Nano Banana Pro", label: "\u{1F34C} Nano Banana Pro" }, { value: "\u{1F34C} Nano Banana 2", label: "\u{1F34C} Nano Banana 2" }, { value: "Imagen 4", label: "Imagen 4" }] })
918
+ ] }),
919
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
920
+ /* @__PURE__ */ jsx10("button", { onClick: () => setIsPromptCollapsed(!isPromptCollapsed), className: "text-white/40 hover:text-white transition-colors", children: /* @__PURE__ */ jsx10("span", { className: "material-symbols-outlined", children: isPromptCollapsed ? "expand_more" : "expand_less" }) }),
921
+ /* @__PURE__ */ jsx10(PillButton, { variant: "solid", icon: "bolt", loading: isGenerating, disabled: !activePrompt.trim(), onClick: () => handleGenerateImage(), children: "Generieren" })
922
+ ] })
923
+ ] }),
924
+ /* @__PURE__ */ jsxs8("div", { className: "flex-1 flex flex-col overflow-hidden relative", children: [
925
+ /* @__PURE__ */ jsx10(AnimatePresence, { children: !isPromptCollapsed && /* @__PURE__ */ jsx10(motion5.div, { initial: { height: 0 }, animate: { height: "auto" }, exit: { height: 0 }, className: "px-6 py-4 border-b border-white/5 bg-black/10 overflow-hidden shrink-0", children: /* @__PURE__ */ jsxs8("div", { className: `relative min-h-[60px] p-4 rounded-2xl border transition-all ${isSynthesizing ? "prompt-loading" : "bg-white/5 border-white/10"}`, children: [
926
+ /* @__PURE__ */ jsx10(
927
+ "textarea",
928
+ {
929
+ value: activePrompt,
930
+ onChange: (e) => setActivePrompt(e.target.value),
931
+ className: "w-full bg-transparent border-none outline-none text-[12px] leading-relaxed text-white/80 resize-none h-20 dark-scrollbar",
932
+ placeholder: "W\xE4hle einen Knoten oder tippe einen Prompt..."
933
+ }
934
+ ),
935
+ activePrompt && !isSynthesizing && /* @__PURE__ */ jsx10("button", { onClick: () => setActivePrompt(""), className: "absolute top-2 right-2 w-6 h-6 rounded-full bg-white/5 hover:bg-white/10 flex items-center justify-center transition-colors text-white/20 hover:text-white", children: /* @__PURE__ */ jsx10("span", { className: "material-symbols-outlined text-[14px]", children: "close" }) })
936
+ ] }) }) }),
937
+ /* @__PURE__ */ jsx10("div", { className: "flex-1 p-6 overflow-hidden flex items-center justify-center", children: /* @__PURE__ */ jsxs8("div", { className: "h-full w-full max-w-4xl aspect-square rounded-3xl border border-white/5 bg-black/40 relative overflow-hidden flex items-center justify-center group shadow-2xl", children: [
938
+ isGenerating && currentResult?.status === "done" && /* @__PURE__ */ jsxs8("div", { className: "absolute top-6 right-6 z-30 bg-black/60 backdrop-blur-md px-4 py-2 rounded-full border border-white/10 flex items-center gap-3", children: [
939
+ /* @__PURE__ */ jsx10("div", { className: "w-3 h-3 border-t-2 border-white rounded-full animate-spin" }),
940
+ /* @__PURE__ */ jsx10("span", { className: "text-[10px] text-white/60 uppercase font-bold tracking-widest", children: "Neue Referenz..." })
941
+ ] }),
942
+ currentResult ? currentResult.status === "processing" ? /* @__PURE__ */ jsxs8("div", { className: "flex flex-col items-center gap-4", children: [
943
+ /* @__PURE__ */ jsx10("div", { className: "w-10 h-10 border-t-2 border-white rounded-full animate-spin" }),
944
+ /* @__PURE__ */ jsx10("span", { className: "text-[10px] text-white/40 uppercase font-bold tracking-widest", children: "Erstelle Bild..." })
945
+ ] }) : currentResult.status === "error" ? /* @__PURE__ */ jsxs8("div", { className: "p-10 text-center flex flex-col items-center gap-5 max-w-md", children: [
946
+ /* @__PURE__ */ jsx10("div", { className: "w-16 h-16 rounded-full bg-red-500/10 flex items-center justify-center", children: /* @__PURE__ */ jsx10("span", { className: "material-symbols-outlined text-red-500 text-[32px]", children: "warning" }) }),
947
+ /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-2", children: [
948
+ /* @__PURE__ */ jsx10("h3", { className: "text-[11px] font-bold uppercase tracking-widest text-red-400", children: "Generierungsfehler" }),
949
+ /* @__PURE__ */ jsx10("p", { className: "text-white/60 text-[12px] leading-relaxed", children: currentResult.error?.message })
950
+ ] }),
951
+ /* @__PURE__ */ jsx10(PillButton, { variant: "outline", icon: "refresh", onClick: () => handleGenerateImage(currentResult.prompt), children: "Erneut versuchen" })
952
+ ] }) : /* @__PURE__ */ jsxs8("div", { className: "h-full w-full relative flex items-center justify-center", children: [
953
+ /* @__PURE__ */ jsx10("img", { src: currentResult.base64, className: "max-h-full max-w-full object-contain rounded-xl shadow-2xl" }),
954
+ /* @__PURE__ */ jsxs8("div", { className: "absolute bottom-6 flex gap-2 opacity-0 group-hover:opacity-100 transition-all translate-y-4 group-hover:translate-y-0 z-20", children: [
955
+ /* @__PURE__ */ jsx10(PillButton, { variant: "outline", icon: "replay", onClick: () => setActivePrompt(currentResult.prompt || ""), children: "Prompt" }),
956
+ /* @__PURE__ */ jsx10(PillButton, { variant: "solid", icon: "auto_fix_high", onClick: () => handleGenerateImage(currentResult.prompt || activePrompt, currentResult.mediaId, void 0, { silent: true }), children: "Referenz" }),
957
+ /* @__PURE__ */ jsx10(PillButton, { variant: "outline", icon: "download", onClick: handleDownloadSingle, children: "Speichern" })
958
+ ] })
959
+ ] }) : /* @__PURE__ */ jsxs8("div", { className: "flex flex-col items-center gap-2 opacity-10", children: [
960
+ /* @__PURE__ */ jsx10("span", { className: "material-symbols-outlined text-[100px]", children: "palette" }),
961
+ /* @__PURE__ */ jsx10("span", { className: "text-[12px] font-bold uppercase tracking-[0.2em]", children: "Avatar Architect" })
962
+ ] })
963
+ ] }) })
964
+ ] })
965
+ ] }),
966
+ /* @__PURE__ */ jsxs8(motion5.div, { animate: { width: isRightCollapsed ? 60 : 320 }, className: "flex flex-col border-l border-white/5 bg-[#0e0e0e] shrink-0", children: [
967
+ /* @__PURE__ */ jsxs8("div", { className: "flex border-b border-white/5 h-14 shrink-0 overflow-hidden", children: [
968
+ /* @__PURE__ */ jsx10("div", { className: "flex flex-1", children: ["history", "gallery", "inspect", "setup"].map((tab) => /* @__PURE__ */ jsx10("button", { onClick: () => {
969
+ setActiveTab(tab);
970
+ setIsRightCollapsed(false);
971
+ }, className: `flex-1 flex items-center justify-center relative transition-colors ${activeTab === tab ? "text-white" : "text-white/20"}`, children: /* @__PURE__ */ jsx10("span", { className: "material-symbols-outlined text-[20px]", children: tab === "history" ? "history" : tab === "gallery" ? "photo_library" : tab === "inspect" ? "info" : "settings" }) }, tab)) }),
972
+ /* @__PURE__ */ jsx10("button", { onClick: () => setIsRightCollapsed(!isRightCollapsed), className: "w-10 flex items-center justify-center text-white/20 hover:text-white", children: /* @__PURE__ */ jsx10("span", { className: "material-symbols-outlined text-[18px]", children: isRightCollapsed ? "chevron_left" : "chevron_right" }) })
973
+ ] }),
974
+ !isRightCollapsed && /* @__PURE__ */ jsx10("div", { className: "flex-1 overflow-hidden relative", children: /* @__PURE__ */ jsxs8(AnimatePresence, { mode: "wait", children: [
975
+ activeTab === "history" && /* @__PURE__ */ jsx10(HistoryPanel, { history, currentResultId: currentResult?.id || null, onSelect: setCurrentResult, onDelete: (id) => setHistory((h) => h.filter((x) => x.id !== id)) }, "history"),
976
+ activeTab === "gallery" && /* @__PURE__ */ jsx10(
977
+ MediaLibrary,
978
+ {
979
+ items: galleryItems,
980
+ onImport: async () => {
981
+ const media = await onSelectMedia();
982
+ if (!media?.length) return;
983
+ const newItems = media.map((m) => ({
984
+ id: crypto.randomUUID(),
985
+ nodeId: "1",
986
+ base64: `data:${m.mimeType};base64,${m.base64}`,
987
+ mediaId: m.mediaId,
988
+ prompt: m.name || "Importiert",
989
+ timestamp: Date.now(),
990
+ status: "done",
991
+ tags: [],
992
+ type: "import"
993
+ }));
994
+ setGalleryItems((prev) => [...newItems, ...prev]);
995
+ },
996
+ onDelete: (id) => setGalleryItems((g) => g.filter((x) => x.id !== id)),
997
+ onSelect: setCurrentResult,
998
+ onGenerateReference: (item) => handleGenerateImage(item.prompt || activePrompt, item.mediaId, void 0, { silent: true })
999
+ },
1000
+ "gallery"
1001
+ ),
1002
+ activeTab === "inspect" && /* @__PURE__ */ jsx10(InspectPanel, { currentResult, history, onSelect: setCurrentResult }, "inspect"),
1003
+ activeTab === "setup" && /* @__PURE__ */ jsx10(
1004
+ SetupPanel,
1005
+ {
1006
+ onProjectExport: handleProjectExport,
1007
+ onProjectImport: handleProjectImport,
1008
+ onWorkspaceImport: handleWorkspaceImport,
1009
+ projectActionState
1010
+ },
1011
+ "setup"
1012
+ )
1013
+ ] }) })
1014
+ ] })
1015
+ ] });
1016
+ }
678
1017
  export {
1018
+ AvatarArchitectApp,
679
1019
  CompactDropdown,
680
1020
  FaToolsBadge,
1021
+ GLOBAL_STYLES,
681
1022
  HistoryPanel,
682
1023
  InspectPanel,
683
1024
  ListView,
@@ -687,8 +1028,10 @@ export {
687
1028
  SetupPanel,
688
1029
  exportProjectToZip,
689
1030
  formatTreeToMarkdown,
1031
+ getFormattedTimestamp,
690
1032
  importProjectFromZip,
691
1033
  injectXMPMetadata,
692
1034
  parsePromptFile,
1035
+ useKeyboardNavigation,
693
1036
  useOnClickOutside
694
1037
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rslsp1/fa-app-tools",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "description": "Shared tools and hooks for Fine Art flow apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",