feat: fanalize straeming adapters
This commit is contained in:
+20
-201
@@ -1984,16 +1984,13 @@ func TestConfig_ToTranscriberConfig_Threads(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfig_EffectiveLanguage(t *testing.T) {
|
||||
t.Run("only general.language set", func(t *testing.T) {
|
||||
func TestConfig_TranscriptionLanguage(t *testing.T) {
|
||||
t.Run("language set in transcription", func(t *testing.T) {
|
||||
config := &Config{
|
||||
General: GeneralConfig{
|
||||
Language: "es",
|
||||
},
|
||||
Transcription: TranscriptionConfig{
|
||||
Provider: "openai",
|
||||
Model: "whisper-1",
|
||||
Language: "", // not set
|
||||
Language: "es",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2003,29 +2000,8 @@ func TestConfig_EffectiveLanguage(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("transcription.language overrides general.language", func(t *testing.T) {
|
||||
t.Run("empty language results in auto-detect", func(t *testing.T) {
|
||||
config := &Config{
|
||||
General: GeneralConfig{
|
||||
Language: "es",
|
||||
},
|
||||
Transcription: TranscriptionConfig{
|
||||
Provider: "openai",
|
||||
Model: "whisper-1",
|
||||
Language: "en", // overrides general
|
||||
},
|
||||
}
|
||||
|
||||
transcriberConfig := config.ToTranscriberConfig()
|
||||
if transcriberConfig.Language != "en" {
|
||||
t.Errorf("Language = %q, want %q", transcriberConfig.Language, "en")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("neither set results in auto", func(t *testing.T) {
|
||||
config := &Config{
|
||||
General: GeneralConfig{
|
||||
Language: "",
|
||||
},
|
||||
Transcription: TranscriptionConfig{
|
||||
Provider: "openai",
|
||||
Model: "whisper-1",
|
||||
@@ -2040,7 +2016,7 @@ func TestConfig_EffectiveLanguage(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfig_Validate_GeneralLanguage(t *testing.T) {
|
||||
func TestConfig_Validate_TranscriptionLanguage(t *testing.T) {
|
||||
baseConfig := func() *Config {
|
||||
return &Config{
|
||||
Recording: RecordingConfig{
|
||||
@@ -2066,63 +2042,46 @@ func TestConfig_Validate_GeneralLanguage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("valid general.language passes validation", func(t *testing.T) {
|
||||
t.Run("valid transcription.language passes validation", func(t *testing.T) {
|
||||
config := baseConfig()
|
||||
config.General.Language = "es"
|
||||
config.Transcription.Language = "es"
|
||||
|
||||
err := config.Validate()
|
||||
if err != nil {
|
||||
t.Errorf("Validate() should pass with valid general.language: %v", err)
|
||||
t.Errorf("Validate() should pass with valid transcription.language: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("general.language validated against model", func(t *testing.T) {
|
||||
t.Run("transcription.language validated against model", func(t *testing.T) {
|
||||
config := baseConfig()
|
||||
config.General.Language = "es"
|
||||
config.Transcription.Language = "es" // incompatible
|
||||
config.Transcription.Provider = "whisper-cpp"
|
||||
config.Transcription.Model = "base.en" // english-only model
|
||||
|
||||
err := config.Validate()
|
||||
if err == nil {
|
||||
t.Error("Validate() should fail when general.language incompatible with model")
|
||||
t.Error("Validate() should fail when transcription.language incompatible with model")
|
||||
}
|
||||
if err != nil && !strings.Contains(err.Error(), "does not support Spanish") {
|
||||
t.Errorf("error should mention Spanish, got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("transcription.language override validated against model", func(t *testing.T) {
|
||||
t.Run("compatible language passes", func(t *testing.T) {
|
||||
config := baseConfig()
|
||||
config.General.Language = "en" // compatible
|
||||
config.Transcription.Language = "es" // override with incompatible
|
||||
config.Transcription.Provider = "whisper-cpp"
|
||||
config.Transcription.Model = "base.en" // english-only model
|
||||
|
||||
err := config.Validate()
|
||||
if err == nil {
|
||||
t.Error("Validate() should fail when transcription.language override is incompatible")
|
||||
}
|
||||
if err != nil && !strings.Contains(err.Error(), "does not support Spanish") {
|
||||
t.Errorf("error should mention Spanish, got: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("valid override with compatible language", func(t *testing.T) {
|
||||
config := baseConfig()
|
||||
config.General.Language = "es" // would be incompatible
|
||||
config.Transcription.Language = "en" // override with compatible
|
||||
config.Transcription.Language = "en" // compatible
|
||||
config.Transcription.Provider = "whisper-cpp"
|
||||
config.Transcription.Model = "base.en" // english-only model
|
||||
|
||||
err := config.Validate()
|
||||
if err != nil {
|
||||
t.Errorf("Validate() should pass when transcription.language override is compatible: %v", err)
|
||||
t.Errorf("Validate() should pass when transcription.language is compatible: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("auto language always passes", func(t *testing.T) {
|
||||
config := baseConfig()
|
||||
config.General.Language = "" // auto
|
||||
config.Transcription.Language = "" // auto
|
||||
config.Transcription.Provider = "whisper-cpp"
|
||||
config.Transcription.Model = "base.en" // english-only model
|
||||
|
||||
@@ -2133,8 +2092,8 @@ func TestConfig_Validate_GeneralLanguage(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestConfig_MigrateLanguageToGeneral(t *testing.T) {
|
||||
t.Run("old config with transcription.language migrates to general.language", func(t *testing.T) {
|
||||
func TestConfig_LoadWithTranscriptionLanguage(t *testing.T) {
|
||||
t.Run("config with transcription.language loads correctly", func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
configPath := filepath.Join(tempDir, "hyprvoice", "config.toml")
|
||||
|
||||
@@ -2143,77 +2102,7 @@ func TestConfig_MigrateLanguageToGeneral(t *testing.T) {
|
||||
t.Fatalf("Failed to create config directory: %v", err)
|
||||
}
|
||||
|
||||
// Old config with language in transcription section
|
||||
oldConfig := `[recording]
|
||||
sample_rate = 16000
|
||||
channels = 1
|
||||
format = "s16"
|
||||
buffer_size = 8192
|
||||
channel_buffer_size = 30
|
||||
timeout = "5m"
|
||||
|
||||
[transcription]
|
||||
provider = "openai"
|
||||
api_key = "test-key"
|
||||
model = "whisper-1"
|
||||
language = "es"
|
||||
|
||||
[injection]
|
||||
backends = ["clipboard"]
|
||||
ydotool_timeout = "5s"
|
||||
wtype_timeout = "5s"
|
||||
clipboard_timeout = "3s"
|
||||
|
||||
[notifications]
|
||||
type = "log"`
|
||||
|
||||
err = os.WriteFile(configPath, []byte(oldConfig), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create config file: %v", err)
|
||||
}
|
||||
|
||||
originalConfigDir := os.Getenv("XDG_CONFIG_HOME")
|
||||
os.Setenv("XDG_CONFIG_HOME", tempDir)
|
||||
defer func() {
|
||||
if originalConfigDir == "" {
|
||||
os.Unsetenv("XDG_CONFIG_HOME")
|
||||
} else {
|
||||
os.Setenv("XDG_CONFIG_HOME", originalConfigDir)
|
||||
}
|
||||
}()
|
||||
|
||||
config, err := Load()
|
||||
if err != nil {
|
||||
t.Errorf("Load() error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Should have migrated to general.language
|
||||
if config.General.Language != "es" {
|
||||
t.Errorf("Expected general.language='es' after migration, got %q", config.General.Language)
|
||||
}
|
||||
|
||||
// Effective language should be 'es'
|
||||
transcriberConfig := config.ToTranscriberConfig()
|
||||
if transcriberConfig.Language != "es" {
|
||||
t.Errorf("Expected effective language 'es', got %q", transcriberConfig.Language)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("migration does not run when general.language already set", func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
configPath := filepath.Join(tempDir, "hyprvoice", "config.toml")
|
||||
|
||||
err := os.MkdirAll(filepath.Dir(configPath), 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create config directory: %v", err)
|
||||
}
|
||||
|
||||
// Config with both general.language and transcription.language set
|
||||
configContent := `[general]
|
||||
language = "fr"
|
||||
|
||||
[recording]
|
||||
configContent := `[recording]
|
||||
sample_rate = 16000
|
||||
channels = 1
|
||||
format = "s16"
|
||||
@@ -2257,80 +2146,10 @@ type = "log"`
|
||||
return
|
||||
}
|
||||
|
||||
// general.language should remain 'fr', not overwritten by migration
|
||||
if config.General.Language != "fr" {
|
||||
t.Errorf("Expected general.language='fr' (not migrated), got %q", config.General.Language)
|
||||
}
|
||||
|
||||
// transcription.language should still override
|
||||
// Effective language should be 'es'
|
||||
transcriberConfig := config.ToTranscriberConfig()
|
||||
if transcriberConfig.Language != "es" {
|
||||
t.Errorf("Expected effective language 'es' (transcription override), got %q", transcriberConfig.Language)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("original file not modified until explicit save", func(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
configPath := filepath.Join(tempDir, "hyprvoice", "config.toml")
|
||||
|
||||
err := os.MkdirAll(filepath.Dir(configPath), 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create config directory: %v", err)
|
||||
}
|
||||
|
||||
oldConfig := `[recording]
|
||||
sample_rate = 16000
|
||||
channels = 1
|
||||
format = "s16"
|
||||
buffer_size = 8192
|
||||
channel_buffer_size = 30
|
||||
timeout = "5m"
|
||||
|
||||
[transcription]
|
||||
provider = "openai"
|
||||
api_key = "test-key"
|
||||
model = "whisper-1"
|
||||
language = "de"
|
||||
|
||||
[injection]
|
||||
backends = ["clipboard"]
|
||||
ydotool_timeout = "5s"
|
||||
wtype_timeout = "5s"
|
||||
clipboard_timeout = "3s"
|
||||
|
||||
[notifications]
|
||||
type = "log"`
|
||||
|
||||
err = os.WriteFile(configPath, []byte(oldConfig), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create config file: %v", err)
|
||||
}
|
||||
|
||||
originalConfigDir := os.Getenv("XDG_CONFIG_HOME")
|
||||
os.Setenv("XDG_CONFIG_HOME", tempDir)
|
||||
defer func() {
|
||||
if originalConfigDir == "" {
|
||||
os.Unsetenv("XDG_CONFIG_HOME")
|
||||
} else {
|
||||
os.Setenv("XDG_CONFIG_HOME", originalConfigDir)
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = Load()
|
||||
if err != nil {
|
||||
t.Errorf("Load() error = %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Read the file again - should still have old format
|
||||
content, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read config file: %v", err)
|
||||
}
|
||||
|
||||
// File should NOT have [general] section (migration is in-memory only)
|
||||
if strings.Contains(string(content), "[general]") {
|
||||
t.Error("Original file should not be modified by migration - [general] section found")
|
||||
t.Errorf("Expected effective language 'es', got %q", transcriberConfig.Language)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -36,13 +36,9 @@ func (c *Config) ToTranscriberConfig() transcriber.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
// resolveEffectiveLanguage returns the effective language for transcription.
|
||||
// transcription.language overrides general.language if set.
|
||||
// resolveEffectiveLanguage returns the language for transcription
|
||||
func (c *Config) resolveEffectiveLanguage() string {
|
||||
if c.Transcription.Language != "" {
|
||||
return c.Transcription.Language
|
||||
}
|
||||
return c.General.Language
|
||||
return c.Transcription.Language
|
||||
}
|
||||
|
||||
// resolveAPIKeyForProvider returns the API key for a provider from multiple sources
|
||||
|
||||
@@ -78,7 +78,6 @@ func Load() (*Config, error) {
|
||||
|
||||
config.applyLLMDefaults()
|
||||
config.applyThreadsDefault()
|
||||
config.migrateLanguageToGeneral()
|
||||
|
||||
log.Printf("Config: configuration loaded successfully")
|
||||
return &config, nil
|
||||
@@ -133,14 +132,6 @@ func (c *Config) applyLLMDefaults() {
|
||||
}
|
||||
}
|
||||
|
||||
// migrateLanguageToGeneral migrates old transcription.language to general.language
|
||||
func (c *Config) migrateLanguageToGeneral() {
|
||||
if c.Transcription.Language != "" && c.General.Language == "" {
|
||||
c.General.Language = c.Transcription.Language
|
||||
log.Printf("Config: migrated language setting to [general] section")
|
||||
}
|
||||
}
|
||||
|
||||
// migrateInjectionMode converts old mode field to new backends array
|
||||
func (c *Config) migrateInjectionMode(mode string) {
|
||||
switch mode {
|
||||
|
||||
+2
-15
@@ -41,13 +41,6 @@ func Save(cfg *Config) error {
|
||||
sb.WriteString("]\n\n")
|
||||
}
|
||||
|
||||
// General section
|
||||
sb.WriteString(`# General Settings
|
||||
[general]
|
||||
`)
|
||||
sb.WriteString(fmt.Sprintf(" language = %q\n", cfg.General.Language))
|
||||
sb.WriteString("\n")
|
||||
|
||||
// Providers section
|
||||
if len(cfg.Providers) > 0 {
|
||||
sb.WriteString("# API Keys for providers\n")
|
||||
@@ -210,13 +203,6 @@ func SaveDefaultConfig() error {
|
||||
# will be automatically migrated to the new [providers.X] format. Run 'hyprvoice configure'
|
||||
# to update your config file structure.
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# General Settings
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
[general]
|
||||
language = "" # Language for transcription (ISO 639-1 code, e.g., en, es, de). Empty for auto-detect.
|
||||
|
||||
# Keywords help both transcription and LLM understand domain-specific terms
|
||||
# Add names, technical terms, or brand names that might be misheard
|
||||
keywords = []
|
||||
@@ -262,8 +248,8 @@ keywords = []
|
||||
[transcription]
|
||||
provider = "openai" # "openai", "groq-transcription", "groq-translation", "mistral-transcription", "elevenlabs", "whisper-cpp"
|
||||
model = "whisper-1" # Model: OpenAI="whisper-1", Groq="whisper-large-v3", Mistral="voxtral-mini-latest", ElevenLabs="scribe_v1"
|
||||
language = "" # ISO 639-1 code (e.g., en, es, de). Empty for auto-detect.
|
||||
threads = 0 # CPU threads for local transcription (0 = auto: uses NumCPU-1)
|
||||
# language = "" # Override general.language for this provider only
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# LLM Post-Processing (Recommended)
|
||||
@@ -353,6 +339,7 @@ keywords = []
|
||||
# - "clipboard": Copies to clipboard only (most reliable, requires manual paste).
|
||||
#
|
||||
# Language codes: "" (auto-detect), "en", "it", "es", "fr", "de", "pt", etc.
|
||||
# Language is configured per transcription model - only supported languages are shown during setup.
|
||||
`
|
||||
|
||||
if _, err := file.WriteString(configContent); err != nil {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
// GeneralConfig holds global settings that apply across the application
|
||||
type GeneralConfig struct {
|
||||
Language string `toml:"language"` // ISO 639-1 code (e.g., en, es, de). Empty for auto-detect.
|
||||
// reserved for future use
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
|
||||
@@ -86,9 +86,6 @@ func (c *Config) Validate() error {
|
||||
}
|
||||
|
||||
// validate language codes - warn if not recognized but don't error
|
||||
if c.General.Language != "" && !language.IsValidCode(c.General.Language) {
|
||||
log.Printf("warning: unrecognized language code '%s' in general.language, will be passed as-is to provider", c.General.Language)
|
||||
}
|
||||
if c.Transcription.Language != "" && !language.IsValidCode(c.Transcription.Language) {
|
||||
log.Printf("warning: unrecognized language code '%s' in transcription.language, will be passed as-is to provider", c.Transcription.Language)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user