@rslsp1/fa-app-tools 0.1.10 → 0.1.12
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 +80 -37
- package/dist/index.d.ts +80 -37
- package/dist/index.js +351 -24
- package/dist/index.mjs +316 -7
- package/package.json +4 -1
package/dist/index.d.mts
CHANGED
|
@@ -4,35 +4,6 @@ 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
|
-
declare function FaToolsBadge(): react_jsx_runtime.JSX.Element;
|
|
8
|
-
|
|
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
7
|
interface WorkspaceTags {
|
|
37
8
|
by_category: Record<string, string[]>;
|
|
38
9
|
all: Array<{
|
|
@@ -68,6 +39,77 @@ interface ProjectSettings {
|
|
|
68
39
|
seedMode: 'random' | 'fixed';
|
|
69
40
|
}
|
|
70
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
|
+
|
|
58
|
+
declare function injectXMPMetadata(base64: string, prompt: string, seed?: number, model?: string, id?: string, tags?: string[], hierarchy?: string): string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Vollständiger Export: JSON + MD + Bilder (mit XMP Meta)
|
|
62
|
+
*/
|
|
63
|
+
declare function exportProjectToZip(nodes: any[], edges: any[], history: any[], galleryItems: any[], settings: any): Promise<{
|
|
64
|
+
base64: string;
|
|
65
|
+
}>;
|
|
66
|
+
declare function importProjectFromZip(file: File): Promise<any>;
|
|
67
|
+
|
|
68
|
+
interface ExtractedCharacter {
|
|
69
|
+
name: string;
|
|
70
|
+
prompts: {
|
|
71
|
+
title: string;
|
|
72
|
+
content: string;
|
|
73
|
+
}[];
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Parsed Charakter-Dateien für den Import.
|
|
77
|
+
*/
|
|
78
|
+
declare function parsePromptFile(text: string): ExtractedCharacter[];
|
|
79
|
+
/**
|
|
80
|
+
* Wandelt die Knotenstruktur in einen sauberen Markdown-Baum um.
|
|
81
|
+
*/
|
|
82
|
+
declare function formatTreeToMarkdown(nodes: any[], edges: any[]): string;
|
|
83
|
+
|
|
84
|
+
declare function FaToolsBadge(): react_jsx_runtime.JSX.Element;
|
|
85
|
+
|
|
86
|
+
interface PillButtonProps {
|
|
87
|
+
icon?: string;
|
|
88
|
+
children: React.ReactNode;
|
|
89
|
+
variant?: 'filled' | 'outline' | 'solid' | 'danger' | 'ghost';
|
|
90
|
+
onClick?: () => void;
|
|
91
|
+
disabled?: boolean;
|
|
92
|
+
loading?: boolean;
|
|
93
|
+
className?: string;
|
|
94
|
+
}
|
|
95
|
+
declare const PillButton: React.FC<PillButtonProps>;
|
|
96
|
+
|
|
97
|
+
declare const SectionLabel: React.FC<{
|
|
98
|
+
children: React.ReactNode;
|
|
99
|
+
}>;
|
|
100
|
+
|
|
101
|
+
interface CompactDropdownProps {
|
|
102
|
+
value: string;
|
|
103
|
+
displayValue?: string;
|
|
104
|
+
options: {
|
|
105
|
+
label: string;
|
|
106
|
+
value: string;
|
|
107
|
+
}[];
|
|
108
|
+
onChange: (val: string) => void;
|
|
109
|
+
className?: string;
|
|
110
|
+
}
|
|
111
|
+
declare const CompactDropdown: React.FC<CompactDropdownProps>;
|
|
112
|
+
|
|
71
113
|
interface HistoryPanelProps {
|
|
72
114
|
history: Generation[];
|
|
73
115
|
currentResultId: string | null;
|
|
@@ -89,7 +131,7 @@ interface SetupPanelProps {
|
|
|
89
131
|
onWorkspaceImport: (file: File) => void;
|
|
90
132
|
onProjectExport: () => void;
|
|
91
133
|
onProjectImport: (file: File) => void;
|
|
92
|
-
projectActionState: 'idle' | 'working' | 'done' | 'error';
|
|
134
|
+
projectActionState: 'idle' | 'working' | 'working-full' | 'done' | 'error';
|
|
93
135
|
}
|
|
94
136
|
declare const SetupPanel: React.FC<SetupPanelProps>;
|
|
95
137
|
|
|
@@ -98,8 +140,9 @@ interface MediaLibraryProps {
|
|
|
98
140
|
onImport: () => void;
|
|
99
141
|
onDelete: (id: string) => void;
|
|
100
142
|
onSelect: (item: Generation) => void;
|
|
101
|
-
onToggleSelection
|
|
102
|
-
onBatchDownload
|
|
143
|
+
onToggleSelection?: (id: string) => void;
|
|
144
|
+
onBatchDownload?: () => void;
|
|
145
|
+
onGenerateReference?: (item: Generation) => void;
|
|
103
146
|
}
|
|
104
147
|
declare const MediaLibrary: React.FC<MediaLibraryProps>;
|
|
105
148
|
|
|
@@ -109,10 +152,10 @@ interface ListViewProps {
|
|
|
109
152
|
onNodeChange: (id: string, label: string) => void;
|
|
110
153
|
onAddChild: (parentId: string) => void;
|
|
111
154
|
onDeleteNode: (id: string) => void;
|
|
112
|
-
onMoveNode
|
|
113
|
-
onIndentNode
|
|
114
|
-
onOutdentNode
|
|
115
|
-
onAddSibling
|
|
155
|
+
onMoveNode?: (id: string, direction: 'up' | 'down') => void;
|
|
156
|
+
onIndentNode?: (id: string) => void;
|
|
157
|
+
onOutdentNode?: (id: string) => void;
|
|
158
|
+
onAddSibling?: (id: string) => void;
|
|
116
159
|
focusedNodeId: string | null;
|
|
117
160
|
onFocus: (id: string | null) => void;
|
|
118
161
|
activePath: Set<string>;
|
|
@@ -123,4 +166,4 @@ interface ListViewProps {
|
|
|
123
166
|
}
|
|
124
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;
|
|
125
168
|
|
|
126
|
-
export { CompactDropdown, FaToolsBadge, type Generation, HistoryPanel, InspectPanel, ListView, MediaLibrary, PillButton, type ProjectSettings, SectionLabel, SetupPanel, type WorkspaceTags, useOnClickOutside };
|
|
169
|
+
export { CompactDropdown, type ExtractedCharacter, FaToolsBadge, GLOBAL_STYLES, type Generation, HistoryPanel, InspectPanel, ListView, MediaLibrary, PillButton, type ProjectSettings, SectionLabel, SetupPanel, type WorkspaceTags, exportProjectToZip, formatTreeToMarkdown, getFormattedTimestamp, importProjectFromZip, injectXMPMetadata, parsePromptFile, useKeyboardNavigation, useOnClickOutside };
|
package/dist/index.d.ts
CHANGED
|
@@ -4,35 +4,6 @@ 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
|
-
declare function FaToolsBadge(): react_jsx_runtime.JSX.Element;
|
|
8
|
-
|
|
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
7
|
interface WorkspaceTags {
|
|
37
8
|
by_category: Record<string, string[]>;
|
|
38
9
|
all: Array<{
|
|
@@ -68,6 +39,77 @@ interface ProjectSettings {
|
|
|
68
39
|
seedMode: 'random' | 'fixed';
|
|
69
40
|
}
|
|
70
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
|
+
|
|
58
|
+
declare function injectXMPMetadata(base64: string, prompt: string, seed?: number, model?: string, id?: string, tags?: string[], hierarchy?: string): string;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Vollständiger Export: JSON + MD + Bilder (mit XMP Meta)
|
|
62
|
+
*/
|
|
63
|
+
declare function exportProjectToZip(nodes: any[], edges: any[], history: any[], galleryItems: any[], settings: any): Promise<{
|
|
64
|
+
base64: string;
|
|
65
|
+
}>;
|
|
66
|
+
declare function importProjectFromZip(file: File): Promise<any>;
|
|
67
|
+
|
|
68
|
+
interface ExtractedCharacter {
|
|
69
|
+
name: string;
|
|
70
|
+
prompts: {
|
|
71
|
+
title: string;
|
|
72
|
+
content: string;
|
|
73
|
+
}[];
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Parsed Charakter-Dateien für den Import.
|
|
77
|
+
*/
|
|
78
|
+
declare function parsePromptFile(text: string): ExtractedCharacter[];
|
|
79
|
+
/**
|
|
80
|
+
* Wandelt die Knotenstruktur in einen sauberen Markdown-Baum um.
|
|
81
|
+
*/
|
|
82
|
+
declare function formatTreeToMarkdown(nodes: any[], edges: any[]): string;
|
|
83
|
+
|
|
84
|
+
declare function FaToolsBadge(): react_jsx_runtime.JSX.Element;
|
|
85
|
+
|
|
86
|
+
interface PillButtonProps {
|
|
87
|
+
icon?: string;
|
|
88
|
+
children: React.ReactNode;
|
|
89
|
+
variant?: 'filled' | 'outline' | 'solid' | 'danger' | 'ghost';
|
|
90
|
+
onClick?: () => void;
|
|
91
|
+
disabled?: boolean;
|
|
92
|
+
loading?: boolean;
|
|
93
|
+
className?: string;
|
|
94
|
+
}
|
|
95
|
+
declare const PillButton: React.FC<PillButtonProps>;
|
|
96
|
+
|
|
97
|
+
declare const SectionLabel: React.FC<{
|
|
98
|
+
children: React.ReactNode;
|
|
99
|
+
}>;
|
|
100
|
+
|
|
101
|
+
interface CompactDropdownProps {
|
|
102
|
+
value: string;
|
|
103
|
+
displayValue?: string;
|
|
104
|
+
options: {
|
|
105
|
+
label: string;
|
|
106
|
+
value: string;
|
|
107
|
+
}[];
|
|
108
|
+
onChange: (val: string) => void;
|
|
109
|
+
className?: string;
|
|
110
|
+
}
|
|
111
|
+
declare const CompactDropdown: React.FC<CompactDropdownProps>;
|
|
112
|
+
|
|
71
113
|
interface HistoryPanelProps {
|
|
72
114
|
history: Generation[];
|
|
73
115
|
currentResultId: string | null;
|
|
@@ -89,7 +131,7 @@ interface SetupPanelProps {
|
|
|
89
131
|
onWorkspaceImport: (file: File) => void;
|
|
90
132
|
onProjectExport: () => void;
|
|
91
133
|
onProjectImport: (file: File) => void;
|
|
92
|
-
projectActionState: 'idle' | 'working' | 'done' | 'error';
|
|
134
|
+
projectActionState: 'idle' | 'working' | 'working-full' | 'done' | 'error';
|
|
93
135
|
}
|
|
94
136
|
declare const SetupPanel: React.FC<SetupPanelProps>;
|
|
95
137
|
|
|
@@ -98,8 +140,9 @@ interface MediaLibraryProps {
|
|
|
98
140
|
onImport: () => void;
|
|
99
141
|
onDelete: (id: string) => void;
|
|
100
142
|
onSelect: (item: Generation) => void;
|
|
101
|
-
onToggleSelection
|
|
102
|
-
onBatchDownload
|
|
143
|
+
onToggleSelection?: (id: string) => void;
|
|
144
|
+
onBatchDownload?: () => void;
|
|
145
|
+
onGenerateReference?: (item: Generation) => void;
|
|
103
146
|
}
|
|
104
147
|
declare const MediaLibrary: React.FC<MediaLibraryProps>;
|
|
105
148
|
|
|
@@ -109,10 +152,10 @@ interface ListViewProps {
|
|
|
109
152
|
onNodeChange: (id: string, label: string) => void;
|
|
110
153
|
onAddChild: (parentId: string) => void;
|
|
111
154
|
onDeleteNode: (id: string) => void;
|
|
112
|
-
onMoveNode
|
|
113
|
-
onIndentNode
|
|
114
|
-
onOutdentNode
|
|
115
|
-
onAddSibling
|
|
155
|
+
onMoveNode?: (id: string, direction: 'up' | 'down') => void;
|
|
156
|
+
onIndentNode?: (id: string) => void;
|
|
157
|
+
onOutdentNode?: (id: string) => void;
|
|
158
|
+
onAddSibling?: (id: string) => void;
|
|
116
159
|
focusedNodeId: string | null;
|
|
117
160
|
onFocus: (id: string | null) => void;
|
|
118
161
|
activePath: Set<string>;
|
|
@@ -123,4 +166,4 @@ interface ListViewProps {
|
|
|
123
166
|
}
|
|
124
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;
|
|
125
168
|
|
|
126
|
-
export { CompactDropdown, FaToolsBadge, type Generation, HistoryPanel, InspectPanel, ListView, MediaLibrary, PillButton, type ProjectSettings, SectionLabel, SetupPanel, type WorkspaceTags, useOnClickOutside };
|
|
169
|
+
export { CompactDropdown, type ExtractedCharacter, FaToolsBadge, GLOBAL_STYLES, type Generation, HistoryPanel, InspectPanel, ListView, MediaLibrary, PillButton, type ProjectSettings, SectionLabel, SetupPanel, type WorkspaceTags, exportProjectToZip, formatTreeToMarkdown, getFormattedTimestamp, importProjectFromZip, injectXMPMetadata, parsePromptFile, useKeyboardNavigation, useOnClickOutside };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -22,6 +32,7 @@ var index_exports = {};
|
|
|
22
32
|
__export(index_exports, {
|
|
23
33
|
CompactDropdown: () => CompactDropdown,
|
|
24
34
|
FaToolsBadge: () => FaToolsBadge,
|
|
35
|
+
GLOBAL_STYLES: () => GLOBAL_STYLES,
|
|
25
36
|
HistoryPanel: () => HistoryPanel,
|
|
26
37
|
InspectPanel: () => InspectPanel,
|
|
27
38
|
ListView: () => ListView,
|
|
@@ -29,6 +40,13 @@ __export(index_exports, {
|
|
|
29
40
|
PillButton: () => PillButton,
|
|
30
41
|
SectionLabel: () => SectionLabel,
|
|
31
42
|
SetupPanel: () => SetupPanel,
|
|
43
|
+
exportProjectToZip: () => exportProjectToZip,
|
|
44
|
+
formatTreeToMarkdown: () => formatTreeToMarkdown,
|
|
45
|
+
getFormattedTimestamp: () => getFormattedTimestamp,
|
|
46
|
+
importProjectFromZip: () => importProjectFromZip,
|
|
47
|
+
injectXMPMetadata: () => injectXMPMetadata,
|
|
48
|
+
parsePromptFile: () => parsePromptFile,
|
|
49
|
+
useKeyboardNavigation: () => useKeyboardNavigation,
|
|
32
50
|
useOnClickOutside: () => useOnClickOutside
|
|
33
51
|
});
|
|
34
52
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -50,6 +68,301 @@ function useOnClickOutside(ref, handler) {
|
|
|
50
68
|
}, [ref, handler]);
|
|
51
69
|
}
|
|
52
70
|
|
|
71
|
+
// src/hooks/useKeyboardNavigation.ts
|
|
72
|
+
var import_react2 = require("react");
|
|
73
|
+
function useKeyboardNavigation(history, currentResult, setCurrentResult) {
|
|
74
|
+
(0, import_react2.useEffect)(() => {
|
|
75
|
+
const handleKeyDown = (e) => {
|
|
76
|
+
if (e.target instanceof HTMLTextAreaElement || e.target instanceof HTMLInputElement) return;
|
|
77
|
+
if (!currentResult || history.length === 0) return;
|
|
78
|
+
const currentIndex = history.findIndex((h) => h.id === currentResult.id);
|
|
79
|
+
if (e.key === "ArrowRight" && currentIndex < history.length - 1) {
|
|
80
|
+
setCurrentResult(history[currentIndex + 1]);
|
|
81
|
+
} else if (e.key === "ArrowLeft" && currentIndex > 0) {
|
|
82
|
+
setCurrentResult(history[currentIndex - 1]);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
86
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
87
|
+
}, [currentResult, history, setCurrentResult]);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/lib/utils.ts
|
|
91
|
+
var getFormattedTimestamp = () => {
|
|
92
|
+
const d = /* @__PURE__ */ new Date();
|
|
93
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
94
|
+
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
95
|
+
};
|
|
96
|
+
var GLOBAL_STYLES = `
|
|
97
|
+
.dark-scrollbar::-webkit-scrollbar { width: 4px; height: 4px; }
|
|
98
|
+
.dark-scrollbar::-webkit-scrollbar-track { background: transparent; }
|
|
99
|
+
.dark-scrollbar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }
|
|
100
|
+
html, body, #root { margin: 0; padding: 0; width: 100%; height: 100%; background: #0e0e0e; font-family: 'Google Sans Text', sans-serif; overflow: hidden; }
|
|
101
|
+
@keyframes prompt-shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }
|
|
102
|
+
.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; }
|
|
103
|
+
@keyframes dropdown-enter { from { opacity: 0; transform: scale(0.95) translateY(-5px); } to { opacity: 1; transform: scale(1) translateY(0); } }
|
|
104
|
+
.animate-dropdown { animation: dropdown-enter 0.1s ease-out forwards; }
|
|
105
|
+
`;
|
|
106
|
+
|
|
107
|
+
// src/lib/metadata.ts
|
|
108
|
+
var crcTable = (() => {
|
|
109
|
+
let c;
|
|
110
|
+
const table = new Uint32Array(256);
|
|
111
|
+
for (let n = 0; n < 256; n++) {
|
|
112
|
+
c = n;
|
|
113
|
+
for (let k = 0; k < 8; k++) {
|
|
114
|
+
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
115
|
+
}
|
|
116
|
+
table[n] = c;
|
|
117
|
+
}
|
|
118
|
+
return table;
|
|
119
|
+
})();
|
|
120
|
+
function calculateCRC(bytes) {
|
|
121
|
+
let crc = 4294967295;
|
|
122
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
123
|
+
crc = crcTable[(crc ^ bytes[i]) & 255] ^ crc >>> 8;
|
|
124
|
+
}
|
|
125
|
+
return (crc ^ 4294967295) >>> 0;
|
|
126
|
+
}
|
|
127
|
+
function indexOfBytes(buffer, search, start = 0) {
|
|
128
|
+
for (let i = start; i <= buffer.length - search.length; i++) {
|
|
129
|
+
let found = true;
|
|
130
|
+
for (let j = 0; j < search.length; j++) {
|
|
131
|
+
if (buffer[i + j] !== search[j]) {
|
|
132
|
+
found = false;
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (found) return i;
|
|
137
|
+
}
|
|
138
|
+
return -1;
|
|
139
|
+
}
|
|
140
|
+
function createXMPPacket(prompt, seed, model, id, tags = [], hierarchy) {
|
|
141
|
+
const sanitize = (str) => (str || "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
142
|
+
const escPrompt = sanitize(prompt);
|
|
143
|
+
const escModel = sanitize(model || "AI Model");
|
|
144
|
+
const escHierarchy = sanitize(hierarchy || "");
|
|
145
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
146
|
+
const allTags = [.../* @__PURE__ */ new Set([...tags, "Avatar Architect", escModel])];
|
|
147
|
+
const rdfTags = allTags.map((t) => `<rdf:li>${sanitize(t)}</rdf:li>`).join("");
|
|
148
|
+
const BOM = "\uFEFF";
|
|
149
|
+
return `<?xpacket begin="${BOM}" id="W5M0MpCehiHzreSzNTczkc9d"?>
|
|
150
|
+
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c140">
|
|
151
|
+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
152
|
+
<rdf:Description rdf:about=""
|
|
153
|
+
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
|
|
154
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
155
|
+
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
|
|
156
|
+
xmlns:ai="http://ns.flow.creative/ai/1.0/">
|
|
157
|
+
<dc:format>image/png</dc:format>
|
|
158
|
+
<dc:description><rdf:Alt><rdf:li xml:lang="x-default">${escPrompt}</rdf:li></rdf:Alt></dc:description>
|
|
159
|
+
<dc:subject><rdf:Bag>${rdfTags}</rdf:Bag></dc:subject>
|
|
160
|
+
<photoshop:Instructions>Seed: ${seed} | Model: ${escModel}</photoshop:Instructions>
|
|
161
|
+
<xmp:CreateDate>${timestamp}</xmp:CreateDate>
|
|
162
|
+
<ai:prompt>${escPrompt}</ai:prompt>
|
|
163
|
+
<ai:seed>${seed ?? "0"}</ai:seed>
|
|
164
|
+
<ai:model>${escModel}</ai:model>
|
|
165
|
+
<ai:hierarchy>${escHierarchy}</ai:hierarchy>
|
|
166
|
+
</rdf:Description>
|
|
167
|
+
</rdf:RDF>
|
|
168
|
+
</x:xmpmeta>${" ".repeat(1024)}<?xpacket end="w"?>`;
|
|
169
|
+
}
|
|
170
|
+
function bytesToBase64(bytes) {
|
|
171
|
+
let binary = "";
|
|
172
|
+
const len = bytes.byteLength;
|
|
173
|
+
for (let i = 0; i < len; i++) binary += String.fromCharCode(bytes[i]);
|
|
174
|
+
return btoa(binary);
|
|
175
|
+
}
|
|
176
|
+
function base64ToBytes(base64) {
|
|
177
|
+
const clean = base64.includes(",") ? base64.split(",")[1] : base64.trim();
|
|
178
|
+
try {
|
|
179
|
+
const binary = atob(clean);
|
|
180
|
+
const bytes = new Uint8Array(binary.length);
|
|
181
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
182
|
+
return bytes;
|
|
183
|
+
} catch (e) {
|
|
184
|
+
return new Uint8Array(0);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
function injectXMPMetadata(base64, prompt, seed, model, id, tags = [], hierarchy) {
|
|
188
|
+
try {
|
|
189
|
+
const bytes = base64ToBytes(base64);
|
|
190
|
+
if (bytes.length < 8 || bytes[0] !== 137 || bytes[1] !== 80) {
|
|
191
|
+
console.warn("Metadaten-Injektion \xFCbersprungen: Datei ist kein PNG.");
|
|
192
|
+
return base64;
|
|
193
|
+
}
|
|
194
|
+
const xmpData = createXMPPacket(prompt, seed, model, id, tags, hierarchy);
|
|
195
|
+
const encoder = new TextEncoder();
|
|
196
|
+
const keyword = encoder.encode("XML:com.adobe.xmp");
|
|
197
|
+
const textBytes = encoder.encode(xmpData);
|
|
198
|
+
const chunkData = new Uint8Array(keyword.length + 5 + textBytes.length);
|
|
199
|
+
chunkData.set(keyword, 0);
|
|
200
|
+
chunkData.set(textBytes, keyword.length + 5);
|
|
201
|
+
const type = encoder.encode("iTXt");
|
|
202
|
+
const crcInput = new Uint8Array(type.length + chunkData.length);
|
|
203
|
+
crcInput.set(type, 0);
|
|
204
|
+
crcInput.set(chunkData, type.length);
|
|
205
|
+
const crc = calculateCRC(crcInput);
|
|
206
|
+
const fullChunk = new Uint8Array(4 + 4 + chunkData.length + 4);
|
|
207
|
+
const view = new DataView(fullChunk.buffer);
|
|
208
|
+
view.setUint32(0, chunkData.length);
|
|
209
|
+
fullChunk.set(type, 4);
|
|
210
|
+
fullChunk.set(chunkData, 8);
|
|
211
|
+
view.setUint32(8 + chunkData.length, crc);
|
|
212
|
+
const IDAT = encoder.encode("IDAT");
|
|
213
|
+
const idatPos = indexOfBytes(bytes, IDAT);
|
|
214
|
+
const insertPos = idatPos !== -1 ? idatPos - 4 : 8;
|
|
215
|
+
const result = new Uint8Array(bytes.length + fullChunk.length);
|
|
216
|
+
result.set(bytes.slice(0, insertPos), 0);
|
|
217
|
+
result.set(fullChunk, insertPos);
|
|
218
|
+
result.set(bytes.slice(insertPos), insertPos + fullChunk.length);
|
|
219
|
+
return bytesToBase64(result);
|
|
220
|
+
} catch (e) {
|
|
221
|
+
console.error("Fehler beim Einbetten der Metadaten:", e);
|
|
222
|
+
return base64;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// src/lib/project.ts
|
|
227
|
+
var import_jszip = __toESM(require("jszip"));
|
|
228
|
+
function generateMarkdownReport(nodes, history) {
|
|
229
|
+
let md = "# Avatar Architect - Projekt Report\n\n";
|
|
230
|
+
md += `Exportiert: ${(/* @__PURE__ */ new Date()).toLocaleString()}
|
|
231
|
+
|
|
232
|
+
`;
|
|
233
|
+
md += "## Hierarchie\n";
|
|
234
|
+
nodes.forEach((n) => {
|
|
235
|
+
md += `- **${n.data?.label || "Unbenannt"}**
|
|
236
|
+
`;
|
|
237
|
+
});
|
|
238
|
+
md += "\n## Historie der Generationen\n";
|
|
239
|
+
history.forEach((h, i) => {
|
|
240
|
+
md += `### ${i + 1}. Generation (${h.model || "AI"})
|
|
241
|
+
`;
|
|
242
|
+
md += `- **Zeitstempel**: ${new Date(h.timestamp).toLocaleString()}
|
|
243
|
+
`;
|
|
244
|
+
md += `- **Seed**: ${h.seed}
|
|
245
|
+
`;
|
|
246
|
+
md += `- **Prompt**:
|
|
247
|
+
> ${h.prompt}
|
|
248
|
+
|
|
249
|
+
`;
|
|
250
|
+
});
|
|
251
|
+
return md;
|
|
252
|
+
}
|
|
253
|
+
async function exportProjectToZip(nodes, edges, history, galleryItems, settings) {
|
|
254
|
+
const zip = new import_jszip.default();
|
|
255
|
+
const projectData = {
|
|
256
|
+
nodes,
|
|
257
|
+
edges,
|
|
258
|
+
history: history.map((h) => ({ ...h, base64: void 0 })),
|
|
259
|
+
galleryItems: galleryItems.map((g) => ({ ...g, base64: void 0 })),
|
|
260
|
+
settings,
|
|
261
|
+
version: "1.2.6"
|
|
262
|
+
};
|
|
263
|
+
zip.file("project.json", JSON.stringify(projectData, null, 2));
|
|
264
|
+
zip.file("projekt_bericht.md", generateMarkdownReport(nodes, history));
|
|
265
|
+
const imgFolder = zip.folder("images");
|
|
266
|
+
if (imgFolder) {
|
|
267
|
+
const allItems = [...history, ...galleryItems];
|
|
268
|
+
for (const item of allItems) {
|
|
269
|
+
if (item.base64) {
|
|
270
|
+
try {
|
|
271
|
+
const cleanB64 = item.base64.includes(",") ? item.base64.split(",")[1] : item.base64;
|
|
272
|
+
const withMeta = injectXMPMetadata(cleanB64, item.prompt || "", item.seed, item.model, item.id, item.tags || []);
|
|
273
|
+
imgFolder.file(`${item.id}.png`, withMeta, { base64: true });
|
|
274
|
+
} catch (e) {
|
|
275
|
+
console.error("Fehler beim Packen eines Bildes:", e);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const blob = await zip.generateAsync({ type: "blob", compression: "STORE" });
|
|
281
|
+
return blobToBase64(blob);
|
|
282
|
+
}
|
|
283
|
+
async function blobToBase64(blob) {
|
|
284
|
+
return new Promise((resolve, reject) => {
|
|
285
|
+
const reader = new FileReader();
|
|
286
|
+
reader.onloadend = () => {
|
|
287
|
+
const result = reader.result;
|
|
288
|
+
resolve({ base64: result.split(",")[1] });
|
|
289
|
+
};
|
|
290
|
+
reader.onerror = reject;
|
|
291
|
+
reader.readAsDataURL(blob);
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
async function importProjectFromZip(file) {
|
|
295
|
+
const zip = await import_jszip.default.loadAsync(file);
|
|
296
|
+
const jsonStr = await zip.file("project.json")?.async("string");
|
|
297
|
+
if (!jsonStr) throw new Error("Keine project.json gefunden.");
|
|
298
|
+
const data = JSON.parse(jsonStr);
|
|
299
|
+
const imgFolder = zip.folder("images");
|
|
300
|
+
if (imgFolder) {
|
|
301
|
+
const loadImgs = async (items) => {
|
|
302
|
+
if (!items) return [];
|
|
303
|
+
return Promise.all(items.map(async (h) => {
|
|
304
|
+
const imgFile = zip.file(`images/${h.id}.png`);
|
|
305
|
+
if (!imgFile) return h;
|
|
306
|
+
const b64 = await imgFile.async("base64");
|
|
307
|
+
return { ...h, base64: `data:image/png;base64,${b64}` };
|
|
308
|
+
}));
|
|
309
|
+
};
|
|
310
|
+
data.history = await loadImgs(data.history || []);
|
|
311
|
+
data.galleryItems = await loadImgs(data.galleryItems || []);
|
|
312
|
+
}
|
|
313
|
+
return data;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// src/lib/parserService.ts
|
|
317
|
+
function parsePromptFile(text) {
|
|
318
|
+
const characters = [];
|
|
319
|
+
const sections = text.split(/^##\s+/m);
|
|
320
|
+
for (let i = 1; i < sections.length; i++) {
|
|
321
|
+
const section = sections[i];
|
|
322
|
+
const lines = section.split("\n");
|
|
323
|
+
const name = lines[0].trim();
|
|
324
|
+
const character = {
|
|
325
|
+
name,
|
|
326
|
+
prompts: []
|
|
327
|
+
};
|
|
328
|
+
const promptRegex = /###\s+(.*?)\s*\n\s*```(?:\w+)?\n([\s\S]*?)```/g;
|
|
329
|
+
let match;
|
|
330
|
+
while ((match = promptRegex.exec(section)) !== null) {
|
|
331
|
+
character.prompts.push({
|
|
332
|
+
title: match[1].trim(),
|
|
333
|
+
content: match[2].trim()
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
if (character.name) {
|
|
337
|
+
characters.push(character);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return characters;
|
|
341
|
+
}
|
|
342
|
+
function formatTreeToMarkdown(nodes, edges) {
|
|
343
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
344
|
+
nodes.forEach((n) => nodeMap.set(n.id, { ...n, children: [] }));
|
|
345
|
+
edges.forEach((e) => {
|
|
346
|
+
const parent = nodeMap.get(e.source);
|
|
347
|
+
if (parent) parent.children.push(e.target);
|
|
348
|
+
});
|
|
349
|
+
const roots = nodes.filter((n) => !edges.some((e) => e.target === n.id));
|
|
350
|
+
const processNode = (id, depth = 0) => {
|
|
351
|
+
const node = nodeMap.get(id);
|
|
352
|
+
if (!node) return "";
|
|
353
|
+
const indent = " ".repeat(depth);
|
|
354
|
+
let md = `${indent}- ${node.data.label || "Unbenannt"}
|
|
355
|
+
`;
|
|
356
|
+
if (node.children) {
|
|
357
|
+
node.children.forEach((childId) => {
|
|
358
|
+
md += processNode(childId, depth + 1);
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
return md;
|
|
362
|
+
};
|
|
363
|
+
return roots.map((root) => processNode(root.id)).join("\n");
|
|
364
|
+
}
|
|
365
|
+
|
|
53
366
|
// src/components/Badge.tsx
|
|
54
367
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
55
368
|
function FaToolsBadge() {
|
|
@@ -107,7 +420,7 @@ var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
|
107
420
|
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
421
|
|
|
109
422
|
// src/components/CompactDropdown.tsx
|
|
110
|
-
var
|
|
423
|
+
var import_react3 = require("react");
|
|
111
424
|
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
112
425
|
var CompactDropdown = ({
|
|
113
426
|
value,
|
|
@@ -116,8 +429,8 @@ var CompactDropdown = ({
|
|
|
116
429
|
onChange,
|
|
117
430
|
className = ""
|
|
118
431
|
}) => {
|
|
119
|
-
const [isOpen, setIsOpen] = (0,
|
|
120
|
-
const ref = (0,
|
|
432
|
+
const [isOpen, setIsOpen] = (0, import_react3.useState)(false);
|
|
433
|
+
const ref = (0, import_react3.useRef)(null);
|
|
121
434
|
useOnClickOutside(ref, () => setIsOpen(false));
|
|
122
435
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { ref, className: `relative shrink-0 ${className}`, children: [
|
|
123
436
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
@@ -149,7 +462,7 @@ var CompactDropdown = ({
|
|
|
149
462
|
};
|
|
150
463
|
|
|
151
464
|
// src/components/HistoryPanel.tsx
|
|
152
|
-
var
|
|
465
|
+
var import_react4 = require("motion/react");
|
|
153
466
|
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
154
467
|
var formatFriendlyTimestamp = (timestamp) => {
|
|
155
468
|
const date = new Date(timestamp);
|
|
@@ -169,7 +482,7 @@ var HistoryPanel = ({ history, currentResultId, onSelect, onDelete }) => {
|
|
|
169
482
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-[10px] font-bold uppercase tracking-widest", children: "Keine Historie" })
|
|
170
483
|
] });
|
|
171
484
|
}
|
|
172
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
485
|
+
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)(
|
|
173
486
|
"div",
|
|
174
487
|
{
|
|
175
488
|
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"}`,
|
|
@@ -198,7 +511,7 @@ var HistoryPanel = ({ history, currentResultId, onSelect, onDelete }) => {
|
|
|
198
511
|
};
|
|
199
512
|
|
|
200
513
|
// src/components/InspectPanel.tsx
|
|
201
|
-
var
|
|
514
|
+
var import_react5 = require("motion/react");
|
|
202
515
|
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
203
516
|
var InspectPanel = ({ currentResult, history, onSelect, workspaceTags, onTagToggle }) => {
|
|
204
517
|
if (!currentResult) {
|
|
@@ -208,7 +521,7 @@ var InspectPanel = ({ currentResult, history, onSelect, workspaceTags, onTagTogg
|
|
|
208
521
|
] });
|
|
209
522
|
}
|
|
210
523
|
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)(
|
|
524
|
+
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: [
|
|
212
525
|
/* @__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)) }) }),
|
|
213
526
|
/* @__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
527
|
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SectionLabel, { children: "Vorschau" }),
|
|
@@ -250,13 +563,13 @@ var InspectPanel = ({ currentResult, history, onSelect, workspaceTags, onTagTogg
|
|
|
250
563
|
};
|
|
251
564
|
|
|
252
565
|
// src/components/SetupPanel.tsx
|
|
253
|
-
var
|
|
254
|
-
var
|
|
566
|
+
var import_react6 = require("react");
|
|
567
|
+
var import_react7 = require("motion/react");
|
|
255
568
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
256
569
|
var SetupPanel = ({ onWorkspaceImport, onProjectExport, onProjectImport, projectActionState }) => {
|
|
257
|
-
const workspaceInputRef = (0,
|
|
258
|
-
const projectInputRef = (0,
|
|
259
|
-
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
|
|
570
|
+
const workspaceInputRef = (0, import_react6.useRef)(null);
|
|
571
|
+
const projectInputRef = (0, import_react6.useRef)(null);
|
|
572
|
+
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: [
|
|
260
573
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-4", children: [
|
|
261
574
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-1", children: [
|
|
262
575
|
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SectionLabel, { children: "Workspace Management" }),
|
|
@@ -299,9 +612,9 @@ var SetupPanel = ({ onWorkspaceImport, onProjectExport, onProjectImport, project
|
|
|
299
612
|
};
|
|
300
613
|
|
|
301
614
|
// src/components/MediaLibrary.tsx
|
|
302
|
-
var
|
|
615
|
+
var import_react8 = require("motion/react");
|
|
303
616
|
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
304
|
-
var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, onBatchDownload }) => {
|
|
617
|
+
var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, onBatchDownload, onGenerateReference }) => {
|
|
305
618
|
const selectedCount = items.filter((i) => i.selectedForExport).length;
|
|
306
619
|
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col h-full overflow-hidden", children: [
|
|
307
620
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex flex-col p-4 border-b border-white/5 gap-3 bg-black/20", children: [
|
|
@@ -318,7 +631,7 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
|
|
|
318
631
|
"Laden"
|
|
319
632
|
] })
|
|
320
633
|
] }),
|
|
321
|
-
selectedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
|
|
634
|
+
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: [
|
|
322
635
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "text-[9px] font-bold uppercase text-blue-400 ml-1", children: [
|
|
323
636
|
selectedCount,
|
|
324
637
|
" ausgew\xE4hlt"
|
|
@@ -335,7 +648,7 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
|
|
|
335
648
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-[12px] font-bold uppercase tracking-widest", children: "Keine Medien" }),
|
|
336
649
|
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-[10px] italic", children: "Importiere Assets aus deinem Projekt." })
|
|
337
650
|
] })
|
|
338
|
-
] }) : /* @__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)(
|
|
651
|
+
] }) : /* @__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: [
|
|
339
652
|
/* @__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 }),
|
|
340
653
|
/* @__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) => {
|
|
341
654
|
e.stopPropagation();
|
|
@@ -343,10 +656,16 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
|
|
|
343
656
|
}, children: item.selectedForExport && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "material-symbols-outlined text-[14px] text-white", children: "check" }) }),
|
|
344
657
|
/* @__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 pointer-events-none", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center justify-between", children: [
|
|
345
658
|
/* @__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" }),
|
|
346
|
-
/* @__PURE__ */ (0, import_jsx_runtime8.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
659
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center gap-1", children: [
|
|
660
|
+
onGenerateReference && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: (e) => {
|
|
661
|
+
e.stopPropagation();
|
|
662
|
+
onGenerateReference(item);
|
|
663
|
+
}, className: "w-6 h-6 flex items-center justify-center bg-blue-500/20 text-blue-400 hover:bg-blue-500 rounded-md transition-all hover:text-white pointer-events-auto", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "material-symbols-outlined text-[14px]", children: "auto_fix_high" }) }),
|
|
664
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", onClick: (e) => {
|
|
665
|
+
e.stopPropagation();
|
|
666
|
+
onDelete?.(item.id);
|
|
667
|
+
}, 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 pointer-events-auto", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "material-symbols-outlined text-[14px]", children: "delete" }) })
|
|
668
|
+
] })
|
|
350
669
|
] }) }),
|
|
351
670
|
/* @__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" })
|
|
352
671
|
] }, item.id)) }) })
|
|
@@ -354,10 +673,10 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
|
|
|
354
673
|
};
|
|
355
674
|
|
|
356
675
|
// src/components/ListView.tsx
|
|
357
|
-
var
|
|
676
|
+
var import_react9 = require("react");
|
|
358
677
|
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
359
678
|
var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, isActive, isInPath, onFocus, onGenerate, onGenerateBranch, onGenerateSubtree, isGenerating, isCollapsed, toggleCollapse, renderNode, children }) => {
|
|
360
|
-
const inputRef = (0,
|
|
679
|
+
const inputRef = (0, import_react9.useRef)(null);
|
|
361
680
|
const hasChildren = children && children.length > 0;
|
|
362
681
|
const handleKeyDown = (e) => {
|
|
363
682
|
if (e.key === "Tab") {
|
|
@@ -385,7 +704,7 @@ var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNod
|
|
|
385
704
|
onMoveNode(node.id, "down");
|
|
386
705
|
}
|
|
387
706
|
};
|
|
388
|
-
(0,
|
|
707
|
+
(0, import_react9.useEffect)(() => {
|
|
389
708
|
if (isActive && inputRef.current) inputRef.current.focus();
|
|
390
709
|
}, [isActive]);
|
|
391
710
|
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col w-full", children: [
|
|
@@ -419,7 +738,7 @@ var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNod
|
|
|
419
738
|
] });
|
|
420
739
|
};
|
|
421
740
|
function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, focusedNodeId, onFocus, activePath, onGenerate, onGenerateBranch, onGenerateSubtree, isGeneratingNodeId }) {
|
|
422
|
-
const [collapsed, setCollapsed] = (0,
|
|
741
|
+
const [collapsed, setCollapsed] = (0, import_react9.useState)(/* @__PURE__ */ new Set());
|
|
423
742
|
const toggleCollapse = (id) => {
|
|
424
743
|
setCollapsed((prev) => {
|
|
425
744
|
const next = new Set(prev);
|
|
@@ -449,6 +768,7 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
|
|
|
449
768
|
0 && (module.exports = {
|
|
450
769
|
CompactDropdown,
|
|
451
770
|
FaToolsBadge,
|
|
771
|
+
GLOBAL_STYLES,
|
|
452
772
|
HistoryPanel,
|
|
453
773
|
InspectPanel,
|
|
454
774
|
ListView,
|
|
@@ -456,5 +776,12 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
|
|
|
456
776
|
PillButton,
|
|
457
777
|
SectionLabel,
|
|
458
778
|
SetupPanel,
|
|
779
|
+
exportProjectToZip,
|
|
780
|
+
formatTreeToMarkdown,
|
|
781
|
+
getFormattedTimestamp,
|
|
782
|
+
importProjectFromZip,
|
|
783
|
+
injectXMPMetadata,
|
|
784
|
+
parsePromptFile,
|
|
785
|
+
useKeyboardNavigation,
|
|
459
786
|
useOnClickOutside
|
|
460
787
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -15,6 +15,301 @@ 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
|
+
|
|
54
|
+
// src/lib/metadata.ts
|
|
55
|
+
var crcTable = (() => {
|
|
56
|
+
let c;
|
|
57
|
+
const table = new Uint32Array(256);
|
|
58
|
+
for (let n = 0; n < 256; n++) {
|
|
59
|
+
c = n;
|
|
60
|
+
for (let k = 0; k < 8; k++) {
|
|
61
|
+
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
62
|
+
}
|
|
63
|
+
table[n] = c;
|
|
64
|
+
}
|
|
65
|
+
return table;
|
|
66
|
+
})();
|
|
67
|
+
function calculateCRC(bytes) {
|
|
68
|
+
let crc = 4294967295;
|
|
69
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
70
|
+
crc = crcTable[(crc ^ bytes[i]) & 255] ^ crc >>> 8;
|
|
71
|
+
}
|
|
72
|
+
return (crc ^ 4294967295) >>> 0;
|
|
73
|
+
}
|
|
74
|
+
function indexOfBytes(buffer, search, start = 0) {
|
|
75
|
+
for (let i = start; i <= buffer.length - search.length; i++) {
|
|
76
|
+
let found = true;
|
|
77
|
+
for (let j = 0; j < search.length; j++) {
|
|
78
|
+
if (buffer[i + j] !== search[j]) {
|
|
79
|
+
found = false;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (found) return i;
|
|
84
|
+
}
|
|
85
|
+
return -1;
|
|
86
|
+
}
|
|
87
|
+
function createXMPPacket(prompt, seed, model, id, tags = [], hierarchy) {
|
|
88
|
+
const sanitize = (str) => (str || "").replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
89
|
+
const escPrompt = sanitize(prompt);
|
|
90
|
+
const escModel = sanitize(model || "AI Model");
|
|
91
|
+
const escHierarchy = sanitize(hierarchy || "");
|
|
92
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
93
|
+
const allTags = [.../* @__PURE__ */ new Set([...tags, "Avatar Architect", escModel])];
|
|
94
|
+
const rdfTags = allTags.map((t) => `<rdf:li>${sanitize(t)}</rdf:li>`).join("");
|
|
95
|
+
const BOM = "\uFEFF";
|
|
96
|
+
return `<?xpacket begin="${BOM}" id="W5M0MpCehiHzreSzNTczkc9d"?>
|
|
97
|
+
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 5.6-c140">
|
|
98
|
+
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
99
|
+
<rdf:Description rdf:about=""
|
|
100
|
+
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
|
|
101
|
+
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
|
102
|
+
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
|
|
103
|
+
xmlns:ai="http://ns.flow.creative/ai/1.0/">
|
|
104
|
+
<dc:format>image/png</dc:format>
|
|
105
|
+
<dc:description><rdf:Alt><rdf:li xml:lang="x-default">${escPrompt}</rdf:li></rdf:Alt></dc:description>
|
|
106
|
+
<dc:subject><rdf:Bag>${rdfTags}</rdf:Bag></dc:subject>
|
|
107
|
+
<photoshop:Instructions>Seed: ${seed} | Model: ${escModel}</photoshop:Instructions>
|
|
108
|
+
<xmp:CreateDate>${timestamp}</xmp:CreateDate>
|
|
109
|
+
<ai:prompt>${escPrompt}</ai:prompt>
|
|
110
|
+
<ai:seed>${seed ?? "0"}</ai:seed>
|
|
111
|
+
<ai:model>${escModel}</ai:model>
|
|
112
|
+
<ai:hierarchy>${escHierarchy}</ai:hierarchy>
|
|
113
|
+
</rdf:Description>
|
|
114
|
+
</rdf:RDF>
|
|
115
|
+
</x:xmpmeta>${" ".repeat(1024)}<?xpacket end="w"?>`;
|
|
116
|
+
}
|
|
117
|
+
function bytesToBase64(bytes) {
|
|
118
|
+
let binary = "";
|
|
119
|
+
const len = bytes.byteLength;
|
|
120
|
+
for (let i = 0; i < len; i++) binary += String.fromCharCode(bytes[i]);
|
|
121
|
+
return btoa(binary);
|
|
122
|
+
}
|
|
123
|
+
function base64ToBytes(base64) {
|
|
124
|
+
const clean = base64.includes(",") ? base64.split(",")[1] : base64.trim();
|
|
125
|
+
try {
|
|
126
|
+
const binary = atob(clean);
|
|
127
|
+
const bytes = new Uint8Array(binary.length);
|
|
128
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
129
|
+
return bytes;
|
|
130
|
+
} catch (e) {
|
|
131
|
+
return new Uint8Array(0);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function injectXMPMetadata(base64, prompt, seed, model, id, tags = [], hierarchy) {
|
|
135
|
+
try {
|
|
136
|
+
const bytes = base64ToBytes(base64);
|
|
137
|
+
if (bytes.length < 8 || bytes[0] !== 137 || bytes[1] !== 80) {
|
|
138
|
+
console.warn("Metadaten-Injektion \xFCbersprungen: Datei ist kein PNG.");
|
|
139
|
+
return base64;
|
|
140
|
+
}
|
|
141
|
+
const xmpData = createXMPPacket(prompt, seed, model, id, tags, hierarchy);
|
|
142
|
+
const encoder = new TextEncoder();
|
|
143
|
+
const keyword = encoder.encode("XML:com.adobe.xmp");
|
|
144
|
+
const textBytes = encoder.encode(xmpData);
|
|
145
|
+
const chunkData = new Uint8Array(keyword.length + 5 + textBytes.length);
|
|
146
|
+
chunkData.set(keyword, 0);
|
|
147
|
+
chunkData.set(textBytes, keyword.length + 5);
|
|
148
|
+
const type = encoder.encode("iTXt");
|
|
149
|
+
const crcInput = new Uint8Array(type.length + chunkData.length);
|
|
150
|
+
crcInput.set(type, 0);
|
|
151
|
+
crcInput.set(chunkData, type.length);
|
|
152
|
+
const crc = calculateCRC(crcInput);
|
|
153
|
+
const fullChunk = new Uint8Array(4 + 4 + chunkData.length + 4);
|
|
154
|
+
const view = new DataView(fullChunk.buffer);
|
|
155
|
+
view.setUint32(0, chunkData.length);
|
|
156
|
+
fullChunk.set(type, 4);
|
|
157
|
+
fullChunk.set(chunkData, 8);
|
|
158
|
+
view.setUint32(8 + chunkData.length, crc);
|
|
159
|
+
const IDAT = encoder.encode("IDAT");
|
|
160
|
+
const idatPos = indexOfBytes(bytes, IDAT);
|
|
161
|
+
const insertPos = idatPos !== -1 ? idatPos - 4 : 8;
|
|
162
|
+
const result = new Uint8Array(bytes.length + fullChunk.length);
|
|
163
|
+
result.set(bytes.slice(0, insertPos), 0);
|
|
164
|
+
result.set(fullChunk, insertPos);
|
|
165
|
+
result.set(bytes.slice(insertPos), insertPos + fullChunk.length);
|
|
166
|
+
return bytesToBase64(result);
|
|
167
|
+
} catch (e) {
|
|
168
|
+
console.error("Fehler beim Einbetten der Metadaten:", e);
|
|
169
|
+
return base64;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// src/lib/project.ts
|
|
174
|
+
import JSZip from "jszip";
|
|
175
|
+
function generateMarkdownReport(nodes, history) {
|
|
176
|
+
let md = "# Avatar Architect - Projekt Report\n\n";
|
|
177
|
+
md += `Exportiert: ${(/* @__PURE__ */ new Date()).toLocaleString()}
|
|
178
|
+
|
|
179
|
+
`;
|
|
180
|
+
md += "## Hierarchie\n";
|
|
181
|
+
nodes.forEach((n) => {
|
|
182
|
+
md += `- **${n.data?.label || "Unbenannt"}**
|
|
183
|
+
`;
|
|
184
|
+
});
|
|
185
|
+
md += "\n## Historie der Generationen\n";
|
|
186
|
+
history.forEach((h, i) => {
|
|
187
|
+
md += `### ${i + 1}. Generation (${h.model || "AI"})
|
|
188
|
+
`;
|
|
189
|
+
md += `- **Zeitstempel**: ${new Date(h.timestamp).toLocaleString()}
|
|
190
|
+
`;
|
|
191
|
+
md += `- **Seed**: ${h.seed}
|
|
192
|
+
`;
|
|
193
|
+
md += `- **Prompt**:
|
|
194
|
+
> ${h.prompt}
|
|
195
|
+
|
|
196
|
+
`;
|
|
197
|
+
});
|
|
198
|
+
return md;
|
|
199
|
+
}
|
|
200
|
+
async function exportProjectToZip(nodes, edges, history, galleryItems, settings) {
|
|
201
|
+
const zip = new JSZip();
|
|
202
|
+
const projectData = {
|
|
203
|
+
nodes,
|
|
204
|
+
edges,
|
|
205
|
+
history: history.map((h) => ({ ...h, base64: void 0 })),
|
|
206
|
+
galleryItems: galleryItems.map((g) => ({ ...g, base64: void 0 })),
|
|
207
|
+
settings,
|
|
208
|
+
version: "1.2.6"
|
|
209
|
+
};
|
|
210
|
+
zip.file("project.json", JSON.stringify(projectData, null, 2));
|
|
211
|
+
zip.file("projekt_bericht.md", generateMarkdownReport(nodes, history));
|
|
212
|
+
const imgFolder = zip.folder("images");
|
|
213
|
+
if (imgFolder) {
|
|
214
|
+
const allItems = [...history, ...galleryItems];
|
|
215
|
+
for (const item of allItems) {
|
|
216
|
+
if (item.base64) {
|
|
217
|
+
try {
|
|
218
|
+
const cleanB64 = item.base64.includes(",") ? item.base64.split(",")[1] : item.base64;
|
|
219
|
+
const withMeta = injectXMPMetadata(cleanB64, item.prompt || "", item.seed, item.model, item.id, item.tags || []);
|
|
220
|
+
imgFolder.file(`${item.id}.png`, withMeta, { base64: true });
|
|
221
|
+
} catch (e) {
|
|
222
|
+
console.error("Fehler beim Packen eines Bildes:", e);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
const blob = await zip.generateAsync({ type: "blob", compression: "STORE" });
|
|
228
|
+
return blobToBase64(blob);
|
|
229
|
+
}
|
|
230
|
+
async function blobToBase64(blob) {
|
|
231
|
+
return new Promise((resolve, reject) => {
|
|
232
|
+
const reader = new FileReader();
|
|
233
|
+
reader.onloadend = () => {
|
|
234
|
+
const result = reader.result;
|
|
235
|
+
resolve({ base64: result.split(",")[1] });
|
|
236
|
+
};
|
|
237
|
+
reader.onerror = reject;
|
|
238
|
+
reader.readAsDataURL(blob);
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
async function importProjectFromZip(file) {
|
|
242
|
+
const zip = await JSZip.loadAsync(file);
|
|
243
|
+
const jsonStr = await zip.file("project.json")?.async("string");
|
|
244
|
+
if (!jsonStr) throw new Error("Keine project.json gefunden.");
|
|
245
|
+
const data = JSON.parse(jsonStr);
|
|
246
|
+
const imgFolder = zip.folder("images");
|
|
247
|
+
if (imgFolder) {
|
|
248
|
+
const loadImgs = async (items) => {
|
|
249
|
+
if (!items) return [];
|
|
250
|
+
return Promise.all(items.map(async (h) => {
|
|
251
|
+
const imgFile = zip.file(`images/${h.id}.png`);
|
|
252
|
+
if (!imgFile) return h;
|
|
253
|
+
const b64 = await imgFile.async("base64");
|
|
254
|
+
return { ...h, base64: `data:image/png;base64,${b64}` };
|
|
255
|
+
}));
|
|
256
|
+
};
|
|
257
|
+
data.history = await loadImgs(data.history || []);
|
|
258
|
+
data.galleryItems = await loadImgs(data.galleryItems || []);
|
|
259
|
+
}
|
|
260
|
+
return data;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/lib/parserService.ts
|
|
264
|
+
function parsePromptFile(text) {
|
|
265
|
+
const characters = [];
|
|
266
|
+
const sections = text.split(/^##\s+/m);
|
|
267
|
+
for (let i = 1; i < sections.length; i++) {
|
|
268
|
+
const section = sections[i];
|
|
269
|
+
const lines = section.split("\n");
|
|
270
|
+
const name = lines[0].trim();
|
|
271
|
+
const character = {
|
|
272
|
+
name,
|
|
273
|
+
prompts: []
|
|
274
|
+
};
|
|
275
|
+
const promptRegex = /###\s+(.*?)\s*\n\s*```(?:\w+)?\n([\s\S]*?)```/g;
|
|
276
|
+
let match;
|
|
277
|
+
while ((match = promptRegex.exec(section)) !== null) {
|
|
278
|
+
character.prompts.push({
|
|
279
|
+
title: match[1].trim(),
|
|
280
|
+
content: match[2].trim()
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
if (character.name) {
|
|
284
|
+
characters.push(character);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return characters;
|
|
288
|
+
}
|
|
289
|
+
function formatTreeToMarkdown(nodes, edges) {
|
|
290
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
291
|
+
nodes.forEach((n) => nodeMap.set(n.id, { ...n, children: [] }));
|
|
292
|
+
edges.forEach((e) => {
|
|
293
|
+
const parent = nodeMap.get(e.source);
|
|
294
|
+
if (parent) parent.children.push(e.target);
|
|
295
|
+
});
|
|
296
|
+
const roots = nodes.filter((n) => !edges.some((e) => e.target === n.id));
|
|
297
|
+
const processNode = (id, depth = 0) => {
|
|
298
|
+
const node = nodeMap.get(id);
|
|
299
|
+
if (!node) return "";
|
|
300
|
+
const indent = " ".repeat(depth);
|
|
301
|
+
let md = `${indent}- ${node.data.label || "Unbenannt"}
|
|
302
|
+
`;
|
|
303
|
+
if (node.children) {
|
|
304
|
+
node.children.forEach((childId) => {
|
|
305
|
+
md += processNode(childId, depth + 1);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return md;
|
|
309
|
+
};
|
|
310
|
+
return roots.map((root) => processNode(root.id)).join("\n");
|
|
311
|
+
}
|
|
312
|
+
|
|
18
313
|
// src/components/Badge.tsx
|
|
19
314
|
import { jsx } from "react/jsx-runtime";
|
|
20
315
|
function FaToolsBadge() {
|
|
@@ -266,7 +561,7 @@ var SetupPanel = ({ onWorkspaceImport, onProjectExport, onProjectImport, project
|
|
|
266
561
|
// src/components/MediaLibrary.tsx
|
|
267
562
|
import { motion as motion4 } from "motion/react";
|
|
268
563
|
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
269
|
-
var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, onBatchDownload }) => {
|
|
564
|
+
var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, onBatchDownload, onGenerateReference }) => {
|
|
270
565
|
const selectedCount = items.filter((i) => i.selectedForExport).length;
|
|
271
566
|
return /* @__PURE__ */ jsxs6("div", { className: "flex flex-col h-full overflow-hidden", children: [
|
|
272
567
|
/* @__PURE__ */ jsxs6("div", { className: "flex flex-col p-4 border-b border-white/5 gap-3 bg-black/20", children: [
|
|
@@ -308,10 +603,16 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
|
|
|
308
603
|
}, children: item.selectedForExport && /* @__PURE__ */ jsx8("span", { className: "material-symbols-outlined text-[14px] text-white", children: "check" }) }),
|
|
309
604
|
/* @__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 pointer-events-none", children: /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between", children: [
|
|
310
605
|
/* @__PURE__ */ jsx8("span", { className: "text-[8px] font-bold text-white/80 truncate max-w-[80px] uppercase tracking-tighter", children: item.prompt || "Importiert" }),
|
|
311
|
-
/* @__PURE__ */
|
|
312
|
-
e
|
|
313
|
-
|
|
314
|
-
|
|
606
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1", children: [
|
|
607
|
+
onGenerateReference && /* @__PURE__ */ jsx8("button", { type: "button", onClick: (e) => {
|
|
608
|
+
e.stopPropagation();
|
|
609
|
+
onGenerateReference(item);
|
|
610
|
+
}, className: "w-6 h-6 flex items-center justify-center bg-blue-500/20 text-blue-400 hover:bg-blue-500 rounded-md transition-all hover:text-white pointer-events-auto", children: /* @__PURE__ */ jsx8("span", { className: "material-symbols-outlined text-[14px]", children: "auto_fix_high" }) }),
|
|
611
|
+
/* @__PURE__ */ jsx8("button", { type: "button", onClick: (e) => {
|
|
612
|
+
e.stopPropagation();
|
|
613
|
+
onDelete?.(item.id);
|
|
614
|
+
}, 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 pointer-events-auto", children: /* @__PURE__ */ jsx8("span", { className: "material-symbols-outlined text-[14px]", children: "delete" }) })
|
|
615
|
+
] })
|
|
315
616
|
] }) }),
|
|
316
617
|
/* @__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" })
|
|
317
618
|
] }, item.id)) }) })
|
|
@@ -319,7 +620,7 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
|
|
|
319
620
|
};
|
|
320
621
|
|
|
321
622
|
// src/components/ListView.tsx
|
|
322
|
-
import { useEffect as
|
|
623
|
+
import { useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
|
|
323
624
|
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
324
625
|
var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, isActive, isInPath, onFocus, onGenerate, onGenerateBranch, onGenerateSubtree, isGenerating, isCollapsed, toggleCollapse, renderNode, children }) => {
|
|
325
626
|
const inputRef = useRef3(null);
|
|
@@ -350,7 +651,7 @@ var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNod
|
|
|
350
651
|
onMoveNode(node.id, "down");
|
|
351
652
|
}
|
|
352
653
|
};
|
|
353
|
-
|
|
654
|
+
useEffect3(() => {
|
|
354
655
|
if (isActive && inputRef.current) inputRef.current.focus();
|
|
355
656
|
}, [isActive]);
|
|
356
657
|
return /* @__PURE__ */ jsxs7("div", { className: "flex flex-col w-full", children: [
|
|
@@ -413,6 +714,7 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
|
|
|
413
714
|
export {
|
|
414
715
|
CompactDropdown,
|
|
415
716
|
FaToolsBadge,
|
|
717
|
+
GLOBAL_STYLES,
|
|
416
718
|
HistoryPanel,
|
|
417
719
|
InspectPanel,
|
|
418
720
|
ListView,
|
|
@@ -420,5 +722,12 @@ export {
|
|
|
420
722
|
PillButton,
|
|
421
723
|
SectionLabel,
|
|
422
724
|
SetupPanel,
|
|
725
|
+
exportProjectToZip,
|
|
726
|
+
formatTreeToMarkdown,
|
|
727
|
+
getFormattedTimestamp,
|
|
728
|
+
importProjectFromZip,
|
|
729
|
+
injectXMPMetadata,
|
|
730
|
+
parsePromptFile,
|
|
731
|
+
useKeyboardNavigation,
|
|
423
732
|
useOnClickOutside
|
|
424
733
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rslsp1/fa-app-tools",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "Shared tools and hooks for Fine Art flow apps",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"react": ">=18"
|
|
24
24
|
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"jszip": "^3.10.1"
|
|
27
|
+
},
|
|
25
28
|
"devDependencies": {
|
|
26
29
|
"@types/react": "^18.0.0",
|
|
27
30
|
"@xyflow/react": "^12.10.2",
|