2025-08-24 19:34:14 -05:00
|
|
|
@using System.Collections.Specialized
|
2026-01-11 14:38:24 -06:00
|
|
|
@using LANCommander.SDK.Models
|
2025-09-24 20:58:23 -05:00
|
|
|
@using LANCommander.SDK.Services
|
2025-12-23 21:12:06 -06:00
|
|
|
@inject IChatClient ChatClient
|
2025-09-06 13:09:47 -05:00
|
|
|
@namespace LANCommander.UI.Components
|
2025-08-20 20:53:48 -05:00
|
|
|
|
2026-01-02 02:10:42 -06:00
|
|
|
<Flex Direction="FlexDirection.Vertical" Class="chat-thread">
|
|
|
|
|
<Flex Direction="FlexDirection.Vertical" Class="chat-thread-messages">
|
2025-12-30 18:08:20 -06:00
|
|
|
@if (_thread != null)
|
|
|
|
|
{
|
2026-01-03 23:22:25 -06:00
|
|
|
<InfiniteLoader
|
2026-01-11 14:38:24 -06:00
|
|
|
T="SDK.Models.ChatMessageGroup"
|
2026-01-03 23:22:25 -06:00
|
|
|
KeySelector="g => g.Id.ToString()"
|
|
|
|
|
Loader="LoadMessages"
|
|
|
|
|
PageSize="10">
|
2026-01-11 14:38:24 -06:00
|
|
|
<ChatMessageGroup
|
|
|
|
|
UserId="context.UserId"
|
|
|
|
|
UserName="@context.UserName"
|
|
|
|
|
StartedOn="context.StartedOn.DateTime">
|
|
|
|
|
@foreach (var message in context.Messages)
|
|
|
|
|
{
|
|
|
|
|
<ChatMessage Id="message.Id" Content="@message.Content" SentOn="message.SentOn" />
|
|
|
|
|
}
|
|
|
|
|
</ChatMessageGroup>
|
2026-01-03 23:22:25 -06:00
|
|
|
</InfiniteLoader>
|
2025-12-30 18:08:20 -06:00
|
|
|
}
|
2026-01-02 02:10:42 -06:00
|
|
|
</Flex>
|
2025-12-30 18:08:20 -06:00
|
|
|
|
2026-01-02 02:10:42 -06:00
|
|
|
<ChatMessageInput ThreadId="@Id" Placeholder="@($"Message {_thread?.Title}")" />
|
2025-08-20 20:53:48 -05:00
|
|
|
</Flex>
|
|
|
|
|
|
|
|
|
|
@code {
|
2026-01-02 02:10:42 -06:00
|
|
|
[Parameter] public Guid? Id { get; set; }
|
2025-08-20 20:53:48 -05:00
|
|
|
|
2025-12-30 18:08:20 -06:00
|
|
|
SDK.Models.ChatThread? _thread;
|
2026-01-11 14:38:24 -06:00
|
|
|
private SDK.Models.ChatMessage? _lastCursor;
|
2025-12-30 18:08:20 -06:00
|
|
|
|
2025-08-20 20:53:48 -05:00
|
|
|
protected override async Task OnParametersSetAsync()
|
|
|
|
|
{
|
2026-01-02 02:10:42 -06:00
|
|
|
if ((_thread == null || _thread.Id != Id) && Id.HasValue)
|
2025-08-21 17:47:30 -05:00
|
|
|
{
|
2026-01-02 02:10:42 -06:00
|
|
|
_thread = await ChatClient.GetThreadAsync(Id.Value);
|
2025-08-21 17:47:30 -05:00
|
|
|
|
2025-12-30 18:08:20 -06:00
|
|
|
_thread.MessageGroups.CollectionChanged += MessageGroupsOnCollectionChanged;
|
|
|
|
|
|
2026-01-03 23:22:25 -06:00
|
|
|
await ChatClient.GetMessagesAsync(Id.Value, null, 10);
|
2025-08-21 17:47:30 -05:00
|
|
|
}
|
2025-08-20 20:53:48 -05:00
|
|
|
}
|
|
|
|
|
|
2025-08-24 19:34:14 -05:00
|
|
|
void MessageGroupsOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
2025-12-30 18:08:20 -06:00
|
|
|
=> InvokeAsync(StateHasChanged);
|
2026-01-03 23:22:25 -06:00
|
|
|
|
2026-01-11 14:38:24 -06:00
|
|
|
async Task<InfiniteResponse<SDK.Models.ChatMessageGroup>> LoadMessages(SDK.Models.ChatMessageGroup? cursor, int pageSize)
|
2026-01-03 23:22:25 -06:00
|
|
|
{
|
2026-01-11 14:38:24 -06:00
|
|
|
// Use the oldest message from the cursor group, or the last cursor message
|
|
|
|
|
SDK.Models.ChatMessage? messageCursor = null;
|
|
|
|
|
if (cursor != null && cursor.Messages.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
// Get the oldest message from the cursor group (first message in chronological order)
|
|
|
|
|
messageCursor = cursor.Messages.OrderBy(m => m.SentOn).First();
|
|
|
|
|
}
|
|
|
|
|
else if (_lastCursor != null)
|
|
|
|
|
{
|
|
|
|
|
messageCursor = _lastCursor;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var response = await ChatClient.GetMessagesAsync(Id.Value, messageCursor?.Id ?? null, pageSize);
|
|
|
|
|
|
|
|
|
|
// Filter out blank/whitespace messages
|
|
|
|
|
var validMessages = (response.Items ?? [])
|
|
|
|
|
.Where(m => !string.IsNullOrWhiteSpace(m.Content))
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
// Mark messages as read when they are loaded
|
|
|
|
|
if (Id.HasValue && validMessages.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
await ChatClient.UpdatedReadStatus(Id.Value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Group messages by sender (messages are already in chronological order from the service)
|
|
|
|
|
var groups = GroupMessagesBySender(validMessages);
|
|
|
|
|
|
|
|
|
|
// Track the oldest message for next cursor (from the oldest group)
|
|
|
|
|
// Groups are in newest-first order (will be reversed by InfiniteLoader)
|
|
|
|
|
// So the oldest group is the last one
|
|
|
|
|
if (groups.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
var oldestGroup = groups[groups.Count - 1];
|
|
|
|
|
if (oldestGroup.Messages.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
// Get the oldest message from the oldest group
|
|
|
|
|
_lastCursor = oldestGroup.Messages.OrderBy(m => m.SentOn).First();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new InfiniteResponse<SDK.Models.ChatMessageGroup>
|
2026-01-03 23:22:25 -06:00
|
|
|
{
|
2026-01-11 14:38:24 -06:00
|
|
|
Items = groups,
|
|
|
|
|
HasMore = response.HasMore
|
2026-01-03 23:22:25 -06:00
|
|
|
};
|
|
|
|
|
}
|
2026-01-11 14:38:24 -06:00
|
|
|
|
|
|
|
|
private List<SDK.Models.ChatMessageGroup> GroupMessagesBySender(IEnumerable<SDK.Models.ChatMessage> messages)
|
|
|
|
|
{
|
|
|
|
|
var groups = new List<SDK.Models.ChatMessageGroup>();
|
|
|
|
|
SDK.Models.ChatMessageGroup? currentGroup = null;
|
|
|
|
|
|
|
|
|
|
// Messages come from service in newest-first order (OrderByDescending)
|
|
|
|
|
// We'll process them in reverse to get oldest-first for grouping
|
|
|
|
|
var messagesList = messages.ToList();
|
|
|
|
|
|
|
|
|
|
// Reverse to get oldest-first order for proper grouping
|
|
|
|
|
messagesList.Reverse();
|
|
|
|
|
|
|
|
|
|
foreach (var message in messagesList)
|
|
|
|
|
{
|
|
|
|
|
// Start a new group if this is the first message, or if the sender changed
|
|
|
|
|
if (currentGroup == null || message.UserId != currentGroup.UserId)
|
|
|
|
|
{
|
|
|
|
|
if (currentGroup != null)
|
|
|
|
|
{
|
|
|
|
|
groups.Add(currentGroup);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentGroup = new SDK.Models.ChatMessageGroup
|
|
|
|
|
{
|
|
|
|
|
Id = Guid.NewGuid(),
|
|
|
|
|
UserId = message.UserId,
|
|
|
|
|
UserName = message.UserName,
|
|
|
|
|
Messages = new List<SDK.Models.ChatMessage> { message }
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Add to current group (messages within group are in chronological order)
|
|
|
|
|
currentGroup.Messages.Add(message);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add the last group
|
|
|
|
|
if (currentGroup != null)
|
|
|
|
|
{
|
|
|
|
|
groups.Add(currentGroup);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Groups are now in oldest-first order (first group is oldest)
|
|
|
|
|
// InfiniteLoader will reverse them, so we need to reverse again to maintain oldest-first
|
|
|
|
|
// Actually, wait - InfiniteLoader reverses, so if we return oldest-first, after reversal it's newest-first (wrong)
|
|
|
|
|
// So we should return newest-first, then InfiniteLoader reverses to oldest-first
|
|
|
|
|
groups.Reverse();
|
|
|
|
|
|
|
|
|
|
return groups;
|
|
|
|
|
}
|
2025-08-20 20:53:48 -05:00
|
|
|
}
|