using System; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using AxCopilot.Models; using AxCopilot.Services; namespace AxCopilot.Views; public partial class ChatWindow { private UIElement CreateV2MessageElement(ChatMessage message) { var isUser = message.Role == "user"; return isUser ? CreateV2UserBubble(message) : CreateV2AssistantBlock(message); } private UIElement CreateV2UserBubble(ChatMessage message) { var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White; var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray; var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue; var hintBg = TryFindResource("HintBackground") as Brush ?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF)); var msgMaxWidth = GetMessageMaxWidth(); var wrapper = new StackPanel { HorizontalAlignment = HorizontalAlignment.Right, MaxWidth = msgMaxWidth * 0.85, Margin = new Thickness(60, 12, 12, 4), }; // 사용자 아이콘 + 이름 헤더 var header = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right, Margin = new Thickness(2, 0, 0, 4), }; header.Children.Add(new TextBlock { Text = "\uE77B", // 사용자 아이콘 FontFamily = s_segoeIconFont, FontSize = 12, Foreground = secondaryText, VerticalAlignment = VerticalAlignment.Center, }); header.Children.Add(new TextBlock { Text = "You", FontSize = 11, FontWeight = FontWeights.SemiBold, Foreground = secondaryText, Margin = new Thickness(6, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center, }); var timestamp = message.Timestamp; header.Children.Add(new TextBlock { Text = timestamp.ToString("HH:mm"), FontSize = 10, Foreground = secondaryText, Opacity = 0.52, Margin = new Thickness(8, 0, 0, 1), VerticalAlignment = VerticalAlignment.Center, }); wrapper.Children.Add(header); // 메시지 본문 var bubble = new Border { BorderBrush = borderBrush, BorderThickness = new Thickness(1), CornerRadius = new CornerRadius(12), Padding = new Thickness(14, 10, 14, 10), HorizontalAlignment = HorizontalAlignment.Stretch, }; bubble.SetResourceReference(Border.BackgroundProperty, "HintBackground"); var content = message.Content ?? ""; if (string.Equals(_activeTab, "Cowork", StringComparison.OrdinalIgnoreCase) || string.Equals(_activeTab, "Code", StringComparison.OrdinalIgnoreCase)) { MarkdownRenderer.EnableFilePathHighlight = (System.Windows.Application.Current as App)?.SettingsService?.Settings.Llm.EnableFilePathHighlight ?? true; MarkdownRenderer.EnableCodeSymbolHighlight = true; bubble.Child = MarkdownRenderer.Render(content, primaryText, secondaryText, accentBrush, hintBg); } else { MarkdownRenderer.EnableCodeSymbolHighlight = false; bubble.Child = new TextBlock { Text = content, TextAlignment = TextAlignment.Left, FontSize = 12, Foreground = primaryText, TextWrapping = TextWrapping.Wrap, LineHeight = 18, }; } wrapper.Children.Add(bubble); // 우클릭 메뉴 var capturedContent = content; wrapper.MouseRightButtonUp += (_, re) => { re.Handled = true; ShowMessageContextMenu(capturedContent, "user"); }; return wrapper; } private UIElement CreateV2AssistantBlock(ChatMessage message) { var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White; var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray; var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue; var hintBg = TryFindResource("HintBackground") as Brush ?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF)); // Compaction 메타 메시지 처리 if (IsCompactionMetaMessage(message)) { var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray; return CreateCompactionMetaCard(message, primaryText, secondaryText, hintBg, borderBrush, accentBrush); } var msgMaxWidth = GetMessageMaxWidth(); var container = new StackPanel { HorizontalAlignment = HorizontalAlignment.Center, Width = msgMaxWidth, MaxWidth = msgMaxWidth, Margin = new Thickness(0, 8, 0, 8), }; // 에이전트 아이콘 + 이름 헤더 var (agentName, _, _) = GetAgentIdentity(); var (iconHost, iconPixels, iconGlows, iconRotate, iconScale) = CreateMiniLauncherIconEx(4.0, "none"); var header = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(2, 0, 0, 4), }; header.Children.Add(iconHost); header.Children.Add(new TextBlock { Text = agentName, FontSize = 11, FontWeight = FontWeights.SemiBold, Foreground = secondaryText, Margin = new Thickness(6, 0, 0, 0), VerticalAlignment = VerticalAlignment.Center, }); // 아이콘 애니메이션 var canvas = iconHost.Children.OfType().FirstOrDefault(); if (canvas != null) { var animState = new ChatIconAnimState { Host = iconHost, Canvas = canvas, Pixels = iconPixels, Glows = iconGlows, Rotate = iconRotate, Scale = iconScale, IsRandomMode = _settings.Settings.Launcher.EnableChatIconRandomAnimation, }; StartChatIconAnimation(animState); } container.Children.Add(header); // 마크다운 렌더 본문 var content = message.Content ?? ""; var codeBgBrush = TryFindResource("HintBackground") as Brush ?? Brushes.DarkGray; MarkdownRenderer.EnableFilePathHighlight = (System.Windows.Application.Current as App)?.SettingsService?.Settings.Llm.EnableFilePathHighlight ?? true; MarkdownRenderer.EnableCodeSymbolHighlight = string.Equals(_activeTab, "Cowork", StringComparison.OrdinalIgnoreCase) || string.Equals(_activeTab, "Code", StringComparison.OrdinalIgnoreCase); var contentPanel = new Border { Background = Brushes.Transparent, Padding = new Thickness(6, 4, 6, 4), }; if (IsBranchContextMessage(content)) { contentPanel.Child = MarkdownRenderer.Render(content, primaryText, secondaryText, accentBrush, codeBgBrush); } else { contentPanel.Child = MarkdownRenderer.Render(content, primaryText, secondaryText, accentBrush, codeBgBrush); } container.Children.Add(contentPanel); // 파일 퀵 액션 var outputFilePath = ExtractOutputFilePathFromContent(content); if (!string.IsNullOrEmpty(outputFilePath) && System.IO.File.Exists(outputFilePath)) { var quickActions = BuildFileQuickActions(outputFilePath); quickActions.Margin = new Thickness(2, 4, 0, 2); container.Children.Add(quickActions); } // 액션 바 (복사, 재생성 등) var actionBar = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Left, Margin = new Thickness(2, 2, 0, 0), Opacity = 0, }; var btnColor = secondaryText; var capturedContent = content; actionBar.Children.Add(CreateActionButton("\uE8C8", "복사", btnColor, () => { try { Clipboard.SetText(capturedContent); } catch { } })); actionBar.Children.Add(CreateActionButton("\uE72C", "다시 생성", btnColor, () => _ = RegenerateLastAsync())); var aiTimestamp = message.Timestamp; actionBar.Children.Add(new TextBlock { Text = aiTimestamp.ToString("HH:mm"), FontSize = 10, Opacity = 0.6, Foreground = btnColor, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(4, 0, 0, 1), }); container.Children.Add(actionBar); // 메타 정보 (토큰 등) var assistantMeta = CreateAssistantMessageMetaText(message); if (assistantMeta != null) container.Children.Add(assistantMeta); container.MouseEnter += (_, _) => actionBar.BeginAnimation(OpacityProperty, new System.Windows.Media.Animation.DoubleAnimation(0.8, TimeSpan.FromMilliseconds(150))); container.MouseLeave += (_, _) => actionBar.BeginAnimation(OpacityProperty, new System.Windows.Media.Animation.DoubleAnimation(0, TimeSpan.FromMilliseconds(200))); var aiContent = content; container.MouseRightButtonUp += (_, re) => { re.Handled = true; ShowMessageContextMenu(aiContent, "assistant"); }; return container; } }