# ๐Ÿš€ HuggingFace Spaces Storage Strategy - Complete Walkthrough ## ๐Ÿ“‹ Overview This guide helps you test and implement HuggingFace Datasets as persistent storage for ColorRM on HF Spaces (free tier). --- ## ๐ŸŽฏ Problem Statement **HF Spaces Free Tier Limitations:** - โŒ No persistent storage (ephemeral filesystem) - โŒ Data lost on restart/redeploy - โŒ Can't use IndexedDB across sessions (browser-based) **Solution:** - โœ… Use HuggingFace Datasets API as cloud storage - โœ… Store PDFs and projects as dataset files - โœ… Free, unlimited storage - โœ… Accessible from any device --- ## ๐Ÿงช Step 1: Run Speed Test ### A. Open Test Page ```bash # Open in browser open test_hf_dataset_storage.html # or python -m http.server 8000 # then visit http://localhost:8000/test_hf_dataset_storage.html ``` ### B. Get HuggingFace Token 1. Go to https://huggingface.co/settings/tokens 2. Click "New token" 3. Name: `colorrm-storage` 4. Type: **Write** (needs read + write access) 5. Copy token (starts with `hf_...`) ### C. Create Test Dataset 1. Go to https://huggingface.co/new-dataset 2. Name: `colorrm-test-storage` 3. Type: **Private** (recommended for user data) 4. Click "Create dataset" ### D. Run Tests 1. Paste HF token in test page 2. Enter dataset name: `username/colorrm-test-storage` 3. Select a test PDF (5-10 pages recommended) 4. Click **"Compare Both"** 5. Wait for results (30-60 seconds) --- ## ๐Ÿ“Š Step 2: Analyze Results ### Expected Performance **IndexedDB (Baseline):** - Write: 10-50ms - Read: 5-20ms - โœ… Very fast (local) - โŒ Not persistent on HF Spaces **HF Dataset API:** - Write: 500-3000ms (network + API) - Read: 200-1000ms (network latency) - โœ… Persistent across sessions - โœ… Cross-device access - โš ๏ธ Slower due to network ### Decision Matrix | Metric | Good Range | Action | |--------|------------|--------| | HF Write | < 2000ms | โœ… Acceptable | | HF Write | > 3000ms | โš ๏ธ Consider hybrid | | HF Read | < 1000ms | โœ… Acceptable | | HF Read | > 2000ms | โš ๏ธ Add caching | --- ## ๐Ÿ—๏ธ Step 3: Implementation Strategy ### Option A: Pure HF Dataset (Simple) **When to use:** - HF read/write < 2s - Small PDFs (< 5MB) - Occasional access **Pros:** - Simple implementation - No dual storage complexity **Cons:** - Slower UX - Network dependency ### Option B: Hybrid (Recommended) **When to use:** - HF write > 2s OR large PDFs - Frequent page navigation - Better UX needed **How it works:** ``` User Session: โ”œโ”€ IndexedDB (Fast cache) โ”‚ โ””โ”€ Active session data โ”‚ โ””โ”€ HF Dataset (Persistent) โ””โ”€ Background sync On Load: 1. Check IndexedDB first (instant) 2. If missing, fetch from HF Dataset 3. Cache in IndexedDB On Save: 1. Save to IndexedDB (instant feedback) 2. Queue background sync to HF Dataset 3. Sync every 30s or on page close ``` **Pros:** - โœ… Fast UX (IndexedDB speed) - โœ… Persistent (HF Dataset) - โœ… Offline-capable **Cons:** - More complex code - Potential sync conflicts --- ## ๐Ÿ’ป Step 4: Code Implementation ### A. Create HF Dataset Storage Module ```javascript // hf-storage.js class HFDatasetStorage { constructor(token, datasetName) { this.token = token; this.datasetName = datasetName; this.baseURL = `https://huggingface.co/api/datasets/${datasetName}`; } async saveProject(project) { const blob = new Blob([JSON.stringify(project)], { type: 'application/json' }); const formData = new FormData(); formData.append('file', blob, `${project.id}.json`); const response = await fetch(`${this.baseURL}/upload/main`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.token}` }, body: formData }); if (!response.ok) throw new Error('HF save failed'); return response.json(); } async loadProject(projectId) { const response = await fetch( `https://huggingface.co/datasets/${this.datasetName}/resolve/main/${projectId}.json`, { headers: { 'Authorization': `Bearer ${this.token}` } } ); if (!response.ok) throw new Error('HF load failed'); return response.json(); } async listProjects() { const response = await fetch( `https://huggingface.co/api/datasets/${this.datasetName}/tree/main`, { headers: { 'Authorization': `Bearer ${this.token}` } } ); if (!response.ok) return []; const files = await response.json(); return files.filter(f => f.path.endsWith('.json')).map(f => f.path.replace('.json', '')); } async deleteProject(projectId) { // HF API doesn't support delete via API // Workaround: Upload empty file or mark as deleted const deleted = { id: projectId, deleted: true }; return this.saveProject(deleted); } } ``` ### B. Hybrid Storage Manager ```javascript // hybrid-storage.js class HybridStorage { constructor(hfToken, datasetName) { this.hfStorage = new HFDatasetStorage(hfToken, datasetName); this.idbName = 'ColorRMCache'; this.syncQueue = []; } async saveProject(project) { // Save to IndexedDB immediately (fast) await this.saveToIndexedDB(project); // Queue for HF sync (background) this.queueSync(project); return project.id; } async loadProject(projectId) { // Try IndexedDB first (fast) const cached = await this.loadFromIndexedDB(projectId); if (cached) return cached; // Fallback to HF Dataset (slower) const remote = await this.hfStorage.loadProject(projectId); // Cache in IndexedDB for next time await this.saveToIndexedDB(remote); return remote; } queueSync(project) { this.syncQueue.push(project); // Debounced sync (every 30s) clearTimeout(this.syncTimer); this.syncTimer = setTimeout(() => this.processSyncQueue(), 30000); } async processSyncQueue() { console.log('Syncing', this.syncQueue.length, 'projects to HF Dataset...'); for (const project of this.syncQueue) { try { await this.hfStorage.saveProject(project); console.log('โœ“ Synced:', project.id); } catch (error) { console.error('โœ— Sync failed:', project.id, error); } } this.syncQueue = []; } async saveToIndexedDB(project) { // Standard IndexedDB code... } async loadFromIndexedDB(projectId) { // Standard IndexedDB code... } } ``` ### C. Usage in ColorRM ```javascript // In splitview-simple.js // Initialize with HF token from environment variable const storage = new HybridStorage( import.meta.env.VITE_HF_TOKEN, 'username/colorrm-storage' ); // Replace existing IndexedDB calls async handlePdfUpload(event) { const file = event.target.files[0]; const project = { id: `proj_${Date.now()}`, name: file.name, pdfBase64: base64, timestamp: Date.now() }; // This now saves to both IDB + HF Dataset await storage.saveProject(project); await this.openProject(project.id); } // Load with automatic fallback async openProject(projectId) { // Tries IDB first, then HF Dataset const project = await storage.loadProject(projectId); // ... rest of code } ``` --- ## ๐Ÿš€ Step 5: Deploy to HF Spaces ### A. Add Environment Variables In HF Space settings: ```bash HF_TOKEN=hf_your_write_token_here HF_DATASET_NAME=username/colorrm-storage ``` ### B. Update Code ```javascript // config.js export const config = { hfToken: import.meta.env.HF_TOKEN || localStorage.getItem('hf_token'), datasetName: import.meta.env.HF_DATASET_NAME || 'colorrm-storage' }; ``` ### C. Create `.env` for local dev ```bash HF_TOKEN=hf_your_token HF_DATASET_NAME=username/colorrm-storage ``` --- ## ๐Ÿ“ˆ Step 6: Monitor Performance ### Add Performance Metrics ```javascript class PerformanceMonitor { async measureOperation(name, fn) { const start = performance.now(); const result = await fn(); const duration = performance.now() - start; console.log(`[${name}] ${duration.toFixed(0)}ms`); // Send to analytics (optional) this.logMetric(name, duration); return result; } } // Usage const monitor = new PerformanceMonitor(); await monitor.measureOperation('HF Save', () => hfStorage.saveProject(project) ); ``` --- ## โœ… Step 7: Testing Checklist - [ ] Test page shows both IDB and HF metrics - [ ] HF write time < 3000ms - [ ] HF read time < 1000ms - [ ] PDFs persist after browser refresh - [ ] PDFs accessible from different devices - [ ] Offline mode works (if hybrid) - [ ] Sync queue processes correctly - [ ] No data loss during sync - [ ] Error handling works - [ ] Performance acceptable for users --- ## ๐ŸŽฏ Expected Results ### Good Performance ``` IndexedDB Write: 15ms IndexedDB Read: 8ms HF Dataset Write: 1200ms โœ… Acceptable HF Dataset Read: 450ms โœ… Good Recommendation: Use Hybrid strategy - Fast user experience (IDB) - Persistent storage (HF) - Background sync every 30s ``` ### Poor Performance ``` IndexedDB Write: 20ms IndexedDB Read: 10ms HF Dataset Write: 5000ms โš ๏ธ Slow HF Dataset Read: 2500ms โš ๏ธ Slow Recommendation: - Add aggressive caching - Increase sync interval to 5min - Show loading indicators - Consider compression ``` --- ## ๐Ÿ”ง Optimization Tips ### 1. Compress Data ```javascript // Use pako for gzip compression import pako from 'pako'; const compressed = pako.gzip(JSON.stringify(project)); const base64 = btoa(String.fromCharCode.apply(null, compressed)); ``` ### 2. Batch Operations ```javascript // Instead of saving each project separately await Promise.all(projects.map(p => hfStorage.saveProject(p))); ``` ### 3. Smart Caching ```javascript // Cache frequently accessed projects const lru = new LRUCache({ max: 10 }); lru.set(projectId, project); ``` ### 4. Progressive Loading ```javascript // Load PDF metadata first, pages on demand const metadata = await loadMetadata(projectId); const page1 = await loadPage(projectId, 1); // Lazy load ``` --- ## ๐Ÿ“ Summary 1. **Run test page** โ†’ Get actual performance numbers 2. **Analyze results** โ†’ Choose pure HF or hybrid 3. **Implement storage** โ†’ Add HF Dataset integration 4. **Deploy to HF Spaces** โ†’ Configure tokens 5. **Monitor performance** โ†’ Track metrics 6. **Optimize if needed** โ†’ Compression, caching, batching **Key Takeaway:** HF Datasets work great for persistent storage on free tier, but hybrid approach gives best UX! --- ## ๐Ÿ†˜ Troubleshooting **"401 Unauthorized"** - Check token has write access - Verify token not expired - Ensure dataset is private or public **"Slow uploads"** - Compress data before upload - Use smaller PDFs for testing - Check network connection **"Data not persisting"** - Verify dataset name correct - Check token permissions - Ensure sync queue processing --- **Ready to test?** Open `test_hf_dataset_storage.html` and start benchmarking! ๐Ÿš€