diff --git a/examples/openarms_web_interface/App.css b/examples/openarms_web_interface/App.css index 8a6a42e43..49271ef82 100644 --- a/examples/openarms_web_interface/App.css +++ b/examples/openarms_web_interface/App.css @@ -205,6 +205,47 @@ h3 { transform: none; } +.delete-episode-section { + margin-top: 1rem; + padding-top: 1rem; + border-top: 1px solid #e5e7eb; +} + +.btn-delete { + width: 100%; + background: #ef4444; + color: white; + border: none; + padding: 0.875rem 1.5rem; + border-radius: 8px; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; + box-shadow: 0 2px 4px rgba(239, 68, 68, 0.2); +} + +.btn-delete:hover:not(:disabled) { + background: #dc2626; + box-shadow: 0 4px 8px rgba(239, 68, 68, 0.3); + transform: translateY(-1px); +} + +.btn-delete:disabled { + background: #d1d5db; + cursor: not-allowed; + box-shadow: none; + transform: none; +} + +.delete-info { + margin-top: 0.5rem; + font-size: 0.875rem; + color: #666; + text-align: center; + font-style: italic; +} + .btn-disconnect { background: #ef4444; color: white; diff --git a/examples/openarms_web_interface/App.jsx b/examples/openarms_web_interface/App.jsx index cd926928f..d634b120c 100644 --- a/examples/openarms_web_interface/App.jsx +++ b/examples/openarms_web_interface/App.jsx @@ -21,6 +21,7 @@ function App() { const [rampUpRemaining, setRampUpRemaining] = useState(0); const [movingToZero, setMovingToZero] = useState(false); const [configExpanded, setConfigExpanded] = useState(false); + const [latestRepoId, setLatestRepoId] = useState(null); // Configuration const [config, setConfig] = useState({ @@ -82,6 +83,11 @@ function App() { setUploadStatus(data.upload_status); setRampUpRemaining(data.ramp_up_remaining || 0); setMovingToZero(data.moving_to_zero || false); + + // Track the latest repo_id from the backend + if (data.latest_repo_id) { + setLatestRepoId(data.latest_repo_id); + } if (data.config) { // Only merge server config if we don't have a saved config (first load) @@ -308,13 +314,54 @@ function App() { throw new Error(data.detail || 'Failed to stop recording'); } - await response.json(); + const data = await response.json(); setError(null); + // Update latest repo_id after recording + if (data.dataset_name) { + setLatestRepoId(`lerobot-data-collection/${data.dataset_name}`); + } } catch (e) { setError(e.message); } }; + const deleteLatestEpisode = async () => { + if (!latestRepoId) { + setError('No episode to delete'); + return; + } + + const confirmed = window.confirm( + `WARNING: This will permanently delete the repository:\n\n${latestRepoId}\n\nThis action cannot be undone. Continue?` + ); + + if (!confirmed) { + return; + } + + try { + const response = await fetch(`${API_BASE}/recording/delete-latest`, { method: 'POST' }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.detail || 'Failed to delete episode'); + } + + const data = await response.json(); + setLatestRepoId(null); + setEpisodeCount(Math.max(0, episodeCount - 1)); + setStatusMessage(`Deleted: ${data.deleted_repo}`); + + setTimeout(() => { + if (!isRecording && !isInitializing) { + setStatusMessage('Ready'); + } + }, 3000); + } catch (e) { + setError(`Delete failed: ${e.message}`); + } + }; + // Reset counter const resetCounter = async () => { try { @@ -730,6 +777,20 @@ function App() { + {/* Delete Latest Episode Button */} + {!isRecording && !isInitializing && latestRepoId && ( +