feat(api): support custom BYO API header override on client fetches and backend config

This commit is contained in:
DESKTOP-I1T6TF3\Q
2026-06-04 13:48:31 +08:00
parent af155ac107
commit e04c51e875
10 changed files with 510 additions and 21 deletions
+63 -9
View File
@@ -29,6 +29,23 @@ import type {
import { track } from "@/lib/analytics";
const MUTED_STORAGE_KEY = "infiplot:muted";
const BYO_STORAGE_KEY = "infiplot:byoApi";
function getByoHeaders(): Record<string, string> {
if (typeof window === "undefined") return {};
try {
const raw = localStorage.getItem(BYO_STORAGE_KEY);
if (raw) {
const parsed = JSON.parse(raw);
if (parsed.llm?.enabled || parsed.painter?.enabled) {
return { "x-byo-api": raw };
}
}
} catch {
/* ignore */
}
return {};
}
// Cap how long we wait for the browser to download + decode a scene image
// before giving up and rendering anyway. Runware's CDN is usually <2s for a
@@ -267,7 +284,10 @@ function prefetchScenePath(
const promise = (async () => {
const res = await fetch("/api/scene", {
method: "POST",
headers: { "Content-Type": "application/json" },
headers: {
"Content-Type": "application/json",
...getByoHeaders(),
},
body: JSON.stringify({ session: specSession }),
signal: abort.signal,
});
@@ -483,7 +503,10 @@ function PlayInner() {
try {
const res = await fetch("/api/beat-audio", {
method: "POST",
headers: { "Content-Type": "application/json" },
headers: {
"Content-Type": "application/json",
...getByoHeaders(),
},
body: JSON.stringify({
beat: { id: beat.id, line: beat.line, lineDelivery: beat.lineDelivery },
voice: speaker.voice,
@@ -693,7 +716,10 @@ function PlayInner() {
)
: fetch("/api/start", {
method: "POST",
headers: { "Content-Type": "application/json" },
headers: {
"Content-Type": "application/json",
...getByoHeaders(),
},
body: JSON.stringify(livePayload),
}).then(async (r) => {
if (!r.ok) {
@@ -918,7 +944,10 @@ function PlayInner() {
const promise = (async () => {
const res = await fetch("/api/scene", {
method: "POST",
headers: { "Content-Type": "application/json" },
headers: {
"Content-Type": "application/json",
...getByoHeaders(),
},
body: JSON.stringify({ session: specSession }),
});
if (!res.ok) {
@@ -940,7 +969,10 @@ function PlayInner() {
const annotatedImageBase64 = await annotateClick(imageUrl, click);
const visionRes = await fetch("/api/vision", {
method: "POST",
headers: { "Content-Type": "application/json" },
headers: {
"Content-Type": "application/json",
...getByoHeaders(),
},
body: JSON.stringify({ session, annotatedImageBase64 }),
});
if (!visionRes.ok) {
@@ -956,7 +988,10 @@ function PlayInner() {
setPhase("inserting-beat");
const insertRes = await fetch("/api/insert-beat", {
method: "POST",
headers: { "Content-Type": "application/json" },
headers: {
"Content-Type": "application/json",
...getByoHeaders(),
},
body: JSON.stringify({
session,
freeformAction: decision.intent.freeformAction,
@@ -1036,7 +1071,10 @@ function PlayInner() {
const promise = (async () => {
const res = await fetch("/api/scene", {
method: "POST",
headers: { "Content-Type": "application/json" },
headers: {
"Content-Type": "application/json",
...getByoHeaders(),
},
body: JSON.stringify({ session: specSession }),
});
if (!res.ok) {
@@ -1065,18 +1103,34 @@ function PlayInner() {
// ── Render ────────────────────────────────────────────────────────────
if (error) {
const isByoActive = typeof window !== "undefined" && (() => {
try {
const raw = localStorage.getItem(BYO_STORAGE_KEY);
if (raw) {
const parsed = JSON.parse(raw);
return parsed.llm?.enabled || parsed.painter?.enabled;
}
} catch {}
return false;
})();
return (
<div className="min-h-screen flex flex-col items-center justify-center px-8">
<div className="max-w-md text-center animate-fade-in">
<p className="text-[10px] smallcaps text-clay-500 mb-6">
· · · ·
</p>
<p className="font-serif italic text-clay-900 text-lg leading-[1.7] mb-10">
<p className="font-serif italic text-clay-900 text-lg leading-[1.7] mb-6">
{error}
</p>
{isByoActive && (
<p className="font-sans text-xs text-ember-600 mb-10 leading-relaxed">
API API KeyEndpoint Model
</p>
)}
<Link
href="/"
className="text-[10px] smallcaps text-clay-700 hover:text-ember-500 transition-colors inline-flex items-center gap-3"
className={"text-[10px] smallcaps text-clay-700 hover:text-ember-500 transition-colors inline-flex items-center gap-3" + (isByoActive ? "" : " mt-4")}
>
<i className="fa-solid fa-arrow-left text-[9px]" />