434 lines
16 KiB
C#
434 lines
16 KiB
C#
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
|
|
{
|
|
private void EnterTitleEditMode(TextBlock titleTb, string conversationId, Brush titleColor)
|
|
{
|
|
try
|
|
{
|
|
var parent = titleTb.Parent as StackPanel;
|
|
if (parent == null) return;
|
|
|
|
var idx = parent.Children.IndexOf(titleTb);
|
|
if (idx < 0) return;
|
|
|
|
var editBox = new TextBox
|
|
{
|
|
Text = titleTb.Text,
|
|
FontSize = 12.5,
|
|
Foreground = titleColor,
|
|
Background = Brushes.Transparent,
|
|
BorderBrush = TryFindResource("AccentColor") as Brush ?? Brushes.Blue,
|
|
BorderThickness = new Thickness(0, 0, 0, 1),
|
|
CaretBrush = titleColor,
|
|
Padding = new Thickness(0),
|
|
Margin = new Thickness(0),
|
|
};
|
|
|
|
parent.Children.RemoveAt(idx);
|
|
parent.Children.Insert(idx, editBox);
|
|
|
|
var committed = false;
|
|
void CommitEdit()
|
|
{
|
|
if (committed) return;
|
|
committed = true;
|
|
|
|
var newTitle = editBox.Text.Trim();
|
|
if (string.IsNullOrEmpty(newTitle)) newTitle = titleTb.Text;
|
|
|
|
titleTb.Text = newTitle;
|
|
try
|
|
{
|
|
var currentIdx = parent.Children.IndexOf(editBox);
|
|
if (currentIdx >= 0)
|
|
{
|
|
parent.Children.RemoveAt(currentIdx);
|
|
parent.Children.Insert(currentIdx, titleTb);
|
|
}
|
|
}
|
|
catch { }
|
|
|
|
var conv = _storage.Load(conversationId);
|
|
if (conv != null)
|
|
{
|
|
conv.Title = newTitle;
|
|
_storage.Save(conv);
|
|
lock (_convLock)
|
|
{
|
|
if (_currentConversation?.Id == conversationId)
|
|
{
|
|
_currentConversation = ChatSession?.UpdateConversationMetadata(_activeTab, c => c.Title = newTitle, _storage) ?? _currentConversation;
|
|
}
|
|
}
|
|
UpdateChatTitle();
|
|
}
|
|
}
|
|
|
|
void CancelEdit()
|
|
{
|
|
if (committed) return;
|
|
committed = true;
|
|
try
|
|
{
|
|
var currentIdx = parent.Children.IndexOf(editBox);
|
|
if (currentIdx >= 0)
|
|
{
|
|
parent.Children.RemoveAt(currentIdx);
|
|
parent.Children.Insert(currentIdx, titleTb);
|
|
}
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
editBox.KeyDown += (_, ke) =>
|
|
{
|
|
if (ke.Key == Key.Enter) { ke.Handled = true; CommitEdit(); }
|
|
if (ke.Key == Key.Escape) { ke.Handled = true; CancelEdit(); }
|
|
};
|
|
editBox.LostFocus += (_, _) => CommitEdit();
|
|
|
|
editBox.Focus();
|
|
editBox.SelectAll();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogService.Error($"제목 편집 오류: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private void ShowConversationMenu(string conversationId)
|
|
{
|
|
var conv = _storage.Load(conversationId);
|
|
var isPinned = conv?.Pinned ?? false;
|
|
|
|
var bgBrush = TryFindResource("LauncherBackground") as Brush ?? new SolidColorBrush(Color.FromRgb(0x1A, 0x1B, 0x2E));
|
|
var borderBrush = TryFindResource("BorderColor") as Brush ?? Brushes.Gray;
|
|
var primaryText = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
|
var secondaryText = TryFindResource("SecondaryText") as Brush ?? Brushes.Gray;
|
|
var hoverBg = TryFindResource("ItemHoverBackground") as Brush ?? new SolidColorBrush(Color.FromArgb(30, 255, 255, 255));
|
|
|
|
var popup = new Popup
|
|
{
|
|
StaysOpen = false,
|
|
AllowsTransparency = true,
|
|
PopupAnimation = PopupAnimation.Fade,
|
|
Placement = PlacementMode.MousePoint,
|
|
};
|
|
|
|
var container = new Border
|
|
{
|
|
Background = bgBrush,
|
|
BorderBrush = borderBrush,
|
|
BorderThickness = new Thickness(1),
|
|
CornerRadius = new CornerRadius(12),
|
|
Padding = new Thickness(6),
|
|
MinWidth = 200,
|
|
Effect = new System.Windows.Media.Effects.DropShadowEffect
|
|
{
|
|
BlurRadius = 16,
|
|
ShadowDepth = 4,
|
|
Opacity = 0.3,
|
|
Color = Colors.Black,
|
|
},
|
|
};
|
|
|
|
var stack = new StackPanel();
|
|
|
|
Border CreateMenuItem(string icon, string text, Brush iconColor, Action onClick)
|
|
{
|
|
var item = new Border
|
|
{
|
|
Background = Brushes.Transparent,
|
|
CornerRadius = new CornerRadius(8),
|
|
Padding = new Thickness(10, 7, 10, 7),
|
|
Margin = new Thickness(0, 1, 0, 1),
|
|
Cursor = Cursors.Hand,
|
|
};
|
|
var g = new Grid();
|
|
g.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(24) });
|
|
g.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
|
|
|
var iconTb = new TextBlock
|
|
{
|
|
Text = icon,
|
|
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
|
FontSize = 12,
|
|
Foreground = iconColor,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
};
|
|
Grid.SetColumn(iconTb, 0);
|
|
g.Children.Add(iconTb);
|
|
|
|
var textTb = new TextBlock
|
|
{
|
|
Text = text,
|
|
FontSize = 12.5,
|
|
Foreground = primaryText,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
};
|
|
Grid.SetColumn(textTb, 1);
|
|
g.Children.Add(textTb);
|
|
|
|
item.Child = g;
|
|
item.MouseEnter += (s, _) => { if (s is Border b) b.Background = hoverBg; };
|
|
item.MouseLeave += (s, _) => { if (s is Border b) b.Background = Brushes.Transparent; };
|
|
item.MouseLeftButtonUp += (_, _) => { popup.IsOpen = false; onClick(); };
|
|
return item;
|
|
}
|
|
|
|
Border CreateSeparator() => new()
|
|
{
|
|
Height = 1,
|
|
Background = borderBrush,
|
|
Opacity = 0.3,
|
|
Margin = new Thickness(8, 4, 8, 4),
|
|
};
|
|
|
|
stack.Children.Add(CreateMenuItem(
|
|
isPinned ? "\uE77A" : "\uE718",
|
|
isPinned ? "고정 해제" : "상단 고정",
|
|
TryFindResource("AccentColor") as Brush ?? Brushes.Blue,
|
|
() =>
|
|
{
|
|
var c = _storage.Load(conversationId);
|
|
if (c == null) return;
|
|
|
|
c.Pinned = !c.Pinned;
|
|
_storage.Save(c);
|
|
lock (_convLock)
|
|
{
|
|
if (_currentConversation?.Id == conversationId)
|
|
{
|
|
_currentConversation = ChatSession?.UpdateConversationMetadata(_activeTab, current => current.Pinned = c.Pinned, _storage) ?? _currentConversation;
|
|
}
|
|
}
|
|
RefreshConversationList();
|
|
}));
|
|
|
|
stack.Children.Add(CreateMenuItem("\uE8AC", "이름 변경", secondaryText, () =>
|
|
{
|
|
foreach (UIElement child in ConversationPanel.Children)
|
|
{
|
|
if (child is not Border b || b.Child is not Grid g) continue;
|
|
foreach (UIElement gc in g.Children)
|
|
{
|
|
if (gc is StackPanel sp && sp.Children.Count > 0 && sp.Children[0] is TextBlock tb)
|
|
{
|
|
if (conv != null && tb.Text == conv.Title)
|
|
{
|
|
var titleColor = TryFindResource("PrimaryText") as Brush ?? Brushes.White;
|
|
EnterTitleEditMode(tb, conversationId, titleColor);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}));
|
|
|
|
if ((_activeTab == "Cowork" || _activeTab == "Code") && conv != null)
|
|
{
|
|
var catKey = conv.Category ?? ChatCategory.General;
|
|
string catSymbol = "\uE8BD", catLabel = catKey, catColor = "#6B7280";
|
|
var chatCat = ChatCategory.All.FirstOrDefault(c => c.Key == catKey);
|
|
if (chatCat != default && chatCat.Key != ChatCategory.General)
|
|
{
|
|
catSymbol = chatCat.Symbol;
|
|
catLabel = chatCat.Label;
|
|
catColor = chatCat.Color;
|
|
}
|
|
else
|
|
{
|
|
var preset = Services.PresetService.GetByTabWithCustom(_activeTab, _settings.Settings.Llm.CustomPresets)
|
|
.FirstOrDefault(p => p.Category == catKey);
|
|
if (preset != null)
|
|
{
|
|
catSymbol = preset.Symbol;
|
|
catLabel = preset.Label;
|
|
catColor = preset.Color;
|
|
}
|
|
}
|
|
|
|
stack.Children.Add(CreateSeparator());
|
|
var infoSp = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(10, 4, 10, 4) };
|
|
try
|
|
{
|
|
var catBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString(catColor));
|
|
infoSp.Children.Add(new TextBlock
|
|
{
|
|
Text = catSymbol,
|
|
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
|
FontSize = 12,
|
|
Foreground = catBrush,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
Margin = new Thickness(0, 0, 6, 0),
|
|
});
|
|
infoSp.Children.Add(new TextBlock
|
|
{
|
|
Text = catLabel,
|
|
FontSize = 12,
|
|
Foreground = primaryText,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
});
|
|
}
|
|
catch
|
|
{
|
|
infoSp.Children.Add(new TextBlock { Text = catLabel, FontSize = 12, Foreground = primaryText });
|
|
}
|
|
stack.Children.Add(infoSp);
|
|
}
|
|
|
|
if (_activeTab == "Chat")
|
|
{
|
|
stack.Children.Add(CreateSeparator());
|
|
stack.Children.Add(new TextBlock
|
|
{
|
|
Text = "분류 변경",
|
|
FontSize = 10.5,
|
|
Foreground = secondaryText,
|
|
Margin = new Thickness(10, 4, 0, 4),
|
|
FontWeight = FontWeights.SemiBold,
|
|
});
|
|
|
|
var currentCategory = conv?.Category ?? ChatCategory.General;
|
|
var accentBrush = TryFindResource("AccentColor") as Brush ?? Brushes.CornflowerBlue;
|
|
|
|
foreach (var (key, label, symbol, color) in ChatCategory.All)
|
|
{
|
|
var capturedKey = key;
|
|
var isCurrentCat = capturedKey == currentCategory;
|
|
|
|
var catItem = new Border
|
|
{
|
|
Background = Brushes.Transparent,
|
|
CornerRadius = new CornerRadius(8),
|
|
Padding = new Thickness(10, 7, 10, 7),
|
|
Margin = new Thickness(0, 1, 0, 1),
|
|
Cursor = Cursors.Hand,
|
|
};
|
|
var catGrid = new Grid();
|
|
catGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(24) });
|
|
catGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
|
catGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(20) });
|
|
|
|
var catIcon = new TextBlock
|
|
{
|
|
Text = symbol,
|
|
FontFamily = new FontFamily("Segoe MDL2 Assets"),
|
|
FontSize = 12,
|
|
Foreground = BrushFromHex(color),
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
};
|
|
Grid.SetColumn(catIcon, 0);
|
|
catGrid.Children.Add(catIcon);
|
|
|
|
var catText = new TextBlock
|
|
{
|
|
Text = label,
|
|
FontSize = 12.5,
|
|
Foreground = primaryText,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
FontWeight = isCurrentCat ? FontWeights.Bold : FontWeights.Normal,
|
|
};
|
|
Grid.SetColumn(catText, 1);
|
|
catGrid.Children.Add(catText);
|
|
|
|
if (isCurrentCat)
|
|
{
|
|
var check = CreateSimpleCheck(accentBrush, 14);
|
|
Grid.SetColumn(check, 2);
|
|
catGrid.Children.Add(check);
|
|
}
|
|
|
|
catItem.Child = catGrid;
|
|
catItem.MouseEnter += (s, _) => { if (s is Border b) b.Background = hoverBg; };
|
|
catItem.MouseLeave += (s, _) => { if (s is Border b) b.Background = Brushes.Transparent; };
|
|
catItem.MouseLeftButtonUp += (_, _) =>
|
|
{
|
|
popup.IsOpen = false;
|
|
var c = _storage.Load(conversationId);
|
|
if (c == null) return;
|
|
|
|
c.Category = capturedKey;
|
|
var preset = Services.PresetService.GetByCategory(capturedKey);
|
|
if (preset != null)
|
|
{
|
|
c.SystemCommand = preset.SystemPrompt;
|
|
}
|
|
_storage.Save(c);
|
|
|
|
lock (_convLock)
|
|
{
|
|
if (_currentConversation?.Id == conversationId)
|
|
{
|
|
_currentConversation = ChatSession?.UpdateConversationMetadata(_activeTab, current =>
|
|
{
|
|
current.Category = capturedKey;
|
|
if (preset != null)
|
|
{
|
|
current.SystemCommand = preset.SystemPrompt;
|
|
}
|
|
}, _storage) ?? _currentConversation;
|
|
}
|
|
}
|
|
|
|
bool isCurrent;
|
|
lock (_convLock) { isCurrent = _currentConversation?.Id == conversationId; }
|
|
if (isCurrent && preset != null && !string.IsNullOrEmpty(preset.Placeholder))
|
|
{
|
|
_promptCardPlaceholder = preset.Placeholder;
|
|
UpdateWatermarkVisibility();
|
|
if (string.IsNullOrEmpty(InputBox.Text))
|
|
{
|
|
InputWatermark.Text = preset.Placeholder;
|
|
InputWatermark.Visibility = Visibility.Visible;
|
|
}
|
|
}
|
|
else if (isCurrent)
|
|
{
|
|
ClearPromptCardPlaceholder();
|
|
}
|
|
|
|
RefreshConversationList();
|
|
};
|
|
stack.Children.Add(catItem);
|
|
}
|
|
}
|
|
|
|
stack.Children.Add(CreateSeparator());
|
|
stack.Children.Add(CreateMenuItem("\uE74D", "이 대화 삭제", Brushes.IndianRed, () =>
|
|
{
|
|
var result = CustomMessageBox.Show("이 대화를 삭제하시겠습니까?", "대화 삭제",
|
|
MessageBoxButton.YesNo, MessageBoxImage.Question);
|
|
if (result != MessageBoxResult.Yes) return;
|
|
|
|
_storage.Delete(conversationId);
|
|
lock (_convLock)
|
|
{
|
|
if (_currentConversation?.Id == conversationId)
|
|
{
|
|
_currentConversation = null;
|
|
MessagePanel.Children.Clear();
|
|
EmptyState.Visibility = Visibility.Visible;
|
|
UpdateChatTitle();
|
|
}
|
|
}
|
|
RefreshConversationList();
|
|
}));
|
|
|
|
container.Child = stack;
|
|
popup.Child = container;
|
|
popup.IsOpen = true;
|
|
}
|
|
}
|