165 lines
4.8 KiB
Go
165 lines
4.8 KiB
Go
package config
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"time"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
)
|
|
|
|
var ErrConfigNotFound = errors.New("config not found")
|
|
|
|
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) {
|
|
return nil, fmt.Errorf("%w: run hyprvoice onboarding", ErrConfigNotFound)
|
|
} else if err != nil {
|
|
return nil, fmt.Errorf("failed to stat config file %s: %w", configPath, err)
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
if config.Transcription.Provider == "groq-translation" {
|
|
log.Printf("Config: deprecated transcription.provider 'groq-translation' detected - using 'groq-transcription' instead")
|
|
config.Transcription.Provider = "groq-transcription"
|
|
}
|
|
|
|
config.applyLLMDefaults()
|
|
config.applyThreadsDefault()
|
|
|
|
log.Printf("Config: configuration loaded successfully")
|
|
return &config, nil
|
|
}
|
|
|
|
// applyThreadsDefault sets default threads for local transcription if not explicitly set
|
|
func (c *Config) applyThreadsDefault() {
|
|
if c.Transcription.Threads == 0 {
|
|
threads := runtime.NumCPU() - 1
|
|
if threads < 1 {
|
|
threads = 1
|
|
}
|
|
c.Transcription.Threads = threads
|
|
}
|
|
}
|
|
|
|
// 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":
|
|
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")
|
|
}
|