add whisper-cpp provider with 9 local models

This commit is contained in:
leonardotrapani
2026-02-01 01:12:18 +01:00
parent 838edef59b
commit f6bf81cf95
5 changed files with 242 additions and 1 deletions
+1
View File
@@ -28,6 +28,7 @@ func init() {
Register(&GroqProvider{})
Register(&MistralProvider{})
Register(&ElevenLabsProvider{})
Register(&WhisperCppProvider{})
}
// Register adds a provider to the registry
+76
View File
@@ -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 ""
}
+151
View File
@@ -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)
}
}
}
+13
View File
@@ -205,3 +205,16 @@ Started: Sun Feb 1 12:22:47 AM CET 2026
- Created comprehensive test file adapter_whisper_cpp_test.go
- Tests: interface implementation, empty audio, missing model, language, threads, context cancellation
- All tests passing, typecheck passes
### Task 21: Create whisper-cpp Provider
- Created `internal/provider/whisper_cpp.go` implementing Provider interface
- Name() returns 'whisper-cpp', RequiresAPIKey() returns false, IsLocal() returns true
- Models() returns 9 whisper models from whisper.ListModels()
- English-only models (*.en) have SupportedLanguages=['en']
- Multilingual models have SupportedLanguages with all 57 language codes
- Each model has: Type=Transcription, AdapterType='whisper-cpp', Local=true, LocalInfo with Filename/Size/DownloadURL
- No Endpoint (local CLI, not HTTP)
- DefaultModel(Transcription) returns 'base.en'
- Registered in provider.init()
- Comprehensive test file created: whisper_cpp_test.go
- All tests passing, typecheck passes
+1 -1
View File
@@ -511,7 +511,7 @@
"RequiresAPIKey() returns false",
"Typecheck passes"
],
"passes": false
"passes": true
},
{
"title": "Wire whisper-cpp into transcriber factory",