package config import ( "fmt" "log" "os" "path/filepath" "time" "github.com/BurntSushi/toml" ) func GetConfigPath() (string, error) { configDir, err := os.UserConfigDir() if err != nil { return "", fmt.Errorf("failed to get user config directory: %w", err) } hyprvoiceDir := filepath.Join(configDir, "hyprvoice") if err := os.MkdirAll(hyprvoiceDir, 0755); err != nil { return "", fmt.Errorf("failed to create config directory: %w", err) } return filepath.Join(hyprvoiceDir, "config.toml"), nil } // legacyInjectionConfig for migration from old mode-based config type legacyInjectionConfig struct { Mode string `toml:"mode"` } // legacyTranscriptionConfig for migration from old api_key in transcription type legacyTranscriptionConfig struct { APIKey string `toml:"api_key"` } type legacyConfig struct { Injection legacyInjectionConfig `toml:"injection"` Transcription legacyTranscriptionConfig `toml:"transcription"` } func Load() (*Config, error) { configPath, err := GetConfigPath() if err != nil { return nil, err } if _, err := os.Stat(configPath); os.IsNotExist(err) { log.Printf("Config: no config file found at %s, creating with defaults", configPath) if err := SaveDefaultConfig(); err != nil { return nil, fmt.Errorf("failed to create default config: %w", err) } log.Printf("Config: default configuration created successfully") return Load() } log.Printf("Config: loading configuration from %s", configPath) var config Config if _, err := toml.DecodeFile(configPath, &config); err != nil { return nil, fmt.Errorf("failed to parse config file %s: %w", configPath, err) } var legacy legacyConfig toml.DecodeFile(configPath, &legacy) if len(config.Injection.Backends) == 0 { config.migrateInjectionMode(legacy.Injection.Mode) } if legacy.Transcription.APIKey != "" && config.Providers == nil { config.migrateTranscriptionAPIKey(legacy.Transcription.APIKey) } if config.Providers == nil { config.Providers = make(map[string]ProviderConfig) } config.applyLLMDefaults() log.Printf("Config: configuration loaded successfully") return &config, nil } // migrateTranscriptionAPIKey migrates old transcription.api_key to providers map func (c *Config) migrateTranscriptionAPIKey(apiKey string) { if c.Providers == nil { c.Providers = make(map[string]ProviderConfig) } providerName := c.Transcription.Provider switch providerName { case "openai": c.Providers["openai"] = ProviderConfig{APIKey: apiKey} case "groq-transcription", "groq-translation": c.Providers["groq"] = ProviderConfig{APIKey: apiKey} case "mistral-transcription": c.Providers["mistral"] = ProviderConfig{APIKey: apiKey} case "elevenlabs": c.Providers["elevenlabs"] = ProviderConfig{APIKey: apiKey} default: if len(apiKey) > 3 && apiKey[:3] == "sk-" { c.Providers["openai"] = ProviderConfig{APIKey: apiKey} } else if len(apiKey) > 4 && apiKey[:4] == "gsk_" { c.Providers["groq"] = ProviderConfig{APIKey: apiKey} } } log.Printf("Config: migrated transcription.api_key to providers map. Run 'hyprvoice configure' to update config format.") } // applyLLMDefaults sets default values for LLM config func (c *Config) applyLLMDefaults() { pp := &c.LLM.PostProcessing if !pp.RemoveStutters && !pp.AddPunctuation && !pp.FixGrammar && !pp.RemoveFillerWords { pp.RemoveStutters = true pp.AddPunctuation = true pp.FixGrammar = true pp.RemoveFillerWords = true } } // migrateInjectionMode converts old mode field to new backends array func (c *Config) migrateInjectionMode(mode string) { switch mode { case "clipboard": c.Injection.Backends = []string{"clipboard"} log.Printf("Config: migrated injection.mode='clipboard' to backends=['clipboard']") case "type": c.Injection.Backends = []string{"wtype"} log.Printf("Config: migrated injection.mode='type' to backends=['wtype']") case "fallback": c.Injection.Backends = []string{"wtype", "clipboard"} log.Printf("Config: migrated injection.mode='fallback' to backends=['wtype', 'clipboard']") default: c.Injection.Backends = []string{"ydotool", "wtype", "clipboard"} if mode != "" { log.Printf("Config: unknown injection.mode='%s', using default backends", mode) } } if c.Injection.YdotoolTimeout == 0 { c.Injection.YdotoolTimeout = 5 * time.Second } log.Printf("Config: legacy 'mode' config detected - please update your config.toml to use 'backends' instead") }