| @@ -1,88 +0,0 @@ |
| 1 | | −# Which Is Better? |
| 2 | | − |
| 3 | | −A local paired-comparison ranking tool built on the [Bradley-Terry model](https://en.wikipedia.org/wiki/Bradley%E2%80%93Terry_model). Upload a dataset of images (JSON or SQLite), rate them by choosing between random pairs, and watch rankings converge in real time. Everything runs in your browser — no server, no accounts, no tracking. |
| 4 | | − |
| 5 | | −**Live site**: [whichisbetter.dev](https://whichisbetter.dev) |
| 6 | | −**Write-up**: [krisyotam.com/which-is-better](https://krisyotam.com/which-is-better) |
| 7 | | − |
| 8 | | −## How it works |
| 9 | | − |
| 10 | | −You are shown two items side by side. Pick the one you prefer. The Bradley-Terry model updates both items' strength parameters using a maximum-likelihood gradient step. Pairs are selected with an exploration bias toward items with fewer comparisons, so rankings stabilize efficiently. |
| 11 | | − |
| 12 | | −A 15-second timer runs on each pair. If it expires or you pause and unpause, the pair is skipped with no score change — only committed choices affect ratings. This keeps the data clean: indecision and distraction don't pollute your rankings. |
| 13 | | − |
| 14 | | −## Features |
| 15 | | − |
| 16 | | −- **JSON and SQLite input** — upload `.json` arrays or `.db`/`.sqlite` files. For databases, pick a table, then choose which metadata fields to display during comparison. |
| 17 | | −- **Metadata display** — datasets can include arbitrary fields (description, date, tags, artist, etc.). Select which to show alongside images when loading. |
| 18 | | −- **No-image support** — items without an `imageUrl` render as blank panels. Useful for text-only comparison sets. |
| 19 | | −- **Local storage** — all ratings and comparison history persist in IndexedDB. Export as JSON anytime, or import a previous session to resume. |
| 20 | | −- **Database re-export** — if your source was a SQLite file, export the original database back with a `paired_rating` column added to your selected table. All other tables and columns are preserved with full integrity. |
| 21 | | −- **Light and dark mode** — light theme matches the minimal serif aesthetic of [thiswaifudoesnotexist.net](https://www.thiswaifudoesnotexist.net). Toggle in the top-right corner. |
| 22 | | −- **Paginated rankings** — live ranking table with 10 items per page, showing rating, win/loss record, and thumbnail. |
| 23 | | − |
| 24 | | −## Dataset format |
| 25 | | − |
| 26 | | −### JSON |
| 27 | | − |
| 28 | | −An array of objects. Only `name` is required. `imageUrl` is optional. Any extra keys become selectable metadata fields. |
| 29 | | − |
| 30 | | −```json |
| 31 | | −[ |
| 32 | | − { |
| 33 | | − "name": "Alpine Lake", |
| 34 | | − "imageUrl": "https://example.com/alpine.jpg", |
| 35 | | − "photographer": "Ansel Adams", |
| 36 | | − "year": 1938 |
| 37 | | − }, |
| 38 | | − { |
| 39 | | − "name": "Desert Dunes", |
| 40 | | − "imageUrl": "https://example.com/desert.jpg", |
| 41 | | − "photographer": "Edward Weston", |
| 42 | | − "year": 1947 |
| 43 | | − } |
| 44 | | −] |
| 45 | | −``` |
| 46 | | − |
| 47 | | −### SQLite |
| 48 | | − |
| 49 | | −Any `.db`, `.sqlite`, or `.sqlite3` file. You choose the table, then the display fields. The first column named `id` (or the first column) is used as the item identifier for re-export. |
| 50 | | − |
| 51 | | −## The model |
| 52 | | − |
| 53 | | −The Bradley-Terry model assigns a strength parameter $\beta_i$ to each item. The probability of item $i$ beating item $j$ is: |
| 54 | | − |
| 55 | | −$$P(i > j) = \frac{e^{\beta_i}}{e^{\beta_i} + e^{\beta_j}}$$ |
| 56 | | − |
| 57 | | −After each comparison where $i$ beats $j$, parameters update via gradient ascent on the log-likelihood: |
| 58 | | − |
| 59 | | −$$\beta_i \leftarrow \beta_i + \eta \cdot (1 - P(i > j))$$ |
| 60 | | −$$\beta_j \leftarrow \beta_j - \eta \cdot P(i > j)$$ |
| 61 | | − |
| 62 | | −The learning rate $\eta = 0.4$ balances responsiveness with stability. Items start at $\beta = 0$ (equal strength). Rankings are simply items sorted by $\beta$ descending. |
| 63 | | − |
| 64 | | −## Stack |
| 65 | | − |
| 66 | | −- [Next.js 16](https://nextjs.org) (App Router, Turbopack) |
| 67 | | −- [sql.js](https://github.com/sql-js/sql.js) for client-side SQLite |
| 68 | | −- [idb](https://github.com/jakearchibald/idb) for IndexedDB |
| 69 | | −- No backend. No dependencies beyond these three. |
| 70 | | − |
| 71 | | −## Running locally |
| 72 | | − |
| 73 | | −```bash |
| 74 | | −git clone https://github.com/krisyotam/whichisbetter.git |
| 75 | | −cd whichisbetter |
| 76 | | −npm install |
| 77 | | −npm run dev |
| 78 | | −``` |
| 79 | | − |
| 80 | | −Open [localhost:3000](http://localhost:3000). |
| 81 | | − |
| 82 | | −## Sample dataset |
| 83 | | − |
| 84 | | −The built-in sample loads 25 AI-generated anime faces from [This Waifu Does Not Exist](https://www.thiswaifudoesnotexist.net) (Gwern Branwen's StyleGAN 2 project). Click "Load sample dataset" on the home screen to try it without uploading anything. |
| 85 | | − |
| 86 | | −## License |
| 87 | | − |
| 88 | | −MIT |