Skip to content

Commit 4890dc3

Browse files
committed
Update index.html
1 parent 72cb991 commit 4890dc3

File tree

1 file changed

+51
-82
lines changed

1 file changed

+51
-82
lines changed

ng_sandbox/index.html

Lines changed: 51 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -3,48 +3,12 @@
33
<head>
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<title>Random Collection Entry</title>
7-
<style>
8-
body {
9-
margin: 0;
10-
min-height: 100vh;
11-
display: flex;
12-
justify-content: center;
13-
align-items: center;
14-
font-family: Arial, Helvetica, sans-serif;
15-
background-color: #f5f5f5;
16-
}
6+
<title>ML Image Similarity Browser</title>
177

18-
.container {
19-
text-align: center;
20-
}
8+
<!-- TensorFlow.js + MobileNet -->
9+
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@4.16.0"></script>
10+
<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/mobilenet@2.1.0"></script>
2111

22-
h1 {
23-
margin-bottom: 20px;
24-
}
25-
26-
img {
27-
max-width: 90vw;
28-
max-height: 70vh;
29-
object-fit: contain;
30-
border-radius: 8px;
31-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
32-
}
33-
</style>
34-
</head>
35-
<body>
36-
<div class="container">
37-
<h1 id="title">Loading...</h1>
38-
<img id="image" alt="Random collection item" />
39-
</div>
40-
41-
<script>
42-
fetch('https:/<!DOCTYPE html>
43-
<html lang="en">
44-
<head>
45-
<meta charset="UTF-8" />
46-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
47-
<title>Smart Collection Browser</title>
4812
<style>
4913
body {
5014
margin: 0;
@@ -72,16 +36,11 @@ <h1 id="title">Loading...</h1>
7236
transform: scale(0.95);
7337
}
7438

75-
h1 {
76-
margin-bottom: 20px;
77-
}
78-
7939
.main img {
8040
max-width: 60vw;
8141
max-height: 70vh;
82-
object-fit: contain;
8342
border-radius: 8px;
84-
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
43+
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
8544
}
8645

8746
.similar {
@@ -108,43 +67,51 @@ <h1 id="title">Loading...</h1>
10867
<body>
10968
<div class="layout">
11069
<div class="main" id="main">
111-
<h1 id="title">Loading...</h1>
112-
<img id="image" />
70+
<h1 id="title">Loading model…</h1>
71+
<img id="image" crossorigin="anonymous" />
11372
</div>
11473
<div class="similar" id="similar"></div>
11574
</div>
11675

11776
<script>
11877
let collection = [];
78+
let model;
11979
let currentIndex = 0;
12080

121-
// --- Simple ML: TF-IDF cosine similarity on titles ---
122-
function tokenize(text) {
123-
return text.toLowerCase().split(/\W+/).filter(Boolean);
124-
}
125-
126-
function buildTf(tokens) {
127-
const tf = {};
128-
tokens.forEach(t => tf[t] = (tf[t] || 0) + 1);
129-
return tf;
81+
async function loadModel() {
82+
model = await mobilenet.load({ version: 2, alpha: 1.0 });
83+
}
84+
85+
async function embedImage(url) {
86+
return new Promise(resolve => {
87+
const img = new Image();
88+
img.crossOrigin = 'anonymous';
89+
img.src = url;
90+
img.onload = async () => {
91+
const embedding = model.infer(img, true);
92+
const values = await embedding.data();
93+
embedding.dispose();
94+
resolve(Array.from(values));
95+
};
96+
});
13097
}
13198

13299
function cosine(a, b) {
133100
let dot = 0, magA = 0, magB = 0;
134-
for (const k in a) {
135-
if (b[k]) dot += a[k] * b[k];
136-
magA += a[k] * a[k];
101+
for (let i = 0; i < a.length; i++) {
102+
dot += a[i] * b[i];
103+
magA += a[i] * a[i];
104+
magB += b[i] * b[i];
137105
}
138-
for (const k in b) magB += b[k] * b[k];
139-
return dot / (Math.sqrt(magA) * Math.sqrt(magB) || 1);
106+
return dot / (Math.sqrt(magA) * Math.sqrt(magB));
140107
}
141108

142109
function findSimilar(index, count = 4) {
143-
const baseTf = collection[index].tf;
110+
const base = collection[index].embedding;
144111
return collection
145112
.map((item, i) => ({
146113
index: i,
147-
score: i === index ? -1 : cosine(baseTf, item.tf)
114+
score: i === index ? -1 : cosine(base, item.embedding)
148115
}))
149116
.sort((a, b) => b.score - a.score)
150117
.slice(0, count);
@@ -160,36 +127,38 @@ <h1 id="title">Loading...</h1>
160127
document.getElementById('title').textContent = item.Title;
161128
document.getElementById('image').src = item['Image URL'];
162129

163-
const similarDiv = document.getElementById('similar');
164-
similarDiv.innerHTML = '';
130+
const similar = document.getElementById('similar');
131+
similar.innerHTML = '';
165132

166133
findSimilar(index).forEach(sim => {
167134
const img = document.createElement('img');
168135
img.src = collection[sim.index]['Image URL'];
169136
img.title = collection[sim.index].Title;
170137
img.onclick = () => render(sim.index);
171-
similarDiv.appendChild(img);
138+
similar.appendChild(img);
172139
});
173140

174141
main.classList.remove('fade');
175142
}, 300);
176143
}
177144

178-
fetch('https://johnstack.github.io/JavaScript-Sandpit/ng_sandbox/collection.json')
179-
.then(r => r.json())
180-
.then(data => {
181-
collection = data.map(item => ({
182-
...item,
183-
tf: buildTf(tokenize(item.Title || ''))
184-
}));
185-
186-
const start = Math.floor(Math.random() * collection.length);
187-
render(start);
188-
})
189-
.catch(err => {
190-
document.getElementById('title').textContent = 'Failed to load collection';
191-
console.error(err);
192-
});
145+
async function init() {
146+
await loadModel();
147+
148+
const data = await fetch('https://johnstack.github.io/JavaScript-Sandpit/ng_sandbox/collection.json').then(r => r.json());
149+
document.getElementById('title').textContent = 'Analyzing images…';
150+
151+
collection = [];
152+
for (const item of data) {
153+
const embedding = await embedImage(item['Image URL']);
154+
collection.push({ ...item, embedding });
155+
}
156+
157+
const start = Math.floor(Math.random() * collection.length);
158+
render(start);
159+
}
160+
161+
init();
193162
</script>
194163
</body>
195164
</html>

0 commit comments

Comments
 (0)