docs: add Player, Impersonation pages; update LightRAG eval results (M020/S07)
parent
e0a52757b0
commit
4f3de1b1c7
4 changed files with 249 additions and 1 deletions
|
|
@ -154,6 +154,42 @@ LightRAG is a graph-based RAG (Retrieval-Augmented Generation) service added to
|
||||||
|
|
||||||
LightRAG provides graph-structured knowledge retrieval as a complement to Qdrant's vector similarity search. It runs as a standalone service within the Docker Compose stack, sharing Qdrant and Ollama with the main application.
|
LightRAG provides graph-structured knowledge retrieval as a complement to Qdrant's vector similarity search. It runs as a standalone service within the Docker Compose stack, sharing Qdrant and Ollama with the main application.
|
||||||
|
|
||||||
|
|
||||||
|
## Admin Impersonation
|
||||||
|
|
||||||
|
Admins can view the site as any creator. Full details on the dedicated [[Impersonation]] page.
|
||||||
|
|
||||||
|
- **Mechanism:** Impersonation JWT with `original_user_id` claim and 1h expiry
|
||||||
|
- **Safety:** Write endpoints blocked via `reject_impersonation` dependency
|
||||||
|
- **Audit:** All start/stop actions logged to `impersonation_log` table with admin ID, target ID, IP, timestamp
|
||||||
|
### Evaluation Results (M020/S05)
|
||||||
|
|
||||||
|
A/B comparison of 25 queries (13 real user queries, 12 curated) against Qdrant search and LightRAG hybrid mode:
|
||||||
|
|
||||||
|
- **LightRAG wins:** 23/25 queries on relevance scoring
|
||||||
|
- **Qdrant wins:** 2/25 (short query rejected by LightRAG, ambiguous incomplete query)
|
||||||
|
- **Avg relevance:** Qdrant 2.09/5, LightRAG 4.52/5
|
||||||
|
- **Avg latency:** Qdrant 99ms, LightRAG 86s
|
||||||
|
|
||||||
|
**Key finding:** LightRAG is a RAG system producing synthesized multi-paragraph answers, not a search engine. The two serve different interaction patterns.
|
||||||
|
|
||||||
|
### Recommended Hybrid Routing
|
||||||
|
|
||||||
|
- **Autocomplete / typeahead** → Qdrant (sub-100ms required)
|
||||||
|
- **Search results list** → Qdrant (users expect instant ranked results)
|
||||||
|
- **"Ask a question" / chat** → LightRAG (synthesized answers are the core value)
|
||||||
|
- **Deep-dive / explore** → Both (LightRAG answer + Qdrant related pages sidebar)
|
||||||
|
|
||||||
|
### Creator-Scoped Retrieval
|
||||||
|
|
||||||
|
LightRAG has no metadata-based filtering. Creator scoping uses `ll_keywords` to bias retrieval toward a specific creator name (soft bias, not hard filter). Each document's text includes structured provenance: Creator name, Creator ID, Source Videos, and per-key-moment source attribution.
|
||||||
|
|
||||||
|
Query utility: `backend/scripts/lightrag_query.py --query "snare design" --creator "COPYCATT"`
|
||||||
|
|
||||||
|
### Data Coverage
|
||||||
|
|
||||||
|
LightRAG reindex in progress. Entity types configured: Creator, Technique, Plugin, Synthesizer, Effect, Genre, DAW, SamplePack, SignalChain, Concept, Frequency, SoundDesignElement.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*See also: [[Architecture]], [[API-Surface]], [[Data-Model]]*
|
*See also: [[Architecture]], [[API-Surface]], [[Data-Model]], [[Impersonation]], [[Player]]*
|
||||||
|
|
|
||||||
124
Impersonation.md
Normal file
124
Impersonation.md
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
# Admin Impersonation
|
||||||
|
|
||||||
|
Admins can view the site as any creator to debug permission issues, verify consent settings, and test the creator experience. Added in M020/S04.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
Admin clicks "View As" on /admin/users
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
POST /api/v1/admin/impersonate/{user_id}
|
||||||
|
│
|
||||||
|
├─ Verify caller is admin
|
||||||
|
├─ Create impersonation JWT (1h expiry, original_user_id claim)
|
||||||
|
├─ Log to impersonation_log table
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Frontend swaps JWT, stashes admin token in sessionStorage
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Amber banner: "Viewing as {Creator Name} — Exit"
|
||||||
|
All API calls use target user's identity
|
||||||
|
Write endpoints blocked by reject_impersonation guard
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Click "Exit" → POST /api/v1/admin/impersonate/stop
|
||||||
|
│
|
||||||
|
├─ Log stop action to impersonation_log
|
||||||
|
├─ Restore admin token from sessionStorage
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Back to normal admin session
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Model
|
||||||
|
|
||||||
|
- **Read-only:** Write endpoints are guarded by `reject_impersonation` FastAPI dependency. Returns 403 if the current token has `original_user_id` claim
|
||||||
|
- **Short-lived:** Impersonation tokens expire in 1 hour (vs 24h for normal tokens)
|
||||||
|
- **Transparent:** The impersonation JWT uses `sub=target_user_id`, so existing `get_current_user` loads the target user transparently. No special-casing in endpoint handlers
|
||||||
|
- **Admin-only:** Only users with `admin` role can call the impersonate endpoint
|
||||||
|
|
||||||
|
## Audit Trail
|
||||||
|
|
||||||
|
Every impersonation action is logged to the `impersonation_log` table:
|
||||||
|
|
||||||
|
- `admin_user_id` — Who initiated
|
||||||
|
- `target_user_id` — Who is being impersonated
|
||||||
|
- `action` — "start" or "stop"
|
||||||
|
- `ip_address` — Client IP
|
||||||
|
- `created_at` — Timestamp
|
||||||
|
|
||||||
|
Indexes on `admin_user_id` and `target_user_id` for efficient querying.
|
||||||
|
|
||||||
|
## Token Structure
|
||||||
|
|
||||||
|
Normal JWT:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sub": "user-uuid",
|
||||||
|
"role": "admin",
|
||||||
|
"iat": 1234567890,
|
||||||
|
"exp": 1234654290
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Impersonation JWT:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"sub": "target-user-uuid",
|
||||||
|
"role": "creator",
|
||||||
|
"original_user_id": "admin-user-uuid",
|
||||||
|
"iat": 1234567890,
|
||||||
|
"exp": 1234571490
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `original_user_id` claim is what `reject_impersonation` checks.
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
- `GET /api/v1/admin/users` — List all users (admin only)
|
||||||
|
- `POST /api/v1/admin/impersonate/{user_id}` — Start impersonation, returns new JWT
|
||||||
|
- `POST /api/v1/admin/impersonate/stop` — Stop impersonation, logs action
|
||||||
|
|
||||||
|
## Frontend Integration
|
||||||
|
|
||||||
|
### AuthContext Extensions
|
||||||
|
|
||||||
|
- `startImpersonation(userId)` — Calls impersonate API, saves current admin token to `sessionStorage`, swaps to impersonation token
|
||||||
|
- `exitImpersonation()` — Calls stop API, restores admin token from `sessionStorage`
|
||||||
|
- `user.impersonating` (boolean) — True when viewing as another user
|
||||||
|
|
||||||
|
### ImpersonationBanner
|
||||||
|
|
||||||
|
Fixed amber bar at page top when impersonating. Shows "Viewing as {name}" with Exit button. Rendered in `AppShell` when `user.impersonating` is true.
|
||||||
|
|
||||||
|
### AdminUsers Page
|
||||||
|
|
||||||
|
Route: `/admin/users`. Table of all users with "View As" buttons for creator-role users. Code-split with `React.lazy`.
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
- `backend/auth.py` — `create_impersonation_token()`, `reject_impersonation` dependency
|
||||||
|
- `backend/routers/admin.py` — Impersonate/stop endpoints, user list
|
||||||
|
- `backend/models.py` — `ImpersonationLog` model
|
||||||
|
- `alembic/versions/018_add_impersonation_log.py` — Migration
|
||||||
|
- `frontend/src/context/AuthContext.tsx` — Impersonation state management
|
||||||
|
- `frontend/src/components/ImpersonationBanner.tsx` — Amber warning banner
|
||||||
|
- `frontend/src/pages/AdminUsers.tsx` — User management page
|
||||||
|
|
||||||
|
## Database
|
||||||
|
|
||||||
|
**Table: `impersonation_log`** (Migration 018)
|
||||||
|
|
||||||
|
- `id` UUID PK
|
||||||
|
- `admin_user_id` UUID FK → users
|
||||||
|
- `target_user_id` UUID FK → users
|
||||||
|
- `action` VARCHAR (start/stop)
|
||||||
|
- `ip_address` VARCHAR
|
||||||
|
- `created_at` TIMESTAMP
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*See also: [[Authentication]], [[API-Surface]]*
|
||||||
86
Player.md
Normal file
86
Player.md
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
# Web Media Player
|
||||||
|
|
||||||
|
Custom video player with HLS playback, synchronized transcript sidebar, and keyboard shortcuts. Added in M020/S01.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────┐
|
||||||
|
│ WatchPage (/watch/:videoId) │
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────────┐ ┌────────────────────────────┐ │
|
||||||
|
│ │ VideoPlayer │ │ TranscriptSidebar │ │
|
||||||
|
│ │ ┌────────────────┐ │ │ Segments sorted by time │ │
|
||||||
|
│ │ │ <video> elem │ │ │ Binary search for active │ │
|
||||||
|
│ │ └────────────────┘ │ │ Auto-scroll on playback │ │
|
||||||
|
│ │ PlayerControls │ │ Click → seek to timestamp │ │
|
||||||
|
│ └──────────┬───────────┘ └──────────┬─────────────────┘ │
|
||||||
|
│ │ useMediaSync() │ │
|
||||||
|
│ └──────────────────────────┘ │
|
||||||
|
└──────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Video Source Detection
|
||||||
|
|
||||||
|
Three-path fallback for maximum browser compatibility:
|
||||||
|
|
||||||
|
1. **HLS via hls.js** — Lazy-loaded (dynamic import). Used when `Hls.isSupported()` returns true (Chrome, Firefox, Edge)
|
||||||
|
2. **Safari native HLS** — `<video>` element handles `.m3u8` natively on Safari/iOS
|
||||||
|
3. **Direct MP4 fallback** — Plain `<video src="...">` for non-HLS sources
|
||||||
|
|
||||||
|
hls.js is loaded only when needed (~200KB), keeping the main bundle small.
|
||||||
|
|
||||||
|
## Synchronized Transcript
|
||||||
|
|
||||||
|
The `TranscriptSidebar` component displays timestamped transcript segments synchronized to video playback:
|
||||||
|
|
||||||
|
- **Active segment detection:** Binary search (O(log n)) on sorted segments by `start_time`
|
||||||
|
- **Auto-scroll:** Active segment scrolls into view with `scrollIntoView({ block: "center" })`
|
||||||
|
- **Click to seek:** Each segment is a `<button>` for keyboard accessibility. Click seeks the video to that timestamp
|
||||||
|
- **Non-blocking:** Transcript fetch failure doesn't break the player — degrades gracefully
|
||||||
|
|
||||||
|
### useMediaSync Hook
|
||||||
|
|
||||||
|
Shared playback state between VideoPlayer and TranscriptSidebar:
|
||||||
|
|
||||||
|
- `currentTime` (number) — Current playback position in seconds
|
||||||
|
- `duration` (number) — Total video duration
|
||||||
|
- `isPlaying` (boolean) — Playback state
|
||||||
|
- `seekTo(time)` (function) — Seek video to specific time
|
||||||
|
|
||||||
|
## Keyboard Shortcuts
|
||||||
|
|
||||||
|
- **Space** — Play/pause
|
||||||
|
- **← / →** — Seek ±5 seconds
|
||||||
|
- **Shift+← / Shift+→** — Seek ±10 seconds
|
||||||
|
- **↑ / ↓** — Volume ±10%
|
||||||
|
- **M** — Mute/unmute
|
||||||
|
- **F** — Toggle fullscreen
|
||||||
|
- **1-9** — Seek to 10%-90%
|
||||||
|
|
||||||
|
## Speed Controls
|
||||||
|
|
||||||
|
Playback rate options: 0.5x, 0.75x, 1x, 1.25x, 1.5x, 2x.
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
- `GET /api/v1/videos/{id}` — Video metadata with creator info (`SourceVideoDetail`)
|
||||||
|
- `GET /api/v1/videos/{id}/transcript` — Ordered transcript segments (`TranscriptForPlayerResponse`)
|
||||||
|
|
||||||
|
## Integration Points
|
||||||
|
|
||||||
|
- **TechniquePage:** Key moment timestamps are clickable links to `/watch/:videoId?t=<seconds>`
|
||||||
|
- **Code-splitting:** WatchPage is lazy-loaded via `React.lazy` + `Suspense`
|
||||||
|
|
||||||
|
## Key Files
|
||||||
|
|
||||||
|
- `frontend/src/pages/WatchPage.tsx` — Page component
|
||||||
|
- `frontend/src/components/VideoPlayer.tsx` — Video element + HLS setup
|
||||||
|
- `frontend/src/components/PlayerControls.tsx` — Play/pause, speed, volume, seek bar
|
||||||
|
- `frontend/src/components/TranscriptSidebar.tsx` — Synchronized transcript display
|
||||||
|
- `frontend/src/hooks/useMediaSync.ts` — Shared playback state hook
|
||||||
|
- `backend/routers/videos.py` — Video detail + transcript API
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*See also: [[Architecture]], [[API-Surface]], [[Frontend]]*
|
||||||
|
|
@ -5,8 +5,10 @@
|
||||||
**Architecture**
|
**Architecture**
|
||||||
- [[Architecture]]
|
- [[Architecture]]
|
||||||
- [[Authentication]]
|
- [[Authentication]]
|
||||||
|
- [[Impersonation]]
|
||||||
- [[Data-Model]]
|
- [[Data-Model]]
|
||||||
- [[Pipeline]]
|
- [[Pipeline]]
|
||||||
|
- [[Player]]
|
||||||
|
|
||||||
**Reference**
|
**Reference**
|
||||||
- [[API-Surface]]
|
- [[API-Surface]]
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue