import "server-only"; import type { ConversationActionResolution, PendingGridConfirmation, } from "@/lib/ai/conversation-action-resolver"; import { isExplicitPromptOptimizationRequest } from "@/lib/ai/smart-analyzer"; import { detectStoryboardCommandIntent } from "@/lib/video-agent/storyboard-command-interpreter"; export type TaskBoundaryType = | "greeting" | "chat" | "analysis_only" | "prompt_only" | "execution_image" | "execution_video" | "workflow_followup"; export type TaskBoundaryMode = "new" | "continue" | "revise" | "confirm" | "cancel"; export interface TaskBoundaryDecision { taskType: TaskBoundaryType; taskMode: TaskBoundaryMode; shouldUseCanvasContext: boolean; shouldUseHistoryImages: boolean; shouldUseWorkflowMemory: boolean; shouldAllowExecution: boolean; confidence: number; reason: string; } function normalizeCompactText(text: string) { return String(text || "").toLowerCase().replace(/\s+/g, "").trim(); } export function isPureGreetingTurnRequest(text: string): boolean { const normalized = normalizeCompactText(text); if (!normalized) return false; return /^(你好|您好|hello|hi|嗨|哈喽|在吗|早上好|中午好|下午好|晚上好|深夜好|谢谢|感谢|收到|好的|ok|okay|明白了|了解了)$/.test( normalized, ); } function isLikelyVideoExecutionRequest(text: string): boolean { const normalized = normalizeCompactText(text); if (!normalized) return false; return /图生视频|文生视频|视频生成|生成视频|做视频|做成视频|开始生成视频|让.*动起来|变成视频|视频/.test( normalized, ); } function isLikelyImageExecutionRequest(text: string): boolean { const normalized = normalizeCompactText(text); if (!normalized) return false; if (/不要生图|先别生图|不需要生图|不要生成|别生成|只要提示词|仅给提示词|先给提示词/.test(normalized)) { 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( normalized, ) || createVisualTarget.test(normalized) || drawSubject.test(normalized) || continueVariants.test(normalized) || /参考.*生成|按这个风格生成|以这张图为灵感生成|仿照.*生成|同风格再来|同样风格再来/.test( normalized, ) ); } function isLikelyChatOnlyRequest(text: string): boolean { const normalized = normalizeCompactText(text); if (!normalized) return false; return /你能做什么|你会什么|你可以做什么|你能帮我做什么|你能干什么|你可以帮我做什么|介绍一下你自己|介绍一下自己|说说你的能力|你有哪些功能|你有什么功能|怎么用|怎么玩|如何使用|帮助说明|帮助手册|功能介绍/.test( normalized, ); } function isLikelyAnalysisOnlyRequest(text: string, hasImageContext: boolean): boolean { if (!hasImageContext) return false; const normalized = normalizeCompactText(text); if (!normalized) return false; return /分析|看图|看看|识别|拆解|提炼|整理|归纳|总结|描述|解释|判断|帮我看看|帮我分析|帮我判断|是什么|适合吗|怎么样|评价/.test( normalized, ); } function resolveStoryboardTaskMode(params: { text: string; hasPendingGridConfirmation: boolean; hasActiveStoryboardTask: boolean; }): TaskBoundaryMode { if (!params.hasPendingGridConfirmation && !params.hasActiveStoryboardTask) { return "continue"; } const intent = detectStoryboardCommandIntent(params.text); switch (intent) { case "confirm": return "confirm"; case "revise": return "revise"; case "cancel": return "cancel"; case "unclear": default: return "continue"; } } export function classifyTaskBoundary(params: { text: string; actionResolution: ConversationActionResolution; hasCurrentImageInput: boolean; hasCanvasSelection: boolean; pendingGridConfirmation?: PendingGridConfirmation | null; hasActiveStoryboardTask: boolean; }) { const hasWorkflowImageContext = params.actionResolution.kind === "image_edit_followup" || params.actionResolution.kind === "image_generation_followup" || params.actionResolution.kind === "prompt_carry_forward"; const hasImageContext = params.hasCurrentImageInput || params.hasCanvasSelection || hasWorkflowImageContext; if (isPureGreetingTurnRequest(params.text) && !params.hasCurrentImageInput) { return { taskType: "greeting", taskMode: "new", shouldUseCanvasContext: false, shouldUseHistoryImages: false, shouldUseWorkflowMemory: false, shouldAllowExecution: false, confidence: 0.99, reason: "pure_greeting_turn", } satisfies TaskBoundaryDecision; } if (isExplicitPromptOptimizationRequest(params.text)) { return { taskType: "prompt_only", taskMode: "new", shouldUseCanvasContext: !params.hasCurrentImageInput && params.hasCanvasSelection, shouldUseHistoryImages: !params.hasCurrentImageInput && !params.hasCanvasSelection, shouldUseWorkflowMemory: false, shouldAllowExecution: false, confidence: 0.97, reason: "explicit_prompt_only_request", } satisfies TaskBoundaryDecision; } if (isLikelyAnalysisOnlyRequest(params.text, hasImageContext)) { return { taskType: "analysis_only", taskMode: "new", shouldUseCanvasContext: params.hasCanvasSelection, shouldUseHistoryImages: !params.hasCurrentImageInput && !params.hasCanvasSelection, shouldUseWorkflowMemory: false, shouldAllowExecution: false, confidence: 0.9, reason: "analysis_only_request", } satisfies TaskBoundaryDecision; } if (isLikelyChatOnlyRequest(params.text)) { return { taskType: "chat", taskMode: "new", shouldUseCanvasContext: false, shouldUseHistoryImages: false, shouldUseWorkflowMemory: false, shouldAllowExecution: false, confidence: 0.96, reason: "chat_only_request", } satisfies TaskBoundaryDecision; } switch (params.actionResolution.kind) { case "grid_confirmation_followup": case "active_storyboard_followup": return { taskType: "workflow_followup", taskMode: resolveStoryboardTaskMode({ text: params.text, hasPendingGridConfirmation: Boolean(params.pendingGridConfirmation), hasActiveStoryboardTask: params.hasActiveStoryboardTask, }), shouldUseCanvasContext: params.hasCanvasSelection, shouldUseHistoryImages: !params.hasCurrentImageInput, shouldUseWorkflowMemory: true, shouldAllowExecution: true, confidence: 0.95, reason: params.actionResolution.kind, } satisfies TaskBoundaryDecision; case "image_edit_followup": case "image_generation_followup": return { taskType: "workflow_followup", taskMode: "continue", shouldUseCanvasContext: params.hasCanvasSelection, shouldUseHistoryImages: !params.hasCurrentImageInput, shouldUseWorkflowMemory: true, shouldAllowExecution: true, confidence: 0.92, reason: params.actionResolution.kind, } satisfies TaskBoundaryDecision; case "prompt_carry_forward": return { taskType: "execution_image", taskMode: "continue", shouldUseCanvasContext: params.hasCanvasSelection, shouldUseHistoryImages: !params.hasCurrentImageInput, shouldUseWorkflowMemory: true, shouldAllowExecution: true, confidence: 0.93, reason: "prompt_carry_forward_execution", } satisfies TaskBoundaryDecision; case "default": default: break; } if (isLikelyVideoExecutionRequest(params.text)) { return { taskType: "execution_video", taskMode: "new", shouldUseCanvasContext: params.hasCanvasSelection, shouldUseHistoryImages: false, shouldUseWorkflowMemory: false, shouldAllowExecution: true, confidence: 0.88, reason: "explicit_video_execution_request", } satisfies TaskBoundaryDecision; } if (isLikelyImageExecutionRequest(params.text)) { return { taskType: "execution_image", taskMode: "new", shouldUseCanvasContext: params.hasCanvasSelection, shouldUseHistoryImages: false, shouldUseWorkflowMemory: false, shouldAllowExecution: true, confidence: 0.87, reason: "explicit_image_execution_request", } satisfies TaskBoundaryDecision; } return { taskType: "chat", taskMode: "new", shouldUseCanvasContext: false, shouldUseHistoryImages: false, shouldUseWorkflowMemory: false, shouldAllowExecution: false, confidence: 0.7, reason: "default_chat_turn", } satisfies TaskBoundaryDecision; }