import { resolveChatMediaInput } from "@/lib/ai/context-memory/media-input"; import { resolveTargetAssets } from "@/lib/ai/context-memory/target-resolution"; import { persistImageWorkflowTurn } from "@/lib/ai/conversation-state-protocol"; import { getConversationWorkingStateByChatId, getMediaAssetsByUserAndUrls } from "@/lib/db/queries"; import { readCanvasImages, readOwnedCanvas, type RuntimeImageNode } from "./canvas"; import type { AgentRunSnapshot } from "./protocol"; import { finalImageResultNodeIds } from "./results"; import { AgentRunError } from "./store"; export async function resolveRuntimeConversationSources(params: { userId: string; chatId: string; projectId: string; text: string; parts: Array>; nodes: RuntimeImageNode[]; selectedNodeIds: string[]; }) { const workingState = await getConversationWorkingStateByChatId({ chatId: params.chatId }); const imageNodes = params.nodes.filter((node) => node.type === "image"); const normalize = (url: string) => url.split("?")[0].split("#")[0]; const files = params.parts .filter((part) => part.type === "file") .map((part) => normalize(part.url)); if ( files.some( (url) => !imageNodes.some((node) => normalize(node.data.src || node.data.url || "") === url), ) ) { throw new AgentRunError("上传图片尚未保存到画布,请稍后重试。"); } const context = { source: "canvas" as const, projectId: params.projectId, selectedNodeIds: params.selectedNodeIds, canvasImageNodes: imageNodes.map((node) => ({ nodeId: node.id, url: node.data.src || node.data.url || "", title: node.data.title, x: node.position.x, y: node.position.y, width: node.size?.width, height: node.size?.height, isSelected: params.selectedNodeIds.includes(node.id), })), }; const mediaInput = resolveChatMediaInput({ messageParts: params.parts, historyMessages: [], contextPayload: context, maxImages: 20, }); const target = resolveTargetAssets({ text: params.text, mediaInput, contextPayload: context, workingState: workingState || undefined, }); const sourceNodeIds = target.targetRefs.flatMap((ref) => { const node = imageNodes.find( (item) => item.id === ref.nodeId || normalize(item.data.src || item.data.url || "") === normalize(ref.url), ); return node ? [node.id] : []; }); return { sourceNodeIds: [...new Set(sourceNodeIds)], targetReason: target.reason, previousContext: workingState?.lastTurnSummary ? JSON.stringify(workingState.lastTurnSummary).slice(0, 4000) : undefined, }; } export async function persistRuntimeConversationResults(params: { userId: string; chatId: string; projectId: string; assistantMessageId: string; snapshot: AgentRunSnapshot; }) { if (!params.snapshot.resultNodeIds.length) return; const current = await readOwnedCanvas(params.userId, params.projectId); const ids = finalImageResultNodeIds(params.snapshot, current.nodes); if (!ids.length) return; const nodes = await readCanvasImages(params.userId, params.projectId, ids); const urls = nodes.map((node) => node.data.src || node.data.url!); const assets = await getMediaAssetsByUserAndUrls({ userId: params.userId, urls }); const assetIds = assets.map((asset) => asset.id); await persistImageWorkflowTurn({ userId: params.userId, chatId: params.chatId, projectId: params.projectId, currentIntent: "image_edit", currentFocusAssetIds: assetIds, currentFocusImageUrls: urls, lastGeneratedAssetIds: assetIds, lastGeneratedImageUrls: urls, lastResolvedTargetAssetIds: assetIds, lastResolvedTargetImageUrls: urls, lastMessageId: params.assistantMessageId, lastUserPrompt: params.snapshot.objective, lastResultImageUrls: urls, lastRequestedImageCount: Math.min(12, urls.length), }); }