Integrating asr/tts models w/ new security config

This commit is contained in:
Huaaudio 2026-03-28 02:38:32 +01:00
parent 1e8cabb063
commit 33b7729252
6 changed files with 38 additions and 23 deletions

View file

@ -46,20 +46,23 @@ func supportsAudioTranscription(model string) bool {
func DetectTranscriber(cfg *config.Config) Transcriber {
if modelName := strings.TrimSpace(cfg.Voice.ModelName); modelName != "" {
modelCfg, err := cfg.GetModelConfig(modelName)
if err != nil {
return nil
}
if supportsAudioTranscription(modelCfg.Model) {
return NewAudioModelTranscriber(modelCfg)
if err == nil {
protocol, _ := providers.ExtractProtocol(modelCfg.Model)
if protocol == "elevenlabs" && modelCfg.APIKey() != "" {
return NewElevenLabsTranscriber(modelCfg.APIKey(), modelCfg.APIBase)
}
if supportsAudioTranscription(modelCfg.Model) {
return NewAudioModelTranscriber(modelCfg)
}
}
}
// ElevenLabs voice config (supports Scribe STT).
if key := strings.TrimSpace(cfg.Voice.ElevenLabsAPIKey); key != "" {
return NewElevenLabsTranscriber(key)
}
// Fall back to any model-list entry that uses the groq/ protocol.
// Fall back to scanning ModelList for suitable ASR providers
for _, mc := range cfg.ModelList {
protocol, _ := providers.ExtractProtocol(mc.Model)
if protocol == "elevenlabs" && mc.APIKey() != "" {
return NewElevenLabsTranscriber(mc.APIKey(), mc.APIBase)
}
if (strings.HasPrefix(mc.Model, "groq/") || mc.ModelName == "groq" || mc.Model == "whisper-large-v3-turbo") &&
mc.APIKey() != "" {
return NewGroqTranscriber(mc.APIKey())

View file

@ -127,15 +127,17 @@ func TestDetectTranscriber(t *testing.T) {
{
name: "elevenlabs voice config key",
cfg: &config.Config{
Voice: config.VoiceConfig{ElevenLabsAPIKey: "sk_elevenlabs_test"},
ModelList: []*config.ModelConfig{
{Model: "elevenlabs/scribe_v1", APIKeys: config.SimpleSecureStrings("sk_elevenlabs_test")},
},
},
wantName: "elevenlabs",
},
{
name: "elevenlabs takes priority over groq model list",
cfg: &config.Config{
Voice: config.VoiceConfig{ElevenLabsAPIKey: "sk_elevenlabs_test"},
ModelList: []*config.ModelConfig{
{Model: "elevenlabs/scribe_v1", APIKeys: config.SimpleSecureStrings("sk_elevenlabs_test")},
{
ModelName: "groq",
Model: "groq/llama-3.3-70b",
@ -149,10 +151,10 @@ func TestDetectTranscriber(t *testing.T) {
name: "voice model name takes priority over elevenlabs",
cfg: &config.Config{
Voice: config.VoiceConfig{
ModelName: "voice-gemini",
ElevenLabsAPIKey: "sk_elevenlabs_test",
ModelName: "voice-gemini",
},
ModelList: []*config.ModelConfig{
{Model: "elevenlabs", APIKeys: config.SimpleSecureStrings("sk_elevenlabs_test")},
{
ModelName: "voice-gemini",
Model: "gemini/gemini-2.5-flash",

View file

@ -23,12 +23,16 @@ type ElevenLabsTranscriber struct {
httpClient *http.Client
}
func NewElevenLabsTranscriber(apiKey string) *ElevenLabsTranscriber {
func NewElevenLabsTranscriber(apiKey, apiBase string) *ElevenLabsTranscriber {
logger.DebugCF("voice", "Creating ElevenLabs transcriber", map[string]any{"has_api_key": apiKey != ""})
if apiBase == "" {
apiBase = "https://api.elevenlabs.io"
}
return &ElevenLabsTranscriber{
apiKey: apiKey,
apiBase: "https://api.elevenlabs.io",
apiBase: apiBase,
httpClient: &http.Client{
Timeout: 120 * time.Second,
},

View file

@ -14,7 +14,7 @@ import (
var _ Transcriber = (*ElevenLabsTranscriber)(nil)
func TestElevenLabsTranscriberName(t *testing.T) {
tr := NewElevenLabsTranscriber("sk_test")
tr := NewElevenLabsTranscriber("sk_test", "")
if got := tr.Name(); got != "elevenlabs" {
t.Errorf("Name() = %q, want %q", got, "elevenlabs")
}
@ -43,7 +43,7 @@ func TestElevenLabsTranscribe(t *testing.T) {
}))
defer srv.Close()
tr := NewElevenLabsTranscriber("sk_test")
tr := NewElevenLabsTranscriber("sk_test", "")
tr.apiBase = srv.URL
resp, err := tr.Transcribe(context.Background(), audioPath)
@ -64,7 +64,7 @@ func TestElevenLabsTranscribe(t *testing.T) {
}))
defer srv.Close()
tr := NewElevenLabsTranscriber("sk_bad")
tr := NewElevenLabsTranscriber("sk_bad", "")
tr.apiBase = srv.URL
_, err := tr.Transcribe(context.Background(), audioPath)
@ -74,7 +74,7 @@ func TestElevenLabsTranscribe(t *testing.T) {
})
t.Run("missing file", func(t *testing.T) {
tr := NewElevenLabsTranscriber("sk_test")
tr := NewElevenLabsTranscriber("sk_test", "")
_, err := tr.Transcribe(context.Background(), filepath.Join(tmpDir, "nonexistent.ogg"))
if err == nil {
t.Fatal("expected error for missing file, got nil")

View file

@ -19,6 +19,12 @@ type TTSProvider interface {
}
func DetectTTS(cfg *config.Config) TTSProvider {
if modelName := strings.TrimSpace(cfg.Voice.TTSModelName); modelName != "" {
if mc, err := cfg.GetModelConfig(modelName); err == nil && mc.APIKey() != "" {
return NewOpenAITTSProvider(mc.APIKey(), mc.APIBase, mc.Proxy)
}
}
for _, mc := range cfg.ModelList {
if strings.Contains(strings.ToLower(mc.Model), "tts") && mc.APIKey() != "" {
return NewOpenAITTSProvider(mc.APIKey(), mc.APIBase, mc.Proxy)

View file

@ -639,9 +639,9 @@ type DevicesConfig struct {
}
type VoiceConfig struct {
ModelName string `json:"model_name,omitempty" env:"PICOCLAW_VOICE_MODEL_NAME"`
EchoTranscription bool `json:"echo_transcription" env:"PICOCLAW_VOICE_ECHO_TRANSCRIPTION"`
ElevenLabsAPIKey string `json:"elevenlabs_api_key,omitempty" env:"PICOCLAW_VOICE_ELEVENLABS_API_KEY"`
ModelName string `json:"model_name,omitempty" env:"PICOCLAW_VOICE_MODEL_NAME"`
TTSModelName string `json:"tts_model_name,omitempty" env:"PICOCLAW_VOICE_TTS_MODEL_NAME"`
EchoTranscription bool `json:"echo_transcription" env:"PICOCLAW_VOICE_ECHO_TRANSCRIPTION"`
}
// ModelConfig represents a model-centric provider configuration.