import { type NextRequest, NextResponse } from "next/server"; import { generateItemStructure } from "@/lib/ai/generate-item-structure"; import { calculateTokenCost } from "@/lib/ai/token-pricing"; import { recordTokenUsage } from "@/lib/ai/token-usage"; import { requireAdminUser } from "@/lib/auth/middleware"; export async function POST(request: NextRequest) { const auth = await requireAdminUser(request); if (auth instanceof NextResponse) { return auth; } const body = await request.json(); const name = typeof body?.name === "string" ? body.name.trim() : ""; const description = typeof body?.description === "string" ? body.description.trim() : ""; const scope = body?.scope === "server" ? "server" : "global"; const server = scope === "server" && typeof body?.server === "string" ? body.server.trim() : null; const categories: string[] = Array.isArray(body?.categories) ? body.categories.filter((item: unknown) => typeof item === "string") : []; const servers: string[] = Array.isArray(body?.servers) ? body.servers.filter((item: unknown) => typeof item === "string") : []; const images: string[] = Array.isArray(body?.images) ? body.images.filter((item: unknown) => typeof item === "string") : []; const hasDescription = !!description; const hasImages = images.length > 0; // Nova regra: aceitar quando houver descrição OU ao menos uma imagem. // Nome pode ser vazio; o assistente do front lida com sugestão/confirmar_nome. if (!hasDescription && !hasImages) { return NextResponse.json( { success: false, error: "Forneça descrição ou ao menos uma imagem para análise.", }, { status: 400 }, ); } const result = await generateItemStructure({ name, description, scope, server, categories, servers, images: images .slice(0, 2) .map((dataUrl) => { const [meta, base64Part] = dataUrl.split(","); const mimeMatch = meta?.match(/data:(.*);base64/); return { mimeType: mimeMatch?.[1] ?? "image/jpeg", base64Data: base64Part ?? meta, }; }) .filter((image) => image.base64Data), }); // Cálculo de custo baseado em usageMetadata const _model = (result.model ?? "gemini-2.5-flash") as keyof typeof import("@/lib/ai/token-pricing").TOKEN_PRICING; const breakdown = calculateTokenCost("gemini-2.5-flash", result.usage); // Persistir histórico para relatórios (não bloqueia resposta; erros são logados no Redis util) const requestId = `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; try { await recordTokenUsage({ id: requestId, timestamp: Date.now(), model: result.model ?? "gemini-2.5-flash", inputTokens: breakdown.inputTokens, outputTokens: breakdown.outputTokens, totalTokens: breakdown.totalTokens, costUsd: breakdown.totalUsd, context: { route: "/api/admin/items/analyze", scope, server, hasImages, hasDescription, categoriesCount: categories.length, }, }); } catch { // Falhas de Redis não devem impedir a análise } return NextResponse.json( { success: true, structure: result.structure, usage: { promptTokens: breakdown.inputTokens, outputTokens: breakdown.outputTokens, totalTokens: breakdown.totalTokens, }, costUsd: breakdown.totalUsd, model: result.model ?? "gemini-2.5-flash", requestId, }, { status: 200 }, ); }