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...";
|
StatusMessage = "Wczytywanie treści...";
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(NavigationService.PendingScrollBlockId))
|
||||||
|
{
|
||||||
|
await ScrollToTopAsync();
|
||||||
|
}
|
||||||
|
|
||||||
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
var authState = await AuthStateProvider.GetAuthenticationStateAsync();
|
||||||
var userId = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
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 Task HandleUpdate() => InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
private void HandleEscape()
|
private void HandleEscape()
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ public sealed partial class KnowledgeCoordinator : IDisposable
|
|||||||
private readonly IPlatformService _platformService;
|
private readonly IPlatformService _platformService;
|
||||||
private readonly IReaderInteractionService _interactionService;
|
private readonly IReaderInteractionService _interactionService;
|
||||||
private readonly ILogger<KnowledgeCoordinator> _logger;
|
private readonly ILogger<KnowledgeCoordinator> _logger;
|
||||||
|
|
||||||
|
private CancellationTokenSource? _graphCts;
|
||||||
|
private CancellationTokenSource? _quizCts;
|
||||||
|
|
||||||
public string CurrentFullPageContent { get; private set; } = string.Empty;
|
public string CurrentFullPageContent { get; private set; } = string.Empty;
|
||||||
|
|
||||||
@@ -77,6 +80,11 @@ public sealed partial class KnowledgeCoordinator : IDisposable
|
|||||||
|
|
||||||
public async Task ProcessFullPageAsync(string fullContent, string tenantId = "global", Guid? ebookId = null)
|
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;
|
if (string.IsNullOrWhiteSpace(fullContent)) return;
|
||||||
|
|
||||||
CurrentFullPageContent = fullContent;
|
CurrentFullPageContent = fullContent;
|
||||||
@@ -87,7 +95,9 @@ public sealed partial class KnowledgeCoordinator : IDisposable
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await _knowledgeService.GetGraphDataAsync(fullContent, tenantId, ebookId);
|
var result = await _knowledgeService.GetGraphDataAsync(fullContent, tenantId, ebookId, token);
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
if (result.IsSuccess)
|
if (result.IsSuccess)
|
||||||
{
|
{
|
||||||
var packet = result.Value;
|
var packet = result.Value;
|
||||||
@@ -103,10 +113,17 @@ public sealed partial class KnowledgeCoordinator : IDisposable
|
|||||||
|
|
||||||
await _graphService.SetLoading(false);
|
await _graphService.SetLoading(false);
|
||||||
}
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("[KnowledgeCoordinator] Graph generation task was canceled.");
|
||||||
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await _graphService.SetLoading(false);
|
if (!token.IsCancellationRequested)
|
||||||
LogGraphError(ex, tenantId);
|
{
|
||||||
|
await _graphService.SetLoading(false);
|
||||||
|
LogGraphError(ex, tenantId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,11 +135,18 @@ public sealed partial class KnowledgeCoordinator : IDisposable
|
|||||||
|
|
||||||
public async Task<Result<KnowledgePacket>> RequestSummaryAndQuizAsync(string content, string tenantId = "global")
|
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);
|
await _quizService.SetHydrating(true);
|
||||||
LogRequestingSummary(tenantId);
|
LogRequestingSummary(tenantId);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = await _knowledgeService.GetSummaryAndQuizAsync(content, tenantId);
|
var result = await _knowledgeService.GetSummaryAndQuizAsync(content, tenantId, cancellationToken: token);
|
||||||
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
if (result.IsSuccess)
|
if (result.IsSuccess)
|
||||||
{
|
{
|
||||||
var packet = result.Value;
|
var packet = result.Value;
|
||||||
@@ -138,10 +162,19 @@ public sealed partial class KnowledgeCoordinator : IDisposable
|
|||||||
LogSummaryWarning(tenantId);
|
LogSummaryWarning(tenantId);
|
||||||
return Result.Fail(result.Errors);
|
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)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogSummaryError(ex, tenantId);
|
if (!token.IsCancellationRequested)
|
||||||
return Result.Fail(new Error("Error requesting summary and quiz").CausedBy(ex));
|
{
|
||||||
|
LogSummaryError(ex, tenantId);
|
||||||
|
return Result.Fail(new Error("Error requesting summary and quiz").CausedBy(ex));
|
||||||
|
}
|
||||||
|
return Result.Fail("Task canceled");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -151,6 +184,14 @@ public sealed partial class KnowledgeCoordinator : IDisposable
|
|||||||
|
|
||||||
public async Task ClearAsync()
|
public async Task ClearAsync()
|
||||||
{
|
{
|
||||||
|
_graphCts?.Cancel();
|
||||||
|
_graphCts?.Dispose();
|
||||||
|
_graphCts = null;
|
||||||
|
|
||||||
|
_quizCts?.Cancel();
|
||||||
|
_quizCts?.Dispose();
|
||||||
|
_quizCts = null;
|
||||||
|
|
||||||
CurrentFullPageContent = string.Empty;
|
CurrentFullPageContent = string.Empty;
|
||||||
await _graphService.Clear();
|
await _graphService.Clear();
|
||||||
await _quizService.SetQuiz(null, null);
|
await _quizService.SetQuiz(null, null);
|
||||||
@@ -159,6 +200,12 @@ public sealed partial class KnowledgeCoordinator : IDisposable
|
|||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_interactionService.OnNodeSelected -= HandleNodeSelected;
|
_interactionService.OnNodeSelected -= HandleNodeSelected;
|
||||||
|
|
||||||
|
_graphCts?.Cancel();
|
||||||
|
_graphCts?.Dispose();
|
||||||
|
|
||||||
|
_quizCts?.Cancel();
|
||||||
|
_quizCts?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
[LoggerMessage(Level = LogLevel.Information, Message = "[KnowledgeCoordinator] Generating full page graph for tenant: {TenantId}")]
|
[LoggerMessage(Level = LogLevel.Information, Message = "[KnowledgeCoordinator] Generating full page graph for tenant: {TenantId}")]
|
||||||
|
|||||||
@@ -38,3 +38,13 @@ export function scrollIntoView(id) {
|
|||||||
}
|
}
|
||||||
return false;
|
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