Skip to content

Latest commit

Β 

History

296 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

FrameIQ

FrameIQ

A cinematic social movie & TV platform with a multi-agent AI assistant.
Track, discover, and discuss what you watch β€” powered by LangGraph, Flask, and TMDb.

Live Demo CI/CD License Python Flask LangGraph Stars


🎬 What is FrameIQ?

FrameIQ is a Letterboxd-inspired social platform for film and TV enthusiasts, elevated with a production-grade multi-agent AI assistant (CineBot) that understands natural language, recommends by vibe, and streams its reasoning in real-time.

Built for people who care about what they watch β€” and want an intelligent companion to help them discover more.

Track everything you watch Discover by mood, genre, era Discuss with friends & community
Watch inline with resume Analyze your taste DNA Chat with an AI film expert

✨ Features

πŸ“š Library & Tracking

  • Watchlist with priority tiers (High / Medium / Low)
  • Diary β€” chronological log with dates, ratings, notes
  • Viewed library with rich filtering
  • Star ratings (½–5β˜…) + written reviews with markdown
  • Custom lists β€” public, private, collaborative
  • Tags with autocomplete & trending suggestions

πŸ“Ί TV Show Tracking (First-Class)

  • Episode-by-episode progress with timestamps
  • Batch season operations β€” mark entire seasons watched
  • Smart completion β€” only marks Completed when TMDb confirms series ended
  • Upcoming Episodes calendar β€” 60-day view, auto-synced daily
  • Continue Watching row with resume points

πŸ‘₯ Social Layer

  • Follow / unfollow with activity feeds (Following / Global / Personal)
  • Friends' activity on every title page
  • Popular With Friends algorithmic ranking
  • Review likes, comments, helpful votes
  • Suggested follows based on taste overlap

▢️ Inline Watching

  • Stream movies & episodes directly β€” no redirects
  • Resume playback with per-title progress persistence
  • Auto-logs diary entry at 85% completion
  • Full watch history with timestamps

πŸ” Discovery & Search

  • TMDb-powered filters: genre, year, language, rating, provider
  • Trending movies & shows (daily/weekly)
  • Entertainment news feed (NewsAPI)
  • Semantic similarity β€” "movies that feel like a rainy Sunday"

πŸ€– CineBot β€” Multi-Agent AI Assistant

Architecture: LangGraph pipeline
User message
     β”‚
     β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Supervisor β”‚ ← zero-LLM heuristic router (saves 2–3 API calls)
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚
   β”Œβ”€β”€β”€β”΄β”€β”€β”€β”
   β–Ό       β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”
β”‚Retrieverβ”‚ β”‚ Chatβ”‚ ← Retriever: 7 TMDb/vector tools
β”‚        β”‚ β”‚     β”‚    Chat: open-ended film knowledge
β””β”€β”€β”€β”¬β”€β”€β”€β”˜ β””β”€β”€β”¬β”€β”€β”˜
    β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜
         β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Enricher β”‚ ← concurrent TMDb poster & metadata fetch
    β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
         β–Ό
    SSE stream β†’ browser (markdown + poster cards)
Agent Role Model
Supervisor Route + extract entities (titles, people, genres, years) gpt-4.1-mini (structured)
Retriever ReAct agent with 7 cached tools gpt-4.1-mini
Chat General film knowledge, recommendations gpt-5-mini
Enricher Extract titles from reply β†’ fetch posters/meta gpt-4.1-mini
  • Streaming responses via SSE β€” see each tool call as it happens
  • Poster & metadata cards injected inline in replies
  • Conversation memory persisted (SQLite checkpointing, survives restarts)
  • User personalization β€” ratings, favorite genres, TV progress, watchlist injected per request
  • Command palette (⌘K) + Slide-over panel (⌘J) for keyboard-first UX
  • Production-hardened streaming: single process event loop owns the AsyncSqliteSaver checkpointer (no cross-loop use); graph + checkpoint verified before headers commit or the API returns 503 JSON (mid-stream failures arrive as SSE error events, never a status flip); per-stream checkpoint threads so concurrent chats can't collide
  • Limits: 5 questions per user per UTC day, 20/min + 100/hour per IP β€” the UI shows remaining quota live via X-Chat-Remaining headers

πŸ“Š Personal Analytics

  • Watch time, genre breakdown, top directors/actors
  • Year-in-review with interactive Chart.js visualizations
  • Taste DNA β€” genre affinity bars with gradient fills
  • Taste Match badges on every title page

πŸ— Tech Stack

Layer Technology
Backend Flask 3.1, SQLAlchemy 2.0, PostgreSQL 16
AI / Agents LangGraph, LangChain, OpenAI (gpt-4.1-mini / gpt-5-mini)
Auth Google OAuth 2.0 (Authlib) + Flask-Login + Flask-WTF CSRF
Frontend Jinja2, Tailwind CSS, Vanilla JS (ESM), Chart.js, Lucide icons
Media APIs TMDb, Cloudinary (avatars), NewsAPI
Streaming Rive, VidKing, Vidy, 1Embed providers
Infra Docker Compose, systemd-nginx (reverse proxy), GitHub Actions
Observability Langfuse-ready, structured logging

🎨 Design System

Monochrome Marquee β€” true black (#000), white type, single amber accent (#F6B73C)

  • Display: Archivo Expanded (variable width)
  • Body: Inter
  • Metadata: JetBrains Mono
  • Motion: cubic-bezier(0.16, 1, 0.3, 1) β€” 180ms/320ms
  • Radii: 4 / 6 / 10px β€” sharp, broadcast-grade
  • Film grain overlay + projector beam/dust atmosphere

All 47 templates unified on a single design token system. Zero Poppins, zero indigo/violet, zero unused dependencies.


πŸš€ Quick Start

Prerequisites

Local Development

# 1. Clone & enter
git clone https://github.com/RobinMillford/FrameIQ.git
cd FrameIQ

# 2. Virtual environment
python -m venv .venv && source .venv/bin/activate

# 3. Dependencies (uv recommended)
uv sync
# or: pip install -r requirements.txt

# 4. Environment
cp .env.example .env
# Edit .env with your keys

# 5. Run
python app.py          # http://localhost:5000

Run Tests

pytest tests/ -v        # 143 tests, ~45s
uv run flake8 .         # lint (max-line 127, complexity 10)

Docker (Production Stack)

cp .env.example .env    # production values
make deploy             # pull + clean build + start (run from /opt/frameiq on the VPS)

Stack: web (Gunicorn 2 workers Γ— 4 threads, Python 3.11-slim) + db (Postgres 16-alpine). TLS terminates at the host nginx (ports 80/443, certbot); Postgres is bound to loopback only β€” never exposed to the internet.

make status    # container status + /agent_health check
make logs      # tail web logs (logs-db for Postgres)
make restart   # restart web container
make backup    # pg_dump snapshot via scripts/backup.sh
make db-shell  # psql into the production DB
make migrate   # create_all() for new tables

Schema-changing migrations (column/index changes)

The startup schema guard REQUIRES schema parity with the declared models and will fail Gunicorn workers if the database is behind β€” that guard is intentional and must not be weakened. New TABLES are created automatically at startup (db.create_all()); COLUMN/index changes are not, so run the matching migrates/ script BEFORE starting the new web image:

git pull origin main
docker compose build web
docker compose run --rm --no-deps web python migrates/<migration>.py
docker compose up -d
docker compose exec web python -m utils.schema_guard

docker compose run --rm --no-deps executes the migration against the shared database in a one-off container (removed on exit, no port bindings, no dependency restarts) β€” the migration scripts set SKIP_SCHEMA_GUARD=1 themselves, so the guard (enforced at application startup via ensure_schema_compatible()) stays active for normal application runs and never runs migrations for you. Never start web before its required schema exists; the guard failing fast is the system protecting you from a half-migrated deploy.

Chat memory (instance/chat_memory.db, WAL mode) persists on the chat_memory volume across restarts and rebuilds.


βš™οΈ Environment Variables

Copy .env.example β†’ .env and fill in.

Variable Required Description
SECRET_KEY βœ… Flask session secret (32+ chars)
DATABASE_URL βœ… (local dev) postgresql://user:pass@host:5432/db β€” auto-built from POSTGRES_* in Docker
TMDB_API_KEY βœ… TMDb API
OPENAI_API_KEY βœ… OpenAI API key (chat + embeddings)
GOOGLE_CLIENT_ID βœ… Google OAuth 2.0
GOOGLE_CLIENT_SECRET βœ… Google OAuth 2.0
CLOUDINARY_CLOUD_NAME βœ… Avatar uploads
CLOUDINARY_API_KEY βœ… Cloudinary
CLOUDINARY_API_SECRET βœ… Cloudinary
CLOUDINARY_URL β­• Cloudinary (alternative single-var config)
STREAM_PROVIDER β­• Default embed source: rive | vidking | vidy | oneembed
RATELIMIT_STORAGE_URI β­• Redis URI for multi-worker rate limits (defaults to memory)
POSTGRES_DB / POSTGRES_USER / POSTGRES_PASSWORD βœ… (Docker) Self-hosted DB credentials
POSTGRES_EXPOSED_PORT β­• Loopback-bound host port for the episode-sync workflow
WEB_WORKERS / WEB_THREADS β­• Gunicorn sizing (defaults 2 / 4 β€” do not raise blindly)
FLASK_DEBUG β­• Never true in production
MAIL_SERVER / MAIL_* β­• Password reset emails
RATELIMIT_STORAGE_URI β­• Redis for rate limiting (defaults to memory)

πŸ“ Project Structure

FrameIQ/
β”œβ”€β”€ app.py                      # Application factory (create_app)
β”œβ”€β”€ models/                     # SQLAlchemy models package (user, media, chat, …)
β”œβ”€β”€ extensions.py               # Flask extensions (limiter, mail, db)
β”œβ”€β”€ requirements.txt
β”‚
β”œβ”€β”€ routes/                     # 28 Flask blueprints (one per domain)
β”‚   β”œβ”€β”€ auth.py                 # Login, register, password reset
β”‚   β”œβ”€β”€ main.py                 # Home, search, news, tonights-pick
β”‚   β”œβ”€β”€ details.py              # Movie / TV detail pages
β”‚   β”œβ”€β”€ reviews.py              # Reviews & ratings
β”‚   β”œβ”€β”€ diary.py                # Watch diary
β”‚   β”œβ”€β”€ lists.py                # Custom lists (basic + advanced)
β”‚   β”œβ”€β”€ tv_tracking.py          # Episode & season tracking
β”‚   β”œβ”€β”€ chat.py                 # SSE streaming chat
β”‚   β”œβ”€β”€ social.py               # Follow, activity feed
β”‚   β”œβ”€β”€ stats.py                # Dashboard, year-in-review
β”‚   └── ...
β”‚
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ agents/                 # LangGraph multi-agent system
β”‚   β”‚   β”œβ”€β”€ graph.py            # StateGraph definition
β”‚   β”‚   β”œβ”€β”€ nodes.py            # Supervisor, Retriever, Chat, Enricher
β”‚   β”‚   β”œβ”€β”€ tools.py            # 7 LangChain tools (cached TMDb + history)
β”‚   β”‚   └── state.py            # GraphState schema
β”‚   └── api/
β”‚       β”œβ”€β”€ agent_service.py    # User context building, SSE streaming
β”‚       └── flask_integration.py
β”‚
β”œβ”€β”€ api/                        # Shared utilities
β”‚   β”œβ”€β”€ tmdb_client.py          # TMDb wrapper (compat shim)
β”‚   β”œβ”€β”€ tmdb/                   # TMDb package: cache, movies, tv, people, search
β”‚   β”œβ”€β”€ stream_providers.py     # Embed providers (Rive, VidKing, Vidy, 1Embed)
β”‚   └── chatbot.py              # LLM helpers
β”‚
β”œβ”€β”€ templates/                  # 47 Jinja2 templates (unified on base.html)
β”œβ”€β”€ static/
β”‚   β”œβ”€β”€ css/                    # tokens.css, chrome.css, detail.css, projector.css
β”‚   β”œβ”€β”€ js/                     # chat-page.js, chrome.js, projector-dust.js
β”‚   └── images/
β”‚
β”œβ”€β”€ scripts/                    # Ops (excluded from Docker)
β”‚   β”œβ”€β”€ sync_upcoming_episodes.py
β”‚   └── collect_media.py
β”‚
β”œβ”€β”€ migrates/                   # Manual schema migrations
β”œβ”€β”€ .github/workflows/          # ci-cd.yml, deploy.yml, sync-upcoming-episodes.yml
β”œβ”€β”€ nginx/nginx.conf            # Reverse proxy + SSL
β”œβ”€β”€ docker-compose.yml
β”œβ”€β”€ Dockerfile
└── Makefile

πŸ”„ CI/CD Pipeline

Workflow Trigger Actions
ci-cd.yml Push to main/develop pytest (143) + flake8
deploy.yml Push to main (after CI pass) SSH β†’ VPS β†’ make deploy
sync-upcoming-episodes.yml Daily 02:00 UTC Sync TMDb β†’ PostgreSQL

VPS: Self-hosted PostgreSQL 16, Docker Compose, system nginx, nightly pg_dump backups, certbot SSL.

Health: /health (app) and /agent_health (chat graph loaded?) β€” make status hits the latter.


πŸ§ͺ Testing & Quality

# Full suite (143 tests)
pytest tests/

# Smoke only
pytest tests/test_basic.py

# Models
pytest tests/test_models.py

# Single test
pytest -k "test_name"

Quality gates: 143 tests passing β€’ flake8 clean β€’ vulture clean β€’ 0 secrets β€’ 0 dead code


🀝 Contributing

We welcome contributions! Please read CLA.md before submitting a PR.

Good first issues:

  • 🌍 Add more languages/regions to TMDb discover tools
  • πŸ§ͺ Write missing unit tests in tests/
  • β™Ώ Improve accessibility (ARIA, keyboard nav)
  • πŸ“ˆ Add chart types to stats dashboard
  • 🎨 Extend design token system
# 1. Fork & clone
git clone https://github.com/YOUR-USERNAME/FrameIQ.git

# 2. Branch
git checkout -b feat/your-feature

# 3. Develop
# ... make changes ...

# 4. Verify
pytest tests/ && uv run flake8 .

# 5. Push & PR
git push origin feat/your-feature
# Open PR against main

πŸ“„ License

Licensed under GNU Affero General Public License v3.0 β€” see LICENSE.

Commercial use, SaaS hosting, or white-labelling requires a separate licence.
Contact: robinmill4d@gmail.com


πŸ™ Acknowledgements


Built with ❀️ by Robin Millford and contributors.
Star ⭐ the repo if you find it useful β€” it helps more people discover FrameIQ.

About

This is a Flask web application that allows users to get recommendations for movies and TV shows with Ai integrated based on genres and specific titles

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages