@rslsp1/fa-app-tools 0.1.12 → 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 +21 -4
- package/dist/index.d.ts +21 -4
- package/dist/index.js +305 -0
- package/dist/index.mjs +304 -0
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -122,7 +122,7 @@ interface InspectPanelProps {
|
|
|
122
122
|
currentResult: Generation | null;
|
|
123
123
|
history: Generation[];
|
|
124
124
|
onSelect: (gen: Generation) => void;
|
|
125
|
-
workspaceTags
|
|
125
|
+
workspaceTags?: WorkspaceTags | null;
|
|
126
126
|
onTagToggle?: (tagName: string) => void;
|
|
127
127
|
}
|
|
128
128
|
declare const InspectPanel: React.FC<InspectPanelProps>;
|
|
@@ -160,10 +160,27 @@ interface ListViewProps {
|
|
|
160
160
|
onFocus: (id: string | null) => void;
|
|
161
161
|
activePath: Set<string>;
|
|
162
162
|
onGenerate: (id: string) => void;
|
|
163
|
-
onGenerateBranch
|
|
164
|
-
onGenerateSubtree
|
|
163
|
+
onGenerateBranch?: (id: string) => void;
|
|
164
|
+
onGenerateSubtree?: (id: string) => void;
|
|
165
165
|
isGeneratingNodeId: (id: string) => boolean;
|
|
166
166
|
}
|
|
167
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;
|
|
168
168
|
|
|
169
|
-
|
|
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
|
@@ -122,7 +122,7 @@ interface InspectPanelProps {
|
|
|
122
122
|
currentResult: Generation | null;
|
|
123
123
|
history: Generation[];
|
|
124
124
|
onSelect: (gen: Generation) => void;
|
|
125
|
-
workspaceTags
|
|
125
|
+
workspaceTags?: WorkspaceTags | null;
|
|
126
126
|
onTagToggle?: (tagName: string) => void;
|
|
127
127
|
}
|
|
128
128
|
declare const InspectPanel: React.FC<InspectPanelProps>;
|
|
@@ -160,10 +160,27 @@ interface ListViewProps {
|
|
|
160
160
|
onFocus: (id: string | null) => void;
|
|
161
161
|
activePath: Set<string>;
|
|
162
162
|
onGenerate: (id: string) => void;
|
|
163
|
-
onGenerateBranch
|
|
164
|
-
onGenerateSubtree
|
|
163
|
+
onGenerateBranch?: (id: string) => void;
|
|
164
|
+
onGenerateSubtree?: (id: string) => void;
|
|
165
165
|
isGeneratingNodeId: (id: string) => boolean;
|
|
166
166
|
}
|
|
167
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;
|
|
168
168
|
|
|
169
|
-
|
|
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,6 +30,7 @@ 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,
|
|
35
36
|
GLOBAL_STYLES: () => GLOBAL_STYLES,
|
|
@@ -764,8 +765,312 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
|
|
|
764
765
|
if (e.target === e.currentTarget) onFocus(null);
|
|
765
766
|
}, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "w-full flex flex-col", children: roots.map((root) => renderNode(root, 0)) }) });
|
|
766
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
|
+
}
|
|
767
1071
|
// Annotate the CommonJS export names for ESM import in node:
|
|
768
1072
|
0 && (module.exports = {
|
|
1073
|
+
AvatarArchitectApp,
|
|
769
1074
|
CompactDropdown,
|
|
770
1075
|
FaToolsBadge,
|
|
771
1076
|
GLOBAL_STYLES,
|
package/dist/index.mjs
CHANGED
|
@@ -711,7 +711,311 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
|
|
|
711
711
|
if (e.target === e.currentTarget) onFocus(null);
|
|
712
712
|
}, children: /* @__PURE__ */ jsx9("div", { className: "w-full flex flex-col", children: roots.map((root) => renderNode(root, 0)) }) });
|
|
713
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
|
+
}
|
|
714
1017
|
export {
|
|
1018
|
+
AvatarArchitectApp,
|
|
715
1019
|
CompactDropdown,
|
|
716
1020
|
FaToolsBadge,
|
|
717
1021
|
GLOBAL_STYLES,
|