AX Agent 실행 보조 UI를 축약형으로 안정화하고 메시지 축 흔들림을 줄임
Some checks failed
Release Gate / gate (push) Has been cancelled
Some checks failed
Release Gate / gate (push) Has been cancelled
- ChatWindow의 suggest_actions 결과를 본문 MessagePanel 직접 삽입 대신 토스트 요약으로 전환 - DraftQueuePanel을 기본 축약형으로 바꾸고 상세 보기 토글을 추가해 컴포저 위 레이아웃 변동을 줄임 - README와 DEVELOPMENT 문서에 2026-04-05 13:12 (KST) 기준 이력과 검증 결과를 반영 - 검증: 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:
@@ -121,6 +121,7 @@ public partial class ChatWindow : Window
|
||||
private int _sessionPostCompactionCompletionTokens;
|
||||
private bool _pendingExecutionHistoryAutoScroll;
|
||||
private readonly Dictionary<string, ChatConversation> _pendingConversationPersists = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly HashSet<string> _expandedDraftQueueTabs = new(StringComparer.OrdinalIgnoreCase);
|
||||
private void ApplyQuickActionVisual(Button button, bool active, string activeBg, string activeFg)
|
||||
{
|
||||
if (button?.Content is not string text)
|
||||
@@ -10031,11 +10032,9 @@ public partial class ChatWindow : Window
|
||||
/// <summary>suggest_actions 도구 결과를 클릭 가능한 칩으로 렌더링합니다.</summary>
|
||||
private void RenderSuggestActionChips(string jsonSummary)
|
||||
{
|
||||
// JSON에서 액션 목록 파싱 시도
|
||||
List<(string label, string command)> actions = new();
|
||||
try
|
||||
{
|
||||
// summary 형식: "label: command" 줄바꿈 구분 또는 JSON
|
||||
if (jsonSummary.Contains("\"label\""))
|
||||
{
|
||||
using var doc = System.Text.Json.JsonDocument.Parse(jsonSummary);
|
||||
@@ -10067,83 +10066,9 @@ public partial class ChatWindow : Window
|
||||
catch { return; }
|
||||
|
||||
if (actions.Count == 0) return;
|
||||
|
||||
var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
|
||||
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||
var hoverBg = TryFindResource("ItemHoverBackground") as Brush
|
||||
?? new SolidColorBrush(Color.FromArgb(0x18, 0xFF, 0xFF, 0xFF));
|
||||
|
||||
var container = new Border
|
||||
{
|
||||
Margin = new Thickness(40, 4, 40, 8),
|
||||
HorizontalAlignment = HorizontalAlignment.Stretch,
|
||||
};
|
||||
|
||||
var headerStack = new StackPanel { Margin = new Thickness(0, 0, 0, 6) };
|
||||
headerStack.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "💡 다음 작업 제안:",
|
||||
FontSize = 12,
|
||||
Foreground = secondaryText,
|
||||
});
|
||||
|
||||
var chipPanel = new WrapPanel { Margin = new Thickness(0, 2, 0, 0) };
|
||||
|
||||
foreach (var (label, command) in actions.Take(5))
|
||||
{
|
||||
var capturedCmd = command;
|
||||
var chip = new Border
|
||||
{
|
||||
CornerRadius = new CornerRadius(16),
|
||||
Padding = new Thickness(14, 7, 14, 7),
|
||||
Margin = new Thickness(0, 0, 8, 6),
|
||||
Cursor = Cursors.Hand,
|
||||
Background = new SolidColorBrush(Color.FromArgb(0x15,
|
||||
((SolidColorBrush)accentBrush).Color.R,
|
||||
((SolidColorBrush)accentBrush).Color.G,
|
||||
((SolidColorBrush)accentBrush).Color.B)),
|
||||
BorderBrush = new SolidColorBrush(Color.FromArgb(0x40,
|
||||
((SolidColorBrush)accentBrush).Color.R,
|
||||
((SolidColorBrush)accentBrush).Color.G,
|
||||
((SolidColorBrush)accentBrush).Color.B)),
|
||||
BorderThickness = new Thickness(1),
|
||||
};
|
||||
chip.Child = new TextBlock
|
||||
{
|
||||
Text = label,
|
||||
FontSize = 12.5,
|
||||
Foreground = accentBrush,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
};
|
||||
chip.MouseEnter += (s, _) => ((Border)s).Opacity = 0.8;
|
||||
chip.MouseLeave += (s, _) => ((Border)s).Opacity = 1.0;
|
||||
chip.MouseLeftButtonUp += (_, _) =>
|
||||
{
|
||||
// 칩 패널 제거 후 해당 명령 실행
|
||||
MessagePanel.Children.Remove(container);
|
||||
if (capturedCmd.StartsWith("/"))
|
||||
{
|
||||
InputBox.Text = capturedCmd + " ";
|
||||
InputBox.CaretIndex = InputBox.Text.Length;
|
||||
InputBox.Focus();
|
||||
}
|
||||
else
|
||||
{
|
||||
InputBox.Text = capturedCmd;
|
||||
_ = SendMessageAsync();
|
||||
}
|
||||
};
|
||||
chipPanel.Children.Add(chip);
|
||||
}
|
||||
|
||||
var outerStack = new StackPanel();
|
||||
outerStack.Children.Add(headerStack);
|
||||
outerStack.Children.Add(chipPanel);
|
||||
container.Child = outerStack;
|
||||
|
||||
ApplyMessageEntryAnimation(container);
|
||||
MessagePanel.Children.Add(container);
|
||||
ForceScrollToEnd();
|
||||
var preview = string.Join(", ", actions.Take(3).Select(static action => action.label));
|
||||
var suffix = actions.Count > 3 ? $" 외 {actions.Count - 3}개" : "";
|
||||
ShowToast($"다음 작업 제안 준비됨: {preview}{suffix}");
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════
|
||||
@@ -19652,6 +19577,17 @@ private static (string icon, string label, string bgHex, string fgHex) GetDecisi
|
||||
RebuildDraftQueuePanel(items);
|
||||
}
|
||||
|
||||
private bool IsDraftQueueExpanded()
|
||||
=> _expandedDraftQueueTabs.Contains(_activeTab);
|
||||
|
||||
private void ToggleDraftQueueExpanded()
|
||||
{
|
||||
if (!_expandedDraftQueueTabs.Add(_activeTab))
|
||||
_expandedDraftQueueTabs.Remove(_activeTab);
|
||||
|
||||
RefreshDraftQueueUi();
|
||||
}
|
||||
|
||||
private void RebuildDraftQueuePanel(IReadOnlyList<DraftQueueItem> items)
|
||||
{
|
||||
if (DraftQueuePanel == null)
|
||||
@@ -19673,7 +19609,13 @@ private static (string icon, string label, string bgHex, string fgHex) GetDecisi
|
||||
|
||||
DraftQueuePanel.Visibility = Visibility.Visible;
|
||||
var summary = _appState.GetDraftQueueSummary(_activeTab);
|
||||
DraftQueuePanel.Children.Add(CreateDraftQueueSummaryStrip(summary));
|
||||
DraftQueuePanel.Children.Add(CreateDraftQueueSummaryStrip(summary, IsDraftQueueExpanded()));
|
||||
if (!IsDraftQueueExpanded())
|
||||
{
|
||||
DraftQueuePanel.Children.Add(CreateCompactDraftQueuePanel(visibleItems, summary));
|
||||
return;
|
||||
}
|
||||
|
||||
const int maxPerSection = 3;
|
||||
var runningItems = visibleItems
|
||||
.Where(item => string.Equals(item.State, "running", StringComparison.OrdinalIgnoreCase))
|
||||
@@ -19720,6 +19662,74 @@ private static (string icon, string label, string bgHex, string fgHex) GetDecisi
|
||||
}
|
||||
}
|
||||
|
||||
private UIElement CreateCompactDraftQueuePanel(IReadOnlyList<DraftQueueItem> items, AppStateService.DraftQueueSummaryState summary)
|
||||
{
|
||||
var primaryText = TryFindResource("PrimaryText") as Brush ?? BrushFromHex("#111827");
|
||||
var secondaryText = TryFindResource("SecondaryText") as Brush ?? BrushFromHex("#6B7280");
|
||||
var background = TryFindResource("ItemBackground") as Brush ?? BrushFromHex("#F7F7F8");
|
||||
var borderBrush = TryFindResource("BorderColor") as Brush ?? BrushFromHex("#E4E4E7");
|
||||
var focusItem = items.FirstOrDefault(item => string.Equals(item.State, "running", StringComparison.OrdinalIgnoreCase))
|
||||
?? items.FirstOrDefault(item => string.Equals(item.State, "queued", StringComparison.OrdinalIgnoreCase) && !IsDraftBlocked(item))
|
||||
?? items.FirstOrDefault(IsDraftBlocked)
|
||||
?? items.FirstOrDefault(item => string.Equals(item.State, "failed", StringComparison.OrdinalIgnoreCase))
|
||||
?? items.FirstOrDefault(item => string.Equals(item.State, "completed", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
var container = new Border
|
||||
{
|
||||
Background = background,
|
||||
BorderBrush = borderBrush,
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(14),
|
||||
Padding = new Thickness(12, 10, 12, 10),
|
||||
Margin = new Thickness(0, 0, 0, 4),
|
||||
};
|
||||
|
||||
var root = new Grid();
|
||||
root.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
root.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||
container.Child = root;
|
||||
|
||||
var left = new StackPanel();
|
||||
left.Children.Add(new TextBlock
|
||||
{
|
||||
Text = focusItem == null
|
||||
? "대기열 항목이 준비되면 여기에서 요약됩니다."
|
||||
: $"{GetDraftStateLabel(focusItem)} · {GetDraftKindLabel(focusItem)}",
|
||||
FontSize = 11,
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = primaryText,
|
||||
});
|
||||
left.Children.Add(new TextBlock
|
||||
{
|
||||
Text = focusItem?.Text ?? BuildDraftQueueCompactSummaryText(summary),
|
||||
FontSize = 10.5,
|
||||
Foreground = secondaryText,
|
||||
TextTrimming = TextTrimming.CharacterEllipsis,
|
||||
Margin = new Thickness(0, 4, 0, 0),
|
||||
MaxWidth = 520,
|
||||
});
|
||||
Grid.SetColumn(left, 0);
|
||||
root.Children.Add(left);
|
||||
|
||||
var action = CreateDraftQueueActionButton("상세", ToggleDraftQueueExpanded);
|
||||
action.Margin = new Thickness(12, 0, 0, 0);
|
||||
Grid.SetColumn(action, 1);
|
||||
root.Children.Add(action);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private static string BuildDraftQueueCompactSummaryText(AppStateService.DraftQueueSummaryState summary)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
if (summary.RunningCount > 0) parts.Add($"실행 {summary.RunningCount}");
|
||||
if (summary.QueuedCount > 0) parts.Add($"다음 {summary.QueuedCount}");
|
||||
if (summary.BlockedCount > 0) parts.Add($"보류 {summary.BlockedCount}");
|
||||
if (summary.CompletedCount > 0) parts.Add($"완료 {summary.CompletedCount}");
|
||||
if (summary.FailedCount > 0) parts.Add($"실패 {summary.FailedCount}");
|
||||
return parts.Count == 0 ? "대기열 0" : string.Join(" · ", parts);
|
||||
}
|
||||
|
||||
private void AddDraftQueueSection(string label, IReadOnlyList<DraftQueueItem> items, int totalCount)
|
||||
{
|
||||
if (DraftQueuePanel == null || totalCount <= 0)
|
||||
@@ -19741,12 +19751,16 @@ private static (string icon, string label, string bgHex, string fgHex) GetDecisi
|
||||
}
|
||||
}
|
||||
|
||||
private UIElement CreateDraftQueueSummaryStrip(AppStateService.DraftQueueSummaryState summary)
|
||||
private UIElement CreateDraftQueueSummaryStrip(AppStateService.DraftQueueSummaryState summary, bool isExpanded)
|
||||
{
|
||||
var wrap = new WrapPanel
|
||||
var root = new Grid
|
||||
{
|
||||
Margin = new Thickness(0, 0, 0, 8),
|
||||
};
|
||||
root.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
root.ColumnDefinitions.Add(new ColumnDefinition { Width = GridLength.Auto });
|
||||
|
||||
var wrap = new WrapPanel();
|
||||
|
||||
if (summary.RunningCount > 0)
|
||||
wrap.Children.Add(CreateQueueSummaryPill("실행 중", summary.RunningCount.ToString(), "#EFF6FF", "#BFDBFE", "#1D4ED8"));
|
||||
@@ -19762,7 +19776,15 @@ private static (string icon, string label, string bgHex, string fgHex) GetDecisi
|
||||
if (wrap.Children.Count == 0)
|
||||
wrap.Children.Add(CreateQueueSummaryPill("대기열", "0", "#F8FAFC", "#E2E8F0", "#475569"));
|
||||
|
||||
return wrap;
|
||||
Grid.SetColumn(wrap, 0);
|
||||
root.Children.Add(wrap);
|
||||
|
||||
var toggle = CreateDraftQueueActionButton(isExpanded ? "간단히" : "상세 보기", ToggleDraftQueueExpanded);
|
||||
toggle.Margin = new Thickness(10, 0, 0, 0);
|
||||
Grid.SetColumn(toggle, 1);
|
||||
root.Children.Add(toggle);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private Border CreateQueueSummaryPill(string label, string value, string bgHex, string borderHex, string fgHex)
|
||||
|
||||
Reference in New Issue
Block a user