@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 +72 -39
- package/dist/index.d.ts +72 -39
- package/dist/index.js +366 -19
- package/dist/index.mjs +345 -2
- package/package.json +1 -1
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
|
|
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
|
|
148
|
-
onGenerateSubtree
|
|
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
|
-
|
|
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
|
|
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
|
|
148
|
-
onGenerateSubtree
|
|
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
|
-
|
|
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
|
|
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,
|
|
394
|
-
const ref = (0,
|
|
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
|
|
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)(
|
|
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
|
|
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)(
|
|
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
|
|
528
|
-
var
|
|
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,
|
|
532
|
-
const projectInputRef = (0,
|
|
533
|
-
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
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
|
|
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)(
|
|
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)(
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
};
|