2026-04-15 20:29:07 +08:00

132 lines
3.9 KiB
TypeScript

"use client";
import { useParams, useRouter, useSearchParams } from "next/navigation";
import { useCallback } from "react";
import { toast } from "sonner";
import { RealtimeCanvasDialog } from "@/components/canvas/RealtimeCanvasDialog";
import { canvasService } from "@/services/canvas";
import { canvasSelectors, useCanvasStore } from "@/store/canvas";
export default function CanvasRealtimePage() {
const router = useRouter();
const routeParams = useParams();
const searchParams = useSearchParams();
const id = String(routeParams?.id || "");
const initialMode =
searchParams?.get("mode") === "enhance-pro" ? "enhance-pro" : "realtime";
const viewport = useCanvasStore(canvasSelectors.viewport);
const selectedNodeIds = useCanvasStore(canvasSelectors.selectedNodeIds);
const nodes = useCanvasStore(canvasSelectors.nodes);
const addNode = useCanvasStore((state) => state.addNode);
const getCanvasState = useCanvasStore((state) => state.getCanvasState);
const selectedImageNode =
selectedNodeIds.length === 1
? nodes.find(
(node) => node.id === selectedNodeIds[0] && node.type === "image"
)
: null;
const handleOpenChange = useCallback(
(nextOpen: boolean) => {
if (!nextOpen) {
router.push(`/canvas/${id}`);
}
},
[id, router]
);
const handleApplyResult = useCallback(
async ({
imageUrl,
sourceImageUrl,
prompt,
title,
model,
width,
height,
}: {
imageUrl: string;
sourceImageUrl?: string;
prompt: string;
title?: string;
model?: string;
width?: number;
height?: number;
}) => {
const viewportX = viewport.x || 0;
const viewportY = viewport.y || 0;
const viewportZoom = viewport.zoom || 1;
const stageWidth =
typeof window !== "undefined" ? window.innerWidth : 1280;
const stageHeight =
typeof window !== "undefined" ? window.innerHeight : 720;
const safeWidth = Number(width);
const safeHeight = Number(height);
const hasNaturalSize = safeWidth > 0 && safeHeight > 0;
const maxPreviewEdge = 420;
const fallbackSize = 360;
const scale = hasNaturalSize
? Math.min(1, maxPreviewEdge / Math.max(safeWidth, safeHeight))
: 1;
const targetWidth = hasNaturalSize
? Math.max(140, Math.round(safeWidth * scale))
: fallbackSize;
const targetHeight = hasNaturalSize
? Math.max(140, Math.round(safeHeight * scale))
: fallbackSize;
const compareSourceUrl = String(sourceImageUrl || "").trim();
const x =
(stageWidth * 0.45 - viewportX) / viewportZoom - targetWidth / 2;
const y =
(stageHeight * 0.45 - viewportY) / viewportZoom - targetHeight / 2;
addNode({
type: "image",
position: { x, y },
size: { width: targetWidth, height: targetHeight },
data: {
src: imageUrl,
originalSrc: compareSourceUrl || undefined,
status: "completed",
title: title || "实时画布结果",
prompt,
model: model || "bfl/flux-2",
metadata: compareSourceUrl
? {
compareSourceUrl,
}
: undefined,
},
connections: [],
});
try {
await canvasService.saveCanvasState(id, getCanvasState());
} catch (error) {
console.error("[RealtimeCanvasPage] 保存结果失败:", error);
}
toast.success(`${title || "结果"}已添加到画布`);
},
[addNode, getCanvasState, id, viewport.x, viewport.y, viewport.zoom]
);
return (
<RealtimeCanvasDialog
initialImageUrl={
selectedImageNode
? ((selectedImageNode.data as any)?.src as string)
: null
}
initialMode={initialMode}
onApplyResult={handleApplyResult}
onOpenChange={handleOpenChange}
open
standalone
/>
);
}