feat(items): add server field and improve item management
refactor(ai): enhance item structure generation with schema validation feat(ai): add token usage tracking and cost calculation fix(ui): improve item card number formatting with locale support docs(items): add documentation for item value separation test(items): add tests for value field validation
This commit is contained in:
1 parent
dc169f9eba
commit
d3704602fd
22 files changed
+1317
-95
No files matched your search
@@ -1,6 +1,8 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { requireAdminUser } from "@/lib/auth/middleware";
|
||||
import { generateItemStructure } from "@/lib/ai/generate-item-structure";
|
||||
import { calculateTokenCost } from "@/lib/ai/token-pricing";
|
||||
import { recordTokenUsage } from "@/lib/ai/token-usage";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const auth = await requireAdminUser(request);
|
||||
@@ -26,12 +28,16 @@ export async function POST(request: NextRequest) {
|
||||
const images: string[] = Array.isArray(body?.images)
|
||||
? body.images.filter((item: unknown) => typeof item === "string")
|
||||
: [];
|
||||
const hasDescription = !!description;
|
||||
const hasImages = images.length > 0;
|
||||
|
||||
if (!name || !description) {
|
||||
// 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: "Informe nome e descrição para gerar o JSON estrutural.",
|
||||
error: "Forneça descrição ou ao menos uma imagem para análise.",
|
||||
},
|
||||
{ status: 400 }
|
||||
);
|
||||
@@ -57,10 +63,46 @@ export async function POST(request: NextRequest) {
|
||||
.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 }
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user