REI is a lightweight, frontend‑only recommendation toolkit. It runs completely in the browser, embedding data with small pretrained models, simple tag rules, or other lightweight methods, and re‑ranking items locally.
REI is built on a simple principle: everything happens on the frontend.
This means no backend logic, no API calls, and no tracking—just client‑side computation. This makes REI private by default, lightweight, and easy to integrate.
- Lightweight: Designed for edge devices and resource-limited environments.
- Privacy by design: User data never leaves the device.
- Low disruption: REI reorders existing items instead of changing what users see, keeping experiences familiar, while working directly with the limited set of content already available in the DOM.
- News readers / RSS feeds – recent items matter most
- Trending or hot lists – popularity-driven content
- E-commerce carousels – limited visible products
REI is best suited for environments where:
- Backend or data resources are limited
- Privacy or compliance is a concern
- Existing sorting (recency, popularity) already works, but subtle personalization is desired
- Minimal engineering effort and stable UX are priorities
There's an MVP of REI that helps you quickly understand how REI works and what it does.
This MVP demonstrates:
- A list of items and a click history panel.
- Clicking an item updates the user profile and instantly reranks items.
- Multi-language embedding via
Xenova/paraphrase-multilingual-MiniLM-L12-v2(running fully in-browser viatransformers.js). - Ranking uses cosine similarity on the user's average embedding.
You can find the working MVP in the root folder: mvp.html.
The demo dataset is sourced from the book rankings on 博客來 (https://www.books.com.tw) and is used solely for research and testing purposes. No commercial use is intended. If any infringement is found, please contact us for immediate removal.
The REI core modules provide the essential components to achieve the system’s goals. Users can integrate these components into their own UIController implementations or other workflows according to their needs.
- statistics: utility functions for computing statistical distributions and probability density functions.
- entities:
ItemandUserstore data, embeddings, and click history. - preProcessor: pre-processor
Item. - reranker: implements ranking strategies.
- storage: Local and session storage for user profiles.
- uiController (optional): Interfaces with the webpage DOM to extract items, handle clicks, and reorder lists.
- registry (optional):
ItemRegistrymanages items and associated DOM elements.
classDiagram
direction TB
class Item {
+getTitle()
+getHash()
+toJSON()
+static fromJSON()
}
class User {
+recordClick(item)
+getClickHistory()
+clearClickHistory()
+toJSON()
+static fromJSON()
}
class preProcessor {
+init()
+process(items)
}
class reranker {
+rank(user, items)
}
class storage {
+save(user)
+load()
+clear()
}
class client
%% Relationships
reranker --> User
reranker --> Item
User --> Item
preProcessor --> Item
storage --> User
client --> storage
client --> registry
client --> User
client --> preProcessor
client --> reranker
classDiagram
direction TB
class Item {
+getTitle()
+getHash()
+toJSON()
+static fromJSON()
}
class registry~T~ {
+getOrCreate(title, source?)
+getByHash(hash)
+getSourceByItem(item)
+getAll()
}
class uiController {
<<interface>>
+extractItems()
+sort(items)
+onItemClick(callback)
}
class client
%% Relationships
client --> uiController
uiController --> registry
registry --> Item
sequenceDiagram
participant UI as uiController
participant Reg as registry
participant Proc as preProcessor
participant User as User
participant Rank as reranker
participant Store as storage
UI->>Reg: extractItems()
Reg-->>UI: items
UI->>Proc: process(items)
Proc-->>UI: items with embeddings
UI->>User: recordClick(item)
UI->>Store: save(User)
UI->>Rank: rank(User, registry.getAll())
Rank-->>UI: ranked items
UI->>UI: sort(items)
REI supports multiple ranking strategies and processors.
- Xenova/paraphrase-multilingual-MiniLM-L12-v2
TODO: Document other processors.
- LILY (Beta-likelihood): Updates scores incrementally based on user clicks.
- PLUTO (TODO): A planned alternative for lightweight, front-end-friendly ranking.
TODO: Document LILY & PLUTO, including strengths, weaknesses, and cost.
We provide examples showing REI applied to existing websites:
- Automatically extract visible items from pages (books, products, etc.).
- Compute embeddings and rerank items locally.
- Demonstrates the concept of personalized browsing without data collection.
TODO: List example websites, including screenshots and which Processors & Rerankers they use.
REI’s core modules have comprehensive test coverage, divided into three levels based on dependency scope. Each module is tested according to its characteristics and responsibilities, so not every module undergoes all three levels of testing.
# run all of them with coverage report
npm run test:all
> test:all
> vitest run tests --coverage
RUN v3.2.4 /app
Coverage enabled with istanbul
✓ tests/unit/core/statistics.test.ts (6 tests) 4ms
✓ tests/unit/core/entities.test.ts (7 tests) 4ms
✓ tests/functional/core/reranker.test.ts (3 tests) 3ms
✓ tests/functional/core/registry.test.ts (7 tests) 4ms
✓ tests/integration/core/preProcessor.test.ts (4 tests) 4516ms
✓ TextEmbeddingProcessor > should work whether allowLocalModels true or false 2269ms
✓ TextEmbeddingProcessor > should embed item titles into embeddings with its dimension 1130ms
✓ TextEmbeddingProcessor > should produce embeddings with values between 0 and 1 1113ms
✓ tests/functional/core/storage.test.ts (12 tests) 6ms
Test Files 6 passed (6)
Tests 39 passed (39)
Start at 14:54:34
Duration 16.98s (transform 1.14s, setup 0ms, collect 4.24s, tests 4.54s, environment 15.68s, prepare 3.40s)
% Coverage report from istanbul
-----------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
entities.ts | 100 | 100 | 100 | 100 |
preProcessor.ts | 100 | 100 | 100 | 100 |
registry.ts | 100 | 100 | 100 | 100 |
reranker.ts | 100 | 100 | 100 | 100 |
statistics.ts | 100 | 100 | 100 | 100 |
storage.ts | 100 | 100 | 100 | 100 |
-----------------|---------|----------|---------|---------|-------------------- Must not depend on other REI modules.
- No access to any external resources (network, file system, etc.).
- Typically used for lowest-level objects or helper functions.
npm run test:unit- Can depend on lower-level REI modules (assumed to be correct).
- Tests logical behavior across multiple components.
- Still no external resources — mock them if necessary (ex: jsdom).
npm run test:functional- May access external resources (e.g., network, local files, APIs).
- Tests full workflows or real pipelines.
- Be cautious of side effects, as these tests execute real operations.
npm run test:integrationREI follows Google TypeScript Style (GTS) for linting and formatting.
lintchecks for style violations.fixautomatically corrects common issues.
npm run lint
npm run fixREI uses esbuild for bundling and type-checking via TypeScript.
typecheckensures type safety without emitting files.buildcompiles all targets (index, books, content) intopublic/dist/.
npm run typecheck
npm run buildThis project is licensed under the MIT License. See the LICENSE file for details.