feat: integrate AI-driven selection panel with context-aware text summarization and quiz generation features.
This commit is contained in:
@@ -2,6 +2,7 @@ using NexusReader.Application.Abstractions.Services;
|
||||
using NexusReader.Application.Queries.Graph;
|
||||
using NexusReader.Application.Queries.Quiz;
|
||||
using NexusReader.UI.Shared.Services;
|
||||
using NexusReader.Application.DTOs.AI;
|
||||
|
||||
namespace NexusReader.UI.Shared.Services;
|
||||
|
||||
@@ -11,131 +12,94 @@ public sealed class KnowledgeCoordinator : IDisposable
|
||||
private readonly IKnowledgeGraphService _graphService;
|
||||
private readonly IQuizStateService _quizService;
|
||||
private readonly IPlatformService _platformService;
|
||||
private readonly IReaderInteractionService _interactionService;
|
||||
|
||||
private CancellationTokenSource? _debounceCts;
|
||||
public event Action<GraphDataDto>? OnGraphUpdated;
|
||||
|
||||
public KnowledgeCoordinator(
|
||||
IKnowledgeService knowledgeService,
|
||||
IKnowledgeGraphService graphService,
|
||||
IQuizStateService quizService,
|
||||
IPlatformService platformService)
|
||||
IPlatformService platformService,
|
||||
IReaderInteractionService interactionService)
|
||||
{
|
||||
_knowledgeService = knowledgeService;
|
||||
_graphService = graphService;
|
||||
_quizService = quizService;
|
||||
_platformService = platformService;
|
||||
_interactionService = interactionService;
|
||||
|
||||
_interactionService.OnNodeSelected += HandleNodeSelected;
|
||||
}
|
||||
|
||||
private void HandleNodeSelected(string nodeId)
|
||||
{
|
||||
_interactionService.RequestScrollToBlock(nodeId);
|
||||
_interactionService.RequestHighlightBlock(nodeId);
|
||||
}
|
||||
|
||||
public async Task ProcessFullPageAsync(string fullContent)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(fullContent)) return;
|
||||
|
||||
Console.WriteLine("[KnowledgeCoordinator] Generating full page graph...");
|
||||
|
||||
_graphService.Clear();
|
||||
_graphService.SetLoading(true);
|
||||
|
||||
try
|
||||
{
|
||||
var result = await _knowledgeService.GetGraphDataAsync(fullContent);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
var packet = result.Value;
|
||||
if (packet.Graph != null)
|
||||
{
|
||||
_graphService.UpdateGraph(packet.Graph);
|
||||
OnGraphUpdated?.Invoke(packet.Graph);
|
||||
await _platformService.VibrateSuccessAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[KnowledgeCoordinator] Error generating graph: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public void OnBlockReached(string blockId, string content)
|
||||
{
|
||||
Console.WriteLine($"[KnowledgeCoordinator] Block reached: {blockId}");
|
||||
|
||||
// 1. Skip extraction for the title page (usually the first block or contains 'title')
|
||||
if (blockId.Equals("seg-0", StringComparison.OrdinalIgnoreCase) ||
|
||||
blockId.Contains("title", StringComparison.OrdinalIgnoreCase) ||
|
||||
content.Length < 50) // Title pages are usually short
|
||||
{
|
||||
Console.WriteLine($"[KnowledgeCoordinator] Skipping extraction for title page/short block: {blockId}");
|
||||
_graphService.SetActiveNode(blockId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Update active node immediately for "TU JESTEŚ" logic
|
||||
// Only update active node for "TU JESTEŚ" logic, do NOT trigger highlight here
|
||||
_graphService.SetActiveNode(blockId);
|
||||
|
||||
// 3. Debounce the AI extraction to prevent spamming while scrolling
|
||||
_debounceCts?.Cancel();
|
||||
_debounceCts = new CancellationTokenSource();
|
||||
var token = _debounceCts.Token;
|
||||
|
||||
_ = DebounceAndExtractAsync(blockId, content, token);
|
||||
}
|
||||
|
||||
private async Task DebounceAndExtractAsync(string blockId, string content, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(1000, token);
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
Console.WriteLine($"[KnowledgeCoordinator] Triggering extraction for block: {blockId}");
|
||||
await ProcessKnowledgeExtractionAsync(blockId, content, token);
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"[KnowledgeCoordinator] Unexpected error in task: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessKnowledgeExtractionAsync(string blockId, string content, CancellationToken ct)
|
||||
public async Task<KnowledgePacket?> RequestSummaryAndQuizAsync(string content)
|
||||
{
|
||||
_quizService.SetHydrating(true);
|
||||
|
||||
var result = await _knowledgeService.GetKnowledgeAsync(content, ct);
|
||||
|
||||
if (result.IsSuccess && !ct.IsCancellationRequested)
|
||||
try
|
||||
{
|
||||
Console.WriteLine($"[KnowledgeCoordinator] Extraction success for block: {blockId}. Updating state...");
|
||||
var packet = result.Value;
|
||||
|
||||
// Update Quiz State
|
||||
var quizQuestions = packet.Quizzes
|
||||
.Select(q => new QuizQuestionDto(q.Question, q.Options, q.CorrectIndex))
|
||||
.ToList();
|
||||
_quizService.SetQuiz(blockId, new QuizDto(quizQuestions));
|
||||
|
||||
// Update Graph State
|
||||
GraphDataDto graphData;
|
||||
if (packet.Graph != null && packet.Graph.Nodes != null && packet.Graph.Nodes.Any())
|
||||
var result = await _knowledgeService.GetSummaryAndQuizAsync(content);
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
// Use AI-generated graph
|
||||
graphData = packet.Graph;
|
||||
|
||||
// Ensure current block is linked to the first concept or added if missing
|
||||
if (!graphData.Nodes.Any(n => n.Id == blockId))
|
||||
{
|
||||
graphData.Nodes.Add(new GraphNodeDto(blockId, "TU JESTEŚ", "current"));
|
||||
if (graphData.Nodes.Count > 1)
|
||||
{
|
||||
graphData.Links.Add(new GraphLinkDto(blockId, graphData.Nodes[0].Id, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback: Transform Concepts to GraphData if AI didn't provide a graph
|
||||
var nodes = packet.Concepts
|
||||
.Select(c => new GraphNodeDto(c.Title.ToLowerInvariant(), c.Title, "concept"))
|
||||
var packet = result.Value;
|
||||
var quizQuestions = packet.Quizzes
|
||||
.Select(q => new QuizQuestionDto(q.Question, q.Options, q.CorrectIndex))
|
||||
.ToList();
|
||||
|
||||
nodes.Add(new GraphNodeDto(blockId, "TU JESTEŚ", "current"));
|
||||
|
||||
var links = packet.Concepts
|
||||
.Select(c => new GraphLinkDto(blockId, c.Title.ToLowerInvariant(), 1))
|
||||
.ToList();
|
||||
|
||||
graphData = new GraphDataDto { Nodes = nodes, Links = links };
|
||||
_quizService.SetQuiz(null, new QuizDto(quizQuestions));
|
||||
await _platformService.VibrateSuccessAsync();
|
||||
return packet;
|
||||
}
|
||||
|
||||
_graphService.UpdateGraph(graphData);
|
||||
|
||||
// Visual/Haptic Feedback
|
||||
await _platformService.VibrateSuccessAsync();
|
||||
}
|
||||
else
|
||||
finally
|
||||
{
|
||||
if (!ct.IsCancellationRequested)
|
||||
{
|
||||
Console.WriteLine($"[KnowledgeCoordinator] Extraction failed or returned empty for block: {blockId}. Error: {result.Errors.FirstOrDefault()?.Message}");
|
||||
}
|
||||
_quizService.SetHydrating(false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_debounceCts?.Cancel();
|
||||
_debounceCts?.Dispose();
|
||||
_interactionService.OnNodeSelected -= HandleNodeSelected;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user