214 lines
5.4 KiB
Go
214 lines
5.4 KiB
Go
package tui
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/charmbracelet/huh"
|
|
"github.com/leonardotrapani/hyprvoice/internal/config"
|
|
)
|
|
|
|
// runFreshInstall runs the full configuration wizard for fresh installs
|
|
func runFreshInstall(cfg *config.Config) (*ConfigureResult, error) {
|
|
fmt.Println(Logo())
|
|
fmt.Println()
|
|
fmt.Println(StyleMuted.Render("Voice-powered typing for Wayland/Hyprland"))
|
|
fmt.Println()
|
|
|
|
selectedProviders, err := selectProviders()
|
|
if err != nil {
|
|
return &ConfigureResult{Cancelled: true}, nil
|
|
}
|
|
if len(selectedProviders) == 0 {
|
|
return &ConfigureResult{Cancelled: true}, fmt.Errorf("no providers selected")
|
|
}
|
|
|
|
if cfg.Providers == nil {
|
|
cfg.Providers = make(map[string]config.ProviderConfig)
|
|
}
|
|
|
|
for _, providerName := range selectedProviders {
|
|
apiKey, err := inputAPIKey(providerName)
|
|
if err != nil {
|
|
return &ConfigureResult{Cancelled: true}, nil
|
|
}
|
|
cfg.Providers[providerName] = config.ProviderConfig{APIKey: apiKey}
|
|
}
|
|
|
|
transcriptionProvider, transcriptionModel, language, err := configureTranscription(selectedProviders, cfg)
|
|
if err != nil {
|
|
return &ConfigureResult{Cancelled: true}, nil
|
|
}
|
|
cfg.Transcription.Provider = transcriptionProvider
|
|
cfg.Transcription.Model = transcriptionModel
|
|
cfg.Transcription.Language = language
|
|
|
|
llmEnabled, llmProvider, llmModel, postProcessing, customPrompt, err := configureLLM(selectedProviders, cfg)
|
|
if err != nil {
|
|
return &ConfigureResult{Cancelled: true}, nil
|
|
}
|
|
cfg.LLM.Enabled = llmEnabled
|
|
cfg.LLM.Provider = llmProvider
|
|
cfg.LLM.Model = llmModel
|
|
cfg.LLM.PostProcessing = postProcessing
|
|
cfg.LLM.CustomPrompt = customPrompt
|
|
|
|
keywords, err := inputKeywords(cfg.Keywords)
|
|
if err != nil {
|
|
return &ConfigureResult{Cancelled: true}, nil
|
|
}
|
|
cfg.Keywords = keywords
|
|
|
|
backends, err := selectBackends(cfg.Injection.Backends)
|
|
if err != nil {
|
|
return &ConfigureResult{Cancelled: true}, nil
|
|
}
|
|
cfg.Injection.Backends = backends
|
|
|
|
notificationsEnabled, err := configureNotifications(cfg.Notifications.Enabled)
|
|
if err != nil {
|
|
return &ConfigureResult{Cancelled: true}, nil
|
|
}
|
|
cfg.Notifications.Enabled = notificationsEnabled
|
|
|
|
confirmed, err := showSummary(cfg)
|
|
if err != nil || !confirmed {
|
|
return &ConfigureResult{Cancelled: true}, nil
|
|
}
|
|
|
|
return &ConfigureResult{Config: cfg, Cancelled: false}, nil
|
|
}
|
|
|
|
func selectProviders() ([]string, error) {
|
|
options := []huh.Option[string]{
|
|
huh.NewOption("OpenAI - Whisper transcription + GPT for LLM", "openai"),
|
|
huh.NewOption("Groq - Fast Whisper transcription + Llama for LLM", "groq"),
|
|
huh.NewOption("Mistral - Voxtral transcription (European languages)", "mistral"),
|
|
huh.NewOption("ElevenLabs - Scribe transcription (99 languages)", "elevenlabs"),
|
|
}
|
|
|
|
var selected []string
|
|
form := huh.NewForm(
|
|
huh.NewGroup(
|
|
huh.NewMultiSelect[string]().
|
|
Title("Which providers do you want to configure?").
|
|
Description("Select all providers you have API keys for").
|
|
Options(options...).
|
|
Value(&selected),
|
|
),
|
|
).WithTheme(getTheme())
|
|
|
|
if err := form.Run(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
valid := make([]string, 0)
|
|
for _, s := range selected {
|
|
for _, p := range AllProviders {
|
|
if s == p {
|
|
valid = append(valid, s)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return valid, nil
|
|
}
|
|
|
|
func inputKeywords(existingKeywords []string) ([]string, error) {
|
|
var keywordsInput string
|
|
if len(existingKeywords) > 0 {
|
|
keywordsInput = strings.Join(existingKeywords, ", ")
|
|
}
|
|
|
|
form := huh.NewForm(
|
|
huh.NewGroup(
|
|
huh.NewInput().
|
|
Title("Keywords").
|
|
Description("Comma-separated words to help with spelling (names, technical terms, etc.)").
|
|
Placeholder("e.g., Kubernetes, PostgreSQL, John Smith").
|
|
Value(&keywordsInput),
|
|
),
|
|
).WithTheme(getTheme())
|
|
|
|
if err := form.Run(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if keywordsInput == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
parts := strings.Split(keywordsInput, ",")
|
|
keywords := make([]string, 0, len(parts))
|
|
for _, p := range parts {
|
|
p = strings.TrimSpace(p)
|
|
if p != "" {
|
|
keywords = append(keywords, p)
|
|
}
|
|
}
|
|
|
|
return keywords, nil
|
|
}
|
|
|
|
func selectBackends(existingBackends []string) ([]string, error) {
|
|
options := []huh.Option[string]{
|
|
huh.NewOption("ydotool - Best for Chromium/Electron (needs ydotoold)", "ydotool"),
|
|
huh.NewOption("wtype - Native Wayland typing", "wtype"),
|
|
huh.NewOption("clipboard - Copy to clipboard only", "clipboard"),
|
|
}
|
|
|
|
var selected []string
|
|
if len(existingBackends) > 0 {
|
|
selected = existingBackends
|
|
} else {
|
|
selected = []string{"ydotool", "wtype", "clipboard"}
|
|
}
|
|
|
|
form := huh.NewForm(
|
|
huh.NewGroup(
|
|
huh.NewMultiSelect[string]().
|
|
Title("Text Injection Backends").
|
|
Description("Backends are tried in order until one succeeds (fallback chain)").
|
|
Options(options...).
|
|
Value(&selected),
|
|
),
|
|
).WithTheme(getTheme())
|
|
|
|
if err := form.Run(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(selected) == 0 {
|
|
return nil, fmt.Errorf("at least one backend required")
|
|
}
|
|
|
|
return selected, nil
|
|
}
|
|
|
|
func configureNotifications(existingEnabled bool) (bool, error) {
|
|
enabled := existingEnabled
|
|
|
|
desc := "Show notifications for recording status changes"
|
|
if existingEnabled {
|
|
desc = "Currently: enabled. " + desc
|
|
} else {
|
|
desc = "Currently: disabled. " + desc
|
|
}
|
|
|
|
form := huh.NewForm(
|
|
huh.NewGroup(
|
|
huh.NewConfirm().
|
|
Title("Enable desktop notifications?").
|
|
Description(desc).
|
|
Value(&enabled),
|
|
),
|
|
).WithTheme(getTheme())
|
|
|
|
if err := form.Run(); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return enabled, nil
|
|
}
|