feat: improve configure
This commit is contained in:
+226
-122
@@ -27,6 +27,8 @@ const (
|
||||
SectionInjection ConfigSection = "injection"
|
||||
SectionNotifications ConfigSection = "notifications"
|
||||
SectionFullSetup ConfigSection = "full_setup"
|
||||
SectionSaveExit ConfigSection = "save_exit"
|
||||
SectionDiscardExit ConfigSection = "discard_exit"
|
||||
)
|
||||
|
||||
// Run starts the TUI configuration wizard
|
||||
@@ -51,38 +53,46 @@ func hasUserChanges(cfg *config.Config) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// runEditExisting runs the section-based edit flow for existing configs
|
||||
// runEditExisting runs the menu-based edit flow for existing configs
|
||||
func runEditExisting(cfg *config.Config) (*ConfigureResult, error) {
|
||||
fmt.Println(Logo())
|
||||
fmt.Println()
|
||||
fmt.Println(StyleMuted.Render("Configuration detected. Select sections to edit."))
|
||||
fmt.Println()
|
||||
|
||||
// Section picker
|
||||
sections, err := selectSections()
|
||||
if err != nil {
|
||||
return &ConfigureResult{Cancelled: true}, nil
|
||||
}
|
||||
if len(sections) == 0 {
|
||||
return &ConfigureResult{Cancelled: true}, nil
|
||||
}
|
||||
|
||||
// Check if full setup requested
|
||||
for _, s := range sections {
|
||||
if s == SectionFullSetup {
|
||||
return runFreshInstall(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
// Track which providers are configured (for smart detection)
|
||||
configuredProviders := getConfiguredProviders(cfg)
|
||||
|
||||
// Process each selected section
|
||||
for _, section := range sections {
|
||||
// Menu loop
|
||||
for {
|
||||
// Clear screen for cleaner UX
|
||||
fmt.Print("\033[H\033[2J")
|
||||
fmt.Println(Logo())
|
||||
fmt.Println()
|
||||
|
||||
section, err := selectSection(cfg)
|
||||
if err != nil {
|
||||
return &ConfigureResult{Cancelled: true}, nil
|
||||
}
|
||||
|
||||
switch section {
|
||||
case SectionSaveExit:
|
||||
confirmed, err := showSummary(cfg)
|
||||
if err != nil {
|
||||
return &ConfigureResult{Cancelled: true}, nil
|
||||
}
|
||||
if confirmed {
|
||||
return &ConfigureResult{Config: cfg, Cancelled: false}, nil
|
||||
}
|
||||
// User cancelled save, back to menu
|
||||
|
||||
case SectionDiscardExit:
|
||||
return &ConfigureResult{Cancelled: true}, nil
|
||||
|
||||
case SectionFullSetup:
|
||||
return runFreshInstall(cfg)
|
||||
|
||||
case SectionProviders:
|
||||
if err := editProviders(cfg); err != nil {
|
||||
return &ConfigureResult{Cancelled: true}, nil
|
||||
continue // back to menu on cancel
|
||||
}
|
||||
configuredProviders = getConfiguredProviders(cfg)
|
||||
|
||||
@@ -90,77 +100,131 @@ func runEditExisting(cfg *config.Config) (*ConfigureResult, error) {
|
||||
var err error
|
||||
configuredProviders, err = editTranscription(cfg, configuredProviders)
|
||||
if err != nil {
|
||||
return &ConfigureResult{Cancelled: true}, nil
|
||||
continue // back to menu on cancel
|
||||
}
|
||||
|
||||
case SectionLLM:
|
||||
var err error
|
||||
configuredProviders, err = editLLM(cfg, configuredProviders)
|
||||
if err != nil {
|
||||
return &ConfigureResult{Cancelled: true}, nil
|
||||
continue // back to menu on cancel
|
||||
}
|
||||
|
||||
case SectionKeywords:
|
||||
keywords, err := inputKeywords(cfg.Keywords)
|
||||
if err != nil {
|
||||
return &ConfigureResult{Cancelled: true}, nil
|
||||
continue // back to menu on cancel
|
||||
}
|
||||
cfg.Keywords = keywords
|
||||
|
||||
case SectionInjection:
|
||||
backends, err := selectBackends(cfg.Injection.Backends)
|
||||
if err != nil {
|
||||
return &ConfigureResult{Cancelled: true}, nil
|
||||
continue // back to menu on cancel
|
||||
}
|
||||
cfg.Injection.Backends = backends
|
||||
|
||||
case SectionNotifications:
|
||||
enabled, err := configureNotifications(cfg.Notifications.Enabled)
|
||||
if err != nil {
|
||||
return &ConfigureResult{Cancelled: true}, nil
|
||||
continue // back to menu on cancel
|
||||
}
|
||||
cfg.Notifications.Enabled = enabled
|
||||
}
|
||||
}
|
||||
|
||||
// Summary and confirm
|
||||
confirmed, err := showSummary(cfg)
|
||||
if err != nil || !confirmed {
|
||||
return &ConfigureResult{Cancelled: true}, nil
|
||||
}
|
||||
|
||||
return &ConfigureResult{Config: cfg, Cancelled: false}, nil
|
||||
}
|
||||
|
||||
func selectSections() ([]ConfigSection, error) {
|
||||
func selectSection(cfg *config.Config) (ConfigSection, error) {
|
||||
options := []huh.Option[ConfigSection]{
|
||||
huh.NewOption("Providers - API keys", SectionProviders),
|
||||
huh.NewOption("Transcription - speech-to-text settings", SectionTranscription),
|
||||
huh.NewOption("LLM - post-processing settings", SectionLLM),
|
||||
huh.NewOption("Keywords - spelling hints", SectionKeywords),
|
||||
huh.NewOption("Injection - text input backends", SectionInjection),
|
||||
huh.NewOption("Notifications - desktop alerts", SectionNotifications),
|
||||
huh.NewOption("Full Setup - reconfigure everything", SectionFullSetup),
|
||||
huh.NewOption(formatProvidersLabel(cfg), SectionProviders),
|
||||
huh.NewOption(formatTranscriptionLabel(cfg), SectionTranscription),
|
||||
huh.NewOption(formatLLMLabel(cfg), SectionLLM),
|
||||
huh.NewOption(formatKeywordsLabel(cfg), SectionKeywords),
|
||||
huh.NewOption(formatInjectionLabel(cfg), SectionInjection),
|
||||
huh.NewOption(formatNotificationsLabel(cfg), SectionNotifications),
|
||||
huh.NewOption("Full Setup (reconfigure everything)", SectionFullSetup),
|
||||
huh.NewOption("Save & Exit", SectionSaveExit),
|
||||
huh.NewOption("Discard & Exit", SectionDiscardExit),
|
||||
}
|
||||
|
||||
var selected []ConfigSection
|
||||
var selected ConfigSection
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewMultiSelect[ConfigSection]().
|
||||
Title("What do you want to configure?").
|
||||
Description("Select one or more sections to edit").
|
||||
huh.NewSelect[ConfigSection]().
|
||||
Title("Configuration Menu").
|
||||
Description("↑/↓ navigate • enter select • esc cancel").
|
||||
Options(options...).
|
||||
Value(&selected),
|
||||
),
|
||||
).WithTheme(getTheme())
|
||||
|
||||
if err := form.Run(); err != nil {
|
||||
return nil, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
return selected, nil
|
||||
}
|
||||
|
||||
// formatProvidersLabel shows configured providers
|
||||
func formatProvidersLabel(cfg *config.Config) string {
|
||||
var providers []string
|
||||
for name, pc := range cfg.Providers {
|
||||
if pc.APIKey != "" {
|
||||
providers = append(providers, name)
|
||||
}
|
||||
}
|
||||
if len(providers) == 0 {
|
||||
return "Providers - none configured"
|
||||
}
|
||||
return fmt.Sprintf("Providers - %s", strings.Join(providers, ", "))
|
||||
}
|
||||
|
||||
// formatTranscriptionLabel shows current transcription settings
|
||||
func formatTranscriptionLabel(cfg *config.Config) string {
|
||||
if cfg.Transcription.Provider == "" {
|
||||
return "Transcription - not configured"
|
||||
}
|
||||
return fmt.Sprintf("Transcription - %s/%s", cfg.Transcription.Provider, cfg.Transcription.Model)
|
||||
}
|
||||
|
||||
// formatLLMLabel shows current LLM settings
|
||||
func formatLLMLabel(cfg *config.Config) string {
|
||||
if !cfg.LLM.Enabled {
|
||||
return "LLM - disabled"
|
||||
}
|
||||
if cfg.LLM.Provider == "" {
|
||||
return "LLM - enabled (not configured)"
|
||||
}
|
||||
return fmt.Sprintf("LLM - %s/%s", cfg.LLM.Provider, cfg.LLM.Model)
|
||||
}
|
||||
|
||||
// formatKeywordsLabel shows keyword count
|
||||
func formatKeywordsLabel(cfg *config.Config) string {
|
||||
if len(cfg.Keywords) == 0 {
|
||||
return "Keywords - none"
|
||||
}
|
||||
if len(cfg.Keywords) <= 3 {
|
||||
return fmt.Sprintf("Keywords - %s", strings.Join(cfg.Keywords, ", "))
|
||||
}
|
||||
return fmt.Sprintf("Keywords - %d configured", len(cfg.Keywords))
|
||||
}
|
||||
|
||||
// formatInjectionLabel shows backends
|
||||
func formatInjectionLabel(cfg *config.Config) string {
|
||||
if len(cfg.Injection.Backends) == 0 {
|
||||
return "Injection - no backends"
|
||||
}
|
||||
return fmt.Sprintf("Injection - %s", strings.Join(cfg.Injection.Backends, " → "))
|
||||
}
|
||||
|
||||
// formatNotificationsLabel shows notification status
|
||||
func formatNotificationsLabel(cfg *config.Config) string {
|
||||
if cfg.Notifications.Enabled {
|
||||
return "Notifications - enabled"
|
||||
}
|
||||
return "Notifications - disabled"
|
||||
}
|
||||
|
||||
// getConfiguredProviders returns list of providers with API keys
|
||||
func getConfiguredProviders(cfg *config.Config) []string {
|
||||
var providers []string
|
||||
@@ -172,35 +236,24 @@ func getConfiguredProviders(cfg *config.Config) []string {
|
||||
return providers
|
||||
}
|
||||
|
||||
// editProviders handles the providers section edit
|
||||
// editProviders handles the providers section edit with submenu
|
||||
func editProviders(cfg *config.Config) error {
|
||||
// Show current providers with option to add/edit
|
||||
allProviders := []string{"openai", "groq", "mistral", "elevenlabs"}
|
||||
|
||||
for {
|
||||
// Build options with current status
|
||||
var options []huh.Option[string]
|
||||
for _, name := range allProviders {
|
||||
label := strings.Title(name)
|
||||
if _, exists := cfg.Providers[name]; exists && cfg.Providers[name].APIKey != "" {
|
||||
label += " (configured)"
|
||||
}
|
||||
switch name {
|
||||
case "openai":
|
||||
options = append(options, huh.NewOption(label+" - Whisper + GPT", name))
|
||||
case "groq":
|
||||
options = append(options, huh.NewOption(label+" - Whisper + Llama", name))
|
||||
case "mistral":
|
||||
options = append(options, huh.NewOption(label+" - Voxtral", name))
|
||||
case "elevenlabs":
|
||||
options = append(options, huh.NewOption(label+" - Scribe", name))
|
||||
}
|
||||
options = append(options, huh.NewOption(formatProviderOption(cfg, name), name))
|
||||
}
|
||||
options = append(options, huh.NewOption("Back", "back"))
|
||||
|
||||
var selected []string
|
||||
var selected string
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewMultiSelect[string]().
|
||||
Title("Configure API keys for:").
|
||||
Description("Select providers to add or update API keys").
|
||||
huh.NewSelect[string]().
|
||||
Title("Provider Settings").
|
||||
Description("Select a provider to configure API key").
|
||||
Options(options...).
|
||||
Value(&selected),
|
||||
),
|
||||
@@ -210,19 +263,44 @@ func editProviders(cfg *config.Config) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Input API keys for selected providers
|
||||
for _, providerName := range selected {
|
||||
apiKey, err := inputAPIKey(providerName)
|
||||
if err != nil {
|
||||
return err
|
||||
if selected == "back" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Configure the selected provider
|
||||
apiKey, err := inputAPIKey(selected)
|
||||
if err != nil {
|
||||
continue // cancelled, back to provider menu
|
||||
}
|
||||
|
||||
if cfg.Providers == nil {
|
||||
cfg.Providers = make(map[string]config.ProviderConfig)
|
||||
}
|
||||
cfg.Providers[providerName] = config.ProviderConfig{APIKey: apiKey}
|
||||
cfg.Providers[selected] = config.ProviderConfig{APIKey: apiKey}
|
||||
}
|
||||
}
|
||||
|
||||
// formatProviderOption formats a provider menu option with status
|
||||
func formatProviderOption(cfg *config.Config, name string) string {
|
||||
var status string
|
||||
if pc, exists := cfg.Providers[name]; exists && pc.APIKey != "" {
|
||||
status = "(configured)"
|
||||
} else {
|
||||
status = "(not configured)"
|
||||
}
|
||||
|
||||
return nil
|
||||
switch name {
|
||||
case "openai":
|
||||
return fmt.Sprintf("OpenAI - Whisper + GPT %s", status)
|
||||
case "groq":
|
||||
return fmt.Sprintf("Groq - Whisper + Llama %s", status)
|
||||
case "mistral":
|
||||
return fmt.Sprintf("Mistral - Voxtral %s", status)
|
||||
case "elevenlabs":
|
||||
return fmt.Sprintf("ElevenLabs - Scribe %s", status)
|
||||
default:
|
||||
return fmt.Sprintf("%s %s", name, status)
|
||||
}
|
||||
}
|
||||
|
||||
// editTranscription handles the transcription section edit with smart provider detection
|
||||
@@ -433,30 +511,11 @@ func editLLM(cfg *config.Config, configuredProviders []string) ([]string, error)
|
||||
|
||||
cfg.LLM.Model = selectedModel
|
||||
|
||||
// Post-processing options
|
||||
ppForm := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewConfirm().
|
||||
Title("Remove stutters").
|
||||
Description("Remove repeated words like 'I I I think'").
|
||||
Value(&postProcessing.RemoveStutters),
|
||||
huh.NewConfirm().
|
||||
Title("Add punctuation").
|
||||
Description("Add proper punctuation to text").
|
||||
Value(&postProcessing.AddPunctuation),
|
||||
huh.NewConfirm().
|
||||
Title("Fix grammar").
|
||||
Description("Correct grammatical errors").
|
||||
Value(&postProcessing.FixGrammar),
|
||||
huh.NewConfirm().
|
||||
Title("Remove filler words").
|
||||
Description("Remove 'um', 'uh', 'like', etc.").
|
||||
Value(&postProcessing.RemoveFillerWords),
|
||||
),
|
||||
).WithTheme(getTheme())
|
||||
|
||||
if err := ppForm.Run(); err != nil {
|
||||
return configuredProviders, err
|
||||
// Post-processing options using MultiSelect
|
||||
var ppErr error
|
||||
postProcessing, ppErr = selectPostProcessingOptions(postProcessing)
|
||||
if ppErr != nil {
|
||||
return configuredProviders, ppErr
|
||||
}
|
||||
|
||||
cfg.LLM.PostProcessing = postProcessing
|
||||
@@ -958,29 +1017,10 @@ func configureLLM(configuredProviders []string, cfg *config.Config) (bool, strin
|
||||
postProcessing = cfg.LLM.PostProcessing
|
||||
}
|
||||
|
||||
ppForm := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewConfirm().
|
||||
Title("Remove stutters").
|
||||
Description("Remove repeated words like 'I I I think'").
|
||||
Value(&postProcessing.RemoveStutters),
|
||||
huh.NewConfirm().
|
||||
Title("Add punctuation").
|
||||
Description("Add proper punctuation to text").
|
||||
Value(&postProcessing.AddPunctuation),
|
||||
huh.NewConfirm().
|
||||
Title("Fix grammar").
|
||||
Description("Correct grammatical errors").
|
||||
Value(&postProcessing.FixGrammar),
|
||||
huh.NewConfirm().
|
||||
Title("Remove filler words").
|
||||
Description("Remove 'um', 'uh', 'like', etc.").
|
||||
Value(&postProcessing.RemoveFillerWords),
|
||||
),
|
||||
).WithTheme(getTheme())
|
||||
|
||||
if err := ppForm.Run(); err != nil {
|
||||
return false, "", "", postProcessing, customPrompt, err
|
||||
var ppErr error
|
||||
postProcessing, ppErr = selectPostProcessingOptions(postProcessing)
|
||||
if ppErr != nil {
|
||||
return false, "", "", postProcessing, customPrompt, ppErr
|
||||
}
|
||||
|
||||
// Custom prompt
|
||||
@@ -1045,6 +1085,70 @@ func getLLMModelOptions(provider string) []huh.Option[string] {
|
||||
}
|
||||
}
|
||||
|
||||
// selectPostProcessingOptions shows a multi-select for LLM post-processing toggles
|
||||
func selectPostProcessingOptions(current config.LLMPostProcessingConfig) (config.LLMPostProcessingConfig, error) {
|
||||
type ppOption string
|
||||
const (
|
||||
optRemoveStutters ppOption = "stutters"
|
||||
optAddPunctuation ppOption = "punctuation"
|
||||
optFixGrammar ppOption = "grammar"
|
||||
optRemoveFillerWords ppOption = "fillers"
|
||||
)
|
||||
|
||||
options := []huh.Option[ppOption]{
|
||||
huh.NewOption("Remove stutters (repeated words)", optRemoveStutters),
|
||||
huh.NewOption("Add punctuation", optAddPunctuation),
|
||||
huh.NewOption("Fix grammar", optFixGrammar),
|
||||
huh.NewOption("Remove filler words (um, uh, like)", optRemoveFillerWords),
|
||||
}
|
||||
|
||||
// Pre-select based on current config
|
||||
var selected []ppOption
|
||||
if current.RemoveStutters {
|
||||
selected = append(selected, optRemoveStutters)
|
||||
}
|
||||
if current.AddPunctuation {
|
||||
selected = append(selected, optAddPunctuation)
|
||||
}
|
||||
if current.FixGrammar {
|
||||
selected = append(selected, optFixGrammar)
|
||||
}
|
||||
if current.RemoveFillerWords {
|
||||
selected = append(selected, optRemoveFillerWords)
|
||||
}
|
||||
|
||||
form := huh.NewForm(
|
||||
huh.NewGroup(
|
||||
huh.NewMultiSelect[ppOption]().
|
||||
Title("Post-Processing Options").
|
||||
Description("Select which improvements to apply").
|
||||
Options(options...).
|
||||
Value(&selected),
|
||||
),
|
||||
).WithTheme(getTheme())
|
||||
|
||||
if err := form.Run(); err != nil {
|
||||
return current, err
|
||||
}
|
||||
|
||||
// Convert selections back to config
|
||||
result := config.LLMPostProcessingConfig{}
|
||||
for _, opt := range selected {
|
||||
switch opt {
|
||||
case optRemoveStutters:
|
||||
result.RemoveStutters = true
|
||||
case optAddPunctuation:
|
||||
result.AddPunctuation = true
|
||||
case optFixGrammar:
|
||||
result.FixGrammar = true
|
||||
case optRemoveFillerWords:
|
||||
result.RemoveFillerWords = true
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func inputKeywords(existingKeywords []string) ([]string, error) {
|
||||
var keywordsInput string
|
||||
if len(existingKeywords) > 0 {
|
||||
|
||||
Reference in New Issue
Block a user