import "server-only"; import { and, eq } from "drizzle-orm"; import { extractAgentProjectStateFromCanvasData, isAgentProjectRow } from "@/lib/agents/agent-project-compat"; import { appendAgentProjectResultForSave, mergeAgentProjectStateForSave, } from "@/lib/agents/agent-project-save-merge"; import { db } from "@/lib/db"; import { canvasProject } from "@/lib/db/schema"; import type { AgentGenerationReplacementResultRecord, AgentGenerationResultModeType, } from "@/types/agent"; const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; function getCanvasDataRecord(value: unknown) { return value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : {}; } export async function persistAgentProjectGeneratedResult(params: { projectId: string; userId: string; imageUrl: string; modeType: AgentGenerationResultModeType; }) { const projectId = String(params.projectId || "").trim(); const userId = String(params.userId || "").trim(); const imageUrl = String(params.imageUrl || "").trim(); if (!UUID_PATTERN.test(projectId) || !UUID_PATTERN.test(userId) || !/^https?:\/\//i.test(imageUrl)) { return false; } return db.transaction(async (tx) => { const [project] = await tx .select() .from(canvasProject) .where(and(eq(canvasProject.id, projectId), eq(canvasProject.userId, userId))) .limit(1) .for("update"); if (!project || !isAgentProjectRow(project)) { return false; } const nextProjectState = appendAgentProjectResultForSave({ existingState: extractAgentProjectStateFromCanvasData(project.canvasData), modeType: params.modeType, imageUrl, }); const canvasData = getCanvasDataRecord(project.canvasData); const [updatedProject] = await tx .update(canvasProject) .set({ canvasData: { ...canvasData, nodes: Array.isArray(canvasData.nodes) ? canvasData.nodes : [], agentProjectState: nextProjectState, } as unknown as typeof canvasProject.$inferInsert.canvasData, thumbnail: project.thumbnail || imageUrl, workspaceType: "agent", updatedAt: new Date(), }) .where(and(eq(canvasProject.id, projectId), eq(canvasProject.userId, userId))) .returning({ id: canvasProject.id }); return Boolean(updatedProject); }); } export async function persistAgentDetailReplacementCard(params: { projectId: string; userId: string; cardId: string; imageUrl: string; referenceImageUrl: string; label: string; status: AgentGenerationReplacementResultRecord["status"]; text: string; }) { if ( !UUID_PATTERN.test(params.projectId) || !UUID_PATTERN.test(params.userId) || !params.cardId || !/^https?:\/\//i.test(params.imageUrl) ) { return false; } return db.transaction(async (tx) => { const [project] = await tx .select() .from(canvasProject) .where(and(eq(canvasProject.id, params.projectId), eq(canvasProject.userId, params.userId))) .limit(1) .for("update"); if (!project || !isAgentProjectRow(project)) return false; const existingState = extractAgentProjectStateFromCanvasData(project.canvasData); const record: AgentGenerationReplacementResultRecord = { url: params.imageUrl, text: params.text, status: params.status, updatedAt: Date.now(), compareSourceUrl: params.referenceImageUrl, referenceImageUrl: params.referenceImageUrl, label: params.label, }; const nextState = mergeAgentProjectStateForSave({ existingState, incomingState: { ...existingState, resultCollections: { ...existingState.resultCollections, detail_replacement: [ ...(existingState.resultCollections?.detail_replacement || []), params.imageUrl, ], }, replacementResultState: { ...existingState.replacementResultState, detail_replacement: { ...existingState.replacementResultState?.detail_replacement, [params.cardId]: record, }, }, }, }); const canvasData = getCanvasDataRecord(project.canvasData); await tx.update(canvasProject).set({ canvasData: { ...canvasData, nodes: Array.isArray(canvasData.nodes) ? canvasData.nodes : [], agentProjectState: nextState, } as typeof canvasProject.$inferInsert.canvasData, thumbnail: project.thumbnail || params.imageUrl, updatedAt: new Date(), }).where(eq(canvasProject.id, params.projectId)); return true; }); }