ux(reader): implement scroll-to-top on chapter switch and cancel stale background AI tasks
This commit is contained in:
@@ -308,6 +308,11 @@
|
||||
StatusMessage = "Wczytywanie treści...";
|
||||
StateHasChanged();
|
||||
|
||||
if (string.IsNullOrEmpty(NavigationService.PendingScrollBlockId))
|
||||
{
|
||||
await ScrollToTopAsync();
|
||||
}
|
||||
|
||||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||
var userId = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
@@ -374,6 +379,19 @@
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ScrollToTopAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var module = _viewportModule ?? await JS.InvokeAsync<IJSObjectReference>("import", "./_content/NexusReader.UI.Shared/js/viewport.js");
|
||||
await module.InvokeVoidAsync("scrollToTop");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning(ex, "Failed to scroll reader canvas to top.");
|
||||
}
|
||||
}
|
||||
|
||||
private Task HandleUpdate() => InvokeAsync(StateHasChanged);
|
||||
|
||||
private void HandleEscape()
|
||||
|
||||
@@ -17,6 +17,9 @@ public sealed partial class KnowledgeCoordinator : IDisposable
|
||||
private readonly IReaderInteractionService _interactionService;
|
||||
private readonly ILogger<KnowledgeCoordinator> _logger;
|
||||
|
||||
private CancellationTokenSource? _graphCts;
|
||||
private CancellationTokenSource? _quizCts;
|
||||
|
||||
public string CurrentFullPageContent { get; private set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
@@ -77,6 +80,11 @@ public sealed partial class KnowledgeCoordinator : IDisposable
|
||||
|
||||
public async Task ProcessFullPageAsync(string fullContent, string tenantId = "global", Guid? ebookId = null)
|
||||
{
|
||||
_graphCts?.Cancel();
|
||||
_graphCts?.Dispose();
|
||||
_graphCts = new CancellationTokenSource();
|
||||
var token = _graphCts.Token;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(fullContent)) return;
|
||||
|
||||
CurrentFullPageContent = fullContent;
|
||||
@@ -87,7 +95,9 @@ public sealed partial class KnowledgeCoordinator : IDisposable
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _knowledgeService.GetGraphDataAsync(fullContent, tenantId, ebookId);
|
||||
var result = await _knowledgeService.GetGraphDataAsync(fullContent, tenantId, ebookId, token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
var packet = result.Value;
|
||||
@@ -103,12 +113,19 @@ public sealed partial class KnowledgeCoordinator : IDisposable
|
||||
|
||||
await _graphService.SetLoading(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.LogInformation("[KnowledgeCoordinator] Graph generation task was canceled.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
await _graphService.SetLoading(false);
|
||||
LogGraphError(ex, tenantId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task OnBlockReachedAsync(string blockId, string content)
|
||||
{
|
||||
@@ -118,11 +135,18 @@ public sealed partial class KnowledgeCoordinator : IDisposable
|
||||
|
||||
public async Task<Result<KnowledgePacket>> RequestSummaryAndQuizAsync(string content, string tenantId = "global")
|
||||
{
|
||||
_quizCts?.Cancel();
|
||||
_quizCts?.Dispose();
|
||||
_quizCts = new CancellationTokenSource();
|
||||
var token = _quizCts.Token;
|
||||
|
||||
await _quizService.SetHydrating(true);
|
||||
LogRequestingSummary(tenantId);
|
||||
try
|
||||
{
|
||||
var result = await _knowledgeService.GetSummaryAndQuizAsync(content, tenantId);
|
||||
var result = await _knowledgeService.GetSummaryAndQuizAsync(content, tenantId, cancellationToken: token);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
var packet = result.Value;
|
||||
@@ -138,11 +162,20 @@ public sealed partial class KnowledgeCoordinator : IDisposable
|
||||
LogSummaryWarning(tenantId);
|
||||
return Result.Fail(result.Errors);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
_logger.LogInformation("[KnowledgeCoordinator] Quiz and summary generation task was canceled.");
|
||||
return Result.Fail("Task canceled");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
LogSummaryError(ex, tenantId);
|
||||
return Result.Fail(new Error("Error requesting summary and quiz").CausedBy(ex));
|
||||
}
|
||||
return Result.Fail("Task canceled");
|
||||
}
|
||||
finally
|
||||
{
|
||||
await _quizService.SetHydrating(false);
|
||||
@@ -151,6 +184,14 @@ public sealed partial class KnowledgeCoordinator : IDisposable
|
||||
|
||||
public async Task ClearAsync()
|
||||
{
|
||||
_graphCts?.Cancel();
|
||||
_graphCts?.Dispose();
|
||||
_graphCts = null;
|
||||
|
||||
_quizCts?.Cancel();
|
||||
_quizCts?.Dispose();
|
||||
_quizCts = null;
|
||||
|
||||
CurrentFullPageContent = string.Empty;
|
||||
await _graphService.Clear();
|
||||
await _quizService.SetQuiz(null, null);
|
||||
@@ -159,6 +200,12 @@ public sealed partial class KnowledgeCoordinator : IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
_interactionService.OnNodeSelected -= HandleNodeSelected;
|
||||
|
||||
_graphCts?.Cancel();
|
||||
_graphCts?.Dispose();
|
||||
|
||||
_quizCts?.Cancel();
|
||||
_quizCts?.Dispose();
|
||||
}
|
||||
|
||||
[LoggerMessage(Level = LogLevel.Information, Message = "[KnowledgeCoordinator] Generating full page graph for tenant: {TenantId}")]
|
||||
|
||||
@@ -38,3 +38,13 @@ export function scrollIntoView(id) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function scrollToTop() {
|
||||
const el = document.querySelector('.reader-canvas');
|
||||
if (el) {
|
||||
el.scrollTop = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user