feat: better language selection

This commit is contained in:
leonardotrapani
2026-02-01 18:53:44 +01:00
parent 0025bf97b6
commit 3ae86f7bae
40 changed files with 452 additions and 852 deletions
+4 -15
View File
@@ -21,21 +21,10 @@ func (p *DeepgramProvider) IsLocal() bool {
}
func (p *DeepgramProvider) Models() []Model {
// Nova-3 language support - maps to our 57 language list
// from https://developers.deepgram.com/docs/models-languages-overview
nova3Langs := []string{
"ar", "be", "bs", "bg", "ca", "hr", "cs", "da", "nl", "en", "et", "fi",
"fr", "de", "el", "hi", "hu", "id", "it", "ja", "kn", "ko", "lv", "lt",
"mk", "ms", "mr", "no", "pl", "pt", "ro", "ru", "sr", "sk", "sl", "es",
"sv", "tl", "ta", "tr", "uk", "vi",
}
// Nova-2 language support - subset of nova-3
nova2Langs := []string{
"bg", "ca", "zh", "cs", "da", "nl", "en", "et", "fi", "fr", "de", "el",
"hi", "hu", "id", "it", "ja", "ko", "lv", "lt", "ms", "no", "pl", "pt",
"ro", "ru", "sk", "es", "sv", "th", "tr", "uk", "vi",
}
// https://developers.deepgram.com/docs/models-languages-overview
nova3Langs := deepgramNova3Languages
// https://developers.deepgram.com/docs/models-languages-overview
nova2Langs := deepgramNova2Languages
docsURL := "https://developers.deepgram.com/docs/language"
+3 -6
View File
@@ -1,7 +1,5 @@
package provider
import "github.com/leonardotrapani/hyprvoice/internal/language"
// ElevenLabsProvider implements Provider for ElevenLabs services (transcription only)
type ElevenLabsProvider struct{}
@@ -23,10 +21,9 @@ func (p *ElevenLabsProvider) IsLocal() bool {
}
func (p *ElevenLabsProvider) Models() []Model {
// ElevenLabs Scribe supports 90+ languages, including all 57 from our master list
// See: https://elevenlabs.io/speech-to-text
allLangs := language.AllLanguageCodes()
docsURL := "https://elevenlabs.io/docs/capabilities/speech-to-text#supported-languages"
// https://elevenlabs.io/speech-to-text
allLangs := elevenLabsTranscriptionLanguages
docsURL := "https://elevenlabs.io/speech-to-text"
return []Model{
{
+30 -36
View File
@@ -1,10 +1,6 @@
package provider
import (
"strings"
"github.com/leonardotrapani/hyprvoice/internal/language"
)
import "strings"
// GroqProvider implements Provider for Groq services
type GroqProvider struct{}
@@ -26,7 +22,8 @@ func (p *GroqProvider) IsLocal() bool {
}
func (p *GroqProvider) Models() []Model {
allLangs := language.AllLanguageCodes()
// https://console.groq.com/docs/speech-to-text#supported-languages
allLangs := groqTranscriptionLanguages
docsURL := "https://console.groq.com/docs/speech-to-text#supported-languages"
return []Model{
@@ -59,40 +56,37 @@ func (p *GroqProvider) Models() []Model {
},
// LLM models
{
ID: "llama-3.3-70b-versatile",
Name: "Llama 3.3 70B Versatile",
Description: "Most capable Llama model",
Type: LLM,
SupportsBatch: true,
SupportsStreaming: false,
Local: false,
AdapterType: AdapterOpenAI,
SupportedLanguages: allLangs,
Endpoint: &EndpointConfig{BaseURL: "https://api.groq.com/openai", Path: "/v1/chat/completions"},
ID: "llama-3.3-70b-versatile",
Name: "Llama 3.3 70B Versatile",
Description: "Most capable Llama model",
Type: LLM,
SupportsBatch: true,
SupportsStreaming: false,
Local: false,
AdapterType: AdapterOpenAI,
Endpoint: &EndpointConfig{BaseURL: "https://api.groq.com/openai", Path: "/v1/chat/completions"},
},
{
ID: "llama-3.1-8b-instant",
Name: "Llama 3.1 8B Instant",
Description: "Fast and efficient",
Type: LLM,
SupportsBatch: true,
SupportsStreaming: false,
Local: false,
AdapterType: AdapterOpenAI,
SupportedLanguages: allLangs,
Endpoint: &EndpointConfig{BaseURL: "https://api.groq.com/openai", Path: "/v1/chat/completions"},
ID: "llama-3.1-8b-instant",
Name: "Llama 3.1 8B Instant",
Description: "Fast and efficient",
Type: LLM,
SupportsBatch: true,
SupportsStreaming: false,
Local: false,
AdapterType: AdapterOpenAI,
Endpoint: &EndpointConfig{BaseURL: "https://api.groq.com/openai", Path: "/v1/chat/completions"},
},
{
ID: "mixtral-8x7b-32768",
Name: "Mixtral 8x7B",
Description: "Mixture of experts model",
Type: LLM,
SupportsBatch: true,
SupportsStreaming: false,
Local: false,
AdapterType: AdapterOpenAI,
SupportedLanguages: allLangs,
Endpoint: &EndpointConfig{BaseURL: "https://api.groq.com/openai", Path: "/v1/chat/completions"},
ID: "mixtral-8x7b-32768",
Name: "Mixtral 8x7B",
Description: "Mixture of experts model",
Type: LLM,
SupportsBatch: true,
SupportsStreaming: false,
Local: false,
AdapterType: AdapterOpenAI,
Endpoint: &EndpointConfig{BaseURL: "https://api.groq.com/openai", Path: "/v1/chat/completions"},
},
}
}
+47
View File
@@ -0,0 +1,47 @@
package provider
var openaiTranscriptionLanguages = []string{
"af", "ar", "hy", "az", "be", "bs", "bg", "ca", "zh", "hr", "cs", "da",
"nl", "en", "et", "fi", "fr", "gl", "de", "el", "he", "hi", "hu", "is",
"id", "it", "ja", "kn", "kk", "ko", "lv", "lt", "mk", "ms", "mr", "mi",
"ne", "no", "fa", "pl", "pt", "ro", "ru", "sr", "sk", "sl", "es", "sw",
"sv", "tl", "ta", "th", "tr", "uk", "ur", "vi", "cy",
}
var groqTranscriptionLanguages = openaiTranscriptionLanguages
var mistralTranscriptionLanguages = openaiTranscriptionLanguages
var whisperTranscriptionLanguages = openaiTranscriptionLanguages
var whisperEnglishOnlyLanguages = []string{"en"}
var deepgramNova3Languages = []string{
"multi",
"ar", "ar-AE", "ar-SA", "ar-QA", "ar-KW", "ar-SY", "ar-LB", "ar-PS", "ar-JO", "ar-EG", "ar-SD", "ar-TD", "ar-MA", "ar-DZ", "ar-TN", "ar-IQ", "ar-IR",
"be", "bn", "bs", "bg", "ca", "hr", "cs", "da", "da-DK", "nl", "nl-BE",
"en", "en-US", "en-AU", "en-GB", "en-IN", "en-NZ", "et", "fi", "fr", "fr-CA",
"de", "de-CH", "el", "hi", "hu", "id", "it", "ja", "kn", "ko", "ko-KR",
"lv", "lt", "mk", "ms", "mr", "no", "pl", "pt", "pt-BR", "pt-PT", "ro",
"ru", "sr", "sk", "sl", "es", "es-419", "sv", "sv-SE", "tl", "ta", "te",
"tr", "uk", "vi",
}
var deepgramNova2Languages = []string{
"multi",
"bg", "ca", "zh", "zh-CN", "zh-Hans", "zh-TW", "zh-Hant", "zh-HK", "cs",
"da", "da-DK", "nl", "nl-BE", "en", "en-US", "en-AU", "en-GB", "en-NZ", "en-IN",
"et", "fi", "fr", "fr-CA", "de", "de-CH", "el", "hi", "hu", "id", "it", "ja",
"ko", "ko-KR", "lv", "lt", "ms", "no", "pl", "pt", "pt-BR", "pt-PT", "ro",
"ru", "sk", "es", "es-419", "sv", "sv-SE", "th", "th-TH", "tr", "uk", "vi",
}
var elevenLabsTranscriptionLanguages = []string{
"bel", "bos", "bul", "cat", "hrv", "ces", "dan", "nld", "eng", "est", "fin", "fra",
"glg", "deu", "ell", "hun", "isl", "ind", "ita", "jpn", "kan", "lav", "mkd", "msa",
"mal", "nor", "pol", "por", "ron", "rus", "slk", "spa", "swe", "tur", "ukr", "vie",
"hye", "aze", "ben", "yue", "fil", "kat", "guj", "hin", "kaz", "lit", "mlt", "cmn",
"mar", "nep", "ori", "fas", "srp", "slv", "swa", "tam", "tel",
"afr", "ara", "asm", "ast", "mya", "hau", "heb", "jav", "kor", "kir", "ltz", "mri",
"oci", "pan", "tgk", "tha", "uzb", "cym",
"amh", "lug", "ibo", "gle", "khm", "kur", "lao", "mon", "nso", "pus", "sna", "snd",
"som", "urd", "wol", "xho", "yor", "zul",
}
+3 -4
View File
@@ -1,7 +1,5 @@
package provider
import "github.com/leonardotrapani/hyprvoice/internal/language"
// MistralProvider implements Provider for Mistral services (transcription only)
type MistralProvider struct{}
@@ -23,8 +21,9 @@ func (p *MistralProvider) IsLocal() bool {
}
func (p *MistralProvider) Models() []Model {
allLangs := language.AllLanguageCodes()
docsURL := "https://docs.mistral.ai/capabilities/speech/"
// https://docs.mistral.ai/capabilities/audio/
allLangs := mistralTranscriptionLanguages
docsURL := "https://docs.mistral.ai/capabilities/audio/"
return []Model{
{
+1 -9
View File
@@ -1,7 +1,5 @@
package provider
import "github.com/leonardotrapani/hyprvoice/internal/language"
// ModelType represents the type of a model
type ModelType int
@@ -22,7 +20,7 @@ type Model struct {
AdapterType string // which adapter to use (e.g., "openai", "elevenlabs", "whisper-cpp")
StreamingAdapter string // adapter for streaming mode (if different from AdapterType)
StreamingEndpoint *EndpointConfig // endpoint for streaming mode (if different from Endpoint)
SupportedLanguages []string // explicit list of supported language codes
SupportedLanguages []string // explicit list of provider language codes
Endpoint *EndpointConfig // nil for local models
LocalInfo *LocalModelInfo // nil for cloud models
DocsURL string // URL to provider's language support documentation
@@ -69,9 +67,3 @@ func (m *Model) SupportsLanguage(code string) bool {
}
return false
}
// SupportsAllLanguages returns true if the model supports all 57 languages
func (m *Model) SupportsAllLanguages() bool {
allCodes := language.AllLanguageCodes()
return len(m.SupportedLanguages) == len(allCodes)
}
+4 -69
View File
@@ -1,10 +1,6 @@
package provider
import (
"testing"
"github.com/leonardotrapani/hyprvoice/internal/language"
)
import "testing"
func TestModel_NeedsDownload(t *testing.T) {
tests := []struct {
@@ -118,11 +114,9 @@ func TestModel_SupportsBothModes(t *testing.T) {
}
func TestModel_SupportsLanguage(t *testing.T) {
allCodes := language.AllLanguageCodes()
multilingualModel := Model{
ID: "whisper-large-v3",
SupportedLanguages: allCodes,
SupportedLanguages: []string{"en", "es", "zh"},
}
englishOnlyModel := Model{
@@ -207,65 +201,6 @@ func TestModel_SupportsLanguage(t *testing.T) {
}
}
func TestModel_SupportsAllLanguages(t *testing.T) {
allCodes := language.AllLanguageCodes()
tests := []struct {
name string
model Model
expected bool
}{
{
name: "model with all 57 languages",
model: Model{
ID: "whisper-large-v3",
SupportedLanguages: allCodes,
},
expected: true,
},
{
name: "english-only model",
model: Model{
ID: "base.en",
SupportedLanguages: []string{"en"},
},
expected: false,
},
{
name: "model with some languages",
model: Model{
ID: "partial",
SupportedLanguages: []string{"en", "es", "fr", "de"},
},
expected: false,
},
{
name: "model with empty languages",
model: Model{
ID: "empty",
SupportedLanguages: []string{},
},
expected: false,
},
{
name: "model with nil languages",
model: Model{
ID: "nil",
SupportedLanguages: nil,
},
expected: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := tc.model.SupportsAllLanguages(); got != tc.expected {
t.Errorf("SupportsAllLanguages() = %v, want %v", got, tc.expected)
}
})
}
}
func TestModelType_Constants(t *testing.T) {
// verify ModelType constants exist and are distinct
if Transcription == LLM {
@@ -387,8 +322,8 @@ func TestAllTranscriptionModels_HaveDocsURL(t *testing.T) {
expectedDocsURLs := map[string]string{
"openai": "https://platform.openai.com/docs/guides/speech-to-text#supported-languages",
"groq": "https://console.groq.com/docs/speech-to-text#supported-languages",
"mistral": "https://docs.mistral.ai/capabilities/speech/",
"elevenlabs": "https://elevenlabs.io/docs/capabilities/speech-to-text#supported-languages",
"mistral": "https://docs.mistral.ai/capabilities/audio/",
"elevenlabs": "https://elevenlabs.io/speech-to-text",
"deepgram": "https://developers.deepgram.com/docs/language",
"whisper-cpp": "https://github.com/openai/whisper#available-models-and-languages",
}
+1 -2
View File
@@ -14,7 +14,6 @@ const (
const (
ConfigProviderOpenAI = "openai"
ConfigProviderGroqTranscription = "groq-transcription"
ConfigProviderGroqTranslation = "groq-translation"
ConfigProviderMistralTranscription = "mistral-transcription"
ConfigProviderElevenLabs = "elevenlabs"
ConfigProviderDeepgram = "deepgram"
@@ -44,7 +43,7 @@ const (
// e.g. "groq-transcription" -> "groq", "mistral-transcription" -> "mistral"
func BaseProviderName(configProvider string) string {
switch configProvider {
case ConfigProviderGroqTranscription, ConfigProviderGroqTranslation:
case ConfigProviderGroqTranscription:
return ProviderGroq
case ConfigProviderMistralTranscription:
return ProviderMistral
+21 -26
View File
@@ -1,10 +1,6 @@
package provider
import (
"strings"
"github.com/leonardotrapani/hyprvoice/internal/language"
)
import "strings"
// OpenAIProvider implements Provider for OpenAI services
type OpenAIProvider struct{}
@@ -26,7 +22,8 @@ func (p *OpenAIProvider) IsLocal() bool {
}
func (p *OpenAIProvider) Models() []Model {
allLangs := language.AllLanguageCodes()
// https://platform.openai.com/docs/guides/speech-to-text#supported-languages
allLangs := openaiTranscriptionLanguages
docsURL := "https://platform.openai.com/docs/guides/speech-to-text#supported-languages"
@@ -77,28 +74,26 @@ func (p *OpenAIProvider) Models() []Model {
},
// LLM models
{
ID: "gpt-4o-mini",
Name: "GPT-4o Mini",
Description: "Fast and affordable GPT-4 variant",
Type: LLM,
SupportsBatch: true,
SupportsStreaming: false,
Local: false,
AdapterType: AdapterOpenAI,
SupportedLanguages: allLangs,
Endpoint: &EndpointConfig{BaseURL: "https://api.openai.com", Path: "/v1/chat/completions"},
ID: "gpt-4o-mini",
Name: "GPT-4o Mini",
Description: "Fast and affordable GPT-4 variant",
Type: LLM,
SupportsBatch: true,
SupportsStreaming: false,
Local: false,
AdapterType: AdapterOpenAI,
Endpoint: &EndpointConfig{BaseURL: "https://api.openai.com", Path: "/v1/chat/completions"},
},
{
ID: "gpt-4o",
Name: "GPT-4o",
Description: "Most capable GPT-4 model",
Type: LLM,
SupportsBatch: true,
SupportsStreaming: false,
Local: false,
AdapterType: AdapterOpenAI,
SupportedLanguages: allLangs,
Endpoint: &EndpointConfig{BaseURL: "https://api.openai.com", Path: "/v1/chat/completions"},
ID: "gpt-4o",
Name: "GPT-4o",
Description: "Most capable GPT-4 model",
Type: LLM,
SupportsBatch: true,
SupportsStreaming: false,
Local: false,
AdapterType: AdapterOpenAI,
Endpoint: &EndpointConfig{BaseURL: "https://api.openai.com", Path: "/v1/chat/completions"},
},
}
}
+5 -6
View File
@@ -1,9 +1,6 @@
package provider
import (
"github.com/leonardotrapani/hyprvoice/internal/language"
"github.com/leonardotrapani/hyprvoice/internal/models/whisper"
)
import "github.com/leonardotrapani/hyprvoice/internal/models/whisper"
// WhisperCppProvider implements Provider for local whisper.cpp transcription
type WhisperCppProvider struct{}
@@ -25,8 +22,10 @@ func (p *WhisperCppProvider) IsLocal() bool {
}
func (p *WhisperCppProvider) Models() []Model {
allLangs := language.AllLanguageCodes()
englishOnly := []string{"en"}
// https://github.com/openai/whisper#available-models-and-languages
allLangs := whisperTranscriptionLanguages
// https://github.com/openai/whisper#available-models-and-languages
englishOnly := whisperEnglishOnlyLanguages
docsURL := "https://github.com/openai/whisper#available-models-and-languages"
whisperModels := whisper.ListModels()
+6 -12
View File
@@ -1,10 +1,6 @@
package provider
import (
"testing"
"github.com/leonardotrapani/hyprvoice/internal/language"
)
import "testing"
func TestWhisperCppProvider_GetProvider(t *testing.T) {
p := GetProvider("whisper-cpp")
@@ -88,20 +84,18 @@ func TestWhisperCppProvider_MultilingualModels(t *testing.T) {
"large-v3": true,
}
allLangs := language.AllLanguageCodes()
for _, m := range models {
isMultilingual := multilingualIDs[m.ID]
if isMultilingual {
if len(m.SupportedLanguages) != len(allLangs) {
t.Errorf("model %s: expected %d languages, got %d", m.ID, len(allLangs), len(m.SupportedLanguages))
}
if !m.SupportsAllLanguages() {
t.Errorf("model %s: SupportsAllLanguages() should be true", m.ID)
if len(m.SupportedLanguages) <= 1 {
t.Errorf("model %s: expected multiple languages, got %d", m.ID, len(m.SupportedLanguages))
}
if !m.SupportsLanguage("es") {
t.Errorf("model %s: SupportsLanguage('es') should be true", m.ID)
}
if !m.SupportsLanguage("en") {
t.Errorf("model %s: SupportsLanguage('en') should be true", m.ID)
}
}
}
}