ai-chatbot/lib/canvas/agent-view-pan.ts

100 lines
3.2 KiB
TypeScript

import type {
CanvasAgentContext,
CanvasViewOperationPlan,
} from "@/types/canvas/agent-protocol";
const VIEWPORT_ANCHOR_X_RATIO = 0.52;
const VIEWPORT_ANCHOR_Y_RATIO = 0.46;
function toFiniteNumber(value: unknown, fallback: number) {
const numericValue = Number(value);
return Number.isFinite(numericValue) ? numericValue : fallback;
}
function getPanDirection(userText: string) {
const text = userText.trim().toLowerCase();
const hasHorizontalIntent =
/左上|左下|右上|右下|向左|往左|左边|左侧|向右|往右|右边|右侧/.test(
text
);
const hasVerticalIntent =
/左上|右上|左下|右下|向上|往上|上面|上方|向下|往下|下面|下方/.test(
text
);
if (!hasHorizontalIntent && !hasVerticalIntent) return null;
const hasPanVerb =
/平移|拖动|拖拽|挪动|移动(?:画布|视角|视野)|拖(?:一下|一点|画布)?|看(?:看|一下|一点)?/.test(
text
) ||
/(?:向|往)(?:左|右|上|下).*(?:看|移动|拖|挪)/.test(text) ||
/(?:左边|左侧|右边|右侧|上面|上方|下面|下方).*(?:看看|看一下|视角|视野|画布)/.test(
text
);
if (!hasPanVerb) return null;
return {
x: /右上|右下|向右|往右|右边|右侧/.test(text)
? 1
: /左上|左下|向左|往左|左边|左侧/.test(text)
? -1
: 0,
y: /左下|右下|向下|往下|下面|下方/.test(text)
? 1
: /左上|右上|向上|往上|上面|上方/.test(text)
? -1
: 0,
};
}
function getPanScale(userText: string) {
const text = userText.trim().toLowerCase();
if (/大幅|多一点|远一点|一大段|很多|more|far/.test(text)) return 0.52;
if (/一点|一下|少许|稍微|轻微|小幅|slightly/.test(text)) return 0.26;
return 0.36;
}
export function hasCanvasAgentPanViewIntent(userText: string) {
const direction = getPanDirection(userText);
return Boolean(direction && (direction.x !== 0 || direction.y !== 0));
}
export function buildCanvasAgentPanViewPlan(params: {
userText: string;
context: CanvasAgentContext;
projectId: string;
operationId: string;
reason?: string;
}): CanvasViewOperationPlan | null {
const direction = getPanDirection(params.userText);
if (!direction || (direction.x === 0 && direction.y === 0)) return null;
const viewport = params.context.viewport || { x: 0, y: 0, zoom: 1 };
const zoom = Math.max(0.1, toFiniteNumber(viewport.zoom, 1));
const stageWidth = Math.max(1, toFiniteNumber(viewport.width, 1200));
const stageHeight = Math.max(1, toFiniteNumber(viewport.height, 800));
const currentCenterX =
(stageWidth * VIEWPORT_ANCHOR_X_RATIO - toFiniteNumber(viewport.x, 0)) /
zoom;
const currentCenterY =
(stageHeight * VIEWPORT_ANCHOR_Y_RATIO - toFiniteNumber(viewport.y, 0)) /
zoom;
const scale = getPanScale(params.userText);
const stepX = (stageWidth * scale) / zoom;
const stepY = (stageHeight * scale) / zoom;
return {
version: 1,
viewOperationId: `${params.operationId}:view`,
operation: "pan_to",
projectId: params.projectId,
center: {
x: currentCenterX + direction.x * stepX,
y: currentCenterY + direction.y * stepY,
},
zoom,
persist: false,
reason: params.reason || "pan_canvas_view",
};
}