123 lines
3.8 KiB
TypeScript
123 lines
3.8 KiB
TypeScript
import type { CanvasViewOperationPlan } from "@/types/canvas/agent-protocol";
|
|
|
|
type CanvasAgentViewNodeLike = {
|
|
id: string;
|
|
type?: string;
|
|
position?: { x?: number; y?: number };
|
|
size?: { width?: number; height?: number };
|
|
data?: unknown;
|
|
createdAt?: string | number | Date;
|
|
updatedAt?: string | number | Date;
|
|
};
|
|
|
|
type CanvasAgentViewportLike = {
|
|
x: number;
|
|
y: number;
|
|
zoom: number;
|
|
};
|
|
|
|
type CanvasAgentStageLike = {
|
|
width: number;
|
|
height: number;
|
|
};
|
|
|
|
const VIEWPORT_ANCHOR_X_RATIO = 0.52;
|
|
const VIEWPORT_ANCHOR_Y_RATIO = 0.46;
|
|
|
|
function toFiniteNumber(value: unknown, fallback = 0) {
|
|
const numericValue = Number(value);
|
|
return Number.isFinite(numericValue) ? numericValue : fallback;
|
|
}
|
|
|
|
function getNodeTimestamp(value: unknown) {
|
|
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
if (value instanceof Date) return value.getTime();
|
|
if (typeof value !== "string" || !value.trim()) return 0;
|
|
const timestamp = Date.parse(value);
|
|
return Number.isFinite(timestamp) ? timestamp : 0;
|
|
}
|
|
|
|
function getCanvasAgentResultScore(node: CanvasAgentViewNodeLike) {
|
|
const data =
|
|
node.data && typeof node.data === "object" && !Array.isArray(node.data)
|
|
? (node.data as Record<string, unknown>)
|
|
: {};
|
|
const metadata =
|
|
data.metadata && typeof data.metadata === "object" && !Array.isArray(data.metadata)
|
|
? (data.metadata as Record<string, unknown>)
|
|
: {};
|
|
const generatedByAgent =
|
|
metadata.generatedByAgent === true ||
|
|
metadata.latestGeneratedHighlight === true ||
|
|
typeof metadata.canvasAgentOperationId === "string";
|
|
if (!generatedByAgent) return 0;
|
|
|
|
return Math.max(
|
|
getNodeTimestamp(metadata.recentlyGeneratedAt),
|
|
getNodeTimestamp(metadata.agentGeneratedAt),
|
|
getNodeTimestamp(metadata.agentReplacedAt),
|
|
getNodeTimestamp(node.updatedAt),
|
|
getNodeTimestamp(node.createdAt),
|
|
1
|
|
);
|
|
}
|
|
|
|
export function resolveCanvasViewOperationTargetNodeIds(params: {
|
|
plan: Pick<CanvasViewOperationPlan, "operation" | "nodeIds" | "anchorNodeId">;
|
|
nodes: CanvasAgentViewNodeLike[];
|
|
selectedNodeIds?: string[];
|
|
maxLatestResults?: number;
|
|
}) {
|
|
const explicitNodeIds = Array.isArray(params.plan.nodeIds)
|
|
? params.plan.nodeIds.map((id) => String(id || "").trim()).filter(Boolean)
|
|
: [];
|
|
if (explicitNodeIds.length > 0) return Array.from(new Set(explicitNodeIds));
|
|
|
|
const anchorNodeId = String(params.plan.anchorNodeId || "").trim();
|
|
if (anchorNodeId) return [anchorNodeId];
|
|
|
|
if (params.plan.operation === "focus_selection") {
|
|
return Array.isArray(params.selectedNodeIds)
|
|
? params.selectedNodeIds.map((id) => String(id || "").trim()).filter(Boolean)
|
|
: [];
|
|
}
|
|
|
|
if (params.plan.operation === "focus_latest_result") {
|
|
const maxLatestResults = Math.max(1, Math.min(8, params.maxLatestResults || 4));
|
|
return params.nodes
|
|
.map((node) => ({
|
|
id: node.id,
|
|
score: getCanvasAgentResultScore(node),
|
|
}))
|
|
.filter((item) => item.id && item.score > 0)
|
|
.sort((a, b) => b.score - a.score)
|
|
.slice(0, maxLatestResults)
|
|
.map((item) => item.id);
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
export function computeCanvasAgentPanViewport(params: {
|
|
currentViewport: CanvasAgentViewportLike;
|
|
center: { x?: number; y?: number };
|
|
stage: CanvasAgentStageLike;
|
|
zoom?: number;
|
|
}) {
|
|
const currentZoom = Math.max(0.1, toFiniteNumber(params.currentViewport.zoom, 1));
|
|
const zoom = Math.max(
|
|
0.1,
|
|
Math.min(4, toFiniteNumber(params.zoom, currentZoom))
|
|
);
|
|
const centerX = toFiniteNumber(params.center.x, 0);
|
|
const centerY = toFiniteNumber(params.center.y, 0);
|
|
const stageWidth = Math.max(1, toFiniteNumber(params.stage.width, 1200));
|
|
const stageHeight = Math.max(1, toFiniteNumber(params.stage.height, 800));
|
|
|
|
return {
|
|
x: stageWidth * VIEWPORT_ANCHOR_X_RATIO - centerX * zoom,
|
|
y: stageHeight * VIEWPORT_ANCHOR_Y_RATIO - centerY * zoom,
|
|
zoom,
|
|
};
|
|
}
|