ux(setup): voice.resource_id 留空时自动用 settings.default_resource_id

问题: step 2 有 "默认资源 ID" 字段,但 step 3 的每个 voice 行
也有 "资源 ID" 字段,要求用户填 2 次。用户经常只在 step 2 填,
留空 step 3 的 resource_id,导致 voice 行拿到 form placeholder
"volc.megatts.icl" 或空串。结果 settings 和 voice 不一致,
运行时火山 500(mismatch / not granted)。

修法 (后端兜底,无需前端改动):
1. 装时校验放宽: resource_id 留空是允许的(原来是必填)
2. 装时遍历 voices, 如果 voice.resource_id == "" 就用
   settings.default_resource_id 兜底, 并记 log

用户收益: step 3 voice 行的 resource_id 字段可以不动,
step 2 填一次就行,始终保持一致。

可选前端优化 (待前端做): step 2 的 "默认资源 ID" 改变时,
自动同步所有 voice 行的 resource_id (placeholder 提示已自动填充)。

e2e 验证 (本机):
- 提交 install body 中 voice[0].resource_id=""
- 启动 log: '[setup] voice "chun" resource_id 留空,自动用 default_resource_id="seed-icl-2.0"'
- dumpdb 看 voice chun.resource_id = seed-icl-2.0 (从 settings 兜底) ✓

二进制: ~18MB → ~18MB (代码量几乎不变)

未 push (待用户确认)
This commit is contained in:
sun
2026-08-30 14:07:03 +08:00
parent 8a4187720c
commit 65247b095a
+14 -4
View File
@@ -190,11 +190,21 @@ func SetupSubmitHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("[setup] 检测到 %d 条已存在 voices,本次将跳过清空(name 冲突由 ErrDuplicate 处理)", len(existing)) log.Printf("[setup] 检测到 %d 条已存在 voices,本次将跳过清空(name 冲突由 ErrDuplicate 处理)", len(existing))
} }
inserted := 0 inserted := 0
// 【UX 改进】voice 行的 resource_id 留空时,自动用 settings.default_resource_id 兜底。
// 用户在 step 2 填了 default_resource_id 后,step 3 的 voice 行 resource_id
// 可以不填 — 保持一致。否则会出现 "settings 里 seed-icl-2.0,voice 里 volc.megatts.icl"
// 这种 mismatch,运行时 500。
defaultResourceID := settingsKV["default_resource_id"]
for _, v := range body.Voices { for _, v := range body.Voices {
voiceResourceID := v.ResourceID
if voiceResourceID == "" {
voiceResourceID = defaultResourceID
log.Printf("[setup] voice %q resource_id 留空,自动用 default_resource_id=%q", v.Name, defaultResourceID)
}
_, err := s.VoiceInsert(store.Voice{ _, err := s.VoiceInsert(store.Voice{
Name: v.Name, Name: v.Name,
Speaker: v.Speaker, Speaker: v.Speaker,
ResourceID: v.ResourceID, ResourceID: voiceResourceID,
Model: v.Model, Model: v.Model,
Language: v.Language, Language: v.Language,
Enabled: true, Enabled: true,
@@ -267,9 +277,9 @@ func validateSetupVoices(vs []SetupVoice) error {
if strings.TrimSpace(v.Speaker) == "" { if strings.TrimSpace(v.Speaker) == "" {
return fmt.Errorf("voices[%d] (%s): speaker is required", i, v.Name) return fmt.Errorf("voices[%d] (%s): speaker is required", i, v.Name)
} }
if strings.TrimSpace(v.ResourceID) == "" { // 【UX 改进】resource_id 留空是允许的 — 装时(下面那个循环)会自动用
return fmt.Errorf("voices[%d] (%s): resource_id is required", i, v.Name) // settings.default_resource_id 兜底。用户只填 step 2 一处即可。
} _ = v.ResourceID // (保留以便以后加更细的校验)
if _, dup := names[v.Name]; dup { if _, dup := names[v.Name]; dup {
return fmt.Errorf("voices[%d]: duplicate name %q", i, v.Name) return fmt.Errorf("voices[%d]: duplicate name %q", i, v.Name)
} }