diff --git a/app/api/insert-beat/route.ts b/app/api/insert-beat/route.ts
index e40450a..2366eb0 100644
--- a/app/api/insert-beat/route.ts
+++ b/app/api/insert-beat/route.ts
@@ -24,7 +24,7 @@ export async function POST(req: Request) {
try {
const base = loadEngineConfig(req.headers);
// See StartRequest.clientTts — BYO clients synth in-browser, so drop server TTS.
- const config = body.clientTts ? { ...base, tts: undefined } : base;
+ const config = body.clientTts === true ? { ...base, tts: undefined } : base;
const result = await requestInsertBeat(config, body);
return NextResponse.json(result);
} catch (err) {
diff --git a/app/api/scene/route.ts b/app/api/scene/route.ts
index e7a8127..c3b177b 100644
--- a/app/api/scene/route.ts
+++ b/app/api/scene/route.ts
@@ -25,7 +25,7 @@ export async function POST(req: Request) {
try {
const base = loadEngineConfig(req.headers);
// See StartRequest.clientTts — BYO clients synth in-browser, so drop server TTS.
- const config = body.clientTts ? { ...base, tts: undefined } : base;
+ const config = body.clientTts === true ? { ...base, tts: undefined } : base;
const result = await requestScene(config, body);
return NextResponse.json(result);
} catch (err) {
diff --git a/app/api/start/route.ts b/app/api/start/route.ts
index 5c9af7c..167c097 100644
--- a/app/api/start/route.ts
+++ b/app/api/start/route.ts
@@ -45,7 +45,7 @@ export async function POST(req: Request) {
// BYO key: the browser provisions + synths voices directly against Xiaomi
// (key never reaches us), so strip server-side TTS so the engine skips all
// provisioning + synth. See StartRequest.clientTts.
- const config = body.clientTts ? { ...base, tts: undefined } : base;
+ const config = body.clientTts === true ? { ...base, tts: undefined } : base;
const result = await startSession(config, body);
return NextResponse.json(result);
} catch (err) {
diff --git a/components/TtsKeyModal.tsx b/components/TtsKeyModal.tsx
index bcdd7cd..1713d15 100644
--- a/components/TtsKeyModal.tsx
+++ b/components/TtsKeyModal.tsx
@@ -48,6 +48,14 @@ export function TtsKeyModal({
const [showKey, setShowKey] = useState(false);
const [shown, setShown] = useState(false);
const alreadyConfigured = initial != null;
+ // Soft guard: tp- keys belong to Token Plan, sk- to pay-as-you-go. A
+ // mismatched pairing hits the wrong endpoint → guaranteed auth failure →
+ // silent playback (the very symptom BYO exists to kill). Warn, but never
+ // block: prefix conventions could change and a hard gate would lock out an
+ // otherwise-valid key.
+ const expectedPrefix = keyType === "payg" ? "sk-" : "tp-";
+ const prefixMismatch =
+ apiKey.trim().length > 0 && !apiKey.trim().startsWith(expectedPrefix);
useEffect(() => {
const id = requestAnimationFrame(() => setShown(true));
@@ -214,6 +222,14 @@ export function TtsKeyModal({
/>
+ {prefixMismatch && (
+
+
+ 此 Key 不是 {expectedPrefix} 开头,可能与所选「
+ {keyType === "payg" ? "按量付费 Pay-as-you-go" : "套餐 Token Plan"}
+ 」类型不符,请确认是否填错。
+
+ )}