Files
famapp/src/app/api/uploads/route.ts
T
ginnoir c8db5475d3 feat(agent): add voice input and photo attachments to assistant
Wire mic through Whisper-compatible transcriptions on LLM_BASE_URL.

Photos upload to MinIO and reach the vision model as base64 image_url parts.
2026-07-05 02:01:48 -05:00

68 lines
2.0 KiB
TypeScript

import { randomUUID } from "node:crypto";
import { NextResponse } from "next/server";
import { getCurrentSession } from "@/lib/session";
import { ensureBucket, minioClient, MINIO_BUCKET } from "@/lib/minio";
export const runtime = "nodejs";
export const maxDuration = 60;
export const config = {
api: { bodyParser: { sizeLimit: "100mb" } },
};
const MAX_FILE_SIZE = 100 * 1024 * 1024;
export async function POST(request: Request) {
let session: Awaited<ReturnType<typeof getCurrentSession>>;
try {
session = await getCurrentSession();
} catch {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
let formData: FormData;
try {
formData = await request.formData();
} catch {
return NextResponse.json({ error: "Invalid multipart body" }, { status: 400 });
}
const file = formData.get("file");
if (!(file instanceof File)) {
return NextResponse.json({ error: "No file field in request" }, { status: 400 });
}
const url = new URL(request.url);
const scopeParam = url.searchParams.get("scope");
const scope =
scopeParam === "notes" ? "notes" : scopeParam === "assistant" ? "assistant" : "garden";
if (!file.type.startsWith("image/")) {
return NextResponse.json({ error: "Only image files are allowed" }, { status: 415 });
}
if (file.size > MAX_FILE_SIZE) {
return NextResponse.json({ error: "File exceeds 100 MB limit" }, { status: 413 });
}
const rawExt = file.name.split(".").pop() ?? "bin";
const safeExt = rawExt
.replace(/[^a-z0-9]/gi, "")
.toLowerCase()
.slice(0, 8);
const key = `${scope}/${session.household.id}/${randomUUID()}.${safeExt}`;
try {
await ensureBucket();
const buffer = Buffer.from(await file.arrayBuffer());
await minioClient.putObject(MINIO_BUCKET, key, buffer, buffer.length, {
"Content-Type": file.type,
});
} catch (err) {
console.error("Upload failed", err);
return NextResponse.json({ error: "Upload service unavailable" }, { status: 503 });
}
return NextResponse.json({ url: `/api/uploads/${key}` });
}