From 516774dec67b274c5c968da5f1ed51ebef44339c Mon Sep 17 00:00:00 2001 From: Daniel Monettelli Date: Tue, 15 Oct 2024 15:51:19 -0500 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=80=20[Code]=20Updated=20architecture?= =?UTF-8?q?=20to=20"Chat=20Completion"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ChatGPT/Constants/APIConstants.cs | 15 +- ChatGPT/Models/Choice.cs | 17 +- ChatGPT/Models/CompletionRequest.cs | 28 +-- ChatGPT/Models/CompletionResponse.cs | 23 +- ChatGPT/Models/GeneratedImage.cs | 9 +- ChatGPT/Models/GenerationRequest.cs | 19 +- ChatGPT/Models/GenerationResponse.cs | 9 +- ChatGPT/Models/LocalMessage.cs | 9 + ChatGPT/Models/Message.cs | 10 - ChatGPT/Services/IOpenAIService.cs | 11 +- ChatGPT/Services/OpenAIService.cs | 94 ++++---- ChatGPT/ViewModels/BaseViewModel.cs | 11 +- ChatGPT/ViewModels/ConversationViewModel.cs | 210 +++++++++--------- .../Templates/BotMessageItemTemplate.xaml | 2 +- .../Templates/MessageDataTemplateSelector.cs | 2 +- .../Templates/UserMessageItemTemplate.xaml | 2 +- 16 files changed, 246 insertions(+), 225 deletions(-) create mode 100644 ChatGPT/Models/LocalMessage.cs delete mode 100644 ChatGPT/Models/Message.cs diff --git a/ChatGPT/Constants/APIConstants.cs b/ChatGPT/Constants/APIConstants.cs index 45b5835..bda67db 100644 --- a/ChatGPT/Constants/APIConstants.cs +++ b/ChatGPT/Constants/APIConstants.cs @@ -1,11 +1,10 @@ -namespace ChatGPT.Constants +namespace ChatGPT.Constants; + +public static class APIConstants { - public static class APIConstants - { - public const string OpenAIUrl = "https://api.openai.com/"; - public const string OpenAIToken = "OPENAI_API_KEY_HERE"; + public const string OpenAIUrl = "https://api.openai.com/"; + public const string OpenAIToken = "OPENAI_API_KEY_HERE"; - public const string OpenAIEndpoint_Completions = "v1/completions"; - public const string OpenAIEndpoint_Generations = "v1/images/generations"; - } + public const string OpenAIEndpoint_Completions = "v1/chat/completions"; + public const string OpenAIEndpoint_Generations = "v1/images/generations"; } diff --git a/ChatGPT/Models/Choice.cs b/ChatGPT/Models/Choice.cs index 786ad65..c686b3e 100644 --- a/ChatGPT/Models/Choice.cs +++ b/ChatGPT/Models/Choice.cs @@ -1,8 +1,13 @@ -namespace ChatGPT.Models +namespace ChatGPT.Models; + +public class Choice { - public class Choice - { - public string Text { get; set; } - public int Index { get; set; } - } + public Message Message { get; set; } } + +public class Message +{ + public string Role { get; set; } = "assistant"; + + public string Content { get; set; } +} \ No newline at end of file diff --git a/ChatGPT/Models/CompletionRequest.cs b/ChatGPT/Models/CompletionRequest.cs index 8c1a1aa..f1efce1 100644 --- a/ChatGPT/Models/CompletionRequest.cs +++ b/ChatGPT/Models/CompletionRequest.cs @@ -1,19 +1,21 @@ using System.Text.Json.Serialization; -namespace ChatGPT.Models +namespace ChatGPT.Models; + +public class CompletionRequest { - public class CompletionRequest - { - [JsonPropertyName("model")] - public string Model { get; set; } = "gpt-3.5-turbo-instruct"; + [JsonPropertyName("model")] + public string Model { get; set; } - [JsonPropertyName("prompt")] - public string Prompt { get; set; } + [JsonPropertyName("messages")] + public List Messages { get; set; } +} - [JsonPropertyName("temperature")] - public double Temperature { get; set; } = 0; +public class MessageRequest +{ + [JsonPropertyName("role")] + public string Role { get; set; } - [JsonPropertyName("max_tokens")] - public int MaxTokens { get; set; } = 100; - } -} + [JsonPropertyName("content")] + public string Content { get; set; } +} \ No newline at end of file diff --git a/ChatGPT/Models/CompletionResponse.cs b/ChatGPT/Models/CompletionResponse.cs index 4ea1bc3..3d15675 100644 --- a/ChatGPT/Models/CompletionResponse.cs +++ b/ChatGPT/Models/CompletionResponse.cs @@ -1,8 +1,19 @@ -namespace ChatGPT.Models +namespace ChatGPT.Models; + +public class CompletionResponse { - public class CompletionResponse - { - public string Id { get; set; } - public List Choices { get; set; } - } + public string Id { get; set; } + + public List Choices { get; set; } + + public Usage Usage { get; set; } } + +public class Usage +{ + public int Prompt_Tokens { get; set; } = 9; + + public int Completion_Tokens { get; set; } = 12; + + public int Total_Tokens { get; set; } = 21; +} \ No newline at end of file diff --git a/ChatGPT/Models/GeneratedImage.cs b/ChatGPT/Models/GeneratedImage.cs index 286baab..5bd2abd 100644 --- a/ChatGPT/Models/GeneratedImage.cs +++ b/ChatGPT/Models/GeneratedImage.cs @@ -1,7 +1,6 @@ -namespace ChatGPT.Models +namespace ChatGPT.Models; + +public class GeneratedImage { - public class GeneratedImage - { - public string Url { get; set; } - } + public string Url { get; set; } } diff --git a/ChatGPT/Models/GenerationRequest.cs b/ChatGPT/Models/GenerationRequest.cs index 96f6f1e..c5c116b 100644 --- a/ChatGPT/Models/GenerationRequest.cs +++ b/ChatGPT/Models/GenerationRequest.cs @@ -1,16 +1,15 @@ using System.Text.Json.Serialization; -namespace ChatGPT.Models +namespace ChatGPT.Models; + +public class GenerationRequest { - public class GenerationRequest - { - [JsonPropertyName("prompt")] - public string Prompt { get; set; } + [JsonPropertyName("prompt")] + public string Prompt { get; set; } - [JsonPropertyName("n")] - public int N { get; set; } = 1; + [JsonPropertyName("n")] + public int N { get; set; } = 1; - [JsonPropertyName("size")] - public string Size { get; set; } = "512x512"; - } + [JsonPropertyName("size")] + public string Size { get; set; } = "512x512"; } \ No newline at end of file diff --git a/ChatGPT/Models/GenerationResponse.cs b/ChatGPT/Models/GenerationResponse.cs index 68a4289..37dc248 100644 --- a/ChatGPT/Models/GenerationResponse.cs +++ b/ChatGPT/Models/GenerationResponse.cs @@ -1,7 +1,6 @@ -namespace ChatGPT.Models +namespace ChatGPT.Models; + +public class GenerationResponse { - public class GenerationResponse - { - public List Data { get; set; } - } + public List Data { get; set; } } diff --git a/ChatGPT/Models/LocalMessage.cs b/ChatGPT/Models/LocalMessage.cs new file mode 100644 index 0000000..47f1e88 --- /dev/null +++ b/ChatGPT/Models/LocalMessage.cs @@ -0,0 +1,9 @@ +namespace ChatGPT.Models; + +public class LocalMessage +{ + public string Text { get; set; } + public bool IsUserMessage { get; set; } + public bool IsTextActive { get; set; } + public bool IsImageActive { get; set; } +} diff --git a/ChatGPT/Models/Message.cs b/ChatGPT/Models/Message.cs deleted file mode 100644 index d79ae24..0000000 --- a/ChatGPT/Models/Message.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ChatGPT.Models -{ - public class Message - { - public string Text { get; set; } - public bool IsUserMessage { get; set; } - public bool IsTextActive { get; set; } - public bool IsImageActive { get; set; } - } -} diff --git a/ChatGPT/Services/IOpenAIService.cs b/ChatGPT/Services/IOpenAIService.cs index 980921f..eacb486 100644 --- a/ChatGPT/Services/IOpenAIService.cs +++ b/ChatGPT/Services/IOpenAIService.cs @@ -1,9 +1,8 @@ -namespace ChatGPT.Services +namespace ChatGPT.Services; + +public interface IOpenAIService { - public interface IOpenAIService - { - Task AskQuestion(string query); + Task AskQuestion(string query); - Task CreateImage(string query); - } + Task CreateImage(string query); } diff --git a/ChatGPT/Services/OpenAIService.cs b/ChatGPT/Services/OpenAIService.cs index 5f8a0f3..cbbc34e 100644 --- a/ChatGPT/Services/OpenAIService.cs +++ b/ChatGPT/Services/OpenAIService.cs @@ -5,61 +5,73 @@ using System.Text; using System.Text.Json; -namespace ChatGPT.Services +namespace ChatGPT.Services; + +public class OpenAIService : IOpenAIService { - public class OpenAIService : IOpenAIService - { - HttpClient client; - JsonSerializerOptions options = new() { PropertyNameCaseInsensitive = true }; + HttpClient client; + JsonSerializerOptions options = new() { PropertyNameCaseInsensitive = true }; - public OpenAIService() - { - client = new HttpClient(); - client.BaseAddress = new Uri(APIConstants.OpenAIUrl); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", APIConstants.OpenAIToken); - client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - } + public OpenAIService() + { + client = new HttpClient(); + client.BaseAddress = new Uri(APIConstants.OpenAIUrl); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", APIConstants.OpenAIToken); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + } - public async Task AskQuestion(string query) + public async Task AskQuestion(string query) + { + var completion = new CompletionRequest { - var completion = new CompletionRequest() + Model = "gpt-4o-mini", + Messages = new List { - Prompt = query - }; - - var body = JsonSerializer.Serialize(completion); - var content = new StringContent(body, Encoding.UTF8, "application/json"); + new MessageRequest + { + Role = "system", + Content = "You are a helpful assistant." + }, + new MessageRequest + { + Role = "user", + Content = query + } + } + }; - var response = await client.PostAsync(APIConstants.OpenAIEndpoint_Completions, content); + var body = JsonSerializer.Serialize(completion); + var content = new StringContent(body, Encoding.UTF8, "application/json"); - if (response.IsSuccessStatusCode) - { - var data = await response.Content.ReadFromJsonAsync(options); - return data?.Choices?.FirstOrDefault().Text; - } + var response = await client.PostAsync(APIConstants.OpenAIEndpoint_Completions, content); - return default; + if (response.IsSuccessStatusCode) + { + var data = await response.Content.ReadFromJsonAsync(options); + return data?.Choices?.FirstOrDefault().Message.Content; } - public async Task CreateImage(string query) - { - var generation = new GenerationRequest() - { - Prompt = query - }; + return default; + } - var body = JsonSerializer.Serialize(generation); - var content = new StringContent(body, Encoding.UTF8, "application/json"); + public async Task CreateImage(string query) + { + var generation = new GenerationRequest() + { + Prompt = query + }; - var response = await client.PostAsync(APIConstants.OpenAIEndpoint_Generations, content); + var body = JsonSerializer.Serialize(generation); + var content = new StringContent(body, Encoding.UTF8, "application/json"); - if (response.IsSuccessStatusCode) - { - var data = await response.Content.ReadFromJsonAsync(options); - return data.Data?.FirstOrDefault()?.Url; - } + var response = await client.PostAsync(APIConstants.OpenAIEndpoint_Generations, content); - return default; + if (response.IsSuccessStatusCode) + { + var data = await response.Content.ReadFromJsonAsync(options); + return data.Data?.FirstOrDefault()?.Url; } + + return default; } } diff --git a/ChatGPT/ViewModels/BaseViewModel.cs b/ChatGPT/ViewModels/BaseViewModel.cs index 5071fca..1dfe01c 100644 --- a/ChatGPT/ViewModels/BaseViewModel.cs +++ b/ChatGPT/ViewModels/BaseViewModel.cs @@ -1,10 +1,9 @@ using CommunityToolkit.Mvvm.ComponentModel; -namespace ChatGPT.ViewModels +namespace ChatGPT.ViewModels; + +public partial class BaseViewModel : ObservableObject { - public partial class BaseViewModel : ObservableObject - { - [ObservableProperty] - bool isBusy; - } + [ObservableProperty] + bool isBusy; } diff --git a/ChatGPT/ViewModels/ConversationViewModel.cs b/ChatGPT/ViewModels/ConversationViewModel.cs index 273977b..63db8d4 100644 --- a/ChatGPT/ViewModels/ConversationViewModel.cs +++ b/ChatGPT/ViewModels/ConversationViewModel.cs @@ -5,145 +5,143 @@ using CommunityToolkit.Mvvm.Input; using System.Collections.ObjectModel; -namespace ChatGPT.ViewModels +namespace ChatGPT.ViewModels; + +public partial class ConversationViewModel : BaseViewModel { - public partial class ConversationViewModel : BaseViewModel - { - [ObservableProperty] - string query; + [ObservableProperty] + string query; - [ObservableProperty] - bool isAnimationVisible = true; + [ObservableProperty] + bool isAnimationVisible = true; - [ObservableProperty] - ObservableCollection messages = new(); + [ObservableProperty] + ObservableCollection messages = new(); - [ObservableProperty] - CollectionView collectionView; + [ObservableProperty] + CollectionView collectionView; - [ObservableProperty] - ContentPage conversationView; + [ObservableProperty] + ContentPage conversationView; - [ObservableProperty] - double opacityModeMessage = 1; + [ObservableProperty] + double opacityModeMessage = 1; - [ObservableProperty] - double opacityModeImage = 0.5; + [ObservableProperty] + double opacityModeImage = 0.5; - [ObservableProperty] - private Message theMessage = new(); + [ObservableProperty] + private LocalMessage theMessage = new(); - IOpenAIService _openAIService; - IDispatcher _dispatcher; + IOpenAIService _openAIService; + IDispatcher _dispatcher; - private AsyncRelayCommand _currentCommand; + private AsyncRelayCommand _currentCommand; - public AsyncRelayCommand CurrentCommand + public AsyncRelayCommand CurrentCommand + { + get { - get - { - return _currentCommand ??= new AsyncRelayCommand(AskQuestionAsync); - } - set - { - SetProperty(ref _currentCommand, value); - } + return _currentCommand ??= new AsyncRelayCommand(AskQuestionAsync); } - - public ConversationViewModel(IOpenAIService openAIService, IDispatcher dispatcher) + set { - _openAIService = openAIService; - _dispatcher = dispatcher; + SetProperty(ref _currentCommand, value); } + } - private void AddMessage(string message, bool isUserMessage) - { - if (Messages.Count <= 0) IsAnimationVisible = false; - - Messages.Add(new Message - { - Text = message, - IsUserMessage = isUserMessage, - IsTextActive = TheMessage.IsTextActive, - IsImageActive = TheMessage.IsImageActive - }); + public ConversationViewModel(IOpenAIService openAIService, IDispatcher dispatcher) + { + _openAIService = openAIService; + _dispatcher = dispatcher; + } - Task.Delay(150).ContinueWith(t => - { - _dispatcher.Dispatch(() => - { - CollectionView.ScrollTo - ( - item: Messages.Last(), - position: ScrollToPosition.End, - animate: true - ); - }); - }); - } + private void AddMessage(string message, bool isUserMessage) + { + if (Messages.Count <= 0) IsAnimationVisible = false; - private async Task QueryManagerAsync(Func> queryManager) + Messages.Add(new LocalMessage { - if (string.IsNullOrEmpty(Query)) return; - string queryCopy = Query; - Query = string.Empty; - AddMessage(message: queryCopy, isUserMessage: true); - IsBusy = true; - string answer = await queryManager(queryCopy); - AddMessage(message: answer.TrimStart(), isUserMessage: false); - IsBusy = false; - } + Text = message, + IsUserMessage = isUserMessage, + IsTextActive = TheMessage.IsTextActive, + IsImageActive = TheMessage.IsImageActive + }); - private async Task AskQuestionAsync() + Task.Delay(150).ContinueWith(t => { - TheMessage.IsTextActive = true; - TheMessage.IsImageActive = false; + _dispatcher.Dispatch(() => + { + CollectionView.ScrollTo + ( + item: Messages.Last(), + position: ScrollToPosition.End, + animate: true + ); + }); + }); + } - await QueryManagerAsync(_openAIService.AskQuestion); - } + private async Task QueryManagerAsync(Func> queryManager) + { + if (string.IsNullOrEmpty(Query)) return; + string queryCopy = Query; + Query = string.Empty; + AddMessage(message: queryCopy, isUserMessage: true); + IsBusy = true; + string answer = await queryManager(queryCopy); + AddMessage(message: answer.TrimStart(), isUserMessage: false); + IsBusy = false; + } - private async Task CreateImageAsync() - { - TheMessage.IsTextActive = false; - TheMessage.IsImageActive = true; + private async Task AskQuestionAsync() + { + TheMessage.IsTextActive = true; + TheMessage.IsImageActive = false; - await QueryManagerAsync(_openAIService.CreateImage); - } + await QueryManagerAsync(_openAIService.AskQuestion); + } - [RelayCommand] - private async Task AskQuestion() - { - OpacityModeMessage = 1; - OpacityModeImage = 0.5; + private async Task CreateImageAsync() + { + TheMessage.IsTextActive = false; + TheMessage.IsImageActive = true; - await Toast.Make("Write mode").Show(); + await QueryManagerAsync(_openAIService.CreateImage); + } - CurrentCommand = new AsyncRelayCommand(AskQuestionAsync); - } + [RelayCommand] + private async Task AskQuestion() + { + OpacityModeMessage = 1; + OpacityModeImage = 0.5; - [RelayCommand] - private async Task CreateImage() - { - OpacityModeMessage = 0.5; - OpacityModeImage = 1; + await Toast.Make("Write mode").Show(); - await Toast.Make("Image mode").Show(); + CurrentCommand = new AsyncRelayCommand(AskQuestionAsync); + } - CurrentCommand = new AsyncRelayCommand(CreateImageAsync); - } + [RelayCommand] + private async Task CreateImage() + { + OpacityModeMessage = 0.5; + OpacityModeImage = 1; - [RelayCommand] - private async Task SelectTheme() - { - AppTheme currentTheme = Application.Current.RequestedTheme; - AppTheme newTheme = currentTheme == AppTheme.Dark ? AppTheme.Light : AppTheme.Dark; - Application.Current.UserAppTheme = newTheme; + await Toast.Make("Image mode").Show(); - await ConversationView.ScaleTo(0.95, 250, Easing.CubicOut); - await ConversationView.ScaleTo(1.05, 250, Easing.CubicIn); - await ConversationView.ScaleTo(1, 250, Easing.CubicOut); - } + CurrentCommand = new AsyncRelayCommand(CreateImageAsync); + } + + [RelayCommand] + private async Task SelectTheme() + { + AppTheme currentTheme = Application.Current.RequestedTheme; + AppTheme newTheme = currentTheme == AppTheme.Dark ? AppTheme.Light : AppTheme.Dark; + Application.Current.UserAppTheme = newTheme; + await ConversationView.ScaleTo(0.95, 250, Easing.CubicOut); + await ConversationView.ScaleTo(1.05, 250, Easing.CubicIn); + await ConversationView.ScaleTo(1, 250, Easing.CubicOut); } } diff --git a/ChatGPT/Views/Templates/BotMessageItemTemplate.xaml b/ChatGPT/Views/Templates/BotMessageItemTemplate.xaml index cd69001..fc042d3 100644 --- a/ChatGPT/Views/Templates/BotMessageItemTemplate.xaml +++ b/ChatGPT/Views/Templates/BotMessageItemTemplate.xaml @@ -5,7 +5,7 @@ xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:model="clr-namespace:ChatGPT.Models" - x:DataType="model:Message" + x:DataType="model:LocalMessage" ColumnDefinitions="*, Auto, 10, 35" SizeChanged="ParentGrid_SizeChanged"> diff --git a/ChatGPT/Views/Templates/MessageDataTemplateSelector.cs b/ChatGPT/Views/Templates/MessageDataTemplateSelector.cs index 643b28b..97de91f 100644 --- a/ChatGPT/Views/Templates/MessageDataTemplateSelector.cs +++ b/ChatGPT/Views/Templates/MessageDataTemplateSelector.cs @@ -9,7 +9,7 @@ internal class MessageDataTemplateSelector : DataTemplateSelector protected override DataTemplate OnSelectTemplate(object item, BindableObject container) { - var message = (Message)item; + var message = (LocalMessage)item; return message.IsUserMessage ? UserMessageItemTemplate : BotMessageTemplate; } diff --git a/ChatGPT/Views/Templates/UserMessageItemTemplate.xaml b/ChatGPT/Views/Templates/UserMessageItemTemplate.xaml index 22a5c15..4122792 100644 --- a/ChatGPT/Views/Templates/UserMessageItemTemplate.xaml +++ b/ChatGPT/Views/Templates/UserMessageItemTemplate.xaml @@ -5,7 +5,7 @@ xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:model="clr-namespace:ChatGPT.Models" - x:DataType="model:Message" + x:DataType="model:LocalMessage" ColumnDefinitions="Auto, 10, Auto, *" SizeChanged="ParentGrid_SizeChanged">