├── Engage ├── Assets │ ├── Logo.png │ ├── Logo_32x32.png │ ├── StoreLogo.png │ ├── SplashScreen.scale-200.png │ ├── LockScreenLogo.scale-200.png │ ├── Square44x44Logo.scale-200.png │ ├── Wide310x150Logo.scale-200.png │ ├── Square150x150Logo.scale-200.png │ └── Square44x44Logo.targetsize-24_altform-unplated.png ├── ViewModels │ ├── LayoutsTabViewModel.cs │ ├── HomeViewModel.cs │ ├── MessageViewModel.cs │ ├── BaseViewModel.cs │ ├── SettingsViewModel.cs │ ├── ChatViewModel.cs │ └── ChatTabViewModel.cs ├── Properties │ └── launchSettings.json ├── Helpers │ ├── ILocalSettingService.cs │ ├── LocalSettingService.cs │ ├── CustomRelayCommand.cs │ ├── DependencyInjection.cs │ └── SignalManager.cs ├── OpenAI │ ├── IApiClient.cs │ ├── Models │ │ ├── Choice.cs │ │ ├── Message.cs │ │ ├── ApiRequestOptions.cs │ │ └── Response.cs │ ├── IChatService.cs │ ├── ChatService.cs │ └── ApiClient.cs ├── Views │ ├── Layouts │ │ ├── MainWindowLayout.xaml.cs │ │ └── MainWindowLayout.xaml │ ├── HomePage.xaml.cs │ ├── HomePage.xaml │ ├── Controls │ │ ├── Signal.xaml │ │ └── Signal.xaml.cs │ ├── LayoutsPage.xaml │ ├── LayoutsPage.xaml.cs │ ├── SettingsPage.xaml.cs │ ├── SettingsPage.xaml │ ├── MainWindow.xaml.cs │ ├── ChatPage.xaml.cs │ ├── MainWindow.xaml │ └── ChatPage.xaml ├── Converters │ ├── StringToTitleCaseConverter.cs │ ├── RoleToAlignmentConverter.cs │ ├── UnixTimestampConverter.cs │ ├── ChatMessageTypeToForegroundColorConverter.cs │ └── ChatMessageTypeToBackgroundColorConverter.cs ├── app.manifest ├── App.xaml ├── App.xaml.cs ├── Package.appxmanifest └── Engage.csproj ├── Screenshots └── chat-screen.png ├── LICENSE.txt ├── README.md ├── Engage.sln ├── .gitattributes └── .gitignore /Engage/Assets/Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamhazel/Engage/HEAD/Engage/Assets/Logo.png -------------------------------------------------------------------------------- /Engage/Assets/Logo_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamhazel/Engage/HEAD/Engage/Assets/Logo_32x32.png -------------------------------------------------------------------------------- /Engage/Assets/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamhazel/Engage/HEAD/Engage/Assets/StoreLogo.png -------------------------------------------------------------------------------- /Screenshots/chat-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamhazel/Engage/HEAD/Screenshots/chat-screen.png -------------------------------------------------------------------------------- /Engage/Assets/SplashScreen.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamhazel/Engage/HEAD/Engage/Assets/SplashScreen.scale-200.png -------------------------------------------------------------------------------- /Engage/Assets/LockScreenLogo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamhazel/Engage/HEAD/Engage/Assets/LockScreenLogo.scale-200.png -------------------------------------------------------------------------------- /Engage/Assets/Square44x44Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamhazel/Engage/HEAD/Engage/Assets/Square44x44Logo.scale-200.png -------------------------------------------------------------------------------- /Engage/Assets/Wide310x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamhazel/Engage/HEAD/Engage/Assets/Wide310x150Logo.scale-200.png -------------------------------------------------------------------------------- /Engage/Assets/Square150x150Logo.scale-200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamhazel/Engage/HEAD/Engage/Assets/Square150x150Logo.scale-200.png -------------------------------------------------------------------------------- /Engage/Assets/Square44x44Logo.targetsize-24_altform-unplated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamhazel/Engage/HEAD/Engage/Assets/Square44x44Logo.targetsize-24_altform-unplated.png -------------------------------------------------------------------------------- /Engage/ViewModels/LayoutsTabViewModel.cs: -------------------------------------------------------------------------------- 1 | // [FILE] Engage.ViewModels.TabViewModel.cs 2 | 3 | namespace Engage.ViewModels; 4 | 5 | public class LayoutsTabViewModel 6 | { 7 | public string LayoutName { get; internal set; } 8 | } 9 | -------------------------------------------------------------------------------- /Engage/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Engage (Package)": { 4 | "commandName": "MsixPackage" 5 | }, 6 | "Engage (Unpackaged)": { 7 | "commandName": "Project" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Engage/Helpers/ILocalSettingService.cs: -------------------------------------------------------------------------------- 1 | namespace Engage.Helpers 2 | { 3 | public interface ILocalSettingsService 4 | { 5 | string GetSetting(string key); 6 | void SetSetting(string key, string value); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Engage/ViewModels/HomeViewModel.cs: -------------------------------------------------------------------------------- 1 | // HomeViewModel.cs 2 | namespace Engage.ViewModels 3 | { 4 | public class HomeViewModel : BaseViewModel 5 | { 6 | public HomeViewModel() 7 | { 8 | // Nothing yet 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Engage/OpenAI/IApiClient.cs: -------------------------------------------------------------------------------- 1 | // [FILE] Engage.OpenAI.IApiClient.cs 2 | using System.Threading.Tasks; 3 | using Engage.OpenAI.Models; 4 | 5 | namespace Engage.OpenAI 6 | { 7 | public interface IApiClient 8 | { 9 | Task SendMessageAsync(ApiRequestOptions requestOptions); 10 | } 11 | } -------------------------------------------------------------------------------- /Engage/OpenAI/Models/Choice.cs: -------------------------------------------------------------------------------- 1 | // [FILE] Engage.OpenAI.Models.Usage.cs 2 | namespace Engage.OpenAI.Models 3 | { 4 | public class Choice 5 | { 6 | public string FinishReason { get; set; } 7 | public int Index { get; set; } 8 | public string Text { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Engage/OpenAI/IChatService.cs: -------------------------------------------------------------------------------- 1 | // [FILE] Engage.OpenAI.IIIChatService.cs 2 | using System.Threading.Tasks; 3 | using Engage.OpenAI.Models; 4 | 5 | namespace Engage.OpenAI 6 | { 7 | public interface IChatService 8 | { 9 | Task SendMessageAsync(ApiRequestOptions requestOptions); 10 | } 11 | } -------------------------------------------------------------------------------- /Engage/OpenAI/Models/Message.cs: -------------------------------------------------------------------------------- 1 | // [FILE] Engage.OpenAI.Models.Message.cs 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Engage.OpenAI.Models 5 | { 6 | public class Message 7 | { 8 | public string Role { get; set; } 9 | public string Content { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Engage/Views/Layouts/MainWindowLayout.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.UI.Xaml.Controls; 2 | 3 | namespace Engage.Views.Layouts 4 | { 5 | public sealed partial class MainWindowLayout : UserControl 6 | { 7 | public MainWindowLayout() 8 | { 9 | this.InitializeComponent(); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /Engage/OpenAI/Models/ApiRequestOptions.cs: -------------------------------------------------------------------------------- 1 | // [FILE] Engage.OpenAI.Models.ApiRequestOptions.cs 2 | using System.Collections.Generic; 3 | 4 | namespace Engage.OpenAI.Models 5 | { 6 | public class ApiRequestOptions 7 | { 8 | public string Model { get; set; } 9 | public List Messages { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Engage/Views/HomePage.xaml.cs: -------------------------------------------------------------------------------- 1 | using Engage.OpenAI; 2 | using Engage.ViewModels; 3 | using Microsoft.UI.Xaml.Controls; 4 | 5 | namespace Engage.Views 6 | { 7 | public sealed partial class HomePage : Page 8 | { 9 | public HomePage() 10 | { 11 | var homePageViewModel = new HomeViewModel(); 12 | 13 | this.DataContext = homePageViewModel; 14 | this.InitializeComponent(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Engage/Views/HomePage.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Engage/Helpers/LocalSettingService.cs: -------------------------------------------------------------------------------- 1 | using Windows.Storage; 2 | 3 | namespace Engage.Helpers 4 | { 5 | public class LocalSettingsService : ILocalSettingsService 6 | { 7 | public string GetSetting(string key) 8 | { 9 | var localSettings = ApplicationData.Current.LocalSettings; 10 | return localSettings.Values[key] as string; 11 | } 12 | 13 | public void SetSetting(string key, string value) 14 | { 15 | var localSettings = ApplicationData.Current.LocalSettings; 16 | localSettings.Values[key] = value; 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Engage/ViewModels/MessageViewModel.cs: -------------------------------------------------------------------------------- 1 | // [FILE] Engage.ViewModels.MessageViewModel.cs 2 | using Engage.OpenAI.Models; 3 | using Engage.Helpers; 4 | 5 | namespace Engage.ViewModels 6 | { 7 | public enum ChatMessageType 8 | { 9 | Sent, 10 | Received 11 | } 12 | 13 | public class MessageViewModel 14 | { 15 | public string Role { get; } 16 | public string Content { get; } 17 | public ChatMessageType ChatMessageType { get; } 18 | 19 | public MessageViewModel(Message message, ChatMessageType chatMessageType) 20 | { 21 | Role = message.Role; 22 | Content = message.Content; 23 | ChatMessageType = chatMessageType; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Engage/Converters/StringToTitleCaseConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using Microsoft.UI.Xaml.Data; 4 | 5 | namespace Engage.Converters 6 | { 7 | public class StringToTitleCaseConverter : IValueConverter 8 | { 9 | public object Convert(object value, Type targetType, object parameter, string language) 10 | { 11 | if (value == null || !(value is string)) 12 | { 13 | return value; 14 | } 15 | 16 | return CultureInfo.CurrentCulture.TextInfo.ToTitleCase((string)value); 17 | } 18 | 19 | public object ConvertBack(object value, Type targetType, object parameter, string language) 20 | { 21 | throw new NotImplementedException(); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /Engage/Views/Controls/Signal.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 48 | 49 | 50 | 55 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /Engage/Helpers/SignalManager.cs: -------------------------------------------------------------------------------- 1 | // Engage.Helpers.SignalManager.cs 2 | using Engage.Views; 3 | using Engage.Views.Controls; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.UI.Xaml; 6 | using Microsoft.UI.Xaml.Controls; 7 | using System; 8 | using System.Collections.Generic; 9 | 10 | namespace Engage.Helpers 11 | { 12 | public class SignalManager 13 | { 14 | private readonly IServiceProvider _serviceProvider; 15 | private readonly Func _getSignalFrame; 16 | 17 | public SignalManager(IServiceProvider serviceProvider, Func getSignalFrame) 18 | { 19 | _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); 20 | _getSignalFrame = getSignalFrame ?? throw new ArgumentNullException(nameof(getSignalFrame)); 21 | } 22 | 23 | private Frame _signalFrame => _getSignalFrame(); 24 | 25 | public void ShowSignal( 26 | string title, 27 | string message, 28 | Signal.SignalType signalType, 29 | bool isClosable = true, 30 | bool isIconVisible = true, 31 | string actionContent = null, 32 | RoutedEventHandler actionClickHandler = null, 33 | IDictionary properties = null) 34 | { 35 | // Create an instance of your Signal control 36 | Signal signal = new Signal 37 | { 38 | Title = title, 39 | Message = message, 40 | Type = signalType, 41 | IsClosable = isClosable, 42 | IsIconVisible = isIconVisible, 43 | ActionContent = actionContent 44 | }; 45 | 46 | // Attach the button click event handler if provided 47 | if (actionClickHandler != null) 48 | { 49 | signal.SetSignalClickHandler(actionClickHandler); 50 | } 51 | 52 | // Set the properties for the Signal control from the properties dictionary 53 | if (properties != null) 54 | { 55 | foreach (var property in properties) 56 | { 57 | signal.SetValue(property.Key, property.Value); 58 | } 59 | } 60 | 61 | // Remove any existing signals from the signal frame (optional) 62 | if (_signalFrame != null) 63 | { 64 | for (int i = _signalFrame.BackStack.Count - 1; i >= 0; i--) 65 | { 66 | if (_signalFrame.BackStack[i].SourcePageType == typeof(Signal)) 67 | { 68 | _signalFrame.BackStack.RemoveAt(i); 69 | } 70 | } 71 | } 72 | 73 | // Show the signal in the signal frame 74 | _signalFrame.Navigate(typeof(Signal), signal); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Engage/Views/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | // Engage.Views.MainWindow.xaml.cs 2 | using System; 3 | using System.Diagnostics; 4 | using Engage.ViewModels; 5 | using Microsoft.UI.Xaml; 6 | using Microsoft.UI.Xaml.Controls; 7 | using Engage.Views.Controls; 8 | using Microsoft.UI.Xaml.Media; 9 | using Engage.Helpers; 10 | using Microsoft.Extensions.DependencyInjection; 11 | 12 | namespace Engage.Views 13 | { 14 | public sealed partial class MainWindow : Window 15 | { 16 | private readonly IServiceProvider _serviceProvider; 17 | private readonly SignalManager _signalManager; 18 | public Frame PublicSignalContainer => GlobalSignalContainer; 19 | 20 | public MainWindow(IServiceProvider serviceProvider) 21 | { 22 | InitializeComponent(); 23 | 24 | _serviceProvider = serviceProvider; 25 | _signalManager = serviceProvider.GetService(); 26 | MainFrame.Content = new HomePage(); 27 | 28 | this.SystemBackdrop = new MicaBackdrop(); 29 | this.ExtendsContentIntoTitleBar = true; 30 | this.SetTitleBar(AppTitleBar); 31 | 32 | MainNavigationView.SelectionChanged += MainNavigationView_SelectionChanged; 33 | MainNavigationView.ItemInvoked += MainNavigationView_SettingsInvoked; 34 | } 35 | 36 | private void MainNavigationView_SelectionChanged(NavigationView sender, NavigationViewSelectionChangedEventArgs args) 37 | { 38 | var app = Application.Current as App; 39 | 40 | if (args.SelectedItem is NavigationViewItem item) 41 | { 42 | string selectedItemTag = item.Tag.ToString(); 43 | 44 | switch (selectedItemTag) 45 | { 46 | case "Home": 47 | MainFrame.Content = new HomePage(); 48 | MainNavigationView.SelectedItem = null; 49 | break; 50 | case "Chat": 51 | MainFrame.Content = new ChatPage(app.ChatViewModel, this); // Pass 'this' as the second argument 52 | MainNavigationView.SelectedItem = null; 53 | break; 54 | case "Layouts": 55 | MainFrame.Content = new LayoutsPage(); // Pass 'this' as the second argument 56 | MainNavigationView.SelectedItem = null; 57 | break; 58 | } 59 | } 60 | } 61 | 62 | private void MainNavigationView_SettingsInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args) 63 | { 64 | var app = Application.Current as App; 65 | 66 | if (args.IsSettingsInvoked) 67 | { 68 | MainFrame.Content = new SettingsPage(app.SettingsViewModel, app.LocalSettingService); 69 | MainNavigationView.SelectedItem = null; 70 | } 71 | } 72 | 73 | private void DevSendSignal_Click(object sender, RoutedEventArgs e) 74 | { 75 | // get an instance of IServiceProvider from the app's service provider 76 | var serviceProvider = (Application.Current as App).ServiceProvider; 77 | var signalManager = serviceProvider.GetService(); 78 | 79 | // use the signal manager to show a signal 80 | signalManager.ShowSignal("Title", "Message", Signal.SignalType.Success); 81 | 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Engage/Views/ChatPage.xaml.cs: -------------------------------------------------------------------------------- 1 | // [FILE] Engage.Views.ChatPage.xaml.cs 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using Engage.Helpers; 6 | using Engage.ViewModels; 7 | using Engage.Views.Controls; 8 | using Microsoft.Extensions.DependencyInjection; 9 | using Microsoft.UI.Xaml; 10 | using Microsoft.UI.Xaml.Controls; 11 | using Microsoft.UI.Xaml.Navigation; 12 | 13 | namespace Engage.Views 14 | { 15 | public sealed partial class ChatPage : Page 16 | { 17 | private readonly ChatViewModel _viewModel; 18 | private readonly Frame _signalFrame; 19 | private readonly SignalManager _signalManager; 20 | private string _newMessageTextBoxValue; 21 | 22 | // Inject MainWindow into the ChatPage constructor 23 | public ChatPage(ChatViewModel viewModel, MainWindow mainWindow) 24 | { 25 | InitializeComponent(); 26 | _viewModel = viewModel; 27 | DataContext = _viewModel; 28 | 29 | _signalFrame = mainWindow.PublicSignalContainer; 30 | _signalManager = (Application.Current as App).ServiceProvider.GetService(); 31 | } 32 | 33 | private void OnSignalManagerShowSignal(object sender, EventArgs e) 34 | { 35 | _signalManager.ShowSignal("Title", "Message", Signal.SignalType.Success); 36 | } 37 | 38 | private void ScrollToBottom() 39 | { 40 | ChatScrollViewer.ChangeView(null, ChatScrollViewer.ScrollableHeight, null); 41 | } 42 | 43 | private void NewMessageTextBox_SelectionChanged(object sender, RoutedEventArgs e) 44 | { 45 | if (string.IsNullOrEmpty(NewMessageTextBox.Text)) 46 | { 47 | MessageSendButton.IsEnabled = false; 48 | } 49 | else 50 | { 51 | MessageSendButton.IsEnabled = true; 52 | } 53 | } 54 | 55 | private void ChatTabView_AddTabButtonClick(TabView sender, object args) 56 | { 57 | _viewModel.AddTab(); 58 | sender.SelectedIndex = sender.TabItems.Count - 1; 59 | } 60 | 61 | private void ChatTabView_TabCloseRequested(TabView sender, TabViewTabCloseRequestedEventArgs args) 62 | { 63 | _viewModel.CloseTab(args.Item as ChatTabViewModel); 64 | } 65 | 66 | private void RoleSelectionComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) 67 | { 68 | if (RoleSelectionComboBox.SelectedIndex == 1) 69 | { 70 | ModelSelectionComboBox.IsEnabled = false; 71 | } 72 | else 73 | { 74 | ModelSelectionComboBox.IsEnabled = true; 75 | } 76 | } 77 | 78 | private void ChatTabView_SelectionChanged(object sender, SelectionChangedEventArgs e) 79 | { 80 | if (e.RemovedItems.Count > 0) 81 | { 82 | var oldTab = e.RemovedItems[0] as ChatTabViewModel; 83 | oldTab.MessageSent -= OnMessageSent; 84 | Debug.WriteLine("Unsubscribed from MessageSent event for oldTab"); 85 | } 86 | 87 | if (e.AddedItems.Count > 0) 88 | { 89 | var newTab = e.AddedItems[0] as ChatTabViewModel; 90 | newTab.MessageSent += OnMessageSent; 91 | Debug.WriteLine("Subscribed to MessageSent event for newTab"); 92 | } 93 | } 94 | 95 | private void OnMessageSent(object sender, EventArgs e) 96 | { 97 | Debug.WriteLine("OnMessageSent called"); 98 | ScrollToBottom(); 99 | } 100 | 101 | private void ScrollToBottomButton_Click(object sender, RoutedEventArgs e) 102 | { 103 | ScrollToBottom(); 104 | } 105 | 106 | protected override void OnNavigatedFrom(NavigationEventArgs e) 107 | { 108 | base.OnNavigatedFrom(e); 109 | _newMessageTextBoxValue = NewMessageTextBox.Text; 110 | } 111 | 112 | protected override void OnNavigatedTo(NavigationEventArgs e) 113 | { 114 | base.OnNavigatedTo(e); 115 | NewMessageTextBox.Text = _newMessageTextBoxValue; 116 | } 117 | 118 | private void TabClearMessages_Click(object sender, RoutedEventArgs e) 119 | { 120 | var selectedTab = ChatTabView.SelectedItem as ChatTabViewModel; 121 | selectedTab?.ClearMessages(); 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /Engage/OpenAI/ApiClient.cs: -------------------------------------------------------------------------------- 1 | // [FILE] Engage.OpenAI.ApiClient.cs 2 | using Engage.OpenAI.Models; 3 | using Engage.Converters; 4 | using Engage.Helpers; 5 | using Microsoft.Extensions.Logging; 6 | using System; 7 | using System.Diagnostics; 8 | using System.Net.Http; 9 | using System.Net.Http.Headers; 10 | using System.Text; 11 | using System.Text.Json; 12 | using System.Threading.Tasks; 13 | 14 | namespace Engage.OpenAI 15 | { 16 | // This class is responsible for sending messages to the OpenAI API 17 | public class ApiClient : IApiClient 18 | { 19 | private readonly HttpClient _httpClient; 20 | private readonly JsonSerializerOptions _jsonSerializerOptions; 21 | private readonly ILogger _logger; 22 | 23 | // Constructor that takes an HttpClient, IConfiguration, and ILogger 24 | public ApiClient(HttpClient httpClient, ILocalSettingsService localSettingsService, ILogger logger) 25 | { 26 | _httpClient = httpClient; 27 | _logger = logger; 28 | _jsonSerializerOptions = new JsonSerializerOptions 29 | { 30 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase, // Add this line 31 | PropertyNameCaseInsensitive = true 32 | }; 33 | _jsonSerializerOptions.Converters.Add(new UnixTimestampConverter()); // Add this line 34 | 35 | // Get the OpenAI API key from the configuration 36 | string apiKey = localSettingsService.GetSetting("OpenAIKey"); 37 | 38 | // Throw an exception if the API key is not found 39 | if (string.IsNullOrEmpty(apiKey)) 40 | { 41 | throw new InvalidOperationException("OpenAI API Key not found in configuration."); 42 | } 43 | 44 | // Set the Authorization header on the HttpClient to include the API key 45 | _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey); 46 | Debug.WriteLine(apiKey); 47 | } 48 | 49 | // Method for sending a message to the OpenAI API 50 | public async Task SendMessageAsync(ApiRequestOptions requestOptions) 51 | { 52 | // Serialize the request body to JSON 53 | string jsonString = JsonSerializer.Serialize(requestOptions, _jsonSerializerOptions); 54 | Debug.WriteLine($"Request JSON: {jsonString}"); 55 | 56 | // Create an HttpContent object with the serialized JSON as its content 57 | HttpContent content = new StringContent(jsonString, Encoding.UTF8, "application/json"); 58 | 59 | HttpResponseMessage response; 60 | 61 | try 62 | { 63 | // Send the POST request to the OpenAI API using the HttpClient 64 | string url = "chat/completions"; 65 | Debug.WriteLine($"Request URL: {_httpClient.BaseAddress}{url}"); // Log the request URL 66 | 67 | // Send the POST request to the OpenAI API using the HttpClient 68 | response = await _httpClient.PostAsync("chat/completions", content).ConfigureAwait(false); 69 | _logger.LogInformation("Response received from API"); 70 | } 71 | catch (HttpRequestException ex) 72 | { 73 | // Handle any exceptions that occur during the request 74 | _logger.LogError(ex, "Error sending message to OpenAI API"); 75 | throw; 76 | } 77 | 78 | if (!response.IsSuccessStatusCode) 79 | { 80 | string responseContent = await response.Content.ReadAsStringAsync(); 81 | Debug.WriteLine($"Received a non-success status code: {response.StatusCode}. Response content: {responseContent}"); 82 | _logger.LogError("Received a non-success status code: {StatusCode}. Response content: {ResponseContent}", response.StatusCode, responseContent); 83 | return null; 84 | } 85 | 86 | try 87 | { 88 | // Read the response content as a string and deserialize it to a Response object 89 | jsonString = await response.Content.ReadAsStringAsync().ConfigureAwait(false); 90 | Debug.Write(jsonString); 91 | return JsonSerializer.Deserialize(jsonString, _jsonSerializerOptions); 92 | } 93 | catch (JsonException ex) 94 | { 95 | Debug.WriteLine(ex, "Error deserializing JSON response"); 96 | throw; 97 | } 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /Engage/Views/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 75 | 76 | 77 | 78 | 89 | 90 | 91 | 92 | 96 | 100 | 104 | 105 | 106 | 107 | 108 | 112 | 113 | 114 | 119 | 120 | 121 | 122 | 123 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /Engage/Engage.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | WinExe 4 | net6.0-windows10.0.19041.0 5 | 10.0.17763.0 6 | Engage 7 | app.manifest 8 | x86;x64;ARM64 9 | win10-x86;win10-x64;win10-arm64 10 | win10-$(Platform).pubxml 11 | true 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 59 | 60 | 61 | 62 | 63 | 64 | MSBuild:Compile 65 | 66 | 67 | 68 | 69 | MSBuild:Compile 70 | 71 | 72 | 73 | 74 | MSBuild:Compile 75 | 76 | 77 | 78 | 79 | MSBuild:Compile 80 | 81 | 82 | 83 | 84 | MSBuild:Compile 85 | 86 | 87 | 88 | 89 | MSBuild:Compile 90 | 91 | 92 | 93 | 94 | MSBuild:Compile 95 | 96 | 97 | 98 | 103 | 104 | true 105 | 106 | 107 | -------------------------------------------------------------------------------- /Engage/ViewModels/ChatTabViewModel.cs: -------------------------------------------------------------------------------- 1 | // [FILE] Engage.ViewModels.ChatTabViewModel.cs 2 | using Engage.Helpers; 3 | using Engage.OpenAI; 4 | using Engage.OpenAI.Models; 5 | using Microsoft.Extensions.Logging; 6 | using Microsoft.UI.Xaml.Controls; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.Collections.ObjectModel; 10 | using System.ComponentModel; 11 | using System.Diagnostics; 12 | using System.Threading.Tasks; 13 | using System.Windows.Input; 14 | 15 | namespace Engage.ViewModels 16 | { 17 | public class ChatTabViewModel : BaseViewModel 18 | { 19 | // Interfaces 20 | private readonly IChatService _chatService; 21 | public ICommand SendMessageCommand { get; set; } 22 | public IChatService ChatService { get; set; } 23 | // Models 24 | public CustomRelayCommand ToggleMessageSendingCommand { get; } 25 | private ObservableCollection _messages; 26 | public ObservableCollection Messages 27 | { 28 | get => _messages; 29 | set => SetProperty(ref _messages, value); 30 | } 31 | 32 | // Properties 33 | private string _message; 34 | public string Message 35 | { 36 | get => _message; 37 | set => SetProperty(ref _message, value); 38 | } 39 | 40 | private int _selectedRoleIndex = 0; 41 | public int SelectedRoleIndex 42 | { 43 | get => _selectedRoleIndex; 44 | set => SetProperty(ref _selectedRoleIndex, value); 45 | } 46 | public string SelectedRole => SelectedRoleIndex == 0 ? "user" : "assistant"; 47 | 48 | private bool _isSendingMessage; 49 | public bool IsSendingMessage 50 | { 51 | get => _isSendingMessage; 52 | set => SetProperty(ref _isSendingMessage, value); 53 | } 54 | 55 | private int _selectedModelIndex = 1; 56 | public int SelectedModelIndex 57 | { 58 | get => _selectedModelIndex; 59 | set => SetProperty(ref _selectedModelIndex, value); 60 | } 61 | public string SelectedModel => SelectedModelIndex == 0 ? "gpt-4" : "gpt-3.5-turbo"; 62 | 63 | private string _newMessageText; 64 | public string NewMessageText 65 | { 66 | get => _newMessageText; 67 | set => SetProperty(ref _newMessageText, value); 68 | } 69 | 70 | private string _tabName; 71 | public string TabName 72 | { 73 | get => _tabName; 74 | set 75 | { 76 | SetProperty(ref _tabName, value); 77 | OnPropertyChanged(nameof(TabName)); 78 | } 79 | } 80 | 81 | private bool _isSelected; 82 | public bool IsSelected 83 | { 84 | get => _isSelected; 85 | set => SetProperty(ref _isSelected, value); 86 | } 87 | 88 | // Methods 89 | public ChatTabViewModel(IChatService service, string tabName) 90 | { 91 | _chatService = service; 92 | SendMessageCommand = new CustomRelayCommand(async () => await SendMessageAsync(), () => !IsSendingMessage); 93 | Messages = new ObservableCollection(); 94 | TabName = tabName; 95 | } 96 | 97 | public void ClearMessages() 98 | { 99 | Messages.Clear(); 100 | } 101 | 102 | public event EventHandler MessageSent; 103 | 104 | private async Task SendMessageAsync() 105 | { 106 | if (string.IsNullOrWhiteSpace(NewMessageText)) 107 | { 108 | return; 109 | } 110 | 111 | ChatMessageType chatMessageType = SelectedRole == "assistant" ? ChatMessageType.Received : ChatMessageType.Sent; 112 | 113 | var userMessage = new Message { Content = NewMessageText, Role = SelectedRole }; 114 | var userMessageViewModel = new MessageViewModel(userMessage, chatMessageType); 115 | Messages.Add(userMessageViewModel); 116 | 117 | NewMessageText = string.Empty; 118 | 119 | if (SelectedRole != "assistant") 120 | { 121 | var messages = new List 122 | { 123 | new Message { Content = "You are a helpful assistant.", Role = "system" } 124 | }; 125 | 126 | // Add previous messages to the list 127 | foreach (var messageViewModel in Messages) 128 | { 129 | messages.Add(new Message { Content = messageViewModel.Content, Role = messageViewModel.Role }); 130 | } 131 | 132 | var requestOptions = new ApiRequestOptions { Model = SelectedModel, Messages = messages }; 133 | var response = await _chatService.SendMessageAsync(requestOptions); 134 | 135 | if (response != null) 136 | { 137 | var assistantMessageViewModel = new MessageViewModel(new Message { Content = response.Content, Role = "assistant" }, ChatMessageType.Received); 138 | Messages.Add(assistantMessageViewModel); 139 | } 140 | } 141 | MessageSent?.Invoke(this, EventArgs.Empty); 142 | } 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /Engage/Views/Layouts/MainWindowLayout.xaml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 44 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 98 | 99 | 108 | 109 | 118 | 119 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /Engage/Views/ChatPage.xaml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 43 | 44 | 45 | 49 | 50 | 51 | 52 | 53 | 54 | 61 | 62 | 63 | 64 | 65 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 109 | 110 | 119 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 159 | 160 | 161 | 162 | 163 | 164 | 175 | 176 | 177 | 184 | 185 | 186 | 187 | 188 | 189 | 200 | 201 | 202 | 203 | --------------------------------------------------------------------------------