feat(voice): 语音配置控制面——火山豆包 appid/token/resource-id/音色(参数落地处)

JARVIS 不依赖火山账号能先做的第二块:配置层,做成'你参数一填就落库'的形态。
- voice.Config: AppID/AccessToken/ASRResourceID/TTSResourceID/TTSVoiceType,AccessToken
  走 secrets AES 加密入库(镜像微信配置);ASREnabled/TTSEnabled 可各自独立判断。
- admin GET/PUT /admin/voice(RequireAdmin),token 空串=沿用已存、明文回显。
- admin「语音设置」页(运维组):五个字段 + ASR/TTS 就绪徽标 + '去哪拿参数'清单。

端点/音频格式(PCM 16k 单声道)由代码固定不入配置。WS 会话 + 火山 ASR/TTS 客户端等
用户回传 resource-id 后按官方 demo 协议做。build+tsc 绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Blizzard
2026-07-21 17:00:35 +08:00
parent 644b635d73
commit 618184689e
6 changed files with 295 additions and 0 deletions
+32
View File
@@ -267,6 +267,38 @@ export async function saveWechatMP(body: { appid: string; app_secret: string; to
return { enabled: !!d.enabled };
}
// —— 语音(火山豆包)配置 ——
export interface VoiceConfig {
appid: string;
access_token: string; // 明文回显(单管理员后台)
asr_resource_id: string;
tts_resource_id: string;
tts_voice_type: string;
asr_enabled: boolean;
tts_enabled: boolean;
}
export async function getVoiceConfig(): Promise<VoiceConfig> {
const res = guard(await fetch(`${ADMIN}/voice`, { headers: authHeaders() }));
const d = (await res.json().catch(() => ({}))) as Partial<VoiceConfig> & { error?: string };
if (!res.ok) throw new Error(d.error ?? `voice config failed: ${res.status}`);
return {
appid: d.appid ?? "", access_token: d.access_token ?? "", asr_resource_id: d.asr_resource_id ?? "",
tts_resource_id: d.tts_resource_id ?? "", tts_voice_type: d.tts_voice_type ?? "",
asr_enabled: !!d.asr_enabled, tts_enabled: !!d.tts_enabled,
};
}
// access_token 传空串 = 沿用已保存的。
export async function saveVoiceConfig(body: {
appid: string; access_token: string; asr_resource_id: string; tts_resource_id: string; tts_voice_type: string;
}): Promise<{ asr_enabled: boolean; tts_enabled: boolean }> {
const res = guard(await fetch(`${ADMIN}/voice`, { method: "PUT", headers: authHeaders(true), body: JSON.stringify(body) }));
const d = (await res.json().catch(() => ({}))) as { asr_enabled?: boolean; tts_enabled?: boolean; error?: string };
if (!res.ok) throw new Error(d.error ?? `save failed: ${res.status}`);
return { asr_enabled: !!d.asr_enabled, tts_enabled: !!d.tts_enabled };
}
export interface WechatPayConfig {
mchid: string;
cert_serial: string;