[Phase46] 대형 파일 분할 리팩터링 2차 — 19개 신규 파셜 파일 생성

## 분할 대상 및 결과

### AgentLoopService.cs (1,334줄 → 846줄)
- AgentLoopService.HtmlReport.cs (151줄): AutoSaveAsHtml, ConvertTextToHtml, EscapeHtml
- AgentLoopService.Verification.cs (349줄): 도구 분류 판별 + RunPostToolVerificationAsync + EmitEvent + CheckDecisionRequired + FormatToolCallSummary

### ChatWindow 분할 (8개 신규 파셜 파일)
- ChatWindow.PlanViewer.cs (474줄): 계획 뷰어 — AddPlanningCard, AddDecisionButtons, CollapseDecisionButtons, ShowPlanButton 등 8개 메서드
- ChatWindow.EventBanner.cs (411줄): AddAgentEventBanner, BuildFileQuickActions
- ChatWindow.TaskDecomposition.cs (1,170줄 → 307줄): RenderSuggestActionChips, BuildFeedbackContext, UpdateProgressBar, BuildDiffView 잔류
- ChatWindow.BottomBar.cs (345줄): BuildBottomBar, BuildCodeBottomBar, ShowLogLevelMenu, ShowLanguageMenu 등
- ChatWindow.MoodMenu.cs (456줄): ShowFormatMenu, ShowMoodMenu, ShowCustomMoodDialog 등
- ChatWindow.CustomPresets.cs (978줄 → 203줄): ShowCustomPresetDialog, SelectTopic 잔류
- ChatWindow.ConversationMenu.cs (255줄): ShowConversationMenu (카테고리/삭제/즐겨찾기 팝업)
- ChatWindow.ConversationTitleEdit.cs (108줄): EnterTitleEditMode

### SettingsViewModel 분할
- SettingsViewModel.LlmProperties.cs (417줄): LLM·에이전트 관련 바인딩 프로퍼티
- SettingsViewModel.Properties.cs (837줄 → 427줄): 기능 토글·테마·스니펫 등 앱 수준 프로퍼티

### TemplateService 분할
- TemplateService.Css.cs (559줄): 11종 CSS 테마 문자열 상수
- TemplateService.cs (734줄 → 179줄): 메서드 로직만 잔류

### PlanViewerWindow 분할
- PlanViewerWindow.StepRenderer.cs (616줄): RenderSteps + SwapSteps + EditStep + 버튼 빌더 9개
- PlanViewerWindow.cs (931줄 → 324줄): Win32/생성자/공개 API 잔류

### App.xaml.cs 분할 (776줄 → 452줄)
- App.Settings.cs (252줄): SetupTrayIcon, OpenSettings, ToggleDockBar, RefreshDockBar, OpenAiChat
- App.Helpers.cs (92줄): LoadAppIcon, IsAutoStartEnabled, SetAutoStart, OnExit

### LlmService.ToolUse.cs 분할 (719줄 → 115줄)
- LlmService.ClaudeTools.cs (180줄): SendClaudeWithToolsAsync, BuildClaudeToolBody
- LlmService.GeminiTools.cs (175줄): SendGeminiWithToolsAsync, BuildGeminiToolBody
- LlmService.OpenAiTools.cs (215줄): SendOpenAiWithToolsAsync, BuildOpenAiToolBody

### SettingsWindow.UI.cs 분할 (802줄 → 310줄)
- SettingsWindow.Storage.cs (167줄): RefreshStorageInfo, BtnStorageCleanup_Click 등
- SettingsWindow.HotkeyUI.cs (127줄): RefreshHotkeyBadges, EnsureHotkeyInCombo, GetKeyName 등
- SettingsWindow.DevMode.cs (90줄): DevModeCheckBox_Checked, UpdateDevModeContentVisibility

## 빌드 결과: 경고 0, 오류 0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-03 20:51:26 +09:00
parent f5a1ba999c
commit aa907d7b79
29 changed files with 5540 additions and 5365 deletions

View File

@@ -449,328 +449,4 @@ public partial class App : System.Windows.Application
ToolTipIcon.Warning);
});
}
private void SetupTrayIcon(PluginHost pluginHost, SettingsService settings)
{
_trayIcon = new NotifyIcon
{
Text = "AX Copilot",
Visible = true,
Icon = LoadAppIcon()
};
// ─── WPF 커스텀 트레이 메뉴 구성 ──────────────────────────────────
_trayMenu = new Views.TrayMenuWindow();
_trayMenu
.AddItem("\uE7C5", "AX Commander 호출하기", () =>
Dispatcher.Invoke(() => _launcher?.Show()))
.AddItem("\uE8BD", "AX Agent 대화하기", () =>
Dispatcher.Invoke(() => OpenAiChat()), out var aiTrayItem)
.AddItem("\uE8A7", "독 바 표시", () =>
Dispatcher.Invoke(() => ToggleDockBar()))
.AddSeparator()
.AddItem("\uE72C", "플러그인 재로드", () =>
{
pluginHost.Reload();
_trayIcon!.ShowBalloonTip(2000, "AX Copilot", "플러그인이 재로드되었습니다.", ToolTipIcon.None);
})
.AddItem("\uE838", "로그 폴더 열기", () =>
{
var logDir = System.IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"AxCopilot", "logs");
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo("explorer.exe", logDir)
{ UseShellExecute = true });
})
.AddItem("\uE9D9", "사용 통계", () =>
Dispatcher.Invoke(() => new StatisticsWindow().Show()))
.AddItem("\uE736", "사용 가이드 문서보기", () =>
{
try { Dispatcher.Invoke(() => new Views.GuideViewerWindow().Show()); }
catch (Exception ex) { LogService.Error($"사용 가이드 열기 실패: {ex.Message}"); }
})
.AddSeparator()
.AddToggleItem("\uE82F", "Windows 시작 시 자동 실행", IsAutoStartEnabled(),
isChecked => SetAutoStart(isChecked), out _, out _)
.AddSeparator()
.AddItem("\uE713", "설정", () =>
Dispatcher.Invoke(OpenSettings))
.AddItem("\uE946", "개발 정보", () =>
Dispatcher.Invoke(() => new AboutWindow().Show()))
.AddItem("\uE711", "종료", () =>
{
_inputListener?.Dispose();
_trayIcon?.Dispose();
Shutdown();
});
// AI 기능 활성화 여부에 따라 메뉴 항목 가시성 동적 업데이트
_trayMenu.Opening += () =>
{
aiTrayItem.Visibility = settings.Settings.AiEnabled
? System.Windows.Visibility.Visible
: System.Windows.Visibility.Collapsed;
};
// 우클릭 → WPF 메뉴 표시, 좌클릭 → 런처 토글
_trayIcon.MouseClick += (_, e) =>
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
Dispatcher.Invoke(() => _launcher?.Show());
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
Dispatcher.Invoke(() => _trayMenu?.ShowWithUpdate());
};
// 타이머/알람 풍선 알림 서비스 연결
NotificationService.Initialize((title, msg) =>
{
Dispatcher.Invoke(() =>
_trayIcon?.ShowBalloonTip(4000, title, msg, ToolTipIcon.None));
});
}
/// <summary>ChatWindow 등 외부에서 설정 창을 여는 공개 메서드.</summary>
public void OpenSettingsFromChat() => Dispatcher.Invoke(OpenSettings);
/// <summary>AX Agent 창 열기 (트레이 메뉴 등에서 호출).</summary>
private Views.ChatWindow? _chatWindow;
/// <summary>
/// ChatWindow를 백그라운드에서 미리 생성합니다 (앱 시작 후 저우선순위로 호출).
/// 이후 OpenAiChat() 시 창 생성 비용 없이 즉시 Show/Activate만 수행합니다.
/// </summary>
internal void PrewarmChatWindow()
{
if (_chatWindow != null || _settings == null) return;
_chatWindow = new Views.ChatWindow(_settings);
}
private void OpenAiChat()
{
if (_settings == null) return;
if (_chatWindow == null)
{
_chatWindow = new Views.ChatWindow(_settings);
}
_chatWindow.Show();
_chatWindow.Activate();
}
public void ToggleDockBar()
{
if (_dockBar != null && _dockBar.IsVisible)
{
_dockBar.Hide();
return;
}
if (_dockBar == null)
{
_dockBar = new DockBarWindow();
_dockBar.OnQuickSearch = query =>
{
if (_launcher == null) return;
_launcher.Show();
_launcher.Activate(); // 독 바 뒤가 아닌 전면에 표시
if (!string.IsNullOrEmpty(query))
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input,
() => _launcher.SetInputText(query));
};
_dockBar.OnCapture = async () =>
{
WindowTracker.Capture();
if (_captureHandler != null)
await _captureHandler.CaptureDirectAsync("region");
};
_dockBar.OnOpenAgent = () =>
{
if (_launcher == null) return;
_launcher.Show();
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input,
() => _launcher.SetInputText("!"));
};
}
var launcher = _settings?.Settings.Launcher;
var dockItems = launcher?.DockBarItems ?? new() { "launcher", "clipboard", "capture", "agent", "clock", "cpu" };
_dockBar.BuildFromSettings(dockItems);
_dockBar.OnPositionChanged = (left, top) =>
{
if (_settings != null)
{
_settings.Settings.Launcher.DockBarLeft = left;
_settings.Settings.Launcher.DockBarTop = top;
_settings.Save();
}
};
_dockBar.Show();
_dockBar.ApplySettings(
launcher?.DockBarOpacity ?? 0.92,
launcher?.DockBarLeft ?? -1,
launcher?.DockBarTop ?? -1,
launcher?.DockBarRainbowGlow ?? false);
}
/// <summary>독 바를 현재 설정으로 즉시 새로고침합니다.</summary>
public void RefreshDockBar()
{
if (_dockBar == null || !_dockBar.IsVisible) return;
var launcher = _settings?.Settings.Launcher;
var dockItems = launcher?.DockBarItems ?? new() { "launcher", "clipboard", "capture", "agent", "clock", "cpu" };
_dockBar.BuildFromSettings(dockItems);
_dockBar.ApplySettings(
launcher?.DockBarOpacity ?? 0.92,
launcher?.DockBarLeft ?? -1,
launcher?.DockBarTop ?? -1,
launcher?.DockBarRainbowGlow ?? false);
}
private void OpenSettings()
{
if (_settingsWindow != null && _settingsWindow.IsVisible)
{
_settingsWindow.Activate();
return;
}
if (_settings == null || _launcher == null) return;
var vm = new ViewModels.SettingsViewModel(_settings);
// 미리보기 콜백: 현재 편집 중인 색상(vm.ColorRows)으로 런처에 즉시 반영
void PreviewCallback(string themeKey)
{
if (themeKey == "custom")
{
var tempColors = new AxCopilot.Models.CustomThemeColors();
foreach (var row in vm.ColorRows)
{
var prop = typeof(AxCopilot.Models.CustomThemeColors).GetProperty(row.Property);
prop?.SetValue(tempColors, row.Hex);
}
_launcher.ApplyTheme(themeKey, tempColors);
}
else
{
_launcher.ApplyTheme(themeKey, _settings.Settings.Launcher.CustomTheme);
}
}
// 취소/X 닫기 콜백: 파일에 저장된 원본 설정으로 복원
void RevertCallback()
{
_launcher.ApplyTheme(
_settings.Settings.Launcher.Theme ?? "system",
_settings.Settings.Launcher.CustomTheme);
}
_settingsWindow = new Views.SettingsWindow(vm, PreviewCallback, RevertCallback)
{
// 핫키 녹화 중 글로벌 핫키 일시 정지
SuspendHotkeyCallback = suspend =>
{
if (_inputListener != null)
_inputListener.SuspendHotkey = suspend;
}
};
// 저장 완료 시 InputListener 핫키 갱신 + 알림 타이머 재시작
vm.SaveCompleted += (_, _) =>
{
if (_inputListener != null && _settings != null)
{
_inputListener.UpdateHotkey(_settings.Settings.Hotkey);
_inputListener.UpdateCaptureHotkey(
_settings.Settings.ScreenCapture.GlobalHotkey,
_settings.Settings.ScreenCapture.GlobalHotkeyEnabled);
}
_worktimeReminder?.RestartTimer();
};
_settingsWindow.Show();
}
// ─── 자동 시작 레지스트리 헬퍼 ──────────────────────────────────────────
private static System.Drawing.Icon LoadAppIcon()
{
// DPI 인식 아이콘 크기 (기본 16 → 고DPI에서 20/24/32)
var iconSize = System.Windows.Forms.SystemInformation.SmallIconSize;
// 1) 파일 시스템에서 로드 (개발 환경)
try
{
var exeDir = System.IO.Path.GetDirectoryName(Environment.ProcessPath)
?? AppContext.BaseDirectory;
var path = System.IO.Path.Combine(exeDir, "Assets", "icon.ico");
if (System.IO.File.Exists(path))
return new System.Drawing.Icon(path, iconSize);
}
catch (Exception) { }
// 2) 내장 리소스에서 로드 (PublishSingleFile 배포)
try
{
var uri = new Uri("pack://application:,,,/Assets/icon.ico");
var stream = System.Windows.Application.GetResourceStream(uri)?.Stream;
if (stream != null)
return new System.Drawing.Icon(stream, iconSize);
}
catch (Exception) { }
return System.Drawing.SystemIcons.Application;
}
private const string AutoRunKey = @"Software\Microsoft\Windows\CurrentVersion\Run";
private const string AutoRunName = "AxCopilot";
private static bool IsAutoStartEnabled()
{
try
{
using var key = Registry.CurrentUser.OpenSubKey(AutoRunKey, writable: false);
return key?.GetValue(AutoRunName) != null;
}
catch (Exception) { return false; }
}
private static void SetAutoStart(bool enable)
{
try
{
using var key = Registry.CurrentUser.OpenSubKey(AutoRunKey, writable: true);
if (key == null) return;
if (enable)
{
var exePath = Environment.ProcessPath
?? System.Diagnostics.Process.GetCurrentProcess().MainModule?.FileName
?? string.Empty;
if (!string.IsNullOrEmpty(exePath))
key.SetValue(AutoRunName, $"\"{exePath}\"");
}
else
{
key.DeleteValue(AutoRunName, throwOnMissingValue: false);
}
}
catch (Exception ex)
{
LogService.Warn($"자동 시작 레지스트리 설정 실패: {ex.Message}");
}
}
protected override void OnExit(ExitEventArgs e)
{
_chatWindow?.ForceClose(); // 미리 생성된 ChatWindow 진짜 닫기
_inputListener?.Dispose();
_clipboardHistory?.Dispose();
_indexService?.Dispose();
_sessionTracking?.Dispose();
_worktimeReminder?.Dispose();
_trayIcon?.Dispose();
try { _singleInstanceMutex?.ReleaseMutex(); } catch (Exception) { }
_singleInstanceMutex?.Dispose();
LogService.Info("=== AX Copilot 종료 ===");
base.OnExit(e);
}
}