""" Configuration Manager Module. ============================= Singleton class to manage loading and saving application settings to a JSON file. Ensures robustness by merging with defaults and handling file paths correctly. """ import json import logging from pathlib import Path from typing import Any, Dict from src.core.paths import get_base_path # Default Configuration DEFAULT_SETTINGS = { "hotkey": "f8", "hotkey_translate": "f10", "model_size": "small", "input_device": None, # Device ID (int) or Name (str), None = Default "save_recordings": False, # Save .wav files for debugging "silence_threshold": 0.02, # Amplitude threshold (0.0 - 1.0) "silence_duration": 1.0, # Seconds of silence to trigger auto-submit "visualizer_style": "line", # 'bar' or 'line' "opacity": 1.0, # Window opacity (0.1 - 1.0) "ui_scale": 1.0, # Global UI Scale (0.75 - 1.5) "always_on_top": True, "run_on_startup": False, # (Placeholder) # Window Position "overlay_position": "Bottom Center", "overlay_offset_x": 0, "overlay_offset_y": 0, # Input "input_method": "Clipboard Paste", # "Clipboard Paste" or "Simulate Typing" "typing_speed": 100, # CPM (Chars Per Minute) if typing # AI - Advanced "language": "auto", # "auto" or ISO code "task": "transcribe", # "transcribe" or "translate" (to English) "compute_device": "auto", # "auto", "cuda", "cpu" "compute_type": "int8", # "int8", "float16", "float32" "beam_size": 5, "best_of": 5, "vad_filter": True, "no_repeat_ngram_size": 0, "condition_on_previous_text": True, "initial_prompt": "Mm-hmm. Okay, let's go. I speak in full sentences.", # Default: Forces punctuation # Low VRAM Mode "unload_models_after_use": False # If True, models are unloaded immediately to free VRAM } class ConfigManager: """ Singleton Configuration Manager. """ _instance = None def __new__(cls): if cls._instance is None: cls._instance = super(ConfigManager, cls).__new__(cls) cls._instance._init() return cls._instance def _init(self): """Initialize the config manager (called only once).""" self.base_path = get_base_path() self.config_file = self.base_path / "settings.json" self.data = DEFAULT_SETTINGS.copy() self.load() def load(self): """Load settings from JSON file, merging with defaults.""" if self.config_file.exists(): try: with open(self.config_file, 'r', encoding='utf-8') as f: loaded = json.load(f) # Merge loaded data into defaults (preserves new default keys) for key, value in loaded.items(): if key in DEFAULT_SETTINGS: self.data[key] = value logging.info(f"Settings loaded from {self.config_file}") except Exception as e: logging.error(f"Failed to load settings: {e}") else: logging.info("No settings file found. Using defaults.") self.save() def save(self): """Save current settings to JSON file.""" try: with open(self.config_file, 'w', encoding='utf-8') as f: json.dump(self.data, f, indent=4) logging.info("Settings saved.") except Exception as e: logging.error(f"Failed to save settings: {e}") def get(self, key: str) -> Any: """Get a setting value.""" return self.data.get(key, DEFAULT_SETTINGS.get(key)) def set(self, key: str, value: Any): """Set a setting value and save.""" if self.data.get(key) != value: self.data[key] = value self.save() def set_bulk(self, updates: Dict[str, Any]): """Update multiple keys and save once.""" changed = False for k, v in updates.items(): if self.data.get(k) != v: self.data[k] = v changed = True if changed: self.save()