cduss commited on
Commit
fbc565a
·
1 Parent(s): 981516e
Files changed (1) hide show
  1. index.html +392 -18
index.html CHANGED
@@ -1,19 +1,393 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Reachy Mini Finder</title>
8
+ <style>
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ body {
16
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
17
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
18
+ min-height: 100vh;
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ padding: 20px;
23
+ }
24
+
25
+ .container {
26
+ background: white;
27
+ border-radius: 20px;
28
+ padding: 40px;
29
+ max-width: 500px;
30
+ width: 100%;
31
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
32
+ }
33
+
34
+ h1 {
35
+ color: #333;
36
+ margin-bottom: 10px;
37
+ font-size: 28px;
38
+ }
39
+
40
+ .subtitle {
41
+ color: #666;
42
+ margin-bottom: 30px;
43
+ font-size: 14px;
44
+ }
45
+
46
+ .status {
47
+ background: #f5f5f5;
48
+ padding: 20px;
49
+ border-radius: 10px;
50
+ margin-bottom: 20px;
51
+ min-height: 120px;
52
+ }
53
+
54
+ .status-text {
55
+ color: #333;
56
+ line-height: 1.6;
57
+ }
58
+
59
+ .progress-bar {
60
+ width: 100%;
61
+ height: 6px;
62
+ background: #e0e0e0;
63
+ border-radius: 3px;
64
+ margin-top: 15px;
65
+ overflow: hidden;
66
+ }
67
+
68
+ .progress-fill {
69
+ height: 100%;
70
+ background: linear-gradient(90deg, #667eea, #764ba2);
71
+ width: 0%;
72
+ transition: width 0.3s ease;
73
+ }
74
+
75
+ button {
76
+ width: 100%;
77
+ padding: 15px;
78
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
79
+ color: white;
80
+ border: none;
81
+ border-radius: 10px;
82
+ font-size: 16px;
83
+ font-weight: 600;
84
+ cursor: pointer;
85
+ transition: transform 0.2s;
86
+ }
87
+
88
+ button:hover {
89
+ transform: translateY(-2px);
90
+ }
91
+
92
+ button:active {
93
+ transform: translateY(0);
94
+ }
95
+
96
+ button:disabled {
97
+ opacity: 0.6;
98
+ cursor: not-allowed;
99
+ transform: none;
100
+ }
101
+
102
+ .manual-input {
103
+ margin-top: 20px;
104
+ padding-top: 20px;
105
+ border-top: 1px solid #e0e0e0;
106
+ }
107
+
108
+ .manual-input input {
109
+ width: 100%;
110
+ padding: 12px;
111
+ border: 2px solid #e0e0e0;
112
+ border-radius: 8px;
113
+ font-size: 14px;
114
+ margin-bottom: 10px;
115
+ }
116
+
117
+ .manual-input input:focus {
118
+ outline: none;
119
+ border-color: #667eea;
120
+ }
121
+
122
+ .ip-list {
123
+ max-height: 200px;
124
+ overflow-y: auto;
125
+ margin-top: 10px;
126
+ }
127
+
128
+ .ip-item {
129
+ padding: 8px;
130
+ background: #f9f9f9;
131
+ margin-bottom: 5px;
132
+ border-radius: 5px;
133
+ font-size: 12px;
134
+ color: #666;
135
+ font-family: monospace;
136
+ }
137
+
138
+ .ip-item.checking {
139
+ background: #fff3cd;
140
+ }
141
+
142
+ .ip-item.found {
143
+ background: #d4edda;
144
+ color: #155724;
145
+ font-weight: 600;
146
+ }
147
+
148
+ .robot-info {
149
+ background: #e7f3ff;
150
+ padding: 15px;
151
+ border-radius: 8px;
152
+ margin-top: 15px;
153
+ font-size: 13px;
154
+ color: #004085;
155
+ }
156
+
157
+ .robot-info strong {
158
+ display: block;
159
+ margin-bottom: 5px;
160
+ }
161
+ </style>
162
+ </head>
163
+
164
+ <body>
165
+ <div class="container">
166
+ <h1>🤖 Reachy Mini Finder</h1>
167
+ <p class="subtitle">Automatically detect your Reachy Mini robot on the network</p>
168
+
169
+ <div class="status">
170
+ <div class="status-text" id="statusText">
171
+ Click "Find Reachy" to start scanning your network.
172
+ </div>
173
+ <div class="progress-bar">
174
+ <div class="progress-fill" id="progressBar"></div>
175
+ </div>
176
  </div>
177
+
178
+ <button id="scanBtn" onclick="startScan()">Find Reachy</button>
179
+
180
+ <div class="manual-input">
181
+ <input type="text" id="manualIP" placeholder="Or enter IP manually (e.g., 192.168.1.100)" />
182
+ <button onclick="connectManual()">Connect</button>
183
+ </div>
184
+
185
+ <div class="ip-list" id="ipList"></div>
186
+ <div id="robotInfo"></div>
187
+ </div>
188
+
189
+ <script>
190
+ const PORT = 8000;
191
+ const TIMEOUT_MS = 500;
192
+ let scanning = false;
193
+
194
+ async function getLocalIP() {
195
+ return new Promise((resolve, reject) => {
196
+ const pc = new RTCPeerConnection({ iceServers: [] });
197
+ pc.createDataChannel('');
198
+ pc.createOffer().then(offer => pc.setLocalDescription(offer));
199
+
200
+ pc.onicecandidate = (ice) => {
201
+ if (!ice || !ice.candidate || !ice.candidate.candidate) return;
202
+
203
+ const match = /([0-9]{1,3}\.){3}[0-9]{1,3}/.exec(ice.candidate.candidate);
204
+ if (match) {
205
+ pc.close();
206
+ resolve(match[0]);
207
+ }
208
+ };
209
+
210
+ setTimeout(() => {
211
+ pc.close();
212
+ reject(new Error('Could not detect local IP'));
213
+ }, 5000);
214
+ });
215
+ }
216
+
217
+ async function tryIP(ip) {
218
+ const controller = new AbortController();
219
+ const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
220
+
221
+ try {
222
+ const response = await fetch(`http://${ip}:${PORT}/api/daemon/status`, {
223
+ signal: controller.signal,
224
+ mode: 'cors'
225
+ });
226
+ clearTimeout(timeoutId);
227
+
228
+ if (response.ok) {
229
+ const data = await response.json();
230
+ return { ip, data };
231
+ }
232
+ } catch (err) {
233
+ // Timeout or connection refused
234
+ }
235
+ clearTimeout(timeoutId);
236
+ return null;
237
+ }
238
+
239
+ function updateStatus(text, progress = null) {
240
+ document.getElementById('statusText').innerHTML = text;
241
+ if (progress !== null) {
242
+ document.getElementById('progressBar').style.width = progress + '%';
243
+ }
244
+ }
245
+
246
+ function addIPToList(ip, status = 'checking') {
247
+ const ipList = document.getElementById('ipList');
248
+ const existing = document.getElementById('ip-' + ip.replace(/\./g, '-'));
249
+
250
+ if (existing) {
251
+ existing.className = 'ip-item ' + status;
252
+ } else {
253
+ const item = document.createElement('div');
254
+ item.id = 'ip-' + ip.replace(/\./g, '-');
255
+ item.className = 'ip-item ' + status;
256
+ item.textContent = status === 'found' ? `✓ Found: ${ip}` : `Checking: ${ip}`;
257
+ ipList.appendChild(item);
258
+ }
259
+ }
260
+
261
+ function showRobotInfo(ip, data) {
262
+ const infoDiv = document.getElementById('robotInfo');
263
+ infoDiv.innerHTML = `
264
+ <div class="robot-info">
265
+ <strong>✅ Reachy Mini Found!</strong>
266
+ IP: ${ip}<br>
267
+ Server ID: ${data.server_id || 'N/A'}<br>
268
+ Version: ${data.version || 'N/A'}
269
+ </div>
270
+ `;
271
+ }
272
+
273
+ async function startScan() {
274
+ if (scanning) return;
275
+ scanning = true;
276
+
277
+ const btn = document.getElementById('scanBtn');
278
+ btn.disabled = true;
279
+ btn.textContent = 'Scanning...';
280
+ document.getElementById('ipList').innerHTML = '';
281
+ document.getElementById('robotInfo').innerHTML = '';
282
+
283
+ try {
284
+ updateStatus('🔍 Detecting your phone\'s IP address...', 5);
285
+ const phoneIP = await getLocalIP();
286
+ const subnet = phoneIP.split('.').slice(0, 3).join('.');
287
+
288
+ updateStatus(`📡 Phone IP: ${phoneIP}<br>Scanning subnet: ${subnet}.0/24`, 10);
289
+
290
+ // Priority IPs to check first
291
+ const priorityIPs = [
292
+ `${subnet}.1`, // Router
293
+ `${subnet}.100`, // Common DHCP start
294
+ ];
295
+
296
+ // Build full scan list, excluding phone's IP
297
+ const allIPs = [];
298
+ for (let i = 1; i < 255; i++) {
299
+ const ip = `${subnet}.${i}`;
300
+ if (ip !== phoneIP) {
301
+ allIPs.push(ip);
302
+ }
303
+ }
304
+
305
+ // Put priority IPs first
306
+ const scanList = [
307
+ ...priorityIPs.filter(ip => allIPs.includes(ip)),
308
+ ...allIPs.filter(ip => !priorityIPs.includes(ip))
309
+ ];
310
+
311
+ // Scan in batches of 20 for better responsiveness
312
+ const BATCH_SIZE = 20;
313
+ let scanned = 0;
314
+
315
+ for (let i = 0; i < scanList.length; i += BATCH_SIZE) {
316
+ const batch = scanList.slice(i, i + BATCH_SIZE);
317
+
318
+ // Show which IPs we're checking
319
+ batch.forEach(ip => addIPToList(ip, 'checking'));
320
+
321
+ const results = await Promise.all(batch.map(tryIP));
322
+ const found = results.find(r => r !== null);
323
+
324
+ scanned += batch.length;
325
+ const progress = 10 + (scanned / scanList.length) * 90;
326
+ updateStatus(
327
+ `🔍 Scanning: ${scanned}/${scanList.length} IPs checked...`,
328
+ progress
329
+ );
330
+
331
+ if (found) {
332
+ addIPToList(found.ip, 'found');
333
+ showRobotInfo(found.ip, found.data);
334
+ updateStatus(
335
+ `✅ Reachy Mini found at ${found.ip}!<br>Redirecting...`,
336
+ 100
337
+ );
338
+ setTimeout(() => {
339
+ window.location.href = `http://${found.ip}:${PORT}`;
340
+ }, 2000);
341
+ return;
342
+ }
343
+ }
344
+
345
+ updateStatus(
346
+ `❌ No Reachy Mini found on ${subnet}.0/24<br>Please check:<br>
347
+ • Robot is powered on<br>
348
+ • Connected to same WiFi<br>
349
+ • Daemon running on port ${PORT}<br>
350
+ • Try manual IP below`,
351
+ 100
352
+ );
353
+
354
+ } catch (err) {
355
+ updateStatus(
356
+ `⚠️ Error: ${err.message}<br>Try entering IP manually below`,
357
+ 0
358
+ );
359
+ } finally {
360
+ scanning = false;
361
+ btn.disabled = false;
362
+ btn.textContent = 'Scan Again';
363
+ }
364
+ }
365
+
366
+ function connectManual() {
367
+ const ip = document.getElementById('manualIP').value.trim();
368
+ if (!ip) {
369
+ alert('Please enter an IP address');
370
+ return;
371
+ }
372
+
373
+ // Validate IP format
374
+ const ipRegex = /^(\d{1,3}\.){3}\d{1,3}$/;
375
+ if (!ipRegex.test(ip)) {
376
+ alert('Invalid IP format. Example: 192.168.1.100');
377
+ return;
378
+ }
379
+
380
+ updateStatus(`🔗 Connecting to ${ip}...`, 50);
381
+ window.location.href = `http://${ip}:${PORT}`;
382
+ }
383
+
384
+ // Allow Enter key in manual input
385
+ document.getElementById('manualIP').addEventListener('keypress', (e) => {
386
+ if (e.key === 'Enter') {
387
+ connectManual();
388
+ }
389
+ });
390
+ </script>
391
+ </body>
392
+
393
+ </html>