"use client"; import { useState, useCallback } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Slider } from "@/components/ui/slider"; import { Label } from "@/components/ui/label"; import { Loader2, Download, Check, RotateCcw, Move3D, Sparkles } from "lucide-react"; import { toast } from "sonner"; import { AngleController3D } from "./angle-controller-3d"; import { cn } from "@/lib/utils"; interface AngleEditDialogProps { open: boolean; onOpenChange: (open: boolean) => void; imageUrl: string; onComplete?: (resultUrl: string) => void; } interface AngleParams { azimuth: string; elevation: string; distance: string; azimuthDegree: number; elevationDegree: number; distanceFactor: number; } export function AngleEditDialog({ open, onOpenChange, imageUrl, onComplete, }: AngleEditDialogProps) { const [isProcessing, setIsProcessing] = useState(false); const [resultUrl, setResultUrl] = useState(null); const [loraScale, setLoraScale] = useState(0.9); const [currentAngles, setCurrentAngles] = useState({ azimuth: "front-right", elevation: "eye-level", distance: "medium", azimuthDegree: 45, elevationDegree: 0, distanceFactor: 1.0, }); // 处理角度变化 const handleAngleChange = useCallback((angles: AngleParams) => { setCurrentAngles(angles); // 清除之前的结果,因为角度变了 setResultUrl(null); }, []); // 生成新视角 const handleGenerate = useCallback(async () => { setIsProcessing(true); setResultUrl(null); try { const response = await fetch("/api/angle-edit", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ image_url: imageUrl, azimuth: currentAngles.azimuth, elevation: currentAngles.elevation, distance: currentAngles.distance, lora_scale: loraScale, }), }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || "生成失败"); } const data = await response.json(); setResultUrl(data.image_url); toast.success("视角生成成功!"); } catch (error) { console.error("角度编辑失败:", error); toast.error(error instanceof Error ? error.message : "生成失败,请重试"); } finally { setIsProcessing(false); } }, [imageUrl, currentAngles, loraScale]); // 下载结果图片 const handleDownload = useCallback(async () => { if (!resultUrl) return; try { const response = await fetch(resultUrl); const blob = await response.blob(); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `angle-edit-${currentAngles.azimuth}-${currentAngles.elevation}-${Date.now()}.png`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); toast.success("图片已下载"); } catch { toast.error("下载失败"); } }, [resultUrl, currentAngles]); // 使用此图片 const handleUseImage = useCallback(() => { if (resultUrl) { onComplete?.(resultUrl); onOpenChange(false); } }, [resultUrl, onComplete, onOpenChange]); // 重置 const handleReset = useCallback(() => { setResultUrl(null); setCurrentAngles({ azimuth: "front-right", elevation: "eye-level", distance: "medium", azimuthDegree: 45, elevationDegree: 0, distanceFactor: 1.0, }); setLoraScale(0.9); }, []); return (
多角度编辑 拖拽 3D 控制器选择相机角度,AI 将生成对应视角的图像
{/* 左侧:3D 控制器 (占 5 列) */}
{/* LoRA 强度控制 - 放入卡片 */}
{(loraScale * 100).toFixed(0)}%
setLoraScale(v)} min={0.5} max={1.0} step={0.05} className="py-2" />

强度越高角度变化越明显,越低则保留更多原图特征。建议 85-95%。

{/* 右侧:图片预览 (占 7 列) */}
{/* 原图 */}
原图
原图
{/* 结果图 */}
生成结果
{resultUrl && ( 完成 )}
{isProcessing ? (

正在生成新视角...

预计需要 10-30 秒

) : resultUrl ? ( 生成结果 ) : (

准备就绪

调整左侧角度后点击生成

)}
{/* 底部操作栏 */}
{resultUrl ? ( <> ) : ( )}
); } export default AngleEditDialog;