Initial commit: EGBE chatbot template
Stripped from vercel/chatbot (Apache 2.0): - Dropped @vercel/* packages and AI Gateway - Removed artifacts feature (code/text/sheet/image side panel) - Switched AI provider to @ai-sdk/openai-compatible -> EGBE LiteLLM - Replaced Vercel Blob upload with data URLs - Dropped Redis resumable streams and rate limiter (in-memory now) - Added Dockerfile (Next.js standalone) + entrypoint that runs migrations - Wired DATABASE_URL, EGBE_AI_API_URL/KEY, NEXT_PUBLIC_BASE_URL for app-deploy.sh
This commit is contained in:
commit
3e21c2334c
129 changed files with 21913 additions and 0 deletions
64
app/(chat)/api/files/upload/route.ts
Normal file
64
app/(chat)/api/files/upload/route.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import { z } from "zod";
|
||||
|
||||
import { auth } from "@/app/(auth)/auth";
|
||||
|
||||
const MAX_FILE_SIZE = 5 * 1024 * 1024;
|
||||
const ALLOWED_TYPES = ["image/jpeg", "image/png", "image/webp", "image/gif"];
|
||||
|
||||
const FileSchema = z.object({
|
||||
file: z
|
||||
.instanceof(Blob)
|
||||
.refine((file) => file.size <= MAX_FILE_SIZE, {
|
||||
message: "File size should be less than 5MB",
|
||||
})
|
||||
.refine((file) => ALLOWED_TYPES.includes(file.type), {
|
||||
message: "File type should be JPEG, PNG, WebP, or GIF",
|
||||
}),
|
||||
});
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const session = await auth();
|
||||
|
||||
if (!session) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
if (request.body === null) {
|
||||
return new Response("Request body is empty", { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const formData = await request.formData();
|
||||
const file = formData.get("file") as Blob;
|
||||
|
||||
if (!file) {
|
||||
return NextResponse.json({ error: "No file uploaded" }, { status: 400 });
|
||||
}
|
||||
|
||||
const validatedFile = FileSchema.safeParse({ file });
|
||||
|
||||
if (!validatedFile.success) {
|
||||
const errorMessage = validatedFile.error.errors
|
||||
.map((error) => error.message)
|
||||
.join(", ");
|
||||
return NextResponse.json({ error: errorMessage }, { status: 400 });
|
||||
}
|
||||
|
||||
const filename = (formData.get("file") as File).name;
|
||||
const safeName = filename.replace(/[^a-zA-Z0-9._-]/g, "_");
|
||||
const buffer = Buffer.from(await file.arrayBuffer());
|
||||
const dataUrl = `data:${file.type};base64,${buffer.toString("base64")}`;
|
||||
|
||||
return NextResponse.json({
|
||||
url: dataUrl,
|
||||
pathname: safeName,
|
||||
contentType: file.type,
|
||||
});
|
||||
} catch (_error) {
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to process request" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue