[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:
@@ -200,779 +200,4 @@ public partial class ChatWindow
|
||||
if (_activeTab == "Cowork")
|
||||
BuildBottomBar();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>선택된 디자인 무드 키 (HtmlSkill에서 사용).</summary>
|
||||
private string _selectedMood = null!; // Loaded 이벤트에서 초기화
|
||||
private string _selectedLanguage = "auto"; // Code 탭 개발 언어
|
||||
private string _folderDataUsage = null!; // Loaded 이벤트에서 초기화
|
||||
|
||||
/// <summary>하단 바를 구성합니다 (포맷 + 디자인 드롭다운 버튼).</summary>
|
||||
private void BuildBottomBar()
|
||||
{
|
||||
MoodIconPanel.Children.Clear();
|
||||
|
||||
var secondaryText = ThemeResourceHelper.Secondary(this);
|
||||
|
||||
// ── 포맷 버튼 ──
|
||||
var currentFormat = Llm.DefaultOutputFormat ?? "auto";
|
||||
var formatLabel = GetFormatLabel(currentFormat);
|
||||
var formatBtn = CreateFolderBarButton("\uE9F9", formatLabel, "보고서 형태 선택", "#8B5CF6");
|
||||
formatBtn.MouseLeftButtonUp += (_, e) => { e.Handled = true; ShowFormatMenu(); };
|
||||
// Name 등록 (Popup PlacementTarget용)
|
||||
try { RegisterName("BtnFormatMenu", formatBtn); } catch (Exception) { try { UnregisterName("BtnFormatMenu"); RegisterName("BtnFormatMenu", formatBtn); } catch (Exception) { /* 이름 등록 실패 */ } }
|
||||
MoodIconPanel.Children.Add(formatBtn);
|
||||
|
||||
// 구분선
|
||||
MoodIconPanel.Children.Add(new Border
|
||||
{
|
||||
Width = 1, Height = 18,
|
||||
Background = ThemeResourceHelper.Separator(this),
|
||||
Margin = new Thickness(4, 0, 4, 0),
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
});
|
||||
|
||||
// ── 디자인 버튼 (소극 스타일) ──
|
||||
var currentMood = TemplateService.AllMoods.FirstOrDefault(m => m.Key == _selectedMood);
|
||||
var moodLabel = currentMood?.Label ?? "모던";
|
||||
var moodIcon = currentMood?.Icon ?? "🔷";
|
||||
var moodBtn = CreateFolderBarButton(null, $"{moodIcon} {moodLabel}", "디자인 무드 선택");
|
||||
moodBtn.MouseLeftButtonUp += (_, e) => { e.Handled = true; ShowMoodMenu(); };
|
||||
try { RegisterName("BtnMoodMenu", moodBtn); } catch (Exception) { try { UnregisterName("BtnMoodMenu"); RegisterName("BtnMoodMenu", moodBtn); } catch (Exception) { /* 이름 등록 실패 */ } }
|
||||
MoodIconPanel.Children.Add(moodBtn);
|
||||
|
||||
// 구분선
|
||||
MoodIconPanel.Children.Add(new Border
|
||||
{
|
||||
Width = 1, Height = 18,
|
||||
Background = ThemeResourceHelper.Separator(this),
|
||||
Margin = new Thickness(4, 0, 4, 0),
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
});
|
||||
|
||||
// ── 파일 탐색기 토글 버튼 ──
|
||||
var fileBrowserBtn = CreateFolderBarButton("\uED25", "파일", "파일 탐색기 열기/닫기", "#D97706");
|
||||
fileBrowserBtn.MouseLeftButtonUp += (_, e) => { e.Handled = true; ToggleFileBrowser(); };
|
||||
MoodIconPanel.Children.Add(fileBrowserBtn);
|
||||
|
||||
// ── 실행 이력 상세도 버튼 ──
|
||||
AppendLogLevelButton();
|
||||
|
||||
// 구분선 표시
|
||||
if (FormatMoodSeparator != null) FormatMoodSeparator.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
/// <summary>Code 탭 하단 바: 개발 언어 선택 + 파일 탐색기 토글.</summary>
|
||||
private void BuildCodeBottomBar()
|
||||
{
|
||||
MoodIconPanel.Children.Clear();
|
||||
|
||||
var secondaryText = ThemeResourceHelper.Secondary(this);
|
||||
|
||||
// 개발 언어 선택 버튼
|
||||
var langLabel = _selectedLanguage switch
|
||||
{
|
||||
"python" => "🐍 Python",
|
||||
"java" => "☕ Java",
|
||||
"csharp" => "🔷 C#",
|
||||
"cpp" => "⚙ C++",
|
||||
"javascript" => "🌐 JavaScript",
|
||||
_ => "🔧 자동 감지",
|
||||
};
|
||||
var langBtn = CreateFolderBarButton(null, langLabel, "개발 언어 선택");
|
||||
langBtn.MouseLeftButtonUp += (_, e) => { e.Handled = true; ShowLanguageMenu(); };
|
||||
try { RegisterName("BtnLangMenu", langBtn); } catch (Exception) { try { UnregisterName("BtnLangMenu"); RegisterName("BtnLangMenu", langBtn); } catch (Exception) { /* 이름 등록 실패 */ } }
|
||||
MoodIconPanel.Children.Add(langBtn);
|
||||
|
||||
// 구분선
|
||||
MoodIconPanel.Children.Add(new Border
|
||||
{
|
||||
Width = 1, Height = 18,
|
||||
Background = ThemeResourceHelper.Separator(this),
|
||||
Margin = new Thickness(4, 0, 4, 0),
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
});
|
||||
|
||||
// 파일 탐색기 토글
|
||||
var fileBrowserBtn = CreateFolderBarButton("\uED25", "파일", "파일 탐색기 열기/닫기", "#D97706");
|
||||
fileBrowserBtn.MouseLeftButtonUp += (_, e) => { e.Handled = true; ToggleFileBrowser(); };
|
||||
MoodIconPanel.Children.Add(fileBrowserBtn);
|
||||
|
||||
// ── 실행 이력 상세도 버튼 ──
|
||||
AppendLogLevelButton();
|
||||
|
||||
if (FormatMoodSeparator != null) FormatMoodSeparator.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
/// <summary>하단 바에 실행 이력 상세도 선택 버튼을 추가합니다.</summary>
|
||||
private void AppendLogLevelButton()
|
||||
{
|
||||
// 구분선
|
||||
MoodIconPanel.Children.Add(new Border
|
||||
{
|
||||
Width = 1, Height = 18,
|
||||
Background = ThemeResourceHelper.Separator(this),
|
||||
Margin = new Thickness(4, 0, 4, 0),
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
});
|
||||
|
||||
var currentLevel = Llm.AgentLogLevel ?? "simple";
|
||||
var levelLabel = currentLevel switch
|
||||
{
|
||||
"debug" => "디버그",
|
||||
"detailed" => "상세",
|
||||
_ => "간략",
|
||||
};
|
||||
var logBtn = CreateFolderBarButton("\uE946", levelLabel, "실행 이력 상세도", "#059669");
|
||||
logBtn.MouseLeftButtonUp += (_, e) => { e.Handled = true; ShowLogLevelMenu(); };
|
||||
try { RegisterName("BtnLogLevelMenu", logBtn); } catch (Exception) { try { UnregisterName("BtnLogLevelMenu"); RegisterName("BtnLogLevelMenu", logBtn); } catch (Exception) { /* 이름 등록 실패 */ } }
|
||||
MoodIconPanel.Children.Add(logBtn);
|
||||
}
|
||||
|
||||
/// <summary>실행 이력 상세도 팝업 메뉴를 표시합니다.</summary>
|
||||
private void ShowLogLevelMenu()
|
||||
{
|
||||
FormatMenuItems.Children.Clear();
|
||||
var primaryText = ThemeResourceHelper.Primary(this);
|
||||
var accentBrush = ThemeResourceHelper.Accent(this);
|
||||
var secondaryText = ThemeResourceHelper.Secondary(this);
|
||||
|
||||
var levels = new (string Key, string Label, string Desc)[]
|
||||
{
|
||||
("simple", "Simple (간략)", "도구 결과만 한 줄로 표시"),
|
||||
("detailed", "Detailed (상세)", "도구 호출/결과 + 접이식 상세"),
|
||||
("debug", "Debug (디버그)", "모든 정보 + 파라미터 표시"),
|
||||
};
|
||||
|
||||
var current = Llm.AgentLogLevel ?? "simple";
|
||||
|
||||
foreach (var (key, label, desc) in levels)
|
||||
{
|
||||
var isActive = current == key;
|
||||
var sp = new StackPanel { Orientation = Orientation.Horizontal };
|
||||
sp.Children.Add(CreateCheckIcon(isActive, accentBrush));
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = label,
|
||||
FontSize = 13,
|
||||
Foreground = isActive ? accentBrush : primaryText,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(0, 0, 8, 0),
|
||||
});
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = desc,
|
||||
FontSize = 10,
|
||||
Foreground = secondaryText,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
});
|
||||
|
||||
var item = new Border
|
||||
{
|
||||
Child = sp,
|
||||
Padding = new Thickness(12, 8, 12, 8),
|
||||
CornerRadius = new CornerRadius(6),
|
||||
Background = Brushes.Transparent,
|
||||
Cursor = Cursors.Hand,
|
||||
};
|
||||
var hoverBg = ThemeResourceHelper.HoverBg(this);
|
||||
item.MouseEnter += (s, _) => ((Border)s!).Background = hoverBg;
|
||||
item.MouseLeave += (s, _) => ((Border)s!).Background = Brushes.Transparent;
|
||||
item.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
Llm.AgentLogLevel = key;
|
||||
_settings.Save();
|
||||
FormatMenuPopup.IsOpen = false;
|
||||
if (_activeTab == "Cowork") BuildBottomBar();
|
||||
else if (_activeTab == "Code") BuildCodeBottomBar();
|
||||
};
|
||||
FormatMenuItems.Children.Add(item);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var target = FindName("BtnLogLevelMenu") as UIElement;
|
||||
if (target != null) FormatMenuPopup.PlacementTarget = target;
|
||||
}
|
||||
catch (Exception) { /* 비핵심 작업 실패 — UI 차단 방지 */ }
|
||||
FormatMenuPopup.IsOpen = true;
|
||||
}
|
||||
|
||||
private void ShowLanguageMenu()
|
||||
{
|
||||
FormatMenuItems.Children.Clear();
|
||||
var primaryText = ThemeResourceHelper.Primary(this);
|
||||
var accentBrush = ThemeResourceHelper.Accent(this);
|
||||
|
||||
var languages = new (string Key, string Label, string Icon)[]
|
||||
{
|
||||
("auto", "자동 감지", "🔧"),
|
||||
("python", "Python", "🐍"),
|
||||
("java", "Java", "☕"),
|
||||
("csharp", "C# (.NET)", "🔷"),
|
||||
("cpp", "C/C++", "⚙"),
|
||||
("javascript", "JavaScript / Vue", "🌐"),
|
||||
};
|
||||
|
||||
foreach (var (key, label, icon) in languages)
|
||||
{
|
||||
var isActive = _selectedLanguage == key;
|
||||
var sp = new StackPanel { Orientation = Orientation.Horizontal };
|
||||
sp.Children.Add(CreateCheckIcon(isActive, accentBrush));
|
||||
sp.Children.Add(new TextBlock { Text = icon, FontSize = 13, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 8, 0) });
|
||||
sp.Children.Add(new TextBlock { Text = label, FontSize = 13, Foreground = isActive ? accentBrush : primaryText, FontWeight = isActive ? FontWeights.SemiBold : FontWeights.Normal });
|
||||
|
||||
var itemBorder = new Border
|
||||
{
|
||||
Child = sp, Background = Brushes.Transparent,
|
||||
CornerRadius = new CornerRadius(8), Cursor = Cursors.Hand,
|
||||
Padding = new Thickness(8, 7, 12, 7),
|
||||
};
|
||||
ApplyMenuItemHover(itemBorder);
|
||||
|
||||
var capturedKey = key;
|
||||
itemBorder.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
FormatMenuPopup.IsOpen = false;
|
||||
_selectedLanguage = capturedKey;
|
||||
BuildCodeBottomBar();
|
||||
};
|
||||
FormatMenuItems.Children.Add(itemBorder);
|
||||
}
|
||||
|
||||
if (FindName("BtnLangMenu") is UIElement langTarget)
|
||||
FormatMenuPopup.PlacementTarget = langTarget;
|
||||
FormatMenuPopup.IsOpen = true;
|
||||
}
|
||||
|
||||
/// <summary>폴더바 내 드롭다운 버튼 (소극/적극 스타일과 동일)</summary>
|
||||
private Border CreateFolderBarButton(string? mdlIcon, string label, string tooltip, string? iconColorHex = null)
|
||||
{
|
||||
var secondaryText = ThemeResourceHelper.Secondary(this);
|
||||
var iconColor = iconColorHex != null ? BrushFromHex(iconColorHex) : secondaryText;
|
||||
var sp = new StackPanel { Orientation = Orientation.Horizontal };
|
||||
|
||||
if (mdlIcon != null)
|
||||
{
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = mdlIcon,
|
||||
FontFamily = ThemeResourceHelper.SegoeMdl2,
|
||||
FontSize = 12,
|
||||
Foreground = iconColor,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(0, 0, 4, 0),
|
||||
});
|
||||
}
|
||||
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = label,
|
||||
FontSize = 12,
|
||||
Foreground = secondaryText,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
});
|
||||
|
||||
return new Border
|
||||
{
|
||||
Child = sp,
|
||||
Background = Brushes.Transparent,
|
||||
Padding = new Thickness(6, 4, 6, 4),
|
||||
Cursor = Cursors.Hand,
|
||||
ToolTip = tooltip,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private static string GetFormatLabel(string key) => key switch
|
||||
{
|
||||
"xlsx" => "Excel",
|
||||
"html" => "HTML 보고서",
|
||||
"docx" => "Word",
|
||||
"md" => "Markdown",
|
||||
"csv" => "CSV",
|
||||
_ => "AI 자동",
|
||||
};
|
||||
|
||||
/// <summary>현재 프리셋/카테고리에 맞는 에이전트 이름, 심볼, 색상을 반환합니다.</summary>
|
||||
private (string Name, string Symbol, string Color) GetAgentIdentity()
|
||||
{
|
||||
string? category = null;
|
||||
lock (_convLock)
|
||||
{
|
||||
category = _currentConversation?.Category;
|
||||
}
|
||||
|
||||
return category switch
|
||||
{
|
||||
// Cowork 프리셋 카테고리
|
||||
"보고서" => ("보고서 에이전트", "◆", "#3B82F6"),
|
||||
"데이터" => ("데이터 분석 에이전트", "◆", "#10B981"),
|
||||
"문서" => ("문서 작성 에이전트", "◆", "#6366F1"),
|
||||
"논문" => ("논문 분석 에이전트", "◆", "#6366F1"),
|
||||
"파일" => ("파일 관리 에이전트", "◆", "#8B5CF6"),
|
||||
"자동화" => ("자동화 에이전트", "◆", "#EF4444"),
|
||||
// Code 프리셋 카테고리
|
||||
"코드개발" => ("코드 개발 에이전트", "◆", "#3B82F6"),
|
||||
"리팩터링" => ("리팩터링 에이전트", "◆", "#6366F1"),
|
||||
"코드리뷰" => ("코드 리뷰 에이전트", "◆", "#10B981"),
|
||||
"보안점검" => ("보안 점검 에이전트", "◆", "#EF4444"),
|
||||
"테스트" => ("테스트 에이전트", "◆", "#F59E0B"),
|
||||
// Chat 카테고리
|
||||
"연구개발" => ("연구개발 에이전트", "◆", "#0EA5E9"),
|
||||
"시스템" => ("시스템 에이전트", "◆", "#64748B"),
|
||||
"수율분석" => ("수율분석 에이전트", "◆", "#F59E0B"),
|
||||
"제품분석" => ("제품분석 에이전트", "◆", "#EC4899"),
|
||||
"경영" => ("경영 분석 에이전트", "◆", "#8B5CF6"),
|
||||
"인사" => ("인사 관리 에이전트", "◆", "#14B8A6"),
|
||||
"제조기술" => ("제조기술 에이전트", "◆", "#F97316"),
|
||||
"재무" => ("재무 분석 에이전트", "◆", "#6366F1"),
|
||||
_ when _activeTab == "Code" => ("코드 에이전트", "◆", "#3B82F6"),
|
||||
_ when _activeTab == "Cowork" => ("코워크 에이전트", "◆", "#4B5EFC"),
|
||||
_ => ("AX 에이전트", "◆", "#4B5EFC"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>포맷 선택 팝업 메뉴를 표시합니다.</summary>
|
||||
private void ShowFormatMenu()
|
||||
{
|
||||
FormatMenuItems.Children.Clear();
|
||||
|
||||
var primaryText = ThemeResourceHelper.Primary(this);
|
||||
var secondaryText = ThemeResourceHelper.Secondary(this);
|
||||
var accentBrush = ThemeResourceHelper.Accent(this);
|
||||
var currentFormat = Llm.DefaultOutputFormat ?? "auto";
|
||||
|
||||
var formats = new (string Key, string Label, string Icon, string Color)[]
|
||||
{
|
||||
("auto", "AI 자동 선택", "\uE8BD", "#8B5CF6"),
|
||||
("xlsx", "Excel", "\uE9F9", "#217346"),
|
||||
("html", "HTML 보고서", "\uE12B", "#E44D26"),
|
||||
("docx", "Word", "\uE8A5", "#2B579A"),
|
||||
("md", "Markdown", "\uE943", "#6B7280"),
|
||||
("csv", "CSV", "\uE9D9", "#10B981"),
|
||||
};
|
||||
|
||||
foreach (var (key, label, icon, color) in formats)
|
||||
{
|
||||
var isActive = key == currentFormat;
|
||||
var sp = new StackPanel { Orientation = Orientation.Horizontal };
|
||||
|
||||
// 커스텀 체크 아이콘
|
||||
sp.Children.Add(CreateCheckIcon(isActive, accentBrush));
|
||||
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = icon,
|
||||
FontFamily = ThemeResourceHelper.SegoeMdl2,
|
||||
FontSize = 13,
|
||||
Foreground = BrushFromHex(color),
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(0, 0, 8, 0),
|
||||
});
|
||||
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = label, FontSize = 13,
|
||||
Foreground = primaryText,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
});
|
||||
|
||||
var itemBorder = new Border
|
||||
{
|
||||
Child = sp,
|
||||
Background = Brushes.Transparent,
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Cursor = Cursors.Hand,
|
||||
Padding = new Thickness(8, 7, 12, 7),
|
||||
};
|
||||
ApplyMenuItemHover(itemBorder);
|
||||
|
||||
var capturedKey = key;
|
||||
itemBorder.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
FormatMenuPopup.IsOpen = false;
|
||||
Llm.DefaultOutputFormat = capturedKey;
|
||||
_settings.Save();
|
||||
BuildBottomBar();
|
||||
};
|
||||
|
||||
FormatMenuItems.Children.Add(itemBorder);
|
||||
}
|
||||
|
||||
// PlacementTarget을 동적 등록된 버튼으로 설정
|
||||
if (FindName("BtnFormatMenu") is UIElement formatTarget)
|
||||
FormatMenuPopup.PlacementTarget = formatTarget;
|
||||
FormatMenuPopup.IsOpen = true;
|
||||
}
|
||||
|
||||
/// <summary>디자인 무드 선택 팝업 메뉴를 표시합니다.</summary>
|
||||
private void ShowMoodMenu()
|
||||
{
|
||||
MoodMenuItems.Children.Clear();
|
||||
|
||||
var primaryText = ThemeResourceHelper.Primary(this);
|
||||
var secondaryText = ThemeResourceHelper.Secondary(this);
|
||||
var accentBrush = ThemeResourceHelper.Accent(this);
|
||||
var borderBrush = ThemeResourceHelper.Border(this);
|
||||
|
||||
// 2열 갤러리 그리드
|
||||
var grid = new System.Windows.Controls.Primitives.UniformGrid { Columns = 2 };
|
||||
|
||||
foreach (var mood in TemplateService.AllMoods)
|
||||
{
|
||||
var isActive = _selectedMood == mood.Key;
|
||||
var isCustom = Llm.CustomMoods.Any(cm => cm.Key == mood.Key);
|
||||
var colors = TemplateService.GetMoodColors(mood.Key);
|
||||
|
||||
// 미니 프리뷰 카드
|
||||
var previewCard = new Border
|
||||
{
|
||||
Width = 160, Height = 80,
|
||||
CornerRadius = new CornerRadius(6),
|
||||
Background = ThemeResourceHelper.HexBrush(colors.Background),
|
||||
BorderBrush = isActive ? accentBrush : ThemeResourceHelper.HexBrush(colors.Border),
|
||||
BorderThickness = new Thickness(isActive ? 2 : 1),
|
||||
Padding = new Thickness(8, 6, 8, 6),
|
||||
Margin = new Thickness(2),
|
||||
};
|
||||
|
||||
var previewContent = new StackPanel();
|
||||
// 헤딩 라인
|
||||
previewContent.Children.Add(new Border
|
||||
{
|
||||
Width = 60, Height = 6, CornerRadius = new CornerRadius(2),
|
||||
Background = ThemeResourceHelper.HexBrush(colors.PrimaryText),
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
Margin = new Thickness(0, 0, 0, 4),
|
||||
});
|
||||
// 악센트 라인
|
||||
previewContent.Children.Add(new Border
|
||||
{
|
||||
Width = 40, Height = 3, CornerRadius = new CornerRadius(1),
|
||||
Background = ThemeResourceHelper.HexBrush(colors.Accent),
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
Margin = new Thickness(0, 0, 0, 6),
|
||||
});
|
||||
// 텍스트 라인들
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
previewContent.Children.Add(new Border
|
||||
{
|
||||
Width = 120 - i * 20, Height = 3, CornerRadius = new CornerRadius(1),
|
||||
Background = new SolidColorBrush(ThemeResourceHelper.HexColor(colors.SecondaryText)) { Opacity = 0.5 },
|
||||
HorizontalAlignment = HorizontalAlignment.Left,
|
||||
Margin = new Thickness(0, 0, 0, 3),
|
||||
});
|
||||
}
|
||||
// 미니 카드 영역
|
||||
var cardRow = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0, 2, 0, 0) };
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
cardRow.Children.Add(new Border
|
||||
{
|
||||
Width = 28, Height = 14, CornerRadius = new CornerRadius(2),
|
||||
Background = ThemeResourceHelper.HexBrush(colors.CardBg),
|
||||
BorderBrush = ThemeResourceHelper.HexBrush(colors.Border),
|
||||
BorderThickness = new Thickness(0.5),
|
||||
Margin = new Thickness(0, 0, 4, 0),
|
||||
});
|
||||
}
|
||||
previewContent.Children.Add(cardRow);
|
||||
previewCard.Child = previewContent;
|
||||
|
||||
// 무드 라벨
|
||||
var labelPanel = new StackPanel { Margin = new Thickness(4, 2, 4, 4) };
|
||||
var labelRow = new StackPanel { Orientation = Orientation.Horizontal };
|
||||
labelRow.Children.Add(new TextBlock
|
||||
{
|
||||
Text = mood.Icon, FontSize = 12,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(0, 0, 4, 0),
|
||||
});
|
||||
labelRow.Children.Add(new TextBlock
|
||||
{
|
||||
Text = mood.Label, FontSize = 11.5,
|
||||
Foreground = primaryText,
|
||||
FontWeight = isActive ? FontWeights.SemiBold : FontWeights.Normal,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
});
|
||||
if (isActive)
|
||||
{
|
||||
labelRow.Children.Add(new TextBlock
|
||||
{
|
||||
Text = " ✓", FontSize = 11,
|
||||
Foreground = accentBrush,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
});
|
||||
}
|
||||
labelPanel.Children.Add(labelRow);
|
||||
|
||||
// 전체 카드 래퍼
|
||||
var cardWrapper = new Border
|
||||
{
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Background = Brushes.Transparent,
|
||||
Cursor = Cursors.Hand,
|
||||
Padding = new Thickness(4),
|
||||
Margin = new Thickness(2),
|
||||
};
|
||||
var wrapperContent = new StackPanel();
|
||||
wrapperContent.Children.Add(previewCard);
|
||||
wrapperContent.Children.Add(labelPanel);
|
||||
cardWrapper.Child = wrapperContent;
|
||||
|
||||
// 호버
|
||||
cardWrapper.MouseEnter += (s, _) => { if (s is Border b) b.Background = new SolidColorBrush(Color.FromArgb(0x12, 0xFF, 0xFF, 0xFF)); };
|
||||
cardWrapper.MouseLeave += (s, _) => { if (s is Border b) b.Background = Brushes.Transparent; };
|
||||
|
||||
var capturedMood = mood;
|
||||
cardWrapper.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
MoodMenuPopup.IsOpen = false;
|
||||
_selectedMood = capturedMood.Key;
|
||||
Llm.DefaultMood = capturedMood.Key;
|
||||
_settings.Save();
|
||||
BuildBottomBar();
|
||||
};
|
||||
|
||||
// 커스텀 무드: 우클릭
|
||||
if (isCustom)
|
||||
{
|
||||
cardWrapper.MouseRightButtonUp += (s, e) =>
|
||||
{
|
||||
e.Handled = true;
|
||||
MoodMenuPopup.IsOpen = false;
|
||||
ShowCustomMoodContextMenu(s as Border, capturedMood.Key);
|
||||
};
|
||||
}
|
||||
|
||||
grid.Children.Add(cardWrapper);
|
||||
}
|
||||
|
||||
MoodMenuItems.Children.Add(grid);
|
||||
|
||||
// ── 구분선 + 추가 버튼 ──
|
||||
MoodMenuItems.Children.Add(new System.Windows.Shapes.Rectangle
|
||||
{
|
||||
Height = 1,
|
||||
Fill = borderBrush,
|
||||
Margin = new Thickness(8, 4, 8, 4),
|
||||
Opacity = 0.4,
|
||||
});
|
||||
|
||||
var addSp = new StackPanel { Orientation = Orientation.Horizontal };
|
||||
addSp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "\uE710",
|
||||
FontFamily = ThemeResourceHelper.SegoeMdl2,
|
||||
FontSize = 13,
|
||||
Foreground = secondaryText,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(4, 0, 8, 0),
|
||||
});
|
||||
addSp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "커스텀 무드 추가",
|
||||
FontSize = 13,
|
||||
Foreground = secondaryText,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
});
|
||||
var addBorder = new Border
|
||||
{
|
||||
Child = addSp,
|
||||
Background = Brushes.Transparent,
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Cursor = Cursors.Hand,
|
||||
Padding = new Thickness(8, 6, 12, 6),
|
||||
};
|
||||
ApplyMenuItemHover(addBorder);
|
||||
addBorder.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
MoodMenuPopup.IsOpen = false;
|
||||
ShowCustomMoodDialog();
|
||||
};
|
||||
MoodMenuItems.Children.Add(addBorder);
|
||||
|
||||
if (FindName("BtnMoodMenu") is UIElement moodTarget)
|
||||
MoodMenuPopup.PlacementTarget = moodTarget;
|
||||
MoodMenuPopup.IsOpen = true;
|
||||
}
|
||||
|
||||
/// <summary>커스텀 무드 추가/편집 다이얼로그를 표시합니다.</summary>
|
||||
private void ShowCustomMoodDialog(Models.CustomMoodEntry? existing = null)
|
||||
{
|
||||
bool isEdit = existing != null;
|
||||
var dlg = new CustomMoodDialog(
|
||||
existingKey: existing?.Key ?? "",
|
||||
existingLabel: existing?.Label ?? "",
|
||||
existingIcon: existing?.Icon ?? "🎯",
|
||||
existingDesc: existing?.Description ?? "",
|
||||
existingCss: existing?.Css ?? "")
|
||||
{
|
||||
Owner = this,
|
||||
};
|
||||
|
||||
if (dlg.ShowDialog() == true)
|
||||
{
|
||||
if (isEdit)
|
||||
{
|
||||
existing!.Label = dlg.MoodLabel;
|
||||
existing.Icon = dlg.MoodIcon;
|
||||
existing.Description = dlg.MoodDescription;
|
||||
existing.Css = dlg.MoodCss;
|
||||
}
|
||||
else
|
||||
{
|
||||
Llm.CustomMoods.Add(new Models.CustomMoodEntry
|
||||
{
|
||||
Key = dlg.MoodKey,
|
||||
Label = dlg.MoodLabel,
|
||||
Icon = dlg.MoodIcon,
|
||||
Description = dlg.MoodDescription,
|
||||
Css = dlg.MoodCss,
|
||||
});
|
||||
}
|
||||
_settings.Save();
|
||||
TemplateService.LoadCustomMoods(Llm.CustomMoods);
|
||||
BuildBottomBar();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>커스텀 무드 우클릭 컨텍스트 메뉴.</summary>
|
||||
private void ShowCustomMoodContextMenu(Border? anchor, string moodKey)
|
||||
{
|
||||
if (anchor == null) return;
|
||||
|
||||
var popup = new System.Windows.Controls.Primitives.Popup
|
||||
{
|
||||
PlacementTarget = anchor,
|
||||
Placement = System.Windows.Controls.Primitives.PlacementMode.Right,
|
||||
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.CustomMoods.FirstOrDefault(c => c.Key == moodKey);
|
||||
if (entry != null) ShowCustomMoodDialog(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(
|
||||
$"이 디자인 무드를 삭제하시겠습니까?",
|
||||
"무드 삭제", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
Llm.CustomMoods.RemoveAll(c => c.Key == moodKey);
|
||||
if (_selectedMood == moodKey) _selectedMood = "modern";
|
||||
_settings.Save();
|
||||
TemplateService.LoadCustomMoods(Llm.CustomMoods);
|
||||
BuildBottomBar();
|
||||
}
|
||||
};
|
||||
stack.Children.Add(deleteItem);
|
||||
|
||||
menuBorder.Child = stack;
|
||||
popup.Child = menuBorder;
|
||||
popup.IsOpen = true;
|
||||
}
|
||||
|
||||
|
||||
private string? _promptCardPlaceholder;
|
||||
|
||||
private void ShowPlaceholder()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_promptCardPlaceholder)) return;
|
||||
InputWatermark.Text = _promptCardPlaceholder;
|
||||
InputWatermark.Visibility = Visibility.Visible;
|
||||
InputBox.Text = "";
|
||||
InputBox.Focus();
|
||||
}
|
||||
|
||||
private void UpdateWatermarkVisibility()
|
||||
{
|
||||
// 슬래시 칩이 활성화되어 있으면 워터마크 숨기기 (겹침 방지)
|
||||
if (_activeSlashCmd != null)
|
||||
{
|
||||
InputWatermark.Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_promptCardPlaceholder != null && string.IsNullOrEmpty(InputBox.Text))
|
||||
InputWatermark.Visibility = Visibility.Visible;
|
||||
else
|
||||
InputWatermark.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void ClearPromptCardPlaceholder()
|
||||
{
|
||||
_promptCardPlaceholder = null;
|
||||
InputWatermark.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void BtnSettings_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Phase 32: Shift+클릭 → 인라인 설정 패널 토글, 일반 클릭 → SettingsWindow
|
||||
if (System.Windows.Input.Keyboard.Modifiers.HasFlag(System.Windows.Input.ModifierKeys.Shift))
|
||||
{
|
||||
ToggleSettingsPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (System.Windows.Application.Current is App app)
|
||||
app.OpenSettingsFromChat();
|
||||
}
|
||||
|
||||
/// <summary>Phase 32-E: 우측 설정 패널 슬라이드인/아웃 토글.</summary>
|
||||
private void ToggleSettingsPanel()
|
||||
{
|
||||
if (SettingsPanel.IsOpen)
|
||||
{
|
||||
SettingsPanel.IsOpen = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var activeTab = "Chat";
|
||||
if (TabCowork?.IsChecked == true) activeTab = "Cowork";
|
||||
else if (TabCode?.IsChecked == true) activeTab = "Code";
|
||||
|
||||
SettingsPanel.LoadFromSettings(_settings, activeTab);
|
||||
SettingsPanel.CloseRequested -= OnSettingsPanelClose;
|
||||
SettingsPanel.CloseRequested += OnSettingsPanelClose;
|
||||
SettingsPanel.IsOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnSettingsPanelClose(object? sender, EventArgs e)
|
||||
{
|
||||
SettingsPanel.IsOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user