132 lines
3.9 KiB
TypeScript
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
|
|
/>
|
|
);
|
|
}
|