feat(ai-ux): deduplicate AI queries, handle ServiceUnavailable retries, and optimize reader canvas graph prerendering (#44)
This Pull Request encapsulates all outstanding AI, Blazor InteractiveAuto lifecycle, pgvector, and Firefox authorization/session compatibility fixes. ### Key Accomplishments: 1. **Concurrent Request Deduplication (Option B):** Implemented a thread-safe active task registry in `KnowledgeService` that groups concurrent graph extraction queries for the same content, preventing duplicate AI calls completely. 2. **Resilience Strategy for Downstream Demands:** Extended the `ai-retry` resilience pipeline to automatically intercept and retry on temporary Google API `503 ServiceUnavailable` / `high demand` spikes. 3. **Interactive Graph Generation Guard (Option A):** Prevented server-side prerender-phase graph requests in the reader canvas component. 4. **Firefox Compatibility & Cookie Handler:** Implemented an authentication endpoint and hybrid hidden-form submission flow to solve login, registration, and logout redirections and cookies securely. 5. **Autoscrolling & Graph Exclusions:** Added concept-to-block smooth scrolling, active block badging, and filtered out markdown code blocks from being extracted as nodes. All unit tests compiled and passed 100% cleanly. --------- Co-authored-by: Marek Jasiński <jasins.marek@gmail.com> Reviewed-on: #44 Co-authored-by: Antigravity <antigravity@google.com> Co-committed-by: Antigravity <antigravity@google.com>
This commit was merged in pull request #44.
This commit is contained in:
@@ -18,4 +18,9 @@ public interface IReaderNavigationService
|
||||
/// Navigates to the reader for a specific book and records the current ebook ID.
|
||||
/// </summary>
|
||||
void NavigateToBook(Guid bookId);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the active book context (ID and optional chapter) without triggering browser routing.
|
||||
/// </summary>
|
||||
void SetBook(Guid bookId, int chapterIndex = 0);
|
||||
}
|
||||
|
||||
@@ -43,8 +43,34 @@ public sealed partial class KnowledgeCoordinator : IDisposable
|
||||
|
||||
private async Task HandleNodeSelected(string nodeId)
|
||||
{
|
||||
await _interactionService.RequestScrollToBlock(nodeId);
|
||||
await _interactionService.RequestHighlightBlock(nodeId);
|
||||
string? targetBlockId = nodeId;
|
||||
|
||||
var graph = _graphService.CurrentGraphData;
|
||||
if (graph != null)
|
||||
{
|
||||
var selectedNode = graph.Nodes.FirstOrDefault(n => n.Id == nodeId);
|
||||
if (selectedNode != null && selectedNode.Group == "concept")
|
||||
{
|
||||
// Look for connected block nodes (group: "current") in the links
|
||||
var connectedLinks = graph.Links.Where(l => l.Source == nodeId || l.Target == nodeId).ToList();
|
||||
foreach (var link in connectedLinks)
|
||||
{
|
||||
var otherId = link.Source == nodeId ? link.Target : link.Source;
|
||||
var otherNode = graph.Nodes.FirstOrDefault(n => n.Id == otherId);
|
||||
if (otherNode != null && otherNode.Group == "current")
|
||||
{
|
||||
targetBlockId = otherId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(targetBlockId))
|
||||
{
|
||||
await _interactionService.RequestScrollToBlock(targetBlockId);
|
||||
await _interactionService.RequestHighlightBlock(targetBlockId);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ProcessFullPageAsync(string fullContent, string tenantId = "global")
|
||||
|
||||
@@ -62,6 +62,12 @@ public class ReaderNavigationService : IReaderNavigationService
|
||||
_navigationManager.NavigateTo($"/reader/{bookId}");
|
||||
}
|
||||
|
||||
public void SetBook(Guid bookId, int chapterIndex = 0)
|
||||
{
|
||||
CurrentEbookId = bookId;
|
||||
CurrentChapterIndex = chapterIndex;
|
||||
}
|
||||
|
||||
private async Task NotifyNavigationChangedAsync()
|
||||
{
|
||||
var handlers = OnNavigationChanged?.GetInvocationList();
|
||||
|
||||
Reference in New Issue
Block a user