import { z } from "zod"; import { guarded, body, json } from "@/lib/api"; import { generateImageTool } from "@/lib/tools/image"; import { openaiEditImage } from "@/lib/ai/openai"; import { readAttachment, saveAttachment } from "@/lib/storage"; import { recordUsage, assertWithinBudget } from "@/lib/usage"; import { sql } from "@/lib/db"; export const maxDuration = 300; /** Direct image generation / editing (outside of a chat turn). */ export const POST = guarded(async (req) => { const b = await body(req, z.object({ prompt: z.string().min(1).max(4000), model: z.string().optional(), size: z.string().optional(), quality: z.string().optional(), editAttachmentId: z.string().optional(), conversationId: z.string().nullable().optional(), projectId: z.string().nullable().optional() })); await assertWithinBudget(); if (b.editAttachmentId) { const src = await readAttachment(b.editAttachmentId); if (!src) return json({ error: "Image source introuvable" }, { status: 404 }); const t0 = Date.now(); const r = await openaiEditImage(new File([new Uint8Array(src.data)], src.name, { type: src.mime }), b.prompt, b.model ?? "gpt-image-1.5"); const att = await saveAttachment({ data: Buffer.from(r.images[0].b64, "base64"), filename: `edited-${Date.now()}.png`, mime: "image/png", conversationId: b.conversationId ?? null, projectId: b.projectId ?? null, kind: "generated-image", meta: { prompt: b.prompt, model: r.model, editedFrom: b.editAttachmentId } }); const u = r.usage as { input_tokens?: number; output_tokens?: number } | undefined; await recordUsage({ provider: "openai", modelId: r.model, kind: "image", usage: { input: u?.input_tokens ?? 0, output: u?.output_tokens ?? 0 }, costEstimated: u ? ((u.input_tokens ?? 0) * 8 + (u.output_tokens ?? 0) * 32) / 1e6 : null, latencyMs: Date.now() - t0 }); return json({ attachmentId: att.id, model: r.model }); } const r = await generateImageTool(b.prompt, { conversationId: b.conversationId ?? "", projectId: b.projectId ?? null, messageId: "", allow: new Set() }, { model: b.model, size: b.size, quality: b.quality }); return json(r); }); export const GET = guarded(async () => { const rows = await sql`SELECT id, filename, meta, conversation_id, created_at FROM attachments WHERE kind = 'generated-image' ORDER BY created_at DESC LIMIT 200`; return json({ images: rows }); });