using System; using System.CodeDom.Compiler; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Markup; using System.Windows.Media; using System.Windows.Media.Effects; using AxCopilot.Services; using AxCopilot.Services.Agent; namespace AxCopilot.Views; public class SkillEditorWindow : Window, IComponentConnector { private static readonly string[] IconCandidates = new string[20] { "\ue70f", "\ue8a5", "\ue943", "\ue74c", "\ue8b7", "\ue896", "\ue713", "\ue753", "\ue774", "\ue8d6", "\ue8f1", "\ue7c3", "\ueca7", "\ue71e", "\ue8c8", "\ue8f6", "\ue81e", "\uebd2", "\ue9d9", "\ue77b" }; private string _selectedIcon = "\ue70f"; private SkillDefinition? _editingSkill; private readonly ToolRegistry _toolRegistry; internal TextBlock TitleText; internal TextBox TxtName; internal TextBox TxtLabel; internal TextBox TxtDescription; internal StackPanel IconSelectorPanel; internal ComboBox CmbRequires; internal Border BtnInsertToolList; internal Border BtnInsertFormat; internal TextBox TxtInstructions; internal StackPanel ToolCheckListPanel; internal TextBlock PreviewTokens; internal TextBlock PreviewFileName; internal TextBlock StatusText; private bool _contentLoaded; public SkillEditorWindow() { InitializeComponent(); _toolRegistry = ToolRegistry.CreateDefault(); base.Loaded += delegate { BuildIconSelector(); BuildToolChecklist(); UpdatePreview(); }; } public SkillEditorWindow(SkillDefinition skill) : this() { SkillEditorWindow skillEditorWindow = this; _editingSkill = skill; base.Loaded += delegate { skillEditorWindow.LoadSkill(skill); }; } private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) Point position = e.GetPosition(this); if (!(((Point)(ref position)).X > base.ActualWidth - 50.0)) { if (e.ClickCount == 2) { base.WindowState = ((base.WindowState != WindowState.Maximized) ? WindowState.Maximized : WindowState.Normal); } else { DragMove(); } } } private void BtnClose_Click(object sender, MouseButtonEventArgs e) { Close(); } private void BtnCancel_Click(object sender, MouseButtonEventArgs e) { Close(); } private void BuildIconSelector() { IconSelectorPanel.Children.Clear(); SolidColorBrush solidColorBrush = new SolidColorBrush(Color.FromRgb(75, 94, 252)); Brush brush = (TryFindResource("SecondaryText") as Brush) ?? Brushes.Gray; Brush bgBrush = (TryFindResource("ItemBackground") as Brush) ?? Brushes.Transparent; string[] iconCandidates = IconCandidates; foreach (string text in iconCandidates) { bool flag = text == _selectedIcon; Border border = new Border { Width = 34.0, Height = 34.0, CornerRadius = new CornerRadius(8.0), Margin = new Thickness(0.0, 0.0, 4.0, 4.0), Cursor = Cursors.Hand, Background = (flag ? solidColorBrush : bgBrush), BorderBrush = (flag ? solidColorBrush : Brushes.Transparent), BorderThickness = new Thickness(1.5), Tag = text }; border.Child = new TextBlock { Text = text, FontFamily = new FontFamily("Segoe MDL2 Assets"), FontSize = 14.0, Foreground = (flag ? Brushes.White : brush), HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center }; border.MouseLeftButtonUp += delegate(object s, MouseButtonEventArgs _) { if (s is Border { Tag: string tag }) { _selectedIcon = tag; BuildIconSelector(); } }; string capturedIcon = text; border.MouseEnter += delegate(object s, MouseEventArgs _) { if (s is Border border2 && capturedIcon != _selectedIcon) { border2.Background = new SolidColorBrush(Color.FromArgb(48, 75, 94, 252)); } }; border.MouseLeave += delegate(object s, MouseEventArgs _) { if (s is Border border2 && capturedIcon != _selectedIcon) { border2.Background = bgBrush; } }; IconSelectorPanel.Children.Add(border); } } private void BuildToolChecklist() { ToolCheckListPanel.Children.Clear(); List list = _toolRegistry.All.OrderBy((IAgentTool t) => t.Name).ToList(); Brush foreground = (TryFindResource("PrimaryText") as Brush) ?? Brushes.White; Brush brush = (TryFindResource("SecondaryText") as Brush) ?? Brushes.Gray; HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); if (_editingSkill != null && !string.IsNullOrWhiteSpace(_editingSkill.AllowedTools)) { string[] array = _editingSkill.AllowedTools.Split(',', StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { hashSet.Add(text.Trim()); } } foreach (IAgentTool item in list) { CheckBox checkBox = new CheckBox(); checkBox.Tag = item.Name; checkBox.IsChecked = hashSet.Count == 0 || hashSet.Contains(item.Name); checkBox.Margin = new Thickness(0.0, 0.0, 0.0, 4.0); checkBox.Style = TryFindResource("ToggleSwitch") as Style; CheckBox element = checkBox; StackPanel stackPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(0.0, 0.0, 0.0, 2.0) }; stackPanel.Children.Add(element); stackPanel.Children.Add(new TextBlock { Text = item.Name, FontSize = 11.5, FontFamily = new FontFamily("Consolas"), Foreground = foreground, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(6.0, 0.0, 0.0, 0.0) }); if (!string.IsNullOrEmpty(item.Description)) { stackPanel.ToolTip = item.Description; } ToolCheckListPanel.Children.Add(stackPanel); } } private List GetCheckedTools() { List list = new List(); foreach (object child in ToolCheckListPanel.Children) { if (child is StackPanel stackPanel && stackPanel.Children.Count > 0 && stackPanel.Children[0] is CheckBox { IsChecked: var isChecked } checkBox && isChecked == true && checkBox.Tag is string item) { list.Add(item); } } return list; } private void BtnInsertTemplate_Click(object sender, MouseButtonEventArgs e) { if (sender is FrameworkElement { Tag: string tag }) { if (1 == 0) { } string text = tag switch { "tools" => BuildToolListTemplate(), "format" => "## 출력 형식\n\n1. 결과를 사용자에게 한국어로 설명합니다.\n2. 코드 블록은 마크다운 형식으로 감쌉니다.\n3. 핵심 정보를 먼저 제시하고, 세부 사항은 뒤에 붙입니다.", "steps" => "## 실행 단계\n\n1. **분석**: 사용자의 요청을 분석합니다.\n2. **계획**: 수행할 작업을 계획합니다.\n3. **실행**: 도구를 사용하여 작업을 수행합니다.\n4. **검증**: 결과를 검증합니다.\n5. **보고**: 결과를 사용자에게 보고합니다.", _ => "", }; if (1 == 0) { } string text2 = text; if (!string.IsNullOrEmpty(text2)) { int caretIndex = TxtInstructions.CaretIndex; string text3 = ((caretIndex > 0 && TxtInstructions.Text.Length > 0 && TxtInstructions.Text[caretIndex - 1] != '\n') ? "\n\n" : ""); TxtInstructions.Text = TxtInstructions.Text.Insert(caretIndex, text3 + text2.Trim()); TxtInstructions.CaretIndex = caretIndex + text3.Length + text2.Trim().Length; TxtInstructions.Focus(); } } } private string BuildToolListTemplate() { List checkedTools = GetCheckedTools(); if (checkedTools.Count == 0) { return "## 사용 가능한 도구\n\n(도구가 선택되지 않았습니다. 우측 패널에서 도구를 선택하세요.)"; } List list = new List { "## 사용 가능한 도구", "" }; foreach (string toolName in checkedTools) { IAgentTool agentTool = _toolRegistry.All.FirstOrDefault((IAgentTool t) => t.Name == toolName); if (agentTool != null) { list.Add("- **" + agentTool.Name + "**: " + agentTool.Description); } } return string.Join("\n", list); } private void TxtInstructions_TextChanged(object sender, TextChangedEventArgs e) { UpdatePreview(); } private void UpdatePreview() { if (PreviewTokens != null && PreviewFileName != null) { string text = GenerateSkillContent(); int value = TokenEstimator.Estimate(text); PreviewTokens.Text = $"예상 토큰: ~{value:N0}자 | {text.Length:N0}자"; string text2 = (string.IsNullOrWhiteSpace(TxtName?.Text) ? "new-skill" : TxtName.Text.Trim()); PreviewFileName.Text = text2 + ".skill.md"; } } private string GenerateSkillContent() { string text = TxtName?.Text?.Trim() ?? "new-skill"; string text2 = TxtLabel?.Text?.Trim() ?? ""; string text3 = TxtDescription?.Text?.Trim() ?? ""; string text4 = TxtInstructions?.Text ?? ""; string text5 = ""; if (CmbRequires?.SelectedItem is ComboBoxItem { Tag: string tag } && !string.IsNullOrEmpty(tag)) { text5 = tag; } List checkedTools = GetCheckedTools(); int count = _toolRegistry.All.Count; string text6 = ((checkedTools.Count < count) ? string.Join(", ", checkedTools) : ""); List list = new List { "---" }; list.Add("name: " + text); if (!string.IsNullOrEmpty(text2)) { list.Add("label: " + text2); } if (!string.IsNullOrEmpty(text3)) { list.Add("description: " + text3); } list.Add("icon: " + _selectedIcon); if (!string.IsNullOrEmpty(text5)) { list.Add("requires: " + text5); } if (!string.IsNullOrEmpty(text6)) { list.Add("allowed-tools: " + text6); } list.Add("---"); list.Add(""); return string.Join("\n", list) + text4; } private void BtnPreview_Click(object sender, MouseButtonEventArgs e) { string text = GenerateSkillContent(); Window previewWin = new Window { Title = "스킬 파일 미리보기", Width = 640.0, Height = 520.0, WindowStyle = WindowStyle.None, AllowsTransparency = true, Background = Brushes.Transparent, WindowStartupLocation = WindowStartupLocation.CenterOwner, Owner = this }; Brush background = (TryFindResource("LauncherBackground") as Brush) ?? Brushes.White; Brush foreground = (TryFindResource("PrimaryText") as Brush) ?? Brushes.White; Brush foreground2 = (TryFindResource("SecondaryText") as Brush) ?? Brushes.Gray; Brush itemBg = (TryFindResource("ItemBackground") as Brush) ?? Brushes.Transparent; Brush borderBrush = (TryFindResource("BorderColor") as Brush) ?? Brushes.Gray; Border border = new Border { Background = background, CornerRadius = new CornerRadius(12.0), BorderBrush = borderBrush, BorderThickness = new Thickness(1.0), Effect = new DropShadowEffect { BlurRadius = 20.0, ShadowDepth = 4.0, Opacity = 0.3, Color = Colors.Black } }; Grid grid = new Grid(); grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(44.0) }); grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1.0, GridUnitType.Star) }); grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); Border border2 = new Border { Background = itemBg, CornerRadius = new CornerRadius(12.0, 12.0, 0.0, 0.0) }; border2.MouseLeftButtonDown += delegate { previewWin.DragMove(); }; TextBlock child = new TextBlock { Text = "미리보기 — .skill.md", FontSize = 14.0, FontWeight = FontWeights.SemiBold, Foreground = foreground, VerticalAlignment = VerticalAlignment.Center, Margin = new Thickness(16.0, 0.0, 0.0, 0.0) }; border2.Child = child; Grid.SetRow(border2, 0); TextBox element = new TextBox { Text = text, FontFamily = new FontFamily("Consolas, Cascadia Code, Segoe UI"), FontSize = 12.5, IsReadOnly = true, AcceptsReturn = true, TextWrapping = TextWrapping.Wrap, VerticalScrollBarVisibility = ScrollBarVisibility.Auto, Background = itemBg, Foreground = foreground, BorderThickness = new Thickness(0.0), Padding = new Thickness(16.0, 12.0, 16.0, 12.0), Margin = new Thickness(8.0, 8.0, 8.0, 0.0) }; Grid.SetRow(element, 1); Border border3 = new Border { Padding = new Thickness(16.0, 10.0, 16.0, 10.0), CornerRadius = new CornerRadius(0.0, 0.0, 12.0, 12.0) }; Border border4 = new Border { CornerRadius = new CornerRadius(8.0), Padding = new Thickness(18.0, 8.0, 18.0, 8.0), Background = itemBg, Cursor = Cursors.Hand, HorizontalAlignment = HorizontalAlignment.Right }; border4.Child = new TextBlock { Text = "닫기", FontSize = 12.5, Foreground = foreground2 }; border4.MouseLeftButtonUp += delegate { previewWin.Close(); }; border4.MouseEnter += delegate(object s, MouseEventArgs _) { if (s is Border border5) { border5.Background = new SolidColorBrush(Color.FromArgb(24, byte.MaxValue, byte.MaxValue, byte.MaxValue)); } }; border4.MouseLeave += delegate(object s, MouseEventArgs _) { if (s is Border border5) { border5.Background = itemBg; } }; border3.Child = border4; Grid.SetRow(border3, 2); grid.Children.Add(border2); grid.Children.Add(element); grid.Children.Add(border3); border.Child = grid; previewWin.Content = border; previewWin.ShowDialog(); } private void BtnSave_Click(object sender, MouseButtonEventArgs e) { string text = TxtName.Text.Trim(); if (string.IsNullOrWhiteSpace(text)) { StatusText.Text = "⚠ 이름을 입력하세요."; StatusText.Foreground = new SolidColorBrush(Color.FromRgb(248, 113, 113)); TxtName.Focus(); return; } if (!Regex.IsMatch(text, "^[a-zA-Z][a-zA-Z0-9\\-]*$")) { StatusText.Text = "⚠ 이름은 영문으로 시작하며 영문, 숫자, 하이픈만 사용 가능합니다."; StatusText.Foreground = new SolidColorBrush(Color.FromRgb(248, 113, 113)); TxtName.Focus(); return; } if (string.IsNullOrWhiteSpace(TxtInstructions.Text)) { StatusText.Text = "⚠ 지시사항을 입력하세요."; StatusText.Foreground = new SolidColorBrush(Color.FromRgb(248, 113, 113)); TxtInstructions.Focus(); return; } string contents = GenerateSkillContent(); string path; if (_editingSkill != null) { path = _editingSkill.FilePath; } else { string text2 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "skills"); if (!Directory.Exists(text2)) { Directory.CreateDirectory(text2); } path = Path.Combine(text2, text + ".skill.md"); if (File.Exists(path)) { int num = 2; while (File.Exists(Path.Combine(text2, $"{text}_{num}.skill.md"))) { num++; } path = Path.Combine(text2, $"{text}_{num}.skill.md"); } } try { File.WriteAllText(path, contents, Encoding.UTF8); SkillService.LoadSkills(); StatusText.Text = "✓ 저장 완료: " + Path.GetFileName(path); StatusText.Foreground = new SolidColorBrush(Color.FromRgb(52, 211, 153)); base.DialogResult = true; } catch (Exception ex) { CustomMessageBox.Show("저장 실패: " + ex.Message, "스킬 저장"); } } private void LoadSkill(SkillDefinition skill) { TitleText.Text = "스킬 편집"; TxtName.Text = skill.Name; TxtLabel.Text = skill.Label; TxtDescription.Text = skill.Description; TxtInstructions.Text = skill.SystemPrompt; _selectedIcon = (IconCandidates.Contains(skill.Icon) ? skill.Icon : IconCandidates[0]); BuildIconSelector(); for (int i = 0; i < CmbRequires.Items.Count; i++) { if (CmbRequires.Items[i] is ComboBoxItem { Tag: string tag } && string.Equals(tag, skill.Requires, StringComparison.OrdinalIgnoreCase)) { CmbRequires.SelectedIndex = i; break; } } UpdatePreview(); } [DebuggerNonUserCode] [GeneratedCode("PresentationBuildTasks", "10.0.5.0")] public void InitializeComponent() { if (!_contentLoaded) { _contentLoaded = true; Uri resourceLocator = new Uri("/AxCopilot;component/views/skilleditorwindow.xaml", UriKind.Relative); Application.LoadComponent(this, resourceLocator); } } [DebuggerNonUserCode] [GeneratedCode("PresentationBuildTasks", "10.0.5.0")] [EditorBrowsable(EditorBrowsableState.Never)] void IComponentConnector.Connect(int connectionId, object target) { switch (connectionId) { case 1: ((Border)target).MouseLeftButtonDown += TitleBar_MouseLeftButtonDown; break; case 2: TitleText = (TextBlock)target; break; case 3: ((Border)target).MouseLeftButtonUp += BtnClose_Click; break; case 4: TxtName = (TextBox)target; break; case 5: TxtLabel = (TextBox)target; break; case 6: TxtDescription = (TextBox)target; break; case 7: IconSelectorPanel = (StackPanel)target; break; case 8: CmbRequires = (ComboBox)target; break; case 9: BtnInsertToolList = (Border)target; BtnInsertToolList.MouseLeftButtonUp += BtnInsertTemplate_Click; break; case 10: BtnInsertFormat = (Border)target; BtnInsertFormat.MouseLeftButtonUp += BtnInsertTemplate_Click; break; case 11: ((Border)target).MouseLeftButtonUp += BtnInsertTemplate_Click; break; case 12: TxtInstructions = (TextBox)target; TxtInstructions.TextChanged += TxtInstructions_TextChanged; break; case 13: ToolCheckListPanel = (StackPanel)target; break; case 14: PreviewTokens = (TextBlock)target; break; case 15: PreviewFileName = (TextBlock)target; break; case 16: StatusText = (TextBlock)target; break; case 17: ((Border)target).MouseLeftButtonUp += BtnCancel_Click; break; case 18: ((Border)target).MouseLeftButtonUp += BtnPreview_Click; break; case 19: ((Border)target).MouseLeftButtonUp += BtnSave_Click; break; default: _contentLoaded = true; break; } } }