[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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user