add whisper-cpp to TUI with dependency check and model download
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user