AX Agent 프리셋 렌더와 주제 선택 흐름을 분리해 메인 창 책임을 줄인다
Some checks failed
Release Gate / gate (push) Has been cancelled
Some checks failed
Release Gate / gate (push) Has been cancelled
- ChatWindow.TopicPresetPresentation을 추가해 프리셋 카드 생성, 커스텀 프리셋 메뉴, 주제 선택 적용 흐름을 별도 프레젠테이션 계층으로 이동한다 - ChatWindow.xaml.cs에서는 프리셋 UI 조립 코드를 제거하고 대화 orchestration 중심 구조를 더 강화한다 - claw-code parity plan과 개발 문서에 구조 개선 진행 상황을 반영해 큰 구조 개선 항목이 사실상 마감 단계임을 기록한다 - 검증: 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:
@@ -4919,3 +4919,5 @@ ow + toggle ?쒓컖 ?몄뼱濡??ㅼ떆 ?뺣젹?덈떎.
|
|||||||
- Document update: 2026-04-06 09:44 (KST) - At this point the completed structure-improvement items are: status presentation cataloging, permission/tool-result catalog enrichment, permission UI cleanup, and ask/plan renderer separation. The remaining larger tracks are footer/composer work-bar refinement and enforcing the regression prompt ritual in day-to-day development.
|
- Document update: 2026-04-06 09:44 (KST) - At this point the completed structure-improvement items are: status presentation cataloging, permission/tool-result catalog enrichment, permission UI cleanup, and ask/plan renderer separation. The remaining larger tracks are footer/composer work-bar refinement and enforcing the regression prompt ritual in day-to-day development.
|
||||||
- Document update: 2026-04-06 09:58 (KST) - Split Git branch popup assembly and footer-adjacent summary helpers out of `ChatWindow.FooterPresentation.cs` into `ChatWindow.GitBranchPresentation.cs`. The footer presentation partial now focuses on folder-bar state and selected-preset guide synchronization instead of mixed popup rendering.
|
- Document update: 2026-04-06 09:58 (KST) - Split Git branch popup assembly and footer-adjacent summary helpers out of `ChatWindow.FooterPresentation.cs` into `ChatWindow.GitBranchPresentation.cs`. The footer presentation partial now focuses on folder-bar state and selected-preset guide synchronization instead of mixed popup rendering.
|
||||||
- Document update: 2026-04-06 09:58 (KST) - Rewrote `docs/AX_AGENT_REGRESSION_PROMPTS.md` into a repeatable regression ritual with explicit failure classes (`blank-reply`, `duplicate-banner`, `bad-approval-flow`, `queue-drift`, `restore-drift`, `status-noise`) and required prompt bundles per change area so runtime/transcript work can be validated consistently.
|
- Document update: 2026-04-06 09:58 (KST) - Rewrote `docs/AX_AGENT_REGRESSION_PROMPTS.md` into a repeatable regression ritual with explicit failure classes (`blank-reply`, `duplicate-banner`, `bad-approval-flow`, `queue-drift`, `restore-drift`, `status-noise`) and required prompt bundles per change area so runtime/transcript work can be validated consistently.
|
||||||
|
- Document update: 2026-04-06 10:07 (KST) - Split topic preset rendering and selection flow out of `ChatWindow.xaml.cs` into `ChatWindow.TopicPresetPresentation.cs`. Preset card creation, custom preset dialogs/context menus, and `SelectTopic(...)` metadata application now live in a dedicated partial.
|
||||||
|
- Document update: 2026-04-06 10:07 (KST) - This keeps the main chat window more orchestration-focused and narrows the remaining maintainability work to small follow-up polish rather than any large structural split.
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
- Updated: 2026-04-06 09:58 (KST)
|
- Updated: 2026-04-06 09:58 (KST)
|
||||||
- Continued the maintainability track by splitting Git branch popup and footer-adjacent summary helpers into `ChatWindow.GitBranchPresentation.cs`, leaving `ChatWindow.FooterPresentation.cs` focused on folder bar state and preset-guide sync only.
|
- Continued the maintainability track by splitting Git branch popup and footer-adjacent summary helpers into `ChatWindow.GitBranchPresentation.cs`, leaving `ChatWindow.FooterPresentation.cs` focused on folder bar state and preset-guide sync only.
|
||||||
- Formalized the regression ritual in `docs/AX_AGENT_REGRESSION_PROMPTS.md` by adding failure classes (`blank-reply`, `duplicate-banner`, `bad-approval-flow`, `queue-drift`, `restore-drift`, `status-noise`) and required prompt bundles per change area.
|
- Formalized the regression ritual in `docs/AX_AGENT_REGRESSION_PROMPTS.md` by adding failure classes (`blank-reply`, `duplicate-banner`, `bad-approval-flow`, `queue-drift`, `restore-drift`, `status-noise`) and required prompt bundles per change area.
|
||||||
|
- Updated: 2026-04-06 10:07 (KST)
|
||||||
|
- Continued the maintainability track by moving topic preset rendering, custom preset context menus, and topic-selection application flow into `ChatWindow.TopicPresetPresentation.cs`. This reduces mixed preset UI logic inside `ChatWindow.xaml.cs` and keeps the main window closer to orchestration-only responsibility.
|
||||||
|
|
||||||
## Preserved History (Summary)
|
## Preserved History (Summary)
|
||||||
- Core loop guards and post-tool verification gates are already partially implemented.
|
- Core loop guards and post-tool verification gates are already partially implemented.
|
||||||
|
|||||||
523
src/AxCopilot/Views/ChatWindow.TopicPresetPresentation.cs
Normal file
523
src/AxCopilot/Views/ChatWindow.TopicPresetPresentation.cs
Normal file
@@ -0,0 +1,523 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Controls.Primitives;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using AxCopilot.Models;
|
||||||
|
using AxCopilot.Services;
|
||||||
|
|
||||||
|
namespace AxCopilot.Views;
|
||||||
|
|
||||||
|
public partial class ChatWindow
|
||||||
|
{
|
||||||
|
/// <summary>프리셋에서 대화 주제 버튼을 동적으로 생성합니다.</summary>
|
||||||
|
private void BuildTopicButtons()
|
||||||
|
{
|
||||||
|
TopicButtonPanel.Children.Clear();
|
||||||
|
TopicButtonPanel.Visibility = Visibility.Visible;
|
||||||
|
if (TopicPresetScrollViewer != null)
|
||||||
|
TopicPresetScrollViewer.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
|
if (_activeTab == "Cowork" || _activeTab == "Code")
|
||||||
|
{
|
||||||
|
if (EmptyStateTitle != null) EmptyStateTitle.Text = _activeTab == "Code"
|
||||||
|
? "코드 작업을 입력하세요"
|
||||||
|
: "작업 유형을 선택하세요";
|
||||||
|
if (EmptyStateDesc != null) EmptyStateDesc.Text = _activeTab == "Code"
|
||||||
|
? "코딩 에이전트가 코드 분석, 수정, 빌드, 테스트를 수행합니다"
|
||||||
|
: "에이전트가 상세한 데이터를 작성합니다";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (EmptyStateTitle != null) EmptyStateTitle.Text = "대화 주제를 선택하세요";
|
||||||
|
if (EmptyStateDesc != null) EmptyStateDesc.Text = "주제에 맞는 전문 프리셋이 자동 적용됩니다";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_activeTab == "Code")
|
||||||
|
{
|
||||||
|
TopicButtonPanel.Visibility = Visibility.Collapsed;
|
||||||
|
if (TopicPresetScrollViewer != null)
|
||||||
|
TopicPresetScrollViewer.Visibility = Visibility.Collapsed;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var presets = Services.PresetService.GetByTabWithCustom(_activeTab, _settings.Settings.Llm.CustomPresets);
|
||||||
|
var cardBackground = TryFindResource("ItemBackground") as Brush ?? Brushes.Transparent;
|
||||||
|
var cardHoverBackground = TryFindResource("ItemHoverBackground") as Brush ?? Brushes.Transparent;
|
||||||
|
var cardBorder = TryFindResource("BorderColor") as Brush ?? BrushFromHex("#E5E7EB");
|
||||||
|
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
||||||
|
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
||||||
|
|
||||||
|
void AttachTopicCardHover(Border card, Brush normalBackground, Brush hoverBackground)
|
||||||
|
{
|
||||||
|
card.MouseEnter += (sender, _) =>
|
||||||
|
{
|
||||||
|
if (sender is Border hovered)
|
||||||
|
{
|
||||||
|
hovered.Background = hoverBackground;
|
||||||
|
hovered.BorderBrush = TryFindResource("AccentColor") as Brush ?? cardBorder;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
card.MouseLeave += (sender, _) =>
|
||||||
|
{
|
||||||
|
if (sender is Border hovered)
|
||||||
|
{
|
||||||
|
hovered.Background = normalBackground;
|
||||||
|
hovered.BorderBrush = cardBorder;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var preset in presets)
|
||||||
|
{
|
||||||
|
var capturedPreset = preset;
|
||||||
|
var buttonColor = BrushFromHex(preset.Color);
|
||||||
|
|
||||||
|
var border = new Border
|
||||||
|
{
|
||||||
|
Background = cardBackground,
|
||||||
|
BorderBrush = cardBorder,
|
||||||
|
BorderThickness = new Thickness(1),
|
||||||
|
CornerRadius = new CornerRadius(14),
|
||||||
|
Padding = new Thickness(14, 14, 14, 12),
|
||||||
|
Margin = new Thickness(6, 6, 6, 8),
|
||||||
|
Cursor = Cursors.Hand,
|
||||||
|
Width = 148,
|
||||||
|
Height = 124,
|
||||||
|
ClipToBounds = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var contentGrid = new Grid();
|
||||||
|
var stack = new StackPanel
|
||||||
|
{
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
};
|
||||||
|
|
||||||
|
var iconCircle = new Border
|
||||||
|
{
|
||||||
|
Width = 34,
|
||||||
|
Height = 34,
|
||||||
|
CornerRadius = new CornerRadius(17),
|
||||||
|
Background = new SolidColorBrush(((SolidColorBrush)buttonColor).Color) { Opacity = 0.15 },
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
Margin = new Thickness(0, 0, 0, 9),
|
||||||
|
};
|
||||||
|
var iconBlock = new TextBlock
|
||||||
|
{
|
||||||
|
Text = preset.Symbol,
|
||||||
|
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||||
|
FontSize = 15,
|
||||||
|
Foreground = buttonColor,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
};
|
||||||
|
iconCircle.Child = iconBlock;
|
||||||
|
stack.Children.Add(iconCircle);
|
||||||
|
|
||||||
|
stack.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = preset.Label,
|
||||||
|
FontSize = 15,
|
||||||
|
FontWeight = FontWeights.SemiBold,
|
||||||
|
Foreground = primaryText,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
TextAlignment = TextAlignment.Center,
|
||||||
|
TextWrapping = TextWrapping.Wrap,
|
||||||
|
MaxWidth = 112,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (capturedPreset.IsCustom)
|
||||||
|
{
|
||||||
|
contentGrid.Children.Add(stack);
|
||||||
|
var badge = new Border
|
||||||
|
{
|
||||||
|
Width = 16,
|
||||||
|
Height = 16,
|
||||||
|
CornerRadius = new CornerRadius(4),
|
||||||
|
Background = new SolidColorBrush(Color.FromArgb(0x60, 0xFF, 0xFF, 0xFF)),
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Left,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top,
|
||||||
|
Margin = new Thickness(2, 2, 0, 0),
|
||||||
|
};
|
||||||
|
badge.Child = new TextBlock
|
||||||
|
{
|
||||||
|
Text = "\uE710",
|
||||||
|
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||||
|
FontSize = 8,
|
||||||
|
Foreground = buttonColor,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
};
|
||||||
|
contentGrid.Children.Add(badge);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
contentGrid.Children.Add(stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
border.Child = contentGrid;
|
||||||
|
AttachTopicCardHover(border, cardBackground, cardHoverBackground);
|
||||||
|
border.MouseLeftButtonDown += (_, _) => SelectTopic(capturedPreset);
|
||||||
|
|
||||||
|
if (capturedPreset.IsCustom)
|
||||||
|
{
|
||||||
|
border.MouseRightButtonUp += (sender, args) =>
|
||||||
|
{
|
||||||
|
args.Handled = true;
|
||||||
|
ShowCustomPresetContextMenu(sender as Border, capturedPreset);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
TopicButtonPanel.Children.Add(border);
|
||||||
|
}
|
||||||
|
|
||||||
|
var etcColor = BrushFromHex("#6B7280");
|
||||||
|
var etcBorder = new Border
|
||||||
|
{
|
||||||
|
Background = cardBackground,
|
||||||
|
BorderBrush = cardBorder,
|
||||||
|
BorderThickness = new Thickness(1),
|
||||||
|
CornerRadius = new CornerRadius(14),
|
||||||
|
Padding = new Thickness(14, 14, 14, 12),
|
||||||
|
Margin = new Thickness(6, 6, 6, 8),
|
||||||
|
Cursor = Cursors.Hand,
|
||||||
|
Width = 148,
|
||||||
|
Height = 124,
|
||||||
|
ClipToBounds = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var etcGrid = new Grid();
|
||||||
|
var etcStack = new StackPanel
|
||||||
|
{
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
};
|
||||||
|
|
||||||
|
var etcIconCircle = new Border
|
||||||
|
{
|
||||||
|
Width = 34,
|
||||||
|
Height = 34,
|
||||||
|
CornerRadius = new CornerRadius(17),
|
||||||
|
Background = new SolidColorBrush(((SolidColorBrush)etcColor).Color) { Opacity = 0.15 },
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
Margin = new Thickness(0, 0, 0, 9),
|
||||||
|
};
|
||||||
|
etcIconCircle.Child = new TextBlock
|
||||||
|
{
|
||||||
|
Text = "\uE70F",
|
||||||
|
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||||
|
FontSize = 15,
|
||||||
|
Foreground = etcColor,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
};
|
||||||
|
etcStack.Children.Add(etcIconCircle);
|
||||||
|
|
||||||
|
etcStack.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = "기타",
|
||||||
|
FontSize = 15,
|
||||||
|
FontWeight = FontWeights.SemiBold,
|
||||||
|
Foreground = primaryText,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
TextAlignment = TextAlignment.Center,
|
||||||
|
});
|
||||||
|
etcGrid.Children.Add(etcStack);
|
||||||
|
etcBorder.Child = etcGrid;
|
||||||
|
AttachTopicCardHover(etcBorder, cardBackground, cardHoverBackground);
|
||||||
|
etcBorder.MouseLeftButtonDown += (_, _) =>
|
||||||
|
{
|
||||||
|
EmptyState.Visibility = Visibility.Collapsed;
|
||||||
|
InputBox.Focus();
|
||||||
|
};
|
||||||
|
TopicButtonPanel.Children.Add(etcBorder);
|
||||||
|
|
||||||
|
var addBorder = new Border
|
||||||
|
{
|
||||||
|
Background = Brushes.Transparent,
|
||||||
|
BorderBrush = cardBorder,
|
||||||
|
BorderThickness = new Thickness(1),
|
||||||
|
CornerRadius = new CornerRadius(14),
|
||||||
|
Padding = new Thickness(14, 14, 14, 12),
|
||||||
|
Margin = new Thickness(6, 6, 6, 8),
|
||||||
|
Cursor = Cursors.Hand,
|
||||||
|
Width = 148,
|
||||||
|
Height = 124,
|
||||||
|
ClipToBounds = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var addGrid = new Grid();
|
||||||
|
var addStack = new StackPanel
|
||||||
|
{
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
};
|
||||||
|
|
||||||
|
addStack.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = "\uE710",
|
||||||
|
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||||
|
FontSize = 18,
|
||||||
|
Foreground = secondaryText,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
Margin = new Thickness(0, 8, 0, 8),
|
||||||
|
});
|
||||||
|
addStack.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = "프리셋 추가",
|
||||||
|
FontSize = 14,
|
||||||
|
FontWeight = FontWeights.SemiBold,
|
||||||
|
Foreground = secondaryText,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
});
|
||||||
|
|
||||||
|
addGrid.Children.Add(addStack);
|
||||||
|
addBorder.Child = addGrid;
|
||||||
|
AttachTopicCardHover(addBorder, Brushes.Transparent, cardHoverBackground);
|
||||||
|
addBorder.MouseLeftButtonDown += (_, _) => ShowCustomPresetDialog();
|
||||||
|
TopicButtonPanel.Children.Add(addBorder);
|
||||||
|
|
||||||
|
UpdateTopicPresetScrollMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTopicPresetScrollMode()
|
||||||
|
{
|
||||||
|
if (TopicPresetScrollViewer == null || TopicButtonPanel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Dispatcher.BeginInvoke(new Action(() =>
|
||||||
|
{
|
||||||
|
if (TopicPresetScrollViewer == null || TopicButtonPanel == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TopicPresetScrollViewer.UpdateLayout();
|
||||||
|
var shouldScroll = TopicPresetScrollViewer.ExtentHeight > TopicPresetScrollViewer.ViewportHeight + 1;
|
||||||
|
TopicPresetScrollViewer.VerticalScrollBarVisibility = shouldScroll
|
||||||
|
? ScrollBarVisibility.Auto
|
||||||
|
: ScrollBarVisibility.Disabled;
|
||||||
|
TopicPresetScrollViewer.Padding = shouldScroll
|
||||||
|
? new Thickness(0, 2, 6, 0)
|
||||||
|
: new Thickness(0, 2, 0, 0);
|
||||||
|
}), System.Windows.Threading.DispatcherPriority.Loaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowCustomPresetDialog(CustomPresetEntry? existing = null)
|
||||||
|
{
|
||||||
|
var dialog = new CustomPresetDialog(
|
||||||
|
existingName: existing?.Label ?? "",
|
||||||
|
existingDesc: existing?.Description ?? "",
|
||||||
|
existingPrompt: existing?.SystemPrompt ?? "",
|
||||||
|
existingColor: existing?.Color ?? "#6366F1",
|
||||||
|
existingSymbol: existing?.Symbol ?? "\uE713",
|
||||||
|
existingTab: existing?.Tab ?? _activeTab)
|
||||||
|
{
|
||||||
|
Owner = this,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dialog.ShowDialog() != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
existing.Label = dialog.PresetName;
|
||||||
|
existing.Description = dialog.PresetDescription;
|
||||||
|
existing.SystemPrompt = dialog.PresetSystemPrompt;
|
||||||
|
existing.Color = dialog.PresetColor;
|
||||||
|
existing.Symbol = dialog.PresetSymbol;
|
||||||
|
existing.Tab = dialog.PresetTab;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_settings.Settings.Llm.CustomPresets.Add(new CustomPresetEntry
|
||||||
|
{
|
||||||
|
Label = dialog.PresetName,
|
||||||
|
Description = dialog.PresetDescription,
|
||||||
|
SystemPrompt = dialog.PresetSystemPrompt,
|
||||||
|
Color = dialog.PresetColor,
|
||||||
|
Symbol = dialog.PresetSymbol,
|
||||||
|
Tab = dialog.PresetTab,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_settings.Save();
|
||||||
|
BuildTopicButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowCustomPresetContextMenu(Border? anchor, Services.TopicPreset preset)
|
||||||
|
{
|
||||||
|
if (anchor == null || preset.CustomId == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var popup = new Popup
|
||||||
|
{
|
||||||
|
PlacementTarget = anchor,
|
||||||
|
Placement = PlacementMode.Bottom,
|
||||||
|
StaysOpen = false,
|
||||||
|
AllowsTransparency = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
var menuBackground = TryFindResource("LauncherBackground") as Brush ?? Brushes.Black;
|
||||||
|
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 menuBorder = new Border
|
||||||
|
{
|
||||||
|
Background = menuBackground,
|
||||||
|
CornerRadius = new CornerRadius(10),
|
||||||
|
BorderBrush = borderBrush,
|
||||||
|
BorderThickness = new Thickness(1),
|
||||||
|
Padding = new Thickness(4),
|
||||||
|
MinWidth = 120,
|
||||||
|
Effect = new System.Windows.Media.Effects.DropShadowEffect
|
||||||
|
{
|
||||||
|
BlurRadius = 12,
|
||||||
|
ShadowDepth = 2,
|
||||||
|
Opacity = 0.3,
|
||||||
|
Color = Colors.Black,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var stack = new StackPanel();
|
||||||
|
|
||||||
|
var editItem = CreateContextMenuItem("\uE70F", "편집", primaryText);
|
||||||
|
editItem.MouseLeftButtonDown += (_, _) =>
|
||||||
|
{
|
||||||
|
popup.IsOpen = false;
|
||||||
|
var entry = _settings.Settings.Llm.CustomPresets.FirstOrDefault(item => item.Id == preset.CustomId);
|
||||||
|
if (entry != null)
|
||||||
|
ShowCustomPresetDialog(entry);
|
||||||
|
};
|
||||||
|
stack.Children.Add(editItem);
|
||||||
|
|
||||||
|
var deleteItem = CreateContextMenuItem("\uE74D", "삭제", new SolidColorBrush(Color.FromRgb(0xEF, 0x44, 0x44)));
|
||||||
|
deleteItem.MouseLeftButtonDown += (_, _) =>
|
||||||
|
{
|
||||||
|
popup.IsOpen = false;
|
||||||
|
var result = CustomMessageBox.Show(
|
||||||
|
$"'{preset.Label}' 프리셋을 삭제하시겠습니까?",
|
||||||
|
"프리셋 삭제",
|
||||||
|
MessageBoxButton.YesNo,
|
||||||
|
MessageBoxImage.Question);
|
||||||
|
if (result != MessageBoxResult.Yes)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_settings.Settings.Llm.CustomPresets.RemoveAll(item => item.Id == preset.CustomId);
|
||||||
|
_settings.Save();
|
||||||
|
BuildTopicButtons();
|
||||||
|
};
|
||||||
|
stack.Children.Add(deleteItem);
|
||||||
|
|
||||||
|
menuBorder.Child = stack;
|
||||||
|
popup.Child = menuBorder;
|
||||||
|
popup.IsOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Border CreateContextMenuItem(string icon, string label, Brush foreground)
|
||||||
|
{
|
||||||
|
var item = new Border
|
||||||
|
{
|
||||||
|
Background = Brushes.Transparent,
|
||||||
|
CornerRadius = new CornerRadius(6),
|
||||||
|
Padding = new Thickness(10, 6, 14, 6),
|
||||||
|
Cursor = Cursors.Hand,
|
||||||
|
};
|
||||||
|
|
||||||
|
var stack = new StackPanel { Orientation = Orientation.Horizontal };
|
||||||
|
stack.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = icon,
|
||||||
|
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
||||||
|
FontSize = 13,
|
||||||
|
Foreground = foreground,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
Margin = new Thickness(0, 0, 8, 0),
|
||||||
|
});
|
||||||
|
stack.Children.Add(new TextBlock
|
||||||
|
{
|
||||||
|
Text = label,
|
||||||
|
FontSize = 13,
|
||||||
|
Foreground = foreground,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
});
|
||||||
|
item.Child = stack;
|
||||||
|
|
||||||
|
var hoverBackground = TryFindResource("ItemHoverBackground") as Brush ?? Brushes.Transparent;
|
||||||
|
item.MouseEnter += (sender, _) =>
|
||||||
|
{
|
||||||
|
if (sender is Border hovered)
|
||||||
|
hovered.Background = hoverBackground;
|
||||||
|
};
|
||||||
|
item.MouseLeave += (sender, _) =>
|
||||||
|
{
|
||||||
|
if (sender is Border hovered)
|
||||||
|
hovered.Background = Brushes.Transparent;
|
||||||
|
};
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Border CreateContextMenuItem(string icon, string label, Brush foreground, Brush secondaryForeground)
|
||||||
|
=> CreateContextMenuItem(icon, label, foreground);
|
||||||
|
|
||||||
|
private void SelectTopic(Services.TopicPreset preset)
|
||||||
|
{
|
||||||
|
bool hasConversation;
|
||||||
|
bool hasMessages;
|
||||||
|
lock (_convLock)
|
||||||
|
{
|
||||||
|
hasConversation = _currentConversation != null;
|
||||||
|
hasMessages = _currentConversation?.Messages.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hasInput = !string.IsNullOrEmpty(InputBox.Text);
|
||||||
|
if (!hasConversation)
|
||||||
|
StartNewConversation();
|
||||||
|
|
||||||
|
lock (_convLock)
|
||||||
|
{
|
||||||
|
if (_currentConversation == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var session = ChatSession;
|
||||||
|
if (session != null)
|
||||||
|
{
|
||||||
|
_currentConversation = session.UpdateConversationMetadata(_activeTab, conversation =>
|
||||||
|
{
|
||||||
|
conversation.SystemCommand = preset.SystemPrompt;
|
||||||
|
conversation.Category = preset.Category;
|
||||||
|
}, _storage);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_currentConversation.SystemCommand = preset.SystemPrompt;
|
||||||
|
_currentConversation.Category = preset.Category;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateCategoryLabel();
|
||||||
|
SaveConversationSettings();
|
||||||
|
RefreshConversationList();
|
||||||
|
UpdateSelectedPresetGuide();
|
||||||
|
if (EmptyState != null)
|
||||||
|
EmptyState.Visibility = Visibility.Collapsed;
|
||||||
|
|
||||||
|
InputBox.Focus();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(preset.Placeholder))
|
||||||
|
{
|
||||||
|
_promptCardPlaceholder = preset.Placeholder;
|
||||||
|
if (!hasMessages && !hasInput)
|
||||||
|
ShowPlaceholder();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasMessages || hasInput)
|
||||||
|
ShowToast($"프리셋 변경: {preset.Label}");
|
||||||
|
|
||||||
|
if (_activeTab == "Cowork")
|
||||||
|
BuildBottomBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10110,521 +10110,6 @@ public partial class ChatWindow : Window
|
|||||||
_toastHideTimer.Start();
|
_toastHideTimer.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── 대화 주제 버튼 ──────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// <summary>프리셋에서 대화 주제 버튼을 동적으로 생성합니다.</summary>
|
|
||||||
private void BuildTopicButtons()
|
|
||||||
{
|
|
||||||
TopicButtonPanel.Children.Clear();
|
|
||||||
TopicButtonPanel.Visibility = Visibility.Visible;
|
|
||||||
if (TopicPresetScrollViewer != null)
|
|
||||||
TopicPresetScrollViewer.Visibility = Visibility.Visible;
|
|
||||||
|
|
||||||
// 탭별 EmptyState 텍스트
|
|
||||||
if (_activeTab == "Cowork" || _activeTab == "Code")
|
|
||||||
{
|
|
||||||
if (EmptyStateTitle != null) EmptyStateTitle.Text = _activeTab == "Code"
|
|
||||||
? "코드 작업을 입력하세요"
|
|
||||||
: "작업 유형을 선택하세요";
|
|
||||||
if (EmptyStateDesc != null) EmptyStateDesc.Text = _activeTab == "Code"
|
|
||||||
? "코딩 에이전트가 코드 분석, 수정, 빌드, 테스트를 수행합니다"
|
|
||||||
: "에이전트가 상세한 데이터를 작성합니다";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (EmptyStateTitle != null) EmptyStateTitle.Text = "대화 주제를 선택하세요";
|
|
||||||
if (EmptyStateDesc != null) EmptyStateDesc.Text = "주제에 맞는 전문 프리셋이 자동 적용됩니다";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_activeTab == "Code")
|
|
||||||
{
|
|
||||||
TopicButtonPanel.Visibility = Visibility.Collapsed;
|
|
||||||
if (TopicPresetScrollViewer != null)
|
|
||||||
TopicPresetScrollViewer.Visibility = Visibility.Collapsed;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var presets = Services.PresetService.GetByTabWithCustom(_activeTab, _settings.Settings.Llm.CustomPresets);
|
|
||||||
var cardBackground = TryFindResource("ItemBackground") as Brush ?? Brushes.Transparent;
|
|
||||||
var cardHoverBackground = TryFindResource("ItemHoverBackground") as Brush ?? Brushes.Transparent;
|
|
||||||
var cardBorder = TryFindResource("BorderColor") as Brush ?? BrushFromHex("#E5E7EB");
|
|
||||||
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
|
||||||
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
|
||||||
|
|
||||||
void AttachTopicCardHover(Border card, Brush normalBackground, Brush hoverBackground)
|
|
||||||
{
|
|
||||||
card.MouseEnter += (s, _) =>
|
|
||||||
{
|
|
||||||
if (s is Border b)
|
|
||||||
{
|
|
||||||
b.Background = hoverBackground;
|
|
||||||
b.BorderBrush = TryFindResource("AccentColor") as Brush ?? cardBorder;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
card.MouseLeave += (s, _) =>
|
|
||||||
{
|
|
||||||
if (s is Border b)
|
|
||||||
{
|
|
||||||
b.Background = normalBackground;
|
|
||||||
b.BorderBrush = cardBorder;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var preset in presets)
|
|
||||||
{
|
|
||||||
var capturedPreset = preset;
|
|
||||||
var btnColor = BrushFromHex(preset.Color);
|
|
||||||
|
|
||||||
var border = new Border
|
|
||||||
{
|
|
||||||
Background = cardBackground,
|
|
||||||
BorderBrush = cardBorder,
|
|
||||||
BorderThickness = new Thickness(1),
|
|
||||||
CornerRadius = new CornerRadius(14),
|
|
||||||
Padding = new Thickness(14, 14, 14, 12),
|
|
||||||
Margin = new Thickness(6, 6, 6, 8),
|
|
||||||
Cursor = Cursors.Hand,
|
|
||||||
Width = 148,
|
|
||||||
Height = 124,
|
|
||||||
ClipToBounds = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
var contentGrid = new Grid();
|
|
||||||
var stack = new StackPanel
|
|
||||||
{
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
|
||||||
};
|
|
||||||
|
|
||||||
var iconCircle = new Border
|
|
||||||
{
|
|
||||||
Width = 34, Height = 34,
|
|
||||||
CornerRadius = new CornerRadius(17),
|
|
||||||
Background = new SolidColorBrush(((SolidColorBrush)btnColor).Color) { Opacity = 0.15 },
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
Margin = new Thickness(0, 0, 0, 9),
|
|
||||||
};
|
|
||||||
var iconTb = new TextBlock
|
|
||||||
{
|
|
||||||
Text = preset.Symbol,
|
|
||||||
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
|
||||||
FontSize = 15,
|
|
||||||
Foreground = btnColor,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
|
||||||
};
|
|
||||||
iconCircle.Child = iconTb;
|
|
||||||
stack.Children.Add(iconCircle);
|
|
||||||
|
|
||||||
stack.Children.Add(new TextBlock
|
|
||||||
{
|
|
||||||
Text = preset.Label,
|
|
||||||
FontSize = 15,
|
|
||||||
FontWeight = FontWeights.SemiBold,
|
|
||||||
Foreground = primaryText,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
TextAlignment = TextAlignment.Center,
|
|
||||||
TextWrapping = TextWrapping.Wrap,
|
|
||||||
MaxWidth = 112,
|
|
||||||
});
|
|
||||||
|
|
||||||
// 커스텀 프리셋: 좌측 상단 뱃지
|
|
||||||
if (capturedPreset.IsCustom)
|
|
||||||
{
|
|
||||||
contentGrid.Children.Add(stack);
|
|
||||||
var badge = new Border
|
|
||||||
{
|
|
||||||
Width = 16, Height = 16,
|
|
||||||
CornerRadius = new CornerRadius(4),
|
|
||||||
Background = new SolidColorBrush(Color.FromArgb(0x60, 0xFF, 0xFF, 0xFF)),
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Left,
|
|
||||||
VerticalAlignment = VerticalAlignment.Top,
|
|
||||||
Margin = new Thickness(2, 2, 0, 0),
|
|
||||||
};
|
|
||||||
badge.Child = new TextBlock
|
|
||||||
{
|
|
||||||
Text = "\uE710", // + 아이콘
|
|
||||||
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
|
||||||
FontSize = 8,
|
|
||||||
Foreground = btnColor,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
|
||||||
};
|
|
||||||
contentGrid.Children.Add(badge);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
contentGrid.Children.Add(stack);
|
|
||||||
}
|
|
||||||
|
|
||||||
border.Child = contentGrid;
|
|
||||||
AttachTopicCardHover(border, cardBackground, cardHoverBackground);
|
|
||||||
// 클릭 → 해당 주제로 새 대화 시작
|
|
||||||
border.MouseLeftButtonDown += (_, _) => SelectTopic(capturedPreset);
|
|
||||||
|
|
||||||
// 커스텀 프리셋: 우클릭 메뉴 (편집/삭제)
|
|
||||||
if (capturedPreset.IsCustom)
|
|
||||||
{
|
|
||||||
border.MouseRightButtonUp += (s, e) =>
|
|
||||||
{
|
|
||||||
e.Handled = true;
|
|
||||||
ShowCustomPresetContextMenu(s as Border, capturedPreset);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
TopicButtonPanel.Children.Add(border);
|
|
||||||
}
|
|
||||||
|
|
||||||
// "기타" 자유 입력 버튼 추가
|
|
||||||
{
|
|
||||||
var etcColor = BrushFromHex("#6B7280"); // 회색
|
|
||||||
var etcBorder = new Border
|
|
||||||
{
|
|
||||||
Background = cardBackground,
|
|
||||||
BorderBrush = cardBorder,
|
|
||||||
BorderThickness = new Thickness(1),
|
|
||||||
CornerRadius = new CornerRadius(14),
|
|
||||||
Padding = new Thickness(14, 14, 14, 12),
|
|
||||||
Margin = new Thickness(6, 6, 6, 8),
|
|
||||||
Cursor = Cursors.Hand,
|
|
||||||
Width = 148,
|
|
||||||
Height = 124,
|
|
||||||
ClipToBounds = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
var etcGrid = new Grid();
|
|
||||||
var etcStack = new StackPanel
|
|
||||||
{
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
|
||||||
};
|
|
||||||
|
|
||||||
var etcIconCircle = new Border
|
|
||||||
{
|
|
||||||
Width = 34, Height = 34,
|
|
||||||
CornerRadius = new CornerRadius(17),
|
|
||||||
Background = new SolidColorBrush(((SolidColorBrush)etcColor).Color) { Opacity = 0.15 },
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
Margin = new Thickness(0, 0, 0, 9),
|
|
||||||
};
|
|
||||||
etcIconCircle.Child = new TextBlock
|
|
||||||
{
|
|
||||||
Text = "\uE70F", // Edit 아이콘
|
|
||||||
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
|
||||||
FontSize = 15,
|
|
||||||
Foreground = etcColor,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
|
||||||
};
|
|
||||||
etcStack.Children.Add(etcIconCircle);
|
|
||||||
|
|
||||||
etcStack.Children.Add(new TextBlock
|
|
||||||
{
|
|
||||||
Text = "기타",
|
|
||||||
FontSize = 15,
|
|
||||||
FontWeight = FontWeights.SemiBold,
|
|
||||||
Foreground = primaryText,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
TextAlignment = TextAlignment.Center,
|
|
||||||
});
|
|
||||||
etcGrid.Children.Add(etcStack);
|
|
||||||
etcBorder.Child = etcGrid;
|
|
||||||
AttachTopicCardHover(etcBorder, cardBackground, cardHoverBackground);
|
|
||||||
etcBorder.MouseLeftButtonDown += (_, _) =>
|
|
||||||
{
|
|
||||||
EmptyState.Visibility = Visibility.Collapsed;
|
|
||||||
InputBox.Focus();
|
|
||||||
};
|
|
||||||
TopicButtonPanel.Children.Add(etcBorder);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── "+" 커스텀 프리셋 추가 버튼 ──
|
|
||||||
{
|
|
||||||
var addColor = BrushFromHex("#6366F1");
|
|
||||||
var addBorder = new Border
|
|
||||||
{
|
|
||||||
Background = Brushes.Transparent,
|
|
||||||
BorderBrush = cardBorder,
|
|
||||||
BorderThickness = new Thickness(1),
|
|
||||||
CornerRadius = new CornerRadius(14),
|
|
||||||
Padding = new Thickness(14, 14, 14, 12),
|
|
||||||
Margin = new Thickness(6, 6, 6, 8),
|
|
||||||
Cursor = Cursors.Hand,
|
|
||||||
Width = 148,
|
|
||||||
Height = 124,
|
|
||||||
ClipToBounds = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
var addGrid = new Grid();
|
|
||||||
var addStack = new StackPanel { HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center };
|
|
||||||
|
|
||||||
// + 아이콘
|
|
||||||
var plusIcon = new TextBlock
|
|
||||||
{
|
|
||||||
Text = "\uE710",
|
|
||||||
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
|
||||||
FontSize = 18,
|
|
||||||
Foreground = secondaryText,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
Margin = new Thickness(0, 8, 0, 8),
|
|
||||||
};
|
|
||||||
addStack.Children.Add(plusIcon);
|
|
||||||
|
|
||||||
addStack.Children.Add(new TextBlock
|
|
||||||
{
|
|
||||||
Text = "프리셋 추가",
|
|
||||||
FontSize = 14,
|
|
||||||
FontWeight = FontWeights.SemiBold,
|
|
||||||
Foreground = secondaryText,
|
|
||||||
HorizontalAlignment = HorizontalAlignment.Center,
|
|
||||||
});
|
|
||||||
|
|
||||||
addGrid.Children.Add(addStack);
|
|
||||||
addBorder.Child = addGrid;
|
|
||||||
AttachTopicCardHover(addBorder, Brushes.Transparent, cardHoverBackground);
|
|
||||||
addBorder.MouseLeftButtonDown += (_, _) => ShowCustomPresetDialog();
|
|
||||||
TopicButtonPanel.Children.Add(addBorder);
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateTopicPresetScrollMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void UpdateTopicPresetScrollMode()
|
|
||||||
{
|
|
||||||
if (TopicPresetScrollViewer == null || TopicButtonPanel == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Dispatcher.BeginInvoke(new Action(() =>
|
|
||||||
{
|
|
||||||
if (TopicPresetScrollViewer == null || TopicButtonPanel == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
TopicPresetScrollViewer.UpdateLayout();
|
|
||||||
var shouldScroll = TopicPresetScrollViewer.ExtentHeight > TopicPresetScrollViewer.ViewportHeight + 1;
|
|
||||||
TopicPresetScrollViewer.VerticalScrollBarVisibility = shouldScroll
|
|
||||||
? ScrollBarVisibility.Auto
|
|
||||||
: ScrollBarVisibility.Disabled;
|
|
||||||
TopicPresetScrollViewer.Padding = shouldScroll
|
|
||||||
? new Thickness(0, 2, 6, 0)
|
|
||||||
: new Thickness(0, 2, 0, 0);
|
|
||||||
}), System.Windows.Threading.DispatcherPriority.Loaded);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ─── 커스텀 프리셋 관리 ─────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// <summary>커스텀 프리셋 추가 다이얼로그를 표시합니다.</summary>
|
|
||||||
private void ShowCustomPresetDialog(Models.CustomPresetEntry? existing = null)
|
|
||||||
{
|
|
||||||
bool isEdit = existing != null;
|
|
||||||
var dlg = new CustomPresetDialog(
|
|
||||||
existingName: existing?.Label ?? "",
|
|
||||||
existingDesc: existing?.Description ?? "",
|
|
||||||
existingPrompt: existing?.SystemPrompt ?? "",
|
|
||||||
existingColor: existing?.Color ?? "#6366F1",
|
|
||||||
existingSymbol: existing?.Symbol ?? "\uE713",
|
|
||||||
existingTab: existing?.Tab ?? _activeTab)
|
|
||||||
{
|
|
||||||
Owner = this,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (dlg.ShowDialog() == true)
|
|
||||||
{
|
|
||||||
if (isEdit)
|
|
||||||
{
|
|
||||||
existing!.Label = dlg.PresetName;
|
|
||||||
existing.Description = dlg.PresetDescription;
|
|
||||||
existing.SystemPrompt = dlg.PresetSystemPrompt;
|
|
||||||
existing.Color = dlg.PresetColor;
|
|
||||||
existing.Symbol = dlg.PresetSymbol;
|
|
||||||
existing.Tab = dlg.PresetTab;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_settings.Settings.Llm.CustomPresets.Add(new Models.CustomPresetEntry
|
|
||||||
{
|
|
||||||
Label = dlg.PresetName,
|
|
||||||
Description = dlg.PresetDescription,
|
|
||||||
SystemPrompt = dlg.PresetSystemPrompt,
|
|
||||||
Color = dlg.PresetColor,
|
|
||||||
Symbol = dlg.PresetSymbol,
|
|
||||||
Tab = dlg.PresetTab,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
_settings.Save();
|
|
||||||
BuildTopicButtons();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>커스텀 프리셋 우클릭 컨텍스트 메뉴를 표시합니다.</summary>
|
|
||||||
private void ShowCustomPresetContextMenu(Border? anchor, Services.TopicPreset preset)
|
|
||||||
{
|
|
||||||
if (anchor == null || preset.CustomId == null) return;
|
|
||||||
|
|
||||||
var popup = new System.Windows.Controls.Primitives.Popup
|
|
||||||
{
|
|
||||||
PlacementTarget = anchor,
|
|
||||||
Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom,
|
|
||||||
StaysOpen = false,
|
|
||||||
AllowsTransparency = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
var menuBg = TryFindResource("LauncherBackground") as Brush ?? Brushes.Black;
|
|
||||||
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 menuBorder = new Border
|
|
||||||
{
|
|
||||||
Background = menuBg,
|
|
||||||
CornerRadius = new CornerRadius(10),
|
|
||||||
BorderBrush = borderBrush,
|
|
||||||
BorderThickness = new Thickness(1),
|
|
||||||
Padding = new Thickness(4),
|
|
||||||
MinWidth = 120,
|
|
||||||
Effect = new System.Windows.Media.Effects.DropShadowEffect
|
|
||||||
{
|
|
||||||
BlurRadius = 12, ShadowDepth = 2, Opacity = 0.3, Color = Colors.Black,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var stack = new StackPanel();
|
|
||||||
|
|
||||||
// 편집 버튼
|
|
||||||
var editItem = CreateContextMenuItem("\uE70F", "편집", primaryText, secondaryText);
|
|
||||||
editItem.MouseLeftButtonDown += (_, _) =>
|
|
||||||
{
|
|
||||||
popup.IsOpen = false;
|
|
||||||
var entry = _settings.Settings.Llm.CustomPresets.FirstOrDefault(c => c.Id == preset.CustomId);
|
|
||||||
if (entry != null) ShowCustomPresetDialog(entry);
|
|
||||||
};
|
|
||||||
stack.Children.Add(editItem);
|
|
||||||
|
|
||||||
// 삭제 버튼
|
|
||||||
var deleteItem = CreateContextMenuItem("\uE74D", "삭제", new SolidColorBrush(Color.FromRgb(0xEF, 0x44, 0x44)), secondaryText);
|
|
||||||
deleteItem.MouseLeftButtonDown += (_, _) =>
|
|
||||||
{
|
|
||||||
popup.IsOpen = false;
|
|
||||||
var result = CustomMessageBox.Show(
|
|
||||||
$"'{preset.Label}' 프리셋을 삭제하시겠습니까?",
|
|
||||||
"프리셋 삭제", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
|
||||||
if (result == MessageBoxResult.Yes)
|
|
||||||
{
|
|
||||||
_settings.Settings.Llm.CustomPresets.RemoveAll(c => c.Id == preset.CustomId);
|
|
||||||
_settings.Save();
|
|
||||||
BuildTopicButtons();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
stack.Children.Add(deleteItem);
|
|
||||||
|
|
||||||
menuBorder.Child = stack;
|
|
||||||
popup.Child = menuBorder;
|
|
||||||
popup.IsOpen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>컨텍스트 메뉴 항목을 생성합니다.</summary>
|
|
||||||
private Border CreateContextMenuItem(string icon, string label, Brush fg, Brush secondaryFg)
|
|
||||||
{
|
|
||||||
var item = new Border
|
|
||||||
{
|
|
||||||
Background = Brushes.Transparent,
|
|
||||||
CornerRadius = new CornerRadius(6),
|
|
||||||
Padding = new Thickness(10, 6, 14, 6),
|
|
||||||
Cursor = Cursors.Hand,
|
|
||||||
};
|
|
||||||
|
|
||||||
var sp = new StackPanel { Orientation = Orientation.Horizontal };
|
|
||||||
sp.Children.Add(new TextBlock
|
|
||||||
{
|
|
||||||
Text = icon,
|
|
||||||
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
|
||||||
FontSize = 13, Foreground = fg,
|
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
|
||||||
Margin = new Thickness(0, 0, 8, 0),
|
|
||||||
});
|
|
||||||
sp.Children.Add(new TextBlock
|
|
||||||
{
|
|
||||||
Text = label, FontSize = 13, Foreground = fg,
|
|
||||||
VerticalAlignment = VerticalAlignment.Center,
|
|
||||||
});
|
|
||||||
item.Child = sp;
|
|
||||||
|
|
||||||
var hoverBg = TryFindResource("ItemHoverBackground") as Brush ?? Brushes.Transparent;
|
|
||||||
item.MouseEnter += (s, _) => { if (s is Border b) b.Background = hoverBg; };
|
|
||||||
item.MouseLeave += (s, _) => { if (s is Border b) b.Background = Brushes.Transparent; };
|
|
||||||
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>대화 주제 선택 — 프리셋 시스템 프롬프트 + 카테고리 적용.</summary>
|
|
||||||
private void SelectTopic(Services.TopicPreset preset)
|
|
||||||
{
|
|
||||||
bool hasMessages;
|
|
||||||
bool hasConversation;
|
|
||||||
lock (_convLock)
|
|
||||||
{
|
|
||||||
hasConversation = _currentConversation != null;
|
|
||||||
hasMessages = _currentConversation?.Messages.Count > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 입력란에 텍스트가 있으면 기존 대화를 유지 (입력 내용 보존)
|
|
||||||
bool hasInput = !string.IsNullOrEmpty(InputBox.Text);
|
|
||||||
bool keepConversation = hasConversation;
|
|
||||||
|
|
||||||
if (!keepConversation)
|
|
||||||
{
|
|
||||||
// 현재 대화가 아예 없는 경우에만 새 대화 시작
|
|
||||||
StartNewConversation();
|
|
||||||
keepConversation = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 프리셋 적용 (기존 대화에도 프리셋 변경 가능)
|
|
||||||
lock (_convLock)
|
|
||||||
{
|
|
||||||
if (_currentConversation != null)
|
|
||||||
{
|
|
||||||
var session = ChatSession;
|
|
||||||
if (session != null)
|
|
||||||
{
|
|
||||||
_currentConversation = session.UpdateConversationMetadata(_activeTab, c =>
|
|
||||||
{
|
|
||||||
c.SystemCommand = preset.SystemPrompt;
|
|
||||||
c.Category = preset.Category;
|
|
||||||
}, _storage);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_currentConversation.SystemCommand = preset.SystemPrompt;
|
|
||||||
_currentConversation.Category = preset.Category;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateCategoryLabel();
|
|
||||||
SaveConversationSettings();
|
|
||||||
RefreshConversationList();
|
|
||||||
UpdateSelectedPresetGuide();
|
|
||||||
if (EmptyState != null)
|
|
||||||
EmptyState.Visibility = Visibility.Collapsed;
|
|
||||||
|
|
||||||
InputBox.Focus();
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(preset.Placeholder))
|
|
||||||
{
|
|
||||||
_promptCardPlaceholder = preset.Placeholder;
|
|
||||||
if (!hasMessages && !hasInput) ShowPlaceholder();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasMessages || hasInput)
|
|
||||||
ShowToast($"프리셋 변경: {preset.Label}");
|
|
||||||
|
|
||||||
// Cowork 탭: 하단 바 갱신
|
|
||||||
if (_activeTab == "Cowork")
|
|
||||||
BuildBottomBar();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>선택된 디자인 무드 키 (HtmlSkill에서 사용).</summary>
|
/// <summary>선택된 디자인 무드 키 (HtmlSkill에서 사용).</summary>
|
||||||
private string _selectedMood = null!; // Loaded 이벤트에서 초기화
|
private string _selectedMood = null!; // Loaded 이벤트에서 초기화
|
||||||
private string _selectedLanguage = "auto"; // Code 탭 개발 언어
|
private string _selectedLanguage = "auto"; // Code 탭 개발 언어
|
||||||
|
|||||||
Reference in New Issue
Block a user