// Real Hugging Face API integration class AssetManager { constructor() { this.assets = []; this.currentId = 0; this.token = null; this.spaceName = null; this.username = null; } async authenticate(token) { this.token = token; try { const response = await fetch('https://huggingface.co/api/whoami-v2', { headers: { 'Authorization': `Bearer ${token}` } }); const userData = await response.json(); this.username = userData.name; return true; } catch (error) { console.error('Authentication failed:', error); return false; } } async loadAssets(spaceName) { this.spaceName = spaceName; try { const response = await fetch(`https://huggingface.co/api/spaces/${this.username}/${spaceName}/files`, { headers: { 'Authorization': `Bearer ${this.token}` } }); const files = await response.json(); this.assets = files.map(file => ({ id: ++this.currentId, name: file.path.split('/').pop(), type: this.getFileType(file.path), url: `https://huggingface.co/spaces/${this.username}/${spaceName}/resolve/main/${file.path}`, preview: this.getPreviewUrl(file.path), path: file.path, lastModified: file.lastModified })); return this.assets; } catch (error) { console.error('Failed to load assets:', error); return []; } } getFileType(filename) { const extension = filename.split('.').pop().toLowerCase(); const types = { 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'png': 'image/png', 'gif': 'image/gif', 'pdf': 'application/pdf', 'txt': 'text/plain', 'csv': 'text/csv', 'json': 'application/json' }; return types[extension] || 'application/octet-stream'; } getPreviewUrl(filename) { const extension = filename.split('.').pop().toLowerCase(); if (['jpg', 'jpeg', 'png', 'gif'].includes(extension)) { return `https://huggingface.co/spaces/${this.username}/${this.spaceName}/preview/${filename}`; } return `http://static.photos/office/320x240/${Math.floor(Math.random() * 100)}`; } async addAsset(file) { const formData = new FormData(); formData.append('file', file); try { const response = await fetch(`https://huggingface.co/api/spaces/${this.username}/${this.spaceName}/upload`, { method: 'POST', headers: { 'Authorization': `Bearer ${this.token}` }, body: formData }); if (response.ok) { const newAsset = { id: ++this.currentId, name: file.name, type: file.type || this.getFileType(file.name), url: `https://huggingface.co/spaces/${this.username}/${this.spaceName}/resolve/main/${file.name}`, preview: this.getPreviewUrl(file.name), path: file.name, lastModified: new Date().toISOString() }; this.assets.push(newAsset); return newAsset; } return null; } catch (error) { console.error('Failed to upload file:', error); return null; } } async updateAsset(id, updates) { const asset = this.assets.find(a => a.id === id); if (!asset) return null; // For Hugging Face, updating means deleting and re-uploading if (updates.file) { await this.deleteAsset(id); return await this.addAsset(updates.file); } else { // For metadata updates const index = this.assets.findIndex(a => a.id === id); this.assets[index] = { ...asset, ...updates }; return this.assets[index]; } } async deleteAsset(id) { const asset = this.assets.find(a => a.id === id); if (!asset) return false; try { const response = await fetch(`https://huggingface.co/api/spaces/${this.username}/${this.spaceName}/delete/${asset.path}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${this.token}` } }); if (response.ok) { this.assets = this.assets.filter(a => a.id !== id); return true; } return false; } catch (error) { console.error('Failed to delete file:', error); return false; } } getAssets() { return [...this.assets]; } exportAsJSON() { return JSON.stringify(this.assets, null, 2); } } // Main application with authentication class HuggingSpaceApp { constructor() { this.assetManager = new AssetManager(); this.initElements(); this.initEventListeners(); this.checkAuth(); } initElements() { this.elements = { assetsTable: document.getElementById('assetsTable'), uploadBtn: document.getElementById('uploadBtn'), uploadModal: document.getElementById('uploadModal'), closeModal: document.getElementById('closeModal'), fileInput: document.getElementById('fileInput'), confirmUpload: document.getElementById('confirmUpload'), exportBtn: document.getElementById('exportBtn'), jsonData: document.getElementById('jsonData'), authModal: document.getElementById('authModal'), tokenInput: document.getElementById('tokenInput'), spaceInput: document.getElementById('spaceInput'), authSubmit: document.getElementById('authSubmit'), authError: document.getElementById('authError'), userInfo: document.getElementById('userInfo') }; } initEventListeners() { this.elements.uploadBtn.addEventListener('click', () => this.toggleModal(true)); this.elements.closeModal.addEventListener('click', () => this.toggleModal(false)); this.elements.confirmUpload.addEventListener('click', () => this.handleFileUpload()); this.elements.exportBtn.addEventListener('click', () => this.exportData()); this.elements.authSubmit.addEventListener('click', () => this.handleAuth()); } async checkAuth() { const token = localStorage.getItem('hfToken'); const space = localStorage.getItem('hfSpace'); if (token && space) { const authenticated = await this.assetManager.authenticate(token); if (authenticated) { await this.assetManager.loadAssets(space); this.render(); this.elements.authModal.classList.add('hidden'); this.updateUserInfo(); return; } } this.elements.authModal.classList.remove('hidden'); } async handleAuth() { const token = this.elements.tokenInput.value.trim(); const space = this.elements.spaceInput.value.trim(); if (!token || !space) { this.elements.authError.textContent = 'Please enter both token and space name'; return; } const authenticated = await this.assetManager.authenticate(token); if (!authenticated) { this.elements.authError.textContent = 'Invalid token. Please check and try again.'; return; } try { await this.assetManager.loadAssets(space); localStorage.setItem('hfToken', token); localStorage.setItem('hfSpace', space); this.elements.authModal.classList.add('hidden'); this.render(); this.updateUserInfo(); } catch (error) { this.elements.authError.textContent = 'Failed to load space. Please check space name and try again.'; } } updateUserInfo() { if (this.assetManager.username && this.assetManager.spaceName) { this.elements.userInfo.innerHTML = `
${this.assetManager.username} / ${this.assetManager.spaceName}
`; } } async render() { await this.renderAssetsTable(); this.updateJsonPreview(); } async renderAssetsTable() { const { assetsTable } = this.elements; assetsTable.innerHTML = ''; const assets = this.assetManager.getAssets(); if (assets.length === 0) { assetsTable.innerHTML = ` No assets found. Upload files to get started. `; return; } assets.forEach(asset => { const row = document.createElement('tr'); row.className = 'hover:bg-gray-50'; row.innerHTML = this.createAssetRowHTML(asset); assetsTable.appendChild(row); this.addRowEventListeners(row, asset.id); }); feather.replace(); } createAssetRowHTML(asset) { return ` ${asset.name} ${asset.name} ${asset.type} ${asset.url} `; } addRowEventListeners(row, assetId) { // Editable cells row.querySelectorAll('.editable-cell').forEach(cell => { cell.addEventListener('dblclick', () => this.makeCellEditable(cell)); }); // Action buttons row.querySelector('[data-action="copy"]')?.addEventListener('click', (e) => this.copyUrl(e)); row.querySelector('[data-action="delete"]')?.addEventListener('click', (e) => this.deleteAsset(e)); } makeCellEditable(cell) { const originalValue = cell.textContent; const id = parseInt(cell.dataset.id); const field = cell.dataset.field; cell.innerHTML = ``; const input = cell.querySelector('input'); input.focus(); const handleBlur = () => { const newValue = input.value; cell.textContent = newValue; this.assetManager.updateAsset(id, { [field]: newValue }); this.updateJsonPreview(); // Re-add event listeners cell.addEventListener('dblclick', () => this.makeCellEditable(cell)); }; input.addEventListener('blur', handleBlur); input.addEventListener('keypress', (e) => { if (e.key === 'Enter') { handleBlur(); } }); } copyUrl(e) { const url = e.target.closest('button').dataset.url; navigator.clipboard.writeText(url).then(() => { const originalHTML = e.target.closest('button').innerHTML; e.target.closest('button').innerHTML = ''; feather.replace(); setTimeout(() => { e.target.closest('button').innerHTML = originalHTML; feather.replace(); }, 2000); }); } deleteAsset(e) { const id = parseInt(e.target.closest('button').dataset.id); if (confirm('Are you sure you want to delete this asset?')) { this.assetManager.deleteAsset(id); this.render(); } } toggleModal(show) { this.elements.uploadModal.classList.toggle('hidden', !show); if (!show) { this.elements.fileInput.value = ''; } } async handleFileUpload() { const files = this.elements.fileInput.files; if (files.length === 0) { alert('Please select files to upload'); return; } try { for (const file of files) { await this.assetManager.addAsset(file); } await this.render(); this.toggleModal(false); } catch (error) { alert('Failed to upload files. Please try again.'); console.error(error); } } exportData() { const dataStr = this.assetManager.exportAsJSON(); const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr); const linkElement = document.createElement('a'); linkElement.setAttribute('href', dataUri); linkElement.setAttribute('download', 'hugging-space-assets.json'); linkElement.click(); } updateJsonPreview() { this.elements.jsonData.value = this.assetManager.exportAsJSON(); } } // Initialize the application when DOM is loaded document.addEventListener('DOMContentLoaded', () => { new HuggingSpaceApp(); });