add whisper-cpp to TUI with dependency check and model download

This commit is contained in:
leonardotrapani
2026-02-01 01:29:12 +01:00
parent 575b55b524
commit 973643a698
4 changed files with 128 additions and 7 deletions
+9 -5
View File
@@ -16,15 +16,19 @@ type ConfigureResult struct {
Cancelled bool Cancelled bool
} }
// AllProviders is the list of all supported providers // AllProviders is the list of all supported cloud providers (require API keys)
var AllProviders = []string{"openai", "groq", "mistral", "elevenlabs"} var AllProviders = []string{"openai", "groq", "mistral", "elevenlabs"}
// LocalProviders is the list of local providers (no API key required)
var LocalProviders = []string{"whisper-cpp"}
// providerDisplayNames maps provider IDs to human-readable names // providerDisplayNames maps provider IDs to human-readable names
var providerDisplayNames = map[string]string{ var providerDisplayNames = map[string]string{
"openai": "OpenAI", "openai": "OpenAI",
"groq": "Groq", "groq": "Groq",
"mistral": "Mistral", "mistral": "Mistral",
"elevenlabs": "ElevenLabs", "elevenlabs": "ElevenLabs",
"whisper-cpp": "Whisper.cpp (local)",
} }
// ConfigSection represents a configuration section // ConfigSection represents a configuration section
+107 -1
View File
@@ -1,17 +1,32 @@
package tui package tui
import ( import (
"context"
"fmt" "fmt"
"github.com/charmbracelet/huh" "github.com/charmbracelet/huh"
"github.com/leonardotrapani/hyprvoice/internal/config" "github.com/leonardotrapani/hyprvoice/internal/config"
"github.com/leonardotrapani/hyprvoice/internal/deps"
"github.com/leonardotrapani/hyprvoice/internal/language" "github.com/leonardotrapani/hyprvoice/internal/language"
"github.com/leonardotrapani/hyprvoice/internal/models/whisper"
"github.com/leonardotrapani/hyprvoice/internal/provider" "github.com/leonardotrapani/hyprvoice/internal/provider"
) )
// editTranscription handles the transcription section edit with smart provider detection // editTranscription handles the transcription section edit with smart provider detection
func editTranscription(cfg *config.Config, configuredProviders []string) ([]string, error) { func editTranscription(cfg *config.Config, configuredProviders []string) ([]string, error) {
var transcriptionOptions []huh.Option[string] var transcriptionOptions []huh.Option[string]
// add local provider first (whisper-cpp)
whisperStatus := deps.CheckWhisperCli()
if whisperStatus.Installed {
transcriptionOptions = append(transcriptionOptions,
huh.NewOption("Whisper.cpp (local, no API key)", "whisper-cpp"))
} else {
transcriptionOptions = append(transcriptionOptions,
huh.NewOption("Whisper.cpp (whisper-cli not found)", "whisper-cpp-disabled"))
}
// add configured cloud providers
for _, name := range configuredProviders { for _, name := range configuredProviders {
p := provider.GetProvider(name) p := provider.GetProvider(name)
if p != nil && len(provider.ModelsOfType(p, provider.Transcription)) > 0 { if p != nil && len(provider.ModelsOfType(p, provider.Transcription)) > 0 {
@@ -66,7 +81,37 @@ func editTranscription(cfg *config.Config, configuredProviders []string) ([]stri
return configuredProviders, err return configuredProviders, err
} }
configuredProviders = ensureProviderConfigured(cfg, selectedProvider, configuredProviders) // handle disabled whisper-cpp selection
if selectedProvider == "whisper-cpp-disabled" {
fmt.Println()
fmt.Println(StyleWarning.Render("whisper-cli not found in PATH"))
fmt.Println(StyleMuted.Render("Install whisper.cpp to use local transcription:"))
fmt.Println(StyleMuted.Render(" https://github.com/ggerganov/whisper.cpp"))
fmt.Println()
var proceed bool
form := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title("Continue?").
Affirmative("Choose another provider").
Negative("Cancel").
Value(&proceed),
),
).WithTheme(getTheme())
if err := form.Run(); err != nil {
return configuredProviders, err
}
if proceed {
return editTranscription(cfg, configuredProviders)
}
return configuredProviders, nil
}
// local providers don't need API key configuration
if selectedProvider != "whisper-cpp" {
configuredProviders = ensureProviderConfigured(cfg, selectedProvider, configuredProviders)
}
cfg.Transcription.Provider = selectedProvider cfg.Transcription.Provider = selectedProvider
modelOptions := getTranscriptionModelOptions(selectedProvider, cfg.Transcription.Language) modelOptions := getTranscriptionModelOptions(selectedProvider, cfg.Transcription.Language)
@@ -106,6 +151,57 @@ func editTranscription(cfg *config.Config, configuredProviders []string) ([]stri
return configuredProviders, err return configuredProviders, err
} }
// for whisper-cpp, check if model needs download
if selectedProvider == "whisper-cpp" && !whisper.IsInstalled(selectedModel) {
modelInfo := whisper.GetModel(selectedModel)
if modelInfo == nil {
return configuredProviders, fmt.Errorf("unknown model: %s", selectedModel)
}
var confirm bool
confirmForm := huh.NewForm(
huh.NewGroup(
huh.NewConfirm().
Title(fmt.Sprintf("Download %s (%s)?", modelInfo.Name, modelInfo.Size)).
Description("Model is not installed. Download now?").
Affirmative("Download").
Negative("Cancel").
Value(&confirm),
),
).WithTheme(getTheme())
if err := confirmForm.Run(); err != nil {
return configuredProviders, err
}
if !confirm {
return configuredProviders, nil
}
// download with progress
fmt.Println()
fmt.Printf("Downloading %s...\n", modelInfo.Name)
lastPct := 0
err := whisper.Download(context.Background(), selectedModel, func(downloaded, total int64) {
if total > 0 {
pct := int(downloaded * 100 / total)
if pct >= lastPct+10 {
fmt.Printf(" %d%%\n", pct)
lastPct = pct
}
}
})
if err != nil {
fmt.Println(StyleError.Render(fmt.Sprintf("Download failed: %v", err)))
return configuredProviders, err
}
fmt.Println(StyleSuccess.Render(fmt.Sprintf("Downloaded %s", modelInfo.Name)))
fmt.Println()
}
cfg.Transcription.Model = selectedModel cfg.Transcription.Model = selectedModel
cfg.Transcription.Language = language cfg.Transcription.Language = language
@@ -162,6 +258,16 @@ func getTranscriptionModelOptions(configProvider string, currentLang string) []h
} }
label := buildModelLabel(m, currentLang) label := buildModelLabel(m, currentLang)
// for local models, show installed status
if m.Local && registryName == "whisper-cpp" {
if whisper.IsInstalled(m.ID) {
label = "[x] " + label
} else {
label = "[ ] " + label
}
}
options = append(options, huh.NewOption(label, m.ID)) options = append(options, huh.NewOption(label, m.ID))
} }
+11
View File
@@ -275,3 +275,14 @@ Started: Sun Feb 1 12:22:47 AM CET 2026
- Added `getLangName()` helper to get human-readable language name from code - Added `getLangName()` helper to get human-readable language name from code
- Added language import to configure_transcription.go - Added language import to configure_transcription.go
- All tests passing, typecheck passes - All tests passing, typecheck passes
### Task 28: Add local provider options to TUI with dependency check
- Added `LocalProviders` list and "whisper-cpp" to providerDisplayNames in configure.go
- Updated editTranscription() to show whisper-cpp option first
- Added deps.CheckWhisperCli() check to show warning if whisper-cli not installed
- Shows disabled option "(whisper-cli not found)" with install instructions when binary missing
- Local providers skip ensureProviderConfigured() (no API key needed)
- Updated getTranscriptionModelOptions() to show [x]/[ ] prefix for installed status
- Added download confirmation dialog after selecting uninstalled model
- Download shows progress percentage (10%, 20%, ...)
- All tests passing, typecheck passes
+1 -1
View File
@@ -665,7 +665,7 @@
"Download completes with progress", "Download completes with progress",
"Typecheck passes" "Typecheck passes"
], ],
"passes": false "passes": true
}, },
{ {
"title": "Add language picker to TUI using language package", "title": "Add language picker to TUI using language package",