From 0f74cbd6ffc3586eaab162c715ac9e4197be8214 Mon Sep 17 00:00:00 2001 From: 134 Date: Tue, 23 Jun 2026 18:51:21 +0800 Subject: [PATCH] fix: infer canvas agent image counts --- app/(chat)/api/chat/route.ts | 15 +++++- lib/ai/runtime/image-count-intent.ts | 60 ++++++++++++++++++++++++ tests/routes/canvas-chat-helpers.test.ts | 11 +++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 lib/ai/runtime/image-count-intent.ts diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts index 2131655fe..d25c6bb32 100644 --- a/app/(chat)/api/chat/route.ts +++ b/app/(chat)/api/chat/route.ts @@ -38,6 +38,7 @@ import { executeDirectVideoGeneration, generateVideo, } from "@/lib/ai/tools/generate-video"; +import { inferAgentImageCountFromText } from "@/lib/ai/runtime/image-count-intent"; import { getAgentRequiredUploadCount, normalizeAgentPresetModeConfig, @@ -2241,7 +2242,7 @@ export async function POST(request: Request) { !smartAutoNeedsClarification && allowAgentVideoGeneration && Boolean(smartAutoExecution?.shouldApplyVideoPipeline); - const effectiveImageParams = shouldApplyAgentImageParams + const mergedEffectiveImageParams = shouldApplyAgentImageParams ? mergeImageParamsWithAgentRecommendation({ incoming: requestBody.imageParams, recommended: smartAutoRecommendedImageParams, @@ -2249,6 +2250,17 @@ export async function POST(request: Request) { hasUserCustomSettings: hasUserCustomImageParams, }) : requestBody.imageParams; + const inferredAgentImageCount = + shouldApplyAgentImageParams && smartAutoDetectedIntent === "image_gen" + ? inferAgentImageCountFromText(requestTextContent) + : null; + const effectiveImageParams = + inferredAgentImageCount !== null + ? { + ...(mergedEffectiveImageParams || {}), + imageCount: inferredAgentImageCount, + } + : mergedEffectiveImageParams; const effectiveVideoParams = shouldApplyAgentVideoParams ? mergeVideoParamsWithAgentRecommendation({ incoming: requestBody.videoParams, @@ -2273,6 +2285,7 @@ export async function POST(request: Request) { chatApiLogger.info("[Smart Auto Mode] 🎯 Effective image params:", { incoming: requestBody.imageParams, recommended: smartAutoRecommendedImageParams, + inferredImageCount: inferredAgentImageCount, merged: effectiveImageGenerationParams, hasUserCustomImageParams, }); diff --git a/lib/ai/runtime/image-count-intent.ts b/lib/ai/runtime/image-count-intent.ts new file mode 100644 index 000000000..2cad53e2c --- /dev/null +++ b/lib/ai/runtime/image-count-intent.ts @@ -0,0 +1,60 @@ +const CHINESE_NUMBER_VALUES: Record = { + 一: 1, + 两: 2, + 二: 2, + 三: 3, + 四: 4, + 五: 5, + 六: 6, + 七: 7, + 八: 8, + 九: 9, + 十: 10, +}; + +const COUNT_TOKEN = "([一两二三四五六七八九十]|[1-9]\\d?)"; +const COUNT_UNIT = "(?:张|幅|版|个|组)"; + +function normalizeCountText(text: string) { + return String(text || "") + .toLowerCase() + .replace(/\s+/g, "") + .trim(); +} + +function parseCountToken(token?: string | null) { + const value = String(token || "").trim(); + if (!value) return null; + if (/^\d+$/.test(value)) { + const numericValue = Number(value); + return Number.isFinite(numericValue) ? numericValue : null; + } + return CHINESE_NUMBER_VALUES[value] || null; +} + +function clampImageCount(value: number, maxCount: number) { + return Math.min(Math.max(Math.round(value), 1), Math.max(1, maxCount)); +} + +export function inferAgentImageCountFromText(text: string, maxCount = 12) { + const normalizedText = normalizeCountText(text); + if (!normalizedText) return null; + + const explicitCountPatterns = [ + new RegExp( + `(?:再来|再出|再做|再给我|补|多来|继续生成|继续出|继续做)${COUNT_TOKEN}${COUNT_UNIT}?` + ), + new RegExp( + `(?:生成|创作|创建|制作|设计|做|出|画|绘制)${COUNT_TOKEN}${COUNT_UNIT}(?:图|图片|海报|头像|插画|场景|主图|产品图|商品图|电商图|壁纸|封面|logo|图标)?` + ), + new RegExp(`(?:来|出|做)${COUNT_TOKEN}${COUNT_UNIT}`), + ]; + + for (const pattern of explicitCountPatterns) { + const match = normalizedText.match(pattern); + const count = parseCountToken(match?.[1]); + if (count !== null && count > 0) return clampImageCount(count, maxCount); + } + + return null; +} diff --git a/tests/routes/canvas-chat-helpers.test.ts b/tests/routes/canvas-chat-helpers.test.ts index d3f5beb7a..8a054e97f 100644 --- a/tests/routes/canvas-chat-helpers.test.ts +++ b/tests/routes/canvas-chat-helpers.test.ts @@ -40,6 +40,7 @@ import { } from "@/lib/canvas-toolbar-config"; import { shouldBypassCanvasAgentMissingTargetClarification } from "@/lib/ai/runtime/canvas-agent-clarification"; import { hasCanvasAgentRoutingContext } from "@/lib/ai/canvas-agent-routing"; +import { inferAgentImageCountFromText } from "@/lib/ai/runtime/image-count-intent"; import { isAgentModeScopedChatId, isScopedEmptyHistoryRequest, @@ -158,6 +159,16 @@ import type { CanvasAgentContext } from "@/types/canvas/agent-protocol"; import type { ChatMessage } from "@/lib/types"; test.describe("canvas chat pure helpers", () => { + test("infers explicit agent image counts from user text", () => { + expect(inferAgentImageCountFromText("再出三版")).toBe(3); + expect(inferAgentImageCountFromText("基于这张图再来 3 张")).toBe(3); + expect(inferAgentImageCountFromText("生成四张主图")).toBe(4); + expect(inferAgentImageCountFromText("多来两张")).toBe(2); + expect(inferAgentImageCountFromText("生成16:9横版海报")).toBeNull(); + expect(inferAgentImageCountFromText("提升到2K清晰度")).toBeNull(); + expect(inferAgentImageCountFromText("再出30张", 12)).toBe(12); + }); + test("keeps canvas message empty-history access scoped and explicit", () => { const canvasChatId = "11111111-1111-4111-8111-111111111111"; const agentScopedChatId = "11111111-1111-4111-8111-a93e17a60001";