"use client"; import { useState, useCallback, useEffect } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } 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, X } from "lucide-react"; import { toast } from "sonner"; import { QwenCameraWidget, type CameraState } from "./qwen-camera-widget"; import { cn } from "@/lib/utils"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; interface AngleEditDialogProps { open: boolean; onOpenChange: (open: boolean) => void; imageUrl: string; onComplete?: (resultUrl: string) => void; } // 根据角度值生成 API 所需的字符串参数 function getAzimuthString(azimuth: number): string { const hAngle = azimuth % 360; if (hAngle < 22.5 || hAngle >= 337.5) return "front"; if (hAngle < 67.5) return "front-right"; if (hAngle < 112.5) return "right"; if (hAngle < 157.5) return "back-right"; if (hAngle < 202.5) return "back"; if (hAngle < 247.5) return "back-left"; if (hAngle < 292.5) return "left"; return "front-left"; } function getElevationString(elevation: number): string { if (elevation < -15) return "low"; if (elevation < 15) return "eye-level"; if (elevation < 45) return "elevated"; return "high"; } function getDistanceString(distance: number): string { if (distance < 3) return "wide"; if (distance < 7) return "medium"; return "close"; } export function AngleEditDialog({ open, onOpenChange, imageUrl, }: AngleEditDialogProps) { const [isProcessing, setIsProcessing] = useState(false); const [resultUrl, setResultUrl] = useState(null); const [loraScale, setLoraScale] = useState(0.9); const [cameraState, setCameraState] = useState({ azimuth: 45, elevation: 0, distance: 5, }); // 预览 Tab 状态:如果生成了结果,默认显示结果;否则显示原图 const [activeTab, setActiveTab] = useState<"original" | "result">("original"); // 当对话框关闭时重置状态 useEffect(() => { if (!open) { setResultUrl(null); setIsProcessing(false); setActiveTab("original"); } }, [open]); // 当生成结果时,自动切换到结果 Tab useEffect(() => { if (resultUrl) { setActiveTab("result"); } }, [resultUrl]); // 处理相机状态变化 const handleCameraStateChange = useCallback((state: CameraState) => { setCameraState(state); // 角度变化时清除之前的结果 if (!isProcessing) { setResultUrl(null); } }, [isProcessing]); // 生成新视角 const handleGenerate = useCallback(async () => { setIsProcessing(true); setResultUrl(null); setActiveTab("result"); // 切换到结果 Tab 显示加载状态 try { const response = await fetch("/api/angle-edit", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ image_url: imageUrl, azimuth: getAzimuthString(cameraState.azimuth), elevation: getElevationString(cameraState.elevation), distance: getDistanceString(cameraState.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 : "生成失败,请重试"); setActiveTab("original"); // 失败后切回原图 } finally { setIsProcessing(false); } }, [imageUrl, cameraState, 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-${cameraState.azimuth}-${cameraState.elevation}-${Date.now()}.png`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); toast.success("图片已下载"); } catch { toast.error("下载失败"); } }, [resultUrl, cameraState]); // 使用此图片(直接发送到聊天) const handleUseImage = useCallback(() => { if (resultUrl) { // 关闭对话框,图片已经通过 API 保存到数据库 onOpenChange(false); toast.success("图片已添加到对话"); } }, [resultUrl, onOpenChange]); // 重置 const handleReset = useCallback(() => { setResultUrl(null); setCameraState({ azimuth: 45, elevation: 0, distance: 5, }); setLoraScale(0.9); setActiveTab("original"); }, []); return ( {/* 紧凑标题栏 */}
3D 视角编辑器
{/* 左侧:3D 控制区 (固定宽度 320px) */}
{(loraScale * 100).toFixed(0)}%
setLoraScale(v)} min={0.5} max={1.0} step={0.05} className="py-1" />
{/* 生成按钮区 */}
{/* 右侧:预览区 (自适应) */}
setActiveTab(v as any)} className="flex-1 flex flex-col">
原图 生成结果 {resultUrl && } {resultUrl && (
)}
{/* 原图 Tab */}
原图
{/* 结果 Tab */}
{isProcessing ? (

正在渲染新视角...

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

暂无结果

)}
); } export default AngleEditDialog;