feat(ui/graph): Knowledge Graph Refinement and Sidebar Hierarchy (#25)

This PR addresses several UI/UX and architectural refinements for the Knowledge Graph and Intelligence Sidebar.

### Key Changes:
- **Knowledge Graph (#21, #22)**:
  - Implemented \"pill-shaped\" nodes with dynamic label truncation and SVG tooltips.
  - Added bound-constrained simulation to keep nodes within the viewport.
  - Integrated `ResizeObserver` for dynamic layout handling.
  - Implemented Zoom-to-Fit functionality.
  - Enforced unique concept IDs in AI prompts and hardened JS logic to prevent multi-selection bugs.
- **Intelligence Sidebar (#23)**:
  - Improved visual depth with a radial gradient background for the graph.
  - Increased sidebar divider contrast for better layering.
  - Transformed graph controls into a floating glassmorphism panel.
  - Relocated the \"Logout\" action to the toolbar bottom and rebranded it as \"Exit\".

Fixes #21
Fixes #22
Fixes #23

Reviewed-on: #25
Co-authored-by: Marek Jasiński <jasins.marek@gmail.com>
Co-committed-by: Marek Jasiński <jasins.marek@gmail.com>
This commit was merged in pull request #25.
This commit is contained in:
2026-05-09 09:36:23 +00:00
committed by Marek Jaisński
parent 9e77aee231
commit 34794db209
11 changed files with 144 additions and 47 deletions
@@ -38,9 +38,18 @@ public class UpdateReadingProgressCommandHandler : IRequestHandler<UpdateReading
await context.SaveChangesAsync(cancellationToken);
// Broadcast to other devices
await _hubContext.Clients
.Group($"User_{request.UserId}")
.SendAsync("ProgressUpdated", request.PageId, now, cancellationToken);
var group = _hubContext.Clients.Group($"User_{request.UserId}");
if (!string.IsNullOrEmpty(request.ExcludedConnectionId))
{
await _hubContext.Clients
.GroupExcept($"User_{request.UserId}", request.ExcludedConnectionId)
.SendAsync("ProgressUpdated", request.PageId, now, cancellationToken);
}
else
{
await group.SendAsync("ProgressUpdated", request.PageId, now, cancellationToken);
}
return Result.Ok();
}
@@ -20,7 +20,7 @@ public class SyncHub : Hub
var userId = Context.UserIdentifier;
if (!string.IsNullOrEmpty(userId))
{
await _mediator.Send(new UpdateReadingProgressCommand(pageId, userId));
await _mediator.Send(new UpdateReadingProgressCommand(pageId, userId, Context.ConnectionId));
}
}
@@ -4,6 +4,8 @@ public static class PromptRegistry
{
public const string KnowledgeExtractionSystemPrompt =
"You are an expert educator. Analyze the provided text to extract key concepts, generate relevant quizzes, and construct a knowledge graph. " +
"CRITICAL: Restrict 'concept.label' to a maximum of 3 words (e.g., 'Dependency Injection' instead of full sentences). " +
"CRITICAL: Extract a MAXIMUM of 15 key concepts/plot points from the text. " +
"CRITICAL: Return ONLY a minified JSON object. Do NOT include markdown formatting like ```json or ```. Do NOT include explanations. " +
"Schema: { " +
"\"concepts\": [ { \"title\": \"string\", \"description\": \"string\" } ], " +
@@ -13,11 +15,15 @@ public static class PromptRegistry
public const string GraphExtractionPrompt =
"You are an expert at information architecture. Extract key concepts and their relationships from the text to build a knowledge graph. " +
"CRITICAL: Each paragraph in the user text starts with [ID: some-id]. You MUST use these exact IDs as the 'id' for the nodes representing those blocks. " +
"CRITICAL: Restrict 'label' to a maximum of 3 words. " +
"CRITICAL: Extract a MAXIMUM of 15 key concepts/plot points and their relationships. " +
"CRITICAL: Each paragraph in the user text starts with [ID: some-id]. Use these IDs ONLY for nodes representing the blocks. " +
"CRITICAL: All other extracted 'concept' nodes MUST have unique, slug-style IDs based on their labels (e.g., 'dependency-injection'). " +
"Include a 'current' node representing the block content itself if applicable. " +
"CRITICAL: Limit the result to a MAXIMUM of 15 most relevant connections. " +
"Return ONLY minified JSON. Schema: { \"graph\": { \"nodes\": [ { \"id\": \"string\", \"label\": \"string\", \"group\": \"concept|current\" } ], \"links\": [ { \"source\": \"string\", \"target\": \"string\", \"value\": 1 } ] } }";
public const string SummaryAndQuizPrompt =
"You are an expert educator. Provide a concise summary of the text and generate a challenging quiz (3-5 questions). " +
"Return ONLY minified JSON. Schema: { \"summary\": \"string\", \"quizzes\": [ { \"question\": \"string\", \"options\": [ \"string\" ], \"correct_index\": 0 } ] }";