fix: treat canvas view centering as focus intent

This commit is contained in:
134 2026-06-23 17:10:44 +08:00
parent 75cfa4c00f
commit 9e6cf69b45
3 changed files with 39 additions and 0 deletions

View File

@ -714,6 +714,7 @@ export interface CanvasViewOperationPlan {
- `fit_all`:缩小到画布主要内容范围,适合“看全局”。 - `fit_all`:缩小到画布主要内容范围,适合“看全局”。
- `zoom_in` / `zoom_out` / `zoom_to`:只调缩放。 - `zoom_in` / `zoom_out` / `zoom_to`:只调缩放。
- `pan_to`:平移到画布坐标或节点附近。 - `pan_to`:平移到画布坐标或节点附近。
- “把选中的图移动到视野中央 / 居中显示当前对象”必须归类为视角操作,不是 `move_nodes`;只有整理、排列、把节点放到画布某位置这类会改变节点坐标的请求才进入移动确认门。
持久化边界: 持久化边界:

View File

@ -150,12 +150,28 @@ function inferHighRiskOperation(userText: string) {
if (/覆盖|替换当前|替换原图|直接替换|覆盖原图/.test(text)) { if (/覆盖|替换当前|替换原图|直接替换|覆盖原图/.test(text)) {
return "replace_node_media"; return "replace_node_media";
} }
if (hasCanvasViewMovementIntent(text)) {
return "";
}
if (/批量移动|整体移动|移动到|移到|整理|排列|排版/.test(text)) { if (/批量移动|整体移动|移动到|移到|整理|排列|排版/.test(text)) {
return "move_nodes"; return "move_nodes";
} }
return ""; return "";
} }
function hasCanvasViewMovementIntent(userText: string) {
const text = userText.toLowerCase();
return (
/(?:移动|平移|拖动|拖拽|挪动)(?:一下|一点)?(?:画布|视角|视野|镜头|视图|视口)/.test(
text
) ||
/(?:视野|视口|屏幕|画面|镜头|视图|窗口).{0,8}(?:中央|中心|中间)/.test(
text
) ||
/(?:居中显示|居中查看|居中看|聚焦到|定位到|拉近到|带我看)/.test(text)
);
}
function defaultConfirmationReply(riskOperation: string) { function defaultConfirmationReply(riskOperation: string) {
if (riskOperation === "delete_nodes") { if (riskOperation === "delete_nodes") {
return "这个动作会删除画布节点,需要先确认后才能执行。"; return "这个动作会删除画布节点,需要先确认后才能执行。";

View File

@ -993,6 +993,28 @@ test.describe("canvas chat pure helpers", () => {
).toBeUndefined(); ).toBeUndefined();
}); });
test("keeps viewport-centering move requests as view operations", () => {
const outcome = normalizeCanvasAgentModelPlannerDecision({
decision: {
action: "focus_nodes",
nodeIds: ["image-1"],
},
context: plannerContext,
projectId: plannerContext.projectId || "",
messageId: "message-1",
chatId: "chat-1",
userText: "把选中的图移动到视野中央",
});
expect(outcome.kind).toBe("view");
expect(outcome.kind === "view" ? outcome.viewPlan : null).toMatchObject({
operation: "focus_selection",
nodeIds: ["image-1"],
anchorNodeId: "image-1",
persist: false,
});
});
test("builds pending delete-node operation from model-planned destructive request", () => { test("builds pending delete-node operation from model-planned destructive request", () => {
const outcome = normalizeCanvasAgentModelPlannerDecision({ const outcome = normalizeCanvasAgentModelPlannerDecision({
decision: { decision: {