Files
AX-Copilot/src/AxCopilot/Views/ChatWindow.CustomPresets.cs
lacvet aa907d7b79 [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>
2026-04-03 20:51:26 +09:00

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();
}
}