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:
601
src/AxCopilot/Views/ChatWindow.V2AgentEventPresentation.cs
Normal file
601
src/AxCopilot/Views/ChatWindow.V2AgentEventPresentation.cs
Normal file
@@ -0,0 +1,601 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using AxCopilot.Services;
|
||||
using AxCopilot.Services.Agent;
|
||||
|
||||
namespace AxCopilot.Views;
|
||||
|
||||
public partial class ChatWindow
|
||||
{
|
||||
/// <summary>V2: 단일 에이전트 이벤트를 렌더링 (ToolCall/ToolResult 병합되지 않은 경우)</summary>
|
||||
private UIElement CreateV2AgentEventElement(AgentEvent agentEvent)
|
||||
{
|
||||
return agentEvent.Type switch
|
||||
{
|
||||
AgentEventType.Thinking => CreateV2ThinkingBlock(agentEvent),
|
||||
AgentEventType.ToolCall => CreateV2ToolCallOnlyCard(agentEvent),
|
||||
AgentEventType.ToolResult => CreateV2ToolResultOnlyCard(agentEvent),
|
||||
AgentEventType.Complete => CreateV2CompleteBanner(agentEvent),
|
||||
AgentEventType.Error => CreateV2ErrorBanner(agentEvent),
|
||||
_ => CreateV2GenericEventPill(agentEvent),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>V2: ToolCall + ToolResult 쌍을 병합한 실행 카드</summary>
|
||||
private UIElement CreateV2ToolExecutionCard(AgentEvent toolCall, AgentEvent toolResult)
|
||||
{
|
||||
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 hintBg = TryFindResource("HintBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF));
|
||||
var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
|
||||
|
||||
var msgMaxWidth = GetMessageMaxWidth();
|
||||
var (icon, iconColor) = GetV2ToolIcon(toolCall.ToolName);
|
||||
var elapsed = NormalizeProgressElapsedMs(toolResult.ElapsedMs);
|
||||
var elapsedText = elapsed > 0 ? $"{elapsed / 1000.0:F1}s" : "";
|
||||
var isSuccess = toolResult.Success;
|
||||
|
||||
// 외부 컨테이너 — 왼쪽 세로선 + 카드
|
||||
var outerGrid = new Grid
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
Width = msgMaxWidth,
|
||||
MaxWidth = msgMaxWidth,
|
||||
Margin = new Thickness(0, 2, 0, 2),
|
||||
};
|
||||
outerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||
outerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
|
||||
// 왼쪽 세로선
|
||||
var lineColor = isSuccess
|
||||
? new SolidColorBrush(Color.FromArgb(0x60, 0x66, 0xBB, 0x6A))
|
||||
: new SolidColorBrush(Color.FromArgb(0x60, 0xEF, 0x53, 0x50));
|
||||
var verticalLine = new Border
|
||||
{
|
||||
Width = 2,
|
||||
Background = lineColor,
|
||||
CornerRadius = new CornerRadius(1),
|
||||
Margin = new Thickness(12, 0, 8, 0),
|
||||
VerticalAlignment = VerticalAlignment.Stretch,
|
||||
};
|
||||
Grid.SetColumn(verticalLine, 0);
|
||||
outerGrid.Children.Add(verticalLine);
|
||||
|
||||
// 메인 카드
|
||||
var card = new Border
|
||||
{
|
||||
Background = hintBg,
|
||||
BorderBrush = borderBrush,
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(10),
|
||||
Padding = new Thickness(12, 8, 12, 8),
|
||||
Cursor = Cursors.Hand,
|
||||
};
|
||||
Grid.SetColumn(card, 1);
|
||||
|
||||
var cardStack = new StackPanel();
|
||||
|
||||
// 헤더: 아이콘 + 도구명 + 파일경로 + 소요시간
|
||||
var headerGrid = new Grid();
|
||||
headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // 아이콘
|
||||
headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // 도구명
|
||||
headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); // 파일경로
|
||||
headerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto }); // 상태+시간
|
||||
|
||||
var statusIcon = isSuccess ? "\uE73E" : "\uE711"; // 체크 or X
|
||||
var statusColor = isSuccess
|
||||
? new SolidColorBrush(Color.FromRgb(0x66, 0xBB, 0x6A))
|
||||
: new SolidColorBrush(Color.FromRgb(0xEF, 0x53, 0x50));
|
||||
|
||||
var iconTb = new TextBlock
|
||||
{
|
||||
Text = icon,
|
||||
FontSize = 13,
|
||||
Foreground = iconColor,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(0, 0, 6, 0),
|
||||
};
|
||||
Grid.SetColumn(iconTb, 0);
|
||||
headerGrid.Children.Add(iconTb);
|
||||
|
||||
var toolNameTb = new TextBlock
|
||||
{
|
||||
Text = GetV2ToolDisplayName(toolCall.ToolName),
|
||||
FontSize = 12,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = primaryText,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(0, 0, 8, 0),
|
||||
};
|
||||
Grid.SetColumn(toolNameTb, 1);
|
||||
headerGrid.Children.Add(toolNameTb);
|
||||
|
||||
// 파일 경로 (있으면)
|
||||
var filePath = toolCall.FilePath ?? toolResult.FilePath;
|
||||
if (!string.IsNullOrWhiteSpace(filePath))
|
||||
{
|
||||
var pathTb = new TextBlock
|
||||
{
|
||||
Text = TruncateFilePath(filePath, 60),
|
||||
FontSize = 10.5,
|
||||
Foreground = secondaryText,
|
||||
Opacity = 0.8,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
TextTrimming = TextTrimming.CharacterEllipsis,
|
||||
ToolTip = filePath,
|
||||
};
|
||||
Grid.SetColumn(pathTb, 2);
|
||||
headerGrid.Children.Add(pathTb);
|
||||
}
|
||||
|
||||
// 상태 아이콘 + 소요시간
|
||||
var statusPanel = new StackPanel { Orientation = Orientation.Horizontal };
|
||||
statusPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = statusIcon,
|
||||
FontFamily = s_segoeIconFont,
|
||||
FontSize = 11,
|
||||
Foreground = statusColor,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(0, 0, 4, 0),
|
||||
});
|
||||
if (!string.IsNullOrEmpty(elapsedText))
|
||||
{
|
||||
statusPanel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = elapsedText,
|
||||
FontSize = 10,
|
||||
Foreground = secondaryText,
|
||||
Opacity = 0.7,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
});
|
||||
}
|
||||
Grid.SetColumn(statusPanel, 3);
|
||||
headerGrid.Children.Add(statusPanel);
|
||||
cardStack.Children.Add(headerGrid);
|
||||
|
||||
// Summary 텍스트 (있으면)
|
||||
var summary = toolCall.Summary ?? toolResult.Summary;
|
||||
if (!string.IsNullOrWhiteSpace(summary))
|
||||
{
|
||||
var summaryTb = new TextBlock
|
||||
{
|
||||
Text = summary,
|
||||
FontSize = 10.5,
|
||||
Foreground = secondaryText,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Margin = new Thickness(0, 4, 0, 0),
|
||||
MaxHeight = 40,
|
||||
TextTrimming = TextTrimming.CharacterEllipsis,
|
||||
};
|
||||
cardStack.Children.Add(summaryTb);
|
||||
}
|
||||
|
||||
// 접힘/펼침 상세 영역
|
||||
var detailBorder = new Border
|
||||
{
|
||||
Visibility = Visibility.Collapsed,
|
||||
Margin = new Thickness(0, 6, 0, 0),
|
||||
};
|
||||
var detailStack = new StackPanel();
|
||||
|
||||
// 도구 결과 상세 내용
|
||||
var resultSummary = toolResult.Summary;
|
||||
if (!string.IsNullOrWhiteSpace(resultSummary) && resultSummary.Length > 80)
|
||||
{
|
||||
var codeBg = TryFindResource("HintBackground") as Brush ?? Brushes.DarkGray;
|
||||
detailStack.Children.Add(MarkdownRenderer.Render(
|
||||
$"```\n{resultSummary}\n```", primaryText, secondaryText, accentBrush, codeBg));
|
||||
}
|
||||
|
||||
// 토큰 메타 정보
|
||||
var inputTokens = toolResult.InputTokens;
|
||||
var outputTokens = toolResult.OutputTokens;
|
||||
if (inputTokens > 0 || outputTokens > 0)
|
||||
{
|
||||
var metaParts = new System.Collections.Generic.List<string>();
|
||||
if (inputTokens > 0) metaParts.Add($"입력: {inputTokens:N0} 토큰");
|
||||
if (outputTokens > 0) metaParts.Add($"출력: {outputTokens:N0} 토큰");
|
||||
|
||||
detailStack.Children.Add(new TextBlock
|
||||
{
|
||||
Text = string.Join(" · ", metaParts),
|
||||
FontSize = 9.5,
|
||||
Foreground = secondaryText,
|
||||
Opacity = 0.6,
|
||||
Margin = new Thickness(0, 4, 0, 0),
|
||||
});
|
||||
}
|
||||
|
||||
detailBorder.Child = detailStack;
|
||||
cardStack.Children.Add(detailBorder);
|
||||
|
||||
// 펼치기 토글 화살표
|
||||
var arrowTb = new TextBlock
|
||||
{
|
||||
Text = "\uE76C", // 아래 화살표
|
||||
FontFamily = s_segoeIconFont,
|
||||
FontSize = 9,
|
||||
Foreground = secondaryText,
|
||||
Opacity = 0.5,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
Margin = new Thickness(0, 3, 0, 0),
|
||||
Cursor = Cursors.Hand,
|
||||
};
|
||||
cardStack.Children.Add(arrowTb);
|
||||
|
||||
card.Child = cardStack;
|
||||
outerGrid.Children.Add(card);
|
||||
|
||||
// 클릭 → 접힘/펼침 토글
|
||||
card.MouseLeftButtonUp += (_, e) =>
|
||||
{
|
||||
e.Handled = true;
|
||||
var isExpanded = detailBorder.Visibility == Visibility.Visible;
|
||||
detailBorder.Visibility = isExpanded ? Visibility.Collapsed : Visibility.Visible;
|
||||
arrowTb.Text = isExpanded ? "\uE76C" : "\uE76B"; // 아래↔위
|
||||
};
|
||||
|
||||
// 호버 효과
|
||||
var normalBg = hintBg;
|
||||
var hoverBg = TryFindResource("ItemHoverBackground") as Brush ?? hintBg;
|
||||
card.MouseEnter += (_, _) => card.Background = hoverBg;
|
||||
card.MouseLeave += (_, _) => card.Background = normalBg;
|
||||
|
||||
return outerGrid;
|
||||
}
|
||||
|
||||
/// <summary>V2: Thinking 블록 — 점선 테두리, 이탤릭 텍스트</summary>
|
||||
private UIElement CreateV2ThinkingBlock(AgentEvent agentEvent)
|
||||
{
|
||||
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||
var msgMaxWidth = GetMessageMaxWidth();
|
||||
|
||||
var summary = agentEvent.Summary;
|
||||
if (string.IsNullOrWhiteSpace(summary))
|
||||
return new Border { Width = 0, Height = 0 }; // 빈 thinking은 숨김
|
||||
|
||||
// agent_wait / context_compaction 같은 운영 이벤트
|
||||
if (string.Equals(agentEvent.ToolName, "agent_wait", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(agentEvent.ToolName, "context_compaction", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return CreateV2GenericEventPill(agentEvent);
|
||||
}
|
||||
|
||||
var outerGrid = new Grid
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
Width = msgMaxWidth,
|
||||
MaxWidth = msgMaxWidth,
|
||||
Margin = new Thickness(0, 2, 0, 2),
|
||||
};
|
||||
outerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||
outerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
|
||||
// 왼쪽 세로선 (사고 과정 = 파란색 점선 느낌)
|
||||
var thinkLine = new Border
|
||||
{
|
||||
Width = 2,
|
||||
Background = new SolidColorBrush(Color.FromArgb(0x40, 0x59, 0xA5, 0xF5)),
|
||||
CornerRadius = new CornerRadius(1),
|
||||
Margin = new Thickness(12, 0, 8, 0),
|
||||
};
|
||||
Grid.SetColumn(thinkLine, 0);
|
||||
outerGrid.Children.Add(thinkLine);
|
||||
|
||||
var thinkCard = new Border
|
||||
{
|
||||
Background = new SolidColorBrush(Color.FromArgb(0x0A, 0x59, 0xA5, 0xF5)),
|
||||
BorderBrush = new SolidColorBrush(Color.FromArgb(0x30, 0x59, 0xA5, 0xF5)),
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Padding = new Thickness(10, 6, 10, 6),
|
||||
};
|
||||
Grid.SetColumn(thinkCard, 1);
|
||||
|
||||
var thinkStack = new StackPanel { Orientation = Orientation.Horizontal };
|
||||
thinkStack.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "\uE915", // 전구 아이콘
|
||||
FontFamily = s_segoeIconFont,
|
||||
FontSize = 11,
|
||||
Foreground = new SolidColorBrush(Color.FromRgb(0x59, 0xA5, 0xF5)),
|
||||
VerticalAlignment = VerticalAlignment.Top,
|
||||
Margin = new Thickness(0, 1, 6, 0),
|
||||
});
|
||||
thinkStack.Children.Add(new TextBlock
|
||||
{
|
||||
Text = summary.Length > 200 ? summary[..200] + "..." : summary,
|
||||
FontSize = 11,
|
||||
FontStyle = FontStyles.Italic,
|
||||
Foreground = secondaryText,
|
||||
Opacity = 0.75,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
MaxWidth = msgMaxWidth - 60,
|
||||
});
|
||||
thinkCard.Child = thinkStack;
|
||||
outerGrid.Children.Add(thinkCard);
|
||||
|
||||
return outerGrid;
|
||||
}
|
||||
|
||||
/// <summary>V2: ToolCall만 있고 ToolResult가 없는 경우</summary>
|
||||
private UIElement CreateV2ToolCallOnlyCard(AgentEvent agentEvent)
|
||||
{
|
||||
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 hintBg = TryFindResource("HintBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF));
|
||||
|
||||
var msgMaxWidth = GetMessageMaxWidth();
|
||||
var (icon, iconColor) = GetV2ToolIcon(agentEvent.ToolName);
|
||||
|
||||
var outerGrid = new Grid
|
||||
{
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
Width = msgMaxWidth,
|
||||
MaxWidth = msgMaxWidth,
|
||||
Margin = new Thickness(0, 2, 0, 2),
|
||||
};
|
||||
outerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||
outerGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
|
||||
var line = new Border
|
||||
{
|
||||
Width = 2,
|
||||
Background = new SolidColorBrush(Color.FromArgb(0x40, 0x59, 0xA5, 0xF5)),
|
||||
CornerRadius = new CornerRadius(1),
|
||||
Margin = new Thickness(12, 0, 8, 0),
|
||||
};
|
||||
Grid.SetColumn(line, 0);
|
||||
outerGrid.Children.Add(line);
|
||||
|
||||
var pill = new Border
|
||||
{
|
||||
Background = hintBg,
|
||||
BorderBrush = borderBrush,
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Padding = new Thickness(10, 6, 10, 6),
|
||||
};
|
||||
Grid.SetColumn(pill, 1);
|
||||
|
||||
var sp = new StackPanel { Orientation = Orientation.Horizontal };
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = icon,
|
||||
FontSize = 12,
|
||||
Foreground = iconColor,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(0, 0, 6, 0),
|
||||
});
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = GetV2ToolDisplayName(agentEvent.ToolName),
|
||||
FontSize = 11,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = primaryText,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
});
|
||||
if (!string.IsNullOrWhiteSpace(agentEvent.FilePath))
|
||||
{
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = TruncateFilePath(agentEvent.FilePath, 50),
|
||||
FontSize = 10,
|
||||
Foreground = secondaryText,
|
||||
Opacity = 0.7,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(8, 0, 0, 0),
|
||||
});
|
||||
}
|
||||
pill.Child = sp;
|
||||
outerGrid.Children.Add(pill);
|
||||
|
||||
return outerGrid;
|
||||
}
|
||||
|
||||
/// <summary>V2: ToolResult만 있고 ToolCall이 없는 경우</summary>
|
||||
private UIElement CreateV2ToolResultOnlyCard(AgentEvent agentEvent)
|
||||
{
|
||||
// ToolCall과 유사하지만 결과 표시
|
||||
return CreateV2ToolCallOnlyCard(agentEvent);
|
||||
}
|
||||
|
||||
/// <summary>V2: 작업 완료 배너</summary>
|
||||
private UIElement CreateV2CompleteBanner(AgentEvent agentEvent)
|
||||
{
|
||||
var msgMaxWidth = GetMessageMaxWidth();
|
||||
var elapsed = NormalizeProgressElapsedMs(agentEvent.ElapsedMs);
|
||||
var elapsedText = elapsed > 0 ? $" · {elapsed / 1000.0:F1}s" : "";
|
||||
|
||||
var tokenInfo = "";
|
||||
if (agentEvent.InputTokens > 0 || agentEvent.OutputTokens > 0)
|
||||
{
|
||||
var parts = new System.Collections.Generic.List<string>();
|
||||
if (agentEvent.InputTokens > 0) parts.Add($"입력 {agentEvent.InputTokens:N0}");
|
||||
if (agentEvent.OutputTokens > 0) parts.Add($"출력 {agentEvent.OutputTokens:N0}");
|
||||
tokenInfo = $" · {string.Join("/", parts)} 토큰";
|
||||
}
|
||||
|
||||
var banner = new Border
|
||||
{
|
||||
Background = new SolidColorBrush(Color.FromArgb(0x18, 0x66, 0xBB, 0x6A)),
|
||||
BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0x66, 0xBB, 0x6A)),
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Padding = new Thickness(12, 6, 12, 6),
|
||||
Margin = new Thickness(0, 4, 0, 4),
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
MaxWidth = msgMaxWidth,
|
||||
};
|
||||
|
||||
var sp = new StackPanel { Orientation = Orientation.Horizontal };
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "\uE73E", // 체크마크
|
||||
FontFamily = s_segoeIconFont,
|
||||
FontSize = 12,
|
||||
Foreground = new SolidColorBrush(Color.FromRgb(0x66, 0xBB, 0x6A)),
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(0, 0, 6, 0),
|
||||
});
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"작업 완료{elapsedText}{tokenInfo}",
|
||||
FontSize = 11,
|
||||
Foreground = new SolidColorBrush(Color.FromRgb(0x66, 0xBB, 0x6A)),
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
});
|
||||
banner.Child = sp;
|
||||
return banner;
|
||||
}
|
||||
|
||||
/// <summary>V2: 에러 배너</summary>
|
||||
private UIElement CreateV2ErrorBanner(AgentEvent agentEvent)
|
||||
{
|
||||
var msgMaxWidth = GetMessageMaxWidth();
|
||||
var banner = new Border
|
||||
{
|
||||
Background = new SolidColorBrush(Color.FromArgb(0x18, 0xEF, 0x53, 0x50)),
|
||||
BorderBrush = new SolidColorBrush(Color.FromArgb(0x40, 0xEF, 0x53, 0x50)),
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Padding = new Thickness(12, 6, 12, 6),
|
||||
Margin = new Thickness(0, 4, 0, 4),
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
MaxWidth = msgMaxWidth,
|
||||
};
|
||||
|
||||
var sp = new StackPanel { Orientation = Orientation.Horizontal };
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "\uE711", // X
|
||||
FontFamily = s_segoeIconFont,
|
||||
FontSize = 12,
|
||||
Foreground = new SolidColorBrush(Color.FromRgb(0xEF, 0x53, 0x50)),
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
Margin = new Thickness(0, 0, 6, 0),
|
||||
});
|
||||
sp.Children.Add(new TextBlock
|
||||
{
|
||||
Text = $"오류 발생: {agentEvent.Summary ?? "알 수 없는 오류"}",
|
||||
FontSize = 11,
|
||||
Foreground = new SolidColorBrush(Color.FromRgb(0xEF, 0x53, 0x50)),
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
});
|
||||
banner.Child = sp;
|
||||
return banner;
|
||||
}
|
||||
|
||||
/// <summary>V2: 일반 이벤트 pill (분류 안 되는 이벤트)</summary>
|
||||
private UIElement CreateV2GenericEventPill(AgentEvent agentEvent)
|
||||
{
|
||||
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||
var hintBg = TryFindResource("HintBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF));
|
||||
var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
|
||||
var msgMaxWidth = GetMessageMaxWidth();
|
||||
|
||||
var displayText = agentEvent.Summary ?? agentEvent.ToolName ?? agentEvent.Type.ToString();
|
||||
|
||||
var pill = new Border
|
||||
{
|
||||
Background = hintBg,
|
||||
BorderBrush = borderBrush,
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Padding = new Thickness(10, 4, 10, 4),
|
||||
Margin = new Thickness(0, 2, 0, 2),
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
MaxWidth = msgMaxWidth,
|
||||
};
|
||||
|
||||
pill.Child = new TextBlock
|
||||
{
|
||||
Text = displayText,
|
||||
FontSize = 10.5,
|
||||
Foreground = secondaryText,
|
||||
TextTrimming = TextTrimming.CharacterEllipsis,
|
||||
};
|
||||
|
||||
return pill;
|
||||
}
|
||||
|
||||
// ─── V2 헬퍼 ────────────────────────────────────────────────────────
|
||||
|
||||
private static (string Icon, Brush Color) GetV2ToolIcon(string? toolName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(toolName))
|
||||
return ("\uE90F", Brushes.Gray); // 기어
|
||||
|
||||
var lower = toolName.ToLowerInvariant();
|
||||
|
||||
if (lower.Contains("read") || lower.Contains("document_read"))
|
||||
return ("\uE8E5", new SolidColorBrush(Color.FromRgb(0x42, 0xA5, 0xF5))); // 파일 읽기 파랑
|
||||
|
||||
if (lower.Contains("write") || lower.Contains("edit") || lower.Contains("create"))
|
||||
return ("\uE70F", new SolidColorBrush(Color.FromRgb(0x66, 0xBB, 0x6A))); // 편집 초록
|
||||
|
||||
if (lower.Contains("search") || lower.Contains("web"))
|
||||
return ("\uE721", new SolidColorBrush(Color.FromRgb(0xFF, 0xB7, 0x4D))); // 검색 주황
|
||||
|
||||
if (lower.Contains("bash") || lower.Contains("script") || lower.Contains("run"))
|
||||
return ("\uE756", new SolidColorBrush(Color.FromRgb(0xAB, 0x47, 0xBC))); // 실행 보라
|
||||
|
||||
if (lower.Contains("delete") || lower.Contains("remove"))
|
||||
return ("\uE74D", new SolidColorBrush(Color.FromRgb(0xEF, 0x53, 0x50))); // 삭제 빨강
|
||||
|
||||
return ("\uE90F", new SolidColorBrush(Color.FromRgb(0x78, 0x90, 0x9C))); // 기타 회색
|
||||
}
|
||||
|
||||
private static string GetV2ToolDisplayName(string? toolName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(toolName)) return "Tool";
|
||||
|
||||
return toolName switch
|
||||
{
|
||||
"file_read" => "Read file",
|
||||
"document_read" => "Read document",
|
||||
"file_write" => "Write file",
|
||||
"file_edit" => "Edit file",
|
||||
"web_search" => "Web search",
|
||||
"bash" => "Run command",
|
||||
"script_run" => "Run script",
|
||||
"html_create" => "Create HTML",
|
||||
"docx_create" => "Create DOCX",
|
||||
"xlsx_create" or "excel_create" => "Create Excel",
|
||||
"pptx_create" => "Create PPTX",
|
||||
"csv_create" => "Create CSV",
|
||||
"markdown_create" or "md_create" => "Create Markdown",
|
||||
"context_compaction" => "Context compaction",
|
||||
"agent_wait" => "Waiting",
|
||||
_ => toolName,
|
||||
};
|
||||
}
|
||||
|
||||
private static string TruncateFilePath(string path, int maxLength)
|
||||
{
|
||||
if (string.IsNullOrEmpty(path) || path.Length <= maxLength)
|
||||
return path;
|
||||
|
||||
var fileName = System.IO.Path.GetFileName(path);
|
||||
if (fileName.Length >= maxLength - 3)
|
||||
return "..." + fileName[^(maxLength - 3)..];
|
||||
|
||||
var remaining = maxLength - fileName.Length - 4; // ".../" 포함
|
||||
if (remaining <= 0)
|
||||
return ".../" + fileName;
|
||||
|
||||
return path[..remaining] + ".../" + fileName;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user