@rslsp1/fa-app-tools 0.1.12 → 0.1.14

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
@@ -65,6 +65,12 @@ declare function exportProjectToZip(nodes: any[], edges: any[], history: any[],
65
65
  }>;
66
66
  declare function importProjectFromZip(file: File): Promise<any>;
67
67
 
68
+ declare function buildGenerationPrompt(hierarchyText: string, mode?: 'literal' | 'creative'): string;
69
+ declare function buildFallbackPrompt(hierarchyText: string): string;
70
+ declare function cleanAiResponse(text: string): string;
71
+ declare function buildImageGenerationOptions(prompt: string, aspectRatio: string, model: string, referenceMediaId?: string): Record<string, any>;
72
+ declare function interpretSdkError(err: any): Error;
73
+
68
74
  interface ExtractedCharacter {
69
75
  name: string;
70
76
  prompts: {
@@ -122,7 +128,7 @@ interface InspectPanelProps {
122
128
  currentResult: Generation | null;
123
129
  history: Generation[];
124
130
  onSelect: (gen: Generation) => void;
125
- workspaceTags: WorkspaceTags | null;
131
+ workspaceTags?: WorkspaceTags | null;
126
132
  onTagToggle?: (tagName: string) => void;
127
133
  }
128
134
  declare const InspectPanel: React.FC<InspectPanelProps>;
@@ -160,10 +166,27 @@ interface ListViewProps {
160
166
  onFocus: (id: string | null) => void;
161
167
  activePath: Set<string>;
162
168
  onGenerate: (id: string) => void;
163
- onGenerateBranch: (id: string) => void;
164
- onGenerateSubtree: (id: string) => void;
169
+ onGenerateBranch?: (id: string) => void;
170
+ onGenerateSubtree?: (id: string) => void;
165
171
  isGeneratingNodeId: (id: string) => boolean;
166
172
  }
167
173
  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
174
 
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 };
175
+ interface MediaItem {
176
+ base64: string;
177
+ mimeType: string;
178
+ mediaId?: string;
179
+ name?: string;
180
+ }
181
+ interface AvatarArchitectAppProps {
182
+ onGenerateImage: (prompt: string, aspectRatio: string, model: string, referenceId?: string) => Promise<{
183
+ base64: string;
184
+ mediaId?: string;
185
+ }>;
186
+ onGeneratePrompt: (treeText: string) => Promise<string>;
187
+ onDownload: (base64: string, mimeType: string, filename: string) => Promise<void>;
188
+ onSelectMedia: () => Promise<MediaItem[]>;
189
+ }
190
+ declare function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia }: AvatarArchitectAppProps): react_jsx_runtime.JSX.Element;
191
+
192
+ 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, buildFallbackPrompt, buildGenerationPrompt, buildImageGenerationOptions, cleanAiResponse, exportProjectToZip, formatTreeToMarkdown, getFormattedTimestamp, importProjectFromZip, injectXMPMetadata, interpretSdkError, parsePromptFile, useKeyboardNavigation, useOnClickOutside };
package/dist/index.d.ts CHANGED
@@ -65,6 +65,12 @@ declare function exportProjectToZip(nodes: any[], edges: any[], history: any[],
65
65
  }>;
66
66
  declare function importProjectFromZip(file: File): Promise<any>;
67
67
 
68
+ declare function buildGenerationPrompt(hierarchyText: string, mode?: 'literal' | 'creative'): string;
69
+ declare function buildFallbackPrompt(hierarchyText: string): string;
70
+ declare function cleanAiResponse(text: string): string;
71
+ declare function buildImageGenerationOptions(prompt: string, aspectRatio: string, model: string, referenceMediaId?: string): Record<string, any>;
72
+ declare function interpretSdkError(err: any): Error;
73
+
68
74
  interface ExtractedCharacter {
69
75
  name: string;
70
76
  prompts: {
@@ -122,7 +128,7 @@ interface InspectPanelProps {
122
128
  currentResult: Generation | null;
123
129
  history: Generation[];
124
130
  onSelect: (gen: Generation) => void;
125
- workspaceTags: WorkspaceTags | null;
131
+ workspaceTags?: WorkspaceTags | null;
126
132
  onTagToggle?: (tagName: string) => void;
127
133
  }
128
134
  declare const InspectPanel: React.FC<InspectPanelProps>;
@@ -160,10 +166,27 @@ interface ListViewProps {
160
166
  onFocus: (id: string | null) => void;
161
167
  activePath: Set<string>;
162
168
  onGenerate: (id: string) => void;
163
- onGenerateBranch: (id: string) => void;
164
- onGenerateSubtree: (id: string) => void;
169
+ onGenerateBranch?: (id: string) => void;
170
+ onGenerateSubtree?: (id: string) => void;
165
171
  isGeneratingNodeId: (id: string) => boolean;
166
172
  }
167
173
  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
174
 
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 };
175
+ interface MediaItem {
176
+ base64: string;
177
+ mimeType: string;
178
+ mediaId?: string;
179
+ name?: string;
180
+ }
181
+ interface AvatarArchitectAppProps {
182
+ onGenerateImage: (prompt: string, aspectRatio: string, model: string, referenceId?: string) => Promise<{
183
+ base64: string;
184
+ mediaId?: string;
185
+ }>;
186
+ onGeneratePrompt: (treeText: string) => Promise<string>;
187
+ onDownload: (base64: string, mimeType: string, filename: string) => Promise<void>;
188
+ onSelectMedia: () => Promise<MediaItem[]>;
189
+ }
190
+ declare function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia }: AvatarArchitectAppProps): react_jsx_runtime.JSX.Element;
191
+
192
+ 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, buildFallbackPrompt, buildGenerationPrompt, buildImageGenerationOptions, cleanAiResponse, exportProjectToZip, formatTreeToMarkdown, getFormattedTimestamp, importProjectFromZip, injectXMPMetadata, interpretSdkError, 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,
@@ -40,11 +41,16 @@ __export(index_exports, {
40
41
  PillButton: () => PillButton,
41
42
  SectionLabel: () => SectionLabel,
42
43
  SetupPanel: () => SetupPanel,
44
+ buildFallbackPrompt: () => buildFallbackPrompt,
45
+ buildGenerationPrompt: () => buildGenerationPrompt,
46
+ buildImageGenerationOptions: () => buildImageGenerationOptions,
47
+ cleanAiResponse: () => cleanAiResponse,
43
48
  exportProjectToZip: () => exportProjectToZip,
44
49
  formatTreeToMarkdown: () => formatTreeToMarkdown,
45
50
  getFormattedTimestamp: () => getFormattedTimestamp,
46
51
  importProjectFromZip: () => importProjectFromZip,
47
52
  injectXMPMetadata: () => injectXMPMetadata,
53
+ interpretSdkError: () => interpretSdkError,
48
54
  parsePromptFile: () => parsePromptFile,
49
55
  useKeyboardNavigation: () => useKeyboardNavigation,
50
56
  useOnClickOutside: () => useOnClickOutside
@@ -313,6 +319,50 @@ async function importProjectFromZip(file) {
313
319
  return data;
314
320
  }
315
321
 
322
+ // src/lib/aiHelpers.ts
323
+ function buildGenerationPrompt(hierarchyText, mode = "creative") {
324
+ const safe = hierarchyText.length > 2500 ? hierarchyText.slice(0, 2500) + "..." : hierarchyText;
325
+ if (mode === "literal") {
326
+ return `Translate this hierarchy into a literal image generation prompt. Output ONLY the raw prompt string. No chat.
327
+
328
+ HIERARCHY:
329
+ ${safe}`;
330
+ }
331
+ return `Create a cinematic, high-quality image generation prompt based on this hierarchy. Translate everything to English. Output ONLY the prompt string. No chat.
332
+
333
+ HIERARCHY:
334
+ ${safe}`;
335
+ }
336
+ function buildFallbackPrompt(hierarchyText) {
337
+ const lines = hierarchyText.split("\n");
338
+ const firstConcept = lines.find((l) => l.includes("-"))?.replace(/^[-\s]+/, "") || "character concept";
339
+ return `Cinematic professional photograph of ${firstConcept}, masterpiece, highly detailed, fine art style`;
340
+ }
341
+ function cleanAiResponse(text) {
342
+ return text.replace(/^["']|["']$/g, "").replace(/```/g, "").trim();
343
+ }
344
+ function buildImageGenerationOptions(prompt, aspectRatio, model, referenceMediaId) {
345
+ const options = {
346
+ prompt: prompt.replace(/\n/g, " ").trim().slice(0, 1500),
347
+ aspectRatio,
348
+ modelDisplayName: model
349
+ };
350
+ if (referenceMediaId && referenceMediaId.length > 5) {
351
+ options.referenceImageMediaIds = [referenceMediaId];
352
+ }
353
+ return options;
354
+ }
355
+ function interpretSdkError(err) {
356
+ const msg = err?.message || "Generierung fehlgeschlagen";
357
+ if (msg.includes("PUBLIC_ERROR_SOMETHING_WENT_WRONG")) {
358
+ return new Error("Plattform-Fehler: Die Anfrage wurde abgelehnt. M\xF6gliche Ursachen: Sicherheitsfilter (Prompt-Inhalt) oder tempor\xE4re \xDCberlastung. Versuche einen neutraleren Prompt.");
359
+ }
360
+ if (msg.includes("PUBLIC_ERROR")) {
361
+ return new Error(`Plattform-Fehler: ${msg}`);
362
+ }
363
+ return new Error(msg);
364
+ }
365
+
316
366
  // src/lib/parserService.ts
317
367
  function parsePromptFile(text) {
318
368
  const characters = [];
@@ -764,8 +814,312 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
764
814
  if (e.target === e.currentTarget) onFocus(null);
765
815
  }, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "w-full flex flex-col", children: roots.map((root) => renderNode(root, 0)) }) });
766
816
  }
817
+
818
+ // src/components/AvatarArchitectApp.tsx
819
+ var import_react10 = require("react");
820
+ var import_react11 = require("motion/react");
821
+ var import_jsx_runtime10 = require("react/jsx-runtime");
822
+ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia }) {
823
+ (0, import_react10.useEffect)(() => {
824
+ const id = "flow-styles";
825
+ if (!document.getElementById(id)) {
826
+ const style = document.createElement("style");
827
+ style.id = id;
828
+ style.textContent = GLOBAL_STYLES;
829
+ document.head.appendChild(style);
830
+ }
831
+ }, []);
832
+ const [nodes, setNodes] = (0, import_react10.useState)([{ id: "1", type: "custom", position: { x: 0, y: 0 }, data: { label: "Fine Art Project", placeholder: "Name..." } }]);
833
+ const [edges, setEdges] = (0, import_react10.useState)([]);
834
+ const [history, setHistory] = (0, import_react10.useState)([]);
835
+ const [galleryItems, setGalleryItems] = (0, import_react10.useState)([]);
836
+ const [activePrompt, setActivePrompt] = (0, import_react10.useState)("");
837
+ const [isSynthesizing, setIsSynthesizing] = (0, import_react10.useState)(false);
838
+ const [activeGenerationsCount, setActiveGenerationsCount] = (0, import_react10.useState)(0);
839
+ const [currentResult, setCurrentResult] = (0, import_react10.useState)(null);
840
+ const [focusedNodeId, setFocusedNodeId] = (0, import_react10.useState)(null);
841
+ const [activeTab, setActiveTab] = (0, import_react10.useState)("history");
842
+ const [aspectRatio, setAspectRatio] = (0, import_react10.useState)("1:1");
843
+ const [selectedModel, setSelectedModel] = (0, import_react10.useState)("\u{1F34C} Nano Banana Pro");
844
+ const [seed, setSeed] = (0, import_react10.useState)(Math.floor(Math.random() * 1e6));
845
+ const [seedMode, setSeedMode] = (0, import_react10.useState)("random");
846
+ const [isLeftCollapsed, setIsLeftCollapsed] = (0, import_react10.useState)(false);
847
+ const [isRightCollapsed, setIsRightCollapsed] = (0, import_react10.useState)(false);
848
+ const [isPromptCollapsed, setIsPromptCollapsed] = (0, import_react10.useState)(false);
849
+ const [projectActionState, setProjectActionState] = (0, import_react10.useState)("idle");
850
+ const isGenerating = activeGenerationsCount > 0;
851
+ useKeyboardNavigation(history, currentResult, setCurrentResult);
852
+ const getSubtreeFormat = (0, import_react10.useCallback)((nodeId, depth = 0) => {
853
+ const node = nodes.find((n) => n.id === nodeId);
854
+ if (!node) return "";
855
+ const childrenIds = edges.filter((e) => e.source === nodeId).map((e) => e.target);
856
+ const indent = " ".repeat(depth);
857
+ return `${indent}- ${node.data.label || "(unbenannt)"}
858
+ ` + childrenIds.map((id) => getSubtreeFormat(id, depth + 1)).join("");
859
+ }, [nodes, edges]);
860
+ const activePath = (0, import_react10.useMemo)(() => {
861
+ if (!focusedNodeId) return /* @__PURE__ */ new Set();
862
+ const path = /* @__PURE__ */ new Set([focusedNodeId]);
863
+ let currId = focusedNodeId;
864
+ const parentMap = /* @__PURE__ */ new Map();
865
+ edges.forEach((e) => parentMap.set(e.target, e.source));
866
+ while (parentMap.has(currId)) {
867
+ currId = parentMap.get(currId);
868
+ path.add(currId);
869
+ }
870
+ return path;
871
+ }, [focusedNodeId, edges]);
872
+ const handleGenerateImage = async (customPrompt, useReferenceId, overrideNodeId, options = { silent: false }) => {
873
+ const promptToUse = (customPrompt || activePrompt).trim();
874
+ if (!promptToUse) return;
875
+ setActiveGenerationsCount((prev) => prev + 1);
876
+ const genId = crypto.randomUUID();
877
+ const activeSeed = seedMode === "random" ? Math.floor(Math.random() * 1e9) : seed;
878
+ const newGen = {
879
+ id: genId,
880
+ nodeId: overrideNodeId || focusedNodeId || "1",
881
+ status: "processing",
882
+ timestamp: Date.now(),
883
+ prompt: promptToUse,
884
+ seed: activeSeed,
885
+ model: selectedModel,
886
+ tags: [],
887
+ type: "generation"
888
+ };
889
+ setHistory((prev) => [newGen, ...prev]);
890
+ if (!options.silent) setCurrentResult(newGen);
891
+ if (seedMode === "random") setSeed(activeSeed);
892
+ try {
893
+ const { base64, mediaId } = await onGenerateImage(promptToUse, aspectRatio, selectedModel, useReferenceId);
894
+ const finishedGen = { ...newGen, status: "done", base64: `data:image/png;base64,${base64}`, mediaId };
895
+ setHistory((prev) => prev.map((g) => g.id === genId ? finishedGen : g));
896
+ setGalleryItems((prev) => [finishedGen, ...prev]);
897
+ setCurrentResult((prev) => {
898
+ if (!prev) return finishedGen;
899
+ if (prev.id === genId || !options.silent) return finishedGen;
900
+ return prev;
901
+ });
902
+ } catch (err) {
903
+ const errorGen = { ...newGen, status: "error", error: { message: err.message || "Unbekannter Fehler", details: err.toString() } };
904
+ setHistory((prev) => prev.map((g) => g.id === genId ? errorGen : g));
905
+ if (!options.silent || currentResult?.id === genId) setCurrentResult(errorGen);
906
+ } finally {
907
+ setActiveGenerationsCount((prev) => Math.max(0, prev - 1));
908
+ }
909
+ };
910
+ const handleSynthesizePrompt = async (nodeId, autoGenerate = false) => {
911
+ setIsSynthesizing(true);
912
+ try {
913
+ const prompt = await onGeneratePrompt(getSubtreeFormat(nodeId));
914
+ setActivePrompt(prompt);
915
+ setFocusedNodeId(nodeId);
916
+ if (autoGenerate) handleGenerateImage(prompt, void 0, nodeId);
917
+ } catch {
918
+ setActivePrompt("Synthese-Fehler");
919
+ } finally {
920
+ setIsSynthesizing(false);
921
+ }
922
+ };
923
+ const handleDownloadSingle = async () => {
924
+ if (!currentResult?.base64) return;
925
+ try {
926
+ const base64Data = currentResult.base64.split(",")[1];
927
+ const mimeType = currentResult.base64.split(";")[0].split(":")[1] || "image/png";
928
+ let finalBase64 = base64Data;
929
+ if (mimeType === "image/png") {
930
+ finalBase64 = injectXMPMetadata(base64Data, currentResult.prompt || "", currentResult.seed, currentResult.model, currentResult.id, currentResult.tags || [], getSubtreeFormat(currentResult.nodeId));
931
+ }
932
+ await onDownload(finalBase64, mimeType, `avatar_${currentResult.id.slice(0, 5)}.${mimeType.split("/")[1]}`);
933
+ } catch (e) {
934
+ console.error("Download Error", e);
935
+ }
936
+ };
937
+ const handleProjectExport = async () => {
938
+ setProjectActionState("working-full");
939
+ try {
940
+ const { base64 } = await exportProjectToZip(nodes, edges, history, galleryItems, { aspectRatio, selectedModel, seed, seedMode });
941
+ await onDownload(base64, "application/zip", `avatar_project_${getFormattedTimestamp()}.zip`);
942
+ setProjectActionState("done");
943
+ setTimeout(() => setProjectActionState("idle"), 3e3);
944
+ } catch {
945
+ setProjectActionState("error");
946
+ }
947
+ };
948
+ const handleProjectImport = async (input) => {
949
+ const file = input instanceof FileList ? input[0] : input;
950
+ if (!file) return;
951
+ setProjectActionState("working-full");
952
+ try {
953
+ const data = await importProjectFromZip(file);
954
+ if (data.nodes) setNodes(data.nodes);
955
+ if (data.edges) setEdges(data.edges);
956
+ if (data.history) setHistory(data.history);
957
+ if (data.galleryItems) setGalleryItems(data.galleryItems);
958
+ if (data.settings) {
959
+ setAspectRatio(data.settings.aspectRatio || "1:1");
960
+ setSelectedModel(data.settings.selectedModel || "\u{1F34C} Nano Banana Pro");
961
+ setSeed(data.settings.seed || 0);
962
+ setSeedMode(data.settings.seedMode || "random");
963
+ }
964
+ setProjectActionState("done");
965
+ setTimeout(() => setProjectActionState("idle"), 2e3);
966
+ } catch {
967
+ setProjectActionState("error");
968
+ setTimeout(() => setProjectActionState("idle"), 4e3);
969
+ }
970
+ };
971
+ const handleWorkspaceImport = (file) => {
972
+ const reader = new FileReader();
973
+ reader.onload = (ev) => {
974
+ try {
975
+ const root = JSON.parse(ev.target?.result);
976
+ const rawTags = root.tags || root;
977
+ if (!rawTags?.by_category) return;
978
+ } catch (err) {
979
+ console.error("Workspace Load failed", err);
980
+ }
981
+ };
982
+ reader.readAsText(file);
983
+ };
984
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex h-screen w-screen bg-[#0e0e0e] text-white overflow-hidden", children: [
985
+ /* @__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: [
986
+ /* @__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: [
987
+ !isLeftCollapsed && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[10px] font-bold uppercase text-white/40", children: "Hierarchie" }),
988
+ /* @__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" })
989
+ ] }),
990
+ !isLeftCollapsed && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
991
+ ListView,
992
+ {
993
+ nodes,
994
+ edges,
995
+ onNodeChange: (id, l) => setNodes((nds) => nds.map((n) => n.id === id ? { ...n, data: { ...n.data, label: l } } : n)),
996
+ onAddChild: (pId) => {
997
+ const newId = crypto.randomUUID();
998
+ setNodes((nds) => [...nds, { id: newId, type: "custom", position: { x: 0, y: 0 }, data: { label: "", placeholder: "Konzept..." } }]);
999
+ setEdges((eds) => [...eds, { id: `e-${pId}-${newId}`, source: pId, target: newId }]);
1000
+ setFocusedNodeId(newId);
1001
+ return newId;
1002
+ },
1003
+ onDeleteNode: (id) => {
1004
+ setNodes((nds) => nds.filter((n) => n.id !== id));
1005
+ setEdges((eds) => eds.filter((e) => e.source !== id && e.target !== id));
1006
+ },
1007
+ onFocus: setFocusedNodeId,
1008
+ focusedNodeId,
1009
+ activePath,
1010
+ onGenerate: handleSynthesizePrompt,
1011
+ onGenerateBranch: (id) => handleSynthesizePrompt(id, true),
1012
+ isGeneratingNodeId: (id) => isSynthesizing && focusedNodeId === id
1013
+ }
1014
+ )
1015
+ ] }),
1016
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex-1 flex flex-col bg-[#0b0b0b] overflow-hidden", children: [
1017
+ /* @__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: [
1018
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-1.5", children: [
1019
+ /* @__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" }] }),
1020
+ /* @__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" }] })
1021
+ ] }),
1022
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex items-center gap-2", children: [
1023
+ /* @__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" }) }),
1024
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PillButton, { variant: "solid", icon: "bolt", loading: isGenerating, disabled: !activePrompt.trim(), onClick: () => handleGenerateImage(), children: "Generieren" })
1025
+ ] })
1026
+ ] }),
1027
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex-1 flex flex-col overflow-hidden relative", children: [
1028
+ /* @__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: [
1029
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1030
+ "textarea",
1031
+ {
1032
+ value: activePrompt,
1033
+ onChange: (e) => setActivePrompt(e.target.value),
1034
+ className: "w-full bg-transparent border-none outline-none text-[12px] leading-relaxed text-white/80 resize-none h-20 dark-scrollbar",
1035
+ placeholder: "W\xE4hle einen Knoten oder tippe einen Prompt..."
1036
+ }
1037
+ ),
1038
+ 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" }) })
1039
+ ] }) }) }),
1040
+ /* @__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: [
1041
+ 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: [
1042
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "w-3 h-3 border-t-2 border-white rounded-full animate-spin" }),
1043
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[10px] text-white/60 uppercase font-bold tracking-widest", children: "Neue Referenz..." })
1044
+ ] }),
1045
+ currentResult ? currentResult.status === "processing" ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col items-center gap-4", children: [
1046
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "w-10 h-10 border-t-2 border-white rounded-full animate-spin" }),
1047
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[10px] text-white/40 uppercase font-bold tracking-widest", children: "Erstelle Bild..." })
1048
+ ] }) : 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: [
1049
+ /* @__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" }) }),
1050
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col gap-2", children: [
1051
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { className: "text-[11px] font-bold uppercase tracking-widest text-red-400", children: "Generierungsfehler" }),
1052
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-white/60 text-[12px] leading-relaxed", children: currentResult.error?.message })
1053
+ ] }),
1054
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PillButton, { variant: "outline", icon: "refresh", onClick: () => handleGenerateImage(currentResult.prompt), children: "Erneut versuchen" })
1055
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "h-full w-full relative flex items-center justify-center", children: [
1056
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("img", { src: currentResult.base64, className: "max-h-full max-w-full object-contain rounded-xl shadow-2xl" }),
1057
+ /* @__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: [
1058
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PillButton, { variant: "outline", icon: "replay", onClick: () => setActivePrompt(currentResult.prompt || ""), children: "Prompt" }),
1059
+ /* @__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" }),
1060
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PillButton, { variant: "outline", icon: "download", onClick: handleDownloadSingle, children: "Speichern" })
1061
+ ] })
1062
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex flex-col items-center gap-2 opacity-10", children: [
1063
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "material-symbols-outlined text-[100px]", children: "palette" }),
1064
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-[12px] font-bold uppercase tracking-[0.2em]", children: "Avatar Architect" })
1065
+ ] })
1066
+ ] }) })
1067
+ ] })
1068
+ ] }),
1069
+ /* @__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: [
1070
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex border-b border-white/5 h-14 shrink-0 overflow-hidden", children: [
1071
+ /* @__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: () => {
1072
+ setActiveTab(tab);
1073
+ setIsRightCollapsed(false);
1074
+ }, 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)) }),
1075
+ /* @__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" }) })
1076
+ ] }),
1077
+ !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: [
1078
+ 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"),
1079
+ activeTab === "gallery" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1080
+ MediaLibrary,
1081
+ {
1082
+ items: galleryItems,
1083
+ onImport: async () => {
1084
+ const media = await onSelectMedia();
1085
+ if (!media?.length) return;
1086
+ const newItems = media.map((m) => ({
1087
+ id: crypto.randomUUID(),
1088
+ nodeId: "1",
1089
+ base64: `data:${m.mimeType};base64,${m.base64}`,
1090
+ mediaId: m.mediaId,
1091
+ prompt: m.name || "Importiert",
1092
+ timestamp: Date.now(),
1093
+ status: "done",
1094
+ tags: [],
1095
+ type: "import"
1096
+ }));
1097
+ setGalleryItems((prev) => [...newItems, ...prev]);
1098
+ },
1099
+ onDelete: (id) => setGalleryItems((g) => g.filter((x) => x.id !== id)),
1100
+ onSelect: setCurrentResult,
1101
+ onGenerateReference: (item) => handleGenerateImage(item.prompt || activePrompt, item.mediaId, void 0, { silent: true })
1102
+ },
1103
+ "gallery"
1104
+ ),
1105
+ activeTab === "inspect" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(InspectPanel, { currentResult, history, onSelect: setCurrentResult }, "inspect"),
1106
+ activeTab === "setup" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1107
+ SetupPanel,
1108
+ {
1109
+ onProjectExport: handleProjectExport,
1110
+ onProjectImport: handleProjectImport,
1111
+ onWorkspaceImport: handleWorkspaceImport,
1112
+ projectActionState
1113
+ },
1114
+ "setup"
1115
+ )
1116
+ ] }) })
1117
+ ] })
1118
+ ] });
1119
+ }
767
1120
  // Annotate the CommonJS export names for ESM import in node:
768
1121
  0 && (module.exports = {
1122
+ AvatarArchitectApp,
769
1123
  CompactDropdown,
770
1124
  FaToolsBadge,
771
1125
  GLOBAL_STYLES,
@@ -776,11 +1130,16 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
776
1130
  PillButton,
777
1131
  SectionLabel,
778
1132
  SetupPanel,
1133
+ buildFallbackPrompt,
1134
+ buildGenerationPrompt,
1135
+ buildImageGenerationOptions,
1136
+ cleanAiResponse,
779
1137
  exportProjectToZip,
780
1138
  formatTreeToMarkdown,
781
1139
  getFormattedTimestamp,
782
1140
  importProjectFromZip,
783
1141
  injectXMPMetadata,
1142
+ interpretSdkError,
784
1143
  parsePromptFile,
785
1144
  useKeyboardNavigation,
786
1145
  useOnClickOutside
package/dist/index.mjs CHANGED
@@ -260,6 +260,50 @@ async function importProjectFromZip(file) {
260
260
  return data;
261
261
  }
262
262
 
263
+ // src/lib/aiHelpers.ts
264
+ function buildGenerationPrompt(hierarchyText, mode = "creative") {
265
+ const safe = hierarchyText.length > 2500 ? hierarchyText.slice(0, 2500) + "..." : hierarchyText;
266
+ if (mode === "literal") {
267
+ return `Translate this hierarchy into a literal image generation prompt. Output ONLY the raw prompt string. No chat.
268
+
269
+ HIERARCHY:
270
+ ${safe}`;
271
+ }
272
+ return `Create a cinematic, high-quality image generation prompt based on this hierarchy. Translate everything to English. Output ONLY the prompt string. No chat.
273
+
274
+ HIERARCHY:
275
+ ${safe}`;
276
+ }
277
+ function buildFallbackPrompt(hierarchyText) {
278
+ const lines = hierarchyText.split("\n");
279
+ const firstConcept = lines.find((l) => l.includes("-"))?.replace(/^[-\s]+/, "") || "character concept";
280
+ return `Cinematic professional photograph of ${firstConcept}, masterpiece, highly detailed, fine art style`;
281
+ }
282
+ function cleanAiResponse(text) {
283
+ return text.replace(/^["']|["']$/g, "").replace(/```/g, "").trim();
284
+ }
285
+ function buildImageGenerationOptions(prompt, aspectRatio, model, referenceMediaId) {
286
+ const options = {
287
+ prompt: prompt.replace(/\n/g, " ").trim().slice(0, 1500),
288
+ aspectRatio,
289
+ modelDisplayName: model
290
+ };
291
+ if (referenceMediaId && referenceMediaId.length > 5) {
292
+ options.referenceImageMediaIds = [referenceMediaId];
293
+ }
294
+ return options;
295
+ }
296
+ function interpretSdkError(err) {
297
+ const msg = err?.message || "Generierung fehlgeschlagen";
298
+ if (msg.includes("PUBLIC_ERROR_SOMETHING_WENT_WRONG")) {
299
+ return new Error("Plattform-Fehler: Die Anfrage wurde abgelehnt. M\xF6gliche Ursachen: Sicherheitsfilter (Prompt-Inhalt) oder tempor\xE4re \xDCberlastung. Versuche einen neutraleren Prompt.");
300
+ }
301
+ if (msg.includes("PUBLIC_ERROR")) {
302
+ return new Error(`Plattform-Fehler: ${msg}`);
303
+ }
304
+ return new Error(msg);
305
+ }
306
+
263
307
  // src/lib/parserService.ts
264
308
  function parsePromptFile(text) {
265
309
  const characters = [];
@@ -711,7 +755,311 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
711
755
  if (e.target === e.currentTarget) onFocus(null);
712
756
  }, children: /* @__PURE__ */ jsx9("div", { className: "w-full flex flex-col", children: roots.map((root) => renderNode(root, 0)) }) });
713
757
  }
758
+
759
+ // src/components/AvatarArchitectApp.tsx
760
+ import { useState as useState3, useCallback, useMemo, useEffect as useEffect4 } from "react";
761
+ import { motion as motion5, AnimatePresence } from "motion/react";
762
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
763
+ function AvatarArchitectApp({ onGenerateImage, onGeneratePrompt, onDownload, onSelectMedia }) {
764
+ useEffect4(() => {
765
+ const id = "flow-styles";
766
+ if (!document.getElementById(id)) {
767
+ const style = document.createElement("style");
768
+ style.id = id;
769
+ style.textContent = GLOBAL_STYLES;
770
+ document.head.appendChild(style);
771
+ }
772
+ }, []);
773
+ const [nodes, setNodes] = useState3([{ id: "1", type: "custom", position: { x: 0, y: 0 }, data: { label: "Fine Art Project", placeholder: "Name..." } }]);
774
+ const [edges, setEdges] = useState3([]);
775
+ const [history, setHistory] = useState3([]);
776
+ const [galleryItems, setGalleryItems] = useState3([]);
777
+ const [activePrompt, setActivePrompt] = useState3("");
778
+ const [isSynthesizing, setIsSynthesizing] = useState3(false);
779
+ const [activeGenerationsCount, setActiveGenerationsCount] = useState3(0);
780
+ const [currentResult, setCurrentResult] = useState3(null);
781
+ const [focusedNodeId, setFocusedNodeId] = useState3(null);
782
+ const [activeTab, setActiveTab] = useState3("history");
783
+ const [aspectRatio, setAspectRatio] = useState3("1:1");
784
+ const [selectedModel, setSelectedModel] = useState3("\u{1F34C} Nano Banana Pro");
785
+ const [seed, setSeed] = useState3(Math.floor(Math.random() * 1e6));
786
+ const [seedMode, setSeedMode] = useState3("random");
787
+ const [isLeftCollapsed, setIsLeftCollapsed] = useState3(false);
788
+ const [isRightCollapsed, setIsRightCollapsed] = useState3(false);
789
+ const [isPromptCollapsed, setIsPromptCollapsed] = useState3(false);
790
+ const [projectActionState, setProjectActionState] = useState3("idle");
791
+ const isGenerating = activeGenerationsCount > 0;
792
+ useKeyboardNavigation(history, currentResult, setCurrentResult);
793
+ const getSubtreeFormat = useCallback((nodeId, depth = 0) => {
794
+ const node = nodes.find((n) => n.id === nodeId);
795
+ if (!node) return "";
796
+ const childrenIds = edges.filter((e) => e.source === nodeId).map((e) => e.target);
797
+ const indent = " ".repeat(depth);
798
+ return `${indent}- ${node.data.label || "(unbenannt)"}
799
+ ` + childrenIds.map((id) => getSubtreeFormat(id, depth + 1)).join("");
800
+ }, [nodes, edges]);
801
+ const activePath = useMemo(() => {
802
+ if (!focusedNodeId) return /* @__PURE__ */ new Set();
803
+ const path = /* @__PURE__ */ new Set([focusedNodeId]);
804
+ let currId = focusedNodeId;
805
+ const parentMap = /* @__PURE__ */ new Map();
806
+ edges.forEach((e) => parentMap.set(e.target, e.source));
807
+ while (parentMap.has(currId)) {
808
+ currId = parentMap.get(currId);
809
+ path.add(currId);
810
+ }
811
+ return path;
812
+ }, [focusedNodeId, edges]);
813
+ const handleGenerateImage = async (customPrompt, useReferenceId, overrideNodeId, options = { silent: false }) => {
814
+ const promptToUse = (customPrompt || activePrompt).trim();
815
+ if (!promptToUse) return;
816
+ setActiveGenerationsCount((prev) => prev + 1);
817
+ const genId = crypto.randomUUID();
818
+ const activeSeed = seedMode === "random" ? Math.floor(Math.random() * 1e9) : seed;
819
+ const newGen = {
820
+ id: genId,
821
+ nodeId: overrideNodeId || focusedNodeId || "1",
822
+ status: "processing",
823
+ timestamp: Date.now(),
824
+ prompt: promptToUse,
825
+ seed: activeSeed,
826
+ model: selectedModel,
827
+ tags: [],
828
+ type: "generation"
829
+ };
830
+ setHistory((prev) => [newGen, ...prev]);
831
+ if (!options.silent) setCurrentResult(newGen);
832
+ if (seedMode === "random") setSeed(activeSeed);
833
+ try {
834
+ const { base64, mediaId } = await onGenerateImage(promptToUse, aspectRatio, selectedModel, useReferenceId);
835
+ const finishedGen = { ...newGen, status: "done", base64: `data:image/png;base64,${base64}`, mediaId };
836
+ setHistory((prev) => prev.map((g) => g.id === genId ? finishedGen : g));
837
+ setGalleryItems((prev) => [finishedGen, ...prev]);
838
+ setCurrentResult((prev) => {
839
+ if (!prev) return finishedGen;
840
+ if (prev.id === genId || !options.silent) return finishedGen;
841
+ return prev;
842
+ });
843
+ } catch (err) {
844
+ const errorGen = { ...newGen, status: "error", error: { message: err.message || "Unbekannter Fehler", details: err.toString() } };
845
+ setHistory((prev) => prev.map((g) => g.id === genId ? errorGen : g));
846
+ if (!options.silent || currentResult?.id === genId) setCurrentResult(errorGen);
847
+ } finally {
848
+ setActiveGenerationsCount((prev) => Math.max(0, prev - 1));
849
+ }
850
+ };
851
+ const handleSynthesizePrompt = async (nodeId, autoGenerate = false) => {
852
+ setIsSynthesizing(true);
853
+ try {
854
+ const prompt = await onGeneratePrompt(getSubtreeFormat(nodeId));
855
+ setActivePrompt(prompt);
856
+ setFocusedNodeId(nodeId);
857
+ if (autoGenerate) handleGenerateImage(prompt, void 0, nodeId);
858
+ } catch {
859
+ setActivePrompt("Synthese-Fehler");
860
+ } finally {
861
+ setIsSynthesizing(false);
862
+ }
863
+ };
864
+ const handleDownloadSingle = async () => {
865
+ if (!currentResult?.base64) return;
866
+ try {
867
+ const base64Data = currentResult.base64.split(",")[1];
868
+ const mimeType = currentResult.base64.split(";")[0].split(":")[1] || "image/png";
869
+ let finalBase64 = base64Data;
870
+ if (mimeType === "image/png") {
871
+ finalBase64 = injectXMPMetadata(base64Data, currentResult.prompt || "", currentResult.seed, currentResult.model, currentResult.id, currentResult.tags || [], getSubtreeFormat(currentResult.nodeId));
872
+ }
873
+ await onDownload(finalBase64, mimeType, `avatar_${currentResult.id.slice(0, 5)}.${mimeType.split("/")[1]}`);
874
+ } catch (e) {
875
+ console.error("Download Error", e);
876
+ }
877
+ };
878
+ const handleProjectExport = async () => {
879
+ setProjectActionState("working-full");
880
+ try {
881
+ const { base64 } = await exportProjectToZip(nodes, edges, history, galleryItems, { aspectRatio, selectedModel, seed, seedMode });
882
+ await onDownload(base64, "application/zip", `avatar_project_${getFormattedTimestamp()}.zip`);
883
+ setProjectActionState("done");
884
+ setTimeout(() => setProjectActionState("idle"), 3e3);
885
+ } catch {
886
+ setProjectActionState("error");
887
+ }
888
+ };
889
+ const handleProjectImport = async (input) => {
890
+ const file = input instanceof FileList ? input[0] : input;
891
+ if (!file) return;
892
+ setProjectActionState("working-full");
893
+ try {
894
+ const data = await importProjectFromZip(file);
895
+ if (data.nodes) setNodes(data.nodes);
896
+ if (data.edges) setEdges(data.edges);
897
+ if (data.history) setHistory(data.history);
898
+ if (data.galleryItems) setGalleryItems(data.galleryItems);
899
+ if (data.settings) {
900
+ setAspectRatio(data.settings.aspectRatio || "1:1");
901
+ setSelectedModel(data.settings.selectedModel || "\u{1F34C} Nano Banana Pro");
902
+ setSeed(data.settings.seed || 0);
903
+ setSeedMode(data.settings.seedMode || "random");
904
+ }
905
+ setProjectActionState("done");
906
+ setTimeout(() => setProjectActionState("idle"), 2e3);
907
+ } catch {
908
+ setProjectActionState("error");
909
+ setTimeout(() => setProjectActionState("idle"), 4e3);
910
+ }
911
+ };
912
+ const handleWorkspaceImport = (file) => {
913
+ const reader = new FileReader();
914
+ reader.onload = (ev) => {
915
+ try {
916
+ const root = JSON.parse(ev.target?.result);
917
+ const rawTags = root.tags || root;
918
+ if (!rawTags?.by_category) return;
919
+ } catch (err) {
920
+ console.error("Workspace Load failed", err);
921
+ }
922
+ };
923
+ reader.readAsText(file);
924
+ };
925
+ return /* @__PURE__ */ jsxs8("div", { className: "flex h-screen w-screen bg-[#0e0e0e] text-white overflow-hidden", children: [
926
+ /* @__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: [
927
+ /* @__PURE__ */ jsxs8("div", { className: "h-14 px-4 border-b border-white/5 flex items-center justify-between shrink-0", children: [
928
+ !isLeftCollapsed && /* @__PURE__ */ jsx10("span", { className: "text-[10px] font-bold uppercase text-white/40", children: "Hierarchie" }),
929
+ /* @__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" })
930
+ ] }),
931
+ !isLeftCollapsed && /* @__PURE__ */ jsx10(
932
+ ListView,
933
+ {
934
+ nodes,
935
+ edges,
936
+ onNodeChange: (id, l) => setNodes((nds) => nds.map((n) => n.id === id ? { ...n, data: { ...n.data, label: l } } : n)),
937
+ onAddChild: (pId) => {
938
+ const newId = crypto.randomUUID();
939
+ setNodes((nds) => [...nds, { id: newId, type: "custom", position: { x: 0, y: 0 }, data: { label: "", placeholder: "Konzept..." } }]);
940
+ setEdges((eds) => [...eds, { id: `e-${pId}-${newId}`, source: pId, target: newId }]);
941
+ setFocusedNodeId(newId);
942
+ return newId;
943
+ },
944
+ onDeleteNode: (id) => {
945
+ setNodes((nds) => nds.filter((n) => n.id !== id));
946
+ setEdges((eds) => eds.filter((e) => e.source !== id && e.target !== id));
947
+ },
948
+ onFocus: setFocusedNodeId,
949
+ focusedNodeId,
950
+ activePath,
951
+ onGenerate: handleSynthesizePrompt,
952
+ onGenerateBranch: (id) => handleSynthesizePrompt(id, true),
953
+ isGeneratingNodeId: (id) => isSynthesizing && focusedNodeId === id
954
+ }
955
+ )
956
+ ] }),
957
+ /* @__PURE__ */ jsxs8("div", { className: "flex-1 flex flex-col bg-[#0b0b0b] overflow-hidden", children: [
958
+ /* @__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: [
959
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1.5", children: [
960
+ /* @__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" }] }),
961
+ /* @__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" }] })
962
+ ] }),
963
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-2", children: [
964
+ /* @__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" }) }),
965
+ /* @__PURE__ */ jsx10(PillButton, { variant: "solid", icon: "bolt", loading: isGenerating, disabled: !activePrompt.trim(), onClick: () => handleGenerateImage(), children: "Generieren" })
966
+ ] })
967
+ ] }),
968
+ /* @__PURE__ */ jsxs8("div", { className: "flex-1 flex flex-col overflow-hidden relative", children: [
969
+ /* @__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: [
970
+ /* @__PURE__ */ jsx10(
971
+ "textarea",
972
+ {
973
+ value: activePrompt,
974
+ onChange: (e) => setActivePrompt(e.target.value),
975
+ className: "w-full bg-transparent border-none outline-none text-[12px] leading-relaxed text-white/80 resize-none h-20 dark-scrollbar",
976
+ placeholder: "W\xE4hle einen Knoten oder tippe einen Prompt..."
977
+ }
978
+ ),
979
+ 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" }) })
980
+ ] }) }) }),
981
+ /* @__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: [
982
+ 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: [
983
+ /* @__PURE__ */ jsx10("div", { className: "w-3 h-3 border-t-2 border-white rounded-full animate-spin" }),
984
+ /* @__PURE__ */ jsx10("span", { className: "text-[10px] text-white/60 uppercase font-bold tracking-widest", children: "Neue Referenz..." })
985
+ ] }),
986
+ currentResult ? currentResult.status === "processing" ? /* @__PURE__ */ jsxs8("div", { className: "flex flex-col items-center gap-4", children: [
987
+ /* @__PURE__ */ jsx10("div", { className: "w-10 h-10 border-t-2 border-white rounded-full animate-spin" }),
988
+ /* @__PURE__ */ jsx10("span", { className: "text-[10px] text-white/40 uppercase font-bold tracking-widest", children: "Erstelle Bild..." })
989
+ ] }) : currentResult.status === "error" ? /* @__PURE__ */ jsxs8("div", { className: "p-10 text-center flex flex-col items-center gap-5 max-w-md", children: [
990
+ /* @__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" }) }),
991
+ /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-2", children: [
992
+ /* @__PURE__ */ jsx10("h3", { className: "text-[11px] font-bold uppercase tracking-widest text-red-400", children: "Generierungsfehler" }),
993
+ /* @__PURE__ */ jsx10("p", { className: "text-white/60 text-[12px] leading-relaxed", children: currentResult.error?.message })
994
+ ] }),
995
+ /* @__PURE__ */ jsx10(PillButton, { variant: "outline", icon: "refresh", onClick: () => handleGenerateImage(currentResult.prompt), children: "Erneut versuchen" })
996
+ ] }) : /* @__PURE__ */ jsxs8("div", { className: "h-full w-full relative flex items-center justify-center", children: [
997
+ /* @__PURE__ */ jsx10("img", { src: currentResult.base64, className: "max-h-full max-w-full object-contain rounded-xl shadow-2xl" }),
998
+ /* @__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: [
999
+ /* @__PURE__ */ jsx10(PillButton, { variant: "outline", icon: "replay", onClick: () => setActivePrompt(currentResult.prompt || ""), children: "Prompt" }),
1000
+ /* @__PURE__ */ jsx10(PillButton, { variant: "solid", icon: "auto_fix_high", onClick: () => handleGenerateImage(currentResult.prompt || activePrompt, currentResult.mediaId, void 0, { silent: true }), children: "Referenz" }),
1001
+ /* @__PURE__ */ jsx10(PillButton, { variant: "outline", icon: "download", onClick: handleDownloadSingle, children: "Speichern" })
1002
+ ] })
1003
+ ] }) : /* @__PURE__ */ jsxs8("div", { className: "flex flex-col items-center gap-2 opacity-10", children: [
1004
+ /* @__PURE__ */ jsx10("span", { className: "material-symbols-outlined text-[100px]", children: "palette" }),
1005
+ /* @__PURE__ */ jsx10("span", { className: "text-[12px] font-bold uppercase tracking-[0.2em]", children: "Avatar Architect" })
1006
+ ] })
1007
+ ] }) })
1008
+ ] })
1009
+ ] }),
1010
+ /* @__PURE__ */ jsxs8(motion5.div, { animate: { width: isRightCollapsed ? 60 : 320 }, className: "flex flex-col border-l border-white/5 bg-[#0e0e0e] shrink-0", children: [
1011
+ /* @__PURE__ */ jsxs8("div", { className: "flex border-b border-white/5 h-14 shrink-0 overflow-hidden", children: [
1012
+ /* @__PURE__ */ jsx10("div", { className: "flex flex-1", children: ["history", "gallery", "inspect", "setup"].map((tab) => /* @__PURE__ */ jsx10("button", { onClick: () => {
1013
+ setActiveTab(tab);
1014
+ setIsRightCollapsed(false);
1015
+ }, 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)) }),
1016
+ /* @__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" }) })
1017
+ ] }),
1018
+ !isRightCollapsed && /* @__PURE__ */ jsx10("div", { className: "flex-1 overflow-hidden relative", children: /* @__PURE__ */ jsxs8(AnimatePresence, { mode: "wait", children: [
1019
+ activeTab === "history" && /* @__PURE__ */ jsx10(HistoryPanel, { history, currentResultId: currentResult?.id || null, onSelect: setCurrentResult, onDelete: (id) => setHistory((h) => h.filter((x) => x.id !== id)) }, "history"),
1020
+ activeTab === "gallery" && /* @__PURE__ */ jsx10(
1021
+ MediaLibrary,
1022
+ {
1023
+ items: galleryItems,
1024
+ onImport: async () => {
1025
+ const media = await onSelectMedia();
1026
+ if (!media?.length) return;
1027
+ const newItems = media.map((m) => ({
1028
+ id: crypto.randomUUID(),
1029
+ nodeId: "1",
1030
+ base64: `data:${m.mimeType};base64,${m.base64}`,
1031
+ mediaId: m.mediaId,
1032
+ prompt: m.name || "Importiert",
1033
+ timestamp: Date.now(),
1034
+ status: "done",
1035
+ tags: [],
1036
+ type: "import"
1037
+ }));
1038
+ setGalleryItems((prev) => [...newItems, ...prev]);
1039
+ },
1040
+ onDelete: (id) => setGalleryItems((g) => g.filter((x) => x.id !== id)),
1041
+ onSelect: setCurrentResult,
1042
+ onGenerateReference: (item) => handleGenerateImage(item.prompt || activePrompt, item.mediaId, void 0, { silent: true })
1043
+ },
1044
+ "gallery"
1045
+ ),
1046
+ activeTab === "inspect" && /* @__PURE__ */ jsx10(InspectPanel, { currentResult, history, onSelect: setCurrentResult }, "inspect"),
1047
+ activeTab === "setup" && /* @__PURE__ */ jsx10(
1048
+ SetupPanel,
1049
+ {
1050
+ onProjectExport: handleProjectExport,
1051
+ onProjectImport: handleProjectImport,
1052
+ onWorkspaceImport: handleWorkspaceImport,
1053
+ projectActionState
1054
+ },
1055
+ "setup"
1056
+ )
1057
+ ] }) })
1058
+ ] })
1059
+ ] });
1060
+ }
714
1061
  export {
1062
+ AvatarArchitectApp,
715
1063
  CompactDropdown,
716
1064
  FaToolsBadge,
717
1065
  GLOBAL_STYLES,
@@ -722,11 +1070,16 @@ export {
722
1070
  PillButton,
723
1071
  SectionLabel,
724
1072
  SetupPanel,
1073
+ buildFallbackPrompt,
1074
+ buildGenerationPrompt,
1075
+ buildImageGenerationOptions,
1076
+ cleanAiResponse,
725
1077
  exportProjectToZip,
726
1078
  formatTreeToMarkdown,
727
1079
  getFormattedTimestamp,
728
1080
  importProjectFromZip,
729
1081
  injectXMPMetadata,
1082
+ interpretSdkError,
730
1083
  parsePromptFile,
731
1084
  useKeyboardNavigation,
732
1085
  useOnClickOutside
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.14",
4
4
  "description": "Shared tools and hooks for Fine Art flow apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",