diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts index d25c6bb32..b92c19b07 100644 --- a/app/(chat)/api/chat/route.ts +++ b/app/(chat)/api/chat/route.ts @@ -94,6 +94,7 @@ import { getCanvasAgentOperationErrorMessage, normalizeCanvasAgentOperationErrorCode, } from "@/lib/canvas/agent-operation-errors"; +import { shouldBypassCanvasImagePointsPrecheckForAgentControl } from "@/lib/canvas/agent-client-intent"; import { applyCanvasOperationPlan } from "@/lib/canvas/agent-mutations"; import { isProductionEnvironment } from "@/lib/constants"; import type { @@ -892,6 +893,22 @@ export async function POST(request: Request) { const canvasAgentFeatures = getCanvasAgentFeatureFlags(); const canApplyCanvasAgentMutation = canApplyCanvasAgentMutations(canvasAgentFeatures); + const currentMessageAttachmentCount = message.parts.filter( + (part) => part.type === "file" + ).length; + const hasCanvasControlIntentFastPath = + composerMode !== "agent" && + (contextPayload?.source === "canvas" || + contextPayload?.source === "realtime-canvas" || + Boolean(requestCanvasAgentContext)) && + shouldBypassCanvasImagePointsPrecheckForAgentControl({ + text: requestTextContent, + activeComposerMode: composerMode, + attachmentCount: currentMessageAttachmentCount, + hasSkillTag: Boolean(agentPresetId || hiddenPrompt), + hasWorkflowContext: Boolean(agentModePayload), + agentEnabled: true, + }); if (requestCanvasAgentContext) { chatApiLogger.info("[CanvasAgentContext] Normalized request context:", { source: requestCanvasAgentContext.source, @@ -3195,7 +3212,7 @@ export async function POST(request: Request) { const resolveCanvasViewOperationPlan = (): CanvasViewOperationPlan | null => { if (!canvasAgentFeatures.viewOperationEnabled) return null; - if (composerMode !== "agent") return null; + if (composerMode !== "agent" && !hasCanvasControlIntentFastPath) return null; if (contextPayload?.source !== "canvas" || !contextPayload.projectId) return null; if (!requestCanvasAgentContext) return null; @@ -3331,7 +3348,7 @@ export async function POST(request: Request) { const resolveCanvasLinkOperationPlan = (): CanvasOperationPlan | null => { if (!canApplyCanvasAgentMutation) return null; - if (composerMode !== "agent") return null; + if (composerMode !== "agent" && !hasCanvasControlIntentFastPath) return null; if (contextPayload?.source !== "canvas" || !contextPayload.projectId) return null; if (!requestCanvasAgentContext) return null; @@ -3401,7 +3418,7 @@ export async function POST(request: Request) { const resolveCanvasTextOperationPlan = (): CanvasOperationPlan | null => { if (!canApplyCanvasAgentMutation) return null; - if (composerMode !== "agent") return null; + if (composerMode !== "agent" && !hasCanvasControlIntentFastPath) return null; if (contextPayload?.source !== "canvas" || !contextPayload.projectId) return null; if (!requestCanvasAgentContext) return null; @@ -3446,7 +3463,7 @@ export async function POST(request: Request) { const resolveCanvasExplicitOperationPlan = (): CanvasAgentExplicitOperationPlan | null => { if (!canApplyCanvasAgentMutation) return null; - if (composerMode !== "agent") return null; + if (composerMode !== "agent" && !hasCanvasControlIntentFastPath) return null; if (contextPayload?.source !== "canvas" || !contextPayload.projectId) return null; if (!requestCanvasAgentContext) return null; @@ -3464,7 +3481,7 @@ export async function POST(request: Request) { const resolveCanvasModelPlannerOutcome = async (): Promise => { - if (composerMode !== "agent") return null; + if (composerMode !== "agent" && !hasCanvasControlIntentFastPath) return null; if (contextPayload?.source !== "canvas" || !contextPayload.projectId) return null; if (!requestCanvasAgentContext) return null; if ( @@ -4688,6 +4705,53 @@ export async function POST(request: Request) { return createSseResponse(canvasModelPlannerStream); } + if (hasCanvasControlIntentFastPath) { + const canvasControlFallbackReply = + "我识别到这是画布控制指令,但当前没有找到可执行的画布目标。请先选中画布对象,或换成“放大画布 / 缩小看全局 / 聚焦刚生成结果”这类明确视角指令。"; + const canvasControlFallbackStream = createUIMessageStream({ + execute: async ({ writer }) => { + const messageId = generateUUID(); + writeAgentProgressChunk( + writer, + { + stage: "clarify", + currentStep: "finalize", + title: "需要明确画布目标", + message: canvasControlFallbackReply, + toolName: "canvasOperation", + capability: "clarify_canvas_control", + }, + "canvas-control-fast-path:clarify", + ); + writeAssistantTextChunk( + writer, + canvasControlFallbackReply, + "canvas-control-fast-path:reply", + messageId, + ); + await saveMessages({ + messages: [ + { + id: messageId, + chatId: id, + role: "assistant", + parts: [{ type: "text", text: canvasControlFallbackReply }], + attachments: [], + createdAt: new Date(), + videoStorageStatus: null, + temporaryVideoUrl: null, + permanentVideoUrl: null, + archivedAt: null, + videoMetadata: null, + }, + ], + }); + }, + }); + + return createSseResponse(canvasControlFallbackStream); + } + const promptOnlySourceImageUrls = requestResolvedMediaInput.currentMessageImageUrls.length > 0 ? requestResolvedMediaInput.currentMessageImageUrls.slice(0, 4) @@ -8377,7 +8441,7 @@ export async function POST(request: Request) { resolution: String(effectiveVideoParams?.resolution || "").trim() || undefined, }); - if (isImageGenerationModel) { + if (isImageGenerationModel && !hasCanvasControlIntentFastPath) { const quota = await checkUserImageQuota(session.user.id); if (!quota.allowed) { @@ -8441,7 +8505,7 @@ export async function POST(request: Request) { : undefined ); - if (isVideoGenerationModel) { + if (isVideoGenerationModel && !hasCanvasControlIntentFastPath) { const pointsConfig = await getPointsSystemConfig(); const normalizedVideoUnitCost = Number.isFinite(modelCreditCostPerVideo) ? Math.max(0, Math.round(modelCreditCostPerVideo))