Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3f18f85925 | ||
|
|
155017a27b | ||
|
|
ad4727c507 | ||
|
|
65247b095a |
@@ -27,6 +27,12 @@ TODO.md
|
|||||||
# Release artifacts (build output)
|
# Release artifacts (build output)
|
||||||
release/
|
release/
|
||||||
|
|
||||||
|
# Local build output (start-local.ps1 builds to bin/)
|
||||||
|
bin/
|
||||||
|
|
||||||
|
# Local dev scripts (per-user; not part of repo)
|
||||||
|
start-local.ps1
|
||||||
|
|
||||||
# Runtime database artifacts (created by M1 installer flow)
|
# Runtime database artifacts (created by M1 installer flow)
|
||||||
tts.db
|
tts.db
|
||||||
tts.db-*
|
tts.db-*
|
||||||
|
|||||||
+14
-4
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -190,7 +190,7 @@ func OpenaiTTSHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
opts.Model = v.Model
|
opts.Model = v.Model
|
||||||
}
|
}
|
||||||
log.Printf("[tts] voice=%s 命中 (speaker=%s resource=%s model=%s) - 客户端=%s",
|
log.Printf("[tts] voice=%s 命中 (speaker=%s resource=%s model=%s) - 客户端=%s",
|
||||||
req.Voice, v.Speaker, v.ResourceID, v.Model, middleware.GetClientIP(r))
|
req.Voice, telemetry.MaskSpeaker(v.Speaker), v.ResourceID, v.Model, middleware.GetClientIP(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(r.Context(), setting.TTSTimeout)
|
ctx, cancel := context.WithTimeout(r.Context(), setting.TTSTimeout)
|
||||||
|
|||||||
+4
-4
@@ -684,7 +684,7 @@
|
|||||||
<div class="grid-2">
|
<div class="grid-2">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>默认资源 ID</label>
|
<label>默认资源 ID</label>
|
||||||
<input v-model="settingsForm.default_resource_id" placeholder="volc.megatts.default">
|
<input v-model="settingsForm.default_resource_id" placeholder="seed-icl-2.0">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>默认音色 (name)</label>
|
<label>默认音色 (name)</label>
|
||||||
@@ -773,7 +773,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>资源 ID <span class="req">*</span></label>
|
<label>资源 ID <span class="req">*</span></label>
|
||||||
<input v-model="form.resource_id" placeholder="volc.megatts.icl" class="mono">
|
<input v-model="form.resource_id" placeholder="seed-icl-2.0" class="mono">
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>Model</label>
|
<label>Model</label>
|
||||||
@@ -847,7 +847,7 @@
|
|||||||
const showAdd = ref(false);
|
const showAdd = ref(false);
|
||||||
const adding = ref(false);
|
const adding = ref(false);
|
||||||
const addErr = ref('');
|
const addErr = ref('');
|
||||||
const form = ref({ name: '', speaker: '', resource_id: 'volc.megatts.icl', model: 'seed-tts-2.0-standard', language: '', description: '' });
|
const form = ref({ name: '', speaker: '', resource_id: 'seed-icl-2.0', model: 'seed-tts-2.0-standard', language: '', description: '' });
|
||||||
|
|
||||||
// Settings
|
// Settings
|
||||||
const settings = ref({});
|
const settings = ref({});
|
||||||
@@ -1006,7 +1006,7 @@
|
|||||||
const resetSettingsForm = () => { loadSettings(); };
|
const resetSettingsForm = () => { loadSettings(); };
|
||||||
|
|
||||||
const openAdd = () => {
|
const openAdd = () => {
|
||||||
form.value = { name: '', speaker: '', resource_id: 'volc.megatts.icl', model: 'seed-tts-2.0-standard', language: '', description: '' };
|
form.value = { name: '', speaker: '', resource_id: 'seed-icl-2.0', model: 'seed-tts-2.0-standard', language: '', description: '' };
|
||||||
addErr.value = '';
|
addErr.value = '';
|
||||||
showAdd.value = true;
|
showAdd.value = true;
|
||||||
};
|
};
|
||||||
|
|||||||
+6
-6
@@ -223,8 +223,8 @@
|
|||||||
<div class="field-row">
|
<div class="field-row">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>默认资源 ID <span class="req">*</span></label>
|
<label>默认资源 ID <span class="req">*</span></label>
|
||||||
<input v-model="form.default_resource_id" placeholder="volc.megatts.default">
|
<input v-model="form.default_resource_id" placeholder="seed-icl-2.0">
|
||||||
<div class="hint">音色所属的计费资源 ID;volc.megatts.icl 用于复刻音色。</div>
|
<div class="hint">音色所属的计费资源 ID;本项目为豆包声音复刻大模型 2.0,固定使用 <code>seed-icl-2.0</code>。</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>默认音色 <span class="req">*</span></label>
|
<label>默认音色 <span class="req">*</span></label>
|
||||||
@@ -275,7 +275,7 @@
|
|||||||
<div v-for="(v, i) in form.voices" :key="i" class="voice-row">
|
<div v-for="(v, i) in form.voices" :key="i" class="voice-row">
|
||||||
<input v-model="v.name" placeholder="qian">
|
<input v-model="v.name" placeholder="qian">
|
||||||
<input v-model="v.speaker" placeholder="S_xxx">
|
<input v-model="v.speaker" placeholder="S_xxx">
|
||||||
<input v-model="v.resource_id" placeholder="volc.megatts.icl">
|
<input v-model="v.resource_id" placeholder="seed-icl-2.0">
|
||||||
<input v-model="v.model" placeholder="seed-tts-2.0-standard">
|
<input v-model="v.model" placeholder="seed-tts-2.0-standard">
|
||||||
<button class="rm" @click="removeVoice(i)" :disabled="form.voices.length <= 1" title="删除">×</button>
|
<button class="rm" @click="removeVoice(i)" :disabled="form.voices.length <= 1" title="删除">×</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -340,18 +340,18 @@
|
|||||||
const form = ref({
|
const form = ref({
|
||||||
auth_key: '',
|
auth_key: '',
|
||||||
api_key: '',
|
api_key: '',
|
||||||
default_resource_id: 'volc.megatts.default',
|
default_resource_id: 'seed-icl-2.0',
|
||||||
default_speaker: '',
|
default_speaker: '',
|
||||||
default_format: 'mp3',
|
default_format: 'mp3',
|
||||||
sample_rate: 24000,
|
sample_rate: 24000,
|
||||||
token: '',
|
token: '',
|
||||||
voices: [{ name: '', speaker: '', resource_id: 'volc.megatts.icl', model: 'seed-tts-2.0-standard' }],
|
voices: [{ name: '', speaker: '', resource_id: 'seed-icl-2.0', model: 'seed-tts-2.0-standard' }],
|
||||||
});
|
});
|
||||||
const errorMsg = ref('');
|
const errorMsg = ref('');
|
||||||
const submitting = ref(false);
|
const submitting = ref(false);
|
||||||
|
|
||||||
const addVoice = () => {
|
const addVoice = () => {
|
||||||
form.value.voices.push({ name: '', speaker: '', resource_id: 'volc.megatts.icl', model: 'seed-tts-2.0-standard' });
|
form.value.voices.push({ name: '', speaker: '', resource_id: 'seed-icl-2.0', model: 'seed-tts-2.0-standard' });
|
||||||
};
|
};
|
||||||
const removeVoice = (i) => {
|
const removeVoice = (i) => {
|
||||||
if (form.value.voices.length > 1) form.value.voices.splice(i, 1);
|
if (form.value.voices.length > 1) form.value.voices.splice(i, 1);
|
||||||
|
|||||||
Reference in New Issue
Block a user