Skip to content

Commit

Permalink
Merge branch 'dev' into feature/dashboard-page
Browse files Browse the repository at this point in the history
  • Loading branch information
KarmaKamikaze committed Nov 9, 2023
2 parents 54c0e58 + 299b120 commit 51c403f
Show file tree
Hide file tree
Showing 24 changed files with 225 additions and 336 deletions.
16 changes: 16 additions & 0 deletions ChatRPG/API/HttpClientFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace ChatRPG.API;

public class HttpClientFactory : IHttpClientFactory
{
private HttpMessageHandlerFactory _httpMessageHandlerFactory;

public HttpClientFactory(HttpMessageHandlerFactory httpMessageHandlerFactory)
{
_httpMessageHandlerFactory = httpMessageHandlerFactory;
}

public HttpClient CreateClient(string name)
{
return new HttpClient(_httpMessageHandlerFactory.CreateHandler());
}
}
10 changes: 5 additions & 5 deletions ChatRPG/API/HttpMessageHandlerFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ public HttpMessageHandler CreateHandler(string name)
return new HttpClientHandler();
}

var mockHttpMessageHandler = new MockHttpMessageHandler();
mockHttpMessageHandler.When("*")
MockHttpMessageHandler messageHandler = new MockHttpMessageHandler();
messageHandler.When("*")
.Respond(GenerateMockResponse);

return mockHttpMessageHandler;
return messageHandler;
}

private static HttpResponseMessage GenerateMockResponse(HttpRequestMessage request)
{
Console.Write("Please enter mocked API response: ");
var input = Console.ReadLine();
var responseContent = new StringContent($$"""
string? input = Console.ReadLine();
StringContent responseContent = new StringContent($$"""
{
"id": "chatcmpl-000",
"object": "chat.completion",
Expand Down
21 changes: 0 additions & 21 deletions ChatRPG/API/IFoodWasteClient.cs

This file was deleted.

15 changes: 3 additions & 12 deletions ChatRPG/API/IOpenAiLlmClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,8 @@ namespace ChatRPG.API;

public interface IOpenAiLlmClient
{
Task<ChatCompletionObject> GetChatCompletion(List<OpenAiGptInputMessage> inputs);
Task<string> GetChatCompletion(params OpenAiGptMessage[] inputs);
IAsyncEnumerable<string> GetStreamedChatCompletion(params OpenAiGptMessage[] inputs);
}

public record ChatCompletionObject(string Id, string Object, int Created, string Model, Choice[] Choices, Usage Usage);

public record Choice(int Index, Message Message, string FinishReason);

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 OpenAiGptInput(string Model, List<OpenAiGptInputMessage> Messages, double Temperature);
public record OpenAiGptMessage(string Role, string Content);
103 changes: 25 additions & 78 deletions ChatRPG/API/OpenAiLlmClient.cs
Original file line number Diff line number Diff line change
@@ -1,101 +1,48 @@
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 ILogger<OpenAiLlmClient> _logger;
private readonly RestClient _client;
private readonly OpenAIAPI _openAiApi;

public OpenAiLlmClient(ILogger<OpenAiLlmClient> 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<string>("OpenAI") ?? string.Empty),
FailOnDeserializationError = false,
ConfigureMessageHandler = _ => httpMessageHandler
};
_client = new RestClient(options);
_openAiApi = new OpenAIAPI(configuration.GetSection("ApiKeys")?.GetValue<string>("OpenAI"));
_openAiApi.Chat.DefaultChatRequestArgs.Model = Model;
_openAiApi.Chat.DefaultChatRequestArgs.Temperature = Temperature;
_openAiApi.HttpClientFactory = httpClientFactory;
}

public async Task<ChatCompletionObject> GetChatCompletion(List<OpenAiGptInputMessage> inputs)
public async Task<string> GetChatCompletion(params OpenAiGptMessage[] inputs)
{
if (inputs.IsNullOrEmpty()) throw new ArgumentNullException(nameof(inputs));

var openAiGptInput = new OpenAiGptInput(Model, inputs, Temperature);
Conversation chat = CreateConversation(inputs);

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))
);
return await chat.GetResponseFromChatbotAsync();
}

var response = await _client.ExecuteAsync<ChatCompletionObject>(request);
public IAsyncEnumerable<string> GetStreamedChatCompletion(params OpenAiGptMessage[] inputs)
{
Conversation chat = CreateConversation(inputs);

if (response.ErrorException != null)
{
_logger.LogError("Error retrieving data from API: {ErrorExceptionMessage}", response.ErrorException.Message);
}
return chat.StreamResponseEnumerableFromChatbotAsync();
}

var data = response!.Data;
private Conversation CreateConversation(params OpenAiGptMessage[] messages)
{
if (messages.IsNullOrEmpty()) throw new ArgumentNullException(nameof(messages));

if (data is null)
Conversation chat = _openAiApi.Chat.CreateConversation();
foreach (OpenAiGptMessage openAiGptInputMessage in messages)
{
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 chat;
}
}
55 changes: 0 additions & 55 deletions ChatRPG/API/SallingClient.cs

This file was deleted.

1 change: 0 additions & 1 deletion ChatRPG/Areas/Identity/Pages/Account/LogOut.cshtml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
@page
@using Microsoft.AspNetCore.Identity
@attribute [IgnoreAntiforgeryToken]
@inject SignInManager<IdentityUser> SignInManager
@functions {
Expand Down
3 changes: 2 additions & 1 deletion ChatRPG/Areas/Identity/Pages/Account/Login.cshtml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@page
@using Microsoft.AspNetCore.Authentication
@model LoginModel

@{
Expand Down Expand Up @@ -65,7 +66,7 @@
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in Model.ExternalLogins!)
@foreach (AuthenticationScheme provider in Model.ExternalLogins!)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
Expand Down
10 changes: 2 additions & 8 deletions ChatRPG/Areas/Identity/Pages/Account/Login.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -112,7 +106,7 @@ public async Task<IActionResult> 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.");
Expand Down
3 changes: 2 additions & 1 deletion ChatRPG/Areas/Identity/Pages/Account/Register.cshtml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@page
@using Microsoft.AspNetCore.Authentication
@model RegisterModel
@{
ViewData["Title"] = "Register";
Expand Down Expand Up @@ -49,7 +50,7 @@
<form id="external-account" asp-page="./ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" method="post" class="form-horizontal">
<div>
<p>
@foreach (var provider in Model.ExternalLogins!)
@foreach (AuthenticationScheme provider in Model.ExternalLogins!)
{
<button type="submit" class="btn btn-primary" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
}
Expand Down
19 changes: 6 additions & 13 deletions ChatRPG/Areas/Identity/Pages/Account/Register.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -113,20 +106,20 @@ public async Task<IActionResult> 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 },
Expand All @@ -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);
}
Expand Down
4 changes: 1 addition & 3 deletions ChatRPG/Areas/Identity/Pages/Shared/_LoginPartial.cshtml
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
@using ChatRPG.Data.Models
@using Microsoft.AspNetCore.Identity
@inject SignInManager<User> SignInManager
@inject SignInManager<User> SignInManager
@inject UserManager<User> UserManager
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Expand Down
Loading

0 comments on commit 51c403f

Please sign in to comment.