mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-25 06:21:10 +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.
46 lines
1.2 KiB
Go
46 lines
1.2 KiB
Go
package audio
|
|
|
|
// SetNested sets a value in a map[string]any using a dot-separated key path.
|
|
// Intermediate maps are created on demand. If an intermediate path segment
|
|
// already holds a non-map value it is replaced with a new map.
|
|
//
|
|
// Example:
|
|
//
|
|
// SetNested(m, "voice_settings.stability", 0.5)
|
|
// // m = {"voice_settings": {"stability": 0.5}}
|
|
func SetNested(m map[string]any, key string, value any) {
|
|
parts, err := parseKeyPath(key)
|
|
if err != nil {
|
|
return // malformed key — silently skip
|
|
}
|
|
cur := m
|
|
for _, part := range parts[:len(parts)-1] {
|
|
next, ok := cur[part].(map[string]any)
|
|
if !ok {
|
|
next = make(map[string]any)
|
|
cur[part] = next
|
|
}
|
|
cur = next
|
|
}
|
|
cur[parts[len(parts)-1]] = value
|
|
}
|
|
|
|
// GetNested retrieves a value from a map[string]any using a dot-separated key
|
|
// path. Returns (nil, false) if any segment is missing or non-traversable.
|
|
func GetNested(m map[string]any, key string) (any, bool) {
|
|
parts, err := parseKeyPath(key)
|
|
if err != nil {
|
|
return nil, false
|
|
}
|
|
cur := m
|
|
for _, part := range parts[:len(parts)-1] {
|
|
next, ok := cur[part].(map[string]any)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
cur = next
|
|
}
|
|
v, ok := cur[parts[len(parts)-1]]
|
|
return v, ok
|
|
}
|