Add multi-board selector and scalable firmware catalog UI.

Board chips reserve future models; firmware uses searchable cards with detail pane and optional meta.json media/docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-19 19:45:24 +08:00
parent ae63378fdb
commit a59aaf1c8d
11 changed files with 788 additions and 226 deletions

View File

@@ -0,0 +1,43 @@
import fs from "fs";
import path from "path";
import { NextResponse } from "next/server";
import { resolveMediaFile } from "@/lib/firmware";
export const dynamic = "force-dynamic";
const MIME: Record<string, string> = {
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".webp": "image/webp",
".gif": "image/gif",
".mp4": "video/mp4",
".webm": "video/webm",
".md": "text/markdown; charset=utf-8",
".txt": "text/plain; charset=utf-8",
".html": "text/html; charset=utf-8",
".pdf": "application/pdf",
};
type Params = {
params: Promise<{ project: string; variant: string; path: string[] }>;
};
export async function GET(_req: Request, { params }: Params) {
const { project, variant, path: parts } = await params;
const rel = parts.join("/");
const abs = resolveMediaFile(project, variant, rel);
if (!abs) {
return NextResponse.json({ error: "not found" }, { status: 404 });
}
const ext = path.extname(abs).toLowerCase();
const buf = fs.readFileSync(abs);
return new NextResponse(buf, {
headers: {
"Content-Type": MIME[ext] || "application/octet-stream",
"Content-Length": String(buf.length),
"Cache-Control": "public, max-age=3600",
},
});
}