Files
Alchegos.HCI/Components/Pages/Chat.razor
2025-05-04 19:11:12 +01:00

251 lines
8.7 KiB
Plaintext

@page "/chat"
@page "/chat/{InitialSessionId}"
@rendermode InteractiveServer
@inject IChatSessionService ChatSessionService
@inject NavigationManager NavigationManager
@using Alchegos.HCI.Models
@using Alchegos.HCI.Services
@implements IAsyncDisposable
<PageTitle>Chat</PageTitle>
<h1>Chat Session</h1>
<div class="session-selector">
<input @bind="currentSessionIdInput" placeholder="Enter or Generate Session ID" />
<button @onclick="LoadSession" disabled="@isLoading">Load / Start Session</button>
<button @onclick="GenerateSessionId" disabled="@isLoading">Generate New ID</button>
<input @bind="externalWebhookUrl" placeholder="Server url">
@if (!string.IsNullOrEmpty(activeSessionId))
{
<span>Active Session: @activeSessionId</span>
}
</div>
@if (isLoading)
{
<p><em>Loading session...</em></p>
}
else if (currentSession != null)
{
<div class="chat-history" style="height: 400px; overflow-y: scroll; border: 1px solid #ccc; margin-bottom: 10px; padding: 5px;">
@foreach (var message in currentSession.Messages)
{
<div class="@(message.IsUser ? "user-message" : "server-message")">
<strong>@(message.IsUser ? "You" : "Server"):</strong>
<span>@message.Content</span>
<small style="font-size: 0.7em; color: grey; display: block;">@message.Timestamp.ToLocalTime()</small>
</div>
}
@if (currentSession.IsWaitingForResponse)
{
<p><em>Waiting for response...</em></p>
}
</div>
<div class="chat-input">
<input @bind="newMessage" placeholder="Type your message..." @onkeydown="HandleKeyDown" disabled="@currentSession.IsWaitingForResponse" />
<button @onclick="SendMessage" disabled="@(currentSession.IsWaitingForResponse || string.IsNullOrWhiteSpace(newMessage))">Send</button>
</div>
}
else if (sessionLoadAttempted)
{
<p><em>Enter a Session ID and click "Load / Start Session" or generate a new one.</em></p>
}
@code {
[Parameter] public string? InitialSessionId { get; set; }
private string currentSessionIdInput { get; set; } = string.Empty;
private string activeSessionId { get; set; } = string.Empty;
private ChatSession? currentSession;
private string newMessage { get; set; } = string.Empty;
private bool isLoading = false;
private bool sessionLoadAttempted = false;
private string externalWebhookUrl { get; set; } = string.Empty;
private string? _previousSessionId = null;
protected override async Task OnParametersSetAsync()
{
Console.WriteLine($"--- OnParametersSetAsync START. InitialSessionId: {InitialSessionId} ---");
if (InitialSessionId != _previousSessionId && !string.IsNullOrEmpty(InitialSessionId))
{
isLoading = true;
sessionLoadAttempted = true;
if (!string.IsNullOrEmpty(_previousSessionId) && _previousSessionId != InitialSessionId)
{
Console.WriteLine($"Parameter changed from {_previousSessionId} to {InitialSessionId}. Unregistering old callbacks for {_previousSessionId}.");
var oldId = _previousSessionId;
_ = Task.Run(() => ChatSessionService.UnregisterSessionCallbacks(oldId));
}
activeSessionId = InitialSessionId;
_previousSessionId = InitialSessionId;
Console.WriteLine($"Loading session from parameter: {activeSessionId}");
currentSession = ChatSessionService.GetOrCreateSession(activeSessionId);
if (currentSession != null)
{
Console.WriteLine($"Session obtained: {currentSession.SessionId}");
await RegisterCallbacksAsync();
}
else
Console.WriteLine("!!! Failed to get/create session in OnParametersSetAsync!");
isLoading = false;
Console.WriteLine("--- OnParametersSetAsync END. isLoading set to false ---");
}
else if (string.IsNullOrEmpty(InitialSessionId) && _previousSessionId != null)
{
Console.WriteLine("--- OnParametersSetAsync: InitialSessionId became null/empty. Clearing session. ---");
await UnregisterCallbacksAsync();
currentSession = null;
activeSessionId = string.Empty;
_previousSessionId = null;
currentSessionIdInput = string.Empty;
sessionLoadAttempted = false;
}
else if (InitialSessionId == _previousSessionId)
{
Console.WriteLine($"--- OnParametersSetAsync: InitialSessionId '{InitialSessionId}' did not change. Skipping load. ---");
}
else
{
Console.WriteLine($"--- OnParametersSetAsync: InitialSessionId '{InitialSessionId}' is null/empty and no previous ID. Initial state. ---");
}
}
protected override void OnInitialized()
{
if (!string.IsNullOrEmpty(InitialSessionId))
currentSessionIdInput = InitialSessionId;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && !string.IsNullOrEmpty(activeSessionId))
await RegisterCallbacksAsync();
}
private void GenerateSessionId()
{
var newId = Guid.NewGuid().ToString("N");
currentSessionIdInput = newId;
Console.WriteLine($"--- GenerateSessionId: Generated {newId}, Navigating... ---");
NavigationManager.NavigateTo($"/chat/{newId}");
}
private void LoadSession()
{
if (string.IsNullOrWhiteSpace(currentSessionIdInput))
{
Console.WriteLine("--- LoadSession Clicked: Input empty, doing nothing. ---");
return;
}
var sessionIdToLoad = currentSessionIdInput;
var targetUrl = $"/chat/{sessionIdToLoad}";
string currentPathAndQuery = new Uri(NavigationManager.Uri).PathAndQuery;
if (!currentPathAndQuery.Equals(targetUrl, StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine($"--- LoadSession Clicked: Navigating to {targetUrl}... ---");
NavigationManager.NavigateTo(targetUrl);
}
else
{
Console.WriteLine($"--- LoadSession Clicked: Already at {targetUrl}. No navigation needed. ---");
}
}
private async Task SendMessage()
{
if (currentSession == null || string.IsNullOrWhiteSpace(newMessage) || currentSession.IsWaitingForResponse || String.IsNullOrWhiteSpace(externalWebhookUrl))
return;
var userMessage = new ChatMessage { IsUser = true, Content = newMessage };
ChatSessionService.AddMessage(activeSessionId, userMessage);
ChatSessionService.SetWaitingStatus(activeSessionId, true);
var messageToSend = newMessage;
newMessage = string.Empty;
StateHasChanged();
await ChatSessionService.SendMessageToExternalWebhook(externalWebhookUrl, activeSessionId, messageToSend);
}
private async Task HandleKeyDown(KeyboardEventArgs e)
{
if (e.Key == "Enter" && !currentSession.IsWaitingForResponse && !string.IsNullOrWhiteSpace(newMessage))
await SendMessage();
}
[JSInvokable]
public async Task HandleMessageReceived(ChatMessage message)
{
await InvokeAsync(StateHasChanged);
}
[JSInvokable]
public async Task HandleSessionClosed()
{
await InvokeAsync(() =>
{
if (currentSession != null && currentSession.SessionId == activeSessionId)
{
Console.WriteLine($"UI notified session closed: {activeSessionId}");
currentSession = null;
activeSessionId = string.Empty;
currentSessionIdInput = string.Empty;
NavigationManager.NavigateTo("/chat", forceLoad: false);
StateHasChanged();
}
});
}
private async Task RegisterCallbacksAsync()
{
if (currentSession != null)
{
_ = Task.Run(() =>
{
ChatSessionService.RegisterSessionCallbacks(
currentSession.SessionId,
HandleMessageReceived,
HandleSessionClosed
);
});
await Task.CompletedTask;
}
}
private async Task UnregisterCallbacksAsync()
{
if (!string.IsNullOrEmpty(activeSessionId))
{
var capturedSessionId = activeSessionId;
_ = Task.Run(() => ChatSessionService.UnregisterSessionCallbacks(capturedSessionId));
await Task.CompletedTask;
}
}
public async ValueTask DisposeAsync()
{
await UnregisterCallbacksAsync();
}
}