[Phase47] 대형 파일 분할 리팩터링 3차 — 8개 신규 파셜 파일 생성

## 분할 대상 및 결과

### ChatWindow.ResponseHandling.cs (741줄 → 269줄)
- ChatWindow.StreamingUI.cs (303줄, 신규): CreateStreamingContainer, FinalizeStreamingContainer, ParseSuggestionChips, FormatTokenCount, EstimateTokenCount, StopGeneration
- ChatWindow.ConversationExport.cs (188줄, 신규): ForkConversation, OpenCommandPalette, ExecuteCommand, ExportConversation, ExportToHtml

### ChatWindow.PreviewAndFiles.cs (709줄 → ~340줄)
- ChatWindow.PreviewPopup.cs (~230줄, 신규): ShowPreviewTabContextMenu, OpenPreviewPopupWindow, _previewTabPopup 필드

### HelpDetailWindow.xaml.cs (673줄 → 254줄)
- HelpDetailWindow.Shortcuts.cs (168줄, 신규): BuildShortcutItems() 정적 메서드 (단축키 항목 160개+ 생성)
- HelpDetailWindow.Navigation.cs (266줄, 신규): 테마 프로퍼티, BuildTopMenu/SwitchTopMenu, BuildCategoryBar, NavigateToPage, 이벤트 핸들러
- partial class 전환: `public partial class HelpDetailWindow : Window`

### SkillService.cs (661줄 → 386줄)
- SkillService.Import.cs (203줄, 신규): ExportSkill, ImportSkills, MapToolNames — 가져오기/내보내기 섹션
- SkillDefinition.cs (81줄, 신규): SkillDefinition 클래스 독립 파일로 분리 (별도 최상위 클래스)
- partial class 전환: `public static partial class SkillService`

## NEXT_ROADMAP.md Phase 46 완료 항목 추가

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

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-03 21:02:53 +09:00
parent aa907d7b79
commit 27bd8de83a
11 changed files with 1472 additions and 1417 deletions

View File

@@ -459,251 +459,4 @@ public partial class ChatWindow
System.Diagnostics.Debug.WriteLine($"외부 프로그램 실행 오류: {ex.Message}");
}
}
/// <summary>프리뷰 탭 우클릭 컨텍스트 메뉴를 표시합니다.</summary>
private Popup? _previewTabPopup;
private void ShowPreviewTabContextMenu(string filePath)
{
// 기존 팝업 닫기
if (_previewTabPopup != null) _previewTabPopup.IsOpen = false;
var bg = ThemeResourceHelper.Background(this);
var borderBrush = ThemeResourceHelper.Border(this);
var primaryText = ThemeResourceHelper.Primary(this);
var secondaryText = ThemeResourceHelper.Secondary(this);
var hoverBg = ThemeResourceHelper.HoverBg(this);
var stack = new StackPanel();
void AddItem(string icon, string iconColor, string label, Action action)
{
var itemBorder = new Border
{
Background = Brushes.Transparent,
CornerRadius = new CornerRadius(6),
Padding = new Thickness(10, 7, 16, 7),
Cursor = Cursors.Hand,
};
var sp = new StackPanel { Orientation = Orientation.Horizontal };
sp.Children.Add(new TextBlock
{
Text = icon, FontFamily = ThemeResourceHelper.SegoeMdl2,
FontSize = 12, Foreground = string.IsNullOrEmpty(iconColor)
? secondaryText
: ThemeResourceHelper.HexBrush(iconColor),
VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(0, 0, 8, 0),
});
sp.Children.Add(new TextBlock
{
Text = label, FontSize = 13, Foreground = primaryText,
VerticalAlignment = VerticalAlignment.Center,
});
itemBorder.Child = sp;
itemBorder.MouseEnter += (s, _) => { if (s is Border b) b.Background = hoverBg; };
itemBorder.MouseLeave += (s, _) => { if (s is Border b) b.Background = Brushes.Transparent; };
itemBorder.MouseLeftButtonUp += (_, _) =>
{
_previewTabPopup!.IsOpen = false;
action();
};
stack.Children.Add(itemBorder);
}
void AddSeparator()
{
stack.Children.Add(new Border
{
Height = 1,
Background = borderBrush,
Margin = new Thickness(8, 3, 8, 3),
});
}
AddItem("\uE8A7", "#64B5F6", "외부 프로그램으로 열기", () =>
{
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = filePath, UseShellExecute = true,
});
}
catch (Exception) { /* 비핵심 작업 실패 — UI 차단 방지 */ }
});
AddItem("\uE838", "#FFB74D", "파일 위치 열기", () =>
{
try { System.Diagnostics.Process.Start("explorer.exe", $"/select,\"{filePath}\""); }
catch (Exception) { /* 비핵심 작업 실패 — UI 차단 방지 */ }
});
AddItem("\uE8A7", "#81C784", "별도 창에서 보기", () => OpenPreviewPopupWindow(filePath));
AddSeparator();
AddItem("\uE8C8", "", "경로 복사", () =>
{
try { Clipboard.SetText(filePath); } catch (Exception) { /* 클립보드 접근 실패 */ }
});
AddSeparator();
AddItem("\uE711", "#EF5350", "이 탭 닫기", () => ClosePreviewTab(filePath));
if (_previewTabs.Count > 1)
{
AddItem("\uE8BB", "#EF5350", "다른 탭 모두 닫기", () =>
{
var keep = filePath;
_previewTabs.RemoveAll(p => !string.Equals(p, keep, StringComparison.OrdinalIgnoreCase));
_activePreviewTab = keep;
RebuildPreviewTabs();
LoadPreviewContent(keep);
});
}
var popupBorder = new Border
{
Background = bg,
BorderBrush = borderBrush,
BorderThickness = new Thickness(1),
CornerRadius = new CornerRadius(12),
Padding = new Thickness(4, 6, 4, 6),
MinWidth = 180,
Effect = new System.Windows.Media.Effects.DropShadowEffect
{
BlurRadius = 16, Opacity = 0.4, ShadowDepth = 4,
Color = Colors.Black,
},
Child = stack,
};
_previewTabPopup = new Popup
{
Child = popupBorder,
Placement = PlacementMode.MousePoint,
StaysOpen = false,
AllowsTransparency = true,
PopupAnimation = PopupAnimation.Fade,
};
_previewTabPopup.IsOpen = true;
}
/// <summary>프리뷰를 별도 팝업 창에서 엽니다.</summary>
private void OpenPreviewPopupWindow(string filePath)
{
if (!System.IO.File.Exists(filePath)) return;
var ext = System.IO.Path.GetExtension(filePath).ToLowerInvariant();
var fileName = System.IO.Path.GetFileName(filePath);
var bg = ThemeResourceHelper.Background(this);
var fg = ThemeResourceHelper.Primary(this);
var win = new Window
{
Title = $"미리보기 — {fileName}",
Width = 900,
Height = 700,
WindowStartupLocation = WindowStartupLocation.CenterScreen,
Background = bg,
};
FrameworkElement content;
switch (ext)
{
case ".html":
case ".htm":
var wv = new Microsoft.Web.WebView2.Wpf.WebView2();
wv.Loaded += async (_, _) =>
{
try
{
var env = await Microsoft.Web.WebView2.Core.CoreWebView2Environment.CreateAsync(
userDataFolder: WebView2DataFolder);
await wv.EnsureCoreWebView2Async(env);
wv.Source = new Uri(filePath);
}
catch (Exception) { /* 비핵심 작업 실패 — UI 차단 방지 */ }
};
content = wv;
break;
case ".md":
var mdWv = new Microsoft.Web.WebView2.Wpf.WebView2();
var mdMood = _selectedMood;
mdWv.Loaded += async (_, _) =>
{
try
{
var env = await Microsoft.Web.WebView2.Core.CoreWebView2Environment.CreateAsync(
userDataFolder: WebView2DataFolder);
await mdWv.EnsureCoreWebView2Async(env);
var mdSrc = System.IO.File.ReadAllText(filePath);
if (mdSrc.Length > 100000) mdSrc = mdSrc[..100000];
var html = Services.Agent.TemplateService.RenderMarkdownToHtml(mdSrc, mdMood);
mdWv.NavigateToString(html);
}
catch (Exception) { /* 비핵심 작업 실패 — UI 차단 방지 */ }
};
content = mdWv;
break;
case ".csv":
var dg = new System.Windows.Controls.DataGrid
{
AutoGenerateColumns = true,
IsReadOnly = true,
Background = Brushes.Transparent,
Foreground = Brushes.White,
BorderThickness = new Thickness(0),
FontSize = 12,
};
try
{
var lines = System.IO.File.ReadAllLines(filePath);
if (lines.Length > 0)
{
var dt = new System.Data.DataTable();
var headers = ParseCsvLine(lines[0]);
foreach (var h in headers) dt.Columns.Add(h);
for (int i = 1; i < Math.Min(lines.Length, 1001); i++)
{
var vals = ParseCsvLine(lines[i]);
var row = dt.NewRow();
for (int j = 0; j < Math.Min(vals.Length, dt.Columns.Count); j++)
row[j] = vals[j];
dt.Rows.Add(row);
}
dg.ItemsSource = dt.DefaultView;
}
}
catch (Exception) { /* 비핵심 작업 실패 — UI 차단 방지 */ }
content = dg;
break;
default:
var text = System.IO.File.ReadAllText(filePath);
if (text.Length > 100000) text = text[..100000] + "\n\n... (이후 생략)";
var sv = new ScrollViewer
{
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
Padding = new Thickness(20),
Content = new TextBlock
{
Text = text,
TextWrapping = TextWrapping.Wrap,
FontFamily = ThemeResourceHelper.Consolas,
FontSize = 13,
Foreground = fg,
},
};
content = sv;
break;
}
win.Content = content;
win.Show();
}
}