"use client"; import { useCallback, useMemo, useState } from "react"; import { DEFAULT_MULTI_ANGLE_SETTINGS, MULTI_ANGLE_PRESET_SETTINGS, MultiAngleEditor, type MultiAnglePresetId, type MultiAngleSettings, } from "@/components/MultiAngleEditor"; import { useI18n } from "@/i18n"; import { useModelStore } from "@/store/modelStore"; import { useWorkflowStore } from "@/store/workflowStore"; import type { GenerateResponse, SelectedModel, WorkflowNode } from "@/types"; import { buildGenerateHeaders } from "@/store/utils/buildApiHeaders"; import { executePopiartTask } from "@/store/execution/popiartTaskClient"; import { createDerivedImageNode } from "@/utils/derivedImageNodes"; import { toSelectedModel } from "@/utils/selectedModel"; interface NodeMultiAnglePanelProps { node: WorkflowNode; imageSource: string; onClose: () => void; } type GeneratedImageResult = { image: string; previewImage?: string; }; function normalizeGeneratedImage(value: unknown): GeneratedImageResult | null { if (typeof value === "string" && value.length > 0) { return { image: value }; } if (!value || typeof value !== "object") return null; const record = value as { url?: unknown; previewImg?: unknown }; if (typeof record.url !== "string" || record.url.length === 0) return null; return { image: record.url, previewImage: typeof record.previewImg === "string" ? record.previewImg : undefined, }; } function readFirstGeneratedImage(result: GenerateResponse): GeneratedImageResult | null { if (Array.isArray(result.images) && result.images.length > 0) { return normalizeGeneratedImage(result.images[0]); } return normalizeGeneratedImage(result.image); } export function NodeMultiAnglePanel({ node, imageSource, onClose }: NodeMultiAnglePanelProps) { const { t } = useI18n(); const addNode = useWorkflowStore((state) => state.addNode); const onConnect = useWorkflowStore((state) => state.onConnect); const providerSettings = useWorkflowStore((state) => state.providerSettings); const selectSingleNode = useWorkflowStore((state) => state.selectSingleNode); const multiAngleModel = useModelStore((state) => state.byKind.multiAngle[0] ?? null); const [settings, setSettings] = useState({ ...DEFAULT_MULTI_ANGLE_SETTINGS }); const [isGenerating, setIsGenerating] = useState(false); const selectedModel = useMemo( () => multiAngleModel ? toSelectedModel(multiAngleModel) : null, [multiAngleModel] ); const canGenerate = Boolean(selectedModel) && !isGenerating; const updateSettings = useCallback((patch: Partial) => { setSettings((current) => ({ ...current, ...patch, preset: patch.preset ?? "custom", })); }, []); const selectPreset = useCallback((preset: MultiAnglePresetId) => { setSettings({ ...MULTI_ANGLE_PRESET_SETTINGS[preset] }); }, []); const togglePrompt = useCallback((enabled: boolean) => { setSettings((current) => ({ ...current, includePrompt: enabled, })); }, []); const resetSettings = useCallback(() => { setSettings((current) => ({ ...DEFAULT_MULTI_ANGLE_SETTINGS, includePrompt: current.includePrompt, promptText: current.promptText, })); }, []); const handleGenerate = useCallback(async () => { if (!selectedModel || isGenerating) return; setIsGenerating(true); try { const currentWidth = typeof node.width === "number" ? node.width : typeof node.style?.width === "number" ? node.style.width : 300; const result = await executePopiartTask( { images: [imageSource], prompt: "multi-angle image generation", selectedModel, parameters: { batchSize: 1, horizontal_angle: settings.horizontal, vertical_angle: settings.vertical, zoom: settings.zoom, }, mediaType: "image", }, { headers: buildGenerateHeaders("popiserver", providerSettings), } ); const generatedImage = readFirstGeneratedImage(result); if (!generatedImage) { throw new Error(result.error || "Generation failed"); } createDerivedImageNode({ sourceNodeId: node.id, position: { x: node.position.x + currentWidth + 220, y: node.position.y, }, image: generatedImage.image, previewImage: generatedImage.previewImage, filename: `multi-angle-${Date.now()}.png`, dimensions: null, label: t("imageInput.multiAngle"), operation: "multiAngle", addNode, onConnect, selectSingleNode, }); onClose(); } finally { setIsGenerating(false); } }, [ addNode, imageSource, isGenerating, node, onClose, onConnect, providerSettings, selectSingleNode, selectedModel, settings, t, ]); return (
event.stopPropagation()} >
} />
); }