267 lines
5.4 KiB
C#
267 lines
5.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text.Json;
|
|
using System.Threading.Tasks;
|
|
using AxCopilot.Models;
|
|
|
|
namespace AxCopilot.Services;
|
|
|
|
internal static class UsageStatisticsService
|
|
{
|
|
private static readonly string _statsDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "stats");
|
|
|
|
private static DailyUsageStats _today = new DailyUsageStats();
|
|
|
|
private static bool _initialized;
|
|
|
|
private static readonly object _lock = new object();
|
|
|
|
private const int RetentionDays = 30;
|
|
|
|
public static void RecordLauncherOpen()
|
|
{
|
|
EnsureInitialized();
|
|
lock (_lock)
|
|
{
|
|
_today.LauncherOpens++;
|
|
}
|
|
SaveTodayAsync();
|
|
}
|
|
|
|
public static void RecordCommandUsage(string commandKey)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(commandKey))
|
|
{
|
|
EnsureInitialized();
|
|
lock (_lock)
|
|
{
|
|
_today.CommandUsage.TryGetValue(commandKey, out var value);
|
|
_today.CommandUsage[commandKey] = value + 1;
|
|
}
|
|
SaveTodayAsync();
|
|
}
|
|
}
|
|
|
|
public static void AddActiveSeconds(int seconds)
|
|
{
|
|
if (seconds > 0)
|
|
{
|
|
EnsureInitialized();
|
|
lock (_lock)
|
|
{
|
|
_today.ActiveSeconds += seconds;
|
|
}
|
|
SaveTodayAsync();
|
|
}
|
|
}
|
|
|
|
public static int GetTodayActiveSeconds()
|
|
{
|
|
EnsureInitialized();
|
|
lock (_lock)
|
|
{
|
|
return _today.ActiveSeconds;
|
|
}
|
|
}
|
|
|
|
public static void RecordChat(string tab)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(tab))
|
|
{
|
|
tab = "Chat";
|
|
}
|
|
EnsureInitialized();
|
|
lock (_lock)
|
|
{
|
|
_today.ChatCounts.TryGetValue(tab, out var value);
|
|
_today.ChatCounts[tab] = value + 1;
|
|
}
|
|
SaveTodayAsync();
|
|
}
|
|
|
|
public static void RecordTokens(int promptTokens, int completionTokens)
|
|
{
|
|
if (promptTokens > 0 || completionTokens > 0)
|
|
{
|
|
EnsureInitialized();
|
|
lock (_lock)
|
|
{
|
|
_today.PromptTokens += promptTokens;
|
|
_today.CompletionTokens += completionTokens;
|
|
_today.TotalTokens += promptTokens + completionTokens;
|
|
}
|
|
SaveTodayAsync();
|
|
}
|
|
}
|
|
|
|
public static List<DailyUsageStats> GetStats(int days = 30)
|
|
{
|
|
EnsureInitialized();
|
|
List<DailyUsageStats> list = new List<DailyUsageStats>();
|
|
DateTime today = DateTime.Today;
|
|
for (int num = days - 1; num >= 0; num--)
|
|
{
|
|
string text = today.AddDays(-num).ToString("yyyy-MM-dd");
|
|
if (text == _today.Date)
|
|
{
|
|
DailyUsageStats item;
|
|
lock (_lock)
|
|
{
|
|
item = CloneToday();
|
|
}
|
|
list.Add(item);
|
|
}
|
|
else
|
|
{
|
|
string filePath = GetFilePath(text);
|
|
if (File.Exists(filePath))
|
|
{
|
|
try
|
|
{
|
|
string json = File.ReadAllText(filePath);
|
|
DailyUsageStats dailyUsageStats = JsonSerializer.Deserialize<DailyUsageStats>(json);
|
|
if (dailyUsageStats != null)
|
|
{
|
|
list.Add(dailyUsageStats);
|
|
}
|
|
else
|
|
{
|
|
list.Add(new DailyUsageStats
|
|
{
|
|
Date = text
|
|
});
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
list.Add(new DailyUsageStats
|
|
{
|
|
Date = text
|
|
});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
list.Add(new DailyUsageStats
|
|
{
|
|
Date = text
|
|
});
|
|
}
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
private static void EnsureInitialized()
|
|
{
|
|
if (_initialized)
|
|
{
|
|
return;
|
|
}
|
|
lock (_lock)
|
|
{
|
|
if (_initialized)
|
|
{
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
Directory.CreateDirectory(_statsDir);
|
|
string text = DateTime.Today.ToString("yyyy-MM-dd");
|
|
string filePath = GetFilePath(text);
|
|
if (File.Exists(filePath))
|
|
{
|
|
string json = File.ReadAllText(filePath);
|
|
DailyUsageStats dailyUsageStats = JsonSerializer.Deserialize<DailyUsageStats>(json);
|
|
_today = dailyUsageStats ?? new DailyUsageStats
|
|
{
|
|
Date = text
|
|
};
|
|
}
|
|
else
|
|
{
|
|
_today = new DailyUsageStats
|
|
{
|
|
Date = text
|
|
};
|
|
}
|
|
PurgeOldFiles();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogService.Warn("통계 서비스 초기화 실패: " + ex.Message);
|
|
_today = new DailyUsageStats
|
|
{
|
|
Date = DateTime.Today.ToString("yyyy-MM-dd")
|
|
};
|
|
}
|
|
_initialized = true;
|
|
}
|
|
}
|
|
|
|
private static void PurgeOldFiles()
|
|
{
|
|
try
|
|
{
|
|
DateTime dateTime = DateTime.Today.AddDays(-30.0);
|
|
string[] files = Directory.GetFiles(_statsDir, "????-??-??.json");
|
|
foreach (string path in files)
|
|
{
|
|
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);
|
|
if (DateTime.TryParse(fileNameWithoutExtension, out var result) && result < dateTime)
|
|
{
|
|
File.Delete(path);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogService.Warn("통계 파일 정리 실패: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
private static async Task SaveTodayAsync()
|
|
{
|
|
try
|
|
{
|
|
DailyUsageStats snapshot;
|
|
lock (_lock)
|
|
{
|
|
snapshot = CloneToday();
|
|
}
|
|
string path = GetFilePath(snapshot.Date);
|
|
string json = JsonSerializer.Serialize(snapshot, new JsonSerializerOptions
|
|
{
|
|
WriteIndented = false
|
|
});
|
|
await File.WriteAllTextAsync(path, json);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Exception ex2 = ex;
|
|
LogService.Warn("통계 저장 실패: " + ex2.Message);
|
|
}
|
|
}
|
|
|
|
private static DailyUsageStats CloneToday()
|
|
{
|
|
return new DailyUsageStats
|
|
{
|
|
Date = _today.Date,
|
|
LauncherOpens = _today.LauncherOpens,
|
|
ActiveSeconds = _today.ActiveSeconds,
|
|
CommandUsage = new Dictionary<string, int>(_today.CommandUsage),
|
|
ChatCounts = new Dictionary<string, int>(_today.ChatCounts),
|
|
TotalTokens = _today.TotalTokens,
|
|
PromptTokens = _today.PromptTokens,
|
|
CompletionTokens = _today.CompletionTokens
|
|
};
|
|
}
|
|
|
|
private static string GetFilePath(string dateStr)
|
|
{
|
|
return Path.Combine(_statsDir, dateStr + ".json");
|
|
}
|
|
}
|