mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-25 14:22:57 +00:00
Baseline groundwork for TTS expansion plan. Introduces a capabilities system that the follow-up plan (phase-01..04) builds on. Backend: - `audio.ParamSchema` / `ProviderCapabilities` types + per-provider capabilities.go for edge, elevenlabs, minimax, openai, gemini. - Gemini TTS provider (client, models, voices, wav encoder, multi-speaker via SpeakerVoice, audio-tag aware prompts). - TTSOptions gains `Params map[string]any` (read-only) + `Speakers`. - `VoiceListProvider` interface decouples HTTP voice handler from provider-specific impls. - `nested_keys.go` resolves dot-separated param paths for nested provider bodies (voice_settings.stability etc.). - Characterization + defaults-invariant tests per provider lock nil-params byte-equivalence before new params land. - `/v1/tts/capabilities` HTTP endpoint + integration coverage. - Dual-read tests (PG + SQLite) for tts_config. Frontend (web + desktop): - `DynamicParamForm` with depends-on evaluation, split into fields/logic modules. Slider primitive added. - `AudioTagPicker` + `MultiSpeakerEditor` for Gemini. - `voice-picker` refactored toward portal UX; combobox tightened. - tts-capabilities API client + typed hooks. - i18n catalogs (en/vi/zh) expanded; parity tests guard key drift. - TTS page reorganised (voice-model-section removed; playground + credentials + provider-setup split cleanly). Docs: codebase-summary, project-changelog, tts-provider-capabilities.
127 lines
3.2 KiB
Go
127 lines
3.2 KiB
Go
package audio
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestSetNested_FlatKey(t *testing.T) {
|
|
m := map[string]any{}
|
|
SetNested(m, "speed", 1.2)
|
|
v, ok := m["speed"]
|
|
if !ok {
|
|
t.Fatal("expected key 'speed' to be set")
|
|
}
|
|
if v != 1.2 {
|
|
t.Errorf("got %v, want 1.2", v)
|
|
}
|
|
}
|
|
|
|
func TestSetNested_TwoLevel(t *testing.T) {
|
|
m := map[string]any{}
|
|
SetNested(m, "voice_settings.stability", 0.5)
|
|
vs, ok := m["voice_settings"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("voice_settings not a map: %T", m["voice_settings"])
|
|
}
|
|
if vs["stability"] != 0.5 {
|
|
t.Errorf("stability: got %v, want 0.5", vs["stability"])
|
|
}
|
|
}
|
|
|
|
func TestSetNested_MergesExisting(t *testing.T) {
|
|
m := map[string]any{
|
|
"voice_settings": map[string]any{"speed": 1.0},
|
|
}
|
|
SetNested(m, "voice_settings.stability", 0.5)
|
|
vs := m["voice_settings"].(map[string]any)
|
|
if vs["speed"] != 1.0 {
|
|
t.Errorf("speed: got %v, want 1.0 (must not be overwritten)", vs["speed"])
|
|
}
|
|
if vs["stability"] != 0.5 {
|
|
t.Errorf("stability: got %v, want 0.5", vs["stability"])
|
|
}
|
|
}
|
|
|
|
func TestSetNested_OverridesScalar(t *testing.T) {
|
|
m := map[string]any{}
|
|
SetNested(m, "voice_settings.stability", 0.5)
|
|
SetNested(m, "voice_settings.stability", 0.7)
|
|
vs := m["voice_settings"].(map[string]any)
|
|
if vs["stability"] != 0.7 {
|
|
t.Errorf("stability: got %v, want 0.7 (override)", vs["stability"])
|
|
}
|
|
}
|
|
|
|
func TestSetNested_ReplacesNonMapIntermediate(t *testing.T) {
|
|
// If intermediate segment holds a scalar, it must be replaced with a map.
|
|
m := map[string]any{"voice_settings": "not-a-map"}
|
|
SetNested(m, "voice_settings.stability", 0.5)
|
|
vs, ok := m["voice_settings"].(map[string]any)
|
|
if !ok {
|
|
t.Fatalf("voice_settings should have been replaced with a map, got %T", m["voice_settings"])
|
|
}
|
|
if vs["stability"] != 0.5 {
|
|
t.Errorf("stability: got %v, want 0.5", vs["stability"])
|
|
}
|
|
}
|
|
|
|
func TestSetNested_ThreeLevel(t *testing.T) {
|
|
m := map[string]any{}
|
|
SetNested(m, "a.b.c", 42)
|
|
ab := m["a"].(map[string]any)
|
|
abc := ab["b"].(map[string]any)
|
|
if abc["c"] != 42 {
|
|
t.Errorf("a.b.c: got %v, want 42", abc["c"])
|
|
}
|
|
}
|
|
|
|
func TestGetNested_FlatKey(t *testing.T) {
|
|
m := map[string]any{"speed": 1.5}
|
|
v, ok := GetNested(m, "speed")
|
|
if !ok || v != 1.5 {
|
|
t.Errorf("GetNested flat: got (%v,%v), want (1.5,true)", v, ok)
|
|
}
|
|
}
|
|
|
|
func TestGetNested_TwoLevel(t *testing.T) {
|
|
m := map[string]any{
|
|
"voice_settings": map[string]any{"stability": 0.5},
|
|
}
|
|
v, ok := GetNested(m, "voice_settings.stability")
|
|
if !ok || v != 0.5 {
|
|
t.Errorf("GetNested two-level: got (%v,%v), want (0.5,true)", v, ok)
|
|
}
|
|
}
|
|
|
|
func TestGetNested_MissingKey(t *testing.T) {
|
|
m := map[string]any{}
|
|
_, ok := GetNested(m, "missing")
|
|
if ok {
|
|
t.Error("expected false for missing key")
|
|
}
|
|
}
|
|
|
|
func TestGetNested_MissingIntermediate(t *testing.T) {
|
|
m := map[string]any{}
|
|
_, ok := GetNested(m, "a.b.c")
|
|
if ok {
|
|
t.Error("expected false when intermediate key absent")
|
|
}
|
|
}
|
|
|
|
func TestGetNested_NonMapIntermediate(t *testing.T) {
|
|
m := map[string]any{"a": "scalar"}
|
|
_, ok := GetNested(m, "a.b")
|
|
if ok {
|
|
t.Error("expected false when intermediate is scalar")
|
|
}
|
|
}
|
|
|
|
func TestSetNested_EmptyKeyIgnored(t *testing.T) {
|
|
m := map[string]any{}
|
|
SetNested(m, "", "value") // must not panic
|
|
if len(m) != 0 {
|
|
t.Error("empty key should leave map unchanged")
|
|
}
|
|
}
|