fix: bypass clarification for selected canvas slots
This commit is contained in:
parent
7d99330eb9
commit
bcea56c0d9
@ -164,6 +164,7 @@ import {
|
||||
resolveClarificationBypass,
|
||||
resolveClarificationMessage,
|
||||
} from "@/lib/ai/runtime/clarification-interruption";
|
||||
import { shouldBypassCanvasAgentMissingTargetClarification } from "@/lib/ai/runtime/canvas-agent-clarification";
|
||||
import {
|
||||
buildAgentToolImagesMarkdown,
|
||||
buildAgentToolVideoMarkdown,
|
||||
@ -4625,6 +4626,27 @@ export async function POST(request: Request) {
|
||||
return createSseResponse(promptOnlyStream);
|
||||
}
|
||||
|
||||
if (shouldRunAgentConversation && smartAutoNeedsClarification) {
|
||||
const canvasAgentSlotClarificationBypass =
|
||||
shouldBypassCanvasAgentMissingTargetClarification({
|
||||
text: requestTextContent,
|
||||
contextPayload: effectiveContextPayload,
|
||||
executionMode: smartAutoExecution?.mode,
|
||||
});
|
||||
if (canvasAgentSlotClarificationBypass.shouldBypass) {
|
||||
smartAutoNeedsClarification = false;
|
||||
smartAutoClarificationQuestion = undefined;
|
||||
smartAutoFriendlyMessage = "收到,我会把结果填入当前选中的画布槽位。";
|
||||
chatApiLogger.info(
|
||||
"[Smart Auto Mode] ✅ Skip clarification for selected canvas Agent slot",
|
||||
{
|
||||
reason: canvasAgentSlotClarificationBypass.reason,
|
||||
executionMode: smartAutoExecution?.mode,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldRunAgentConversation && smartAutoNeedsClarification) {
|
||||
const clarificationBypass = resolveClarificationBypass({
|
||||
text: message.parts
|
||||
|
||||
67
lib/ai/runtime/canvas-agent-clarification.ts
Normal file
67
lib/ai/runtime/canvas-agent-clarification.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import type { ContextPayload } from "@/lib/ai/context-memory/types";
|
||||
|
||||
type CanvasAgentExecutionMode = "edit_image" | "generate_image" | "generate_video";
|
||||
|
||||
const CANVAS_AGENT_SLOT_GENERATION_REGEX =
|
||||
/(生成|生图|出图|画一张|来一张|做一张|做图|做视频|视频生成|生成视频|填充|放到|放进|在这里|这个空框|这个槽位|槽位|slot)/i;
|
||||
|
||||
function isCanvasSource(contextPayload?: ContextPayload | null) {
|
||||
return (
|
||||
contextPayload?.source === "canvas" || contextPayload?.source === "realtime-canvas"
|
||||
);
|
||||
}
|
||||
|
||||
function hasSelectedCanvasAgentSlot(contextPayload?: ContextPayload | null) {
|
||||
const context = contextPayload?.canvasAgentContext;
|
||||
if (!context) return false;
|
||||
const selectedNodeIds = new Set(
|
||||
(context.selectedNodeIds || contextPayload?.selectedNodeIds || [])
|
||||
.map((id) => String(id || "").trim())
|
||||
.filter(Boolean)
|
||||
);
|
||||
|
||||
return (context.slots || []).some((slot) => {
|
||||
const slotId = String(slot.nodeId || "").trim();
|
||||
return Boolean(slotId && (slot.isSelected || selectedNodeIds.has(slotId)));
|
||||
});
|
||||
}
|
||||
|
||||
export function shouldBypassCanvasAgentMissingTargetClarification(params: {
|
||||
text: string;
|
||||
contextPayload?: ContextPayload | null;
|
||||
executionMode?: string | null;
|
||||
}) {
|
||||
const executionMode = String(params.executionMode || "") as CanvasAgentExecutionMode;
|
||||
if (executionMode !== "generate_image" && executionMode !== "generate_video") {
|
||||
return {
|
||||
shouldBypass: false,
|
||||
reason: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (!isCanvasSource(params.contextPayload)) {
|
||||
return {
|
||||
shouldBypass: false,
|
||||
reason: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (!hasSelectedCanvasAgentSlot(params.contextPayload)) {
|
||||
return {
|
||||
shouldBypass: false,
|
||||
reason: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
if (!CANVAS_AGENT_SLOT_GENERATION_REGEX.test(String(params.text || ""))) {
|
||||
return {
|
||||
shouldBypass: false,
|
||||
reason: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
shouldBypass: true,
|
||||
reason: "selected_canvas_agent_slot_generation_intent",
|
||||
};
|
||||
}
|
||||
@ -9,6 +9,7 @@ import type {
|
||||
} from "@/lib/ai/context-memory/types";
|
||||
import type { IntentArbiterMode } from "@/lib/ai/runtime/intent-arbiter";
|
||||
import type { TaskBoundaryDecision } from "@/lib/ai/runtime/task-boundary-classifier";
|
||||
import { shouldBypassCanvasAgentMissingTargetClarification } from "@/lib/ai/runtime/canvas-agent-clarification";
|
||||
|
||||
export interface CanvasAgentClarificationGateDecision {
|
||||
shouldClarify: boolean;
|
||||
@ -205,6 +206,12 @@ export function resolveCanvasAgentClarificationGate(params: {
|
||||
const targetResolution = params.targetResolution;
|
||||
const selectedRefs = mediaInput?.selectedImageRefs || [];
|
||||
const candidateRefs = targetResolution?.candidateRefs || mediaInput?.imageRefs || [];
|
||||
const missingTargetClarificationBypass =
|
||||
shouldBypassCanvasAgentMissingTargetClarification({
|
||||
text,
|
||||
contextPayload: params.contextPayload,
|
||||
executionMode: params.executionMode,
|
||||
});
|
||||
|
||||
if (
|
||||
params.executionMode === "edit_image" &&
|
||||
@ -219,7 +226,10 @@ export function resolveCanvasAgentClarificationGate(params: {
|
||||
} satisfies CanvasAgentClarificationGateDecision;
|
||||
}
|
||||
|
||||
if (targetResolution?.needsClarification) {
|
||||
if (
|
||||
targetResolution?.needsClarification &&
|
||||
!missingTargetClarificationBypass.shouldBypass
|
||||
) {
|
||||
return {
|
||||
shouldClarify: true,
|
||||
clarificationMessage: buildMissingTargetClarificationMessage({
|
||||
|
||||
@ -20,6 +20,7 @@ import {
|
||||
buildCanvasAgentAnnotationEditPrompt,
|
||||
hasCanvasAgentAnnotationEditIntent,
|
||||
} from "@/lib/canvas/agent-annotation-prompt";
|
||||
import { shouldBypassCanvasAgentMissingTargetClarification } from "@/lib/ai/runtime/canvas-agent-clarification";
|
||||
import {
|
||||
CANVAS_LOCAL_EDIT_REGION_ANNOTATION_SOURCE,
|
||||
CANVAS_NODE_NOTE_ANNOTATION_SOURCE,
|
||||
@ -1545,6 +1546,84 @@ test.describe("canvas chat pure helpers", () => {
|
||||
expect(context?.focusPolicy).toBe("slot");
|
||||
});
|
||||
|
||||
test("bypasses missing-target clarification for selected agent slot generation", () => {
|
||||
const slotOnlyContext = {
|
||||
source: "canvas" as const,
|
||||
projectId: "11111111-1111-4111-8111-111111111111",
|
||||
selectedNodeIds: ["slot-1"],
|
||||
canvasImageNodes: [],
|
||||
canvasAgentContext: {
|
||||
version: 1 as const,
|
||||
source: "canvas" as const,
|
||||
projectId: "11111111-1111-4111-8111-111111111111",
|
||||
selectedNodeIds: ["slot-1"],
|
||||
selectedNodes: [
|
||||
{
|
||||
nodeId: "slot-1",
|
||||
type: "agent-slot" as const,
|
||||
x: 120,
|
||||
y: 80,
|
||||
width: 320,
|
||||
height: 240,
|
||||
isSelected: true,
|
||||
},
|
||||
],
|
||||
candidateNodes: [],
|
||||
slots: [
|
||||
{
|
||||
nodeId: "slot-1",
|
||||
slotType: "image" as const,
|
||||
x: 120,
|
||||
y: 80,
|
||||
width: 320,
|
||||
height: 240,
|
||||
aspectRatio: "4:3",
|
||||
isSelected: true,
|
||||
fillPolicy: "replace-empty" as const,
|
||||
},
|
||||
],
|
||||
annotations: [],
|
||||
viewport: { x: 0, y: 0, zoom: 1 },
|
||||
focusPolicy: "slot" as const,
|
||||
},
|
||||
};
|
||||
|
||||
expect(
|
||||
shouldBypassCanvasAgentMissingTargetClarification({
|
||||
text: "在这里生成一张电商主图",
|
||||
contextPayload: slotOnlyContext,
|
||||
executionMode: "generate_image",
|
||||
})
|
||||
).toMatchObject({
|
||||
shouldBypass: true,
|
||||
reason: "selected_canvas_agent_slot_generation_intent",
|
||||
});
|
||||
|
||||
expect(
|
||||
shouldBypassCanvasAgentMissingTargetClarification({
|
||||
text: "把这张图背景换白",
|
||||
contextPayload: slotOnlyContext,
|
||||
executionMode: "edit_image",
|
||||
}).shouldBypass
|
||||
).toBe(false);
|
||||
|
||||
expect(
|
||||
shouldBypassCanvasAgentMissingTargetClarification({
|
||||
text: "在这里生成一张电商主图",
|
||||
contextPayload: {
|
||||
...slotOnlyContext,
|
||||
selectedNodeIds: [],
|
||||
canvasAgentContext: {
|
||||
...slotOnlyContext.canvasAgentContext,
|
||||
selectedNodeIds: [],
|
||||
slots: [{ ...slotOnlyContext.canvasAgentContext.slots[0], isSelected: false }],
|
||||
},
|
||||
},
|
||||
executionMode: "generate_image",
|
||||
}).shouldBypass
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
test("keeps view-only canvas agent context without nodes or media", () => {
|
||||
const context = normalizeCanvasAgentContext({
|
||||
source: "canvas",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user