feat(api): support custom BYO API header override on client fetches and backend config
This commit is contained in:
+63
-9
@@ -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 配置的 Key、Endpoint 和 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]" />
|
||||
返 回
|
||||
|
||||
Reference in New Issue
Block a user