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 `
${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;