@rslsp1/fa-app-tools 0.1.1 → 0.1.3

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
@@ -1,8 +1,126 @@
1
- import { RefObject } from 'react';
1
+ import React, { RefObject } from 'react';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
+ import { Node, Edge } from '@xyflow/react';
3
4
 
4
5
  declare function useOnClickOutside(ref: RefObject<HTMLElement | null>, handler: (e: MouseEvent | TouchEvent) => void): void;
5
6
 
6
7
  declare function FaToolsBadge(): react_jsx_runtime.JSX.Element;
7
8
 
8
- export { FaToolsBadge, useOnClickOutside };
9
+ interface PillButtonProps {
10
+ icon?: string;
11
+ children: React.ReactNode;
12
+ variant?: 'filled' | 'outline' | 'solid' | 'danger' | 'ghost';
13
+ onClick?: () => void;
14
+ disabled?: boolean;
15
+ loading?: boolean;
16
+ className?: string;
17
+ }
18
+ declare const PillButton: React.FC<PillButtonProps>;
19
+
20
+ declare const SectionLabel: React.FC<{
21
+ children: React.ReactNode;
22
+ }>;
23
+
24
+ interface CompactDropdownProps {
25
+ value: string;
26
+ displayValue?: string;
27
+ options: {
28
+ label: string;
29
+ value: string;
30
+ }[];
31
+ onChange: (val: string) => void;
32
+ className?: string;
33
+ }
34
+ declare const CompactDropdown: React.FC<CompactDropdownProps>;
35
+
36
+ interface WorkspaceTags {
37
+ by_category: Record<string, string[]>;
38
+ all: Array<{
39
+ label: string;
40
+ value: string;
41
+ category: string;
42
+ }>;
43
+ }
44
+ interface Generation {
45
+ id: string;
46
+ nodeId: string;
47
+ base64?: string;
48
+ mediaId?: string;
49
+ prompt?: string;
50
+ hierarchy?: string;
51
+ seed?: number;
52
+ model?: string;
53
+ timestamp: number;
54
+ status: 'processing' | 'done' | 'error';
55
+ step?: string;
56
+ tags: string[];
57
+ type?: 'generation' | 'import';
58
+ error?: {
59
+ message: string;
60
+ details?: string;
61
+ };
62
+ selectedForExport?: boolean;
63
+ }
64
+ interface ProjectSettings {
65
+ aspectRatio: string;
66
+ selectedModel: string;
67
+ seed: number;
68
+ seedMode: 'random' | 'fixed';
69
+ }
70
+
71
+ interface HistoryPanelProps {
72
+ history: Generation[];
73
+ currentResultId: string | null;
74
+ onSelect: (gen: Generation) => void;
75
+ onDelete: (id: string) => void;
76
+ }
77
+ declare const HistoryPanel: React.FC<HistoryPanelProps>;
78
+
79
+ interface InspectPanelProps {
80
+ currentResult: Generation | null;
81
+ history: Generation[];
82
+ onSelect: (gen: Generation) => void;
83
+ workspaceTags: WorkspaceTags | null;
84
+ onTagToggle?: (tagName: string) => void;
85
+ }
86
+ declare const InspectPanel: React.FC<InspectPanelProps>;
87
+
88
+ interface SetupPanelProps {
89
+ onWorkspaceImport: (e: React.ChangeEvent<HTMLInputElement>) => void;
90
+ onProjectExport: () => void;
91
+ onProjectImport: (e: React.ChangeEvent<HTMLInputElement>) => void;
92
+ projectActionState: 'idle' | 'working' | 'done' | 'error';
93
+ }
94
+ declare const SetupPanel: React.FC<SetupPanelProps>;
95
+
96
+ interface MediaLibraryProps {
97
+ items: Generation[];
98
+ onImport: () => void;
99
+ onDelete: (id: string) => void;
100
+ onSelect: (item: Generation) => void;
101
+ onToggleSelection: (id: string) => void;
102
+ onBatchDownload: () => void;
103
+ }
104
+ declare const MediaLibrary: React.FC<MediaLibraryProps>;
105
+
106
+ interface ListViewProps {
107
+ nodes: Node[];
108
+ edges: Edge[];
109
+ onNodeChange: (id: string, label: string) => void;
110
+ onAddChild: (parentId: string) => void;
111
+ onDeleteNode: (id: string) => void;
112
+ onMoveNode: (id: string, direction: 'up' | 'down') => void;
113
+ onIndentNode: (id: string) => void;
114
+ onOutdentNode: (id: string) => void;
115
+ onAddSibling: (id: string) => void;
116
+ focusedNodeId: string | null;
117
+ onFocus: (id: string | null) => void;
118
+ activePath: Set<string>;
119
+ onGenerate: (id: string) => void;
120
+ onGenerateBranch: (id: string) => void;
121
+ onGenerateSubtree: (id: string) => void;
122
+ isGeneratingNodeId: (id: string) => boolean;
123
+ }
124
+ 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;
125
+
126
+ export { CompactDropdown, FaToolsBadge, type Generation, HistoryPanel, InspectPanel, ListView, MediaLibrary, PillButton, type ProjectSettings, SectionLabel, SetupPanel, type WorkspaceTags, useOnClickOutside };
package/dist/index.d.ts CHANGED
@@ -1,8 +1,126 @@
1
- import { RefObject } from 'react';
1
+ import React, { RefObject } from 'react';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
+ import { Node, Edge } from '@xyflow/react';
3
4
 
4
5
  declare function useOnClickOutside(ref: RefObject<HTMLElement | null>, handler: (e: MouseEvent | TouchEvent) => void): void;
5
6
 
6
7
  declare function FaToolsBadge(): react_jsx_runtime.JSX.Element;
7
8
 
8
- export { FaToolsBadge, useOnClickOutside };
9
+ interface PillButtonProps {
10
+ icon?: string;
11
+ children: React.ReactNode;
12
+ variant?: 'filled' | 'outline' | 'solid' | 'danger' | 'ghost';
13
+ onClick?: () => void;
14
+ disabled?: boolean;
15
+ loading?: boolean;
16
+ className?: string;
17
+ }
18
+ declare const PillButton: React.FC<PillButtonProps>;
19
+
20
+ declare const SectionLabel: React.FC<{
21
+ children: React.ReactNode;
22
+ }>;
23
+
24
+ interface CompactDropdownProps {
25
+ value: string;
26
+ displayValue?: string;
27
+ options: {
28
+ label: string;
29
+ value: string;
30
+ }[];
31
+ onChange: (val: string) => void;
32
+ className?: string;
33
+ }
34
+ declare const CompactDropdown: React.FC<CompactDropdownProps>;
35
+
36
+ interface WorkspaceTags {
37
+ by_category: Record<string, string[]>;
38
+ all: Array<{
39
+ label: string;
40
+ value: string;
41
+ category: string;
42
+ }>;
43
+ }
44
+ interface Generation {
45
+ id: string;
46
+ nodeId: string;
47
+ base64?: string;
48
+ mediaId?: string;
49
+ prompt?: string;
50
+ hierarchy?: string;
51
+ seed?: number;
52
+ model?: string;
53
+ timestamp: number;
54
+ status: 'processing' | 'done' | 'error';
55
+ step?: string;
56
+ tags: string[];
57
+ type?: 'generation' | 'import';
58
+ error?: {
59
+ message: string;
60
+ details?: string;
61
+ };
62
+ selectedForExport?: boolean;
63
+ }
64
+ interface ProjectSettings {
65
+ aspectRatio: string;
66
+ selectedModel: string;
67
+ seed: number;
68
+ seedMode: 'random' | 'fixed';
69
+ }
70
+
71
+ interface HistoryPanelProps {
72
+ history: Generation[];
73
+ currentResultId: string | null;
74
+ onSelect: (gen: Generation) => void;
75
+ onDelete: (id: string) => void;
76
+ }
77
+ declare const HistoryPanel: React.FC<HistoryPanelProps>;
78
+
79
+ interface InspectPanelProps {
80
+ currentResult: Generation | null;
81
+ history: Generation[];
82
+ onSelect: (gen: Generation) => void;
83
+ workspaceTags: WorkspaceTags | null;
84
+ onTagToggle?: (tagName: string) => void;
85
+ }
86
+ declare const InspectPanel: React.FC<InspectPanelProps>;
87
+
88
+ interface SetupPanelProps {
89
+ onWorkspaceImport: (e: React.ChangeEvent<HTMLInputElement>) => void;
90
+ onProjectExport: () => void;
91
+ onProjectImport: (e: React.ChangeEvent<HTMLInputElement>) => void;
92
+ projectActionState: 'idle' | 'working' | 'done' | 'error';
93
+ }
94
+ declare const SetupPanel: React.FC<SetupPanelProps>;
95
+
96
+ interface MediaLibraryProps {
97
+ items: Generation[];
98
+ onImport: () => void;
99
+ onDelete: (id: string) => void;
100
+ onSelect: (item: Generation) => void;
101
+ onToggleSelection: (id: string) => void;
102
+ onBatchDownload: () => void;
103
+ }
104
+ declare const MediaLibrary: React.FC<MediaLibraryProps>;
105
+
106
+ interface ListViewProps {
107
+ nodes: Node[];
108
+ edges: Edge[];
109
+ onNodeChange: (id: string, label: string) => void;
110
+ onAddChild: (parentId: string) => void;
111
+ onDeleteNode: (id: string) => void;
112
+ onMoveNode: (id: string, direction: 'up' | 'down') => void;
113
+ onIndentNode: (id: string) => void;
114
+ onOutdentNode: (id: string) => void;
115
+ onAddSibling: (id: string) => void;
116
+ focusedNodeId: string | null;
117
+ onFocus: (id: string | null) => void;
118
+ activePath: Set<string>;
119
+ onGenerate: (id: string) => void;
120
+ onGenerateBranch: (id: string) => void;
121
+ onGenerateSubtree: (id: string) => void;
122
+ isGeneratingNodeId: (id: string) => boolean;
123
+ }
124
+ 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;
125
+
126
+ export { CompactDropdown, FaToolsBadge, type Generation, HistoryPanel, InspectPanel, ListView, MediaLibrary, PillButton, type ProjectSettings, SectionLabel, SetupPanel, type WorkspaceTags, useOnClickOutside };
package/dist/index.js CHANGED
@@ -20,7 +20,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ CompactDropdown: () => CompactDropdown,
23
24
  FaToolsBadge: () => FaToolsBadge,
25
+ HistoryPanel: () => HistoryPanel,
26
+ InspectPanel: () => InspectPanel,
27
+ ListView: () => ListView,
28
+ MediaLibrary: () => MediaLibrary,
29
+ PillButton: () => PillButton,
30
+ SectionLabel: () => SectionLabel,
31
+ SetupPanel: () => SetupPanel,
24
32
  useOnClickOutside: () => useOnClickOutside
25
33
  });
26
34
  module.exports = __toCommonJS(index_exports);
@@ -60,8 +68,388 @@ function FaToolsBadge() {
60
68
  pointerEvents: "none"
61
69
  }, children: "fa-app-tools v0.1.1" });
62
70
  }
71
+
72
+ // src/components/PillButton.tsx
73
+ var import_jsx_runtime2 = require("react/jsx-runtime");
74
+ var PillButton = ({
75
+ icon,
76
+ children,
77
+ variant = "filled",
78
+ onClick,
79
+ disabled,
80
+ loading,
81
+ className = ""
82
+ }) => {
83
+ const base = "flex items-center gap-[6px] justify-center h-[34px] rounded-xl font-bold tracking-[0.1px] transition-all cursor-pointer text-[10px] uppercase px-3";
84
+ const variants = {
85
+ filled: "bg-[#333] hover:bg-[#444] active:bg-[#222] text-white border border-white/5 disabled:opacity-30",
86
+ outline: "border border-[#595959] hover:bg-white/5 active:bg-white/10 text-white disabled:opacity-30",
87
+ solid: "bg-white hover:bg-gray-200 active:bg-gray-300 text-black disabled:opacity-30",
88
+ danger: "bg-red-500/10 border border-red-500/20 text-red-400 hover:bg-red-500/20 disabled:opacity-30",
89
+ ghost: "text-white/40 hover:text-white hover:bg-white/5 disabled:opacity-30"
90
+ };
91
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
92
+ "button",
93
+ {
94
+ className: `${base} ${variants[variant]} ${className}`,
95
+ onClick,
96
+ disabled,
97
+ children: [
98
+ loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "material-symbols-outlined text-[16px] animate-spin text-inherit", children: "autorenew" }) : icon && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "material-symbols-outlined text-[16px] text-inherit", children: icon }),
99
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "inline-block", children })
100
+ ]
101
+ }
102
+ );
103
+ };
104
+
105
+ // src/components/SectionLabel.tsx
106
+ var import_jsx_runtime3 = require("react/jsx-runtime");
107
+ 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 }) });
108
+
109
+ // src/components/CompactDropdown.tsx
110
+ var import_react2 = require("react");
111
+ var import_jsx_runtime4 = require("react/jsx-runtime");
112
+ var CompactDropdown = ({
113
+ value,
114
+ displayValue,
115
+ options,
116
+ onChange,
117
+ className = ""
118
+ }) => {
119
+ const [isOpen, setIsOpen] = (0, import_react2.useState)(false);
120
+ const ref = (0, import_react2.useRef)(null);
121
+ useOnClickOutside(ref, () => setIsOpen(false));
122
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { ref, className: `relative shrink-0 ${className}`, children: [
123
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
124
+ "button",
125
+ {
126
+ type: "button",
127
+ onClick: () => setIsOpen(!isOpen),
128
+ className: "h-8 px-2.5 rounded-xl bg-white/5 border border-white/10 text-[9px] font-bold uppercase hover:bg-white/10 transition-all flex items-center justify-between gap-1.5 min-w-[50px]",
129
+ children: [
130
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "truncate", children: displayValue || value }),
131
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `material-symbols-outlined text-[14px] text-white/40 transition-transform ${isOpen ? "rotate-180" : ""}`, children: "expand_more" })
132
+ ]
133
+ }
134
+ ),
135
+ isOpen && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute z-50 top-[calc(100%+4px)] left-0 min-w-[120px] bg-[#1a1a1a] border border-white/10 rounded-xl overflow-hidden shadow-2xl backdrop-blur-md animate-dropdown origin-top-left", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "max-h-60 overflow-y-auto dark-scrollbar", children: options.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
136
+ "button",
137
+ {
138
+ type: "button",
139
+ className: `w-full text-left px-3 py-2 text-[9px] font-bold uppercase tracking-[0.1px] hover:bg-white/5 transition-colors ${value === opt.value ? "bg-white/10 text-white" : "text-white/40"}`,
140
+ onClick: () => {
141
+ onChange(opt.value);
142
+ setIsOpen(false);
143
+ },
144
+ children: opt.label
145
+ },
146
+ opt.value
147
+ )) }) })
148
+ ] });
149
+ };
150
+
151
+ // src/components/HistoryPanel.tsx
152
+ var import_react3 = require("motion/react");
153
+ var import_jsx_runtime5 = require("react/jsx-runtime");
154
+ var formatFriendlyTimestamp = (timestamp) => {
155
+ const date = new Date(timestamp);
156
+ const now = /* @__PURE__ */ new Date();
157
+ const isToday = date.toDateString() === now.toDateString();
158
+ const timeStr = date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
159
+ if (isToday) return `Heute, ${timeStr}`;
160
+ const yesterday = /* @__PURE__ */ new Date();
161
+ yesterday.setDate(now.getDate() - 1);
162
+ if (date.toDateString() === yesterday.toDateString()) return `Gestern, ${timeStr}`;
163
+ return `${date.toLocaleDateString([], { day: "2-digit", month: "2-digit" })}, ${timeStr}`;
164
+ };
165
+ var HistoryPanel = ({ history, currentResultId, onSelect, onDelete }) => {
166
+ if (history.length === 0) {
167
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex flex-col items-center justify-center py-20 text-center gap-4 opacity-10", children: [
168
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "material-symbols-outlined text-[64px]", children: "history" }),
169
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-[10px] font-bold uppercase tracking-widest", children: "Keine Historie" })
170
+ ] });
171
+ }
172
+ 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)(
173
+ "div",
174
+ {
175
+ 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"}`,
176
+ onClick: () => onSelect(gen),
177
+ children: [
178
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "w-14 h-14 rounded-xl overflow-hidden bg-black shrink-0 border border-white/5", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: gen.base64?.startsWith("data:") ? gen.base64 : `data:image/png;base64,${gen.base64}`, className: "w-full h-full object-cover", alt: "Thumbnail" }) }),
179
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex-1 py-0.5 overflow-hidden", children: [
180
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center justify-between mb-1", children: [
181
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[7px] font-bold text-white/20 uppercase whitespace-nowrap", children: formatFriendlyTimestamp(gen.timestamp) }),
182
+ gen.model && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-[7px] font-bold text-white/40 bg-white/5 px-1 rounded uppercase truncate max-w-[60px]", children: gen.model })
183
+ ] }),
184
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "text-[9px] text-white/60 line-clamp-2 leading-tight", children: [
185
+ '"',
186
+ gen.prompt,
187
+ '"'
188
+ ] })
189
+ ] }),
190
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("button", { onClick: (e) => {
191
+ e.stopPropagation();
192
+ onDelete(gen.id);
193
+ }, className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 w-6 h-6 flex items-center justify-center bg-red-500/20 text-red-400 hover:bg-red-500 hover:text-white rounded-md transition-all", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "material-symbols-outlined text-[14px]", children: "delete" }) })
194
+ ]
195
+ },
196
+ gen.id
197
+ )) });
198
+ };
199
+
200
+ // src/components/InspectPanel.tsx
201
+ var import_react4 = require("motion/react");
202
+ var import_jsx_runtime6 = require("react/jsx-runtime");
203
+ var InspectPanel = ({ currentResult, history, onSelect, workspaceTags, onTagToggle }) => {
204
+ if (!currentResult) {
205
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col items-center justify-center py-20 text-center gap-4 opacity-10", children: [
206
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "material-symbols-outlined text-[64px]", children: "info" }),
207
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-[10px] font-bold uppercase tracking-widest", children: "Kein Asset ausgew\xE4hlt" })
208
+ ] });
209
+ }
210
+ const fullDateStr = new Date(currentResult.timestamp).toLocaleString([], { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit" });
211
+ 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: [
212
+ /* @__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?.startsWith("data:") ? gen.base64 : `data:image/png;base64,${gen.base64}`, className: "w-full h-full object-cover", alt: "Thumbnail" }) }, gen.id)) }) }),
213
+ /* @__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: [
214
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SectionLabel, { children: "Vorschau" }),
215
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "aspect-square w-full rounded-2xl bg-black overflow-hidden border border-white/10 shadow-2xl", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { src: currentResult.base64?.startsWith("data:") ? currentResult.base64 : `data:image/png;base64,${currentResult.base64}`, className: "w-full h-full object-cover", alt: "Preview" }) }),
216
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "p-4 bg-white/5 rounded-2xl border border-white/5", children: [
217
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-[8px] font-bold text-white/20 uppercase mb-2 tracking-widest", children: "Prompt Details" }),
218
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "text-[10px] text-white/70 italic leading-relaxed font-medium", children: [
219
+ '"',
220
+ currentResult.prompt,
221
+ '"'
222
+ ] })
223
+ ] }),
224
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "grid grid-cols-2 gap-2", children: [
225
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "p-3 bg-white/5 rounded-xl border border-white/5", children: [
226
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-[7px] font-bold text-white/20 uppercase mb-1", children: "Modell" }),
227
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-[9px] text-white/80 font-bold", children: currentResult.model || "Standard" })
228
+ ] }),
229
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "p-3 bg-white/5 rounded-xl border border-white/5", children: [
230
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-[7px] font-bold text-white/20 uppercase mb-1", children: "Seed" }),
231
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-[9px] text-white/80 font-mono", children: currentResult.seed || "0" })
232
+ ] })
233
+ ] }),
234
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "p-3 bg-white/5 rounded-xl border border-white/5", children: [
235
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-[7px] font-bold text-white/20 uppercase mb-1", children: "Zeitstempel" }),
236
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-[9px] text-white/80 font-mono", children: fullDateStr })
237
+ ] }),
238
+ workspaceTags && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col gap-4", children: [
239
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SectionLabel, { children: "Workspace Tags" }),
240
+ Object.entries(workspaceTags.by_category).map(([cat, tags]) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col gap-2", children: [
241
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "text-[8px] font-bold text-white/30 uppercase tracking-widest px-1", children: cat }),
242
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex flex-wrap gap-1.5", children: tags.map((t) => {
243
+ const isActive = (currentResult.tags || []).includes(t);
244
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { onClick: () => onTagToggle?.(t), className: `px-2.5 py-1 rounded-lg text-[8px] font-bold uppercase transition-all border ${isActive ? "bg-blue-500 border-blue-400 text-white shadow-[0_0_10px_rgba(59,130,246,0.3)]" : "bg-white/5 border-white/5 text-white/20 hover:text-white/50 hover:bg-white/10"}`, children: t }, t);
245
+ }) })
246
+ ] }, cat))
247
+ ] })
248
+ ] }) })
249
+ ] });
250
+ };
251
+
252
+ // src/components/SetupPanel.tsx
253
+ var import_react5 = require("react");
254
+ var import_react6 = require("motion/react");
255
+ var import_jsx_runtime7 = require("react/jsx-runtime");
256
+ var SetupPanel = ({ onWorkspaceImport, onProjectExport, onProjectImport, projectActionState }) => {
257
+ const workspaceInputRef = (0, import_react5.useRef)(null);
258
+ const projectInputRef = (0, import_react5.useRef)(null);
259
+ 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: [
260
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-4", children: [
261
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-1", children: [
262
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SectionLabel, { children: "Workspace Management" }),
263
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-[9px] text-white/30 px-2 italic", children: "Importiere Kategorien und Tags als JSON-Struktur." })
264
+ ] }),
265
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PillButton, { variant: "outline", icon: "upload_file", onClick: () => workspaceInputRef.current?.click(), children: "Tags laden (.json)" }),
266
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { ref: workspaceInputRef, type: "file", accept: ".json", className: "hidden", onChange: onWorkspaceImport })
267
+ ] }),
268
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-4", children: [
269
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-1", children: [
270
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SectionLabel, { children: "Projekt Management" }),
271
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-[9px] text-white/30 px-2 italic", children: "Speichere den gesamten Baum und alle Assets." })
272
+ ] }),
273
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-2", children: [
274
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PillButton, { variant: "filled", icon: "download", onClick: onProjectExport, loading: projectActionState === "working", children: "Exportieren (.zip)" }),
275
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(PillButton, { variant: "outline", icon: "upload", onClick: () => projectInputRef.current?.click(), children: "Projekt laden (.zip)" }),
276
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { ref: projectInputRef, type: "file", accept: ".zip", className: "hidden", onChange: (e) => {
277
+ onProjectImport(e);
278
+ if (e.target) e.target.value = "";
279
+ } })
280
+ ] })
281
+ ] }),
282
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "mt-auto p-4 bg-white/5 rounded-2xl border border-white/5 flex flex-col gap-2 opacity-50", children: [
283
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "text-[8px] font-bold text-white/20 uppercase tracking-widest", children: "System Info" }),
284
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-between text-[9px] text-white/40", children: [
285
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Version" }),
286
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-mono", children: "1.2.0" })
287
+ ] }),
288
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex items-center justify-between text-[9px] text-white/40", children: [
289
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: "Engine" }),
290
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "font-mono", children: "Avatar Architect" })
291
+ ] })
292
+ ] })
293
+ ] });
294
+ };
295
+
296
+ // src/components/MediaLibrary.tsx
297
+ var import_react7 = require("motion/react");
298
+ var import_jsx_runtime8 = require("react/jsx-runtime");
299
+ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, onBatchDownload }) => {
300
+ const selectedCount = items.filter((i) => i.selectedForExport).length;
301
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col h-full overflow-hidden", children: [
302
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col p-4 border-b border-white/5 gap-3 bg-black/20", children: [
303
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center justify-between", children: [
304
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col", children: [
305
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-[10px] font-bold text-white uppercase tracking-widest", children: "Projekt Galerie" }),
306
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "text-[9px] text-white/30 uppercase tracking-tighter", children: [
307
+ items.length,
308
+ " Assets geladen"
309
+ ] })
310
+ ] }),
311
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("button", { onClick: onImport, className: "flex items-center gap-1.5 px-3 py-1.5 bg-blue-500 hover:bg-blue-400 text-white rounded-lg text-[10px] font-bold uppercase transition-all shadow-[0_0_15px_rgba(59,130,246,0.3)]", children: [
312
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "material-symbols-outlined text-[16px]", children: "add_photo_alternate" }),
313
+ "Laden"
314
+ ] })
315
+ ] }),
316
+ 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: [
317
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "text-[9px] font-bold uppercase text-blue-400 ml-1", children: [
318
+ selectedCount,
319
+ " ausgew\xE4hlt"
320
+ ] }),
321
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("button", { onClick: onBatchDownload, className: "text-[9px] font-bold uppercase bg-blue-500 text-white px-3 py-1 rounded-lg hover:bg-blue-400 transition-all flex items-center gap-1", children: [
322
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "material-symbols-outlined text-[14px]", children: "archive" }),
323
+ " ZIP Download"
324
+ ] })
325
+ ] })
326
+ ] }),
327
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex-1 overflow-y-auto p-4 dark-scrollbar", children: items.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col items-center justify-center py-20 text-center gap-4 opacity-10", children: [
328
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "material-symbols-outlined text-[64px]", children: "photo_library" }),
329
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col gap-1", children: [
330
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-[12px] font-bold uppercase tracking-widest", children: "Keine Medien" }),
331
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-[10px] italic", children: "Importiere Assets aus deinem Projekt." })
332
+ ] })
333
+ ] }) : /* @__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, { layout: true, 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: [
334
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: 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 }),
335
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: `absolute top-2 left-2 w-5 h-5 rounded-md border flex items-center justify-center transition-all z-20 ${item.selectedForExport ? "bg-blue-500 border-blue-400" : "bg-black/60 border-white/20"}`, onClick: (e) => {
336
+ e.stopPropagation();
337
+ onToggleSelection(item.id);
338
+ }, children: item.selectedForExport && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "material-symbols-outlined text-[14px] text-white", children: "check" }) }),
339
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-0 group-hover/item:opacity-100 transition-opacity flex flex-col justify-end p-2 gap-2", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center justify-between", children: [
340
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "text-[8px] font-bold text-white/80 truncate max-w-[80px] uppercase tracking-tighter", children: item.prompt || "Importiert" }),
341
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { onClick: (e) => {
342
+ e.stopPropagation();
343
+ onDelete(item.id);
344
+ }, className: "w-6 h-6 flex items-center justify-center bg-red-500/20 text-red-400 hover:bg-red-500 rounded-md transition-all hover:text-white", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "material-symbols-outlined text-[14px]", children: "delete" }) })
345
+ ] }) }),
346
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "absolute top-1 right-1 px-1.5 py-0.5 bg-black/60 backdrop-blur-md rounded text-[7px] font-bold text-white/60 uppercase tracking-tight", children: item.type === "import" ? "Library" : "Gen" })
347
+ ] }, item.id)) }) })
348
+ ] });
349
+ };
350
+
351
+ // src/components/ListView.tsx
352
+ var import_react8 = require("react");
353
+ var import_jsx_runtime9 = require("react/jsx-runtime");
354
+ var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, isActive, isInPath, onFocus, onGenerate, onGenerateBranch, onGenerateSubtree, isGenerating, isCollapsed, toggleCollapse, renderNode, children }) => {
355
+ const inputRef = (0, import_react8.useRef)(null);
356
+ const hasChildren = children && children.length > 0;
357
+ const handleKeyDown = (e) => {
358
+ if (e.key === "Tab") {
359
+ e.preventDefault();
360
+ if (e.shiftKey) onOutdentNode(node.id);
361
+ else onIndentNode(node.id);
362
+ return;
363
+ }
364
+ if (e.key === "Enter") {
365
+ e.preventDefault();
366
+ if (e.ctrlKey) {
367
+ if (e.shiftKey) onGenerateSubtree(node.id);
368
+ else onGenerateBranch(node.id);
369
+ } else {
370
+ onAddSibling(node.id);
371
+ }
372
+ return;
373
+ }
374
+ if (e.shiftKey && e.key === "ArrowUp") {
375
+ e.preventDefault();
376
+ onMoveNode(node.id, "up");
377
+ }
378
+ if (e.shiftKey && e.key === "ArrowDown") {
379
+ e.preventDefault();
380
+ onMoveNode(node.id, "down");
381
+ }
382
+ };
383
+ (0, import_react8.useEffect)(() => {
384
+ if (isActive && inputRef.current) inputRef.current.focus();
385
+ }, [isActive]);
386
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col w-full", children: [
387
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: `flex items-center gap-1 group relative py-0.5 ${depth === 0 ? "mt-6 mb-2" : ""}`, children: [
388
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "w-4 h-4 flex items-center justify-center shrink-0 z-10", children: hasChildren ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { onClick: () => toggleCollapse(node.id), className: `material-symbols-outlined text-[14px] transition-colors cursor-pointer ${isInPath ? "text-white" : "text-white/20 hover:text-white"}`, children: isCollapsed ? "chevron_right" : "expand_more" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: `rounded-full transition-all ${depth === 0 ? "w-1.5 h-1.5 bg-white/40" : "w-1 h-1 bg-white/10"}` }) }),
389
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex-1 flex items-center min-w-0", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
390
+ "input",
391
+ {
392
+ ref: inputRef,
393
+ value: node.data.label,
394
+ onChange: (e) => onNodeChange(node.id, e.target.value),
395
+ onFocus: () => onFocus(node.id),
396
+ onKeyDown: handleKeyDown,
397
+ className: `bg-transparent outline-none flex-1 py-1 px-1 rounded-md hover:bg-white/5 transition-all truncate ${depth === 0 ? "text-[11px] font-bold uppercase tracking-tight" : "text-[11px]"} ${isActive ? "text-white bg-white/5 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.1)]" : isInPath ? "text-white/80" : "text-white/30"}`,
398
+ placeholder: node.data.placeholder || "..."
399
+ }
400
+ ) }),
401
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center opacity-0 group-hover:opacity-100 transition-opacity pr-1 gap-px", children: [
402
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center bg-white/5 rounded-md px-1 mr-1", children: [
403
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { onClick: () => onMoveNode(node.id, "up"), title: "Shift+Up", className: "material-symbols-outlined text-[14px] text-white/20 hover:text-white p-0.5", children: "keyboard_arrow_up" }),
404
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { onClick: () => onMoveNode(node.id, "down"), title: "Shift+Down", className: "material-symbols-outlined text-[14px] text-white/20 hover:text-white p-0.5", children: "keyboard_arrow_down" }),
405
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { onClick: () => onOutdentNode(node.id), title: "Shift+Tab", className: "material-symbols-outlined text-[14px] text-white/20 hover:text-white p-0.5", children: "format_indent_decrease" }),
406
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { onClick: () => onIndentNode(node.id), title: "Tab", className: "material-symbols-outlined text-[14px] text-white/20 hover:text-white p-0.5", children: "format_indent_increase" })
407
+ ] }),
408
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { onClick: () => onGenerate(node.id), disabled: isGenerating, className: `material-symbols-outlined text-[16px] p-1 rounded-md transition-all ${isGenerating ? "animate-spin text-blue-400" : "text-white/20 hover:text-white hover:bg-white/10"}`, children: isGenerating ? "autorenew" : "auto_fix_high" }),
409
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { onClick: () => onAddChild(node.id), className: "material-symbols-outlined text-[16px] text-white/10 hover:text-white p-1 hover:bg-white/10 rounded-md", children: "add" }),
410
+ depth > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { onClick: () => onDeleteNode(node.id), className: "material-symbols-outlined text-[16px] text-white/5 hover:text-red-400 p-1 hover:bg-red-500/10 rounded-md", children: "close" })
411
+ ] })
412
+ ] }),
413
+ !isCollapsed && hasChildren && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex flex-col w-full ml-1.5 pl-3 border-l border-white/5", children: children.map((child) => renderNode(child, depth + 1)) })
414
+ ] });
415
+ };
416
+ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, focusedNodeId, onFocus, activePath, onGenerate, onGenerateBranch, onGenerateSubtree, isGeneratingNodeId }) {
417
+ const [collapsed, setCollapsed] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
418
+ const toggleCollapse = (id) => {
419
+ setCollapsed((prev) => {
420
+ const next = new Set(prev);
421
+ if (next.has(id)) next.delete(id);
422
+ else next.add(id);
423
+ return next;
424
+ });
425
+ };
426
+ const nodeMap = /* @__PURE__ */ new Map();
427
+ nodes.forEach((n) => nodeMap.set(n.id, { ...n, children: [] }));
428
+ edges.forEach((e) => {
429
+ const parent = nodeMap.get(e.source);
430
+ const child = nodeMap.get(e.target);
431
+ if (parent && child) parent.children.push(child);
432
+ });
433
+ const targetIds = new Set(edges.map((e) => e.target));
434
+ const roots = nodes.filter((n) => !targetIds.has(n.id)).map((n) => nodeMap.get(n.id)).filter(Boolean);
435
+ const renderNode = (node, depth = 0) => {
436
+ if (!node) return null;
437
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ListNode, { node, depth, children: node.children || [], onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, isActive: focusedNodeId === node.id, isInPath: activePath.has(node.id), onFocus, onGenerate, onGenerateBranch, onGenerateSubtree, isGenerating: isGeneratingNodeId(node.id), isCollapsed: collapsed.has(node.id), toggleCollapse, renderNode }, node.id);
438
+ };
439
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "w-full h-full overflow-y-auto px-4 py-4 dark-scrollbar scroll-smooth", onClick: (e) => {
440
+ if (e.target === e.currentTarget) onFocus(null);
441
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "w-full flex flex-col", children: roots.map((root) => renderNode(root, 0)) }) });
442
+ }
63
443
  // Annotate the CommonJS export names for ESM import in node:
64
444
  0 && (module.exports = {
445
+ CompactDropdown,
65
446
  FaToolsBadge,
447
+ HistoryPanel,
448
+ InspectPanel,
449
+ ListView,
450
+ MediaLibrary,
451
+ PillButton,
452
+ SectionLabel,
453
+ SetupPanel,
66
454
  useOnClickOutside
67
455
  });
package/dist/index.mjs CHANGED
@@ -33,7 +33,387 @@ function FaToolsBadge() {
33
33
  pointerEvents: "none"
34
34
  }, children: "fa-app-tools v0.1.1" });
35
35
  }
36
+
37
+ // src/components/PillButton.tsx
38
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
39
+ var PillButton = ({
40
+ icon,
41
+ children,
42
+ variant = "filled",
43
+ onClick,
44
+ disabled,
45
+ loading,
46
+ className = ""
47
+ }) => {
48
+ const base = "flex items-center gap-[6px] justify-center h-[34px] rounded-xl font-bold tracking-[0.1px] transition-all cursor-pointer text-[10px] uppercase px-3";
49
+ const variants = {
50
+ filled: "bg-[#333] hover:bg-[#444] active:bg-[#222] text-white border border-white/5 disabled:opacity-30",
51
+ outline: "border border-[#595959] hover:bg-white/5 active:bg-white/10 text-white disabled:opacity-30",
52
+ solid: "bg-white hover:bg-gray-200 active:bg-gray-300 text-black disabled:opacity-30",
53
+ danger: "bg-red-500/10 border border-red-500/20 text-red-400 hover:bg-red-500/20 disabled:opacity-30",
54
+ ghost: "text-white/40 hover:text-white hover:bg-white/5 disabled:opacity-30"
55
+ };
56
+ return /* @__PURE__ */ jsxs(
57
+ "button",
58
+ {
59
+ className: `${base} ${variants[variant]} ${className}`,
60
+ onClick,
61
+ disabled,
62
+ children: [
63
+ loading ? /* @__PURE__ */ jsx2("span", { className: "material-symbols-outlined text-[16px] animate-spin text-inherit", children: "autorenew" }) : icon && /* @__PURE__ */ jsx2("span", { className: "material-symbols-outlined text-[16px] text-inherit", children: icon }),
64
+ /* @__PURE__ */ jsx2("span", { className: "inline-block", children })
65
+ ]
66
+ }
67
+ );
68
+ };
69
+
70
+ // src/components/SectionLabel.tsx
71
+ import { jsx as jsx3 } from "react/jsx-runtime";
72
+ var SectionLabel = ({ children }) => /* @__PURE__ */ jsx3("div", { className: "flex items-center px-2", children: /* @__PURE__ */ jsx3("span", { className: "text-[9px] font-bold text-white/30 uppercase tracking-widest text-nowrap", children }) });
73
+
74
+ // src/components/CompactDropdown.tsx
75
+ import { useState, useRef } from "react";
76
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
77
+ var CompactDropdown = ({
78
+ value,
79
+ displayValue,
80
+ options,
81
+ onChange,
82
+ className = ""
83
+ }) => {
84
+ const [isOpen, setIsOpen] = useState(false);
85
+ const ref = useRef(null);
86
+ useOnClickOutside(ref, () => setIsOpen(false));
87
+ return /* @__PURE__ */ jsxs2("div", { ref, className: `relative shrink-0 ${className}`, children: [
88
+ /* @__PURE__ */ jsxs2(
89
+ "button",
90
+ {
91
+ type: "button",
92
+ onClick: () => setIsOpen(!isOpen),
93
+ className: "h-8 px-2.5 rounded-xl bg-white/5 border border-white/10 text-[9px] font-bold uppercase hover:bg-white/10 transition-all flex items-center justify-between gap-1.5 min-w-[50px]",
94
+ children: [
95
+ /* @__PURE__ */ jsx4("span", { className: "truncate", children: displayValue || value }),
96
+ /* @__PURE__ */ jsx4("span", { className: `material-symbols-outlined text-[14px] text-white/40 transition-transform ${isOpen ? "rotate-180" : ""}`, children: "expand_more" })
97
+ ]
98
+ }
99
+ ),
100
+ isOpen && /* @__PURE__ */ jsx4("div", { className: "absolute z-50 top-[calc(100%+4px)] left-0 min-w-[120px] bg-[#1a1a1a] border border-white/10 rounded-xl overflow-hidden shadow-2xl backdrop-blur-md animate-dropdown origin-top-left", children: /* @__PURE__ */ jsx4("div", { className: "max-h-60 overflow-y-auto dark-scrollbar", children: options.map((opt) => /* @__PURE__ */ jsx4(
101
+ "button",
102
+ {
103
+ type: "button",
104
+ className: `w-full text-left px-3 py-2 text-[9px] font-bold uppercase tracking-[0.1px] hover:bg-white/5 transition-colors ${value === opt.value ? "bg-white/10 text-white" : "text-white/40"}`,
105
+ onClick: () => {
106
+ onChange(opt.value);
107
+ setIsOpen(false);
108
+ },
109
+ children: opt.label
110
+ },
111
+ opt.value
112
+ )) }) })
113
+ ] });
114
+ };
115
+
116
+ // src/components/HistoryPanel.tsx
117
+ import { motion } from "motion/react";
118
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
119
+ var formatFriendlyTimestamp = (timestamp) => {
120
+ const date = new Date(timestamp);
121
+ const now = /* @__PURE__ */ new Date();
122
+ const isToday = date.toDateString() === now.toDateString();
123
+ const timeStr = date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
124
+ if (isToday) return `Heute, ${timeStr}`;
125
+ const yesterday = /* @__PURE__ */ new Date();
126
+ yesterday.setDate(now.getDate() - 1);
127
+ if (date.toDateString() === yesterday.toDateString()) return `Gestern, ${timeStr}`;
128
+ return `${date.toLocaleDateString([], { day: "2-digit", month: "2-digit" })}, ${timeStr}`;
129
+ };
130
+ var HistoryPanel = ({ history, currentResultId, onSelect, onDelete }) => {
131
+ if (history.length === 0) {
132
+ return /* @__PURE__ */ jsxs3("div", { className: "flex flex-col items-center justify-center py-20 text-center gap-4 opacity-10", children: [
133
+ /* @__PURE__ */ jsx5("span", { className: "material-symbols-outlined text-[64px]", children: "history" }),
134
+ /* @__PURE__ */ jsx5("p", { className: "text-[10px] font-bold uppercase tracking-widest", children: "Keine Historie" })
135
+ ] });
136
+ }
137
+ return /* @__PURE__ */ jsx5(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__ */ jsxs3(
138
+ "div",
139
+ {
140
+ 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"}`,
141
+ onClick: () => onSelect(gen),
142
+ children: [
143
+ /* @__PURE__ */ jsx5("div", { className: "w-14 h-14 rounded-xl overflow-hidden bg-black shrink-0 border border-white/5", children: /* @__PURE__ */ jsx5("img", { src: gen.base64?.startsWith("data:") ? gen.base64 : `data:image/png;base64,${gen.base64}`, className: "w-full h-full object-cover", alt: "Thumbnail" }) }),
144
+ /* @__PURE__ */ jsxs3("div", { className: "flex-1 py-0.5 overflow-hidden", children: [
145
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center justify-between mb-1", children: [
146
+ /* @__PURE__ */ jsx5("span", { className: "text-[7px] font-bold text-white/20 uppercase whitespace-nowrap", children: formatFriendlyTimestamp(gen.timestamp) }),
147
+ gen.model && /* @__PURE__ */ jsx5("span", { className: "text-[7px] font-bold text-white/40 bg-white/5 px-1 rounded uppercase truncate max-w-[60px]", children: gen.model })
148
+ ] }),
149
+ /* @__PURE__ */ jsxs3("p", { className: "text-[9px] text-white/60 line-clamp-2 leading-tight", children: [
150
+ '"',
151
+ gen.prompt,
152
+ '"'
153
+ ] })
154
+ ] }),
155
+ /* @__PURE__ */ jsx5("button", { onClick: (e) => {
156
+ e.stopPropagation();
157
+ onDelete(gen.id);
158
+ }, className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 w-6 h-6 flex items-center justify-center bg-red-500/20 text-red-400 hover:bg-red-500 hover:text-white rounded-md transition-all", children: /* @__PURE__ */ jsx5("span", { className: "material-symbols-outlined text-[14px]", children: "delete" }) })
159
+ ]
160
+ },
161
+ gen.id
162
+ )) });
163
+ };
164
+
165
+ // src/components/InspectPanel.tsx
166
+ import { motion as motion2 } from "motion/react";
167
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
168
+ var InspectPanel = ({ currentResult, history, onSelect, workspaceTags, onTagToggle }) => {
169
+ if (!currentResult) {
170
+ return /* @__PURE__ */ jsxs4("div", { className: "flex flex-col items-center justify-center py-20 text-center gap-4 opacity-10", children: [
171
+ /* @__PURE__ */ jsx6("span", { className: "material-symbols-outlined text-[64px]", children: "info" }),
172
+ /* @__PURE__ */ jsx6("p", { className: "text-[10px] font-bold uppercase tracking-widest", children: "Kein Asset ausgew\xE4hlt" })
173
+ ] });
174
+ }
175
+ const fullDateStr = new Date(currentResult.timestamp).toLocaleString([], { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit" });
176
+ return /* @__PURE__ */ jsxs4(motion2.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 flex flex-col overflow-hidden", children: [
177
+ /* @__PURE__ */ jsx6("div", { className: "flex-shrink-0 border-b border-white/5 bg-black/20 py-3 px-4", children: /* @__PURE__ */ jsx6("div", { className: "flex gap-2 overflow-x-auto no-scrollbar pb-1", children: history.map((gen) => /* @__PURE__ */ jsx6("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__ */ jsx6("img", { src: gen.base64?.startsWith("data:") ? gen.base64 : `data:image/png;base64,${gen.base64}`, className: "w-full h-full object-cover", alt: "Thumbnail" }) }, gen.id)) }) }),
178
+ /* @__PURE__ */ jsx6("div", { className: "flex-1 overflow-y-auto p-4 flex flex-col gap-6 dark-scrollbar pb-10", children: /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-4", children: [
179
+ /* @__PURE__ */ jsx6(SectionLabel, { children: "Vorschau" }),
180
+ /* @__PURE__ */ jsx6("div", { className: "aspect-square w-full rounded-2xl bg-black overflow-hidden border border-white/10 shadow-2xl", children: /* @__PURE__ */ jsx6("img", { src: currentResult.base64?.startsWith("data:") ? currentResult.base64 : `data:image/png;base64,${currentResult.base64}`, className: "w-full h-full object-cover", alt: "Preview" }) }),
181
+ /* @__PURE__ */ jsxs4("div", { className: "p-4 bg-white/5 rounded-2xl border border-white/5", children: [
182
+ /* @__PURE__ */ jsx6("p", { className: "text-[8px] font-bold text-white/20 uppercase mb-2 tracking-widest", children: "Prompt Details" }),
183
+ /* @__PURE__ */ jsxs4("p", { className: "text-[10px] text-white/70 italic leading-relaxed font-medium", children: [
184
+ '"',
185
+ currentResult.prompt,
186
+ '"'
187
+ ] })
188
+ ] }),
189
+ /* @__PURE__ */ jsxs4("div", { className: "grid grid-cols-2 gap-2", children: [
190
+ /* @__PURE__ */ jsxs4("div", { className: "p-3 bg-white/5 rounded-xl border border-white/5", children: [
191
+ /* @__PURE__ */ jsx6("p", { className: "text-[7px] font-bold text-white/20 uppercase mb-1", children: "Modell" }),
192
+ /* @__PURE__ */ jsx6("p", { className: "text-[9px] text-white/80 font-bold", children: currentResult.model || "Standard" })
193
+ ] }),
194
+ /* @__PURE__ */ jsxs4("div", { className: "p-3 bg-white/5 rounded-xl border border-white/5", children: [
195
+ /* @__PURE__ */ jsx6("p", { className: "text-[7px] font-bold text-white/20 uppercase mb-1", children: "Seed" }),
196
+ /* @__PURE__ */ jsx6("p", { className: "text-[9px] text-white/80 font-mono", children: currentResult.seed || "0" })
197
+ ] })
198
+ ] }),
199
+ /* @__PURE__ */ jsxs4("div", { className: "p-3 bg-white/5 rounded-xl border border-white/5", children: [
200
+ /* @__PURE__ */ jsx6("p", { className: "text-[7px] font-bold text-white/20 uppercase mb-1", children: "Zeitstempel" }),
201
+ /* @__PURE__ */ jsx6("p", { className: "text-[9px] text-white/80 font-mono", children: fullDateStr })
202
+ ] }),
203
+ workspaceTags && /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-4", children: [
204
+ /* @__PURE__ */ jsx6(SectionLabel, { children: "Workspace Tags" }),
205
+ Object.entries(workspaceTags.by_category).map(([cat, tags]) => /* @__PURE__ */ jsxs4("div", { className: "flex flex-col gap-2", children: [
206
+ /* @__PURE__ */ jsx6("span", { className: "text-[8px] font-bold text-white/30 uppercase tracking-widest px-1", children: cat }),
207
+ /* @__PURE__ */ jsx6("div", { className: "flex flex-wrap gap-1.5", children: tags.map((t) => {
208
+ const isActive = (currentResult.tags || []).includes(t);
209
+ return /* @__PURE__ */ jsx6("button", { onClick: () => onTagToggle?.(t), className: `px-2.5 py-1 rounded-lg text-[8px] font-bold uppercase transition-all border ${isActive ? "bg-blue-500 border-blue-400 text-white shadow-[0_0_10px_rgba(59,130,246,0.3)]" : "bg-white/5 border-white/5 text-white/20 hover:text-white/50 hover:bg-white/10"}`, children: t }, t);
210
+ }) })
211
+ ] }, cat))
212
+ ] })
213
+ ] }) })
214
+ ] });
215
+ };
216
+
217
+ // src/components/SetupPanel.tsx
218
+ import { useRef as useRef2 } from "react";
219
+ import { motion as motion3 } from "motion/react";
220
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
221
+ var SetupPanel = ({ onWorkspaceImport, onProjectExport, onProjectImport, projectActionState }) => {
222
+ const workspaceInputRef = useRef2(null);
223
+ const projectInputRef = useRef2(null);
224
+ return /* @__PURE__ */ jsxs5(motion3.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 p-6 flex flex-col gap-10", children: [
225
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-4", children: [
226
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1", children: [
227
+ /* @__PURE__ */ jsx7(SectionLabel, { children: "Workspace Management" }),
228
+ /* @__PURE__ */ jsx7("p", { className: "text-[9px] text-white/30 px-2 italic", children: "Importiere Kategorien und Tags als JSON-Struktur." })
229
+ ] }),
230
+ /* @__PURE__ */ jsx7(PillButton, { variant: "outline", icon: "upload_file", onClick: () => workspaceInputRef.current?.click(), children: "Tags laden (.json)" }),
231
+ /* @__PURE__ */ jsx7("input", { ref: workspaceInputRef, type: "file", accept: ".json", className: "hidden", onChange: onWorkspaceImport })
232
+ ] }),
233
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-4", children: [
234
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-1", children: [
235
+ /* @__PURE__ */ jsx7(SectionLabel, { children: "Projekt Management" }),
236
+ /* @__PURE__ */ jsx7("p", { className: "text-[9px] text-white/30 px-2 italic", children: "Speichere den gesamten Baum und alle Assets." })
237
+ ] }),
238
+ /* @__PURE__ */ jsxs5("div", { className: "flex flex-col gap-2", children: [
239
+ /* @__PURE__ */ jsx7(PillButton, { variant: "filled", icon: "download", onClick: onProjectExport, loading: projectActionState === "working", children: "Exportieren (.zip)" }),
240
+ /* @__PURE__ */ jsx7(PillButton, { variant: "outline", icon: "upload", onClick: () => projectInputRef.current?.click(), children: "Projekt laden (.zip)" }),
241
+ /* @__PURE__ */ jsx7("input", { ref: projectInputRef, type: "file", accept: ".zip", className: "hidden", onChange: (e) => {
242
+ onProjectImport(e);
243
+ if (e.target) e.target.value = "";
244
+ } })
245
+ ] })
246
+ ] }),
247
+ /* @__PURE__ */ jsxs5("div", { className: "mt-auto p-4 bg-white/5 rounded-2xl border border-white/5 flex flex-col gap-2 opacity-50", children: [
248
+ /* @__PURE__ */ jsx7("span", { className: "text-[8px] font-bold text-white/20 uppercase tracking-widest", children: "System Info" }),
249
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between text-[9px] text-white/40", children: [
250
+ /* @__PURE__ */ jsx7("span", { children: "Version" }),
251
+ /* @__PURE__ */ jsx7("span", { className: "font-mono", children: "1.2.0" })
252
+ ] }),
253
+ /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between text-[9px] text-white/40", children: [
254
+ /* @__PURE__ */ jsx7("span", { children: "Engine" }),
255
+ /* @__PURE__ */ jsx7("span", { className: "font-mono", children: "Avatar Architect" })
256
+ ] })
257
+ ] })
258
+ ] });
259
+ };
260
+
261
+ // src/components/MediaLibrary.tsx
262
+ import { motion as motion4 } from "motion/react";
263
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
264
+ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, onBatchDownload }) => {
265
+ const selectedCount = items.filter((i) => i.selectedForExport).length;
266
+ return /* @__PURE__ */ jsxs6("div", { className: "flex flex-col h-full overflow-hidden", children: [
267
+ /* @__PURE__ */ jsxs6("div", { className: "flex flex-col p-4 border-b border-white/5 gap-3 bg-black/20", children: [
268
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between", children: [
269
+ /* @__PURE__ */ jsxs6("div", { className: "flex flex-col", children: [
270
+ /* @__PURE__ */ jsx8("span", { className: "text-[10px] font-bold text-white uppercase tracking-widest", children: "Projekt Galerie" }),
271
+ /* @__PURE__ */ jsxs6("span", { className: "text-[9px] text-white/30 uppercase tracking-tighter", children: [
272
+ items.length,
273
+ " Assets geladen"
274
+ ] })
275
+ ] }),
276
+ /* @__PURE__ */ jsxs6("button", { onClick: onImport, className: "flex items-center gap-1.5 px-3 py-1.5 bg-blue-500 hover:bg-blue-400 text-white rounded-lg text-[10px] font-bold uppercase transition-all shadow-[0_0_15px_rgba(59,130,246,0.3)]", children: [
277
+ /* @__PURE__ */ jsx8("span", { className: "material-symbols-outlined text-[16px]", children: "add_photo_alternate" }),
278
+ "Laden"
279
+ ] })
280
+ ] }),
281
+ selectedCount > 0 && /* @__PURE__ */ jsxs6(motion4.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: [
282
+ /* @__PURE__ */ jsxs6("span", { className: "text-[9px] font-bold uppercase text-blue-400 ml-1", children: [
283
+ selectedCount,
284
+ " ausgew\xE4hlt"
285
+ ] }),
286
+ /* @__PURE__ */ jsxs6("button", { onClick: onBatchDownload, className: "text-[9px] font-bold uppercase bg-blue-500 text-white px-3 py-1 rounded-lg hover:bg-blue-400 transition-all flex items-center gap-1", children: [
287
+ /* @__PURE__ */ jsx8("span", { className: "material-symbols-outlined text-[14px]", children: "archive" }),
288
+ " ZIP Download"
289
+ ] })
290
+ ] })
291
+ ] }),
292
+ /* @__PURE__ */ jsx8("div", { className: "flex-1 overflow-y-auto p-4 dark-scrollbar", children: items.length === 0 ? /* @__PURE__ */ jsxs6("div", { className: "flex flex-col items-center justify-center py-20 text-center gap-4 opacity-10", children: [
293
+ /* @__PURE__ */ jsx8("span", { className: "material-symbols-outlined text-[64px]", children: "photo_library" }),
294
+ /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-1", children: [
295
+ /* @__PURE__ */ jsx8("p", { className: "text-[12px] font-bold uppercase tracking-widest", children: "Keine Medien" }),
296
+ /* @__PURE__ */ jsx8("p", { className: "text-[10px] italic", children: "Importiere Assets aus deinem Projekt." })
297
+ ] })
298
+ ] }) : /* @__PURE__ */ jsx8("div", { className: "grid grid-cols-2 gap-3 pb-10", children: items.map((item) => /* @__PURE__ */ jsxs6(motion4.div, { layout: true, 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: [
299
+ /* @__PURE__ */ jsx8("img", { src: 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 }),
300
+ /* @__PURE__ */ jsx8("div", { className: `absolute top-2 left-2 w-5 h-5 rounded-md border flex items-center justify-center transition-all z-20 ${item.selectedForExport ? "bg-blue-500 border-blue-400" : "bg-black/60 border-white/20"}`, onClick: (e) => {
301
+ e.stopPropagation();
302
+ onToggleSelection(item.id);
303
+ }, children: item.selectedForExport && /* @__PURE__ */ jsx8("span", { className: "material-symbols-outlined text-[14px] text-white", children: "check" }) }),
304
+ /* @__PURE__ */ jsx8("div", { className: "absolute inset-0 bg-gradient-to-t from-black/80 via-transparent to-transparent opacity-0 group-hover/item:opacity-100 transition-opacity flex flex-col justify-end p-2 gap-2", children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between", children: [
305
+ /* @__PURE__ */ jsx8("span", { className: "text-[8px] font-bold text-white/80 truncate max-w-[80px] uppercase tracking-tighter", children: item.prompt || "Importiert" }),
306
+ /* @__PURE__ */ jsx8("button", { onClick: (e) => {
307
+ e.stopPropagation();
308
+ onDelete(item.id);
309
+ }, className: "w-6 h-6 flex items-center justify-center bg-red-500/20 text-red-400 hover:bg-red-500 rounded-md transition-all hover:text-white", children: /* @__PURE__ */ jsx8("span", { className: "material-symbols-outlined text-[14px]", children: "delete" }) })
310
+ ] }) }),
311
+ /* @__PURE__ */ jsx8("div", { className: "absolute top-1 right-1 px-1.5 py-0.5 bg-black/60 backdrop-blur-md rounded text-[7px] font-bold text-white/60 uppercase tracking-tight", children: item.type === "import" ? "Library" : "Gen" })
312
+ ] }, item.id)) }) })
313
+ ] });
314
+ };
315
+
316
+ // src/components/ListView.tsx
317
+ import { useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
318
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
319
+ var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, isActive, isInPath, onFocus, onGenerate, onGenerateBranch, onGenerateSubtree, isGenerating, isCollapsed, toggleCollapse, renderNode, children }) => {
320
+ const inputRef = useRef3(null);
321
+ const hasChildren = children && children.length > 0;
322
+ const handleKeyDown = (e) => {
323
+ if (e.key === "Tab") {
324
+ e.preventDefault();
325
+ if (e.shiftKey) onOutdentNode(node.id);
326
+ else onIndentNode(node.id);
327
+ return;
328
+ }
329
+ if (e.key === "Enter") {
330
+ e.preventDefault();
331
+ if (e.ctrlKey) {
332
+ if (e.shiftKey) onGenerateSubtree(node.id);
333
+ else onGenerateBranch(node.id);
334
+ } else {
335
+ onAddSibling(node.id);
336
+ }
337
+ return;
338
+ }
339
+ if (e.shiftKey && e.key === "ArrowUp") {
340
+ e.preventDefault();
341
+ onMoveNode(node.id, "up");
342
+ }
343
+ if (e.shiftKey && e.key === "ArrowDown") {
344
+ e.preventDefault();
345
+ onMoveNode(node.id, "down");
346
+ }
347
+ };
348
+ useEffect2(() => {
349
+ if (isActive && inputRef.current) inputRef.current.focus();
350
+ }, [isActive]);
351
+ return /* @__PURE__ */ jsxs7("div", { className: "flex flex-col w-full", children: [
352
+ /* @__PURE__ */ jsxs7("div", { className: `flex items-center gap-1 group relative py-0.5 ${depth === 0 ? "mt-6 mb-2" : ""}`, children: [
353
+ /* @__PURE__ */ jsx9("div", { className: "w-4 h-4 flex items-center justify-center shrink-0 z-10", children: hasChildren ? /* @__PURE__ */ jsx9("button", { onClick: () => toggleCollapse(node.id), className: `material-symbols-outlined text-[14px] transition-colors cursor-pointer ${isInPath ? "text-white" : "text-white/20 hover:text-white"}`, children: isCollapsed ? "chevron_right" : "expand_more" }) : /* @__PURE__ */ jsx9("div", { className: `rounded-full transition-all ${depth === 0 ? "w-1.5 h-1.5 bg-white/40" : "w-1 h-1 bg-white/10"}` }) }),
354
+ /* @__PURE__ */ jsx9("div", { className: "flex-1 flex items-center min-w-0", children: /* @__PURE__ */ jsx9(
355
+ "input",
356
+ {
357
+ ref: inputRef,
358
+ value: node.data.label,
359
+ onChange: (e) => onNodeChange(node.id, e.target.value),
360
+ onFocus: () => onFocus(node.id),
361
+ onKeyDown: handleKeyDown,
362
+ className: `bg-transparent outline-none flex-1 py-1 px-1 rounded-md hover:bg-white/5 transition-all truncate ${depth === 0 ? "text-[11px] font-bold uppercase tracking-tight" : "text-[11px]"} ${isActive ? "text-white bg-white/5 shadow-[inset_0_0_0_1px_rgba(255,255,255,0.1)]" : isInPath ? "text-white/80" : "text-white/30"}`,
363
+ placeholder: node.data.placeholder || "..."
364
+ }
365
+ ) }),
366
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center opacity-0 group-hover:opacity-100 transition-opacity pr-1 gap-px", children: [
367
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center bg-white/5 rounded-md px-1 mr-1", children: [
368
+ /* @__PURE__ */ jsx9("button", { onClick: () => onMoveNode(node.id, "up"), title: "Shift+Up", className: "material-symbols-outlined text-[14px] text-white/20 hover:text-white p-0.5", children: "keyboard_arrow_up" }),
369
+ /* @__PURE__ */ jsx9("button", { onClick: () => onMoveNode(node.id, "down"), title: "Shift+Down", className: "material-symbols-outlined text-[14px] text-white/20 hover:text-white p-0.5", children: "keyboard_arrow_down" }),
370
+ /* @__PURE__ */ jsx9("button", { onClick: () => onOutdentNode(node.id), title: "Shift+Tab", className: "material-symbols-outlined text-[14px] text-white/20 hover:text-white p-0.5", children: "format_indent_decrease" }),
371
+ /* @__PURE__ */ jsx9("button", { onClick: () => onIndentNode(node.id), title: "Tab", className: "material-symbols-outlined text-[14px] text-white/20 hover:text-white p-0.5", children: "format_indent_increase" })
372
+ ] }),
373
+ /* @__PURE__ */ jsx9("button", { onClick: () => onGenerate(node.id), disabled: isGenerating, className: `material-symbols-outlined text-[16px] p-1 rounded-md transition-all ${isGenerating ? "animate-spin text-blue-400" : "text-white/20 hover:text-white hover:bg-white/10"}`, children: isGenerating ? "autorenew" : "auto_fix_high" }),
374
+ /* @__PURE__ */ jsx9("button", { onClick: () => onAddChild(node.id), className: "material-symbols-outlined text-[16px] text-white/10 hover:text-white p-1 hover:bg-white/10 rounded-md", children: "add" }),
375
+ depth > 0 && /* @__PURE__ */ jsx9("button", { onClick: () => onDeleteNode(node.id), className: "material-symbols-outlined text-[16px] text-white/5 hover:text-red-400 p-1 hover:bg-red-500/10 rounded-md", children: "close" })
376
+ ] })
377
+ ] }),
378
+ !isCollapsed && hasChildren && /* @__PURE__ */ jsx9("div", { className: "flex flex-col w-full ml-1.5 pl-3 border-l border-white/5", children: children.map((child) => renderNode(child, depth + 1)) })
379
+ ] });
380
+ };
381
+ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, focusedNodeId, onFocus, activePath, onGenerate, onGenerateBranch, onGenerateSubtree, isGeneratingNodeId }) {
382
+ const [collapsed, setCollapsed] = useState2(/* @__PURE__ */ new Set());
383
+ const toggleCollapse = (id) => {
384
+ setCollapsed((prev) => {
385
+ const next = new Set(prev);
386
+ if (next.has(id)) next.delete(id);
387
+ else next.add(id);
388
+ return next;
389
+ });
390
+ };
391
+ const nodeMap = /* @__PURE__ */ new Map();
392
+ nodes.forEach((n) => nodeMap.set(n.id, { ...n, children: [] }));
393
+ edges.forEach((e) => {
394
+ const parent = nodeMap.get(e.source);
395
+ const child = nodeMap.get(e.target);
396
+ if (parent && child) parent.children.push(child);
397
+ });
398
+ const targetIds = new Set(edges.map((e) => e.target));
399
+ const roots = nodes.filter((n) => !targetIds.has(n.id)).map((n) => nodeMap.get(n.id)).filter(Boolean);
400
+ const renderNode = (node, depth = 0) => {
401
+ if (!node) return null;
402
+ return /* @__PURE__ */ jsx9(ListNode, { node, depth, children: node.children || [], onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, isActive: focusedNodeId === node.id, isInPath: activePath.has(node.id), onFocus, onGenerate, onGenerateBranch, onGenerateSubtree, isGenerating: isGeneratingNodeId(node.id), isCollapsed: collapsed.has(node.id), toggleCollapse, renderNode }, node.id);
403
+ };
404
+ return /* @__PURE__ */ jsx9("div", { className: "w-full h-full overflow-y-auto px-4 py-4 dark-scrollbar scroll-smooth", onClick: (e) => {
405
+ if (e.target === e.currentTarget) onFocus(null);
406
+ }, children: /* @__PURE__ */ jsx9("div", { className: "w-full flex flex-col", children: roots.map((root) => renderNode(root, 0)) }) });
407
+ }
36
408
  export {
409
+ CompactDropdown,
37
410
  FaToolsBadge,
411
+ HistoryPanel,
412
+ InspectPanel,
413
+ ListView,
414
+ MediaLibrary,
415
+ PillButton,
416
+ SectionLabel,
417
+ SetupPanel,
38
418
  useOnClickOutside
39
419
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rslsp1/fa-app-tools",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Shared tools and hooks for Fine Art flow apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -12,18 +12,22 @@
12
12
  "require": "./dist/index.js"
13
13
  }
14
14
  },
15
- "files": ["dist"],
15
+ "files": [
16
+ "dist"
17
+ ],
16
18
  "scripts": {
17
- "build": "tsup src/index.ts --format esm,cjs --dts --clean",
19
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean --external react --external @xyflow/react --external motion/react",
18
20
  "prepublishOnly": "npm run build"
19
21
  },
20
22
  "peerDependencies": {
21
23
  "react": ">=18"
22
24
  },
23
25
  "devDependencies": {
26
+ "@types/react": "^18.0.0",
27
+ "@xyflow/react": "^12.10.2",
28
+ "motion": "^12.40.0",
24
29
  "tsup": "^8.0.0",
25
- "typescript": "^5.0.0",
26
- "@types/react": "^18.0.0"
30
+ "typescript": "^5.0.0"
27
31
  },
28
32
  "publishConfig": {
29
33
  "access": "public"