Files
AX-Copilot/src/AxCopilot/Views/LauncherWindow.Widgets.cs
lacvet 72b92b905c [Phase L3-9 + 버그] AX Agent 오류 수정 + 런처 미니 위젯 4종 구현
AX Agent 오류 수정:
- AgentSettingsPanel.xaml에 <UserControl.Resources> 추가
- ToggleSwitch 스타일 자체 정의 (SettingsWindow 리소스 미접근 문제 해결)
- 원인: XamlParseException — 'ToggleSwitch' 리소스 찾을 수 없음 (CS 로그 확인)

PerformanceMonitorService.cs (신규, 138줄):
- GetSystemTimes P/Invoke → CPU% (이전/현재 샘플 델타 계산)
- GlobalMemoryStatusEx P/Invoke → RAM% + "6.1/16GB" 형식 텍스트
- DriveInfo → C: 드라이브 사용률/용량 텍스트
- 2초 폴링, StartPolling/StopPolling 제어

PomodoroService.cs (신규, 179줄):
- 집중(25분)/휴식(5분) 타이머, 상태: Idle/Focus/Break
- pomodoro.json 영속성 (경과 시간 자동 보정)
- StateChanged 이벤트 → 위젯 실시간 갱신

ServerStatusService.cs (신규, 124줄):
- Ollama(/api/version), LLM API, 첫 번째 MCP 서버 15초 주기 핑
- HttpClient 1.5초 타임아웃, StatusChanged 이벤트

PomoHandler.cs (신규, 130줄):
- pomo prefix: 상태보기/start/break/stop/reset
- PomodoroService 직접 연동

LauncherViewModel.Widgets.cs (신규, 81줄):
- Widget_PerfText, Widget_PomoText, Widget_PomoRunning
- Widget_NoteText, Widget_OllamaOnline, Widget_LlmOnline, Widget_McpOnline
- UpdateWidgets() — 5틱마다 메모 건수 갱신 (파일 I/O 최소화)

LauncherWindow.Widgets.cs (신규, 143줄):
- IsVisibleChanged 이벤트로 위젯 자동 시작/중지
- DispatcherTimer 1초마다 UpdateWidgets + 서버 상태 dot 색상 직접 갱신
- 위젯 클릭 → 해당 prefix 자동 입력 (perf→info, pomo→pomo, note→note, server→port)

LauncherWindow.xaml:
- RowDefinition 6개 → 7개
- Row 6: 위젯 바 (시스템모니터/뽀모도로/메모/서버 4열)

빌드: 경고 0, 오류 0
2026-04-04 09:28:07 +09:00

144 lines
5.1 KiB
C#

using System.Windows;
using System.Windows.Media;
using System.Windows.Threading;
using AxCopilot.Services;
namespace AxCopilot.Views;
/// <summary>
/// Phase L3-9: 런처 하단 미니 위젯 바 로직.
/// 타이머로 1초마다 ViewModel 갱신 + 서버 상태 dot 색상 직접 업데이트.
/// </summary>
public partial class LauncherWindow
{
private DispatcherTimer? _widgetTimer;
private static readonly SolidColorBrush _dotOnline = new(Color.FromRgb(0x10, 0xB9, 0x81));
private static readonly SolidColorBrush _dotOffline = new(Color.FromRgb(0x9E, 0x9E, 0x9E));
/// <summary>LauncherWindow.xaml.cs의 Show()에서 최초 1회 호출 — IsVisibleChanged로 자동 관리.</summary>
internal void InitWidgets()
{
IsVisibleChanged -= OnLauncherVisibleChanged;
IsVisibleChanged += OnLauncherVisibleChanged;
}
private void OnLauncherVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue) StartWidgetUpdates();
else StopWidgetUpdates();
}
/// <summary>런처가 표시될 때 위젯 업데이트 시작.</summary>
internal void StartWidgetUpdates()
{
var settings = CurrentApp?.SettingsService?.Settings;
// 서비스 시작
PerformanceMonitorService.Instance.StartPolling();
ServerStatusService.Instance.Start(settings);
PomodoroService.Instance.StateChanged -= OnPomoStateChanged;
PomodoroService.Instance.StateChanged += OnPomoStateChanged;
ServerStatusService.Instance.StatusChanged -= OnServerStatusChanged;
ServerStatusService.Instance.StatusChanged += OnServerStatusChanged;
// 메모 건수 즉시 갱신 (최초 1회)
_vm.UpdateWidgets();
UpdateServerDots();
// 1초 타이머
if (_widgetTimer == null)
{
_widgetTimer = new DispatcherTimer(DispatcherPriority.Background)
{
Interval = TimeSpan.FromSeconds(1)
};
_widgetTimer.Tick += (_, _) =>
{
_vm.UpdateWidgets();
UpdateServerDots();
};
}
_widgetTimer.Start();
// 뽀모도로 실행 중이면 위젯 색상 강조
UpdatePomoWidgetStyle();
}
/// <summary>런처가 숨겨질 때 타이머 중지 (뽀모도로는 계속 실행).</summary>
internal void StopWidgetUpdates()
{
_widgetTimer?.Stop();
PerformanceMonitorService.Instance.StopPolling();
PomodoroService.Instance.StateChanged -= OnPomoStateChanged;
ServerStatusService.Instance.StatusChanged -= OnServerStatusChanged;
}
// ─── 이벤트 핸들러 ──────────────────────────────────────────────────────
private void OnPomoStateChanged(object? sender, EventArgs e)
{
Dispatcher.InvokeAsync(() =>
{
_vm.UpdateWidgets();
UpdatePomoWidgetStyle();
});
}
private void OnServerStatusChanged(object? sender, EventArgs e)
{
Dispatcher.InvokeAsync(UpdateServerDots);
}
// ─── UI 업데이트 ────────────────────────────────────────────────────────
private void UpdateServerDots()
{
var srv = ServerStatusService.Instance;
if (OllamaStatusDot != null)
OllamaStatusDot.Fill = srv.OllamaOnline ? _dotOnline : _dotOffline;
if (LlmStatusDot != null)
LlmStatusDot.Fill = srv.LlmOnline ? _dotOnline : _dotOffline;
if (McpStatusDot != null)
McpStatusDot.Fill = srv.McpOnline ? _dotOnline : _dotOffline;
}
private void UpdatePomoWidgetStyle()
{
if (WgtPomo == null) return;
var running = PomodoroService.Instance.IsRunning;
WgtPomo.Background = running
? new SolidColorBrush(Color.FromArgb(0x1E, 0xF5, 0x9E, 0x0B))
: new SolidColorBrush(Color.FromArgb(0x0D, 0xF5, 0x9E, 0x0B));
}
// ─── 위젯 클릭 핸들러 ───────────────────────────────────────────────────
private void WgtPerf_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_vm.InputText = "info ";
InputBox?.Focus();
}
private void WgtPomo_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_vm.InputText = "pomo ";
InputBox?.Focus();
}
private void WgtNote_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_vm.InputText = "note ";
InputBox?.Focus();
}
private void WgtServer_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
// 서버 상태 강제 새로고침
var settings = CurrentApp?.SettingsService?.Settings;
ServerStatusService.Instance.Refresh(settings);
_vm.InputText = "port";
InputBox?.Focus();
}
}