feat: integrate AI-driven selection panel with context-aware text summarization and quiz generation features.

This commit is contained in:
2026-04-26 20:36:08 +02:00
parent 82d726097f
commit 39a9ca5706
25 changed files with 819 additions and 219 deletions
@@ -9,6 +9,7 @@
@inject IFocusModeService FocusMode
@inject IReaderNavigationService NavigationService
@inject KnowledgeCoordinator Coordinator
@inject IReaderInteractionService InteractionService
<div class="reader-canvas @(ThemeService.IsLightMode ? "theme-light" : "theme-dark")">
@if (ViewModel == null)
@@ -19,36 +20,45 @@
}
else
{
<div class="reader-flow-container">
<div @ref="_containerRef" class="reader-flow-container">
@foreach (var block in ViewModel.Blocks)
{
<div id="@block.Id" class="block-wrapper">
<div id="@block.Id" class="block-wrapper @(_highlightedBlockId == block.Id ? "highlighted" : "")">
@if (block is TextSegmentBlock textSegment)
{
<NexusTypography Variant="NexusTypography.TypographyVariant.Ebook">@((MarkupString)textSegment.Content)</NexusTypography>
}
else if (block is AiActionTriggerBlock aiTrigger)
{
<AiAssistantBubble
ContextBlockId="@block.Id"
Dialogue="@aiTrigger.Dialogue"
Actions="@aiTrigger.ActionOptions"
OnActionTriggered="HandleAiAction" />
}
</div>
}
</div>
}
<SelectionAiPanel
SelectedText="@_selectedText"
BlockId="@_selectedBlockId"
Coordinates="@_selectionCoords"
FullPageContent="@GetFullPageContent()" />
</div>
@code {
private ReaderPageViewModel? ViewModel;
private string StatusMessage = "Loading chapter...";
private string _selectedText = string.Empty;
private string _selectedBlockId = string.Empty;
private SelectionCoordinates? _selectionCoords;
private string? _highlightedBlockId;
private bool _isJsInitialized;
private ElementReference _containerRef;
protected override void OnInitialized()
{
ThemeService.OnThemeChanged += StateHasChanged;
NavigationService.OnNavigationChanged += OnNavigationChanged;
InteractionService.OnScrollToBlockRequested += HandleScrollRequested;
InteractionService.OnHighlightBlockRequested += HandleHighlightRequested;
InteractionService.OnTextSelected += HandleTextSelected;
}
protected override async Task OnParametersSetAsync()
@@ -58,19 +68,33 @@
private async void OnNavigationChanged()
{
_isJsInitialized = false;
_selectedText = string.Empty;
_selectionCoords = null;
await LoadChapterAsync(NavigationService.CurrentChapterIndex);
StateHasChanged();
await InitializeObserverAsync();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
if (ViewModel != null && !_isJsInitialized)
{
_isJsInitialized = true;
await InitializeObserverAsync();
await InitializeSelectionListenerAsync();
}
}
private async Task InitializeSelectionListenerAsync()
{
try
{
var module = await JS.InvokeAsync<IJSObjectReference>("import", "./_content/NexusReader.UI.Shared/js/selectionHandler.js");
await module.InvokeVoidAsync("initSelectionListener", DotNetObjectReference.Create(this), _containerRef);
}
catch { }
}
private async Task InitializeObserverAsync()
{
try
@@ -87,6 +111,49 @@
Coordinator.OnBlockReached(blockId, content);
}
[JSInvokable]
public void HandleTextSelected(string text, string blockId, SelectionCoordinates coords)
{
Console.WriteLine($"[ReaderCanvas] Text selected: {text} at {coords.Top},{coords.Left}");
_selectedText = text;
_selectedBlockId = blockId;
_selectionCoords = coords;
StateHasChanged();
}
[JSInvokable]
public void HandleSelectionCleared()
{
_selectedText = string.Empty;
_selectionCoords = null;
StateHasChanged();
}
private void HandleScrollRequested(string blockId)
{
_ = ScrollToNodeAsync(blockId);
}
private async void HandleHighlightRequested(string blockId)
{
_highlightedBlockId = blockId;
StateHasChanged();
await Task.Delay(3000); // Highlight for 3 seconds
if (_highlightedBlockId == blockId)
{
_highlightedBlockId = null;
StateHasChanged();
}
}
private string GetFullPageContent()
{
if (ViewModel == null) return string.Empty;
return string.Join("\n\n", ViewModel.Blocks
.OfType<TextSegmentBlock>()
.Select(b => $"[ID: {b.Id}]\n{b.Content}"));
}
private async Task LoadChapterAsync(int index)
{
ViewModel = null;
@@ -97,6 +164,9 @@
{
ViewModel = result.Value;
NavigationService.UpdateMetadata(ViewModel.CurrentChapterIndex, ViewModel.TotalChapters, ViewModel.ChapterTitle);
// Trigger full page graph generation after loading
_ = Coordinator.ProcessFullPageAsync(GetFullPageContent());
}
else
{
@@ -122,5 +192,9 @@
{
ThemeService.OnThemeChanged -= StateHasChanged;
NavigationService.OnNavigationChanged -= OnNavigationChanged;
InteractionService.OnScrollToBlockRequested -= HandleScrollRequested;
InteractionService.OnHighlightBlockRequested -= HandleHighlightRequested;
InteractionService.OnTextSelected -= HandleTextSelected;
}
}