508 lines
15 KiB
C#
508 lines
15 KiB
C#
using System;
|
|
using System.CodeDom.Compiler;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Controls.Primitives;
|
|
using System.Windows.Input;
|
|
using System.Windows.Markup;
|
|
using System.Windows.Media;
|
|
using System.Windows.Shapes;
|
|
using AxCopilot.Services;
|
|
|
|
namespace AxCopilot.Views;
|
|
|
|
public class AgentStatsDashboardWindow : Window, IComponentConnector
|
|
{
|
|
private int _currentDays = 1;
|
|
|
|
internal Border BtnClose;
|
|
|
|
internal Border FilterToday;
|
|
|
|
internal Border Filter7d;
|
|
|
|
internal Border Filter30d;
|
|
|
|
internal Border FilterAll;
|
|
|
|
internal UniformGrid SummaryCards;
|
|
|
|
internal TextBlock DailyChartTitle;
|
|
|
|
internal Canvas DailyBarChart;
|
|
|
|
internal Canvas DailyBarLabels;
|
|
|
|
internal StackPanel ToolFreqPanel;
|
|
|
|
internal StackPanel TabBreakPanel;
|
|
|
|
internal StackPanel ModelBreakPanel;
|
|
|
|
internal TextBlock StatusText;
|
|
|
|
private bool _contentLoaded;
|
|
|
|
public AgentStatsDashboardWindow()
|
|
{
|
|
InitializeComponent();
|
|
base.Loaded += delegate
|
|
{
|
|
LoadStats(_currentDays);
|
|
};
|
|
}
|
|
|
|
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
//IL_0003: Unknown result type (might be due to invalid IL or missing references)
|
|
//IL_0008: Unknown result type (might be due to invalid IL or missing references)
|
|
Point position = e.GetPosition(this);
|
|
if (!(((Point)(ref position)).X > base.ActualWidth - 50.0))
|
|
{
|
|
if (e.ClickCount == 2)
|
|
{
|
|
base.WindowState = ((base.WindowState != WindowState.Maximized) ? WindowState.Maximized : WindowState.Normal);
|
|
}
|
|
else
|
|
{
|
|
DragMove();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void BtnClose_Click(object sender, MouseButtonEventArgs e)
|
|
{
|
|
Close();
|
|
}
|
|
|
|
private void Filter_Click(object sender, MouseButtonEventArgs e)
|
|
{
|
|
if (sender is Border { Tag: string tag } border && int.TryParse(tag, out var result))
|
|
{
|
|
_currentDays = result;
|
|
UpdateFilterButtons(border);
|
|
LoadStats(result);
|
|
}
|
|
}
|
|
|
|
private void BtnClearStats_Click(object sender, MouseButtonEventArgs e)
|
|
{
|
|
MessageBoxResult messageBoxResult = CustomMessageBox.Show("모든 통계 데이터를 삭제하시겠습니까?\n이 작업은 되돌릴 수 없습니다.", "통계 초기화", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
|
if (messageBoxResult == MessageBoxResult.Yes)
|
|
{
|
|
AgentStatsService.Clear();
|
|
LoadStats(_currentDays);
|
|
}
|
|
}
|
|
|
|
private void LoadStats(int days)
|
|
{
|
|
AgentStatsService.AgentStatsSummary s = AgentStatsService.Aggregate(days);
|
|
RenderSummaryCards(s);
|
|
RenderDailyChart(s, days);
|
|
RenderToolFreq(s);
|
|
RenderBreakdowns(s);
|
|
UpdateStatus(s, days);
|
|
}
|
|
|
|
private void RenderSummaryCards(AgentStatsService.AgentStatsSummary s)
|
|
{
|
|
SummaryCards.Children.Clear();
|
|
SummaryCards.Children.Add(MakeSummaryCard("\ue9d9", "총 실행", s.TotalSessions.ToString("N0"), "#A78BFA"));
|
|
SummaryCards.Children.Add(MakeSummaryCard("\ue8a7", "도구 호출", s.TotalToolCalls.ToString("N0"), "#3B82F6"));
|
|
SummaryCards.Children.Add(MakeSummaryCard("\ue7c8", "총 토큰", FormatTokens(s.TotalTokens), "#10B981"));
|
|
SummaryCards.Children.Add(MakeSummaryCard("\ue916", "평균 시간", (s.TotalSessions > 0) ? $"{TimeSpan.FromMilliseconds((double)s.TotalDurationMs / (double)s.TotalSessions):mm\\:ss}" : "—", "#F59E0B"));
|
|
}
|
|
|
|
private Border MakeSummaryCard(string icon, string label, string value, string colorHex)
|
|
{
|
|
Brush background = (TryFindResource("ItemBackground") as Brush) ?? new SolidColorBrush(Color.FromRgb(42, 42, 64));
|
|
Brush foreground = (TryFindResource("PrimaryText") as Brush) ?? Brushes.White;
|
|
Brush foreground2 = (TryFindResource("SecondaryText") as Brush) ?? Brushes.Gray;
|
|
Color color = (Color)ColorConverter.ConvertFromString(colorHex);
|
|
Border border = new Border
|
|
{
|
|
Background = background,
|
|
CornerRadius = new CornerRadius(10.0),
|
|
Padding = new Thickness(14.0, 12.0, 14.0, 12.0),
|
|
Margin = new Thickness(0.0, 0.0, 8.0, 0.0)
|
|
};
|
|
StackPanel stackPanel = new StackPanel();
|
|
stackPanel.Children.Add(new TextBlock
|
|
{
|
|
Text = icon,
|
|
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
|
FontSize = 16.0,
|
|
Foreground = new SolidColorBrush(color),
|
|
Margin = new Thickness(0.0, 0.0, 0.0, 6.0)
|
|
});
|
|
stackPanel.Children.Add(new TextBlock
|
|
{
|
|
Text = value,
|
|
FontSize = 20.0,
|
|
FontWeight = FontWeights.Bold,
|
|
Foreground = foreground
|
|
});
|
|
stackPanel.Children.Add(new TextBlock
|
|
{
|
|
Text = label,
|
|
FontSize = 11.0,
|
|
Foreground = foreground2,
|
|
Margin = new Thickness(0.0, 2.0, 0.0, 0.0)
|
|
});
|
|
border.Child = stackPanel;
|
|
return border;
|
|
}
|
|
|
|
private void RenderDailyChart(AgentStatsService.AgentStatsSummary s, int days)
|
|
{
|
|
DailyBarChart.Children.Clear();
|
|
DailyBarLabels.Children.Clear();
|
|
DateTime endDate = DateTime.Today;
|
|
int chartDays = days switch
|
|
{
|
|
0 => 30,
|
|
1 => 1,
|
|
_ => days,
|
|
};
|
|
List<DateTime> list = (from i in Enumerable.Range(0, chartDays)
|
|
select endDate.AddDays(-(chartDays - 1 - i))).ToList();
|
|
int num = list.Select((DateTime d) => s.DailySessions.GetValueOrDefault(d.ToString("yyyy-MM-dd"), 0)).DefaultIfEmpty(0).Max();
|
|
if (num == 0)
|
|
{
|
|
num = 1;
|
|
}
|
|
Color color = (Color)ColorConverter.ConvertFromString("#4B5EFC");
|
|
double num2 = 748.0;
|
|
double num3 = 2.0;
|
|
double num4 = Math.Max(4.0, (num2 - num3 * (double)list.Count) / (double)list.Count);
|
|
double num5 = 100.0;
|
|
for (int num6 = 0; num6 < list.Count; num6++)
|
|
{
|
|
string text = list[num6].ToString("yyyy-MM-dd");
|
|
int valueOrDefault = s.DailySessions.GetValueOrDefault(text, 0);
|
|
double num7 = Math.Max(2.0, (double)valueOrDefault / (double)num * (num5 - 4.0));
|
|
double length = (double)num6 * (num4 + num3);
|
|
Rectangle element = new Rectangle
|
|
{
|
|
Width = num4,
|
|
Height = num7,
|
|
Fill = new SolidColorBrush(Color.FromArgb(204, color.R, color.G, color.B)),
|
|
RadiusX = 3.0,
|
|
RadiusY = 3.0,
|
|
ToolTip = $"{text}: {valueOrDefault}회"
|
|
};
|
|
Canvas.SetLeft(element, length);
|
|
Canvas.SetTop(element, num5 - num7);
|
|
DailyBarChart.Children.Add(element);
|
|
if (list.Count <= 7 || list[num6].DayOfWeek == DayOfWeek.Monday)
|
|
{
|
|
TextBlock textBlock = new TextBlock();
|
|
textBlock.Text = list[num6].ToString("MM/dd");
|
|
textBlock.FontSize = 9.0;
|
|
textBlock.Foreground = (TryFindResource("SecondaryText") as Brush) ?? Brushes.Gray;
|
|
TextBlock element2 = textBlock;
|
|
Canvas.SetLeft(element2, length);
|
|
DailyBarLabels.Children.Add(element2);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RenderToolFreq(AgentStatsService.AgentStatsSummary s)
|
|
{
|
|
ToolFreqPanel.Children.Clear();
|
|
if (s.ToolFrequency.Count == 0)
|
|
{
|
|
ToolFreqPanel.Children.Add(new TextBlock
|
|
{
|
|
Text = "데이터 없음",
|
|
FontSize = 12.0,
|
|
Foreground = ((TryFindResource("SecondaryText") as Brush) ?? Brushes.Gray)
|
|
});
|
|
return;
|
|
}
|
|
int num = s.ToolFrequency.Values.Max();
|
|
Color color = (Color)ColorConverter.ConvertFromString("#3B82F6");
|
|
foreach (KeyValuePair<string, int> item in s.ToolFrequency)
|
|
{
|
|
item.Deconstruct(out var key, out var value);
|
|
string text = key;
|
|
int num2 = value;
|
|
double num3 = (double)num2 / (double)num;
|
|
Grid grid = new Grid
|
|
{
|
|
Margin = new Thickness(0.0, 2.0, 0.0, 2.0)
|
|
};
|
|
grid.ColumnDefinitions.Add(new ColumnDefinition
|
|
{
|
|
Width = new GridLength(120.0)
|
|
});
|
|
grid.ColumnDefinitions.Add(new ColumnDefinition());
|
|
grid.ColumnDefinitions.Add(new ColumnDefinition
|
|
{
|
|
Width = new GridLength(36.0)
|
|
});
|
|
TextBlock textBlock = new TextBlock();
|
|
textBlock.Text = text;
|
|
textBlock.FontSize = 11.0;
|
|
textBlock.FontFamily = new FontFamily("Consolas");
|
|
textBlock.Foreground = (TryFindResource("PrimaryText") as Brush) ?? Brushes.White;
|
|
textBlock.TextTrimming = TextTrimming.CharacterEllipsis;
|
|
textBlock.VerticalAlignment = VerticalAlignment.Center;
|
|
TextBlock element = textBlock;
|
|
Grid.SetColumn(element, 0);
|
|
Border border = new Border
|
|
{
|
|
Background = new SolidColorBrush(Color.FromArgb(32, 59, 130, 246)),
|
|
CornerRadius = new CornerRadius(3.0),
|
|
Height = 10.0,
|
|
Margin = new Thickness(6.0, 0.0, 4.0, 0.0),
|
|
VerticalAlignment = VerticalAlignment.Center
|
|
};
|
|
Border border2 = new Border
|
|
{
|
|
Background = new SolidColorBrush(Color.FromArgb(204, color.R, color.G, color.B)),
|
|
CornerRadius = new CornerRadius(3.0),
|
|
Height = 10.0,
|
|
HorizontalAlignment = HorizontalAlignment.Left
|
|
};
|
|
border2.Width = num3 * 150.0;
|
|
border.Child = border2;
|
|
Grid.SetColumn(border, 1);
|
|
textBlock = new TextBlock();
|
|
textBlock.Text = num2.ToString();
|
|
textBlock.FontSize = 11.0;
|
|
textBlock.FontWeight = FontWeights.SemiBold;
|
|
textBlock.Foreground = (TryFindResource("SecondaryText") as Brush) ?? Brushes.Gray;
|
|
textBlock.HorizontalAlignment = HorizontalAlignment.Right;
|
|
textBlock.VerticalAlignment = VerticalAlignment.Center;
|
|
TextBlock element2 = textBlock;
|
|
Grid.SetColumn(element2, 2);
|
|
grid.Children.Add(element);
|
|
grid.Children.Add(border);
|
|
grid.Children.Add(element2);
|
|
ToolFreqPanel.Children.Add(grid);
|
|
}
|
|
}
|
|
|
|
private void RenderBreakdowns(AgentStatsService.AgentStatsSummary s)
|
|
{
|
|
TabBreakPanel.Children.Clear();
|
|
Dictionary<string, string> dictionary = new Dictionary<string, string>
|
|
{
|
|
["Cowork"] = "#A78BFA",
|
|
["Code"] = "#34D399",
|
|
["Chat"] = "#3B82F6"
|
|
};
|
|
string key;
|
|
int value;
|
|
foreach (KeyValuePair<string, int> item in s.TabBreakdown.OrderByDescending<KeyValuePair<string, int>, int>((KeyValuePair<string, int> kv) => kv.Value))
|
|
{
|
|
item.Deconstruct(out key, out value);
|
|
string text = key;
|
|
int count = value;
|
|
string valueOrDefault = dictionary.GetValueOrDefault(text, "#9CA3AF");
|
|
TabBreakPanel.Children.Add(MakeBreakRow(text, count, valueOrDefault, s.TabBreakdown.Values.DefaultIfEmpty(1).Max()));
|
|
}
|
|
if (s.TabBreakdown.Count == 0)
|
|
{
|
|
TabBreakPanel.Children.Add(MakeEmptyText());
|
|
}
|
|
ModelBreakPanel.Children.Clear();
|
|
foreach (KeyValuePair<string, int> item2 in s.ModelBreakdown.OrderByDescending<KeyValuePair<string, int>, int>((KeyValuePair<string, int> kv) => kv.Value))
|
|
{
|
|
item2.Deconstruct(out key, out value);
|
|
string text2 = key;
|
|
int count2 = value;
|
|
string label = ((text2.Length > 18) ? (text2.Substring(0, 18) + "…") : text2);
|
|
ModelBreakPanel.Children.Add(MakeBreakRow(label, count2, "#F59E0B", s.ModelBreakdown.Values.DefaultIfEmpty(1).Max()));
|
|
}
|
|
if (s.ModelBreakdown.Count == 0)
|
|
{
|
|
ModelBreakPanel.Children.Add(MakeEmptyText());
|
|
}
|
|
}
|
|
|
|
private UIElement MakeBreakRow(string label, int count, string colorHex, int maxVal)
|
|
{
|
|
double num = ((maxVal > 0) ? ((double)count / (double)maxVal) : 0.0);
|
|
Color color = (Color)ColorConverter.ConvertFromString(colorHex);
|
|
StackPanel stackPanel = new StackPanel
|
|
{
|
|
Margin = new Thickness(0.0, 2.0, 0.0, 2.0)
|
|
};
|
|
Grid grid = new Grid();
|
|
grid.ColumnDefinitions.Add(new ColumnDefinition());
|
|
grid.ColumnDefinitions.Add(new ColumnDefinition
|
|
{
|
|
Width = new GridLength(1.0, GridUnitType.Auto)
|
|
});
|
|
TextBlock textBlock = new TextBlock();
|
|
textBlock.Text = label;
|
|
textBlock.FontSize = 11.0;
|
|
textBlock.Foreground = (TryFindResource("PrimaryText") as Brush) ?? Brushes.White;
|
|
TextBlock element = textBlock;
|
|
Grid.SetColumn(element, 0);
|
|
textBlock = new TextBlock();
|
|
textBlock.Text = count.ToString();
|
|
textBlock.FontSize = 11.0;
|
|
textBlock.FontWeight = FontWeights.SemiBold;
|
|
textBlock.Foreground = (TryFindResource("SecondaryText") as Brush) ?? Brushes.Gray;
|
|
TextBlock element2 = textBlock;
|
|
Grid.SetColumn(element2, 1);
|
|
grid.Children.Add(element);
|
|
grid.Children.Add(element2);
|
|
stackPanel.Children.Add(grid);
|
|
Border border = new Border
|
|
{
|
|
Background = new SolidColorBrush(Color.FromArgb(32, color.R, color.G, color.B)),
|
|
CornerRadius = new CornerRadius(3.0),
|
|
Height = 6.0,
|
|
Margin = new Thickness(0.0, 3.0, 0.0, 0.0)
|
|
};
|
|
Border border2 = new Border
|
|
{
|
|
Background = new SolidColorBrush(Color.FromArgb(204, color.R, color.G, color.B)),
|
|
CornerRadius = new CornerRadius(3.0),
|
|
Height = 6.0,
|
|
HorizontalAlignment = HorizontalAlignment.Left
|
|
};
|
|
border2.Width = num * 160.0;
|
|
border.Child = border2;
|
|
stackPanel.Children.Add(border);
|
|
return stackPanel;
|
|
}
|
|
|
|
private TextBlock MakeEmptyText()
|
|
{
|
|
TextBlock textBlock = new TextBlock();
|
|
textBlock.Text = "데이터 없음";
|
|
textBlock.FontSize = 11.0;
|
|
textBlock.Foreground = (TryFindResource("SecondaryText") as Brush) ?? Brushes.Gray;
|
|
return textBlock;
|
|
}
|
|
|
|
private void UpdateFilterButtons(Border active)
|
|
{
|
|
Border[] array = new Border[4] { FilterToday, Filter7d, Filter30d, FilterAll };
|
|
foreach (Border border in array)
|
|
{
|
|
bool flag = border == active;
|
|
border.Background = (flag ? new SolidColorBrush(Color.FromRgb(75, 94, 252)) : ((TryFindResource("ItemBackground") as Brush) ?? new SolidColorBrush(Color.FromRgb(48, 48, 69))));
|
|
if (border.Child is TextBlock textBlock)
|
|
{
|
|
textBlock.Foreground = (flag ? Brushes.White : ((TryFindResource("SecondaryText") as Brush) ?? Brushes.Gray));
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateStatus(AgentStatsService.AgentStatsSummary s, int days)
|
|
{
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
string text = days switch
|
|
{
|
|
1 => "오늘",
|
|
7 => "최근 7일",
|
|
30 => "최근 30일",
|
|
_ => "전체 기간",
|
|
};
|
|
if (1 == 0)
|
|
{
|
|
}
|
|
string value = text;
|
|
long fileSize = AgentStatsService.GetFileSize();
|
|
StatusText.Text = $"{value} 기준 | 세션 {s.TotalSessions}회, 도구 호출 {s.TotalToolCalls}회, 토큰 {FormatTokens(s.TotalTokens)} | 저장 파일 {FormatFileSize(fileSize)}";
|
|
}
|
|
|
|
private static string FormatTokens(int t)
|
|
{
|
|
return (t >= 1000000) ? $"{(double)t / 1000000.0:F1}M" : ((t >= 1000) ? $"{(double)t / 1000.0:F1}K" : t.ToString());
|
|
}
|
|
|
|
private static string FormatFileSize(long bytes)
|
|
{
|
|
return (bytes >= 1048576) ? $"{(double)bytes / 1048576.0:F1}MB" : ((bytes >= 1024) ? $"{(double)bytes / 1024.0:F1}KB" : $"{bytes}B");
|
|
}
|
|
|
|
[DebuggerNonUserCode]
|
|
[GeneratedCode("PresentationBuildTasks", "10.0.5.0")]
|
|
public void InitializeComponent()
|
|
{
|
|
if (!_contentLoaded)
|
|
{
|
|
_contentLoaded = true;
|
|
Uri resourceLocator = new Uri("/AxCopilot;component/views/agentstatsdashboardwindow.xaml", UriKind.Relative);
|
|
Application.LoadComponent(this, resourceLocator);
|
|
}
|
|
}
|
|
|
|
[DebuggerNonUserCode]
|
|
[GeneratedCode("PresentationBuildTasks", "10.0.5.0")]
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
void IComponentConnector.Connect(int connectionId, object target)
|
|
{
|
|
switch (connectionId)
|
|
{
|
|
case 1:
|
|
((Border)target).MouseLeftButtonDown += TitleBar_MouseLeftButtonDown;
|
|
break;
|
|
case 2:
|
|
BtnClose = (Border)target;
|
|
BtnClose.MouseLeftButtonUp += BtnClose_Click;
|
|
break;
|
|
case 3:
|
|
FilterToday = (Border)target;
|
|
FilterToday.MouseLeftButtonUp += Filter_Click;
|
|
break;
|
|
case 4:
|
|
Filter7d = (Border)target;
|
|
Filter7d.MouseLeftButtonUp += Filter_Click;
|
|
break;
|
|
case 5:
|
|
Filter30d = (Border)target;
|
|
Filter30d.MouseLeftButtonUp += Filter_Click;
|
|
break;
|
|
case 6:
|
|
FilterAll = (Border)target;
|
|
FilterAll.MouseLeftButtonUp += Filter_Click;
|
|
break;
|
|
case 7:
|
|
((Border)target).MouseLeftButtonUp += BtnClearStats_Click;
|
|
break;
|
|
case 8:
|
|
SummaryCards = (UniformGrid)target;
|
|
break;
|
|
case 9:
|
|
DailyChartTitle = (TextBlock)target;
|
|
break;
|
|
case 10:
|
|
DailyBarChart = (Canvas)target;
|
|
break;
|
|
case 11:
|
|
DailyBarLabels = (Canvas)target;
|
|
break;
|
|
case 12:
|
|
ToolFreqPanel = (StackPanel)target;
|
|
break;
|
|
case 13:
|
|
TabBreakPanel = (StackPanel)target;
|
|
break;
|
|
case 14:
|
|
ModelBreakPanel = (StackPanel)target;
|
|
break;
|
|
case 15:
|
|
StatusText = (TextBlock)target;
|
|
break;
|
|
default:
|
|
_contentLoaded = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|