## 분할 대상 및 결과 ### 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>
204 lines
7.2 KiB
C#
204 lines
7.2 KiB
C#
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Controls.Primitives;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using AxCopilot.Models;
|
|
using AxCopilot.Services;
|
|
using AxCopilot.Services.Agent;
|
|
|
|
namespace AxCopilot.Views;
|
|
|
|
public partial class ChatWindow
|
|
{
|
|
// ─── 커스텀 프리셋 관리 ─────────────────────────────────────────────
|
|
|
|
/// <summary>커스텀 프리셋 추가 다이얼로그를 표시합니다.</summary>
|
|
private void ShowCustomPresetDialog(Models.CustomPresetEntry? existing = null)
|
|
{
|
|
bool isEdit = existing != null;
|
|
var dlg = new CustomPresetDialog(
|
|
existingName: existing?.Label ?? "",
|
|
existingDesc: existing?.Description ?? "",
|
|
existingPrompt: existing?.SystemPrompt ?? "",
|
|
existingColor: existing?.Color ?? "#6366F1",
|
|
existingSymbol: existing?.Symbol ?? "\uE713",
|
|
existingTab: existing?.Tab ?? _activeTab)
|
|
{
|
|
Owner = this,
|
|
};
|
|
|
|
if (dlg.ShowDialog() == true)
|
|
{
|
|
if (isEdit)
|
|
{
|
|
existing!.Label = dlg.PresetName;
|
|
existing.Description = dlg.PresetDescription;
|
|
existing.SystemPrompt = dlg.PresetSystemPrompt;
|
|
existing.Color = dlg.PresetColor;
|
|
existing.Symbol = dlg.PresetSymbol;
|
|
existing.Tab = dlg.PresetTab;
|
|
}
|
|
else
|
|
{
|
|
Llm.CustomPresets.Add(new Models.CustomPresetEntry
|
|
{
|
|
Label = dlg.PresetName,
|
|
Description = dlg.PresetDescription,
|
|
SystemPrompt = dlg.PresetSystemPrompt,
|
|
Color = dlg.PresetColor,
|
|
Symbol = dlg.PresetSymbol,
|
|
Tab = dlg.PresetTab,
|
|
});
|
|
}
|
|
_settings.Save();
|
|
BuildTopicButtons();
|
|
}
|
|
}
|
|
|
|
/// <summary>커스텀 프리셋 우클릭 컨텍스트 메뉴를 표시합니다.</summary>
|
|
private void ShowCustomPresetContextMenu(Border? anchor, Services.TopicPreset preset)
|
|
{
|
|
if (anchor == null || preset.CustomId == null) return;
|
|
|
|
var popup = new System.Windows.Controls.Primitives.Popup
|
|
{
|
|
PlacementTarget = anchor,
|
|
Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom,
|
|
StaysOpen = false,
|
|
AllowsTransparency = true,
|
|
};
|
|
|
|
var menuBg = ThemeResourceHelper.Background(this);
|
|
var primaryText = ThemeResourceHelper.Primary(this);
|
|
var secondaryText = ThemeResourceHelper.Secondary(this);
|
|
var borderBrush = ThemeResourceHelper.Border(this);
|
|
|
|
var menuBorder = new Border
|
|
{
|
|
Background = menuBg,
|
|
CornerRadius = new CornerRadius(10),
|
|
BorderBrush = borderBrush,
|
|
BorderThickness = new Thickness(1),
|
|
Padding = new Thickness(4),
|
|
MinWidth = 120,
|
|
Effect = new System.Windows.Media.Effects.DropShadowEffect
|
|
{
|
|
BlurRadius = 12, ShadowDepth = 2, Opacity = 0.3, Color = Colors.Black,
|
|
},
|
|
};
|
|
|
|
var stack = new StackPanel();
|
|
|
|
// 편집 버튼
|
|
var editItem = CreateContextMenuItem("\uE70F", "편집", primaryText, secondaryText);
|
|
editItem.MouseLeftButtonDown += (_, _) =>
|
|
{
|
|
popup.IsOpen = false;
|
|
var entry = Llm.CustomPresets.FirstOrDefault(c => c.Id == preset.CustomId);
|
|
if (entry != null) ShowCustomPresetDialog(entry);
|
|
};
|
|
stack.Children.Add(editItem);
|
|
|
|
// 삭제 버튼
|
|
var deleteItem = CreateContextMenuItem("\uE74D", "삭제", new SolidColorBrush(Color.FromRgb(0xEF, 0x44, 0x44)), secondaryText);
|
|
deleteItem.MouseLeftButtonDown += (_, _) =>
|
|
{
|
|
popup.IsOpen = false;
|
|
var result = CustomMessageBox.Show(
|
|
$"'{preset.Label}' 프리셋을 삭제하시겠습니까?",
|
|
"프리셋 삭제", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
|
if (result == MessageBoxResult.Yes)
|
|
{
|
|
Llm.CustomPresets.RemoveAll(c => c.Id == preset.CustomId);
|
|
_settings.Save();
|
|
BuildTopicButtons();
|
|
}
|
|
};
|
|
stack.Children.Add(deleteItem);
|
|
|
|
menuBorder.Child = stack;
|
|
popup.Child = menuBorder;
|
|
popup.IsOpen = true;
|
|
}
|
|
|
|
/// <summary>컨텍스트 메뉴 항목을 생성합니다.</summary>
|
|
private Border CreateContextMenuItem(string icon, string label, Brush fg, Brush secondaryFg)
|
|
{
|
|
var item = new Border
|
|
{
|
|
Background = Brushes.Transparent,
|
|
CornerRadius = new CornerRadius(6),
|
|
Padding = new Thickness(10, 6, 14, 6),
|
|
Cursor = Cursors.Hand,
|
|
};
|
|
|
|
var sp = new StackPanel { Orientation = Orientation.Horizontal };
|
|
sp.Children.Add(new TextBlock
|
|
{
|
|
Text = icon,
|
|
FontFamily = ThemeResourceHelper.SegoeMdl2,
|
|
FontSize = 13, Foreground = fg,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
Margin = new Thickness(0, 0, 8, 0),
|
|
});
|
|
sp.Children.Add(new TextBlock
|
|
{
|
|
Text = label, FontSize = 13, Foreground = fg,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
});
|
|
item.Child = sp;
|
|
|
|
var hoverBg = ThemeResourceHelper.HoverBg(this);
|
|
item.MouseEnter += (s, _) => { if (s is Border b) b.Background = hoverBg; };
|
|
item.MouseLeave += (s, _) => { if (s is Border b) b.Background = Brushes.Transparent; };
|
|
|
|
return item;
|
|
}
|
|
|
|
/// <summary>대화 주제 선택 — 프리셋 시스템 프롬프트 + 카테고리 적용.</summary>
|
|
private void SelectTopic(Services.TopicPreset preset)
|
|
{
|
|
bool hasMessages;
|
|
lock (_convLock) hasMessages = _currentConversation?.Messages.Count > 0;
|
|
|
|
// 입력란에 텍스트가 있으면 기존 대화를 유지 (입력 내용 보존)
|
|
bool hasInput = !string.IsNullOrEmpty(InputBox.Text);
|
|
bool keepConversation = hasMessages || hasInput;
|
|
|
|
if (!keepConversation)
|
|
{
|
|
// 메시지도 입력 텍스트도 없으면 새 대화 시작
|
|
StartNewConversation();
|
|
}
|
|
|
|
// 프리셋 적용 (기존 대화에도 프리셋 변경 가능)
|
|
lock (_convLock)
|
|
{
|
|
if (_currentConversation != null)
|
|
{
|
|
_currentConversation.SystemCommand = preset.SystemPrompt;
|
|
_currentConversation.Category = preset.Category;
|
|
}
|
|
}
|
|
|
|
if (!keepConversation)
|
|
EmptyState.Visibility = Visibility.Collapsed;
|
|
|
|
InputBox.Focus();
|
|
|
|
if (!string.IsNullOrEmpty(preset.Placeholder))
|
|
{
|
|
_promptCardPlaceholder = preset.Placeholder;
|
|
if (!keepConversation) ShowPlaceholder();
|
|
}
|
|
|
|
if (keepConversation)
|
|
ShowToast($"프리셋 변경: {preset.Label}");
|
|
|
|
// Cowork 탭: 하단 바 갱신
|
|
if (_activeTab == "Cowork")
|
|
BuildBottomBar();
|
|
}
|
|
}
|