import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client@1.9.0/dist/index.min.js"; // Read/write tokens from sessionStorage function getReadToken() { return sessionStorage.getItem('hf_read_token') || ''; } function getWriteToken() { return sessionStorage.getItem('hf_write_token') || ''; } // Helper to add tokens to every API call function withTokens(obj = {}) { return { ...obj, read_token: getReadToken(), write_token: getWriteToken(), }; } // Helper to call secure server-side proxy async function callSecureApi(fn, args) { const resp = await fetch('/api/proxy', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ fn, args }) }); if (!resp.ok) throw new Error('Proxy error: ' + resp.status); return await resp.json(); } let client; let currentUser = null; let currentPassword = null; let files = []; let selected = new Set(); let currentView = 'grid'; let previewFile = null; let isLoadingFiles = false; const iconMap = { 'pdf': '📄', 'doc': '📄', 'docx': '📄', 'png': '🖼️', 'jpg': '🖼️', 'jpeg': '🖼️', 'gif': '🖼️', 'mp3': '🎵', 'wav': '🎵', 'flac': '🎵', 'mp4': '📹', 'avi': '📹', 'mov': '📹', 'zip': '🗜️', 'rar': '🗜️', '7z': '🗜️', 'txt': '📝', 'md': '📝', 'py': '🐍', 'js': '📜', 'html': '🌐', 'css': '🌐', 'psd': '🎨', 'ai': '🎨', 'sql': '🗃️', 'xlsx': '📊', 'csv': '📊', 'pptx': '📑', 'ppt': '📑', }; // Initialize dashboard on page load async function initDashboard() { console.log('=== Dashboard Init Started ==='); currentUser = sessionStorage.getItem('pockit_user'); currentPassword = sessionStorage.getItem('pockit_pass'); if (!currentUser || !currentPassword) { console.warn('No session found, redirecting to login'); window.location.href = 'index.html'; return; } try { // Update account info first const initials = currentUser.substring(0, 2).toUpperCase(); document.querySelector('.avatar').textContent = initials; document.querySelector('.account-name').textContent = currentUser; // Load data using proxy await loadUserData(); await loadFiles(); console.log('✓ Dashboard loaded'); } catch (err) { console.error('Init error:', err); showToast('Connection failed'); setTimeout(() => window.location.href = 'index.html', 2000); } } // Load user info and update cards async function loadUserData() { try { console.log('Loading user data...'); const result = await callSecureApi("/search_user", withTokens({ user_id: currentUser })); const [log, isDev, isPro, isTester, capacityUsed, usedTotal] = result.data; // Determine role let role = 'free'; if (isDev) role = 'dev'; else if (isPro) role = 'pro'; else if (isTester) role = 'tester'; const tag = document.getElementById('role-tag'); tag.className = 'role-tag ' + role; tag.textContent = role.charAt(0).toUpperCase() + role.slice(1); document.getElementById('tier-val').textContent = tag.textContent; // Parse capacity const capacityPercent = Math.round(capacityUsed); let usedDisplay = usedTotal || '0 / 5 GB'; let usedParts = usedDisplay.split('/'); let usedAmount = usedParts[0].trim(); let maxAmount = usedParts[1] ? usedParts[1].trim() : '5 GB'; // Update storage displays document.querySelector('.card-bar-fill').style.width = capacityPercent + '%'; document.querySelector('.stor-label span:last-child').textContent = usedDisplay; document.querySelector('.sbar-fill').style.width = capacityPercent + '%'; const storageCard = document.querySelectorAll('.card')[0]; storageCard.querySelector('.card-val').innerHTML = usedAmount + ' (' + capacityPercent + '%)'; storageCard.querySelector('.card-sub').textContent = usedDisplay + ' used'; document.querySelectorAll('.card')[3].querySelector('.card-sub').textContent = maxAmount + ' max capacity'; console.log('✓ User data loaded'); } catch (err) { console.error('User data error:', err); } } // Load files from API async function loadFiles() { // Prevent concurrent loads if (isLoadingFiles) return; isLoadingFiles = true; try { console.log('Loading files...'); loading(true); const result = await callSecureApi("/get_files_secure", withTokens({ user_id: currentUser, password: currentPassword, })); // API returns [dropdown, status] const dropdownData = result.data[0]; console.log('Raw dropdownData:', dropdownData); let fileNames = []; if (dropdownData && typeof dropdownData === 'object' && dropdownData.choices) { // Flatten all sub-arrays in choices fileNames = dropdownData.choices.flat ? dropdownData.choices.flat() : [].concat(...dropdownData.choices); } else if (Array.isArray(dropdownData)) { fileNames = dropdownData; } else if (typeof dropdownData === 'string') { fileNames = [dropdownData]; } // Convert all to string, trim, and filter empty fileNames = fileNames.map(f => String(f).trim()).filter(Boolean); console.log('Extracted fileNames from dropdown:', fileNames); // Deduplicate file names (case-insensitive, trim) const seen = new Set(); const deduped = []; for (const name of fileNames) { const key = name.toLowerCase(); if (!key || seen.has(key)) continue; seen.add(key); deduped.push(name); } files = deduped.map((name, idx) => { const ext = name.includes('.') ? name.split('.').pop().toLowerCase() : ''; return { id: idx, name: name, icon: iconMap[ext] || '📁', size: '—', date: 'Recently', type: ext ? ext.toUpperCase() : '', preview: null // for preview content }; }); console.log('Final files array before render:', files); selected.clear(); updateStatsCards(); render(files); loading(false); console.log('✓ Files loaded and rendered'); preloadTextPreviews(); } catch (err) { console.error('Files load error:', err); loading(false); files = []; selected.clear(); render([]); showToast('Failed to load files'); } finally { isLoadingFiles = false; } } // Preload short text previews for text files so grid view // can show the first few words instead of just an icon. async function preloadTextPreviews() { // Only attempt after files are loaded and user is known if (!currentUser || !currentPassword || !Array.isArray(files)) return; for (const file of files) { // Simple heuristic: only fetch preview for obvious text formats const name = (file && file.name) ? String(file.name).toLowerCase() : ''; if (!name.endsWith('.txt') && !name.endsWith('.md')) continue; try { const result = await callSecureApi("/get_preview_text_action", withTokens({ user_id: currentUser, password: currentPassword, filename: file.name, })); const text = Array.isArray(result.data) ? result.data[0] : result.data; if (typeof text === 'string' && text.trim()) { file.preview = text; } } catch (err) { console.error('Text preview preload error for', file.name, err); } } render(files); } // Update stats cards function updateStatsCards() { const cards = document.querySelectorAll('.card'); if (cards.length < 3) return; // Files count cards[1].querySelector('.card-val').textContent = files.length; // File types const fileTypes = new Set(files.map(f => f.type)); cards[1].querySelector('.card-sub').textContent = `Across ${fileTypes.size} file type${fileTypes.size !== 1 ? 's' : ''}`; // Upload card cards[2].querySelector('.card-val').textContent = files.length > 0 ? '1' : '0'; cards[2].querySelector('.card-sub').textContent = files.length > 0 ? 'Last upload: Recently' : 'No uploads yet'; } // Render grid and table function render(list) { console.log('Rendering', list.length, 'files:', list.map(f => f.name)); const grid = document.getElementById('file-grid'); const tbody = document.getElementById('file-tbody'); if (!grid) { console.error('file-grid missing'); return; } if (!tbody) { console.error('file-tbody missing'); return; } // Clear everything first grid.innerHTML = ''; tbody.innerHTML = ''; // Only render if there are files if (!list || list.length === 0) { console.log('No files to render'); return; } // Grid HTML const gridHTML = list.map((f, i) => { let iconOrPreview = f.icon; if (f.preview && typeof f.preview === 'string' && f.preview.trim()) { const words = f.preview.trim().split(/\s+/).slice(0, 5).join(' '); const snippet = words + (f.preview.trim().length > words.length ? '...' : ''); iconOrPreview = escapeHtml(snippet); } return `
${iconOrPreview} ${f.name} ${f.size}
`; }).join(''); grid.innerHTML = gridHTML; console.log('Grid rendered with', list.length, 'items'); // Table HTML const tableHTML = list.map((f, i) => ` ${f.icon} ${f.name} ${f.size} ${f.date} ${f.type} `).join(''); tbody.innerHTML = tableHTML; console.log('Table rendered with', list.length, 'items'); } // Select/deselect file function toggleSelect(id) { if (id < 0 || id >= files.length) return; selected.has(id) ? selected.delete(id) : selected.add(id); render(currentView === 'grid' ? files : files); } function clearSelection() { selected.clear(); render(files); } function handleSelectAll(checked) { if (checked) { files.forEach((_, i) => selected.add(i)); } else { selected.clear(); } render(files); } // Open file preview function openPreview(id) { if (id < 0 || id >= files.length) return; previewFile = files[id]; document.getElementById('preview-thumb').textContent = previewFile.icon; document.getElementById('preview-name').textContent = previewFile.name; document.getElementById('preview-name').style.color = '#1a1a2e'; document.getElementById('pv-size').textContent = previewFile.size; document.getElementById('pv-date').textContent = previewFile.date; document.getElementById('pv-type').textContent = previewFile.type; // Load preview content from API loadPreviewContent(previewFile); selected.clear(); selected.add(id); render(files); } // Load preview content for a file using /handle_preview_secure and /get_preview_text_action async function loadPreviewContent(file) { if (!file || !file.name) return; const previewContainer = document.getElementById('preview-content'); if (!previewContainer) return; previewContainer.innerHTML = 'Loading preview...'; try { // Try /handle_preview_secure first const result = await callSecureApi("/handle_preview_secure", withTokens({ user_id: currentUser, password: currentPassword, filename: file.name, })); // result.data: [file, localPath, html, image, code] let previewHtml = ''; if (result.data[3] && typeof result.data[3] === 'string' && result.data[3].startsWith('data:image')) { // Image preview previewHtml = `Preview`; } else if (result.data[2] && typeof result.data[2] === 'string' && result.data[2].length > 0) { // HTML preview previewHtml = `
${result.data[2]}
`; } else if (result.data[4] && typeof result.data[4] === 'string' && result.data[4].length > 0) { // Code preview previewHtml = `
${escapeHtml(result.data[4])}
`; } else { // Try /get_preview_text_action for text preview const textResult = await callSecureApi("/get_preview_text_action", withTokens({ user_id: currentUser, password: currentPassword, filename: file.name, })); if (textResult.data && typeof textResult.data[0] === 'string' && textResult.data[0].length > 0) { previewHtml = `
${escapeHtml(textResult.data[0])}
`; } else { previewHtml = 'No preview available.'; } } previewContainer.innerHTML = previewHtml; } catch (err) { console.error('Preview error:', err); previewContainer.innerHTML = 'Failed to load preview.'; } } // Helper to escape HTML for code/text preview function escapeHtml(str) { return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } // Delete single file async function deleteFile(id) { if (id < 0 || id >= files.length) return; const file = files[id]; try { loading(true); const result = await callSecureApi("/delete_file_secure", withTokens({ user_id: currentUser, password: currentPassword, filename: file.name, })); console.log('Deleted:', file.name); // Update storage const [status, capacityUsed, usedTotal] = result.data; const percent = Math.round(capacityUsed); document.querySelector('.card-bar-fill').style.width = percent + '%'; document.querySelector('.sbar-fill').style.width = percent + '%'; document.querySelector('.stor-label span:last-child').textContent = usedTotal; // Remove from array files.splice(id, 1); selected.delete(id); updateStatsCards(); render(files); loading(false); showToast(file.name + ' deleted'); } catch (err) { console.error('Delete error:', err); loading(false); showToast('Delete failed'); } } // Delete selected files async function deleteSelected() { if (selected.size === 0) { showToast('No files selected'); return; } const ids = Array.from(selected).sort((a, b) => b - a); for (const id of ids) { await deleteFile(id); } } // Delete preview file async function deletePreviewFile() { if (!previewFile) return; const idx = files.indexOf(previewFile); if (idx >= 0) await deleteFile(idx); previewFile = null; document.getElementById('preview-name').textContent = 'Select a file'; document.getElementById('preview-name').style.color = 'rgba(0,0,0,.3)'; } // Download file async function downloadFile(id) { if (id < 0 || id >= files.length) return; const file = files[id]; try { loading(true); const resp = await fetch('/api/download-link', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: currentUser, password: currentPassword, filename: file.name, }), }); if (!resp.ok) throw new Error('Download-link HTTP ' + resp.status); const payload = await resp.json(); const fileTarget = Array.isArray(payload.data) ? payload.data[0] : payload.data; if (typeof fileTarget === 'string' && fileTarget) { const a = document.createElement('a'); a.href = '/api/download?file=' + encodeURIComponent(fileTarget); a.download = file.name; document.body.appendChild(a); a.click(); setTimeout(() => { document.body.removeChild(a); }, 100); showToast('Download started: ' + file.name); } else { showToast('Download link error'); } loading(false); } catch (err) { console.error('Download error:', err); loading(false); showToast('Download failed'); } } async function downloadPreviewFile() { if (!previewFile) return; const idx = files.indexOf(previewFile); if (idx >= 0) { await downloadFile(idx); } } // Upload file (single file) via server-side endpoint so private HF token stays on the server async function handleUpload(input) { if (!input.files || !input.files[0]) return; const file = input.files[0]; try { loading(true); const formData = new FormData(); formData.append('file', file); formData.append('user_id', currentUser); formData.append('password', currentPassword); formData.append('custom_name', file.name); const resp = await fetch('/api/upload', { method: 'POST', body: formData, }); if (!resp.ok) throw new Error('Upload HTTP ' + resp.status); const result = await resp.json(); console.log('Uploaded:', file.name); // Update storage const [status, capacityUsed, usedTotal] = result.data; const percent = Math.round(capacityUsed); document.querySelector('.card-bar-fill').style.width = percent + '%'; document.querySelector('.sbar-fill').style.width = percent + '%'; document.querySelector('.stor-label span:last-child').textContent = usedTotal; // Reload files await loadFiles(); showToast(file.name + ' uploaded'); input.value = ''; } catch (err) { console.error('Upload error:', err); showToast('Upload failed'); } finally { loading(false); } } function handleDrop(e) { e.preventDefault(); document.getElementById('drop-zone').classList.remove('drag'); if (e.dataTransfer.files) { handleUpload({ files: e.dataTransfer.files }); } } // Filter files by search function filterFiles(q) { const query = q.toLowerCase(); const filtered = files.filter(f => f.name.toLowerCase().includes(query)); render(filtered); } // Toggle view function setView(v) { currentView = v; document.getElementById('file-grid').style.display = v === 'grid' ? 'grid' : 'none'; document.getElementById('file-table-wrap').style.display = v === 'list' ? 'block' : 'none'; document.getElementById('vt-grid').classList.toggle('active', v === 'grid'); document.getElementById('vt-list').classList.toggle('active', v === 'list'); } // Navigation sections function setNav(el, section) { document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active')); el.classList.add('active'); if (section === 'recent') { document.getElementById('page-title').textContent = 'Recent Files'; render(files.slice(0, 5)); } else { document.getElementById('page-title').textContent = 'My Files'; render(files); } } // Change password async function changePassword() { const p1 = document.getElementById('new-pw').value; const p2 = document.getElementById('conf-pw').value; if (!p1 || p1 !== p2) { showToast('Passwords do not match'); return; } try { loading(true); const result = await callSecureApi("/update_password", withTokens({ user_id: currentUser, new_password: p1, })); if (result.data[0].toLowerCase().includes('success') || result.data[0].toLowerCase().includes('updated')) { currentPassword = p1; sessionStorage.setItem('pockit_pass', p1); closeModal('pw-modal'); showToast('Password updated'); document.getElementById('new-pw').value = ''; document.getElementById('conf-pw').value = ''; } else { showToast('Password update failed'); } loading(false); } catch (err) { console.error('Password error:', err); loading(false); showToast('Password update failed'); } } // Logout function logoutUser() { sessionStorage.clear(); showToast('Logged out'); setTimeout(() => window.location.href = 'index.html', 1500); } // Modal helpers function openModal(id) { document.getElementById(id).classList.add('open'); } function closeModal(id) { document.getElementById(id).classList.remove('open'); } // UI helpers function loading(on) { document.getElementById('loader').classList.toggle('on', on); } let toastTimer; function showToast(msg) { const toast = document.getElementById('toast'); document.getElementById('toast-msg').textContent = msg; toast.classList.add('show'); clearTimeout(toastTimer); toastTimer = setTimeout(() => toast.classList.remove('show'), 3000); } // Initialize on page load document.addEventListener('DOMContentLoaded', initDashboard); // Expose to global scope window.initDashboard = initDashboard; window.loadUserData = loadUserData; window.loadFiles = loadFiles; window.updateStatsCards = updateStatsCards; window.render = render; window.toggleSelect = toggleSelect; window.clearSelection = clearSelection; window.handleSelectAll = handleSelectAll; window.openPreview = openPreview; window.loadPreviewContent = loadPreviewContent; window.deleteFile = deleteFile; window.deleteSelected = deleteSelected; window.deletePreviewFile = deletePreviewFile; window.downloadFile = downloadFile; window.downloadPreviewFile = downloadPreviewFile; window.handleUpload = handleUpload; window.handleDrop = handleDrop; window.filterFiles = filterFiles; window.setView = setView; window.setNav = setNav; window.changePassword = changePassword; window.logoutUser = logoutUser; window.openModal = openModal; window.closeModal = closeModal; window.loading = loading; window.showToast = showToast;