AX Agent 코워크·코드 흐름과 컨텍스트 관리를 claude-code 기준으로 대폭 정리
- 코워크·코드 프롬프트, 도구 선택, 문서 생성/검증 흐름을 claude-code 동등 품질 기준으로 재정렬함 - OpenAI/vLLM 경로의 오래된 tool history를 평탄화하고 최근 이력만 구조화해 컨텍스트 직렬화를 경량화함 - AX Agent UI를 테마 기준으로 재구성하고 플랜 승인/오버레이/이벤트 렌더링/명령 입력 상호작용을 개선함 - 파일 후보 제안, 반복 경로 정체 복구, LSP 보강, 문서·PPT 처리 개선, 설정/서비스 인터페이스 정리를 함께 반영함 - README.md 및 docs/DEVELOPMENT.md를 작업 시점별로 갱신함 - 검증: dotnet build src/AxCopilot/AxCopilot.csproj -c Release -v minimal -p:OutputPath=bin\\verify\\ -p:IntermediateOutputPath=obj\\verify\\ (경고 0, 오류 0)
This commit is contained in:
260
src/AxCopilot/Views/ChatWindow.V2MessagePresentation.cs
Normal file
260
src/AxCopilot/Views/ChatWindow.V2MessagePresentation.cs
Normal file
@@ -0,0 +1,260 @@
|
||||
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.Center,
|
||||
Width = msgMaxWidth,
|
||||
MaxWidth = msgMaxWidth,
|
||||
Margin = new Thickness(0, 12, 0, 4),
|
||||
};
|
||||
|
||||
// 사용자 아이콘 + 이름 헤더
|
||||
var header = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
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<Canvas>().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(2, 4, 2, 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.7,
|
||||
};
|
||||
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.52,
|
||||
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.Opacity = 1;
|
||||
container.MouseLeave += (_, _) => actionBar.Opacity = 0.7;
|
||||
|
||||
var aiContent = content;
|
||||
container.MouseRightButtonUp += (_, re) =>
|
||||
{
|
||||
re.Handled = true;
|
||||
ShowMessageContextMenu(aiContent, "assistant");
|
||||
};
|
||||
|
||||
return container;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user