Create Architecture wiki page for fractafrag
parent
73d2dbe30d
commit
5ce2131802
1 changed files with 220 additions and 0 deletions
220
Architecture.md
Normal file
220
Architecture.md
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
# Architecture
|
||||
|
||||
| Meta | Value |
|
||||
|------|-------|
|
||||
| **Repo** | `xpltdco/fractafrag` |
|
||||
| **Page** | `Architecture` |
|
||||
| **Audience** | developers, agents, newcomers |
|
||||
| **Last Updated** | 2026-04-04 |
|
||||
| **Status** | current |
|
||||
|
||||
## System Overview
|
||||
|
||||
Fractafrag is a multi-service application orchestrated by Docker Compose. Eight containers work together: nginx routes traffic, the FastAPI backend handles business logic, a React SPA provides the UI, Celery workers process async jobs, a headless Chromium renderer captures shader thumbnails, an MCP server exposes tools for AI agents, and PostgreSQL + Redis provide persistence and caching.
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
Internet["Internet / Browser"]
|
||||
Nginx["nginx :80/443"]
|
||||
Frontend["Frontend :5173<br/>(React + Vite)"]
|
||||
API["API :8000<br/>(FastAPI)"]
|
||||
MCP["MCP Server :3200<br/>(FastMCP)"]
|
||||
Worker["Celery Worker"]
|
||||
Renderer["Renderer :3100<br/>(Puppeteer + Chromium)"]
|
||||
Postgres["PostgreSQL :5432<br/>(pgvector)"]
|
||||
Redis["Redis :6379"]
|
||||
Renders["Render Output<br/>(/renders volume)"]
|
||||
|
||||
Internet -->|"HTTP/WS"| Nginx
|
||||
Nginx -->|"/"| Frontend
|
||||
Nginx -->|"/api/*"| API
|
||||
Nginx -->|"/mcp/*"| MCP
|
||||
Nginx -->|"/renders/*"| Renders
|
||||
|
||||
API --> Postgres
|
||||
API --> Redis
|
||||
Worker --> Postgres
|
||||
Worker --> Redis
|
||||
Worker -->|"POST /render"| Renderer
|
||||
Renderer --> Renders
|
||||
MCP -->|"internal API"| API
|
||||
```
|
||||
|
||||
## Service Topology
|
||||
|
||||
| Service | Image/Base | Port | Purpose |
|
||||
|---------|-----------|------|---------|
|
||||
| **nginx** | nginx:alpine | 80 | Reverse proxy, static render serving |
|
||||
| **frontend** | node:20-alpine | 5173 | React SPA (Vite dev/static prod) |
|
||||
| **api** | python:3.12-slim | 8000 | FastAPI REST API |
|
||||
| **mcp** | python:3.12-slim | 3200 | AI agent MCP interface (HTTP+SSE) |
|
||||
| **renderer** | node:20-slim + Chromium | 3100 | Headless shader rendering |
|
||||
| **worker** | python:3.12-slim (reuses api image) | — | Celery async task processing |
|
||||
| **postgres** | pgvector/pgvector:pg16 | 5432 | Primary database with vector search |
|
||||
| **redis** | redis:7-alpine | 6379 | Cache, job queue, token blocklist |
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Layer | Technology | Purpose |
|
||||
|-------|-----------|---------|
|
||||
| HTTP Proxy | nginx | Routing, TLS termination, static files |
|
||||
| Frontend | React 18, Vite, Three.js | SPA with WebGL shader preview |
|
||||
| Styling | Tailwind CSS | Utility-first CSS framework |
|
||||
| Client State | Zustand | Lightweight state management |
|
||||
| Server State | TanStack Query 5 | API caching and background refetch |
|
||||
| Backend | FastAPI + Uvicorn | Async ASGI web framework |
|
||||
| ORM | SQLAlchemy 2 (async) | Type-safe database access |
|
||||
| Database | PostgreSQL 16 + pgvector | Relational + vector similarity search |
|
||||
| Migrations | Alembic | Database schema versioning |
|
||||
| Task Queue | Celery + Redis | Distributed async job processing |
|
||||
| Auth | JWT (python-jose) + bcrypt | Token-based authentication |
|
||||
| Payments | Stripe SDK | Subscriptions and payouts (planned) |
|
||||
| AI Interface | FastMCP | MCP server for external AI agents |
|
||||
| Rendering | Puppeteer Core + Chromium | Headless shader screenshot capture |
|
||||
| Embeddings | scikit-learn (TF-IDF + SVD) | Text-to-vector for desire clustering |
|
||||
| Vector Search | pgvector (HNSW) | Cosine similarity for recommendations |
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
fractafrag/
|
||||
├── db/
|
||||
│ └── init.sql # PostgreSQL bootstrap (schema + extensions + indexes)
|
||||
├── scripts/
|
||||
│ └── seed.py # Sample data seeding (WIP)
|
||||
├── services/
|
||||
│ ├── api/ # FastAPI backend
|
||||
│ │ ├── Dockerfile
|
||||
│ │ ├── pyproject.toml # Python dependencies
|
||||
│ │ └── app/
|
||||
│ │ ├── main.py # FastAPI app setup, lifespan, CORS
|
||||
│ │ ├── config.py # Pydantic Settings (env vars)
|
||||
│ │ ├── database.py # Async SQLAlchemy engine + sessions
|
||||
│ │ ├── redis.py # Async Redis client singleton
|
||||
│ │ ├── models/
|
||||
│ │ │ └── models.py # All SQLAlchemy ORM models
|
||||
│ │ ├── schemas/
|
||||
│ │ │ └── schemas.py # Pydantic request/response schemas
|
||||
│ │ ├── middleware/
|
||||
│ │ │ ├── auth.py # JWT auth, password hashing, dependencies
|
||||
│ │ │ └── rate_limit.py # Redis-based rate limiting
|
||||
│ │ ├── routers/
|
||||
│ │ │ ├── auth.py # Register, login, refresh, logout
|
||||
│ │ │ ├── shaders.py # CRUD, versioning, fork, search
|
||||
│ │ │ ├── feed.py # Personalized feed, trending, similar
|
||||
│ │ │ ├── votes.py # Upvote/downvote, hot score
|
||||
│ │ │ ├── desires.py # Bounty board
|
||||
│ │ │ ├── generate.py # AI generation (stub)
|
||||
│ │ │ ├── users.py # Profile, BYOK keys
|
||||
│ │ │ ├── payments.py # Stripe integration (stub)
|
||||
│ │ │ ├── mcp_keys.py # API key management
|
||||
│ │ │ └── health.py # Liveness check
|
||||
│ │ ├── services/
|
||||
│ │ │ ├── embedding.py # TF-IDF + SVD vectorizer (512-dim)
|
||||
│ │ │ ├── clustering.py # Desire clustering via pgvector
|
||||
│ │ │ ├── glsl_validator.py # Static GLSL syntax validation
|
||||
│ │ │ ├── renderer_client.py # HTTP client to renderer service
|
||||
│ │ │ └── byok.py # BYOK key encryption
|
||||
│ │ └── worker/
|
||||
│ │ └── __init__.py # Celery app + task definitions
|
||||
│ ├── frontend/ # React SPA
|
||||
│ │ ├── Dockerfile
|
||||
│ │ ├── package.json
|
||||
│ │ ├── vite.config.ts
|
||||
│ │ ├── tailwind.config.js
|
||||
│ │ └── src/
|
||||
│ │ ├── main.tsx # React entry point
|
||||
│ │ ├── stores/ # Zustand state (auth)
|
||||
│ │ ├── pages/ # Route pages (Feed, Editor, Explore, etc.)
|
||||
│ │ ├── components/ # ShaderCanvas, Navbar, Layout
|
||||
│ │ └── ...
|
||||
│ ├── mcp/ # MCP server
|
||||
│ │ ├── Dockerfile
|
||||
│ │ ├── requirements.txt
|
||||
│ │ └── server.py # FastMCP tools + resources
|
||||
│ ├── renderer/ # Headless rendering
|
||||
│ │ ├── Dockerfile
|
||||
│ │ ├── package.json
|
||||
│ │ └── server.js # Express + Puppeteer rendering pipeline
|
||||
│ └── nginx/
|
||||
│ └── conf/
|
||||
│ └── default.conf # Proxy routing rules
|
||||
├── docker-compose.yml # Production compose
|
||||
├── docker-compose.override.yml # Dev overrides (volume mounts, hot reload)
|
||||
├── docker-compose.dev.yml # Data stores only (for local dev outside Docker)
|
||||
├── Makefile # Developer convenience commands
|
||||
├── .env.example # Environment template
|
||||
└── .forgejo/workflows/ci.yml # CI pipeline
|
||||
```
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
### Microservices in a Monorepo
|
||||
All services live in one repo under `services/`. Docker Compose orchestrates them. This gives microservice isolation (separate runtimes, independent scaling) with monorepo convenience (single clone, shared CI, atomic changes).
|
||||
|
||||
### pgvector for Similarity Search
|
||||
Instead of a dedicated vector database (Pinecone, Weaviate), Fractafrag uses pgvector as a PostgreSQL extension. This keeps everything in one database, simplifies backups, and avoids an additional service. HNSW indexes provide fast approximate nearest-neighbor search for taste vectors, style vectors, and desire embeddings.
|
||||
|
||||
### Headless Chromium for Rendering
|
||||
Shader thumbnails are generated by a real browser (Chromium via Puppeteer) rather than a lightweight WebGL library. This guarantees pixel-perfect rendering matching what users see in their browsers. The tradeoff is higher resource usage (512MB shared memory) and slower rendering.
|
||||
|
||||
### Celery over In-Process Jobs
|
||||
Unlike Tubearr's in-process queue, Fractafrag uses Celery with Redis as a proper distributed task queue. This allows the API to remain responsive while rendering, embedding, and AI generation happen asynchronously. The worker runs as a separate container with independent scaling.
|
||||
|
||||
### JWT with Redis Blocklist
|
||||
Refresh tokens are stateless JWTs but validated against a Redis blocklist on each use. This provides immediate token revocation without a database round-trip, while keeping the auth flow mostly stateless.
|
||||
|
||||
### Immutable Shader Versions
|
||||
Every shader update creates a new version snapshot in the `shader_versions` table. This provides full history without git-level complexity, enables version restoration, and supports future diff/compare features.
|
||||
|
||||
### TF-IDF + SVD Embeddings
|
||||
Desire prompts are embedded using a custom TF-IDF + TruncatedSVD pipeline trained on a shader/visual-art domain corpus. This avoids external embedding API calls while providing domain-relevant 512-dimensional vectors for cosine similarity clustering.
|
||||
|
||||
## Request Lifecycle
|
||||
|
||||
### API Request
|
||||
```
|
||||
Browser → nginx:80 → /api/* → api:8000
|
||||
→ CORS middleware
|
||||
→ Auth dependency (JWT validation)
|
||||
→ Router handler → SQLAlchemy async → PostgreSQL
|
||||
→ Pydantic serialization → JSON response
|
||||
```
|
||||
|
||||
### Shader Publish Flow
|
||||
```
|
||||
User submits GLSL → POST /api/v1/shaders
|
||||
→ GLSL validator (static analysis)
|
||||
→ Free-tier rate limit check (5/month)
|
||||
→ Create Shader + ShaderVersion v1 in DB
|
||||
→ Enqueue render_shader Celery task
|
||||
→ Return shader (render_status=pending)
|
||||
|
||||
Worker picks up task:
|
||||
→ POST /render to renderer:3100 with GLSL
|
||||
→ Chromium renders shader, captures screenshots
|
||||
→ Store thumbnail_url + preview_url
|
||||
→ Update shader render_status → "ready"
|
||||
```
|
||||
|
||||
### Feed Personalization
|
||||
```
|
||||
GET /api/v1/feed (authenticated)
|
||||
→ Over-fetch 2-3x candidates from DB (published, rendered)
|
||||
→ Build tag affinity from user's votes + dwell events
|
||||
→ Score each candidate: 0.5*score + 0.2*recency + 0.2*tag_affinity + 0.1*random
|
||||
→ Sort, slice top N
|
||||
→ Return ShaderFeedItem list
|
||||
```
|
||||
|
||||
### Desire Processing
|
||||
```
|
||||
POST /api/v1/desires → Create desire row → Enqueue process_desire task
|
||||
|
||||
Worker:
|
||||
→ Embed prompt text (TF-IDF + SVD → 512-dim vector)
|
||||
→ pgvector cosine search: find nearest cluster (threshold 0.82)
|
||||
→ If match: join cluster, recalculate heat_score = cluster_size
|
||||
→ If no match: create new cluster with this desire
|
||||
→ Update desire row with embedding + cluster info
|
||||
```
|
||||
Loading…
Add table
Reference in a new issue