Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Dashboard and finalizing login page #35

Merged
merged 40 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
c3d18b3
Added Authorized Views and big title animation
KarmaKamikaze Nov 7, 2023
33bab15
Use CancelationToken to end thread with cursor
KarmaKamikaze Nov 7, 2023
3a28ad8
Use CancelationToken for typing delay thread
KarmaKamikaze Nov 7, 2023
d1a2a58
Update About section
KarmaKamikaze Nov 8, 2023
df29060
Background images and ensure consistant styling
KarmaKamikaze Nov 8, 2023
8049329
yeet SurveyPrompt
KarmaKamikaze Nov 8, 2023
f324e5b
yeet NavMenu
KarmaKamikaze Nov 8, 2023
ecb804a
Changed Index.razor to Code-Behind approach, adding partial class thr…
KarmaKamikaze Nov 8, 2023
54c0e58
Make btn-send css class so we can standardize primary buttons
KarmaKamikaze Nov 9, 2023
51c403f
Merge branch 'dev' into feature/dashboard-page
KarmaKamikaze Nov 9, 2023
8e79cb5
Add Code-Behind files to Campaign page
KarmaKamikaze Nov 9, 2023
a81a077
Fixed redirect to login and register page by force loading
KarmaKamikaze Nov 9, 2023
3d6d27d
Cool background only on unauthorized views
KarmaKamikaze Nov 9, 2023
7e29ee0
Fix buttons in LoginDisplay
KarmaKamikaze Nov 9, 2023
7e11b3f
Added UserCampaignOverview
damniko Nov 9, 2023
da61e6e
fix merge
damniko Nov 9, 2023
6043718
Fix logout by passing User instead of IdentityUser (thank you Sarmilan)
KarmaKamikaze Nov 9, 2023
e672c92
added persistance he he
damniko Nov 9, 2023
ec7e00b
Merge branch 'feature/dashboard-page' of https://github.com/KarmaKami…
damniko Nov 9, 2023
b2145ec
Cool buttons on the front page to entrice the ladies
KarmaKamikaze Nov 9, 2023
44c655e
Merge branch 'feature/dashboard-page' of https://github.com/KarmaKami…
KarmaKamikaze Nov 9, 2023
026e4ab
Please Linda and fix document-start in Build.yaml
KarmaKamikaze Nov 9, 2023
9e1b2f7
Fix cached title text. Now renders smoothly every time, baby.
KarmaKamikaze Nov 9, 2023
2529069
Document methods in Index
KarmaKamikaze Nov 9, 2023
5c662d2
Document methods in Campaign
KarmaKamikaze Nov 9, 2023
35bad00
Document methods in FileUtility and extract interface
KarmaKamikaze Nov 9, 2023
f23f351
Extract Code-Behind for UserCampaignOverview and change display order…
KarmaKamikaze Nov 10, 2023
43a4ca8
Remove content frame to yeet scrollbar on all pages.
KarmaKamikaze Nov 10, 2023
78f5d78
Adjusted dashboard style for dark background
KarmaKamikaze Nov 10, 2023
684d789
Header and footer especially useful for, you know, logging out
KarmaKamikaze Nov 10, 2023
082c521
Fix sign out option for buttons by adding OnGet method
KarmaKamikaze Nov 11, 2023
8b15702
Added brand logo to Index navbar and fixed styling in css
KarmaKamikaze Nov 11, 2023
2605f68
Moved Navbar to its own component
KarmaKamikaze Nov 11, 2023
74336cd
Add Navbar to campaign page
KarmaKamikaze Nov 11, 2023
16877a1
yeet NavMenu
KarmaKamikaze Nov 11, 2023
440c29d
Removed article html elements since they add unwieldy side-margins
KarmaKamikaze Nov 11, 2023
78b7f86
Finally managed to ensure consistant layout on Campaign page
KarmaKamikaze Nov 11, 2023
2cb9a9c
Add pleasant scrollbar to list of previously played campaigns
KarmaKamikaze Nov 11, 2023
3258e26
Add test project and ChatRPGFixture
KarmaKamikaze Nov 13, 2023
b10f271
Better test case names
KarmaKamikaze Nov 13, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
---
name: Build

on:
Expand Down
6 changes: 6 additions & 0 deletions ChatRPG.sln
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ VisualStudioVersion = 17.5.33627.172
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatRPG", "ChatRPG\ChatRPG.csproj", "{02177064-5C6F-4AD0-A3E2-8FDE212D1B8E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChatRPGTests", "ChatRPGTests\ChatRPGTests.csproj", "{38AA38FA-5260-4499-8448-97F1D962035B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -15,6 +17,10 @@ Global
{02177064-5C6F-4AD0-A3E2-8FDE212D1B8E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{02177064-5C6F-4AD0-A3E2-8FDE212D1B8E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{02177064-5C6F-4AD0-A3E2-8FDE212D1B8E}.Release|Any CPU.Build.0 = Release|Any CPU
{38AA38FA-5260-4499-8448-97F1D962035B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{38AA38FA-5260-4499-8448-97F1D962035B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{38AA38FA-5260-4499-8448-97F1D962035B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{38AA38FA-5260-4499-8448-97F1D962035B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
25 changes: 20 additions & 5 deletions ChatRPG/Areas/Identity/Pages/Account/LogOut.cshtml
Original file line number Diff line number Diff line change
@@ -1,14 +1,29 @@
@page
@page "/Logout"
@attribute [IgnoreAntiforgeryToken]
@inject SignInManager<IdentityUser> SignInManager
@inject SignInManager<User> SignInManager

@functions {

public async Task<IActionResult> OnPost()
{
await SignOut();

return Redirect("~/");
}

public async Task<IActionResult> OnGet()
{
await SignOut();

return Redirect("~/");
}

private async Task SignOut()
{
if (SignInManager.IsSignedIn(User))
{
await SignInManager.SignOutAsync();
}

return Redirect("~/");
}
}

}
2 changes: 1 addition & 1 deletion ChatRPG/Areas/Identity/Pages/Account/Login.cshtml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@page
@page "/Login"
@using Microsoft.AspNetCore.Authentication
@model LoginModel

Expand Down
2 changes: 1 addition & 1 deletion ChatRPG/Areas/Identity/Pages/Account/Register.cshtml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@page
@page "/Register"
@using Microsoft.AspNetCore.Authentication
@model RegisterModel
@{
Expand Down
4 changes: 4 additions & 0 deletions ChatRPG/ChatRPG.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@
<PackageReference Include="RichardSzalay.MockHttp" Version="7.0.0" />
</ItemGroup>

<ItemGroup>
<InternalsVisibleTo Include="ChatRPGTests" />
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion ChatRPG/Data/ApplicationDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.Entity<CharacterAbility>(builder =>
{
builder.HasKey("CharacterId", "AbilityId");
builder.HasOne(c => c.Character).WithMany(c => c.CharacterAbilities);
builder.HasOne(c => c.Character).WithMany(c => c.CharacterAbilities!);
})
.Entity<Event>(builder => builder.Property(x => x.Ordering).HasDefaultValue(1))
;
Expand Down
34 changes: 31 additions & 3 deletions ChatRPG/Data/FileUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

namespace ChatRPG.Data;

public record MessagePair(string PlayerMessage, string AssistantMessage);

public class FileUtility
public class FileUtility : IFileUtility
{
private readonly string _currentUser;
private readonly string _path;
Expand All @@ -14,13 +12,19 @@ public class FileUtility
private readonly string _playerKeyword = "#<Player>: ";
private readonly string _gameKeyword = "#<Game>: ";

/// <summary>
/// Initializes a new instance of the FileUtility class with the specified user and save directory.
/// </summary>
/// <param name="currentUser">The username of the current user.</param>
/// <param name="saveDir">The directory where conversation files are saved (default: "Saves/").</param>
public FileUtility(string currentUser, string saveDir = "Saves/")
{
_currentUser = currentUser;
_saveDir = Path.Join(AppDomain.CurrentDomain.BaseDirectory, saveDir);
_path = SetPath(DateTime.Now);
}

/// <inheritdoc />
public async Task UpdateSaveFileAsync(MessagePair messages)
{
// According to .NET docs, you do not need to check if directory exists first
Expand All @@ -37,12 +41,19 @@ public async Task UpdateSaveFileAsync(MessagePair messages)
await fs.WriteAsync(encodedAssistantMessage, 0, encodedAssistantMessage.Length);
}

/// <inheritdoc />
public async Task<List<string>> GetMostRecentConversationAsync(string playerTag, string assistantTag)
{
string filePath = GetMostRecentFile(Directory.GetFiles(_saveDir, $"{_currentUser}*"));
return await GetConversationsStringFromSaveFileAsync(filePath, playerTag, assistantTag);
}

/// <summary>
/// Prepares a message for saving by ensuring a newline character and specifying the author.
/// </summary>
/// <param name="message">The message to prepare for saving.</param>
/// <param name="isPlayerMessage">A boolean indicating if the message is from the player (default: false).</param>
/// <returns>The prepared message with a newline character and author tag.</returns>
private string PrepareMessageForSave(string message, bool isPlayerMessage = false)
{
if (!message.EndsWith("\n"))
Expand All @@ -52,6 +63,13 @@ private string PrepareMessageForSave(string message, bool isPlayerMessage = fals
return message;
}

/// <summary>
/// Reads a conversation string from a save file asynchronously and converts it into a list of messages.
/// </summary>
/// <param name="path">The path to the save file to read.</param>
/// <param name="playerTag">The tag to mark a player message.</param>
/// <param name="assistantTag">The tag to mark an assistant message.</param>
/// <returns>A list of messages extracted from the save file.</returns>
private async Task<List<string>> GetConversationsStringFromSaveFileAsync(string path, string playerTag,
string assistantTag)
{
Expand All @@ -70,6 +88,11 @@ private async Task<List<string>> GetConversationsStringFromSaveFileAsync(string
return ConvertConversationStringToList(sb.ToString(), playerTag, assistantTag);
}

/// <summary>
/// Gets the most recent file from a list of files based on their timestamps.
/// </summary>
/// <param name="files">An array of file paths to choose from.</param>
/// <returns>The path to the most recent file.</returns>
private string GetMostRecentFile(string[] files)
{
DateTime mostRecent = new DateTime(1, 1, 1, 0, 0, 0); // Hello Jesus
Expand Down Expand Up @@ -130,6 +153,11 @@ private List<string> ConvertConversationStringToList(string conversation, string
return fullConversation;
}

/// <summary>
/// Sets the path for a save file based on the user and a timestamp.
/// </summary>
/// <param name="timestamp">The timestamp used to create the file name.</param>
/// <returns>The complete path to the save file.</returns>
private string SetPath(DateTime timestamp)
{
// Add save directory and file name to path
Expand Down
21 changes: 21 additions & 0 deletions ChatRPG/Data/IFileUtility.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace ChatRPG.Data;

public interface IFileUtility
{
/// <summary>
/// Updates the conversation save file asynchronously with the provided message pair.
/// </summary>
/// <param name="messages">A pair of messages (player and assistant) to save in the file.</param>
/// <returns>A task representing the asynchronous file update process.</returns>
Task UpdateSaveFileAsync(MessagePair messages);
/// <summary>
/// Retrieves the most recent conversation from a save file asynchronously, parsing and inserting
/// into messages the player and assistant tags.
/// </summary>
/// <param name="playerTag">The tag to mark a player message.</param>
/// <param name="assistantTag">The tag to mark an assistant message.</param>
/// <returns>A list of messages representing the most recent conversation.</returns>
Task<List<string>> GetMostRecentConversationAsync(string playerTag, string assistantTag);
}

public record MessagePair(string PlayerMessage, string AssistantMessage);
4 changes: 2 additions & 2 deletions ChatRPG/Data/Models/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ namespace ChatRPG.Data.Models;

public class User : IdentityUser
{
private User()
public User()
{
}

public User(string username) : base(username)
{
}

public ICollection<Campaign> Campaigns { get; } = new List<Campaign>();
public virtual ICollection<Campaign> Campaigns { get; } = new List<Campaign>();
}
110 changes: 9 additions & 101 deletions ChatRPG/Pages/Campaign.razor
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
@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
@inject AuthenticationStateProvider AuthenticationStateProvider

<PageTitle>Campaign</PageTitle>

<div class="text-center mx-auto">
<Navbar Username="@_loggedInUsername"/>

<div class="text-center campaign-title">
<h1>Campaign</h1>
</div>

Expand All @@ -34,7 +30,7 @@
<input type="text" style="resize:vertical;" class="form-control user-prompt custom-text-field"
@bind="_userInput" placeholder="What do you do?" @onkeyup="@EnterKeyHandler" disabled="@_isWaitingForResponse"/>
<div class="input-group-append">
<button class="btn btn-primary ml-2" type="button" @onclick="SendPrompt" disabled="@_isWaitingForResponse">
<button class="btn btn-send ml-2" type="button" @onclick="SendPrompt" disabled="@_isWaitingForResponse">
Send
</button>
</div>
Expand All @@ -43,96 +39,8 @@

<span id="bottom-id"></span>

@code {
private string? _loggedInUsername;
private bool _shouldSave;
private IJSObjectReference? _scrollJsScript;
private FileUtility? _fileUtil;
readonly List<OpenAiGptMessage> _conversation = new();
private string _userInput = "";
private string _tempMessage = "";
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<bool>("SaveConversationsToFile");
_shouldStream = !Configuration.GetValue<bool>("UseMocks") && Configuration.GetValue<bool>("StreamChatCompletions");
}

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
_scrollJsScript ??= await JsRuntime.InvokeAsync<IJSObjectReference>("import", "./js/scroll.js");
}
}

async Task EnterKeyHandler(KeyboardEventArgs e)
{
if (e.Code is "Enter" or "NumpadEnter")
{
await SendPrompt();
}
}

private async Task SendPrompt()
{
if (string.IsNullOrWhiteSpace(_userInput))
{
return;
}
_isWaitingForResponse = true;

OpenAiGptMessage userInput = new OpenAiGptMessage("user", _userInput);
_conversation.Add(userInput);

if (_shouldStream)
{
await HandleStreamedResponse(OpenAiLlmClient.GetStreamedChatCompletion(userInput));
}
else
{
string response = await OpenAiLlmClient.GetChatCompletion(userInput);
HandleResponse(response);
}

if (_shouldSave && _fileUtil != null)
{
string assistantOutput = _conversation.Last().Content;
await _fileUtil.UpdateSaveFileAsync(new MessagePair(_userInput, assistantOutput));
}

_userInput = "";
StateHasChanged();
await ScrollToElement("bottom-id");
_isWaitingForResponse = false;
}

private void HandleResponse(string response)
{
OpenAiGptMessage assistantOutput = new OpenAiGptMessage("assistant", response);
_conversation.Add(assistantOutput);
}

private async Task HandleStreamedResponse(IAsyncEnumerable<string> streamedResponse)
{
await foreach (string res in streamedResponse)
{
_tempMessage += res;
StateHasChanged();
await ScrollToElement("bottom-id");
}
HandleResponse(_tempMessage);
_tempMessage = "";
}

private async Task ScrollToElement(string elementId)
{
await _scrollJsScript!.InvokeVoidAsync("ScrollToId", elementId);
}

}
<footer class="footer pl-3 text-muted footer-formatting fixed-bottom custom-gray-container">
<div class="container">
&copy; 2023 - ChatRPG
</div>
</footer>
Loading