import "server-only"; import type { ConversationActionResolution } from "@/lib/ai/conversation-action-resolver"; import type { ResolvedTargetAssets } from "@/lib/ai/context-memory/types"; import type { AnalysisResult } from "@/lib/ai/smart-analyzer"; import type { TaskBoundaryDecision } from "@/lib/ai/runtime/task-boundary-classifier"; import { hasDirectAgentLocalEditIntent } from "@/lib/ai/runtime/local-edit-options"; import { hasCanvasAgentActionableImageEditIntent, inferCanvasAgentImageEditCapability, } from "@/lib/ai/runtime/canvas-agent-kernel"; export type IntentArbiterMode = | "respond" | "clarify" | "analyze_image" | "generate_image" | "edit_image" | "generate_video"; export type IntentArbiterPreferredCapability = | "respond" | "analyze" | "generic_edit" | "remove_background" | "remove_watermark" | "enhance" | "extend" | "resize" | "angle_variant" | "local_edit" | "box_cutout" | "generate_image" | "generate_video" | "publish_canvas"; export interface IntentArbiterExecution { mode: IntentArbiterMode; routeIntent: AnalysisResult["intent"]; routeSubIntent?: string; targetSource: ResolvedTargetAssets["resolutionMode"]; preferredCapability: IntentArbiterPreferredCapability; shouldUseStructuredReply: boolean; shouldOptimizePrompt: boolean; shouldApplyImagePipeline: boolean; shouldApplyVideoPipeline: boolean; shouldStayConversational: boolean; shouldPublishToCanvas: boolean; toolCalls: Array<{ name: string; reason?: string; args?: Record }>; availableTools: string[]; } export interface IntentArbiterResult { analysis: AnalysisResult; execution: IntentArbiterExecution; overridden: boolean; reason: string; friendlyMessage?: string; } function normalizeCompactText(text: string): string { return String(text || "").toLowerCase().replace(/\s+/g, "").trim(); } function isLikelyAdvisoryOrPlanningRequest(text: string): boolean { const normalizedText = String(text || "").toLowerCase(); return /怎么|如何|为什么|是否|可不可以|能不能|行不行|建议|思路|方案|流程|步骤|教程|推荐|对比|区别|差别|优缺点|评估|分析|判断|解释|讲讲|科普|适合吗|可行吗|值不值得|要不要|帮我看看|帮我分析|帮我判断/.test( normalizedText, ); } function isExplicitVideoGenerationRequest(text: string): boolean { const normalizedText = normalizeCompactText(text); if (!normalizedText) return false; const hasStrongVideoMarkers = /图生视频|文生视频|首帧|尾帧|分镜|运镜|视频生成/.test(normalizedText); if (hasStrongVideoMarkers) return true; if (isLikelyAdvisoryOrPlanningRequest(text)) return false; return ( /让.+动起来/.test(normalizedText) || (/(生成|制作|做|创建|输出|渲染|合成|出|做成|变成|转成)/.test(normalizedText) && /(视频|动画|短片|片子|动起来|会动)/.test(normalizedText)) ); } function isExplicitImageGenerationRequest(text: string): boolean { const normalizedText = normalizeCompactText(text); if (!normalizedText || isLikelyAdvisoryOrPlanningRequest(text)) return false; if (/不要生图|先别生图|不需要生图|不要生成|别生成|只要提示词|仅给提示词|先给提示词/.test(normalizedText)) { return false; } const countToken = "(?:一|两|二|三|四|五|六|七|八|九|十|[1-9]\\d?)"; const visualTarget = "(?:图|图片|海报|头像|插画|场景|主图|产品图|商品图|电商图|壁纸|封面|logo|图标)"; const createVisualTarget = new RegExp( `(?:生成|创作|创建|制作|设计|做|出|画(?!布)|绘制)${countToken}?张?.{0,32}${visualTarget}`, ); const drawSubject = new RegExp( `(?:画(?!布)|绘制|生成|创作|创建)${countToken}?(?:只|个|位|幅|张)?.{0,24}(?:小猫|猫咪|猫|狗|人物|角色|ip|头像|logo|图标|插画)`, ); const continueVariants = new RegExp( `(?:再来|再出|再做|再给我|补|多来|继续生成|继续出)${countToken}?张`, ); return ( /重新生成|生成新图|新建图片|创作一张|生成一张|画一张|给我画|帮我画|出一张|做一张图|做一张海报|创建一张图片|生成一只|生成一个|生成一幅|生成一版|生成一套|生成一组|生图|出图/.test( normalizedText, ) || createVisualTarget.test(normalizedText) || drawSubject.test(normalizedText) || continueVariants.test(normalizedText) || /参考.*生成|按这个风格生成|以这张图为灵感生成|仿照.*生成|同风格再来|同样风格再来/.test( normalizedText, ) || /帮我生成.*(?:图|图片|海报|头像|插画|场景|小猫|猫咪|人物|产品图)|给我生成.*(?:图|图片|海报|头像|插画|场景|小猫|猫咪|人物|产品图)/.test( normalizedText, ) ); } function isLikelyImageEditRequest(text: string): boolean { return hasDirectAgentLocalEditIntent(text) || hasCanvasAgentActionableImageEditIntent(text); } function inferDefaultImageEditSubIntent(text: string): string { const inferredCapability = inferCanvasAgentImageEditCapability(text); if ( inferredCapability && inferredCapability !== "generic_edit" && inferredCapability !== "generate_image" && inferredCapability !== "generate_video" && inferredCapability !== "analyze" ) { return inferredCapability; } if (hasDirectAgentLocalEditIntent(text)) return "local_edit"; return "enhance"; } function mapIntentToMode( intent: AnalysisResult["intent"], needsClarification?: boolean, ): IntentArbiterMode { if (needsClarification) return "clarify"; switch (intent) { case "image_edit": return "edit_image"; case "image_gen": return "generate_image"; case "video_gen": return "generate_video"; case "vision": return "analyze_image"; case "chat": default: return "respond"; } } function mapPreferredCapability( intent: AnalysisResult["intent"], subIntent?: string, ): IntentArbiterPreferredCapability { if (intent === "video_gen") return "generate_video"; if (intent === "image_gen") return "generate_image"; if (intent === "vision") return "analyze"; if (intent === "chat") return "respond"; switch (subIntent) { case "remove_background": return "remove_background"; case "remove_watermark": return "remove_watermark"; case "extend": return "extend"; case "resize": return "resize"; case "angle_variant": case "generate_angle_variant": return "angle_variant"; case "local_edit": case "inpaint_region": return "local_edit"; case "box_cutout": return "box_cutout"; case "enhance": return "enhance"; default: return "generic_edit"; } } function buildExecutionFromAnalysis(params: { analysis: AnalysisResult; execution?: IntentArbiterExecution | null; targetSource: ResolvedTargetAssets["resolutionMode"]; }): IntentArbiterExecution { const mode = mapIntentToMode(params.analysis.intent, params.analysis.needsClarification); const shouldKeepPlannedToolCalls = params.execution?.mode === mode && params.execution?.routeIntent === params.analysis.intent; return { mode, routeIntent: params.analysis.intent, routeSubIntent: params.analysis.subIntent, targetSource: params.targetSource, preferredCapability: mapPreferredCapability(params.analysis.intent, params.analysis.subIntent), shouldUseStructuredReply: params.analysis.intent === "image_edit" || params.analysis.intent === "image_gen" || params.analysis.intent === "video_gen", shouldOptimizePrompt: params.analysis.intent === "image_edit" || params.analysis.intent === "image_gen" || params.analysis.intent === "video_gen", shouldApplyImagePipeline: params.analysis.intent === "image_edit" || params.analysis.intent === "image_gen", shouldApplyVideoPipeline: params.analysis.intent === "video_gen", shouldStayConversational: mode === "respond" || mode === "analyze_image" || mode === "clarify", shouldPublishToCanvas: params.execution?.shouldPublishToCanvas || false, toolCalls: shouldKeepPlannedToolCalls ? params.execution?.toolCalls || [] : [], availableTools: params.execution?.availableTools || [], }; } function buildDefaultFriendlyMessage( intent: AnalysisResult["intent"], params: { hasImage: boolean; text: string }, ) { switch (intent) { case "image_gen": return params.hasImage ? "收到,我会结合你提供的参考图直接执行生图。" : "好的,我来直接帮你生成这张图。"; case "image_edit": return params.hasImage ? "收到,我会按你的目标直接处理这张图。" : "收到,我来继续执行这轮图片处理。"; case "video_gen": return params.hasImage ? "收到,我会结合这轮图片先规划并执行视频生成。" : "收到,我先按你的需求规划并执行视频生成。"; case "vision": return params.hasImage ? "收到,我先分析这张图。" : "收到,我先按你的问题做分析。"; case "chat": default: return undefined; } } function resolvePolicyIntent(params: { text: string; hasImage: boolean; hasHistoryImage: boolean; taskBoundaryDecision: TaskBoundaryDecision; actionResolution: ConversationActionResolution; analysis: AnalysisResult; }): { intent: AnalysisResult["intent"]; subIntent?: string; reason: string } { const explicitImageGen = isExplicitImageGenerationRequest(params.text); const explicitVideoGen = isExplicitVideoGenerationRequest(params.text); const likelyEdit = isLikelyImageEditRequest(params.text); const hasUsableImage = params.hasImage || params.hasHistoryImage; if (explicitVideoGen) { return { intent: "video_gen", reason: "explicit_video_generation_request", }; } if (hasUsableImage && likelyEdit) { return { intent: "image_edit", subIntent: params.analysis.subIntent || inferDefaultImageEditSubIntent(params.text), reason: "image_edit_policy_override", }; } switch (params.actionResolution.kind) { case "image_edit_followup": return { intent: "image_edit", subIntent: params.analysis.subIntent || inferDefaultImageEditSubIntent(params.text), reason: "conversation_action_image_edit_followup", }; case "image_generation_followup": case "prompt_carry_forward": return { intent: "image_gen", reason: "conversation_action_image_generation_followup", }; default: break; } switch (params.taskBoundaryDecision.taskType) { case "prompt_only": case "analysis_only": return { intent: "vision", reason: `task_boundary_${params.taskBoundaryDecision.taskType}`, }; case "execution_video": return { intent: "video_gen", reason: "task_boundary_execution_video", }; case "execution_image": return { intent: "image_gen", reason: "task_boundary_execution_image_generate", }; case "workflow_followup": if (hasUsableImage && likelyEdit) { return { intent: "image_edit", subIntent: params.analysis.subIntent || inferDefaultImageEditSubIntent(params.text), reason: "task_boundary_workflow_followup_edit", }; } if (explicitImageGen) { return { intent: "image_gen", reason: "task_boundary_workflow_followup_generate", }; } break; default: break; } if (explicitImageGen) { return { intent: "image_gen", reason: "explicit_image_generation_request", }; } return { intent: params.analysis.intent, subIntent: params.analysis.intent === "image_edit" ? params.analysis.subIntent || inferDefaultImageEditSubIntent(params.text) : params.analysis.subIntent, reason: "keep_ai_analysis", }; } export function arbitrateAgentIntent(params: { text: string; hasImage: boolean; hasHistoryImage: boolean; actionResolution: ConversationActionResolution; taskBoundaryDecision: TaskBoundaryDecision; analysis: AnalysisResult; execution?: IntentArbiterExecution | null; targetSource: ResolvedTargetAssets["resolutionMode"]; }): IntentArbiterResult { const resolved = resolvePolicyIntent(params); const nextAnalysis: AnalysisResult = { ...params.analysis, intent: resolved.intent, subIntent: resolved.intent === "image_edit" ? resolved.subIntent || inferDefaultImageEditSubIntent(params.text) : undefined, confidence: resolved.reason === "keep_ai_analysis" ? params.analysis.confidence : Math.max(params.analysis.confidence, 0.84), needsClarification: resolved.reason === "keep_ai_analysis" ? Boolean(params.analysis.needsClarification) : false, clarifyingQuestions: resolved.reason === "keep_ai_analysis" && params.analysis.needsClarification ? params.analysis.clarifyingQuestions || [] : [], }; const nextExecution = buildExecutionFromAnalysis({ analysis: nextAnalysis, execution: params.execution, targetSource: params.targetSource, }); const overridden = nextAnalysis.intent !== params.analysis.intent || nextAnalysis.subIntent !== params.analysis.subIntent || nextExecution.mode !== params.execution?.mode || nextExecution.routeIntent !== params.execution?.routeIntent; const friendlyMessage = overridden || !params.analysis.conversationalReply?.trim() ? buildDefaultFriendlyMessage(nextAnalysis.intent, { hasImage: params.hasImage || params.hasHistoryImage, text: params.text, }) : undefined; return { analysis: friendlyMessage ? { ...nextAnalysis, conversationalReply: friendlyMessage, } : nextAnalysis, execution: nextExecution, overridden, reason: resolved.reason, friendlyMessage, }; }