{"id":23582694,"url":"https://github.com/codehunt101/plotline-ai","last_synced_at":"2026-04-04T21:32:43.222Z","repository":{"id":269621143,"uuid":"907801646","full_name":"CodeHunt101/plotline-ai","owner":"CodeHunt101","description":"Movie recommendation app that helps groups find the perfect movie for their next watch party.","archived":false,"fork":false,"pushed_at":"2026-03-28T11:55:02.000Z","size":1044,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-28T15:36:47.518Z","etag":null,"topics":["daisyui","jest","langchain-js","nextjs","openai-api","reactjs","supabase-js","tailwindcss","typescript"],"latest_commit_sha":null,"homepage":"https://plotline-ai.vercel.app/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CodeHunt101.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-12-24T12:25:32.000Z","updated_at":"2026-03-28T11:55:06.000Z","dependencies_parsed_at":null,"dependency_job_id":"e0269641-4e65-4c83-8e1a-53a024846458","html_url":"https://github.com/CodeHunt101/plotline-ai","commit_stats":{"total_commits":46,"total_committers":1,"mean_commits":46.0,"dds":0.0,"last_synced_commit":"c4b3f4dbfc20579c9073c1ff8ee50a34fc7fa410"},"previous_names":["codehunt101/plotline-ai"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/CodeHunt101/plotline-ai","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeHunt101%2Fplotline-ai","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeHunt101%2Fplotline-ai/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeHunt101%2Fplotline-ai/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeHunt101%2Fplotline-ai/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CodeHunt101","download_url":"https://codeload.github.com/CodeHunt101/plotline-ai/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CodeHunt101%2Fplotline-ai/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31415110,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-04T20:09:54.854Z","status":"ssl_error","status_checked_at":"2026-04-04T20:09:44.350Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["daisyui","jest","langchain-js","nextjs","openai-api","reactjs","supabase-js","tailwindcss","typescript"],"created_at":"2024-12-27T01:12:38.377Z","updated_at":"2026-04-04T21:32:43.208Z","avatar_url":"https://github.com/CodeHunt101.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PlotlineAI\n\nPlotlineAI is a group movie recommendation app. Each participant shares their tastes -- favourite film, preferred era, current mood, and a favourite film personality -- and the system uses **embedding-based vector search** combined with a **language model** to surface movies the whole group will enjoy.\n\n[![Live Demo](https://img.shields.io/badge/demo-plotline--ai-blue)](https://plotline-ai.vercel.app/)\n[![CI](https://github.com/CodeHunt101/plotline-ai/actions/workflows/ci.yml/badge.svg)](https://github.com/CodeHunt101/plotline-ai/actions/workflows/ci.yml)\n\n## Table of Contents\n\n- [Architecture](#architecture)\n- [How It Works](#how-it-works)\n- [Tech Stack](#tech-stack)\n- [Getting Started](#getting-started)\n- [Project Structure](#project-structure)\n- [Cloudflare Workers](#cloudflare-workers)\n- [CI/CD](#cicd)\n- [AI Limitations](#ai-limitations)\n\n---\n\n## Architecture\n\n```mermaid\ngraph TD\n    Browser[\"Browser\\n(React + MovieContext)\"]\n\n    subgraph NextJS[\"Next.js App\"]\n        Pages[\"Pages\\n/  /movieForm  /recommendations\"]\n        API_Rec[\"POST /api/recommendations\"]\n        API_Seed[\"GET /api/embeddings-seed\"]\n\n        subgraph Services[\"lib/services/\"]\n            SvcRec[\"movie-recommendations\\n(pipeline orchestrator)\"]\n            SvcEmb[\"embeddings-server\\n(Google Gemini embed)\"]\n            SvcOAI[\"openai\\n(LLM interface)\"]\n            SvcSup[\"supabase\\n(worker proxy)\"]\n            SvcTMDB[\"tmdb\\n(poster lookup)\"]\n            SvcSeed[\"seed\\n(corpus seeding)\"]\n        end\n    end\n\n    subgraph CloudflareEdge[\"Cloudflare Edge\"]\n        AIGateway[\"AI Gateway\\n(logging / caching)\"]\n        Worker[\"Supabase CF Worker\\n/api/match-movies\\n/api/insert-movies\\n/api/truncate-movies\\n/api/check-empty\"]\n    end\n\n    subgraph ExternalAPIs[\"External APIs\"]\n        Gemini[\"Google Gemini\\ngemini-embedding-001\\ngemini-2.5-flash\"]\n        OpenRouter[\"OpenRouter\\nminimax-m2.5\\nllama-3.3-70b\\nopenrouter/free\"]\n        SupabaseDB[\"Supabase Postgres\\nmovies_4 + pgvector\\nmatch_movies_4 RPC\"]\n        TMDB[\"TMDB API\\ movie posters\"]\n    end\n\n    Browser --\u003e|\"form submit / navigate\"| Pages\n    Pages --\u003e|\"fetch\"| API_Rec\n    API_Rec --\u003e SvcRec\n    SvcRec --\u003e SvcEmb\n    SvcRec --\u003e SvcSup\n    SvcRec --\u003e SvcOAI\n    API_Seed --\u003e SvcSeed\n    SvcSeed --\u003e SvcEmb\n\n    SvcEmb --\u003e|\"embed request\"| AIGateway\n    SvcOAI --\u003e|\"LLM request (primary)\"| AIGateway\n    AIGateway --\u003e Gemini\n\n    SvcOAI --\u003e|\"LLM request (fallback)\"| OpenRouter\n\n    SvcSup --\u003e|\"POST /api/match-movies\\nx-worker-secret\"| Worker\n    SvcSeed --\u003e|\"POST /api/insert-movies\\nDELETE /api/truncate-movies\"| Worker\n    Worker --\u003e|\"Supabase RPC\"| SupabaseDB\n\n    Pages --\u003e|\"searchMoviePoster\"| SvcTMDB\n    SvcTMDB --\u003e TMDB\n```\n\n\u003e Full diagrams — React component tree, AI fallback circuit breaker, and CI/CD pipeline → [`docs/diagrams.md`](./docs/diagrams.md)\n\n---\n\n## How It Works\n\nThe recommendation pipeline has three stages: **embed**, **retrieve**, and **rank**.\n\n```mermaid\nflowchart LR\n    A[\"🎬 Participants'\\npreferences\"] --\u003e B[\"1. Embed\\nGemini → 768-dim vector\"]\n    B --\u003e C[\"2. Retrieve\\npgvector similarity search\\ntop-10 matches\"]\n    C --\u003e D[\"3. Rank\\nLLM re-ranking\\n+ JSON response\"]\n    D --\u003e E[\"🍿 Recommended\\nmovies + posters\"]\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eDetailed sequence diagram\u003c/summary\u003e\n\n```mermaid\nsequenceDiagram\n    actor User\n    participant Browser as Browser\u003cbr/\u003e(MovieFormClient)\n    participant RecAPI as POST /api/recommendations\n    participant Pipeline as movie-recommendations\u003cbr/\u003e(lib/services)\n    participant Gemini as Google Gemini\u003cbr/\u003e(via CF AI Gateway)\n    participant CFWorker as Cloudflare Worker\n    participant Supabase as Supabase\u003cbr/\u003e(pgvector)\n    participant LLM as LLM\u003cbr/\u003e(Gemini / OpenRouter)\n    participant TMDB as TMDB API\n    participant API_Country as API Country\u003cbr/\u003e(api.country.is)\n\n    User-\u003e\u003eBrowser: Submit movie preferences\u003cbr/\u003e(last participant)\n    Browser-\u003e\u003eRecAPI: POST participantsData + timeAvailable\n    RecAPI-\u003e\u003ePipeline: streamMovieRecommendations()\n\n    Note over Pipeline: 1. Build embedding \u0026 normalise\u003cbr/\u003ecreateServerEmbedding(text blob)\n\n    Pipeline-\u003e\u003eGemini: gemini-embedding-001\u003cbr/\u003e768-dim vector\n    Gemini--\u003e\u003ePipeline: embedding vector\n\n    Note over Pipeline: 2. Vector similarity search\n\n    Pipeline-\u003e\u003eCFWorker: POST /api/match-movies\u003cbr/\u003e{ embedding, threshold: 0.25, count: 10 }\n    CFWorker-\u003e\u003eSupabase: RPC match_movies_4()\n    Supabase--\u003e\u003eCFWorker: top-10 similar MovieRecords\n    CFWorker--\u003e\u003ePipeline: matched movies (id, content, similarity)\n\n    Note over Pipeline: 3. LLM streaming with zod schema\n\n    Pipeline-\u003e\u003eLLM: system prompt + movie list + user prefs\u003cbr/\u003etemperature: 0.65\n    LLM--\u003e\u003ePipeline: Stream chunks via zod schema\n\n    Note over Pipeline: 4. streamObject conversion\n\n    Pipeline--\u003e\u003eRecAPI: Stream text response\n    RecAPI--\u003e\u003eBrowser: 200 ReadableStream\n\n    Browser-\u003e\u003eBrowser: consume stream with useObject hook\n\n    loop For each recommended movie\n        Browser-\u003e\u003eTMDB: searchMoviePoster(title)\n        TMDB--\u003e\u003eBrowser: poster URL + movie ID\n        Browser-\u003e\u003eAPI_Country: fetch user country\n        API_Country--\u003e\u003eBrowser: AU (cached)\n        Browser-\u003e\u003eTMDB: getMovieWatchProviders(id, country)\n        TMDB--\u003e\u003eBrowser: watch providers list\n    end\n\n    Browser--\u003e\u003eUser: Recommendations carousel\u003cbr/\u003ewith posters \u0026 watch providers\n```\n\n\u003c/details\u003e\n\n### 1. Collect preferences\n\nEach participant fills in:\n\n- A **favourite movie** and why they love it\n- **New vs classic** preference (2015-present or pre-2015)\n- **Mood** (fun, serious, inspiring, or scary)\n- A **favourite film person** they would want to be stranded on an island with\n\nThe group also sets how much **time** is available for the session.\n\n### 2. Embed\n\nAll preferences are concatenated into a single text blob within the **movie-recommendations** service. The server calls **Google Gemini** (`gemini-embedding-001`) via the Vercel AI SDK to produce a 768-dimensional vector, which is then **L2-normalised** on the server.\n\n### 3. Retrieve -- vector similarity search\n\nThe server forwards the normalised vector to the **Supabase Cloudflare Worker** (`POST /api/match-movies`). That worker runs the Postgres RPC `match_movies_4`, using the pgvector `\u003c=\u003e` (cosine distance) operator against a pre-seeded corpus of movie embeddings and returning the **top 10 matches** above a 0.25 similarity threshold.\n\nThe movie corpus lives in `public/constants/movies.txt` and is chunked and embedded via the `/api/embeddings-seed` endpoint on first run.\n\n### 4. Rank -- language model re-ranking and streaming\n\nThe matched movie content is split into individual entries and formatted as a \"Movie List Context\". This context, together with the original participant preferences, is sent to the LLM. We call **Google Gemini 2.5 Flash** (primary) via the Vercel AI SDK `streamObject` function to rank and filter the candidates. If Google is unavailable or its daily quota is exhausted (HTTP 429/403), the request automatically cascades through a series of **OpenRouter** fallbacks: **MiniMax M2.5**, **Llama 3.3 70B**, and finally **openrouter/free** (dynamic auto-router).\n\nQuota errors trigger individualised circuit breakers: Google drops subsequent requests for **24 hours**, whereas transient OpenRouter drops bypass that specific model for just **5 minutes** before retrying.\n\nA structured system prompt paired with a **Zod** schema (`movieRecommendationSchema`) instructs the model to return a stream of between 1 and 10 movies as a structured object, filtered by time constraints, era preference, mood, and genre fit. The server pipes this stream continuously back to the Next.js client, allowing the UI to display recommendations progressively as they are generated.\n\n### 5. Fallback and display\n\nIf the LLM response cannot be parsed as valid JSON, a **heuristic fallback** (`lib/utils/recommendations.ts`) extracts movie titles, years, and synopses directly from the raw vector-match text. Movie posters and location-based streaming watch providers (provided by JustWatch) are fetched from the **TMDB API** and displayed in a carousel. The user's country is determined via `api.country.is` to localise the streaming providers shown.\n\n## Tech Stack\n\n| Layer       | Technology                                                                                 |\n| ----------- | ------------------------------------------------------------------------------------------ |\n| Framework   | Next.js 16 (App Router, Turbopack)                                                         |\n| UI          | React 19, Tailwind CSS, DaisyUI                                                            |\n| AI          | Vercel AI SDK, Google Gemini (primary), OpenRouter (Minimax, Llama, Auto-Router fallbacks) |\n| Gateway     | Cloudflare AI Gateway (Google language model path)                                         |\n| Database    | Supabase (Postgres + pgvector)                                                             |\n| Edge worker | Cloudflare Workers                                                                         |\n| Testing     | Jest 29, React Testing Library                                                             |\n| Tooling     | TypeScript (strict), ESLint, Prettier, Husky, lint-staged                                  |\n\n## Getting Started\n\n### Prerequisites\n\n- Node.js v22.13.1 or higher\n- pnpm\n- A Supabase project with the pgvector extension enabled\n- API keys for Google Gemini and OpenRouter, plus TMDB\n- A Cloudflare account for the AI Gateway\n\n### Install\n\n```bash\ngit clone https://github.com/CodeHunt101/plotline-ai.git\ncd plotline-ai\npnpm install\n```\n\n### Environment variables\n\nCreate **`.env.local`** for the Next.js app:\n\n```env\n# Google Gemini -- primary language model + embeddings\nGOOGLE_GENERATIVE_AI_API_KEY=\n\n# OpenRouter -- fallback language model (used when Google is unavailable or quota-limited)\nOPENROUTER_API_KEY=\nOPENROUTER_LANGUAGE_MODEL=             # optional, defaults to minimax/minimax-m2.5:free\n\n# Cloudflare AI Gateway (required for the primary Google language model)\nCLOUDFLARE_ACCOUNT_ID=\nCLOUDFLARE_GATEWAY_NAME=\nCLOUDFLARE_API_KEY=                    # optional\n\n# Supabase worker URL + shared secret used by server-side worker calls\nSUPABASE_WORKER_URL=\nSUPABASE_WORKER_SECRET=\n\n# TMDB poster lookup\nNEXT_PUBLIC_TMBD_ACCESS_TOKEN=\n```\n\nCreate **`.dev.vars`** for the Cloudflare Supabase worker (see `.dev.vars.example`):\n\n```env\nSUPABASE_URL=\nSUPABASE_API_KEY=\nWORKER_SHARED_SECRET=              # must match SUPABASE_WORKER_SECRET\n```\n\n### Database setup\n\nEnable the pgvector extension and create the movies table. The embedding provider is Google Gemini (`gemini-embedding-001`), which produces **768-dimensional** vectors.\n\n```sql\ncreate extension vector;\n\ncreate table movies_4 (\n  id bigserial primary key,\n  content text,\n  embedding vector(768)\n);\n\ncreate function match_movies_4(\n  query_embedding vector(768),\n  match_threshold float,\n  match_count int\n)\nreturns table (\n  id bigint,\n  content text,\n  similarity float\n)\nlanguage sql stable\nas $$\n  select\n    id,\n    content,\n    1 - (movies_4.embedding \u003c=\u003e query_embedding) as similarity\n  from movies_4\n  where 1 - (movies_4.embedding \u003c=\u003e query_embedding) \u003e match_threshold\n  order by similarity desc\n  limit match_count;\n$$;\n```\n\nTo verify the dimensions of an existing table:\n\n```sql\nselect atttypmod from pg_attribute\nwhere attrelid = 'movies_4'::regclass and attname = 'embedding';\n```\n\n### Development\n\nStart the Next.js dev server and the Supabase worker:\n\n```bash\npnpm dev\nnpx wrangler dev --config wrangler.supabase.toml\n```\n\nThen open [http://localhost:3000](http://localhost:3000).\n\nTo seed the movie corpus into Supabase on first run, call `GET /api/embeddings-seed` with the `x-worker-secret` header set to `SUPABASE_WORKER_SECRET`. This splits `public/constants/movies.txt` on movie boundaries (one entry per embedding), embeds each entry, and inserts them into the `movies_4` table if it is empty.\n\nTo force a full reseed (truncates existing data first), call `GET /api/embeddings-seed?force=true` with the same `x-worker-secret` header.\n\n### Testing\n\n```bash\npnpm test              # run the Jest suite\npnpm test:integration  # run the colocated integration tests only\npnpm test:coverage     # coverage report -- 95% threshold enforced\npnpm test:e2e          # run the Playwright browser suite\n```\n\nThe first-pass Playwright coverage is deterministic by design: it stubs `/api/recommendations` and TMDb responses in the browser, so the suite does not depend on live AI, worker, or poster services.\n\nTo run Playwright locally, make sure the browser binary is installed once:\n\n```bash\npnpm exec playwright install chromium\npnpm test:e2e\n```\n\n`pnpm test:e2e` reuses an existing local server when one is already running on `http://127.0.0.1:3000`; otherwise it builds the app and starts a local production server automatically.\n\n### Deployment\n\nDeploy the Next.js app to Vercel:\n\n```bash\nvercel\n```\n\nDeploy the Supabase worker to Cloudflare:\n\n```bash\nnpx wrangler deploy --config wrangler.supabase.toml\n```\n\n### Supabase keepalive\n\nThis repo includes a GitHub Actions workflow at `.github/workflows/supabase-keepalive.yml` that runs a lightweight Postgres query once per day.\n\nTo enable it:\n\n1. In GitHub, open **Settings -\u003e Secrets and variables -\u003e Actions**.\n2. Add a repository secret named `SUPABASE_DB_URL`.\n3. Paste your Supabase **transaction pooler** connection string from **Connect -\u003e Transaction mode** in the Supabase dashboard.\n\nThe workflow also supports manual runs from the **Actions** tab via `workflow_dispatch`.\n\n## CI/CD\n\nThis project uses GitHub Actions for continuous integration and automated Cloudflare Worker deploys.\n\n### Workflows\n\n| Workflow                 | Trigger                           | What it does                                                                                                                                        |\n| ------------------------ | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `ci.yml`                 | Push / PR → `main`                | ESLint, TypeScript type-check, Jest coverage, and a deterministic Playwright Chromium suite. Uploads coverage and Playwright artefacts when needed. |\n| `deploy.yml`             | Push → `main` (worker files only) | Deploys the Supabase Cloudflare Worker via Wrangler.                                                                                                |\n| `supabase-keepalive.yml` | Daily schedule                    | Runs a keepalive query against Supabase so the free-tier project stays active.                                                                      |\n\n### Required GitHub secrets\n\nOpen **Settings → Secrets and variables → Actions** in your GitHub repo and add:\n\n**Repository secrets** (sensitive credentials):\n\n| Secret                 | Used by                  | Where to find it                                |\n| ---------------------- | ------------------------ | ----------------------------------------------- |\n| `SUPABASE_DB_URL`      | `supabase-keepalive.yml` | Supabase dashboard → Connect → Transaction mode |\n| `CLOUDFLARE_API_TOKEN` | `deploy.yml`             | Cloudflare dashboard → My Profile → API Tokens  |\n\n**Repository variables** (non-sensitive config):\n\n| Variable                | Used by      | Where to find it                          |\n| ----------------------- | ------------ | ----------------------------------------- |\n| `CLOUDFLARE_ACCOUNT_ID` | `deploy.yml` | Cloudflare dashboard → right-hand sidebar |\n\n\u003e [!NOTE]\n\u003e The Cloudflare Worker runtime secrets (`SUPABASE_URL`, `SUPABASE_API_KEY`, `WORKER_SHARED_SECRET`) are set directly in the **Cloudflare dashboard → Workers → supabase-worker → Settings → Variables and Secrets**. They are not managed through GitHub Actions.\n\n## Project Structure\n\n```\nplotline-ai/\n├── app/\n│   ├── (routes)/                 # UI pages\n│   │   ├── page.tsx                # Home -- participant setup\n│   │   ├── movieForm/page.tsx      # Per-person preference form\n│   │   └── recommendations/page.tsx# Results carousel\n│   ├── api/\n│   │   ├── recommendations/route.ts# Server-side recommendation pipeline (streaming)\n│   │   └── embeddings-seed/route.ts# Corpus seeding\n│   ├── layout.tsx\n│   └── globals.css\n├── components/\n│   ├── features/                 # Header, Logo, ParticipantsSetup, MovieFormFields\n│   └── ui/                       # TextAreaField, TabGroup\n├── contexts/                     # MovieContext (shared state)\n├── constants/                    # MOVIE_TYPES, MOOD_TYPES, sample data\n├── types/                        # TypeScript interfaces (api.ts, movie.ts)\n├── lib/\n│   ├── config/                   # ai.ts (model selection), supabase.ts\n│   ├── services/                 # movies, embeddings, openai, supabase, seed, tmdb\n│   └── utils/                    # recommendations.ts, urls.ts\n├── workers/\n│   └── supabase-worker.ts        # Cloudflare Worker for Supabase operations\n├── public/\n│   └── constants/movies.txt      # Movie corpus for embedding seeding\n├── tests/\n│   ├── e2e/                      # Playwright browser specs + route stubs\n│   └── support/                  # Shared deterministic test fixtures\n├── wrangler.supabase.toml\n├── jest.config.js\n├── playwright.config.ts\n├── tailwind.config.ts\n└── package.json\n```\n\n## Cloudflare Workers\n\n### Supabase Worker\n\nThe Supabase worker (`workers/supabase-worker.ts`, port 7878) proxies database operations so that Supabase credentials stay server-side:\n\n- `POST /api/insert-movies` -- batch-insert chunked movie data during seeding. Requires `x-worker-secret`.\n- `GET /api/check-empty` -- check whether the movies table needs seeding. Requires `x-worker-secret`.\n- `POST /api/match-movies` -- run the pgvector similarity RPC and return the top matches. Requires `x-worker-secret`.\n- `DELETE /api/truncate-movies` -- delete all rows from the movies table (used by force-reseed). Requires `x-worker-secret`.\n\n### AI Gateway\n\nText generation calls to Google Gemini are routed through the **Cloudflare AI Gateway** for logging, caching, and rate limiting. The gateway is configured in `lib/config/ai.ts` using the `ai-gateway-provider` package. The OpenRouter fallback path does not use the gateway.\n\n## AI Limitations\n\nPlotlineAI uses artificial intelligence for movie recommendations, and while it strives for accuracy:\n\n- Recommendations may not always perfectly match group preferences.\n- Movie information and details might occasionally be incomplete or imprecise.\n- The system works best with clear, detailed input from all participants.\n- Results can vary based on the quality and specificity of user inputs.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodehunt101%2Fplotline-ai","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcodehunt101%2Fplotline-ai","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcodehunt101%2Fplotline-ai/lists"}