ai-chatbot/lib/ai/conversation-action-resolver.ts
Your Name 5a4e9fd582
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: unify agent workflow protocol
2026-03-13 16:16:09 +08:00

219 lines
6.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import "server-only";
import type { ImageWorkflowMemory } from "@/lib/ai/image-workflow-memory";
import { mentionsImageEditFollowup } from "@/lib/ai/image-edit-subagent";
import { mentionsImageGenerationFollowup } from "@/lib/ai/image-generation-subagent";
import { isPromptCarryForwardRequest } from "@/lib/ai/prompt-memory";
export interface PendingGridConfirmation {
gridType: "grid_2x2" | "grid_3x2" | "grid_3x3";
shotCount: 4 | 6 | 9;
label: string;
originalPrompt: string;
}
export type ConversationActionKind =
| "default"
| "grid_confirmation_followup"
| "active_storyboard_followup"
| "image_edit_followup"
| "image_generation_followup"
| "prompt_carry_forward";
export interface ConversationActionResolution {
kind: ConversationActionKind;
forceAgentConversation: boolean;
shouldKeepSelectedModel: boolean;
effectiveInputText?: string;
usePromptCarryForward: boolean;
}
export function isConfirmedPromptGenerationRequest(text: string): boolean {
const normalized = String(text || "").toLowerCase().replace(/\s+/g, "");
if (!normalized) return false;
const refersPrompt =
/根据这个提示词|按这个提示词|就按这个提示词|按刚才的提示词|按上面的提示词|用这个提示词|用刚才的提示词|按该提示词/.test(
normalized,
);
const wantsImageGeneration =
/生图|生成图片|出图|画出来|生成一张|来一张|开始生成|按此生成|按这个生成/.test(normalized);
return refersPrompt && wantsImageGeneration;
}
export function detectRequestedStoryboardGrid(text: string): PendingGridConfirmation | null {
const normalized = String(text || "").toLowerCase().replace(/\s+/g, "");
if (!normalized) return null;
if (/4宫格|四宫格|2x2|2×2/.test(normalized)) {
return {
gridType: "grid_2x2",
shotCount: 4,
label: "4宫格",
originalPrompt: String(text || "").trim(),
};
}
if (/6宫格|六宫格|3x2|3×2|2x3|2×3/.test(normalized)) {
return {
gridType: "grid_3x2",
shotCount: 6,
label: "6宫格",
originalPrompt: String(text || "").trim(),
};
}
if (/9宫格|九宫格|3x3|3×3/.test(normalized)) {
return {
gridType: "grid_3x3",
shotCount: 9,
label: "9宫格",
originalPrompt: String(text || "").trim(),
};
}
return null;
}
export function isGridConfirmationReply(text: string): boolean {
const normalized = String(text || "").toLowerCase().replace(/\s+/g, "");
if (!normalized) return false;
return /确认|就按这个|开始生成|可以开始了|没问题|对的|就这样/.test(normalized);
}
export function extractPendingGridConfirmationFromWorkingState(
workingState?: {
lastTurnSummary?: unknown;
} | null,
): PendingGridConfirmation | null {
const summary = workingState?.lastTurnSummary;
if (!summary || typeof summary !== "object") return null;
const summaryRecord = summary as Record<string, unknown>;
const pending = summaryRecord.pendingGridConfirmation;
if (!pending || typeof pending !== "object") return null;
const record = pending as Record<string, unknown>;
const gridType = String(record.gridType || "").trim();
const shotCount = Number(record.shotCount || 0);
const label = String(record.label || "").trim();
const originalPrompt = String(record.originalPrompt || "").trim();
if (
!originalPrompt ||
!label ||
!["grid_2x2", "grid_3x2", "grid_3x3"].includes(gridType) ||
![4, 6, 9].includes(shotCount)
) {
return null;
}
return {
gridType: gridType as PendingGridConfirmation["gridType"],
shotCount: shotCount as PendingGridConfirmation["shotCount"],
label,
originalPrompt,
};
}
export function buildGridConfirmationMessage(grid: PendingGridConfirmation): string {
return [
`我看到你希望按 ${grid.label} 来生成分镜图。`,
"为了确保后续自动切片稳定,我会按标准网格、统一格线、固定顺序来出图。",
`请确认是否按 ${grid.label} 标准版式开始生成。`,
"",
"如果确认,请回复:",
`- 确认按${grid.label}生成`,
"",
"如果要修改,也可以直接回复:",
"- 改成 9 宫格",
"- 改成 6 宫格",
"- 改成 4 宫格",
].join("\n");
}
export function resolveConversationAction(params: {
text: string;
pendingGridConfirmation?: PendingGridConfirmation | null;
hasActiveStoryboardTask: boolean;
imageWorkflowMemory?: ImageWorkflowMemory | null;
confirmedPrompt?: string;
hasImageInputForPromptCarryForward: boolean;
}) {
const requestedGrid = detectRequestedStoryboardGrid(params.text);
const shouldContinuePendingGrid =
Boolean(params.pendingGridConfirmation) &&
(isGridConfirmationReply(params.text) || requestedGrid !== null);
if (shouldContinuePendingGrid) {
return {
kind: "grid_confirmation_followup",
forceAgentConversation: true,
shouldKeepSelectedModel: true,
effectiveInputText: params.pendingGridConfirmation?.originalPrompt,
usePromptCarryForward: false,
} satisfies ConversationActionResolution;
}
if (params.hasActiveStoryboardTask) {
return {
kind: "active_storyboard_followup",
forceAgentConversation: true,
shouldKeepSelectedModel: true,
usePromptCarryForward: false,
} satisfies ConversationActionResolution;
}
const hasImageEditFollowup =
params.imageWorkflowMemory?.lastIntent === "image_edit" &&
(mentionsImageEditFollowup(params.text) ||
Boolean(params.imageWorkflowMemory?.lastResultImageUrls?.length));
if (hasImageEditFollowup) {
return {
kind: "image_edit_followup",
forceAgentConversation: true,
shouldKeepSelectedModel: true,
usePromptCarryForward: false,
} satisfies ConversationActionResolution;
}
const promptCarryForwardRequested =
Boolean(params.confirmedPrompt) &&
(isConfirmedPromptGenerationRequest(params.text) ||
isPromptCarryForwardRequest({
text: params.text,
hasImage: params.hasImageInputForPromptCarryForward,
}));
const hasImageGenerationFollowup =
params.imageWorkflowMemory?.lastIntent === "image_gen" &&
(mentionsImageGenerationFollowup(params.text) || promptCarryForwardRequested);
if (hasImageGenerationFollowup) {
return {
kind: "image_generation_followup",
forceAgentConversation: true,
shouldKeepSelectedModel: true,
usePromptCarryForward: promptCarryForwardRequested,
} satisfies ConversationActionResolution;
}
const usePromptCarryForward = promptCarryForwardRequested;
if (usePromptCarryForward) {
return {
kind: "prompt_carry_forward",
forceAgentConversation: false,
shouldKeepSelectedModel: false,
usePromptCarryForward: true,
} satisfies ConversationActionResolution;
}
return {
kind: "default",
forceAgentConversation: false,
shouldKeepSelectedModel: false,
usePromptCarryForward: false,
} satisfies ConversationActionResolution;
}