ai-chatbot/lib/ai/runtime/task-boundary-classifier.ts
Your Name d4e6fb3fc8
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
fix: restore agent routing and isolate canvas context
2026-03-16 17:25:15 +08:00

254 lines
8.5 KiB
TypeScript

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;
return /生图|生成图片|出图|画一张|帮我画|做图|做一张图|重新生成|再来一张|执行生成|开始生成|生成出来|生成一只|生成一个|生成一张|生成一幅|生成一版|帮我生成.*(?:图|图片|海报|头像|插画|场景|小猫|猫咪|人物|产品图)|给我生成.*(?:图|图片|海报|头像|插画|场景|小猫|猫咪|人物|产品图)/.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;
}