"use client"; import type { CSSProperties } from "react"; import { useState } from "react"; import { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Edit3, Eye, Image as ImageIcon, LoaderCircle, MessageSquare, Play } from "lucide-react"; import { App, Button, Empty, Input, InputNumber, Modal, Segmented } from "antd"; import { ModelPicker } from "@/components/model-picker"; import { defaultConfig, type AiConfig } from "@/lib/ai-config"; import { canvasThemes } from "@/lib/canvas-theme"; import { useAiConfigStore } from "@/stores/use-ai-config-store"; import { useConfigDialogStore } from "@/stores/use-config-dialog-store"; import { useThemeStore } from "@/stores/use-theme-store"; import { CanvasSizePicker } from "./canvas-size-picker"; import type { NodeGenerationInput } from "./canvas-node-generation"; import type { CanvasGenerationMode, CanvasNodeData, CanvasNodeMetadata } from "../types"; type CanvasConfigNodePanelProps = { node: CanvasNodeData; isRunning: boolean; inputSummary: { textCount: number; imageCount: number }; inputs: NodeGenerationInput[]; onConfigChange: (nodeId: string, patch: Partial) => void; onTextInputChange: (nodeId: string, content: string) => void; onGenerate: (nodeId: string) => void; }; export function CanvasConfigNodePanel({ node, isRunning, inputSummary, inputs, onConfigChange, onTextInputChange, onGenerate }: CanvasConfigNodePanelProps) { const { message } = App.useApp(); const [previewOpen, setPreviewOpen] = useState(false); const [editingTextId, setEditingTextId] = useState(null); const [editingText, setEditingText] = useState(""); const globalConfig = useAiConfigStore((state) => state.config); const openConfigDialog = useConfigDialogStore((state) => state.openConfigDialog); const theme = canvasThemes[useThemeStore((state) => state.theme)]; const mode = node.metadata?.generationMode || "image"; const config = buildNodeConfig(globalConfig, node, mode); const count = Math.max(1, Math.min(15, Math.floor(Math.abs(Number(node.metadata?.count || 3)) || 1))); const chipStyle = { background: theme.node.fill, borderColor: theme.node.stroke, color: theme.node.text }; const textInputs = inputs.filter((input) => input.type === "text"); const imageInputs = inputs.filter((input) => input.type === "image"); const moveInput = (input: NodeGenerationInput, offset: number) => { const sameTypeInputs = inputs.filter((item) => item.type === input.type); const sameTypeIndex = sameTypeInputs.findIndex((item) => item.nodeId === input.nodeId); const targetInput = sameTypeInputs[sameTypeIndex + offset]; if (!targetInput) return; const index = inputs.findIndex((item) => item.nodeId === input.nodeId); const targetIndex = inputs.findIndex((item) => item.nodeId === targetInput.nodeId); const next = [...inputs]; [next[index], next[targetIndex]] = [next[targetIndex], next[index]]; onConfigChange(node.id, { inputOrder: next.map((input) => input.nodeId) }); message.success("已调整输入顺序"); }; const startTextEdit = (input: NodeGenerationInput) => { setEditingTextId(input.nodeId); setEditingText(input.text || ""); }; const saveTextEdit = () => { if (!editingTextId) return; onTextInputChange(editingTextId, editingText); setEditingText(""); setEditingTextId(null); message.success("已保存文本提示词"); }; return (
event.stopPropagation()} onPointerDown={(event) => event.stopPropagation()} onWheel={(event) => event.stopPropagation()}>
生成配置
onConfigChange(node.id, { generationMode: value as CanvasGenerationMode })} options={[ { value: "image", label: 生图 }, { value: "text", label: 文本 }, ]} />
onConfigChange(node.id, { model })} onMissingConfig={() => openConfigDialog(true)} fullWidth /> {mode === "image" ? onConfigChange(node.id, { size: value })} /> : null} onConfigChange(node.id, { count: Number(value) || 1 })} />
setPreviewOpen(false)} footer={null} centered width={860} mask={{ closable: true }} keyboard destroyOnHidden modalRender={(modal) => (
event.stopPropagation()} onMouseDown={(event) => event.stopPropagation()} onPointerDown={(event) => event.stopPropagation()}> {modal}
)} >
event.stopPropagation()} onPointerDown={(event) => event.stopPropagation()} onWheelCapture={(event) => event.stopPropagation()}> {inputs.length ? (
{imageInputs.map((input, index) => )}
{textInputs.map((input, index) => )}
{editingTextId ? ( <>
编辑文本提示词
setEditingText(event.target.value)} />
) : (
选择一条文本后在这里编辑
)}
) : ( )}
); } function PreviewSection({ title, count, empty, children }: { title: string; count: number; empty: string; children: React.ReactNode }) { return (
{title}
{count} 个
{count ? children :
{empty}
}
); } function TextSortCard({ input, textIndex, textTotal, inputs, theme, onMove, onEdit }: { input: NodeGenerationInput; textIndex: number; textTotal: number; inputs: NodeGenerationInput[]; theme: (typeof canvasThemes)[keyof typeof canvasThemes]; onMove: (input: NodeGenerationInput, offset: number) => void; onEdit: (input: NodeGenerationInput) => void }) { return (
文本 {textIndex + 1}
{input.text}
); } function ImageSortCard({ input, imageIndex, imageTotal, inputs, theme, onMove }: { input: NodeGenerationInput; imageIndex: number; imageTotal: number; inputs: NodeGenerationInput[]; theme: (typeof canvasThemes)[keyof typeof canvasThemes]; onMove: (input: NodeGenerationInput, offset: number) => void }) { if (!input.image) return null; return (
{input.title} {imageIndex + 1} onMove(input, offset)} />
); } function VerticalOrderButtons({ index, total, onMove }: { index: number; total: number; onMove: (offset: number) => void }) { return ( <>