import "server-only"; import type { AgentThreadState } from "@/lib/ai/runtime/agent-thread-state"; import type { ImageWorkflowMemory } from "@/lib/ai/image-workflow-memory"; import type { PromptMemoryEntry } from "@/lib/ai/prompt-memory"; import { applyContextLineLimit, applyMemoryHintLimit, type MemoryRelevancePolicy, } from "@/lib/ai/runtime/memory-relevance"; import type { StoryboardPreferenceMemory } from "@/lib/video-agent/storyboard-preference-memory"; import type { ResolvedChatMediaInput, ResolvedTargetAssets, } from "@/lib/ai/context-memory/types"; export interface ComposedConversationMemory { contextSummary: string; memoryHints: string[]; metadata: Record; } function normalizeText(value: unknown, max = 400): string | undefined { const normalized = String(value || "").trim(); if (!normalized) return undefined; return normalized.slice(0, max); } function uniqueStrings(values: Array) { return Array.from( new Set( values .map((item) => normalizeText(item, 240)) .filter((item): item is string => Boolean(item)) ) ); } function summarizeTargetResolution(target?: ResolvedTargetAssets | null) { if (!target) return { lines: [] as string[], metadata: {} as Record }; const lines: string[] = []; lines.push( `当前目标解析:mode=${target.resolutionMode}; confidence=${target.confidence}; fromHistory=${ target.fromHistory ? "yes" : "no" }` ); if (target.targetImageUrls.length > 0) { lines.push(`当前命中的目标图片数:${target.targetImageUrls.length}`); } if (target.reason) { lines.push(`目标解析原因:${target.reason}`); } return { lines, metadata: { targetResolutionMode: target.resolutionMode, targetResolutionConfidence: target.confidence, targetImageCount: target.targetImageUrls.length, targetReason: target.reason, }, }; } function summarizeResolvedMedia(media?: ResolvedChatMediaInput | null) { if (!media) return { lines: [] as string[], metadata: {} as Record }; return { lines: [ `当前媒体输入:source=${media.source}; hasImage=${media.hasImage ? "yes" : "no"}`, media.currentMessageImageUrls.length > 0 ? `本轮上传图片数:${media.currentMessageImageUrls.length}` : "", media.selectedImageUrls.length > 0 ? `当前选中图片数:${media.selectedImageUrls.length}` : "", media.historyAssistantImageUrls.length + media.historyUserImageUrls.length > 0 ? `历史可参考图片数:${ media.historyAssistantImageUrls.length + media.historyUserImageUrls.length }` : "", ].filter(Boolean), metadata: { mediaSource: media.source, hasImage: media.hasImage, currentImageCount: media.currentMessageImageUrls.length, selectedImageCount: media.selectedImageUrls.length, historyImageCount: media.historyAssistantImageUrls.length + media.historyUserImageUrls.length, }, }; } function summarizePromptMemory(promptMemory?: PromptMemoryEntry | null) { if (!promptMemory?.optimizedPrompt) { return { lines: [] as string[], hints: [] as string[], metadata: {} as Record }; } const preview = promptMemory.optimizedPrompt.replace(/\s+/g, " ").trim().slice(0, 220); return { lines: [`上一轮可复用提示词:${preview}`], hints: [ "如果用户明确要求沿用上一轮提示词继续做图,优先把这段提示词作为本轮生成基础。", ], metadata: { promptMemory, }, }; } function summarizeImageWorkflow(memory?: ImageWorkflowMemory | null) { if (!memory) { return { lines: [] as string[], hints: [] as string[], metadata: {} as Record }; } const lines = [ memory.lastOptimizedPrompt ? `上一轮优化提示词摘要:${memory.lastOptimizedPrompt.replace(/\s+/g, " ").slice(0, 180)}` : "", memory.lastRequestedImageCount ? `上一轮目标出图数:${memory.lastRequestedImageCount} 张` : "", memory.lastAspectRatio ? `上一轮画幅:${memory.lastAspectRatio}` : "", memory.lastImageSize ? `上一轮清晰度:${memory.lastImageSize}` : "", memory.lastSourceImageUrls?.length ? `上一轮参考/目标图数量:${memory.lastSourceImageUrls.length}` : "", memory.lastResultImageUrls?.length ? `上一轮生成结果数量:${memory.lastResultImageUrls.length}` : "", ].filter(Boolean); const hints = memory.lastOptimizedPrompt ? ["如果用户要求沿用上一轮提示词继续做图,应优先复用这段提示词。"] : []; return { lines, hints, metadata: { imageWorkflowMemory: memory, }, }; } function summarizeStoryboardPreference(memory?: StoryboardPreferenceMemory | null) { if (!memory) { return { lines: [] as string[], hints: [] as string[], metadata: {} as Record }; } const lines = [ memory.preferredGridType ? `用户偏好宫格:${memory.preferredGridType}` : "", memory.preferredShotCount ? `用户偏好镜头数:${memory.preferredShotCount}` : "", memory.prefersConfirmation === true ? "用户偏好先确认再执行" : "", memory.lastUsedGridType && memory.lastUsedShotCount ? `最近一次分镜任务:${memory.lastUsedGridType} / ${memory.lastUsedShotCount} 段` : "", ].filter(Boolean); const hints = memory.prefersConfirmation ? ["如果当前任务涉及分镜或高成本执行,优先先确认再推进。"] : []; return { lines, hints, metadata: { storyboardPreferenceMemory: memory, }, }; } export function composeConversationMemory(params: { autoContextText?: string; resolvedMediaInput?: ResolvedChatMediaInput | null; targetResolution?: ResolvedTargetAssets | null; promptMemory?: PromptMemoryEntry | null; imageWorkflowMemory?: ImageWorkflowMemory | null; storyboardPreferenceMemory?: StoryboardPreferenceMemory | null; threadState?: AgentThreadState | null; memoryRelevancePolicy?: MemoryRelevancePolicy; }) { const targetSummary = summarizeTargetResolution(params.targetResolution); const mediaSummary = summarizeResolvedMedia(params.resolvedMediaInput); const promptSummary = summarizePromptMemory(params.promptMemory); const imageWorkflowSummary = summarizeImageWorkflow(params.imageWorkflowMemory); const storyboardSummary = summarizeStoryboardPreference(params.storyboardPreferenceMemory); const contextSummary = uniqueStrings([ params.autoContextText, ...mediaSummary.lines, ...targetSummary.lines, ...promptSummary.lines, ...imageWorkflowSummary.lines, ...storyboardSummary.lines, ]).join("\n"); const memoryHints = uniqueStrings([ ...(params.threadState?.memoryHints || []), ...promptSummary.hints, ...imageWorkflowSummary.hints, ...storyboardSummary.hints, ]); const metadata: Record = { ...(params.threadState?.metadata || {}), ...mediaSummary.metadata, ...targetSummary.metadata, ...promptSummary.metadata, ...imageWorkflowSummary.metadata, ...storyboardSummary.metadata, }; return { contextSummary: params.memoryRelevancePolicy ? applyContextLineLimit(contextSummary, params.memoryRelevancePolicy.maxContextLines) : contextSummary, memoryHints: params.memoryRelevancePolicy ? applyMemoryHintLimit(memoryHints, params.memoryRelevancePolicy.maxMemoryHints) : memoryHints, metadata, } satisfies ComposedConversationMemory; }