feat: formalize agent conversation state protocol
This commit is contained in:
parent
e7812060b8
commit
159137b3d7
@ -79,7 +79,6 @@ import {
|
||||
getAccessibleAgentPresetById,
|
||||
getConversationWorkingStateByChatId,
|
||||
getMediaAssetsByUserAndUrls,
|
||||
upsertConversationWorkingState,
|
||||
upsertMediaAsset,
|
||||
} from "@/lib/db/queries";
|
||||
import { db } from "@/lib/db";
|
||||
@ -93,13 +92,25 @@ import { generateCanvasImage } from "@/lib/canvas/generate-image";
|
||||
import {
|
||||
buildGridConfirmationMessage,
|
||||
detectRequestedStoryboardGrid,
|
||||
extractPendingGridConfirmationFromWorkingState,
|
||||
isGridConfirmationReply,
|
||||
type PendingGridConfirmation,
|
||||
resolveConversationAction,
|
||||
} from "@/lib/ai/conversation-action-resolver";
|
||||
import { composeConversationMemory } from "@/lib/ai/conversation-memory-composer";
|
||||
import { buildConversationThreadState } from "@/lib/ai/conversation-thread-state-builder";
|
||||
import {
|
||||
bootstrapConversationStateProtocol,
|
||||
composeConversationStateProtocol,
|
||||
persistAgentPresetTurn,
|
||||
persistConversationStateProtocol,
|
||||
setConversationStateProtocolEventObserver,
|
||||
persistResolvedTargetTurn,
|
||||
persistGridConfirmationTurn,
|
||||
persistImageWorkflowTurn,
|
||||
persistPromptOnlyTurn,
|
||||
resolveGridConfirmationRequest,
|
||||
resolveTargetExecutionOverride,
|
||||
persistStoryboardConfirmedTurn,
|
||||
persistStoryboardAssistantTurn,
|
||||
persistVideoWorkflowTurn,
|
||||
} from "@/lib/ai/conversation-state-protocol";
|
||||
import { resolveChatMediaInput } from "@/lib/ai/context-memory/media-input";
|
||||
import { resolveTargetAssets } from "@/lib/ai/context-memory/target-resolution";
|
||||
import type {
|
||||
@ -114,7 +125,6 @@ import {
|
||||
} from "@/lib/ai/image-workflow-memory";
|
||||
import {
|
||||
buildPromptMemoryUpdate,
|
||||
extractPromptMemoryFromWorkingState,
|
||||
} from "@/lib/ai/prompt-memory";
|
||||
import { uploadFile } from "@/lib/storage/service";
|
||||
import { getVideoStoryboardConfig } from "@/lib/video-agent/config";
|
||||
@ -403,6 +413,16 @@ function writeStructuredDataChunk(
|
||||
);
|
||||
}
|
||||
|
||||
function writeProtocolDebugChunk(
|
||||
writer: StreamWriter,
|
||||
enabled: boolean,
|
||||
data: unknown,
|
||||
label: string,
|
||||
) {
|
||||
if (!enabled) return;
|
||||
writeStructuredDataChunk(writer, "data-protocolDebug", data, label);
|
||||
}
|
||||
|
||||
function normalizeImageLoadingProviderType(providerType?: string | null): string {
|
||||
const normalized = String(providerType || "").trim().toLowerCase();
|
||||
if (normalized === "gemini-image" || normalized === "huanke-gemini") {
|
||||
@ -1772,38 +1792,6 @@ function buildPromptOnlyReply(params: {
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
function extractConfirmedPromptFromWorkingState(
|
||||
workingState?: {
|
||||
lastTurnSummary?: unknown;
|
||||
} | null,
|
||||
): string | undefined {
|
||||
const summary = workingState?.lastTurnSummary;
|
||||
if (!summary || typeof summary !== "object") return undefined;
|
||||
const summaryRecord = summary as Record<string, unknown>;
|
||||
|
||||
const resolutionMode =
|
||||
typeof summaryRecord.resolutionMode === "string" ? summaryRecord.resolutionMode.trim() : "";
|
||||
const optimizedPrompt =
|
||||
typeof summaryRecord.optimizedPrompt === "string" ? summaryRecord.optimizedPrompt.trim() : "";
|
||||
|
||||
if (resolutionMode !== "prompt_only" || !optimizedPrompt) return undefined;
|
||||
return optimizedPrompt;
|
||||
}
|
||||
|
||||
function hasStructuredAgentSections(message?: string): boolean {
|
||||
if (!message) return false;
|
||||
return /(?:###\s*需求理解|【\s*需求理解\s*】|###\s*图片分析|【\s*图片分析\s*】|###\s*执行计划|【\s*执行计划\s*】|###\s*优化后提示词|【\s*优化后提示词\s*】)/.test(
|
||||
@ -2571,16 +2559,11 @@ async function saveGeneratedImages(
|
||||
|
||||
if (savedOriginalUrls.length > 0) {
|
||||
try {
|
||||
const existingWorkingState = await getConversationWorkingStateByChatId({
|
||||
chatId,
|
||||
}).catch(() => null);
|
||||
const existingSummary =
|
||||
existingWorkingState?.lastTurnSummary &&
|
||||
typeof existingWorkingState.lastTurnSummary === "object"
|
||||
? (existingWorkingState.lastTurnSummary as Record<string, unknown>)
|
||||
: {};
|
||||
const existingWorkingState = await getConversationWorkingStateByChatId({ chatId }).catch(
|
||||
() => null
|
||||
);
|
||||
const existingImageWorkflowMemory = extractImageWorkflowMemory(existingWorkingState);
|
||||
await upsertConversationWorkingState({
|
||||
await persistConversationStateProtocol({
|
||||
userId,
|
||||
chatId,
|
||||
currentFocusAssetIds: savedAssetIds,
|
||||
@ -2590,8 +2573,7 @@ async function saveGeneratedImages(
|
||||
lastResolvedTargetAssetIds: savedAssetIds,
|
||||
lastResolvedTargetImageUrls: savedOriginalUrls,
|
||||
lastMessageId: messageId,
|
||||
lastTurnSummary: {
|
||||
...existingSummary,
|
||||
lastTurnSummaryPatch: {
|
||||
imageWorkflowMemory: buildImageWorkflowMemoryUpdate({
|
||||
existing: existingImageWorkflowMemory,
|
||||
lastIntent: options?.relationType === "edited_from" ? "image_edit" : "image_gen",
|
||||
@ -2926,6 +2908,7 @@ export async function POST(request: Request) {
|
||||
autoContext,
|
||||
contextPayload,
|
||||
deepThinkingEnabled,
|
||||
debugProtocol,
|
||||
}: {
|
||||
id: string;
|
||||
message: ChatMessage;
|
||||
@ -2936,6 +2919,7 @@ export async function POST(request: Request) {
|
||||
autoContext?: string;
|
||||
contextPayload?: PostRequestBody["contextPayload"];
|
||||
deepThinkingEnabled?: boolean;
|
||||
debugProtocol?: boolean;
|
||||
} = requestBody;
|
||||
const requestStartedAt = Date.now();
|
||||
const requestOrigin = (() => {
|
||||
@ -3002,6 +2986,7 @@ export async function POST(request: Request) {
|
||||
let storyboardPreferenceMemory = null;
|
||||
let imageWorkflowMemory = null;
|
||||
let reusablePromptMemory = null;
|
||||
let confirmedPromptForConversationState: string | undefined;
|
||||
let smartAutoHistoryMessagesFromDb: DBMessage[] = [];
|
||||
let preferredAgentModelId: string | undefined;
|
||||
let pendingGridConfirmation:
|
||||
@ -3011,6 +2996,7 @@ export async function POST(request: Request) {
|
||||
const deepThinkingOn = deepThinkingEnabled !== false;
|
||||
let shouldRunAgentConversation = finalModelId === "auto";
|
||||
let shouldKeepSelectedModel = false;
|
||||
const pendingProtocolDebugEvents: Array<{ phase: string; payload: Record<string, unknown> }> = [];
|
||||
const requestedModelId = selectedChatModel;
|
||||
let requestedModelProviderType: string | undefined;
|
||||
let requestedModelIsFixedVisual = false;
|
||||
@ -3128,25 +3114,40 @@ export async function POST(request: Request) {
|
||||
);
|
||||
return null;
|
||||
});
|
||||
storyboardPreferenceMemory = extractStoryboardPreferenceMemory(requestWorkingState);
|
||||
imageWorkflowMemory = extractImageWorkflowMemory(requestWorkingState);
|
||||
reusablePromptMemory = extractPromptMemoryFromWorkingState(requestWorkingState);
|
||||
pendingGridConfirmation =
|
||||
extractPendingGridConfirmationFromWorkingState(requestWorkingState);
|
||||
const hasImageInputForConversationAction = message.parts.some((part: any) =>
|
||||
part?.type === "file" || part?.type === "image" || part?.type === "image_url"
|
||||
);
|
||||
const preloadedConfirmedPrompt =
|
||||
extractConfirmedPromptFromWorkingState(requestWorkingState) ||
|
||||
reusablePromptMemory?.optimizedPrompt;
|
||||
const resolvedConversationAction = resolveConversationAction({
|
||||
const conversationStateBootstrap = bootstrapConversationStateProtocol({
|
||||
text: requestTextContent,
|
||||
pendingGridConfirmation,
|
||||
hasActiveStoryboardTask: Boolean(activeStoryboardTaskForChat?.task?.id),
|
||||
imageWorkflowMemory,
|
||||
confirmedPrompt: preloadedConfirmedPrompt,
|
||||
workingState: requestWorkingState,
|
||||
activeStoryboardTask: activeStoryboardTaskForChat,
|
||||
hasImageInputForPromptCarryForward: hasImageInputForConversationAction,
|
||||
});
|
||||
console.log("[ConversationStateProtocol] Bootstrap:", {
|
||||
action: conversationStateBootstrap.actionResolution.kind,
|
||||
hasPromptMemory: Boolean(conversationStateBootstrap.promptMemory?.optimizedPrompt),
|
||||
hasImageWorkflowMemory: Boolean(conversationStateBootstrap.imageWorkflowMemory),
|
||||
hasStoryboardPreferenceMemory: Boolean(conversationStateBootstrap.storyboardPreferenceMemory),
|
||||
hasPendingGridConfirmation: Boolean(conversationStateBootstrap.pendingGridConfirmation),
|
||||
hasActiveStoryboardTask: Boolean(conversationStateBootstrap.activeStoryboardTask?.task?.id),
|
||||
});
|
||||
pendingProtocolDebugEvents.push({
|
||||
phase: "bootstrap",
|
||||
payload: {
|
||||
action: conversationStateBootstrap.actionResolution.kind,
|
||||
pendingGridConfirmation: conversationStateBootstrap.pendingGridConfirmation,
|
||||
hasPromptMemory: Boolean(conversationStateBootstrap.promptMemory?.optimizedPrompt),
|
||||
hasImageWorkflowMemory: Boolean(conversationStateBootstrap.imageWorkflowMemory),
|
||||
hasStoryboardPreferenceMemory: Boolean(conversationStateBootstrap.storyboardPreferenceMemory),
|
||||
hasActiveStoryboardTask: Boolean(conversationStateBootstrap.activeStoryboardTask?.task?.id),
|
||||
},
|
||||
});
|
||||
storyboardPreferenceMemory = conversationStateBootstrap.storyboardPreferenceMemory;
|
||||
imageWorkflowMemory = conversationStateBootstrap.imageWorkflowMemory;
|
||||
reusablePromptMemory = conversationStateBootstrap.promptMemory;
|
||||
pendingGridConfirmation = conversationStateBootstrap.pendingGridConfirmation;
|
||||
confirmedPromptForConversationState = conversationStateBootstrap.confirmedPrompt;
|
||||
const resolvedConversationAction = conversationStateBootstrap.actionResolution;
|
||||
|
||||
if (resolvedConversationAction.forceAgentConversation && !shouldRunAgentConversation) {
|
||||
shouldRunAgentConversation = true;
|
||||
@ -3189,7 +3190,7 @@ export async function POST(request: Request) {
|
||||
chatId: id,
|
||||
});
|
||||
}
|
||||
confirmedPromptForGeneration = preloadedConfirmedPrompt;
|
||||
confirmedPromptForGeneration = confirmedPromptForConversationState;
|
||||
const isPromptCarryForwardExecution =
|
||||
resolvedConversationAction.kind === "prompt_carry_forward";
|
||||
forcePromptDrivenImageGeneration =
|
||||
@ -3287,37 +3288,26 @@ export async function POST(request: Request) {
|
||||
CHAT_PRE_RESPONSE_SOFT_TIMEOUT_MS
|
||||
);
|
||||
}
|
||||
const baseThreadState = buildConversationThreadState({
|
||||
activeStoryboardTask: activeStoryboardTaskForChat,
|
||||
storyboardPreferenceMemory,
|
||||
promptMemory: reusablePromptMemory,
|
||||
imageWorkflowMemory,
|
||||
});
|
||||
const composedConversationMemory = composeConversationMemory({
|
||||
const composedConversationState = composeConversationStateProtocol({
|
||||
bootstrap: conversationStateBootstrap,
|
||||
autoContextText: autoContextText || undefined,
|
||||
resolvedMediaInput: requestResolvedMediaInput,
|
||||
targetResolution: requestTargetResolution,
|
||||
promptMemory: reusablePromptMemory,
|
||||
imageWorkflowMemory,
|
||||
storyboardPreferenceMemory,
|
||||
threadState: baseThreadState,
|
||||
});
|
||||
const composedThreadState = {
|
||||
workflowState: baseThreadState?.workflowState || null,
|
||||
memoryHints: Array.from(
|
||||
new Set([
|
||||
...(baseThreadState?.memoryHints || []),
|
||||
...composedConversationMemory.memoryHints,
|
||||
])
|
||||
),
|
||||
contextSummary:
|
||||
composedConversationMemory.contextSummary ||
|
||||
baseThreadState?.contextSummary,
|
||||
metadata: {
|
||||
...(baseThreadState?.metadata || {}),
|
||||
...composedConversationMemory.metadata,
|
||||
console.log("[ConversationStateProtocol] Composed:", {
|
||||
workflowType: composedConversationState.threadState.workflowState?.type || null,
|
||||
workflowStatus: composedConversationState.threadState.workflowState?.status || null,
|
||||
memoryHintsCount: composedConversationState.threadState.memoryHints.length,
|
||||
contextSummaryPreview: composedConversationState.threadState.contextSummary?.slice(0, 180),
|
||||
});
|
||||
pendingProtocolDebugEvents.push({
|
||||
phase: "composed",
|
||||
payload: {
|
||||
workflowState: composedConversationState.threadState.workflowState,
|
||||
memoryHints: composedConversationState.threadState.memoryHints,
|
||||
contextSummary: composedConversationState.threadState.contextSummary,
|
||||
},
|
||||
};
|
||||
});
|
||||
const autoGraphResult = await withOperationTimeout(
|
||||
runAutoAgentGraph({
|
||||
text: smartAutoInputText,
|
||||
@ -3342,9 +3332,11 @@ export async function POST(request: Request) {
|
||||
currentUploadImageCount: requestResolvedMediaInput.currentMessageImageUrls.length,
|
||||
historyMessages,
|
||||
context:
|
||||
composedConversationMemory.contextSummary || autoContextText || undefined,
|
||||
composedConversationState.composedConversationMemory.contextSummary ||
|
||||
autoContextText ||
|
||||
undefined,
|
||||
preferredModelId: preferredAgentModelId,
|
||||
threadState: composedThreadState,
|
||||
threadState: composedConversationState.threadState,
|
||||
}),
|
||||
"runAutoAgentGraph",
|
||||
autoGraphTimeoutMs,
|
||||
@ -3474,26 +3466,25 @@ export async function POST(request: Request) {
|
||||
Boolean(autoGraphResult.analysis.needsClarification);
|
||||
smartAutoClarificationQuestion = autoGraphResult.analysis.clarifyingQuestions?.[0];
|
||||
|
||||
const targetExecutionOverride = resolveTargetExecutionOverride({
|
||||
hasImage,
|
||||
hasSelectedImageHint,
|
||||
hasExplicitTargetHint,
|
||||
hasActionableEditIntent,
|
||||
executionMode: smartAutoExecution?.mode,
|
||||
friendlyMessage: smartAutoFriendlyMessage,
|
||||
optimizedPrompt: smartAutoOptimizedPrompt,
|
||||
requestText: requestTextContent,
|
||||
});
|
||||
|
||||
if (
|
||||
smartAutoNeedsClarification &&
|
||||
hasImage &&
|
||||
hasActionableEditIntent &&
|
||||
(hasSelectedImageHint || hasExplicitTargetHint) &&
|
||||
(smartAutoExecution?.mode === "edit_image" ||
|
||||
smartAutoExecution?.mode === "generate_image")
|
||||
targetExecutionOverride.shouldBypassClarification
|
||||
) {
|
||||
smartAutoNeedsClarification = false;
|
||||
smartAutoClarificationQuestion = undefined;
|
||||
if (!smartAutoOptimizedPrompt && requestTextContent.trim()) {
|
||||
smartAutoOptimizedPrompt = requestTextContent.trim();
|
||||
}
|
||||
if (
|
||||
!smartAutoFriendlyMessage ||
|
||||
/[??]$/.test(smartAutoFriendlyMessage.trim()) ||
|
||||
/哪一张|哪个|请先确认/.test(smartAutoFriendlyMessage)
|
||||
) {
|
||||
smartAutoFriendlyMessage = "收到,我将按你指定的目标图直接执行。";
|
||||
}
|
||||
smartAutoOptimizedPrompt = targetExecutionOverride.nextOptimizedPrompt;
|
||||
smartAutoFriendlyMessage = targetExecutionOverride.nextFriendlyMessage;
|
||||
console.log(
|
||||
"[Smart Auto Mode] ✅ Skip clarification due to explicit target/selection context"
|
||||
);
|
||||
@ -3504,28 +3495,23 @@ export async function POST(request: Request) {
|
||||
!pendingGridConfirmation || smartAutoInputText === requestTextContent
|
||||
? detectRequestedStoryboardGrid(requestTextContent)
|
||||
: pendingGridConfirmation;
|
||||
const isGridTaskCandidate =
|
||||
smartAutoExecution?.mode === "generate_image" ||
|
||||
smartAutoExecution?.mode === "generate_video" ||
|
||||
autoGraphResult.analysis?.intent === "image_gen" ||
|
||||
autoGraphResult.analysis?.intent === "video_gen";
|
||||
if (
|
||||
requestedGrid &&
|
||||
!isGridConfirmationReply(requestTextContent) &&
|
||||
!activeStoryboardTaskForChat?.task?.id &&
|
||||
isGridTaskCandidate
|
||||
) {
|
||||
const gridConfirmationResolution = resolveGridConfirmationRequest({
|
||||
requestedGrid,
|
||||
hasActiveStoryboardTask: Boolean(activeStoryboardTaskForChat?.task?.id),
|
||||
executionMode: smartAutoExecution?.mode,
|
||||
detectedIntent: autoGraphResult.analysis?.intent,
|
||||
requestText: requestTextContent,
|
||||
effectiveInputText: smartAutoInputText,
|
||||
});
|
||||
if (gridConfirmationResolution.shouldRequestConfirmation) {
|
||||
shouldAskGridConfirmation = true;
|
||||
smartAutoNeedsClarification = true;
|
||||
smartAutoClarificationQuestion = buildGridConfirmationMessage(requestedGrid);
|
||||
smartAutoFriendlyMessage = smartAutoClarificationQuestion;
|
||||
pendingGridConfirmation = {
|
||||
...requestedGrid,
|
||||
originalPrompt: smartAutoInputText,
|
||||
};
|
||||
smartAutoClarificationQuestion = gridConfirmationResolution.clarificationMessage;
|
||||
smartAutoFriendlyMessage = gridConfirmationResolution.clarificationMessage;
|
||||
pendingGridConfirmation = gridConfirmationResolution.pendingGridConfirmation;
|
||||
console.log("[Smart Auto Mode] 🧱 Require grid confirmation before generation:", {
|
||||
gridType: requestedGrid.gridType,
|
||||
shotCount: requestedGrid.shotCount,
|
||||
gridType: gridConfirmationResolution.pendingGridConfirmation?.gridType,
|
||||
shotCount: gridConfirmationResolution.pendingGridConfirmation?.shotCount,
|
||||
});
|
||||
}
|
||||
|
||||
@ -4484,7 +4470,7 @@ export async function POST(request: Request) {
|
||||
},
|
||||
});
|
||||
|
||||
await upsertConversationWorkingState({
|
||||
await persistResolvedTargetTurn({
|
||||
userId: session.user.id,
|
||||
chatId: id,
|
||||
projectId: contextPayload?.projectId || null,
|
||||
@ -4515,13 +4501,10 @@ export async function POST(request: Request) {
|
||||
canvasImageNodes: contextPayload?.canvasImageNodes,
|
||||
}
|
||||
: null,
|
||||
lastTurnSummary: {
|
||||
text: requestTextContent.slice(0, 500),
|
||||
lastMessageId: message.id,
|
||||
text: requestTextContent,
|
||||
resolutionMode: requestTargetResolution.resolutionMode,
|
||||
confidence: requestTargetResolution.confidence,
|
||||
},
|
||||
lastMessageId: message.id,
|
||||
incrementTurnCount: true,
|
||||
});
|
||||
} catch (memoryError) {
|
||||
console.warn("[Chat API] Failed to persist context-memory state:", memoryError);
|
||||
@ -4659,40 +4642,15 @@ export async function POST(request: Request) {
|
||||
});
|
||||
|
||||
try {
|
||||
await upsertConversationWorkingState({
|
||||
await persistPromptOnlyTurn({
|
||||
userId: session.user.id,
|
||||
chatId: id,
|
||||
projectId: contextPayload?.projectId || null,
|
||||
currentIntent: "vision",
|
||||
currentFocusAssetIds:
|
||||
promptOnlySourceAssetIds.length > 0 ? promptOnlySourceAssetIds : null,
|
||||
currentFocusImageUrls: promptOnlySourceImageUrls,
|
||||
lastResolvedTargetAssetIds:
|
||||
promptOnlySourceAssetIds.length > 0 ? promptOnlySourceAssetIds : null,
|
||||
lastResolvedTargetImageUrls: promptOnlySourceImageUrls,
|
||||
lastMessageId: messageId,
|
||||
lastTurnSummary: {
|
||||
text: requestTextContent.slice(0, 500),
|
||||
resolutionMode: "prompt_only",
|
||||
confidence: 90,
|
||||
optimizedPrompt: promptOnlyOptimizedPrompt.slice(0, 1000),
|
||||
promptMemory: buildPromptMemoryUpdate({
|
||||
optimizedPrompt: promptOnlyOptimizedPrompt.slice(0, 1000),
|
||||
sourceIntent: "image_gen",
|
||||
sourceText: requestTextContent.slice(0, 500),
|
||||
reusable: true,
|
||||
}),
|
||||
imageWorkflowMemory: buildImageWorkflowMemoryUpdate({
|
||||
existing: imageWorkflowMemory,
|
||||
lastIntent: "vision",
|
||||
lastOptimizedPrompt: promptOnlyOptimizedPrompt.slice(0, 1000),
|
||||
lastUserPrompt: requestTextContent.slice(0, 500),
|
||||
lastRequestedImageCount: 1,
|
||||
lastSourceImageUrls: promptOnlySourceImageUrls,
|
||||
}),
|
||||
imageSummary: promptOnlyImageSummary.slice(0, 1200),
|
||||
messageId,
|
||||
text: requestTextContent,
|
||||
optimizedPrompt: promptOnlyOptimizedPrompt,
|
||||
imageSummary: promptOnlyImageSummary,
|
||||
sourceImageUrls: promptOnlySourceImageUrls,
|
||||
},
|
||||
});
|
||||
} catch (promptOnlyMemoryError) {
|
||||
console.warn("[Chat API] Failed to persist prompt-only response state:", promptOnlyMemoryError);
|
||||
@ -4782,31 +4740,13 @@ export async function POST(request: Request) {
|
||||
|
||||
if (shouldAskGridConfirmation && pendingGridConfirmation) {
|
||||
try {
|
||||
await upsertConversationWorkingState({
|
||||
await persistGridConfirmationTurn({
|
||||
userId: session.user.id,
|
||||
chatId: id,
|
||||
projectId: contextPayload?.projectId || null,
|
||||
currentIntent: "image_gen",
|
||||
lastMessageId: messageId,
|
||||
lastTurnSummary: {
|
||||
text: requestTextContent.slice(0, 500),
|
||||
resolutionMode: "grid_confirmation",
|
||||
confidence: 90,
|
||||
storyboardMemory: buildStoryboardPreferenceMemoryUpdate({
|
||||
existing: storyboardPreferenceMemory,
|
||||
preferredGridType: pendingGridConfirmation.gridType,
|
||||
preferredShotCount: pendingGridConfirmation.shotCount,
|
||||
prefersConfirmation: true,
|
||||
lastUsedGridType: pendingGridConfirmation.gridType,
|
||||
lastUsedShotCount: pendingGridConfirmation.shotCount,
|
||||
}),
|
||||
pendingGridConfirmation: {
|
||||
gridType: pendingGridConfirmation.gridType,
|
||||
shotCount: pendingGridConfirmation.shotCount,
|
||||
label: pendingGridConfirmation.label,
|
||||
originalPrompt: pendingGridConfirmation.originalPrompt.slice(0, 1000),
|
||||
},
|
||||
},
|
||||
messageId,
|
||||
text: requestTextContent,
|
||||
pendingGridConfirmation,
|
||||
});
|
||||
} catch (gridConfirmationPersistError) {
|
||||
console.warn(
|
||||
@ -4956,21 +4896,12 @@ export async function POST(request: Request) {
|
||||
});
|
||||
|
||||
try {
|
||||
await upsertConversationWorkingState({
|
||||
await persistStoryboardConfirmedTurn({
|
||||
userId: session.user.id,
|
||||
chatId: id,
|
||||
projectId: contextPayload?.projectId || null,
|
||||
currentIntent: "video_gen",
|
||||
lastMessageId: acknowledgeMessageId,
|
||||
lastTurnSummary: {
|
||||
text: requestTextContent.slice(0, 500),
|
||||
resolutionMode: "storyboard_confirmed",
|
||||
confidence: 95,
|
||||
storyboardMemory: buildStoryboardPreferenceMemoryUpdate({
|
||||
existing: storyboardPreferenceMemory,
|
||||
prefersConfirmation: true,
|
||||
}),
|
||||
},
|
||||
messageId: acknowledgeMessageId,
|
||||
text: requestTextContent,
|
||||
});
|
||||
} catch (storyboardConfirmMemoryError) {
|
||||
console.warn(
|
||||
@ -5441,12 +5372,13 @@ export async function POST(request: Request) {
|
||||
});
|
||||
|
||||
try {
|
||||
await upsertConversationWorkingState({
|
||||
await persistVideoWorkflowTurn({
|
||||
userId: session.user.id,
|
||||
chatId: id,
|
||||
projectId: contextPayload?.projectId || null,
|
||||
currentIntent: "video_gen",
|
||||
lastMessageId: assistantMessageId,
|
||||
text: requestTextContent,
|
||||
status: "completed",
|
||||
});
|
||||
} catch (memoryError) {
|
||||
console.warn(
|
||||
@ -5536,19 +5468,17 @@ export async function POST(request: Request) {
|
||||
}
|
||||
|
||||
try {
|
||||
await upsertConversationWorkingState({
|
||||
await persistAgentPresetTurn({
|
||||
userId: session.user.id,
|
||||
chatId: id,
|
||||
projectId: contextPayload?.projectId || null,
|
||||
currentIntent: selectedAgentPreset.type === "video" ? "video_gen" : "image_gen",
|
||||
lastMessageId: message.id,
|
||||
lastTurnSummary: {
|
||||
text: requestTextContent.slice(0, 500),
|
||||
text: requestTextContent,
|
||||
agentPresetId: selectedAgentPreset.id,
|
||||
agentPresetName: selectedAgentPreset.name,
|
||||
slotCount: orderedSlots.length,
|
||||
successCount,
|
||||
},
|
||||
});
|
||||
} catch (agentPresetMemoryError) {
|
||||
console.warn("[Chat API] Failed to persist agent preset working state:", agentPresetMemoryError);
|
||||
@ -5874,12 +5804,13 @@ export async function POST(request: Request) {
|
||||
});
|
||||
|
||||
try {
|
||||
await upsertConversationWorkingState({
|
||||
await persistVideoWorkflowTurn({
|
||||
userId: session.user.id,
|
||||
chatId: id,
|
||||
projectId: contextPayload?.projectId || null,
|
||||
currentIntent: "video_gen",
|
||||
lastMessageId: assistantMessageId,
|
||||
text: requestTextContent,
|
||||
status: "completed",
|
||||
});
|
||||
} catch (memoryError) {
|
||||
console.warn(
|
||||
@ -6186,13 +6117,7 @@ export async function POST(request: Request) {
|
||||
try {
|
||||
const resultAssetIds = resultAssetId ? [resultAssetId] : null;
|
||||
const resultImageUrls = resultOriginalUrl ? [resultOriginalUrl] : null;
|
||||
const existingImageWorkflowMemory = extractImageWorkflowMemory(requestWorkingState);
|
||||
const existingSummary =
|
||||
requestWorkingState?.lastTurnSummary &&
|
||||
typeof requestWorkingState.lastTurnSummary === "object"
|
||||
? (requestWorkingState.lastTurnSummary as Record<string, unknown>)
|
||||
: {};
|
||||
await upsertConversationWorkingState({
|
||||
await persistImageWorkflowTurn({
|
||||
userId: session.user.id,
|
||||
chatId: id,
|
||||
projectId: contextPayload?.projectId || null,
|
||||
@ -6205,17 +6130,10 @@ export async function POST(request: Request) {
|
||||
lastResolvedTargetAssetIds: resultAssetIds,
|
||||
lastResolvedTargetImageUrls: resultImageUrls,
|
||||
lastMessageId: assistantMessageId,
|
||||
lastTurnSummary: {
|
||||
...existingSummary,
|
||||
imageWorkflowMemory: buildImageWorkflowMemoryUpdate({
|
||||
existing: existingImageWorkflowMemory,
|
||||
lastIntent: "image_edit",
|
||||
lastUserPrompt: requestTextContent.slice(0, 500),
|
||||
lastUserPrompt: requestTextContent,
|
||||
lastRequestedImageCount: resultImageUrls?.length || 1,
|
||||
lastSourceImageUrls: directAgentToolTargetImageUrls,
|
||||
lastResultImageUrls: resultImageUrls,
|
||||
}),
|
||||
},
|
||||
});
|
||||
} catch (memoryError) {
|
||||
console.warn(
|
||||
@ -7931,33 +7849,24 @@ export async function POST(request: Request) {
|
||||
try {
|
||||
const storyboardGridType = String((videoMetadata as any)?.gridType || "").trim();
|
||||
const storyboardShotCount = Number((videoMetadata as any)?.shotCount || 0);
|
||||
await upsertConversationWorkingState({
|
||||
await persistStoryboardAssistantTurn({
|
||||
userId: session.user.id,
|
||||
chatId: id,
|
||||
projectId: contextPayload?.projectId || null,
|
||||
currentIntent: "video_gen",
|
||||
lastMessageId: messageId,
|
||||
lastTurnSummary: {
|
||||
text: requestTextContent.slice(0, 500),
|
||||
resolutionMode: "storyboard",
|
||||
confidence: 95,
|
||||
storyboardMemory: buildStoryboardPreferenceMemoryUpdate({
|
||||
existing: storyboardPreferenceMemory,
|
||||
prefersConfirmation: true,
|
||||
lastUsedGridType:
|
||||
text: requestTextContent,
|
||||
storyboardGridType:
|
||||
storyboardGridType === "grid_2x2" ||
|
||||
storyboardGridType === "grid_3x2" ||
|
||||
storyboardGridType === "grid_3x3"
|
||||
? (storyboardGridType as "grid_2x2" | "grid_3x2" | "grid_3x3")
|
||||
: undefined,
|
||||
lastUsedShotCount:
|
||||
storyboardShotCount:
|
||||
storyboardShotCount === 4 ||
|
||||
storyboardShotCount === 6 ||
|
||||
storyboardShotCount === 9
|
||||
? storyboardShotCount
|
||||
: undefined,
|
||||
}),
|
||||
},
|
||||
});
|
||||
} catch (storyboardMemoryPersistError) {
|
||||
console.warn(
|
||||
@ -8020,6 +7929,18 @@ export async function POST(request: Request) {
|
||||
DEFAULT_STREAM_KEEPALIVE_INTERVAL_MS
|
||||
);
|
||||
stopStreamKeepalive = stopDefaultKeepalive;
|
||||
setConversationStateProtocolEventObserver((event) => {
|
||||
writeProtocolDebugChunk(
|
||||
dataStream,
|
||||
Boolean(debugProtocol),
|
||||
{
|
||||
phase: "event",
|
||||
type: event.type,
|
||||
payload: event.payload,
|
||||
},
|
||||
`protocol:event:${event.type}`
|
||||
);
|
||||
});
|
||||
safeWriteStreamChunk(
|
||||
dataStream,
|
||||
{
|
||||
@ -8028,6 +7949,29 @@ export async function POST(request: Request) {
|
||||
},
|
||||
"chat-default:init"
|
||||
);
|
||||
writeProtocolDebugChunk(
|
||||
dataStream,
|
||||
Boolean(debugProtocol),
|
||||
{
|
||||
phase: "request",
|
||||
chatId: id,
|
||||
selectedChatModel,
|
||||
hasHiddenPrompt: Boolean(hiddenPrompt),
|
||||
hasAutoContext: Boolean(autoContext),
|
||||
},
|
||||
"protocol:request"
|
||||
);
|
||||
for (const event of pendingProtocolDebugEvents) {
|
||||
writeProtocolDebugChunk(
|
||||
dataStream,
|
||||
Boolean(debugProtocol),
|
||||
{
|
||||
phase: event.phase,
|
||||
...event.payload,
|
||||
},
|
||||
`protocol:${event.phase}`
|
||||
);
|
||||
}
|
||||
try {
|
||||
// 构建工具对象,只包含启用的工具
|
||||
const tools: any = {};
|
||||
@ -8977,6 +8921,7 @@ export async function POST(request: Request) {
|
||||
})
|
||||
);
|
||||
} finally {
|
||||
setConversationStateProtocolEventObserver(null);
|
||||
stopStreamKeepalive = stopDefaultKeepalive;
|
||||
}
|
||||
},
|
||||
|
||||
@ -85,6 +85,7 @@ export const postRequestBodySchema = z.object({
|
||||
selectedChatModel: z.string().min(1).max(128),
|
||||
selectedVisibilityType: z.enum(["public", "private"]),
|
||||
deepThinkingEnabled: z.boolean().optional(),
|
||||
debugProtocol: z.boolean().optional(),
|
||||
videoParams: videoParamsSchema, // 视频参数
|
||||
imageParams: imageParamsSchema, // 🆕 图片生成参数
|
||||
contextPayload: contextPayloadSchema,
|
||||
|
||||
@ -30,6 +30,7 @@ import { requestLongTaskResult } from "@/lib/client/long-task";
|
||||
import { fetchUploadWithRetry } from "@/lib/client/upload-with-retry";
|
||||
import { fetcher, fetchWithErrorHandlers, generateUUID } from "@/lib/utils";
|
||||
import { Artifact } from "@/components/artifact";
|
||||
import { ProtocolDebugPanel } from "@/components/protocol-debug-panel";
|
||||
import { useDataStream } from "@/components/data-stream-provider";
|
||||
import { Messages } from "@/components/messages";
|
||||
import { MultimodalInput } from "@/components/multimodal-input";
|
||||
@ -318,6 +319,11 @@ export function Chat({
|
||||
userType?: UserType;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const protocolDebugEnabled = searchParams.get("debugProtocol") === "1";
|
||||
const [protocolDebugEvents, setProtocolDebugEvents] = useState<
|
||||
Array<{ id: string; at: number; payload: Record<string, unknown> }>
|
||||
>([]);
|
||||
|
||||
const { visibilityType } = useChatVisibility({
|
||||
chatId: id,
|
||||
@ -600,6 +606,7 @@ export function Chat({
|
||||
message: request.messages.at(-1),
|
||||
selectedChatModel: currentModelIdRef.current,
|
||||
selectedVisibilityType: visibilityType,
|
||||
debugProtocol: protocolDebugEnabled,
|
||||
videoParams: videoParamsRef.current || undefined, // 🆕 传递视频参数(使用ref获取最新值)
|
||||
imageParams: imageParamsRef.current || undefined, // 🆕 传递图片参数(使用ref获取最新值)
|
||||
...request.body,
|
||||
@ -753,6 +760,24 @@ export function Chat({
|
||||
console.error("[Chat] Failed to parse data-appendMessage:", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (dataPart.type === "data-protocolDebug") {
|
||||
try {
|
||||
const payload =
|
||||
typeof dataPart.data === "string" ? JSON.parse(dataPart.data) : dataPart.data;
|
||||
if (!payload || typeof payload !== "object") return;
|
||||
setProtocolDebugEvents((prev) => [
|
||||
...prev.slice(-59),
|
||||
{
|
||||
id: generateUUID(),
|
||||
at: Date.now(),
|
||||
payload,
|
||||
},
|
||||
]);
|
||||
} catch (e) {
|
||||
console.error("[Chat] Failed to parse data-protocolDebug:", e);
|
||||
}
|
||||
}
|
||||
},
|
||||
onFinish: async () => {
|
||||
mutate(unstable_serialize(getChatHistoryPaginationKey));
|
||||
@ -1074,7 +1099,6 @@ export function Chat({
|
||||
}
|
||||
}, [status, currentModelId, messages, startGeneration, updateImageGenerationPhase]);
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
const query = searchParams.get("query");
|
||||
const editImageUrl = searchParams.get("editImage");
|
||||
|
||||
@ -2070,6 +2094,7 @@ export function Chat({
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<ProtocolDebugPanel enabled={protocolDebugEnabled} events={protocolDebugEvents} />
|
||||
</div>
|
||||
|
||||
<Artifact
|
||||
|
||||
51
components/protocol-debug-panel.tsx
Normal file
51
components/protocol-debug-panel.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
"use client";
|
||||
|
||||
type ProtocolDebugEvent = {
|
||||
id: string;
|
||||
at: number;
|
||||
payload: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export function ProtocolDebugPanel({
|
||||
enabled,
|
||||
events,
|
||||
}: {
|
||||
enabled: boolean;
|
||||
events: ProtocolDebugEvent[];
|
||||
}) {
|
||||
if (!enabled) return null;
|
||||
|
||||
return (
|
||||
<div className="mx-auto mt-2 w-full max-w-4xl px-2 md:px-4">
|
||||
<details className="rounded-xl border border-border/60 bg-muted/30 p-3 text-xs">
|
||||
<summary className="cursor-pointer select-none font-medium text-foreground">
|
||||
Protocol Debug
|
||||
</summary>
|
||||
<div className="mt-3 space-y-2">
|
||||
{events.length === 0 ? (
|
||||
<div className="rounded-lg border border-dashed border-border/60 px-3 py-2 text-muted-foreground">
|
||||
暂无协议调试事件
|
||||
</div>
|
||||
) : (
|
||||
events.map((event) => (
|
||||
<div
|
||||
key={event.id}
|
||||
className="overflow-hidden rounded-lg border border-border/60 bg-background"
|
||||
>
|
||||
<div className="border-b border-border/60 px-3 py-2 font-medium">
|
||||
{new Date(event.at).toLocaleTimeString()}
|
||||
{" · "}
|
||||
{String(event.payload.phase || event.payload.type || "unknown")}
|
||||
</div>
|
||||
<pre className="overflow-x-auto px-3 py-2 whitespace-pre-wrap break-words text-[11px] leading-relaxed text-muted-foreground">
|
||||
{JSON.stringify(event.payload, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
776
docs/Agent模式与DeerFlow对比-大白话版-2026-03-13.md
Normal file
776
docs/Agent模式与DeerFlow对比-大白话版-2026-03-13.md
Normal file
@ -0,0 +1,776 @@
|
||||
# Agent 模式与 DeerFlow 对比说明(大白话版)
|
||||
|
||||
> 写给谁看:不懂技术,但想真正看明白这个项目的人。
|
||||
> 更新时间:2026-03-13。
|
||||
> 本文已经按**今天实际落地的代码**重写,不再沿用旧判断。
|
||||
|
||||
---
|
||||
|
||||
## 先说结论
|
||||
|
||||
### 一句话结论
|
||||
|
||||
- **我们这个项目**,现在已经不是“普通聊天 + 一堆工具”的形态了。
|
||||
它正在变成一个**面向视觉生产的统一 Agent Runtime**。
|
||||
|
||||
- **DeerFlow** 仍然更像一个**通用超级智能体底座**。
|
||||
它更擅长:通用任务拆解、子代理协作、沙箱执行、技能体系、长期记忆、MCP 集成。
|
||||
|
||||
### 现在最准确的判断
|
||||
|
||||
过去说我们项目“还只是主脑 + 一堆补丁”,今天之后就不完全对了。
|
||||
|
||||
因为我们已经补上了这些关键层:
|
||||
|
||||
1. `conversation action resolver`
|
||||
2. `conversation memory composer`
|
||||
3. `conversation thread state builder`
|
||||
4. `conversation state protocol`
|
||||
5. `conversation state protocol events`
|
||||
6. `human-confirmation-gate`
|
||||
7. `storyboard-subagent`
|
||||
8. `image-generation-subagent`
|
||||
9. `image-edit-subagent`
|
||||
|
||||
也就是说:
|
||||
|
||||
**我们还不是 DeerFlow 那种通用超级智能体平台,但已经明确进入“统一运行时 Agent”阶段。**
|
||||
|
||||
---
|
||||
|
||||
## 1. 这个项目现在的 Agent,到底是什么
|
||||
|
||||
这个项目现在有两层 Agent:
|
||||
|
||||
### 1.1 运行时 Agent
|
||||
|
||||
这是核心大脑,负责:
|
||||
|
||||
1. 理解用户这轮到底在说什么
|
||||
2. 判断用户到底指哪张图
|
||||
3. 判断这是普通聊天、看图、修图、生图、视频、还是分镜任务
|
||||
4. 恢复上一轮工作记忆
|
||||
5. 根据当前 workflow 决定是继续、修改、确认、取消,还是新开任务
|
||||
6. 选择工具、选择模型、执行
|
||||
|
||||
主要代码:
|
||||
|
||||
- `app/(chat)/api/chat/route.ts`
|
||||
- `lib/ai/auto-agent-graph.ts`
|
||||
- `lib/ai/smart-analyzer.ts`
|
||||
- `lib/ai/skill-router.ts`
|
||||
- `lib/ai/runtime/*`
|
||||
- `lib/ai/conversation-*`
|
||||
|
||||
### 1.2 预设智能体层
|
||||
|
||||
这层更像“模板化工作流”:
|
||||
|
||||
- 默认模型
|
||||
- 固定提示词
|
||||
- 固定槽位
|
||||
- 固定参数
|
||||
|
||||
主要代码:
|
||||
|
||||
- `app/api/agents/*`
|
||||
- `components/agents/*`
|
||||
- `types/agent.ts`
|
||||
|
||||
所以一定要分清:
|
||||
|
||||
- **运行时 Agent** = 真正思考和调度的系统大脑
|
||||
- **预设智能体** = 预先配置好的业务模板
|
||||
|
||||
---
|
||||
|
||||
## 2. 今天升级后,Agent 底层结构已经变成什么样了
|
||||
|
||||
今天最重要的升级,不是多接了一个模型,而是把 Agent 的“脑子”和“记忆”开始统一起来。
|
||||
|
||||
### 2.1 现在已经有一套会话协议层
|
||||
|
||||
我们已经新增了以下模块:
|
||||
|
||||
- `lib/ai/conversation-action-resolver.ts`
|
||||
- `lib/ai/conversation-memory-composer.ts`
|
||||
- `lib/ai/conversation-thread-state-builder.ts`
|
||||
- `lib/ai/conversation-state-protocol.ts`
|
||||
- `lib/ai/conversation-state-protocol-events.ts`
|
||||
|
||||
你可以把它理解成:
|
||||
|
||||
#### 以前
|
||||
|
||||
像很多项目一样,是:
|
||||
|
||||
- 用户说一句
|
||||
- 系统临时判断一次
|
||||
- 执行一次
|
||||
- 结果存一点
|
||||
|
||||
每轮都更像“重新开始”
|
||||
|
||||
#### 现在
|
||||
|
||||
变成:
|
||||
|
||||
- **先恢复这段对话的会话状态**
|
||||
- **再判断这句话属于什么动作**
|
||||
- **再拼装统一上下文**
|
||||
- **再让 planner 决定下一步**
|
||||
- **执行完成后,再按统一协议写回状态**
|
||||
|
||||
也就是说,我们开始有真正的:
|
||||
|
||||
**会话状态协议**
|
||||
|
||||
而不是单轮聊天补丁。
|
||||
|
||||
### 2.2 现在已经有统一的动作协议
|
||||
|
||||
现在系统会先判断,这轮对话属于哪种动作:
|
||||
|
||||
- `default`
|
||||
- `grid_confirmation_followup`
|
||||
- `active_storyboard_followup`
|
||||
- `image_edit_followup`
|
||||
- `prompt_carry_forward`
|
||||
|
||||
这一步在:
|
||||
|
||||
- `lib/ai/conversation-action-resolver.ts`
|
||||
|
||||
这件事很重要,因为它解决了一个以前特别容易乱的问题:
|
||||
|
||||
#### 用户到底是在:
|
||||
|
||||
- 开一个新任务
|
||||
- 继续上一轮任务
|
||||
- 修改上一轮任务
|
||||
- 确认一个待执行任务
|
||||
- 取消一个待执行任务
|
||||
|
||||
以前这些判断很多散在 `chat/route.ts` 里。
|
||||
今天开始,它们在往统一协议收。
|
||||
|
||||
### 2.3 现在已经有统一的 thread state
|
||||
|
||||
现在运行时 Agent 已经不只是收到一段文本了。
|
||||
|
||||
它会收到一个 `threadState`,里面可能包含:
|
||||
|
||||
- 当前 workflow
|
||||
- 当前 workflow 状态
|
||||
- 当前 memory hints
|
||||
- 当前上下文摘要
|
||||
- 当前 prompt memory
|
||||
- 当前 image workflow memory
|
||||
- 当前 storyboard preference memory
|
||||
|
||||
这一步主要在:
|
||||
|
||||
- `lib/ai/runtime/agent-thread-state.ts`
|
||||
- `lib/ai/conversation-thread-state-builder.ts`
|
||||
- `lib/video-agent/agent-thread-state.ts`
|
||||
|
||||
这意味着:
|
||||
|
||||
**Agent 已经开始像人一样“带着上下文继续理解”,不是每轮都失忆。**
|
||||
|
||||
### 2.4 现在已经有统一的读写协议
|
||||
|
||||
今天另一个很关键的变化是:
|
||||
|
||||
`app/(chat)/api/chat/route.ts` 里已经不再直接调用 `upsertConversationWorkingState(...)`。
|
||||
|
||||
写回现在统一走:
|
||||
|
||||
- `persistConversationStateEvent(...)`
|
||||
- `persistPromptOnlyTurn(...)`
|
||||
- `persistGridConfirmationTurn(...)`
|
||||
- `persistStoryboardConfirmedTurn(...)`
|
||||
- `persistImageWorkflowTurn(...)`
|
||||
- `persistResolvedTargetTurn(...)`
|
||||
- `persistVideoWorkflowTurn(...)`
|
||||
- `persistAgentPresetTurn(...)`
|
||||
- `persistStoryboardAssistantTurn(...)`
|
||||
|
||||
也就是说:
|
||||
|
||||
**聊天主路由已经从“自己管理记忆结构”,升级为“只触发语义事件”。**
|
||||
|
||||
这是一件很大的架构进步。
|
||||
|
||||
### 2.5 现在已经有协议级调试流
|
||||
|
||||
当请求里带 `debugProtocol: true` 时,前端会收到:
|
||||
|
||||
- `request`
|
||||
- `bootstrap`
|
||||
- `composed`
|
||||
- `event`
|
||||
|
||||
这些 `data-protocolDebug` 事件。
|
||||
|
||||
前端也已经能显示协议调试面板:
|
||||
|
||||
- `components/protocol-debug-panel.tsx`
|
||||
- `components/chat.tsx`
|
||||
|
||||
这意味着你们现在不是只能“看结果”,而是能直接看:
|
||||
|
||||
- 这轮命中了什么动作
|
||||
- 这轮 workflow 是什么
|
||||
- Agent 拿到的 memory hints 是什么
|
||||
- 这轮写回了哪个协议事件
|
||||
|
||||
这对后面继续调优 Agent 非常重要。
|
||||
|
||||
---
|
||||
|
||||
## 3. 现在这套 Agent 实际怎么工作
|
||||
|
||||
### 3.1 先收素材
|
||||
|
||||
系统先统一整理:
|
||||
|
||||
- 当前上传图
|
||||
- 画布选中图
|
||||
- 历史图
|
||||
- 上一轮结果图
|
||||
- 文本里贴的 URL 图
|
||||
|
||||
主要代码:
|
||||
|
||||
- `lib/ai/context-memory/media-input.ts`
|
||||
- `lib/ai/context-memory/target-resolution.ts`
|
||||
|
||||
### 3.2 恢复会话状态
|
||||
|
||||
系统会先从数据库恢复:
|
||||
|
||||
- prompt memory
|
||||
- image workflow memory
|
||||
- storyboard preference memory
|
||||
- active storyboard task
|
||||
- pending grid confirmation
|
||||
|
||||
然后用协议层统一拼成:
|
||||
|
||||
- `actionResolution`
|
||||
- `threadState`
|
||||
- `contextSummary`
|
||||
- `memoryHints`
|
||||
|
||||
主要代码:
|
||||
|
||||
- `lib/ai/conversation-state-protocol.ts`
|
||||
- `lib/ai/conversation-memory-composer.ts`
|
||||
- `lib/ai/conversation-thread-state-builder.ts`
|
||||
|
||||
### 3.3 先判断“这轮属于什么动作”
|
||||
|
||||
比如:
|
||||
|
||||
- “根据刚才那个提示词来做这张图”
|
||||
- “把第三镜头改成近景”
|
||||
- “先别生成了”
|
||||
- “按 6 宫格来”
|
||||
- “确认开始生成视频”
|
||||
|
||||
系统会先给这句话归类。
|
||||
|
||||
这不是最终业务意图,而是:
|
||||
|
||||
**当前回合的会话动作**
|
||||
|
||||
这一步特别像一个“对话状态机入口”。
|
||||
|
||||
### 3.4 再进入 Auto Agent Graph
|
||||
|
||||
当前 graph 主要节点是:
|
||||
|
||||
1. `confirmationGate`
|
||||
2. `analyze`
|
||||
3. `plan`
|
||||
4. `prepareExecution`
|
||||
5. `optimize`
|
||||
6. `routeModel`
|
||||
|
||||
也就是:
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["用户输入"] --> B["恢复会话状态"]
|
||||
B --> C["conversation action resolver"]
|
||||
C --> D["threadState + memory composer"]
|
||||
D --> E["confirmationGate"]
|
||||
E --> F["analyze"]
|
||||
F --> G["plan"]
|
||||
G --> H["prepareExecution"]
|
||||
H --> I{"要不要优化提示词/分镜"}
|
||||
I -- "要" --> J["optimize"]
|
||||
I -- "不要" --> K["routeModel"]
|
||||
J --> K
|
||||
K --> L["执行工具 / 模型"]
|
||||
L --> M["协议化写回状态"]
|
||||
```
|
||||
|
||||
### 3.5 专家和子代理会介入
|
||||
|
||||
现在已经有明确的子代理/专家层:
|
||||
|
||||
- `storyboard-subagent`
|
||||
- `image-generation-subagent`
|
||||
- `image-edit-subagent`
|
||||
- `canvas-specialist`
|
||||
|
||||
对应文件:
|
||||
|
||||
- `lib/video-agent/storyboard-subagent.ts`
|
||||
- `lib/ai/image-generation-subagent.ts`
|
||||
- `lib/ai/image-edit-subagent.ts`
|
||||
- `lib/ai/specialists/index.ts`
|
||||
- `lib/ai/skill-router.ts`
|
||||
|
||||
虽然它们还没有发展成 DeerFlow 那种“很多通用子代理并发工作”的程度,
|
||||
但已经不是简单的路由白名单了。
|
||||
|
||||
它们现在已经会:
|
||||
|
||||
- 给 planner 注入强约束
|
||||
- 修正执行计划
|
||||
- 改变工具选择
|
||||
- 改变结果回画布策略
|
||||
- 改变是否继续执行
|
||||
|
||||
所以这里要纠正一个旧说法:
|
||||
|
||||
> 旧判断:我们项目还只是“主脑 + 一堆专家修正”,不算真正子代理。
|
||||
|
||||
这个说法现在只算“部分正确”。
|
||||
|
||||
更准确的说法应该是:
|
||||
|
||||
> 当前项目已经进入了“子代理化早期阶段”,
|
||||
> 但还没有发展成 DeerFlow 那种真正的大规模通用子代理系统。
|
||||
|
||||
---
|
||||
|
||||
## 4. 普通生图对话现在怎么理解了
|
||||
|
||||
这是今天升级里非常重要的一块。
|
||||
|
||||
### 4.1 以前
|
||||
|
||||
用户说:
|
||||
|
||||
1. “帮我根据这张图优化提示词”
|
||||
2. 下一轮再说:“按上一个优化后的提示词来优化这张图”
|
||||
|
||||
以前很多系统会丢上下文,只看到第二句话。
|
||||
|
||||
### 4.2 现在
|
||||
|
||||
系统已经有:
|
||||
|
||||
- `promptMemory`
|
||||
- `imageWorkflowMemory`
|
||||
- `image_generation_followup workflow`
|
||||
|
||||
这意味着 Agent 更可能理解成:
|
||||
|
||||
> 使用上一轮优化后的提示词
|
||||
> + 当前这轮的新图片
|
||||
> + 当前用户的数量/画幅/清晰度需求
|
||||
> 来继续执行这轮生图任务
|
||||
|
||||
这一步主要在:
|
||||
|
||||
- `lib/ai/prompt-memory.ts`
|
||||
- `lib/ai/image-workflow-memory.ts`
|
||||
- `lib/ai/image-generation-subagent.ts`
|
||||
|
||||
### 4.3 多张图能力也已经升级
|
||||
|
||||
今天还补了一个很重要的底层能力:
|
||||
|
||||
如果用户要 6 张、8 张、12 张图,而当前模型单次只能返回 1 张或 2 张,
|
||||
系统会自动分批调用,直到凑够目标数量。
|
||||
|
||||
主要代码:
|
||||
|
||||
- `lib/canvas/generate-image.ts`
|
||||
|
||||
这说明现在系统对:
|
||||
|
||||
- “连续生图”
|
||||
- “多张图需求”
|
||||
- “提示词接力”
|
||||
|
||||
已经开始形成统一能力,不再是三个分开的 feature。
|
||||
|
||||
---
|
||||
|
||||
## 5. 连续修图对话现在怎么理解了
|
||||
|
||||
今天还补了:
|
||||
|
||||
- `image_edit_followup workflow`
|
||||
- `image-edit-subagent`
|
||||
|
||||
这意味着如果用户说:
|
||||
|
||||
- “继续改上一张结果图”
|
||||
- “保留主体不变,只把背景换掉”
|
||||
- “上一版再亮一点”
|
||||
- “这张继续优化”
|
||||
|
||||
系统更可能理解成:
|
||||
|
||||
> 这是连续修图任务
|
||||
> 目标图优先取上一轮结果图或当前绑定目标图
|
||||
> 不要误判成重新生图
|
||||
|
||||
这一步主要在:
|
||||
|
||||
- `lib/ai/image-edit-subagent.ts`
|
||||
- `lib/ai/conversation-thread-state-builder.ts`
|
||||
- `lib/ai/conversation-action-resolver.ts`
|
||||
|
||||
---
|
||||
|
||||
## 6. 分镜视频链路现在是什么水平
|
||||
|
||||
这里也要纠正旧判断。
|
||||
|
||||
### 6.1 以前可以说“文档多、草图多、骨架多”
|
||||
|
||||
### 6.2 现在已经不是纯方案阶段了
|
||||
|
||||
现在已经实际落地了这些模块:
|
||||
|
||||
- `lib/video-agent/pipeline/ruxa-nine-grid-pipeline.ts`
|
||||
- `lib/video-agent/execute-storyboard-task.ts`
|
||||
- `lib/video-agent/revise-storyboard-task.ts`
|
||||
- `lib/video-agent/storyboard-command-interpreter.ts`
|
||||
- `lib/video-agent/storyboard-preference-memory.ts`
|
||||
- `lib/video-agent/task-store.ts`
|
||||
- `app/(chat)/api/video/storyboard/*`
|
||||
|
||||
而且链路已经具备:
|
||||
|
||||
1. 分镜拼图检测
|
||||
2. 4/6/9 宫格识别
|
||||
3. 镜头切片
|
||||
4. 高清预览图生成
|
||||
5. 用户确认门
|
||||
6. 单镜头修改
|
||||
7. 单镜头重生高清图
|
||||
8. 确认后逐镜头开始视频执行
|
||||
|
||||
所以这里也要纠正一个旧说法:
|
||||
|
||||
> 旧判断:分镜视频还主要停留在方案文档阶段。
|
||||
|
||||
现在更准确的说法是:
|
||||
|
||||
> 分镜视频已经进入“半落地工作流阶段”,
|
||||
> 不是只有文档,已经有实际 runtime、task-store、API 和前端联动。
|
||||
> 但还没有完全进化成通用化的视频工作流平台。
|
||||
|
||||
---
|
||||
|
||||
## 7. 这个项目当前到底像什么
|
||||
|
||||
今天之后,再用一句话总结这个项目:
|
||||
|
||||
**它已经不是单纯的多模态聊天应用,而是一个“面向视觉生产的 Agent 工作流系统”。**
|
||||
|
||||
更准确一点说,它现在像:
|
||||
|
||||
### 产品层
|
||||
|
||||
- 图片编辑
|
||||
- 图片生成
|
||||
- 视频生成
|
||||
- 分镜工作流
|
||||
- 无限画布
|
||||
|
||||
### Agent 层
|
||||
|
||||
- 统一动作解析
|
||||
- 统一记忆合成
|
||||
- 统一 thread state
|
||||
- 统一 workflow state
|
||||
- 统一写回协议
|
||||
- 统一协议事件
|
||||
- 可选调试面板
|
||||
|
||||
也就是说,它现在已经开始具备一个真正 Agent Runtime 的轮廓。
|
||||
|
||||
---
|
||||
|
||||
## 8. DeerFlow 到底是什么,今天要怎么正确看
|
||||
|
||||
根据 DeerFlow 官方仓库和架构说明,最准确的大白话是:
|
||||
|
||||
**DeerFlow 是一个“通用超级智能体运行底座”,不是一个专门给视觉生产做深度业务适配的产品。**
|
||||
|
||||
它的强项主要在:
|
||||
|
||||
- 通用任务拆解
|
||||
- Sub-agents
|
||||
- Skills
|
||||
- Sandbox
|
||||
- Middleware
|
||||
- Long-term Memory
|
||||
- MCP 集成
|
||||
|
||||
简单说:
|
||||
|
||||
### DeerFlow 擅长
|
||||
|
||||
- 做一个什么都能接的“超级智能体平台”
|
||||
|
||||
### 我们现在擅长
|
||||
|
||||
- 把图片、视频、画布、分镜这种视觉业务做深
|
||||
|
||||
### DeerFlow 强于我们的地方
|
||||
|
||||
1. 通用子代理体系更成熟
|
||||
2. 沙箱执行更完整
|
||||
3. MCP 和工具生态更平台化
|
||||
4. 长期记忆体系更偏平台级
|
||||
|
||||
### 我们强于 DeerFlow 的地方
|
||||
|
||||
1. 图片目标定位更细
|
||||
2. 画布联动更深
|
||||
3. 连续修图/连续生图更贴业务
|
||||
4. 分镜视频确认流程更贴实际视觉生产
|
||||
|
||||
---
|
||||
|
||||
## 9. 现在该怎么评价我们和 DeerFlow 的差距
|
||||
|
||||
现在最准确的说法不应该是:
|
||||
|
||||
> 我们只是一个普通业务项目,DeerFlow 才是真 Agent。
|
||||
|
||||
这个说法已经不对了。
|
||||
|
||||
现在更准确的说法是:
|
||||
|
||||
> 我们已经开始构建自己的统一 Agent Runtime,
|
||||
> 但它仍然是**视觉业务优先**的 Agent Runtime,
|
||||
> 不是 DeerFlow 那种**通用超级 Agent 底座**。
|
||||
|
||||
所以差距主要不在“有没有 Agent”,而在:
|
||||
|
||||
### 我们还缺这些
|
||||
|
||||
1. 更彻底的 route 解耦
|
||||
`app/(chat)/api/chat/route.ts` 仍然是超大总控(现在 9065 行)
|
||||
|
||||
2. 更强的通用子代理并发体系
|
||||
当前已有 subagent,但还不算真正的多工人协作系统
|
||||
|
||||
3. 更成熟的长期记忆
|
||||
现在更强的是会话工作记忆,不是跨会话长期人格/知识记忆
|
||||
|
||||
4. 更彻底的执行环境抽象
|
||||
我们强在视觉业务执行,不强在通用沙箱型任务执行
|
||||
|
||||
### 但我们已经补上的
|
||||
|
||||
1. 统一会话协议
|
||||
2. 统一 workflow 语义
|
||||
3. 统一协议事件
|
||||
4. 统一前端调试入口
|
||||
5. 统一图片/分镜连续任务理解
|
||||
|
||||
这说明我们不是“还没开始”,而是已经跨过了最难的第一阶段。
|
||||
|
||||
---
|
||||
|
||||
## 10. 今天最该纠正的旧判断
|
||||
|
||||
下面这些是现在必须纠正的:
|
||||
|
||||
### 旧判断 1
|
||||
|
||||
> 当前项目还是一堆业务补丁,没形成统一运行时。
|
||||
|
||||
#### 现在更准确
|
||||
|
||||
已经形成了统一运行时骨架:
|
||||
|
||||
- action resolver
|
||||
- memory composer
|
||||
- thread state builder
|
||||
- state protocol
|
||||
- event schema
|
||||
- debug stream
|
||||
|
||||
还没彻底清完旧逻辑,但不能再说“没有统一运行时”了。
|
||||
|
||||
### 旧判断 2
|
||||
|
||||
> specialist 只是一些工具白名单,不算真子代理。
|
||||
|
||||
#### 现在更准确
|
||||
|
||||
已经进入早期子代理阶段:
|
||||
|
||||
- `storyboard-subagent`
|
||||
- `image-generation-subagent`
|
||||
- `image-edit-subagent`
|
||||
|
||||
它们已经能改变 planning hints、tool 选择、workflow 行为和对话理解。
|
||||
|
||||
### 旧判断 3
|
||||
|
||||
> 当前项目只擅长单轮修图/生图。
|
||||
|
||||
#### 现在更准确
|
||||
|
||||
多轮能力已经明显增强:
|
||||
|
||||
- 连续生图
|
||||
- 连续修图
|
||||
- 提示词接力
|
||||
- 分镜确认
|
||||
- 单镜头修改
|
||||
- 单镜头重生
|
||||
|
||||
### 旧判断 4
|
||||
|
||||
> 这套系统不够像真人对话。
|
||||
|
||||
#### 现在更准确
|
||||
|
||||
它离“真人协作理解”还没完全到位,但已经开始具备几个核心特征:
|
||||
|
||||
- 记得上一轮的提示词
|
||||
- 记得上一轮的目标图和结果图
|
||||
- 知道当前这句话是在继续任务还是新开任务
|
||||
- 知道贵活要不要先确认
|
||||
- 能理解“继续改上一版 / 按上一个 prompt 来 / 第三镜头改成……”
|
||||
|
||||
这已经不是普通问答模型的水平了。
|
||||
|
||||
---
|
||||
|
||||
## 11. 我现在给这个项目的最准确定位
|
||||
|
||||
今天之后,我会把这个项目定位成:
|
||||
|
||||
> **一个以视觉生产为核心、正在持续进化成统一 Agent Runtime 的内容创作系统。**
|
||||
|
||||
不是:
|
||||
|
||||
- 普通聊天机器人
|
||||
- 只会调几个工具的脚手架
|
||||
- 纯文档阶段的 Agent 方案
|
||||
|
||||
也还不是:
|
||||
|
||||
- DeerFlow 那种通用超级 Agent 操作系统
|
||||
|
||||
而是处在中间的一个非常关键阶段:
|
||||
|
||||
**业务已经很深,底座开始统一。**
|
||||
|
||||
这其实是最有价值的阶段。
|
||||
|
||||
---
|
||||
|
||||
## 12. 现在最值得继续做的事
|
||||
|
||||
如果后面继续投入,最值得的方向不是乱加功能,而是继续收敛:
|
||||
|
||||
### 12.1 继续把旧逻辑迁入统一协议
|
||||
|
||||
现在最该做的,是继续把 `chat/route.ts` 里剩下的零散判断收进:
|
||||
|
||||
- `conversation state protocol`
|
||||
- subagents
|
||||
- workflow state
|
||||
|
||||
### 12.2 把协议消费侧再做强
|
||||
|
||||
现在已经有协议调试事件和前端调试面板了,下一步可以继续做:
|
||||
|
||||
- 后台协议事件查看
|
||||
- 任务追踪面板
|
||||
- Agent 解释路径可视化
|
||||
|
||||
### 12.3 长期记忆继续补强
|
||||
|
||||
下一步建议补:
|
||||
|
||||
- 用户长期风格偏好
|
||||
- 常用画幅/清晰度/数量偏好
|
||||
- 品牌/行业上下文
|
||||
- 跨项目偏好
|
||||
|
||||
### 12.4 真正统一“图片/视频/画布/分镜”工作流状态
|
||||
|
||||
现在已经有:
|
||||
|
||||
- `storyboard_video`
|
||||
- `image_generation_followup`
|
||||
- `image_edit_followup`
|
||||
|
||||
后面可以继续扩成完整的统一 workflow catalog。
|
||||
|
||||
---
|
||||
|
||||
## 13. 最后一句人话总结
|
||||
|
||||
### DeerFlow
|
||||
|
||||
像一个通用的超级智能体操作系统。
|
||||
|
||||
### 我们现在的项目
|
||||
|
||||
像一个已经非常懂视觉生产业务、而且今天开始真正长出统一 Agent 神经系统的创作引擎。
|
||||
|
||||
### 今天之后最准确的一句话
|
||||
|
||||
**不要再把我们看成“很多智能 patch 拼起来的聊天应用”,
|
||||
它已经在往“统一会话协议驱动的视觉 Agent Runtime”演进了。**
|
||||
|
||||
---
|
||||
|
||||
## 资料来源
|
||||
|
||||
### 本项目代码与文档
|
||||
|
||||
- `app/(chat)/api/chat/route.ts`
|
||||
- `app/(chat)/api/chat/schema.ts`
|
||||
- `components/chat.tsx`
|
||||
- `components/protocol-debug-panel.tsx`
|
||||
- `lib/ai/auto-agent-graph.ts`
|
||||
- `lib/ai/smart-analyzer.ts`
|
||||
- `lib/ai/skill-router.ts`
|
||||
- `lib/ai/specialists/index.ts`
|
||||
- `lib/ai/conversation-action-resolver.ts`
|
||||
- `lib/ai/conversation-memory-composer.ts`
|
||||
- `lib/ai/conversation-thread-state-builder.ts`
|
||||
- `lib/ai/conversation-state-protocol.ts`
|
||||
- `lib/ai/conversation-state-protocol-events.ts`
|
||||
- `lib/ai/prompt-memory.ts`
|
||||
- `lib/ai/image-workflow-memory.ts`
|
||||
- `lib/ai/image-generation-subagent.ts`
|
||||
- `lib/ai/image-edit-subagent.ts`
|
||||
- `lib/video-agent/*`
|
||||
- `docs/DeerFlow调研与Agent升级建议-2026-03-12.md`
|
||||
|
||||
### DeerFlow 官方资料
|
||||
|
||||
- 官方仓库 README:<https://github.com/bytedance/deer-flow>
|
||||
- 架构/运行说明:仓库内 `README`、`backend/README`、`backend/CLAUDE.md`
|
||||
123
lib/ai/conversation-state-protocol-events.ts
Normal file
123
lib/ai/conversation-state-protocol-events.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import "server-only";
|
||||
|
||||
import { z } from "zod";
|
||||
|
||||
const stringArraySchema = z.array(z.string().trim()).optional();
|
||||
const nullableStringArraySchema = stringArraySchema.nullable().optional();
|
||||
|
||||
export const conversationStateEventSchemas = {
|
||||
prompt_only_ready: z.object({
|
||||
userId: z.string().uuid(),
|
||||
chatId: z.string().uuid(),
|
||||
projectId: z.string().uuid().nullable().optional(),
|
||||
messageId: z.string().uuid(),
|
||||
text: z.string(),
|
||||
optimizedPrompt: z.string().min(1),
|
||||
imageSummary: z.string().optional(),
|
||||
sourceImageUrls: nullableStringArraySchema,
|
||||
}),
|
||||
grid_confirmation_requested: z.object({
|
||||
userId: z.string().uuid(),
|
||||
chatId: z.string().uuid(),
|
||||
projectId: z.string().uuid().nullable().optional(),
|
||||
messageId: z.string().uuid(),
|
||||
text: z.string(),
|
||||
pendingGridConfirmation: z.object({
|
||||
gridType: z.enum(["grid_2x2", "grid_3x2", "grid_3x3"]),
|
||||
shotCount: z.union([z.literal(4), z.literal(6), z.literal(9)]),
|
||||
label: z.string().min(1),
|
||||
originalPrompt: z.string().min(1),
|
||||
}),
|
||||
}),
|
||||
storyboard_confirmed: z.object({
|
||||
userId: z.string().uuid(),
|
||||
chatId: z.string().uuid(),
|
||||
projectId: z.string().uuid().nullable().optional(),
|
||||
messageId: z.string().uuid(),
|
||||
text: z.string(),
|
||||
}),
|
||||
image_workflow_updated: z.object({
|
||||
userId: z.string().uuid(),
|
||||
chatId: z.string().uuid(),
|
||||
projectId: z.string().uuid().nullable().optional(),
|
||||
currentIntent: z.enum(["image_gen", "image_edit", "vision"]),
|
||||
currentSubIntent: z.string().nullable().optional(),
|
||||
currentFocusAssetIds: nullableStringArraySchema,
|
||||
currentFocusImageUrls: nullableStringArraySchema,
|
||||
lastGeneratedAssetIds: nullableStringArraySchema,
|
||||
lastGeneratedImageUrls: nullableStringArraySchema,
|
||||
lastResolvedTargetAssetIds: nullableStringArraySchema,
|
||||
lastResolvedTargetImageUrls: nullableStringArraySchema,
|
||||
lastMessageId: z.string().uuid(),
|
||||
lastUserPrompt: z.string(),
|
||||
lastRequestedImageCount: z.number().int().min(1).max(12).optional(),
|
||||
lastSourceImageUrls: nullableStringArraySchema,
|
||||
lastResultImageUrls: nullableStringArraySchema,
|
||||
}),
|
||||
target_resolved: z.object({
|
||||
userId: z.string().uuid(),
|
||||
chatId: z.string().uuid(),
|
||||
projectId: z.string().uuid().nullable().optional(),
|
||||
currentIntent: z.string().nullable().optional(),
|
||||
currentFocusAssetIds: nullableStringArraySchema,
|
||||
currentFocusImageUrls: nullableStringArraySchema,
|
||||
selectedAssetIds: nullableStringArraySchema,
|
||||
selectedImageUrls: nullableStringArraySchema,
|
||||
lastUploadedAssetIds: nullableStringArraySchema,
|
||||
lastUploadedImageUrls: nullableStringArraySchema,
|
||||
lastReferencedAssetIds: nullableStringArraySchema,
|
||||
lastReferencedImageUrls: nullableStringArraySchema,
|
||||
lastResolvedTargetAssetIds: nullableStringArraySchema,
|
||||
lastResolvedTargetImageUrls: nullableStringArraySchema,
|
||||
activeCanvasNodeIds: nullableStringArraySchema,
|
||||
lastCanvasSnapshot: z.unknown().optional(),
|
||||
lastMessageId: z.string().uuid(),
|
||||
text: z.string(),
|
||||
resolutionMode: z.string(),
|
||||
confidence: z.number(),
|
||||
}),
|
||||
video_workflow_updated: z.object({
|
||||
userId: z.string().uuid(),
|
||||
chatId: z.string().uuid(),
|
||||
projectId: z.string().uuid().nullable().optional(),
|
||||
lastMessageId: z.string().uuid(),
|
||||
text: z.string().optional(),
|
||||
status: z.string().optional(),
|
||||
}),
|
||||
agent_preset_completed: z.object({
|
||||
userId: z.string().uuid(),
|
||||
chatId: z.string().uuid(),
|
||||
projectId: z.string().uuid().nullable().optional(),
|
||||
currentIntent: z.enum(["image_gen", "video_gen"]),
|
||||
lastMessageId: z.string().uuid(),
|
||||
text: z.string(),
|
||||
agentPresetId: z.string().uuid(),
|
||||
agentPresetName: z.string().min(1),
|
||||
slotCount: z.number().int().min(0),
|
||||
successCount: z.number().int().min(0),
|
||||
}),
|
||||
storyboard_assistant_updated: z.object({
|
||||
userId: z.string().uuid(),
|
||||
chatId: z.string().uuid(),
|
||||
projectId: z.string().uuid().nullable().optional(),
|
||||
lastMessageId: z.string().uuid(),
|
||||
text: z.string(),
|
||||
storyboardGridType: z.enum(["grid_2x2", "grid_3x2", "grid_3x3"]).optional(),
|
||||
storyboardShotCount: z.union([z.literal(4), z.literal(6), z.literal(9)]).optional(),
|
||||
}),
|
||||
} as const;
|
||||
|
||||
export type ConversationStateEventType = keyof typeof conversationStateEventSchemas;
|
||||
|
||||
export type ConversationStateEventPayload<T extends ConversationStateEventType> = z.infer<
|
||||
(typeof conversationStateEventSchemas)[T]
|
||||
>;
|
||||
|
||||
export type ConversationStateEvent =
|
||||
{
|
||||
[K in ConversationStateEventType]: {
|
||||
type: K;
|
||||
payload: ConversationStateEventPayload<K>;
|
||||
};
|
||||
}[ConversationStateEventType];
|
||||
|
||||
712
lib/ai/conversation-state-protocol.ts
Normal file
712
lib/ai/conversation-state-protocol.ts
Normal file
@ -0,0 +1,712 @@
|
||||
import "server-only";
|
||||
|
||||
import {
|
||||
getConversationWorkingStateByChatId,
|
||||
upsertConversationWorkingState,
|
||||
} from "@/lib/db/queries";
|
||||
import {
|
||||
conversationStateEventSchemas,
|
||||
type ConversationStateEvent,
|
||||
type ConversationStateEventPayload,
|
||||
type ConversationStateEventType,
|
||||
} from "@/lib/ai/conversation-state-protocol-events";
|
||||
import {
|
||||
buildImageWorkflowMemoryUpdate,
|
||||
extractImageWorkflowMemory,
|
||||
} from "@/lib/ai/image-workflow-memory";
|
||||
import type { ResolvedChatMediaInput, ResolvedTargetAssets } from "@/lib/ai/context-memory/types";
|
||||
import {
|
||||
buildGridConfirmationMessage,
|
||||
isGridConfirmationReply,
|
||||
type PendingGridConfirmation,
|
||||
resolveConversationAction,
|
||||
extractPendingGridConfirmationFromWorkingState,
|
||||
} from "@/lib/ai/conversation-action-resolver";
|
||||
import {
|
||||
composeConversationMemory,
|
||||
type ComposedConversationMemory,
|
||||
} from "@/lib/ai/conversation-memory-composer";
|
||||
import {
|
||||
buildConversationThreadState,
|
||||
} from "@/lib/ai/conversation-thread-state-builder";
|
||||
import type { ImageWorkflowMemory } from "@/lib/ai/image-workflow-memory";
|
||||
import type { PromptMemoryEntry } from "@/lib/ai/prompt-memory";
|
||||
import {
|
||||
extractConfirmedPromptFromWorkingState,
|
||||
extractPromptMemoryFromWorkingState,
|
||||
buildPromptMemoryUpdate,
|
||||
} from "@/lib/ai/prompt-memory";
|
||||
import type { AgentThreadState } from "@/lib/ai/runtime/agent-thread-state";
|
||||
import type { StoryboardPreferenceMemory } from "@/lib/video-agent/storyboard-preference-memory";
|
||||
import {
|
||||
extractStoryboardPreferenceMemory,
|
||||
buildStoryboardPreferenceMemoryUpdate,
|
||||
} from "@/lib/video-agent/storyboard-preference-memory";
|
||||
|
||||
type StoryboardTaskDetail = Parameters<typeof buildConversationThreadState>[0]["activeStoryboardTask"];
|
||||
|
||||
export interface ConversationStateProtocolBootstrap {
|
||||
workingState: {
|
||||
lastTurnSummary?: unknown;
|
||||
} | null;
|
||||
activeStoryboardTask: StoryboardTaskDetail | null;
|
||||
storyboardPreferenceMemory: StoryboardPreferenceMemory | null;
|
||||
imageWorkflowMemory: ImageWorkflowMemory | null;
|
||||
promptMemory: PromptMemoryEntry | null;
|
||||
pendingGridConfirmation: PendingGridConfirmation | null;
|
||||
confirmedPrompt: string | undefined;
|
||||
actionResolution: ReturnType<typeof resolveConversationAction>;
|
||||
}
|
||||
|
||||
export interface ConversationStateProtocolComposed {
|
||||
baseThreadState: AgentThreadState | null;
|
||||
composedConversationMemory: ComposedConversationMemory;
|
||||
threadState: AgentThreadState;
|
||||
}
|
||||
|
||||
export function resolveTargetExecutionOverride(params: {
|
||||
hasImage: boolean;
|
||||
hasSelectedImageHint: boolean;
|
||||
hasExplicitTargetHint: boolean;
|
||||
hasActionableEditIntent: boolean;
|
||||
executionMode?: string;
|
||||
friendlyMessage?: string;
|
||||
optimizedPrompt?: string;
|
||||
requestText: string;
|
||||
}) {
|
||||
const isExecutionMode =
|
||||
params.executionMode === "edit_image" || params.executionMode === "generate_image";
|
||||
const shouldBypassClarification =
|
||||
params.hasImage &&
|
||||
params.hasActionableEditIntent &&
|
||||
(params.hasSelectedImageHint || params.hasExplicitTargetHint) &&
|
||||
isExecutionMode;
|
||||
|
||||
if (!shouldBypassClarification) {
|
||||
return {
|
||||
shouldBypassClarification: false,
|
||||
nextFriendlyMessage: params.friendlyMessage,
|
||||
nextOptimizedPrompt: params.optimizedPrompt,
|
||||
};
|
||||
}
|
||||
|
||||
const shouldReplaceFriendlyMessage =
|
||||
!params.friendlyMessage ||
|
||||
/[??]$/.test(params.friendlyMessage.trim()) ||
|
||||
/哪一张|哪个|请先确认/.test(params.friendlyMessage);
|
||||
|
||||
return {
|
||||
shouldBypassClarification: true,
|
||||
nextFriendlyMessage: shouldReplaceFriendlyMessage
|
||||
? "收到,我将按你指定的目标图直接执行。"
|
||||
: params.friendlyMessage,
|
||||
nextOptimizedPrompt: params.optimizedPrompt || params.requestText.trim() || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveGridConfirmationRequest(params: {
|
||||
requestedGrid: PendingGridConfirmation | null;
|
||||
hasActiveStoryboardTask: boolean;
|
||||
executionMode?: string;
|
||||
detectedIntent?: string;
|
||||
requestText: string;
|
||||
effectiveInputText: string;
|
||||
}) {
|
||||
const isGridTaskCandidate =
|
||||
params.executionMode === "generate_image" ||
|
||||
params.executionMode === "generate_video" ||
|
||||
params.detectedIntent === "image_gen" ||
|
||||
params.detectedIntent === "video_gen";
|
||||
|
||||
const shouldRequestConfirmation =
|
||||
Boolean(params.requestedGrid) &&
|
||||
!isGridConfirmationReply(params.requestText) &&
|
||||
!params.hasActiveStoryboardTask &&
|
||||
isGridTaskCandidate;
|
||||
|
||||
if (!shouldRequestConfirmation || !params.requestedGrid) {
|
||||
return {
|
||||
shouldRequestConfirmation: false,
|
||||
pendingGridConfirmation: null as PendingGridConfirmation | null,
|
||||
clarificationMessage: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
shouldRequestConfirmation: true,
|
||||
pendingGridConfirmation: {
|
||||
...params.requestedGrid,
|
||||
originalPrompt: params.effectiveInputText,
|
||||
},
|
||||
clarificationMessage: buildGridConfirmationMessage(params.requestedGrid),
|
||||
};
|
||||
}
|
||||
|
||||
type ConversationStateProtocolPersistParams = {
|
||||
userId: string;
|
||||
chatId: string;
|
||||
projectId?: string | null;
|
||||
currentIntent?: string | null;
|
||||
currentSubIntent?: string | null;
|
||||
currentFocusAssetIds?: string[] | null;
|
||||
currentFocusImageUrls?: string[] | null;
|
||||
selectedAssetIds?: string[] | null;
|
||||
selectedImageUrls?: string[] | null;
|
||||
lastUploadedAssetIds?: string[] | null;
|
||||
lastUploadedImageUrls?: string[] | null;
|
||||
lastGeneratedAssetIds?: string[] | null;
|
||||
lastGeneratedImageUrls?: string[] | null;
|
||||
lastReferencedAssetIds?: string[] | null;
|
||||
lastReferencedImageUrls?: string[] | null;
|
||||
lastResolvedTargetAssetIds?: string[] | null;
|
||||
lastResolvedTargetImageUrls?: string[] | null;
|
||||
activeCanvasNodeIds?: string[] | null;
|
||||
lastCanvasSnapshot?: unknown;
|
||||
lastMessageId?: string | null;
|
||||
incrementTurnCount?: boolean;
|
||||
lastTurnSummaryPatch?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
type ConversationStateProtocolEventObserver = (event: {
|
||||
type: ConversationStateEventType;
|
||||
payload: Record<string, unknown>;
|
||||
}) => void;
|
||||
|
||||
let protocolEventObserver: ConversationStateProtocolEventObserver | null = null;
|
||||
|
||||
export function bootstrapConversationStateProtocol(params: {
|
||||
text: string;
|
||||
workingState?: {
|
||||
lastTurnSummary?: unknown;
|
||||
} | null;
|
||||
activeStoryboardTask?: StoryboardTaskDetail | null;
|
||||
hasImageInputForPromptCarryForward: boolean;
|
||||
}) {
|
||||
const storyboardPreferenceMemory = extractStoryboardPreferenceMemory(params.workingState);
|
||||
const imageWorkflowMemory = extractImageWorkflowMemory(params.workingState);
|
||||
const promptMemory = extractPromptMemoryFromWorkingState(params.workingState);
|
||||
const pendingGridConfirmation =
|
||||
extractPendingGridConfirmationFromWorkingState(params.workingState);
|
||||
const confirmedPrompt =
|
||||
extractConfirmedPromptFromWorkingState(params.workingState) || promptMemory?.optimizedPrompt;
|
||||
const actionResolution = resolveConversationAction({
|
||||
text: params.text,
|
||||
pendingGridConfirmation,
|
||||
hasActiveStoryboardTask: Boolean(params.activeStoryboardTask?.task?.id),
|
||||
imageWorkflowMemory,
|
||||
confirmedPrompt,
|
||||
hasImageInputForPromptCarryForward: params.hasImageInputForPromptCarryForward,
|
||||
});
|
||||
|
||||
return {
|
||||
workingState: params.workingState || null,
|
||||
activeStoryboardTask: params.activeStoryboardTask || null,
|
||||
storyboardPreferenceMemory,
|
||||
imageWorkflowMemory,
|
||||
promptMemory,
|
||||
pendingGridConfirmation,
|
||||
confirmedPrompt,
|
||||
actionResolution,
|
||||
} satisfies ConversationStateProtocolBootstrap;
|
||||
}
|
||||
|
||||
export function composeConversationStateProtocol(params: {
|
||||
bootstrap: ConversationStateProtocolBootstrap;
|
||||
autoContextText?: string;
|
||||
resolvedMediaInput: ResolvedChatMediaInput;
|
||||
targetResolution: ResolvedTargetAssets;
|
||||
}) {
|
||||
const baseThreadState = buildConversationThreadState({
|
||||
activeStoryboardTask: params.bootstrap.activeStoryboardTask,
|
||||
storyboardPreferenceMemory: params.bootstrap.storyboardPreferenceMemory,
|
||||
promptMemory: params.bootstrap.promptMemory,
|
||||
imageWorkflowMemory: params.bootstrap.imageWorkflowMemory,
|
||||
});
|
||||
|
||||
const composedConversationMemory = composeConversationMemory({
|
||||
autoContextText: params.autoContextText || undefined,
|
||||
resolvedMediaInput: params.resolvedMediaInput,
|
||||
targetResolution: params.targetResolution,
|
||||
promptMemory: params.bootstrap.promptMemory,
|
||||
imageWorkflowMemory: params.bootstrap.imageWorkflowMemory,
|
||||
storyboardPreferenceMemory: params.bootstrap.storyboardPreferenceMemory,
|
||||
threadState: baseThreadState,
|
||||
});
|
||||
|
||||
const threadState: AgentThreadState = {
|
||||
workflowState: baseThreadState?.workflowState || null,
|
||||
memoryHints: Array.from(
|
||||
new Set([
|
||||
...(baseThreadState?.memoryHints || []),
|
||||
...composedConversationMemory.memoryHints,
|
||||
])
|
||||
),
|
||||
contextSummary:
|
||||
composedConversationMemory.contextSummary || baseThreadState?.contextSummary,
|
||||
metadata: {
|
||||
...(baseThreadState?.metadata || {}),
|
||||
...composedConversationMemory.metadata,
|
||||
},
|
||||
};
|
||||
|
||||
return {
|
||||
baseThreadState,
|
||||
composedConversationMemory,
|
||||
threadState,
|
||||
} satisfies ConversationStateProtocolComposed;
|
||||
}
|
||||
|
||||
export async function persistConversationStateProtocol(
|
||||
params: ConversationStateProtocolPersistParams,
|
||||
) {
|
||||
const existing = await getConversationWorkingStateByChatId({
|
||||
chatId: params.chatId,
|
||||
}).catch(() => null);
|
||||
|
||||
const existingSummary =
|
||||
existing?.lastTurnSummary && typeof existing.lastTurnSummary === "object"
|
||||
? (existing.lastTurnSummary as Record<string, unknown>)
|
||||
: {};
|
||||
|
||||
return upsertConversationWorkingState({
|
||||
userId: params.userId,
|
||||
chatId: params.chatId,
|
||||
projectId: params.projectId,
|
||||
currentIntent: params.currentIntent,
|
||||
currentSubIntent: params.currentSubIntent,
|
||||
currentFocusAssetIds: params.currentFocusAssetIds,
|
||||
currentFocusImageUrls: params.currentFocusImageUrls,
|
||||
selectedAssetIds: params.selectedAssetIds,
|
||||
selectedImageUrls: params.selectedImageUrls,
|
||||
lastUploadedAssetIds: params.lastUploadedAssetIds,
|
||||
lastUploadedImageUrls: params.lastUploadedImageUrls,
|
||||
lastGeneratedAssetIds: params.lastGeneratedAssetIds,
|
||||
lastGeneratedImageUrls: params.lastGeneratedImageUrls,
|
||||
lastReferencedAssetIds: params.lastReferencedAssetIds,
|
||||
lastReferencedImageUrls: params.lastReferencedImageUrls,
|
||||
lastResolvedTargetAssetIds: params.lastResolvedTargetAssetIds,
|
||||
lastResolvedTargetImageUrls: params.lastResolvedTargetImageUrls,
|
||||
activeCanvasNodeIds: params.activeCanvasNodeIds,
|
||||
lastCanvasSnapshot: params.lastCanvasSnapshot,
|
||||
lastMessageId: params.lastMessageId,
|
||||
incrementTurnCount: params.incrementTurnCount,
|
||||
lastTurnSummary:
|
||||
params.lastTurnSummaryPatch !== undefined
|
||||
? {
|
||||
...existingSummary,
|
||||
...params.lastTurnSummaryPatch,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
export async function persistConversationStateEvent<T extends ConversationStateEventType>(
|
||||
type: T,
|
||||
payload: ConversationStateEventPayload<T>,
|
||||
) {
|
||||
const schema = conversationStateEventSchemas[type];
|
||||
const parsed = schema.parse(payload) as ConversationStateEvent["payload"];
|
||||
console.log("[ConversationStateProtocol] Event:", {
|
||||
type,
|
||||
chatId: (parsed as any).chatId,
|
||||
userId: (parsed as any).userId,
|
||||
messageId: (parsed as any).messageId,
|
||||
});
|
||||
protocolEventObserver?.({
|
||||
type,
|
||||
payload: parsed as Record<string, unknown>,
|
||||
});
|
||||
|
||||
switch (type) {
|
||||
case "prompt_only_ready": {
|
||||
const typed = parsed as ConversationStateEventPayload<"prompt_only_ready">;
|
||||
const existing = await getConversationWorkingStateByChatId({
|
||||
chatId: typed.chatId,
|
||||
}).catch(() => null);
|
||||
const existingImageWorkflowMemory = extractImageWorkflowMemory(existing);
|
||||
|
||||
return persistConversationStateProtocol({
|
||||
userId: typed.userId,
|
||||
chatId: typed.chatId,
|
||||
projectId: typed.projectId,
|
||||
currentIntent: "vision",
|
||||
currentFocusImageUrls: typed.sourceImageUrls || null,
|
||||
lastResolvedTargetImageUrls: typed.sourceImageUrls || null,
|
||||
lastMessageId: typed.messageId,
|
||||
lastTurnSummaryPatch: {
|
||||
text: typed.text.slice(0, 500),
|
||||
resolutionMode: "prompt_only",
|
||||
confidence: 90,
|
||||
optimizedPrompt: typed.optimizedPrompt.slice(0, 1000),
|
||||
promptMemory: buildPromptMemoryUpdate({
|
||||
optimizedPrompt: typed.optimizedPrompt.slice(0, 1000),
|
||||
sourceIntent: "image_gen",
|
||||
sourceText: typed.text.slice(0, 500),
|
||||
reusable: true,
|
||||
}),
|
||||
imageWorkflowMemory: buildImageWorkflowMemoryUpdate({
|
||||
existing: existingImageWorkflowMemory,
|
||||
lastIntent: "vision",
|
||||
lastOptimizedPrompt: typed.optimizedPrompt.slice(0, 1000),
|
||||
lastUserPrompt: typed.text.slice(0, 500),
|
||||
lastRequestedImageCount: 1,
|
||||
lastSourceImageUrls: typed.sourceImageUrls || null,
|
||||
}),
|
||||
imageSummary: typed.imageSummary?.slice(0, 1200),
|
||||
sourceImageUrls: typed.sourceImageUrls || [],
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
case "grid_confirmation_requested": {
|
||||
const typed = parsed as ConversationStateEventPayload<"grid_confirmation_requested">;
|
||||
const existing = await getConversationWorkingStateByChatId({
|
||||
chatId: typed.chatId,
|
||||
}).catch(() => null);
|
||||
const existingStoryboardMemory = extractStoryboardPreferenceMemory(existing);
|
||||
|
||||
return persistConversationStateProtocol({
|
||||
userId: typed.userId,
|
||||
chatId: typed.chatId,
|
||||
projectId: typed.projectId,
|
||||
currentIntent: "image_gen",
|
||||
lastMessageId: typed.messageId,
|
||||
lastTurnSummaryPatch: {
|
||||
text: typed.text.slice(0, 500),
|
||||
resolutionMode: "grid_confirmation",
|
||||
confidence: 90,
|
||||
storyboardMemory: buildStoryboardPreferenceMemoryUpdate({
|
||||
existing: existingStoryboardMemory,
|
||||
preferredGridType: typed.pendingGridConfirmation.gridType,
|
||||
preferredShotCount: typed.pendingGridConfirmation.shotCount,
|
||||
prefersConfirmation: true,
|
||||
lastUsedGridType: typed.pendingGridConfirmation.gridType,
|
||||
lastUsedShotCount: typed.pendingGridConfirmation.shotCount,
|
||||
}),
|
||||
pendingGridConfirmation: {
|
||||
gridType: typed.pendingGridConfirmation.gridType,
|
||||
shotCount: typed.pendingGridConfirmation.shotCount,
|
||||
label: typed.pendingGridConfirmation.label,
|
||||
originalPrompt: typed.pendingGridConfirmation.originalPrompt.slice(0, 1000),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
case "storyboard_confirmed": {
|
||||
const typed = parsed as ConversationStateEventPayload<"storyboard_confirmed">;
|
||||
const existing = await getConversationWorkingStateByChatId({
|
||||
chatId: typed.chatId,
|
||||
}).catch(() => null);
|
||||
const existingStoryboardMemory = extractStoryboardPreferenceMemory(existing);
|
||||
|
||||
return persistConversationStateProtocol({
|
||||
userId: typed.userId,
|
||||
chatId: typed.chatId,
|
||||
projectId: typed.projectId,
|
||||
currentIntent: "video_gen",
|
||||
lastMessageId: typed.messageId,
|
||||
lastTurnSummaryPatch: {
|
||||
text: typed.text.slice(0, 500),
|
||||
resolutionMode: "storyboard_confirmed",
|
||||
confidence: 95,
|
||||
storyboardMemory: buildStoryboardPreferenceMemoryUpdate({
|
||||
existing: existingStoryboardMemory,
|
||||
prefersConfirmation: true,
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
case "image_workflow_updated": {
|
||||
const typed = parsed as ConversationStateEventPayload<"image_workflow_updated">;
|
||||
const existing = await getConversationWorkingStateByChatId({
|
||||
chatId: typed.chatId,
|
||||
}).catch(() => null);
|
||||
const existingImageWorkflowMemory = extractImageWorkflowMemory(existing);
|
||||
|
||||
return persistConversationStateProtocol({
|
||||
userId: typed.userId,
|
||||
chatId: typed.chatId,
|
||||
projectId: typed.projectId,
|
||||
currentIntent: typed.currentIntent,
|
||||
currentSubIntent: typed.currentSubIntent,
|
||||
currentFocusAssetIds: typed.currentFocusAssetIds,
|
||||
currentFocusImageUrls: typed.currentFocusImageUrls,
|
||||
lastGeneratedAssetIds: typed.lastGeneratedAssetIds,
|
||||
lastGeneratedImageUrls: typed.lastGeneratedImageUrls,
|
||||
lastResolvedTargetAssetIds: typed.lastResolvedTargetAssetIds,
|
||||
lastResolvedTargetImageUrls: typed.lastResolvedTargetImageUrls,
|
||||
lastMessageId: typed.lastMessageId,
|
||||
lastTurnSummaryPatch: {
|
||||
imageWorkflowMemory: buildImageWorkflowMemoryUpdate({
|
||||
existing: existingImageWorkflowMemory,
|
||||
lastIntent: typed.currentIntent,
|
||||
lastUserPrompt: typed.lastUserPrompt.slice(0, 500),
|
||||
lastRequestedImageCount: typed.lastRequestedImageCount,
|
||||
lastSourceImageUrls: typed.lastSourceImageUrls || null,
|
||||
lastResultImageUrls: typed.lastResultImageUrls || null,
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
case "target_resolved": {
|
||||
const typed = parsed as ConversationStateEventPayload<"target_resolved">;
|
||||
return persistConversationStateProtocol({
|
||||
userId: typed.userId,
|
||||
chatId: typed.chatId,
|
||||
projectId: typed.projectId,
|
||||
currentIntent: typed.currentIntent,
|
||||
currentFocusAssetIds: typed.currentFocusAssetIds,
|
||||
currentFocusImageUrls: typed.currentFocusImageUrls,
|
||||
selectedAssetIds: typed.selectedAssetIds,
|
||||
selectedImageUrls: typed.selectedImageUrls,
|
||||
lastUploadedAssetIds: typed.lastUploadedAssetIds,
|
||||
lastUploadedImageUrls: typed.lastUploadedImageUrls,
|
||||
lastReferencedAssetIds: typed.lastReferencedAssetIds,
|
||||
lastReferencedImageUrls: typed.lastReferencedImageUrls,
|
||||
lastResolvedTargetAssetIds: typed.lastResolvedTargetAssetIds,
|
||||
lastResolvedTargetImageUrls: typed.lastResolvedTargetImageUrls,
|
||||
activeCanvasNodeIds: typed.activeCanvasNodeIds,
|
||||
lastCanvasSnapshot: typed.lastCanvasSnapshot,
|
||||
lastMessageId: typed.lastMessageId,
|
||||
incrementTurnCount: true,
|
||||
lastTurnSummaryPatch: {
|
||||
text: typed.text.slice(0, 500),
|
||||
resolutionMode: typed.resolutionMode,
|
||||
confidence: typed.confidence,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
case "video_workflow_updated": {
|
||||
const typed = parsed as ConversationStateEventPayload<"video_workflow_updated">;
|
||||
return persistConversationStateProtocol({
|
||||
userId: typed.userId,
|
||||
chatId: typed.chatId,
|
||||
projectId: typed.projectId,
|
||||
currentIntent: "video_gen",
|
||||
lastMessageId: typed.lastMessageId,
|
||||
lastTurnSummaryPatch:
|
||||
typed.text || typed.status
|
||||
? {
|
||||
text: typed.text?.slice(0, 500),
|
||||
videoWorkflowStatus: typed.status,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
case "agent_preset_completed": {
|
||||
const typed = parsed as ConversationStateEventPayload<"agent_preset_completed">;
|
||||
return persistConversationStateProtocol({
|
||||
userId: typed.userId,
|
||||
chatId: typed.chatId,
|
||||
projectId: typed.projectId,
|
||||
currentIntent: typed.currentIntent,
|
||||
lastMessageId: typed.lastMessageId,
|
||||
lastTurnSummaryPatch: {
|
||||
text: typed.text.slice(0, 500),
|
||||
agentPresetId: typed.agentPresetId,
|
||||
agentPresetName: typed.agentPresetName,
|
||||
slotCount: typed.slotCount,
|
||||
successCount: typed.successCount,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
case "storyboard_assistant_updated": {
|
||||
const typed = parsed as ConversationStateEventPayload<"storyboard_assistant_updated">;
|
||||
const existing = await getConversationWorkingStateByChatId({
|
||||
chatId: typed.chatId,
|
||||
}).catch(() => null);
|
||||
const existingStoryboardMemory = extractStoryboardPreferenceMemory(existing);
|
||||
|
||||
return persistConversationStateProtocol({
|
||||
userId: typed.userId,
|
||||
chatId: typed.chatId,
|
||||
projectId: typed.projectId,
|
||||
currentIntent: "video_gen",
|
||||
lastMessageId: typed.lastMessageId,
|
||||
lastTurnSummaryPatch: {
|
||||
text: typed.text.slice(0, 500),
|
||||
resolutionMode: "storyboard",
|
||||
confidence: 95,
|
||||
storyboardMemory: buildStoryboardPreferenceMemoryUpdate({
|
||||
existing: existingStoryboardMemory,
|
||||
prefersConfirmation: true,
|
||||
lastUsedGridType: typed.storyboardGridType,
|
||||
lastUsedShotCount: typed.storyboardShotCount,
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function setConversationStateProtocolEventObserver(
|
||||
observer: ConversationStateProtocolEventObserver | null,
|
||||
) {
|
||||
protocolEventObserver = observer;
|
||||
}
|
||||
|
||||
export async function persistPromptOnlyTurn(params: {
|
||||
userId: string;
|
||||
chatId: string;
|
||||
projectId?: string | null;
|
||||
messageId: string;
|
||||
text: string;
|
||||
optimizedPrompt: string;
|
||||
imageSummary?: string;
|
||||
sourceImageUrls?: string[] | null;
|
||||
}) {
|
||||
return persistConversationStateEvent("prompt_only_ready", {
|
||||
userId: params.userId,
|
||||
chatId: params.chatId,
|
||||
projectId: params.projectId,
|
||||
messageId: params.messageId,
|
||||
text: params.text,
|
||||
optimizedPrompt: params.optimizedPrompt,
|
||||
imageSummary: params.imageSummary,
|
||||
sourceImageUrls: params.sourceImageUrls || null,
|
||||
});
|
||||
}
|
||||
|
||||
export async function persistGridConfirmationTurn(params: {
|
||||
userId: string;
|
||||
chatId: string;
|
||||
projectId?: string | null;
|
||||
messageId: string;
|
||||
text: string;
|
||||
pendingGridConfirmation: PendingGridConfirmation;
|
||||
}) {
|
||||
return persistConversationStateEvent("grid_confirmation_requested", {
|
||||
userId: params.userId,
|
||||
chatId: params.chatId,
|
||||
projectId: params.projectId,
|
||||
messageId: params.messageId,
|
||||
text: params.text,
|
||||
pendingGridConfirmation: params.pendingGridConfirmation,
|
||||
});
|
||||
}
|
||||
|
||||
export async function persistStoryboardConfirmedTurn(params: {
|
||||
userId: string;
|
||||
chatId: string;
|
||||
projectId?: string | null;
|
||||
messageId: string;
|
||||
text: string;
|
||||
}) {
|
||||
return persistConversationStateEvent("storyboard_confirmed", {
|
||||
userId: params.userId,
|
||||
chatId: params.chatId,
|
||||
projectId: params.projectId,
|
||||
messageId: params.messageId,
|
||||
text: params.text,
|
||||
});
|
||||
}
|
||||
|
||||
export async function persistImageWorkflowTurn(params: {
|
||||
userId: string;
|
||||
chatId: string;
|
||||
projectId?: string | null;
|
||||
currentIntent: "image_gen" | "image_edit" | "vision";
|
||||
currentSubIntent?: string | null;
|
||||
currentFocusAssetIds?: string[] | null;
|
||||
currentFocusImageUrls?: string[] | null;
|
||||
lastGeneratedAssetIds?: string[] | null;
|
||||
lastGeneratedImageUrls?: string[] | null;
|
||||
lastResolvedTargetAssetIds?: string[] | null;
|
||||
lastResolvedTargetImageUrls?: string[] | null;
|
||||
lastMessageId: string;
|
||||
lastUserPrompt: string;
|
||||
lastRequestedImageCount?: number;
|
||||
lastSourceImageUrls?: string[] | null;
|
||||
lastResultImageUrls?: string[] | null;
|
||||
}) {
|
||||
return persistConversationStateEvent("image_workflow_updated", {
|
||||
userId: params.userId,
|
||||
chatId: params.chatId,
|
||||
projectId: params.projectId,
|
||||
currentIntent: params.currentIntent,
|
||||
currentSubIntent: params.currentSubIntent,
|
||||
currentFocusAssetIds: params.currentFocusAssetIds,
|
||||
currentFocusImageUrls: params.currentFocusImageUrls,
|
||||
lastGeneratedAssetIds: params.lastGeneratedAssetIds,
|
||||
lastGeneratedImageUrls: params.lastGeneratedImageUrls,
|
||||
lastResolvedTargetAssetIds: params.lastResolvedTargetAssetIds,
|
||||
lastResolvedTargetImageUrls: params.lastResolvedTargetImageUrls,
|
||||
lastMessageId: params.lastMessageId,
|
||||
lastUserPrompt: params.lastUserPrompt,
|
||||
lastRequestedImageCount: params.lastRequestedImageCount,
|
||||
lastSourceImageUrls: params.lastSourceImageUrls || null,
|
||||
lastResultImageUrls: params.lastResultImageUrls || null,
|
||||
});
|
||||
}
|
||||
|
||||
export async function persistResolvedTargetTurn(params: {
|
||||
userId: string;
|
||||
chatId: string;
|
||||
projectId?: string | null;
|
||||
currentIntent?: string | null;
|
||||
currentFocusAssetIds?: string[] | null;
|
||||
currentFocusImageUrls?: string[] | null;
|
||||
selectedAssetIds?: string[] | null;
|
||||
selectedImageUrls?: string[] | null;
|
||||
lastUploadedAssetIds?: string[] | null;
|
||||
lastUploadedImageUrls?: string[] | null;
|
||||
lastReferencedAssetIds?: string[] | null;
|
||||
lastReferencedImageUrls?: string[] | null;
|
||||
lastResolvedTargetAssetIds?: string[] | null;
|
||||
lastResolvedTargetImageUrls?: string[] | null;
|
||||
activeCanvasNodeIds?: string[] | null;
|
||||
lastCanvasSnapshot?: unknown;
|
||||
lastMessageId: string;
|
||||
text: string;
|
||||
resolutionMode: string;
|
||||
confidence: number;
|
||||
}) {
|
||||
return persistConversationStateEvent("target_resolved", params);
|
||||
}
|
||||
|
||||
export async function persistVideoWorkflowTurn(params: {
|
||||
userId: string;
|
||||
chatId: string;
|
||||
projectId?: string | null;
|
||||
lastMessageId: string;
|
||||
text?: string;
|
||||
status?: string;
|
||||
}) {
|
||||
return persistConversationStateEvent("video_workflow_updated", params);
|
||||
}
|
||||
|
||||
export async function persistAgentPresetTurn(params: {
|
||||
userId: string;
|
||||
chatId: string;
|
||||
projectId?: string | null;
|
||||
currentIntent: "image_gen" | "video_gen";
|
||||
lastMessageId: string;
|
||||
text: string;
|
||||
agentPresetId: string;
|
||||
agentPresetName: string;
|
||||
slotCount: number;
|
||||
successCount: number;
|
||||
}) {
|
||||
return persistConversationStateEvent("agent_preset_completed", params);
|
||||
}
|
||||
|
||||
export async function persistStoryboardAssistantTurn(params: {
|
||||
userId: string;
|
||||
chatId: string;
|
||||
projectId?: string | null;
|
||||
lastMessageId: string;
|
||||
text: string;
|
||||
storyboardGridType?: "grid_2x2" | "grid_3x2" | "grid_3x3";
|
||||
storyboardShotCount?: 4 | 6 | 9;
|
||||
}) {
|
||||
return persistConversationStateEvent("storyboard_assistant_updated", params);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user