Public Access
252 lines
7.3 KiB
C#
252 lines
7.3 KiB
C#
#if WINDOWS
|
|
using Aprillz.MewUI;
|
|
using Aprillz.MewUI.Controls;
|
|
|
|
namespace MeetingAssistant.Workflow;
|
|
|
|
public sealed class MewUiWorkflowRulesEditorWindowService : IWorkflowRulesEditorWindowService
|
|
{
|
|
private readonly IServiceProvider services;
|
|
private readonly ILogger<MewUiWorkflowRulesEditorWindowService> logger;
|
|
private readonly object sync = new();
|
|
private bool isRunning;
|
|
|
|
public MewUiWorkflowRulesEditorWindowService(
|
|
IServiceProvider services,
|
|
ILogger<MewUiWorkflowRulesEditorWindowService> logger)
|
|
{
|
|
this.services = services;
|
|
this.logger = logger;
|
|
}
|
|
|
|
public void Show()
|
|
{
|
|
lock (sync)
|
|
{
|
|
if (isRunning)
|
|
{
|
|
logger.LogInformation("Workflow rules editor UI is already running");
|
|
return;
|
|
}
|
|
|
|
isRunning = true;
|
|
}
|
|
|
|
logger.LogInformation("Starting workflow rules editor UI thread");
|
|
var thread = new Thread(RunWindow)
|
|
{
|
|
IsBackground = true,
|
|
Name = "Meeting Assistant Rules Editor"
|
|
};
|
|
thread.SetApartmentState(ApartmentState.STA);
|
|
thread.Start();
|
|
}
|
|
|
|
private void RunWindow()
|
|
{
|
|
try
|
|
{
|
|
logger.LogInformation("Workflow rules editor UI thread started");
|
|
var viewModel = services.GetRequiredService<WorkflowRulesEditorChatViewModel>();
|
|
var window = new WorkflowRulesEditorMewWindow(viewModel);
|
|
window.Run();
|
|
logger.LogInformation("Workflow rules editor UI window closed");
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
logger.LogError(exception, "Workflow rules editor UI failed");
|
|
}
|
|
finally
|
|
{
|
|
lock (sync)
|
|
{
|
|
isRunning = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
internal sealed class WorkflowRulesEditorMewWindow
|
|
{
|
|
private readonly WorkflowRulesEditorChatViewModel viewModel;
|
|
private readonly StackPanel conversationPanel = new();
|
|
private readonly ScrollViewer conversationScroll = new();
|
|
private readonly MultiLineTextBox input = new();
|
|
private readonly Button sendButton = new();
|
|
private readonly EventHandler changedHandler;
|
|
|
|
public WorkflowRulesEditorMewWindow(WorkflowRulesEditorChatViewModel viewModel)
|
|
{
|
|
this.viewModel = viewModel;
|
|
changedHandler = (_, _) => RequestRender();
|
|
this.viewModel.Changed += changedHandler;
|
|
}
|
|
|
|
public void Run()
|
|
{
|
|
ConfigureTheme();
|
|
|
|
conversationPanel
|
|
.Vertical()
|
|
.Spacing(8);
|
|
input
|
|
.Height(92)
|
|
.Wrap(true)
|
|
.Placeholder("ask me what I can do for you")
|
|
.OnTextChanged(text => viewModel.Draft = text)
|
|
.OnKeyDown(args =>
|
|
{
|
|
if (args.Key == Key.Enter && !args.ShiftKey)
|
|
{
|
|
args.Handled = true;
|
|
_ = SendAsync();
|
|
}
|
|
});
|
|
sendButton
|
|
.Content("Send")
|
|
.Width(84)
|
|
.OnClick(() => _ = SendAsync());
|
|
|
|
var window = new Window()
|
|
.Title("Settings and logs")
|
|
.Resizable(680, 760, 520, 460)
|
|
.Padding(0)
|
|
.OnLoaded(Render)
|
|
.OnClosed(() => viewModel.Changed -= changedHandler)
|
|
.Content(
|
|
new DockPanel()
|
|
.LastChildFill()
|
|
.Padding(12)
|
|
.Spacing(10)
|
|
.Children(
|
|
new DockPanel()
|
|
.LastChildFill()
|
|
.Spacing(8)
|
|
.DockBottom()
|
|
.Children(
|
|
sendButton.DockRight(),
|
|
input),
|
|
conversationScroll
|
|
.AutoVerticalScroll()
|
|
.NoHorizontalScroll()
|
|
.Content(conversationPanel)));
|
|
var icon = LoadWindowIcon();
|
|
if (icon is not null)
|
|
{
|
|
window.Icon = icon;
|
|
}
|
|
|
|
Application.Create()
|
|
.UseTheme(ThemeVariant.Dark)
|
|
.UseWin32()
|
|
.UseDirect2D()
|
|
.Run(window);
|
|
}
|
|
|
|
private static IconSource? LoadWindowIcon()
|
|
{
|
|
var iconPath = Path.Combine(AppContext.BaseDirectory, "Assets", "meeting-assistant.ico");
|
|
return File.Exists(iconPath)
|
|
? IconSource.FromFile(iconPath)
|
|
: null;
|
|
}
|
|
|
|
private static void ConfigureTheme()
|
|
{
|
|
ThemeManager.DefaultLightSeed = ThemeSeed.DefaultLight with
|
|
{
|
|
ButtonFace = Color.FromRgb(245, 245, 245)
|
|
};
|
|
ThemeManager.DefaultDarkSeed = ThemeSeed.DefaultDark with
|
|
{
|
|
ButtonFace = Color.FromRgb(42, 46, 54)
|
|
};
|
|
}
|
|
|
|
private async Task SendAsync()
|
|
{
|
|
var sendTask = viewModel.SendAsync();
|
|
input.Text = viewModel.Draft;
|
|
await sendTask;
|
|
}
|
|
|
|
private void RequestRender()
|
|
{
|
|
var dispatcher = Application.Current?.Dispatcher;
|
|
if (dispatcher is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
dispatcher.BeginInvoke(Render);
|
|
}
|
|
|
|
private void Render()
|
|
{
|
|
var shouldAutoScroll = WorkflowRulesEditorScrollPolicy.ShouldAutoScroll(
|
|
conversationScroll.VerticalOffset,
|
|
conversationScroll.ViewportHeight,
|
|
conversationPanel.ActualHeight);
|
|
|
|
conversationPanel.Clear();
|
|
|
|
foreach (var message in viewModel.Messages)
|
|
{
|
|
conversationPanel.Add(CreateMessageCard(message));
|
|
}
|
|
|
|
if (viewModel.IsThinking)
|
|
{
|
|
conversationPanel.Add(new TextBlock()
|
|
.Text("Thinking...")
|
|
.Foreground(Color.FromRgb(156, 166, 181))
|
|
.Margin(4, 2, 4, 2));
|
|
}
|
|
|
|
sendButton.IsEnabled = !viewModel.IsThinking;
|
|
|
|
if (shouldAutoScroll)
|
|
{
|
|
QueueScrollConversationToBottom();
|
|
}
|
|
}
|
|
|
|
private void QueueScrollConversationToBottom()
|
|
{
|
|
var dispatcher = Application.Current?.Dispatcher;
|
|
if (dispatcher is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
dispatcher.BeginInvoke(DispatcherPriority.Idle, () =>
|
|
{
|
|
ScrollConversationToBottom();
|
|
dispatcher.BeginInvoke(DispatcherPriority.Idle, ScrollConversationToBottom);
|
|
});
|
|
}
|
|
|
|
private void ScrollConversationToBottom()
|
|
{
|
|
conversationScroll.SetScrollOffsets(
|
|
conversationScroll.HorizontalOffset,
|
|
WorkflowRulesEditorScrollPolicy.GetBottomOffset(
|
|
conversationScroll.ViewportHeight,
|
|
conversationPanel.ActualHeight));
|
|
}
|
|
|
|
private static Element CreateMessageCard(WorkflowRulesEditorChatMessage message)
|
|
{
|
|
var isUser = message.Role == WorkflowRulesEditorChatRole.User;
|
|
return new Border()
|
|
.Padding(10)
|
|
.Margin(isUser ? new Thickness(42, 0, 4, 0) : new Thickness(0, 0, 42, 0))
|
|
.CornerRadius(8)
|
|
.BorderThickness(1)
|
|
.BorderBrush(isUser ? Color.FromRgb(68, 95, 132) : Color.FromRgb(69, 75, 86))
|
|
.Background(isUser ? Color.FromRgb(26, 48, 78) : Color.FromRgb(35, 39, 46))
|
|
.Child(WorkflowRulesEditorMarkdownRenderer.CreateContent(message.Content));
|
|
}
|
|
}
|
|
#endif
|