Download model.js from usmar/flickpick: direct link, hf CLI and curl.
- Browser
- Download file 5.46 kB
-
https://huggingface.co/spaces/usmar/flickpick/resolve/main/model.js
- Command line
-
hf download hf://spaces/usmar/flickpick/model.js
-
curl -L -o model.js https://huggingface.co/spaces/usmar/flickpick/resolve/main/model.js
5.46 kB
| // Client-side model runtime: intercepts the app's fetch() calls and answers | |
| // them from item factors shipped with the page. Generated by export_static.py. | |
| (() => { | |
| const ALPHA = 20.0, REG = 0.1, F = 96; | |
| const PERSONAS = [{"name": "Sci-fi night", "titles": ["Matrix, The", "Star Wars: Episode IV - A New Hope", "Terminator 2: Judgment Day", "Alien", "Blade Runner"]}, {"name": "Rom-com classics", "titles": ["Sleepless in Seattle", "You've Got Mail", "Pretty Woman", "Four Weddings and a Funeral", "Notting Hill"]}, {"name": "Animated favorites", "titles": ["Toy Story", "Bug's Life, A", "Aladdin", "Lion King, The", "Beauty and the Beast"]}, {"name": "Horror night", "titles": ["Shining, The", "Halloween", "Exorcist, The", "Scream", "Psycho"]}]; | |
| let catalog = null, V = null, VtV = null, unit = null, byId = null; | |
| async function load() { | |
| if (catalog) return; | |
| catalog = await (await realFetch("catalog.json")).json(); | |
| const buf = await (await realFetch("factors.bin")).arrayBuffer(); | |
| V = new Float32Array(buf); | |
| byId = new Map(catalog.map((m, i) => [m.movie_id, i])); | |
| VtV = new Float64Array(F * F); | |
| const n = catalog.length; | |
| for (let i = 0; i < n; i++) { | |
| const o = i * F; | |
| for (let a = 0; a < F; a++) { | |
| const va = V[o + a]; | |
| for (let b = a; b < F; b++) VtV[a * F + b] += va * V[o + b]; | |
| } | |
| } | |
| for (let a = 0; a < F; a++) for (let b = 0; b < a; b++) VtV[a * F + b] = VtV[b * F + a]; | |
| unit = new Float32Array(V.length); | |
| for (let i = 0; i < n; i++) { | |
| const o = i * F; | |
| let s = 0; | |
| for (let a = 0; a < F; a++) s += V[o + a] * V[o + a]; | |
| const inv = s > 0 ? 1 / Math.sqrt(s) : 0; | |
| for (let a = 0; a < F; a++) unit[o + a] = V[o + a] * inv; | |
| } | |
| } | |
| function solve(A, b) { // Gaussian elimination with partial pivoting | |
| const n = b.length, x = Float64Array.from(b), M = Float64Array.from(A); | |
| for (let c = 0; c < n; c++) { | |
| let p = c; | |
| for (let r = c + 1; r < n; r++) if (Math.abs(M[r * n + c]) > Math.abs(M[p * n + c])) p = r; | |
| if (p !== c) { | |
| for (let k = c; k < n; k++) { const t = M[c * n + k]; M[c * n + k] = M[p * n + k]; M[p * n + k] = t; } | |
| const t = x[c]; x[c] = x[p]; x[p] = t; | |
| } | |
| const piv = M[c * n + c]; | |
| for (let r = c + 1; r < n; r++) { | |
| const f = M[r * n + c] / piv; | |
| if (f === 0) continue; | |
| for (let k = c; k < n; k++) M[r * n + k] -= f * M[c * n + k]; | |
| x[r] -= f * x[c]; | |
| } | |
| } | |
| for (let r = n - 1; r >= 0; r--) { | |
| let s = x[r]; | |
| for (let k = r + 1; k < n; k++) s -= M[r * n + k] * x[k]; | |
| x[r] = s / M[r * n + r]; | |
| } | |
| return x; | |
| } | |
| const movie = i => { | |
| const m = catalog[i]; | |
| return { movie_id: m.movie_id, title: m.title, year: m.year, genres: m.genres }; | |
| }; | |
| function recommend(movieIds, topN) { | |
| const idx = [...new Set(movieIds.map(id => byId.get(id)))].filter(i => i !== undefined); | |
| const A = Float64Array.from(VtV), b = new Float64Array(F); | |
| for (const i of idx) { | |
| const o = i * F; | |
| for (let a = 0; a < F; a++) { | |
| b[a] += (1 + ALPHA) * V[o + a]; | |
| for (let c = 0; c < F; c++) A[a * F + c] += ALPHA * V[o + a] * V[o + c]; | |
| } | |
| } | |
| for (let a = 0; a < F; a++) A[a * F + a] += REG; | |
| const u = solve(A, b); | |
| const n = catalog.length, scores = new Float64Array(n); | |
| for (let i = 0; i < n; i++) { | |
| const o = i * F; | |
| let s = 0; | |
| for (let a = 0; a < F; a++) s += V[o + a] * u[a]; | |
| scores[i] = s; | |
| } | |
| const excluded = new Set(idx); | |
| const order = [...scores.keys()].filter(i => !excluded.has(i)) | |
| .sort((x, y) => scores[y] - scores[x]).slice(0, topN); | |
| const maxScore = Math.max(scores[order[0]], 1e-9); | |
| const recs = order.map((i, r) => { | |
| let best = idx[0], bestSim = -2; | |
| for (const p of idx) { | |
| let s = 0; | |
| for (let a = 0; a < F; a++) s += unit[i * F + a] * unit[p * F + a]; | |
| if (s > bestSim) { bestSim = s; best = p; } | |
| } | |
| return { ...movie(i), rank: r + 1, score: Math.round(scores[i] * 1e4) / 1e4, | |
| match: Math.round(scores[i] / maxScore * 1e4) / 1e4, because: catalog[best].title }; | |
| }); | |
| return { picks: idx.map(movie), recommendations: recs }; | |
| } | |
| const respond = data => Promise.resolve({ ok: true, status: 200, json: async () => data }); | |
| const realFetch = window.fetch.bind(window); | |
| window.fetch = async (url, opts) => { | |
| const u = String(url); | |
| if (u.startsWith("search?")) { | |
| await load(); | |
| const q = new URLSearchParams(u.split("?")[1]).get("q").trim().toLowerCase(); | |
| if (q.length < 2) return respond([]); | |
| const hits = catalog.filter(m => m.title.toLowerCase().includes(q)) | |
| .sort((a, b) => b.n_likes - a.n_likes).slice(0, 20) | |
| .map(m => movie(byId.get(m.movie_id))); | |
| return respond(hits); | |
| } | |
| if (u.startsWith("sample?")) { | |
| await load(); | |
| const i = parseInt(new URLSearchParams(u.split("?")[1]).get("index") || "0", 10); | |
| const p = PERSONAS[i % PERSONAS.length]; | |
| return respond({ name: p.name, | |
| movies: p.titles.filter(t => catalog.some(m => m.title === t)) | |
| .map(t => movie(byId.get(catalog.find(m => m.title === t).movie_id))) }); | |
| } | |
| if (u === "recommend") { | |
| await load(); | |
| const body = JSON.parse(opts.body); | |
| return respond(recommend(body.movie_ids, body.n || 10)); | |
| } | |
| return realFetch(url, opts); | |
| }; | |
| })(); | |