Wire mic through Whisper-compatible transcriptions on LLM_BASE_URL. Photos upload to MinIO and reach the vision model as base64 image_url parts.
68 lines
2.0 KiB
TypeScript
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}` });
|
|
}
|