mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-30 12:23:09 +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.
102 lines
3.0 KiB
Go
102 lines
3.0 KiB
Go
package audio
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
)
|
|
|
|
// fakeDescribableProvider is a TTSProvider that also implements DescribableProvider.
|
|
type fakeDescribableProvider struct {
|
|
name string
|
|
caps ProviderCapabilities
|
|
}
|
|
|
|
func (f *fakeDescribableProvider) Name() string { return f.name }
|
|
func (f *fakeDescribableProvider) Synthesize(_ context.Context, _ string, _ TTSOptions) (*SynthResult, error) {
|
|
return nil, nil
|
|
}
|
|
func (f *fakeDescribableProvider) Capabilities() ProviderCapabilities { return f.caps }
|
|
|
|
// fakePlainProvider is a TTSProvider only (no DescribableProvider).
|
|
type fakePlainProvider struct{ name string }
|
|
|
|
func (f *fakePlainProvider) Name() string { return f.name }
|
|
func (f *fakePlainProvider) Synthesize(_ context.Context, _ string, _ TTSOptions) (*SynthResult, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// TestListCapabilities_AggregatesProviders verifies that ListCapabilities returns
|
|
// one entry per registered provider: describable ones return full schema, plain
|
|
// ones return a minimal stub.
|
|
func TestListCapabilities_AggregatesProviders(t *testing.T) {
|
|
mgr := NewManager(ManagerConfig{Primary: "describable"})
|
|
|
|
describable := &fakeDescribableProvider{
|
|
name: "describable",
|
|
caps: ProviderCapabilities{
|
|
Provider: "describable",
|
|
DisplayName: "Describable TTS",
|
|
RequiresAPIKey: true,
|
|
Models: []string{"model-a"},
|
|
},
|
|
}
|
|
plain := &fakePlainProvider{name: "plain"}
|
|
|
|
mgr.RegisterTTS(describable)
|
|
mgr.RegisterTTS(plain)
|
|
|
|
caps := mgr.ListCapabilities()
|
|
if len(caps) != 2 {
|
|
t.Fatalf("ListCapabilities: got %d entries, want 2", len(caps))
|
|
}
|
|
|
|
// Build lookup map for order-independent checks.
|
|
byName := make(map[string]ProviderCapabilities)
|
|
for _, c := range caps {
|
|
byName[c.Provider] = c
|
|
}
|
|
|
|
// Describable entry — should have full schema.
|
|
dc, ok := byName["describable"]
|
|
if !ok {
|
|
t.Fatal("missing 'describable' entry")
|
|
}
|
|
if dc.DisplayName != "Describable TTS" {
|
|
t.Errorf("DisplayName: got %q want %q", dc.DisplayName, "Describable TTS")
|
|
}
|
|
if !dc.RequiresAPIKey {
|
|
t.Error("RequiresAPIKey: expected true")
|
|
}
|
|
if len(dc.Models) != 1 || dc.Models[0] != "model-a" {
|
|
t.Errorf("Models: got %v", dc.Models)
|
|
}
|
|
|
|
// Plain entry — stub: Provider+DisplayName set, everything else zero.
|
|
pc, ok := byName["plain"]
|
|
if !ok {
|
|
t.Fatal("missing 'plain' entry")
|
|
}
|
|
if pc.Provider != "plain" {
|
|
t.Errorf("stub Provider: got %q want %q", pc.Provider, "plain")
|
|
}
|
|
if pc.DisplayName != "plain" {
|
|
t.Errorf("stub DisplayName: got %q want %q", pc.DisplayName, "plain")
|
|
}
|
|
if pc.RequiresAPIKey {
|
|
t.Error("stub RequiresAPIKey: expected false")
|
|
}
|
|
}
|
|
|
|
// TestListCapabilities_EmptyManager verifies that ListCapabilities returns an empty
|
|
// non-nil slice when no providers are registered.
|
|
func TestListCapabilities_EmptyManager(t *testing.T) {
|
|
mgr := NewManager(ManagerConfig{})
|
|
caps := mgr.ListCapabilities()
|
|
if caps == nil {
|
|
t.Error("ListCapabilities on empty manager: got nil, want empty slice")
|
|
}
|
|
if len(caps) != 0 {
|
|
t.Errorf("ListCapabilities on empty manager: got %d entries, want 0", len(caps))
|
|
}
|
|
}
|