ai-chatbot/lib/ai/runtime/task-boundary-classifier.ts

277 lines
9.4 KiB
TypeScript

import "server-only";
import type {
ConversationActionResolution,
PendingGridConfirmation,
} from "@/lib/ai/conversation-action-resolver";
import { isExplicitPromptOptimizationRequest } from "@/lib/ai/smart-analyzer";
import { isCapabilityQuestionTurnRequest } from "@/lib/ai/runtime/task-boundary-text";
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 isCapabilityQuestionTurnRequest(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;
}