"use client"; import React, { useState, useRef } from "react"; import { ImagePlus, Loader2, AlertCircle, MoreVertical, Scissors, Sparkles, Expand, Maximize2, Move3D, Layers, Download, Trash2, RefreshCw } from "lucide-react"; import { Button } from "@/components/ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { toast } from "sonner"; import { useCanvasStore } from "@/store/canvas"; import { imageGenerationService } from "@/services/canvas"; import type { CanvasNode, ImageNodeData } from "@/types/canvas"; import { cn } from "@/lib/utils"; import { AngleEditDialog } from "@/components/angle-edit-dialog"; import { SceneFusionDialog } from "@/components/scene-fusion-dialog"; interface ImageNodeProps { node: CanvasNode; isSelected: boolean; } export function ImageNode({ node, isSelected }: ImageNodeProps) { const data = node.data as ImageNodeData; const [isHovered, setIsHovered] = useState(false); const [isProcessing, setIsProcessing] = useState(false); const [angleDialogOpen, setAngleDialogOpen] = useState(false); const [sceneFusionDialogOpen, setSceneFusionDialogOpen] = useState(false); const [isUploading, setIsUploading] = useState(false); // 文件输入的 ref const fileInputRef = useRef(null); const updateNode = useCanvasStore((state) => state.updateNode); const deleteNode = useCanvasStore((state) => state.deleteNode); const addNode = useCanvasStore((state) => state.addNode); // 触发文件选择 const handleSelectFile = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); fileInputRef.current?.click(); }; // 处理图片上传 - 上传到存储桶,不使用 Base64 const handleImageUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; // 验证文件类型 if (!file.type.startsWith('image/')) { toast.error('请上传图片文件'); return; } // 验证文件大小 (8MB) if (file.size > 8 * 1024 * 1024) { toast.error('图片大小不能超过 8MB'); return; } setIsUploading(true); // 先显示上传中状态 updateNode(node.id, { data: { ...data, status: 'generating' }, }); try { // 上传到存储桶 const result = await imageGenerationService.uploadImage(file); if (result.success && result.data?.url) { updateNode(node.id, { data: { ...data, src: result.data.url, status: 'completed' }, }); toast.success('图片上传成功'); } else { updateNode(node.id, { data: { ...data, status: 'failed' }, }); toast.error(result.error || '图片上传失败'); } } catch (error) { console.error('图片上传失败:', error); updateNode(node.id, { data: { ...data, status: 'failed' }, }); toast.error('图片上传失败,请重试'); } finally { setIsUploading(false); } // 重置 input e.target.value = ''; }; // 获取操作的中文名称 const getActionName = (action: string): string => { const names: Record = { 'rmbg': '抠图', 'enhance': '变清晰', 'extend': '图片扩展', 'resize': '尺寸调整', 'angle': '角度编辑', 'scene-fusion': '场景裂变', }; return names[action] || action; }; // 处理图片编辑操作 - 集成我们现有的 API const handleAction = async (action: string) => { if (!data.src) { toast.error("请先上传图片"); return; } // 对话框类型的操作 if (action === 'angle') { setAngleDialogOpen(true); return; } if (action === 'scene-fusion') { setSceneFusionDialogOpen(true); return; } if (action === 'resize') { toast.info("尺寸调整功能即将上线"); return; } const actionName = getActionName(action); // 先创建一个"处理中"状态的节点,显示在原图右侧 const newNodeId = addNode({ type: 'image', position: { x: node.position.x + node.size.width + 20, y: node.position.y }, size: { width: node.size.width, height: node.size.height }, data: { src: '', // 暂时没有图片 status: 'generating', prompt: `${actionName}处理中...`, originalSrc: data.src, }, connections: [node.id], }); let result; try { switch (action) { case 'rmbg': result = await imageGenerationService.removeBackground(data.src); break; case 'enhance': result = await imageGenerationService.enhanceImage(data.src); break; case 'extend': result = await imageGenerationService.extendImage(data.src); break; default: // 删除创建的节点 if (newNodeId) deleteNode(newNodeId); return; } if (result?.success && result.data?.url) { // 更新节点为完成状态 if (newNodeId) { updateNode(newNodeId, { data: { src: result.data.url, status: 'completed', prompt: `${actionName}结果`, originalSrc: data.src, }, }); } toast.success(`${actionName}处理完成`); } else { // 更新节点为失败状态 if (newNodeId) { updateNode(newNodeId, { data: { src: '', status: 'failed', prompt: `${actionName}失败`, originalSrc: data.src, }, }); } toast.error(result?.error || "处理失败"); } } catch (error) { console.error(`${action} 处理失败:`, error); // 更新节点为失败状态 if (newNodeId) { updateNode(newNodeId, { data: { src: '', status: 'failed', prompt: `${actionName}失败`, originalSrc: data.src, }, }); } toast.error("处理失败,请重试"); } }; // 角度编辑完成回调 const handleAngleEditComplete = (resultUrl: string) => { // 创建新的图片节点显示结果 addNode({ type: 'image', position: { x: node.position.x + node.size.width + 20, y: node.position.y }, size: { width: node.size.width, height: node.size.height }, data: { src: resultUrl, status: 'completed', prompt: '角度编辑结果', originalSrc: data.src, }, connections: [node.id], }); }; // 下载图片 const handleDownload = () => { if (data.src) { const link = document.createElement('a'); link.href = data.src; link.download = `image-${node.id}.png`; link.click(); } }; return ( <>
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} > {/* 图片内容 */} {data.src ? ( {data.prompt ) : data.status === 'generating' ? (
生成中...
) : data.status === 'failed' ? (
生成失败
) : (
点击上传图片 {/* 隐藏的文件输入 */} {/* 选择文件按钮 */}
)} {/* 处理中遮罩 */} {isProcessing && (
处理中...
)} {/* 选中时显示的顶部工具栏 - 和聊天窗口中的功能一致 */} {isSelected && data.src && !isProcessing && (
e.stopPropagation()} >
)} {/* 悬浮时的更多操作按钮 (右上角) */} {isHovered && data.src && !isProcessing && !isSelected && (
handleAction('rmbg')}> 抠图 handleAction('enhance')}> 变清晰 handleAction('extend')}> 图片扩展 handleAction('resize')}> 尺寸调整 handleAction('angle')}> 角度编辑 handleAction('scene-fusion')}> 场景裂变 下载 deleteNode(node.id)} > 删除
)} {/* 提示词显示 */} {data.prompt && isHovered && !isProcessing && (

{data.prompt}

)}
{/* 角度编辑对话框 */} {data.src && ( )} {/* 场景裂变对话框 */} ); }