108 lines
3.9 KiB
TypeScript

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<Record<string, any>>;
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),
});
}