refactor provider interface to return models with metadata

- replaced old TranscriptionModels/LLMModels/SupportsX methods with Models() []Model
- added DefaultModel(t ModelType), IsLocal() to interface
- added helpers: GetModel, ModelsOfType, FindModelByID, ModelsForLanguage, ValidateModelLanguage
- all providers now return full Model metadata with SupportedLanguages, AdapterType, Endpoint
- groq distil-whisper-large-v3-en marked as english-only
- updated TUI to use new interface
This commit is contained in:
leonardotrapani
2026-02-01 00:33:20 +01:00
parent 51aba743f8
commit c23ac60b8f
10 changed files with 498 additions and 98 deletions
+35 -16
View File
@@ -1,5 +1,7 @@
package provider package provider
import "github.com/leonardotrapani/hyprvoice/internal/language"
// ElevenLabsProvider implements Provider for ElevenLabs services (transcription only) // ElevenLabsProvider implements Provider for ElevenLabs services (transcription only)
type ElevenLabsProvider struct{} type ElevenLabsProvider struct{}
@@ -16,26 +18,43 @@ func (p *ElevenLabsProvider) ValidateAPIKey(key string) bool {
return len(key) > 0 return len(key) > 0
} }
func (p *ElevenLabsProvider) SupportsTranscription() bool { func (p *ElevenLabsProvider) IsLocal() bool {
return true
}
func (p *ElevenLabsProvider) SupportsLLM() bool {
return false return false
} }
func (p *ElevenLabsProvider) DefaultTranscriptionModel() string { func (p *ElevenLabsProvider) Models() []Model {
return "scribe_v1" allLangs := language.AllLanguageCodes()
return []Model{
{
ID: "scribe_v1",
Name: "Scribe v1",
Description: "99 languages, best accuracy",
Type: Transcription,
Streaming: false,
Local: false,
AdapterType: "elevenlabs",
SupportedLanguages: allLangs,
Endpoint: &EndpointConfig{BaseURL: "https://api.elevenlabs.io", Path: "/v1/speech-to-text"},
},
{
ID: "scribe_v2",
Name: "Scribe v2",
Description: "Lower latency, real-time optimized",
Type: Transcription,
Streaming: false,
Local: false,
AdapterType: "elevenlabs",
SupportedLanguages: allLangs,
Endpoint: &EndpointConfig{BaseURL: "https://api.elevenlabs.io", Path: "/v1/speech-to-text"},
},
}
} }
func (p *ElevenLabsProvider) DefaultLLMModel() string { func (p *ElevenLabsProvider) DefaultModel(t ModelType) string {
switch t {
case Transcription:
return "scribe_v1"
}
return "" return ""
} }
func (p *ElevenLabsProvider) TranscriptionModels() []string {
return []string{"scribe_v1", "scribe_v2"}
}
func (p *ElevenLabsProvider) LLMModels() []string {
return nil
}
+88 -19
View File
@@ -1,6 +1,10 @@
package provider package provider
import "strings" import (
"strings"
"github.com/leonardotrapani/hyprvoice/internal/language"
)
// GroqProvider implements Provider for Groq services // GroqProvider implements Provider for Groq services
type GroqProvider struct{} type GroqProvider struct{}
@@ -17,26 +21,91 @@ func (p *GroqProvider) ValidateAPIKey(key string) bool {
return strings.HasPrefix(key, "gsk_") return strings.HasPrefix(key, "gsk_")
} }
func (p *GroqProvider) SupportsTranscription() bool { func (p *GroqProvider) IsLocal() bool {
return true return false
} }
func (p *GroqProvider) SupportsLLM() bool { func (p *GroqProvider) Models() []Model {
return true allLangs := language.AllLanguageCodes()
return []Model{
// transcription models
{
ID: "whisper-large-v3",
Name: "Whisper Large v3",
Description: "Full Whisper v3 model, best accuracy",
Type: Transcription,
Streaming: false,
Local: false,
AdapterType: "openai",
SupportedLanguages: allLangs,
Endpoint: &EndpointConfig{BaseURL: "https://api.groq.com/openai", Path: "/v1/audio/transcriptions"},
},
{
ID: "whisper-large-v3-turbo",
Name: "Whisper Large v3 Turbo",
Description: "Faster Whisper v3 with slightly lower accuracy",
Type: Transcription,
Streaming: false,
Local: false,
AdapterType: "openai",
SupportedLanguages: allLangs,
Endpoint: &EndpointConfig{BaseURL: "https://api.groq.com/openai", Path: "/v1/audio/transcriptions"},
},
{
ID: "distil-whisper-large-v3-en",
Name: "Distil Whisper Large v3 EN",
Description: "English-only, fastest option",
Type: Transcription,
Streaming: false,
Local: false,
AdapterType: "openai",
SupportedLanguages: []string{"en"},
Endpoint: &EndpointConfig{BaseURL: "https://api.groq.com/openai", Path: "/v1/audio/transcriptions"},
},
// LLM models
{
ID: "llama-3.3-70b-versatile",
Name: "Llama 3.3 70B Versatile",
Description: "Most capable Llama model",
Type: LLM,
Streaming: false,
Local: false,
AdapterType: "openai",
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,
Streaming: false,
Local: false,
AdapterType: "openai",
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,
Streaming: false,
Local: false,
AdapterType: "openai",
SupportedLanguages: allLangs,
Endpoint: &EndpointConfig{BaseURL: "https://api.groq.com/openai", Path: "/v1/chat/completions"},
},
}
} }
func (p *GroqProvider) DefaultTranscriptionModel() string { func (p *GroqProvider) DefaultModel(t ModelType) string {
return "whisper-large-v3-turbo" switch t {
} case Transcription:
return "whisper-large-v3-turbo"
func (p *GroqProvider) DefaultLLMModel() string { case LLM:
return "llama-3.3-70b-versatile" return "llama-3.3-70b-versatile"
} }
return ""
func (p *GroqProvider) TranscriptionModels() []string {
return []string{"whisper-large-v3", "whisper-large-v3-turbo"}
}
func (p *GroqProvider) LLMModels() []string {
return []string{"llama-3.3-70b-versatile", "llama-3.1-8b-instant", "mixtral-8x7b-32768"}
} }
+35 -16
View File
@@ -1,5 +1,7 @@
package provider package provider
import "github.com/leonardotrapani/hyprvoice/internal/language"
// MistralProvider implements Provider for Mistral services (transcription only) // MistralProvider implements Provider for Mistral services (transcription only)
type MistralProvider struct{} type MistralProvider struct{}
@@ -16,26 +18,43 @@ func (p *MistralProvider) ValidateAPIKey(key string) bool {
return len(key) > 0 return len(key) > 0
} }
func (p *MistralProvider) SupportsTranscription() bool { func (p *MistralProvider) IsLocal() bool {
return true
}
func (p *MistralProvider) SupportsLLM() bool {
return false return false
} }
func (p *MistralProvider) DefaultTranscriptionModel() string { func (p *MistralProvider) Models() []Model {
return "voxtral-mini-latest" allLangs := language.AllLanguageCodes()
return []Model{
{
ID: "voxtral-mini-latest",
Name: "Voxtral Mini Latest",
Description: "Latest Voxtral model, best for most uses",
Type: Transcription,
Streaming: false,
Local: false,
AdapterType: "openai",
SupportedLanguages: allLangs,
Endpoint: &EndpointConfig{BaseURL: "https://api.mistral.ai", Path: "/v1/audio/transcriptions"},
},
{
ID: "voxtral-mini-2507",
Name: "Voxtral Mini 2507",
Description: "Stable Voxtral version from July 2025",
Type: Transcription,
Streaming: false,
Local: false,
AdapterType: "openai",
SupportedLanguages: allLangs,
Endpoint: &EndpointConfig{BaseURL: "https://api.mistral.ai", Path: "/v1/audio/transcriptions"},
},
}
} }
func (p *MistralProvider) DefaultLLMModel() string { func (p *MistralProvider) DefaultModel(t ModelType) string {
switch t {
case Transcription:
return "voxtral-mini-latest"
}
return "" return ""
} }
func (p *MistralProvider) TranscriptionModels() []string {
return []string{"voxtral-mini-latest", "voxtral-mini-2507"}
}
func (p *MistralProvider) LLMModels() []string {
return nil
}
+77 -19
View File
@@ -1,6 +1,10 @@
package provider package provider
import "strings" import (
"strings"
"github.com/leonardotrapani/hyprvoice/internal/language"
)
// OpenAIProvider implements Provider for OpenAI services // OpenAIProvider implements Provider for OpenAI services
type OpenAIProvider struct{} type OpenAIProvider struct{}
@@ -17,26 +21,80 @@ func (p *OpenAIProvider) ValidateAPIKey(key string) bool {
return strings.HasPrefix(key, "sk-") return strings.HasPrefix(key, "sk-")
} }
func (p *OpenAIProvider) SupportsTranscription() bool { func (p *OpenAIProvider) IsLocal() bool {
return true return false
} }
func (p *OpenAIProvider) SupportsLLM() bool { func (p *OpenAIProvider) Models() []Model {
return true allLangs := language.AllLanguageCodes()
return []Model{
// transcription models
{
ID: "whisper-1",
Name: "Whisper 1",
Description: "OpenAI's production speech-to-text model",
Type: Transcription,
Streaming: false,
Local: false,
AdapterType: "openai",
SupportedLanguages: allLangs,
Endpoint: &EndpointConfig{BaseURL: "https://api.openai.com", Path: "/v1/audio/transcriptions"},
},
// LLM models
{
ID: "gpt-4o-mini",
Name: "GPT-4o Mini",
Description: "Fast and affordable GPT-4 variant",
Type: LLM,
Streaming: false,
Local: false,
AdapterType: "openai",
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,
Streaming: false,
Local: false,
AdapterType: "openai",
SupportedLanguages: allLangs,
Endpoint: &EndpointConfig{BaseURL: "https://api.openai.com", Path: "/v1/chat/completions"},
},
{
ID: "gpt-4-turbo",
Name: "GPT-4 Turbo",
Description: "Faster GPT-4 with large context window",
Type: LLM,
Streaming: false,
Local: false,
AdapterType: "openai",
SupportedLanguages: allLangs,
Endpoint: &EndpointConfig{BaseURL: "https://api.openai.com", Path: "/v1/chat/completions"},
},
{
ID: "gpt-3.5-turbo",
Name: "GPT-3.5 Turbo",
Description: "Fast and cost-effective",
Type: LLM,
Streaming: false,
Local: false,
AdapterType: "openai",
SupportedLanguages: allLangs,
Endpoint: &EndpointConfig{BaseURL: "https://api.openai.com", Path: "/v1/chat/completions"},
},
}
} }
func (p *OpenAIProvider) DefaultTranscriptionModel() string { func (p *OpenAIProvider) DefaultModel(t ModelType) string {
return "whisper-1" switch t {
} case Transcription:
return "whisper-1"
func (p *OpenAIProvider) DefaultLLMModel() string { case LLM:
return "gpt-4o-mini" return "gpt-4o-mini"
} }
return ""
func (p *OpenAIProvider) TranscriptionModels() []string {
return []string{"whisper-1"}
}
func (p *OpenAIProvider) LLMModels() []string {
return []string{"gpt-4o-mini", "gpt-4o", "gpt-4-turbo", "gpt-3.5-turbo"}
} }
+106 -8
View File
@@ -1,16 +1,19 @@
package provider package provider
import (
"errors"
"fmt"
"strings"
)
// Provider defines the interface for a transcription/LLM service provider // Provider defines the interface for a transcription/LLM service provider
type Provider interface { type Provider interface {
Name() string Name() string
RequiresAPIKey() bool RequiresAPIKey() bool
ValidateAPIKey(key string) bool ValidateAPIKey(key string) bool
SupportsTranscription() bool IsLocal() bool
SupportsLLM() bool Models() []Model
DefaultTranscriptionModel() string DefaultModel(t ModelType) string
DefaultLLMModel() string
TranscriptionModels() []string
LLMModels() []string
} }
// ProviderConfig holds configuration for a single provider // ProviderConfig holds configuration for a single provider
@@ -50,7 +53,7 @@ func ListProviders() []string {
func ListProvidersWithTranscription() []string { func ListProvidersWithTranscription() []string {
var names []string var names []string
for name, p := range registry { for name, p := range registry {
if p.SupportsTranscription() { if hasModelsOfType(p, Transcription) {
names = append(names, name) names = append(names, name)
} }
} }
@@ -61,9 +64,104 @@ func ListProvidersWithTranscription() []string {
func ListProvidersWithLLM() []string { func ListProvidersWithLLM() []string {
var names []string var names []string
for name, p := range registry { for name, p := range registry {
if p.SupportsLLM() { if hasModelsOfType(p, LLM) {
names = append(names, name) names = append(names, name)
} }
} }
return names return names
} }
// hasModelsOfType returns true if provider has any models of the given type
func hasModelsOfType(p Provider, t ModelType) bool {
for _, m := range p.Models() {
if m.Type == t {
return true
}
}
return false
}
// GetModel returns a model from a specific provider, or error if not found
func GetModel(providerName, modelID string) (*Model, error) {
p := GetProvider(providerName)
if p == nil {
return nil, fmt.Errorf("unknown provider: %s", providerName)
}
for _, m := range p.Models() {
if m.ID == modelID {
return &m, nil
}
}
return nil, fmt.Errorf("model %s not found in provider %s", modelID, providerName)
}
// ModelsOfType returns all models of the given type from a provider
func ModelsOfType(p Provider, t ModelType) []Model {
var result []Model
for _, m := range p.Models() {
if m.Type == t {
result = append(result, m)
}
}
return result
}
// FindModelByID searches all providers for a model with the given ID
func FindModelByID(modelID string) (*Model, Provider, error) {
for _, p := range registry {
for _, m := range p.Models() {
if m.ID == modelID {
return &m, p, nil
}
}
}
return nil, nil, fmt.Errorf("model %s not found in any provider", modelID)
}
// ModelsForLanguage returns models from a provider that support the given language
func ModelsForLanguage(p Provider, t ModelType, langCode string) []Model {
var result []Model
for _, m := range p.Models() {
if m.Type == t && m.SupportsLanguage(langCode) {
result = append(result, m)
}
}
return result
}
// ValidateModelLanguage checks if a model supports the given language.
// Returns error with list of supported languages if not supported.
// Returns nil if langCode is "" (auto) - auto is always supported.
func ValidateModelLanguage(providerName, modelID, langCode string) error {
if langCode == "" {
return nil // auto always supported
}
model, err := GetModel(providerName, modelID)
if err != nil {
return err
}
if model.SupportsLanguage(langCode) {
return nil
}
// truncate supported languages list for error message
supported := model.SupportedLanguages
if len(supported) > 10 {
supported = append(supported[:10], "...")
}
return fmt.Errorf(
"model %s does not support language '%s'. Supported: %s",
modelID,
langCode,
strings.Join(supported, ", "),
)
}
var (
ErrProviderNotFound = errors.New("provider not found")
ErrModelNotFound = errors.New("model not found")
)
+140 -17
View File
@@ -10,13 +10,14 @@ func TestProviderInterface(t *testing.T) {
name string name string
hasTranscription bool hasTranscription bool
hasLLM bool hasLLM bool
isLocal bool
defaultTransModel string defaultTransModel string
defaultLLMModel string defaultLLMModel string
}{ }{
{"openai", true, true, "whisper-1", "gpt-4o-mini"}, {"openai", true, true, false, "whisper-1", "gpt-4o-mini"},
{"groq", true, true, "whisper-large-v3-turbo", "llama-3.3-70b-versatile"}, {"groq", true, true, false, "whisper-large-v3-turbo", "llama-3.3-70b-versatile"},
{"mistral", true, false, "voxtral-mini-latest", ""}, {"mistral", true, false, false, "voxtral-mini-latest", ""},
{"elevenlabs", true, false, "scribe_v1", ""}, {"elevenlabs", true, false, false, "scribe_v1", ""},
} }
for _, tc := range providers { for _, tc := range providers {
@@ -30,32 +31,38 @@ func TestProviderInterface(t *testing.T) {
t.Errorf("Name() = %q, want %q", p.Name(), tc.name) t.Errorf("Name() = %q, want %q", p.Name(), tc.name)
} }
if p.SupportsTranscription() != tc.hasTranscription { hasTranscription := len(ModelsOfType(p, Transcription)) > 0
t.Errorf("SupportsTranscription() = %v, want %v", p.SupportsTranscription(), tc.hasTranscription) if hasTranscription != tc.hasTranscription {
t.Errorf("hasTranscription = %v, want %v", hasTranscription, tc.hasTranscription)
} }
if p.SupportsLLM() != tc.hasLLM { hasLLM := len(ModelsOfType(p, LLM)) > 0
t.Errorf("SupportsLLM() = %v, want %v", p.SupportsLLM(), tc.hasLLM) if hasLLM != tc.hasLLM {
t.Errorf("hasLLM = %v, want %v", hasLLM, tc.hasLLM)
} }
if p.DefaultTranscriptionModel() != tc.defaultTransModel { if p.IsLocal() != tc.isLocal {
t.Errorf("DefaultTranscriptionModel() = %q, want %q", p.DefaultTranscriptionModel(), tc.defaultTransModel) t.Errorf("IsLocal() = %v, want %v", p.IsLocal(), tc.isLocal)
} }
if p.DefaultLLMModel() != tc.defaultLLMModel { if p.DefaultModel(Transcription) != tc.defaultTransModel {
t.Errorf("DefaultLLMModel() = %q, want %q", p.DefaultLLMModel(), tc.defaultLLMModel) t.Errorf("DefaultModel(Transcription) = %q, want %q", p.DefaultModel(Transcription), tc.defaultTransModel)
}
if p.DefaultModel(LLM) != tc.defaultLLMModel {
t.Errorf("DefaultModel(LLM) = %q, want %q", p.DefaultModel(LLM), tc.defaultLLMModel)
} }
if !p.RequiresAPIKey() { if !p.RequiresAPIKey() {
t.Error("RequiresAPIKey() should be true for all providers") t.Error("RequiresAPIKey() should be true for all cloud providers")
} }
if tc.hasTranscription && len(p.TranscriptionModels()) == 0 { if tc.hasTranscription && len(ModelsOfType(p, Transcription)) == 0 {
t.Error("TranscriptionModels() should not be empty for transcription provider") t.Error("should have transcription models")
} }
if tc.hasLLM && len(p.LLMModels()) == 0 { if tc.hasLLM && len(ModelsOfType(p, LLM)) == 0 {
t.Error("LLMModels() should not be empty for LLM provider") t.Error("should have LLM models")
} }
}) })
} }
@@ -137,3 +144,119 @@ func TestValidateAPIKey(t *testing.T) {
}) })
} }
} }
func TestGetModel(t *testing.T) {
// valid provider and model
m, err := GetModel("openai", "whisper-1")
if err != nil {
t.Errorf("GetModel('openai', 'whisper-1') unexpected error: %v", err)
}
if m == nil {
t.Fatal("GetModel returned nil model")
}
if m.ID != "whisper-1" {
t.Errorf("GetModel returned model with ID %q, want 'whisper-1'", m.ID)
}
// unknown provider
_, err = GetModel("nonexistent", "whisper-1")
if err == nil {
t.Error("GetModel('nonexistent', ...) should return error")
}
// unknown model
_, err = GetModel("openai", "nonexistent")
if err == nil {
t.Error("GetModel('openai', 'nonexistent') should return error")
}
}
func TestModelsOfType(t *testing.T) {
p := GetProvider("openai")
trans := ModelsOfType(p, Transcription)
llm := ModelsOfType(p, LLM)
if len(trans) != 1 {
t.Errorf("ModelsOfType(Transcription) = %d, want 1", len(trans))
}
if len(llm) != 4 {
t.Errorf("ModelsOfType(LLM) = %d, want 4", len(llm))
}
}
func TestFindModelByID(t *testing.T) {
// find model that exists
m, p, err := FindModelByID("whisper-1")
if err != nil {
t.Errorf("FindModelByID('whisper-1') unexpected error: %v", err)
}
if m == nil || p == nil {
t.Fatal("FindModelByID returned nil")
}
if m.ID != "whisper-1" {
t.Errorf("FindModelByID returned model %q, want 'whisper-1'", m.ID)
}
if p.Name() != "openai" {
t.Errorf("FindModelByID returned provider %q, want 'openai'", p.Name())
}
// model not found
_, _, err = FindModelByID("nonexistent")
if err == nil {
t.Error("FindModelByID('nonexistent') should return error")
}
}
func TestModelsForLanguage(t *testing.T) {
groq := GetProvider("groq")
// en should include all models (distil supports en)
enModels := ModelsForLanguage(groq, Transcription, "en")
if len(enModels) != 3 {
t.Errorf("ModelsForLanguage('en') = %d, want 3", len(enModels))
}
// es should exclude distil-whisper-large-v3-en
esModels := ModelsForLanguage(groq, Transcription, "es")
if len(esModels) != 2 {
t.Errorf("ModelsForLanguage('es') = %d, want 2 (distil excluded)", len(esModels))
}
// auto ("") should include all models
autoModels := ModelsForLanguage(groq, Transcription, "")
if len(autoModels) != 3 {
t.Errorf("ModelsForLanguage('') = %d, want 3 (auto returns all)", len(autoModels))
}
}
func TestValidateModelLanguage(t *testing.T) {
// valid language for multilingual model
err := ValidateModelLanguage("groq", "whisper-large-v3", "es")
if err != nil {
t.Errorf("ValidateModelLanguage(whisper-large-v3, 'es') unexpected error: %v", err)
}
// invalid language for English-only model
err = ValidateModelLanguage("groq", "distil-whisper-large-v3-en", "es")
if err == nil {
t.Error("ValidateModelLanguage(distil-whisper, 'es') should return error")
}
// auto always passes
err = ValidateModelLanguage("groq", "distil-whisper-large-v3-en", "")
if err != nil {
t.Errorf("ValidateModelLanguage(distil-whisper, '') should pass (auto): %v", err)
}
// unknown provider
err = ValidateModelLanguage("nonexistent", "whisper-1", "en")
if err == nil {
t.Error("ValidateModelLanguage with unknown provider should return error")
}
// unknown model
err = ValidateModelLanguage("openai", "nonexistent", "en")
if err == nil {
t.Error("ValidateModelLanguage with unknown model should return error")
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ func editLLM(cfg *config.Config, configuredProviders []string) ([]string, error)
var llmProviders []string var llmProviders []string
for _, name := range configuredProviders { for _, name := range configuredProviders {
p := provider.GetProvider(name) p := provider.GetProvider(name)
if p != nil && p.SupportsLLM() { if p != nil && len(provider.ModelsOfType(p, provider.LLM)) > 0 {
llmProviders = append(llmProviders, name) llmProviders = append(llmProviders, name)
} }
} }
+1 -1
View File
@@ -13,7 +13,7 @@ func editTranscription(cfg *config.Config, configuredProviders []string) ([]stri
var transcriptionOptions []huh.Option[string] var transcriptionOptions []huh.Option[string]
for _, name := range configuredProviders { for _, name := range configuredProviders {
p := provider.GetProvider(name) p := provider.GetProvider(name)
if p != nil && p.SupportsTranscription() { if p != nil && len(provider.ModelsOfType(p, provider.Transcription)) > 0 {
switch name { switch name {
case "openai": case "openai":
transcriptionOptions = append(transcriptionOptions, transcriptionOptions = append(transcriptionOptions,
+14
View File
@@ -26,3 +26,17 @@ Started: Sun Feb 1 12:22:47 AM CET 2026
- Helper methods: NeedsDownload(), IsStreaming(), SupportsLanguage(code), SupportsAllLanguages() - Helper methods: NeedsDownload(), IsStreaming(), SupportsLanguage(code), SupportsAllLanguages()
- SupportsLanguage("") always returns true (auto always allowed) - SupportsLanguage("") always returns true (auto always allowed)
- All tests passing, typecheck passes - All tests passing, typecheck passes
### Task 4: Refactor Provider interface to return Models
- Updated `internal/provider/provider.go` Provider interface
- Replaced old methods with: Models() []Model, DefaultModel(t ModelType) string, IsLocal() bool
- Added package-level helpers:
- GetModel(providerName, modelID string) (*Model, error)
- ModelsOfType(p Provider, t ModelType) []Model
- FindModelByID(modelID string) (*Model, Provider, error)
- ModelsForLanguage(p Provider, t ModelType, langCode string) []Model
- ValidateModelLanguage(providerName, modelID, langCode string) error
- Updated all providers (openai, groq, mistral, elevenlabs) with full Model metadata
- Updated TUI files to use ModelsOfType instead of old SupportsTranscription/SupportsLLM
- Added comprehensive tests for all new helper functions
- All tests passing, typecheck passes
+1 -1
View File
@@ -110,7 +110,7 @@
"ValidateModelLanguage returns nil for '' (auto) on any model", "ValidateModelLanguage returns nil for '' (auto) on any model",
"Typecheck passes" "Typecheck passes"
], ],
"passes": false "passes": true
}, },
{ {
"title": "Define BatchAdapter and StreamingAdapter interfaces", "title": "Define BatchAdapter and StreamingAdapter interfaces",