add whisper-cpp provider with 9 local models
This commit is contained in:
@@ -28,6 +28,7 @@ func init() {
|
||||
Register(&GroqProvider{})
|
||||
Register(&MistralProvider{})
|
||||
Register(&ElevenLabsProvider{})
|
||||
Register(&WhisperCppProvider{})
|
||||
}
|
||||
|
||||
// Register adds a provider to the registry
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/leonardotrapani/hyprvoice/internal/language"
|
||||
"github.com/leonardotrapani/hyprvoice/internal/models/whisper"
|
||||
)
|
||||
|
||||
// WhisperCppProvider implements Provider for local whisper.cpp transcription
|
||||
type WhisperCppProvider struct{}
|
||||
|
||||
func (p *WhisperCppProvider) Name() string {
|
||||
return "whisper-cpp"
|
||||
}
|
||||
|
||||
func (p *WhisperCppProvider) RequiresAPIKey() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *WhisperCppProvider) ValidateAPIKey(key string) bool {
|
||||
return true // no API key needed
|
||||
}
|
||||
|
||||
func (p *WhisperCppProvider) IsLocal() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *WhisperCppProvider) Models() []Model {
|
||||
allLangs := language.AllLanguageCodes()
|
||||
englishOnly := []string{"en"}
|
||||
|
||||
whisperModels := whisper.ListModels()
|
||||
result := make([]Model, 0, len(whisperModels))
|
||||
|
||||
for _, wm := range whisperModels {
|
||||
var langs []string
|
||||
if wm.Multilingual {
|
||||
langs = allLangs
|
||||
} else {
|
||||
langs = englishOnly
|
||||
}
|
||||
|
||||
result = append(result, Model{
|
||||
ID: wm.ID,
|
||||
Name: wm.Name,
|
||||
Description: modelDescription(wm),
|
||||
Type: Transcription,
|
||||
Streaming: false,
|
||||
Local: true,
|
||||
AdapterType: "whisper-cpp",
|
||||
SupportedLanguages: langs,
|
||||
Endpoint: nil, // local CLI, no HTTP endpoint
|
||||
LocalInfo: &LocalModelInfo{
|
||||
Filename: wm.Filename,
|
||||
Size: wm.Size,
|
||||
DownloadURL: whisper.GetDownloadURL(wm.ID),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func modelDescription(m whisper.ModelInfo) string {
|
||||
if m.Multilingual {
|
||||
return "Multilingual local transcription"
|
||||
}
|
||||
return "English-only local transcription (faster)"
|
||||
}
|
||||
|
||||
func (p *WhisperCppProvider) DefaultModel(t ModelType) string {
|
||||
switch t {
|
||||
case Transcription:
|
||||
return "base.en"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/leonardotrapani/hyprvoice/internal/language"
|
||||
)
|
||||
|
||||
func TestWhisperCppProvider_GetProvider(t *testing.T) {
|
||||
p := GetProvider("whisper-cpp")
|
||||
if p == nil {
|
||||
t.Fatal("GetProvider('whisper-cpp') returned nil")
|
||||
}
|
||||
if p.Name() != "whisper-cpp" {
|
||||
t.Errorf("expected name 'whisper-cpp', got '%s'", p.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestWhisperCppProvider_Models(t *testing.T) {
|
||||
p := &WhisperCppProvider{}
|
||||
models := p.Models()
|
||||
|
||||
// verify we have 9 models
|
||||
if len(models) != 9 {
|
||||
t.Errorf("expected 9 models, got %d", len(models))
|
||||
}
|
||||
|
||||
// verify all models have required fields
|
||||
for _, m := range models {
|
||||
if !m.Local {
|
||||
t.Errorf("model %s: expected Local=true", m.ID)
|
||||
}
|
||||
if m.LocalInfo == nil {
|
||||
t.Errorf("model %s: expected LocalInfo to be set", m.ID)
|
||||
}
|
||||
if m.AdapterType != "whisper-cpp" {
|
||||
t.Errorf("model %s: expected AdapterType='whisper-cpp', got '%s'", m.ID, m.AdapterType)
|
||||
}
|
||||
if m.Type != Transcription {
|
||||
t.Errorf("model %s: expected Type=Transcription", m.ID)
|
||||
}
|
||||
if m.Endpoint != nil {
|
||||
t.Errorf("model %s: expected Endpoint=nil for local model", m.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWhisperCppProvider_EnglishOnlyModels(t *testing.T) {
|
||||
p := &WhisperCppProvider{}
|
||||
models := p.Models()
|
||||
|
||||
englishOnlyIDs := map[string]bool{
|
||||
"tiny.en": true,
|
||||
"base.en": true,
|
||||
"small.en": true,
|
||||
"medium.en": true,
|
||||
}
|
||||
|
||||
for _, m := range models {
|
||||
isEnglishOnly := englishOnlyIDs[m.ID]
|
||||
if isEnglishOnly {
|
||||
// english-only models should only support 'en'
|
||||
if len(m.SupportedLanguages) != 1 || m.SupportedLanguages[0] != "en" {
|
||||
t.Errorf("model %s: expected SupportedLanguages=['en'], got %v", m.ID, m.SupportedLanguages)
|
||||
}
|
||||
if m.SupportsLanguage("es") {
|
||||
t.Errorf("model %s: SupportsLanguage('es') should be false", m.ID)
|
||||
}
|
||||
if !m.SupportsLanguage("en") {
|
||||
t.Errorf("model %s: SupportsLanguage('en') should be true", m.ID)
|
||||
}
|
||||
if !m.SupportsLanguage("") {
|
||||
t.Errorf("model %s: SupportsLanguage('') should be true (auto always supported)", m.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWhisperCppProvider_MultilingualModels(t *testing.T) {
|
||||
p := &WhisperCppProvider{}
|
||||
models := p.Models()
|
||||
|
||||
multilingualIDs := map[string]bool{
|
||||
"tiny": true,
|
||||
"base": true,
|
||||
"small": true,
|
||||
"medium": true,
|
||||
"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 !m.SupportsLanguage("es") {
|
||||
t.Errorf("model %s: SupportsLanguage('es') should be true", m.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestWhisperCppProvider_RequiresAPIKey(t *testing.T) {
|
||||
p := &WhisperCppProvider{}
|
||||
if p.RequiresAPIKey() {
|
||||
t.Error("RequiresAPIKey() should return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWhisperCppProvider_IsLocal(t *testing.T) {
|
||||
p := &WhisperCppProvider{}
|
||||
if !p.IsLocal() {
|
||||
t.Error("IsLocal() should return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWhisperCppProvider_DefaultModel(t *testing.T) {
|
||||
p := &WhisperCppProvider{}
|
||||
if p.DefaultModel(Transcription) != "base.en" {
|
||||
t.Errorf("expected DefaultModel(Transcription)='base.en', got '%s'", p.DefaultModel(Transcription))
|
||||
}
|
||||
if p.DefaultModel(LLM) != "" {
|
||||
t.Errorf("expected DefaultModel(LLM)='', got '%s'", p.DefaultModel(LLM))
|
||||
}
|
||||
}
|
||||
|
||||
func TestWhisperCppProvider_LocalInfo(t *testing.T) {
|
||||
p := &WhisperCppProvider{}
|
||||
models := p.Models()
|
||||
|
||||
for _, m := range models {
|
||||
if m.LocalInfo.Filename == "" {
|
||||
t.Errorf("model %s: LocalInfo.Filename should not be empty", m.ID)
|
||||
}
|
||||
if m.LocalInfo.Size == "" {
|
||||
t.Errorf("model %s: LocalInfo.Size should not be empty", m.ID)
|
||||
}
|
||||
if m.LocalInfo.DownloadURL == "" {
|
||||
t.Errorf("model %s: LocalInfo.DownloadURL should not be empty", m.ID)
|
||||
}
|
||||
if !m.NeedsDownload() {
|
||||
t.Errorf("model %s: NeedsDownload() should be true for local model", m.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user