108 lines
3.9 KiB
TypeScript
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),
|
|
});
|
|
}
|