313 lines
10 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { and, eq } from "drizzle-orm";
import { auth } from "@/app/(auth)/auth";
import { db } from "@/lib/db";
import { canvasProject } from "@/lib/db/schema";
import {
extractAgentProjectStateFromCanvasData,
isAgentProjectRow,
} from "@/lib/agents/agent-project-compat";
import { normalizeAgentGenerationProjectPersistedState } from "@/lib/agents/generation-project-state";
import type {
AgentGenerationProjectPersistedState,
AgentGenerationProjectReplacementResultState,
AgentGenerationProjectResultCollections,
} from "@/types/agent";
export const dynamic = "force-dynamic";
export const revalidate = 0;
function mapAgentProjectRow(row: typeof canvasProject.$inferSelect) {
return {
id: row.id,
userId: row.userId,
title: row.title,
description: row.description || null,
thumbnail: row.thumbnail || null,
projectState: extractAgentProjectStateFromCanvasData(row.canvasData),
createdAt: row.createdAt,
updatedAt: row.updatedAt,
};
}
const RESULT_COLLECTION_KEYS = [
"scene_generation",
"detail_generation",
"detail_replacement",
"sku_replacement",
] as const;
const REPLACEMENT_RESULT_KEYS = [
"detail_replacement",
"sku_replacement",
] as const;
function normalizeProjectResultUrlList(urls: string[] | null | undefined) {
return Array.from(
new Set(
(urls || [])
.map((url) => String(url || "").trim())
.filter((url) => /^https?:\/\//i.test(url))
)
);
}
function mergeResultCollections(
existing: AgentGenerationProjectResultCollections | null | undefined,
incoming: AgentGenerationProjectResultCollections | null | undefined
): AgentGenerationProjectResultCollections {
return RESULT_COLLECTION_KEYS.reduce<AgentGenerationProjectResultCollections>(
(next, key) => {
next[key] = normalizeProjectResultUrlList([
...normalizeProjectResultUrlList(existing?.[key]),
...normalizeProjectResultUrlList(incoming?.[key]),
]);
return next;
},
{}
);
}
function getResultCollectionUrlSet(
collections: AgentGenerationProjectResultCollections | null | undefined
) {
return new Set(
RESULT_COLLECTION_KEYS.flatMap((key) => normalizeProjectResultUrlList(collections?.[key]))
);
}
function removeDeletedUrlsFromCollections(
collections: AgentGenerationProjectResultCollections,
deletedUrls: Set<string>
): AgentGenerationProjectResultCollections {
return RESULT_COLLECTION_KEYS.reduce<AgentGenerationProjectResultCollections>(
(next, key) => {
next[key] = normalizeProjectResultUrlList(collections[key]).filter(
(url) => !deletedUrls.has(url)
);
return next;
},
{}
);
}
function mergeReplacementResultState(
existing: AgentGenerationProjectReplacementResultState | null | undefined,
incoming: AgentGenerationProjectReplacementResultState | null | undefined,
deletedUrls: Set<string>
): AgentGenerationProjectReplacementResultState {
return REPLACEMENT_RESULT_KEYS.reduce<AgentGenerationProjectReplacementResultState>(
(next, key) => {
const existingModeState = existing?.[key] || {};
const incomingModeState = incoming?.[key] || {};
const mergedModeState = { ...existingModeState, ...incomingModeState };
next[key] = Object.fromEntries(
Object.entries(mergedModeState).filter(([, record]) => {
const url = String(record?.url || "").trim();
return !url || !deletedUrls.has(url);
})
);
return next;
},
{}
);
}
function getReplacementResultUrls(
state: AgentGenerationProjectReplacementResultState | null | undefined
) {
return REPLACEMENT_RESULT_KEYS.flatMap((key) =>
Object.values(state?.[key] || {})
.map((record) => String(record?.url || "").trim())
.filter((url) => /^https?:\/\//i.test(url))
);
}
function getAllResultUrlsFromState(state: AgentGenerationProjectPersistedState) {
return normalizeProjectResultUrlList([
...normalizeProjectResultUrlList(state.resultImageUrls),
...RESULT_COLLECTION_KEYS.flatMap((key) =>
normalizeProjectResultUrlList(state.resultCollections?.[key])
),
...getReplacementResultUrls(state.replacementResultState),
]);
}
function mergeAgentProjectStateForSave(params: {
existingState: AgentGenerationProjectPersistedState;
incomingState: unknown;
}) {
const existingState = normalizeAgentGenerationProjectPersistedState(params.existingState);
const incomingState = normalizeAgentGenerationProjectPersistedState(params.incomingState);
const deletedResultCollections = mergeResultCollections(
existingState.deletedResultCollections,
incomingState.deletedResultCollections
);
const deletedUrls = getResultCollectionUrlSet(deletedResultCollections);
const replacementResultState = mergeReplacementResultState(
existingState.replacementResultState,
incomingState.replacementResultState,
deletedUrls
);
const resultCollections = removeDeletedUrlsFromCollections(
mergeResultCollections(existingState.resultCollections, incomingState.resultCollections),
deletedUrls
);
return normalizeAgentGenerationProjectPersistedState({
...incomingState,
resultCollections,
deletedResultCollections,
replacementResultState,
resultImageUrls: normalizeProjectResultUrlList([
...getAllResultUrlsFromState(existingState),
...getAllResultUrlsFromState(incomingState),
...RESULT_COLLECTION_KEYS.flatMap((key) => resultCollections[key] || []),
...getReplacementResultUrls(replacementResultState),
]).filter((url) => !deletedUrls.has(url)),
});
}
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ success: false, error: "未登录" }, { status: 401 });
}
const { id } = await params;
const [project] = await db
.select()
.from(canvasProject)
.where(
and(eq(canvasProject.id, id), eq(canvasProject.userId, session.user.id))
)
.limit(1);
if (!project || !isAgentProjectRow(project)) {
return NextResponse.json({ success: false, error: "生图项目不存在" }, { status: 404 });
}
return NextResponse.json({ success: true, data: mapAgentProjectRow(project) });
} catch (error) {
console.error("[AgentProjects API] 获取项目失败:", error);
return NextResponse.json({ success: false, error: "获取生图项目失败" }, { status: 500 });
}
}
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ success: false, error: "未登录" }, { status: 401 });
}
const { id } = await params;
const body = await request.json().catch(() => ({}));
const [existingProject] = await db
.select()
.from(canvasProject)
.where(
and(eq(canvasProject.id, id), eq(canvasProject.userId, session.user.id))
)
.limit(1);
if (!existingProject || !isAgentProjectRow(existingProject)) {
return NextResponse.json({ success: false, error: "生图项目不存在" }, { status: 404 });
}
const title =
body.title !== undefined
? String(body.title || "").trim() || "未命名生图项目"
: existingProject.title;
const description =
body.description !== undefined
? String(body.description || "").trim() || null
: existingProject.description || null;
const existingProjectState = extractAgentProjectStateFromCanvasData(existingProject.canvasData);
const nextProjectState =
body.projectState !== undefined
? mergeAgentProjectStateForSave({
existingState: existingProjectState,
incomingState: body.projectState,
})
: existingProjectState;
const firstResultUrl = getAllResultUrlsFromState(nextProjectState)[0] || null;
const requestedThumbnail =
body.thumbnail !== undefined ? String(body.thumbnail || "").trim() || null : undefined;
const thumbnail =
requestedThumbnail || existingProject.thumbnail || firstResultUrl || null;
const [project] = await db
.update(canvasProject)
.set({
title,
description,
thumbnail,
workspaceType: "agent",
updatedAt: new Date(),
canvasData: {
nodes: [],
edges: [],
selectedNodeIds: [],
viewport: { x: 0, y: 0, zoom: 1 },
tool: "select",
agentProjectState: nextProjectState,
} as any,
})
.where(
and(eq(canvasProject.id, id), eq(canvasProject.userId, session.user.id))
)
.returning();
return NextResponse.json({ success: true, data: mapAgentProjectRow(project) });
} catch (error) {
console.error("[AgentProjects API] 保存项目失败:", error);
return NextResponse.json({ success: false, error: "保存生图项目失败" }, { status: 500 });
}
}
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ success: false, error: "未登录" }, { status: 401 });
}
const { id } = await params;
const [existingProject] = await db
.select()
.from(canvasProject)
.where(and(eq(canvasProject.id, id), eq(canvasProject.userId, session.user.id)))
.limit(1);
if (!existingProject || !isAgentProjectRow(existingProject)) {
return NextResponse.json({ success: false, error: "生图项目不存在" }, { status: 404 });
}
const [project] = await db
.delete(canvasProject)
.where(and(eq(canvasProject.id, id), eq(canvasProject.userId, session.user.id)))
.returning();
if (!project) {
return NextResponse.json({ success: false, error: "生图项目不存在" }, { status: 404 });
}
return NextResponse.json({ success: true, data: mapAgentProjectRow(project) });
} catch (error) {
console.error("[AgentProjects API] 删除项目失败:", error);
return NextResponse.json({ success: false, error: "删除生图项目失败" }, { status: 500 });
}
}