using System.IO; using System.Text.Json; namespace LlamaApp; /// /// User-configured application settings, persisted as JSON in the app's /// per-user local data folder. Holds the Hugging Face access token (for /// authenticated downloads / private repos) and the local models cache /// directory (where GGUF files live, shared with the HF cache layout). /// public sealed class Settings { // Declared BEFORE Current so its static field initializer runs first. // Static field initializers run in textual order, and Current's // initializer calls — if SettingsPath were declared // below it, Load would see SettingsPath == null (still its default), // would return false, and every saved setting // (HuggingFace token, cache directory, startup hint) would be silently // discarded on every launch. Keep this above any member that calls Load. private static readonly string SettingsPath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "LlamaApp", "settings.json"); /// Singleton instance; loaded lazily on first access and cached. public static Settings Current { get; } = Load(); static Settings() { // Make sure the directory exists so Save() never throws on a missing dir. try { Directory.CreateDirectory(Path.GetDirectoryName(SettingsPath)!); } catch { /* best-effort */ } } /// /// Hugging Face access token (hf_…). Optional — only needed for downloading /// private/gated repos. Stored in the local settings file (per-user, not /// roamed); leave empty for anonymous access to public repos. /// public string HuggingFaceToken { get; set; } = ""; /// /// Local directory where downloaded GGUF models are cached. Defaults to the /// standard Hugging Face cache (%USERPROFILE%\.cache\huggingface\hub) /// so models are shared with llama.cpp and other HF-aware tools. /// public string CacheDirectory { get; set; } = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".cache", "huggingface", "hub"); /// /// Port the local llama server listens on (default 9931). Read once at /// startup when the singleton is created /// (App.OnLaunched), so a changed value takes effect on the next app /// launch. Valid range: 1–65535; out-of-range values fall back to the /// default at startup. /// public int ServerPort { get; set; } = Llama.LlamaManager.DefaultServerPort; /// /// Whether LlamaApp should launch automatically when the user signs in to /// Windows. The authoritative state is the presence of the startup /// shortcut managed by (in the user's Startup /// folder); this value is a persisted hint so the Settings checkbox can /// reflect intent on first open before re-reading the OS state. /// public bool LaunchAtStartup { get; set; } = false; /// /// Whether the one-time first-run hint ("LlamaApp lives in the system /// tray; Alt+Space opens the chat overlay") has been shown. Persisted so /// the toast fires exactly once, on the first launch. /// public bool TrayHintShown { get; set; } = false; private static Settings Load() { try { if (File.Exists(SettingsPath)) { var json = File.ReadAllText(SettingsPath); var s = JsonSerializer.Deserialize(json); if (s != null) return s; } } catch (Exception ex) { // Corrupt or unreadable settings — fall back to defaults rather than // crashing the app. The user can re-enter values in the Settings UI. Common.Log.Warn(ex, "settings load failed; using defaults"); } return new Settings(); } /// /// Persists the current values to settings.json. Best-effort: a /// failure (e.g. disk full) is swallowed and returns false rather than /// surfacing in the UI flow. /// public bool Save() { try { var json = JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true, }); File.WriteAllText(SettingsPath, json); return true; } catch (Exception ex) { Common.Log.Warn(ex, "settings save failed"); return false; } } }