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.
|
||||
|
||||
|
||||
## 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]]
|
||||
- [[Authentication]]
|
||||
- [[Impersonation]]
|
||||
- [[Data-Model]]
|
||||
- [[Pipeline]]
|
||||
- [[Player]]
|
||||
|
||||
**Reference**
|
||||
- [[API-Surface]]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue