100 lines
3.2 KiB
TypeScript
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",
|
|
};
|
|
}
|