From 9e6709458aff2e1dba7b98373321ba1e45785183 Mon Sep 17 00:00:00 2001 From: mirakst Date: Tue, 7 Nov 2023 13:33:09 +0100 Subject: [PATCH 01/12] Cleaned up original API Client code, now uses Nuget package --- ChatRPG/API/HttpClientFactory.cs | 16 +++++ ChatRPG/API/IOpenAiLlmClient.cs | 2 +- ChatRPG/API/OpenAiLlmClient.cs | 103 ++++++------------------------- ChatRPG/Pages/ApiAccess.razor | 4 +- ChatRPG/Pages/Campaign.razor | 4 +- ChatRPG/Program.cs | 1 + 6 files changed, 40 insertions(+), 90 deletions(-) create mode 100644 ChatRPG/API/HttpClientFactory.cs diff --git a/ChatRPG/API/HttpClientFactory.cs b/ChatRPG/API/HttpClientFactory.cs new file mode 100644 index 0000000..16bbe5a --- /dev/null +++ b/ChatRPG/API/HttpClientFactory.cs @@ -0,0 +1,16 @@ +namespace ChatRPG.API; + +public class HttpClientFactory : IHttpClientFactory +{ + private HttpMessageHandler _httpMessageHandler; + + public HttpClientFactory(HttpMessageHandler httpMessageHandler) + { + _httpMessageHandler = httpMessageHandler; + } + + public HttpClient CreateClient(string name) + { + return new HttpClient(_httpMessageHandler); + } +} diff --git a/ChatRPG/API/IOpenAiLlmClient.cs b/ChatRPG/API/IOpenAiLlmClient.cs index 71b0cc1..e6e77f6 100644 --- a/ChatRPG/API/IOpenAiLlmClient.cs +++ b/ChatRPG/API/IOpenAiLlmClient.cs @@ -2,7 +2,7 @@ namespace ChatRPG.API; public interface IOpenAiLlmClient { - Task GetChatCompletion(List inputs); + Task GetChatCompletion(List inputs); } public record ChatCompletionObject(string Id, string Object, int Created, string Model, Choice[] Choices, Usage Usage); diff --git a/ChatRPG/API/OpenAiLlmClient.cs b/ChatRPG/API/OpenAiLlmClient.cs index 79fc9aa..9a82a36 100644 --- a/ChatRPG/API/OpenAiLlmClient.cs +++ b/ChatRPG/API/OpenAiLlmClient.cs @@ -1,101 +1,36 @@ using Microsoft.IdentityModel.Tokens; -using RestSharp; -using RestSharp.Authenticators; +using OpenAI_API; +using OpenAI_API.Chat; namespace ChatRPG.API; -public class OpenAiLlmClient : IOpenAiLlmClient, IDisposable +public class OpenAiLlmClient : IOpenAiLlmClient { - private const string OpenAiBaseUrl = "https://api.openai.com/v1/"; private const string Model = "gpt-3.5-turbo"; private const double Temperature = 0.7; - private const double PromptToken1KCost = 0.0015; - private const double CompletionToken1KCost = 0.002; + + private readonly IHttpClientFactory _httpClientFactory; + private readonly OpenAIAPI _openAiApi; - private readonly ILogger _logger; - private readonly RestClient _client; - - public OpenAiLlmClient(ILogger logger, IConfiguration configuration, - HttpMessageHandler httpMessageHandler) + public OpenAiLlmClient(IConfiguration configuration, IHttpClientFactory httpClientFactory) { - _logger = logger; - - var options = new RestClientOptions(OpenAiBaseUrl) - { - Authenticator = new JwtAuthenticator(configuration.GetSection("ApiKeys").GetValue("OpenAI") ?? string.Empty), - FailOnDeserializationError = false, - ConfigureMessageHandler = _ => httpMessageHandler - }; - _client = new RestClient(options); + _httpClientFactory = httpClientFactory; + _openAiApi = new OpenAIAPI(configuration.GetSection("ApiKeys").GetValue("OpenAI") ?? string.Empty); + _openAiApi.Chat.DefaultChatRequestArgs.Model = Model; + _openAiApi.Chat.DefaultChatRequestArgs.Temperature = Temperature; } - public async Task GetChatCompletion(List inputs) + public async Task GetChatCompletion(List inputs) { if (inputs.IsNullOrEmpty()) throw new ArgumentNullException(nameof(inputs)); - - var openAiGptInput = new OpenAiGptInput(Model, inputs, Temperature); - - var request = new RestRequest("chat/completions", Method.Post); - request.AddJsonBody(openAiGptInput, ContentType.Json); - - _logger.LogInformation(""" - Request URL: {Url} - Method: {Method} - Parameters: {Parameters} - Messages: {Messages} - """, - OpenAiBaseUrl + request.Resource, - request.Method, - string.Join(", ", request.Parameters.Select(p => $"{p.Name}={p.Value}")), - string.Join(", ", inputs.Select(input => input.Content)) - ); - - var response = await _client.ExecuteAsync(request); - - if (response.ErrorException != null) - { - _logger.LogError("Error retrieving data from API: {ErrorExceptionMessage}", response.ErrorException.Message); - } - - var data = response!.Data; - - if (data is null) + + var chat = _openAiApi.Chat.CreateConversation(); + _openAiApi.HttpClientFactory = _httpClientFactory; + foreach (var openAiGptInputMessage in inputs) { - throw new EmptyResponseException(response, "The API response has no data."); + chat.AppendMessage(ChatMessageRole.FromString(openAiGptInputMessage.Role), openAiGptInputMessage.Content); } - - var promptTokens = data.Usage.PromptTokens; - var completionTokens = data.Usage.CompletionTokens; - - var promptCost = (promptTokens / 1000.0) * PromptToken1KCost; - var completionCost = (completionTokens / 1000.0) * CompletionToken1KCost; - var estimatedCost = promptCost + completionCost; - - _logger.LogInformation(""" - Prompt tokens: {PTokens} - Completion tokens: {CTokens} - Estimated cost: {EstCost} - """, - promptTokens, - completionTokens, - "$" + estimatedCost - ); - - return data; - } - - public void Dispose() - { - _client.Dispose(); - GC.SuppressFinalize(this); - } -} - -public class EmptyResponseException : Exception -{ - public RestResponse Response; - public EmptyResponseException(RestResponse response, string message) : base(message) - { - Response = response; + + return await chat.GetResponseFromChatbotAsync(); } } diff --git a/ChatRPG/Pages/ApiAccess.razor b/ChatRPG/Pages/ApiAccess.razor index 3d3eb40..09861da 100644 --- a/ChatRPG/Pages/ApiAccess.razor +++ b/ChatRPG/Pages/ApiAccess.razor @@ -16,7 +16,7 @@ @if (_openAiResponse is not null) { -

Response: @_openAiResponse.Choices.First().Message.Content

+

Response: @_openAiResponse

}


@@ -49,7 +49,7 @@ } @code { private string _input; - private ChatCompletionObject _openAiResponse; + private string _openAiResponse; private string _zip; private List _foodWasteResponses; diff --git a/ChatRPG/Pages/Campaign.razor b/ChatRPG/Pages/Campaign.razor index 9b4c65c..5e9a1b4 100644 --- a/ChatRPG/Pages/Campaign.razor +++ b/ChatRPG/Pages/Campaign.razor @@ -91,9 +91,7 @@ { List inputMessage = new List { new OpenAiGptInputMessage("user", input) }; - ChatCompletionObject response = await OpenAiLlmClient.GetChatCompletion(inputMessage); - - return response.Choices[0].Message.Content; + return await OpenAiLlmClient.GetChatCompletion(inputMessage); } private async Task ScrollToElement(string elementId) diff --git a/ChatRPG/Program.cs b/ChatRPG/Program.cs index dfe0d96..d62a981 100644 --- a/ChatRPG/Program.cs +++ b/ChatRPG/Program.cs @@ -24,6 +24,7 @@ var httpMessageHandlerFactory = new HttpMessageHandlerFactory(configuration); builder.Services.AddScoped>() .AddSingleton(_ => httpMessageHandlerFactory.CreateHandler()) + .AddSingleton() .AddSingleton() .AddSingleton() .AddTransient(); From 520c940408cdd0bc813c18dc79aad59007dd785b Mon Sep 17 00:00:00 2001 From: mirakst Date: Wed, 8 Nov 2023 10:30:29 +0100 Subject: [PATCH 02/12] Improved streaming, general stability and readability of Campaign page code --- ChatRPG/API/HttpClientFactory.cs | 8 +- ChatRPG/API/IOpenAiLlmClient.cs | 9 ++- ChatRPG/API/OpenAiLlmClient.cs | 28 ++++++- ChatRPG/ChatRPG.csproj | 15 ++++ ChatRPG/OpenAiGptMessageComponent.razor | 24 ++++++ ChatRPG/Pages/ApiAccess.razor | 3 +- ChatRPG/Pages/Campaign.razor | 100 ++++++++++++++++-------- ChatRPG/Program.cs | 2 +- ChatRPG/appsettings.json | 3 +- 9 files changed, 147 insertions(+), 45 deletions(-) create mode 100644 ChatRPG/OpenAiGptMessageComponent.razor diff --git a/ChatRPG/API/HttpClientFactory.cs b/ChatRPG/API/HttpClientFactory.cs index 16bbe5a..90b5556 100644 --- a/ChatRPG/API/HttpClientFactory.cs +++ b/ChatRPG/API/HttpClientFactory.cs @@ -2,15 +2,15 @@ public class HttpClientFactory : IHttpClientFactory { - private HttpMessageHandler _httpMessageHandler; + private HttpMessageHandlerFactory _httpMessageHandlerFactory; - public HttpClientFactory(HttpMessageHandler httpMessageHandler) + public HttpClientFactory(HttpMessageHandlerFactory httpMessageHandlerFactory) { - _httpMessageHandler = httpMessageHandler; + _httpMessageHandlerFactory = httpMessageHandlerFactory; } public HttpClient CreateClient(string name) { - return new HttpClient(_httpMessageHandler); + return new HttpClient(_httpMessageHandlerFactory.CreateHandler()); } } diff --git a/ChatRPG/API/IOpenAiLlmClient.cs b/ChatRPG/API/IOpenAiLlmClient.cs index e6e77f6..d84b217 100644 --- a/ChatRPG/API/IOpenAiLlmClient.cs +++ b/ChatRPG/API/IOpenAiLlmClient.cs @@ -2,7 +2,10 @@ namespace ChatRPG.API; public interface IOpenAiLlmClient { - Task GetChatCompletion(List inputs); + Task GetChatCompletion(OpenAiGptMessage input); + Task GetChatCompletion(List inputs); + IAsyncEnumerable GetStreamedChatCompletion(OpenAiGptMessage input); + IAsyncEnumerable GetStreamedChatCompletion(List inputs); } public record ChatCompletionObject(string Id, string Object, int Created, string Model, Choice[] Choices, Usage Usage); @@ -13,6 +16,6 @@ public record Message(string Role, string Content); public record Usage(int PromptTokens, int CompletionTokens, int TotalTokens); -public record OpenAiGptInputMessage(string Role, string Content); +public record OpenAiGptMessage(string Role, string Content); -public record OpenAiGptInput(string Model, List Messages, double Temperature); +public record OpenAiGptInput(string Model, List Messages, double Temperature); diff --git a/ChatRPG/API/OpenAiLlmClient.cs b/ChatRPG/API/OpenAiLlmClient.cs index 9a82a36..e8ed0d3 100644 --- a/ChatRPG/API/OpenAiLlmClient.cs +++ b/ChatRPG/API/OpenAiLlmClient.cs @@ -19,8 +19,14 @@ public OpenAiLlmClient(IConfiguration configuration, IHttpClientFactory httpClie _openAiApi.Chat.DefaultChatRequestArgs.Model = Model; _openAiApi.Chat.DefaultChatRequestArgs.Temperature = Temperature; } + + public async Task GetChatCompletion(OpenAiGptMessage input) + { + var inputList = new List {input}; + return await GetChatCompletion(inputList); + } - public async Task GetChatCompletion(List inputs) + public async Task GetChatCompletion(List inputs) { if (inputs.IsNullOrEmpty()) throw new ArgumentNullException(nameof(inputs)); @@ -33,4 +39,24 @@ public async Task GetChatCompletion(List inputs) return await chat.GetResponseFromChatbotAsync(); } + + public IAsyncEnumerable GetStreamedChatCompletion(OpenAiGptMessage input) + { + var inputList = new List {input}; + return GetStreamedChatCompletion(inputList); + } + + public IAsyncEnumerable GetStreamedChatCompletion(List inputs) + { + if (inputs.IsNullOrEmpty()) throw new ArgumentNullException(nameof(inputs)); + + var chat = _openAiApi.Chat.CreateConversation(); + _openAiApi.HttpClientFactory = _httpClientFactory; + foreach (var openAiGptInputMessage in inputs) + { + chat.AppendMessage(ChatMessageRole.FromString(openAiGptInputMessage.Role), openAiGptInputMessage.Content); + } + + return chat.StreamResponseEnumerableFromChatbotAsync(); + } } diff --git a/ChatRPG/ChatRPG.csproj b/ChatRPG/ChatRPG.csproj index 7a7185e..72c8968 100644 --- a/ChatRPG/ChatRPG.csproj +++ b/ChatRPG/ChatRPG.csproj @@ -24,8 +24,23 @@ + + + <_ContentIncludedByDefault Remove="ChatRPG\bin\Debug\net7.0\appsettings.Development.json" /> + <_ContentIncludedByDefault Remove="ChatRPG\bin\Debug\net7.0\appsettings.json" /> + <_ContentIncludedByDefault Remove="ChatRPG\bin\Debug\net7.0\ChatRPG.deps.json" /> + <_ContentIncludedByDefault Remove="ChatRPG\bin\Debug\net7.0\ChatRPG.runtimeconfig.json" /> + <_ContentIncludedByDefault Remove="ChatRPG\bin\Debug\net7.0\ChatRPG.staticwebassets.runtime.json" /> + <_ContentIncludedByDefault Remove="ChatRPG\obj\ChatRPG.csproj.nuget.dgspec.json" /> + <_ContentIncludedByDefault Remove="ChatRPG\obj\Debug\net7.0\staticwebassets.build.json" /> + <_ContentIncludedByDefault Remove="ChatRPG\obj\Debug\net7.0\staticwebassets.development.json" /> + <_ContentIncludedByDefault Remove="ChatRPG\obj\Debug\net7.0\staticwebassets.pack.json" /> + <_ContentIncludedByDefault Remove="ChatRPG\obj\project.assets.json" /> + <_ContentIncludedByDefault Remove="ChatRPG\obj\project.packagespec.json" /> + + diff --git a/ChatRPG/OpenAiGptMessageComponent.razor b/ChatRPG/OpenAiGptMessageComponent.razor new file mode 100644 index 0000000..233129e --- /dev/null +++ b/ChatRPG/OpenAiGptMessageComponent.razor @@ -0,0 +1,24 @@ +@using ChatRPG.API +@inherits ComponentBase + +
+

@MessagePrefix@OpenAiGptMessage.Content

+
+ +@code { + [Parameter] + public OpenAiGptMessage OpenAiGptMessage { get; set; } + + private string MessagePrefix + { + get + { + return OpenAiGptMessage.Role switch + { + "assistant" => "Assistant: ", + "user" => "Player: ", + _ => "" + }; + } + } +} diff --git a/ChatRPG/Pages/ApiAccess.razor b/ChatRPG/Pages/ApiAccess.razor index 09861da..ff02604 100644 --- a/ChatRPG/Pages/ApiAccess.razor +++ b/ChatRPG/Pages/ApiAccess.razor @@ -1,6 +1,7 @@ @page "/ApiAccess" @using ChatRPG.API @using Microsoft.IdentityModel.Tokens +@using OpenAiGptMessage = ChatRPG.API.OpenAiGptMessage @inject ILogger Logger @inject IOpenAiLlmClient OpenAiLlmClient; @inject IFoodWasteClient FoodWasteClient; @@ -60,7 +61,7 @@ private async Task CallOpenAiApiWithInput() { - var input = new List { new ("user", _input) }; + var input = new List { new ("user", _input) }; _openAiResponse = await OpenAiLlmClient.GetChatCompletion(input); } diff --git a/ChatRPG/Pages/Campaign.razor b/ChatRPG/Pages/Campaign.razor index 5e9a1b4..d3450a0 100644 --- a/ChatRPG/Pages/Campaign.razor +++ b/ChatRPG/Pages/Campaign.razor @@ -1,6 +1,8 @@ @page "/Campaign" @using ChatRPG.Data @using ChatRPG.API +@using Microsoft.IdentityModel.Tokens +@using OpenAiGptMessage = ChatRPG.API.OpenAiGptMessage @inject IConfiguration Configuration @inject IOpenAiLlmClient OpenAiLlmClient; @inject IJSRuntime JsRuntime @@ -14,11 +16,14 @@
- @foreach (string message in _conversation) + @foreach (OpenAiGptMessage message in _conversation) { - string prefix = message.StartsWith("Player: ") ? "user" : "assistant"; -
-

@message

+ + } + @if (!_tempMessage.IsNullOrEmpty()) + { +
+

Assistant: @_tempMessage

}
@@ -27,9 +32,9 @@
+ @bind="_userInput" placeholder="What do you do?" @onkeyup="@EnterKeyHandler" disabled="@_isWaitingForResponse"/>
-
@@ -43,16 +48,23 @@ private bool _shouldSave; private IJSObjectReference? _scrollJsScript; private FileUtility? _fileUtil; - readonly List _conversation = new List(); + readonly List _conversation = new(); private string _userInput = ""; + private string _tempMessage = ""; + private bool _shouldStream; + private bool _isWaitingForResponse; - protected override async Task OnInitializedAsync() + protected override async Task OnAfterRenderAsync(bool firstRender) { - AuthenticationState authenticationState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); - _loggedInUsername = authenticationState.User.Identity?.Name; - if (_loggedInUsername != null) _fileUtil = new FileUtility(_loggedInUsername); - _shouldSave = Configuration.GetValue("SaveConversationsToFile"); - _scrollJsScript = await JsRuntime.InvokeAsync("import", "./js/scroll.js"); + if (firstRender) + { + AuthenticationState authenticationState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + _loggedInUsername = authenticationState.User.Identity?.Name; + if (_loggedInUsername != null) _fileUtil = new FileUtility(_loggedInUsername); + _shouldSave = Configuration.GetValue("SaveConversationsToFile"); + _scrollJsScript ??= await JsRuntime.InvokeAsync("import", "./js/scroll.js"); + } + _shouldStream = !Configuration.GetValue("UseMocks") && Configuration.GetValue("StreamChatCompletions"); } async Task EnterKeyHandler(KeyboardEventArgs e) @@ -65,33 +77,53 @@ private async Task SendPrompt() { - if (!string.IsNullOrWhiteSpace(_userInput)) + if (string.IsNullOrWhiteSpace(_userInput)) { - // Use the user input to generate a response - _conversation.Add($"Player: {_userInput}"); - string response = await ProcessUserInput(_userInput); - _conversation.Add($"Assistant: {response}"); - if (_shouldSave && _fileUtil != null) - { - await _fileUtil.UpdateSaveFileAsync(new MessagePair(_userInput, response)); - } + return; + } + _isWaitingForResponse = true; - // Clear the user input - _userInput = ""; + var userInput = new OpenAiGptMessage("user", _userInput); + _conversation.Add(userInput); + + if (!_shouldStream) + { + string response = await OpenAiLlmClient.GetChatCompletion(userInput); + HandleResponse(response); + } + else + { + await HandleStreamedResponse(OpenAiLlmClient.GetStreamedChatCompletion(userInput)); + } + + if (_shouldSave && _fileUtil != null) + { + string assistantOutput = _conversation.Last().Content; + await _fileUtil.UpdateSaveFileAsync(new MessagePair(_userInput, assistantOutput)); + } - // Notify the component to re-render because of async methods + _userInput = ""; + StateHasChanged(); + await ScrollToElement("bottom-id"); + _isWaitingForResponse = false; + } + + private void HandleResponse(string response) + { + var assistantOutput = new OpenAiGptMessage("assistant", response); + _conversation.Add(assistantOutput); + } + + private async Task HandleStreamedResponse(IAsyncEnumerable streamedResponse) + { + await foreach (var res in streamedResponse) + { + _tempMessage += res; StateHasChanged(); - - // Scroll to bottom of page await ScrollToElement("bottom-id"); } - } - - async Task ProcessUserInput(string input) - { - List inputMessage = new List { new OpenAiGptInputMessage("user", input) }; - - return await OpenAiLlmClient.GetChatCompletion(inputMessage); + HandleResponse(_tempMessage); + _tempMessage = ""; } private async Task ScrollToElement(string elementId) diff --git a/ChatRPG/Program.cs b/ChatRPG/Program.cs index d62a981..ebd8236 100644 --- a/ChatRPG/Program.cs +++ b/ChatRPG/Program.cs @@ -23,7 +23,7 @@ var httpMessageHandlerFactory = new HttpMessageHandlerFactory(configuration); builder.Services.AddScoped>() - .AddSingleton(_ => httpMessageHandlerFactory.CreateHandler()) + .AddSingleton(httpMessageHandlerFactory) .AddSingleton() .AddSingleton() .AddSingleton() diff --git a/ChatRPG/appsettings.json b/ChatRPG/appsettings.json index 8047ed5..5b2a4da 100644 --- a/ChatRPG/appsettings.json +++ b/ChatRPG/appsettings.json @@ -9,5 +9,6 @@ } }, "AllowedHosts": "*", - "SaveConversationsToFile": true + "SaveConversationsToFile": true, + "StreamChatCompletions": true } From 4ecd403ee6ace7a98f394c9267e02d4cbb9dceef Mon Sep 17 00:00:00 2001 From: mirakst Date: Wed, 8 Nov 2023 10:39:45 +0100 Subject: [PATCH 03/12] Moved scroll script invocation into after render method, the rest in oninit --- ChatRPG/Pages/Campaign.razor | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ChatRPG/Pages/Campaign.razor b/ChatRPG/Pages/Campaign.razor index d3450a0..6f06d21 100644 --- a/ChatRPG/Pages/Campaign.razor +++ b/ChatRPG/Pages/Campaign.razor @@ -54,17 +54,21 @@ private bool _shouldStream; private bool _isWaitingForResponse; + protected override async Task OnInitializedAsync() + { + AuthenticationState authenticationState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); + _loggedInUsername = authenticationState.User.Identity?.Name; + if (_loggedInUsername != null) _fileUtil = new FileUtility(_loggedInUsername); + _shouldSave = Configuration.GetValue("SaveConversationsToFile"); + _shouldStream = !Configuration.GetValue("UseMocks") && Configuration.GetValue("StreamChatCompletions"); + } + protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { - AuthenticationState authenticationState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); - _loggedInUsername = authenticationState.User.Identity?.Name; - if (_loggedInUsername != null) _fileUtil = new FileUtility(_loggedInUsername); - _shouldSave = Configuration.GetValue("SaveConversationsToFile"); _scrollJsScript ??= await JsRuntime.InvokeAsync("import", "./js/scroll.js"); } - _shouldStream = !Configuration.GetValue("UseMocks") && Configuration.GetValue("StreamChatCompletions"); } async Task EnterKeyHandler(KeyboardEventArgs e) From dd4851407ae8bab038debdeaaacc784836b8ae00 Mon Sep 17 00:00:00 2001 From: mirakst Date: Wed, 8 Nov 2023 10:53:04 +0100 Subject: [PATCH 04/12] nuked spaces --- ChatRPG/API/HttpClientFactory.cs | 4 ++-- ChatRPG/API/OpenAiLlmClient.cs | 10 +++++----- ChatRPG/Pages/Campaign.razor | 8 ++++---- ChatRPG/{ => Pages}/OpenAiGptMessageComponent.razor | 0 4 files changed, 11 insertions(+), 11 deletions(-) rename ChatRPG/{ => Pages}/OpenAiGptMessageComponent.razor (100%) diff --git a/ChatRPG/API/HttpClientFactory.cs b/ChatRPG/API/HttpClientFactory.cs index 90b5556..dc55acb 100644 --- a/ChatRPG/API/HttpClientFactory.cs +++ b/ChatRPG/API/HttpClientFactory.cs @@ -3,12 +3,12 @@ public class HttpClientFactory : IHttpClientFactory { private HttpMessageHandlerFactory _httpMessageHandlerFactory; - + public HttpClientFactory(HttpMessageHandlerFactory httpMessageHandlerFactory) { _httpMessageHandlerFactory = httpMessageHandlerFactory; } - + public HttpClient CreateClient(string name) { return new HttpClient(_httpMessageHandlerFactory.CreateHandler()); diff --git a/ChatRPG/API/OpenAiLlmClient.cs b/ChatRPG/API/OpenAiLlmClient.cs index e8ed0d3..bfa6dec 100644 --- a/ChatRPG/API/OpenAiLlmClient.cs +++ b/ChatRPG/API/OpenAiLlmClient.cs @@ -8,7 +8,7 @@ public class OpenAiLlmClient : IOpenAiLlmClient { private const string Model = "gpt-3.5-turbo"; private const double Temperature = 0.7; - + private readonly IHttpClientFactory _httpClientFactory; private readonly OpenAIAPI _openAiApi; @@ -19,7 +19,7 @@ public OpenAiLlmClient(IConfiguration configuration, IHttpClientFactory httpClie _openAiApi.Chat.DefaultChatRequestArgs.Model = Model; _openAiApi.Chat.DefaultChatRequestArgs.Temperature = Temperature; } - + public async Task GetChatCompletion(OpenAiGptMessage input) { var inputList = new List {input}; @@ -29,14 +29,14 @@ public async Task GetChatCompletion(OpenAiGptMessage input) public async Task GetChatCompletion(List inputs) { if (inputs.IsNullOrEmpty()) throw new ArgumentNullException(nameof(inputs)); - + var chat = _openAiApi.Chat.CreateConversation(); _openAiApi.HttpClientFactory = _httpClientFactory; foreach (var openAiGptInputMessage in inputs) { chat.AppendMessage(ChatMessageRole.FromString(openAiGptInputMessage.Role), openAiGptInputMessage.Content); } - + return await chat.GetResponseFromChatbotAsync(); } @@ -49,7 +49,7 @@ public IAsyncEnumerable GetStreamedChatCompletion(OpenAiGptMessage input public IAsyncEnumerable GetStreamedChatCompletion(List inputs) { if (inputs.IsNullOrEmpty()) throw new ArgumentNullException(nameof(inputs)); - + var chat = _openAiApi.Chat.CreateConversation(); _openAiApi.HttpClientFactory = _httpClientFactory; foreach (var openAiGptInputMessage in inputs) diff --git a/ChatRPG/Pages/Campaign.razor b/ChatRPG/Pages/Campaign.razor index 6f06d21..50a7a57 100644 --- a/ChatRPG/Pages/Campaign.razor +++ b/ChatRPG/Pages/Campaign.razor @@ -89,7 +89,7 @@ var userInput = new OpenAiGptMessage("user", _userInput); _conversation.Add(userInput); - + if (!_shouldStream) { string response = await OpenAiLlmClient.GetChatCompletion(userInput); @@ -99,7 +99,7 @@ { await HandleStreamedResponse(OpenAiLlmClient.GetStreamedChatCompletion(userInput)); } - + if (_shouldSave && _fileUtil != null) { string assistantOutput = _conversation.Last().Content; @@ -111,13 +111,13 @@ await ScrollToElement("bottom-id"); _isWaitingForResponse = false; } - + private void HandleResponse(string response) { var assistantOutput = new OpenAiGptMessage("assistant", response); _conversation.Add(assistantOutput); } - + private async Task HandleStreamedResponse(IAsyncEnumerable streamedResponse) { await foreach (var res in streamedResponse) diff --git a/ChatRPG/OpenAiGptMessageComponent.razor b/ChatRPG/Pages/OpenAiGptMessageComponent.razor similarity index 100% rename from ChatRPG/OpenAiGptMessageComponent.razor rename to ChatRPG/Pages/OpenAiGptMessageComponent.razor From 6cd58684024dff4ea40fa4ccd3a38a681c322d08 Mon Sep 17 00:00:00 2001 From: mirakst Date: Wed, 8 Nov 2023 11:11:27 +0100 Subject: [PATCH 05/12] last linter changes surely --- ChatRPG/API/OpenAiLlmClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ChatRPG/API/OpenAiLlmClient.cs b/ChatRPG/API/OpenAiLlmClient.cs index bfa6dec..eb8406a 100644 --- a/ChatRPG/API/OpenAiLlmClient.cs +++ b/ChatRPG/API/OpenAiLlmClient.cs @@ -22,7 +22,7 @@ public OpenAiLlmClient(IConfiguration configuration, IHttpClientFactory httpClie public async Task GetChatCompletion(OpenAiGptMessage input) { - var inputList = new List {input}; + var inputList = new List { input }; return await GetChatCompletion(inputList); } @@ -42,7 +42,7 @@ public async Task GetChatCompletion(List inputs) public IAsyncEnumerable GetStreamedChatCompletion(OpenAiGptMessage input) { - var inputList = new List {input}; + var inputList = new List { input }; return GetStreamedChatCompletion(inputList); } From 273e2dc2525dc4a2676d3ec7cb88d9e34907558b Mon Sep 17 00:00:00 2001 From: mirakst Date: Wed, 8 Nov 2023 11:23:09 +0100 Subject: [PATCH 06/12] Nuked ApiAccess page and salling API client --- ChatRPG/API/IFoodWasteClient.cs | 21 --------- ChatRPG/API/SallingClient.cs | 55 ----------------------- ChatRPG/Pages/ApiAccess.razor | 78 --------------------------------- ChatRPG/Program.cs | 1 - 4 files changed, 155 deletions(-) delete mode 100644 ChatRPG/API/IFoodWasteClient.cs delete mode 100644 ChatRPG/API/SallingClient.cs delete mode 100644 ChatRPG/Pages/ApiAccess.razor diff --git a/ChatRPG/API/IFoodWasteClient.cs b/ChatRPG/API/IFoodWasteClient.cs deleted file mode 100644 index 9e9b6c7..0000000 --- a/ChatRPG/API/IFoodWasteClient.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace ChatRPG.API; - -public interface IFoodWasteClient -{ - Task> GetFoodwasteResponse(string zip); -} - -public record Offer(string Currency, double Discount, string Ean, DateTime EndTime, DateTime LastUpdate, - double NewPrice, double OriginalPrice, double PercentDiscount, DateTime StartTime, double Stock, string StockUnit); - -public record Product(string Description, string Ean, string Image); - -public record Clearance(Offer Offer, Product Product); - -public record Address(string City, string Country, string Extra, string Street, string Zip); - -public record Hour(DateOnly Date, string Type, DateTime Open, string Close, bool Closed, List CustomerFlow); - -public record Store(Address Address, string Brand, List Coordinates, List Hours, string Name, string Id, string Type); - -public record FoodWasteResponse(List Clearances, Store Store); diff --git a/ChatRPG/API/SallingClient.cs b/ChatRPG/API/SallingClient.cs deleted file mode 100644 index 824a6f4..0000000 --- a/ChatRPG/API/SallingClient.cs +++ /dev/null @@ -1,55 +0,0 @@ -using RestSharp; -using RestSharp.Authenticators; - -namespace ChatRPG.API; - -public class SallingClient : IFoodWasteClient, IDisposable -{ - private const string SallingBaseUrl = "https://api.sallinggroup.com/v1/"; - private readonly ILogger _logger; - private readonly RestClient _client; - - public SallingClient(ILogger logger, IConfiguration configuration) - { - _logger = logger; - - var options = new RestClientOptions(SallingBaseUrl) - { - Authenticator = new JwtAuthenticator(configuration.GetSection("ApiKeys").GetValue("Salling")), - FailOnDeserializationError = false - }; - _client = new RestClient(options); - } - - public async Task> GetFoodwasteResponse(string zip) - { - var request = new RestRequest("food-waste/", Method.Get); - request.AddQueryParameter("zip", zip); - - _logger.LogInformation(""" - Request URL: {Url} - Method: {Method} - Parameters: {Parameters} - """, - SallingBaseUrl + request.Resource, - request.Method, - string.Join(", ", request.Parameters.Select(p => $"{p.Name}={p.Value}")) - ); - - - var response = await _client.ExecuteAsync>(request); - - if (response.ErrorException != null) - { - _logger.LogError($"Error retrieving data from API: {response.ErrorException.Message}"); - } - - return response!.Data; - } - - public void Dispose() - { - _client.Dispose(); - GC.SuppressFinalize(this); - } -} diff --git a/ChatRPG/Pages/ApiAccess.razor b/ChatRPG/Pages/ApiAccess.razor deleted file mode 100644 index ff02604..0000000 --- a/ChatRPG/Pages/ApiAccess.razor +++ /dev/null @@ -1,78 +0,0 @@ -@page "/ApiAccess" -@using ChatRPG.API -@using Microsoft.IdentityModel.Tokens -@using OpenAiGptMessage = ChatRPG.API.OpenAiGptMessage -@inject ILogger Logger -@inject IOpenAiLlmClient OpenAiLlmClient; -@inject IFoodWasteClient FoodWasteClient; - -

ApiAccess

- -

OpenAI GPT 3.5 Turbo

-
- -
-
- - -@if (_openAiResponse is not null) -{ -

Response: @_openAiResponse

-} - -


- -

Salling Food-waste

-
- -
-
- - -@if (_foodWasteResponses is not null) -{ -

Stores in @_zip with food waste information:

- - @foreach (var item in _foodWasteResponses) - { -
-

@item.Store.Name, @item.Store.Address.Street

- @foreach (var clearance in item.Clearances) - { -

Produkt: @clearance.Product.Description

-
Tilbudspris: @clearance.Offer.NewPrice, Originalpris: @clearance.Offer.OriginalPrice
- -
- } -
-
- } -} -@code { - private string _input; - private string _openAiResponse; - private string _zip; - private List _foodWasteResponses; - - private async Task OpenAiInput(Microsoft.AspNetCore.Components.ChangeEventArgs patharg) - { - _input = (string)patharg.Value; - } - - private async Task CallOpenAiApiWithInput() - { - var input = new List { new ("user", _input) }; - - _openAiResponse = await OpenAiLlmClient.GetChatCompletion(input); - } - - private async Task SallingInput(Microsoft.AspNetCore.Components.ChangeEventArgs patharg) - { - _zip = (string)patharg.Value; - } - - private async Task CallFoodWasteApiWithZip() - { - _foodWasteResponses = await FoodWasteClient.GetFoodwasteResponse(_zip); - } -} diff --git a/ChatRPG/Program.cs b/ChatRPG/Program.cs index ebd8236..8931ebe 100644 --- a/ChatRPG/Program.cs +++ b/ChatRPG/Program.cs @@ -26,7 +26,6 @@ .AddSingleton(httpMessageHandlerFactory) .AddSingleton() .AddSingleton() - .AddSingleton() .AddTransient(); builder.Services.Configure(options => From a5f58a1a328a54e5ea470a25d5c362196dbd9958 Mon Sep 17 00:00:00 2001 From: mirakst Date: Wed, 8 Nov 2023 11:26:28 +0100 Subject: [PATCH 07/12] Removed navmenu ref to api access --- ChatRPG/Shared/NavMenu.razor | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ChatRPG/Shared/NavMenu.razor b/ChatRPG/Shared/NavMenu.razor index af2e379..9307910 100644 --- a/ChatRPG/Shared/NavMenu.razor +++ b/ChatRPG/Shared/NavMenu.razor @@ -27,11 +27,6 @@ Campaign
- Adventure icons created by Freepik - Flaticon From 7b4092bc61b5df4399def89f91918d6c1e30b6a1 Mon Sep 17 00:00:00 2001 From: mirakst Date: Thu, 9 Nov 2023 09:21:06 +0100 Subject: [PATCH 08/12] General cleanup (vars included @KarmaKamikaze @Sarmisuper) --- ChatRPG/API/HttpMessageHandlerFactory.cs | 2 +- ChatRPG/API/OpenAiLlmClient.cs | 22 +++++++++---------- .../Identity/Pages/Account/LogOut.cshtml | 1 - .../Areas/Identity/Pages/Account/Login.cshtml | 3 ++- .../Identity/Pages/Account/Login.cshtml.cs | 10 ++------- .../Identity/Pages/Account/Register.cshtml | 3 ++- .../Identity/Pages/Account/Register.cshtml.cs | 19 +++++----------- .../Pages/Shared/_LoginPartial.cshtml | 4 +--- ...tingIdentityAuthenticationStateProvider.cs | 20 ++++++++--------- ChatRPG/ChatRPG.csproj | 1 - ChatRPG/Data/Models/Character.cs | 2 +- ChatRPG/Pages/Campaign.razor | 2 +- ChatRPG/Pages/OpenAiGptMessageComponent.razor | 2 +- ChatRPG/Pages/Shared/_Layout.cshtml | 4 ++-- ChatRPG/Pages/Shared/_LoginPartial.cshtml | 1 - ChatRPG/Program.cs | 18 +++++++-------- ChatRPG/Services/EfPersisterService.cs | 3 ++- 17 files changed, 50 insertions(+), 67 deletions(-) diff --git a/ChatRPG/API/HttpMessageHandlerFactory.cs b/ChatRPG/API/HttpMessageHandlerFactory.cs index ee8d8af..5cf82ec 100644 --- a/ChatRPG/API/HttpMessageHandlerFactory.cs +++ b/ChatRPG/API/HttpMessageHandlerFactory.cs @@ -28,7 +28,7 @@ public HttpMessageHandler CreateHandler(string name) private static HttpResponseMessage GenerateMockResponse(HttpRequestMessage request) { Console.Write("Please enter mocked API response: "); - var input = Console.ReadLine(); + string input = Console.ReadLine(); var responseContent = new StringContent($$""" { "id": "chatcmpl-000", diff --git a/ChatRPG/API/OpenAiLlmClient.cs b/ChatRPG/API/OpenAiLlmClient.cs index eb8406a..30126a7 100644 --- a/ChatRPG/API/OpenAiLlmClient.cs +++ b/ChatRPG/API/OpenAiLlmClient.cs @@ -28,14 +28,7 @@ public async Task GetChatCompletion(OpenAiGptMessage input) public async Task GetChatCompletion(List inputs) { - if (inputs.IsNullOrEmpty()) throw new ArgumentNullException(nameof(inputs)); - - var chat = _openAiApi.Chat.CreateConversation(); - _openAiApi.HttpClientFactory = _httpClientFactory; - foreach (var openAiGptInputMessage in inputs) - { - chat.AppendMessage(ChatMessageRole.FromString(openAiGptInputMessage.Role), openAiGptInputMessage.Content); - } + Conversation chat = CreateConversation(inputs); return await chat.GetResponseFromChatbotAsync(); } @@ -47,16 +40,23 @@ public IAsyncEnumerable GetStreamedChatCompletion(OpenAiGptMessage input } public IAsyncEnumerable GetStreamedChatCompletion(List inputs) + { + Conversation chat = CreateConversation(inputs); + + return chat.StreamResponseEnumerableFromChatbotAsync(); + } + + private Conversation CreateConversation(List inputs) { if (inputs.IsNullOrEmpty()) throw new ArgumentNullException(nameof(inputs)); - var chat = _openAiApi.Chat.CreateConversation(); + Conversation chat = _openAiApi.Chat.CreateConversation(); _openAiApi.HttpClientFactory = _httpClientFactory; - foreach (var openAiGptInputMessage in inputs) + foreach (OpenAiGptMessage openAiGptInputMessage in inputs) { chat.AppendMessage(ChatMessageRole.FromString(openAiGptInputMessage.Role), openAiGptInputMessage.Content); } - return chat.StreamResponseEnumerableFromChatbotAsync(); + return chat; } } diff --git a/ChatRPG/Areas/Identity/Pages/Account/LogOut.cshtml b/ChatRPG/Areas/Identity/Pages/Account/LogOut.cshtml index 21b7fe2..0addf87 100644 --- a/ChatRPG/Areas/Identity/Pages/Account/LogOut.cshtml +++ b/ChatRPG/Areas/Identity/Pages/Account/LogOut.cshtml @@ -1,5 +1,4 @@ @page -@using Microsoft.AspNetCore.Identity @attribute [IgnoreAntiforgeryToken] @inject SignInManager SignInManager @functions { diff --git a/ChatRPG/Areas/Identity/Pages/Account/Login.cshtml b/ChatRPG/Areas/Identity/Pages/Account/Login.cshtml index 6354164..6946e7c 100644 --- a/ChatRPG/Areas/Identity/Pages/Account/Login.cshtml +++ b/ChatRPG/Areas/Identity/Pages/Account/Login.cshtml @@ -1,4 +1,5 @@ @page +@using Microsoft.AspNetCore.Authentication @model LoginModel @{ @@ -65,7 +66,7 @@

- @foreach (var provider in Model.ExternalLogins!) + @foreach (AuthenticationScheme provider in Model.ExternalLogins!) { } diff --git a/ChatRPG/Areas/Identity/Pages/Account/Login.cshtml.cs b/ChatRPG/Areas/Identity/Pages/Account/Login.cshtml.cs index 2034743..c22bcd0 100644 --- a/ChatRPG/Areas/Identity/Pages/Account/Login.cshtml.cs +++ b/ChatRPG/Areas/Identity/Pages/Account/Login.cshtml.cs @@ -2,19 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. #nullable disable -using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; using ChatRPG.Data.Models; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.Extensions.Logging; +using SignInResult = Microsoft.AspNetCore.Identity.SignInResult; namespace ChatRPG.Areas.Identity.Pages.Account { @@ -112,7 +106,7 @@ public async Task OnPostAsync(string returnUrl = null) { // This doesn't count login failures towards account lockout // To enable password failures to trigger account lockout, set lockoutOnFailure: true - var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false); + SignInResult result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false); if (result.Succeeded) { _logger.LogInformation("User logged in."); diff --git a/ChatRPG/Areas/Identity/Pages/Account/Register.cshtml b/ChatRPG/Areas/Identity/Pages/Account/Register.cshtml index 6fbfc8f..0fdd818 100644 --- a/ChatRPG/Areas/Identity/Pages/Account/Register.cshtml +++ b/ChatRPG/Areas/Identity/Pages/Account/Register.cshtml @@ -1,4 +1,5 @@ @page +@using Microsoft.AspNetCore.Authentication @model RegisterModel @{ ViewData["Title"] = "Register"; @@ -49,7 +50,7 @@

- @foreach (var provider in Model.ExternalLogins!) + @foreach (AuthenticationScheme provider in Model.ExternalLogins!) { } diff --git a/ChatRPG/Areas/Identity/Pages/Account/Register.cshtml.cs b/ChatRPG/Areas/Identity/Pages/Account/Register.cshtml.cs index 1406bcc..e7539d6 100644 --- a/ChatRPG/Areas/Identity/Pages/Account/Register.cshtml.cs +++ b/ChatRPG/Areas/Identity/Pages/Account/Register.cshtml.cs @@ -2,23 +2,16 @@ // The .NET Foundation licenses this file to you under the MIT license. #nullable disable -using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; using System.Text; using System.Text.Encodings.Web; -using System.Threading; -using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authorization; using ChatRPG.Data.Models; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.WebUtilities; -using Microsoft.Extensions.Logging; namespace ChatRPG.Areas.Identity.Pages.Account { @@ -113,20 +106,20 @@ public async Task OnPostAsync(string returnUrl = null) ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); if (ModelState.IsValid) { - var user = CreateUser(); + User user = CreateUser(); await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); - var result = await _userManager.CreateAsync(user, Input.Password); + IdentityResult result = await _userManager.CreateAsync(user, Input.Password); if (result.Succeeded) { _logger.LogInformation("User created a new account with password."); - var userId = await _userManager.GetUserIdAsync(user); - var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + string userId = await _userManager.GetUserIdAsync(user); + string code = await _userManager.GenerateEmailConfirmationTokenAsync(user); code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); - var callbackUrl = Url.Page( + string callbackUrl = Url.Page( "/Account/ConfirmEmail", pageHandler: null, values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl }, @@ -145,7 +138,7 @@ await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", return LocalRedirect(returnUrl); } } - foreach (var error in result.Errors) + foreach (IdentityError error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } diff --git a/ChatRPG/Areas/Identity/Pages/Shared/_LoginPartial.cshtml b/ChatRPG/Areas/Identity/Pages/Shared/_LoginPartial.cshtml index 2fc6411..d71541d 100644 --- a/ChatRPG/Areas/Identity/Pages/Shared/_LoginPartial.cshtml +++ b/ChatRPG/Areas/Identity/Pages/Shared/_LoginPartial.cshtml @@ -1,6 +1,4 @@ -@using ChatRPG.Data.Models -@using Microsoft.AspNetCore.Identity -@inject SignInManager SignInManager +@inject SignInManager SignInManager @inject UserManager UserManager @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/ChatRPG/Areas/Identity/RevalidatingIdentityAuthenticationStateProvider.cs b/ChatRPG/Areas/Identity/RevalidatingIdentityAuthenticationStateProvider.cs index 4cfbe76..b869596 100644 --- a/ChatRPG/Areas/Identity/RevalidatingIdentityAuthenticationStateProvider.cs +++ b/ChatRPG/Areas/Identity/RevalidatingIdentityAuthenticationStateProvider.cs @@ -28,10 +28,10 @@ protected override async Task ValidateAuthenticationStateAsync( AuthenticationState authenticationState, CancellationToken cancellationToken) { // Get the user manager from a new scope to ensure it fetches fresh data - var scope = _scopeFactory.CreateScope(); + IServiceScope scope = _scopeFactory.CreateScope(); try { - var userManager = scope.ServiceProvider.GetRequiredService>(); + UserManager userManager = scope.ServiceProvider.GetRequiredService>(); return await ValidateSecurityStampAsync(userManager, authenticationState.User); } finally @@ -47,22 +47,20 @@ protected override async Task ValidateAuthenticationStateAsync( } } - private async Task ValidateSecurityStampAsync(UserManager userManager, ClaimsPrincipal principal) + private async Task ValidateSecurityStampAsync(UserManager userManager, ClaimsPrincipal principal) { - var user = await userManager.GetUserAsync(principal); + TUser? user = await userManager.GetUserAsync(principal); if (user == null) { return false; } - else if (!userManager.SupportsUserSecurityStamp) + if (!userManager.SupportsUserSecurityStamp) { return true; } - else - { - var principalStamp = principal.FindFirstValue(_options.ClaimsIdentity.SecurityStampClaimType); - var userStamp = await userManager.GetSecurityStampAsync(user); - return principalStamp == userStamp; - } + + string? principalStamp = principal.FindFirstValue(_options.ClaimsIdentity.SecurityStampClaimType); + string userStamp = await userManager.GetSecurityStampAsync(user); + return principalStamp == userStamp; } } diff --git a/ChatRPG/ChatRPG.csproj b/ChatRPG/ChatRPG.csproj index 72c8968..f070d68 100644 --- a/ChatRPG/ChatRPG.csproj +++ b/ChatRPG/ChatRPG.csproj @@ -25,7 +25,6 @@ - diff --git a/ChatRPG/Data/Models/Character.cs b/ChatRPG/Data/Models/Character.cs index 574d035..d438b15 100644 --- a/ChatRPG/Data/Models/Character.cs +++ b/ChatRPG/Data/Models/Character.cs @@ -56,7 +56,7 @@ public void AdjustHealth(int value) /// The created entity. public CharacterAbility AddAbility(Ability ability) { - var charAbility = CharacterAbilities.FirstOrDefault(a => a!.Ability == ability, null); + CharacterAbility charAbility = CharacterAbilities.FirstOrDefault(a => a!.Ability == ability, null); if (charAbility is not null) { return charAbility; diff --git a/ChatRPG/Pages/Campaign.razor b/ChatRPG/Pages/Campaign.razor index 50a7a57..838d3b9 100644 --- a/ChatRPG/Pages/Campaign.razor +++ b/ChatRPG/Pages/Campaign.razor @@ -120,7 +120,7 @@ private async Task HandleStreamedResponse(IAsyncEnumerable streamedResponse) { - await foreach (var res in streamedResponse) + await foreach (string res in streamedResponse) { _tempMessage += res; StateHasChanged(); diff --git a/ChatRPG/Pages/OpenAiGptMessageComponent.razor b/ChatRPG/Pages/OpenAiGptMessageComponent.razor index 233129e..ad88bdf 100644 --- a/ChatRPG/Pages/OpenAiGptMessageComponent.razor +++ b/ChatRPG/Pages/OpenAiGptMessageComponent.razor @@ -7,7 +7,7 @@ @code { [Parameter] - public OpenAiGptMessage OpenAiGptMessage { get; set; } + public required OpenAiGptMessage OpenAiGptMessage { get; set; } private string MessagePrefix { diff --git a/ChatRPG/Pages/Shared/_Layout.cshtml b/ChatRPG/Pages/Shared/_Layout.cshtml index 92fcc1a..46212bd 100644 --- a/ChatRPG/Pages/Shared/_Layout.cshtml +++ b/ChatRPG/Pages/Shared/_Layout.cshtml @@ -32,7 +32,7 @@