import { useEffect, useState } from "react"; import { ArrowUp, LoaderCircle, Square } from "lucide-react"; import { Button } from "antd"; import { ModelPicker } from "@/components/model-picker"; import { defaultConfig, modelMatchesCapability, useConfigStore, useEffectiveConfig, type AiConfig } from "@/stores/use-config-store"; import { CreditSymbol, requestCreditCost } from "@/constant/credits"; import { canvasThemes } from "@/lib/canvas-theme"; import { useThemeStore } from "@/stores/use-theme-store"; import { CanvasImageSettingsPopover } from "./canvas-image-settings-popover"; import { CanvasPromptLibrary } from "./canvas-prompt-library"; import { CanvasAudioSettingsPopover, type CanvasAudioSettingKey } from "./canvas-audio-settings-popover"; import { CanvasResourceMentionTextarea } from "./canvas-resource-mention-textarea"; import { CanvasVideoSettingsPopover } from "./canvas-video-settings-popover"; import { CanvasNodeType, type CanvasGenerationMode, type CanvasNodeData } from "@/types/canvas"; import type { CanvasResourceReference } from "@/lib/canvas/canvas-resource-references"; export type CanvasNodeGenerationMode = CanvasGenerationMode; type CanvasNodePromptPanelProps = { node: CanvasNodeData; isRunning: boolean; onPromptChange: (nodeId: string, prompt: string) => void; onConfigChange: (nodeId: string, patch: Partial) => void; onGenerate: (nodeId: string, mode: CanvasNodeGenerationMode, prompt: string) => void; onStop: (nodeId: string) => void; mentionReferences?: CanvasResourceReference[]; onImageSettingsOpenChange?: (open: boolean) => void; }; export function CanvasNodePromptPanel({ node, isRunning, onPromptChange, onConfigChange, onGenerate, onStop, mentionReferences = [], onImageSettingsOpenChange }: CanvasNodePromptPanelProps) { const globalConfig = useEffectiveConfig(); const openConfigDialog = useConfigStore((state) => state.openConfigDialog); const theme = canvasThemes[useThemeStore((state) => state.theme)]; const mode = defaultMode(node.type); const config = buildNodeConfig(globalConfig, node, mode); const hasTextContent = node.type === CanvasNodeType.Text && Boolean(node.metadata?.content?.trim()); const hasImageContent = node.type === CanvasNodeType.Image && Boolean(node.metadata?.content); const isEditingExistingContent = hasTextContent || hasImageContent; const [prompt, setPrompt] = useState(isEditingExistingContent ? "" : node.metadata?.prompt || ""); const credits = requestCreditCost({ channelMode: config.channelMode, model: config.model, count: mode === "image" ? config.count : 1 }); useEffect(() => { setPrompt(isEditingExistingContent ? "" : node.metadata?.prompt || ""); }, [isEditingExistingContent, node.id]); const updatePrompt = (value: string) => { setPrompt(value); if (!isEditingExistingContent) onPromptChange(node.id, value); }; const submit = () => { const text = prompt.trim(); if (!text || isRunning) return; onGenerate(node.id, mode, text); setPrompt(""); }; return (
event.stopPropagation()} onPointerDown={(event) => event.stopPropagation()} onWheel={(event) => event.stopPropagation()} >
{mode === "image" ? ( <> onConfigChange(node.id, { model })} capability="image" onMissingConfig={() => openConfigDialog(true)} /> onConfigChange(node.id, key === "count" ? { count: Number(value) || 1 } : { [key]: value })} onMissingConfig={() => openConfigDialog(true)} onOpenChange={onImageSettingsOpenChange} /> ) : mode === "video" ? ( <> onConfigChange(node.id, { model })} capability="video" onMissingConfig={() => openConfigDialog(true)} /> onConfigChange(node.id, videoConfigPatch(key, value))} /> ) : mode === "audio" ? ( <> onConfigChange(node.id, { model })} capability="audio" onMissingConfig={() => openConfigDialog(true)} /> onConfigChange(node.id, audioConfigPatch(key, value))} /> ) : ( onConfigChange(node.id, { model })} capability="text" onMissingConfig={() => openConfigDialog(true)} /> )}
); } function defaultMode(type: CanvasNodeData["type"]): CanvasNodeGenerationMode { return type === CanvasNodeType.Text ? "text" : type === CanvasNodeType.Video ? "video" : type === CanvasNodeType.Audio ? "audio" : "image"; } function buildNodeConfig(globalConfig: AiConfig, node: CanvasNodeData, mode: CanvasNodeGenerationMode): AiConfig { const defaultModel = mode === "image" ? globalConfig.imageModel : mode === "video" ? globalConfig.videoModel : mode === "audio" ? globalConfig.audioModel : globalConfig.textModel; const fallbackModel = mode === "image" ? defaultConfig.imageModel : mode === "video" ? defaultConfig.videoModel : mode === "audio" ? defaultConfig.audioModel : defaultConfig.textModel; const currentModel = node.metadata?.model; const model = currentModel && modelMatchesCapability(currentModel, mode) ? currentModel : defaultModel && modelMatchesCapability(defaultModel, mode) ? defaultModel : fallbackModel; return { ...globalConfig, model, quality: node.metadata?.quality || globalConfig.quality || defaultConfig.quality, size: node.metadata?.size || globalConfig.size || defaultConfig.size, videoSeconds: node.metadata?.seconds || globalConfig.videoSeconds || defaultConfig.videoSeconds, vquality: node.metadata?.vquality || globalConfig.vquality || defaultConfig.vquality, videoGenerateAudio: node.metadata?.generateAudio || globalConfig.videoGenerateAudio || defaultConfig.videoGenerateAudio, videoWatermark: node.metadata?.watermark || globalConfig.videoWatermark || defaultConfig.videoWatermark, audioVoice: node.metadata?.audioVoice || globalConfig.audioVoice || defaultConfig.audioVoice, audioFormat: node.metadata?.audioFormat || globalConfig.audioFormat || defaultConfig.audioFormat, audioSpeed: node.metadata?.audioSpeed || globalConfig.audioSpeed || defaultConfig.audioSpeed, audioInstructions: node.metadata?.audioInstructions || globalConfig.audioInstructions || defaultConfig.audioInstructions, count: String(node.metadata?.count || (mode === "image" ? globalConfig.canvasImageCount || globalConfig.count : globalConfig.count) || defaultConfig.count), }; } function promptPlaceholder(mode: CanvasNodeGenerationMode, hasImageContent: boolean, hasTextContent: boolean) { if (mode === "video") return "描述要生成的视频内容"; if (mode === "audio") return "描述要生成的音频内容"; if (mode === "image") return hasImageContent ? "请输入你想要把这张图修改成什么" : "描述要生成的图片内容"; return hasTextContent ? "请输入你想要将本段文本修改成什么" : "请输入你想要生成的文本内容"; } function videoConfigPatch(key: keyof AiConfig, value: string) { if (key === "videoSeconds") return { seconds: value }; if (key === "videoGenerateAudio") return { generateAudio: value }; if (key === "videoWatermark") return { watermark: value }; return { [key]: value }; } function audioConfigPatch(key: CanvasAudioSettingKey, value: string) { if (key === "audioVoice") return { audioVoice: value }; if (key === "audioFormat") return { audioFormat: value }; if (key === "audioSpeed") return { audioSpeed: value }; return { audioInstructions: value }; }