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;
}
}
}