@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 CHANGED
@@ -122,7 +122,7 @@ interface InspectPanelProps {
122
122
  currentResult: Generation | null;
123
123
  history: Generation[];
124
124
  onSelect: (gen: Generation) => void;
125
- workspaceTags: WorkspaceTags | null;
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: (id: string) => void;
164
- onGenerateSubtree: (id: string) => void;
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
- 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 };
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: WorkspaceTags | null;
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: (id: string) => void;
164
- onGenerateSubtree: (id: string) => void;
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
- 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 };
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rslsp1/fa-app-tools",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "Shared tools and hooks for Fine Art flow apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",