reachy-mini 1.2.5rc1__py3-none-any.whl → 1.2.11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- reachy_mini/apps/app.py +24 -21
- reachy_mini/apps/manager.py +17 -3
- reachy_mini/apps/sources/hf_auth.py +92 -0
- reachy_mini/apps/sources/hf_space.py +1 -1
- reachy_mini/apps/sources/local_common_venv.py +199 -24
- reachy_mini/apps/templates/main.py.j2 +4 -3
- reachy_mini/daemon/app/dashboard/static/js/apps.js +9 -1
- reachy_mini/daemon/app/dashboard/static/js/appstore.js +228 -0
- reachy_mini/daemon/app/dashboard/static/js/logs.js +148 -0
- reachy_mini/daemon/app/dashboard/templates/logs.html +37 -0
- reachy_mini/daemon/app/dashboard/templates/sections/appstore.html +92 -0
- reachy_mini/daemon/app/dashboard/templates/sections/cache.html +82 -0
- reachy_mini/daemon/app/dashboard/templates/sections/daemon.html +5 -0
- reachy_mini/daemon/app/dashboard/templates/settings.html +1 -0
- reachy_mini/daemon/app/main.py +172 -7
- reachy_mini/daemon/app/models.py +8 -0
- reachy_mini/daemon/app/routers/apps.py +56 -0
- reachy_mini/daemon/app/routers/cache.py +58 -0
- reachy_mini/daemon/app/routers/hf_auth.py +57 -0
- reachy_mini/daemon/app/routers/logs.py +124 -0
- reachy_mini/daemon/app/routers/state.py +25 -1
- reachy_mini/daemon/app/routers/wifi_config.py +75 -0
- reachy_mini/daemon/app/services/bluetooth/bluetooth_service.py +1 -1
- reachy_mini/daemon/app/services/bluetooth/commands/WIFI_RESET.sh +8 -0
- reachy_mini/daemon/app/services/wireless/launcher.sh +8 -2
- reachy_mini/daemon/app/services/wireless/reachy-mini-daemon.service +13 -0
- reachy_mini/daemon/backend/abstract.py +29 -9
- reachy_mini/daemon/backend/mockup_sim/__init__.py +12 -0
- reachy_mini/daemon/backend/mockup_sim/backend.py +176 -0
- reachy_mini/daemon/backend/mujoco/backend.py +0 -5
- reachy_mini/daemon/backend/robot/backend.py +78 -5
- reachy_mini/daemon/daemon.py +46 -7
- reachy_mini/daemon/utils.py +71 -15
- reachy_mini/io/zenoh_client.py +26 -0
- reachy_mini/io/zenoh_server.py +10 -6
- reachy_mini/kinematics/nn_kinematics.py +2 -2
- reachy_mini/kinematics/placo_kinematics.py +15 -15
- reachy_mini/media/__init__.py +55 -1
- reachy_mini/media/audio_base.py +185 -13
- reachy_mini/media/audio_control_utils.py +60 -5
- reachy_mini/media/audio_gstreamer.py +97 -16
- reachy_mini/media/audio_sounddevice.py +120 -19
- reachy_mini/media/audio_utils.py +110 -5
- reachy_mini/media/camera_base.py +182 -11
- reachy_mini/media/camera_constants.py +132 -4
- reachy_mini/media/camera_gstreamer.py +42 -2
- reachy_mini/media/camera_opencv.py +83 -5
- reachy_mini/media/camera_utils.py +95 -7
- reachy_mini/media/media_manager.py +139 -6
- reachy_mini/media/webrtc_client_gstreamer.py +142 -13
- reachy_mini/media/webrtc_daemon.py +72 -7
- reachy_mini/motion/recorded_move.py +76 -2
- reachy_mini/reachy_mini.py +196 -40
- reachy_mini/tools/reflash_motors.py +1 -1
- reachy_mini/tools/scan_motors.py +86 -0
- reachy_mini/tools/setup_motor.py +49 -31
- reachy_mini/utils/interpolation.py +1 -1
- reachy_mini/utils/wireless_version/startup_check.py +278 -21
- reachy_mini/utils/wireless_version/update.py +44 -1
- {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/METADATA +7 -6
- {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/RECORD +65 -53
- {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/WHEEL +0 -0
- {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/entry_points.txt +0 -0
- {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/licenses/LICENSE +0 -0
- {reachy_mini-1.2.5rc1.dist-info → reachy_mini-1.2.11.dist-info}/top_level.txt +0 -0
|
@@ -152,6 +152,231 @@ const hfAppsStore = {
|
|
|
152
152
|
installedApps.refreshAppList();
|
|
153
153
|
};
|
|
154
154
|
},
|
|
155
|
+
|
|
156
|
+
// Advanced functionality for private spaces
|
|
157
|
+
advanced: {
|
|
158
|
+
isAuthenticated: false,
|
|
159
|
+
username: null,
|
|
160
|
+
|
|
161
|
+
init: async () => {
|
|
162
|
+
// Check if we're on wireless version
|
|
163
|
+
try {
|
|
164
|
+
const status = await fetch('/api/daemon/status').then(r => r.json());
|
|
165
|
+
const isWireless = status.wireless_version;
|
|
166
|
+
|
|
167
|
+
if (!isWireless) {
|
|
168
|
+
// Don't show advanced section on non-wireless
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Show the advanced section
|
|
173
|
+
document.getElementById('hf-advanced-section').classList.remove('hidden');
|
|
174
|
+
|
|
175
|
+
// Set up event listeners
|
|
176
|
+
document.getElementById('hf-advanced-toggle').onclick = hfAppsStore.advanced.toggleSection;
|
|
177
|
+
document.getElementById('hf-login-button').onclick = hfAppsStore.advanced.login;
|
|
178
|
+
document.getElementById('hf-logout-button').onclick = hfAppsStore.advanced.logout;
|
|
179
|
+
document.getElementById('hf-install-private-button').onclick = hfAppsStore.advanced.installPrivateSpace;
|
|
180
|
+
|
|
181
|
+
// Add Enter key support for token input
|
|
182
|
+
document.getElementById('hf-token-input').addEventListener('keypress', (e) => {
|
|
183
|
+
if (e.key === 'Enter') {
|
|
184
|
+
hfAppsStore.advanced.login();
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Add Enter key support for space ID input
|
|
189
|
+
document.getElementById('hf-space-id-input').addEventListener('keypress', (e) => {
|
|
190
|
+
if (e.key === 'Enter') {
|
|
191
|
+
hfAppsStore.advanced.installPrivateSpace();
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Check authentication status
|
|
196
|
+
await hfAppsStore.advanced.checkAuthStatus();
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error('Error initializing advanced section:', error);
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
|
|
202
|
+
toggleSection: () => {
|
|
203
|
+
const content = document.getElementById('hf-advanced-content');
|
|
204
|
+
const chevron = document.getElementById('hf-advanced-chevron');
|
|
205
|
+
|
|
206
|
+
if (content.classList.contains('hidden')) {
|
|
207
|
+
content.classList.remove('hidden');
|
|
208
|
+
chevron.style.transform = 'rotate(90deg)';
|
|
209
|
+
} else {
|
|
210
|
+
content.classList.add('hidden');
|
|
211
|
+
chevron.style.transform = 'rotate(0deg)';
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
checkAuthStatus: async () => {
|
|
216
|
+
try {
|
|
217
|
+
const response = await fetch('/api/hf-auth/status');
|
|
218
|
+
const data = await response.json();
|
|
219
|
+
|
|
220
|
+
hfAppsStore.advanced.isAuthenticated = data.is_logged_in;
|
|
221
|
+
hfAppsStore.advanced.username = data.username;
|
|
222
|
+
|
|
223
|
+
hfAppsStore.advanced.updateAuthUI();
|
|
224
|
+
} catch (error) {
|
|
225
|
+
console.error('Error checking auth status:', error);
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
updateAuthUI: () => {
|
|
230
|
+
const indicator = document.getElementById('hf-auth-indicator');
|
|
231
|
+
const authText = document.getElementById('hf-auth-text');
|
|
232
|
+
const loginForm = document.getElementById('hf-login-form');
|
|
233
|
+
const loggedInView = document.getElementById('hf-logged-in-view');
|
|
234
|
+
const usernameSpan = document.getElementById('hf-username');
|
|
235
|
+
|
|
236
|
+
if (hfAppsStore.advanced.isAuthenticated) {
|
|
237
|
+
// Authenticated state
|
|
238
|
+
indicator.classList.remove('bg-gray-400');
|
|
239
|
+
indicator.classList.add('bg-green-500');
|
|
240
|
+
authText.textContent = 'Authenticated';
|
|
241
|
+
loginForm.classList.add('hidden');
|
|
242
|
+
loggedInView.classList.remove('hidden');
|
|
243
|
+
usernameSpan.textContent = hfAppsStore.advanced.username || 'Unknown';
|
|
244
|
+
} else {
|
|
245
|
+
// Not authenticated state
|
|
246
|
+
indicator.classList.remove('bg-green-500');
|
|
247
|
+
indicator.classList.add('bg-gray-400');
|
|
248
|
+
authText.textContent = 'Not authenticated';
|
|
249
|
+
loginForm.classList.remove('hidden');
|
|
250
|
+
loggedInView.classList.add('hidden');
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
login: async () => {
|
|
255
|
+
const tokenInput = document.getElementById('hf-token-input');
|
|
256
|
+
const errorDiv = document.getElementById('hf-login-error');
|
|
257
|
+
const loginButton = document.getElementById('hf-login-button');
|
|
258
|
+
|
|
259
|
+
const token = tokenInput.value.trim();
|
|
260
|
+
|
|
261
|
+
if (!token) {
|
|
262
|
+
errorDiv.textContent = 'Please enter a token';
|
|
263
|
+
errorDiv.classList.remove('hidden');
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Disable button during request
|
|
268
|
+
loginButton.disabled = true;
|
|
269
|
+
loginButton.textContent = 'Logging in...';
|
|
270
|
+
errorDiv.classList.add('hidden');
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
const response = await fetch('/api/hf-auth/save-token', {
|
|
274
|
+
method: 'POST',
|
|
275
|
+
headers: { 'Content-Type': 'application/json' },
|
|
276
|
+
body: JSON.stringify({ token })
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
if (!response.ok) {
|
|
280
|
+
const error = await response.json();
|
|
281
|
+
throw new Error(error.detail || 'Login failed');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const data = await response.json();
|
|
285
|
+
|
|
286
|
+
// Clear token input
|
|
287
|
+
tokenInput.value = '';
|
|
288
|
+
|
|
289
|
+
// Update state
|
|
290
|
+
hfAppsStore.advanced.isAuthenticated = true;
|
|
291
|
+
hfAppsStore.advanced.username = data.username;
|
|
292
|
+
hfAppsStore.advanced.updateAuthUI();
|
|
293
|
+
|
|
294
|
+
} catch (error) {
|
|
295
|
+
errorDiv.textContent = error.message;
|
|
296
|
+
errorDiv.classList.remove('hidden');
|
|
297
|
+
} finally {
|
|
298
|
+
loginButton.disabled = false;
|
|
299
|
+
loginButton.textContent = 'Login';
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
logout: async () => {
|
|
304
|
+
try {
|
|
305
|
+
await fetch('/api/hf-auth/token', { method: 'DELETE' });
|
|
306
|
+
|
|
307
|
+
hfAppsStore.advanced.isAuthenticated = false;
|
|
308
|
+
hfAppsStore.advanced.username = null;
|
|
309
|
+
hfAppsStore.advanced.updateAuthUI();
|
|
310
|
+
|
|
311
|
+
} catch (error) {
|
|
312
|
+
console.error('Error logging out:', error);
|
|
313
|
+
}
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
installPrivateSpace: async () => {
|
|
317
|
+
const spaceIdInput = document.getElementById('hf-space-id-input');
|
|
318
|
+
const errorDiv = document.getElementById('hf-private-install-error');
|
|
319
|
+
const installButton = document.getElementById('hf-install-private-button');
|
|
320
|
+
|
|
321
|
+
const spaceId = spaceIdInput.value.trim();
|
|
322
|
+
|
|
323
|
+
if (!spaceId) {
|
|
324
|
+
errorDiv.textContent = 'Please enter a space ID';
|
|
325
|
+
errorDiv.classList.remove('hidden');
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Validate format (should be "username/space-name")
|
|
330
|
+
if (!spaceId.includes('/')) {
|
|
331
|
+
errorDiv.textContent = 'Space ID should be in format: username/space-name';
|
|
332
|
+
errorDiv.classList.remove('hidden');
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Disable button during request
|
|
337
|
+
installButton.disabled = true;
|
|
338
|
+
installButton.textContent = 'Installing...';
|
|
339
|
+
errorDiv.classList.add('hidden');
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
const response = await fetch('/api/apps/install-private-space', {
|
|
343
|
+
method: 'POST',
|
|
344
|
+
headers: { 'Content-Type': 'application/json' },
|
|
345
|
+
body: JSON.stringify({ space_id: spaceId })
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
if (!response.ok) {
|
|
349
|
+
const error = await response.json();
|
|
350
|
+
throw new Error(error.detail || 'Installation failed');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const data = await response.json();
|
|
354
|
+
const jobId = data.job_id;
|
|
355
|
+
|
|
356
|
+
// Clear input
|
|
357
|
+
spaceIdInput.value = '';
|
|
358
|
+
|
|
359
|
+
// Show installation modal (reuse existing modal)
|
|
360
|
+
const spaceName = spaceId.split('/')[1];
|
|
361
|
+
hfAppsStore.appInstallLogHandler(spaceName, jobId);
|
|
362
|
+
|
|
363
|
+
} catch (error) {
|
|
364
|
+
if (error.message.includes('authenticate') || error.message.includes('401')) {
|
|
365
|
+
// Token expired or invalid - show login form
|
|
366
|
+
errorDiv.textContent = 'Authentication expired. Please login again.';
|
|
367
|
+
hfAppsStore.advanced.isAuthenticated = false;
|
|
368
|
+
hfAppsStore.advanced.username = null;
|
|
369
|
+
hfAppsStore.advanced.updateAuthUI();
|
|
370
|
+
} else {
|
|
371
|
+
errorDiv.textContent = error.message;
|
|
372
|
+
}
|
|
373
|
+
errorDiv.classList.remove('hidden');
|
|
374
|
+
} finally {
|
|
375
|
+
installButton.disabled = false;
|
|
376
|
+
installButton.textContent = 'Install Private Space';
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
},
|
|
155
380
|
};
|
|
156
381
|
|
|
157
382
|
|
|
@@ -164,4 +389,7 @@ window.addEventListener('load', async () => {
|
|
|
164
389
|
});
|
|
165
390
|
}
|
|
166
391
|
await hfAppsStore.refreshAppList();
|
|
392
|
+
|
|
393
|
+
// Initialize advanced section for private spaces
|
|
394
|
+
await hfAppsStore.advanced.init();
|
|
167
395
|
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const daemonLogs = {
|
|
2
|
+
ws: null,
|
|
3
|
+
isConnected: false,
|
|
4
|
+
|
|
5
|
+
connectWebSocket: () => {
|
|
6
|
+
const logsDiv = document.getElementById('daemon-logs-content');
|
|
7
|
+
const statusDiv = document.getElementById('logs-status');
|
|
8
|
+
|
|
9
|
+
if (!logsDiv || !statusDiv) {
|
|
10
|
+
console.error('Logs elements not found');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Create WebSocket connection
|
|
15
|
+
daemonLogs.ws = new WebSocket(`ws://${location.host}/logs/ws/daemon`);
|
|
16
|
+
|
|
17
|
+
daemonLogs.ws.onopen = () => {
|
|
18
|
+
daemonLogs.isConnected = true;
|
|
19
|
+
statusDiv.textContent = 'Connected';
|
|
20
|
+
statusDiv.className = 'text-sm text-green-600 font-semibold';
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
daemonLogs.ws.onmessage = (event) => {
|
|
24
|
+
// Ignore empty keepalive messages
|
|
25
|
+
if (event.data && event.data.trim()) {
|
|
26
|
+
daemonLogs.appendLog(event.data);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
daemonLogs.ws.onclose = () => {
|
|
31
|
+
daemonLogs.isConnected = false;
|
|
32
|
+
statusDiv.textContent = 'Disconnected';
|
|
33
|
+
statusDiv.className = 'text-sm text-red-600 font-semibold';
|
|
34
|
+
|
|
35
|
+
// Attempt reconnection after 2 seconds
|
|
36
|
+
setTimeout(() => {
|
|
37
|
+
if (!daemonLogs.isConnected) {
|
|
38
|
+
console.log('Attempting to reconnect to logs WebSocket...');
|
|
39
|
+
daemonLogs.connectWebSocket();
|
|
40
|
+
}
|
|
41
|
+
}, 2000);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
daemonLogs.ws.onerror = (error) => {
|
|
45
|
+
console.error('WebSocket error:', error);
|
|
46
|
+
statusDiv.textContent = 'Connection Error';
|
|
47
|
+
statusDiv.className = 'text-sm text-red-600 font-semibold';
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
appendLog: (line) => {
|
|
52
|
+
const logsDiv = document.getElementById('daemon-logs-content');
|
|
53
|
+
|
|
54
|
+
if (!logsDiv) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check if user is scrolled to bottom before adding new log
|
|
59
|
+
const isScrolledToBottom = logsDiv.scrollHeight - logsDiv.scrollTop <= logsDiv.clientHeight + 50;
|
|
60
|
+
|
|
61
|
+
// Create new log line element
|
|
62
|
+
const logLine = document.createElement('div');
|
|
63
|
+
logLine.className = 'text-gray-300 leading-tight';
|
|
64
|
+
|
|
65
|
+
// Highlight error and warning lines
|
|
66
|
+
if (line.includes('ERROR') || line.includes('Error') || line.includes('error')) {
|
|
67
|
+
logLine.className = 'text-red-400 font-semibold leading-tight';
|
|
68
|
+
} else if (line.includes('WARNING') || line.includes('Warning') || line.includes('warning')) {
|
|
69
|
+
logLine.className = 'text-yellow-400 leading-tight';
|
|
70
|
+
} else if (line.includes('INFO')) {
|
|
71
|
+
logLine.className = 'text-green-400 leading-tight';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
logLine.textContent = line;
|
|
75
|
+
logsDiv.appendChild(logLine);
|
|
76
|
+
|
|
77
|
+
// Only auto-scroll if user was already at the bottom
|
|
78
|
+
if (isScrolledToBottom) {
|
|
79
|
+
requestAnimationFrame(() => {
|
|
80
|
+
logsDiv.scrollTop = logsDiv.scrollHeight;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
clearLogs: () => {
|
|
86
|
+
const logsDiv = document.getElementById('daemon-logs-content');
|
|
87
|
+
|
|
88
|
+
if (logsDiv) {
|
|
89
|
+
logsDiv.innerHTML = '';
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
copyLogs: () => {
|
|
94
|
+
const logsDiv = document.getElementById('daemon-logs-content');
|
|
95
|
+
const buttonText = document.getElementById('copy-button-text');
|
|
96
|
+
|
|
97
|
+
if (!logsDiv) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Get all log text
|
|
102
|
+
const logText = logsDiv.innerText;
|
|
103
|
+
|
|
104
|
+
// Create temporary textarea for copying (works without HTTPS)
|
|
105
|
+
const textarea = document.createElement('textarea');
|
|
106
|
+
textarea.value = logText;
|
|
107
|
+
textarea.style.position = 'fixed';
|
|
108
|
+
textarea.style.opacity = '0';
|
|
109
|
+
document.body.appendChild(textarea);
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
// Select and copy
|
|
113
|
+
textarea.select();
|
|
114
|
+
textarea.setSelectionRange(0, 99999); // For mobile devices
|
|
115
|
+
const successful = document.execCommand('copy');
|
|
116
|
+
|
|
117
|
+
if (successful && buttonText) {
|
|
118
|
+
buttonText.textContent = 'Copied!';
|
|
119
|
+
setTimeout(() => {
|
|
120
|
+
buttonText.textContent = 'Copy';
|
|
121
|
+
}, 2000);
|
|
122
|
+
} else if (buttonText) {
|
|
123
|
+
buttonText.textContent = 'Failed';
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
buttonText.textContent = 'Copy';
|
|
126
|
+
}, 2000);
|
|
127
|
+
}
|
|
128
|
+
} catch (err) {
|
|
129
|
+
console.error('Failed to copy logs:', err);
|
|
130
|
+
if (buttonText) {
|
|
131
|
+
buttonText.textContent = 'Failed';
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
buttonText.textContent = 'Copy';
|
|
134
|
+
}, 2000);
|
|
135
|
+
}
|
|
136
|
+
} finally {
|
|
137
|
+
document.body.removeChild(textarea);
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
disconnect: () => {
|
|
142
|
+
if (daemonLogs.ws) {
|
|
143
|
+
daemonLogs.ws.close();
|
|
144
|
+
daemonLogs.ws = null;
|
|
145
|
+
daemonLogs.isConnected = false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
|
|
3
|
+
{% block content %}
|
|
4
|
+
|
|
5
|
+
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4">
|
|
6
|
+
<div class="flex items-center justify-between mb-4">
|
|
7
|
+
<div class="flex items-center gap-3">
|
|
8
|
+
<h1 class="text-xl font-semibold text-gray-900 dark:text-white">Daemon Logs</h1>
|
|
9
|
+
<span id="logs-status" class="text-sm text-gray-500">Disconnected</span>
|
|
10
|
+
</div>
|
|
11
|
+
<div class="flex gap-2">
|
|
12
|
+
<button onclick="daemonLogs.copyLogs()" class="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded text-sm">
|
|
13
|
+
<span id="copy-button-text">Copy</span>
|
|
14
|
+
</button>
|
|
15
|
+
<button onclick="daemonLogs.clearLogs()" class="px-4 py-2 bg-gray-200 hover:bg-gray-300 text-gray-800 rounded dark:bg-gray-600 dark:hover:bg-gray-500 dark:text-white">
|
|
16
|
+
Clear
|
|
17
|
+
</button>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div id="daemon-logs-content"
|
|
22
|
+
class="overflow-y-auto h-[calc(100vh-200px)] bg-gray-900 border border-gray-600 rounded p-3 font-mono text-xs text-gray-100 leading-relaxed">
|
|
23
|
+
<!-- Logs will be appended here by JavaScript -->
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
{% endblock %}
|
|
28
|
+
|
|
29
|
+
{% block extra_js %}
|
|
30
|
+
<script src="/static/js/logs.js"></script>
|
|
31
|
+
<script>
|
|
32
|
+
// Auto-connect when page loads
|
|
33
|
+
window.addEventListener('load', () => {
|
|
34
|
+
daemonLogs.connectWebSocket();
|
|
35
|
+
});
|
|
36
|
+
</script>
|
|
37
|
+
{% endblock %}
|
|
@@ -32,4 +32,96 @@
|
|
|
32
32
|
<div class="">
|
|
33
33
|
<ul id="hf-available-apps" class="overflow-y-auto max-h-96">Loading apps from 🤗...</ul>
|
|
34
34
|
</div>
|
|
35
|
+
|
|
36
|
+
<!-- Advanced section for private spaces (wireless only) -->
|
|
37
|
+
<div id="hf-advanced-section" class="mt-6 hidden">
|
|
38
|
+
<!-- Collapsible header -->
|
|
39
|
+
<button
|
|
40
|
+
id="hf-advanced-toggle"
|
|
41
|
+
class="flex items-center justify-between w-full text-left font-medium text-gray-700 dark:text-gray-200 hover:text-gray-900 dark:hover:text-white transition-colors py-2"
|
|
42
|
+
type="button"
|
|
43
|
+
>
|
|
44
|
+
<span class="flex items-center gap-2 text-base">
|
|
45
|
+
<svg id="hf-advanced-chevron" class="w-5 h-5 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
46
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
|
47
|
+
</svg>
|
|
48
|
+
Advanced: Install private space
|
|
49
|
+
</span>
|
|
50
|
+
</button>
|
|
51
|
+
|
|
52
|
+
<!-- Collapsible content -->
|
|
53
|
+
<div id="hf-advanced-content" class="hidden mt-4 p-6 bg-gray-100 rounded-lg border-2 border-gray-300" style="background-color: #f3f4f6; border-color: #d1d5db;">
|
|
54
|
+
|
|
55
|
+
<!-- Auth status display -->
|
|
56
|
+
<div id="hf-auth-status" class="flex items-center gap-3 mb-6 pb-4 border-b-2 border-gray-300" style="border-color: #d1d5db;">
|
|
57
|
+
<div id="hf-auth-indicator" class="w-3 h-3 rounded-full bg-gray-400"></div>
|
|
58
|
+
<span id="hf-auth-text" class="text-base font-semibold" style="color: #374151;">Not authenticated</span>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<!-- Login form (shown when not authenticated) -->
|
|
62
|
+
<div id="hf-login-form" class="space-y-4">
|
|
63
|
+
<div>
|
|
64
|
+
<label for="hf-token-input" class="block text-lg font-bold mb-3" style="color: #111827;">
|
|
65
|
+
HuggingFace Token
|
|
66
|
+
</label>
|
|
67
|
+
<input
|
|
68
|
+
type="password"
|
|
69
|
+
id="hf-token-input"
|
|
70
|
+
placeholder="hf_..."
|
|
71
|
+
style="width: 100%; min-height: 48px; padding: 12px 16px; font-size: 16px; font-family: monospace; border: 2px solid #9ca3af; border-radius: 8px; background-color: #ffffff; color: #111827;"
|
|
72
|
+
class="focus:outline-none focus:ring-2 focus:ring-red-500"
|
|
73
|
+
/>
|
|
74
|
+
<p class="mt-3 text-sm" style="color: #6b7280;">
|
|
75
|
+
Get your token from <a href="https://huggingface.co/settings/tokens" target="_blank" class="hover:underline font-medium" style="color: #dc2626;">huggingface.co/settings/tokens</a>
|
|
76
|
+
</p>
|
|
77
|
+
</div>
|
|
78
|
+
<button
|
|
79
|
+
id="hf-login-button"
|
|
80
|
+
style="width: 100%; min-height: 48px; padding: 12px 24px; font-size: 18px; font-weight: 600; background-color: #dc2626; color: #ffffff; border: none; border-radius: 8px; cursor: pointer;"
|
|
81
|
+
class="hover:bg-red-700 transition-colors shadow-md"
|
|
82
|
+
>
|
|
83
|
+
Login to HuggingFace
|
|
84
|
+
</button>
|
|
85
|
+
<div id="hf-login-error" class="hidden text-base font-medium p-3 rounded-lg border-2" style="color: #dc2626; background-color: #fee2e2; border-color: #fecaca;"></div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<!-- Logged in view (shown when authenticated) -->
|
|
89
|
+
<div id="hf-logged-in-view" class="hidden space-y-5">
|
|
90
|
+
<div class="flex items-center justify-between p-4 rounded-lg border-2" style="background-color: #d1fae5; border-color: #86efac;">
|
|
91
|
+
<span class="text-base" style="color: #065f46;">
|
|
92
|
+
Logged in as <span id="hf-username" class="font-bold" style="color: #064e3b;"></span>
|
|
93
|
+
</span>
|
|
94
|
+
<button
|
|
95
|
+
id="hf-logout-button"
|
|
96
|
+
class="text-base hover:underline font-medium"
|
|
97
|
+
style="color: #dc2626;"
|
|
98
|
+
>
|
|
99
|
+
Logout
|
|
100
|
+
</button>
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<!-- Private space installation -->
|
|
104
|
+
<div>
|
|
105
|
+
<label for="hf-space-id-input" class="block text-lg font-bold mb-3" style="color: #111827;">
|
|
106
|
+
Private Space ID
|
|
107
|
+
</label>
|
|
108
|
+
<input
|
|
109
|
+
type="text"
|
|
110
|
+
id="hf-space-id-input"
|
|
111
|
+
placeholder="username/space-name"
|
|
112
|
+
style="width: 100%; min-height: 48px; padding: 12px 16px; font-size: 16px; font-family: monospace; border: 2px solid #9ca3af; border-radius: 8px; background-color: #ffffff; color: #111827;"
|
|
113
|
+
class="focus:outline-none focus:ring-2 focus:ring-red-500"
|
|
114
|
+
/>
|
|
115
|
+
</div>
|
|
116
|
+
<button
|
|
117
|
+
id="hf-install-private-button"
|
|
118
|
+
style="width: 100%; min-height: 48px; padding: 12px 24px; font-size: 18px; font-weight: 600; background-color: #ffffff; color: #dc2626; border: 2px solid #dc2626; border-radius: 8px; cursor: pointer;"
|
|
119
|
+
class="hover:bg-red-600 hover:text-white transition-colors shadow-md"
|
|
120
|
+
>
|
|
121
|
+
Install Private Space
|
|
122
|
+
</button>
|
|
123
|
+
<div id="hf-private-install-error" class="hidden text-base font-medium p-3 rounded-lg border-2" style="color: #dc2626; background-color: #fee2e2; border-color: #fecaca;"></div>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
35
127
|
</section>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<div class="app-section">
|
|
2
|
+
<div class="app-section-title">Cache Management</div>
|
|
3
|
+
|
|
4
|
+
<div class="mt-2">
|
|
5
|
+
<button id="clear-hf-cache-btn"
|
|
6
|
+
onclick="clearHFCache()"
|
|
7
|
+
class="px-4 py-2 rounded-lg shadow bg-red-500 hover:bg-red-600"
|
|
8
|
+
style="color: white;">
|
|
9
|
+
Clear HuggingFace Cache
|
|
10
|
+
</button>
|
|
11
|
+
<span id="cache-status" class="ml-3 text-sm"></span>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="mt-2">
|
|
14
|
+
<button id="reset-apps"
|
|
15
|
+
onclick="resetApps()"
|
|
16
|
+
class="px-4 py-2 rounded-lg shadow bg-red-500 hover:bg-red-600"
|
|
17
|
+
style="color: white;">
|
|
18
|
+
Reset Applications
|
|
19
|
+
</button>
|
|
20
|
+
<span id="reset-status" class="ml-3 text-sm"></span>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<script>
|
|
25
|
+
async function clearHFCache() {
|
|
26
|
+
const btn = document.getElementById('clear-hf-cache-btn');
|
|
27
|
+
const status = document.getElementById('cache-status');
|
|
28
|
+
|
|
29
|
+
btn.disabled = true;
|
|
30
|
+
btn.textContent = 'Clearing...';
|
|
31
|
+
status.textContent = '';
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch('/cache/clear-hf', { method: 'POST' });
|
|
35
|
+
const data = await response.json();
|
|
36
|
+
|
|
37
|
+
if (response.ok) {
|
|
38
|
+
status.textContent = '✓ Cache cleared';
|
|
39
|
+
status.className = 'ml-3 text-sm text-green-600';
|
|
40
|
+
} else {
|
|
41
|
+
status.textContent = '✗ ' + (data.detail || 'Failed');
|
|
42
|
+
status.className = 'ml-3 text-sm text-red-600';
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
status.textContent = '✗ Error: ' + error.message;
|
|
46
|
+
status.className = 'ml-3 text-sm text-red-600';
|
|
47
|
+
} finally {
|
|
48
|
+
btn.disabled = false;
|
|
49
|
+
btn.textContent = 'Clear HuggingFace Cache';
|
|
50
|
+
setTimeout(() => { status.textContent = ''; }, 3000);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function resetApps() {
|
|
55
|
+
const btn = document.getElementById('reset-apps');
|
|
56
|
+
const status = document.getElementById('reset-status');
|
|
57
|
+
|
|
58
|
+
btn.disabled = true;
|
|
59
|
+
btn.textContent = 'Resetting...';
|
|
60
|
+
status.textContent = '';
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const response = await fetch('/cache/reset-apps', { method: 'POST' });
|
|
64
|
+
const data = await response.json();
|
|
65
|
+
|
|
66
|
+
if (response.ok) {
|
|
67
|
+
status.textContent = '✓ Apps reset';
|
|
68
|
+
status.className = 'ml-3 text-sm text-green-600';
|
|
69
|
+
} else {
|
|
70
|
+
status.textContent = '✗ ' + (data.detail || 'Failed');
|
|
71
|
+
status.className = 'ml-3 text-sm text-red-600';
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
status.textContent = '✗ Error: ' + error.message;
|
|
75
|
+
status.className = 'ml-3 text-sm text-red-600';
|
|
76
|
+
} finally {
|
|
77
|
+
btn.disabled = false;
|
|
78
|
+
btn.textContent = 'Reset Applications';
|
|
79
|
+
setTimeout(() => { status.textContent = ''; }, 3000);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
</script>
|
|
@@ -35,6 +35,11 @@
|
|
|
35
35
|
</div>
|
|
36
36
|
{% if args.wireless_version %}
|
|
37
37
|
<div class="w-full flex pt-4 gap-4 justify-center">
|
|
38
|
+
<a id="daemon-logs-btn"
|
|
39
|
+
class="flex items-center gap-2 px-4 py-2 bg-gray-100 hover:bg-gray-300 rounded-lg shadow border border-gray-300"
|
|
40
|
+
href="/logs">
|
|
41
|
+
Logs
|
|
42
|
+
</a>
|
|
38
43
|
<a id="daemon-settings-btn"
|
|
39
44
|
class="flex items-center gap-2 px-4 py-2 bg-gray-100 hover:bg-gray-300 rounded-lg shadow border border-gray-300"
|
|
40
45
|
href="/settings">
|