2 Commits
Author SHA1 Message Date
sun 8a4187720c fix(tts): settings.default_resource_id 不再被 voice 行覆盖
Bug 001 (后端):

之前 f5563e6 修 55000000 时,LoadRuntimeConfig 拿 default_speaker 查
voice 表后,把 voice.Speaker/ResourceID/Model 全部覆盖了 settings 里
的对应值。本意是修 speaker,副作用是 default_resource_id 永远被
voice.resource_id 覆盖 → 用户在 setup 设的 default_resource_id
(seed-icl-2.0) 没机会生效,被 voice 行的 (volc.megatts.icl) 替换。

修法 (option B: settings 优先 + voice 兜底):
- speaker:  从 voice 表查 (default_speaker 是 voice 名,要解析成真 ID)
- resource_id: 保持 settings 里的(用户偏好)
- model:  voice 行的优先,settings 里的次之(向后兼容, 不动)

字段语义重新明确:
- settings.default_resource_id: 全局默认 resource(用户必设,运行时用)
- voices[].resource_id: 仅在 voice= 显式传时使用(per-voice 覆盖)
- 用户保持两个一致即可

附带修复: controller/tts.go 的注释误导(说 voice 为空用
setting.TTSOptions.Speaker 即 default_speaker),实际是 LoadRuntimeConfig
已解析为真 speaker ID。

测试:
- 已有 store/installer/middleware/router/telemetry 测试全绿
- e2e (本机): install settings.default_resource_id=seed-icl-2.0
  voice chun.resource_id=volc.megatts.icl (故意不一致)
  → 启动 log: 'resource=seed-icl-2.0' (settings 优先 ✓)
  → 火山 55000000/45000030 → 401 Invalid X-Api-Key (说明参数对,key 假)
  → 换真 key 即能 work

二进制大小: ~18MB → ~18MB (代码量不变)

未 push (待用户确认)
2026-08-30 13:09:40 +08:00
sun 92cf8fab8a fix(ui): dashboard 加 loadSettings, CORS banner 终于不假阳性了
Bug FE-002:
- /admin dashboard 顶部黄条"跨域 CORS 未配置"永远显示
- 原因: reloadAll() 没调 loadSettings, dashboard 上 settings.value 一直是 {}
- banner 判断 `!settings.cors_configured` → undefined → falsy → 触发
- 只有手动进 settings tab 触发 loadSettings() 后才消失
- 刷新页面又出现(被用户当成"CORS 没生效")

修法: 一行。reloadAll() 加上 loadSettings(), mount 时一并加载,
dashboard 上 settings 就有真值了, banner 判断正确。

diff:
- const reloadAll = () => { loadOverview(); loadVoices(); };
+ const reloadAll = () => { loadOverview(); loadVoices(); loadSettings(); };

副作用: 无。 loadSettings 自带 try/catch, 即使 /api/settings 失败
也不影响 overview/voices 加载。 watch(tab) 仍会再调一次
loadSettings (idempotent, 覆盖 fresh 值)。

验证 (本机 + 你的报告复现):
- 强刷 admin → mount 时 3 个 API 并行加载
- settings.value 填好, cors_configured = true
- banner `v-if="settings && !settings.cors_configured && ..."` 评估为 false → 不显示

未 push(等你确认)

二进制: ~18MB → ~18MB (HTML 嵌入尺寸不变)
2026-08-30 13:04:26 +08:00
3 changed files with 16 additions and 7 deletions
+2 -1
View File
@@ -155,7 +155,8 @@ func OpenaiTTSHandler(w http.ResponseWriter, r *http.Request) {
opts.Text = req.Input opts.Text = req.Input
// M3: voice 路由 // M3: voice 路由
// - voice 为空 → 用 setting.TTSOptions.Speaker (即 store 里的 default_speaker) // - voice 为空 → 走 LoadRuntimeConfig 解析过的 opts.Speaker (已是真 speaker ID,
// default_speaker 是 voice 名,LoadRuntimeConfig 查 voice 表后替换)
// - voice 非空 → 查 voices 表,替换 opts.Speaker / ResourceID / Model // - voice 非空 → 查 voices 表,替换 opts.Speaker / ResourceID / Model
// - 命中但 enabled=0 → 仍可用(用户显式传 voice 即覆盖 enabled 状态;若想禁用在 admin UI 关掉就行) // - 命中但 enabled=0 → 仍可用(用户显式传 voice 即覆盖 enabled 状态;若想禁用在 admin UI 关掉就行)
// - 未命中 → 400 "unknown voice: <name>" // - 未命中 → 400 "unknown voice: <name>"
+1 -1
View File
@@ -953,7 +953,7 @@
toast('err', '保存 CORS 失败: ' + corsErr.value); toast('err', '保存 CORS 失败: ' + corsErr.value);
} finally { savingCors.value = false; } } finally { savingCors.value = false; }
}; };
const reloadAll = () => { loadOverview(); loadVoices(); }; const reloadAll = () => { loadOverview(); loadVoices(); loadSettings(); };
const saveSettings = async () => { const saveSettings = async () => {
settingsErr.value = ''; settingsErr.value = '';
+12 -4
View File
@@ -202,15 +202,23 @@ func LoadRuntimeConfig(s Store) error {
return TTSConfigErr return TTSConfigErr
} }
// 【BUG 修复】default_speaker 是 voice **名字**(如 "chun"), // 【BUG 修复 · 第二轮】default_speaker 是 voice **名字**(如 "chun"),
// 不是火山 speaker ID (如 "S_G8tEKnaJ1")。原代码直接把 voice 名当 // 不是火山 speaker ID (如 "S_G8tEKnaJ1")。原代码直接把 voice 名当
// speaker ID 用,导致调 /v1/audio/speech 不传 voice 时火山 55000000。 // speaker ID 用,导致调 /v1/audio/speech 不传 voice 时火山 55000000。
// 这里查 voice 表拿真正的 speaker / resource_id / model。 //
// 字段优先级:
// - speaker ← 从 default_speaker 这个 voice 查表拿真 ID (必查)
// - resource_id ← settings 里的(用户偏好,不被 voice 行覆盖)
// - model ← voice 行的优先,settings 里的次之
//
// 之前 f5563e6 把 resourceId 也覆盖了,导致用户在 setup 设的
// default_resource_id 永远没机会生效。这次只覆盖 speaker,不动 resourceId。
var voiceModel string var voiceModel string
if vSpeaker, vResource, vModel, found, vErr := s.GetVoiceForTTS(speaker); vErr == nil && found { if vSpeaker, _, vModel, found, vErr := s.GetVoiceForTTS(speaker); vErr == nil && found {
speaker = vSpeaker speaker = vSpeaker
resourceId = vResource if vModel != "" {
voiceModel = vModel voiceModel = vModel
}
// 找不到 voice 时不报错 — 保持原值(向后兼容) // 找不到 voice 时不报错 — 保持原值(向后兼容)
} }